2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2015,2016,2017,2018,2019,2020,2021, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements the Awh class.
40 * \author Viveca Lindahl
41 * \author Berk Hess <hess@kth.se>
42 * \author Magnus Lundborg
58 #include "gromacs/fileio/enxio.h"
59 #include "gromacs/gmxlib/network.h"
60 #include "gromacs/math/units.h"
61 #include "gromacs/mdrunutility/multisim.h"
62 #include "gromacs/mdtypes/awh_history.h"
63 #include "gromacs/mdtypes/awh_params.h"
64 #include "gromacs/mdtypes/commrec.h"
65 #include "gromacs/mdtypes/forceoutput.h"
66 #include "gromacs/mdtypes/inputrec.h"
67 #include "gromacs/mdtypes/pull_params.h"
68 #include "gromacs/mdtypes/state.h"
69 #include "gromacs/pbcutil/pbc.h"
70 #include "gromacs/pulling/pull.h"
71 #include "gromacs/timing/wallcycle.h"
72 #include "gromacs/trajectory/energyframe.h"
73 #include "gromacs/utility/arrayref.h"
74 #include "gromacs/utility/exceptions.h"
75 #include "gromacs/utility/gmxassert.h"
76 #include "gromacs/utility/pleasecite.h"
79 #include "biassharing.h"
80 #include "correlationgrid.h"
81 #include "pointstate.h"
87 * \brief A bias and its coupling to the system.
89 * This struct is used to separate the bias machinery in the Bias class,
90 * which should be independent from the reaction coordinate, from the
91 * obtaining of the reaction coordinate values and passing the computed forces.
92 * Currently the AWH method couples to the system by mapping each
93 * AWH bias to a pull coordinate. This can easily be generalized here.
95 struct BiasCoupledToSystem
97 /*! \brief Constructor, couple a bias to a set of pull coordinates.
99 * \param[in] bias The bias.
100 * \param[in] pullCoordIndex The pull coordinate indices.
102 BiasCoupledToSystem(Bias bias, const std::vector<int>& pullCoordIndex);
104 Bias bias_; /**< The bias. */
105 const std::vector<int> pullCoordIndex_; /**< The pull coordinates this bias acts on. */
107 /* Here AWH can be extended to work on other coordinates than pull. */
110 /*! \brief Checks whether any dimension uses the given coordinate provider type.
112 * \param[in] awhBiasParams The bias params to check.
113 * \param[in] awhCoordProvider The type of coordinate provider
114 * \returns true if any dimension of the bias is linked to the given provider
116 static bool anyDimUsesProvider(const AwhBiasParams& awhBiasParams, const int awhCoordProvider)
118 for (int d = 0; d < awhBiasParams.ndim; d++)
120 const AwhDimParams& awhDimParams = awhBiasParams.dimParams[d];
121 if (awhDimParams.eCoordProvider == awhCoordProvider)
129 /*! \brief Checks whether any dimension uses the given coordinate provider type.
131 * \param[in] awhParams The AWH params to check.
132 * \param[in] awhCoordProvider The type of coordinate provider
133 * \returns true if any dimension of awh is linked to the given provider type.
135 static bool anyDimUsesProvider(const AwhParams& awhParams, const int awhCoordProvider)
137 for (int k = 0; k < awhParams.numBias; k++)
139 const AwhBiasParams& awhBiasParams = awhParams.awhBiasParams[k];
140 if (anyDimUsesProvider(awhBiasParams, awhCoordProvider))
148 /*! \brief Checks whether any dimension uses pulling as a coordinate provider.
150 * \param[in] biasCoupledToSystem The AWH biases to check.
151 * \returns true if any dimension of the provided biases is linked to pulling.
153 static bool anyDimUsesPull(const ArrayRef<BiasCoupledToSystem> biasCoupledToSystem)
155 for (const auto& biasCts : biasCoupledToSystem)
157 if (!biasCts.pullCoordIndex_.empty())
165 BiasCoupledToSystem::BiasCoupledToSystem(Bias bias, const std::vector<int>& pullCoordIndex) :
166 bias_(std::move(bias)),
167 pullCoordIndex_(pullCoordIndex)
169 /* We already checked for this in grompp, but check again here. */
171 static_cast<size_t>(bias_.ndim()) == pullCoordIndex_.size() + bias_.hasFepLambdaDimension() ? 1 : 0,
172 "The bias dimensionality should match the number of pull and lambda coordinates.");
175 Awh::Awh(FILE* fplog,
176 const t_inputrec& inputRecord,
177 const t_commrec* commRecord,
178 const gmx_multisim_t* multiSimRecord,
179 const AwhParams& awhParams,
180 const std::string& biasInitFilename,
182 int numFepLambdaStates,
183 int fepLambdaState) :
184 seed_(awhParams.seed),
185 nstout_(awhParams.nstOut),
186 commRecord_(commRecord),
187 multiSimRecord_(multiSimRecord),
190 numFepLambdaStates_(numFepLambdaStates),
191 fepLambdaState_(fepLambdaState)
193 if (anyDimUsesProvider(awhParams, eawhcoordproviderPULL))
195 GMX_RELEASE_ASSERT(inputRecord.pull != nullptr, "With AWH we should have pull parameters");
196 GMX_RELEASE_ASSERT(pull_work != nullptr,
197 "With AWH pull should be initialized before initializing AWH");
200 if (fplog != nullptr)
202 please_cite(fplog, "Lindahl2014");
204 if (anyDimUsesProvider(awhParams, eawhcoordproviderFREE_ENERGY_LAMBDA))
206 please_cite(fplog, "Lundborg2021");
210 if (haveBiasSharingWithinSimulation(awhParams))
212 /* This has likely been checked by grompp, but throw anyhow. */
214 InvalidInputError("Biases within a simulation are shared, currently sharing of "
215 "biases is only supported between simulations"));
218 int numSharingSimulations = 1;
219 if (awhParams.shareBiasMultisim && isMultiSim(multiSimRecord_))
221 numSharingSimulations = multiSimRecord_->numSimulations_;
224 /* Initialize all the biases */
225 const double beta = 1 / (BOLTZ * inputRecord.opts.ref_t[0]);
226 for (int k = 0; k < awhParams.numBias; k++)
228 const AwhBiasParams& awhBiasParams = awhParams.awhBiasParams[k];
230 std::vector<int> pullCoordIndex;
231 std::vector<DimParams> dimParams;
232 for (int d = 0; d < awhBiasParams.ndim; d++)
234 const AwhDimParams& awhDimParams = awhBiasParams.dimParams[d];
235 if (awhDimParams.eCoordProvider != eawhcoordproviderPULL
236 && awhDimParams.eCoordProvider != eawhcoordproviderFREE_ENERGY_LAMBDA)
239 InvalidInputError("Currently only the pull code and lambda are supported "
240 "as coordinate providers"));
242 if (awhDimParams.eCoordProvider == eawhcoordproviderPULL)
244 const t_pull_coord& pullCoord = inputRecord.pull->coord[awhDimParams.coordIndex];
245 if (pullCoord.eGeom == epullgDIRPBC)
247 GMX_THROW(InvalidInputError(
248 "Pull geometry 'direction-periodic' is not supported by AWH"));
250 double conversionFactor = pull_coordinate_is_angletype(&pullCoord) ? DEG2RAD : 1;
251 pullCoordIndex.push_back(awhDimParams.coordIndex);
252 dimParams.push_back(DimParams::pullDimParams(conversionFactor,
253 awhDimParams.forceConstant, beta));
257 dimParams.push_back(DimParams::fepLambdaDimParams(numFepLambdaStates_, beta));
261 /* Construct the bias and couple it to the system. */
262 Bias::ThisRankWillDoIO thisRankWillDoIO =
263 (MASTER(commRecord_) ? Bias::ThisRankWillDoIO::Yes : Bias::ThisRankWillDoIO::No);
264 biasCoupledToSystem_.emplace_back(
265 Bias(k, awhParams, awhParams.awhBiasParams[k], dimParams, beta, inputRecord.delta_t,
266 numSharingSimulations, biasInitFilename, thisRankWillDoIO),
269 biasCoupledToSystem_.back().bias_.printInitializationToLog(fplog);
272 /* Need to register the AWH coordinates to be allowed to apply forces to the pull coordinates. */
273 registerAwhWithPull(awhParams, pull_);
275 if (numSharingSimulations > 1 && MASTER(commRecord_))
277 std::vector<size_t> pointSize;
278 for (auto const& biasCts : biasCoupledToSystem_)
280 pointSize.push_back(biasCts.bias_.state().points().size());
282 /* Ensure that the shared biased are compatible between simulations */
283 biasesAreCompatibleForSharingBetweenSimulations(awhParams, pointSize, multiSimRecord_);
287 Awh::~Awh() = default;
289 bool Awh::isOutputStep(int64_t step) const
291 return (nstout_ > 0 && step % nstout_ == 0);
294 real Awh::applyBiasForcesAndUpdateBias(PbcType pbcType,
296 ArrayRef<const double> neighborLambdaEnergies,
297 ArrayRef<const double> neighborLambdaDhdl,
299 gmx::ForceWithVirial* forceWithVirial,
302 gmx_wallcycle* wallcycle,
305 if (anyDimUsesPull(biasCoupledToSystem_))
307 GMX_ASSERT(forceWithVirial, "Need a valid ForceWithVirial object");
310 wallcycle_start(wallcycle, ewcAWH);
313 set_pbc(&pbc, pbcType, box);
315 /* During the AWH update the potential can instantaneously jump due to either
316 an bias update or moving the umbrella. The jumps are kept track of and
317 subtracted from the potential in order to get a useful conserved energy quantity. */
318 double awhPotential = potentialOffset_;
320 for (auto& biasCts : biasCoupledToSystem_)
322 /* Update the AWH coordinate values with those of the corresponding
325 awh_dvec coordValue = { 0, 0, 0, 0 };
326 int numLambdaDimsCounted = 0;
327 for (int d = 0; d < biasCts.bias_.ndim(); d++)
329 if (biasCts.bias_.dimParams()[d].isPullDimension())
331 coordValue[d] = get_pull_coord_value(
332 pull_, biasCts.pullCoordIndex_[d - numLambdaDimsCounted], &pbc);
336 coordValue[d] = fepLambdaState_;
337 numLambdaDimsCounted += 1;
341 /* Perform an AWH biasing step: this means, at regular intervals,
342 * sampling observables based on the input pull coordinate value,
343 * setting the bias force and/or updating the AWH bias state.
345 double biasPotential;
346 double biasPotentialJump;
347 /* Note: In the near future this call will be split in calls
348 * to supports bias sharing within a single simulation.
350 gmx::ArrayRef<const double> biasForce = biasCts.bias_.calcForceAndUpdateBias(
351 coordValue, neighborLambdaEnergies, neighborLambdaDhdl, &biasPotential,
352 &biasPotentialJump, commRecord_, multiSimRecord_, t, step, seed_, fplog);
354 awhPotential += biasPotential;
356 /* Keep track of the total potential shift needed to remove the potential jumps. */
357 potentialOffset_ -= biasPotentialJump;
359 /* Communicate the bias force to the pull struct.
360 * The bias potential is returned at the end of this function,
361 * so that it can be added externally to the correct energy data block.
363 numLambdaDimsCounted = 0;
364 for (int d = 0; d < biasCts.bias_.ndim(); d++)
366 if (biasCts.bias_.dimParams()[d].isPullDimension())
368 apply_external_pull_coord_force(pull_, biasCts.pullCoordIndex_[d - numLambdaDimsCounted],
369 biasForce[d], masses, forceWithVirial);
373 int umbrellaGridpointIndex = biasCts.bias_.state().coordState().umbrellaGridpoint();
374 fepLambdaState_ = biasCts.bias_.getGridCoordValue(umbrellaGridpointIndex)[d];
375 numLambdaDimsCounted += 1;
379 if (isOutputStep(step))
381 /* We might have skipped updates for part of the grid points.
382 * Ensure all points are updated before writing out their data.
384 biasCts.bias_.doSkippedUpdatesForAllPoints();
388 wallcycle_stop(wallcycle, ewcAWH);
390 return MASTER(commRecord_) ? static_cast<real>(awhPotential) : 0;
393 std::shared_ptr<AwhHistory> Awh::initHistoryFromState() const
395 if (MASTER(commRecord_))
397 std::shared_ptr<AwhHistory> awhHistory(new AwhHistory);
398 awhHistory->bias.clear();
399 awhHistory->bias.resize(biasCoupledToSystem_.size());
401 for (size_t k = 0; k < awhHistory->bias.size(); k++)
403 biasCoupledToSystem_[k].bias_.initHistoryFromState(&awhHistory->bias[k]);
410 /* Return an empty pointer */
411 return std::shared_ptr<AwhHistory>();
415 void Awh::restoreStateFromHistory(const AwhHistory* awhHistory)
417 /* Restore the history to the current state */
418 if (MASTER(commRecord_))
420 GMX_RELEASE_ASSERT(awhHistory != nullptr,
421 "The master rank should have a valid awhHistory when restoring the "
422 "state from history.");
424 if (awhHistory->bias.size() != biasCoupledToSystem_.size())
426 GMX_THROW(InvalidInputError(
427 "AWH state and history contain different numbers of biases. Likely you "
428 "provided a checkpoint from a different simulation."));
431 potentialOffset_ = awhHistory->potentialOffset;
433 if (PAR(commRecord_))
435 gmx_bcast(sizeof(potentialOffset_), &potentialOffset_, commRecord_->mpi_comm_mygroup);
438 for (size_t k = 0; k < biasCoupledToSystem_.size(); k++)
440 biasCoupledToSystem_[k].bias_.restoreStateFromHistory(
441 awhHistory ? &awhHistory->bias[k] : nullptr, commRecord_);
445 void Awh::updateHistory(AwhHistory* awhHistory) const
447 if (!MASTER(commRecord_))
452 /* This assert will also catch a non-master rank calling this function. */
453 GMX_RELEASE_ASSERT(awhHistory->bias.size() == biasCoupledToSystem_.size(),
454 "AWH state and history bias count should match");
456 awhHistory->potentialOffset = potentialOffset_;
458 for (size_t k = 0; k < awhHistory->bias.size(); k++)
460 biasCoupledToSystem_[k].bias_.updateHistory(&awhHistory->bias[k]);
464 const char* Awh::externalPotentialString()
469 void Awh::registerAwhWithPull(const AwhParams& awhParams, pull_t* pull_work)
471 GMX_RELEASE_ASSERT(!anyDimUsesProvider(awhParams, eawhcoordproviderPULL) || pull_work,
472 "Need a valid pull object");
474 for (int k = 0; k < awhParams.numBias; k++)
476 const AwhBiasParams& biasParams = awhParams.awhBiasParams[k];
478 for (int d = 0; d < biasParams.ndim; d++)
480 if (biasParams.dimParams[d].eCoordProvider == eawhcoordproviderPULL)
482 register_external_pull_potential(pull_work, biasParams.dimParams[d].coordIndex,
483 Awh::externalPotentialString());
489 /* Fill the AWH data block of an energy frame with data (if there is any). */
490 void Awh::writeToEnergyFrame(int64_t step, t_enxframe* frame) const
492 GMX_ASSERT(MASTER(commRecord_), "writeToEnergyFrame should only be called on the master rank");
493 GMX_ASSERT(frame != nullptr, "Need a valid energy frame");
495 if (!isOutputStep(step))
497 /* This is not an AWH output step, don't write any AWH data */
501 /* Get the total number of energy subblocks that AWH needs */
502 int numSubblocks = 0;
503 for (auto& biasCoupledToSystem : biasCoupledToSystem_)
505 numSubblocks += biasCoupledToSystem.bias_.numEnergySubblocksToWrite();
507 GMX_ASSERT(numSubblocks > 0, "We should always have data to write");
509 /* Add 1 energy block */
510 add_blocks_enxframe(frame, frame->nblock + 1);
512 /* Take the block that was just added and set the number of subblocks. */
513 t_enxblock* awhEnergyBlock = &(frame->block[frame->nblock - 1]);
514 add_subblocks_enxblock(awhEnergyBlock, numSubblocks);
516 /* Claim it as an AWH block. */
517 awhEnergyBlock->id = enxAWH;
519 /* Transfer AWH data blocks to energy sub blocks */
520 int energySubblockCount = 0;
521 for (auto& biasCoupledToSystem : biasCoupledToSystem_)
523 energySubblockCount += biasCoupledToSystem.bias_.writeToEnergySubblocks(
524 &(awhEnergyBlock->sub[energySubblockCount]));
528 bool Awh::hasFepLambdaDimension() const
531 std::begin(biasCoupledToSystem_), std::end(biasCoupledToSystem_),
532 [](const auto& coupledBias) { return coupledBias.bias_.hasFepLambdaDimension(); });
535 bool Awh::needForeignEnergyDifferences(const int64_t step) const
537 /* If there is no FEP lambda dimension at all in any bias there will be no need for foreign
538 * energy differences */
539 if (!hasFepLambdaDimension())
547 /* Check whether the bias(es) that has/have a FEP lambda dimension should sample coordinates
548 * this step. Since the biases may have different sampleCoordStep it is necessary to check
549 * this combination. */
550 for (const auto& biasCts : biasCoupledToSystem_)
552 if (biasCts.bias_.hasFepLambdaDimension() && biasCts.bias_.isSampleCoordStep(step))
560 std::unique_ptr<Awh> prepareAwhModule(FILE* fplog,
561 const t_inputrec& inputRecord,
562 t_state* stateGlobal,
563 const t_commrec* commRecord,
564 const gmx_multisim_t* multiSimRecord,
565 const bool startingFromCheckpoint,
566 const bool usingShellParticles,
567 const std::string& biasInitFilename,
570 if (!inputRecord.bDoAwh)
574 if (usingShellParticles)
576 GMX_THROW(InvalidInputError("AWH biasing does not support shell particles."));
579 auto awh = std::make_unique<Awh>(
580 fplog, inputRecord, commRecord, multiSimRecord, *inputRecord.awhParams, biasInitFilename,
581 pull_work, inputRecord.fepvals->n_lambda, inputRecord.fepvals->init_fep_state);
583 if (startingFromCheckpoint)
585 // Restore the AWH history read from checkpoint
586 awh->restoreStateFromHistory(MASTER(commRecord) ? stateGlobal->awhHistory.get() : nullptr);
588 else if (MASTER(commRecord))
590 // Initialize the AWH history here
591 stateGlobal->awhHistory = awh->initHistoryFromState();