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);
}