Introduce Andersen temperature coupling for modular simulator
authorPascal Merz <pascal.merz@me.com>
Mon, 31 May 2021 00:10:49 +0000 (18:10 -0600)
committerMark Abraham <mark.j.abraham@gmail.com>
Mon, 31 May 2021 06:33:26 +0000 (06:33 +0000)
This introduces the `andersen` and `andersen-massive` temperature coupling
for modular simulator. Since reshuffeling the velocities does require a
constraining step, the Andersen temperature coupling uses the composite
element to combine the Andersen element with a constraining element.

Refs #3423, refs #3417

src/gromacs/modularsimulator/andersentemperaturecoupling.cpp [new file with mode: 0644]
src/gromacs/modularsimulator/andersentemperaturecoupling.h [new file with mode: 0644]
src/gromacs/modularsimulator/modularsimulator.cpp
src/programs/mdrun/tests/simulator.cpp

diff --git a/src/gromacs/modularsimulator/andersentemperaturecoupling.cpp b/src/gromacs/modularsimulator/andersentemperaturecoupling.cpp
new file mode 100644 (file)
index 0000000..eee933b
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2021, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief Defines Andersen temperature coupling for the modular simulator
+ *
+ * \author Pascal Merz <pascal.merz@me.com>
+ * \ingroup module_modularsimulator
+ */
+
+#include "andersentemperaturecoupling.h"
+
+#include <vector>
+
+#include "gromacs/domdec/domdec_struct.h"
+#include "gromacs/math/functions.h"
+#include "gromacs/math/units.h"
+#include "gromacs/mdlib/constr.h"
+#include "gromacs/mdlib/mdatoms.h"
+#include "gromacs/mdlib/stat.h"
+#include "gromacs/mdtypes/commrec.h"
+#include "gromacs/mdtypes/inputrec.h"
+#include "gromacs/mdtypes/mdatom.h"
+#include "gromacs/mdrun/isimulator.h"
+#include "gromacs/random/tabulatednormaldistribution.h"
+#include "gromacs/random/threefry.h"
+#include "gromacs/random/uniformrealdistribution.h"
+
+#include "compositesimulatorelement.h"
+#include "constraintelement.h"
+#include "simulatoralgorithm.h"
+#include "statepropagatordata.h"
+
+namespace gmx
+{
+AndersenTemperatureCoupling::AndersenTemperatureCoupling(double               simulationTimestep,
+                                                         bool                 doMassive,
+                                                         int64_t              seed,
+                                                         ArrayRef<const real> referenceTemperature,
+                                                         ArrayRef<const real> couplingTime,
+                                                         StatePropagatorData* statePropagatorData,
+                                                         const MDAtoms*       mdAtoms,
+                                                         const t_commrec*     cr) :
+    doMassive_(doMassive),
+    randomizationRate_(simulationTimestep / couplingTime[0]),
+    couplingFrequency_(doMassive ? roundToInt(1. / randomizationRate_) : 1),
+    seed_(seed),
+    referenceTemperature_(referenceTemperature),
+    couplingTime_(couplingTime),
+    statePropagatorData_(statePropagatorData),
+    mdAtoms_(mdAtoms->mdatoms()),
+    cr_(cr)
+{
+}
+
+void AndersenTemperatureCoupling::scheduleTask(Step                       step,
+                                               Time gmx_unused            time,
+                                               const RegisterRunFunction& registerRunFunction)
+{
+    if (do_per_step(step, couplingFrequency_))
+    {
+        registerRunFunction([this, step]() { apply(step); });
+    }
+}
+
+void AndersenTemperatureCoupling::apply(Step step)
+{
+    ThreeFry2x64<0>                       rng(seed_, RandomDomain::Thermostat);
+    UniformRealDistribution<real>         uniformDist;
+    TabulatedNormalDistribution<real, 14> normalDist;
+
+    const bool doDomainDecomposition = DOMAINDECOMP(cr_);
+
+    auto velocities = statePropagatorData_->velocitiesView().unpaddedArrayRef();
+
+    for (int atomIdx = 0; atomIdx < mdAtoms_->homenr; ++atomIdx)
+    {
+        const int temperatureGroup = mdAtoms_->cTC ? mdAtoms_->cTC[atomIdx] : 0;
+        if (referenceTemperature_[temperatureGroup] <= 0 || couplingTime_[temperatureGroup] <= 0)
+        {
+            continue;
+        }
+
+        const int globalAtomIdx = doDomainDecomposition ? cr_->dd->globalAtomIndices[atomIdx] : atomIdx;
+        rng.restart(step, globalAtomIdx);
+
+        // For massive Andersen, this function is only called periodically, but we apply each time
+        // Otherwise, this function is called every step, but we randomize atoms probabilistically
+        if (!doMassive_)
+        {
+            uniformDist.reset();
+        }
+        if (doMassive_ || (uniformDist(rng) < randomizationRate_))
+        {
+            const real scalingFactor = std::sqrt(c_boltz * referenceTemperature_[temperatureGroup]
+                                                 * mdAtoms_->invmass[atomIdx]);
+            normalDist.reset();
+            for (int d = 0; d < DIM; d++)
+            {
+                velocities[atomIdx][d] = scalingFactor * normalDist(rng);
+            }
+        }
+    }
+}
+
+int AndersenTemperatureCoupling::frequency() const
+{
+    return couplingFrequency_;
+}
+
+void               AndersenTemperatureCoupling::elementSetup() {}
+ISimulatorElement* AndersenTemperatureCoupling::getElementPointerImpl(
+        LegacySimulatorData*                    legacySimulatorData,
+        ModularSimulatorAlgorithmBuilderHelper* builderHelper,
+        StatePropagatorData*                    statePropagatorData,
+        EnergyData*                             energyData,
+        FreeEnergyPerturbationData*             freeEnergyPerturbationData,
+        GlobalCommunicationHelper gmx_unused* globalCommunicationHelper)
+{
+    GMX_RELEASE_ASSERT(legacySimulatorData->inputrec->etc == TemperatureCoupling::Andersen
+                               || legacySimulatorData->inputrec->etc == TemperatureCoupling::AndersenMassive,
+                       "Expected the thermostat type to be andersen or andersen-massive.");
+    auto andersenThermostat = std::make_unique<AndersenTemperatureCoupling>(
+            legacySimulatorData->inputrec->delta_t,
+            legacySimulatorData->inputrec->etc == TemperatureCoupling::AndersenMassive,
+            legacySimulatorData->inputrec->andersen_seed,
+            constArrayRefFromArray(legacySimulatorData->inputrec->opts.ref_t,
+                                   legacySimulatorData->inputrec->opts.ngtc),
+            constArrayRefFromArray(legacySimulatorData->inputrec->opts.tau_t,
+                                   legacySimulatorData->inputrec->opts.ngtc),
+            statePropagatorData,
+            legacySimulatorData->mdAtoms,
+            legacySimulatorData->cr);
+
+    // T-coupling frequency will be composite element frequency
+    const auto frequency = andersenThermostat->frequency();
+    // Set up call list for composite element
+    std::vector<compat::not_null<ISimulatorElement*>> elementCallList = { compat::make_not_null(
+            andersenThermostat.get()) };
+    // Set up element list for composite element
+    std::vector<std::unique_ptr<gmx::ISimulatorElement>> elements;
+    elements.emplace_back(std::move(andersenThermostat));
+
+    // If there are constraints, add constraint element after Andersen element
+    if (legacySimulatorData->constr)
+    {
+        // This is excluded in preprocessing -
+        // asserted here to make sure things don't get out of sync
+        GMX_RELEASE_ASSERT(
+                legacySimulatorData->inputrec->etc == TemperatureCoupling::AndersenMassive,
+                "Per-particle Andersen thermostat is not implemented for systems with constrains.");
+        // Build constraint element
+        auto constraintElement = std::make_unique<ConstraintsElement<ConstraintVariable::Velocities>>(
+                legacySimulatorData->constr,
+                statePropagatorData,
+                energyData,
+                freeEnergyPerturbationData,
+                MASTER(legacySimulatorData->cr),
+                legacySimulatorData->fplog,
+                legacySimulatorData->inputrec,
+                legacySimulatorData->mdAtoms->mdatoms());
+        // Add call to composite element call list
+        elementCallList.emplace_back(compat::make_not_null(constraintElement.get()));
+        // Move ownership of constraint element to composite element
+        elements.emplace_back(std::move(constraintElement));
+    }
+
+    // Store composite element in builder helper and return pointer
+    return builderHelper->storeElement(std::make_unique<CompositeSimulatorElement>(
+            std::move(elementCallList), std::move(elements), frequency));
+}
+
+} // namespace gmx
diff --git a/src/gromacs/modularsimulator/andersentemperaturecoupling.h b/src/gromacs/modularsimulator/andersentemperaturecoupling.h
new file mode 100644 (file)
index 0000000..acf912d
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 2021, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief Declares Andersen temperature coupling for the modular simulator
+ *
+ * \author Pascal Merz <pascal.merz@me.com>
+ * \ingroup module_modularsimulator
+ */
+
+#ifndef GMX_MODULARSIMULATOR_ANDERSENTHERMOSTAT_H
+#define GMX_MODULARSIMULATOR_ANDERSENTHERMOSTAT_H
+
+#include "gromacs/utility/arrayref.h"
+
+#include "energydata.h"
+#include "modularsimulatorinterfaces.h"
+#include "propagator.h"
+
+struct t_commrec;
+struct t_mdatoms;
+
+namespace gmx
+{
+
+/*! \internal
+ * \ingroup module_modularsimulator
+ * \brief Element implementing the Andersen thermostat
+ *
+ */
+class AndersenTemperatureCoupling final : public ISimulatorElement
+{
+public:
+    //! Constructor
+    AndersenTemperatureCoupling(double               simulationTimestep,
+                                bool                 doMassive,
+                                int64_t              seed,
+                                ArrayRef<const real> referenceTemperature,
+                                ArrayRef<const real> couplingTime,
+                                StatePropagatorData* statePropagatorData,
+                                const MDAtoms*       mdAtoms,
+                                const t_commrec*     cr);
+
+    /*! \brief Register run function for step / time
+     *
+     * \param step                 The step number
+     * \param time                 The time
+     * \param registerRunFunction  Function allowing to register a run function
+     */
+    void scheduleTask(Step step, Time time, const RegisterRunFunction& registerRunFunction) override;
+
+    //! Sanity check at setup time
+    void elementSetup() override;
+    //! No element teardown needed
+    void elementTeardown() override {}
+
+    /*! \brief Factory method implementation
+     *
+     * \param legacySimulatorData  Pointer allowing access to simulator level data
+     * \param builderHelper  ModularSimulatorAlgorithmBuilder helper object
+     * \param statePropagatorData  Pointer to the \c StatePropagatorData object
+     * \param energyData  Pointer to the \c EnergyData object
+     * \param freeEnergyPerturbationData  Pointer to the \c FreeEnergyPerturbationData object
+     * \param globalCommunicationHelper  Pointer to the \c GlobalCommunicationHelper object
+     *
+     * \return  Pointer to the element to be added. Element needs to have been stored using \c storeElement
+     */
+    static ISimulatorElement* getElementPointerImpl(LegacySimulatorData* legacySimulatorData,
+                                                    ModularSimulatorAlgorithmBuilderHelper* builderHelper,
+                                                    StatePropagatorData*        statePropagatorData,
+                                                    EnergyData*                 energyData,
+                                                    FreeEnergyPerturbationData* freeEnergyPerturbationData,
+                                                    GlobalCommunicationHelper* globalCommunicationHelper);
+
+    //! Returns the frequency at which temperature coupling is performed
+    [[nodiscard]] int frequency() const;
+
+private:
+    //! Whether we're doing massive Andersen thermostatting
+    const bool doMassive_;
+    //! The rate
+    const real randomizationRate_;
+    //! The frequency at which the thermostat is applied
+    const int couplingFrequency_;
+    //! The random seed
+    const int64_t seed_;
+    //! Coupling temperature per group
+    ArrayRef<const real> referenceTemperature_;
+    //! Coupling time per group
+    ArrayRef<const real> couplingTime_;
+
+    // TODO: Clarify relationship to data objects and find a more robust alternative to raw pointers (#3583)
+    //! Pointer to the micro state
+    StatePropagatorData* statePropagatorData_;
+    //! Atom parameters for this domain.
+    const t_mdatoms* mdAtoms_;
+    //! Handles communication.
+    const t_commrec* cr_;
+
+    //! Apply the thermostat at step
+    void apply(Step step);
+};
+
+} // namespace gmx
+
+#endif // GMX_MODULARSIMULATOR_ANDERSENTHERMOSTAT_H
index f1fdd17d8a3e941323f0789c77fb6eb7caf89f21..b1834399783dba83749e327787f671db2bf28a47 100644 (file)
@@ -76,6 +76,7 @@
 #include "gromacs/utility/fatalerror.h"
 #include "gromacs/utility/int64_to_int.h"
 
