ReferenceCustomGBIxn.cpp 21.4 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

/* Portions copyright (c) 2009 Stanford University and Simbios.
 * Contributors: Peter Eastman
 *
 * 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 <string.h>
#include <sstream>

#include "../SimTKUtilities/SimTKOpenMMCommon.h"
#include "../SimTKUtilities/SimTKOpenMMLog.h"
#include "../SimTKUtilities/SimTKOpenMMUtilities.h"
#include "ReferenceForce.h"
#include "ReferenceCustomGBIxn.h"

using std::map;
using std::set;
using std::string;
using std::stringstream;
using std::vector;

/**---------------------------------------------------------------------------------------

   ReferenceCustomGBIxn constructor

   --------------------------------------------------------------------------------------- */

ReferenceCustomGBIxn::ReferenceCustomGBIxn(const vector<Lepton::ExpressionProgram>& valueExpressions,
47
                     const vector<vector<Lepton::ExpressionProgram> > valueDerivExpressions,
48
                     const vector<vector<Lepton::ExpressionProgram> > valueGradientExpressions,
49
50
51
52
                     const vector<string>& valueNames,
                     const vector<OpenMM::CustomGBForce::ComputationType>& valueTypes,
                     const vector<Lepton::ExpressionProgram>& energyExpressions,
                     const vector<vector<Lepton::ExpressionProgram> > energyDerivExpressions,
53
                     const vector<vector<Lepton::ExpressionProgram> > energyGradientExpressions,
54
55
                     const vector<OpenMM::CustomGBForce::ComputationType>& energyTypes,
                     const vector<string>& parameterNames) :
56
57
            cutoff(false), periodic(false), valueExpressions(valueExpressions), valueDerivExpressions(valueDerivExpressions), valueGradientExpressions(valueGradientExpressions),
            valueNames(valueNames), valueTypes(valueTypes), energyExpressions(energyExpressions), energyDerivExpressions(energyDerivExpressions), energyGradientExpressions(energyGradientExpressions),
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
            energyTypes(energyTypes), paramNames(parameterNames) {

   // ---------------------------------------------------------------------------------------

   // static const char* methodName = "\nReferenceCustomGBIxn::ReferenceCustomGBIxn";

   // ---------------------------------------------------------------------------------------

    for (int i = 0; i < (int) paramNames.size(); i++) {
        for (int j = 1; j < 3; j++) {
            stringstream name;
            name << paramNames[i] << j;
            particleParamNames.push_back(name.str());
        }
    }
    for (int i = 0; i < (int) valueNames.size(); i++) {
        for (int j = 1; j < 3; j++) {
            stringstream name;
            name << valueNames[i] << j;
            particleValueNames.push_back(name.str());
        }
    }
}

/**---------------------------------------------------------------------------------------

   ReferenceCustomGBIxn destructor

   --------------------------------------------------------------------------------------- */

ReferenceCustomGBIxn::~ReferenceCustomGBIxn( ){

   // ---------------------------------------------------------------------------------------

   // static const char* methodName = "\nReferenceCustomGBIxn::~ReferenceCustomGBIxn";

   // ---------------------------------------------------------------------------------------

}

  /**---------------------------------------------------------------------------------------

     Set the force to use a cutoff.

     @param distance            the cutoff distance
     @param neighbors           the neighbor list to use

     --------------------------------------------------------------------------------------- */

107
  void ReferenceCustomGBIxn::setUseCutoff( RealOpenMM distance, const OpenMM::NeighborList& neighbors ) {
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123

    cutoff = true;
    cutoffDistance = distance;
    neighborList = &neighbors;
  }

  /**---------------------------------------------------------------------------------------

     Set the force to use periodic boundary conditions.  This requires that a cutoff has
     also been set, and the smallest side of the periodic box is at least twice the cutoff
     distance.

     @param boxSize             the X, Y, and Z widths of the periodic box

     --------------------------------------------------------------------------------------- */

