TestCudaAmoebaInPlaneAngleForce.cpp 15.9 KB
Newer Older
1
2
3
4
5
6
7
8
/* -------------------------------------------------------------------------- *
 *                                   OpenMMAmoeba                             *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
9
 * Portions copyright (c) 2008-2016 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
32
 * Authors: Mark Friedrichs                                                   *
 * 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.                                     *
 * -------------------------------------------------------------------------- */

/**
33
 * This tests the CUDA implementation of AmoebaInPlaneAngleForce.
34
35
36
37
38
39
40
 */

#include "openmm/internal/AssertionUtilities.h"
#include "openmm/Context.h"
#include "OpenMMAmoeba.h"
#include "openmm/System.h"
#include "openmm/LangevinIntegrator.h"
41
#include "SimTKOpenMMRealType.h"
42
43
44
45
46
#include <iostream>
#include <vector>

using namespace OpenMM;

47
48
extern "C" void registerAmoebaCudaKernelFactories();

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const double TOL = 1e-5;

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

   Compute cross product of two 3-vectors and place in 3rd vector

   vectorZ = vectorX x vectorY

   @param vectorX             x-vector
   @param vectorY             y-vector
   @param vectorZ             z-vector

   @return vector is vectorZ

   --------------------------------------------------------------------------------------- */
     
65
static void crossProductVector3(double* vectorX, double* vectorY, double* vectorZ) {
66
67
68
69
70
71
72
73

    vectorZ[0]  = vectorX[1]*vectorY[2] - vectorX[2]*vectorY[1];
    vectorZ[1]  = vectorX[2]*vectorY[0] - vectorX[0]*vectorY[2];
    vectorZ[2]  = vectorX[0]*vectorY[1] - vectorX[1]*vectorY[0];

    return;
}

74
static double dotVector3(double* vectorX, double* vectorY) {
75
76
77
    return vectorX[0]*vectorY[0] + vectorX[1]*vectorY[1] + vectorX[2]*vectorY[2];
}

78
79
80
static void getPrefactorsGivenInPlaneAngleCosine(double cosine, double idealInPlaneAngle, double quadraticK, double cubicK,
                                                 double quarticK, double penticK, double sexticK,
                                                 double* dEdR, double* energyTerm) {
81
82

    double angle;
83
    if (cosine >= 1.0) {
84
        angle = 0.0f;
85
    } else if (cosine <= -1.0) {
86
87
88
89
90
91
92
93
94
95
96
97
        angle = RADIAN*PI_M;
    } else {
        angle = RADIAN*acos(cosine);
    }

    double deltaIdeal         = angle - idealInPlaneAngle;
    double deltaIdeal2        = deltaIdeal*deltaIdeal;
    double deltaIdeal3        = deltaIdeal*deltaIdeal2;
    double deltaIdeal4        = deltaIdeal2*deltaIdeal2;
 
    // deltaIdeal = r - r_0
 
98
99
100
101
102
    *dEdR        = (2.0                        +
                    3.0*cubicK*  deltaIdeal    +
                    4.0*quarticK*deltaIdeal2   +
                    5.0*penticK* deltaIdeal3   +
                    6.0*sexticK* deltaIdeal4    );
103
104
105
106
107
108
109
110
111
112
113
114
115
 
    *dEdR       *= RADIAN*quadraticK*deltaIdeal;
 

    *energyTerm  = 1.0f + cubicK* deltaIdeal    +
                          quarticK*deltaIdeal2   +
                          penticK* deltaIdeal3   +
                          sexticK* deltaIdeal4;
    *energyTerm *= quadraticK*deltaIdeal2;

    return;
}

