Bias the Landscape or Drive the System?

Separate adaptive metadynamics bias from nonequilibrium steering, implement both central updates in JAX, and interpret real kUPS Ar-pair paths.

A molecular-dynamics trajectory can remain trapped in one free-energy basin for far longer than we can afford to simulate. Enhanced sampling changes the calculation so that rare regions become easier to visit. The dangerous leap is to confuse visiting a region with measuring its equilibrium probability.

This chapter studies two ways of changing the calculation:

  • adaptive bias changes the energy landscape according to the trajectory’s accumulated history;
  • nonequilibrium steering prescribes a time-dependent protocol and records the work performed along each path.

They solve related sampling problems, but they generate different data and require different estimators. A metadynamics bias history is not a work path. A steered path is not a biased equilibrium histogram. We will implement the central update for each method in JAX, then use real kUPS trajectories to see what a moving restraint actually does to two atoms.

What you will learn
  • how a collective variable compresses atomic configurations into a coordinate;
  • why well-tempered Gaussian hills become shorter as local bias accumulates;
  • why adaptive bias and nonequilibrium steering are distinct sampling procedures;
  • how a discrete restraint-center change performs work at a fixed coordinate;
  • how kUPS makes the restraint center depend on the MD step;
  • why slower pulling can reduce lag and hysteresis without becoming equilibrium;
  • how Jarzynski’s equality turns a path ensemble into a tail-sensitive estimate;
  • why effective sample size and forward–reverse agreement do not prove convergence.

Prerequisites: biased distributions from Post 08, overlap and exponential weights from Post 09, harmonic restraints from Post 10, and Langevin dynamics from Post 04.

The collapsed setup selects JAX CPU for the teaching kernels and imports the same workflow that launches the real kUPS smoke experiment later.

Python
from pathlib import Path
import os
import warnings

warnings.filterwarnings("ignore", message="IProgress not found")
os.environ.setdefault("JAX_PLATFORMS", "cpu")

import jax
from jax import Array
import jax.numpy as jnp
from IPython.display import Image, display

from kups_md_tutorials.config import load_enhanced_sampling_spec
from kups_md_tutorials.enhanced_sampling import (
    load_enhanced_sampling_summary,
    write_enhanced_sampling_outputs,
)
from kups_md_tutorials.workflows import verify_post

repo_root = Path.cwd()
if not (repo_root / "configs").exists():
    repo_root = repo_root.parent
os.chdir(repo_root)

Part I: adaptive bias remembers where the path has been

Let \(\mathbf R\) collect all atomic positions. A collective variable

\[s=\xi(\mathbf R)\]

maps that high-dimensional configuration to a smaller coordinate. It could be an atom-pair distance, a torsion angle, a coordination number, or a learned descriptor. Choosing \(s\) is a scientific assumption: motion hidden from \(s\) can remain slow even when \(s\) appears to move freely.

Metadynamics builds a history-dependent bias by depositing Gaussian hills at previously visited values \(s_i=s(t_i)\):

\[V_t(s) = \sum_{t_i<t} w_i \exp\left[-\frac{(s-s_i)^2}{2\sigma^2}\right].\]

Here \(V_t\) is the accumulated bias at time \(t\), \(w_i\) is the height of hill \(i\), and \(\sigma\) is its width in collective-variable units. A new positive hill makes the visited neighborhood less favorable. Repeated hills therefore encourage the path to leave an already explored basin.

The time subscript matters. A fixed umbrella samples one stationary modified Hamiltonian. Metadynamics changes its Hamiltonian while sampling. The path at time \(t\) depends on every earlier deposition event.

Well tempering slows the deposition

Depositing hills of constant height forever would keep forcing the landscape away from a steady limit. Well-tempered metadynamics reduces a new hill using the bias already present at the sampled point:1

\[w_i = w_0 \exp\left[ -\frac{V_{t_i}(s_i)}{k_{\mathrm B}\Delta T} \right], \qquad \gamma=\frac{T+\Delta T}{T}.\]

