NonbondedForce.cpp 16 KB
Newer Older
1
2
3
4
5
6
7
8
/* -------------------------------------------------------------------------- *
 *                                   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.               *
 *                                                                            *
9
 * Portions copyright (c) 2008-2024 Stanford University and the Authors.      *
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                                                     *
 * 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
33
34
#include "openmm/Force.h"
#include "openmm/OpenMMException.h"
#include "openmm/NonbondedForce.h"
35
#include "openmm/internal/AssertionUtilities.h"
36
#include "openmm/internal/NonbondedForceImpl.h"
37
#include <cmath>
38
39
#include <map>
#include <sstream>
40
#include <utility>
41
42

using namespace OpenMM;
43
using namespace std;
44

45
NonbondedForce::NonbondedForce() : nonbondedMethod(NoCutoff), cutoffDistance(1.0), switchingDistance(-1.0), rfDielectric(78.3),
46
        ewaldErrorTol(5e-4), alpha(0.0), dalpha(0.0), useSwitchingFunction(false), useDispersionCorrection(true), exceptionsUsePeriodic(false), recipForceGroup(-1),
47
        includeDirectSpace(true), nx(0), ny(0), nz(0), dnx(0), dny(0), dnz(0), numContexts(0) {
48
49
50
51
52
53
54
}

NonbondedForce::NonbondedMethod NonbondedForce::getNonbondedMethod() const {
    return nonbondedMethod;
}

void NonbondedForce::setNonbondedMethod(NonbondedMethod method) {
55
56
    if (method < 0 || method > 5)
        throw OpenMMException("NonbondedForce: Illegal value for nonbonded method");
57
58
59
60
61
62
63
64
65
66
67
    nonbondedMethod = method;
}

double NonbondedForce::getCutoffDistance() const {
    return cutoffDistance;
}

void NonbondedForce::setCutoffDistance(double distance) {
    cutoffDistance = distance;
}

68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
bool NonbondedForce::getUseSwitchingFunction() const {
    return useSwitchingFunction;
}

void NonbondedForce::setUseSwitchingFunction(bool use) {
    useSwitchingFunction = use;
}

double NonbondedForce::getSwitchingDistance() const {
    return switchingDistance;
}

void NonbondedForce::setSwitchingDistance(double distance) {
    switchingDistance = distance;
}

84
85
86
87
88
89
90
91
double NonbondedForce::getReactionFieldDielectric() const {
    return rfDielectric;
}

void NonbondedForce::setReactionFieldDielectric(double dielectric) {
    rfDielectric = dielectric;
}

92
93
94
95
double NonbondedForce::getEwaldErrorTolerance() const {
    return ewaldErrorTol;
}

96
void NonbondedForce::setEwaldErrorTolerance(double tol) {
97
98
99
    ewaldErrorTol = tol;
}

100
101
102
103
104
105
106
void NonbondedForce::getPMEParameters(double& alpha, int& nx, int& ny, int& nz) const {
    alpha = this->alpha;
    nx = this->nx;
    ny = this->ny;
    nz = this->nz;
}

107
108
109
110
111
void NonbondedForce::getLJPMEParameters(double& alpha, int& nx, int& ny, int& nz) const {
    alpha = this->dalpha;
    nx = this->dnx;
    ny = this->dny;
    nz = this->dnz;
112
113
}

114
115
116
117
118
119
120
void NonbondedForce::setPMEParameters(double alpha, int nx, int ny, int nz) {
    this->alpha = alpha;
    this->nx = nx;
    this->ny = ny;
    this->nz = nz;
}

121
122
123
124
125
void NonbondedForce::setLJPMEParameters(double alpha, int nx, int ny, int nz) {
    this->dalpha = alpha;
    this->dnx = nx;
    this->dny = ny;
    this->dnz = nz;
126
127
}

128
129
130
131
void NonbondedForce::getPMEParametersInContext(const Context& context, double& alpha, int& nx, int& ny, int& nz) const {
    dynamic_cast<const NonbondedForceImpl&>(getImplInContext(context)).getPMEParameters(alpha, nx, ny, nz);
}

132
133
void NonbondedForce::getLJPMEParametersInContext(const Context& context, double& alpha, int& nx, int& ny, int& nz) const {
    dynamic_cast<const NonbondedForceImpl&>(getImplInContext(context)).getLJPMEParameters(alpha, nx, ny, nz);
134
135
}

136
int NonbondedForce::addParticle(double charge, double sigma, double epsilon) {
137
    particles.push_back(ParticleInfo(charge, sigma, epsilon));
138
    return particles.size()-1;
139
140
}

141
void NonbondedForce::getParticleParameters(int index, double& charge, double& sigma, double& epsilon) const {
142
    ASSERT_VALID_INDEX(index, particles);
Peter Eastman's avatar
Peter Eastman committed
143
    charge = particles[index].charge;
144
145
    sigma = particles[index].sigma;
    epsilon = particles[index].epsilon;
146
147
}

148
void NonbondedForce::setParticleParameters(int index, double charge, double sigma, double epsilon) {
149
    ASSERT_VALID_INDEX(index, particles);
Peter Eastman's avatar
Peter Eastman committed
150
    particles[index].charge = charge;
151
152
    particles[index].sigma = sigma;
    particles[index].epsilon = epsilon;
153
154
155
156
    if (numContexts > 0) {
        firstChangedParticle = min(index, firstChangedParticle);
        lastChangedParticle = max(index, lastChangedParticle);
    }}
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
int NonbondedForce::addException(int particle1, int particle2, double chargeProd, double sigma, double epsilon, bool replace) {
    map<pair<int, int>, int>::iterator iter = exceptionMap.find(pair<int, int>(particle1, particle2));
    int newIndex;
    if (iter == exceptionMap.end())
        iter = exceptionMap.find(pair<int, int>(particle2, particle1));
    if (iter != exceptionMap.end()) {
        if (!replace) {
            stringstream msg;
            msg << "NonbondedForce: There is already an exception for particles ";
            msg << particle1;
            msg << " and ";
            msg << particle2;
            throw OpenMMException(msg.str());
        }
        exceptions[iter->second] = ExceptionInfo(particle1, particle2, chargeProd, sigma, epsilon);
        newIndex = iter->second;
        exceptionMap.erase(iter->first);
    }
    else {
        exceptions.push_back(ExceptionInfo(particle1, particle2, chargeProd, sigma, epsilon));
        newIndex = exceptions.size()-1;
    }
    exceptionMap[pair<int, int>(particle1, particle2)] = newIndex;
    return newIndex;
182
183
}
void NonbondedForce::getExceptionParameters(int index, int& particle1, int& particle2, double& chargeProd, double& sigma, double& epsilon) const {
184
    ASSERT_VALID_INDEX(index, exceptions);
185
186
187
188
189
    particle1 = exceptions[index].particle1;
    particle2 = exceptions[index].particle2;
    chargeProd = exceptions[index].chargeProd;
    sigma = exceptions[index].sigma;
    epsilon = exceptions[index].epsilon;
190
191
}

192
void NonbondedForce::setExceptionParameters(int index, int particle1, int particle2, double chargeProd, double sigma, double epsilon) {
193
    ASSERT_VALID_INDEX(index, exceptions);
194
195
196
197
198
    exceptions[index].particle1 = particle1;
    exceptions[index].particle2 = particle2;
    exceptions[index].chargeProd = chargeProd;
    exceptions[index].sigma = sigma;
    exceptions[index].epsilon = epsilon;
199
200
201
202
    if (numContexts > 0) {
        firstChangedException = min(index, firstChangedException);
        lastChangedException = max(index, lastChangedException);
    }}
203

204
ForceImpl* NonbondedForce::createImpl() const {
205
206
207
208
209
210
211
212
    if (numContexts == 0) {
        // Begin tracking changes to particles and exceptions.
        firstChangedParticle = particles.size();
        lastChangedParticle = -1;
        firstChangedException = exceptions.size();
        lastChangedException = -1;
    }
    numContexts++;
213
214
    return new NonbondedForceImpl(*this);
}
215
216

void NonbondedForce::createExceptionsFromBonds(const vector<pair<int, int> >& bonds, double coulomb14Scale, double lj14Scale) {
peastman's avatar
peastman committed
217
218
    for (auto& bond : bonds)
        if (bond.first < 0 || bond.second < 0 || bond.first >= particles.size() || bond.second >= particles.size())
219
            throw OpenMMException("createExceptionsFromBonds: Illegal particle index in list of bonds");
220
221
222
223
224

    // Find particles separated by 1, 2, or 3 bonds.

    vector<set<int> > exclusions(particles.size());
    vector<set<int> > bonded12(exclusions.size());
peastman's avatar
peastman committed
225
226
227
    for (auto& bond : bonds) {
        bonded12[bond.first].insert(bond.second);
        bonded12[bond.second].insert(bond.first);
228
229
230
231
232
233
234
235
236
    }
    for (int i = 0; i < (int) exclusions.size(); ++i)
        addExclusionsToSet(bonded12, exclusions[i], i, i, 2);

    // Find particles separated by 1 or 2 bonds and create the exceptions.

    for (int i = 0; i < (int) exclusions.size(); ++i) {
        set<int> bonded13;
        addExclusionsToSet(bonded12, bonded13, i, i, 1);
peastman's avatar
peastman committed
237
238
239
        for (int j : exclusions[i]) {
            if (j < i) {
                if (bonded13.find(j) == bonded13.end()) {
240
241
                    // This is a 1-4 interaction.

peastman's avatar
peastman committed
242
                    const ParticleInfo& particle1 = particles[j];
243
244
245
246
                    const ParticleInfo& particle2 = particles[i];
                    const double chargeProd = coulomb14Scale*particle1.charge*particle2.charge;
                    const double sigma = 0.5*(particle1.sigma+particle2.sigma);
                    const double epsilon = lj14Scale*std::sqrt(particle1.epsilon*particle2.epsilon);
peastman's avatar
peastman committed
247
                    addException(j, i, chargeProd, sigma, epsilon);
248
249
250
251
                }
                else {
                    // This interaction should be completely excluded.

peastman's avatar
peastman committed
252
                    addException(j, i, 0.0, 1.0, 0.0);
253
254
                }
            }
peastman's avatar
peastman committed
255
        }
256
257
258
259
    }
}

void NonbondedForce::addExclusionsToSet(const vector<set<int> >& bonded12, set<int>& exclusions, int baseParticle, int fromParticle, int currentLevel) const {
peastman's avatar
peastman committed
260
261
262
    for (int i : bonded12[fromParticle]) {
        if (i != baseParticle)
            exclusions.insert(i);
263
        if (currentLevel > 0)
peastman's avatar
peastman committed
264
            addExclusionsToSet(bonded12, exclusions, baseParticle, i, currentLevel-1);
265
266
    }
}
267

268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
int NonbondedForce::addGlobalParameter(const string& name, double defaultValue) {
    globalParameters.push_back(GlobalParameterInfo(name, defaultValue));
    return globalParameters.size()-1;
}

const string& NonbondedForce::getGlobalParameterName(int index) const {
    ASSERT_VALID_INDEX(index, globalParameters);
    return globalParameters[index].name;
}

void NonbondedForce::setGlobalParameterName(int index, const string& name) {
    ASSERT_VALID_INDEX(index, globalParameters);
    globalParameters[index].name = name;
}

double NonbondedForce::getGlobalParameterDefaultValue(int index) const {
    ASSERT_VALID_INDEX(index, globalParameters);
    return globalParameters[index].defaultValue;
}

void NonbondedForce::setGlobalParameterDefaultValue(int index, double defaultValue) {
    ASSERT_VALID_INDEX(index, globalParameters);
    globalParameters[index].defaultValue = defaultValue;
}

int NonbondedForce::getGlobalParameterIndex(const std::string& parameter) const {
    for (int i = 0; i < globalParameters.size(); i++)
        if (globalParameters[i].name == parameter)
            return i;
    throw OpenMMException("There is no global parameter called '"+parameter+"'");
}

300
int NonbondedForce::addParticleParameterOffset(const std::string& parameter, int particleIndex, double chargeScale, double sigmaScale, double epsilonScale) {
301
    particleOffsets.push_back(ParticleOffsetInfo(getGlobalParameterIndex(parameter), particleIndex, chargeScale, sigmaScale, epsilonScale));
302
303
304
305
306
    return particleOffsets.size()-1;
}

void NonbondedForce::getParticleParameterOffset(int index, std::string& parameter, int& particleIndex, double& chargeScale, double& sigmaScale, double& epsilonScale) const {
    ASSERT_VALID_INDEX(index, particleOffsets);
307
    parameter = globalParameters[particleOffsets[index].parameter].name;
308
309
310
311
312
313
314
315
    particleIndex = particleOffsets[index].particle;
    chargeScale = particleOffsets[index].chargeScale;
    sigmaScale = particleOffsets[index].sigmaScale;
    epsilonScale = particleOffsets[index].epsilonScale;
}

void NonbondedForce::setParticleParameterOffset(int index, const std::string& parameter, int particleIndex, double chargeScale, double sigmaScale, double epsilonScale) {
    ASSERT_VALID_INDEX(index, particleOffsets);
316
    particleOffsets[index].parameter = getGlobalParameterIndex(parameter);
317
318
319
320
321
322
323
    particleOffsets[index].particle = particleIndex;
    particleOffsets[index].chargeScale = chargeScale;
    particleOffsets[index].sigmaScale = sigmaScale;
    particleOffsets[index].epsilonScale = epsilonScale;
}

int NonbondedForce::addExceptionParameterOffset(const std::string& parameter, int exceptionIndex, double chargeProdScale, double sigmaScale, double epsilonScale) {
324
    exceptionOffsets.push_back(ExceptionOffsetInfo(getGlobalParameterIndex(parameter), exceptionIndex, chargeProdScale, sigmaScale, epsilonScale));
325
326
327
328
329
    return exceptionOffsets.size()-1;
}

void NonbondedForce::getExceptionParameterOffset(int index, std::string& parameter, int& exceptionIndex, double& chargeProdScale, double& sigmaScale, double& epsilonScale) const {
    ASSERT_VALID_INDEX(index, exceptionOffsets);
330
    parameter = globalParameters[exceptionOffsets[index].parameter].name;
331
332
333
334
335
336
337
338
    exceptionIndex = exceptionOffsets[index].exception;
    chargeProdScale = exceptionOffsets[index].chargeProdScale;
    sigmaScale = exceptionOffsets[index].sigmaScale;
    epsilonScale = exceptionOffsets[index].epsilonScale;
}

void NonbondedForce::setExceptionParameterOffset(int index, const std::string& parameter, int exceptionIndex, double chargeProdScale, double sigmaScale, double epsilonScale) {
    ASSERT_VALID_INDEX(index, exceptionOffsets);
339
    exceptionOffsets[index].parameter = getGlobalParameterIndex(parameter);
340
341
342
343
344
345
    exceptionOffsets[index].exception = exceptionIndex;
    exceptionOffsets[index].chargeProdScale = chargeProdScale;
    exceptionOffsets[index].sigmaScale = sigmaScale;
    exceptionOffsets[index].epsilonScale = epsilonScale;
}

346
347
348
349
350
int NonbondedForce::getReciprocalSpaceForceGroup() const {
    return recipForceGroup;
}

void NonbondedForce::setReciprocalSpaceForceGroup(int group) {
351
352
    if (group < -1 || group > 31)
        throw OpenMMException("Force group must be between -1 and 31");
353
354
    recipForceGroup = group;
}
355

356
357
358
359
360
361
362
363
bool NonbondedForce::getIncludeDirectSpace() const {
    return includeDirectSpace;
}

void NonbondedForce::setIncludeDirectSpace(bool include) {
    includeDirectSpace = include;
}

364
void NonbondedForce::updateParametersInContext(Context& context) {
365
366
367
368
369
370
371
372
373
374
    dynamic_cast<NonbondedForceImpl&>(getImplInContext(context)).updateParametersInContext(getContextImpl(context),
            firstChangedParticle, lastChangedParticle, firstChangedException, lastChangedException);
    if (numContexts == 1) {
        // We just updated the only existing context for this force, so we can reset
        // the tracking of changed particles and exceptions.
        firstChangedParticle = particles.size();
        lastChangedParticle = -1;
        firstChangedException = exceptions.size();
        lastChangedException = -1;
    }
375
}
376
377
378
379
380
381
382
383

bool NonbondedForce::getExceptionsUsePeriodicBoundaryConditions() const {
    return exceptionsUsePeriodic;
}

void NonbondedForce::setExceptionsUsePeriodicBoundaryConditions(bool periodic) {
    exceptionsUsePeriodic = periodic;
}