CpuGayBerneForce.cpp 20 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.               *
 *                                                                            *
peastman's avatar
peastman committed
9
 * Portions copyright (c) 2016-2017 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.                                     *
 * -------------------------------------------------------------------------- */

peastman's avatar
peastman committed
32
33
34
35
#ifdef _MSC_VER
    // Prevent Windows from defining macros that interfere with other code.
    #define NOMINMAX
#endif
36
37
38
39
#include "CpuGayBerneForce.h"
#include "ReferenceForce.h"
#include "openmm/OpenMMException.h"
#include "openmm/GayBerneForce.h"
40
#include "openmm/internal/gmx_atomic.h"
peastman's avatar
peastman committed
41
#include <algorithm>
42
43
44
45
46
#include <cmath>

using namespace OpenMM;
using namespace std;

47
CpuGayBerneForce::CpuGayBerneForce(const GayBerneForce& force) {
48
49
50
51
52
53
54
55
56
57
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
    // Record the force parameters.

    int numParticles = force.getNumParticles();
    particles.resize(numParticles);
    for (int i = 0; i < numParticles; i++) {
        ParticleInfo& p = particles[i];
        double sigma, epsilon, sx, sy, sz, ex, ey, ez;
        force.getParticleParameters(i, sigma, epsilon, p.xparticle, p.yparticle, sx, sy, sz, ex, ey, ez);
        p.sigmaOver2 = 0.5*sigma;
        p.sqrtEpsilon = sqrt(epsilon);
        p.rx = 0.5*sx;
        p.ry = 0.5*sy;
        p.rz = 0.5*sz;
        p.ex = ex;
        p.ey = ey;
        p.ez = ez;
        p.isPointParticle = (sx == sigma && sy == sigma && sz == sigma && ex == 1.0 && ey == 1.0 && ez == 1.0);
    }
    int numExceptions = force.getNumExceptions();
    exceptions.resize(numExceptions);
    particleExclusions.resize(numParticles);
    for (int i = 0; i < numExceptions; i++) {
        ExceptionInfo& e = exceptions[i];
        double sigma, epsilon;
        force.getExceptionParameters(i, e.particle1, e.particle2, sigma, epsilon);
        e.sigma = sigma;
        e.epsilon = epsilon;
        exclusions.insert(make_pair(min(e.particle1, e.particle2), max(e.particle1, e.particle2)));
        particleExclusions[e.particle1].insert(e.particle2);
        particleExclusions[e.particle2].insert(e.particle1);
    }
    nonbondedMethod = force.getNonbondedMethod();
    cutoffDistance = force.getCutoffDistance();
    switchingDistance = force.getSwitchingDistance();
    useSwitchingFunction = force.getUseSwitchingFunction();

    // Allocate workspace for calculations.

    s.resize(numParticles);
    A.resize(numParticles);
    B.resize(numParticles);
    G.resize(numParticles);

    // We can precompute the shape factors.

    for (int i = 0; i < numParticles; i++) {
        ParticleInfo& p = particles[i];
        s[i] = (p.rx*p.ry + p.rz*p.rz)*sqrtf(p.rx*p.ry);
    }
}

99
100
const vector<set<int> >& CpuGayBerneForce::getExclusions() const {
    return particleExclusions;
101
102
}

peastman's avatar
peastman committed
103
double CpuGayBerneForce::calculateForce(const vector<Vec3>& positions, std::vector<Vec3>& forces, std::vector<AlignedArray<float> >& threadForce, Vec3* boxVectors, CpuPlatform::PlatformData& data) {
104
105
106
107
108
109
110
111
112
113
    if (nonbondedMethod == GayBerneForce::CutoffPeriodic) {
        double minAllowedSize = 1.999999*cutoffDistance;
        if (boxVectors[0][0] < minAllowedSize || boxVectors[1][1] < minAllowedSize || boxVectors[2][2] < minAllowedSize)
            throw OpenMMException("The periodic box size has decreased to less than twice the nonbonded cutoff.");
    }

    // First find the orientations of the particles and compute the matrices we'll be needing.

    computeEllipsoidFrames(positions);

114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
    // Record the parameters for the threads.
    
    ThreadPool& threads = data.threads;
    int numThreads = threads.getNumThreads();
    this->positions = &positions[0];
    this->threadForce = &threadForce;
    this->boxVectors = boxVectors;
    threadEnergy.resize(numThreads);
    threadTorque.resize(numThreads);
    gmx_atomic_t counter;
    gmx_atomic_set(&counter, 0);
    this->atomicCounter = &counter;
    
    // Signal the threads to compute the pairwise interactions.
    
peastman's avatar
peastman committed
129
    threads.execute([&] (ThreadPool& threads, int threadIndex) { threadComputeForce(threads, threadIndex, data.neighborList); });
130
131
132
133
134
135
136
137
138
139
    threads.waitForThreads();
    
    // Signal the threads to compute exceptions.
    
    gmx_atomic_set(&counter, 0);
    threads.resumeThreads();
    threads.waitForThreads();
    
    // Combine the energies from all the threads.
    
140
    double energy = 0;
141
142
143
144
145
146
147
148
149
    for (int i = 0; i < numThreads; i++)
        energy += threadEnergy[i];
    
    // Apply torques.
    
    applyTorques(positions, forces);
    return energy;
}

150
void CpuGayBerneForce::threadComputeForce(ThreadPool& threads, int threadIndex, CpuNeighborList* neighborList) {
151
152
153
154
    int numParticles = particles.size();
    int numThreads = threads.getNumThreads();
    threadEnergy[threadIndex] = 0;
    float* forces = &(*threadForce)[threadIndex][0];
peastman's avatar
peastman committed
155
    vector<Vec3>& torques = threadTorque[threadIndex];
156
157
    torques.resize(numParticles);
    for (int i = 0; i < numParticles; i++)
peastman's avatar
peastman committed
158
        torques[i] = Vec3();
159
160
161
162
    double energy = 0.0;

    // Compute this thread's subset of interactions.
    
163
    if (neighborList == NULL) {
164
165
166
167
        while (true) {
            int i = gmx_atomic_fetch_add(reinterpret_cast<gmx_atomic_t*>(atomicCounter), 1);
            if (i >= numParticles)
                break;
168
169
170
171
172
173
174
            if (particles[i].sqrtEpsilon == 0.0f)
                continue;
            for (int j = 0; j < i; j++) {
                if (particles[j].sqrtEpsilon == 0.0f)
                    continue;
                if (particleExclusions[i].find(j) != particleExclusions[i].end())
                    continue; // This interaction will be handled by an exception.
peastman's avatar
peastman committed
175
176
                double sigma = particles[i].sigmaOver2+particles[j].sigmaOver2;
                double epsilon = particles[i].sqrtEpsilon*particles[j].sqrtEpsilon;
177
178
179
180
181
                energy += computeOneInteraction(i, j, sigma, epsilon, positions, forces, torques, boxVectors);
            }
        }
    }
    else {
182
183
184
185
        while (true) {
            int blockIndex = gmx_atomic_fetch_add(reinterpret_cast<gmx_atomic_t*>(atomicCounter), 1);
            if (blockIndex >= neighborList->getNumBlocks())
                break;
186
187
            const int blockSize = neighborList->getBlockSize();
            const int* blockAtom = &neighborList->getSortedAtoms()[blockSize*blockIndex];
188
189
190
191
192
193
            const vector<int>& neighbors = neighborList->getBlockNeighbors(blockIndex);
            const vector<char>& exclusions = neighborList->getBlockExclusions(blockIndex);
            for (int i = 0; i < (int) neighbors.size(); i++) {
                int first = neighbors[i];
                if (particles[first].sqrtEpsilon == 0.0f)
                    continue;
194
                for (int k = 0; k < blockSize; k++) {
195
196
197
198
                    if ((exclusions[i] & (1<<k)) == 0) {
                        int second = blockAtom[k];
                        if (particles[second].sqrtEpsilon == 0.0f)
                            continue;
peastman's avatar
peastman committed
199
200
                        double sigma = particles[first].sigmaOver2+particles[second].sigmaOver2;
                        double epsilon = particles[first].sqrtEpsilon*particles[second].sqrtEpsilon;
201
202
203
204
205
206
207
208
209
                        energy += computeOneInteraction(first, second, sigma, epsilon, positions, forces, torques, boxVectors);
                    }
                }
            }
        }
    }

    // Compute exceptions.

210
    threads.syncThreads();
211
    int numExceptions = exceptions.size();
212
213
214
215
216
217
218
219
220
221
    const int groupSize = max(1, numExceptions/(10*numThreads));
    while (true) {
        int start = gmx_atomic_fetch_add(reinterpret_cast<gmx_atomic_t*>(atomicCounter), groupSize);
        if (start >= numExceptions)
            break;
        int end = min(start+groupSize, numExceptions);
        for (int i = start; i < end; i++) {
            ExceptionInfo& e = exceptions[i];
            energy += computeOneInteraction(e.particle1, e.particle2, e.sigma, e.epsilon, positions, forces, torques, boxVectors);
        }
222
    }
223
    threadEnergy[threadIndex] = energy;
224
225
}

peastman's avatar
peastman committed
226
void CpuGayBerneForce::computeEllipsoidFrames(const vector<Vec3>& positions) {
227
228
229
230
231
232
    int numParticles = particles.size();
    for (int particle = 0; particle < numParticles; particle++) {
        ParticleInfo& p = particles[particle];

        // Compute the local coordinate system of the ellipsoid;

peastman's avatar
peastman committed
233
        Vec3 xdir, ydir, zdir;
234
        if (p.xparticle == -1) {
peastman's avatar
peastman committed
235
236
            xdir = Vec3(1, 0, 0);
            ydir = Vec3(0, 1, 0);
237
238
239
        }
        else {
            xdir = positions[particle]-positions[p.xparticle];
peastman's avatar
peastman committed
240
            xdir /= sqrt(xdir.dot(xdir));
241
242
            if (p.yparticle == -1) {
                if (xdir[1] > -0.5 && xdir[1] < 0.5)
peastman's avatar
peastman committed
243
                    ydir = Vec3(0, 1, 0);
244
                else
peastman's avatar
peastman committed
245
                    ydir = Vec3(1, 0, 0);
246
247
248
249
            }
            else
                ydir = positions[particle]-positions[p.yparticle];
            ydir -= xdir*(xdir.dot(ydir));
peastman's avatar
peastman committed
250
            ydir /= sqrt(ydir.dot(ydir));
251
252
253
254
255
        }
        zdir = xdir.cross(ydir);

        // Compute matrices we will need later.

peastman's avatar
peastman committed
256
257
258
        double (&a)[3][3] = A[particle].v;
        double (&b)[3][3] = B[particle].v;
        double (&g)[3][3] = G[particle].v;
259
260
261
262
263
264
265
266
267
        a[0][0] = xdir[0];
        a[0][1] = xdir[1];
        a[0][2] = xdir[2];
        a[1][0] = ydir[0];
        a[1][1] = ydir[1];
        a[1][2] = ydir[2];
        a[2][0] = zdir[0];
        a[2][1] = zdir[1];
        a[2][2] = zdir[2];
peastman's avatar
peastman committed
268
269
        Vec3 r2(p.rx*p.rx, p.ry*p.ry, p.rz*p.rz);
        Vec3 e2(1/sqrt(p.ex), 1/sqrt(p.ey), 1/sqrt(p.ez));
270
271
272
273
274
275
276
277
278
279
280
281
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++) {
                b[i][j] = 0;
                g[i][j] = 0;
                for (int k = 0; k < 3; k++) {
                    b[i][j] += a[k][i]*e2[k]*a[k][j];
                    g[i][j] += a[k][i]*r2[k]*a[k][j];
                }
            }
    }
}

peastman's avatar
peastman committed
282
void CpuGayBerneForce::applyTorques(const vector<Vec3>& positions, vector<Vec3>& forces) {
283
    int numParticles = particles.size();
284
    int numThreads = threadTorque.size();
285
286
    for (int particle = 0; particle < numParticles; particle++) {
        ParticleInfo& p = particles[particle];
peastman's avatar
peastman committed
287
        Vec3 pos = positions[particle];
288
        if (p.xparticle != -1) {
289
290
            // Add up the torques from the individual threads.
            
peastman's avatar
peastman committed
291
            Vec3 torque;
292
293
294
            for (int i = 0; i < numThreads; i++)
                torque += threadTorque[i][particle];
            
295
296
            // Apply a force to the x particle.
            
peastman's avatar
peastman committed
297
            Vec3 dx = positions[p.xparticle]-pos;
298
            double dx2 = dx.dot(dx);
peastman's avatar
peastman committed
299
            Vec3 f = torque.cross(dx)/dx2;
300
301
302
303
304
305
            forces[p.xparticle] += f;
            forces[particle] -= f;
            if (p.yparticle != -1) {
                // Apply a force to the y particle.  This is based on the component of the torque
                // that was not already applied to the x particle.
                
peastman's avatar
peastman committed
306
                Vec3 dy = positions[p.yparticle]-pos;
307
                double dy2 = dy.dot(dy);
peastman's avatar
peastman committed
308
                Vec3 torque2 = dx*(torque.dot(dx)/dx2);
309
                f = torque2.cross(dy)/dy2;
310
311
312
313
314
315
316
                forces[p.yparticle] += f;
                forces[particle] -= f;
            }
        }
    }
}

