NoseHooverIntegrator.cpp 19 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
 * Portions copyright (c) 2019 Stanford University and the Authors.           *
 * Authors: Andreas Krämer and Andrew C. Simmonett                            *
 * Contributors:                                                              *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
 * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
 * -------------------------------------------------------------------------- */

32
#include "openmm/NoseHooverIntegrator.h"
33
34
35
#include "openmm/Context.h"
#include "openmm/Force.h"
#include "openmm/System.h"
36
#include "openmm/NoseHooverChain.h"
37
38
39
#include "openmm/OpenMMException.h"
#include "openmm/CMMotionRemover.h"
#include "openmm/internal/ContextImpl.h"
40
#include "openmm/internal/AssertionUtilities.h"
41
#include "openmm/kernels.h"
42
#include <iostream>
43
#include <string>
44
#include <algorithm>
45
46
47
48
49

using namespace OpenMM;
using std::string;
using std::vector;

50
NoseHooverIntegrator::NoseHooverIntegrator(double stepSize):
51
52
    forcesAreValid(false),
    hasSubsystemThermostats_(true)
53
{
54
55
    setStepSize(stepSize);
    setConstraintTolerance(1e-5);
Andy Simmonett's avatar
Andy Simmonett committed
56
    setMaximumPairDistance(0.0);
57
}
58
NoseHooverIntegrator::NoseHooverIntegrator(double temperature, double collisionFrequency, double stepSize,
59
60
61
62
                                           int chainLength, int numMTS, int numYoshidaSuzuki) : 
    forcesAreValid(false),
    hasSubsystemThermostats_(false) {

63
64
    setStepSize(stepSize);
    setConstraintTolerance(1e-5);
Andy Simmonett's avatar
Andy Simmonett committed
65
    setMaximumPairDistance(0.0);
66
    addThermostat(temperature, collisionFrequency, chainLength, numMTS, numYoshidaSuzuki);
67
}
68

69
NoseHooverIntegrator::~NoseHooverIntegrator() {}
70

71
std::pair<double, double> NoseHooverIntegrator::propagateChain(std::pair<double, double> kineticEnergy, int chainID) {
72
    return nhcKernel.getAs<NoseHooverChainKernel>().propagateChain(*context, noseHooverChains.at(chainID), kineticEnergy, getStepSize());
73
74
}

75
76

int NoseHooverIntegrator::addThermostat(double temperature, double collisionFrequency,
77
                                        int chainLength, int numMTS, int numYoshidaSuzuki) {
78
    hasSubsystemThermostats_ = false;
79
80
    return addSubsystemThermostat(std::vector<int>(), std::vector<std::pair<int, int>>(), temperature,
                                  collisionFrequency, temperature, collisionFrequency, chainLength, numMTS, numYoshidaSuzuki);
81
82
}

83
int NoseHooverIntegrator::addSubsystemThermostat(const std::vector<int>& thermostatedParticles,
84
                                                 const std::vector< std::pair< int, int> > &thermostatedPairs,
85
86
                                                 double temperature, double collisionFrequency,
                                                 double relativeTemperature, double relativeCollisionFrequency,
87
                                                 int chainLength, int numMTS, int numYoshidaSuzuki) {
88
    int chainID = noseHooverChains.size();
89
90
91
92
93
94
95
96
    // check if one thermostat already applies to all atoms or pairs
    if ( (chainID > 0) && (noseHooverChains[0].getThermostatedAtoms().size()*noseHooverChains[0].getThermostatedPairs().size() == 0) ) {
        throw OpenMMException(
            "Cannot add thermostat, since one of the thermostats already in the integrator applies to all particles. "
            "To manually add thermostats, use the constructor that takes only the "
            "step size as an input argument."
        );
    }
97
98
99
100
101
102
103
    int nDOF = 0; // is set in initializeThermostats()
    noseHooverChains.emplace_back(temperature, relativeTemperature,
                                 collisionFrequency, relativeCollisionFrequency,
                                 nDOF, chainLength, numMTS,
                                 numYoshidaSuzuki, chainID,
                                 thermostatedParticles, thermostatedPairs);
    return chainID;
104
105
}

106
const NoseHooverChain& NoseHooverIntegrator::getThermostat(int chainID) const {
107
108
109
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
    return noseHooverChains[chainID];
}
110

