Introduce ReferenceTemperatureManager
authorPascal Merz <pascal.merz@me.com>
Fri, 18 Jun 2021 08:33:18 +0000 (08:33 +0000)
committerPaul Bauer <paul.bauer.q@gmail.com>
Fri, 18 Jun 2021 08:33:18 +0000 (08:33 +0000)
15 files changed:
src/gromacs/modularsimulator/andersentemperaturecoupling.cpp
src/gromacs/modularsimulator/andersentemperaturecoupling.h
src/gromacs/modularsimulator/modularsimulatorinterfaces.h
src/gromacs/modularsimulator/mttk.cpp
src/gromacs/modularsimulator/mttk.h
src/gromacs/modularsimulator/nosehooverchains.cpp
src/gromacs/modularsimulator/nosehooverchains.h
src/gromacs/modularsimulator/referencetemperaturemanager.cpp [new file with mode: 0644]
src/gromacs/modularsimulator/referencetemperaturemanager.h [new file with mode: 0644]
src/gromacs/modularsimulator/simulatoralgorithm.cpp
src/gromacs/modularsimulator/simulatoralgorithm.h
src/gromacs/modularsimulator/statepropagatordata.cpp
src/gromacs/modularsimulator/statepropagatordata.h
src/gromacs/modularsimulator/velocityscalingtemperaturecoupling.cpp
src/gromacs/modularsimulator/velocityscalingtemperaturecoupling.h

index eee933b1ea3b6cd0cf1d1291dedfb859a0df05d8..c404b03534170888be1fb38adc7b21ef0ad069d7 100644 (file)
@@ -139,6 +139,13 @@ int AndersenTemperatureCoupling::frequency() const
     return couplingFrequency_;
 }
 
+void AndersenTemperatureCoupling::updateReferenceTemperature(ArrayRef<const real> gmx_unused temperatures,
+                                                             ReferenceTemperatureChangeAlgorithm gmx_unused algorithm)
+{
+    // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+    GMX_ASSERT(false, "AndersenTemperatureCoupling: Unknown ReferenceTemperatureChangeAlgorithm.");
+}
+
 void               AndersenTemperatureCoupling::elementSetup() {}
 ISimulatorElement* AndersenTemperatureCoupling::getElementPointerImpl(
         LegacySimulatorData*                    legacySimulatorData,
@@ -162,6 +169,12 @@ ISimulatorElement* AndersenTemperatureCoupling::getElementPointerImpl(
             statePropagatorData,
             legacySimulatorData->mdAtoms,
             legacySimulatorData->cr);
+    auto* andersenThermostatPtr = andersenThermostat.get();
+    builderHelper->registerReferenceTemperatureUpdate(
+            [andersenThermostatPtr](ArrayRef<const real>                temperatures,
+                                    ReferenceTemperatureChangeAlgorithm algorithm) {
+                andersenThermostatPtr->updateReferenceTemperature(temperatures, algorithm);
+            });
 
     // T-coupling frequency will be composite element frequency
     const auto frequency = andersenThermostat->frequency();
index acf912d88f933a395a0a621abe20c06e84326a56..cba4b56f9595efbfed1e40a74ca5339c30acc437 100644 (file)
@@ -107,6 +107,10 @@ public:
     [[nodiscard]] int frequency() const;
 
 private:
+    //! Update the reference temperature
+    static void updateReferenceTemperature(ArrayRef<const real>                temperatures,
+                                           ReferenceTemperatureChangeAlgorithm algorithm);
+
     //! Whether we're doing massive Andersen thermostatting
     const bool doMassive_;
     //! The rate
index 5358a108a4e6f2bf6e48f343fa2506d4dd561143..1f0cc1f98edaf1b74bdac62d116e2a095d4cfc46 100644 (file)
@@ -78,6 +78,7 @@ class EnergySignaller;
 class LastStepSignaller;
 class LoggingSignaller;
 class NeighborSearchSignaller;
+enum class ReferenceTemperatureChangeAlgorithm;
 enum class ScaleVelocities;
 template<class Signaller>
 class SignallerBuilder;
@@ -586,6 +587,10 @@ public:
     virtual DomDecCallback registerDomDecCallback() = 0;
 };
 
+//! Callback updating the reference temperature
+using ReferenceTemperatureCallback =
+        std::function<void(ArrayRef<const real>, ReferenceTemperatureChangeAlgorithm algorithm)>;
+
 //! /}
 } // namespace gmx
 