The initial height is \(w_0\), the physical temperature is \(T\), and \(\Delta T=(\gamma-1)T\) sets how quickly hills shrink. Large accumulated bias means a smaller next hill. The dimensionless bias factor \(\gamma\) must exceed one.

The JAX function below performs exactly one deposition event. jnp.interp reads the current local bias, the exponential tempers the height, and the final line adds a Gaussian across the grid.

Python
def well_tempered_gaussian_deposit(
    bias: Array,
    grid: Array,
    sample: Array,
    initial_height: float,
    width: float,
    bias_factor: float,
    thermal_energy: float,
) -> tuple[Array, Array]:
    """Deposit one well-tempered Gaussian hill on a one-dimensional grid."""

    local_bias = jnp.interp(sample, grid, bias)
    tempering_energy = (bias_factor - 1.0) * thermal_energy
    deposited_height = initial_height * jnp.exp(-local_bias / tempering_energy)
    gaussian = jnp.exp(-0.5 * ((grid - sample) / width) ** 2)
    return bias + deposited_height * gaussian, deposited_height


grid = jnp.linspace(-2.5, 2.5, 2001)
empty_bias = jnp.zeros_like(grid)
accumulated_bias = 2.0 * jnp.exp(-0.5 * (grid / 0.12) ** 2)
_, first_height = well_tempered_gaussian_deposit(
    empty_bias, grid, jnp.array(0.0), 0.03, 0.12, 10.0, 1.0
)
_, tempered_height = well_tempered_gaussian_deposit(
    accumulated_bias, grid, jnp.array(0.0), 0.03, 0.12, 10.0, 1.0
)
print(f"first hill at zero bias={float(first_height):.5f}")
print(f"hill after local bias reaches 2 kBT={float(tempered_height):.5f}")
Output
first hill at zero bias=0.03000
hill after local bias reaches 2 kBT=0.02402

With no accumulated bias, the deposited height equals the requested 0.03000. When the local bias is already \(2k_{\mathrm B}T\) and \(\gamma=10\), the same request deposits only 0.02402. This is the adaptive mechanism—not yet a free-energy estimate.

In the long-time well-tempered limit, the standard reconstruction is

\[F(s) = -\frac{\gamma}{\gamma-1}V(s)+C,\]

where \(C\) is an arbitrary additive constant. The relation is asymptotic. It does not excuse a poor collective variable, an unconverged bias history, or missing support in a hidden slow coordinate.

Test adaptive bias against an answer key

The full analytic control uses a one-dimensional double well whose barrier is known before sampling. It deposits 3,000 hills with \(\gamma=10\). The final bias range is 6.378 reduced-energy units, both basins receive nearly equal support, and the reconstructed barrier differs from the answer by 0.0749.

Adaptive-bias check Full value Interpretation
deposited hills 3,000 length of the history
final bias range 6.378 the landscape changed substantially
left / right basin visits 0.360 / 0.362 both basins were explored
barrier-region visits 0.134 the barrier region gained support
barrier-height error 0.0749 known-answer reconstruction error

Equal basin counts alone would not validate the method. The answer-key error does. On a molecular problem without an answer key, repeatability across independent bias histories and stability to hill height, width, bias factor, and deposition interval become essential.

This metadynamics example is an analytic teaching control. The physical kUPS experiment below performs steered MD, not metadynamics. Keeping that boundary explicit prevents evidence from one method being borrowed to support another.

Part II: steering prescribes a protocol

For the kUPS experiment, the collective variable is the minimum-image distance \(r(\mathbf R)\) between two argon atoms. A harmonic restraint has a center \(c(t)\) that moves from 3.8 Å to 7.5 Å:

\[U_{\mathrm{bias}}(\mathbf R,t) = \frac{K}{2}\left[r(\mathbf R)-c(t)\right]^2.\]

The restraint center is an energetic preference, not a position constraint. At every MD step, Lennard–Jones forces, thermal noise, inertia, and the bias force jointly determine the next atomic state. The realized distance can lag, overshoot, or fluctuate around the center.

Work occurs when the protocol changes

The implementation keeps the center fixed within each stored MD block. At a block boundary it changes the center from \(c_i\) to \(c_{i+1}\) while holding the stored coordinate \(r_i\) fixed. Work is the instantaneous energy change caused by that parameter update:

