Commit cceb3171 authored by peastman's avatar peastman
Browse files

Reference implementation of periodicdistance() function for CustomExternalForce

parent 992487c6
......@@ -67,6 +67,14 @@ namespace OpenMM {
* force->addPerParticleParameter("z0");
* </pre></tt>
*
* Special care is needed in systems that use periodic boundary conditions. In that case, each particle really represents
* an infinite set of particles repeating through space. The variables x, y, and z contain the coordinates of one of those
* periodic copies, but there is no guarantee about which. It might even change from one time step to the next. You can handle
* this situation by using the function periodicdistance(x1, y1, z1, x2, y2, z2), which returns the minimum distance between
* periodic copies of the points (x1, y1, z1) and (x2, y2, z2). For example, the force given above would be rewritten as
*
* <tt>CustomExternalForce* force = new CustomExternalForce("k*periodicdistance(x, y, z, x0, y0, z0)^2");</tt>
*
* Expressions may involve the operators + (add), - (subtract), * (multiply), / (divide), and ^ (power), and the following
* functions: sqrt, exp, log, sin, cos, sec, csc, tan, cot, asin, acos, atan, sinh, cosh, tanh, erf, erfc, min, max, abs, floor, ceil, step, delta, select. All trigonometric functions
* are defined in radians, and log is the natural logarithm. step(x) = 0 if x is less than 0, 1 otherwise. delta(x) = 1 if x is 0, 0 otherwise.
......
......@@ -37,6 +37,7 @@
#include "SimTKOpenMMRealType.h"
#include "ReferenceNeighborList.h"
#include "lepton/CompiledExpression.h"
#include "lepton/CustomFunction.h"
#include "lepton/ExpressionProgram.h"
namespace OpenMM {
......@@ -795,11 +796,23 @@ public:
*/
void copyParametersToContext(ContextImpl& context, const CustomExternalForce& force);
private:
class PeriodicDistanceFunction;
int numParticles;
std::vector<int> particles;
RealOpenMM **particleParamArray;
Lepton::CompiledExpression energyExpression, forceExpressionX, forceExpressionY, forceExpressionZ;
std::vector<std::string> parameterNames, globalParameterNames;
RealVec* boxVectors;
};
class ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction : public Lepton::CustomFunction {
public:
RealVec** boxVectorHandle;
PeriodicDistanceFunction(RealVec** boxVectorHandle);
int getNumArguments() const;
double evaluate(const double* arguments) const;
double evaluateDerivative(const double* arguments, const int* derivOrder) const;
Lepton::CustomFunction* clone() const;
};
/**
......
......@@ -1402,6 +1402,46 @@ void ReferenceCalcCustomGBForceKernel::copyParametersToContext(ContextImpl& cont
}
}
ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction::PeriodicDistanceFunction(RealVec** boxVectorHandle) : boxVectorHandle(boxVectorHandle) {
}
int ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction::getNumArguments() const {
return 6;
}
double ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction::evaluate(const double* arguments) const {
RealVec* boxVectors = *boxVectorHandle;
RealVec delta = RealVec(arguments[0], arguments[1], arguments[2])-RealVec(arguments[3], arguments[4], arguments[5]);
delta -= boxVectors[2]*floor(delta[2]/boxVectors[2][2]+0.5);
delta -= boxVectors[1]*floor(delta[1]/boxVectors[1][1]+0.5);
delta -= boxVectors[0]*floor(delta[0]/boxVectors[0][0]+0.5);
return sqrt(delta.dot(delta));
}
double ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction::evaluateDerivative(const double* arguments, const int* derivOrder) const {
int argIndex = -1;
for (int i = 0; i < 6; i++) {
if (derivOrder[i] > 0) {
if (derivOrder[i] > 1 || argIndex != -1)
throw OpenMMException("Unsupported derivative of periodicdistance"); // Should be impossible for this to happen.
argIndex = i;
}
}
RealVec* boxVectors = *boxVectorHandle;
RealVec delta = RealVec(arguments[0], arguments[1], arguments[2])-RealVec(arguments[3], arguments[4], arguments[5]);
delta -= boxVectors[2]*floor(delta[2]/boxVectors[2][2]+0.5);
delta -= boxVectors[1]*floor(delta[1]/boxVectors[1][1]+0.5);
delta -= boxVectors[0]*floor(delta[0]/boxVectors[0][0]+0.5);
double r = sqrt(delta.dot(delta));
if (argIndex < 3)
return delta[argIndex]/r;
return -delta[argIndex-3]/r;
}
Lepton::CustomFunction* ReferenceCalcCustomExternalForceKernel::PeriodicDistanceFunction::clone() const {
return new PeriodicDistanceFunction(boxVectorHandle);
}
ReferenceCalcCustomExternalForceKernel::~ReferenceCalcCustomExternalForceKernel() {
disposeRealArray(particleParamArray, numParticles);
}
......@@ -1423,7 +1463,10 @@ void ReferenceCalcCustomExternalForceKernel::initialize(const System& system, co
// Parse the expression used to calculate the force.
Lepton::ParsedExpression expression = Lepton::Parser::parse(force.getEnergyFunction()).optimize();
map<string, Lepton::CustomFunction*> functions;
PeriodicDistanceFunction periodicDistance(&boxVectors);
functions["periodicdistance"] = &periodicDistance;
Lepton::ParsedExpression expression = Lepton::Parser::parse(force.getEnergyFunction(), functions).optimize();
energyExpression = expression.createCompiledExpression();
forceExpressionX = expression.differentiate("x").createCompiledExpression();
forceExpressionY = expression.differentiate("y").createCompiledExpression();
......@@ -1437,6 +1480,7 @@ void ReferenceCalcCustomExternalForceKernel::initialize(const System& system, co
double ReferenceCalcCustomExternalForceKernel::execute(ContextImpl& context, bool includeForces, bool includeEnergy) {
vector<RealVec>& posData = extractPositions(context);
vector<RealVec>& forceData = extractForces(context);
boxVectors = extractBoxVectors(context);
RealOpenMM energy = 0;
map<string, double> globalParameters;
for (int i = 0; i < (int) globalParameterNames.size(); i++)
......
......@@ -101,9 +101,51 @@ void testForce() {
}
}
void testPeriodic() {
Vec3 vx(5, 0, 0);
Vec3 vy(0, 6, 0);
Vec3 vz(1, 2, 7);
double x0 = 51, y0 = -17, z0 = 11.2;
System system;
system.setDefaultPeriodicBoxVectors(vx, vy, vz);
system.addParticle(1.0);
CustomExternalForce* force = new CustomExternalForce("periodicdistance(x, y, z, x0, y0, z0)^2");
force->addPerParticleParameter("x0");
force->addPerParticleParameter("y0");
force->addPerParticleParameter("z0");
vector<double> params(3);
params[0] = x0;
params[1] = y0;
params[2] = z0;
force->addParticle(0, params);
system.addForce(force);
VerletIntegrator integrator(0.01);
Context context(system, integrator, platform);
vector<Vec3> positions(1);
positions[0] = Vec3(0, 2, 0);
context.setPositions(positions);
for (int i = 0; i < 100; i++) {
State state = context.getState(State::Positions | State::Forces | State::Energy);
// Apply periodic boundary conditions to the difference between the two positions.
Vec3 delta = Vec3(x0, y0, z0)-state.getPositions()[0];
delta -= vz*floor(delta[2]/vz[2]+0.5);
delta -= vy*floor(delta[1]/vy[1]+0.5);
delta -= vx*floor(delta[0]/vx[0]+0.5);
// Verify that the force and energy are correct.
ASSERT_EQUAL_VEC(delta*2, state.getForces()[0], 1e-6);
ASSERT_EQUAL_TOL(delta.dot(delta), state.getPotentialEnergy(), 1e-6);
integrator.step(1);
}
}
int main() {
try {
testForce();
testPeriodic();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment