Skip to content

Fix ENERGY CV in gromacs2024 - #1444

Merged
carlocamilloni merged 3 commits into
plumed:v2.9from
invemichele-peptone:fix-1205-gromacs2024-energy
Aug 15, 2026
Merged

Fix ENERGY CV in gromacs2024#1444
carlocamilloni merged 3 commits into
plumed:v2.9from
invemichele-peptone:fix-1205-gromacs2024-energy

Conversation

@invemichele-peptone

Copy link
Copy Markdown

Fix the ENERGY collective variable with GROMACS 2024

Branch: fix-1205-gromacs2024-energy → base v2.9 · Fixes: #1205 (and the root cause behind #1384)

Disclaimer. This PR was entirely generated by Claude, supervised by @invemichele.

Description

With GROMACS 2024 the ENERGY collective variable silently returns 0.0 on every step that is
not a multiple of nstcalcenergy (default 100), so PLUMED sees a garbage energy on 99 steps out
of 100. OPES_EXPANDED with ECV_MULTITHERMAL, the well-tempered ensemble,
PT-WTE and reweighting on ENERGY are all affected. Nothing warns the user: the patch applies
cleanly, compiles without warnings, and the run completes.

The patch asks GROMACS for the energy by OR-ing bits into force_flags:

plumed_cmd(plumedmain, "isEnergyNeeded", &plumedNeedsEnergy);
if (plumedNeedsEnergy) force_flags |= GMX_FORCE_ENERGY | GMX_FORCE_VIRIAL;

In 2023 this worked because force_flags went straight into the do_force() call a few lines
below. In 2024 do_force() no longer takes a flags argument: the step workload is built by
setupStepWorkload() about 120 lines earlier and stored in the run-schedule object, so the
PLUMED line became dead code. do_force() zeroes term[F_EPOT] every step via
reset_enerdata() and only refills it when stepWork.computeEnergy is set, so
setEnergy hands PLUMED exactly 0.0 the rest of the time. GMX_FORCE_VIRIAL is lost the same
way, so the virial rescaling PLUMED performs when biasing the energy is wrong as well.

This changes only patches/gromacs-2024.3.diff/src/gromacs/mdrun/md.cpp, plus a CHANGES entry:

  • carry the request forward into force_flags before setupStepWorkload();
  • rebuild runScheduleWork_->stepWork right after isEnergyNeeded has answered for this step,
    which makes the fix exact rather than one step late — this also covers the first step of a run
    or of a restart, and input where the energy is needed only on some steps (PRINT ARG=ene STRIDE=7);
  • gmx_fatal if the energy is still not scheduled, so this cannot regress silently;
  • treat plumedNeedsEnergy like bCalcVir in the MD GPU graph reset and reuse conditions,
    since the step workload changes on exactly those steps.

Reproducer. nstcalcenergy = 100 with ene: ENERGY and PRINT ARG=ene STRIDE=1: before the
fix 99 of every 100 lines read 0.000000; after it, every line is a sane potential energy.

Verification

216 SPC waters (648 atoms), OPLS-AA, 0.8 nm cut-offs, dt = 2 fs, v-rescale at 300 K, LINCS on
h-bonds, nstcalcenergy = 100. ENERGY printed every step over 250 steps goes from 4 / 251
finite non-zero values to 251 / 251; the four survivors in the broken run are exactly the
nstcalcenergy steps. OPES multithermal (ECV_MULTITHERMAL 300–350 K, PACE=25,
OBSERVATION_STEPS=8, 1500 steps) was checked against a GROMACS 2023.5 run of the same input,
which predates the regression:

2023.5 (reference) 2024.3 with this PR
temperatures chosen by OPES 10 10
steps with ENERGY == 0 0 / 1501 0 / 1501
bias switches on at 0.402 ps 0.402 ps
∂V/∂E non-zero after observation 1300 / 1300 1300 / 1300

The temperature count is the sharpest check, because OPES sizes that grid from the energy
fluctuations measured during the observation phase: with the bug present the grid collapses to 2
temperatures ("range very narrow") and every DeltaF degenerates. Trajectories are not
bit-identical across GROMACS versions, so the comparison is semi-quantitative by construction.

Also: the long tail correction warning in the docs is stale (#567). While checking what the
energy PLUMED receives actually contains, it turned out that the \bug note on ENERGY — "does
not include long tail corrections … GROMACS DispCorr Ener" — no longer holds for GROMACS. The
correction is computed inside do_force() and folded into F_EPOT by sum_epot(), both before
PLUMED reads the energy. With DispCorr = EnerPres, ENERGY matches the Potential term in the
GROMACS energy file to 0.005 kJ/mol on 2023.5, 2024.3 and 2026.3, while the correction itself
is −154 kJ/mol. Under NPT, where the volume varies by up to 29% and the correction with it by
40 kJ/mol, ENERGY still tracks GROMACS to 0.005 kJ/mol over 401 frames — so the correction is
not merely present but correctly updated. The note is reworded rather than deleted, and kept short
and engine-agnostic: codes other than GROMACS, such as LAMMPS with pair_modify tail yes, were not
tested here, and where the mismatch is real the advice to reweight with the energy of the engine
still stands.

Remaining gaps. The isEnergyNeeded mechanism only exists in the do_force branch, so
relax_shell_flexcon and the modular simulator are untouched, as before. Everything above was
validated on CPU, so the MD GPU graph conditions were not exercised at runtime.

Target release

I would like my code to appear in release 2.9.

Type of contribution
  • changes to code or doc authored by PLUMED developers, or additions of code in the core or within the default modules
  • changes to a module not authored by you
  • new module contribution or edit of a module authored by you
Copyright
  • I agree to transfer the copyright of the code I have written to the PLUMED developers or to the author of the code I am modifying.
Tests
  • I added a new regtest or modified an existing regtest to validate my changes.
  • I verified that all regtests are passed successfully on GitHub Actions.

No regtest is added, for the reason the existing patches/ tree carries none: nothing under
patches/ is compiled by PLUMED, so the test suite cannot exercise it. It is consumed only when
a user runs plumed patch against a GROMACS source tree, at which point GROMACS compiles it. The
GROMACS builds and MD runs above are the substitute.

invemichele-peptone and others added 3 commits August 14, 2026 15:01
GROMACS 2024 snapshots force_flags into stepWork before the PLUMED
isEnergyNeeded query, so OR-ing GMX_FORCE_ENERGY was ignored and ENERGY
was left at 0 except on nstcalcenergy steps. Rebuild the workload when
PLUMED needs the potential energy.

Fixes plumed#1205

Co-authored-by: Cursor <cursoragent@cursor.com>
The manual stated that ENERGY does not include long tail corrections.
For GROMACS that is not the case: the correction is computed inside
do_force() and folded into F_EPOT by sum_epot() before PLUMED reads it.
Verified against the GROMACS energy file with DispCorr=EnerPres on
2023.5, 2024.3 and 2026.3, in NVT and in NPT where the volume varies by
up to 29% and the correction itself by 40 kJ/mol: PLUMED tracks the
GROMACS potential energy to 0.005 kJ/mol over 401 frames.

The claim is left in place for other codes, which were not tested.

Addresses plumed#567

Co-authored-by: Cursor <cursoragent@cursor.com>
Keep the note short and engine-agnostic: what matters to the user is
that the energy PLUMED receives may not contain the long tail
corrections, and that the energy of the MD engine should then be used
for reweighting.

Co-authored-by: Cursor <cursoragent@cursor.com>
@carlocamilloni
carlocamilloni merged commit db80aed into plumed:v2.9 Aug 15, 2026
9 of 11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants