Skip to content

Cross-backend MD handoff (PyCHARMM ↔ JAX-MD ↔ ASE)

Campaign jobs chain via depends_on. Each completed stage writes output_dir/handoff/state.npz (and optionally handoff/final.res) containing:

Field Description
positions Cartesian coordinates (Å)
velocities Atomic velocities (Å/ps) when available
cell 3×3 periodic cell from PBC equilibration
metadata Prior backend, stages, source path

See MdHandoffState.

Default policy on handoff

When continuing from a handoff (depends_on or --continue-from):

  1. Pre-minimization is skipped unless handoff_pre_minimize: true.
  2. Overlap rescue at placement is skipped (geometry is assumed equilibrated).
  3. Cutoffs must match the predecessor job (defaults: in campaign YAML).
  4. Box: handoff cell wins over campaign box_size; a warning is printed if both differ.
  5. Velocities: used when present and continue_velocities: true (default), unless pre-min ran.

When to enable pre-minimization

YAML flag Use when
handoff_pre_minimize: true Always relax on the MMML surface after PyCHARMM equil
handoff_quality_gate: true Only pre-min if initial MMML |F| > handoff_quality_fmax_eVA (default 1.0 eV/Å)
defaults:
  mm_switch_on: 9.0
  mm_switch_width: 1.5
  ml_switch_width: 1.0

runs:
  pycharmm_equil:
    backend: pycharmm
    setup: pbc_npt
    md_stages: "mini,heat,equi"

  jaxmd_prod:
    backend: jaxmd
    setup: pbc_nvt
    depends_on: pycharmm_equil
    handoff_quality_gate: true
    handoff_quality_fmax_eVA: 1.0
    jaxmd_minimize_steps: 500

Diagnostics

JAX-MD / ASE write handoff_policy.json in the job output directory with box source, velocity policy, cutoffs, and initial MMML energy/forces.

PyCHARMM runtime guards

Topology, parameter, PSF, and coordinate reads use a relaxed BOMLEV/WRNLEV context only while the read is active, then restore the prior levels before the next CHARMM command. This avoids leaving bomlev 0 pinned after benign read warnings, which can later abort MLpot registration or dynamics setup.

Before production dynamics, MMML clears CHARMM COMP coordinates and scalar components so stale comparison data is never interpreted as velocities when iasvel=0 or restart paths are reused. clear_comp_for_production() preserves its quiet argument: normal calls are visible by default, and staged workflows can opt into quiet CHARMM housekeeping when running with reduced log noise.

Interpreting initial MMML energy

Positive total MMML energy (eV) is normal — the hybrid calculator is not zero-referenced like CHARMM. High |F| (≫ 1 eV/Å) after handoff usually means cutoff mismatch or missing pre-min on the MMML surface.

Box and velocities on write (PyCHARMM)

At the end of pycharmm_equil, handoff_from_charmm resolves the cubic cell as:

  1. Live CHARMM pbound_get_size (during/after CPT)
  2. !CRYSTAL PARAMETERS in the last stage restart (equi_*.res)
  3. Campaign --box-size fallback

Velocities are taken from the live CHARMM coordinate set, then from the last restart !VELOCITIES block if needed.

If an older handoff/state.npz lacks cell or velocities, load_dependency_handoff enriches from staged .res files in the predecessor job directory.

PyCHARMM continuation (JAX-MD → prod)

When a later campaign job runs PyCHARMM with a handoff, prepare_pycharmm_handoff_continuation patches coordinates (and velocities when continue_velocities is true) into a restart file using, in order:

  1. --handoff-template-res
  2. restart_path metadata on the handoff
  3. handoff/final.res next to continue_from / state.npz
  4. Local staged heat / equi / prod restarts

The patched handoff/continue_seed.res is READ restart into CHARMM so the first dynamics stage uses restart=True (not new + Boltzmann assign).

Velocity carry-over (JAX-MD)

Handoff velocities are applied to the JAX-MD integrator momentum (mass × v) when:

  • continue_velocities: true (default)
  • Pre-minimization did not run (positions unchanged)

After pre-min, velocities are re-thermalized at --temperature.

Drift removal (handoff_velocity_remove_drift: true, default) zeros net momentum and angular momentum before dynamics.

Continue a JAX-MD NVE from a partial trajectory

Aborted or partial NVE runs write output_dir/pbc_nve_jaxmd_nve.h5 (and .traj) with positions and velocities each recording interval. You can restart without Packmol rebuild:

# Single job (mmml md-system --config …) — do NOT pass --rebuild-packmol
setup: pbc_nve
backend: jaxmd
composition: DCM:80
checkpoint: /path/to/ckpt
output_dir: artifacts/md_run_nve_cont
box_size: 30.0
temperature: 250.0
ps: 100
dt_fs: 0.5
md_stages: nve

continue_from: artifacts/md_run/pbc_nve_jaxmd_nve.h5
continue_from_frame: 140          # 0-based; NOT -1 if the last frame exploded
continue_velocities: true         # keep momenta for true NVE continuation

jax_md_update_interval: 1
min_intermonomer_atom_distance: 1.5
dynamics_overlap_action: error

CLI equivalent:

mmml md-system --config md_system.yaml \
  --continue-from artifacts/md_run/pbc_nve_jaxmd_nve.h5 \
  --continue-from-frame 140

Frame choice: with steps_per_recording: 500 and dt_fs: 0.5, frame k is at step (k+1)*500 and time (k+1)*0.25 ps. After a PE→KE spike, pick a frame before the bad record (e.g. the last quiet row in the log). Last frame (continue_from_frame: -1) is wrong if that frame is the explosion.

Also accepts handoff/state.npz, ASE .traj, or CHARMM .res via the same continue_from key.

Replicate NVE from one restart (repeat, not repeats)

Single-job YAML has no repeats: key. Use a campaign with job key repeat: (singular) and --run-all:

# campaign_nve_reps.yaml
defaults:
  backend: jaxmd
  setup: pbc_nve
  composition: DCM:80
  checkpoint: /path/to/ckpt
  box_size: 30.0
  temperature: 250.0
  dt_fs: 0.5
  ps: 100
  md_stages: nve
  jax_md_update_interval: 1
  min_intermonomer_atom_distance: 1.5
  dynamics_overlap_action: error

runs:
  nve_from_partial:
    description: 10 NVE replicas from one pre-crash HDF5 frame
    repeat: 10
    output_dir: artifacts/nve_reps
    continue_from: artifacts/md_run/pbc_nve_jaxmd_nve.h5
    continue_from_frame: 140
    # Independent trajectories: redraw Maxwell–Boltzmann per seed offset
    continue_velocities: false
    seed: 22
mmml md-system --config campaign_nve_reps.yaml --run-all
# resume later: add --resume
Behavior Detail
Outputs artifacts/nve_reps/rep00rep09
Seeds seed, seed+1, … (replicate index offset)
Same continue_from All reps start from that geometry/frame
continue_velocities: true Same momenta → near-identical NVE paths (seed unused for velocities)
continue_velocities: false Re-thermalize at temperature with per-rep seed → independent runs

depends_on: some_equil_job is the usual campaign pattern when the predecessor wrote handoff/state.npz; continue_from + continue_from_frame is for an existing .h5 / .traj / .npz path (including a crashed NVE).