Tutorial 08

June 15, 2026 · View on GitHub

OPES deposits an adaptive bias along a CV to flatten sampling and recover a free-energy surface. On real systems this runs through PLUMED; the algorithm core is also runnable here on the toy surface.

Toy mode (runnable now)

import numpy as np
from pathgennie.core.toy import ToyLangevinEngine, wolfe_quapp_gradient
from pathgennie.sampling import build_path_ensemble
from pathgennie.sampling.opes import OPESStage

engine = ToyLangevinEngine(dt=0.005, kT=2.0)
initial = engine.create_state((-1.0, -1.4))
ens = build_path_ensemble(np.array([engine.get_coords(initial)]), np.array([0.0]),
                          cv_fn=lambda c: c[0, 1])

stage = OPESStage(
    mode="toy", potential_grad=wolfe_quapp_gradient, cv_axis=1,
    grid=np.linspace(-2.0, 2.0, 33), n_steps=20000, pace=20,
    gamma=15.0, sigma=0.2, barrier=8.0, kT=2.0, seed=0,
)
result = stage.run(ens, engine)
for y, f in zip(result.metadata["grid"], result.free_energy):
    print(f"y={y:+.2f}  F={f:.2f}")

The recovered F(y) matches the analytic Wolfe–Quapp marginal (FES minima in the basins, barrier in between).

PLUMED mode (production)

OPESStage(mode="plumed") generates an OPES_METAD input and drives a PLUMED-capable engine:

from pathgennie.sampling.opes import build_plumed_opes_input

print(build_plumed_opes_input(
    ["phi: TORSION ATOMS=5,7,9,15", "psi: TORSION ATOMS=7,9,15,17"],
    ["phi", "psi"], pace=500, barrier=40.0, temp=300.0, sigma=[0.1, 0.1],
))

To run it end-to-end the engine must expose run_plumed(plumed_input, ensemble, **cfg) (a PLUMED-patched MD engine). Without it the stage raises an informative NotImplementedError. From input.yaml:

pathgennie:
  downstream: opes
opes:
  mode: plumed
  plumed_cv_definitions: ["phi: TORSION ATOMS=5,7,9,15"]
  plumed_arg_names: ["phi"]
  pace: 500
  barrier: 40.0

See opes.md for the full reference and the PLUMED integration point.