124
  void ReferenceCustomGBIxn::setPeriodic( RealOpenMM* boxSize ) {
125
126
127
128
129
130
131
132
133
134

    assert(boxSize[0] >= 2.0*cutoffDistance);
    assert(boxSize[1] >= 2.0*cutoffDistance);
    assert(boxSize[2] >= 2.0*cutoffDistance);
    periodic = true;
    periodicBoxSize[0] = boxSize[0];
    periodicBoxSize[1] = boxSize[1];
    periodicBoxSize[2] = boxSize[2];
  }

135
void ReferenceCustomGBIxn::calculateIxn(int numberOfAtoms, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
136
                                           const vector<set<int> >& exclusions, map<string, double>& globalParameters, RealOpenMM** forces,
137
138
139
                                           RealOpenMM* totalEnergy) const {
    // First calculate the computed values.

140
    int numValues = valueTypes.size();
141
    vector<vector<RealOpenMM> > values(numValues);
142
143
    for (int valueIndex = 0; valueIndex < numValues; valueIndex++) {
        if (valueTypes[valueIndex] == OpenMM::CustomGBForce::SingleParticle)
144
            calculateSingleParticleValue(valueIndex, numberOfAtoms, atomCoordinates, values, globalParameters, atomParameters);
145
146
147
148
149
        else if (valueTypes[valueIndex] == OpenMM::CustomGBForce::ParticlePair)
            calculateParticlePairValue(valueIndex, numberOfAtoms, atomCoordinates, atomParameters, values, globalParameters, exclusions, true);
        else
            calculateParticlePairValue(valueIndex, numberOfAtoms, atomCoordinates, atomParameters, values, globalParameters, exclusions, false);
    }
150
151
152
153

    // Now calculate the energy and its derivates.

    vector<vector<RealOpenMM> > dEdV(numValues, vector<RealOpenMM>(numberOfAtoms, (RealOpenMM) 0));
154
155
    for (int termIndex = 0; termIndex < (int) energyExpressions.size(); termIndex++) {
        if (energyTypes[termIndex] == OpenMM::CustomGBForce::SingleParticle)
156
            calculateSingleParticleEnergyTerm(termIndex, numberOfAtoms, atomCoordinates, values, globalParameters, atomParameters, forces, totalEnergy, dEdV);
157
        else if (energyTypes[termIndex] == OpenMM::CustomGBForce::ParticlePair)
158
            calculateParticlePairEnergyTerm(termIndex, numberOfAtoms, atomCoordinates, atomParameters, values, globalParameters, exclusions, true, forces, totalEnergy, dEdV);
159
        else
160
            calculateParticlePairEnergyTerm(termIndex, numberOfAtoms, atomCoordinates, atomParameters, values, globalParameters, exclusions, false, forces, totalEnergy, dEdV);
161
    }
162
163
164
165

    // Apply the chain rule to evaluate forces.

    calculateChainRuleForces(numberOfAtoms, atomCoordinates, atomParameters, values, globalParameters, exclusions, forces, dEdV);
166
167
}

168
void ReferenceCustomGBIxn::calculateSingleParticleValue(int index, int numAtoms, RealOpenMM** atomCoordinates, vector<vector<RealOpenMM> >& values,
169
170
171
172
        const map<string, double>& globalParameters, RealOpenMM** atomParameters) const {
    values[index].resize(numAtoms);
    map<string, double> variables = globalParameters;
    for (int i = 0; i < numAtoms; i++) {
173
174
175
        variables["x"] = atomCoordinates[i][0];
        variables["y"] = atomCoordinates[i][1];
        variables["z"] = atomCoordinates[i][2];
176
177
178
        for (int j = 0; j < (int) paramNames.size(); j++)
            variables[paramNames[j]] = atomParameters[i][j];
        for (int j = 0; j < index; j++)
179
180
            variables[valueNames[j]] = values[j][i];
        values[index][i] = (RealOpenMM) valueExpressions[index].evaluate(variables);
181
182
183
184
    }
}

