Source code for batter.exec.amber.mdin

"""Helpers for constructing AMBER ``mdin`` control files."""

from __future__ import annotations

from pathlib import Path
from typing import Dict, List, Optional


[docs] class AmberMdin: """Mutable representation of an AMBER mdin file. Parameters ---------- cut : float, optional Non-bonded cutoff in Å (default: 9.0). ioutfm : int, optional Output format flag (1 → NetCDF). ntb : int, optional Periodic boundary condition flag. ntxo : int, optional Restart write format. """ def __init__(self, *, cut: float = 9.0, ioutfm: int = 1, ntb: int = 1, ntxo: int = 2): self.blocks: List[Dict[str, object]] = [] self.apply_defaults(cut=cut, ioutfm=ioutfm, ntb=ntb, ntxo=ntxo) # ----------------------- mutation helpers -----------------------
[docs] def add_block(self, name: str, params: Optional[Dict[str, object]] = None) -> None: """Append a named control block.""" cleaned = name.strip("&") self.blocks.append({"type": "block", "name": cleaned, "params": params or {}})
[docs] def add_raw(self, line: str) -> None: """Append a raw line verbatim to the output.""" self.blocks.append({"type": "raw", "line": line.strip()})
[docs] def update_param(self, block_name: str, key: str, value: object) -> None: """Update a single parameter within ``block_name``.""" for block in self.blocks: if block["type"] == "block" and block["name"] == block_name: block["params"][key] = value return raise KeyError(f"Block '{block_name}' not found")
[docs] def override_block(self, block_name: str, param_dict: Dict[str, object]) -> None: """Merge ``param_dict`` into an existing block or create the block.""" for block in self.blocks: if block["type"] == "block" and block["name"] == block_name: block["params"].update(param_dict) return self.add_block(block_name, param_dict)
# ----------------------- serialisation -----------------------
[docs] def to_string(self) -> str: """Render the mdin contents as text.""" lines: List[str] = [] for block in self.blocks: if block["type"] == "block": lines.append(f" &{block['name']}") for key, value in block["params"].items(): lines.append(f" {key} = {value},") lines.append(" /") elif block["type"] == "raw": lines.append(block["line"]) return "\n".join(lines)
[docs] def save(self, filename: str | Path) -> None: """Write the mdin file to ``filename``.""" path = Path(filename) path.write_text("# Generated by AmberMdin\n" + self.to_string())
# ----------------------- templates -----------------------
[docs] def apply_defaults(self, *, cut: float = 9.0, ioutfm: int = 1, ntb: int = 1, ntxo: int = 2) -> None: """Initialise with a baseline ``cntrl`` block.""" self.add_block( "cntrl", { "imin": 0, "irest": 0, "ntx": 1, "ntxo": ntxo, "ntf": 2, "ntc": 2, "cut": cut, "ntpr": 5000, "ntwr": 5000, "ntwx": 5000, "iwrap": 1, "ioutfm": ioutfm, "ntb": ntb, }, )
# ----------------------- convenience builders -----------------------
[docs] def apply_minimization(mdin: AmberMdin, *, steps: int = 5000) -> None: """Enable energy minimisation for ``steps`` iterations.""" mdin.override_block( "cntrl", { "imin": 1, "maxcyc": steps, "ncyc": steps // 2, }, )
[docs] def apply_npt(mdin: AmberMdin, *, temp: float = 298.15, steps: int = 50000, barostat: int = 2, dt: float = 0.004) -> None: """Configure standard NPT dynamics.""" mdin.override_block( "cntrl", { "ntp": 1, "barostat": barostat, "ntt": 3, "temp0": temp, "gamma_ln": 1.0, "nstlim": steps, "dt": dt, }, )
[docs] def apply_membrane_npt( mdin: AmberMdin, *, temp: float = 298.15, steps: int = 50000, barostat: int = 2, dt: float = 0.004, ) -> None: """Configure semi-isotropic NPT suitable for membranes.""" mdin.override_block( "cntrl", { "ntp": 3, "barostat": barostat, "csurften": 3, "ntt": 3, "temp0": temp, "gamma_ln": 1.0, "nstlim": steps, "dt": dt, }, )
[docs] def apply_ti( mdin: AmberMdin, *, lbd_val: float, timask1: str, timask2: str, scmask1: str, scmask2: str, crgmask: str, ) -> None: """Configure thermodynamic integration (TI) parameters.""" mdin.override_block( "cntrl", { "icfe": 1, "clambda": lbd_val, "timask1": f"'{timask1}'", "timask2": f"'{timask2}'", "scmask1": f"'{scmask1}'", "scmask2": f"'{scmask2}'", "crgmask": f"'{crgmask}'", "ifsc": 1, "ifmbar": 1, }, )
[docs] def apply_restraints(mdin: AmberMdin, *, mask: str, weight: float = 50.0) -> None: """Add positional restraints.""" mdin.override_block( "cntrl", { "ntr": 1, "restraintmask": f"'{mask}'", "restraint_wt": weight, }, )
[docs] def apply_wt_end(mdin: AmberMdin) -> None: """Append the ``&wt type='END'`` control line.""" mdin.add_raw("&wt type='END', /")
[docs] def apply_disang(mdin: AmberMdin, *, filename: str = "disang.rest") -> None: """Reference a DISANG restraint file.""" mdin.add_raw(f"DISANG={filename}") mdin.add_raw("LISTOUT=POUT")