ReferenceLJCoulombIxn.cpp 27.6 KB
Newer Older
1

2
/* Portions copyright (c) 2006-2013 Stanford University and Simbios.
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 * Contributors: Pande Group
 *
 * 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>
27
#include <complex>
28
#include <algorithm>
29
#include <iostream>
30

31
#include "SimTKOpenMMUtilities.h"
32
33
#include "ReferenceLJCoulombIxn.h"
#include "ReferenceForce.h"
34
#include "ReferencePME.h"
peastman's avatar
peastman committed
35
#include "openmm/OpenMMException.h"
36

37
38
// In case we're using some primitive version of Visual Studio this will
// make sure that erf() and erfc() are defined.
39
#include "openmm/internal/MSVC_erfc.h"
40

41
using std::set;
42
using std::vector;
43
using namespace OpenMM;
44

45
46
47
48
49
50
/**---------------------------------------------------------------------------------------

   ReferenceLJCoulombIxn constructor

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

51
ReferenceLJCoulombIxn::ReferenceLJCoulombIxn() : cutoff(false), useSwitch(false), periodic(false), ewald(false), pme(false), ljpme(false) {
52
53
54
55
56
57
58
59
}

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

   ReferenceLJCoulombIxn destructor

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

60
ReferenceLJCoulombIxn::~ReferenceLJCoulombIxn() {
61
62
}

63
/**---------------------------------------------------------------------------------------
64
65
66
67
68
69
70
71
72

     Set the force to use a cutoff.

     @param distance            the cutoff distance
     @param neighbors           the neighbor list to use
     @param solventDielectric   the dielectric constant of the bulk solvent

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

peastman's avatar
peastman committed
73
void ReferenceLJCoulombIxn::setUseCutoff(double distance, const OpenMM::NeighborList& neighbors, double solventDielectric) {
74

75
76
77
    cutoff = true;
    cutoffDistance = distance;
    neighborList = &neighbors;
78
79
    krf = pow(cutoffDistance, -3.0)*(solventDielectric-1.0)/(2.0*solventDielectric+1.0);
    crf = (1.0/cutoffDistance)*(3.0*solventDielectric)/(2.0*solventDielectric+1.0);
80
}
81

82
83
84
85
86
87
88
89
/**---------------------------------------------------------------------------------------

   Set the force to use a switching function on the Lennard-Jones interaction.

   @param distance            the switching distance

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

peastman's avatar
peastman committed
90
void ReferenceLJCoulombIxn::setUseSwitchingFunction(double distance) {
91
92
93
94
    useSwitch = true;
    switchingDistance = distance;
}

95
/**---------------------------------------------------------------------------------------
96
97
98
99
100

     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.

101
     @param vectors    the vectors defining the periodic box
102
103
104

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

peastman's avatar
peastman committed
105
void ReferenceLJCoulombIxn::setPeriodic(OpenMM::Vec3* vectors) {
106
107

    assert(cutoff);
108
109
110
    assert(vectors[0][0] >= 2.0*cutoffDistance);
    assert(vectors[1][1] >= 2.0*cutoffDistance);
    assert(vectors[2][2] >= 2.0*cutoffDistance);
111
    periodic = true;
112
113
114
    periodicBoxVectors[0] = vectors[0];
    periodicBoxVectors[1] = vectors[1];
    periodicBoxVectors[2] = vectors[2];
115
}
116

117
/**---------------------------------------------------------------------------------------
118
119
120
121
122
123
124
125
126
127

     Set the force to use Ewald summation.

     @param alpha  the Ewald separation parameter
     @param kmaxx  the largest wave vector in the x direction
     @param kmaxy  the largest wave vector in the y direction
     @param kmaxz  the largest wave vector in the z direction

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

peastman's avatar
peastman committed
128
void ReferenceLJCoulombIxn::setUseEwald(double alpha, int kmaxx, int kmaxy, int kmaxz) {
129
130
131
132
133
134
    alphaEwald = alpha;
    numRx = kmaxx;
    numRy = kmaxy;
    numRz = kmaxz;
    ewald = true;
}
135

136
/**---------------------------------------------------------------------------------------
137
138
139
140

     Set the force to use Particle-Mesh Ewald (PME) summation.

     @param alpha  the Ewald separation parameter
141
     @param gridSize the dimensions of the mesh
142
143
144

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

peastman's avatar
peastman committed
145
void ReferenceLJCoulombIxn::setUsePME(double alpha, int meshSize[3]) {
146
147
148
149
150
151
152
153
154
    alphaEwald = alpha;
    meshDim[0] = meshSize[0];
    meshDim[1] = meshSize[1];
    meshDim[2] = meshSize[2];
    pme = true;
}

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

155
     Set the force to use Particle-Mesh Ewald (PME) summation for dispersion terms.
156

157
158
     @param alpha  the dispersion Ewald separation parameter
     @param gridSize the dimensions of the dispersion mesh
159
160
161

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

peastman's avatar
peastman committed
162
void ReferenceLJCoulombIxn::setUseLJPME(double alpha, int meshSize[3]) {
163
164
165
166
    alphaDispersionEwald = alpha;
    dispersionMeshDim[0] = meshSize[0];
    dispersionMeshDim[1] = meshSize[1];
    dispersionMeshDim[2] = meshSize[2];
167
168
    ljpme = true;
}
169

170
171
172
173
174
175
176
/**---------------------------------------------------------------------------------------

   Calculate Ewald ixn

   @param numberOfAtoms    number of atoms
   @param atomCoordinates  atom coordinates
   @param atomParameters   atom parameters                             atomParameters[atomIndex][paramterIndex]
177
178
   @param exclusions       atom exclusion indices
                           exclusions[atomIndex] contains the list of exclusions for that atom
179
180
181
182
   @param fixedParameters  non atom parameters (not currently used)
   @param forces           force array (forces added)
   @param energyByAtom     atom energy
   @param totalEnergy      total energy
183
184
   @param includeDirect      true if direct space interactions should be included
   @param includeReciprocal  true if reciprocal space interactions should be included
185
186

   --------------------------------------------------------------------------------------- */