111
void NoseHooverIntegrator::initializeThermostats(const System &system) {
112

113
114
115
116
117
    allAtoms.clear();
    allPairs.clear();
    std::set<int> allAtomsSet;
    for (int atom = 0; atom < system.getNumParticles(); ++atom) allAtomsSet.insert(atom);

118
119
120
    for (auto &thermostat : noseHooverChains) {
        const auto &thermostatedParticles = thermostat.getThermostatedAtoms();
        const auto &thermostatedPairs = thermostat.getThermostatedPairs();
121

122
123
124
        for( auto& pair : thermostatedPairs ) {
            allAtomsSet.erase(pair.first);
            allAtomsSet.erase(pair.second);
125
            allPairs.emplace_back(pair.first, pair.second, thermostat.getRelativeTemperature());
126
127
        }

128
        // figure out the number of DOFs
129
        int nDOF = 3*(thermostatedParticles.size() + thermostatedPairs.size());
130
131
132
133
        for (int constraintNum = 0; constraintNum < system.getNumConstraints(); constraintNum++) {
            int particle1, particle2;
            double distance;
            system.getConstraintParameters(constraintNum, particle1, particle2, distance);
134
135
136
137
138
            bool particle1_in_thermostatedParticles = ((std::find(thermostatedParticles.begin(),
                                                                  thermostatedParticles.end(), particle1)
                                                                    != thermostatedParticles.end())) ||
                                                      (std::find_if(thermostatedPairs.begin(),
                                                                    thermostatedPairs.end(),
139
140
                                                                    [&particle1](const std::pair<int, int>& pair){
                                                                           return pair.first == particle1 || pair.second == particle1;})
141
142
143
144
145
146
                                                                      != thermostatedPairs.end());
            bool particle2_in_thermostatedParticles = ((std::find(thermostatedParticles.begin(),
                                                                  thermostatedParticles.end(), particle2)
                                                                    != thermostatedParticles.end())) ||
                                                      (std::find_if(thermostatedPairs.begin(),
                                                                    thermostatedPairs.end(),
147
148
                                                                    [&particle2](const std::pair<int, int>& pair){
                                                                           return pair.first == particle2 || pair.second == particle2;})
149
                                                                      != thermostatedPairs.end());
150
151
152
153
154
155
156
157
158
            if ((system.getParticleMass(particle1) > 0) && (system.getParticleMass(particle2) > 0)){
                if ((particle1_in_thermostatedParticles && !particle2_in_thermostatedParticles) ||
                     (!particle1_in_thermostatedParticles && particle2_in_thermostatedParticles)){
                    throw OpenMMException("Cannot add only one of particles " + std::to_string(particle1) + " and " + std::to_string(particle2)
                                            + " to NoseHooverChain, because they are connected by a constraint.");
                }
                if (particle1_in_thermostatedParticles && particle2_in_thermostatedParticles){
                    nDOF -= 1;
                }
159
            }
160
161
162
163
        }

        // remove 3 degrees of freedom from thermostats that act on absolute motions
        int numForces = system.getNumForces();
164
        if (thermostatedPairs.size() == 0){
165
166
            for (int forceNum = 0; forceNum < numForces; ++forceNum) {
                if (dynamic_cast<const CMMotionRemover*>(&system.getForce(forceNum))) nDOF -= 3;
167
168
            }
        }
169

170
        // set number of DoFs for chain 
171
        thermostat.setNumDegreesOfFreedom(nDOF);
172
    }
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212


    for (int chain1 = 0; chain1 < noseHooverChains.size(); ++chain1){
        const auto& nhc = noseHooverChains[chain1];

       // make sure that thermostats do not overlap
        for (int chain2 = 0; chain2 < chain1; ++chain2){
            const auto& other_nhc = noseHooverChains[chain2];
            for (auto &particle: nhc.getThermostatedAtoms()){
                bool isParticleInOtherChain = (std::find(other_nhc.getThermostatedAtoms().begin(),
                                                         other_nhc.getThermostatedAtoms().end(),
                                                         particle) != other_nhc.getThermostatedAtoms().end()) ||
                                              (std::find_if(other_nhc.getThermostatedPairs().begin(),
                                                            other_nhc.getThermostatedPairs().end(),
                                               [&particle](const std::pair<int, int>& pair){ return pair.first == particle || pair.second == particle;})
                                                  != other_nhc.getThermostatedPairs().end());
                if (isParticleInOtherChain){
                    throw OpenMMException("Found particle " + std::to_string(particle) + "in a different NoseHooverChain, "
                                          "but particles can only be thermostated by one thermostat.");
                }
            }
            for (auto &pair: nhc.getThermostatedPairs()){
                bool isParticleInOtherChain = (std::find(other_nhc.getThermostatedAtoms().begin(),
                                                         other_nhc.getThermostatedAtoms().end(),
                                                         pair.first) != other_nhc.getThermostatedAtoms().end()) ||
                                              (std::find(other_nhc.getThermostatedAtoms().begin(),
                                                         other_nhc.getThermostatedAtoms().end(),
                                                         pair.second) != other_nhc.getThermostatedAtoms().end()) ||
                                              (std::find_if(other_nhc.getThermostatedPairs().begin(),
                                                            other_nhc.getThermostatedPairs().end(),
                                               [&pair](const std::pair<int, int>& other_pair){
                                                        return pair.first == other_pair.first || pair.first == other_pair.second ||
                                                               pair.second == other_pair.first || pair.second == other_pair.second;})
                                                  != other_nhc.getThermostatedPairs().end());
                if (isParticleInOtherChain){
                    throw OpenMMException("Found pair " + std::to_string(pair.first) + "," +
                                          std::to_string(pair.second) + " in a different NoseHooverChain, "
                                          "but particles can only be thermostated by one thermostat.");
                }
            }
213
        }
214

215
216
217
        // make sure that massless particles are not thermostated
        for(auto particle: nhc.getThermostatedAtoms()){
            double mass = system.getParticleMass(particle);
218
            if (mass == 0.0) {
219
                throw OpenMMException("Found a particle with no mass (" + std::to_string(particle) + ") in a thermostat. Massless particles cannot be thermostated.");
220
221
222
            }
        }
    }
223
224

    for(const auto& atom : allAtomsSet) allAtoms.push_back(atom);
225
226
}