void ReferenceCustomGBIxn::calculateParticlePairValue(int index, int numAtoms, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
185
        vector<vector<RealOpenMM> >& values, const map<string, double>& globalParameters, const vector<set<int> >& exclusions, bool useExclusions) const {
186
187
    values[index].resize(numAtoms);
    for (int i = 0; i < numAtoms; i++)
188
        values[index][i] = (RealOpenMM) 0.0;
189
    if (cutoff) {
190
191
        // Loop over all pairs in the neighbor list.

192
193
194
195
196
197
198
199
200
        for (int i = 0; i < (int) neighborList->size(); i++) {
            OpenMM::AtomPair pair = (*neighborList)[i];
            if (useExclusions && exclusions[pair.first].find(pair.second) != exclusions[pair.first].end())
                continue;
            calculateOnePairValue(index, pair.first, pair.second, atomCoordinates, atomParameters, globalParameters, values);
            calculateOnePairValue(index, pair.second, pair.first, atomCoordinates, atomParameters, globalParameters, values);
        }
    }
    else {
201
202
        // Perform an O(N^2) loop over all atom pairs.

203
204
205
206
207
208
209
210
211
212
213
214
        for (int i = 0; i < numAtoms; i++){
            for (int j = i+1; j < numAtoms; j++ ){
                if (useExclusions && exclusions[i].find(j) != exclusions[i].end())
                    continue;
                calculateOnePairValue(index, i, j, atomCoordinates, atomParameters, globalParameters, values);
                calculateOnePairValue(index, j, i, atomCoordinates, atomParameters, globalParameters, values);
           }
        }
    }
}

void ReferenceCustomGBIxn::calculateOnePairValue(int index, int atom1, int atom2, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
215
        const map<string, double>& globalParameters, vector<vector<RealOpenMM> >& values) const {
216
217
218
219
220
221
222
223
224
    RealOpenMM deltaR[ReferenceForce::LastDeltaRIndex];
    if (periodic)
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[atom2], atomCoordinates[atom1], periodicBoxSize, deltaR);
    else
        ReferenceForce::getDeltaR(atomCoordinates[atom2], atomCoordinates[atom1], deltaR);
    RealOpenMM r = deltaR[ReferenceForce::RIndex];
    if (cutoff && r >= cutoffDistance)
        return;
    map<string, double> variables = globalParameters;
225
    for (int i = 0; i < (int) paramNames.size(); i++) {
226
227
228
229
230
        variables[particleParamNames[i*2]] = atomParameters[atom1][i];
        variables[particleParamNames[i*2+1]] = atomParameters[atom2][i];
    }
    variables["r"] = r;
    for (int i = 0; i < index; i++) {
231
232
        variables[particleValueNames[i*2]] = values[i][atom1];
        variables[particleValueNames[i*2+1]] = values[i][atom2];
233
    }
234
    values[index][atom1] += (RealOpenMM) valueExpressions[index].evaluate(variables);
235
236
}

237
238
void ReferenceCustomGBIxn::calculateSingleParticleEnergyTerm(int index, int numAtoms, RealOpenMM** atomCoordinates, const vector<vector<RealOpenMM> >& values,
        const map<string, double>& globalParameters, RealOpenMM** atomParameters, RealOpenMM** forces, RealOpenMM* totalEnergy,
239
        vector<vector<RealOpenMM> >& dEdV) const {
240
241
    map<string, double> variables = globalParameters;
    for (int i = 0; i < numAtoms; i++) {
242
243
244
        variables["x"] = atomCoordinates[i][0];
        variables["y"] = atomCoordinates[i][1];
        variables["z"] = atomCoordinates[i][2];
245
246
247
        for (int j = 0; j < (int) paramNames.size(); j++)
            variables[paramNames[j]] = atomParameters[i][j];
        for (int j = 0; j < (int) valueNames.size(); j++)
248
            variables[valueNames[j]] = values[j][i];
249
250
        if (totalEnergy != NULL)
            *totalEnergy += (RealOpenMM) energyExpressions[index].evaluate(variables);
251
252
        for (int j = 0; j < (int) valueNames.size(); j++)
            dEdV[j][i] += (RealOpenMM) energyDerivExpressions[index][j].evaluate(variables);
253
254
255
        forces[i][0] -= (RealOpenMM) energyGradientExpressions[index][0].evaluate(variables);
        forces[i][1] -= (RealOpenMM) energyGradientExpressions[index][1].evaluate(variables);
        forces[i][2] -= (RealOpenMM) energyGradientExpressions[index][2].evaluate(variables);
256
257
258
259
    }
}

