ReferenceCustomDynamics::ReferenceCustomDynamics(int numberOfAtoms, const CustomIntegrator& integrator) : ReferenceDynamics(numberOfAtoms, integrator.getStepSize(), 0.0), integrator(integrator) { sumBuffer.resize(numberOfAtoms); oldPos.resize(numberOfAtoms); 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); } }
ReferenceCustomDynamics::ReferenceCustomDynamics(int numberOfAtoms, const CustomIntegrator& integrator) : ReferenceDynamics(numberOfAtoms, integrator.getStepSize(), 0.0), integrator(integrator) { sumBuffer.resize(numberOfAtoms); oldPos.resize(numberOfAtoms); 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); } kineticEnergyExpression = Lepton::Parser::parse(integrator.getKineticEnergyExpression()).optimize().createProgram(); kineticEnergyNeedsForce = false; for (int i = 0; i < kineticEnergyExpression.getNumOperations(); i++) { const Lepton::Operation& op = kineticEnergyExpression.getOperation(i); if (op.getId() == Lepton::Operation::VARIABLE && op.getName() == "f") kineticEnergyNeedsForce = true; } }
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 testSerializeCustomIntegrator() { CustomIntegrator *intg = new CustomIntegrator(0.002234); intg->addPerDofVariable("temp",0); vector<Vec3> initialValues(123); for(int i = 0; i < 123; i++) initialValues[i] = Vec3(i+0.1, i+0.2, i+0.3); intg->setPerDofVariable(0, initialValues); intg->addPerDofVariable("oldx", 0); intg->addComputePerDof("v", "v+dt*f/m"); intg->addComputePerDof("oldx", "x"); intg->addComputePerDof("x", "x+dt*v"); intg->addConstrainPositions(); intg->addComputePerDof("v", "(x-oldx)/dt"); intg->addUpdateContextState(); intg->addConstrainVelocities(); intg->addComputeSum("summand", "x*x+v*v"); intg->addPerDofVariable("outf", 0); intg->addPerDofVariable("outf1", 0); intg->addPerDofVariable("outf2", 0); intg->addGlobalVariable("oute", 0); intg->addGlobalVariable("oute1", 0); intg->addGlobalVariable("oute2", 0); intg->addComputePerDof("outf", "f"); intg->addComputePerDof("outf1", "f1"); intg->addComputePerDof("outf2", "f2"); intg->addComputeGlobal("oute", "energy"); intg->addComputeGlobal("oute1", "energy1"); intg->addComputeGlobal("oute2", "energy2"); intg->addUpdateContextState(); intg->addConstrainVelocities(); intg->addComputeSum("summand2", "v*v+f*f"); intg->setConstraintTolerance(1e-5); intg->setKineticEnergyExpression("m*v1*v1/2; v1=v+0.5*dt*f/m"); stringstream ss; XmlSerializer::serialize<Integrator>(intg, "CustomIntegrator", ss); CustomIntegrator *intg2 = dynamic_cast<CustomIntegrator*>(XmlSerializer::deserialize<Integrator>(ss)); ASSERT_EQUAL(intg->getNumGlobalVariables(), intg->getNumGlobalVariables()); for (int i = 0; i < intg->getNumGlobalVariables(); i++) { ASSERT_EQUAL(intg->getGlobalVariable(i), intg2->getGlobalVariable(i)); ASSERT_EQUAL(intg->getGlobalVariableName(i), intg2->getGlobalVariableName(i)); } ASSERT_EQUAL(intg->getNumPerDofVariables(), intg2->getNumPerDofVariables()); for(int i = 0; i < intg->getNumPerDofVariables(); i++) { vector<Vec3> vars1; intg->getPerDofVariable(i, vars1); vector<Vec3> vars2; intg2->getPerDofVariable(i, vars2); ASSERT_EQUAL(vars1.size(),vars2.size()); for (int j = 0; j < (int) vars1.size(); j++) { ASSERT_EQUAL(vars1[j][0], vars2[j][0]); ASSERT_EQUAL(vars1[j][1], vars2[j][1]); ASSERT_EQUAL(vars1[j][2], vars2[j][2]); } } ASSERT_EQUAL(intg->getNumComputations(), intg2->getNumComputations()); for(int i=0; i<intg->getNumComputations(); i++) { CustomIntegrator::ComputationType type1, type2; string variable1, variable2; string expression1, expression2; intg->getComputationStep(i, type1, variable1, expression1); intg2->getComputationStep(i, type2, variable2, expression2); ASSERT_EQUAL(type1, type2); ASSERT_EQUAL(variable1, variable2); ASSERT_EQUAL(expression1, expression2); } ASSERT_EQUAL(intg->getKineticEnergyExpression(), intg2->getKineticEnergyExpression()); ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed()); ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize()); ASSERT_EQUAL(intg->getConstraintTolerance(), intg2->getConstraintTolerance()); delete intg; delete intg2; }