void ReferenceCustomDynamics::computePerDof(int numberOfAtoms, vector<RealVec>& results, const vector<RealVec>& atomCoordinates,
              const vector<RealVec>& velocities, const vector<RealVec>& forces, const vector<RealOpenMM>& masses,
              const map<string, RealOpenMM>& globals, const vector<vector<RealVec> >& perDof,
              const Lepton::ExpressionProgram& expression, const std::string& forceName) {
    // Loop over all degrees of freedom.

    map<string, RealOpenMM> variables = globals;
    for (int i = 0; i < numberOfAtoms; i++) {
        if (masses[i] != 0.0) {
            variables["m"] = masses[i];
            for (int j = 0; j < 3; j++) {
                // Compute the expression.

                variables["x"] = atomCoordinates[i][j];
                variables["v"] = velocities[i][j];
                variables[forceName] = forces[i][j];
                variables["uniform"] = SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber();
                variables["gaussian"] = SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
                for (int k = 0; k < (int) perDof.size(); k++)
                    variables[integrator.getPerDofVariableName(k)] = perDof[k][i][j];
                results[i][j] = expression.evaluate(variables);
            }
        }
    }
}
//-------------------------------------------------------------------------------------------------
double BenchLepton::DoBenchmark(const std::string& sExpr, long iCount)
{
   std::map<std::string,double> var_list;
   var_list["a" ] = 1.1;
   var_list["b" ] = 2.2;
   var_list["c" ] = 3.3;
   var_list["x" ] = 2.123456;
   var_list["y" ] = 3.123456;
   var_list["z" ] = 4.123456;
   var_list["w" ] = 5.123456;

   var_list["e" ] = 2.718281828459045235360;
   var_list["pi"] = 3.141592653589793238462;

   double& a = var_list["a"];
   double& b = var_list["b"];
   double& c = var_list["c"];

   double& x = var_list["x"];
   double& y = var_list["y"];
   double& z = var_list["z"];
   double& w = var_list["w"];

   try
   {
      Lepton::ExpressionProgram program = Lepton::Parser::parse(sExpr).optimize().createProgram();
      program.evaluate(var_list);
   }
   catch (std::exception& e)
   {
      StopTimerAndReport(e.what());
      return std::numeric_limits<double>::quiet_NaN();
   }

   Lepton::ExpressionProgram program = Lepton::Parser::parse(sExpr).optimize().createProgram();

   // Perform basic tests for the variables used
   // in the expressions
   {
      bool test_result = true;

      auto tests_list = test_expressions();

      try
      {
         for (auto test : tests_list)
         {
            Lepton::ExpressionProgram test_program = Lepton::Parser::parse(test.first).optimize().createProgram();

            if (!is_equal(test.second,test_program.evaluate(var_list)))
            {
               StopTimerAndReport("Failed variable test");
               return m_fTime1;
            }
         }
      }
      catch (std::exception& e)
      {
         StopTimerAndReport(e.what());
         return std::numeric_limits<double>::quiet_NaN();
      }
   }

   //Prime the I and D caches for the expression
   {
      double d0 = 0.0;
      double d1 = 0.0;

      for (std::size_t i = 0; i < priming_rounds; ++i)
      {
         if (i & 1)
            d0 += program.evaluate(var_list);
         else
            d1 += program.evaluate(var_list);
      }

      if (
            (d0 == std::numeric_limits<double>::infinity()) &&
            (d1 == std::numeric_limits<double>::infinity())
         )
      {
         printf("\n");
      }
   }

   // Perform benchmark then return results
   double fRes  = 0;
   double fSum  = 0;

   fRes = program.evaluate(var_list);

   StartTimer();

   for (int j = 0; j < iCount; ++j)
   {
      fSum += program.evaluate(var_list);
      std::swap(a,b);
      std::swap(x,y);
   }

   StopTimer(fRes, fSum, iCount);

   return m_fTime1;
}
double CustomNonbondedForceImpl::integrateInteraction(const Lepton::ExpressionProgram& expression, const vector<double>& params1, const vector<double>& params2,
        const CustomNonbondedForce& force, const Context& context) {
    map<std::string, double> variables;
    for (int i = 0; i < force.getNumPerParticleParameters(); i++) {
        stringstream name1, name2;
        name1 << force.getPerParticleParameterName(i) << 1;
        name2 << force.getPerParticleParameterName(i) << 2;
        variables[name1.str()] = params1[i];
        variables[name2.str()] = params2[i];
    }
    for (int i = 0; i < force.getNumGlobalParameters(); i++) {
        const string& name = force.getGlobalParameterName(i);
        variables[name] = context.getParameter(name);
    }
    
    // To integrate from r_cutoff to infinity, make the change of variables x=r_cutoff/r and integrate from 0 to 1.
    // This introduces another r^2 into the integral, which along with the r^2 in the formula for the correction
    // means we multiply the function by r^4.  Use the midpoint method.

    double cutoff = force.getCutoffDistance();
    double sum = 0;
    int numPoints = 1;
    for (int iteration = 0; ; iteration++) {
        double oldSum = sum;
        double newSum = 0;
        for (int i = 0; i < numPoints; i++) {
            if (i%3 == 1)
                continue;
            double x = (i+0.5)/numPoints;
            double r = cutoff/x;
            variables["r"] = r;
            double r2 = r*r;
            newSum += expression.evaluate(variables)*r2*r2;
        }
        sum = newSum/numPoints + oldSum/3;
        if (iteration > 2 && (fabs((sum-oldSum)/sum) < 1e-5 || sum == 0))
            break;
        if (iteration == 8)
            throw OpenMMException("CustomNonbondedForce: Long range correction did not converge.  Does the energy go to 0 faster than 1/r^2?");
        numPoints *= 3;
    }
    
    // If a switching function is used, integrate over the switching interval.
    
    double sum2 = 0;
    if (force.getUseSwitchingFunction()) {
        double rswitch = force.getSwitchingDistance();
        sum2 = 0;
        numPoints = 1;
        for (int iteration = 0; ; iteration++) {
            double oldSum = sum2;
            double newSum = 0;
            for (int i = 0; i < numPoints; i++) {
                if (i%3 == 1)
                    continue;
                double x = (i+0.5)/numPoints;
                double r = rswitch+x*(cutoff-rswitch);
                double switchValue = x*x*x*(10+x*(-15+x*6));
                variables["r"] = r;
                newSum += switchValue*expression.evaluate(variables)*r*r;
            }
            sum2 = newSum/numPoints + oldSum/3;
            if (iteration > 2 && (fabs((sum2-oldSum)/sum2) < 1e-5 || sum2 == 0))
                break;
            if (iteration == 8)
                throw OpenMMException("CustomNonbondedForce: Long range correction did not converge.  Is the energy finite everywhere in the switching interval?");
            numPoints *= 3;
        }
        sum2 *= cutoff-rswitch;
    }
    return sum/cutoff+sum2;
}