Skip to content

MD Plugin

Package: caddforge-md

Public source tag

This page describes caddforge-md 0.13.0, tagged as v0.13.0. PyPI publication is pending; see the version matrix.

The MD plugin provides molecular dynamics preparation, simulation, and analysis workflows.

Requirements and example status

The example library provides downloadable configuration templates for every workflow on this page, plus a shared Python runner. File paths refer to inputs you must prepare; these are not bundled research datasets. Schema validation does not establish scientific validity or engine availability. See the version matrix for release tags and publication status.

fix-structure

Conservatively repairs protein hydrogens in a protein-only PDB, protein-ligand complex PDB, or existing POPC/POPE membrane system before OpenMM simulations. The tool preserves explicit protein protonation choices where possible, repairs configured membrane lipid hydrogens with OpenMM force-field templates, leaves ligands/cofactors untouched, and preserves ligand/cofactor CONECT records and PDB formal-charge fields by default.

CLI:

caddforge run md fix-structure --config fix_structure.yaml

YAML:

backend: local
input_pdb: data/complex.pdb
forcefields:
  - amber14-all.xml
  - amber14/tip3p.xml
ph: 7.0
protonation_policy: preserve
termini_policy: charged
fix_nonstandard_residues: auto
strict_all: false
membrane_lipid_resnames:
  - POPC
  - POPE
validate_openmm_system: auto
sanitize_mixed_residues: auto
preserve_conect: true
quiet: false
output_dir_name: output
output_filename: null

validate_openmm_system: auto keeps normal non-membrane behavior unchanged and runs an OpenMM ForceField.createSystem() validation when configured membrane lipid residues are present. sanitize_mixed_residues: auto separates mixed PDB residue ids and reassembles split atom records before OpenMM parsing, for example POPC records interrupted by CL/T3P records that reuse the same residue number. POPC/POPE are detected from both standard and 4-character PDB residue-name layouts. For POPC/POPE Amber-style inputs, include force fields that cover protein, lipids, water, and ions; amber14-all.xml includes lipid templates and amber14/tip3p.xml covers TIP3P water and common ions. Unmatched ligands/cofactors left untouched by this repair tool are reported in repair_metadata.openmm_system_validation.ignored_unmatched_residues and must still be parameterized by the downstream MD workflow.

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "fix-structure")
cfg = spec.config_cls(
    input_pdb="data/complex.pdb",
    forcefields=["amber14-all.xml", "amber14/tip3p.xml"],
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.paths["output_pdb"])

Outputs to inspect: results/output/<input_stem>_fixed.pdb, results/result_record.json. In pipelines, bind input artifact structure and consume output artifact repaired_structure.

Terminal chemistry

termini_policy controls terminal chemistry separately from side-chain protonation:

Policy Behavior
charged (default) Prepare free N-termini as positively charged and free C-termini as negatively charged, independently of pH. Repair missing terminal OXT when the backbone permits reconstruction.
preserve Preserve the input terminal state and validate against the supplied force fields. Ambiguous or incomplete terminal groups fail with a residue-specific explanation.
cap Add ACE/NME caps to eligible neutral or incomplete termini. Already charged or capped termini remain unchanged.

A protonated, uncapped C-terminus (–COOH) is neutral. A deprotonated C-terminus (–COO⁻) is charged. A neutral NME cap (–CONHCH₃) is chemically different from an uncapped neutral acid. Capping can affect both N- and C-termini.

Terminal policy takes precedence over protonation_policy only at terminal groups. Thus the default charged policy can remove HXT even when protonation_policy: preserve; unrelated residue substitutions remain forbidden. Missing terminal hydrogens can make preservation ambiguous (for example, only one H on an ordinary free N-terminus). Prepare the intended terminal state explicitly rather than relying on pH to select it in preserve mode. Hydrogen repair still fills unrelated missing hydrogens.

Stock OpenMM amber14-all.xml supports standard charged termini and ACE/NME caps, but does not include a neutral uncapped –COOH template. To preserve that form, supply a compatible force field or additional, scientifically validated templates:

input_pdb: data/protein_with_explicit_terminal_states.pdb
forcefields:
  - amber14-all.xml
  - /absolute/path/to/your_validated_terminal_templates.xml
protonation_policy: preserve
termini_policy: preserve

The extra XML above is user-supplied, not bundled with CADDForge. Force-field compatibility is checked; preserve mode never silently converts an unsupported neutral terminus. Setting validate_openmm_system: never does not disable protein/cap template matching or terminal-state validation. This feature concerns terminal states of recognized protein residues, not arbitrary modified residues.

For standard charged preparation, set termini_policy: charged. For explicitly requested caps, set termini_policy: cap; side-chain preservation can stay enabled. Approximate reconstructed atoms and caps should be minimized before production MD.

Migration: cap_neutral_termini: true is a deprecated alias for termini_policy: cap. Combining it with an explicit different policy fails. false does not override the new policy. fix_nonstandard_residues now controls modified-residue normalization only; setting it to never does not disable terminal treatment. Users who previously relied on protonation_policy: preserve to reject terminal conversions must now also set termini_policy: preserve.

