MonteCarloMembraneBarostatImpl.cpp 9.62 KB
Newer Older
peastman's avatar
peastman committed
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.                                        *
peastman's avatar
peastman committed
6
 *                                                                            *
7
 * Portions copyright (c) 2010-2025 Stanford University and the Authors.      *
peastman's avatar
peastman committed
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 * Authors: Peter Eastman, Lee-Ping Wang                                      *
 * 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/MonteCarloMembraneBarostatImpl.h"
#include "openmm/internal/ContextImpl.h"
32
#include "openmm/internal/OSRngSeed.h"
peastman's avatar
peastman committed
33
34
#include "openmm/Context.h"
#include "openmm/kernels.h"
35
#include "openmm/OpenMMException.h"
36
#include "SimTKOpenMMUtilities.h"
peastman's avatar
peastman committed
37
38
39
40
41
#include <cmath>
#include <vector>
#include <algorithm>

using namespace OpenMM;
42
using namespace std;
peastman's avatar
peastman committed
43
44
45
46
47

MonteCarloMembraneBarostatImpl::MonteCarloMembraneBarostatImpl(const MonteCarloMembraneBarostat& owner) : owner(owner), step(0) {
}

void MonteCarloMembraneBarostatImpl::initialize(ContextImpl& context) {
48
49
    if (!context.getSystem().usesPeriodicBoundaryConditions())
        throw OpenMMException("A barostat cannot be used with a non-periodic system");
peastman's avatar
peastman committed
50
    kernel = context.getPlatform().createKernel(ApplyMonteCarloBarostatKernel::Name(), context);
51
    kernel.getAs<ApplyMonteCarloBarostatKernel>().initialize(context.getSystem(), owner, 3);
peastman's avatar
peastman committed
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());
peastman's avatar
peastman committed
61
62
}

63
void MonteCarloMembraneBarostatImpl::updateContextState(ContextImpl& context, bool& forcesInvalid) {
peastman's avatar
peastman committed
64
65
66
67
68
69
    if (++step < owner.getFrequency() || owner.getFrequency() == 0)
        return;
    step = 0;
    
    // Compute the current potential energy.
    
70
71
    int groups = context.getIntegrator().getIntegrationForceGroups();
    double initialEnergy = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();
peastman's avatar
peastman committed
72
73
74
75
76
77
    double pressure = context.getParameter(MonteCarloMembraneBarostat::Pressure())*(AVOGADRO*1e-25);
    double tension = context.getParameter(MonteCarloMembraneBarostat::SurfaceTension())*(AVOGADRO*1e-25);
    
    // Choose which axis to modify at random.
    int axis;
    while (true) {
78
        double rnd = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber()*3.0;
peastman's avatar
peastman committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
        if (rnd < 1.0) {
            axis = 0;
            break;
        } else if (rnd < 2.0) {
            axis = (owner.getXYMode() == MonteCarloMembraneBarostat::XYIsotropic ? 0 : 1);
            break;
        } else if (owner.getZMode() == MonteCarloMembraneBarostat::ZFree) {
            axis = 2;
            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];
96
    double deltaVolume = volumeScale[axis]*2*(SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber()-0.5);
peastman's avatar
peastman committed
97
98
99
100
101
102
103
104
105
106
107
108
    double newVolume = volume+deltaVolume;
    Vec3 lengthScale(1.0, 1.0, 1.0);
    if ((axis == 0 || axis == 1) && owner.getXYMode() == MonteCarloMembraneBarostat::XYIsotropic)
        lengthScale[0] = lengthScale[1] = sqrt(newVolume/volume);
    else
        lengthScale[axis] = newVolume/volume;
    if (owner.getZMode() == MonteCarloMembraneBarostat::ConstantVolume) {
        lengthScale[2] = 1.0/(lengthScale[0]*lengthScale[1]);
        newVolume = volume;
        deltaVolume = 0;
    }
    double deltaArea = box[0][0]*lengthScale[0]*box[1][1]*lengthScale[1] - box[0][0]*box[1][1];
109
    kernel.getAs<ApplyMonteCarloBarostatKernel>().saveCoordinates(context);
110
111
112
    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]));
113
114
    kernel.getAs<ApplyMonteCarloBarostatKernel>().scaleCoordinates(context, lengthScale[0], lengthScale[1], lengthScale[2]);

peastman's avatar
peastman committed
115
    // Compute the energy of the modified system.
116

117
    double finalEnergy = context.getOwner().getState(State::Energy, false, groups).getPotentialEnergy();
118
    double kT = BOLTZ*context.getParameter(MonteCarloMembraneBarostat::Temperature());
119
120
    double w = finalEnergy-initialEnergy + pressure*deltaVolume - tension*deltaArea - context.getMolecules().size()*kT*log(newVolume/volume);
    if (w > 0 && SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber() > exp(-w/kT)) {
peastman's avatar
peastman committed
121
122
123
        // Reject the step.
        
        context.getOwner().setPeriodicBoxVectors(box[0], box[1], box[2]);
124
        kernel.getAs<ApplyMonteCarloBarostatKernel>().restoreCoordinates(context);
peastman's avatar
peastman committed
125
    }
126
    else {
peastman's avatar
peastman committed
127
        numAccepted[axis]++;
128
129
        forcesInvalid = true;
    }
peastman's avatar
peastman committed
130
131
132
133
134
135
136
137
    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]) {
138
            volumeScale[axis] = min(volumeScale[axis]*1.1, volume*0.3);
peastman's avatar
peastman committed
139
140
141
142
143
144
            numAttempted[axis] = 0;
            numAccepted[axis] = 0;
        }
    }
}

145
map<string, double> MonteCarloMembraneBarostatImpl::getDefaultParameters() {
Peter Eastman's avatar
Peter Eastman committed
146
147
148
    return {{MonteCarloMembraneBarostat::Pressure(), getOwner().getDefaultPressure()},
            {MonteCarloMembraneBarostat::SurfaceTension(), getOwner().getDefaultSurfaceTension()},
            {MonteCarloMembraneBarostat::Temperature(), getOwner().getDefaultTemperature()}};
peastman's avatar
peastman committed
149
150
}

151
vector<string> MonteCarloMembraneBarostatImpl::getKernelNames() {
Peter Eastman's avatar
Peter Eastman committed
152
    return {ApplyMonteCarloBarostatKernel::Name()};
peastman's avatar
peastman committed
153
}
154
155
156
157
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

Vec3 MonteCarloMembraneBarostatImpl::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;
}