ReferenceLJCoulombIxn.cpp 27.4 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

peastman's avatar
peastman committed
398
    for (auto& pair : *neighborList) {
399
400
401
        int ii = pair.first;
        int jj = pair.second;

peastman's avatar
peastman committed
402
        double deltaR[2][ReferenceForce::LastDeltaRIndex];
403
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[jj], atomCoordinates[ii], periodicBoxVectors, deltaR[0]);
peastman's avatar
peastman committed
404
405
406
        double r         = deltaR[0][ReferenceForce::RIndex];
        double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
        double switchValue = 1, switchDeriv = 0;
407
        if (useSwitch && r > switchingDistance) {
peastman's avatar
peastman committed
408
            double t = (r-switchingDistance)/(cutoffDistance-switchingDistance);
409
410
411
            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
412
        double alphaR = alphaEwald * r;
413
414


peastman's avatar
peastman committed
415
416
        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);
417

peastman's avatar
peastman committed
418
419
420
421
422
423
424
        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;
425
426

        if (ljpme) {
peastman's avatar
peastman committed
427
428
429
430
431
432
433
            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];
434
435
436
437
            // 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
438
            double emult = c6i*c6j*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4));
439
            dEdR += 6.0*c6i*c6j*inverseR2*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4 + dar6/6.0));
440

peastman's avatar
peastman committed
441
442
            double inverseCut2 = 1.0/(cutoffDistance*cutoffDistance);
            double inverseCut6 = inverseCut2*inverseCut2*inverseCut2;
443
444
445
            sig2 = atomParameters[ii][SigIndex] +  atomParameters[jj][SigIndex];
            sig2 *= sig2;
            sig6 = sig2*sig2*sig2;
446
            // The additive part of the potential shift
peastman's avatar
peastman committed
447
            double potentialshift = eps*(1.0-sig6*inverseCut6)*sig6*inverseCut6;
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
            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
464
            double force  = dEdR*deltaR[0][kk];
465
466
467
468
469
470
            forces[ii][kk]   += force;
            forces[jj][kk]   -= force;
        }

        // accumulate energies

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

        totalVdwEnergy             += vdwEnergy;
        totalRealSpaceEwaldEnergy  += realSpaceEwaldEnergy;
475

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

481
    }
482

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

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

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

peastman's avatar
peastman committed
496
                double deltaR[2][ReferenceForce::LastDeltaRIndex];
497
                ReferenceForce::getDeltaR(atomCoordinates[jj], atomCoordinates[ii], deltaR[0]);
peastman's avatar
peastman committed
498
499
500
                double r         = deltaR[0][ReferenceForce::RIndex];
                double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
                double alphaR    = alphaEwald * r;
501
                if (erf(alphaR) > 1e-6) {
peastman's avatar
peastman committed
502
503
                    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);
504
505
506
507

                    // accumulate forces

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

                    // accumulate energies

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

                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
523
524
525
526
527
528
529
                    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];
530
                    realSpaceEwaldEnergy -= c6i*c6j*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4));
peastman's avatar
peastman committed
531
                    double dEdR = -6.0*c6i*c6j*inverseR2*inverseR2*inverseR2*inverseR2*(1.0 - EXP(-dar2) * (1.0 + dar2 + 0.5*dar4 + dar6/6.0));
532
                    for (int kk = 0; kk < 3; kk++) {
peastman's avatar
peastman committed
533
534
535
                        double force = dEdR*deltaR[0][kk];
                        forces[ii][kk] -= force;
                        forces[jj][kk] += force;
536
537
538
539
540
541
542
543
                    }
                }

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

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


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

   Calculate LJ Coulomb pair ixn

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

   --------------------------------------------------------------------------------------- */
569

peastman's avatar
peastman committed
570
571
572
573
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 {
574

575
576
577
578
579
580
581
582
    if (ewald || pme || ljpme) {
        calculateEwaldIxn(numberOfAtoms, atomCoordinates, atomParameters, exclusions, fixedParameters, forces, energyByAtom,
                          totalEnergy, includeDirect, includeReciprocal);
        return;
    }
    if (!includeDirect)
        return;
    if (cutoff) {
peastman's avatar
peastman committed
583
        for (auto& pair : *neighborList)
584
585
586
587
588
589
590
591
592
593
594
            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);
        }
    }
595
}
596

597
/**---------------------------------------------------------------------------------------
598

599
     Calculate LJ Coulomb pair ixn between two atoms
600

601
602
603
604
605
606
607
     @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
608

609
     --------------------------------------------------------------------------------------- */
610

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

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

    if (periodic)
619
        ReferenceForce::getDeltaRPeriodic(atomCoordinates[jj], atomCoordinates[ii], periodicBoxVectors, deltaR[0]);
620
    else
621
        ReferenceForce::getDeltaR(atomCoordinates[jj], atomCoordinates[ii], deltaR[0]);
622

peastman's avatar
peastman committed
623
624
625
    double r2        = deltaR[0][ReferenceForce::R2Index];
    double inverseR  = 1.0/(deltaR[0][ReferenceForce::RIndex]);
    double switchValue = 1, switchDeriv = 0;
626
    if (useSwitch) {
peastman's avatar
peastman committed
627
        double r = deltaR[0][ReferenceForce::RIndex];
628
        if (r > switchingDistance) {
peastman's avatar
peastman committed
629
            double t = (r-switchingDistance)/(cutoffDistance-switchingDistance);
630
631
632
633
            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
634
635
636
637
    double sig = atomParameters[ii][SigIndex] +  atomParameters[jj][SigIndex];
    double sig2 = inverseR*sig;
    sig2 *= sig2;
    double sig6 = sig2*sig2*sig2;
638

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

656
657
    // accumulate forces

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

    // accumulate energies

666
    if (totalEnergy)
667
        *totalEnergy += energy;
668
    if (energyByAtom) {
669
670
        energyByAtom[ii] += energy;
        energyByAtom[jj] += energy;
671
    }
672
}
673