227
double NoseHooverIntegrator::getTemperature(int chainID) const {
228
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
229
    return noseHooverChains[chainID].getTemperature();
230
231
}

232
void NoseHooverIntegrator::setTemperature(double temperature, int chainID){
233
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
234
    noseHooverChains[chainID].setTemperature(temperature);
235
236
237

}

238
double NoseHooverIntegrator::getRelativeTemperature(int chainID) const {
239
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
240
    return noseHooverChains[chainID].getRelativeTemperature();
241
242
243
}

void NoseHooverIntegrator::setRelativeTemperature(double temperature, int chainID){
244
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
245
    noseHooverChains[chainID].setRelativeTemperature(temperature);
246
247
248
249

}

double NoseHooverIntegrator::getCollisionFrequency(int chainID) const {
250
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
251
    return noseHooverChains[chainID].getCollisionFrequency();
252
253
254
}

void NoseHooverIntegrator::setCollisionFrequency(double frequency, int chainID){
255
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
256
    noseHooverChains[chainID].setCollisionFrequency(frequency);
257
258
}

259
double NoseHooverIntegrator::getRelativeCollisionFrequency(int chainID) const {
260
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
261
    return noseHooverChains[chainID].getRelativeCollisionFrequency();
262
263
264
}

void NoseHooverIntegrator::setRelativeCollisionFrequency(double frequency, int chainID){
265
    ASSERT_VALID_INDEX(chainID, noseHooverChains);
266
    noseHooverChains[chainID].setRelativeCollisionFrequency(frequency);
267
268
269
}

double NoseHooverIntegrator::computeKineticEnergy() {
270
    forcesAreValid = false;
271
    double kE = 0.0;
272
    if(noseHooverChains.size() > 0) {
273
        for (const auto &nhc: noseHooverChains){
274
            kE += nhcKernel.getAs<NoseHooverChainKernel>().computeMaskedKineticEnergy(*context, nhc, true).first;
275
276
277
278
279
        }
    } else {
        kE = vvKernel.getAs<IntegrateVelocityVerletStepKernel>().computeKineticEnergy(*context, *this);
    }
    return kE;
280
281
}

282
283
284
285
bool NoseHooverIntegrator::kineticEnergyRequiresForce() const {
    return false;
}