187

peastman's avatar
peastman committed
188
void ReferenceLJCoulombIxn::calculateEwaldIxn(int numberOfAtoms, vector<Vec3>& atomCoordinates,
peastman's avatar
peastman committed
189
190
191
                                              double** atomParameters, vector<set<int> >& exclusions,
                                              double* fixedParameters, vector<Vec3>& forces,
                                              double* energyByAtom, double* totalEnergy, bool includeDirect, bool includeReciprocal) const {
peastman's avatar
peastman committed
192
    typedef std::complex<double> d_complex;
193

peastman's avatar
peastman committed
194
    static const double epsilon     =  1.0;
195

196
    int kmax                            = (ewald ? std::max(numRx, std::max(numRy,numRz)) : 0);
peastman's avatar
peastman committed
197
198
199
200
    double factorEwald              = -1 / (4*alphaEwald*alphaEwald);
    double SQRT_PI                  = sqrt(PI_M);
    double TWO_PI                   = 2.0 * PI_M;
    double recipCoeff               = ONE_4PI_EPS0*4*PI_M/(periodicBoxVectors[0][0] * periodicBoxVectors[1][1] * periodicBoxVectors[2][2]) /epsilon;
201

peastman's avatar
peastman committed
202
203
204
    double totalSelfEwaldEnergy     = 0.0;
    double realSpaceEwaldEnergy     = 0.0;
    double recipEnergy              = 0.0;
peastman's avatar
peastman committed
205
    double recipDispersionEnergy    = 0.0;
peastman's avatar
peastman committed
206
207
    double totalRecipEnergy         = 0.0;
    double vdwEnergy                = 0.0;
208

209
210
211
212
213
214
215
216
217
    // A couple of sanity checks for
    if(ljpme && useSwitch)
        throw OpenMMException("Switching cannot be used with LJPME");
    if(ljpme && !pme)
        throw OpenMMException("LJPME has been set, without PME being set");

    // **************************************************************************************
    // SELF ENERGY
    // **************************************************************************************
218

219
    if (includeReciprocal) {
220
        for (int atomID = 0; atomID < numberOfAtoms; atomID++) {
peastman's avatar
peastman committed
221
            double selfEwaldEnergy       = ONE_4PI_EPS0*atomParameters[atomID][QIndex]*atomParameters[atomID][QIndex] * alphaEwald/SQRT_PI;
222
223
224
225
            if(ljpme) {
                // Dispersion self term
                selfEwaldEnergy -= pow(alphaDispersionEwald, 6.0) * 64.0*pow(atomParameters[atomID][SigIndex], 6.0) * pow(atomParameters[atomID][EpsIndex], 2.0) / 12.0;
            }
226
            totalSelfEwaldEnergy            -= selfEwaldEnergy;
227
            if (energyByAtom) {
228
229
                energyByAtom[atomID]        -= selfEwaldEnergy;
            }
230
        }
231
232
    }

233
    if (totalEnergy) {
234
235
236
        *totalEnergy += totalSelfEwaldEnergy;
    }

237
238
239
    // **************************************************************************************
    // RECIPROCAL SPACE EWALD ENERGY AND FORCES
    // **************************************************************************************
240
    // PME
241

242
243
    if (pme && includeReciprocal) {
        pme_t          pmedata; /* abstract handle for PME data */
244

245
        pme_init(&pmedata,alphaEwald,numberOfAtoms,meshDim,5,1);
246

peastman's avatar
peastman committed
247
        vector<double> charges(numberOfAtoms);
248
249
250
        for (int i = 0; i < numberOfAtoms; i++)
            charges[i] = atomParameters[i][QIndex];
        pme_exec(pmedata,atomCoordinates,forces,charges,periodicBoxVectors,&recipEnergy);
251

252
253
        if (totalEnergy)
            *totalEnergy += recipEnergy;
254

255
256
257
        if (energyByAtom)
            for (int n = 0; n < numberOfAtoms; n++)
                energyByAtom[n] += recipEnergy;
258

259
        pme_destroy(pmedata);
260

261
262
263
264
        if (ljpme) {
            // Dispersion reciprocal space terms
            pme_init(&pmedata,alphaDispersionEwald,numberOfAtoms,dispersionMeshDim,5,1);

peastman's avatar
peastman committed
265
            std::vector<Vec3> dpmeforces;
266
267
            for (int i = 0; i < numberOfAtoms; i++){
                charges[i] = 8.0*pow(atomParameters[i][SigIndex], 3.0) * atomParameters[i][EpsIndex];
peastman's avatar
peastman committed
268
                dpmeforces.push_back(Vec3());
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
            }
            pme_exec_dpme(pmedata,atomCoordinates,dpmeforces,charges,periodicBoxVectors,&recipDispersionEnergy);
            for (int i = 0; i < numberOfAtoms; i++){
                forces[i][0] -= 2.0*dpmeforces[i][0];
                forces[i][1] -= 2.0*dpmeforces[i][1];
                forces[i][2] -= 2.0*dpmeforces[i][2];
            }
            if (totalEnergy)
                *totalEnergy += recipDispersionEnergy;

            if (energyByAtom)
                for (int n = 0; n < numberOfAtoms; n++)
                    energyByAtom[n] += recipDispersionEnergy;
            pme_destroy(pmedata);
        }
    }
285
286
    // Ewald method

287
    else if (ewald && includeReciprocal) {
288

289
        // setup reciprocal box
290

peastman's avatar
peastman committed
291
        double recipBoxSize[3] = { TWO_PI / periodicBoxVectors[0][0], TWO_PI / periodicBoxVectors[1][1], TWO_PI / periodicBoxVectors[2][2]};
292
293


294
        // setup K-vectors
295

296
297
298
299
#define EIR(x, y, z) eir[(x)*numberOfAtoms*3+(y)*3+z]
        vector<d_complex> eir(kmax*numberOfAtoms*3);
        vector<d_complex> tab_xy(numberOfAtoms);
        vector<d_complex> tab_qxyz(numberOfAtoms);
300

301
302
        if (kmax < 1)
            throw OpenMMException("kmax for Ewald summation < 1");
303

304
305
306
        for (int i = 0; (i < numberOfAtoms); i++) {
            for (int m = 0; (m < 3); m++)
                EIR(0, i, m) = d_complex(1,0);
307

308
309
310
            for (int m=0; (m<3); m++)
                EIR(1, i, m) = d_complex(cos(atomCoordinates[i][m]*recipBoxSize[m]),
                                         sin(atomCoordinates[i][m]*recipBoxSize[m]));
311

312
313
314
315
            for (int j=2; (j<kmax); j++)
                for (int m=0; (m<3); m++)
                    EIR(j, i, m) = EIR(j-1, i, m) * EIR(1, i, m);
        }
316

317
        // calculate reciprocal space energy and forces
318

319
320
        int lowry = 0;
        int lowrz = 1;
321

322
        for (int rx = 0; rx < numRx; rx++) {
323

peastman's avatar
peastman committed
324
            double kx = rx * recipBoxSize[0];
325

326
            for (int ry = lowry; ry < numRy; ry++) {
327

peastman's avatar
peastman committed
328
                double ky = ry * recipBoxSize[1];
329

330
331
332
333
                if (ry >= 0) {
                    for (int n = 0; n < numberOfAtoms; n++)
                        tab_xy[n] = EIR(rx, n, 0) * EIR(ry, n, 1);
                }
334

335
336
337
338
                else {
                    for (int n = 0; n < numberOfAtoms; n++)
                        tab_xy[n]= EIR(rx, n, 0) * conj (EIR(-ry, n, 1));
                }
339

340
                for (int rz = lowrz; rz < numRz; rz++) {
341

342
343
344
345
                    if (rz >= 0) {
                        for (int n = 0; n < numberOfAtoms; n++)
                            tab_qxyz[n] = atomParameters[n][QIndex] * (tab_xy[n] * EIR(rz, n, 2));
                    }
346

347
348
349
350
                    else {
                        for (int n = 0; n < numberOfAtoms; n++)
                            tab_qxyz[n] = atomParameters[n][QIndex] * (tab_xy[n] * conj(EIR(-rz, n, 2)));
                    }
351

peastman's avatar
peastman committed
352
353
                    double cs = 0.0f;
                    double ss = 0.0f;
354

355
356
357
358
                    for (int n = 0; n < numberOfAtoms; n++) {
                        cs += tab_qxyz[n].real();
                        ss += tab_qxyz[n].imag();
                    }
359

peastman's avatar
peastman committed
360
361
362
                    double kz = rz * recipBoxSize[2];
                    double k2 = kx * kx + ky * ky + kz * kz;
                    double ak = exp(k2*factorEwald) / k2;
363

364
                    for (int n = 0; n < numberOfAtoms; n++) {
peastman's avatar
peastman committed
365
                        double force = ak * (cs * tab_qxyz[n].imag() - ss * tab_qxyz[n].real());
366
367
368
369
                        forces[n][0] += 2 * recipCoeff * force * kx ;
                        forces[n][1] += 2 * recipCoeff * force * ky ;
                        forces[n][2] += 2 * recipCoeff * force * kz ;
                    }
370

371
372
                    recipEnergy       = recipCoeff * ak * (cs * cs + ss * ss);
                    totalRecipEnergy += recipEnergy;
373

374
375
                    if (totalEnergy)
                        *totalEnergy += recipEnergy;
376

377
378
379
                    if (energyByAtom)
                        for (int n = 0; n < numberOfAtoms; n++)
                            energyByAtom[n] += recipEnergy;
380

381
382
383
384
                    lowrz = 1 - numRz;
                }
                lowry = 1 - numRy;
            }
385
386
387
        }
    }

388
389
390
    // **************************************************************************************
    // SHORT-RANGE ENERGY AND FORCES
    // **************************************************************************************
391

392
393
    if (!includeDirect)
        return;
peastman's avatar
peastman committed
394
395
    double totalVdwEnergy            = 0.0f;
    double totalRealSpaceEwaldEnergy = 0.0f;
396

397

398
    for (int i = 0; i < (int) neighborList->size(); i++) {
399
400
401
402
        OpenMM::AtomPair pair = (*neighborList)[i];
        int ii = pair.first;
        int jj = pair.second;

peastman's avatar
peastman committed
403
        double deltaR[2][ReferenceForce::LastDeltaRIndex];
404
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[jj], atomCoordinates[ii], periodicBoxVectors, deltaR[0]);
peastman's avatar
peastman committed
405
406
407
        double r         = deltaR[0][ReferenceForce::RIndex];
        double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
        double switchValue = 1, switchDeriv = 0;
408
        if (useSwitch && r > switchingDistance) {
peastman's avatar
peastman committed
409
            double t = (r-switchingDistance)/(cutoffDistance-switchingDistance);
410
411
412
            switchValue = 1+t*t*t*(-10+t*(15-t*6));
            switchDeriv = t*t*(-30+t*(60-t*30))/(cutoffDistance-switchingDistance);
        }
peastman's avatar
peastman committed
413
        double alphaR = alphaEwald * r;
414
415


peastman's avatar
peastman committed
416
417
        double dEdR = ONE_4PI_EPS0 * atomParameters[ii][QIndex] * atomParameters[jj][QIndex] * inverseR * inverseR * inverseR;
        dEdR = dEdR * (erfc(alphaR) + 2 * alphaR * exp (- alphaR * alphaR) / SQRT_PI);
418

peastman's avatar
peastman committed
419
420
421
422
423
424
425
        double sig = atomParameters[ii][SigIndex] +  atomParameters[jj][SigIndex];
        double sig2 = inverseR*sig;
        sig2 *= sig2;
        double sig6 = sig2*sig2*sig2;
        double eps = atomParameters[ii][EpsIndex]*atomParameters[jj][EpsIndex];
        dEdR += switchValue*eps*(12.0*sig6 - 6.0)*sig6*inverseR*inverseR;
        vdwEnergy = eps*(sig6-1.0)*sig6;
426
427

        if (ljpme) {
peastman's avatar
peastman committed
428
429
430
431
432
433
434
            double dalphaR   = alphaDispersionEwald * r;
            double dar2 = dalphaR*dalphaR;
            double dar4 = dar2*dar2;
            double dar6 = dar4*dar2;
            double inverseR2 = inverseR*inverseR;
            double c6i = 8.0*pow(atomParameters[ii][SigIndex], 3.0) * atomParameters[ii][EpsIndex];
            double c6j = 8.0*pow(atomParameters[jj][SigIndex], 3.0) * atomParameters[jj][EpsIndex];
435
436
437
438
            // For the energies and forces, we first add the regular Lorentz−Berthelot terms.  The C12 term is treated as usual
            // but we then subtract out (remembering that the C6 term is negative) the multiplicative C6 term that has been
            // computed in real space.  Finally, we add a potential shift term to account for the difference between the LB
            // and multiplicative functional forms at the cutoff.
peastman's avatar
peastman committed
439
            double emult = c6i*c6j*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4));