void ReferenceCustomGBIxn::calculateParticlePairEnergyTerm(int index, int numAtoms, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
260
261
        const vector<vector<RealOpenMM> >& values, const map<string, double>& globalParameters, const vector<set<int> >& exclusions, bool useExclusions,
        RealOpenMM** forces, RealOpenMM* totalEnergy, vector<vector<RealOpenMM> >& dEdV) const {
262
    if (cutoff) {
263
264
        // Loop over all pairs in the neighbor list.

265
266
267
268
        for (int i = 0; i < (int) neighborList->size(); i++) {
            OpenMM::AtomPair pair = (*neighborList)[i];
            if (useExclusions && exclusions[pair.first].find(pair.second) != exclusions[pair.first].end())
                continue;
269
            calculateOnePairEnergyTerm(index, pair.first, pair.second, atomCoordinates, atomParameters, globalParameters, values, forces, totalEnergy, dEdV);
270
271
272
        }
    }
    else {
273
274
        // Perform an O(N^2) loop over all atom pairs.

275
276
277
278
        for (int i = 0; i < numAtoms; i++){
            for (int j = i+1; j < numAtoms; j++ ){
                if (useExclusions && exclusions[i].find(j) != exclusions[i].end())
                    continue;
279
                calculateOnePairEnergyTerm(index, i, j, atomCoordinates, atomParameters, globalParameters, values, forces, totalEnergy, dEdV);
280
281
282
283
284
285
           }
        }
    }
}

void ReferenceCustomGBIxn::calculateOnePairEnergyTerm(int index, int atom1, int atom2, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
286
287
288
289
        const map<string, double>& globalParameters, const vector<vector<RealOpenMM> >& values, RealOpenMM** forces, RealOpenMM* totalEnergy,
        vector<vector<RealOpenMM> >& dEdV) const {
    // Compute the displacement.

290
291
292
293
294
295
296
297
    RealOpenMM deltaR[ReferenceForce::LastDeltaRIndex];
    if (periodic)
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[atom2], atomCoordinates[atom1], periodicBoxSize, deltaR);
    else
        ReferenceForce::getDeltaR(atomCoordinates[atom2], atomCoordinates[atom1], deltaR);
    RealOpenMM r = deltaR[ReferenceForce::RIndex];
    if (cutoff && r >= cutoffDistance)
        return;
298
299
300

    // Record variables for evaluating expressions.

301
    map<string, double> variables = globalParameters;
302
    for (int i = 0; i < (int) paramNames.size(); i++) {
303
304
305
306
307
        variables[particleParamNames[i*2]] = atomParameters[atom1][i];
        variables[particleParamNames[i*2+1]] = atomParameters[atom2][i];
    }
    variables["r"] = r;
    for (int i = 0; i < (int) valueNames.size(); i++) {
308
309
        variables[particleValueNames[i*2]] = values[i][atom1];
        variables[particleValueNames[i*2+1]] = values[i][atom2];
310
    }
311
312
313

    // Evaluate the energy and its derivatives.

314
315
316
317
318
319
320
321
322
    if (totalEnergy != NULL)
        *totalEnergy += (RealOpenMM) energyExpressions[index].evaluate(variables);
    RealOpenMM dEdR = (RealOpenMM) energyDerivExpressions[index][0].evaluate(variables);
    dEdR *= 1/r;
    for (int i = 0; i < 3; i++) {
       forces[atom1][i] -= dEdR*deltaR[i];
       forces[atom2][i] += dEdR*deltaR[i];
    }
    for (int i = 0; i < (int) valueNames.size(); i++) {
323
324
325
326
327
328
329
330
331
332
333
334
335
        dEdV[i][atom1] += (RealOpenMM) energyDerivExpressions[index][2*i+1].evaluate(variables);
        dEdV[i][atom2] += (RealOpenMM) energyDerivExpressions[index][2*i+2].evaluate(variables);
    }
}

