ReferenceCustomDynamics.cpp 18.2 KB
Newer Older
1

2
/* Portions copyright (c) 2011-2016 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
 * Contributors: Peter Eastman
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

25
#include "SimTKOpenMMUtilities.h"
26
#include "ReferenceVirtualSites.h"
27
#include "ReferenceCustomDynamics.h"
28
#include "ReferenceTabulatedFunction.h"
29
#include "openmm/OpenMMException.h"
30
31
32
#include "openmm/internal/ContextImpl.h"
#include "openmm/internal/ForceImpl.h"
#include "lepton/Operation.h"
33
34
#include "lepton/ParsedExpression.h"
#include "lepton/Parser.h"
35
#include <set>
36
#include <sstream>
37
38
39

using namespace std;
using namespace OpenMM;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
using namespace Lepton;

class ReferenceCustomDynamics::DerivFunction : public CustomFunction {
public:
    DerivFunction(map<string, double>& energyParamDerivs, const string& param) : energyParamDerivs(energyParamDerivs), param(param) {
    }
    int getNumArguments() const {
        return 0;
    }
    double evaluate(const double* arguments) const {
        return energyParamDerivs[param];
    }
    double evaluateDerivative(const double* arguments, const int* derivOrder) const {
        return 0;
    }
    CustomFunction* clone() const {
        return new DerivFunction(energyParamDerivs, param);
    }
private:
    map<string, double>& energyParamDerivs;
    string param;
};
62
63
64
65
66
67
68
69
70
71
72
73

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

   ReferenceCustomDynamics constructor

   @param numberOfAtoms  number of atoms
   @param integrator     the integrator definition to use

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

ReferenceCustomDynamics::ReferenceCustomDynamics(int numberOfAtoms, const CustomIntegrator& integrator) : 
           ReferenceDynamics(numberOfAtoms, integrator.getStepSize(), 0.0), integrator(integrator) {
74
    sumBuffer.resize(numberOfAtoms);
75
    oldPos.resize(numberOfAtoms);
76
77
78
79
80
    stepType.resize(integrator.getNumComputations());
    stepVariable.resize(integrator.getNumComputations());
    for (int i = 0; i < integrator.getNumComputations(); i++) {
        string expression;
        integrator.getComputationStep(i, stepType[i], stepVariable[i], expression);
81
    }
82
83
84
85
86
87
88
89
90
91
92
}

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

   ReferenceCustomDynamics destructor

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

ReferenceCustomDynamics::~ReferenceCustomDynamics() {
}

peastman's avatar
peastman committed
93
void ReferenceCustomDynamics::initialize(ContextImpl& context, vector<double>& masses, map<string, double>& globals) {
94
95
96
    // Some initialization can't be done in the constructor, since we need a ContextImpl from which to get the list of
    // Context parameters.  Instead, we do it the first time update() or computeKineticEnergy() is called.

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
    std::map<std::string, double*> variableLocations;
    variableLocations["x"] = &x;
    variableLocations["v"] = &v;
    variableLocations["m"] = &m;
    variableLocations["f"] = &f;
    variableLocations["energy"] = &energy;
    variableLocations["gaussian"] = &gaussian;
    variableLocations["uniform"] = &uniform;
    perDofVariable.resize(integrator.getNumPerDofVariables());
    for (int i = 0; i < integrator.getNumPerDofVariables(); i++)
        variableLocations[integrator.getPerDofVariableName(i)] = &perDofVariable[i];
    for (int i = 0; i < 32; i++) {
        stringstream fname;
        fname << "f" << i;
        variableLocations[fname.str()] = &f;
        stringstream ename;
        ename << "energy" << i;
        variableLocations[ename.str()] = &energy;
    }
    
117
118
119
120
121
122
    // Create custom functions for the tabulated functions.

    map<string, Lepton::CustomFunction*> functions;
    for (int i = 0; i < integrator.getNumTabulatedFunctions(); i++)
        functions[integrator.getTabulatedFunctionName(i)] = createReferenceTabulatedFunction(integrator.getTabulatedFunction(i));
    
123
124
    // Parse the expressions.
    
125
126
    int numSteps = stepType.size();
    vector<int> forceGroup;
127
    vector<vector<ParsedExpression> > expressions;
128
    CustomIntegratorUtilities::analyzeComputations(context, integrator, expressions, comparisons, blockEnd, invalidatesForces, needsForces, needsEnergy, computeBothForceAndEnergy, forceGroup, functions);
129
130
131
132
    stepExpressions.resize(expressions.size());
    for (int i = 0; i < numSteps; i++) {
        stepExpressions[i].resize(expressions[i].size());
        for (int j = 0; j < (int) expressions[i].size(); j++) {
133
            stepExpressions[i][j] = ParsedExpression(replaceDerivFunctions(expressions[i][j].getRootNode(), context)).createCompiledExpression();
134
            stepExpressions[i][j].setVariableLocations(variableLocations);
135
136
137
138
139
            expressionSet.registerExpression(stepExpressions[i][j]);
        }
        if (stepType[i] == CustomIntegrator::WhileBlockStart)
            blockEnd[blockEnd[i]] = i; // Record where to branch back to.
    }
140
141
142
143
144
145
    kineticEnergyExpression = Parser::parse(integrator.getKineticEnergyExpression()).optimize().createCompiledExpression();
    kineticEnergyExpression.setVariableLocations(variableLocations);
    expressionSet.registerExpression(kineticEnergyExpression);
    kineticEnergyNeedsForce = false;
    if (kineticEnergyExpression.getVariables().find("f") != kineticEnergyExpression.getVariables().end())
        kineticEnergyNeedsForce = true;
146

147
148
149
150
151
    // Delete the custom functions.

    for (auto& function : functions)
        delete function.second;

152
    // Record the force group flags for each step.
153
154

    forceGroupFlags.resize(numSteps, -1);
155
    for (int i = 0; i < numSteps; i++)
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        if (forceGroup[i] > -1)
            forceGroupFlags[i] = 1<<forceGroup[i];

    // Build the list of inverse masses.

    int numberOfAtoms = masses.size();
    inverseMasses.resize(numberOfAtoms);
    for (int i = 0; i < numberOfAtoms; i++) {
        if (masses[i] == 0.0)
            inverseMasses[i] = 0.0;
        else
            inverseMasses[i] = 1.0/masses[i];
    }

170
    // Record indices of variables.
171
172
173
174
175
176
177
178
179

    xIndex = expressionSet.getVariableIndex("x");
    vIndex = expressionSet.getVariableIndex("v");
    for (int i = 0; i < integrator.getNumPerDofVariables(); i++)
        perDofVariableIndex.push_back(expressionSet.getVariableIndex(integrator.getPerDofVariableName(i)));
    for (int i = 0; i < stepVariable.size(); i++)
        stepVariableIndex.push_back(expressionSet.getVariableIndex(stepVariable[i]));
}

180
181
182
183
184
185
186
187
188
189
ExpressionTreeNode ReferenceCustomDynamics::replaceDerivFunctions(const ExpressionTreeNode& node, ContextImpl& context) {
    const Operation& op = node.getOperation();
    if (op.getId() == Operation::CUSTOM && op.getName() == "deriv") {
        string param = node.getChildren()[1].getOperation().getName();
        if (context.getParameters().find(param) == context.getParameters().end())
            throw OpenMMException("The second argument to deriv() must be a context parameter");
        return ExpressionTreeNode(new Operation::Custom("deriv", new DerivFunction(energyParamDerivs, param)));
    }
    else {
        vector<ExpressionTreeNode> children;
peastman's avatar
peastman committed
190
191
        for (auto& child : node.getChildren())
            children.push_back(replaceDerivFunctions(child, context));
192
193
194
195
        return ExpressionTreeNode(op.clone(), children);
    }
}

196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
/**---------------------------------------------------------------------------------------

   Update -- driver routine for performing Custom dynamics update of coordinates
   and velocities

   @param context             the context this integrator is updating
   @param numberOfAtoms       number of atoms
   @param atomCoordinates     atom coordinates
   @param velocities          velocities
   @param forces              forces
   @param masses              atom masses
   @param globals             a map containing values of global variables
   @param forcesAreValid      whether the current forces are valid or need to be recomputed

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

peastman's avatar
peastman committed
212
213
214
void ReferenceCustomDynamics::update(ContextImpl& context, int numberOfAtoms, vector<Vec3>& atomCoordinates,
                                     vector<Vec3>& velocities, vector<Vec3>& forces, vector<double>& masses,
                                     map<string, double>& globals, vector<vector<Vec3> >& perDof, bool& forcesAreValid, double tolerance) {
215
216
    if (invalidatesForces.size() == 0)
        initialize(context, masses, globals);
217
218
    int numSteps = stepType.size();
    globals.insert(context.getParameters().begin(), context.getParameters().end());
peastman's avatar
peastman committed
219
220
    for (auto& global : globals)
        expressionSet.setVariable(expressionSet.getVariableIndex(global.first), global.second);
221
    oldPos = atomCoordinates;
222
223
224
    
    // Loop over steps and execute them.
    
225
226
227
    for (int step = 0; step < numSteps; ) {
        if ((needsForces[step] || needsEnergy[step]) && (!forcesAreValid || context.getLastForceGroups() != forceGroupFlags[step])) {
            // Recompute forces and/or energy.
228
            
229
230
            bool computeForce = needsForces[step] || computeBothForceAndEnergy[step];
            bool computeEnergy = needsEnergy[step] || computeBothForceAndEnergy[step];
231
            recordChangedParameters(context, globals);
peastman's avatar
peastman committed
232
            double e = context.calcForcesAndEnergy(computeForce, computeEnergy, forceGroupFlags[step]);
233
            if (computeEnergy) {
234
                energy = e;
235
236
                context.getEnergyParameterDerivatives(energyParamDerivs);
            }
237
238
239
240
            forcesAreValid = true;
        }
        
        // Execute the step.
241
242
243

        int nextStep = step+1;
        switch (stepType[step]) {
244
            case CustomIntegrator::ComputeGlobal: {
245
246
                uniform = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber();
                gaussian = SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
peastman's avatar
peastman committed
247
                double result = stepExpressions[step][0].evaluate();
248
249
                globals[stepVariable[step]] = result;
                expressionSet.setVariable(stepVariableIndex[step], result);
250
251
252
                break;
            }
            case CustomIntegrator::ComputePerDof: {
peastman's avatar
peastman committed
253
                vector<Vec3>* results = NULL;
254
                if (stepVariableIndex[step] == xIndex)
255
                    results = &atomCoordinates;
256
                else if (stepVariableIndex[step] == vIndex)
257
258
259
                    results = &velocities;
                else {
                    for (int j = 0; j < integrator.getNumPerDofVariables(); j++)
260
                        if (stepVariableIndex[step] == perDofVariableIndex[j])
261
262
263
                            results = &perDof[j];
                }
                if (results == NULL)
264
                    throw OpenMMException("Illegal per-DOF output variable: "+stepVariable[step]);
265
                computePerDof(numberOfAtoms, *results, atomCoordinates, velocities, forces, masses, perDof, stepExpressions[step][0]);
266
267
268
                break;
            }
            case CustomIntegrator::ComputeSum: {
269
                computePerDof(numberOfAtoms, sumBuffer, atomCoordinates, velocities, forces, masses, perDof, stepExpressions[step][0]);
peastman's avatar
peastman committed
270
                double sum = 0.0;
271
                for (int j = 0; j < numberOfAtoms; j++)
272
273
                    if (masses[j] != 0.0)
                        sum += sumBuffer[j][0]+sumBuffer[j][1]+sumBuffer[j][2];
274
                globals[stepVariable[step]] = sum;
275
                expressionSet.setVariable(stepVariableIndex[step], sum);
276
277
278
                break;
            }
            case CustomIntegrator::ConstrainPositions: {
279
                getReferenceConstraintAlgorithm()->apply(oldPos, atomCoordinates, inverseMasses, tolerance);
280
                oldPos = atomCoordinates;
281
282
283
                break;
            }
            case CustomIntegrator::ConstrainVelocities: {
284
                getReferenceConstraintAlgorithm()->applyToVelocities(oldPos, velocities, inverseMasses, tolerance);
285
                break;
286
287
288
289
290
            }
            case CustomIntegrator::UpdateContextState: {
                recordChangedParameters(context, globals);
                context.updateContextState();
                globals.insert(context.getParameters().begin(), context.getParameters().end());
peastman's avatar
peastman committed
291
292
                for (auto& global : globals)
                    expressionSet.setVariable(expressionSet.getVariableIndex(global.first), global.second);
293
294
                break;
            }
295
            case CustomIntegrator::IfBlockStart: {
296
                if (!evaluateCondition(step))
297
298
299
                    nextStep = blockEnd[step]+1;
                break;
            }
300
            case CustomIntegrator::WhileBlockStart: {
301
                if (!evaluateCondition(step))
302
303
304
                    nextStep = blockEnd[step]+1;
                break;
            }
305
            case CustomIntegrator::BlockEnd: {
306
307
308
                if (blockEnd[step] != -1)
                    nextStep = blockEnd[step]; // Return to the start of a while block.
                break;
309
310
            }
        }
311
        if (invalidatesForces[step])
312
            forcesAreValid = false;
313
        step = nextStep;
314
    }
315
    ReferenceVirtualSites::computePositions(context.getSystem(), atomCoordinates);
316
317
318
319
    incrementTimeStep();
    recordChangedParameters(context, globals);
}

peastman's avatar
peastman committed
320
321
322
void ReferenceCustomDynamics::computePerDof(int numberOfAtoms, vector<Vec3>& results, const vector<Vec3>& atomCoordinates,
              const vector<Vec3>& velocities, const vector<Vec3>& forces, const vector<double>& masses,
              const vector<vector<Vec3> >& perDof, const CompiledExpression& expression) {
323
    // Loop over all degrees of freedom.
324

325
    for (int i = 0; i < numberOfAtoms; i++) {
326
        if (masses[i] != 0.0) {
327
            m = masses[i];
328
329
330
            for (int j = 0; j < 3; j++) {
                // Compute the expression.

331
332
333
334
335
                x = atomCoordinates[i][j];
                v = velocities[i][j];
                f = forces[i][j];
                uniform = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber();
                gaussian = SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
336
                for (int k = 0; k < (int) perDof.size(); k++)
337
                    perDofVariable[k] = perDof[k][i][j];
338
                results[i][j] = expression.evaluate();
339
            }
340
341
342
343
        }
    }
}

344
bool ReferenceCustomDynamics::evaluateCondition(int step) {
345
346
    uniform = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber();
    gaussian = SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
347
348
    double lhs = stepExpressions[step][0].evaluate();
    double rhs = stepExpressions[step][1].evaluate();
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
    switch (comparisons[step]) {
        case CustomIntegratorUtilities::EQUAL:
            return (lhs == rhs);
        case CustomIntegratorUtilities::LESS_THAN:
            return (lhs < rhs);
        case CustomIntegratorUtilities::GREATER_THAN:
            return (lhs > rhs);
        case CustomIntegratorUtilities::NOT_EQUAL:
            return (lhs != rhs);
        case CustomIntegratorUtilities::LESS_THAN_OR_EQUAL:
            return (lhs <= rhs);
        case CustomIntegratorUtilities::GREATER_THAN_OR_EQUAL:
            return (lhs >= rhs);
    }
    throw OpenMMException("ReferenceCustomDynamics: Invalid comparison operator");
}

366
367
368
/**
 * Check which context parameters have changed and register them with the context.
 */