440
            dEdR += 6.0*c6i*c6j*inverseR2*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4 + dar6/6.0));
441

peastman's avatar
peastman committed
442
443
            double inverseCut2 = 1.0/(cutoffDistance*cutoffDistance);
            double inverseCut6 = inverseCut2*inverseCut2*inverseCut2;
444
445
446
            sig2 = atomParameters[ii][SigIndex] +  atomParameters[jj][SigIndex];
            sig2 *= sig2;
            sig6 = sig2*sig2*sig2;
447
            // The additive part of the potential shift
peastman's avatar
peastman committed
448
            double potentialshift = eps*(1.0-sig6*inverseCut6)*sig6*inverseCut6;
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
            dalphaR   = alphaDispersionEwald * cutoffDistance;
            dar2 = dalphaR*dalphaR;
            dar4 = dar2*dar2;
            // The multiplicative part of the potential shift
            potentialshift -= c6i*c6j*inverseCut6*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4));
            vdwEnergy += emult + potentialshift;
        }

        if (useSwitch) {
            dEdR -= vdwEnergy*switchDeriv*inverseR;
            vdwEnergy *= switchValue;
        }

        // accumulate forces

        for (int kk = 0; kk < 3; kk++) {
peastman's avatar
peastman committed
465
            double force  = dEdR*deltaR[0][kk];
466
467
468
469
470
471
            forces[ii][kk]   += force;
            forces[jj][kk]   -= force;
        }

        // accumulate energies

