Commit 67b261f6 authored by Peter Eastman's avatar Peter Eastman
Browse files

Created OpenCL implementation of CustomExternalForce. Also fixed a bug.

parent 20e2483b
......@@ -54,6 +54,8 @@ KernelImpl* OpenCLKernelFactory::createKernelImpl(std::string name, const Platfo
return new OpenCLCalcCustomNonbondedForceKernel(name, platform, cl, context.getSystem());
if (name == CalcGBSAOBCForceKernel::Name())
return new OpenCLCalcGBSAOBCForceKernel(name, platform, cl);
if (name == CalcCustomExternalForceKernel::Name())
return new OpenCLCalcCustomExternalForceKernel(name, platform, cl, context.getSystem());
if (name == IntegrateVerletStepKernel::Name())
return new OpenCLIntegrateVerletStepKernel(name, platform, cl);
if (name == IntegrateLangevinStepKernel::Name())
......
......@@ -348,7 +348,7 @@ void OpenCLCalcCustomBondForceKernel::initialize(const System& system, const Cus
string suffixes[] = {".x", ".y", ".z", ".w"};
for (int i = 0; i < force.getNumPerBondParameters(); i++) {
const string& name = force.getPerBondParameterName(i);
variables[name] = "exceptionParams"+suffixes[i];
variables[name] = "bondParams"+suffixes[i];
}
for (int i = 0; i < force.getNumGlobalParameters(); i++) {
const string& name = force.getGlobalParameterName(i);
......@@ -1316,6 +1316,155 @@ double OpenCLCalcGBSAOBCForceKernel::executeEnergy(ContextImpl& context) {
executeForces(context);
return 0.0;
}
class OpenCLCustomExternalForceInfo : public OpenCLForceInfo {
public:
OpenCLCustomExternalForceInfo(const CustomExternalForce& force, int numParticles) : OpenCLForceInfo(1), force(force), indices(numParticles, -1) {
vector<double> params;
for (int i = 0; i < force.getNumParticles(); i++) {
int particle;
force.getParticleParameters(i, particle, params);
indices[particle] = i;
}
}
bool areParticlesIdentical(int particle1, int particle2) {
particle1 = indices[particle1];
particle2 = indices[particle2];
if (particle1 == -1 && particle2 == -1)
return true;
if (particle1 == -1 || particle2 == -1)
return false;
int temp;
vector<double> params1;
vector<double> params2;
force.getParticleParameters(particle1, temp, params1);
force.getParticleParameters(particle2, temp, params2);
for (int i = 0; i < params1.size(); i++)
if (params1[i] != params2[i])
return false;
return true;
}
private:
const CustomExternalForce& force;
vector<int> indices;
};
OpenCLCalcCustomExternalForceKernel::~OpenCLCalcCustomExternalForceKernel() {
if (params != NULL)
delete params;
if (indices != NULL)
delete indices;
if (globals != NULL)
delete globals;
}
void OpenCLCalcCustomExternalForceKernel::initialize(const System& system, const CustomExternalForce& force) {
if (force.getNumPerParticleParameters() > 4)
throw OpenMMException("OpenCLPlatform only supports four per-particle parameters for custom external forces");
numParticles = force.getNumParticles();
params = new OpenCLArray<mm_float4>(cl, numParticles, "customExternalParams");
indices = new OpenCLArray<cl_int>(cl, numParticles, "customExternalIndices");
string extraArguments;
if (force.getNumGlobalParameters() > 0) {
globals = new OpenCLArray<cl_float>(cl, force.getNumGlobalParameters(), "customExternalGlobals", false, CL_MEM_READ_ONLY);
extraArguments += ", __constant float* globals";
}
vector<mm_float4> paramVector(numParticles);
vector<cl_int> indicesVector(numParticles);
for (int i = 0; i < numParticles; i++) {
vector<double> parameters;
force.getParticleParameters(i, indicesVector[i], parameters);
if (parameters.size() > 0)
paramVector[i].x = (cl_float) parameters[0];
if (parameters.size() > 1)
paramVector[i].y = (cl_float) parameters[1];
if (parameters.size() > 2)
paramVector[i].z = (cl_float) parameters[2];
if (parameters.size() > 3)
paramVector[i].w = (cl_float) parameters[3];
}
params->upload(paramVector);
indices->upload(indicesVector);
cl.addForce(new OpenCLCustomExternalForceInfo(force, system.getNumParticles()));
// Record information for the expressions.
vector<string> paramNames;
for (int i = 0; i < force.getNumPerParticleParameters(); i++)
paramNames.push_back(force.getPerParticleParameterName(i));
globalParamNames.resize(force.getNumGlobalParameters());
globalParamValues.resize(force.getNumGlobalParameters());
for (int i = 0; i < force.getNumGlobalParameters(); i++) {
globalParamNames[i] = force.getGlobalParameterName(i);
globalParamValues[i] = (cl_float) force.getGlobalParameterDefaultValue(i);
}
if (globals != NULL)
globals->upload(globalParamValues);
Lepton::ParsedExpression energyExpression = Lepton::Parser::parse(force.getEnergyFunction()).optimize();
Lepton::ParsedExpression forceExpressionX = energyExpression.differentiate("x").optimize();
Lepton::ParsedExpression forceExpressionY = energyExpression.differentiate("y").optimize();
Lepton::ParsedExpression forceExpressionZ = energyExpression.differentiate("z").optimize();
map<string, Lepton::ParsedExpression> expressions;
expressions["energy += "] = energyExpression;
expressions["float dEdX = "] = forceExpressionX;
expressions["float dEdY = "] = forceExpressionY;
expressions["float dEdZ = "] = forceExpressionZ;
// Create the kernels.
map<string, string> variables;
variables["x"] = "pos.x";
variables["y"] = "pos.y";
variables["z"] = "pos.z";
string suffixes[] = {".x", ".y", ".z", ".w"};
for (int i = 0; i < force.getNumPerParticleParameters(); i++) {
const string& name = force.getPerParticleParameterName(i);
variables[name] = "particleParams"+suffixes[i];
}
for (int i = 0; i < force.getNumGlobalParameters(); i++) {
const string& name = force.getGlobalParameterName(i);
string value = "globals["+intToString(i)+"]";
variables[name] = value;
}
map<string, string> replacements;
stringstream compute;
vector<pair<string, string> > functions;
compute << OpenCLExpressionUtilities::createExpressions(expressions, variables, functions, "temp", "");
replacements["COMPUTE_FORCE"] = compute.str();
replacements["EXTRA_ARGUMENTS"] = extraArguments;
cl::Program program = cl.createProgram(cl.loadSourceFromFile("customExternalForce.cl", replacements));
kernel = cl::Kernel(program, "computeCustomExternalForces");
}
void OpenCLCalcCustomExternalForceKernel::executeForces(ContextImpl& context) {
if (globals != NULL) {
bool changed = false;
for (int i = 0; i < globalParamNames.size(); i++) {
cl_float value = (cl_float) context.getParameter(globalParamNames[i]);
if (value != globalParamValues[i])
changed = true;
globalParamValues[i] = value;
}
if (changed)
globals->upload(globalParamValues);
}
if (!hasInitializedKernel) {
hasInitializedKernel = true;
kernel.setArg<cl_int>(0, numParticles);
kernel.setArg<cl::Buffer>(1, cl.getForceBuffers().getDeviceBuffer());
kernel.setArg<cl::Buffer>(2, cl.getEnergyBuffer().getDeviceBuffer());
kernel.setArg<cl::Buffer>(3, cl.getPosq().getDeviceBuffer());
kernel.setArg<cl::Buffer>(4, params->getDeviceBuffer());
kernel.setArg<cl::Buffer>(5, indices->getDeviceBuffer());
if (globals != NULL)
kernel.setArg<cl::Buffer>(6, globals->getDeviceBuffer());
}
cl.executeKernel(kernel, numParticles);
}
double OpenCLCalcCustomExternalForceKernel::executeEnergy(ContextImpl& context) {
executeForces(context);
return 0.0;
}
OpenCLIntegrateVerletStepKernel::~OpenCLIntegrateVerletStepKernel() {
}
......
......@@ -472,6 +472,48 @@ private:
cl::Kernel reduceBornForceKernel;
};
/**
* This kernel is invoked by CustomExternalForce to calculate the forces acting on the system and the energy of the system.
*/
class OpenCLCalcCustomExternalForceKernel : public CalcCustomExternalForceKernel {
public:
OpenCLCalcCustomExternalForceKernel(std::string name, const Platform& platform, OpenCLContext& cl, System& system) : CalcCustomExternalForceKernel(name, platform),
hasInitializedKernel(false), cl(cl), system(system), params(NULL), indices(NULL), globals(NULL) {
}
~OpenCLCalcCustomExternalForceKernel();
/**
* Initialize the kernel.
*
* @param system the System this kernel will be applied to
* @param force the CustomExternalForce this kernel will be used for
*/
void initialize(const System& system, const CustomExternalForce& force);
/**
* Execute the kernel to calculate the forces.
*
* @param context the context in which to execute this kernel
*/
void executeForces(ContextImpl& context);
/**
* Execute the kernel to calculate the energy.
*
* @param context the context in which to execute this kernel
* @return the potential energy due to the CustomExternalForce
*/
double executeEnergy(ContextImpl& context);
private:
int numParticles;
bool hasInitializedKernel;
OpenCLContext& cl;
System& system;
OpenCLArray<mm_float4>* params;
OpenCLArray<cl_int>* indices;
OpenCLArray<cl_float>* globals;
std::vector<std::string> globalParamNames;
std::vector<cl_float> globalParamValues;
cl::Kernel kernel;
};
/**
* This kernel is invoked by VerletIntegrator to take one time step.
*/
......
......@@ -55,6 +55,7 @@ OpenCLPlatform::OpenCLPlatform() {
registerKernelFactory(CalcNonbondedForceKernel::Name(), factory);
registerKernelFactory(CalcCustomNonbondedForceKernel::Name(), factory);
registerKernelFactory(CalcGBSAOBCForceKernel::Name(), factory);
registerKernelFactory(CalcCustomExternalForceKernel::Name(), factory);
registerKernelFactory(IntegrateVerletStepKernel::Name(), factory);
registerKernelFactory(IntegrateLangevinStepKernel::Name(), factory);
registerKernelFactory(IntegrateBrownianStepKernel::Name(), factory);
......
......@@ -5,13 +5,12 @@
__kernel void computeCustomBondForces(int numAtoms, int numBonds, __global float4* forceBuffers, __global float* energyBuffer,
__global float4* posq, __global float4* params, __global int4* indices
EXTRA_ARGUMENTS) {
int index = get_global_id(0);
float energy = 0.0f;
while (index < numBonds) {
// Look up the data for this exception.
for (int index = get_global_id(0); index < numBonds; index += get_global_size(0)) {
// Look up the data for this bond.
int4 atoms = indices[index];
float4 exceptionParams = params[index];
float4 bondParams = params[index];
float4 delta = posq[atoms.y]-posq[atoms.x];
// Compute the force.
......@@ -30,7 +29,6 @@ __kernel void computeCustomBondForces(int numAtoms, int numBonds, __global float
forceB.xyz += delta.xyz;
forceBuffers[offsetA] = forceA;
forceBuffers[offsetB] = forceB;
index += get_global_size(0);
}
energyBuffer[get_global_id(0)] += energy;
}
/**
* Compute custom external forces.
*/
__kernel void computeCustomExternalForces(int numTerms, __global float4* forceBuffers, __global float* energyBuffer,
__global float4* posq, __global float4* params, __global int* indices
EXTRA_ARGUMENTS) {
float energy = 0.0f;
for (int index = get_global_id(0); index < numTerms; index += get_global_size(0)) {
// Look up the data for this particle.
int atom = indices[index];
float4 particleParams = params[index];
float4 pos = posq[atom];
// Compute the force.
COMPUTE_FORCE
// Record the force on the atom.
float4 force = forceBuffers[atom];
force.x -= dEdX;
force.y -= dEdY;
force.z -= dEdZ;
forceBuffers[atom] = force;
}
energyBuffer[get_global_id(0)] += energy;
}
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit originating from *
* Simbios, the NIH National Center for Physics-Based Simulation of *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2009 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
/**
* This tests the OpenCL implementation of CustomExternalForce.
*/
#include "../../../tests/AssertionUtilities.h"
#include "openmm/Context.h"
#include "OpenCLPlatform.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/System.h"
#include "openmm/VerletIntegrator.h"
#include "../src/SimTKUtilities/SimTKOpenMMRealType.h"
#include <iostream>
#include <vector>
using namespace OpenMM;
using namespace std;
const double TOL = 1e-5;
void testForce() {
OpenCLPlatform platform;
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
VerletIntegrator integrator(0.01);
CustomExternalForce* forceField = new CustomExternalForce("scale*(x+yscale*(y-y0)^2)");
forceField->addPerParticleParameter("y0");
forceField->addPerParticleParameter("yscale");
forceField->addGlobalParameter("scale", 0.5);
vector<double> parameters(2);
parameters[0] = 0.5;
parameters[1] = 2.0;
forceField->addParticle(0, parameters);
parameters[0] = 1.5;
parameters[1] = 3.0;
forceField->addParticle(2, parameters);
system.addForce(forceField);
Context context(system, integrator, platform);
vector<Vec3> positions(3);
positions[0] = Vec3(0, 2, 0);
positions[1] = Vec3(0, 0, 1);
positions[2] = Vec3(1, 0, 1);
context.setPositions(positions);
State state = context.getState(State::Forces | State::Energy);
const vector<Vec3>& forces = state.getForces();
ASSERT_EQUAL_VEC(Vec3(-0.5, -0.5*2.0*2.0*1.5, 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(0, 0, 0), forces[1], TOL);
ASSERT_EQUAL_VEC(Vec3(-0.5, 0.5*3.0*2.0*1.5, 0), forces[2], TOL);
ASSERT_EQUAL_TOL(0.5*(1.0 + 2.0*1.5*1.5 + 3.0*1.5*1.5), state.getPotentialEnergy(), TOL);
}
int main() {
try {
testForce();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
......@@ -106,9 +106,9 @@ int ReferenceCustomExternalIxn::calculateForce( int atomIndex,
// ---------------------------------------------------------------------------------------
forces[atomIndex][0] += forceExpressionX.evaluate(variables);
forces[atomIndex][1] += forceExpressionY.evaluate(variables);
forces[atomIndex][2] += forceExpressionZ.evaluate(variables);
forces[atomIndex][0] -= forceExpressionX.evaluate(variables);
forces[atomIndex][1] -= forceExpressionY.evaluate(variables);
forces[atomIndex][2] -= forceExpressionZ.evaluate(variables);
if (energy != NULL)
*energy += energyExpression.evaluate(variables);
......
......@@ -75,9 +75,9 @@ void testForce() {
context.setPositions(positions);
State state = context.getState(State::Forces | State::Energy);
const vector<Vec3>& forces = state.getForces();
ASSERT_EQUAL_VEC(Vec3(0.5, 0.5*2.0*2.0*1.5, 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(-0.5, -0.5*2.0*2.0*1.5, 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(0, 0, 0), forces[1], TOL);
ASSERT_EQUAL_VEC(Vec3(0.5, -0.5*3.0*2.0*1.5, 0), forces[2], TOL);
ASSERT_EQUAL_VEC(Vec3(-0.5, 0.5*3.0*2.0*1.5, 0), forces[2], TOL);
ASSERT_EQUAL_TOL(0.5*(1.0 + 2.0*1.5*1.5 + 3.0*1.5*1.5), state.getPotentialEnergy(), TOL);
}
......
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