\[\Delta W_i = U_{\mathrm{bias}}(r_i,c_{i+1}) -U_{\mathrm{bias}}(r_i,c_i) = \frac{K}{2} \left[ (r_i-c_{i+1})^2-(r_i-c_i)^2 \right].\]

The atoms do not move during this accounting event. Coordinate propagation at a fixed center contributes heat and internal-energy change, but not protocol work. The last stored frame has no following center change, so its increment is zero.

Python
def discrete_harmonic_protocol_work(
    distances: Array,
    centers: Array,
    force_constant: float,
) -> Array:
    """Evaluate work from discrete harmonic-center changes at fixed coordinates."""

    old_bias = 0.5 * force_constant * (distances[:-1] - centers[:-1]) ** 2
    new_bias = 0.5 * force_constant * (distances[:-1] - centers[1:]) ** 2
    return jnp.concatenate((new_bias - old_bias, jnp.zeros(1, distances.dtype)))


full_spec = load_enhanced_sampling_spec("11", "full")
steering = full_spec.pair_distance_steered
assert steering is not None
centers = jnp.array([3.8, 5.0, 7.5])
distances = jnp.array([4.0, 4.8, 6.9])
increments = discrete_harmonic_protocol_work(
    distances,
    centers,
    steering.force_constant_ev_per_angstrom2,
)
{
    "stored distance (angstrom)": [round(x, 2) for x in distances.tolist()],
    "restraint center (angstrom)": [round(x, 2) for x in centers.tolist()],
    "protocol-work increment (eV)": [round(x, 6) for x in increments.tolist()],
    "kUPS harmonic k = K/2 (eV/angstrom^2)": (
        0.5 * steering.force_constant_ev_per_angstrom2
    ),
}
Output
{'stored distance (angstrom)': [4.0, 4.8, 6.9],
 'restraint center (angstrom)': [3.8, 5.0, 7.5],
 'protocol-work increment (eV)': [0.0096, 0.0725, 0.0],
 'kUPS harmonic k = K/2 (eV/angstrom^2)': 0.01}

For the three-event control, the two center changes contribute 0.0096 and 0.0725 eV. The final zero is not padding; it encodes the fact that no later protocol value exists. Summing this array reconstructs the total work from the stored distances and center schedule.

Map that definition to kUPS

The real worker implements the same event sequence:

  1. read the kUPS MD step counter and select the corresponding center;
  2. evaluate the harmonic pair restraint with that center;
  3. add it to the Lennard–Jones energy with sum_potentials;
  4. obtain forces from the combined energy inside the kUPS propagator;
  5. advance BAOAB Langevin dynamics with run_md;
  6. store positions, reconstruct minimum-image distances, and evaluate the JAX work formula above at block boundaries.

kUPS defines its harmonic bond as \(k(r-r_0)^2\), whereas this chapter writes \(K(r-r_0)^2/2\). The worker therefore passes \(k=K/2\). Before production, it compares the dynamic first-center potential with an independently built static restraint in both energy and gradient. It also verifies that zero drive gives zero work and that a kUPS energy difference matches the analytic increment.

Those checks establish the implemented Hamiltonian and accounting convention. They do not show that the protocol is slow or that the path ensemble is large enough.

Run actual steering trajectories

The fresh-kernel notebook launches four CPU-sized kUPS paths: fast and slow protocols in both forward and reverse directions. Each isolated worker constructs the combined potential, propagates real MD, writes HDF5 positions, and returns a compact work trace. The notebook then invokes the same verifier used by the command-line workflow.