The report at paths.normalization records requested/effective policy, original terminal evidence, normalization edits or added caps, final decisions, and validation status under normalization.terminal_treatment. It is retained on failure. Successful results also expose repair_metadata.terminal_treatment.

Pipeline example

version: 1
name: prepare-protein
stages:
  - id: repair
    tool: md/fix-structure
    config:
      input_pdb: data/protein.pdb
      forcefields: [amber14-all.xml, amber14/tip3p.xml]
      protonation_policy: preserve
      termini_policy: charged
    outputs:
      protein: output_pdb
  - id: cleanup
    tool: md/minimize
    inputs:
      protein: repair.protein
    config:
      ligand_path: data/ligand.sdf
      mode: ligand_only

The input must be a protein-only receptor for this example; ligand chemistry is supplied separately to minimize. The repair tool's existing input port structure and output port repaired_structure are unchanged. The explicit protein: output_pdb output alias above binds the same PDB for the next stage.

center-and-align

Centers, wraps, optionally aligns, and writes a processed trajectory.

CLI:

caddforge run md center-and-align --config center_and_align.yaml

YAML:

backend: local
topology_path: data/system.pdb
trajectory_path: data/traj.xtc
center_selection: protein or resname MOL
keep_selection: protein or resname MOL
align_selection: protein and backbone
unwrap: true
center: true
wrap: true
center_mode: geometry
wrap_compound: residues
align_weights: mass
output_dir_name: output
output_trajectory_filename: traj-centered.xtc
output_structure_filename: traj-centered.pdb

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "center-and-align")
cfg = spec.config_cls(
    topology_path="data/system.pdb",
    trajectory_path="data/traj.xtc",
    center_selection="protein or resname MOL",
    keep_selection="protein or resname MOL",
    align_selection="protein and backbone",
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.paths)

Outputs to inspect: results/output/<trajectory>, results/output/<structure>, results/result_record.json.

extract-frames

Extracts one or more frames from a trajectory. For multi-frame selections it writes a sliced trajectory and a first selected-frame structure; for single-frame selections it writes only structure-style outputs.

CLI:

caddforge run md extract-frames --config extract_frames.yaml

YAML:

backend: local
topology_path: data/traj-centered.pdb
trajectory_path: data/traj-centered.xtc
ligand_template_sdf: data/template.sdf
ligand_resname: UNK
extract_selection: protein or resname UNK
frame_index: null
last_ns: 10.0
frame_slice: null
write_per_frame_pdbs: false
output_dir_name: output
output_trajectory_filename: last-10ns.xtc
output_structure_filename: last-10ns_first_complex.pdb
protein_trajectory_filename: last-10ns_protein.xtc
protein_structure_filename: last-10ns_first_protein.pdb
ligand_sdf_filename: last-10ns_first_ligand.sdf

Use exactly one frame-selection mode. A Python-style slice is written as:

frame_index: null
last_ns: null
frame_slice:
  start: 100
  stop: 1000
  step: 10

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "extract-frames")
cfg = spec.config_cls(
    topology_path="data/traj-centered.pdb",
    trajectory_path="data/traj-centered.xtc",
    ligand_template_sdf="data/template.sdf",
    ligand_resname="UNK",
    extract_selection="protein or resname UNK",
    last_ns=10.0,
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.paths)

Outputs to inspect: results/output/<structure>, optional results/output/<trajectory> for multi-frame selections, optional protein-only outputs, optional corrected ligand SDF, results/output/frame_manifest.json, results/result_record.json.

cluster-representative

Clusters selected trajectory frames and writes the medoid of the largest cluster. By default, ligand systems cluster on ligand heavy atoms when ligand_resname is provided, while extract_selection controls the atoms written to the representative complex PDB.

CLI:

caddforge run md cluster-representative --config cluster_representative.yaml

YAML:

backend: local
topology_path: data/traj-centered.pdb
trajectory_path: data/traj-centered.xtc
ligand_template_sdf: data/template.sdf
ligand_resname: UNK
cluster_selection: null
extract_selection: protein or resname UNK
last_ns: 10.0
frame_slice: null
distance_threshold_nm: 0.2
output_dir_name: output
output_complex_filename: largest_cluster_representative_complex.pdb
output_protein_filename: largest_cluster_representative_protein.pdb
output_ligand_sdf_filename: largest_cluster_representative_ligand.sdf
clustering_summary_filename: clustering_summary.json

For slice-based clustering, replace last_ns with:

last_ns: null
frame_slice:
  start: 1000
  stop: null
  step: 5

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "cluster-representative")
cfg = spec.config_cls(
    topology_path="data/traj-centered.pdb",
    trajectory_path="data/traj-centered.xtc",
    ligand_template_sdf="data/template.sdf",
    ligand_resname="UNK",
    extract_selection="protein or resname UNK",
    last_ns=10.0,
    distance_threshold_nm=0.2,
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.metrics)