index 89bad65fe515f7211094f63e03bd03a84bbd1052..7b890e6d55280afb1682a43ea64d3d9b3f8e8c22 100644 (file)
@@ -95,13 +95,17 @@ void MttkData::build(LegacySimulatorData*                    legacySimulatorData
                      initialVolume,
                      legacySimulatorData->inputrec->compress,
                      statePropagatorData));
-    const auto* ptrToDataObject = builderHelper->simulationData<MttkData>(MttkData::dataID()).value();
+    auto* ptrToDataObject = builderHelper->simulationData<MttkData>(MttkData::dataID()).value();
 
     energyData->addConservedEnergyContribution([ptrToDataObject](Step /*unused*/, Time time) {
         return ptrToDataObject->temperatureCouplingIntegral(time);
     });
     energyData->setParrinelloRahmanBoxVelocities(
             [ptrToDataObject]() { return ptrToDataObject->boxVelocity_; });
+    builderHelper->registerReferenceTemperatureUpdate(
+            [ptrToDataObject](ArrayRef<const real> temperatures, ReferenceTemperatureChangeAlgorithm algorithm) {
+                ptrToDataObject->updateReferenceTemperature(temperatures[0], algorithm);
+            });
 }
 
 std::string MttkData::dataID()
@@ -125,6 +129,7 @@ MttkData::MttkData(real                       referenceTemperature,
     integralTime_(0.0),
     referencePressure_(referencePressure),
     boxVelocity_{ { 0 } },
+    referenceTemperature_(referenceTemperature),
     statePropagatorData_(statePropagatorData)
 {
 }
@@ -207,6 +212,15 @@ rvec* MttkData::boxVelocities()
     return boxVelocity_;
 }
 
