Add a patch for GROMACS 2026 - #1439
Conversation
|
I changed the target branch from master to 2.10 to allow more versions to use it. |
|
@aalhossary but the source branch should match v2.10 as well |
There is no patch for GROMACS 2026, so `plumed patch -e` offers nothing newer than
gromacs-2025.0 and there is no supported way to build a PLUMED-enabled GROMACS 2026.
Applying the 2025.0 patch to 2026.4 does not work. GROMACS replaced the t_commrec
plumbing with the MpiComm abstraction in db61f87e0a ("Use MpiComm in t_commrec"), so
PlumedOptions now carries `const MpiComm* mpiComm_` instead of `const t_commrec* cr_`:
$ plumed patch -p -e gromacs-2025.0 # on a 2026.4 tree
patching file ./src/gromacs/applied_forces/plumed/plumedforceprovider.cpp
Hunk plumed#3 FAILED at 113.
1 out of 4 hunks FAILED -- saving rejects to ...plumedforceprovider.cpp.rej
patching file ./src/gromacs/CMakeLists.txt
Reversed (or previously applied) patch detected! Skipping patch.
2 out of 2 hunks ignored -- saving rejects to ./src/gromacs/CMakeLists.txt.rej
and six further files apply only with fuzz 5. The failing hunk is the one that hands
PLUMED the multi-replica communicators, which is the most damaging thing to lose
silently: `GREX setMPIIntercomm` is what populates PlumedMain::multi_sim_comm
(GREX.cpp), so without it every multi-replica action degrades to a single replica
without any error.
The new patch is the 2025.0 one carried onto the 2026 API:
* the GREX/multisim block uses MpiComm::isMainRank() and MpiComm::comm()
* src/gromacs/CMakeLists.txt is dropped entirely -- GROMACS 2026 already calls
gmx_manage_plumed() early, which is why those hunks reported as already applied
* setNumOMPthreads is carried over from plumed#1435
* the replex_ plumbing, the PLUMED_LOG_FILE override and forcing GMX_USE_PLUMED=ON
are unchanged in substance
It also fixes one bug in GROMACS's own code. The same MpiComm commit rewrote
- plumed_->cmd("setMPIComm", &options.cr_->mpi_comm_mygroup);
+ plumed_->cmd("setMPIComm", options.mpiComm_->comm());
dropping the address-of. MpiComm::comm() returns MPI_Comm by value, but PLUMED reads
that argument as `*(const MPI_Comm*)` (Communicator::Set_comm), so where MPI_Comm is a
pointer type this compiles and silently passes the wrong handle. The patch passes the
address of a named local instead. This is worth reporting to GROMACS separately; until
it is fixed there, any domain-decomposed PLUMED run on 2026 is affected.
Verified on GROMACS 2026.4 with clang 20.1.1, OpenMPI 5.0.7 and libc++:
* `plumed patch -p -e gromacs-2026.0` applies with no fuzz and no rejects, and
`plumed patch -r` restores the tree byte-for-byte
* the patched GROMACS builds clean (GMX_MPI=ON, GMX_THREAD_MPI=OFF, GMX_OPENMP=ON);
the four changed translation units compile with no errors and no warnings
* a two-replica `mdrun -multidir` run expands `@replicas:` to a different value per
replica, which is only possible when multi_sim_comm has been set up
Scope for 2027: the plumed module sources on GROMACS main are currently identical to
release-2026, and the runner.cpp and mdmodulesnotifiers.h edits apply there unchanged;
only the stored .preplumed snapshots would need regenerating once 2027 branches.
831a00e to
34dcd1c
Compare
|
Sorry. It slipped out of my mind (being just files addition). |
Reading this PR: 17 files, but only 98 lines actually changeGitHub renders every file here as "new file, +N", so the diff view shows ~8800 additions and gives Why the files come in pairs. diff -U 5 "$diff/$bckfile" "$diff/$file" --label="$bckfile" --label="$file" |
patch -u -l -b -F 5 -N --suffix=.preplumed "$file"So every patched file must be stored twice, in full. There is no option to store a diff instead. For
It also carries one fewer build file than gromacs-2025.0: What each file is for
On the cmake file, since it is the one that looks least related. GROMACS 2026's own logic is that The three smallest files ( The effective diff — all 98 changed lines--- a/cmake/gmxManagePlumed.cmake
+++ b/cmake/gmxManagePlumed.cmake
@@ -41,8 +41,8 @@
gmx_option_multichoice(GMX_USE_PLUMED
"Build the PLUMED wrapper with GROMACS"
- AUTO
- AUTO ON OFF)
+ ON
+ ON)
mark_as_advanced(GMX_USE_PLUMED)
function(gmx_manage_plumed)
--- a/src/gromacs/applied_forces/plumed/plumedforceprovider.cpp
+++ b/src/gromacs/applied_forces/plumed/plumedforceprovider.cpp
@@ -41,10 +41,14 @@
#include "plumedforceprovider.h"
+#include <cstdlib>
+
#include "gromacs/domdec/domdec.h"
#include "gromacs/domdec/domdec_struct.h"
#include "gromacs/math/units.h"
+#include "gromacs/mdlib/gmx_omp_nthreads.h"
#include "gromacs/mdrunutility/handlerestart.h"
+#include "gromacs/mdrunutility/multisim.h"
#include "gromacs/mdtypes/enerdata.h"
#include "gromacs/mdtypes/forceoutput.h"
#include "gromacs/utility/exceptions.h"
@@ -70,7 +74,7 @@
PlumedForceProvider::~PlumedForceProvider() = default;
PlumedForceProvider::PlumedForceProvider(const PlumedOptions& options)
-try : plumed_(std::make_unique<PLMD::Plumed>())
+try : plumed_(std::make_unique<PLMD::Plumed>()), replex_(options.replex_)
{
// I prefer to pass a struct with data because it stops the coupling
// at the implementation and not at the function signature:
@@ -114,14 +118,45 @@
}
}
+ if (plumedAPIversion_ > 5)
+ {
+ /* Tell PLUMED how many OpenMP threads GROMACS is using, so that it can
+ parallelise its own work. Without this PLMD::OpenMP::getNumThreads()
+ always reports 1 unless PLUMED_NUM_THREADS is set by hand. */
+ int numOmpThreads = gmx_omp_nthreads_get(ModuleMultiThread::Default);
+ plumed_->cmd("setNumOMPthreads", &numOmpThreads);
+ }
+
+ if (isMultiSim(options.ms_))
+ {
+ /* Hand PLUMED the multi-replica communicators. This is what populates
+ PLMD::PlumedMain::multi_sim_comm, without which every multi-replica
+ action silently degrades to a single replica. */
+ if (options.mpiComm_->isMainRank())
+ {
+ plumed_->cmd("GREX setMPIIntercomm", &options.ms_->mainRanksComm_);
+ }
+ MPI_Comm intracomm = options.mpiComm_->comm();
+ plumed_->cmd("GREX setMPIIntracomm", &intracomm);
+ plumed_->cmd("GREX init", nullptr);
+ }
+
if (options.mpiComm_->isParallel())
{
- plumed_->cmd("setMPIComm", options.mpiComm_->comm());
+ /* PLUMED dereferences this argument as an MPI_Comm*, so it must be given
+ the address of a communicator, not the communicator itself. */
+ MPI_Comm intracomm = options.mpiComm_->comm();
+ plumed_->cmd("setMPIComm", &intracomm);
}
plumed_->cmd("setNatoms", options.natoms_);
plumed_->cmd("setMDEngine", "gromacs");
- plumed_->cmd("setLogFile", "PLUMED.OUT");
+ {
+ const char* logFileEnv = std::getenv("PLUMED_LOG_FILE");
+ const std::string logFile =
+ (logFileEnv && logFileEnv[0] != '\0') ? logFileEnv : "PLUMED.OUT";
+ plumed_->cmd("setLogFile", logFile.c_str());
+ }
plumed_->cmd("setTimestep", &options.simulationTimeStep_);
plumed_->cmd("init", nullptr);
@@ -190,6 +225,17 @@
// Do the work
plumed_->cmd("performCalc", nullptr);
+ if (replex_)
+ {
+ double bias = 0.0;
+ plumed_->cmd("getBias", &bias);
+ if (bias != 0.0)
+ {
+ GMX_THROW(NotImplementedError("The PLUMED patch is still not compatible"
+ " with the replica exchange if PLUMED computes biases"));
+ }
+ }
+
msmul(plumed_vir, 0.5, plumed_vir);
forceProviderOutput->forceWithVirial_.addVirialContribution(plumed_vir);
}
--- a/src/gromacs/applied_forces/plumed/plumedforceprovider.h
+++ b/src/gromacs/applied_forces/plumed/plumedforceprovider.h
@@ -86,6 +86,7 @@
private:
std::unique_ptr<PLMD::Plumed> plumed_;
int plumedAPIversion_;
+ bool replex_;
std::optional<ArrayRef<const int>> globalAtomIndices_;
};
--- a/src/gromacs/applied_forces/plumed/plumedMDModule.cpp
+++ b/src/gromacs/applied_forces/plumed/plumedMDModule.cpp
@@ -88,7 +88,14 @@
// Access the plumed filename this is used to activate the plumed module
notifier->simulationSetupNotifier_.subscribe(
[this](const PlumedInputFilename& plumedFilename)
- { this->options_.setPlumedFile(plumedFilename.plumedFilename_); });
+ {
+ this->options_.setPlumedFile(plumedFilename.plumedFilename_);
+ this->options_.setReplex(plumedFilename.replex_);
+ });
+ // Retrieve the multi-simulation object, needed to set up PLUMED's
+ // multi-replica communicator
+ notifier->simulationSetupNotifier_.subscribe([this](const gmx_multisim_t* ms)
+ { this->options_.setMultisim(ms); });
// Access the temperature if it is constant during the simulation
notifier->simulationSetupNotifier_.subscribe(
[this](const EnsembleTemperature& ensembleT)
--- a/src/gromacs/applied_forces/plumed/plumedOptions.cpp
+++ b/src/gromacs/applied_forces/plumed/plumedOptions.cpp
@@ -91,6 +91,16 @@
opts_.mpiComm_ = &mpiComm;
}
+void PlumedOptionProvider::setReplex(bool replex)
+{
+ opts_.replex_ = replex;
+}
+
+void PlumedOptionProvider::setMultisim(const gmx_multisim_t* ms)
+{
+ opts_.ms_ = ms;
+}
+
bool PlumedOptionProvider::active() const
{
return opts_.active_;
--- a/src/gromacs/applied_forces/plumed/plumedOptions.h
+++ b/src/gromacs/applied_forces/plumed/plumedOptions.h
@@ -49,6 +49,7 @@
struct gmx_domdec_t;
struct gmx_mtop_t;
+struct gmx_multisim_t;
namespace gmx
{
@@ -58,13 +59,15 @@
struct PlumedOptions
{
- std::string plumedFile_;
- int natoms_;
- const MpiComm* mpiComm_;
- real simulationTimeStep_;
- std::optional<real> ensembleTemperature_{};
- StartingBehavior startingBehavior_{};
- bool active_{ false };
+ std::string plumedFile_;
+ int natoms_;
+ const MpiComm* mpiComm_;
+ const gmx_multisim_t* ms_{ nullptr };
+ real simulationTimeStep_;
+ std::optional<real> ensembleTemperature_{};
+ StartingBehavior startingBehavior_{};
+ bool active_{ false };
+ bool replex_{ false };
};
class PlumedOptionProvider
@@ -87,6 +90,14 @@
* @param fname the (optional) name of the file
*/
void setPlumedFile(const std::optional<std::string>& fname);
+ /*! @brief Sets the replica-exchange flag
+ * @param replex true when mdrun was started with -replex
+ */
+ void setReplex(bool replex);
+ /*! @brief Sets the address of the multi-simulation object
+ * @param ms the address of the multi-simulation object (may be nullptr)
+ */
+ void setMultisim(const gmx_multisim_t* ms);
/*! @brief Sets the timestep
* @param timeStep the timestep value
*/
--- a/src/gromacs/mdrun/runner.cpp
+++ b/src/gromacs/mdrun/runner.cpp
@@ -1134,6 +1134,7 @@
gmx::PlumedInputFilename plumedFilename;
if (opt2bSet(plumedOptionName, filenames.size(), filenames.data()))
{
+ plumedFilename.replex_ = replExParams.exchangeInterval > 0;
plumedFilename.plumedFilename_ =
std::string(opt2fn(plumedOptionName, filenames.size(), filenames.data()));
}
--- a/src/gromacs/mdrunutility/mdmodulesnotifiers.h
+++ b/src/gromacs/mdrunutility/mdmodulesnotifiers.h
@@ -306,6 +306,8 @@
{
//! The name of plumed input file, empty by default
std::optional<std::string> plumedFilename_{};
+ //! Whether replica exchange is active (-replex), needed by PLUMED's GREX setup
+ bool replex_{};
};
/*! \libinternal \brief Provides the constant ensemble temperature |
|
@carlocamilloni the PR is ready now for your full review. |
Description
There is no patch for GROMACS 2026, so
plumed patch -eoffers nothing newer thangromacs-2025.0and there is no supported way to build a PLUMED-enabled GROMACS 2026.
Applying the 2025.0 patch to 2026.4 does not work:
Six of the remaining files apply only with fuzz 5, i.e. by luck rather than by matching.
The cause is
db61f87e0a("Use MpiComm in t_commrec"), which replaced thet_commrecplumbing withthe
MpiCommabstraction, soPlumedOptionsnow carriesconst MpiComm* mpiComm_where it used tocarry
const t_commrec* cr_. The failing hunk is the one that hands PLUMED the multi-replicacommunicators, and losing it is worse than a build error:
GREX setMPIIntercommis what populatesPlumedMain::multi_sim_comm(GREX.cpp), so without itmulti_sim_commstaysMPI_COMM_SELFandevery multi-replica action silently degrades to a single replica — no error, just wrong science.
The
CMakeLists.txtrejects are the opposite problem: GROMACS 2026 adopted the earlygmx_manage_plumed()call natively, so those hunks must simply be dropped.This adds
patches/gromacs-2026.0.diff, the 2025.0 patch carried onto the 2026 API:MpiComm::isMainRank()andMpiComm::comm()src/gromacs/CMakeLists.txtis dropped entirely (already upstream in 2026)setNumOMPthreadsis carried over from Restore setNumOMPthreads in the GROMACS 2025 patch #1435replex_plumbing, thePLUMED_LOG_FILEoverride and forcingGMX_USE_PLUMED=ONareunchanged in substance
It also fixes a bug in GROMACS's own PLUMED module
The same commit rewrote
dropping the address-of.
MpiComm::comm()returnsMPI_Commby value, but PLUMED reads thatargument as
*(const MPI_Comm*)(Communicator::Set_comm). WhereMPI_Commis a pointer type — asin Open MPI — this compiles and silently hands PLUMED the wrong handle, so a domain-decomposed run
looks fine and is not. The patch passes the address of a named local instead. I will report this to
GROMACS separately; until it is fixed there, the patch is the only thing protecting 2026 users.
Verification
On GROMACS 2026.4 with clang 20.1.1, Open MPI 5.0.7 and libc++:
plumed patch -p -e gromacs-2026.0applies with no fuzz and no rejects, andplumed patch -rrestores the tree byte-for-byte.
The patched GROMACS builds clean with
GMX_MPI=ON -DGMX_THREAD_MPI=OFF -DGMX_OPENMP=ON; the fourchanged translation units compile with no errors and no warnings.
Multi-replica actually works. A two-replica
mdrun -multidir rep0 rep1with@replicas:is expanded usingmulti_sim_comm.Get_rank(), so it is a direct probe of whether thecommunicator was set up. Patched, the two replicas parse different values, and the log reports
GROMACS-like replica exchange is on:As a control I built the same GROMACS 2026.4 commit unpatched (
GMX_USE_PLUMED=ON, nativemodule) and ran the identical input. Both replicas take the rank-0 value and the
GREXline isabsent:
Both runs exit 0. That is the failure mode worth emphasising: unpatched, every replica believes it
is replica 0, and nothing in the output says so.
Target release
I would like my code to appear in release 2.10.
This is filed against
v2.10so that it forward-merges v2.10 -> master in the usual way. GROMACS 2026is already released, and users on PLUMED 2.10 have no way to patch it today, so the support is more
useful on the release branch than held back for the next feature release.
patches/gromacs-2025.0.diffalready lives on
v2.10, so this sits beside it rather than being split across branches. It adds a newpatches/directory and touches nothing existing, so the forward merge is conflict-free.Scope for 2027: the plumed module sources on GROMACS
mainare currently identical torelease-2026, and therunner.cppandmdmodulesnotifiers.hedits apply there unchanged; only thestored
.preplumedsnapshots would need regenerating once 2027 branches. Happy to add that patch toowhen it makes sense.
Type of contribution
Copyright
author of the code I am modifying.
Tests
No regtest is added, for the same reason the existing
patches/tree carries none: nothing underpatches/is compiled by PLUMED, so there is no way for the test suite to exercise it. It is consumedonly when a user runs
plumed patchagainst a GROMACS source tree, at which point GROMACS — notPLUMED — compiles it. The verification above is the substitute, and it is a real GROMACS build and a
real two-replica MD run rather than a source-level check.