Python
smoke_spec = load_enhanced_sampling_spec("11", "smoke")
smoke_dir = write_enhanced_sampling_outputs(
    smoke_spec,
    output_root=Path("notebook-runs"),
    quiet=True,
)
verify_post("11", "smoke", output_root=Path("notebook-runs"))
smoke = load_enhanced_sampling_summary(
    smoke_dir / "enhanced_sampling_summary.json"
)
smoke_pair = smoke.pair_distance_steered
assert smoke_pair is not None
{
    "engine and device": (smoke_pair.engine, smoke_pair.observed_devices),
    "runs / stored frames": (smoke_pair.run_count, smoke_pair.total_frames),
    "fast / slow hysteresis gaps (eV)": (
        round(smoke_pair.fast_hysteresis_gap, 5),
        round(smoke_pair.slow_hysteresis_gap, 5),
    ),
    "zero-drive work error (eV)": smoke_pair.zero_drive_work_error_ev,
    "kUPS work-increment error (eV)": (
        smoke_pair.kups_work_increment_error_ev
    ),
}
Output
{'engine and device': ('kups', ('cpu:cpu',)),
 'runs / stored frames': (4, 160),
 'fast / slow hysteresis gaps (eV)': (0.20316, 0.02302),
 'zero-drive work error (eV)': 0.0,
 'kUPS work-increment error (eV)': 3.895587141929113e-09}

The new smoke run stores 160 frames. Its fast hysteresis gap is 0.2032 eV and its slow gap is 0.0230 eV. Zero drive gives exactly zero work, and the largest kUPS-versus-analytic increment error is \(3.90\times10^{-9}\) eV. This is bounded execution evidence on CPU; the independent-replica GPU record carries the quantitative comparison.

Projected atomic path and restraint-tracking curves from real steered kUPS trajectories
A moving restraint guides rather than prescribes the atomic path. Left: 50 frames selected from one real 200-frame slow-forward kUPS trajectory, with atom 0 fixed at the origin and atom 1 shown by its minimum-image x-y displacement. Right: mean Ar--Ar distance and one-standard-deviation ribbons from all four forward replicas at each speed. The gray dashed line is the prescribed center.

The left panel is an actual atomic trajectory, not a schematic interpolation between the endpoints. The three-dimensional pair displacement is projected onto the x-y plane, so radial pulling and rotational diffusion both appear. The dashed circles show only the initial and final restraint centers.

The right panel separates the protocol from the response. Across the four forward paths, the mean absolute distance-to-center lag is 0.74 Å for fast pulling and 0.52 Å for slow pulling. Slowing the schedule lets the coordinate track its moving preference more closely, but the shaded replica spread makes clear that thermal fluctuations remain.

Hysteresis asks whether slower was actually gentler

For a forward path, the second law implies

\[\langle W_{\mathrm F}\rangle\geq\Delta F.\]

For the reverse direction,

\[\langle W_{\mathrm R}\rangle\geq-\Delta F.\]

Their sum defines a loop-width diagnostic,

\[H = \langle W_{\mathrm F}\rangle +\langle W_{\mathrm R}\rangle.\]

An ideal quasistatic pair of protocols has \(H=0\). Finite-speed driving, coordinate lag, and relaxation hidden from the chosen coordinate make the loop wider. Hysteresis is not itself a free-energy estimator; it answers a more limited question: did allocating more MD steps reduce dissipation?

The full experiment uses two Ar atoms in a periodic 30 Å cube at 100 K, Lennard–Jones parameters \(\sigma=3.405\) Å and \(\epsilon=0.010326\) eV, and \(K=0.02\) eV/Å\(^2\). Four independent replicas are run for every speed–direction combination, giving 16 GPU paths and 2,000 stored frames.