+void MttkData::updateReferenceTemperature(real temperature,
+                                          ReferenceTemperatureChangeAlgorithm gmx_unused algorithm)
+{
+    // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+    GMX_ASSERT(false, "MttkData: Unknown ReferenceTemperatureChangeAlgorithm.");
+    invMass_ *= temperature / referenceTemperature_;
+    referenceTemperature_ = temperature;
+}
+
 namespace
 {
 /*!
index 2747a096e66b408c4d88f6f5d708c10ca2851054..0cccc7161983e9d1d6491be856ad9b1f6450a300 100644 (file)
@@ -121,6 +121,8 @@ private:
     double temperatureCouplingIntegral(Time time) const;
     //! Calculate the current value of the MTTK conserved energy if it is needed
     void calculateIntegralIfNeeded();
+    //! Update the reference temperature
+    void updateReferenceTemperature(real temperature, ReferenceTemperatureChangeAlgorithm algorithm);
 
     //! The coupling time step
     const real couplingTimeStep_;
@@ -138,6 +140,8 @@ private:
     const real referencePressure_;
     //! The current box velocities (used for reporting only)
     tensor boxVelocity_;
+    //! The reference temperature the mass is based on
+    real referenceTemperature_;
 
     // TODO: Clarify relationship to data objects and find a more robust alternative to raw pointers (#3583)
     //! Pointer to the micro state data (access to the current box)
index de3d38e37e1a87889b38ac3f8b7ba9b47af068e9..02b411553131c5b21021f7de904f9bf5d30f6e0d 100644 (file)
@@ -97,12 +97,17 @@ public:
     //! Return the current time of the NHC integral for the group
     real integralTime() const;
 
+    //! Set the reference temperature
+    void updateReferenceTemperature(real temperature);
+
 private:
     //! Increment coordinate time and update integral if applicable
     void finalizeUpdate(real couplingTimeStep);
 
     //! The reference temperature of this group
-    const real referenceTemperature_;
+    real referenceTemperature_;
+    //! The coupling time of this group
+    const real couplingTime_;
     //! The number of degrees of freedom in this group
     const real numDegreesOfFreedom_;
     //! The chain length of this group
@@ -130,6 +135,7 @@ NoseHooverGroup::NoseHooverGroup(int      chainLength,
                                  real     couplingTimeStep,
                                  NhcUsage nhcUsage) :
     referenceTemperature_(referenceTemperature),
+    couplingTime_(couplingTime),
     numDegreesOfFreedom_(numDegreesOfFreedom),
     chainLength_(chainLength),
     couplingTimeStep_(couplingTimeStep),
@@ -231,6 +237,14 @@ void NoseHooverChainsData::build(NhcUsage                                nhcUsag
                         ArrayRef<real>(),
                         nhcUsage));
     }
+    auto* nhcDataPtr =
+            builderHelper
+                    ->simulationData<NoseHooverChainsData>(NoseHooverChainsData::dataID(nhcUsage))
+                    .value();
+    builderHelper->registerReferenceTemperatureUpdate(
+            [nhcDataPtr](ArrayRef<const real> temperatures, ReferenceTemperatureChangeAlgorithm algorithm) {
+                nhcDataPtr->updateReferenceTemperature(temperatures, algorithm);
+            });
 
     const auto* ptrToDataObject =
             builderHelper
@@ -314,6 +328,44 @@ inline bool NoseHooverGroup::isAtFullCouplingTimeStep() const
     return timesClose(std::lround(coordinateTime_ / couplingTimeStep_) * couplingTimeStep_, coordinateTime_);
 }
 
+void NoseHooverChainsData::updateReferenceTemperature(ArrayRef<const real> temperatures,
+                                                      ReferenceTemperatureChangeAlgorithm gmx_unused algorithm)
+{
+    // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+    GMX_ASSERT(false, "NoseHooverChainsData: Unknown ReferenceTemperatureChangeAlgorithm.");
+    for (auto temperatureGroup = 0; temperatureGroup < numTemperatureGroups_; ++temperatureGroup)
+    {
+        noseHooverGroups_[temperatureGroup].updateReferenceTemperature(temperatures[temperatureGroup]);
+        if (noseHooverGroups_[temperatureGroup].isAtFullCouplingTimeStep())
+        {
+            noseHooverGroups_[temperatureGroup].calculateIntegral();
+        }
+    }
+}
+
+void NoseHooverGroup::updateReferenceTemperature(real temperature)
+{
+    const bool newTemperatureIsValid = (temperature > 0 && couplingTime_ > 0 && numDegreesOfFreedom_ > 0);
+    const bool oldTemperatureIsValid =
+            (referenceTemperature_ > 0 && couplingTime_ > 0 && numDegreesOfFreedom_ > 0);
+    GMX_RELEASE_ASSERT(newTemperatureIsValid == oldTemperatureIsValid,
+                       "Cannot turn temperature coupling on / off during simulation run.");
+    if (oldTemperatureIsValid && newTemperatureIsValid)
+    {
+        const real velocityFactor = std::sqrt(temperature / referenceTemperature_);
+        for (auto chainPosition = 0; chainPosition < chainLength_; ++chainPosition)
+        {
+            invXiMass_[chainPosition] *= (referenceTemperature_ / temperature);
+            xiVelocities_[chainPosition] *= velocityFactor;
+        }
+    }
+    referenceTemperature_ = temperature;
+    if (isAtFullCouplingTimeStep())
+    {
+        calculateIntegral();
+    }
+}
+
 namespace
 {
 /*!
index 8573e02f642c09002c5b64d34c8b26ebe1b38a0a..6960a52c564dcb7b642065d672735bfa5c50cf07 100644 (file)
@@ -142,6 +142,9 @@ public:
 private:
     //! Return the value of the coupling integral at a specific time
     double temperatureCouplingIntegral(Time time) const;
+    //! Update the reference temperature
+    void updateReferenceTemperature(ArrayRef<const real>                temperatures,
+                                    ReferenceTemperatureChangeAlgorithm algorithm);
 
     //! CheckpointHelper identifier
     const std::string identifier_;
diff --git a/src/gromacs/modularsimulator/referencetemperaturemanager.cpp b/src/gromacs/modularsimulator/referencetemperaturemanager.cpp
new file mode 100644 (file)
index 0000000..0544402
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * 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 the a helper struct managing reference temperature changes
+ *
+ * \author Pascal Merz <pascal.merz@me.com>
+ * \ingroup module_modularsimulator
+ */
+
+#include "gmxpre.h"
+
+#include "referencetemperaturemanager.h"
+
+#include "gromacs/mdtypes/inputrec.h"
+#include "gromacs/utility/gmxassert.h"
+
+namespace gmx
+{
+
+ReferenceTemperatureManager::ReferenceTemperatureManager(t_inputrec* inputrec) : inputrec_(inputrec)
+{
+}
+
+void ReferenceTemperatureManager::registerUpdateCallback(ReferenceTemperatureCallback referenceTemperatureCallback)
+{
+    callbacks_.emplace_back(std::move(referenceTemperatureCallback));
+}
+
+void ReferenceTemperatureManager::setReferenceTemperature(ArrayRef<const real> newReferenceTemperatures,
+                                                          ReferenceTemperatureChangeAlgorithm algorithm)
+{
+    GMX_RELEASE_ASSERT(newReferenceTemperatures.ssize() == inputrec_->opts.ngtc,
+                       "Expected one new reference temperature per temperature group.");
+
+    std::copy(newReferenceTemperatures.begin(), newReferenceTemperatures.end(), inputrec_->opts.ref_t);
+    for (const auto& callback : callbacks_)
+    {
+        callback(constArrayRefFromArray(inputrec_->opts.ref_t, inputrec_->opts.ngtc), algorithm);
+    }
+}
+
+} // namespace gmx
diff --git a/src/gromacs/modularsimulator/referencetemperaturemanager.h b/src/gromacs/modularsimulator/referencetemperaturemanager.h
new file mode 100644 (file)
index 0000000..422565f
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * 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 the a helper struct managing reference temperature changes
+ *
+ * \author Pascal Merz <pascal.merz@me.com>
+ * \ingroup module_modularsimulator
+ *
+ * This header is only used within the modular simulator module
+ */
+
+#ifndef GMX_MODULARSIMULATOR_REFERENCETEMPERATUREMANAGER_H
+#define GMX_MODULARSIMULATOR_REFERENCETEMPERATUREMANAGER_H
+
+#include <functional>
+
+#include "gromacs/utility/arrayref.h"
+#include "gromacs/utility/real.h"
+
+#include "modularsimulatorinterfaces.h"
+
+struct t_inputrec;
+
+namespace gmx
+{
+
+/*! \internal
+ * \brief The algorithm changing the reference temperature
+ *
+ * In the legacy implementation, reference temperature changes by
+ * different algorithms are not handled identically. This enum is
+ * used to inform clients what algorithm caused the temperature
+ * change, allowing them to customize their response.
+ */
+enum class ReferenceTemperatureChangeAlgorithm
+{
+};
+
+/*! \internal
+ * \brief Object managing reference temperature changes
+ *
+ * The ReferenceTemperatureManager allows to change the reference
+ * temperatures of the temperature groups. Elements can register a callback
+ * if they need to be informed about changes.
+ *
+ * The ReferenceTemperatureManager updates the inputrec. Elements
+ * might, however, have a copy of the reference temperature they
+ * need updated, or perform another action upon change of the
+ * reference temperature (e.g. velocity scaling or recalculating
+ * a temperature coupling integral).
+ */
+class ReferenceTemperatureManager final
+{
+public:
+    //! Constructor
+    ReferenceTemperatureManager(t_inputrec* inputrec);
+    //! Register a callback for reference temperature update
+    void registerUpdateCallback(ReferenceTemperatureCallback referenceTemperatureCallback);
+    //! Set reference temperatures (one per temperature group)
+    void setReferenceTemperature(ArrayRef<const real>                newReferenceTemperatures,
+                                 ReferenceTemperatureChangeAlgorithm algorithm);
+
+private:
+    //! List of callbacks
+    std::vector<ReferenceTemperatureCallback> callbacks_;
+    //! Pointer to the input record
+    t_inputrec* inputrec_;
+};
+
+} // namespace gmx
+
+#endif // GMX_MODULARSIMULATOR_REFERENCETEMPERATUREMANAGER_H
index 0b9fb2a1fb0cc768347d7b52a9297413c4f7b8c6..f4abbf7add149a2f94e26e64f0c9bd6eee23c3cd 100644 (file)
@@ -83,6 +83,7 @@
 #include "modularsimulator.h"
 #include "pmeloadbalancehelper.h"
 #include "propagator.h"
+#include "referencetemperaturemanager.h"
 #include "statepropagatordata.h"
 
 namespace gmx
@@ -454,6 +455,22 @@ ModularSimulatorAlgorithmBuilder::ModularSimulatorAlgorithmBuilder(
                                                legacySimulatorData->startingBehavior,
                                                simulationsShareState);
     registerExistingElement(energyData_->element());
+
+    // This is the only modular simulator object which changes the inputrec
+    // TODO: Avoid changing inputrec (#3854)
+    storeSimulationData(
+            "ReferenceTemperatureManager",
+            ReferenceTemperatureManager(const_cast<t_inputrec*>(legacySimulatorData->inputrec)));
+    auto* referenceTemperatureManager =
+            simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
+
+    // State propagator data is scaling velocities if reference temperature is updated
+    auto* statePropagatorDataPtr = statePropagatorData_.get();
+    referenceTemperatureManager->registerUpdateCallback(
+            [statePropagatorDataPtr](ArrayRef<const real>                temperatures,
+                                     ReferenceTemperatureChangeAlgorithm algorithm) {
+                statePropagatorDataPtr->updateReferenceTemperature(temperatures, algorithm);
+            });
 }
 
 ModularSimulatorAlgorithm ModularSimulatorAlgorithmBuilder::build()