void ReferenceCustomGBIxn::calculateChainRuleForces(int numAtoms, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
        const vector<vector<RealOpenMM> >& values, const map<string, double>& globalParameters,
        const vector<set<int> >& exclusions, RealOpenMM** forces, vector<vector<RealOpenMM> >& dEdV) const {
    if (cutoff) {
        // Loop over all pairs in the neighbor list.

        for (int i = 0; i < (int) neighborList->size(); i++) {
            OpenMM::AtomPair pair = (*neighborList)[i];
336
337
338
            bool isExcluded = (exclusions[pair.first].find(pair.second) != exclusions[pair.first].end());
            calculateOnePairChainRule(pair.first, pair.second, atomCoordinates, atomParameters, globalParameters, values, forces, dEdV, isExcluded);
            calculateOnePairChainRule(pair.second, pair.first, atomCoordinates, atomParameters, globalParameters, values, forces, dEdV, isExcluded);
339
340
341
342
343
344
345
        }
    }
    else {
        // Perform an O(N^2) loop over all atom pairs.

        for (int i = 0; i < numAtoms; i++){
            for (int j = i+1; j < numAtoms; j++ ){
346
347
348
                bool isExcluded = (exclusions[i].find(j) != exclusions[i].end());
                calculateOnePairChainRule(i, j, atomCoordinates, atomParameters, globalParameters, values, forces, dEdV, isExcluded);
                calculateOnePairChainRule(j, i, atomCoordinates, atomParameters, globalParameters, values, forces, dEdV, isExcluded);
349
350
351
           }
        }
    }
Peter Eastman's avatar
Peter Eastman committed
352
353
354
355
356
357
358
359

    // Compute chain rule terms for computed values that depend explicitly on particle coordinates.

    map<string, double> variables = globalParameters;
    for (int i = 0; i < numAtoms; i++) {
        variables["x"] = atomCoordinates[i][0];
        variables["y"] = atomCoordinates[i][1];
        variables["z"] = atomCoordinates[i][2];
360
361
362
        vector<RealOpenMM> dVdX(valueDerivExpressions.size(), 0.0);
        vector<RealOpenMM> dVdY(valueDerivExpressions.size(), 0.0);
        vector<RealOpenMM> dVdZ(valueDerivExpressions.size(), 0.0);
Peter Eastman's avatar
Peter Eastman committed
363
364
365
366
        for (int j = 0; j < (int) paramNames.size(); j++)
            variables[paramNames[j]] = atomParameters[i][j];
        for (int j = 1; j < (int) valueNames.size(); j++) {
            variables[valueNames[j-1]] = values[j-1][i];
367
368
369
370
371
            for (int k = 1; k < j; k++) {
                RealOpenMM dVdV = (RealOpenMM) valueDerivExpressions[j][k].evaluate(variables);
                dVdX[j] += dVdV*dVdX[k];
                dVdY[j] += dVdV*dVdY[k];
                dVdZ[j] += dVdV*dVdZ[k];
Peter Eastman's avatar
Peter Eastman committed
372
            }
373
374
375
376
377
378
            dVdX[j] += (RealOpenMM) valueGradientExpressions[j][0].evaluate(variables);
            dVdY[j] += (RealOpenMM) valueGradientExpressions[j][1].evaluate(variables);
            dVdZ[j] += (RealOpenMM) valueGradientExpressions[j][2].evaluate(variables);
            forces[i][0] -= dEdV[j][i]*dVdX[j];
            forces[i][1] -= dEdV[j][i]*dVdY[j];
            forces[i][2] -= dEdV[j][i]*dVdZ[j];
Peter Eastman's avatar
Peter Eastman committed
379
380
        }
    }
381
382
383
384
}