peastman's avatar
peastman committed
472
        realSpaceEwaldEnergy        = ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*inverseR*erfc(alphaR);
473
474
475

        totalVdwEnergy             += vdwEnergy;
        totalRealSpaceEwaldEnergy  += realSpaceEwaldEnergy;
476

477
        if (energyByAtom) {
478
479
            energyByAtom[ii] += realSpaceEwaldEnergy + vdwEnergy;
            energyByAtom[jj] += realSpaceEwaldEnergy + vdwEnergy;
480
        }
481

482
    }
483

484
    if (totalEnergy)
485
486
        *totalEnergy += totalRealSpaceEwaldEnergy + totalVdwEnergy;

Peter Eastman's avatar
Peter Eastman committed
487
488
    // Now subtract off the exclusions, since they were implicitly included in the reciprocal space sum.

peastman's avatar
peastman committed
489
    double totalExclusionEnergy = 0.0f;
490
    const double TWO_OVER_SQRT_PI = 2/sqrt(PI_M);
Peter Eastman's avatar
Peter Eastman committed
491
    for (int i = 0; i < numberOfAtoms; i++)
492
493
        for (set<int>::const_iterator iter = exclusions[i].begin(); iter != exclusions[i].end(); ++iter) {
            if (*iter > i) {
494
495
496
                int ii = i;
                int jj = *iter;

peastman's avatar
peastman committed
497
                double deltaR[2][ReferenceForce::LastDeltaRIndex];
498
                ReferenceForce::getDeltaR(atomCoordinates[jj], atomCoordinates[ii], deltaR[0]);
peastman's avatar
peastman committed
499
500
501
                double r         = deltaR[0][ReferenceForce::RIndex];
                double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
                double alphaR    = alphaEwald * r;
502
                if (erf(alphaR) > 1e-6) {
peastman's avatar
peastman committed
503
504
                    double dEdR = ONE_4PI_EPS0 * atomParameters[ii][QIndex] * atomParameters[jj][QIndex] * inverseR * inverseR * inverseR;
                    dEdR = dEdR * (erf(alphaR) - 2 * alphaR * exp (- alphaR * alphaR) / SQRT_PI);
505
506
507
508

                    // accumulate forces

                    for (int kk = 0; kk < 3; kk++) {
peastman's avatar
peastman committed
509
510
511
                        double force = dEdR*deltaR[0][kk];
                        forces[ii][kk] -= force;
                        forces[jj][kk] += force;
512
513
514
515
                    }

                    // accumulate energies

peastman's avatar
peastman committed
516
                    realSpaceEwaldEnergy = ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*inverseR*erf(alphaR);
517
518
                }
                else {
peastman's avatar
peastman committed
519
                    realSpaceEwaldEnergy = alphaEwald*TWO_OVER_SQRT_PI*ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex];
520
521
522
523
                }

                if(ljpme){
                    // Dispersion terms.  Here we just back out the reciprocal space terms, and don't add any extra real space terms.
peastman's avatar
peastman committed
524
525
526
527
528
529
530
                    double dalphaR   = alphaDispersionEwald * r;
                    double inverseR2 = inverseR*inverseR;
                    double dar2 = dalphaR*dalphaR;
                    double dar4 = dar2*dar2;
                    double dar6 = dar4*dar2;
                    double c6i = 8.0*pow(atomParameters[ii][SigIndex], 3.0) * atomParameters[ii][EpsIndex];
                    double c6j = 8.0*pow(atomParameters[jj][SigIndex], 3.0) * atomParameters[jj][EpsIndex];
531
                    realSpaceEwaldEnergy -= c6i*c6j*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4));