@@ -783,4 +800,24 @@ void ModularSimulatorAlgorithmBuilderHelper::registerPropagator(PropagatorConnec
     builder_->propagatorConnections_.emplace_back(std::move(connectionData));
 }
 
+void ModularSimulatorAlgorithmBuilderHelper::registerReferenceTemperatureUpdate(
+        ReferenceTemperatureCallback referenceTemperatureCallback)
+{
+    auto* referenceTemperatureManager =
+            simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
+    referenceTemperatureManager->registerUpdateCallback(std::move(referenceTemperatureCallback));
+}
+
+ReferenceTemperatureCallback ModularSimulatorAlgorithmBuilderHelper::changeReferenceTemperatureCallback()
+{
+    // Capture is safe because SimulatorAlgorithm will manage life time of both the
+    // recipient of the callback and the reference temperature manager
+    auto* referenceTemperatureManager =
+            simulationData<ReferenceTemperatureManager>("ReferenceTemperatureManager").value();
+    return [referenceTemperatureManager](ArrayRef<const real>                temperatures,
+                                         ReferenceTemperatureChangeAlgorithm algorithm) {
+        referenceTemperatureManager->setReferenceTemperature(temperatures, algorithm);
+    };
+}
+
 } // namespace gmx
index 7eea8ee3b50e1c68e55fe3e1923d8afbba861fb5..eb559245a4acf553231ce51caa9354120770d6d0 100644 (file)
@@ -363,6 +363,10 @@ public:
     void registerTemperaturePressureControl(std::function<void(const PropagatorConnection&)> registrationFunction);
     //! Register a propagator to be used with a temperature / pressure control algorithm
     void registerPropagator(PropagatorConnection connectionData);