void ReferenceCustomGBIxn::calculateOnePairChainRule(int atom1, int atom2, RealOpenMM** atomCoordinates, RealOpenMM** atomParameters,
        const map<string, double>& globalParameters, const vector<vector<RealOpenMM> >& values, RealOpenMM** forces,
385
        vector<vector<RealOpenMM> >& dEdV, bool isExcluded) const {
386
387
388
389
390
391
392
393
394
395
396
397
398
399
    // Compute the displacement.

    RealOpenMM deltaR[ReferenceForce::LastDeltaRIndex];
    if (periodic)
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[atom2], atomCoordinates[atom1], periodicBoxSize, deltaR);
    else
        ReferenceForce::getDeltaR(atomCoordinates[atom2], atomCoordinates[atom1], deltaR);
    RealOpenMM r = deltaR[ReferenceForce::RIndex];
    if (cutoff && r >= cutoffDistance)
        return;

    // Record variables for evaluating expressions.

    map<string, double> variables = globalParameters;
400
    for (int i = 0; i < (int) paramNames.size(); i++) {
401
402
403
404
405
406
407
408
409
        variables[particleParamNames[i*2]] = atomParameters[atom1][i];
        variables[particleParamNames[i*2+1]] = atomParameters[atom2][i];
    }
    variables["r"] = r;
    variables[particleValueNames[0]] = values[0][atom1];
    variables[particleValueNames[1]] = values[0][atom2];

    // Evaluate the derivative of each parameter with respect to position and apply forces.

Peter Eastman's avatar
Peter Eastman committed
410
411
412
413
414
415
    RealOpenMM rinv = 1/r;
    deltaR[0] *= rinv;
    deltaR[1] *= rinv;
    deltaR[2] *= rinv;
    vector<RealOpenMM> dVdR1(valueDerivExpressions.size(), 0.0);
    vector<RealOpenMM> dVdR2(valueDerivExpressions.size(), 0.0);
416
    if (!isExcluded || valueTypes[0] != OpenMM::CustomGBForce::ParticlePair) {
Peter Eastman's avatar
Peter Eastman committed
417
418
        dVdR1[0] = (RealOpenMM) valueDerivExpressions[0][0].evaluate(variables);;
        dVdR2[0] = -dVdR1[0];
419
        for (int i = 0; i < 3; i++) {
Peter Eastman's avatar
Peter Eastman committed
420
421
            forces[atom1][i] -= dEdV[0][atom1]*dVdR1[0]*deltaR[i];
            forces[atom2][i] -= dEdV[0][atom1]*dVdR2[0]*deltaR[i];
422
        }
423
424
    }
    variables = globalParameters;
425
    for (int i = 0; i < (int) paramNames.size(); i++)
426
427
428
429
        variables[paramNames[i]] = atomParameters[atom1][i];
    variables[valueNames[0]] = values[0][atom1];
    for (int i = 1; i < (int) valueNames.size(); i++) {
        variables[valueNames[i]] = values[i][atom1];
Peter Eastman's avatar
Peter Eastman committed
430
431
432
        variables["x"] = atomCoordinates[atom1][0];
        variables["y"] = atomCoordinates[atom1][1];
        variables["z"] = atomCoordinates[atom1][2];
433
434
        for (int j = 0; j < i; j++) {
            RealOpenMM dVdV = (RealOpenMM) valueDerivExpressions[i][j].evaluate(variables);
Peter Eastman's avatar
Peter Eastman committed
435
436
            dVdR1[i] += dVdV*dVdR1[j];
            dVdR2[i] += dVdV*dVdR2[j];
437
438
        }
        for (int k = 0; k < 3; k++) {
Peter Eastman's avatar
Peter Eastman committed
439
440
            forces[atom1][k] -= dEdV[i][atom1]*dVdR1[i]*deltaR[k];
            forces[atom2][k] -= dEdV[i][atom1]*dVdR2[i]*deltaR[k];
441
        }
442
443
    }
}