+#include "andersentemperaturecoupling.h"
 #include "computeglobalselement.h"
 #include "constraintelement.h"
 #include "firstorderpressurecoupling.h"
@@ -159,6 +160,10 @@ void ModularSimulator::addIntegrationElements(ModularSimulatorAlgorithmBuilder*
                     ReportPreviousStepConservedEnergy::Yes,
                     PropagatorTag("VelocityHalfAndPositionFullStep"));
         }
+        else if (ETC_ANDERSEN(legacySimulatorData_->inputrec->etc))
+        {
+            builder->add<AndersenTemperatureCoupling>();
+        }
         builder->add<Propagator<IntegrationStage::VelocityVerletPositionsAndVelocities>>(
                 PropagatorTag("VelocityHalfAndPositionFullStep"),
                 TimeStep(legacySimulatorData_->inputrec->delta_t));
@@ -245,8 +250,10 @@ bool ModularSimulator::isInputCompatible(bool                             exitOn
                                                      || inputrec->etc == TemperatureCoupling::VRescale
                                                      || inputrec->etc == TemperatureCoupling::Berendsen
                                                      || (inputrec->etc == TemperatureCoupling::NoseHoover
-                                                         && inputrec->eI == IntegrationAlgorithm::MD),
-                                             "Only v-rescale, Berendsen and Nose-Hoover "
+                                                         && inputrec->eI == IntegrationAlgorithm::MD)
+                                                     || ETC_ANDERSEN(inputrec->etc),
+                                             "Only v-rescale, Berendsen, Nose-Hoover, "
+                                             "and Andersen / Andersen-massive "
                                              "thermostats are supported by the modular simulator.");
     isInputCompatible = isInputCompatible
                         && conditionalAssert(inputrec->epc == PressureCoupling::No
index 8e3c4791d7214fbff56ec9c947365b32c50028f7..48777913981c20b3c9933d9466f260bf45dae336 100644 (file)
@@ -92,6 +92,9 @@ TEST_P(SimulatorComparisonTest, WithinTolerances)
 
     int maxNumWarnings = 0;
 
+    const bool isAndersen     = (tcoupling == "andersen-massive" || tcoupling == "andersen");
+    const bool hasConstraints = (simulationName != "argon12");
+
     // TODO At some point we should also test PME-only ranks.
     const int numRanksAvailable = getNumberOfTestMpiRanks();
     if (!isNumberOfPpRanksSupported(simulationName, numRanksAvailable))
@@ -112,6 +115,17 @@ TEST_P(SimulatorComparisonTest, WithinTolerances)
         return;
     }
 