peastman's avatar
peastman committed
317
318
double CpuGayBerneForce::computeOneInteraction(int particle1, int particle2, double sigma, double epsilon, const Vec3* positions,
        float* forces, vector<Vec3>& torques, const Vec3* boxVectors) {
319
320
    // Compute the displacement and check against the cutoff.

peastman's avatar
peastman committed
321
    double deltaR[ReferenceForce::LastDeltaRIndex];
322
323
324
325
    if (nonbondedMethod == GayBerneForce::CutoffPeriodic)
        ReferenceForce::getDeltaRPeriodic(positions[particle2], positions[particle1], boxVectors, deltaR);
    else
        ReferenceForce::getDeltaR(positions[particle2], positions[particle1], deltaR);
peastman's avatar
peastman committed
326
    double r = deltaR[ReferenceForce::RIndex];
327
328
    if (nonbondedMethod != GayBerneForce::NoCutoff && r >= cutoffDistance)
        return 0;
peastman's avatar
peastman committed
329
330
331
    double rInv = 1/r;
    Vec3 dr(deltaR[ReferenceForce::XIndex], deltaR[ReferenceForce::YIndex], deltaR[ReferenceForce::ZIndex]);
    Vec3 drUnit = dr*rInv;
332
333
334
    
    // Compute the switching function.

peastman's avatar
peastman committed
335
    double switchValue = 1, switchDeriv = 0;
336
    if (useSwitchingFunction && r > switchingDistance) {
peastman's avatar
peastman committed
337
        double t = (r-switchingDistance)/(cutoffDistance-switchingDistance);
338
339
340
341
342
343
344
        switchValue = 1+t*t*t*(-10+t*(15-t*6));
        switchDeriv = t*t*(-30+t*(60-t*30))/(cutoffDistance-switchingDistance);
    }

    // Interactions between two point particles can be computed more easily.
    
    if (particles[particle1].isPointParticle && particles[particle2].isPointParticle) {
peastman's avatar
peastman committed
345
346
347
348
349
        double sig = sigma*rInv;
        double sig2 = sig*sig;
        double sig6 = sig2*sig2*sig2;
        double energy = 4*epsilon*(sig6-1)*sig6;
        Vec3 force = drUnit*(switchValue*4*epsilon*(12*sig6 - 6)*sig6*rInv - energy*switchDeriv);
350
351
352
353
354
355
        forces[4*particle1] += force[0];
        forces[4*particle1+1] += force[1];
        forces[4*particle1+2] += force[2];
        forces[4*particle2] -= force[0];
        forces[4*particle2+1] -= force[1];
        forces[4*particle2+2] -= force[2];
356
357
358
359
360
361
362
363
364
        return energy*switchValue;
    }

    // Compute vectors and matrices we'll be needing.

    Matrix B12 = B[particle1]+B[particle2];
    Matrix G12 = G[particle1]+G[particle2];
    Matrix B12inv = B12.inverse();
    Matrix G12inv = G12.inverse();
peastman's avatar
peastman committed
365
    double detG12 = G12.determinant();
366
367
368

    // Estimate the distance between the ellipsoids and compute the first terms needed for the energy.

peastman's avatar
peastman committed
369
370
371
372
373
374
375
376
    double sigma12 = 1/sqrt(0.5*drUnit.dot(G12inv*drUnit));
    double h12 = r - sigma12;
    double rho = sigma/(h12+sigma);
    double rho2 = rho*rho;
    double rho6 = rho2*rho2*rho2;
    double u = 4*epsilon*(rho6*rho6-rho6);
    double eta = sqrt(2*s[particle1]*s[particle2]/detG12);
    double chi = 2*drUnit.dot(B12inv*drUnit);
377
    chi *= chi;
peastman's avatar
peastman committed
378
    double energy = u*eta*chi;
379
380
381
    
    // Compute the terms needed for the force.

peastman's avatar
peastman committed
382
383
384
385
386
387
388
389
    Vec3 kappa = G12inv*dr;
    Vec3 iota = B12inv*dr;
    double rInv2 = rInv*rInv;
    double dUSLJdr = 24*epsilon*(2*rho6-1)*rho6*rho/sigma;
    double temp = 0.5*sigma12*sigma12*sigma12*rInv2;
    Vec3 dudr = (drUnit + (kappa-drUnit*kappa.dot(drUnit))*temp)*dUSLJdr;
    Vec3 dchidr = (iota-drUnit*iota.dot(drUnit))*(-8*rInv2*sqrt(chi));
    Vec3 force = (dchidr*u + dudr*chi)*(eta*switchValue) - drUnit*(energy*switchDeriv);
390
391
392
393
394
395
    forces[4*particle1] += force[0];
    forces[4*particle1+1] += force[1];
    forces[4*particle1+2] += force[2];
    forces[4*particle2] -= force[0];
    forces[4*particle2+1] -= force[1];
    forces[4*particle2+2] -= force[2];
396
397
398
399
400

    // Compute the terms needed for the torque.

    for (int j = 0; j < 2; j++) {
        int particle = (j == 0 ? particle1 : particle2);
401
402
403
        ParticleInfo& p = particles[particle];
        if (p.isPointParticle)
            continue;
peastman's avatar
peastman committed
404
405
406
407
408
        Vec3 dudq = (kappa*G[particle]).cross(kappa*(temp*dUSLJdr));
        Vec3 dchidq = (iota*B[particle]).cross(iota)*(-4*rInv2);
        double (&g12)[3][3] = G12.v;
        double (&a)[3][3] = A[particle].v;
        Vec3 scale = Vec3(p.rx*p.rx, p.ry*p.ry, p.rz*p.rz)*(-0.5*eta/detG12);
409
        Matrix D;
peastman's avatar
peastman committed
410
        double (&d)[3][3] = D.v;
peastman's avatar
peastman committed
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        d[0][0] = scale[0]*(2*a[0][0]*(g12[1][1]*g12[2][2] - g12[1][2]*g12[2][1]) +
                              a[0][2]*(g12[1][2]*g12[0][1] + g12[1][0]*g12[2][1] - g12[1][1]*(g12[0][2] + g12[2][0])) +
                              a[0][1]*(g12[0][2]*g12[2][1] + g12[2][0]*g12[1][2] - g12[2][2]*(g12[0][1] + g12[1][0])));
        d[0][1] = scale[0]*(  a[0][0]*(g12[0][2]*g12[2][1] + g12[2][0]*g12[1][2] - g12[2][2]*(g12[0][1] + g12[1][0])) +
                            2*a[0][1]*(g12[0][0]*g12[2][2] - g12[2][0]*g12[0][2]) +
                              a[0][2]*(g12[1][0]*g12[0][2] + g12[2][0]*g12[0][1] - g12[0][0]*(g12[1][2] + g12[2][1])));
        d[0][2] = scale[0]*(  a[0][0]*(g12[0][1]*g12[1][2] + g12[1][0]*g12[2][1] - g12[1][1]*(g12[0][2] + g12[2][0])) +
                              a[0][1]*(g12[1][0]*g12[0][2] + g12[2][0]*g12[0][1] - g12[0][0]*(g12[1][2] + g12[2][1])) +
                            2*a[0][2]*(g12[1][1]*g12[0][0] - g12[1][0]*g12[0][1]));
        d[1][0] = scale[1]*(2*a[1][0]*(g12[1][1]*g12[2][2] - g12[1][2]*g12[2][1]) +
                              a[1][1]*(g12[0][2]*g12[2][1] + g12[2][0]*g12[1][2] - g12[2][2]*(g12[0][1] + g12[1][0])) +
                              a[1][2]*(g12[1][2]*g12[0][1] + g12[1][0]*g12[2][1] - g12[1][1]*(g12[0][2] + g12[2][0])));
        d[1][1] = scale[1]*(  a[1][0]*(g12[0][2]*g12[2][1] + g12[2][0]*g12[1][2] - g12[2][2]*(g12[0][1] + g12[1][0])) +
                            2*a[1][1]*(g12[2][2]*g12[0][0] - g12[2][0]*g12[0][2]) +
                              a[1][2]*(g12[1][0]*g12[0][2] + g12[0][1]*g12[2][0] - g12[0][0]*(g12[1][2] + g12[2][1])));
        d[1][2] = scale[1]*(  a[1][0]*(g12[0][1]*g12[1][2] + g12[1][0]*g12[2][1] - g12[1][1]*(g12[0][2] + g12[2][0])) +
                              a[1][1]*(g12[1][0]*g12[0][2] + g12[0][1]*g12[2][0] - g12[0][0]*(g12[1][2] + g12[2][1])) +
                            2*a[1][2]*(g12[1][1]*g12[0][0] - g12[1][0]*g12[0][1]));
        d[2][0] = scale[2]*(2*a[2][0]*(g12[1][1]*g12[2][2] - g12[2][1]*g12[1][2]) +
                              a[2][1]*(g12[0][2]*g12[2][1] + g12[1][2]*g12[2][0] - g12[2][2]*(g12[0][1] + g12[1][0])) +
                              a[2][2]*(g12[0][1]*g12[1][2] + g12[2][1]*g12[1][0] - g12[1][1]*(g12[0][2] + g12[2][0])));
        d[2][1] = scale[2]*(  a[2][0]*(g12[0][2]*g12[2][1] + g12[1][2]*g12[2][0] - g12[2][2]*(g12[0][1] + g12[1][0])) +
                            2*a[2][1]*(g12[0][0]*g12[2][2] - g12[0][2]*g12[2][0]) +
                              a[2][2]*(g12[1][0]*g12[0][2] + g12[0][1]*g12[2][0] - g12[0][0]*(g12[1][2] + g12[2][1])));
        d[2][2] = scale[2]*(  a[2][0]*(g12[0][1]*g12[1][2] + g12[2][1]*g12[1][0] - g12[1][1]*(g12[0][2] + g12[2][0])) +
                              a[2][1]*(g12[1][0]*g12[0][2] + g12[2][0]*g12[0][1] - g12[0][0]*(g12[1][2] + g12[2][1])) +
                            2*a[2][2]*(g12[1][1]*g12[0][0] - g12[1][0]*g12[0][1]));
peastman's avatar
peastman committed
438
        Vec3 detadq;
439
        for (int i = 0; i < 3; i++)
peastman's avatar
peastman committed
440
441
            detadq += Vec3(a[i][0], a[i][1], a[i][2]).cross(Vec3(d[i][0], d[i][1], d[i][2]));
        Vec3 torque = (dchidq*(u*eta) + detadq*(u*chi) + dudq*(eta*chi))*switchValue;
442
443
444
445
        torques[particle] -= torque;
    }
    return switchValue*energy;
}