peastman's avatar
peastman committed
532
                    double dEdR = -6.0*c6i*c6j*inverseR2*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4 + dar6/6.0));
533
                    for (int kk = 0; kk < 3; kk++) {
peastman's avatar
peastman committed
534
535
536
                        double force = dEdR*deltaR[0][kk];
                        forces[ii][kk] -= force;
                        forces[jj][kk] += force;
537
538
539
540
541
542
543
544
                    }
                }

                totalExclusionEnergy += realSpaceEwaldEnergy;
                if (energyByAtom) {
                    energyByAtom[ii] -= realSpaceEwaldEnergy;
                    energyByAtom[jj] -= realSpaceEwaldEnergy;
                }
Peter Eastman's avatar
Peter Eastman committed
545
            }
546
        }
Peter Eastman's avatar
Peter Eastman committed
547

548
    if (totalEnergy)
549
        *totalEnergy -= totalExclusionEnergy;
550
551
552
}


553
554
555
556
557
558
559
/**---------------------------------------------------------------------------------------

   Calculate LJ Coulomb pair ixn

   @param numberOfAtoms    number of atoms
   @param atomCoordinates  atom coordinates
   @param atomParameters   atom parameters                             atomParameters[atomIndex][paramterIndex]
560
561
   @param exclusions       atom exclusion indices
                           exclusions[atomIndex] contains the list of exclusions for that atom
562
563
564
565
   @param fixedParameters  non atom parameters (not currently used)
   @param forces           force array (forces added)
   @param energyByAtom     atom energy
   @param totalEnergy      total energy
566
567
   @param includeDirect      true if direct space interactions should be included
   @param includeReciprocal  true if reciprocal space interactions should be included
568
569

   --------------------------------------------------------------------------------------- */
