topo.utils package¶
Submodules¶
topo.utils.config module¶
Read a simulation control file (md.ini) into a structured configuration.
The control file is an INI file: a flat list of key = value lines. Parsing it
used to be a ~100-line block copy-pasted into every run_simulation.py. This
module centralizes that logic so a run script can simply do:
import topo
cfg = topo.read_simulation_config("md.ini")
model = topo.models.buildCoarseGrainModel(cfg.pdb_file, **cfg.build_kwargs())
integrator = openmm.LangevinIntegrator(cfg.ref_t, cfg.tau_t, cfg.dt)
All parsed values are returned on a SimulationConfig dataclass, with
OpenMM units already applied where appropriate (dt, ref_t, tau_t,
ref_p).
- class topo.utils.config.SimulationConfig(md_steps: int = 10000, dt: ~typing.Any = <factory>, nstxout: int = 5000, nstlog: int = 5000, nstchk: int | None = 5000, nstcomm: int | None = None, log_precision: int | None = 4, log_width: int | None = 14, model: str = 'topo', constraints: ~typing.Any = 'AllBonds', constraint_tolerance: float = 1e-05, tcoupl: bool = True, ref_t: ~typing.Any = 300.0, tau_t: ~typing.Any | None = None, anneal: bool = False, t_high: ~typing.Any | None = None, anneal_steps: int = 0, anneal_ramp: str = 'jump', anneal_ramp_steps: int = 0, anneal_ramp_increments: int = 20, pcoupl: bool = False, ref_p: ~typing.Any = 1.0, frequency_p: int = 25, pbc: bool = False, box_dimension: ~typing.List[float] | None = None, pdb_file: str | None = None, init_position: str | None = None, domain_def: str | None = None, stride_output_file: str | None = None, output_dir: str = 'traj', outname: str = 'traj', checkpoint: str | None = None, device: str = 'CPU', ppn: int = 1, n_copies: int = 1, copy_shift: float = 5.0, restart: bool = False, minimize: bool = True, config_file: str | None = None, quiet: bool = False)[source]¶
Bases:
objectParsed contents of a simulation control file (
md.ini).- anneal: bool = False¶
- anneal_ramp: str = 'jump'¶
- anneal_ramp_increments: int = 20¶
- anneal_ramp_steps: int = 0¶
- anneal_steps: int = 0¶
- box_dimension: List[float] | None = None¶
- build_kwargs() dict[source]¶
Keyword arguments for
topo.models.buildCoarseGrainModel().Always passes
minimize,modelandbox_dimension; passesdomain_def/stride_output_fileonly when they are set, so the builder’s own defaults apply otherwise. On restart the build-time energy check is skipped (check_forces=False) because the loaded checkpoint state, not the input structure, is what gets simulated.
- checkpoint: str | None = None¶
- checkpoint_path() str[source]¶
Resolved checkpoint path: the explicit
checkpointoption if given, otherwise<output_dir>/<outname>.chk.
- config_file: str | None = None¶
- constraint_tolerance: float = 1e-05¶
- constraints: Any = 'AllBonds'¶
- copy_shift: float = 5.0¶
- device: str = 'CPU'¶
- domain_def: str | None = None¶
- dt: Any¶
- frequency_p: int = 25¶
- init_position: str | None = None¶
- log_precision: int | None = 4¶
- log_width: int | None = 14¶
- make_platform()[source]¶
Build the OpenMM
(platform, properties)pair selected bydevice.device = 'GPU'-> CUDA (mixed precision, device 0); anything else -> CPU usingppnthreads.
- md_steps: int = 10000¶
- minimize: bool = True¶
- model: str = 'topo'¶
- n_copies: int = 1¶
- nstchk: int | None = 5000¶
- nstcomm: int | None = None¶
- nstlog: int = 5000¶
- nstxout: int = 5000¶
- outname: str = 'traj'¶
- output_dir: str = 'traj'¶
- output_path(suffix: str = '') str[source]¶
Path for a generated output file:
<output_dir>/<outname><suffix>.Examples:
output_path('.dcd')->traj/traj.dcd;output_path('_multi.psf')->traj/traj_multi.psf.Built with
pathlib.Pathbut returned asstrso it can be passed directly to OpenMM/parmed writers (some of which special-casestrand would mishandle a rawPath).
- pbc: bool = False¶
- pcoupl: bool = False¶
- pdb_file: str | None = None¶
- ppn: int = 1¶
- prepare_output_dir() None[source]¶
Ensure
output_direxists.Path.mkdir(parents=True, exist_ok=True)creates any missing parents and is a no-op if the folder already exists (no manual “does it exist?” check needed).
- quench_steps() int[source]¶
Total steps in the quench phase (
0whenannealis off).The quench is a separate phase from production: it holds at
t_highforanneal_stepsand, for a linear ramp, additionally cools overanneal_ramp_steps. Production then runsmd_stepsatref_t.
- quiet: bool = False¶
- ref_p: Any = 1.0¶
- ref_t: Any = 300.0¶
- restart: bool = False¶
- stride_output_file: str | None = None¶
- t_high: Any = None¶
- tau_t: Any = None¶
- tcoupl: bool = True¶
- topo.utils.config._unshift_parsing_error(exc: ParsingError, config_file: str) ParsingError[source]¶
Rebuild a
configparser.ParsingErrorwith honest line numbers.- Parameters:
exc (configparser.ParsingError) – The error raised against the shifted text.
config_file (str) – Path to report as the source.
- Returns:
The same errors, renumbered to the user’s file.
- Return type:
configparser.ParsingError
- topo.utils.config.read_ini(config_file: str, preserve_case: bool = False) ConfigParser[source]¶
Parse a topo control file into a
configparser.ConfigParser.A topo control file is a flat list of
key = valuelines. Section headers are optional and their names carry no meaning: every setting in the file is read into one flat set, whether it is written under one header, several, or none at all. So no setting can go unread because of where it sits in the file – a key that is written is a key that is applied.- Parameters:
config_file (str) – Path to the control file (
md.ini,optimize.ini,csp.ini, …). Inline comments starting with#or;are ignored.preserve_case (bool, optional (default: False)) – If True, keep option names as written instead of lower-casing them (
configparser’s default). Needed when the keys are round-tripped back out to another file, as intopo.optimize.
- Returns:
Every setting in the file, collected into one section. The section is always present, and is empty only for a file that sets nothing.
- Return type:
configparser.ConfigParser
- Raises:
FileNotFoundError – If
config_filedoes not exist.ValueError – If a key is set more than once. There is no principled way to pick a winner, and silently applying one of two values would change a run’s physics without saying so, so a repeated key is an error rather than a last-one-wins.
configparser.ParsingError – If a line is neither a comment nor a
key = valuepair.
- topo.utils.config.read_simulation_config(config_file: str, verbose: bool = True) SimulationConfig[source]¶
Parse a simulation control file into a
SimulationConfig.- Parameters:
config_file (str) – Path to the control file (e.g.
md.ini): a flatkey = valuelist (seeread_ini()). Inline comments starting with#or;are ignored. Underscores inmd_steps(e.g.500_000) are allowed.verbose (bool, optional (default: True)) – If True, echo each parsed setting to stdout (the original behaviour of the inline parser). Set False for a quiet read.
- Returns:
All settings with OpenMM units applied where relevant. Options absent from the file fall back to the dataclass defaults.
- Return type:
Notes
Behavioural details preserved from the original inline parser:
ref_t/tau_tget units only whentcouplis on.box_dimensionaccepts a scalarL(cubic[L, L, L]) or an explicit[x, y, z]list, and isNonewhenpbcis off.pcouplrequirespbc(asserted).restartforcesminimizeoff.
topo.utils.multichain module¶
Replicate a single-chain coarse-grained system into many non-interacting
copies inside one OpenMM System.
Coarse-grained protein models are tiny (a few hundred beads), so a single chain
badly underuses a GPU. Packing N independent copies into one simulation fills the
device and yields N trajectories per run for better sampling. The copies must not
interact: bonded terms are duplicated per copy, and every CustomNonbondedForce
is restricted to intra-copy interactions with addInteractionGroup so copy i
never sees copy j.
Typical use:
import topo
cg = topo.models.buildCoarseGrainModel("protein.pdb", domain_def="domain.yaml")
system, topology, positions = topo.make_noninteracting_copies(
cg.system, cg.topology, cg.positions, n_copies=20)
# ... build a Simulation from system/topology/positions, run, then split the
# multi-chain DCD into per-chain trajectories for analysis.
After the run, the atoms of copy k are the contiguous block
[k*n_atoms : (k+1)*n_atoms] (see split_indices()), which makes splitting
the trajectory trivial.
- topo.utils.multichain._split_cli()[source]¶
CLI:
python -m topo.utils.multichain— split a combined multi-copy DCD.
- topo.utils.multichain.make_noninteracting_copies(system: ~openmm.openmm.System, topology: ~openmm.app.topology.Topology, positions, n_copies: int, shift=5.0 nm)[source]¶
Convenience wrapper: replicate
system,topologyandpositionsinton_copiesnon-interacting copies in one call.- Return type:
(full_system, full_topology, full_positions)
- topo.utils.multichain.replicate_positions(template_positions, n_copies: int, shift=5.0 nm)[source]¶
Replicate positions
n_copiestimes, translating each copy along x byshiftso the chains start in separate regions of space (they do not interact, but separation keeps the start clean and aids visualization).
- topo.utils.multichain.replicate_system_intra_only(template_system: System, n_copies: int) System[source]¶
Replicate
template_systeminton_copiesindependent copies.Particles and constraints are duplicated per copy; each supported force is rebuilt with per-copy atom-index offsets.
CustomNonbondedForceobjects get one interaction group per copy so copies never interact with one another.Supported forces:
HarmonicBondForce,CustomAngleForce,PeriodicTorsionForce,CustomTorsionForce,CustomNonbondedForce.CMMotionRemoveris skipped (add a fresh one to the full system if wanted); any other force type raisesNotImplementedError.
- topo.utils.multichain.replicate_topology(template_topology: Topology, n_copies: int) Topology[source]¶
Replicate an OpenMM
Topologyinton_copieschains.
- topo.utils.multichain.split_chains(combined_dcd, output_paths, *, chunk_size: int = 1000, center: bool = True)[source]¶
Split a combined multi-copy DCD into one DCD per copy.
The combined trajectory’s atoms are assumed to be
n_copiescontiguous equal-size blocks (the layout produced bymake_noninteracting_copies()/split_indices()), so copy k is atoms[k*m : (k+1)*m].Memory-bounded streaming. The combined DCD is read in chunks of
chunk_sizeframes and each chunk is written straight to the per-copy files, so peak memory ischunk_size * n_atoms * 3floats regardless of how long the trajectory is — a combined DCD too large to fit in memory is handled fine. A single pass over the input writes all copies.Coordinates stay in Å throughout (mdtraj’s low-level DCD read and write are both in Å), so no unit conversion is needed or applied.
- Parameters:
combined_dcd (str or Path) – The combined multi-copy DCD.
output_paths (sequence of str or Path) – One output DCD path per copy;
n_copies = len(output_paths). Parent directories are created as needed.chunk_size (int, optional) – Frames read/written per chunk (default 1000).
center (bool, optional) – If True (default), translate each copy to its own centre of geometry per frame, so every per-copy trajectory is centred at the origin and the inter-copy
copy_shiftoffset is removed. Set False to preserve the raw coordinates exactly.
- Returns:
(n_copies, atoms_per_copy, n_frames)
- Return type:
tuple[int, int, int]
topo.utils.nonbonded module¶
Build non-bonded interaction matrices for the TOPO coarse-grained model.
This module constructs distance and energy matrices used by OpenMM for structure-based (native) contacts and repulsive non-native contacts. Inputs: PDB structure, STRIDE hydrogen-bond output, and domain definition YAML.
Main workflow¶
Hydrogen bonds (STRIDE): parsed from stride output; 1 H-bond -> 0.75 kcal/mol, 2+ H-bonds capped at 1.5 kcal/mol per pair.
Backbone-sidechain (BS) and sidechain-sidechain (SS) contacts: distance-based (default cutoff 4.5 Angstrom); BS energy 0.37 kcal/mol; SS energies from BT potential and domain scaling.
Domain scaling: YAML defines intra_domains (residue lists + nscale) and optional inter_domains (pair nscales).
nscaleis the native-contact scaling factor applied to the sidechain-sidechain well depths (not an absolute energy – the contacts still interact at nscale = 1.0). Single-domain proteins can omit inter_domains.Non-native contacts: repulsive well with per-residue Rmin/2 from the nearest non-contact CA-CA distance (sum rule); energy NON_NATIVE_KJ.
Typical use¶
>>> from topo.utils.nonbonded import build_nonbonded_interaction
>>> rmin_matrix, energy_matrix = build_nonbonded_interaction(
... "protein.pdb", "domain.yaml", "stride.dat"
... )
>>> # rmin_matrix (well positions), energy_matrix in nm and kJ/mol for OpenMM
- topo.utils.nonbonded._norm_chain(chain) str[source]¶
Normalize a chain identifier to a canonical string.
Residues are keyed by
(chain, resid)so that structures with multiple chains (where the same residue number can appear in more than one chain) are handled correctly. Different sources spell a blank chain differently: MDAnalysis reports''or a space, while STRIDE prints'-'. All of these are normalized to the empty string so the two sources agree.
- topo.utils.nonbonded._residue_chain(res) str[source]¶
Return the normalized chain ID for an MDAnalysis residue.
Prefers the PDB
chainID(matching STRIDE’s chain column); falls back tosegidifchainIDis unavailable.
- topo.utils.nonbonded._scaling_matrix_from_config(domain_to_residues: Dict, intra_nscales: Dict, inter_nscales: Dict) ndarray[source]¶
Build the SS-energy scaling matrix from already-parsed domain config.
Split out of
get_scaling_ss_matrix()sobuild_nonbonded_interaction()can parse the domain YAML once (for both scaling and the disorder section) rather than reading it twice. Seeget_scaling_ss_matrix()for the returned matrix.
- topo.utils.nonbonded.apply_disorder(rmin_matrix: ndarray, eps_ij: ndarray, rmin_2_nm: ndarray, dis_idx: ndarray, idr_scale: float, index_to_resname: Dict[int, str], ss_interaction_energy: ndarray, eps_gen_kj: float = 0.0)[source]¶
Transform a folded non-bonded build into an IDR-aware one (spec §2.3).
Runs at the very end of
build_nonbonded_interaction(), after the folded build is complete and everything is in nm. It overwrites – in place – only the entries that touch a disordered residue, leaving folded-folded pairs and every folded residue’s Karanicolas-Brooks radius byte-identical to a folded-only run.Three pair classes result (spec §2.1): folded-folded (untouched, Go), IDR-IDR (
max(NON_NATIVE_KJ, eps_gen_kj + idr_scale * eps_BT)), and IDR-folded (excluded-volume only).eps_gen_kjis an optional additive, sequence-independent generic-cohesion term (0 by default -> the original idr_scale-only well). Each IDR residue’s radius is switched to the transferable per-AA sigma-radiusrvdw = IDR_RMIN_2_NM / 2^(1/6)(folded residues keep their K-B Rmin/2), and every IDR-involving well is placed at the bare sum of those per-bead radii. So IDR-IDR comes outrvdw[i] + rvdw[j]– the O’Brien generic-bt convention, ~11% tighter than the bare Rmin sum – while IDR-folded isRmin/2[folded] + rvdw[IDR], keeping the folded bead on its native radius rather than shrinking it in cross pairs.Overriding the per-residue
rmin_2_nmarray (not merely the pair matrix) is a requirement: the same array feeds the NC<->ribosome excluded volume in CSP (spec §2.6), so both excluded-volume channels see the same rvdw for IDR beads.- Parameters:
rmin_matrix (np.ndarray, shape (n, n)) – Folded pairwise well positions (nm). Modified in place.
eps_ij (np.ndarray, shape (n, n)) – Folded pairwise well depths (kJ/mol). Modified in place.
rmin_2_nm (np.ndarray, shape (n,)) – Per-residue radius (nm). Modified in place: IDR entries are overridden with the per-AA sigma-radius
rvdw = IDR_RMIN_2_NM / 2^(1/6); folded entries keep K-B Rmin/2. (So the array mixes conventions by bead class, both feeding a bare sum.)dis_idx (np.ndarray of int) – 0-based indices of the disordered residues.
idr_scale (float) – IDR-IDR attraction scale (0 -> self-avoiding chain).
index_to_resname (dict) – 0-based index -> three-letter residue name (from
get_residue_mapping()).ss_interaction_energy (np.ndarray, shape (n, n)) – The RAW per-pair BT energy matrix (kJ/mol) from
get_ss_interaction_energy()(abs + shift + kcal->kJ already applied); NOT the domain-scaled version – the IDR well must not inheritnscale.eps_gen_kj (float, optional) – Additive, sequence-independent generic-cohesion depth (kJ/mol) added to every IDR-IDR well before the excluded-volume floor. Defaults to 0.0 at this level (the neutral, idr_scale-only transform); a domain_def that omits
eps_gen_kj:gets the calibratedDEFAULT_EPS_GEN_KJfromread_yaml_config()instead, so user-facing runs default to generic cohesion being ON.
- Returns:
rmin_matrix, eps_ij, rmin_2_nm – The same three arrays, now IDR-aware.
- Return type:
np.ndarray
- topo.utils.nonbonded.build_hb_contact_matrix(hb_pairs: List[Tuple], key_to_index: Dict[Tuple[str, int], int], n_residues: int) ndarray[source]¶
Build the hydrogen-bond contact matrix from a list of H-bond pairs.
Counts how many H-bonds exist between each residue pair and stores 0, 1, or 2 (values greater than 2 are capped at 2). The model uses 0.75 kcal/mol per single H-bond and 1.5 kcal/mol total for multiple H-bonds; capping at 2 enforces that.
- Parameters:
hb_pairs (list of tuple) – List of canonical pairs from
parse_hydrogen_bonds(). Each pair is((chain1, resid1), (chain2, resid2)).key_to_index (dict) – Maps
(chain, resid)-> 0-based residue index, fromget_residue_mapping(). Used to place each H-bond at the correct matrix index regardless of chain (multi-chain safe).n_residues (int) – Number of residues (determines matrix shape).
- Returns:
Symmetric matrix. Entry [i, j] is 0, 1, or 2 (number of H-bonds between residue index i and j, capped at 2). Indices are the 0-based residue order from
get_residue_mapping().- Return type:
np.ndarray, shape (n_residues, n_residues), dtype int
Notes
H-bond
(chain, resid)keys are resolved to matrix indices viakey_to_index. A pair whose residue is not found in the mapping (e.g. a hetero residue STRIDE reported but that is not in the protein selection) is skipped with a warning rather than mis-placed.
- topo.utils.nonbonded.build_nonbonded_interaction(pdb_file: str, domain_def: str | None = None, stride_output_file: str | None = None, return_rmin_2: bool = False)[source]¶
Build the well-position (Rmin) and energy matrices for TOPO non-bonded contacts.
Combines hydrogen bonds (STRIDE), backbone–sidechain and sidechain–sidechain contacts (distance cutoffs), and domain-based scaling. Native contacts get their well at the measured CA–CA distance with energies from H-bond, BS, and scaled SS terms; non-native pairs get their well at the sum-rule collision distance
Rmin/2_i + Rmin/2_jand a small repulsive energy (non_native). Output matrices are (n_residues × n_residues), symmetric, in nm and kJ/mol for use with OpenMM. Withreturn_rmin_2=Truealso returns the per-residue Rmin/2 array (nm).- Parameters:
pdb_file (str) – Path to PDB structure (all-atom; used for residue list and CA distances).
domain_def (str, optional) – Path to domain YAML (intra_domains, n_residues, optional inter_domains). If None, all residues are treated as a single domain (scaling 1.0 everywhere).
stride_output_file (str, optional) – Path to STRIDE output for hydrogen bond list. If None, the function checks whether the
strideprogram is available; if yes, it runsstride -h pdb_file, caches the output to{prefix}_stride.dat(prefix taken from the PDB filename), and parses that file. STRIDE success is judged by output content (presence of DNR records), not the process exit code, since some STRIDE builds return non-zero even on success. If stride is not found, raises an error (provide a precomputed STRIDE file or install STRIDE).
- Returns:
rmin_matrix (np.ndarray, shape (n_residues, n_residues)) – Pairwise well position Rmin (nm). Native: CA–CA distance; non-native: Rmin/2_i + Rmin/2_j (sum rule).
energy_matrix (np.ndarray, shape (n_residues, n_residues)) – Pairwise well depth in kJ/mol. Native: sum of H-bond (0.75/1.5 kcal/mol), backbone–sidechain (0.37 kcal/mol), and scaled SS; non-native: NON_NATIVE_KJ.
rmin_2_nm (np.ndarray, shape (n_residues,)) – Only when
return_rmin_2=True(the return is then a 3-tuple): the per-residue Rmin/2 array (nm) the sum rule was built from.
- Raises:
FileNotFoundError – If pdb_file (or stride_output_file when given) cannot be opened.
RuntimeError – If stride_output_file is None and the
strideprogram is not found or not executable.
Example
>>> dist, energy = build_nonbonded_interaction("2ww4.pdb", "domain.yaml", "stride.dat") >>> dist, energy = build_nonbonded_interaction("2ww4.pdb") # single domain, run stride if available >>> dist.shape (283, 283)
- topo.utils.nonbonded.calculate_rmin_2_values(binary_contact_matrix: ndarray, ca_distances: ndarray, n_residues: int) List[float][source]¶
Compute per-residue collision radius Rmin/2 for non-native contacts.
For residue i,
Rmin/2[i] = 0.5 * RMIN_SCALE_FACTOR * (minimum CA-CA distance to residues that are (1) not in contact with i and (2) not within LOCAL_SEPARATION in sequence). The minimum CA-CA distance is taken as the LJ sigma (closest approach);RMIN_SCALE_FACTOR = 2^(1/6)scales it to the full collision diameter Rmin, and the0.5gives Rmin/2 – the per-residue collision radius (structure-derived Karanicolas-Brooks). If no such residue j exists,Rmin/2[i] = 0.0. Non-native pairs combine by the SUM ruleRmin_ij = Rmin/2[i] + Rmin/2[j].- Parameters:
binary_contact_matrix (np.ndarray, shape (n_residues, n_residues)) – Binary contact matrix (1 = in contact, 0 = not).
ca_distances (np.ndarray, shape (n_residues, n_residues)) – CA–CA distances (same units as used later; typically Angstrom).
n_residues (int) – Number of residues.
- Returns:
Rmin/2[i]per residue i; non-native well position via the sum rulermin_matrix[i, j] = Rmin/2[i] + Rmin/2[j].- Return type:
list of float
- topo.utils.nonbonded.format_residue_ranges(nums: List[int]) str[source]¶
Compress a list of residue numbers into a compact
[a-b, c, d-e]string.The inverse of
parse_residue_list(): consecutive runs collapse tostart-endso a long contiguous block prints as one range instead of thousands of numbers (e.g. an IDR covering residues 18..164 prints[18-164]). Duplicates are removed and the output is sorted.- Parameters:
nums (list of int) – Residue numbers (any order, duplicates allowed).
- Returns:
e.g.
"[18-164, 177-219, 279-363]";"[]"for an empty list.- Return type:
str
Example
>>> format_residue_ranges([3, 1, 2, 5, 6, 7, 10]) '[1-3, 5-7, 10]'
- topo.utils.nonbonded.get_bs_contact_matrix(u: Universe, cutoff: float = 4.5) ndarray[source]¶
Build backbone–sidechain contact matrix from structure (distance-based).
Two residues are in contact if any backbone atom of one is within
cutoffof any sidechain atom of the other (and vice versa), and they are not within LOCAL_SEPARATION along the sequence. The matrix is symmetric and stores the total number of directional contacts (i → j and j → i).- Parameters:
u (mda.Universe) – MDAnalysis universe with protein structure (must have backbone/sidechain).
cutoff (float, optional) – Distance cutoff in Angstrom (default
DEFAULT_CUTOFF= 4.5).
- Returns:
Symmetric matrix. Entry [i, j] is the number of directional backbone–sidechain contacts between residue index i and j (0, 1, or 2). Indices are 0-based residue order from
get_residue_mapping().- Return type:
np.ndarray, shape (n_residues, n_residues), dtype int
Notes
Backbone: protein backbone atoms excluding H. Sidechain: protein non-backbone excluding H. Pairs within abs(resid_i - resid_j) <= LOCAL_SEPARATION are excluded, but only when both residues are in the same chain; residues in different chains are never excluded by sequence separation (multi-chain safe).
Why the count is 0, 1, or 2 (and not just a 0/1 flag). A BS contact is directional: the backbone belongs to one residue and the sidechain to the other, so the roles are not interchangeable. A pair (i, j) therefore admits two independent contacts, and either, both, or neither may be present:
0 — neither backbone reaches the other’s sidechain. (The pair may still be a native contact via an H-bond or an SS contact; see
build_nonbonded_interaction().)1 — exactly one direction: backbone of i is within cutoff of a sidechain atom of j, or backbone of j is within cutoff of a sidechain atom of i.
2 — both directions at once: backbone of i touches sidechain of j and backbone of j touches sidechain of i (mutually interdigitated).
That is exactly what the two-step construction below computes: contacts_bs holds directed (backbone-residue -> sidechain-residue) pairs, which are stored in an asymmetric matrix where M[i, j] = 1 means “backbone of i within cutoff of sidechain of j”. The returned value is the symmetrized count M + M.T, whose entries are the number of directions realized. The energy is linear in that count (0, 1, or 2 x BACKBONE_SIDECHAIN_KJ), so a count of 2 marks a tighter, mutually buried pair and earns twice the well depth.
Two easy confusions worth flagging:
This is not the same rule as the H-bond count, which also tops out at 2 (see
get_hb_contact_matrix()) but for an unrelated reason: there the 2 counts two H-bonds reported by STRIDE, not two directions. The BS 2 is a structural maximum, not a cap.Glycine has no sidechain atoms, so it can only ever be the backbone partner — any BS contact involving a glycine caps at 1 by construction.
- topo.utils.nonbonded.get_hb_contact_matrix(stride_output_file: str, key_to_index: Dict[Tuple[str, int], int], n_residues: int) ndarray[source]¶
Build the hydrogen-bond contact matrix directly from a STRIDE output file.
Convenience function that parses the file and builds the matrix. Values are 0, 1, or 2 (multiple H-bonds capped at 2).
- Parameters:
stride_output_file (str) – Path to the STRIDE output file.
key_to_index (dict) – Maps
(chain, resid)-> 0-based residue index, fromget_residue_mapping()(multi-chain safe).n_residues (int) – Number of residues (must match the system used for STRIDE).
- Returns:
Symmetric H-bond contact matrix. See
build_hb_contact_matrix().- Return type:
np.ndarray, shape (n_residues, n_residues), dtype int
Example
>>> key_to_index, _, n_residues = get_residue_mapping(u) >>> hb_matrix = get_hb_contact_matrix("stride.dat", key_to_index, n_residues) >>> hb_matrix.shape (283, 283) >>> (hb_matrix > 1).sum() // 2 # number of pairs with 2 H-bonds (symmetric) 20
- topo.utils.nonbonded.get_residue_mapping(universe: Universe) Tuple[Dict[Tuple[str, int], int], Dict[int, str], int][source]¶
Build residue index and name mappings from an MDAnalysis protein universe.
Uses the order of residues in
universe.select_atoms("protein").residuesto define 0-based indices. Useful for filling contact/energy matrices by residue index instead of PDB resid.- Parameters:
universe (mda.Universe) – MDAnalysis universe with a loaded protein structure.
- Returns:
key_to_index (dict) – Maps
(chain, resid)-> 0-based index in residue list, wherechainis the normalized chain ID (see_norm_chain()) andresidis the PDB residue ID. Keying on(chain, resid)instead ofresidalone is required for multi-chain structures, where the same residue number can appear in more than one chain.index_to_resname (dict) – Maps 0-based index -> three-letter residue name (e.g. ‘ALA’, ‘GLY’).
n_residues (int) – Total number of protein residues.
Example
>>> u = mda.Universe("protein.pdb") >>> key_to_idx, idx_to_name, n = get_residue_mapping(u) >>> key_to_idx[('A', 1)] # first residue's index (chain A, resid 1) 0 >>> idx_to_name[0] 'MET'
- topo.utils.nonbonded.get_scaling_ss_matrix(domain_def: str) ndarray[source]¶
Build scaling matrix for sidechain–sidechain energies by domain.
Reads domain definitions from YAML and builds an (n × n) matrix where entry [i, j] is the intra-domain nscale if residues i and j are in the same domain, or the inter-domain nscale if they are in different domains (defaulting to 1.0 — no scaling — when no inter nscale is defined for that domain pair).
- Parameters:
domain_def (str) – Path to domain definition YAML (see
read_yaml_config()).- Returns:
Symmetric matrix. Rows/columns are ordered by sorted residue list (all residues that appear in domain_to_residues). Values are floats (typically 0.0 to 1.0) used to scale SS contact energies.
- Return type:
np.ndarray, shape (n, n)
- topo.utils.nonbonded.get_ss_contact_matrix(u: Universe, cutoff: float = 4.5) ndarray[source]¶
Build sidechain–sidechain contact matrix from structure (distance-based).
Two residues are in contact if any sidechain atom of one is within
cutoffof any sidechain atom of the other, and they are not within LOCAL_SEPARATION along the sequence. The matrix is binary (0 or 1) and symmetric.- Parameters:
u (mda.Universe) – MDAnalysis universe with protein structure.
cutoff (float, optional) – Distance cutoff in Angstrom (default
DEFAULT_CUTOFF= 4.5).
- Returns:
Symmetric binary matrix. Entry [i, j] is 1 if residue pair (i, j) has at least one sidechain–sidechain contact within cutoff, else 0. Indices are 0-based residue order from
get_residue_mapping().- Return type:
np.ndarray, shape (n_residues, n_residues), dtype int
- topo.utils.nonbonded.get_ss_interaction_energy(u: Universe, bt_file: str = 'bt_potential.csv') ndarray[source]¶
Build residue–residue sidechain interaction energy matrix from BT potential.
Uses the BT potential CSV (residue name vs residue name) and the residue sequence in
uto fill an (n_residues × n_residues) matrix of pairwise energies in kJ/mol.- Parameters:
u (mda.Universe) – MDAnalysis universe; residue order determines matrix indices.
bt_file (str, optional) – Passed to
load_bt_potential()(default'bt_potential.csv').
- Returns:
Matrix of pairwise sidechain interaction energies in kJ/mol. Entry [i, j] is the BT energy for the residue types at indices i and j.
- Return type:
np.ndarray, shape (n_residues, n_residues)
- topo.utils.nonbonded.load_bt_potential(bt_file: str = 'bt_potential.csv') DataFrame[source]¶
Load BT (Betancourt–Thirumalai) potential matrix from CSV.
If
bt_fileis a filename only (no path), the file is loaded fromtopo/parameters/data/so the package works regardless of current working directory. Ifbt_fileis an absolute path, that path is used.- Parameters:
bt_file (str, optional) – Filename (e.g.
'bt_potential.csv') or absolute path to the CSV. Default'bt_potential.csv'is looked up intopo/parameters/data/.- Returns:
Matrix indexed by residue name (rows and columns). Values are in kJ/mol, computed as
KCAL_TO_KJ * |raw_value - bt_shift|from the CSV.- Return type:
pd.DataFrame
- Raises:
FileNotFoundError – If the CSV file cannot be found.
Example
>>> df = load_bt_potential() # uses topo/parameters/data/bt_potential.csv >>> df.loc['ALA', 'GLY'] # energy for ALA–GLY pair in kJ/mol 1.234
- topo.utils.nonbonded.parse_hydrogen_bonds(stride_output_file: str) List[Tuple][source]¶
Parse hydrogen bonds from a STRIDE output file (donor-acceptor pairs).
Only DNR (donor) lines are read so that each physical H-bond is counted once. STRIDE writes each bond twice (DNR and ACC); using DNR only avoids double-counting while still allowing pairs with multiple H-bonds to appear multiple times in the list.
- Parameters:
stride_output_file (str) – Path to the STRIDE output file (e.g.
stride.dat).- Returns:
Each element is a pair
((chain1, resid1), (chain2, resid2))with the tuple sorted so the pair is canonical.chainis the normalized chain ID andresidthe PDB residue ID. Repeated pairs indicate multiple H-bonds between the same residue pair. The(chain, resid)keys match those produced byget_residue_mapping(), so multi-chain structures (where a residue number repeats across chains) are resolved correctly.- Return type:
list of tuple
- Raises:
FileNotFoundError – If the STRIDE file cannot be opened.
Notes
STRIDE line format:
DNR RES chain resid seq -> RES chain resid seq, wherechainis a chain letter (e.g.A) or-for a blank chain. The regex captures resname, chain, and the first resid for donor and acceptor.Example
>>> pairs = parse_hydrogen_bonds("stride.dat") >>> pairs[0] # e.g. (('A', 3), ('A', 43)) (('A', 3), ('A', 43))
- topo.utils.nonbonded.parse_residue_list(residue_items: List) List[int][source]¶
Parse a list of residue specifiers into a flat list of residue numbers.
Accepts integers and strings. Strings may be a single number (e.g.
"5") or a range"start-end"(inclusive), which is expanded to all integers in [start, end].- Parameters:
residue_items (list) – Elements are int or str. Examples:
[1, 2, "5-10", 15].- Returns:
Sorted residue numbers are not guaranteed; order follows the input and range expansion.
- Return type:
list of int
Example
>>> parse_residue_list([1, "3-5", 7]) [1, 3, 4, 5, 7]
- topo.utils.nonbonded.print_pairs_with_multiple_hb(hb_pairs: List[Tuple]) Dict[Tuple, int][source]¶
Count H-bonds per residue pair, print pairs with more than one, and return counts.
- Parameters:
hb_pairs (list of tuple) – List of canonical H-bond pairs from
parse_hydrogen_bonds().- Returns:
Maps canonical pair
((chain1, resid1), (chain2, resid2))to number of H-bonds (before any cap). Useful for logging or downstream analysis.- Return type:
dict
Example
>>> pairs = parse_hydrogen_bonds("stride.dat") >>> counts = print_pairs_with_multiple_hb(pairs) Residue pairs with more than 1 hydrogen bond: A3 -- A43: 2 H-bonds ... (total: 20 pairs) >>> counts[(('A', 3), ('A', 43))] 2
- topo.utils.nonbonded.read_yaml_config(filepath: str) Tuple[Dict, Dict, Dict, Dict | None][source]¶
Read and parse domain definition YAML (intra/inter nscales, residue lists).
nscaleis the native-contact scaling factor applied to the sidechain-sidechain contact well depths – a multiplier, not an absolute energy (the contacts still interact atnscale = 1.0). Per-domain values scale intra-domain contacts; per-interface values scale contacts between two domains.Required key:
n_residues. All three sections are optional (spec §2.2):intra_domains(omit for a single unscaled domain),inter_domains(omit for single-domain proteins; then inter_nscales will be empty), anddisordered(omit for a fully-folded protein). Residues not listed in any domain are assigned to domain'X'with intra nscale 1.0 and inter 1.0 to all other domains.The optional
disordered:section marks intrinsically-disordered (IDR) residues; it carriesresidues:(required, same syntax as a domain’s residue list),idr_scale:(optional, defaultDEFAULT_IDR_SCALE= 1.0 – the sequence-specific IDR-IDR attraction knob) andeps_gen_kj:(optional, defaultDEFAULT_EPS_GEN_KJ= 2.25 kJ/mol – an additive, sequence-independent generic-cohesion depth). The two defaults are the jointly SAXS-calibrated pair. It is returned as the fourth value (Nonewhen absent) and consumed byapply_disorder()insidebuild_nonbonded_interaction().The per-domain scaling key is
nscale. The legacy keystrengthis still accepted as a deprecated alias (a one-time deprecation notice is printed).- Parameters:
filepath (str) – Path to the YAML file (e.g.
domain.yaml).- Returns:
domain_to_residues (dict) – Domain name -> list of residue numbers (1-based).
intra_nscales (dict) – Domain name -> float (intra-domain contact nscale).
inter_nscales (dict) – (domain1, domain2) -> float (inter-domain nscale); symmetric keys (d1, d2) and (d2, d1) are both set.
disorder (dict or None) –
Nonewhen there is nodisordered:section; otherwise{'residues': [int, ...], 'idr_scale': float, 'eps_gen_kj': float}(1-based residue numbers).
- Raises:
FileNotFoundError – If the YAML file cannot be opened.
KeyError – If a domain entry has neither an
nscalenor a legacystrengthvalue.
Example
YAML format:
n_residues: 110 intra_domains: A: { residues: [1-50], nscale: 1.0 } B: { residues: [51-110], nscale: 1.0 } inter_domains: A-B: 0.5 disordered: # optional; None when absent residues: [1-24] idr_scale: 1.0 # sequence-specific BT scale (default) eps_gen_kj: 2.25 # additive generic cohesion, kJ/mol (default)
>>> dom, intra, inter, disorder = read_yaml_config("domain.yaml") >>> dom["A"][:3] [1, 2, 3] >>> inter[("A", "B")] 0.5
topo.utils.runinfo module¶
Run provenance / metadata logging.
Writes a human-readable *_runinfo.log capturing software versions, hardware,
GPU/CUDA details, and timing for a simulation run. This is a pure side channel:
nothing here changes the simulation, it only records how and where a run was
produced, which is useful for reproducibility and for debugging performance
differences between machines.
Typical use from a run script:
import time, topo
start = time.time()
... # build model, set up `simulation`
topo.runinfo.write_run_start(
path, control_file=cfg.config_file, checkpoint_file=checkpoint,
restart=restart_active, steps_planned=nsteps_remain,
simulation=simulation, use_gpu=(cfg.device == 'GPU'), ppn=cfg.ppn)
simulation.step(nsteps_remain)
topo.runinfo.write_run_end(path, simulation=simulation, start_epoch=start)
The log is a simple INI-like text file: a one-line # TOPO run info banner
followed by aligned [run], [software], [hardware], [gpu] (GPU runs
only) and [result] sections. The first four are written before the run so a
crashed run still leaves a “what was attempted” file; [result] is appended at
the end.
- topo.utils.runinfo._context_from_path(path: str) str[source]¶
Short banner context from the output dir, e.g.
.../run/prod/x.log->run/prod.
- topo.utils.runinfo._human_time(seconds: float) str[source]¶
Wall-clock as a single human-friendly figure (s / min / h).
- topo.utils.runinfo._module_version(module) str[source]¶
Best-effort version string for a Python module.
- topo.utils.runinfo._safe_platform_property(simulation, key: str) str[source]¶
Best-effort OpenMM platform property lookup.
- topo.utils.runinfo.collect_cuda_metadata(simulation) dict[source]¶
Collect CUDA/device metadata when available.
Combines OpenMM platform properties (device name, driver/runtime version, precision, device index) with a best-effort
nvidia-smiquery for friendlier diagnostics. Every lookup degrades gracefully to a descriptive “not available” string rather than raising.
- topo.utils.runinfo.write_banner(path: str, title: str) None[source]¶
(Over)write the file with the
# TOPO run infobanner header.
- topo.utils.runinfo.write_run_end(path: str, *, simulation, start_epoch: float, final_structure=None, section_label=None) None[source]¶
Append the
[result]section (status + wall-clock timing + final state).section_labelrenames the section for a folded multi-phase file (e.g."result: stage 2 translocation"); defaults to"result".- Parameters:
path (str) – Output run-info log path (same as passed to
write_run_start()).simulation (openmm.app.Simulation) – The simulation, queried for final step count and simulated time.
start_epoch (float) –
time.time()value captured at the start of the run, used to compute elapsed wall-clock time.final_structure (str, optional) – Path to the PDB of the last conformation written for this run (usable as
init_positionfor a follow-up run).
- topo.utils.runinfo.write_run_start(path: str, *, control_file, checkpoint_file, restart, steps_planned, simulation, use_gpu, ppn, coord_source=None, vel_source=None, title=None, append=False, section_label=None) None[source]¶
Write the banner +
[run]/[software]/[hardware](and[gpu]) provenance sections, overwriting any existing file.append/section_labelsupport a folded, multi-phase run-info file (one file per residue, one section group per stage – used by the CSP runner). Whenappendis True the banner is not re-written and the run-invariant[software]/[hardware]/[gpu]sections are skipped (they were written by the first phase); only the[<section_label>]run section is appended.section_labelnames that section (default"run"), so successive stages read as e.g.[run: stage 2 translocation].Records timing, input/checkpoint paths, planned step count, the source of the initial coordinates/velocities, package versions (Python/NumPy/ParmEd/OpenMM), hardware (host, OS, CPU, selected platform), and — for GPU runs — a friendly accelerator line plus a detailed
[gpu]section.- Parameters:
path (str) – Output run-info log path (e.g.
"traj/traj_runinfo.log").control_file (str) – Path to the simulation control file used for this run.
checkpoint_file (str) – Resolved checkpoint path for the run.
restart (bool) – Whether the run actually restarted from a checkpoint.
steps_planned (int) – Number of integration steps this invocation will run.
simulation (openmm.app.Simulation) – The constructed simulation (used to read the selected platform / GPU info).
use_gpu (bool) – Whether the run is using the CUDA platform.
ppn (int) – Requested CPU threads (informational).
coord_source (str, optional) – Human-readable description of where the initial coordinates came from (checkpoint, init_position, or pdb_file).
vel_source (str, optional) – Human-readable description of where the initial velocities came from (checkpoint or a Boltzmann distribution).
title (str, optional) – Short banner label (e.g.
"L=8 · stage 3"). When omitted, a context is derived from the output directory (e.g."L_008/stage_3").