void CustomIntegratorUtilities::analyzeComputations(const ContextImpl& context, const CustomIntegrator& integrator, vector<vector<Lepton::ParsedExpression> >& expressions, vector<Comparison>& comparisons, vector<int>& blockEnd, vector<bool>& invalidatesForces, vector<bool>& needsForces, vector<bool>& needsEnergy, vector<bool>& computeBoth, vector<int>& forceGroup) { int numSteps = integrator.getNumComputations(); expressions.resize(numSteps); comparisons.resize(numSteps); invalidatesForces.resize(numSteps, false); needsForces.resize(numSteps, false); needsEnergy.resize(numSteps, false); computeBoth.resize(numSteps, false); forceGroup.resize(numSteps, -2); vector<CustomIntegrator::ComputationType> stepType(numSteps); vector<string> stepVariable(numSteps); // Parse the expressions. for (int step = 0; step < numSteps; step++) { string expression; integrator.getComputationStep(step, stepType[step], stepVariable[step], expression); if (stepType[step] == CustomIntegrator::BeginIfBlock || stepType[step] == CustomIntegrator::BeginWhileBlock) { // This step involves a condition. string lhs, rhs; parseCondition(expression, lhs, rhs, comparisons[step]); expressions[step].push_back(Lepton::Parser::parse(lhs).optimize()); expressions[step].push_back(Lepton::Parser::parse(rhs).optimize()); } else if (expression.size() > 0) expressions[step].push_back(Lepton::Parser::parse(expression).optimize()); } // Identify which steps invalidate the forces. set<string> affectsForce; affectsForce.insert("x"); for (vector<ForceImpl*>::const_iterator iter = context.getForceImpls().begin(); iter != context.getForceImpls().end(); ++iter) { const map<string, double> params = (*iter)->getDefaultParameters(); for (map<string, double>::const_iterator param = params.begin(); param != params.end(); ++param) affectsForce.insert(param->first); } for (int i = 0; i < numSteps; i++) invalidatesForces[i] = (stepType[i] == CustomIntegrator::ConstrainPositions || affectsForce.find(stepVariable[i]) != affectsForce.end()); // Make a list of which steps require valid forces or energy to be known. vector<string> forceGroupName; vector<string> energyGroupName; for (int i = 0; i < 32; i++) { stringstream fname; fname << "f" << i; forceGroupName.push_back(fname.str()); stringstream ename; ename << "energy" << i; energyGroupName.push_back(ename.str()); } for (int step = 0; step < numSteps; step++) { for (int expr = 0; expr < expressions[step].size(); expr++) { if (usesVariable(expressions[step][expr], "f")) { needsForces[step] = true; forceGroup[step] = -1; } if (usesVariable(expressions[step][expr], "energy")) { needsEnergy[step] = true; forceGroup[step] = -1; } for (int i = 0; i < 32; i++) { if (usesVariable(expressions[step][expr], forceGroupName[i])) { if (forceGroup[step] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsForces[step] = true; forceGroup[step] = i; } if (usesVariable(expressions[step][expr], energyGroupName[i])) { if (forceGroup[step] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsEnergy[step] = true; forceGroup[step] = i; } } } } for (int step = numSteps-2; step >= 0; step--) if (forceGroup[step] == -2) forceGroup[step] = forceGroup[step+1]; // Find the end point of each block. vector<int> blockStart; blockEnd.resize(numSteps, -1); for (int step = 0; step < numSteps; step++) { if (stepType[step] == CustomIntegrator::BeginIfBlock || stepType[step] == CustomIntegrator::BeginWhileBlock) blockStart.push_back(step); else if (stepType[step] == CustomIntegrator::EndBlock) { if (blockStart.size() == 0) { stringstream error("CustomIntegrator: Unexpected end of block at computation "); error << step; throw OpenMMException(error.str()); } blockEnd[blockStart.back()] = step; blockStart.pop_back(); } } if (blockStart.size() > 0) throw OpenMMException("CustomIntegrator: Missing EndBlock"); // If a step requires either forces or energy, and a later step will require the other one, it's most efficient // to compute both at the same time. Figure out whether we should do that. In principle it's easy: step through // the sequence of computations and see if the other one is used before the next time they get invalidated. // Unfortunately, flow control makes this much more complicated, because there are many possible paths to // consider. // // The cost of computing both when we really only needed one is much less than the cost of computing only one, // then later finding we need to compute the other separately. So we always err on the side of computing both. // If there is any possible path that would lead to us needing it, go ahead and compute it. // // So we need to enumerate all possible paths. For each "if" block, there are two possibilities: execute it // or don't. For each "while" block there are three possibilities: don't execute it; execute it and then // continue on; or execute it and then jump back to the beginning. I'm assuming the number of blocks will // always remain small. Otherwise, this could become very expensive! vector<int> jumps(numSteps, -1); vector<int> stepsInPath; enumeratePaths(0, stepsInPath, jumps, blockEnd, stepType, needsForces, needsEnergy, invalidatesForces, forceGroup, computeBoth); }
void ReferenceCustomDynamics::update(ContextImpl& context, int numberOfAtoms, vector<RealVec>& atomCoordinates, vector<RealVec>& velocities, vector<RealVec>& forces, vector<RealOpenMM>& masses, map<string, RealOpenMM>& globals, vector<vector<RealVec> >& perDof, bool& forcesAreValid, RealOpenMM tolerance){ int numSteps = stepType.size(); globals.insert(context.getParameters().begin(), context.getParameters().end()); oldPos = atomCoordinates; if (invalidatesForces.size() == 0) { // The first time this is called, work out when to recompute forces and energy. First build a // list of every step that invalidates the forces. invalidatesForces.resize(numSteps, false); needsForces.resize(numSteps, false); needsEnergy.resize(numSteps, false); forceGroup.resize(numSteps, -2); forceName.resize(numSteps, "f"); energyName.resize(numSteps, "energy"); set<string> affectsForce; affectsForce.insert("x"); for (vector<ForceImpl*>::const_iterator iter = context.getForceImpls().begin(); iter != context.getForceImpls().end(); ++iter) { const map<string, double> params = (*iter)->getDefaultParameters(); for (map<string, double>::const_iterator param = params.begin(); param != params.end(); ++param) affectsForce.insert(param->first); } for (int i = 0; i < numSteps; i++) invalidatesForces[i] = (stepType[i] == CustomIntegrator::ConstrainPositions || affectsForce.find(stepVariable[i]) != affectsForce.end()); // Make a list of which steps require valid forces or energy to be known. vector<string> forceGroupName; vector<string> energyGroupName; for (int i = 0; i < 32; i++) { stringstream fname; fname << "f" << i; forceGroupName.push_back(fname.str()); stringstream ename; ename << "energy" << i; energyGroupName.push_back(ename.str()); } for (int i = 0; i < numSteps; i++) { if (stepType[i] == CustomIntegrator::ComputeGlobal || stepType[i] == CustomIntegrator::ComputePerDof || stepType[i] == CustomIntegrator::ComputeSum) { for (int j = 0; j < stepExpression[i].getNumOperations(); j++) { const Lepton::Operation& op = stepExpression[i].getOperation(j); if (op.getId() == Lepton::Operation::VARIABLE) { if (op.getName() == "energy") { if (forceGroup[i] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsEnergy[i] = true; forceGroup[i] = -1; } else if (op.getName().substr(0, 6) == "energy") { for (int k = 0; k < (int) energyGroupName.size(); k++) if (op.getName() == energyGroupName[k]) { if (forceGroup[i] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsForces[i] = true; forceGroup[i] = 1<<k; energyName[i] = energyGroupName[k]; break; } } else if (op.getName() == "f") { if (forceGroup[i] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsForces[i] = true; forceGroup[i] = -1; } else if (op.getName()[0] == 'f') { for (int k = 0; k < (int) forceGroupName.size(); k++) if (op.getName() == forceGroupName[k]) { if (forceGroup[i] != -2) throw OpenMMException("A single computation step cannot depend on multiple force groups"); needsForces[i] = true; forceGroup[i] = 1<<k; forceName[i] = forceGroupName[k]; break; } } } } } } // Build the list of inverse masses. 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]; } } // Loop over steps and execute them. for (int i = 0; i < numSteps; i++) { if ((needsForces[i] || needsEnergy[i]) && (!forcesAreValid || context.getLastForceGroups() != forceGroup[i])) { // Recompute forces and or energy. Figure out what is actually needed // between now and the next time they get invalidated again. bool computeForce = false, computeEnergy = false; for (int j = i; ; j++) { if (needsForces[j]) computeForce = true; if (needsEnergy[j]) computeEnergy = true; if (invalidatesForces[j]) break; if (j == numSteps-1) j = -1; if (j == i-1) break; } recordChangedParameters(context, globals); RealOpenMM e = context.calcForcesAndEnergy(computeForce, computeEnergy, forceGroup[i]); if (computeEnergy) energy = e; forcesAreValid = true; } globals[energyName[i]] = energy; // Execute the step. switch (stepType[i]) { case CustomIntegrator::ComputeGlobal: { map<string, RealOpenMM> variables = globals; variables["uniform"] = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber(); variables["gaussian"] = SimTKOpenMMUtilities::getNormallyDistributedRandomNumber(); globals[stepVariable[i]] = stepExpression[i].evaluate(variables); break; } case CustomIntegrator::ComputePerDof: { vector<RealVec>* results = NULL; if (stepVariable[i] == "x") results = &atomCoordinates; else if (stepVariable[i] == "v") results = &velocities; else { for (int j = 0; j < integrator.getNumPerDofVariables(); j++) if (stepVariable[i] == integrator.getPerDofVariableName(j)) results = &perDof[j]; } if (results == NULL) throw OpenMMException("Illegal per-DOF output variable: "+stepVariable[i]); computePerDof(numberOfAtoms, *results, atomCoordinates, velocities, forces, masses, globals, perDof, stepExpression[i], forceName[i]); break; } case CustomIntegrator::ComputeSum: { computePerDof(numberOfAtoms, sumBuffer, atomCoordinates, velocities, forces, masses, globals, perDof, stepExpression[i], forceName[i]); RealOpenMM sum = 0.0; for (int j = 0; j < numberOfAtoms; j++) if (masses[j] != 0.0) sum += sumBuffer[j][0]+sumBuffer[j][1]+sumBuffer[j][2]; globals[stepVariable[i]] = sum; break; } case CustomIntegrator::ConstrainPositions: { getReferenceConstraintAlgorithm()->apply(oldPos, atomCoordinates, inverseMasses, tolerance); oldPos = atomCoordinates; break; } case CustomIntegrator::ConstrainVelocities: { getReferenceConstraintAlgorithm()->applyToVelocities(oldPos, velocities, inverseMasses, tolerance); break; } case CustomIntegrator::UpdateContextState: { recordChangedParameters(context, globals); context.updateContextState(); globals.insert(context.getParameters().begin(), context.getParameters().end()); } } if (invalidatesForces[i]) forcesAreValid = false; } ReferenceVirtualSites::computePositions(context.getSystem(), atomCoordinates); incrementTimeStep(); recordChangedParameters(context, globals); }