570

peastman's avatar
peastman committed
571
572
573
574
void ReferenceLJCoulombIxn::calculatePairIxn(int numberOfAtoms, vector<Vec3>& atomCoordinates,
                                             double** atomParameters, vector<set<int> >& exclusions,
                                             double* fixedParameters, vector<Vec3>& forces,
                                             double* energyByAtom, double* totalEnergy, bool includeDirect, bool includeReciprocal) const {
575

576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
    if (ewald || pme || ljpme) {
        calculateEwaldIxn(numberOfAtoms, atomCoordinates, atomParameters, exclusions, fixedParameters, forces, energyByAtom,
                          totalEnergy, includeDirect, includeReciprocal);
        return;
    }
    if (!includeDirect)
        return;
    if (cutoff) {
        for (int i = 0; i < (int) neighborList->size(); i++) {
            OpenMM::AtomPair pair = (*neighborList)[i];
            calculateOneIxn(pair.first, pair.second, atomCoordinates, atomParameters, forces, energyByAtom, totalEnergy);
        }
    }
    else {
        for (int ii = 0; ii < numberOfAtoms; ii++) {
            // loop over atom pairs

            for (int jj = ii+1; jj < numberOfAtoms; jj++)
                if (exclusions[jj].find(ii) == exclusions[jj].end())
                    calculateOneIxn(ii, jj, atomCoordinates, atomParameters, forces, energyByAtom, totalEnergy);
        }
    }
598
}
599

600
/**---------------------------------------------------------------------------------------
601

602
     Calculate LJ Coulomb pair ixn between two atoms
603

604
605
606
607
608
609
610
     @param ii               the index of the first atom
     @param jj               the index of the second atom
     @param atomCoordinates  atom coordinates
     @param atomParameters   atom parameters (charges, c6, c12, ...)     atomParameters[atomIndex][paramterIndex]
     @param forces           force array (forces added)
     @param energyByAtom     atom energy
     @param totalEnergy      total energy
611

612
     --------------------------------------------------------------------------------------- */
613

peastman's avatar
peastman committed
614
void ReferenceLJCoulombIxn::calculateOneIxn(int ii, int jj, vector<Vec3>& atomCoordinates,
peastman's avatar
peastman committed
615
616
                                            double** atomParameters, vector<Vec3>& forces,
                                            double* energyByAtom, double* totalEnergy) const {
peastman's avatar
peastman committed
617
    double deltaR[2][ReferenceForce::LastDeltaRIndex];
618
619
620
621

    // get deltaR, R2, and R between 2 atoms

    if (periodic)
622
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[jj], atomCoordinates[ii], periodicBoxVectors, deltaR[0]);
623
    else
624
        ReferenceForce::getDeltaR(atomCoordinates[jj], atomCoordinates[ii], deltaR[0]);