Python
production = load_enhanced_sampling_summary(
    Path("results/post-11/full/enhanced_sampling_summary.json")
)
pair = production.pair_distance_steered
assert pair is not None
{
    "kUPS version / device": (pair.kups_version, pair.observed_devices),
    "replicas / runs / frames": (
        pair.replica_count, pair.run_count, pair.total_frames
    ),
    "fast hysteresis gap +/- SEM (eV)": (
        round(pair.fast_hysteresis_gap, 5),
        round(pair.fast_hysteresis_gap_sem, 5),
    ),
    "slow hysteresis gap +/- SEM (eV)": (
        round(pair.slow_hysteresis_gap, 5),
        round(pair.slow_hysteresis_gap_sem, 5),
    ),
    "fast / slow gap ratio": round(pair.hysteresis_gap_ratio, 2),
    "forward / reverse Jarzynski (eV)": (
        round(pair.forward_jarzynski_delta_f, 5),
        round(pair.reverse_jarzynski_delta_f, 5),
    ),
    "zero-drive / work-increment errors (eV)": (
        pair.zero_drive_work_error_ev, pair.kups_work_increment_error_ev
    ),
}
Output
{'kUPS version / device': ('1.0.3', ('gpu:NVIDIA RTX A5000',)),
 'replicas / runs / frames': (4, 16, 2000),
 'fast hysteresis gap +/- SEM (eV)': (0.05258, 0.026),
 'slow hysteresis gap +/- SEM (eV)': (0.01113, 0.00866),
 'fast / slow gap ratio': 4.72,
 'forward / reverse Jarzynski (eV)': (-0.01126, -0.01238),
 'zero-drive / work-increment errors (eV)': (0.0, 6.815571232388073e-10)}
Production check Full-profile value What it supports
observed device NVIDIA RTX A5000 required GPU execution
fast hysteresis 0.05258 ± 0.02600 eV four-path mean and path SEM
slow hysteresis 0.01113 ± 0.00866 eV smaller than fast
fast / slow ratio 4.72 slower protocol narrows this loop
static energy / gradient error 0 / 0 dynamic first center matches static bias
zero-drive work error 0 eV no parameter change means no work
maximum work-increment error \(6.82\times10^{-10}\) eV stored-frame accounting matches kUPS

The uncertainty is not decorative. One fast-forward replica records 0.0848 eV of work while another records -0.0205 eV. Four paths support the limited claim that this slower protocol reduces the loop width in this teaching system. They do not determine the tails of a work distribution precisely.

Jarzynski is exact and tail-sensitive

Jarzynski’s equality relates a nonequilibrium path ensemble to the equilibrium free-energy difference:2

\[e^{-\beta\Delta F} = \left\langle e^{-\beta W}\right\rangle, \qquad \beta=\frac{1}{k_{\mathrm B}T}.\]

The average is over independently initialized paths generated by the same protocol. Taking the logarithm gives the finite-sample estimator

\[\widehat{\Delta F} = -k_{\mathrm B}T \log\left[ \frac{1}{N}\sum_{n=1}^{N} e^{-W_n/(k_{\mathrm B}T)} \right].\]

Low-work paths receive exponentially larger weights. Numerically, the JAX implementation uses logsumexp so tiny weights do not underflow. It also reports the normalized effective sample size

\[\frac{N_{\mathrm{eff}}}{N} = \frac{\left(\sum_n a_n\right)^2} {N\sum_n a_n^2}, \qquad a_n=e^{-W_n/(k_{\mathrm B}T)}.\]
Python
def jarzynski_free_energy_and_ess(
    work: Array,
    thermal_energy: float,
) -> tuple[Array, Array]:
    """Estimate a physical free-energy difference and exponential-weight ESS."""

    log_weights = -work / thermal_energy
    log_mean_weight = jax.scipy.special.logsumexp(log_weights) - jnp.log(work.size)
    delta_free_energy = -thermal_energy * log_mean_weight
    shifted_weights = jnp.exp(log_weights - jnp.max(log_weights))
    effective_samples = jnp.sum(shifted_weights) ** 2 / jnp.sum(shifted_weights**2)
    return delta_free_energy, effective_samples / work.size


constant_work = jnp.full(12, 0.07)
delta_f, ess_fraction = jarzynski_free_energy_and_ess(
    constant_work, thermal_energy=0.025
)
print(f"known work / estimated delta F={float(constant_work[0]):.3f} / {float(delta_f):.3f} eV")
print(f"exponential-weight ESS={float(ess_fraction):.3f} of the 12 paths")
Output
known work / estimated delta F=0.070 / 0.070 eV
exponential-weight ESS=1.000 of the 12 paths

When all 12 control paths have the same work, Jarzynski must return that work and every path carries equal weight. The output recovers 0.070 eV and an ESS fraction of 1.000. Real nonequilibrium work is neither constant nor so kind.