+    //! Register for callback after an update to the reference temperature
+    void registerReferenceTemperatureUpdate(ReferenceTemperatureCallback referenceTemperatureCallback);
+    //! Get a callback to change reference temperature
+    ReferenceTemperatureCallback changeReferenceTemperatureCallback();
 
 private:
     //! Pointer to the associated ModularSimulatorAlgorithmBuilder
index 430dd70dbb19b31617024a1cd3f4505e47d0553d..c9f72964099d40a2f2f8b19893ff2d0ab26c11e7 100644 (file)
 
 namespace gmx
 {
+/*! \internal
+ * \brief Helper object to scale velocities according to reference temperature change
+ */
+class StatePropagatorData::ReferenceTemperatureHelper
+{
+public:
+    //! Constructor
+    ReferenceTemperatureHelper(const t_inputrec*    inputrec,
+                               StatePropagatorData* statePropagatorData,
+                               const t_mdatoms*     mdatoms) :
+        numTemperatureGroups_(inputrec->opts.ngtc),
+        referenceTemperature_(inputrec->opts.ref_t, inputrec->opts.ref_t + inputrec->opts.ngtc),
+        velocityScalingFactors_(numTemperatureGroups_),
+        statePropagatorData_(statePropagatorData),
+        mdatoms_(mdatoms)
+    {
+    }
+
+    /*! \brief Update the reference temperature
+     *
+     * Changing the reference temperature requires scaling the velocities, which
+     * is done here.
+     *
+     * \param temperatures  New reference temperatures
+     * \param algorithm     The algorithm which initiated the temperature update
+     */
+    void updateReferenceTemperature(ArrayRef<const real>                           temperatures,
+                                    ReferenceTemperatureChangeAlgorithm gmx_unused algorithm)
+    {
+        // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+        GMX_ASSERT(false, "StatePropagatorData: Unknown ReferenceTemperatureChangeAlgorithm.");
+        for (int temperatureGroup = 0; temperatureGroup < numTemperatureGroups_; ++temperatureGroup)
+        {
+            velocityScalingFactors_[temperatureGroup] =
+                    std::sqrt(temperatures[temperatureGroup] / referenceTemperature_[temperatureGroup]);
+        }
+
+        auto velocities = statePropagatorData_->velocitiesView().unpaddedArrayRef();
+        int  nth        = gmx_omp_nthreads_get(ModuleMultiThread::Update);
+#pragma omp parallel for num_threads(nth) schedule(static) default(none) \
+        shared(nth, velocityScalingFactors_, velocities)
+        for (int threadIndex = 0; threadIndex < nth; threadIndex++)
+        {
+            int startAtom = 0;
+            int endAtom   = 0;
+            getThreadAtomRange(nth, threadIndex, statePropagatorData_->localNAtoms_, &startAtom, &endAtom);
+            for (int atomIdx = startAtom; atomIdx < endAtom; ++atomIdx)
+            {
+                const int temperatureGroup = (mdatoms_->cTC != nullptr) ? mdatoms_->cTC[atomIdx] : 0;
+                velocities[atomIdx] *= velocityScalingFactors_[temperatureGroup];
+            }
+        }
+        std::copy(temperatures.begin(), temperatures.end(), referenceTemperature_.begin());
+    }
+
+private:
+    //! The number of temperature groups
+    const int numTemperatureGroups_;
+    //! Coupling temperature per group
+    std::vector<real> referenceTemperature_;
+    //! Working array used at every temperature update
+    std::vector<real> velocityScalingFactors_;
+
+    //! Pointer to StatePropagatorData to scale velocities
+    StatePropagatorData* statePropagatorData_;
+    //! Atom parameters for this domain (temperature group information)
+    const t_mdatoms* mdatoms_;
+};
+
 StatePropagatorData::StatePropagatorData(int                numAtoms,
                                          FILE*              fplog,
                                          const t_commrec*   cr,
@@ -101,6 +170,7 @@ StatePropagatorData::StatePropagatorData(int                numAtoms,
                                        finalConfigurationFilename,
                                        inputrec,
                                        globalTop)),
+    referenceTemperatureHelper_(std::make_unique<ReferenceTemperatureHelper>(inputrec, this, mdatoms)),
     vvResetVelocities_(false),
     isRegularSimulationEnd_(false),
     lastStep_(-1),
@@ -172,6 +242,8 @@ StatePropagatorData::StatePropagatorData(int                numAtoms,
     }
 }
 
