Source code for batter.exec.handlers.prepare_equil
"""Prepare equilibration inputs for a ligand."""
from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any, Dict, Optional
from loguru import logger
from batter._internal.builders.equil import PrepareEquilBuilder
from batter.config.simulation import SimulationConfig
from batter.orchestrate.state_registry import register_phase_state
from batter.pipeline.payloads import StepPayload, SystemParams
from batter.pipeline.step import ExecResult, Step
from batter.systems.core import SimSystem
[docs]
def prepare_equil_handler(step: Step, system: SimSystem, params: Dict[str, Any]) -> ExecResult:
"""Build equilibration inputs for the current ligand.
Parameters
----------
step : Step
Pipeline step metadata (unused).
system : SimSystem
Simulation system descriptor.
params : dict
Handler payload validated into :class:`StepPayload`.
Returns
-------
ExecResult
Contains the output directory and any generated metadata.
"""
# 1) Parse sim config
payload = StepPayload.model_validate(params)
if payload.sim is None:
raise ValueError("[prepare_equil] Missing simulation configuration in payload.")
sim = payload.sim
partition = payload.get("partition") or payload.get("queue") or "normal"
# 2) Resolve ligand name (e.g., folder name under ligands/)
ligand = system.meta["ligand"]
residue_name = system.meta["residue_name"]
working_dir: Path = system.root / "equil"
comp_windows: dict = payload.get("component_windows", {})
sys_params = payload.sys_params or SystemParams()
extra_restraints: Optional[str] = sys_params.get("extra_restraints", None)
extra_restraint_fc = float(sys_params.get("extra_restraint_fc", 10.0))
extra_conformation_restraints: Optional[Path] = sys_params.get(
"extra_conformation_restraints", None
)
infe = bool(sim.infe)
system_root = system.root
# 3) Read parameter index (produced by param_ligands)
param_dir_dict = system.meta['param_dir_dict']
logger.debug(
f"[prepare_equil] start for ligand={ligand} "
f"| residue={residue_name} | workdir={working_dir}"
)
# 4) Build equilibration system
builder = PrepareEquilBuilder(
ligand=ligand,
sim_config=sim,
component_windows_dict=comp_windows,
working_dir=working_dir,
infe=infe,
system_root=system_root.parents[1],
residue_name=residue_name,
param_dir_dict=param_dir_dict,
extra={
"extra_restraints": extra_restraints,
"extra_restraint_fc": extra_restraint_fc,
"extra_conformation_restraints": extra_conformation_restraints,
"partition": partition,
}
)
def _mark_prepare_equil_failed() -> None:
equil_dir = system_root / "equil"
os.makedirs(equil_dir, exist_ok=True)
(equil_dir / "prepare_equil.failed").touch()
try:
ok = builder.build()
except Exception:
_mark_prepare_equil_failed()
raise
if not ok:
_mark_prepare_equil_failed()
raise RuntimeError(f"[prepare_equil] anchor detection failed for ligand={ligand}")
os.makedirs(system_root / "equil", exist_ok=True)
prepare_finished = system_root / "equil" / "prepare_equil.ok"
open(prepare_finished, "w").close()
prepare_rel = prepare_finished.relative_to(system.root).as_posix()
full_prmtop = (system_root / "equil" / "full.prmtop").relative_to(system.root).as_posix()
failed_rel = (system_root / "equil" / "prepare_equil.failed").relative_to(
system.root
).as_posix()
register_phase_state(
system.root,
"prepare_equil",
required=[[full_prmtop, prepare_rel], [failed_rel]],
success=[[full_prmtop, prepare_rel]],
failure=[[failed_rel]],
)
logger.debug(f"[prepare_equil] finished for ligand={ligand}")
return ExecResult(
[],
{
"ligand": ligand,
"prepared": True,
"residue_name": residue_name,
"workdir": str(working_dir),
},
)