MonteCarloAnisotropicBarostatImpl.cpp 9.75 KB
Newer Older
1
2
3
/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
Evan Pretti's avatar
Evan Pretti committed
4
5
 * This is part of the OpenMM molecular simulation toolkit.                   *
 * See https://openmm.org/development.                                        *
6
 *                                                                            *
7
 * Portions copyright (c) 2010-2026 Stanford University and the Authors.      *
8
 * Authors: Peter Eastman, Lee-Ping Wang                                      *
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 * 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.                                     *
 * -------------------------------------------------------------------------- */

#include "openmm/internal/MonteCarloAnisotropicBarostatImpl.h"
#include "openmm/internal/ContextImpl.h"
32
#include "openmm/internal/OSRngSeed.h"
33
34
#include "openmm/Context.h"
#include "openmm/kernels.h"
35
#include "openmm/OpenMMException.h"
36
#include "SimTKOpenMMUtilities.h"
37
38
#include <cmath>
#include <vector>
39
#include <algorithm>
40
41

using namespace OpenMM;
42
using namespace std;
43
44
45
46
47

MonteCarloAnisotropicBarostatImpl::MonteCarloAnisotropicBarostatImpl(const MonteCarloAnisotropicBarostat& owner) : owner(owner), step(0) {
}

void MonteCarloAnisotropicBarostatImpl::initialize(ContextImpl& context) {
48
49
    if (!context.getSystem().usesPeriodicBoundaryConditions())
        throw OpenMMException("A barostat cannot be used with a non-periodic system");
50
    kernel = context.getPlatform().createKernel(ApplyMonteCarloBarostatKernel::Name(), context);
51
    kernel.getAs<ApplyMonteCarloBarostatKernel>().initialize(context.getSystem(), owner, 3, owner.getScaleMoleculesAsRigid());
52
53
54
55
56
57
58
59
    Vec3 box[3];
    context.getPeriodicBoxVectors(box[0], box[1], box[2]);
    double volume = box[0][0]*box[1][1]*box[2][2];
    for (int i=0; i<3; i++) {
        volumeScale[i] = 0.01*volume;
        numAttempted[i] = 0;
        numAccepted[i] = 0;
    }
60
    SimTKOpenMMUtilities::setRandomNumberSeed(owner.getRandomNumberSeed());
61
62
}

63
void MonteCarloAnisotropicBarostatImpl::updateContextState(ContextImpl& context, bool& forcesInvalid) {
64
65
    if (++step < owner.getFrequency() || owner.getFrequency() == 0)
        return;
66
    if (!owner.getScaleX() && !owner.getScaleY() && !owner.getScaleZ())
67
68
69
70
71
        return;
    step = 0;
    
    // Compute the current potential energy.
    
72
73
    int groups = context.getIntegrator().getIntegrationForceGroups();
    double initialEnergy = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();
74
75
76
77
    double pressure;
    
    // Choose which axis to modify at random.
    int axis;
78
    while (true) {
79
        double rnd = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber()*3.0;
80
81
82
83
84
85
86
87
88
89
90
91
        if (rnd < 1.0) {
            if (owner.getScaleX()) {
                axis = 0;
                pressure = context.getParameter(MonteCarloAnisotropicBarostat::PressureX())*(AVOGADRO*1e-25);
                break;
            }
        } else if (rnd < 2.0) {
            if (owner.getScaleY()) {
                axis = 1;
                pressure = context.getParameter(MonteCarloAnisotropicBarostat::PressureY())*(AVOGADRO*1e-25);
                break;
            }
92
93
94
95
96
97
98
99
100
101
102
103
        } else if (owner.getScaleZ()) {
            axis = 2;
            pressure = context.getParameter(MonteCarloAnisotropicBarostat::PressureZ())*(AVOGADRO*1e-25);
            break;
        }
    }
    
    // Modify the periodic box size.
    
    Vec3 box[3];
    context.getPeriodicBoxVectors(box[0], box[1], box[2]);
    double volume = box[0][0]*box[1][1]*box[2][2];
104
    double deltaVolume = volumeScale[axis]*2*(SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber()-0.5);
105
    double newVolume = volume+deltaVolume;
106
    Vec3 lengthScale(1.0, 1.0, 1.0);
107
    lengthScale[axis] = newVolume/volume;
108
    kernel.getAs<ApplyMonteCarloBarostatKernel>().saveCoordinates(context);
109
110
111
    context.getOwner().setPeriodicBoxVectors(Vec3(box[0][0]*lengthScale[0], box[0][1]*lengthScale[1], box[0][2]*lengthScale[2]),
                                             Vec3(box[1][0]*lengthScale[0], box[1][1]*lengthScale[1], box[1][2]*lengthScale[2]),
                                             Vec3(box[2][0]*lengthScale[0], box[2][1]*lengthScale[1], box[2][2]*lengthScale[2]));
112
    kernel.getAs<ApplyMonteCarloBarostatKernel>().scaleCoordinates(context, lengthScale[0], lengthScale[1], lengthScale[2]);
113

114
    // Compute the energy of the modified system.
115
116
117
118
119
120

    double numberOfScaledParticles;
    if (owner.getScaleMoleculesAsRigid())
        numberOfScaledParticles = context.getMolecules().size();
    else
        numberOfScaledParticles = context.getSystem().getNumParticles();
121
    double finalEnergy = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();
122
    double kT = BOLTZ*context.getParameter(MonteCarloAnisotropicBarostat::Temperature());
123
    double w = finalEnergy-initialEnergy + pressure*deltaVolume - numberOfScaledParticles*kT*log(newVolume/volume);
124
    if (w > 0 && SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber() > exp(-w/kT)) {
125
126
127
        // Reject the step.
        
        context.getOwner().setPeriodicBoxVectors(box[0], box[1], box[2]);
128
        kernel.getAs<ApplyMonteCarloBarostatKernel>().restoreCoordinates(context);
129
    }
130
    else {
131
        numAccepted[axis]++;
132
133
        forcesInvalid = true;
    }
134
135
136
137
138
139
140
141
    numAttempted[axis]++;
    if (numAttempted[axis] >= 10) {
        if (numAccepted[axis] < 0.25*numAttempted[axis]) {
            volumeScale[axis] /= 1.1;
            numAttempted[axis] = 0;
            numAccepted[axis] = 0;
        }
        else if (numAccepted[axis] > 0.75*numAttempted[axis]) {
142
            volumeScale[axis] = min(volumeScale[axis]*1.1, volume*0.3);
143
144
145
146
147
148
            numAttempted[axis] = 0;
            numAccepted[axis] = 0;
        }
    }
}