Outputs to inspect: results/output/largest_cluster_representative_complex.pdb, optional results/output/largest_cluster_representative_protein.pdb, optional corrected ligand SDF, results/output/clustering_summary.json, results/result_record.json.

plain-md

Runs a standard OpenMM simulation, with optional OpenFF/SMIRNOFF parameterization for non-standard molecules. For already complete systems, system_preparation_mode: prepared skips hydrogen, solvent, and ion addition and builds a snug periodic box around the provided atoms. Membrane systems can use OpenMM's membrane-specific barostat with barostat: membrane.

CLI:

caddforge run md plain-md --config plain_md.yaml
caddforge run md plain-md --config plain_md.yaml --backend slurm --slurm-time 24:00:00

YAML:

backend: local
system_pdb_path: data/system.pdb
molecule_sdf_paths:
  - data/ligand.sdf
ph: 7.4
system_preparation_mode: solvate
padding: 1.0
ionic_strength: 0.15
prepared_box_margin: 0.0
nonbonded_cutoff: 1.0
barostat: isotropic
pressure: 1.0
membrane_surface_tension: 0.0
membrane_xy_mode: isotropic
membrane_z_mode: free
barostat_frequency: 25
temperature: 300.0
step_size_fs: 4
simulation_time_ns: 1.0
trajectory_filename: traj.xtc
report_interval: 10000
checkpoint_interval: 5000
output_dir_name: output
seed: null
repeats: 1
checkpoint_path: null
max_restarts: 10

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "plain-md")
cfg = spec.config_cls(
    system_pdb_path="data/system.pdb",
    molecule_sdf_paths=["data/ligand.sdf"],
    simulation_time_ns=1.0,
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.metrics)