For the radial Ar-pair answer key, \(\Delta F=-0.00570\) eV between the two restrained endpoints. Only four slow paths per direction give forward and reverse Jarzynski estimates of -0.01126 and -0.01238 eV. They agree with each other to 0.00112 eV, yet both miss the answer by roughly 0.006–0.007 eV. The ESS fractions are 0.546 and 0.672.

This is the central warning: directional agreement and a moderate ESS do not manufacture an unsampled exponential tail. A defensible free-energy claim would need more independent equilibrated starting states, path-count and tail stability tests, and preferably a bidirectional estimator using forward–reverse overlap.3

A prediction to test

Before changing code, predict the effect of each intervention:

  1. Halve the metadynamics hill width while keeping its height fixed. Which parts of the reconstructed free energy should become noisier?
  2. Double the number of MD steps in the slow steering protocol without changing its endpoints. Should the restraint-center lag, hysteresis, and thermal trajectory spread all decrease in the same way?
  3. Add many typical-work paths but no unusually low-work paths. Can the ordinary work mean stabilize while the Jarzynski estimate remains biased?

The useful answer to the second question is “not necessarily.” Slower driving should reduce systematic lag and dissipation, but it does not remove equilibrium thermal fluctuations. For the third, Jarzynski can remain unstable because its information lives in a different tail than the ordinary mean.

Reproducibility record and full diagnostic dashboard

The committed source of truth includes the smoke configuration, full configuration, notebook, smoke summary, production summary, stored steering trace, manifest, kUPS worker, JAX reference algorithms, figure source, figure-generation entry point, and review record.

uv run kups-tutorial run 11 --profile smoke
uv run kups-tutorial verify 11 --profile smoke
uv run kups-tutorial run 11 --profile full
uv run kups-tutorial verify 11 --profile full
uv run kups-tutorial verify-notebooks --posts 11 --timeout 180

Each of the 16 production paths records the input CIF and HDF5 SHA-256, raw byte count and dataset schema, seed, speed, direction, replica, frame count, observed device, runtime, block thermodynamics, and work controls. The compact CSV retains every center, distance, work increment, and cumulative work value. The figure provenance records the selected HDF5 hash, frame indices, minimum-image convention, projection, and all-forward-replica trace hash.

The multi-panel dashboard below preserves the analytic work controls, complete metadynamics reconstruction, driven-coordinate traces, and cumulative work. It is useful for audit, but the two-panel atomic figure carries the main physical argument.

Diagnostic plots for metadynamics controls, steered coordinates, and protocol work
Full Post 11 diagnostic dashboard. The analytic metadynamics, work-identity, and finite-speed controls are separated from the real kUPS Ar-pair coordinate and cumulative protocol work.

What this chapter establishes—and what it does not

The adaptive control shows that the standard well-tempered update can fill and approximately reconstruct a known double well. It does not show that kUPS ran metadynamics on an atomistic system.

The physical experiment shows that kUPS ran a time-dependent harmonic restraint on real Ar-pair trajectories, that work is reconstructible at the actual discrete parameter changes, and that a slower schedule reduced lag and forward–reverse hysteresis. It does not show that four paths converge an exponential-work free energy or that pair distance is a challenging reaction coordinate.

That separation is the point. Enhanced sampling is trustworthy when the modified dynamics, stored evidence, and estimator correspond to the same mathematical experiment.

Closing

Adaptive bias and steering both make rare motion easier by changing the measure that generates trajectories. The correction must match the change.

For metadynamics, preserve and test the bias history. For steered MD, define work at the same protocol events the code executes and collect an ensemble of independent paths. In both cases, a visually successful crossing is only the beginning. The result starts when you can explain what distribution generated the data and why the estimator has support there.

References

  1. Barducci, A., Bussi, G. & Parrinello, M. (2008). Well-tempered metadynamics: A smoothly converging and tunable free-energy method. Physical Review Letters 100, 020603.
  2. Jarzynski, C. (1997). Nonequilibrium equality for free energy differences. Physical Review Letters 78, 2690.
  3. Crooks, G. E. (1999). Entropy production fluctuation theorem and the nonequilibrium work relation for free energy differences. Physical Review E 60, 2721.