peastman's avatar
peastman committed
369
void ReferenceCustomDynamics::recordChangedParameters(OpenMM::ContextImpl& context, std::map<std::string, double>& globals) {
peastman's avatar
peastman committed
370
371
    for (auto& param : context.getParameters()) {
        string name = param.first;
372
        double value = globals[name];
peastman's avatar
peastman committed
373
        if (value != param.second)
374
375
376
            context.setParameter(name, globals[name]);
    }
}
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393

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

   Compute the kinetic energy of the system.

   @param context             the context this integrator is updating
   @param numberOfAtoms       number of atoms
   @param atomCoordinates     atom coordinates
   @param velocities          velocities
   @param forces              forces
   @param masses              atom masses
   @param globals             a map containing values of global variables
   @param perDof              the values of per-DOF variables
   @param forcesAreValid      whether the current forces are valid or need to be recomputed

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

peastman's avatar
peastman committed
394
395
396
double ReferenceCustomDynamics::computeKineticEnergy(OpenMM::ContextImpl& context, int numberOfAtoms, std::vector<OpenMM::Vec3>& atomCoordinates,
        std::vector<OpenMM::Vec3>& velocities, std::vector<OpenMM::Vec3>& forces, std::vector<double>& masses,
        std::map<std::string, double>& globals, std::vector<std::vector<OpenMM::Vec3> >& perDof, bool& forcesAreValid) {
397
398
    if (invalidatesForces.size() == 0)
        initialize(context, masses, globals);
399
    globals.insert(context.getParameters().begin(), context.getParameters().end());
peastman's avatar
peastman committed
400
401
    for (auto& global : globals)
        expressionSet.setVariable(expressionSet.getVariableIndex(global.first), global.second);
402
403
404
405
    if (kineticEnergyNeedsForce) {
        energy = context.calcForcesAndEnergy(true, true, -1);
        forcesAreValid = true;
    }
406
    computePerDof(numberOfAtoms, sumBuffer, atomCoordinates, velocities, forces, masses, perDof, kineticEnergyExpression);
peastman's avatar
peastman committed
407
    double sum = 0.0;
408
409
410
411
412
    for (int j = 0; j < numberOfAtoms; j++)
        if (masses[j] != 0.0)
            sum += sumBuffer[j][0]+sumBuffer[j][1]+sumBuffer[j][2];
    return sum;
}