Outputs to inspect: results/output/*_prepared.pdb, results/output/*_minimized.pdb, results/output/traj.xtc, results/output/checkpoint.chk, results/result_record.json. The standardized result exposes prepared_structure, minimized_structure, trajectory, checkpoint, and output_dir as pipeline-ready result paths.

Set repeats above one to submit independent simulations with distinct reproducible seeds. The parent records child IDs and publishes replicas_manifest.json with aggregate completion metrics when every child reaches a terminal state. Slurm repeat children are submitted before the parent waits for results.

For an already prepared system, use:

system_preparation_mode: prepared
prepared_box_margin: 0.0

In this mode, padding and ionic_strength are ignored. The workflow derives an orthorhombic box from the existing coordinates plus prepared_box_margin and fails if the resulting box is too small for the configured nonbonded cutoff.

For membrane systems oriented in the XY plane with the membrane normal along Z, use:

barostat: membrane
membrane_surface_tension: 0.0
membrane_xy_mode: isotropic
membrane_z_mode: free
barostat_frequency: 25

minimize

Minimize one protein–ligand complex with OpenMM L-BFGS. This is local pose cleanup: the reported physical-model energy includes implicit solvation and is not a binding affinity or a score for comparing different chemical systems. The tool emits finite poses with quality flags by default; strict mode can reject flagged jobs while retaining their structures and diagnostics.

Minimize installation and execution

Use an environment containing AmberTools (pdb4amber, antechamber, parmchk2, tleap), OpenMM >=8.2, ParmEd, RDKit, NumPy, SciPy, and this plugin. For example:

conda create -n caddforge-minimize -c conda-forge python=3.13 ambertools openmm=8.2 parmed rdkit numpy scipy
conda activate caddforge-minimize
# Use the reviewed public checkout, which includes the Amber validation fixes.
python -m pip install -e '/path/to/caddforge-md[minimize]'
caddforge show-config md minimize
caddforge run md minimize --config minimize.yaml

Save this configuration as minimize.yaml, replacing the input paths:

complex_pdb_path: data/complex.pdb
ligand_path: data/ligand.sdf
lig_resname: LIG
mode: ligand_only
pocket_radius_angstrom: 5.0
shell_radius_angstrom: 5.0
backbone_restraint_strength: moderate
shell_restraint_strength: very_strong
tolerance_kj_mol_nm: 10.0
max_iterations: 1000
platform: CPU
run_directory: runs/minimize-one-complex

For separate inputs, replace complex_pdb_path with protein_pdb_path; retain ligand_path in the same coordinate frame. To submit through Slurm, use caddforge run md minimize --config minimize.yaml --backend slurm with your site's backend/environment settings. There is no dedicated minimize GUI in this revision.

AmberTools is installed separately from the Python extra. A prepared Amber input does not run AmberTools. Scientific imports are deferred until execution. CLI and pipeline execution use core input staging, including transparent gzip for declared input files. Checksums in chemistry/mapping records refer to the decompressed bytes. Nested cofactor chemistry paths are ordinary uncompressed configuration inputs and must be available on the execution node.

from cadd_md.minimize import MinimizeConfig, MinimizeWorkflow

config = MinimizeConfig(
    complex_pdb_path="complex.pdb",
    ligand_path="ligand.sdf",
    lig_resname="LIG",
    mode="pocket_restrained",
    run_directory="runs/cleanup",
    registry_path="runs/job_registry.sqlite",
)
workflow = MinimizeWorkflow(config)
result = workflow.run(workflow.prepare_job())
print(result.status, result.metrics)
print(result.paths["minimized_complex"])

Paths follow the existing config convention: relative paths resolve from the submission working directory. Use the core CLI/backend for Slurm submission; the scientific workflow contains no scheduler-specific execution logic. Choose a fresh run_directory for each run; existing minimization outputs are preserved and cannot be overwritten by a new job.

Minimize inputs and physical model

Supply either complex_pdb_path, or protein_pdb_path plus a ligand pose in the same coordinate frame. ligand_path is always required and supplies authoritative SDF/MOL/MOL2 chemistry. For a complex PDB, coordinates come from that complex; template coordinates are not used to align or move the pose. For separate inputs, the ligand file must contain one explicitly hydrogenated 3D molecule. The target residue is selected by lig_resname, which must occur exactly once.

Preparation preserves supplied atoms, protonation, ligand stereochemistry, and disulfides, adds only missing hydrogens, and rejects missing protein heavy atoms. Repair missing protein heavy atoms upstream before submitting the complex; this tool only adds missing hydrogens. Bare histidines need unambiguous hydrogen evidence or an explicit residue_states entry. Optional protein_states_path and protein_links_path must be provided together and match the input structure checksum. Covalent target ligands are rejected.

Automatic preparation uses ff14SB, GAFF2/AM1-BCC, TIP3P retained waters, and OBC2 with mbondi2 radii, ACE surface area, solute dielectric 1, solvent dielectric 78.5, and zero salt screening. It is nonperiodic with NoCutoff; no solvent box or neutralizing ions are added. Retained waters are explicit solute particles in the GB model, a hybrid approximation requiring application-specific validation. All force-field choices, parameterization commands, checksums, and available software versions are retained. Both atom typing and missing-parameter lookup explicitly select GAFF2. Unresolved parmchk2 parameter warnings fail preparation and require reviewed parameters.

Generated GAFF2/AM1-BCC charges are checked and, when necessary, normalized independently for each organic component. Minimize and OpenBPMD apply a uniform per-atom offset only when the total correction is at most 0.005 e and each atom changes by at most 0.0001 e. These are conservative engineering limits, not universal scientific guarantees. Final component charges must sum to the authoritative formal charge within 1e-8 e, and mapped charges are verified again after Amber topology construction. Invalid chemistry or larger residuals fail preparation; supplied Amber charges are never normalized.

Preparation retains the original *.raw.mol2, the final MOL2, and a *.charge_validation.json report with raw/final sums, correction, policy limits, checksums, and the decision. charge_validation in the preparation summary links the reports, including rejected corrections. Applied corrections also produce a log warning. A residual within the charge-token rounding bound is distinguished from a larger generation residual; the latter is not assumed to be rounding. Changing ligand_net_charge or disabling quality checks is not a remedy for this error. These rules also apply to pipeline and scatter jobs without new YAML options.

Automatic preparation also retains Antechamber's ANTECHAMBER_AC.AC bond evidence. The shared validator checks mapped atoms, coordinates, atom types, connectivity (including hydrogen attachments), and total formal charge against the authoritative ligand. A missing or inconsistent AC file fails preparation.

Amber MOL2 bond tokens are not always literal chemical bond orders: for example, carboxylate C–O bonds can both be written as single bonds. Validation accepts supported Amber encodings only in their permitted chemical environments and requires one complete authoritative or RDKit resonance form to explain every bond. Aromatic/Kekulé alternatives are supported; arbitrary bond-order changes, tautomers, and changed protonation are not accepted. At most 1,000 candidate forms are examined. An inconclusive capped search fails explicitly.

The charge report contains a graph_validation record with source checksums, atom mapping, candidate count, interpreted bonds, and the acceptance or rejection reason. The validators never rewrite bond records, atom types, coordinates, or the authoritative ligand. Charge normalization runs only after graph validation. These rules apply equally to minimize, OpenBPMD, organic cofactors, and pipeline jobs; no configuration override is needed for supported representations.

Noncovalent organic cofactors require cofactors entries with residue_index and chemistry_path; optional atom_map_path uses the shared explicit atom-map schema against the original complex. Residue and atom indices are zero-based indices in preparation/inputs/input_identity.json, not PDB serial numbers. Automatic isolated ions are restricted to Na+, K+, and Cl- and require explicit isolated_ion_residue_indices. These declarations must not be used to stand in for a coordinated metal-site model.

Coordinated metals, metall cofactors, and other supported supplied components use all three fields: amber_topology_path, amber_coordinates_path, and amber_mapping_path, alongside the complete input complex. The topology must be nonperiodic, contain all bonded/nonbonded parameters and positive GB radii/screening factors, and have finite charges and positive masses. Unsupported force forms and virtual sites fail explicitly. The supplied radii and charges are used without substituting automatic values. Every non-isolated metal needs a metal_sites entry containing atom_index, oxidation_state, coordinating_atom_indices, and a descriptive parameter_source.

The prepared Amber mapping is JSON:

{
  "schema_version": 1,
  "input_sha256": "SHA256 of the complex PDB",
  "topology_sha256": "SHA256 of the Amber topology",
  "coordinates_sha256": "SHA256 of the Amber coordinates",
  "input_atom_indices": [0, 1, 2, null],
  "input_residue_indices": [0]
}

The arrays follow Amber topology order and must cover every input atom/residue exactly once. null is allowed only for an added hydrogen. Array lengths must match the actual topology; the short arrays above illustrate the schema only. Parameter sources and oxidation states are declarations, not an automatic validation of a metal model's physical accuracy.

Minimize regions and modes

Pocket residues have any heavy atom within pocket_radius_angstrom: 5.0 of any ligand heavy atom. The shell contains other protein residues with any heavy atom within shell_radius_angstrom: 5.0 of any pocket heavy atom. Both selections expand to whole residues and stay fixed during minimization. An explicit pocket_residues: ["A:42", "A:43:B"] replaces automatic pocket selection; _ denotes a blank chain. Unknown, duplicate, ambiguous, or empty selections fail.

mode Pocket side chains Pocket backbone Shell
ligand_only (default) Fixed Fixed Fixed
sidechains_free_backbone_fixed Free Fixed Fixed
sidechains_free_backbone_restrained Free Restrained Fixed
pocket_restrained Free Restrained Strongly restrained
pocket_free_shell_restrained Free Free Strongly restrained

The target ligand is free and distant protein is fixed in every mode. Backbone atoms are N/CA/C/O/OXT; terminal caps count as backbone. Hydrogens inherit their bonded parent's region. Waters, ions, metals, and cofactors are fixed by default; component_mobility permits explicit whole-residue fixed, free, or restrained overrides keyed by input residue index.

Positional restraints use E = ½ k |r − r₀|², with k in kJ mol⁻¹ nm⁻². Named strengths are weak: 100, moderate: 1000, and very_strong: 10000; positive numeric strengths are accepted. backbone_restraint_strength defaults to moderate, and shell_restraint_strength to very_strong. They are starting regimes, not universally validated settings. Only heavy atoms are restrained unless restrain_hydrogens: true. Fixed regions include their hydrogens.

Fixed particles have zero mass in a private minimization-system copy. The exported physical system retains original masses. Bonds and water geometry are flexible by default. constraints: mobile_hbonds constrains bonds involving hydrogen only when both endpoints are mobile, using their force-field equilibrium lengths and retaining the physical bonded terms. Constraints involving fixed atoms are rejected. Disulfide connectivity is preserved across region boundaries.

Minimize convergence, diagnostics, and quality

Defaults are tolerance_kj_mol_nm: 10.0, max_iterations: 1000, constraint_tolerance: 0.00001, platform: CPU, and cpu_threads: 1. GPU platforms accept precision: single|mixed|double where supported. Explicitly requested unavailable platforms or precision settings fail instead of falling back.

The iteration budget spans OpenMM constraint restarts. The trace distinguishes the physical-plus-positional objective from OpenMM's temporary bond-constraint penalty. Final convergence is independently checked using RMS force components over mobile atoms, projected onto the allowed constraint tangent space when needed, plus maximum relative constraint error. A normal OpenMM return alone does not establish convergence. Status is verified_converged, iteration_limit, not_verified, or failed; the record also reports a reached budget even if the last iterate independently satisfies the convergence tests. The implementation targets the OpenMM 8.2 minimizer and reporter behavior.

Diagnostics include initial/final molecular-mechanics, GB, physical, positional, and total objective energies; physical/total RMS and maximum forces for all, mobile, and fixed atoms; iteration counts; constraint errors; heavy-atom ligand, pocket, backbone, shell, receptor, and component RMSDs without alignment; and maximum displacements with atom indices. Preparation displacement is separate. Ordinary nonbonded clashes, 1–4 overlaps, and declared metal coordination are reported separately. Bonded and angle-connected contacts are excluded.

Default review triggers under quality_thresholds are:

Field Default
ligand_rmsd_angstrom 1.0
ligand_max_displacement_angstrom 2.0
pocket_backbone_rmsd_angstrom 0.5
receptor_max_displacement_angstrom 2.0
absolute_energy_per_atom_kj_mol 10000
maximum_force_kj_mol_nm 100000
allowed_vdw_overlap_angstrom 0.6
coordination_distance_change_angstrom 0.3
objective_increase_absolute_kj_mol 0.01
objective_increase_relative 1e-8

These are review triggers, not universal validity bounds; geometric overlaps, especially 1–4 contacts, need chemical interpretation. Nonfinite states, invalid chemistry, or any fixed-atom movement always fail. Other findings produce machine-readable quality_flags. Set strict_quality: true to fail on every flag, or combine strict_quality: true with strict_quality_flags to fail only on selected codes such as convergence_unverified, large_ligand_displacement, or residual_nonbonded_clashes. Finite strict-failure poses remain available.

Minimize artifacts and pipelines

Canonical results are saved in results/result_record.json. Scientific artifacts are under results/minimize/: structures/ contains minimized PDB/SDF files and fresh handoff records; preparation/ contains the physical system, initial coordinates, atom mapping, and provenance; minimization/ contains diagnostics, full-precision final coordinates, and the iteration trace. Exact region membership is in results/minimize/regions.json.

Pipeline input names differ from config fields:

Input port Config field
complex complex_pdb_path
protein protein_pdb_path
ligand ligand_path
ligand_atom_map ligand_atom_map_path
protein_states, protein_links protein_states_path, protein_links_path
amber_topology, amber_coordinates, amber_mapping amber_topology_path, amber_coordinates_path, amber_mapping_path

Supply exactly one structure input and a ligand chemistry file. The paired protein-state/link inputs and the three prepared-Amber inputs must be complete when provided.

Output port Result.paths key
complex minimized_complex
protein minimized_protein
ligand minimized_ligand
result_record result_record
diagnostics diagnostics

All other output ports below use the same name as their result-path key. Output ports expose complex, protein, ligand, diagnostics, regions, atom_mapping, output_mapping, preparation_summary, physical_system, prepared_positions, minimized_positions, amber_topology, amber_coordinates, structure_manifest, protein_states, protein_links, receptor_protein_states, receptor_protein_links, optional iteration_trace, result_record, and output_dir. The complex handoff records match the complex checksum; receptor_* records match the protein output. A covalently connected environment component is retained in the receptor export to avoid cutting chemical bonds, and this is recorded.

The ligand SDF preserves chemistry, title, and user properties. PDB coordinates are rounded to 0.001 Å and SDF coordinates to 0.0001 Å; NumPy coordinate artifacts retain full precision in nanometers, in physical-system particle order. The Amber coordinate artifact is the prepared starting configuration. Use the separate minimized coordinate artifact for the final pose. The exported physical system contains no zero-mass freezes or positional restraints and still needs an appropriate MD protocol before dynamics.

Download example configurations for ligand only, free side chains/fixed backbone, free side chains/restrained backbone, restrained pocket, free pocket/restrained shell, component overrides, and supplied metal parameters. See the minimize pipeline examples for complete YAML and artifact flow. The pipeline uses existing scatter/collect/filter stages and preserves per-pose provenance. Filtering quality flags does not establish pose accuracy; do not rank unrelated ligands by total minimization energy.

Validation in the caddforge-md checkout: python -m pytest tests/test_minimize_*.py. Scientific fixtures use generated peptides/ligands and are marked external; optional GPU comparison tests skip with an explicit reason when no usable GPU platform exists.

openbpmd

Runs OpenBPMD preprocessing, simulations, and averaged multi-replica analysis.

CLI:

caddforge run md openbpmd --config openbpmd.yaml

YAML:

backend: local
complex_pdb_path: data/complex.pdb
lig_resname: MOL
ligand_net_charge: 0
nreps: 10
hill_height: 0.05
deposit_interval_ps: 1.0
log_sync_interval_seconds: 30
output_dir_name: output
analysis_dir_name: analysis

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "openbpmd")
cfg = spec.config_cls(
    complex_pdb_path="data/complex.pdb",
    lig_resname="MOL",
    nreps=10,
    hill_height=0.05,
    deposit_interval_ps=1.0,
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.paths)

Outputs to inspect: prep/, results/output/, results/analysis/results.csv, results/analysis/multi_rep_timeseries.csv, results/progress.json, results/result_record.json.

OpenBPMD streams subprocess output to scratch-first logs and publishes best-effort replica progress from the per-replica simulation logs. log_sync_interval_seconds controls publication frequency; temporary shared-filesystem failures are retried without changing the scientific outcome.

grand

Runs Grand Canonical Monte Carlo (GCMC) water sampling in a ligand-centred pocket region and then selects a single representative protein-ligand-water structure from the resulting trajectory, all within one job. The tool combines two stages so you obtain a usable representative hydration state without a separate manual step.

GCMC sampling stage. The first stage prepares the input protein-ligand complex (adds hydrogens at the configured pH, optionally solvates with padding and ionic strength, restrains solute heavy atoms, and adds ghost waters) and then performs GCMC insertion and deletion sampling inside a ligand-centred spherical region defined by sphere_radius_angstrom. It uses the configured initialization passes and maximum production passes with a running water-count convergence check. The stage writes the final-state checkpoint before any trajectory readback, then produces a trajectory, a per-frame ghost-water state file (one row per frame listing the inactive ghost-water residue indices for that frame), and a hydrated-complex structure. Post-processing is non-strict: if it fails, the error is recorded in the stage summary while the completed checkpoint and trajectory are preserved so the run can continue.

Representative-selection stage. The second stage runs on the produced trajectory and uses the per-frame ghost-water state file to exclude, for each analysed frame, that frame's ghost waters from all downstream selection and output. It deterministically selects exactly one representative frame from within the configured frame analysis interval (repeated runs on the same trajectory and configuration select the same frame) and writes a representative complex structure containing the ligand residue, the protein atoms, and the active non-ghost water residues present at the selected frame. When a ligand template SDF and ligand_resname are configured, it also writes a template-corrected ligand SDF whose bond orders and chemistry come from the template and whose coordinates come from the selected frame, plus an optional protein-only structure. Template ligand export is non-strict: on failure the error is recorded in the selection summary and the representative complex is still written.

CLI:

caddforge run md grand --config grand.yaml
caddforge run md grand --config grand.yaml --backend slurm --slurm-time 24:00:00

YAML:

backend: local
complex_pdb_path: data/complex.pdb
ligand_template_sdf: data/ligand_template.sdf
ligand_resname: LIG
sphere_radius_angstrom: 10.0
n_ghost_waters: 150
nonbonded_cutoff_nm: 0.9
temperature_kelvin: 300.0
initialization_passes: 7500
max_production_passes: 100000
ph: 7.4
padding_nm: 1.2
ionic_strength_molar: 0.15
ligand_selection: null
align_selection: null
frame_analysis_interval: 1
log_sync_interval_seconds: 30
output_dir_name: output
trajectory_filename: gcmc_trajectory.dcd
ghost_water_filename: ghost_water_state.txt
checkpoint_filename: gcmc_checkpoint.chk
hydrated_complex_filename: hydrated_complex.pdb
gcmc_summary_filename: gcmc_summary.json
representative_complex_filename: representative_complex.pdb
representative_protein_filename: representative_protein.pdb
representative_ligand_filename: representative_ligand.sdf
selection_summary_filename: selection_summary.json

Python:

from caddforge.core.execution import load_result_for_job, submit_workflow
from caddforge.plugins import get_tool

spec = get_tool("md", "grand")
cfg = spec.config_cls(
    complex_pdb_path="data/complex.pdb",
    ligand_template_sdf="data/ligand_template.sdf",
    ligand_resname="LIG",
    sphere_radius_angstrom=10.0,
    frame_analysis_interval=1,
    backend="local",
)
workflow = spec.workflow_cls(cfg)
job = submit_workflow(
    workflow, cfg, backend_name=cfg.backend,
    input_ports=tuple(getattr(spec.plugin, "input_ports", ())),
)
result = load_result_for_job(job)
if result is not None:
    print(result.metrics)

Configuration options

GCMC sampling:

  • complex_pdb_path: input protein-ligand complex PDB (required, must exist, .pdb).
  • ligand_template_sdf: ligand template SDF providing bond orders and chemistry for ligand export (required, must exist, .sdf/.sd).
  • ligand_resname: residue name identifying the ligand in the complex (required, 1-4 characters, no whitespace or path separators). Used by both stages.
  • sphere_radius_angstrom: radius of the ligand-centred sampling region (default 10.0, > 0).
  • n_ghost_waters: number of ghost waters added for insertion/deletion (default 150, >= 1).
  • nonbonded_cutoff_nm: nonbonded interaction cutoff in nanometres (default 0.9, > 0).
  • temperature_kelvin: simulation temperature in kelvin (default 300.0, > 0).
  • initialization_passes: equilibration passes before production sampling (default 7500, >= 1).
  • max_production_passes: maximum production passes (default 100000, >= 1).
  • ph: pH used when adding hydrogens during preparation (default 7.4).
  • padding_nm: solvent box padding in nanometres (default 1.2, > 0).
  • ionic_strength_molar: ionic strength in molar used when solvating (default 0.15, >= 0).

Representative selection:

  • ligand_selection: atom selection for the ligand. Derived from ligand_resname (resname <ligand_resname>) when left unset.
  • align_selection: optional atom selection used to align frames; blank is normalized to none.
  • frame_analysis_interval: analyse every Nth trajectory frame (default 1, positive integer).

Log syncing and output:

  • log_sync_interval_seconds: interval between periodic log-sync copies (default 30, 1-3600 inclusive).
  • output_dir_name: directory under results/ that holds stage outputs (default output, no path separators).
  • Output filename fields (trajectory_filename, ghost_water_filename, checkpoint_filename, hydrated_complex_filename, gcmc_summary_filename, representative_complex_filename, representative_protein_filename, representative_ligand_filename, selection_summary_filename): plain filenames only, each with a documented default.

All input file paths are validated and normalized to absolute paths at configuration time, so invalid inputs are reported before any compute job is submitted and inputs resolve correctly when the run executes from a scratch directory.

Slurm scratch execution

On the Slurm backend under an allocated runtime, the workflow executes both stages inside a node-local scratch working directory (rooted at SLURM_TMPDIR) rather than on shared storage, so intermediate I/O does not stress the shared filesystem. When both stages finish successfully, the complete produced output directory is copied back from scratch to the job directory results output directory, preserving its relative structure, and a completeness check verifies every produced file was reproduced. If a stage fails after producing some outputs, those partial outputs are copied back on a best-effort basis and the result records whether the partial copy succeeded. The scratch working directory is removed after the copy-back attempt. When not running under an allocated runtime (for example on the local backend), both stages execute directly in the job directory results output directory and no copy-back is needed.

Log syncing

During long runs under an allocated runtime, the stdout and stderr logs are written in the scratch working directory and periodically copied back to the job directory once per log_sync_interval_seconds, so you can monitor progress while the job is still running. Log-sync copying is resilient: a temporary copy failure is logged as a warning and never crashes the job or changes its status, and the next copy is attempted at the following interval. When a stage finishes, whether it succeeds or fails, a final copy is performed so the job directory logs reflect the final state of the run.

Outputs

Stage outputs are written under results/<output_dir_name>/:

  • gcmc_trajectory.dcd: the GCMC sampling trajectory.
  • ghost_water_state.txt: per-frame ghost-water state file (one row per trajectory frame).
  • gcmc_checkpoint.chk: final-state checkpoint written before trajectory readback.
  • hydrated_complex.pdb: the hydrated-complex structure.
  • gcmc_summary.json: GCMC stage summary (produced paths, frame count, success/failure).
  • representative_complex.pdb: the selected representative protein-ligand-water complex.
  • representative_protein.pdb: optional protein-only representative structure.
  • representative_ligand.sdf: optional template-corrected representative ligand.
  • selection_summary.json: selection stage summary (selected frame index, success/failure).
  • results/result_record.json and results/results.json: the standardized result and a human-readable summary.
  • results/progress.json: best-effort stage, pass-count, acceptance-rate, water-count, and ETA snapshot.

The standardized result exposes representative_complex, representative_protein, representative_ligand, trajectory, checkpoint, and output_dir as pipeline-ready result paths, and its metrics include n_frames_sampled and representative_frame_index.

Compressed inputs

Declared MD structure, molecule, topology, trajectory, checkpoint, and restart inputs may use a trailing .gz. CADDForge expands transparent wrappers at runtime while preserving the original paths. Plain inputs retain their existing behavior.

inducia

Inducia is a composite induced-fit docking and pose-stability workflow built from PheSA, GNINA, GRAND, and OpenBPMD. One job accepts one three-dimensional query ligand; use pipeline scatter for a multi-record query library.

The modes are:

  • template: run PheSA at the fixed pharmacophore-point weights 0.00 through 1.00 in steps of 0.05, cluster the resulting poses by direct heavy-atom RMSD in the binding coordinate frame, identify clashing protein residues, and minimize each representative with those residues flexible in GNINA.
  • independent: run full flexible GNINA docking in an explicit box or autobox. The flexible-residue radius and maximum residue count are configurable.

Every surviving GNINA pose is hydrated by GRAND. Hydrated poses with finite GNINA CNNscores are ordered from highest to lowest, and openbpmd.max_input_complexes limits how many continue to OpenBPMD. Final structures are ranked by ascending OpenBPMD CompScore.

CLI:

caddforge show-config md inducia
caddforge run md inducia --config inducia.yaml

Template-mode configuration shape:

mode: template
backend: local
receptor_pdb_path: data/receptor.pdb
query_ligand_sdf_path: data/query.sdf
template_ligand_sdf_path: data/known_binder.sdf
ligand_resname: LIG
max_local_workers: 1
phesa:
  max_confs: 200
clustering:
  rmsd_cutoff_angstrom: 1.0
clash_detection:
  allowed_vdw_overlap_angstrom: 0.4
gnina:
  cnn_scoring: rescore
  pose_sort_order: CNNscore
grand:
  sphere_radius_angstrom: 10.0
  n_ghost_waters: 150
  initialization_passes: 7500
  max_production_passes: 100000
openbpmd:
  max_input_complexes: null
  nreps: 10
  hill_height: 0.05
  deposit_interval_ps: 1.0
progress:
  interval_seconds: 30

Independent mode replaces template_ligand_sdf_path with either autobox_ligand_path or both box_center and box_size. Its GNINA settings include independent_flex_radius_angstrom (default 3.5) and independent_max_flexible_residues (default 12).

Child phases use at most max_local_workers locally. With Slurm, all siblings in a phase are submitted before monitoring begins. Parent stdout records phase lifecycle, child IDs and statuses, heartbeats, concise child progress, completion, and failure information. Follow it with caddforge logs <job-id> --follow; detailed command logs remain in the child directories.

Structured events are written to results/inducia_progress.jsonl. Temporary registry or shared filesystem failures are treated as monitoring outages and retried. Allocated-runtime publication copies file content without filesystem metadata for CIFS compatibility and verifies it by SHA-256.

Primary outputs are best_complex, best_protein, best_ligand, ranking_table, pose_manifest, ranked complex/protein/ligand path collections, and output_dir. The complete pose manifest retains PheSA scores, cluster membership, flexible residues, all child jobs and statuses, CNNscore shortlist rank, GRAND/OpenBPMD artifacts, CompScore, and failure provenance.

Inducia requires caddforge-core>=0.21.0, caddforge-descriptors>=0.8.0, and caddforge-docking>=0.3.0, plus the external scientific runtimes required by PheSA, GNINA, GRAND, OpenMM, AmberTools, and OpenBPMD.