"platforms/brook/tests/testBrookPeriodicTorsionForce.cpp" did not exist on "8c3878b27503402cf889d7a06feb9115cc1724ed"
Commit 18295108 authored by peastman's avatar peastman
Browse files

Merge changes from main branch

parents e6101f68 8d7234e5
......@@ -40,22 +40,22 @@ CustomIntegratorProxy::CustomIntegratorProxy() : SerializationProxy("CustomInteg
}
void CustomIntegratorProxy::serialize(const void* object, SerializationNode& node) const {
node.setIntProperty("version", 1);
node.setIntProperty("version", 2);
const CustomIntegrator& integrator = *reinterpret_cast<const CustomIntegrator*>(object);
SerializationNode& globalVariablesNode = node.createChildNode("GlobalVariables");
for(int i=0; i<integrator.getNumGlobalVariables(); i++) {
for (int i = 0; i < integrator.getNumGlobalVariables(); i++) {
globalVariablesNode.setDoubleProperty(integrator.getGlobalVariableName(i), integrator.getGlobalVariable(i));
}
SerializationNode& perDofVariablesNode = node.createChildNode("PerDofVariables");
for(int i=0; i<integrator.getNumPerDofVariables(); i++) {
for (int i = 0; i < integrator.getNumPerDofVariables(); i++) {
SerializationNode& perDofValuesNode = perDofVariablesNode.createChildNode(integrator.getPerDofVariableName(i));
vector<Vec3> perDofValues; integrator.getPerDofVariable(i, perDofValues);
for(int j=0; j<perDofValues.size(); j++) {
for (int j = 0; j < perDofValues.size(); j++) {
perDofValuesNode.createChildNode("Value").setDoubleProperty("x",perDofValues[j][0]).setDoubleProperty("y",perDofValues[j][1]).setDoubleProperty("z",perDofValues[j][2]);
}
}
SerializationNode& computationsNode = node.createChildNode("Computations");
for(int i=0; i<integrator.getNumComputations(); i++) {
for (int i = 0; i < integrator.getNumComputations(); i++) {
CustomIntegrator::ComputationType computationType;
string computationVariable;
string computationExpression;
......@@ -63,6 +63,9 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod
computationsNode.createChildNode("Computation").setIntProperty("computationType",static_cast<int>(computationType))
.setStringProperty("computationVariable",computationVariable).setStringProperty("computationExpression",computationExpression);
}
SerializationNode& functions = node.createChildNode("Functions");
for (int i = 0; i < integrator.getNumTabulatedFunctions(); i++)
functions.createChildNode("Function", &integrator.getTabulatedFunction(i)).setStringProperty("name", integrator.getTabulatedFunctionName(i));
node.setStringProperty("kineticEnergyExpression",integrator.getKineticEnergyExpression());
node.setIntProperty("randomSeed",integrator.getRandomNumberSeed());
node.setDoubleProperty("stepSize",integrator.getStepSize());
......@@ -70,7 +73,8 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod
}
void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const {
if (node.getIntProperty("version") != 1)
int version = node.getIntProperty("version");
if (version < 1 || version > 2)
throw OpenMMException("Unsupported version number");
CustomIntegrator* integrator = new CustomIntegrator(node.getDoubleProperty("stepSize"));
const SerializationNode& globalVariablesNode = node.getChildNode("GlobalVariables");
......@@ -112,6 +116,11 @@ void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const {
throw(OpenMMException("Custom Integrator Deserialization: Unknown computation type"));
}
}
if (version > 1) {
const SerializationNode& functions = node.getChildNode("Functions");
for (auto& function : functions.getChildren())
integrator->addTabulatedFunction(function.getStringProperty("name"), function.decodeObject<TabulatedFunction>());
}
integrator->setKineticEnergyExpression(node.getStringProperty("kineticEnergyExpression"));
integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
......
......@@ -38,6 +38,7 @@
#include "openmm/CustomBondForce.h"
#include "openmm/CustomCentroidBondForce.h"
#include "openmm/CustomCompoundBondForce.h"
#include "openmm/CustomCVForce.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/CustomGBForce.h"
#include "openmm/CustomHbondForce.h"
......@@ -72,6 +73,7 @@
#include "openmm/serialization/CustomBondForceProxy.h"
#include "openmm/serialization/CustomCentroidBondForceProxy.h"
#include "openmm/serialization/CustomCompoundBondForceProxy.h"
#include "openmm/serialization/CustomCVForceProxy.h"
#include "openmm/serialization/CustomExternalForceProxy.h"
#include "openmm/serialization/CustomGBForceProxy.h"
#include "openmm/serialization/CustomHbondForceProxy.h"
......@@ -124,6 +126,7 @@ extern "C" void registerSerializationProxies() {
SerializationProxy::registerProxy(typeid(CustomBondForce), new CustomBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCentroidBondForce), new CustomCentroidBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCompoundBondForce), new CustomCompoundBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCVForce), new CustomCVForceProxy());
SerializationProxy::registerProxy(typeid(CustomExternalForce), new CustomExternalForceProxy());
SerializationProxy::registerProxy(typeid(CustomGBForce), new CustomGBForceProxy());
SerializationProxy::registerProxy(typeid(CustomHbondForce), new CustomHbondForceProxy());
......
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2010-2015 Stanford University and the Authors. *
* Portions copyright (c) 2010-2017 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -71,15 +71,23 @@ void SystemProxy::serialize(const void* object, SerializationNode& node) const {
}
else if (typeid(system.getVirtualSite(i)) == typeid(LocalCoordinatesSite)) {
const LocalCoordinatesSite& site = dynamic_cast<const LocalCoordinatesSite&>(system.getVirtualSite(i));
Vec3 wo = site.getOriginWeights();
Vec3 wx = site.getXWeights();
Vec3 wy = site.getYWeights();
int numParticles = site.getNumParticles();
vector<double> wo, wx, wy;
site.getOriginWeights(wo);
site.getXWeights(wx);
site.getYWeights(wy);
Vec3 p = site.getLocalPosition();
particle.createChildNode("LocalCoordinatesSite").setIntProperty("p1", site.getParticle(0)).setIntProperty("p2", site.getParticle(1)).setIntProperty("p3", site.getParticle(2)).
setDoubleProperty("wo1", wo[0]).setDoubleProperty("wo2", wo[1]).setDoubleProperty("wo3", wo[2]).
setDoubleProperty("wx1", wx[0]).setDoubleProperty("wx2", wx[1]).setDoubleProperty("wx3", wx[2]).
setDoubleProperty("wy1", wy[0]).setDoubleProperty("wy2", wy[1]).setDoubleProperty("wy3", wy[2]).
setDoubleProperty("pos1", p[0]).setDoubleProperty("pos2", p[1]).setDoubleProperty("pos3", p[2]);
SerializationNode& siteNode = particle.createChildNode("LocalCoordinatesSite");
siteNode.setDoubleProperty("pos1", p[0]).setDoubleProperty("pos2", p[1]).setDoubleProperty("pos3", p[2]);
for (int j = 0; j < numParticles; j++) {
stringstream ss;
ss << (j+1);
string index = ss.str();
siteNode.setIntProperty("p"+index, site.getParticle(j));
siteNode.setDoubleProperty("wo"+index, wo[j]);
siteNode.setDoubleProperty("wx"+index, wx[j]);
siteNode.setDoubleProperty("wy"+index, wy[j]);
}
}
}
}
......@@ -120,11 +128,21 @@ void* SystemProxy::deserialize(const SerializationNode& node) const {
else if (vsite.getName() == "OutOfPlaneSite")
system->setVirtualSite(i, new OutOfPlaneSite(vsite.getIntProperty("p1"), vsite.getIntProperty("p2"), vsite.getIntProperty("p3"), vsite.getDoubleProperty("w12"), vsite.getDoubleProperty("w13"), vsite.getDoubleProperty("wc")));
else if (vsite.getName() == "LocalCoordinatesSite") {
Vec3 wo(vsite.getDoubleProperty("wo1"), vsite.getDoubleProperty("wo2"), vsite.getDoubleProperty("wo3"));
Vec3 wx(vsite.getDoubleProperty("wx1"), vsite.getDoubleProperty("wx2"), vsite.getDoubleProperty("wx3"));
Vec3 wy(vsite.getDoubleProperty("wy1"), vsite.getDoubleProperty("wy2"), vsite.getDoubleProperty("wy3"));
vector<int> particles;
vector<double> wo, wx, wy;
for (int j = 0; ; j++) {
stringstream ss;
ss << (j+1);
string index = ss.str();
if (!vsite.hasProperty("p"+index))
break;
particles.push_back(vsite.getIntProperty("p"+index));
wo.push_back(vsite.getDoubleProperty("wo"+index));
wx.push_back(vsite.getDoubleProperty("wx"+index));
wy.push_back(vsite.getDoubleProperty("wy"+index));
}
Vec3 p(vsite.getDoubleProperty("pos1"), vsite.getDoubleProperty("pos2"), vsite.getDoubleProperty("pos3"));
system->setVirtualSite(i, new LocalCoordinatesSite(vsite.getIntProperty("p1"), vsite.getIntProperty("p2"), vsite.getIntProperty("p3"), wo, wx, wy, p));
system->setVirtualSite(i, new LocalCoordinatesSite(particles, wo, wx, wy, p));
}
}
}
......
/* -------------------------------------------------------------------------- *
* 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) 2010-2017 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. *
* -------------------------------------------------------------------------- */
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/CustomCVForce.h"
#include "openmm/HarmonicAngleForce.h"
#include "openmm/HarmonicBondForce.h"
#include "openmm/serialization/XmlSerializer.h"
#include <iostream>
#include <sstream>
using namespace OpenMM;
using namespace std;
void testSerialization() {
// Create a Force.
CustomCVForce force("2*v1+v2");
force.setForceGroup(3);
force.addGlobalParameter("x", 1.3);
force.addGlobalParameter("y", 2.221);
force.addEnergyParameterDerivative("y");
HarmonicBondForce* v1 = new HarmonicBondForce();
v1->addBond(2, 3, 5.2, 1.1);
force.addCollectiveVariable("v1", v1);
HarmonicAngleForce* v2 = new HarmonicAngleForce();
v2->addAngle(3, 11, 15, 0.4, 0.2);
force.addCollectiveVariable("v2", v2);
vector<double> values(10);
for (int i = 0; i < 10; i++)
values[i] = sin((double) i);
force.addTabulatedFunction("f", new Continuous1DFunction(values, 0.5, 1.5));
// Serialize and then deserialize it.
stringstream buffer;
XmlSerializer::serialize<CustomCVForce>(&force, "Force", buffer);
CustomCVForce* copy = XmlSerializer::deserialize<CustomCVForce>(buffer);
// Compare the two forces to see if they are identical.
CustomCVForce& force2 = *copy;
ASSERT_EQUAL(force.getForceGroup(), force2.getForceGroup());
ASSERT_EQUAL(force.getEnergyFunction(), force2.getEnergyFunction());
ASSERT_EQUAL(force.getNumGlobalParameters(), force2.getNumGlobalParameters());
for (int i = 0; i < force.getNumGlobalParameters(); i++) {
ASSERT_EQUAL(force.getGlobalParameterName(i), force2.getGlobalParameterName(i));
ASSERT_EQUAL(force.getGlobalParameterDefaultValue(i), force2.getGlobalParameterDefaultValue(i));
}
ASSERT_EQUAL(force.getNumEnergyParameterDerivatives(), force2.getNumEnergyParameterDerivatives());
for (int i = 0; i < force.getNumEnergyParameterDerivatives(); i++)
ASSERT_EQUAL(force.getEnergyParameterDerivativeName(i), force2.getEnergyParameterDerivativeName(i));
ASSERT_EQUAL(force.getNumCollectiveVariables(), force2.getNumCollectiveVariables());
for (int i = 0; i < force.getNumCollectiveVariables(); i++) {
ASSERT_EQUAL(force.getCollectiveVariableName(i), force2.getCollectiveVariableName(i));
stringstream buffer1, buffer2;
XmlSerializer::serialize<Force>(&force.getCollectiveVariable(i), "Force", buffer1);
XmlSerializer::serialize<Force>(&force2.getCollectiveVariable(i), "Force", buffer2);
ASSERT_EQUAL(buffer1.str(), buffer2.str());
}
ASSERT_EQUAL(force.getNumTabulatedFunctions(), force2.getNumTabulatedFunctions());
for (int i = 0; i < force.getNumTabulatedFunctions(); i++) {
double min1, min2, max1, max2;
vector<double> val1, val2;
dynamic_cast<Continuous1DFunction&>(force.getTabulatedFunction(i)).getFunctionParameters(val1, min1, max1);
dynamic_cast<Continuous1DFunction&>(force2.getTabulatedFunction(i)).getFunctionParameters(val2, min2, max2);
ASSERT_EQUAL(force.getTabulatedFunctionName(i), force2.getTabulatedFunctionName(i));
ASSERT_EQUAL(min1, min2);
ASSERT_EQUAL(max1, max2);
ASSERT_EQUAL(val1.size(), val2.size());
for (int j = 0; j < (int) val1.size(); j++)
ASSERT_EQUAL(val1[j], val2[j]);
}
}
int main() {
try {
testSerialization();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
......@@ -156,6 +156,10 @@ void testSerializeCustomIntegrator() {
intg->addComputeSum("summand2", "v*v+f*f");
intg->setConstraintTolerance(1e-5);
intg->setKineticEnergyExpression("m*v1*v1/2; v1=v+0.5*dt*f/m");
vector<double> values(10);
for (int i = 0; i < 10; i++)
values[i] = sin((double) i);
intg->addTabulatedFunction("f", new Continuous1DFunction(values, 0.5, 1.5));
stringstream ss;
XmlSerializer::serialize<Integrator>(intg, "CustomIntegrator", ss);
CustomIntegrator *intg2 = dynamic_cast<CustomIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
......@@ -190,6 +194,19 @@ void testSerializeCustomIntegrator() {
ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize());
ASSERT_EQUAL(intg->getConstraintTolerance(), intg2->getConstraintTolerance());
ASSERT_EQUAL(intg->getNumTabulatedFunctions(), intg2->getNumTabulatedFunctions());
for (int i = 0; i < intg->getNumTabulatedFunctions(); i++) {
double min1, min2, max1, max2;
vector<double> val1, val2;
dynamic_cast<Continuous1DFunction&>(intg->getTabulatedFunction(i)).getFunctionParameters(val1, min1, max1);
dynamic_cast<Continuous1DFunction&>(intg2->getTabulatedFunction(i)).getFunctionParameters(val2, min2, max2);
ASSERT_EQUAL(intg->getTabulatedFunctionName(i), intg2->getTabulatedFunctionName(i));
ASSERT_EQUAL(min1, min2);
ASSERT_EQUAL(max1, max2);
ASSERT_EQUAL(val1.size(), val2.size());
for (int j = 0; j < (int) val1.size(); j++)
ASSERT_EQUAL(val1[j], val2[j]);
}
delete intg;
delete intg2;
}
......
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2010 Stanford University and the Authors. *
* Portions copyright (c) 2010-2017 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -82,6 +82,23 @@ void compareSystems(System& system, System& system2) {
ASSERT_EQUAL(0.1, site7.getWeight12());
ASSERT_EQUAL(0.2, site7.getWeight13());
ASSERT_EQUAL(0.5, site7.getWeightCross());
const LocalCoordinatesSite& site8 = dynamic_cast<const LocalCoordinatesSite&>(system2.getVirtualSite(8));
ASSERT_EQUAL(4, site8.getNumParticles());
ASSERT_EQUAL(4, site8.getParticle(0));
ASSERT_EQUAL(3, site8.getParticle(1));
ASSERT_EQUAL(2, site8.getParticle(2));
ASSERT_EQUAL(1, site8.getParticle(3));
ASSERT_EQUAL(Vec3(-0.5, 1.0, 1.5), site8.getLocalPosition());
vector<double> wo, wx, wy;
site8.getOriginWeights(wo);
site8.getXWeights(wx);
site8.getYWeights(wy);
vector<double> woExpected = {0.1, 0.2, 0.3, 0.4};
vector<double> wxExpected = {-1.0, 0.4, 0.4, 0.2};
vector<double> wyExpected = {0.3, 0.7, 0.0, -1.0};
ASSERT_EQUAL_CONTAINERS(woExpected, wo);
ASSERT_EQUAL_CONTAINERS(wxExpected, wx);
ASSERT_EQUAL_CONTAINERS(wyExpected, wy);
ASSERT_EQUAL(system.getNumForces(), system2.getNumForces());
for (int i = 0; i < system.getNumForces(); i++)
ASSERT(typeid(system.getForce(i)) == typeid(system2.getForce(i)))
......@@ -93,7 +110,7 @@ void testSerialization() {
System system;
for (int i = 0; i < 5; i++)
system.addParticle(0.1*i+1);
for (int i = 0; i < 4; i++)
for (int i = 0; i < 5; i++)
system.addParticle(0.0);
system.addConstraint(0, 1, 3.0);
system.addConstraint(1, 2, 2.5);
......@@ -102,6 +119,7 @@ void testSerialization() {
system.setVirtualSite(5, new TwoParticleAverageSite(0, 1, 0.3, 0.7));
system.setVirtualSite(6, new ThreeParticleAverageSite(2, 4, 3, 0.5, 0.2, 0.3));
system.setVirtualSite(7, new OutOfPlaneSite(0, 3, 1, 0.1, 0.2, 0.5));
system.setVirtualSite(8, new LocalCoordinatesSite({4, 3, 2, 1}, {0.1, 0.2, 0.3, 0.4}, {-1.0, 0.4, 0.4, 0.2}, {0.3, 0.7, 0.0, -1.0}, Vec3(-0.5, 1.0, 1.5)));
system.addForce(new HarmonicBondForce());
// Serialize and then deserialize it, then make sure the systems are identical.
......
/* -------------------------------------------------------------------------- *
* 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) 2017 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. *
* -------------------------------------------------------------------------- */
#ifdef WIN32
#define _USE_MATH_DEFINES // Needed to get M_PI
#endif
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/Context.h"
#include "openmm/CustomBondForce.h"
#include "openmm/CustomCVForce.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/CustomNonbondedForce.h"
#include "openmm/System.h"
#include "openmm/VerletIntegrator.h"
#include "sfmt/SFMT.h"
#include <iostream>
#include <vector>
using namespace OpenMM;
using namespace std;
void testCVs() {
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
// Create a CustomCVForce with two collective variables.
CustomCVForce* cv = new CustomCVForce("v1+3*v2");
system.addForce(cv);
CustomBondForce* v1 = new CustomBondForce("2*r");
v1->addBond(0, 1);
cv->addCollectiveVariable("v1", v1);
CustomBondForce* v2 = new CustomBondForce("r");
v2->addBond(0, 2);
cv->addCollectiveVariable("v2", v2);
ASSERT(!cv->usesPeriodicBoundaryConditions());
// Create a context for evaluating it.
VerletIntegrator integrator(1.0);
Context context(system, integrator, platform);
vector<Vec3> positions(3);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(1.5, 0, 0);
positions[2] = Vec3(0, 0, 2.5);
context.setPositions(positions);
// Verify the values of the collective variables.
vector<double> values;
cv->getCollectiveVariableValues(context, values);
ASSERT_EQUAL_TOL(2*1.5, values[0], 1e-6);
ASSERT_EQUAL_TOL(2.5, values[1], 1e-6);
// Verify the energy and forces.
State state = context.getState(State::Energy | State::Forces);
ASSERT_EQUAL_TOL(2*1.5+3*2.5, state.getPotentialEnergy(), 1e-6);
ASSERT_EQUAL_VEC(Vec3(2.0, 0.0, 3.0), state.getForces()[0], 1e-6);
ASSERT_EQUAL_VEC(Vec3(-2.0, 0.0, 0.0), state.getForces()[1], 1e-6);
ASSERT_EQUAL_VEC(Vec3(0.0, 0.0, -3.0), state.getForces()[2], 1e-6);
}
void testEnergyParameterDerivatives() {
System system;
system.addParticle(1.0);
system.addParticle(1.0);
// Create a CustomCVForce with one collective variable and two global parameters.
// The CV in turn depends on a global parameter.
CustomCVForce* cv = new CustomCVForce("v1*g1+g2");
system.addForce(cv);
CustomBondForce* v1 = new CustomBondForce("r*g3");
v1->addGlobalParameter("g3", 2.0);
v1->addEnergyParameterDerivative("g3");
v1->addBond(0, 1);
cv->addCollectiveVariable("v1", v1);
cv->addGlobalParameter("g1", 1.5);
cv->addGlobalParameter("g2", -1.0);
cv->addEnergyParameterDerivative("g2");
cv->addEnergyParameterDerivative("g1");
// Create a context for evaluating it.
VerletIntegrator integrator(1.0);
Context context(system, integrator, platform);
vector<Vec3> positions(2);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(2.0, 0, 0);
context.setPositions(positions);
// Verify the energy and parameter derivatives.
State state = context.getState(State::Energy | State::ParameterDerivatives);
ASSERT_EQUAL_TOL(4.0*1.5-1.0, state.getPotentialEnergy(), 1e-6);
map<string, double> derivs = state.getEnergyParameterDerivatives();
ASSERT_EQUAL_TOL(4.0, derivs["g1"], 1e-6);
ASSERT_EQUAL_TOL(1.0, derivs["g2"], 1e-6);
ASSERT_EQUAL_TOL(2.0*1.5, derivs["g3"], 1e-6);
}
void testTabulatedFunction() {
const int xsize = 10;
const int ysize = 11;
const double xmin = 0.4;
const double xmax = 1.1;
const double ymin = 0.0;
const double ymax = 0.95;
System system;
system.addParticle(1.0);
VerletIntegrator integrator(0.01);
CustomCVForce* cv = new CustomCVForce("fn(x,y)+1");
CustomExternalForce* v1 = new CustomExternalForce("x");
v1->addParticle(0);
cv->addCollectiveVariable("x", v1);
CustomExternalForce* v2 = new CustomExternalForce("y");
v2->addParticle(0);
cv->addCollectiveVariable("y", v2);
vector<double> table(xsize*ysize);
for (int i = 0; i < xsize; i++) {
for (int j = 0; j < ysize; j++) {
double x = xmin + i*(xmax-xmin)/xsize;
double y = ymin + j*(ymax-ymin)/ysize;
table[i+xsize*j] = sin(0.25*x)*cos(0.33*y);
}
}
cv->addTabulatedFunction("fn", new Continuous2DFunction(xsize, ysize, table, xmin, xmax, ymin, ymax));
system.addForce(cv);
Context context(system, integrator, platform);
vector<Vec3> positions(1);
for (double x = xmin-0.15; x < xmax+0.2; x += 0.1) {
for (double y = ymin-0.15; y < ymax+0.2; y += 0.1) {
positions[0] = Vec3(x, y, 1.5);
context.setPositions(positions);
State state = context.getState(State::Forces | State::Energy);
const vector<Vec3>& forces = state.getForces();
double energy = 1;
Vec3 force(0, 0, 0);
if (x >= xmin && x <= xmax && y >= ymin && y <= ymax) {
energy = sin(0.25*x)*cos(0.33*y)+1;
force[0] = -0.25*cos(0.25*x)*cos(0.33*y);
force[1] = 0.3*sin(0.25*x)*sin(0.33*y);
}
ASSERT_EQUAL_VEC(force, forces[0], 0.1);
ASSERT_EQUAL_TOL(energy, state.getPotentialEnergy(), 0.05);
}
}
}
void testReordering() {
// Create a larger system with a nonbonded force, since that will trigger atom
// reordering on the GPU.
const int numParticles = 100;
System system;
CustomCVForce* cv = new CustomCVForce("2*v2");
CustomNonbondedForce* v1 = new CustomNonbondedForce("r");
v1->addPerParticleParameter("a");
CustomBondForce* v2 = new CustomBondForce("r+1");
v2->addBond(5, 10);
cv->addCollectiveVariable("v1", v1);
cv->addCollectiveVariable("v2", v2);
cv->setForceGroup(2);
system.addForce(cv);
CustomNonbondedForce* nb = new CustomNonbondedForce("r^2");
nb->addPerParticleParameter("a");
system.addForce(nb);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
vector<Vec3> positions;
vector<double> params(1);
for (int i = 0; i < numParticles; i++) {
system.addParticle(1.0);
params[0] = i%2;
v1->addParticle(params);
params[0] = (i < numParticles/2 ? 2.0 : 3.0);
nb->addParticle(params);
positions.push_back(Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt))*10);
}
// Make sure it works correctly.
VerletIntegrator integrator(0.01);
Context context(system, integrator, platform);
context.setPositions(positions);
State state = context.getState(State::Energy | State::Forces, false, 1<<2);
Vec3 delta = positions[5]-positions[10];
double r = sqrt(delta.dot(delta));
ASSERT_EQUAL_TOL(2*(r+1), state.getPotentialEnergy(), 1e-5);
ASSERT_EQUAL_VEC(-delta*2/r, state.getForces()[5], 1e-5);
ASSERT_EQUAL_VEC(delta*2/r, state.getForces()[10], 1e-5);
}
void runPlatformTests();
int main(int argc, char* argv[]) {
try {
initializeTests(argc, argv);
testCVs();
testEnergyParameterDerivatives();
testTabulatedFunction();
testReordering();
runPlatformTests();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
......@@ -118,6 +118,7 @@ void testOBC(GBSAOBCForce::NonbondedMethod obcMethod, CustomGBForce::NonbondedMe
params[1] = 0.1;
custom->addParticle(params);
}
custom->addExclusion(2*i, 2*i+1);
positions[2*i] = Vec3(boxSize*genrand_real2(sfmt), boxSize*genrand_real2(sfmt), boxSize*genrand_real2(sfmt));
positions[2*i+1] = Vec3(positions[2*i][0]+1.0, positions[2*i][1], positions[2*i][2]);
velocities[2*i] = Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt));
......
......@@ -244,6 +244,33 @@ void testIllegalVariable() {
ASSERT(threwException);
}
void testParameters() {
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
VerletIntegrator integrator(0.01);
CustomHbondForce* custom = new CustomHbondForce("(2*d+a)*distance(d1,a1)");
custom->addPerDonorParameter("d");
custom->addPerAcceptorParameter("a");
custom->addDonor(1, 0, -1, vector<double>({1.5}));
custom->addDonor(2, 0, -1, vector<double>({1.8}));
custom->addAcceptor(0, 1, -1, vector<double>({2.1}));
system.addForce(custom);
Context context(system, integrator, platform);
vector<Vec3> positions(3);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(0, 2, 0);
positions[2] = Vec3(2, 0, 0);
context.setPositions(positions);
State state = context.getState(State::Forces | State::Energy);
const vector<Vec3>& forces = state.getForces();
ASSERT_EQUAL_VEC(Vec3((2*1.8+2.1), (2*1.5+2.1), 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(0, -(2*1.5+2.1), 0), forces[1], TOL);
ASSERT_EQUAL_VEC(Vec3(-(2*1.8+2.1), 0, 0), forces[2], TOL);
ASSERT_EQUAL_TOL(2*(2*1.8+2.1)+2*(2*1.5+2.1), state.getPotentialEnergy(), TOL);
}
void runPlatformTests();
int main(int argc, char* argv[]) {
......@@ -254,6 +281,7 @@ int main(int argc, char* argv[]) {
testCutoff();
testCustomFunctions();
testIllegalVariable();
testParameters();
runPlatformTests();
}
catch(const exception& e) {
......
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2008-2016 Stanford University and the Authors. *
* Portions copyright (c) 2008-2017 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -37,8 +37,10 @@
#include "openmm/AndersenThermostat.h"
#include "openmm/CustomAngleForce.h"
#include "openmm/CustomBondForce.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/CustomIntegrator.h"
#include "openmm/HarmonicBondForce.h"
#include "openmm/MonteCarloBarostat.h"
#include "openmm/NonbondedForce.h"
#include "openmm/System.h"
#include "SimTKOpenMMRealType.h"
......@@ -895,6 +897,128 @@ void testChangeDT() {
}
}
/**
* Test an integrator that uses a tabulated function.
*/
void testTabulatedFunction() {
System system;
system.addParticle(1.0);
CustomIntegrator integrator(1.0);
integrator.addGlobalVariable("global", 1.5);
integrator.addPerDofVariable("dof", 0.0);
integrator.addComputeGlobal("global", "fn(global)");
integrator.addComputePerDof("dof", "fn(x)");
vector<double> table;
table.push_back(10.0);
table.push_back(20.0);
integrator.addTabulatedFunction("fn", new Continuous1DFunction(table, 1.0, 2.0));
Context context(system, integrator, platform);
vector<Vec3> positions(1);
positions[0] = Vec3(1.2, 1.3, 1.4);
context.setPositions(positions);
integrator.step(1);
ASSERT_EQUAL_TOL(15.0, integrator.getGlobalVariable(0), 1e-5);
vector<Vec3> values;
integrator.getPerDofVariable(0, values);
ASSERT_EQUAL_VEC(Vec3(12.0, 13.0, 14.0), values[0], 1e-5);
}
/**
* Test an integrator that alternates repeatedly between force groups.
*/
void testAlternatingGroups() {
System system;
system.addParticle(1.0);
CustomExternalForce* force1 = new CustomExternalForce("-0.5*x");
force1->addParticle(0);
system.addForce(force1);
CustomExternalForce* force2 = new CustomExternalForce("-0.8*y");
force2->addParticle(0);
force2->setForceGroup(1);
system.addForce(force2);
CustomIntegrator integrator(0.5);
integrator.addGlobalVariable("savede1", 0.0);
integrator.addGlobalVariable("savede2", 0.0);
integrator.addGlobalVariable("savede3", 0.0);
integrator.addGlobalVariable("savede4", 0.0);
integrator.addPerDofVariable("savedf1", 0.0);
integrator.addPerDofVariable("savedf2", 0.0);
integrator.addPerDofVariable("savedf3", 0.0);
integrator.addPerDofVariable("savedf4", 0.0);
integrator.addComputeGlobal("savede1", "energy0");
integrator.addComputeGlobal("savede2", "energy1");
integrator.addComputePerDof("savedf1", "f0");
integrator.addComputePerDof("savedf2", "f1");
integrator.addComputeGlobal("savede3", "energy0");
integrator.addComputeGlobal("savede4", "energy1");
integrator.addComputePerDof("savedf3", "f0");
integrator.addComputePerDof("savedf4", "f1");
Context context(system, integrator, platform);
vector<Vec3> positions(1);
positions[0] = Vec3(1, 2, 3);
context.setPositions(positions);
integrator.step(1);
vector<Vec3> f;
for (int i = 0; i < 2; i++) {
ASSERT_EQUAL_TOL(-0.5*1, integrator.getGlobalVariable(2*i), 1e-5);
ASSERT_EQUAL_TOL(-0.8*2, integrator.getGlobalVariable(2*i+1), 1e-5);
integrator.getPerDofVariable(2*i, f);
ASSERT_EQUAL_VEC(Vec3(0.5, 0, 0), f[0], 1e-5);
integrator.getPerDofVariable(2*i+1, f);
ASSERT_EQUAL_VEC(Vec3(0, 0.8, 0), f[0], 1e-5);
}
}
/**
* Test that the forces are recomputed when updateContextState() modifies positions.
*/
void testUpdateContextState() {
const int numParticles = 100;
const double boxSize = 5.0;
System system;
system.setDefaultPeriodicBoxVectors(Vec3(boxSize, 0, 0), Vec3(0, boxSize, 0), Vec3(0, 0, boxSize));
CustomIntegrator integrator(0.003);
integrator.addPerDofVariable("force1", 0.0);
integrator.addPerDofVariable("force2", 0.0);
integrator.addComputePerDof("force1", "f");
integrator.addUpdateContextState();
integrator.addComputePerDof("force2", "f");
NonbondedForce* nonbonded = new NonbondedForce();
nonbonded->setNonbondedMethod(NonbondedForce::CutoffPeriodic);
nonbonded->setCutoffDistance(2.0);
for (int i = 0; i < numParticles; ++i) {
system.addParticle(2.0);
nonbonded->addParticle((i%2 == 0 ? 1.0 : -1.0), 1.0, 5.0);
}
system.addForce(nonbonded);
system.addForce(new MonteCarloBarostat(1.0, 300.0, 1));
Context context(system, integrator, platform);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
vector<Vec3> positions(numParticles);
for (int i = 0; i < numParticles; ++i)
positions[i] = Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt))*boxSize;
context.setPositions(positions);
// Make sure the forces change when the barostat accepts a step, and don't change
// otherwise.
for (int i = 0; i < 50; i++) {
State state1 = context.getState(0);
integrator.step(1);
State state2 = context.getState(0);
bool changed = (state1.getPeriodicBoxVolume() != state2.getPeriodicBoxVolume());
vector<Vec3> f1, f2;
integrator.getPerDofVariable(0, f1);
integrator.getPerDofVariable(1, f2);
bool different = false;
for (int j = 0; j < numParticles; j++)
if (f1[j] != f2[j])
different = true;
ASSERT_EQUAL(changed, different);
}
}
void runPlatformTests();
int main(int argc, char* argv[]) {
......@@ -917,6 +1041,9 @@ int main(int argc, char* argv[]) {
testChangingGlobal();
testEnergyParameterDerivatives();
testChangeDT();
testTabulatedFunction();
testAlternatingGroups();
testUpdateContextState();
runPlatformTests();
}
catch(const exception& e) {
......
......@@ -206,30 +206,51 @@ void testOutOfPlane() {
}
}
Vec3 computeWeightedPosition(const vector<Vec3>& positions, const vector<double>& weights) {
Vec3 sum;
for (int i = 0; i < weights.size(); i++)
sum += positions[i]*weights[i];
return sum;
}
/**
* Test a LocalCoordinatesSite virtual site.
*/
void testLocalCoordinates() {
const Vec3 originWeights(0.2, 0.3, 0.5);
const Vec3 xWeights(-1.0, 0.5, 0.5);
const Vec3 yWeights(0.0, -1.0, 1.0);
void testLocalCoordinates(int numSiteParticles) {
vector<int> particles;
vector<double> originWeights, xWeights, yWeights;
if (numSiteParticles == 2) {
particles = {0, 1};
originWeights = {0.4, 0.6};
xWeights = {-1.0, 1.0};
yWeights = {1.0, -1.0};
}
else if (numSiteParticles == 3) {
particles = {0, 1, 2};
originWeights = {0.2, 0.3, 0.5};
xWeights = {-1.0, 0.5, 0.5};
yWeights = {0.0, -1.0, 1.0};
}
else if (numSiteParticles == 4) {
particles = {0, 1, 2, 3};
originWeights = {0.2, 0.3, 0.1, 0.4};
xWeights = {-1.0, 0.3, 0.3, 0.4};
yWeights = {0.5, 0.5, -0.5, -0.5};
}
const Vec3 localPosition(0.4, 0.3, 0.2);
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
for (int i = 0; i < numSiteParticles; i++)
system.addParticle(1.0);
system.addParticle(0.0);
system.setVirtualSite(3, new LocalCoordinatesSite(0, 1, 2, originWeights, xWeights, yWeights, localPosition));
system.setVirtualSite(numSiteParticles, new LocalCoordinatesSite(particles, originWeights, xWeights, yWeights, localPosition));
CustomExternalForce* forceField = new CustomExternalForce("2*x^2+3*y^2+4*z^2");
system.addForce(forceField);
vector<double> params;
forceField->addParticle(0, params);
forceField->addParticle(1, params);
forceField->addParticle(2, params);
forceField->addParticle(3, params);
for (int i = 0; i < numSiteParticles+1; i++)
forceField->addParticle(0, params);
LangevinIntegrator integrator(300.0, 0.1, 0.002);
Context context(system, integrator, platform);
vector<Vec3> positions(4), positions2(4), positions3(4);
vector<Vec3> positions(numSiteParticles+1), positions2(numSiteParticles+1), positions3(numSiteParticles+1);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
for (int i = 0; i < 100; i++) {
......@@ -237,12 +258,12 @@ void testLocalCoordinates() {
Vec3 xdir, ydir, zdir;
do {
for (int j = 0; j < 3; j++)
for (int j = 0; j < numSiteParticles; j++)
positions[j] = Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt));
xdir = positions[0]*xWeights[0] + positions[1]*xWeights[1] + positions[2]*xWeights[2];
ydir = positions[0]*yWeights[0] + positions[1]*yWeights[1] + positions[2]*yWeights[2];
xdir = computeWeightedPosition(positions, xWeights);
ydir = computeWeightedPosition(positions, yWeights);;
zdir = xdir.cross(ydir);
if (sqrt(xdir.dot(xdir)) > 0.1 && sqrt(ydir.dot(ydir)) > 0.1 && sqrt(zdir.dot(zdir)) > 0.1)
if (sqrt(xdir.dot(xdir)) > 0.1 && (numSiteParticles == 2 || (sqrt(ydir.dot(ydir)) > 0.1 && sqrt(zdir.dot(zdir)) > 0.1)))
break; // These positions give a reasonable coordinate system.
} while (true);
context.setPositions(positions);
......@@ -252,23 +273,23 @@ void testLocalCoordinates() {
State state = context.getState(State::Positions | State::Forces);
const vector<Vec3>& pos = state.getPositions();
Vec3 origin = pos[0]*originWeights[0] + pos[1]*originWeights[1] + pos[2]*originWeights[2];
Vec3 origin = computeWeightedPosition(pos, originWeights);;
xdir /= sqrt(xdir.dot(xdir));
zdir /= sqrt(zdir.dot(zdir));
ydir = zdir.cross(xdir);
ASSERT_EQUAL_VEC(origin+xdir*localPosition[0]+ydir*localPosition[1]+zdir*localPosition[2], pos[3], 1e-5);
ASSERT_EQUAL_VEC(origin+xdir*localPosition[0]+ydir*localPosition[1]+zdir*localPosition[2], pos[numSiteParticles], 1e-5);
// Take a small step in the direction of the energy gradient and see whether the potential energy changes by the expected amount.
double norm = 0.0;
for (int i = 0; i < 3; ++i) {
for (int i = 0; i < numSiteParticles; ++i) {
Vec3 f = state.getForces()[i];
norm += f[0]*f[0] + f[1]*f[1] + f[2]*f[2];
}
norm = std::sqrt(norm);
const double delta = 1e-2;
double step = 0.5*delta/norm;
for (int i = 0; i < 3; ++i) {
for (int i = 0; i < numSiteParticles; ++i) {
Vec3 p = positions[i];
Vec3 f = state.getForces()[i];
positions2[i] = Vec3(p[0]-f[0]*step, p[1]-f[1]*step, p[2]-f[2]*step);
......@@ -469,7 +490,9 @@ int main(int argc, char* argv[]) {
testTwoParticleAverage();
testThreeParticleAverage();
testOutOfPlane();
testLocalCoordinates();
testLocalCoordinates(2);
testLocalCoordinates(3);
testLocalCoordinates(4);
testConservationLaws();
testOverlappingSites();
runPlatformTests();
......
......@@ -68,7 +68,15 @@ class WrapperGenerator:
def __init__(self, inputDirname, output):
self.skipClasses = ['OpenMM::Vec3', 'OpenMM::XmlSerializer', 'OpenMM::Kernel', 'OpenMM::KernelImpl', 'OpenMM::KernelFactory', 'OpenMM::ContextImpl', 'OpenMM::SerializationNode', 'OpenMM::SerializationProxy']
self.skipMethods = ['OpenMM::Context::getState', 'OpenMM::Platform::loadPluginsFromDirectory', 'OpenMM::Platform::getPluginLoadFailures', 'OpenMM::Context::createCheckpoint', 'OpenMM::Context::loadCheckpoint', 'OpenMM::Context::getMolecules']
self.skipMethods = ['State OpenMM::Context::getState',
'void OpenMM::Context::createCheckpoint',
'void OpenMM::Context::loadCheckpoint',
'const std::vector<std::vector<int> >& OpenMM::Context::getMolecules',
'static std::vector<std::string> OpenMM::Platform::getPluginLoadFailures',
'static std::vector<std::string> OpenMM::Platform::loadPluginsFromDirectory',
'Vec3 OpenMM::LocalCoordinatesSite::getOriginWeights',
'Vec3 OpenMM::LocalCoordinatesSite::getXWeights',
'Vec3 OpenMM::LocalCoordinatesSite::getYWeights']
self.hideClasses = ['Kernel', 'KernelImpl', 'KernelFactory', 'ContextImpl', 'SerializationNode', 'SerializationProxy']
self.nodeByID={}
......@@ -119,9 +127,7 @@ class WrapperGenerator:
for section in findNodes(classNode, "sectiondef", kind="public-static-func")+findNodes(classNode, "sectiondef", kind="public-func"):
for memberNode in findNodes(section, "memberdef", kind="function", prot="public"):
methodDefinition = getText("definition", memberNode)
shortMethodDefinition = stripOpenMMPrefix(methodDefinition)
methodName = shortMethodDefinition.split()[-1]
if className+'::'+methodName in self.skipMethods:
if methodDefinition in self.skipMethods:
continue
methodList.append(memberNode)
return methodList
......
......@@ -161,7 +161,7 @@ class AmberPrmtopFile(object):
implicitSolventKappa=None, temperature=298.15*u.kelvin,
soluteDielectric=1.0, solventDielectric=78.5,
removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005,
switchDistance=0.0*u.nanometer):
switchDistance=0.0*u.nanometer, gbsaModel='ACE'):
"""Construct an OpenMM System representing the topology described by this
prmtop file.
......@@ -208,6 +208,11 @@ class AmberPrmtopFile(object):
turned on for Lennard-Jones interactions. If the switchDistance is 0
or evaluates to boolean False, no switching function will be used.
Values greater than nonbondedCutoff or less than 0 raise ValueError
gbsaModel : str='ACE'
The SA model used to model the nonpolar solvation component of GB
implicit solvent models. If GB is active, this must be 'ACE' or None
(the latter indicates no SA model will be used). Other values will
result in a ValueError
Returns
-------
......@@ -273,7 +278,7 @@ class AmberPrmtopFile(object):
nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod],
flexibleConstraints=False, gbmodel=implicitString, soluteDielectric=soluteDielectric,
solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa,
rigidWater=rigidWater, elements=self.elements)
rigidWater=rigidWater, elements=self.elements, gbsaModel=gbsaModel)
if hydrogenMass is not None:
for atom1, atom2 in self.topology.bonds():
......
......@@ -117,22 +117,23 @@ class CharmmPsfFile(object):
This structure has numerous attributes that are lists of the elements of
this structure, including atoms, bonds, torsions, etc. The attributes are
- residue_list
- atom_list
- bond_list
- angle_list
- dihedral_list
- dihedral_parameter_list
- improper_list
- cmap_list
- donor_list # hbonds donors?
- acceptor_list # hbond acceptors?
- group_list # list of nonbonded interaction groups
- residue_list
- atom_list
- bond_list
- angle_list
- dihedral_list
- dihedral_parameter_list
- improper_list
- cmap_list
- donor_list # hbonds donors?
- acceptor_list # hbond acceptors?
- group_list # list of nonbonded interaction groups
Additional attribute is available if a CharmmParameterSet is loaded into
this structure.
- urey_bradley_list
- urey_bradley_list
The lengths of each of these lists gives the pointers (e.g., natom, nres,
etc.)
......@@ -678,7 +679,8 @@ class CharmmPsfFile(object):
hydrogenMass=None,
ewaldErrorTolerance=0.0005,
flexibleConstraints=True,
verbose=False):
verbose=False,
gbsaModel=None):
"""Construct an OpenMM System representing the topology described by the
prmtop file. You MUST have loaded a parameter set into this PSF before
calling createSystem. If not, AttributeError will be raised. ValueError
......@@ -712,7 +714,7 @@ class CharmmPsfFile(object):
solvent.
implicitSolventSaltConc : float=0.0*u.moles/u.liter
Salt concentration for GB simulations. Converted to Debye length
`kappa'
``kappa``
temperature : float=298.15*u.kelvin
Temperature used in the salt concentration-to-kappa conversion for
GB salt concentration term
......@@ -733,10 +735,16 @@ class CharmmPsfFile(object):
Are our constraints flexible or not?
verbose : bool=False
Optionally prints out a running progress report
gbsaModel : str=None
Can be ACE (to use the ACE solvation model) or None. Other values
raise a ValueError
"""
# Load the parameter set
self.loadParameters(params.condense())
hasbox = self.topology.getUnitCellDimensions() is not None
# Check GB input parameters
if implicitSolvent is not None and gbsaModel not in ('ACE', None):
raise ValueError('gbsaModel must be ACE or None')
# Set the cutoff distance in nanometers
cutoff = None
if nonbondedMethod is not ff.NoCutoff:
......@@ -906,8 +914,10 @@ class CharmmPsfFile(object):
if verbose: print('Adding impropers...')
# Ick. OpenMM does not have an improper torsion class. Need to
# construct one from CustomTorsionForce
force = mm.CustomTorsionForce('k*(theta-theta0)^2')
# construct one from CustomTorsionForce that respects toroidal boundaries
energy_function = 'k*min(dtheta, 2*pi-dtheta)^2; dtheta = abs(theta-theta0);'
energy_function += 'pi = %f;' % pi
force = mm.CustomTorsionForce(energy_function)
force.addPerTorsionParameter('k')
force.addPerTorsionParameter('theta0')
force.setForceGroup(self.IMPROPER_FORCE_GROUP)
......@@ -1189,19 +1199,19 @@ class CharmmPsfFile(object):
implicitSolventKappa = implicitSolventKappa.value_in_unit(
(1.0/u.nanometer).unit)
if implicitSolvent is HCT:
gb = GBSAHCTForce(solventDielectric, soluteDielectric, None,
gb = GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC1:
gb = GBSAOBC1Force(solventDielectric, soluteDielectric, None,
gb = GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC2:
gb = GBSAOBC2Force(solventDielectric, soluteDielectric, None,
gb = GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn:
gb = GBSAGBnForce(solventDielectric, soluteDielectric, None,
gb = GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn2:
gb = GBSAGBn2Force(solventDielectric, soluteDielectric, None,
gb = GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa)
gb_parms = gb.getStandardParameters(self.topology)
for atom, gb_parm in zip(self.atom_list, gb_parms):
......
......@@ -57,7 +57,7 @@ class DCDReporter(object):
self._reportInterval = reportInterval
self._append = append
if append:
mode = 'a+b'
mode = 'r+b'
else:
mode = 'wb'
self._out = open(file, mode)
......
......@@ -47,6 +47,16 @@ from . import element as elem
from simtk.openmm.app import Topology
from simtk.openmm.app.internal.singleton import Singleton
# Directories from which to load built in force fields.
_dataDirectories = [os.path.join(os.path.dirname(__file__), 'data')]
try:
from pkg_resources import iter_entry_points
for entry in iter_entry_points(group='openmm.forcefielddir'):
_dataDirectories.append(entry.load()())
except:
pass # pkg_resources is not installed
def _convertParameterToNumber(param):
if unit.is_quantity(param):
if param.unit.is_compatible(unit.bar):
......@@ -186,11 +196,16 @@ class ForceField(object):
trees = []
for file in files:
tree = None
try:
# this handles either filenames or open file-like objects
tree = etree.parse(file)
except IOError:
tree = etree.parse(os.path.join(os.path.dirname(__file__), 'data', file))
for dataDir in _dataDirectories:
f = os.path.join(dataDir, file)
if os.path.isfile(f):
tree = etree.parse(f)
break
except Exception as e:
# Fail with an error message about which file could not be read.
# TODO: Also handle case where fallback to 'data' directory encounters problems,
......@@ -202,9 +217,24 @@ class ForceField(object):
filename = str(file)
msg += "ForceField.loadFile() encountered an error reading file '%s'\n" % filename
raise Exception(msg)
if tree is None:
raise ValueError('Could not locate file "%s"' % file)
trees.append(tree)
# Process includes.
for parentFile, tree in zip(files, trees):
if isinstance(parentFile, str):
parentDir = os.path.dirname(parentFile)
else:
parentDir = ''
for include in tree.getroot().findall('Include'):
includeFile = include.attrib['file']
joined = os.path.join(parentDir, includeFile)
if os.path.isfile(joined):
includeFile = joined
self.loadFile(includeFile)
# Load the atom types.
......@@ -415,17 +445,19 @@ class ForceField(object):
generator : function
A function that will be called when a residue is encountered that does not match an existing forcefield template.
When a residue without a template is encountered, the `generator` function is called with:
When a residue without a template is encountered, the ``generator`` function is called with:
::
success = generator(forcefield, residue)
```
where `forcefield` is the calling `ForceField` object and `residue` is a simtk.openmm.app.topology.Residue object.
where ``forcefield`` is the calling ``ForceField`` object and ``residue`` is a simtk.openmm.app.topology.Residue object.
``generator`` must conform to the following API:
`generator` must conform to the following API:
::
Parameters
generator API
Parameters
----------
forcefield : simtk.openmm.app.ForceField
The ForceField object to which residue templates and/or parameters are to be added.
......@@ -508,6 +540,7 @@ class ForceField(object):
def __init__(self):
self.atomType = {}
self.atomParameters = {}
self.atomTemplateIndexes = {}
self.atoms = []
self.excludeAtomWith = []
self.virtualSites = {}
......@@ -535,6 +568,7 @@ class ForceField(object):
for atom, match in zip(residue.atoms(), matches):
self.atomType[atom] = template.atoms[match].type
self.atomParameters[atom] = template.atoms[match].parameters
self.atomTemplateIndexes[atom] = match
for site in template.virtualSites:
if match == site.index:
self.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index)
......@@ -615,10 +649,15 @@ class ForceField(object):
numAtoms = 3
self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])]
elif self.type == 'localCoords':
numAtoms = 3
self.originWeights = [float(attrib['wo1']), float(attrib['wo2']), float(attrib['wo3'])]
self.xWeights = [float(attrib['wx1']), float(attrib['wx2']), float(attrib['wx3'])]
self.yWeights = [float(attrib['wy1']), float(attrib['wy2']), float(attrib['wy3'])]
numAtoms = 0
self.originWeights = []
self.xWeights = []
self.yWeights = []
while ('wo%d' % (numAtoms+1)) in attrib:
numAtoms += 1
self.originWeights.append(float(attrib['wo%d' % numAtoms]))
self.xWeights.append(float(attrib['wx%d' % numAtoms]))
self.yWeights.append(float(attrib['wy%d' % numAtoms]))
self.localPos = [float(attrib['p1']), float(attrib['p2']), float(attrib['p3'])]
else:
raise ValueError('Unknown virtual site type: %s' % self.type)
......@@ -1003,6 +1042,8 @@ class ForceField(object):
not terminated properly. This option can create ambiguities where multiple
templates match the same residue. If that happens, use the residueTemplates
argument to specify which one to use.
flexibleConstraints : boolean=False
If True, parameters for constrained degrees of freedom will be added to the System
args
Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to
......@@ -1108,9 +1149,9 @@ class ForceField(object):
if not unit.is_quantity(hydrogenMass):
hydrogenMass *= unit.dalton
for atom1, atom2 in topology.bonds():
if atom1.element == elem.hydrogen:
if atom1.element is elem.hydrogen:
(atom1, atom2) = (atom2, atom1)
if atom2.element == elem.hydrogen and atom1.element not in (elem.hydrogen, None):
if atom2.element is elem.hydrogen and atom1.element not in (elem.hydrogen, None):
transferMass = hydrogenMass-sys.getParticleMass(atom2.index)
sys.setParticleMass(atom2.index, hydrogenMass)
sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass)
......@@ -1176,7 +1217,7 @@ class ForceField(object):
for bond in data.bonds:
atom1 = data.atoms[bond.atom1]
atom2 = data.atoms[bond.atom2]
bond.isConstrained = atom1.name.startswith('H') or atom2.name.startswith('H')
bond.isConstrained = atom1.element is elem.hydrogen or atom2.element is elem.hydrogen
if rigidWater:
for bond in data.bonds:
atom1 = data.atoms[bond.atom1]
......@@ -1192,11 +1233,11 @@ class ForceField(object):
atom2 = data.atoms[angle[1]]
atom3 = data.atoms[angle[2]]
numH = 0
if atom1.name.startswith('H'):
if atom1.element is elem.hydrogen:
numH += 1
if atom3.name.startswith('H'):
if atom3.element is elem.hydrogen:
numH += 1
data.isAngleConstrained.append(numH == 2 or (numH == 1 and atom2.name.startswith('O')))
data.isAngleConstrained.append(numH == 2 or (numH == 1 and atom2.element is elem.oxygen))
else:
data.isAngleConstrained = len(data.angles)*[False]
if rigidWater:
......@@ -1221,11 +1262,7 @@ class ForceField(object):
elif site.type == 'outOfPlane':
sys.setVirtualSite(index, mm.OutOfPlaneSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2]))
elif site.type == 'localCoords':
sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms[0], atoms[1], atoms[2],
mm.Vec3(site.originWeights[0], site.originWeights[1], site.originWeights[2]),
mm.Vec3(site.xWeights[0], site.xWeights[1], site.xWeights[2]),
mm.Vec3(site.yWeights[0], site.yWeights[1], site.yWeights[2]),
mm.Vec3(site.localPos[0], site.localPos[1], site.localPos[2])))
sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms, site.originWeights, site.xWeights, site.yWeights, site.localPos))
# Add forces to the System
......@@ -1704,6 +1741,108 @@ def _createResidueTemplate(residue):
template.addExternalBondByName(atom2.name)
return template
def _matchImproper(data, torsion, generator):
type1 = data.atomType[data.atoms[torsion[0]]]
type2 = data.atomType[data.atoms[torsion[1]]]
type3 = data.atomType[data.atoms[torsion[2]]]
type4 = data.atomType[data.atoms[torsion[3]]]
wildcard = generator.ff._atomClasses['']
match = None
for tordef in generator.improper:
types1 = tordef.types1
types2 = tordef.types2
types3 = tordef.types3
types4 = tordef.types4
hasWildcard = (wildcard in (types1, types2, types3, types4))
if match is not None and hasWildcard:
# Prefer specific definitions over ones with wildcards
continue
if type1 in types1:
for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
if tordef.ordering == 'default':
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
# to pick the order.
a1 = torsion[t2[1]]
a2 = torsion[t3[1]]
e1 = data.atoms[a1].element
e2 = data.atoms[a2].element
if e1 == e2 and a1 > a2:
(a1, a2) = (a2, a1)
elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
(a1, a2) = (a2, a1)
match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
break
elif tordef.ordering == 'charmm':
if hasWildcard:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
# to pick the order.
a1 = torsion[t2[1]]
a2 = torsion[t3[1]]
e1 = data.atoms[a1].element
e2 = data.atoms[a2].element
if e1 == e2 and a1 > a2:
(a1, a2) = (a2, a1)
elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
(a1, a2) = (a2, a1)
match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
else:
# There are no wildcards, so the order is unambiguous.
match = (torsion[0], torsion[t2[1]], torsion[t3[1]], torsion[t4[1]], tordef)
break
elif tordef.ordering == 'amber':
# topology atom indexes
a2 = torsion[t2[1]]
a3 = torsion[t3[1]]
a4 = torsion[t4[1]]
# residue indexes
r2 = data.atoms[a2].residue.index
r3 = data.atoms[a3].residue.index
r4 = data.atoms[a4].residue.index
# template atom indexes
ta2 = data.atomTemplateIndexes[data.atoms[a2]]
ta3 = data.atomTemplateIndexes[data.atoms[a3]]
ta4 = data.atomTemplateIndexes[data.atoms[a4]]
# elements
e2 = data.atoms[a2].element
e3 = data.atoms[a3].element
e4 = data.atoms[a4].element
if not hasWildcard:
if t2[0] == t4[0] and (r2 > r4 or (r2 == r4 and ta2 > ta4)):
(a2, a4) = (a4, a2)
r2 = data.atoms[a2].residue.index
r4 = data.atoms[a4].residue.index
ta2 = data.atomTemplateIndexes[data.atoms[a2]]
ta4 = data.atomTemplateIndexes[data.atoms[a4]]
if t3[0] == t4[0] and (r3 > r4 or (r3 == r4 and ta3 > ta4)):
(a3, a4) = (a4, a3)
r3 = data.atoms[a3].residue.index
r4 = data.atoms[a4].residue.index
ta3 = data.atomTemplateIndexes[data.atoms[a3]]
ta4 = data.atomTemplateIndexes[data.atoms[a4]]
if t2[0] == t3[0] and (r2 > r3 or (r2 == r3 and ta2 > ta3)):
(a2, a3) = (a3, a2)
else:
if e2 == e4 and (r2 > r4 or (r2 == r4 and ta2 > ta4)):
(a2, a4) = (a4, a2)
r2 = data.atoms[a2].residue.index
r4 = data.atoms[a4].residue.index
ta2 = data.atomTemplateIndexes[data.atoms[a2]]
ta4 = data.atomTemplateIndexes[data.atoms[a4]]
if e3 == e4 and (r3 > r4 or (r3 == r4 and ta3 > ta4)):
(a3, a4) = (a4, a3)
r3 = data.atoms[a3].residue.index
r4 = data.atoms[a4].residue.index
ta3 = data.atomTemplateIndexes[data.atoms[a3]]
ta4 = data.atomTemplateIndexes[data.atoms[a4]]
if r2 > r3 or (r2 == r3 and ta2 > ta3):
(a2, a3) = (a3, a2)
match = (a2, a3, torsion[0], a4, tordef)
break
return match
# The following classes are generators that know how to create Force subclasses and add them to a System that is being
# created. Each generator class must define two methods: 1) a static method that takes an etree Element and a ForceField,
# and returns the corresponding generator object; 2) a createForce() method that constructs the Force object and adds it
......@@ -1763,8 +1902,11 @@ class HarmonicBondGenerator(object):
bond.length = self.length[i]
if bond.isConstrained:
data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
if self.k[i] != 0:
# flexibleConstraints allows us to add parameters even if the DOF is
# constrained
if not bond.isConstrained or args.get('flexibleConstraints', False):
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
break
parsers["HarmonicBondForce"] = HarmonicBondGenerator.parseElement
......@@ -1844,8 +1986,9 @@ class HarmonicAngleGenerator(object):
if l1 is not None and l2 is not None:
length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i]))
data.addConstraint(sys, angle[0], angle[2], length)
elif self.k[i] != 0:
force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i])
if self.k[i] != 0:
if not isConstrained or args.get('flexibleConstraints', False):
force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i])
break
parsers["HarmonicAngleForce"] = HarmonicAngleGenerator.parseElement
......@@ -1863,6 +2006,7 @@ class PeriodicTorsion(object):
self.periodicity = []
self.phase = []
self.k = []
self.ordering = 'default'
## @private
class PeriodicTorsionGenerator(object):
......@@ -1878,9 +2022,13 @@ class PeriodicTorsionGenerator(object):
if torsion is not None:
self.proper.append(torsion)
def registerImproperTorsion(self, parameters):
def registerImproperTorsion(self, parameters, ordering='default'):
torsion = self.ff._parseTorsion(parameters)
if torsion is not None:
if ordering in ['default', 'charmm', 'amber']:
torsion.ordering = ordering
else:
raise ValueError('Illegal ordering type %s for improper torsion %s' % (ordering, torsion))
self.improper.append(torsion)
@staticmethod
......@@ -1894,7 +2042,10 @@ class PeriodicTorsionGenerator(object):
for torsion in element.findall('Proper'):
generator.registerProperTorsion(torsion.attrib)
for torsion in element.findall('Improper'):
generator.registerImproperTorsion(torsion.attrib)
if 'ordering' in element.attrib:
generator.registerImproperTorsion(torsion.attrib, element.attrib['ordering'])
else:
generator.registerImproperTorsion(torsion.attrib)
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
existing = [sys.getForce(i) for i in range(sys.getNumForces())]
......@@ -1927,36 +2078,7 @@ class PeriodicTorsionGenerator(object):
if match.k[i] != 0:
force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.periodicity[i], match.phase[i], match.k[i])
for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]]
type2 = data.atomType[data.atoms[torsion[1]]]
type3 = data.atomType[data.atoms[torsion[2]]]
type4 = data.atomType[data.atoms[torsion[3]]]
match = None
for tordef in self.improper:
types1 = tordef.types1
types2 = tordef.types2
types3 = tordef.types3
types4 = tordef.types4
hasWildcard = (wildcard in (types1, types2, types3, types4))
if match is not None and hasWildcard:
# Prefer specific definitions over ones with wildcards
continue
if type1 in types1:
for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
# to pick the order.
a1 = torsion[t2[1]]
a2 = torsion[t3[1]]
e1 = data.atoms[a1].element
e2 = data.atoms[a2].element
if e1 == e2 and a1 > a2:
(a1, a2) = (a2, a1)
elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
(a1, a2) = (a2, a1)
match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
break
match = _matchImproper(data, torsion, self)
if match is not None:
(a1, a2, a3, a4, tordef) = match
for i in range(len(tordef.phase)):
......@@ -1970,12 +2092,16 @@ parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement
class RBTorsion(object):
"""An RBTorsion records the information for a Ryckaert-Bellemans torsion definition."""
def __init__(self, types, c):
def __init__(self, types, c, ordering='charmm'):
self.types1 = types[0]
self.types2 = types[1]
self.types3 = types[2]
self.types4 = types[3]
self.c = c
if ordering in ['default', 'charmm', 'amber']:
self.ordering = ordering
else:
raise ValueError('Illegal ordering type %s for RBTorsion (%s,%s,%s,%s)' % (ordering, types[0], types[1], types[2], types[3]))
## @private
class RBTorsionGenerator(object):
......@@ -2001,7 +2127,10 @@ class RBTorsionGenerator(object):
for torsion in element.findall('Improper'):
types = ff._findAtomTypes(torsion.attrib, 4)
if None not in types:
generator.improper.append(RBTorsion(types, [float(torsion.attrib['c'+str(i)]) for i in range(6)]))
if 'ordering' in element.attrib:
generator.improper.append(RBTorsion(types, [float(torsion.attrib['c'+str(i)]) for i in range(6)], element.attrib['ordering']))
else:
generator.improper.append(RBTorsion(types, [float(torsion.attrib['c'+str(i)]) for i in range(6)]))
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
existing = [sys.getForce(i) for i in range(sys.getNumForces())]
......@@ -2032,40 +2161,7 @@ class RBTorsionGenerator(object):
if match is not None:
force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.c[0], match.c[1], match.c[2], match.c[3], match.c[4], match.c[5])
for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]]
type2 = data.atomType[data.atoms[torsion[1]]]
type3 = data.atomType[data.atoms[torsion[2]]]
type4 = data.atomType[data.atoms[torsion[3]]]
match = None
for tordef in self.improper:
types1 = tordef.types1
types2 = tordef.types2
types3 = tordef.types3
types4 = tordef.types4
hasWildcard = (wildcard in (types1, types2, types3, types4))
if match is not None and hasWildcard:
# Prefer specific definitions over ones with wildcards
continue
if type1 in types1:
for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
if hasWildcard:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
# to pick the order.
a1 = torsion[t2[1]]
a2 = torsion[t3[1]]
e1 = data.atoms[a1].element
e2 = data.atoms[a2].element
if e1 == e2 and a1 > a2:
(a1, a2) = (a2, a1)
elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
(a1, a2) = (a2, a1)
match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
else:
# There are no wildcards, so the order is unambiguous.
match = (torsion[0], torsion[t2[1]], torsion[t3[1]], torsion[t4[1]], tordef)
break
match = _matchImproper(data, torsion, self)
if match is not None:
(a1, a2, a3, a4, tordef) = match
force.addTorsion(a1, a2, a3, a4, tordef.c[0], tordef.c[1], tordef.c[2], tordef.c[3], tordef.c[4], tordef.c[5])
......@@ -2526,12 +2622,16 @@ parsers["CustomAngleForce"] = CustomAngleGenerator.parseElement
class CustomTorsion(object):
"""A CustomTorsion records the information for a custom torsion definition."""
def __init__(self, types, paramValues):
def __init__(self, types, paramValues, ordering='charmm'):
self.types1 = types[0]
self.types2 = types[1]
self.types3 = types[2]
self.types4 = types[3]
self.paramValues = paramValues
if ordering in ['default', 'charmm', 'amber']:
self.ordering = ordering
else:
raise ValueError('Illegal ordering type %s for CustomTorsion (%s,%s,%s,%s)' % (ordering, types[0], types[1], types[2], types[3]))
## @private
class CustomTorsionGenerator(object):
......@@ -2560,7 +2660,10 @@ class CustomTorsionGenerator(object):
for torsion in element.findall('Improper'):
types = ff._findAtomTypes(torsion.attrib, 4)
if None not in types:
generator.improper.append(CustomTorsion(types, [float(torsion.attrib[param]) for param in generator.perTorsionParams]))
if 'ordering' in element.attrib:
generator.improper.append(CustomTorsion(types, [float(torsion.attrib[param]) for param in generator.perTorsionParams], element.attrib['ordering']))
else:
generator.improper.append(CustomTorsion(types, [float(torsion.attrib[param]) for param in generator.perTorsionParams]))
def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
force = mm.CustomTorsionForce(self.energy)
......@@ -2590,40 +2693,7 @@ class CustomTorsionGenerator(object):
if match is not None:
force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.paramValues)
for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]]
type2 = data.atomType[data.atoms[torsion[1]]]
type3 = data.atomType[data.atoms[torsion[2]]]
type4 = data.atomType[data.atoms[torsion[3]]]
match = None
for tordef in self.improper:
types1 = tordef.types1
types2 = tordef.types2
types3 = tordef.types3
types4 = tordef.types4
hasWildcard = (wildcard in (types1, types2, types3, types4))
if match is not None and hasWildcard:
# Prefer specific definitions over ones with wildcards
continue
if type1 in types1:
for (t2, t3, t4) in itertools.permutations(((type2, 1), (type3, 2), (type4, 3))):
if t2[0] in types2 and t3[0] in types3 and t4[0] in types4:
if hasWildcard:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambiguous. It then follows some bizarre rules
# to pick the order.
a1 = torsion[t2[1]]
a2 = torsion[t3[1]]
e1 = data.atoms[a1].element
e2 = data.atoms[a2].element
if e1 == e2 and a1 > a2:
(a1, a2) = (a2, a1)
elif e1 != elem.carbon and (e2 == elem.carbon or e1.mass < e2.mass):
(a1, a2) = (a2, a1)
match = (a1, a2, torsion[0], torsion[t4[1]], tordef)
else:
# There are no wildcards, so the order is unambiguous.
match = (torsion[0], torsion[t2[1]], torsion[t3[1]], torsion[t4[1]], tordef)
break
match = _matchImproper(data, torsion, self)
if match is not None:
(a1, a2, a3, a4, tordef) = match
force.addTorsion(a1, a2, a3, a4, tordef.paramValues)
......@@ -3099,8 +3169,9 @@ class AmoebaBondGenerator(object):
bond.length = self.length[i]
if bond.isConstrained:
data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
if self.k[i] != 0:
if not bond.isConstrained or args.get('flexibleConstraints', False):
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
break
parsers["AmoebaBondForce"] = AmoebaBondGenerator.parseElement
......@@ -3227,6 +3298,8 @@ class AmoebaAngleGenerator(object):
force.setAmoebaGlobalAnglePentic(self.pentic)
force.setAmoebaGlobalAngleSextic(self.sextic)
DEG_TO_RAD = math.pi / 180
for angleDict in angleList:
angle = angleDict['angle']
isConstrained = angleDict['isConstrained']
......@@ -3241,8 +3314,8 @@ class AmoebaAngleGenerator(object):
if (type1 in types1 and type2 in types2 and type3 in types3) or (type1 in types3 and type2 in types2 and type3 in types1):
if isConstrained and self.k[i] != 0.0:
angleDict['idealAngle'] = self.angle[i][0]
addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys)
elif self.k[i] != 0:
addAngleConstraint(angle, self.angle[i][0]*DEG_TO_RAD, data, sys)
if self.k[i] != 0 and (not isConstrained or args.get('flexibleConstraints', False)):
lenAngle = len(self.angle[i])
if (lenAngle > 1):
# get k-index by counting number of non-angle hydrogens on the central atom
......@@ -3311,7 +3384,7 @@ class AmoebaAngleGenerator(object):
angleDict['idealAngle'] = self.angle[i][0]
if (isConstrained and self.k[i] != 0.0):
addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys)
else:
if self.k[i] != 0.0 and (not isConstrained or args.get('flexibleConstraints', False)):
force.addAngle(angle[0], angle[1], angle[2], angle[3], self.angle[i][0], self.k[i])
break
......@@ -3963,7 +4036,7 @@ class AmoebaTorsionTorsionGenerator(object):
# raise error if atoms E or F not found
if (atomE == -1 or atomF == -1):
outputString = "getChiralAtomIndex: error getting bonded partners of atomC=%s %d %s" % (atomC.name, atomC.resiude.index, atomC.resiude.name,)
outputString = "getChiralAtomIndex: error getting bonded partners of atomC=%s %d %s" % (atomC.name, atomC.residue.index, atomC.residue.name,)
raise ValueError(outputString)
# check for different type/mass between atoms E & F
......@@ -5370,7 +5443,7 @@ class AmoebaUreyBradleyGenerator(object):
force = existing[0]
for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
if (isConstrained):
if (isConstrained and not args.get('flexibleConstraints', False)):
continue
type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]]
......
......@@ -69,11 +69,14 @@ def _is_gro_coord(line):
@param[in] line The line to be tested
"""
sline = line.split()
if len(sline) == 6 or len(sline) == 9:
return all([_isint(sline[2]), _isfloat(sline[3]), _isfloat(sline[4]), _isfloat(sline[5])])
elif len(sline) == 5 or len(sline) == 8:
return all([_isint(line[15:20]), _isfloat(sline[2]), _isfloat(sline[3]), _isfloat(sline[4])])
# data lines are fixed field
fields = []
fields.append(line[16:20].strip()) # atom number
fields.append(line[21:28].strip()) # x coord
fields.append(line[29:36].strip()) # y coord
fields.append(line[37:44].strip()) # z coord
if (all([f != '' for f in fields])): # check for empty fields
return all([_isint(fields[0]), _isfloat(fields[1]), _isfloat(fields[2]), _isfloat(fields[3])])
else:
return 0
......
......@@ -258,11 +258,16 @@ class GromacsTopFile(object):
def _processDefaults(self, line):
"""Process the [ defaults ] line."""
fields = line.split()
if len(fields) < 4:
raise ValueError('Too few fields in [ defaults ] line: '+line)
if len(fields) < 5:
# fudgeLJ and fudgeQQ not specified, assumed 1.0 by default
if len(fields) == 3:
fields.append(1.0)
fields.append(1.0)
else:
raise ValueError('Too few fields in [ defaults ] line: '+line)
if fields[0] != '1':
raise ValueError('Unsupported nonbonded type: '+fields[0])
if fields[1] != '2':
if not (fields[1] == '2' or fields[1] == '1'):
raise ValueError('Unsupported combination rule: '+fields[1])
if fields[2].lower() == 'no':
self._genpairs = False
......@@ -570,7 +575,7 @@ class GromacsTopFile(object):
The solvent dielectric constant to use in the implicit solvent
model.
ewaldErrorTolerance : float=0.0005
The error tolerance to use if nonbondedMethod is Ewald, PME, or LJPME.
The error tolerance to use if nonbondedMethod is Ewald, PME or LJPME.
removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None
......@@ -593,6 +598,11 @@ class GromacsTopFile(object):
raise ValueError('Illegal nonbonded method for a non-periodic system')
nb = mm.NonbondedForce()
sys.addForce(nb)
if self._defaults[1] == '1':
lj = mm.CustomNonbondedForce('sqrt(A1*A2)/r^12-sqrt(C1*C2)/r^6')
lj.addPerParticleParameter('C')
lj.addPerParticleParameter('A')
sys.addForce(lj)
if implicitSolvent is OBC2:
gb = mm.GBSAOBCForce()
gb.setSoluteDielectric(soluteDielectric)
......@@ -610,8 +620,10 @@ class GromacsTopFile(object):
mapIndices = {}
bondIndices = []
topologyAtoms = list(self.topology.atoms())
exceptions = []
exclusions = []
pairs = []
fudgeQQ = float(self._defaults[4])
fudgeLJ = float(self._defaults[3])
# Build a lookup table to let us process dihedrals more quickly.
......@@ -748,7 +760,7 @@ class GromacsTopFile(object):
dihedralType = fields[4]
reversedTypes = types[::-1]+(dihedralType,)
types = types+(dihedralType,)
if (dihedralType in ('1', '2', '4', '9') and len(fields) > 7) or (dihedralType == '3' and len(fields) > 10):
if (dihedralType in ('1', '4', '9') and len(fields) > 7) or (dihedralType == '3' and len(fields) > 10) or (dihedralType == '2' and len(fields) > 6):
paramsList = [fields]
else:
# Look for a matching dihedral type.
......@@ -776,13 +788,16 @@ class GromacsTopFile(object):
elif dihedralType == '2':
# Harmonic torsion
k = float(params[6])
phi0 = float(params[5])
if k != 0:
if harmonicTorsion is None:
harmonicTorsion = mm.CustomTorsionForce('0.5*k*(theta-theta0)^2')
harmonicTorsion = mm.CustomTorsionForce('0.5*k*(thetap-theta0)^2; thetap = step(-(theta-theta0+pi))*2*pi+theta+step(theta-theta0-pi)*(-2*pi); pi = %.15g' % math.pi)
harmonicTorsion.addPerTorsionParameter('theta0')
harmonicTorsion.addPerTorsionParameter('k')
sys.addForce(harmonicTorsion)
harmonicTorsion.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], (float(params[5])*degToRad, k))
# map phi0 into correct space
phi0 = phi0 - 360 if phi0 > 180 else phi0
harmonicTorsion.addTorsion(baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], baseAtomIndex+atoms[2], baseAtomIndex+atoms[3], (phi0*degToRad, k))
else:
# RB Torsion
c = [float(x) for x in params[5:11]]
......@@ -825,11 +840,18 @@ class GromacsTopFile(object):
for fields in moleculeType.atoms:
params = self._atomTypes[fields[1]]
if len(fields) > 6:
q = float(fields[6])
else:
q = float(params[4])
nb.addParticle(q, float(params[6]), float(params[7]))
if self._defaults[1] == '1':
nb.addParticle(q, 1.0, 0.0)
lj.addParticle([float(params[6]), float(params[7])])
elif self._defaults[1] == '2':
nb.addParticle(q, float(params[6]), float(params[7]))
if implicitSolvent is OBC2:
if fields[1] not in self._implicitTypes:
raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1])
......@@ -857,19 +879,37 @@ class GromacsTopFile(object):
continue # We'll use the automatically generated parameters
atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0])
atom2params = nb.getParticleParameters(baseAtomIndex+atoms[1])
exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], atom1params[0]*atom2params[0]*fudgeQQ, params[0], params[1]))
pairs.append((baseAtomIndex+atoms[0], baseAtomIndex+atoms[1], atom1params[0]*atom2params[0]*fudgeQQ, params[0], params[1]))
for fields in moleculeType.exclusions:
atoms = [int(x)-1 for x in fields]
for atom in atoms[1:]:
if atom > atoms[0]:
exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0))
exclusions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom))
# Create nonbonded exceptions.
nb.createExceptionsFromBonds(bondIndices, fudgeQQ, float(self._defaults[3]))
for exception in exceptions:
nb.addException(exception[0], exception[1], exception[2], float(exception[3]), float(exception[4]), True)
nb.createExceptionsFromBonds(bondIndices, fudgeQQ, fudgeLJ)
for exclusion in exclusions:
nb.addException(exclusion[0], exclusion[1], 0.0, 1.0, 0.0, True)
if self._defaults[1] == '1':
# We're using a CustomNonbondedForce for LJ interactions, so also create a CustomBondForce
# to handle the exceptions.
pair_bond = mm.CustomBondForce('138.935456*q/r-C/r^6+A/r^12')
pair_bond.addPerBondParameter('q')
pair_bond.addPerBondParameter('C')
pair_bond.addPerBondParameter('A')
sys.addForce(pair_bond)
lj.createExclusionsFromBonds(bondIndices, 3)
for pair in pairs:
nb.addException(pair[0], pair[1], pair[2], 1.0, 0.0, True)
pair_bond.addBond(pair[0], pair[1], [pair[2],float(pair[3]), float(pair[4])])
for exclusion in exclusions:
lj.addExclusion(exclusion[0], exclusion[1])
elif self._defaults[1] == '2':
for pair in pairs:
nb.addException(pair[0], pair[1], pair[2], float(pair[3]), float(pair[4]), True)
# Finish configuring the NonbondedForce.
......@@ -882,6 +922,15 @@ class GromacsTopFile(object):
nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance)
if self._defaults[1] == '1':
methodMap = {ff.NoCutoff:mm.CustomNonbondedForce.NoCutoff,
ff.CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic,
ff.CutoffPeriodic:mm.CustomNonbondedForce.CutoffPeriodic,
ff.Ewald:mm.CustomNonbondedForce.CutoffPeriodic,
ff.PME:mm.CustomNonbondedForce.CutoffPeriodic,
ff.LJPME:mm.CustomNonbondedForce.CutoffPeriodic}
lj.setNonbondedMethod(methodMap[nonbondedMethod])
lj.setCutoffDistance(nonbondedCutoff)
# Adjust masses.
......
......@@ -525,7 +525,7 @@ class PrmtopLoader(object):
iScnb = float(self._raw_data["SCNB_SCALE_FACTOR"][iidx])
except KeyError:
iScnb = 2.0
returnList.append((iAtom, lAtom, chargeProd, rMin, epsilon, iScee, iScnb))
return returnList
......@@ -579,7 +579,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
soluteDielectric=1.0, solventDielectric=78.5,
implicitSolventKappa=0.0*(1/units.nanometer), nonbondedCutoff=None,
nonbondedMethod='NoCutoff', scee=None, scnb=None, mm=None, verbose=False,
EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True, elements=None):
EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True, elements=None,
gbsaModel='ACE'):
"""
Create an OpenMM System from an Amber prmtop file.
......@@ -603,6 +604,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
verbose (boolean) - if True, print out information on progress (default: False)
flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds
rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the shake argument
gbsaModel (str='ACE') The string representing the SA model to use for GB calculations. Must be 'ACE' or None
NOTES
......@@ -648,6 +650,9 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
warnings.warn("1-4 scaling parameters in topology file are being ignored. "
"This is not recommended unless you know what you are doing.")
if gbmodel is not None and gbsaModel not in ('ACE', None):
raise ValueError('gbsaModel must be ACE or None')
has_1264 = 'LENNARD_JONES_CCOEF' in prmtop._raw_data.keys()
if has_1264:
parm_ccoef = [float(x) for x in prmtop._raw_data['LENNARD_JONES_CCOEF']]
......@@ -974,20 +979,20 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
if units.is_quantity(cutoff):
cutoff = cutoff.value_in_unit(units.nanometers)
if gbmodel == 'HCT':
gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa)
gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC1':
gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa)
gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC2':
if implicitSolventKappa > 0:
gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa)
gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
else:
gb = mm.GBSAOBCForce()
gb.setSoluteDielectric(soluteDielectric)
gb.setSolventDielectric(solventDielectric)
elif gbmodel == 'GBn':
gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa)
gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'GBn2':
gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa)
gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
else:
raise ValueError("Illegal value specified for implicit solvent model")
if isinstance(gb, mm.GBSAOBCForce):
......@@ -1121,7 +1126,7 @@ class AmberAsciiRestart(object):
self.natom = int(words[0])
except (IndexError, ValueError):
raise TypeError('Unrecognized file type [%s]' % self.filename)
if len(words) >= 2:
self.time = float(words[1]) * units.picoseconds
......@@ -1290,7 +1295,7 @@ class AmberNetcdfRestart(object):
from scipy.io.netcdf import NetCDFFile
except ImportError:
raise ImportError('scipy is necessary to parse NetCDF restarts')
self.filename = filename
self.velocities = self.boxVectors = self.time = None
......
......@@ -123,6 +123,8 @@ class PDBFile(object):
# Try to guess the element.
upper = atomName.upper()
while len(upper) > 1 and upper[0].isdigit():
upper = upper[1:]
if upper.startswith('CL'):
element = elem.chlorine
elif upper.startswith('NA'):
......@@ -141,7 +143,7 @@ class PDBFile(object):
element = elem.calcium
else:
try:
element = elem.get_by_symbol(atomName[0])
element = elem.get_by_symbol(upper[0])
except KeyError:
pass
newAtom = top.addAtom(atomName, element, r, str(atom.serial_number))
......
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