625

peastman's avatar
peastman committed
626
627
628
    double r2        = deltaR[0][ReferenceForce::R2Index];
    double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
    double switchValue = 1, switchDeriv = 0;
629
    if (useSwitch) {
peastman's avatar
peastman committed
630
        double r = deltaR[0][ReferenceForce::RIndex];
631
        if (r > switchingDistance) {
peastman's avatar
peastman committed
632
            double t = (r-switchingDistance)/(cutoffDistance-switchingDistance);
633
634
635
636
            switchValue = 1+t*t*t*(-10+t*(15-t*6));
            switchDeriv = t*t*(-30+t*(60-t*30))/(cutoffDistance-switchingDistance);
        }
    }
peastman's avatar
peastman committed
637
638
639
640
    double sig = atomParameters[ii][SigIndex] +  atomParameters[jj][SigIndex];
    double sig2 = inverseR*sig;
    sig2 *= sig2;
    double sig6 = sig2*sig2*sig2;
641

peastman's avatar
peastman committed
642
643
    double eps = atomParameters[ii][EpsIndex]*atomParameters[jj][EpsIndex];
    double dEdR = switchValue*eps*(12.0*sig6 - 6.0)*sig6;
644
    if (cutoff)
peastman's avatar
peastman committed
645
        dEdR += ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*(inverseR-2.0f*krf*r2);
646
    else
peastman's avatar
peastman committed
647
        dEdR += ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*inverseR;
648
    dEdR     *= inverseR*inverseR;
peastman's avatar
peastman committed
649
    double energy = eps*(sig6-1.0)*sig6;
650
651
652
653
654
    if (useSwitch) {
        dEdR -= energy*switchDeriv*inverseR;
        energy *= switchValue;
    }
    if (cutoff)
peastman's avatar
peastman committed
655
        energy += ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*(inverseR+krf*r2-crf);
656
    else
peastman's avatar
peastman committed
657
        energy += ONE_4PI_EPS0*atomParameters[ii][QIndex]*atomParameters[jj][QIndex]*inverseR;
658

659
660
    // accumulate forces

661
    for (int kk = 0; kk < 3; kk++) {
peastman's avatar
peastman committed
662
        double force  = dEdR*deltaR[0][kk];
663
664
        forces[ii][kk]   += force;
        forces[jj][kk]   -= force;
665
666
667
668
    }

    // accumulate energies

669
    if (totalEnergy)
670
        *totalEnergy += energy;
671
    if (energyByAtom) {
672
673
        energyByAtom[ii] += energy;
        energyByAtom[jj] += energy;
674
    }
675
}
676