+StatePropagatorData::~StatePropagatorData() = default;
+
 StatePropagatorData::Element* StatePropagatorData::element()
 {
     return element_.get();
@@ -335,6 +407,12 @@ void StatePropagatorData::copyPosition(int start, int end)
     }
 }
 
+void StatePropagatorData::updateReferenceTemperature(ArrayRef<const real> temperatures,
+                                                     ReferenceTemperatureChangeAlgorithm algorithm)
+{
+    referenceTemperatureHelper_->updateReferenceTemperature(temperatures, algorithm);
+}
+
 void StatePropagatorData::Element::scheduleTask(Step                       step,
                                                 Time gmx_unused            time,
                                                 const RegisterRunFunction& registerRunFunction)
index 3994d8b62486857714168dffad3f77fe7141cbb6..78007403d50b0f083d3e268baba1f548329fd84d 100644 (file)
@@ -112,6 +112,9 @@ public:
                         const t_mdatoms*   mdatoms,
                         const gmx_mtop_t&  globalTop);
 
+    //! Destructor (allows forward declaration of internal type)
+    ~StatePropagatorData();
+
     // Allow access to state
     //! Get write access to position vector
     ArrayRefWithPadding<RVec> positionsView();
@@ -149,6 +152,12 @@ public:
     //! Initial set up for the associated element
     void setup();
 
+    //! Update the reference temperature
+    void updateReferenceTemperature(ArrayRef<const real>                temperatures,
+                                    ReferenceTemperatureChangeAlgorithm algorithm);
+    //! Helper class handling reference temperature change
+    class ReferenceTemperatureHelper;
+
     //! Read everything that can be stored in t_trxframe from a checkpoint file
     static void readCheckpointToTrxFrame(t_trxframe* trxFrame, ReadCheckpointData readCheckpointData);
     //! CheckpointHelper identifier
@@ -197,6 +206,8 @@ private:
 
     //! The element
     std::unique_ptr<Element> element_;
+    //! Instance of reference temperature helper
+    std::unique_ptr<ReferenceTemperatureHelper> referenceTemperatureHelper_;
 
     //! Move x_ to previousX_
     void copyPosition();
index c2b8b6ab713efc103cbd8df8254e47233f82f8a8..847bb15a8f2f88848a82363dcc0fe2258df06472 100644 (file)
@@ -113,6 +113,12 @@ public:
     //! Read private data from checkpoint
     virtual void readCheckpoint(std::optional<ReadCheckpointData> checkpointData, const t_commrec* cr) = 0;
 
+    //! Update the reference temperature and update and return the temperature coupling integral
+    virtual real updateReferenceTemperatureAndIntegral(int  temperatureGroup,
+                                                       real newTemperature,
+                                                       ReferenceTemperatureChangeAlgorithm algorithm,
+                                                       const TemperatureCouplingData& temperatureCouplingData) = 0;
+
     //! Standard virtual destructor
     virtual ~ITemperatureCouplingImpl() = default;
 };
@@ -196,6 +202,15 @@ public:
     {
     }
 
+    //! No changes needed
+    real updateReferenceTemperatureAndIntegral(int             temperatureGroup,
+                                               real gmx_unused newTemperature,
+                                               ReferenceTemperatureChangeAlgorithm gmx_unused algorithm,
+                                               const TemperatureCouplingData& temperatureCouplingData) override
+    {
+        return temperatureCouplingData.temperatureCouplingIntegral[temperatureGroup];
+    }
+
     //! Constructor
     VRescaleTemperatureCoupling(int64_t seed) : seed_(seed) {}
 
@@ -269,6 +284,15 @@ public:
     {
     }
 
+    //! No changes needed
+    real updateReferenceTemperatureAndIntegral(int             temperatureGroup,
+                                               real gmx_unused newTemperature,
+                                               ReferenceTemperatureChangeAlgorithm gmx_unused algorithm,
+                                               const TemperatureCouplingData& temperatureCouplingData) override
+    {
+        return temperatureCouplingData.temperatureCouplingIntegral[temperatureGroup];
+    }
+
 private:
     //! View on the scaling factor of the propagator (pre-step velocities)
     ArrayRef<real> lambdaStartVelocities_;
@@ -297,6 +321,15 @@ constexpr auto c_nhCurrentVersion = NHCheckpointVersion(int(NHCheckpointVersion:
 class NoseHooverTemperatureCoupling final : public ITemperatureCouplingImpl
 {
 public:
+    //! Calculate the current value of the temperature coupling integral
+    real integral(int temperatureGroup, real numDegreesOfFreedom, real referenceTemperature)
+    {
+        return 0.5 * c_boltz * numDegreesOfFreedom
+                       * (xiVelocities_[temperatureGroup] * xiVelocities_[temperatureGroup])
+                       / invXiMass_[temperatureGroup]
+               + numDegreesOfFreedom * xi_[temperatureGroup] * c_boltz * referenceTemperature;
+    }
+
     //! Apply the Nose-Hoover temperature control
     real apply(Step gmx_unused                step,
                int                            temperatureGroup,
@@ -357,11 +390,9 @@ public:
                 1. / (1 + 0.5 * thermostatData.couplingTimeStep * xiVelocities_[temperatureGroup]);
 
         // Current value of the thermostat integral
-        return 0.5 * c_boltz * thermostatData.numDegreesOfFreedom[temperatureGroup]
-                       * (xiVelocities_[temperatureGroup] * xiVelocities_[temperatureGroup])
-                       / invXiMass_[temperatureGroup]
-               + thermostatData.numDegreesOfFreedom[temperatureGroup] * xi_[temperatureGroup]
-                         * c_boltz * thermostatData.referenceTemperature[temperatureGroup];
+        return integral(temperatureGroup,
+                        thermostatData.numDegreesOfFreedom[temperatureGroup],
+                        thermostatData.referenceTemperature[temperatureGroup]);
     }
 
     //! Connect with propagator - Nose-Hoover scales start and end step velocities
@@ -428,6 +459,36 @@ public:
         }
     }
 
+    //! Adapt masses
+    real updateReferenceTemperatureAndIntegral(int             temperatureGroup,
+                                               real gmx_unused newTemperature,
+                                               ReferenceTemperatureChangeAlgorithm gmx_unused algorithm,
+                                               const TemperatureCouplingData& temperatureCouplingData) override
+    {
+        // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+        GMX_ASSERT(false,
+                   "NoseHooverTemperatureCoupling: Unknown ReferenceTemperatureChangeAlgorithm.");
+        const bool newTemperatureIsValid =
+                (newTemperature > 0 && temperatureCouplingData.couplingTime[temperatureGroup] > 0
+                 && temperatureCouplingData.numDegreesOfFreedom[temperatureGroup] > 0);
+        const bool oldTemperatureIsValid =
+                (temperatureCouplingData.referenceTemperature[temperatureGroup] > 0
+                 && temperatureCouplingData.couplingTime[temperatureGroup] > 0
+                 && temperatureCouplingData.numDegreesOfFreedom[temperatureGroup] > 0);
+        GMX_RELEASE_ASSERT(newTemperatureIsValid == oldTemperatureIsValid,
+                           "Cannot turn temperature coupling on / off during simulation run.");
+        if (oldTemperatureIsValid && newTemperatureIsValid)
+        {
+            invXiMass_[temperatureGroup] *=
+                    (temperatureCouplingData.referenceTemperature[temperatureGroup] / newTemperature);
+            xiVelocities_[temperatureGroup] *= std::sqrt(
+                    newTemperature / temperatureCouplingData.referenceTemperature[temperatureGroup]);
+        }
+        return integral(temperatureGroup,
+                        temperatureCouplingData.numDegreesOfFreedom[temperatureGroup],
+                        newTemperature);
+    }
+
 private:
     //! The thermostat degree of freedom
     std::vector<real> xi_;
@@ -577,6 +638,24 @@ void VelocityScalingTemperatureCoupling::setLambda(Step step)
     }
 }
 
+void VelocityScalingTemperatureCoupling::updateReferenceTemperature(ArrayRef<const real> temperatures,
+                                                                    ReferenceTemperatureChangeAlgorithm algorithm)
+{
+    TemperatureCouplingData thermostatData = {
+        couplingTimeStep_, referenceTemperature_, couplingTime_, numDegreesOfFreedom_, temperatureCouplingIntegral_
+    };
+    for (int temperatureGroup = 0; (temperatureGroup < numTemperatureGroups_); temperatureGroup++)
+    {
+        temperatureCouplingIntegral_[temperatureGroup] =
+                temperatureCouplingImpl_->updateReferenceTemperatureAndIntegral(
+                        temperatureGroup, temperatures[temperatureGroup], algorithm, thermostatData);
+    }
+    // Currently, we don't know about any temperature change algorithms, so we assert this never gets called
+    GMX_ASSERT(false,
+               "VelocityScalingTemperatureCoupling: Unknown ReferenceTemperatureChangeAlgorithm.");
+    std::copy(temperatures.begin(), temperatures.end(), referenceTemperature_.begin());
+}
+
 namespace
 {
 /*!
@@ -687,6 +766,10 @@ ISimulatorElement* VelocityScalingTemperatureCoupling::getElementPointerImpl(
             [thermostat, propagatorTag](const PropagatorConnection& connection) {
                 thermostat->connectWithMatchingPropagator(connection, propagatorTag);
             });
+    builderHelper->registerReferenceTemperatureUpdate(
+            [thermostat](ArrayRef<const real> temperatures, ReferenceTemperatureChangeAlgorithm algorithm) {
+                thermostat->updateReferenceTemperature(temperatures, algorithm);
+            });
     return element;
 }
 
index 2cd8f58541e62134d3b3a2a50c5792e8257ba5a9..9bd04b9e46b87078ccfd596bbbb7341ffd3fb3a1 100644 (file)
@@ -150,6 +150,10 @@ public:
                           const PropagatorTag&                    propagatorTag);
 
 private:
+    //! Update the reference temperature
+    void updateReferenceTemperature(ArrayRef<const real>                temperatures,
+                                    ReferenceTemperatureChangeAlgorithm algorithm);
+
     //! The frequency at which the thermostat is applied
     const int nstcouple_;
     //! If != 0, offset the step at which the thermostat is applied
@@ -164,7 +168,7 @@ private:
     //! The coupling time step - simulation time step x nstcouple_
     const double couplingTimeStep_;
     //! Coupling temperature per group
-    const std::vector<real> referenceTemperature_;
+    std::vector<real> referenceTemperature_;
     //! Coupling time per group
     const std::vector<real> couplingTime_;
     //! Number of degrees of freedom per group