116
static void computeAmoebaInPlaneAngleForce(int bondIndex,  std::vector<Vec3>& positions, AmoebaInPlaneAngleForce& amoebaInPlaneAngleForce,
117
                                                   std::vector<Vec3>& forces, double* energy) {
118
119
120
121

    int particle1, particle2, particle3, particle4;
    double idealInPlaneAngle;
    double quadraticK;
122
    amoebaInPlaneAngleForce.getAngleParameters(bondIndex, particle1, particle2, particle3, particle4, idealInPlaneAngle, quadraticK);
123

124
125
126
127
    double cubicK         = amoebaInPlaneAngleForce.getAmoebaGlobalInPlaneAngleCubic();
    double quarticK       = amoebaInPlaneAngleForce.getAmoebaGlobalInPlaneAngleQuartic();
    double penticK        = amoebaInPlaneAngleForce.getAmoebaGlobalInPlaneAnglePentic();
    double sexticK        = amoebaInPlaneAngleForce.getAmoebaGlobalInPlaneAngleSextic();
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147

    // T   = AD x CD
    // P   = B + T*delta
    // AP  = A - P
    // CP  = A - P
    // M   = CP x AP

    enum { AD, BD, CD, T, AP, P, CP, M, APxM, CPxM, ADxBD, BDxCD, TxCD, ADxT, dBxAD, CDxdB, LastDeltaAtomIndex };
 
    // AD   0
    // BD   1
    // CD   2 
    //  T   3
    // AP   4
    //  P   5
    // CP   6
    // M    7
    // APxM, CPxM, ADxBD, BDxCD, TxCD, ADxT, dBxAD, CDxdB, LastDeltaAtomIndex

    double deltaR[LastDeltaAtomIndex][3];
148
    for (int ii = 0; ii < 3; ii++) {
149
150
151
152
        deltaR[AD][ii] = positions[particle1][ii] - positions[particle4][ii];
        deltaR[BD][ii] = positions[particle2][ii] - positions[particle4][ii];
        deltaR[CD][ii] = positions[particle3][ii] - positions[particle4][ii];
    }
153
    crossProductVector3(deltaR[AD], deltaR[CD], deltaR[T]);
154
 
155
156
    double rT2     = dotVector3(deltaR[T], deltaR[T]);
    double delta   = dotVector3(deltaR[T], deltaR[BD]);
157
158
         delta    *= -1.0/rT2;
 
159
    for (int ii = 0; ii < 3; ii++) {
160
161
162
163
164
       deltaR[P][ii]  = positions[particle2][ii] + deltaR[T][ii]*delta;
       deltaR[AP][ii] = positions[particle1][ii] - deltaR[P][ii];
       deltaR[CP][ii] = positions[particle3][ii] - deltaR[P][ii];
    }   
 
165
166
167
    double rAp2 = dotVector3(deltaR[AP],  deltaR[AP]);
    double rCp2 = dotVector3(deltaR[CP],  deltaR[CP]);
    if (rAp2 <= 0.0 && rCp2 <= 0.0) {
168
169
    }

170
    crossProductVector3(deltaR[CP], deltaR[AP], deltaR[M]);
171
 
172
173
174
    double rm = dotVector3(deltaR[M], deltaR[M]);
         rm   = sqrt(rm);
    if (rm < 0.000001) {
175
176
177
       rm = 0.000001;
    }
 
178
179
    double dot     = dotVector3(deltaR[AP], deltaR[CP]);
    double cosine  = dot/sqrt(rAp2*rCp2);
180
181
182
 
    double dEdR;
    double energyTerm;
183
184
    getPrefactorsGivenInPlaneAngleCosine(cosine, idealInPlaneAngle, quadraticK, cubicK,
                                         quarticK, penticK, sexticK, &dEdR,  &energyTerm);
185
186
187
188
 
    double termA   = -dEdR/(rAp2*rm);
    double termC   =  dEdR/(rCp2*rm);
 
189
190
    crossProductVector3(deltaR[AP], deltaR[M], deltaR[APxM]);
    crossProductVector3(deltaR[CP], deltaR[M], deltaR[CPxM]);
191
192
193
194
195
196
 
    // forces will be gathered here
 
    enum { dA, dB, dC, dD, LastDIndex };
    double forceTerm[LastDIndex][3];
 
197
    for (int ii = 0; ii < 3; ii++) {
198
199
       forceTerm[dA][ii] = deltaR[APxM][ii]*termA;
       forceTerm[dC][ii] = deltaR[CPxM][ii]*termC;
200
       forceTerm[dB][ii] = -1.0*(forceTerm[dA][ii] + forceTerm[dC][ii]);
201
202
    }
 
203
    double pTrT2  = dotVector3(forceTerm[dB], deltaR[T]);
204
205
         pTrT2   /= rT2;
 
206
207
    crossProductVector3(deltaR[CD], forceTerm[dB], deltaR[CDxdB]);
    crossProductVector3(forceTerm[dB], deltaR[AD], deltaR[dBxAD]);
208
 
209
    if (fabs(pTrT2) > 1.0e-08) {
210
211
       double delta2 = delta*2.0;
 
212
213
214
215
216
       crossProductVector3(deltaR[BD], deltaR[CD], deltaR[BDxCD]);
       crossProductVector3(deltaR[T],  deltaR[CD], deltaR[TxCD] );
       crossProductVector3(deltaR[AD], deltaR[BD], deltaR[ADxBD]);
       crossProductVector3(deltaR[AD], deltaR[T],  deltaR[ADxT] );
       for (int ii = 0; ii < 3; ii++) {
217
218
219
220
221
222
223
 
          double term           = deltaR[BDxCD][ii] + delta2*deltaR[TxCD][ii];
          forceTerm[dA][ii]  += delta*deltaR[CDxdB][ii] + term*pTrT2;
 
               term           = deltaR[ADxBD][ii] + delta2*deltaR[ADxT][ii];
          forceTerm[dC][ii]  += delta*deltaR[dBxAD][ii] + term*pTrT2;
 
224
          forceTerm[dD][ii]  = -(forceTerm[dA][ii] + forceTerm[dB][ii] + forceTerm[dC][ii]);
225
226
       }
    } else {
227
       for (int ii = 0; ii < 3; ii++) {
228
229
230
231
 
          forceTerm[dA][ii] += delta*deltaR[CDxdB][ii];
          forceTerm[dC][ii] += delta*deltaR[dBxAD][ii];
 
232
          forceTerm[dD][ii]  = -(forceTerm[dA][ii] + forceTerm[dB][ii] + forceTerm[dC][ii]);
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
       }
    }
 
    // accumulate forces and energy
 
    *energy                    += energyTerm;
 
    forces[particle1][0]       -= forceTerm[0][0];
    forces[particle1][1]       -= forceTerm[0][1];
    forces[particle1][2]       -= forceTerm[0][2];

    forces[particle2][0]       -= forceTerm[1][0];
    forces[particle2][1]       -= forceTerm[1][1];
    forces[particle2][2]       -= forceTerm[1][2];

    forces[particle3][0]       -= forceTerm[2][0];
    forces[particle3][1]       -= forceTerm[2][1];
    forces[particle3][2]       -= forceTerm[2][2];

    forces[particle4][0]       -= forceTerm[3][0];
    forces[particle4][1]       -= forceTerm[3][1];
    forces[particle4][2]       -= forceTerm[3][2];

}

258
259
static void computeAmoebaInPlaneAngleForces(Context& context, AmoebaInPlaneAngleForce& amoebaInPlaneAngleForce,
                                                     std::vector<Vec3>& expectedForces, double* expectedEnergy) {
260
261
262
263
264

    // get positions and zero forces

    State state                 = context.getState(State::Positions);
    std::vector<Vec3> positions = state.getPositions();
265
    expectedForces.resize(positions.size());
266
    
267
    for (unsigned int ii = 0; ii < expectedForces.size(); ii++) {
268
269
270
271
272
273
        expectedForces[ii][0] = expectedForces[ii][1] = expectedForces[ii][2] = 0.0;
    }

    // calculates forces/energy

    *expectedEnergy = 0.0;
274
    for (int ii = 0; ii < amoebaInPlaneAngleForce.getNumAngles(); ii++) {
peastman's avatar
peastman committed
275
        computeAmoebaInPlaneAngleForce(ii, positions, amoebaInPlaneAngleForce, expectedForces, expectedEnergy);
276
277
278
    }
}

279
280
void compareWithExpectedForceAndEnergy(Context& context, AmoebaInPlaneAngleForce& amoebaInPlaneAngleForce,
                                       double tolerance, const std::string& idString) {
281
282
283

    std::vector<Vec3> expectedForces;
    double expectedEnergy;
284
    computeAmoebaInPlaneAngleForces(context, amoebaInPlaneAngleForce, expectedForces, &expectedEnergy);
285
286
287
   
    State state                      = context.getState(State::Forces | State::Energy);
    const std::vector<Vec3> forces   = state.getForces();
288
289
    for (unsigned int ii = 0; ii < forces.size(); ii++) {
        ASSERT_EQUAL_VEC(expectedForces[ii], forces[ii], tolerance);
290
    }
291
    ASSERT_EQUAL_TOL(expectedEnergy, state.getPotentialEnergy(), tolerance);
292
293
}