286
double NoseHooverIntegrator::computeHeatBathEnergy() {
287
288
    double energy = 0;
    for(auto &nhc : noseHooverChains) {
289
        if (context && (nhc.getNumDegreesOfFreedom() > 0)) {
Andy Simmonett's avatar
Andy Simmonett committed
290
291
            energy += nhcKernel.getAs<NoseHooverChainKernel>().computeHeatBathEnergy(*context, nhc);
        }
292
293
294
295
    }
    return energy;
}

296
void NoseHooverIntegrator::initialize(ContextImpl& contextRef) {
297
298
    if (owner != NULL && &contextRef.getOwner() != owner)
        throw OpenMMException("This Integrator is already bound to a context");
299

300
    context = &contextRef;
301
    const System& system = context->getSystem();
302
303
304
    owner = &contextRef.getOwner();
    vvKernel = context->getPlatform().createKernel(IntegrateVelocityVerletStepKernel::Name(), contextRef);
    vvKernel.getAs<IntegrateVelocityVerletStepKernel>().initialize(contextRef.getSystem(), *this);
305
306
    nhcKernel = context->getPlatform().createKernel(NoseHooverChainKernel::Name(), contextRef);
    nhcKernel.getAs<NoseHooverChainKernel>().initialize();
307
    forcesAreValid = false;
308
309

    // check for drude particles and build the Nose-Hoover Chains
310
    for (auto& thermostat: noseHooverChains){
311
        // if there are no thermostated particles or pairs in the lists this is a regular thermostat for the whole (non-Drude) system
312
313
        if ( (thermostat.getThermostatedAtoms().size() == 0) && (thermostat.getThermostatedPairs().size() == 0) ){
            std::vector<int> thermostatedParticles;
314
315
316
            for(int particle = 0; particle < system.getNumParticles(); ++particle) {
                double mass = system.getParticleMass(particle);
                if ( (mass > 0) && (mass < 0.8) ){
317
                    std::cout << "Warning: Found particles with mass between 0.0 and 0.8 dalton. Did you mean to make a DrudeNoseHooverIntegrator instead? "
318
319
320
                                 "The thermostat you are about to use will not treat these particles as Drude particles!" << std::endl;
                }
                if(system.getParticleMass(particle) > 0) {
321
                   thermostatedParticles.push_back(particle);
322
323
                }
            }
324
            thermostat.setThermostatedAtoms(thermostatedParticles);
325
326
327
        }
    }

328
    initializeThermostats(system);
329
330
}

331
void NoseHooverIntegrator::cleanup() {
332
    vvKernel = Kernel();
333
    nhcKernel = Kernel();
334
335
}

336
vector<string> NoseHooverIntegrator::getKernelNames() {
337
    std::vector<std::string> names;
338
    names.push_back(NoseHooverChainKernel::Name());
339
340
341
342
    names.push_back(IntegrateVelocityVerletStepKernel::Name());
    return names;
}

343
void NoseHooverIntegrator::step(int steps) {
344
345
    if (context == NULL)
        throw OpenMMException("This Integrator is not bound to a context!");
346
    std::pair<double, double> scale, kineticEnergy;
347
    for (int i = 0; i < steps; ++i) {
348
349
        if(context->updateContextState())
            forcesAreValid = false;
350
        for(auto &nhc : noseHooverChains) {
351
            kineticEnergy = nhcKernel.getAs<NoseHooverChainKernel>().computeMaskedKineticEnergy(*context, nhc, false);
352
353
            scale = nhcKernel.getAs<NoseHooverChainKernel>().propagateChain(*context, nhc, kineticEnergy, getStepSize());
            nhcKernel.getAs<NoseHooverChainKernel>().scaleVelocities(*context, nhc, scale);
354
        }
355
        vvKernel.getAs<IntegrateVelocityVerletStepKernel>().execute(*context, *this, forcesAreValid);
356
        for(auto &nhc : noseHooverChains) {
357
            kineticEnergy = nhcKernel.getAs<NoseHooverChainKernel>().computeMaskedKineticEnergy(*context, nhc, false);
358
359
            scale = nhcKernel.getAs<NoseHooverChainKernel>().propagateChain(*context, nhc, kineticEnergy, getStepSize());
            nhcKernel.getAs<NoseHooverChainKernel>().scaleVelocities(*context, nhc, scale);
360
        }
361
362
    }
}