+    if (isAndersen && pcoupling == "berendsen")
+    {
+        // "Using Berendsen pressure coupling invalidates the true ensemble for the thermostat"
+        maxNumWarnings++;
+    }
+    if (tcoupling == "andersen" && hasConstraints)
+    {
+        // Constraints are not allowed with non-massive Andersen
+        return;
+    }
+
     if (tcoupling == "nose-hoover" && pcoupling == "berendsen")
     {
         if (integrator == "md-vv")
@@ -134,7 +148,8 @@ TEST_P(SimulatorComparisonTest, WithinTolerances)
             ("Expected tested environment variable to be " + envVariableModSimOn + " or " + envVariableModSimOff)
                     .c_str());
 
-    const auto hasConservedField = !(tcoupling == "no" && pcoupling == "no");
+    const auto hasConservedField = !(tcoupling == "no" && pcoupling == "no")
+                                   && !(tcoupling == "andersen-massive" || tcoupling == "andersen");
 
     SCOPED_TRACE(formatString(
             "Comparing two simulations of '%s' "
@@ -146,8 +161,15 @@ TEST_P(SimulatorComparisonTest, WithinTolerances)
             pcoupling.c_str(),
             environmentVariable.c_str()));
 
-    const auto mdpFieldValues = prepareMdpFieldValues(
+    auto mdpFieldValues = prepareMdpFieldValues(
             simulationName.c_str(), integrator.c_str(), tcoupling.c_str(), pcoupling.c_str());
+    if (tcoupling == "andersen")
+    {
+        // Fixes error "nstcomm must be 1, not 4 for Andersen, as velocities of
+        //              atoms in coupled groups are randomized every time step"
+        mdpFieldValues["nstcomm"]       = "1";
+        mdpFieldValues["nstcalcenergy"] = "1";
+    }
 
     EnergyTermsToCompare energyTermsToCompare{ {
             { interaction_function[F_EPOT].longname, relativeToleranceAsPrecisionDependentUlp(60.0, 200, 160) },
@@ -257,7 +279,11 @@ INSTANTIATE_TEST_CASE_P(
         SimulatorComparisonTest,
         ::testing::Combine(::testing::Combine(::testing::Values("argon12", "tip3p5"),
                                               ::testing::Values("md-vv"),
-                                              ::testing::Values("no", "v-rescale", "berendsen"),
+                                              ::testing::Values("no",
+                                                                "v-rescale",
+                                                                "berendsen",
+                                                                "andersen-massive",
+                                                                "andersen"),
                                               ::testing::Values("no", "berendsen", "c-rescale")),
                            ::testing::Values("GMX_DISABLE_MODULAR_SIMULATOR")));
 INSTANTIATE_TEST_CASE_P(
@@ -276,7 +302,11 @@ INSTANTIATE_TEST_CASE_P(
         SimulatorComparisonTest,
         ::testing::Combine(::testing::Combine(::testing::Values("argon12", "tip3p5"),
                                               ::testing::Values("md-vv"),
-                                              ::testing::Values("no", "v-rescale", "berendsen"),
+                                              ::testing::Values("no",
+                                                                "v-rescale",
+                                                                "berendsen",
+                                                                "andersen-massive",
+                                                                "andersen"),
                                               ::testing::Values("no", "berendsen", "c-rescale")),
                            ::testing::Values("GMX_DISABLE_MODULAR_SIMULATOR")));
 INSTANTIATE_TEST_CASE_P(