peastman's avatar
peastman committed
294
void testOneAngle() {
295
296
297

    System system;
    int numberOfParticles = 4;
298
    for (int ii = 0; ii < numberOfParticles; ii++) {
299
300
301
302
303
        system.addParticle(1.0);
    }

    LangevinIntegrator integrator(0.0, 0.1, 0.01);

304
    AmoebaInPlaneAngleForce* amoebaInPlaneAngleForce = new AmoebaInPlaneAngleForce();
305
306
307
308
309
310
311

    double angle      = 65.0;
    double quadraticK = 1.0;
    double cubicK     = 0.0e-01;
    double quarticK   = 0.0e-02;
    double penticK    = 0.0e-03;
    double sexticK    = 0.0e-04;
312
    amoebaInPlaneAngleForce->addAngle(0, 1, 2, 3, angle, quadraticK);
313

314
315
316
317
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleCubic(cubicK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleQuartic(quarticK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAnglePentic(penticK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleSextic(sexticK);
318

319
    system.addForce(amoebaInPlaneAngleForce);
320
    Context context(system, integrator, Platform::getPlatformByName("CUDA"));
321
322
323
324
325
326
327
328
329

    std::vector<Vec3> positions(numberOfParticles);

    positions[0] = Vec3(0, 1, 0);
    positions[1] = Vec3(0, 0, 0);
    positions[2] = Vec3(0, 0, 1);
    positions[3] = Vec3(1, 1, 1);

    context.setPositions(positions);
330
    compareWithExpectedForceAndEnergy(context, *amoebaInPlaneAngleForce, TOL, "testOneInPlaneAngle");
331
332
333
334
335
336
337
    
    // Try changing the angle parameters and make sure it's still correct.
    
    amoebaInPlaneAngleForce->setAngleParameters(0, 0, 1, 2, 3, 1.1*angle, 1.4*quadraticK);
    bool exceptionThrown = false;
    try {
        // This should throw an exception.
338
        compareWithExpectedForceAndEnergy(context, *amoebaInPlaneAngleForce, TOL, "testOneInPlaneAngle");
339
340
341
342
343
344
    }
    catch (std::exception ex) {
        exceptionThrown = true;
    }
    ASSERT(exceptionThrown);
    amoebaInPlaneAngleForce->updateParametersInContext(context);
345
    compareWithExpectedForceAndEnergy(context, *amoebaInPlaneAngleForce, TOL, "testOneInPlaneAngle");
346
347
}

348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
void testPeriodic() {
    // Create a force that uses periodic boundary conditions.

    System system;
    system.setDefaultPeriodicBoxVectors(Vec3(3, 0, 0), Vec3(0, 3, 0), Vec3(0, 0, 3));
    int numberOfParticles = 4;
    for (int ii = 0; ii < numberOfParticles; ii++)
        system.addParticle(1.0);
    LangevinIntegrator integrator(0.0, 0.1, 0.01);
    AmoebaInPlaneAngleForce* amoebaInPlaneAngleForce = new AmoebaInPlaneAngleForce();
    double angle      = 65.0;
    double quadraticK = 1.0;
    double cubicK     = 0.0e-01;
    double quarticK   = 0.0e-02;
    double penticK    = 0.0e-03;
    double sexticK    = 0.0e-04;
    amoebaInPlaneAngleForce->addAngle(0, 1, 2, 3, angle, quadraticK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleCubic(cubicK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleQuartic(quarticK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAnglePentic(penticK);
    amoebaInPlaneAngleForce->setAmoebaGlobalInPlaneAngleSextic(sexticK);
    amoebaInPlaneAngleForce->setUsesPeriodicBoundaryConditions(true);
    system.addForce(amoebaInPlaneAngleForce);
    Context context(system, integrator, Platform::getPlatformByName("CUDA"));

    std::vector<Vec3> positions(numberOfParticles);
    positions[0] = Vec3(0, 1, 0);
    positions[1] = Vec3(0, 0, 0);
    positions[2] = Vec3(0, 0, 1);
    positions[3] = Vec3(1, 1, 1);

    context.setPositions(positions);
    State s1 = context.getState(State::Forces | State::Energy);
    
    // Move one atom to a position that should give identical results.

    positions[2] = Vec3(0, 0, -2);
    context.setPositions(positions);
    State s2 = context.getState(State::Forces | State::Energy);
    ASSERT_EQUAL_TOL(s1.getPotentialEnergy(), s2.getPotentialEnergy(), 1e-5);
    for (int i = 0; i < numberOfParticles; i++)
        ASSERT_EQUAL_VEC(s1.getForces()[i], s2.getForces()[i], 1e-5);
}

392
int main(int argc, char* argv[]) {
393
    try {
394
        std::cout << "TestCudaAmoebaInPlaneAngleForce running test..." << std::endl;
395
        registerAmoebaCudaKernelFactories();
396
        if (argc > 1)
397
            Platform::getPlatformByName("CUDA").setPropertyDefaultValue("Precision", std::string(argv[1]));
peastman's avatar
peastman committed
398
        testOneAngle();
399
        testPeriodic();
400
401
402
403
404
405
406
407
    } catch(const std::exception& e) {
        std::cout << "exception: " << e.what() << std::endl;
        std::cout << "FAIL - ERROR.  Test failed." << std::endl;
        return 1;
    }
    std::cout << "Done" << std::endl;
    return 0;
}