|
| 1 | +#!/usr/bin/env bash |
| 2 | +# bdprep.sh - build BrownDye XML inputs from prepared PQR/APBS files. |
| 3 | +# |
| 4 | +# Prerequisites: |
| 5 | +# conda activate ambertools |
| 6 | +# cd examples/browndye |
| 7 | +# bash run_ambertools.sh |
| 8 | +# bash run_apbs.sh |
| 9 | +# |
| 10 | +# This script creates: |
| 11 | +# protein_atoms.xml, ligand_atoms.xml |
| 12 | +# contact_types.xml, reaction_pairs.xml, reactions.xml |
| 13 | +# input.xml |
| 14 | +# protein_ligand_simulation.xml (via bd_top) |
| 15 | + |
| 16 | +set -euo pipefail |
| 17 | + |
| 18 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 19 | +WORKDIR="${WORKDIR:-$SCRIPT_DIR/tmp}" |
| 20 | +BD_HOME="${BD_HOME:-/apps/browndye2}" |
| 21 | +BD_BIN="${BD_BIN:-$BD_HOME/bin}" |
| 22 | +BD_AUX="${BD_AUX:-$BD_HOME/aux}" |
| 23 | + |
| 24 | +CORE0="${CORE0:-protein}" |
| 25 | +CORE1="${CORE1:-ligand}" |
| 26 | +CORE0_IS_PROTEIN="${CORE0_IS_PROTEIN:-true}" |
| 27 | +CORE1_IS_PROTEIN="${CORE1_IS_PROTEIN:-false}" |
| 28 | +CORE0_DIELECTRIC="${CORE0_DIELECTRIC:-4.0}" |
| 29 | +CORE1_DIELECTRIC="${CORE1_DIELECTRIC:-4.0}" |
| 30 | + |
| 31 | +RXN_SEARCH_DISTANCE="${RXN_SEARCH_DISTANCE:-5.5}" |
| 32 | +RXN_DISTANCE="${RXN_DISTANCE:-5.5}" |
| 33 | +RXN_NEEDED="${RXN_NEEDED:-3}" |
| 34 | + |
| 35 | +N_THREADS="${N_THREADS:-1}" |
| 36 | +SEED="${SEED:-11111111}" |
| 37 | +N_TRAJECTORIES="${N_TRAJECTORIES:-1000}" |
| 38 | +N_TRAJECTORIES_PER_OUTPUT="${N_TRAJECTORIES_PER_OUTPUT:-10}" |
| 39 | +MAX_N_STEPS="${MAX_N_STEPS:-1000000}" |
| 40 | +RESULTS_FILE="${RESULTS_FILE:-results.xml}" |
| 41 | + |
| 42 | +SOLVENT_DIELECTRIC="${SOLVENT_DIELECTRIC:-78.0}" |
| 43 | +RELATIVE_VISCOSITY="${RELATIVE_VISCOSITY:-1.0}" |
| 44 | +KT="${KT:-1.0}" |
| 45 | +DESOLVATION_PARAMETER="${DESOLVATION_PARAMETER:-1.0}" |
| 46 | +SOLVENT_RADIUS="${SOLVENT_RADIUS:-1.4}" |
| 47 | +DEBYE_LENGTH="${DEBYE_LENGTH:-}" |
| 48 | + |
| 49 | +require_file() { |
| 50 | + local path="$1" |
| 51 | + if [[ ! -s "$path" ]]; then |
| 52 | + printf 'Missing required file: %s\n' "$path" >&2 |
| 53 | + exit 1 |
| 54 | + fi |
| 55 | +} |
| 56 | + |
| 57 | +resolve_tool() { |
| 58 | + local tool="$1" |
| 59 | + if [[ -x "$BD_BIN/$tool" ]]; then |
| 60 | + printf '%s\n' "$BD_BIN/$tool" |
| 61 | + return |
| 62 | + fi |
| 63 | + if [[ -x "$BD_AUX/$tool" ]]; then |
| 64 | + printf '%s\n' "$BD_AUX/$tool" |
| 65 | + return |
| 66 | + fi |
| 67 | + if command -v "$tool" >/dev/null 2>&1; then |
| 68 | + command -v "$tool" |
| 69 | + return |
| 70 | + fi |
| 71 | + |
| 72 | + printf 'Missing BrownDye tool: %s\n' "$tool" >&2 |
| 73 | + printf 'Checked BD_BIN=%s, BD_AUX=%s, and PATH.\n' "$BD_BIN" "$BD_AUX" >&2 |
| 74 | + printf 'If /apps/browndye2 was built from source, compile/install the aux tools first.\n' >&2 |
| 75 | + exit 1 |
| 76 | +} |
| 77 | + |
| 78 | +infer_debye_length() { |
| 79 | + python3 - "$CORE0.apbs.log" "$CORE1.apbs.log" <<'PY' |
| 80 | +import re |
| 81 | +import sys |
| 82 | +from pathlib import Path |
| 83 | +
|
| 84 | +for name in sys.argv[1:]: |
| 85 | + path = Path(name) |
| 86 | + if not path.is_file(): |
| 87 | + continue |
| 88 | + text = path.read_text(errors="ignore") |
| 89 | + match = re.search(r"[Dd]ebye[- ]length[:\s]+([0-9.]+)", text) |
| 90 | + match = match or re.search(r"[Gg]ot debye length\s+([0-9.]+)", text) |
| 91 | + if match: |
| 92 | + print(match.group(1)) |
| 93 | + raise SystemExit |
| 94 | +raise SystemExit(1) |
| 95 | +PY |
| 96 | +} |
| 97 | + |
| 98 | +write_contact_types() { |
| 99 | + python3 - "$CORE0.pqr" "$CORE1.pqr" >contact_types.xml <<'PY' |
| 100 | +import sys |
| 101 | +from pathlib import Path |
| 102 | +
|
| 103 | +
|
| 104 | +def is_heavy_atom(atom_name: str) -> bool: |
| 105 | + stripped = atom_name.strip() |
| 106 | + return bool(stripped) and not stripped.upper().startswith("H") |
| 107 | +
|
| 108 | +
|
| 109 | +def contacts(path: Path) -> list[tuple[str, str]]: |
| 110 | + seen: set[tuple[str, str]] = set() |
| 111 | + ordered: list[tuple[str, str]] = [] |
| 112 | + for line in path.read_text().splitlines(): |
| 113 | + if not line.startswith(("ATOM", "HETATM")): |
| 114 | + continue |
| 115 | + parts = line.split() |
| 116 | + if len(parts) < 4: |
| 117 | + continue |
| 118 | + atom = parts[2] |
| 119 | + residue = parts[3] |
| 120 | + key = (atom, residue) |
| 121 | + if is_heavy_atom(atom) and key not in seen: |
| 122 | + seen.add(key) |
| 123 | + ordered.append(key) |
| 124 | + return ordered |
| 125 | +
|
| 126 | +
|
| 127 | +mol0 = contacts(Path(sys.argv[1])) |
| 128 | +mol1 = contacts(Path(sys.argv[2])) |
| 129 | +
|
| 130 | +print("<contacts>") |
| 131 | +print(" <combinations>") |
| 132 | +for label, entries in (("molecule0", mol0), ("molecule1", mol1)): |
| 133 | + print(f" <{label}>") |
| 134 | + for atom, residue in entries: |
| 135 | + print(f" <contact> <atom> {atom} </atom> <residue> {residue} </residue> </contact>") |
| 136 | + print(f" </{label}>") |
| 137 | +print(" </combinations>") |
| 138 | +print("</contacts>") |
| 139 | +PY |
| 140 | +} |
| 141 | + |
| 142 | +write_input_xml() { |
| 143 | + cat >input.xml <<EOF |
| 144 | +<top> |
| 145 | + <n_threads> $N_THREADS </n_threads> |
| 146 | + <seed> $SEED </seed> |
| 147 | + <output> $RESULTS_FILE </output> |
| 148 | +
|
| 149 | + <n_trajectories> $N_TRAJECTORIES </n_trajectories> |
| 150 | + <n_trajectories_per_output> $N_TRAJECTORIES_PER_OUTPUT </n_trajectories_per_output> |
| 151 | + <max_n_steps> $MAX_N_STEPS </max_n_steps> |
| 152 | +
|
| 153 | + <system> |
| 154 | + <reaction_file> reactions.xml </reaction_file> |
| 155 | +
|
| 156 | + <solvent> |
| 157 | + <debye_length> $DEBYE_LENGTH </debye_length> |
| 158 | + <dielectric> $SOLVENT_DIELECTRIC </dielectric> |
| 159 | + <relative_viscosity> $RELATIVE_VISCOSITY </relative_viscosity> |
| 160 | + <kT> $KT </kT> |
| 161 | + <desolvation_parameter> $DESOLVATION_PARAMETER </desolvation_parameter> |
| 162 | + <solvent_radius> $SOLVENT_RADIUS </solvent_radius> |
| 163 | + </solvent> |
| 164 | +
|
| 165 | + <time_step_tolerances> |
| 166 | + <minimum_core_dt> 0.0 </minimum_core_dt> |
| 167 | + <minimum_core_reaction_dt> 0.0 </minimum_core_reaction_dt> |
| 168 | + </time_step_tolerances> |
| 169 | +
|
| 170 | + <group> |
| 171 | + <name> $CORE0 </name> |
| 172 | + <core> |
| 173 | + <name> $CORE0 </name> |
| 174 | + <all_in_surface> false </all_in_surface> |
| 175 | + <is_protein> $CORE0_IS_PROTEIN </is_protein> |
| 176 | + <atoms> ${CORE0}_atoms.xml </atoms> |
| 177 | + <electric_field> |
| 178 | + <grid> $CORE0.dx </grid> |
| 179 | + </electric_field> |
| 180 | + <dielectric> $CORE0_DIELECTRIC </dielectric> |
| 181 | + </core> |
| 182 | + </group> |
| 183 | +
|
| 184 | + <group> |
| 185 | + <name> $CORE1 </name> |
| 186 | + <core> |
| 187 | + <name> $CORE1 </name> |
| 188 | + <all_in_surface> false </all_in_surface> |
| 189 | + <is_protein> $CORE1_IS_PROTEIN </is_protein> |
| 190 | + <atoms> ${CORE1}_atoms.xml </atoms> |
| 191 | + <electric_field> |
| 192 | + <grid> $CORE1.dx </grid> |
| 193 | + </electric_field> |
| 194 | + <dielectric> $CORE1_DIELECTRIC </dielectric> |
| 195 | + </core> |
| 196 | + </group> |
| 197 | + </system> |
| 198 | +</top> |
| 199 | +EOF |
| 200 | +} |
| 201 | + |
| 202 | +require_file "$WORKDIR/$CORE0.pqr" |
| 203 | +require_file "$WORKDIR/$CORE1.pqr" |
| 204 | +require_file "$WORKDIR/$CORE0.dx" |
| 205 | +require_file "$WORKDIR/$CORE1.dx" |
| 206 | +PQR2XML="$(resolve_tool pqr2xml)" |
| 207 | +MAKE_RXN_PAIRS="$(resolve_tool make_rxn_pairs)" |
| 208 | +MAKE_RXN_FILE="$(resolve_tool make_rxn_file)" |
| 209 | +BD_TOP="$(resolve_tool bd_top)" |
| 210 | + |
| 211 | +cd "$WORKDIR" |
| 212 | + |
| 213 | +if [[ -z "$DEBYE_LENGTH" ]]; then |
| 214 | + if ! DEBYE_LENGTH="$(infer_debye_length)"; then |
| 215 | + printf 'Could not infer DEBYE_LENGTH from APBS logs. Set DEBYE_LENGTH explicitly.\n' >&2 |
| 216 | + exit 1 |
| 217 | + fi |
| 218 | +fi |
| 219 | + |
| 220 | +echo "=== 1. PQR -> BrownDye XML ===" |
| 221 | +"$PQR2XML" <"$CORE0.pqr" >"${CORE0}_atoms.xml" |
| 222 | +"$PQR2XML" <"$CORE1.pqr" >"${CORE1}_atoms.xml" |
| 223 | + |
| 224 | +echo "=== 2. Reaction criteria ===" |
| 225 | +write_contact_types |
| 226 | +"$MAKE_RXN_PAIRS" -nonred \ |
| 227 | + -mol0 "${CORE0}_atoms.xml" \ |
| 228 | + -mol1 "${CORE1}_atoms.xml" \ |
| 229 | + -ctypes contact_types.xml \ |
| 230 | + -dist "$RXN_SEARCH_DISTANCE" >reaction_pairs.xml |
| 231 | +"$MAKE_RXN_FILE" \ |
| 232 | + -pairs reaction_pairs.xml \ |
| 233 | + -state_from before \ |
| 234 | + -state_to after \ |
| 235 | + -rxn association \ |
| 236 | + -mol0 "$CORE0" "$CORE0" \ |
| 237 | + -mol1 "$CORE1" "$CORE1" \ |
| 238 | + -distance "$RXN_DISTANCE" \ |
| 239 | + -nneeded "$RXN_NEEDED" >reactions.xml |
| 240 | + |
| 241 | +echo "=== 3. BrownDye top-level input ===" |
| 242 | +write_input_xml |
| 243 | +"$BD_TOP" input.xml |
| 244 | + |
| 245 | +echo "=== Done ===" |
| 246 | +echo "Simulation XML should be ${CORE0}_${CORE1}_simulation.xml" |
0 commit comments