149
map<string, double> MonteCarloAnisotropicBarostatImpl::getDefaultParameters() {
Peter Eastman's avatar
Peter Eastman committed
150
151
152
153
    return {{MonteCarloAnisotropicBarostat::PressureX(), getOwner().getDefaultPressure()[0]},
            {MonteCarloAnisotropicBarostat::PressureY(), getOwner().getDefaultPressure()[1]},
            {MonteCarloAnisotropicBarostat::PressureZ(), getOwner().getDefaultPressure()[2]},
            {MonteCarloAnisotropicBarostat::Temperature(), getOwner().getDefaultTemperature()}};
154
155
}

156
vector<string> MonteCarloAnisotropicBarostatImpl::getKernelNames() {
Peter Eastman's avatar
Peter Eastman committed
157
    return {ApplyMonteCarloBarostatKernel::Name()};
158
159
}

160
161
162
163
164
165
166
167
168
169
170
171
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
Vec3 MonteCarloAnisotropicBarostatImpl::computeCurrentPressure(ContextImpl& context) {
    Vec3 box[3];
    context.getPeriodicBoxVectors(box[0], box[1], box[2]);
    double volume = box[0][0]*box[1][1]*box[2][2];
    double delta = 1e-3;
    int groups = context.getIntegrator().getIntegrationForceGroups();
    kernel.getAs<ApplyMonteCarloBarostatKernel>().saveCoordinates(context);
    vector<double> ke;
    kernel.getAs<ApplyMonteCarloBarostatKernel>().computeKineticEnergy(context, ke);
    double deltaVolume = volume*2*delta;
    Vec3 pressure;

    // Compute the pressure along each axis.

    for (int axis = 0; axis < 3; axis++) {
        // Compute the first energy.

        Vec3 scale1(1, 1, 1);
        scale1[axis] = 1.0+delta;
        context.getOwner().setPeriodicBoxVectors(box[0]*scale1[0], box[1]*scale1[1], box[2]*scale1[2]);
        kernel.getAs<ApplyMonteCarloBarostatKernel>().scaleCoordinates(context, scale1[0], scale1[1], scale1[2]);
        double energy1 = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();

        // Compute the second energy.

        Vec3 scale2(1, 1, 1);
        scale2[axis] = 1.0-delta;
        context.getOwner().setPeriodicBoxVectors(box[0]*scale2[0], box[1]*scale2[1], box[2]*scale2[2]);
        kernel.getAs<ApplyMonteCarloBarostatKernel>().scaleCoordinates(context, scale2[0]/scale1[0], scale2[1]/scale1[1], scale2[2]/scale1[2]);
        double energy2 = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();

        // Reset the box shape.

        context.getOwner().setPeriodicBoxVectors(box[0], box[1], box[2]);
        kernel.getAs<ApplyMonteCarloBarostatKernel>().scaleCoordinates(context, 1/scale2[0], 1/scale2[1], 1/scale2[2]);

        // Compute the pressure.

        pressure[axis] = (2.0*ke[axis]/volume - (energy1-energy2)/deltaVolume)/(AVOGADRO*1e-25);
    }

    // Restore the context to its original state.

    kernel.getAs<ApplyMonteCarloBarostatKernel>().restoreCoordinates(context);
    return pressure;
}