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 ...@@ -40,22 +40,22 @@ CustomIntegratorProxy::CustomIntegratorProxy() : SerializationProxy("CustomInteg
} }
void CustomIntegratorProxy::serialize(const void* object, SerializationNode& node) const { 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); const CustomIntegrator& integrator = *reinterpret_cast<const CustomIntegrator*>(object);
SerializationNode& globalVariablesNode = node.createChildNode("GlobalVariables"); 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)); globalVariablesNode.setDoubleProperty(integrator.getGlobalVariableName(i), integrator.getGlobalVariable(i));
} }
SerializationNode& perDofVariablesNode = node.createChildNode("PerDofVariables"); 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)); SerializationNode& perDofValuesNode = perDofVariablesNode.createChildNode(integrator.getPerDofVariableName(i));
vector<Vec3> perDofValues; integrator.getPerDofVariable(i, perDofValues); 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]); perDofValuesNode.createChildNode("Value").setDoubleProperty("x",perDofValues[j][0]).setDoubleProperty("y",perDofValues[j][1]).setDoubleProperty("z",perDofValues[j][2]);
} }
} }
SerializationNode& computationsNode = node.createChildNode("Computations"); SerializationNode& computationsNode = node.createChildNode("Computations");
for(int i=0; i<integrator.getNumComputations(); i++) { for (int i = 0; i < integrator.getNumComputations(); i++) {
CustomIntegrator::ComputationType computationType; CustomIntegrator::ComputationType computationType;
string computationVariable; string computationVariable;
string computationExpression; string computationExpression;
...@@ -63,6 +63,9 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod ...@@ -63,6 +63,9 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod
computationsNode.createChildNode("Computation").setIntProperty("computationType",static_cast<int>(computationType)) computationsNode.createChildNode("Computation").setIntProperty("computationType",static_cast<int>(computationType))
.setStringProperty("computationVariable",computationVariable).setStringProperty("computationExpression",computationExpression); .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.setStringProperty("kineticEnergyExpression",integrator.getKineticEnergyExpression());
node.setIntProperty("randomSeed",integrator.getRandomNumberSeed()); node.setIntProperty("randomSeed",integrator.getRandomNumberSeed());
node.setDoubleProperty("stepSize",integrator.getStepSize()); node.setDoubleProperty("stepSize",integrator.getStepSize());
...@@ -70,7 +73,8 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod ...@@ -70,7 +73,8 @@ void CustomIntegratorProxy::serialize(const void* object, SerializationNode& nod
} }
void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const { 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"); throw OpenMMException("Unsupported version number");
CustomIntegrator* integrator = new CustomIntegrator(node.getDoubleProperty("stepSize")); CustomIntegrator* integrator = new CustomIntegrator(node.getDoubleProperty("stepSize"));
const SerializationNode& globalVariablesNode = node.getChildNode("GlobalVariables"); const SerializationNode& globalVariablesNode = node.getChildNode("GlobalVariables");
...@@ -112,6 +116,11 @@ void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const { ...@@ -112,6 +116,11 @@ void* CustomIntegratorProxy::deserialize(const SerializationNode& node) const {
throw(OpenMMException("Custom Integrator Deserialization: Unknown computation type")); 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->setKineticEnergyExpression(node.getStringProperty("kineticEnergyExpression"));
integrator->setRandomNumberSeed(node.getIntProperty("randomSeed")); integrator->setRandomNumberSeed(node.getIntProperty("randomSeed"));
integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance")); integrator->setConstraintTolerance(node.getDoubleProperty("constraintTolerance"));
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include "openmm/CustomBondForce.h" #include "openmm/CustomBondForce.h"
#include "openmm/CustomCentroidBondForce.h" #include "openmm/CustomCentroidBondForce.h"
#include "openmm/CustomCompoundBondForce.h" #include "openmm/CustomCompoundBondForce.h"
#include "openmm/CustomCVForce.h"
#include "openmm/CustomExternalForce.h" #include "openmm/CustomExternalForce.h"
#include "openmm/CustomGBForce.h" #include "openmm/CustomGBForce.h"
#include "openmm/CustomHbondForce.h" #include "openmm/CustomHbondForce.h"
...@@ -72,6 +73,7 @@ ...@@ -72,6 +73,7 @@
#include "openmm/serialization/CustomBondForceProxy.h" #include "openmm/serialization/CustomBondForceProxy.h"
#include "openmm/serialization/CustomCentroidBondForceProxy.h" #include "openmm/serialization/CustomCentroidBondForceProxy.h"
#include "openmm/serialization/CustomCompoundBondForceProxy.h" #include "openmm/serialization/CustomCompoundBondForceProxy.h"
#include "openmm/serialization/CustomCVForceProxy.h"
#include "openmm/serialization/CustomExternalForceProxy.h" #include "openmm/serialization/CustomExternalForceProxy.h"
#include "openmm/serialization/CustomGBForceProxy.h" #include "openmm/serialization/CustomGBForceProxy.h"
#include "openmm/serialization/CustomHbondForceProxy.h" #include "openmm/serialization/CustomHbondForceProxy.h"
...@@ -124,6 +126,7 @@ extern "C" void registerSerializationProxies() { ...@@ -124,6 +126,7 @@ extern "C" void registerSerializationProxies() {
SerializationProxy::registerProxy(typeid(CustomBondForce), new CustomBondForceProxy()); SerializationProxy::registerProxy(typeid(CustomBondForce), new CustomBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCentroidBondForce), new CustomCentroidBondForceProxy()); SerializationProxy::registerProxy(typeid(CustomCentroidBondForce), new CustomCentroidBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCompoundBondForce), new CustomCompoundBondForceProxy()); SerializationProxy::registerProxy(typeid(CustomCompoundBondForce), new CustomCompoundBondForceProxy());
SerializationProxy::registerProxy(typeid(CustomCVForce), new CustomCVForceProxy());
SerializationProxy::registerProxy(typeid(CustomExternalForce), new CustomExternalForceProxy()); SerializationProxy::registerProxy(typeid(CustomExternalForce), new CustomExternalForceProxy());
SerializationProxy::registerProxy(typeid(CustomGBForce), new CustomGBForceProxy()); SerializationProxy::registerProxy(typeid(CustomGBForce), new CustomGBForceProxy());
SerializationProxy::registerProxy(typeid(CustomHbondForce), new CustomHbondForceProxy()); SerializationProxy::registerProxy(typeid(CustomHbondForce), new CustomHbondForceProxy());
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for * * Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. * * 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 * * Authors: Peter Eastman *
* Contributors: * * Contributors: *
* * * *
...@@ -71,15 +71,23 @@ void SystemProxy::serialize(const void* object, SerializationNode& node) const { ...@@ -71,15 +71,23 @@ void SystemProxy::serialize(const void* object, SerializationNode& node) const {
} }
else if (typeid(system.getVirtualSite(i)) == typeid(LocalCoordinatesSite)) { else if (typeid(system.getVirtualSite(i)) == typeid(LocalCoordinatesSite)) {
const LocalCoordinatesSite& site = dynamic_cast<const LocalCoordinatesSite&>(system.getVirtualSite(i)); const LocalCoordinatesSite& site = dynamic_cast<const LocalCoordinatesSite&>(system.getVirtualSite(i));
Vec3 wo = site.getOriginWeights(); int numParticles = site.getNumParticles();
Vec3 wx = site.getXWeights(); vector<double> wo, wx, wy;
Vec3 wy = site.getYWeights(); site.getOriginWeights(wo);
site.getXWeights(wx);
site.getYWeights(wy);
Vec3 p = site.getLocalPosition(); Vec3 p = site.getLocalPosition();
particle.createChildNode("LocalCoordinatesSite").setIntProperty("p1", site.getParticle(0)).setIntProperty("p2", site.getParticle(1)).setIntProperty("p3", site.getParticle(2)). SerializationNode& siteNode = particle.createChildNode("LocalCoordinatesSite");
setDoubleProperty("wo1", wo[0]).setDoubleProperty("wo2", wo[1]).setDoubleProperty("wo3", wo[2]). siteNode.setDoubleProperty("pos1", p[0]).setDoubleProperty("pos2", p[1]).setDoubleProperty("pos3", p[2]);
setDoubleProperty("wx1", wx[0]).setDoubleProperty("wx2", wx[1]).setDoubleProperty("wx3", wx[2]). for (int j = 0; j < numParticles; j++) {
setDoubleProperty("wy1", wy[0]).setDoubleProperty("wy2", wy[1]).setDoubleProperty("wy3", wy[2]). stringstream ss;
setDoubleProperty("pos1", p[0]).setDoubleProperty("pos2", p[1]).setDoubleProperty("pos3", p[2]); 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 { ...@@ -120,11 +128,21 @@ void* SystemProxy::deserialize(const SerializationNode& node) const {
else if (vsite.getName() == "OutOfPlaneSite") 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"))); 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") { else if (vsite.getName() == "LocalCoordinatesSite") {
Vec3 wo(vsite.getDoubleProperty("wo1"), vsite.getDoubleProperty("wo2"), vsite.getDoubleProperty("wo3")); vector<int> particles;
Vec3 wx(vsite.getDoubleProperty("wx1"), vsite.getDoubleProperty("wx2"), vsite.getDoubleProperty("wx3")); vector<double> wo, wx, wy;
Vec3 wy(vsite.getDoubleProperty("wy1"), vsite.getDoubleProperty("wy2"), vsite.getDoubleProperty("wy3")); 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")); 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() { ...@@ -156,6 +156,10 @@ void testSerializeCustomIntegrator() {
intg->addComputeSum("summand2", "v*v+f*f"); intg->addComputeSum("summand2", "v*v+f*f");
intg->setConstraintTolerance(1e-5); intg->setConstraintTolerance(1e-5);
intg->setKineticEnergyExpression("m*v1*v1/2; v1=v+0.5*dt*f/m"); 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; stringstream ss;
XmlSerializer::serialize<Integrator>(intg, "CustomIntegrator", ss); XmlSerializer::serialize<Integrator>(intg, "CustomIntegrator", ss);
CustomIntegrator *intg2 = dynamic_cast<CustomIntegrator*>(XmlSerializer::deserialize<Integrator>(ss)); CustomIntegrator *intg2 = dynamic_cast<CustomIntegrator*>(XmlSerializer::deserialize<Integrator>(ss));
...@@ -190,6 +194,19 @@ void testSerializeCustomIntegrator() { ...@@ -190,6 +194,19 @@ void testSerializeCustomIntegrator() {
ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed()); ASSERT_EQUAL(intg->getRandomNumberSeed(), intg2->getRandomNumberSeed());
ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize()); ASSERT_EQUAL(intg->getStepSize(), intg2->getStepSize());
ASSERT_EQUAL(intg->getConstraintTolerance(), intg2->getConstraintTolerance()); 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 intg;
delete intg2; delete intg2;
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for * * Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. * * 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 * * Authors: Peter Eastman *
* Contributors: * * Contributors: *
* * * *
...@@ -82,6 +82,23 @@ void compareSystems(System& system, System& system2) { ...@@ -82,6 +82,23 @@ void compareSystems(System& system, System& system2) {
ASSERT_EQUAL(0.1, site7.getWeight12()); ASSERT_EQUAL(0.1, site7.getWeight12());
ASSERT_EQUAL(0.2, site7.getWeight13()); ASSERT_EQUAL(0.2, site7.getWeight13());
ASSERT_EQUAL(0.5, site7.getWeightCross()); 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()); ASSERT_EQUAL(system.getNumForces(), system2.getNumForces());
for (int i = 0; i < system.getNumForces(); i++) for (int i = 0; i < system.getNumForces(); i++)
ASSERT(typeid(system.getForce(i)) == typeid(system2.getForce(i))) ASSERT(typeid(system.getForce(i)) == typeid(system2.getForce(i)))
...@@ -93,7 +110,7 @@ void testSerialization() { ...@@ -93,7 +110,7 @@ void testSerialization() {
System system; System system;
for (int i = 0; i < 5; i++) for (int i = 0; i < 5; i++)
system.addParticle(0.1*i+1); 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.addParticle(0.0);
system.addConstraint(0, 1, 3.0); system.addConstraint(0, 1, 3.0);
system.addConstraint(1, 2, 2.5); system.addConstraint(1, 2, 2.5);
...@@ -102,6 +119,7 @@ void testSerialization() { ...@@ -102,6 +119,7 @@ void testSerialization() {
system.setVirtualSite(5, new TwoParticleAverageSite(0, 1, 0.3, 0.7)); 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(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(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()); system.addForce(new HarmonicBondForce());
// Serialize and then deserialize it, then make sure the systems are identical. // 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 ...@@ -118,6 +118,7 @@ void testOBC(GBSAOBCForce::NonbondedMethod obcMethod, CustomGBForce::NonbondedMe
params[1] = 0.1; params[1] = 0.1;
custom->addParticle(params); 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] = 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]); 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)); velocities[2*i] = Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt));
......
...@@ -244,6 +244,33 @@ void testIllegalVariable() { ...@@ -244,6 +244,33 @@ void testIllegalVariable() {
ASSERT(threwException); 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(); void runPlatformTests();
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
...@@ -254,6 +281,7 @@ int main(int argc, char* argv[]) { ...@@ -254,6 +281,7 @@ int main(int argc, char* argv[]) {
testCutoff(); testCutoff();
testCustomFunctions(); testCustomFunctions();
testIllegalVariable(); testIllegalVariable();
testParameters();
runPlatformTests(); runPlatformTests();
} }
catch(const exception& e) { catch(const exception& e) {
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for * * Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. * * 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 * * Authors: Peter Eastman *
* Contributors: * * Contributors: *
* * * *
...@@ -37,8 +37,10 @@ ...@@ -37,8 +37,10 @@
#include "openmm/AndersenThermostat.h" #include "openmm/AndersenThermostat.h"
#include "openmm/CustomAngleForce.h" #include "openmm/CustomAngleForce.h"
#include "openmm/CustomBondForce.h" #include "openmm/CustomBondForce.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/CustomIntegrator.h" #include "openmm/CustomIntegrator.h"
#include "openmm/HarmonicBondForce.h" #include "openmm/HarmonicBondForce.h"
#include "openmm/MonteCarloBarostat.h"
#include "openmm/NonbondedForce.h" #include "openmm/NonbondedForce.h"
#include "openmm/System.h" #include "openmm/System.h"
#include "SimTKOpenMMRealType.h" #include "SimTKOpenMMRealType.h"
...@@ -895,6 +897,128 @@ void testChangeDT() { ...@@ -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(); void runPlatformTests();
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
...@@ -917,6 +1041,9 @@ int main(int argc, char* argv[]) { ...@@ -917,6 +1041,9 @@ int main(int argc, char* argv[]) {
testChangingGlobal(); testChangingGlobal();
testEnergyParameterDerivatives(); testEnergyParameterDerivatives();
testChangeDT(); testChangeDT();
testTabulatedFunction();
testAlternatingGroups();
testUpdateContextState();
runPlatformTests(); runPlatformTests();
} }
catch(const exception& e) { catch(const exception& e) {
......
...@@ -206,30 +206,51 @@ void testOutOfPlane() { ...@@ -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. * Test a LocalCoordinatesSite virtual site.
*/ */
void testLocalCoordinates() { void testLocalCoordinates(int numSiteParticles) {
const Vec3 originWeights(0.2, 0.3, 0.5); vector<int> particles;
const Vec3 xWeights(-1.0, 0.5, 0.5); vector<double> originWeights, xWeights, yWeights;
const Vec3 yWeights(0.0, -1.0, 1.0); 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); const Vec3 localPosition(0.4, 0.3, 0.2);
System system; System system;
system.addParticle(1.0); for (int i = 0; i < numSiteParticles; i++)
system.addParticle(1.0); system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(0.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"); CustomExternalForce* forceField = new CustomExternalForce("2*x^2+3*y^2+4*z^2");
system.addForce(forceField); system.addForce(forceField);
vector<double> params; vector<double> params;
forceField->addParticle(0, params); for (int i = 0; i < numSiteParticles+1; i++)
forceField->addParticle(1, params); forceField->addParticle(0, params);
forceField->addParticle(2, params);
forceField->addParticle(3, params);
LangevinIntegrator integrator(300.0, 0.1, 0.002); LangevinIntegrator integrator(300.0, 0.1, 0.002);
Context context(system, integrator, platform); 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; OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt); init_gen_rand(0, sfmt);
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
...@@ -237,12 +258,12 @@ void testLocalCoordinates() { ...@@ -237,12 +258,12 @@ void testLocalCoordinates() {
Vec3 xdir, ydir, zdir; Vec3 xdir, ydir, zdir;
do { 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)); 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]; xdir = computeWeightedPosition(positions, xWeights);
ydir = positions[0]*yWeights[0] + positions[1]*yWeights[1] + positions[2]*yWeights[2]; ydir = computeWeightedPosition(positions, yWeights);;
zdir = xdir.cross(ydir); 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. break; // These positions give a reasonable coordinate system.
} while (true); } while (true);
context.setPositions(positions); context.setPositions(positions);
...@@ -252,23 +273,23 @@ void testLocalCoordinates() { ...@@ -252,23 +273,23 @@ void testLocalCoordinates() {
State state = context.getState(State::Positions | State::Forces); State state = context.getState(State::Positions | State::Forces);
const vector<Vec3>& pos = state.getPositions(); 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)); xdir /= sqrt(xdir.dot(xdir));
zdir /= sqrt(zdir.dot(zdir)); zdir /= sqrt(zdir.dot(zdir));
ydir = zdir.cross(xdir); 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. // 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; double norm = 0.0;
for (int i = 0; i < 3; ++i) { for (int i = 0; i < numSiteParticles; ++i) {
Vec3 f = state.getForces()[i]; Vec3 f = state.getForces()[i];
norm += f[0]*f[0] + f[1]*f[1] + f[2]*f[2]; norm += f[0]*f[0] + f[1]*f[1] + f[2]*f[2];
} }
norm = std::sqrt(norm); norm = std::sqrt(norm);
const double delta = 1e-2; const double delta = 1e-2;
double step = 0.5*delta/norm; 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 p = positions[i];
Vec3 f = state.getForces()[i]; Vec3 f = state.getForces()[i];
positions2[i] = Vec3(p[0]-f[0]*step, p[1]-f[1]*step, p[2]-f[2]*step); 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[]) { ...@@ -469,7 +490,9 @@ int main(int argc, char* argv[]) {
testTwoParticleAverage(); testTwoParticleAverage();
testThreeParticleAverage(); testThreeParticleAverage();
testOutOfPlane(); testOutOfPlane();
testLocalCoordinates(); testLocalCoordinates(2);
testLocalCoordinates(3);
testLocalCoordinates(4);
testConservationLaws(); testConservationLaws();
testOverlappingSites(); testOverlappingSites();
runPlatformTests(); runPlatformTests();
......
...@@ -68,7 +68,15 @@ class WrapperGenerator: ...@@ -68,7 +68,15 @@ class WrapperGenerator:
def __init__(self, inputDirname, output): def __init__(self, inputDirname, output):
self.skipClasses = ['OpenMM::Vec3', 'OpenMM::XmlSerializer', 'OpenMM::Kernel', 'OpenMM::KernelImpl', 'OpenMM::KernelFactory', 'OpenMM::ContextImpl', 'OpenMM::SerializationNode', 'OpenMM::SerializationProxy'] 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.hideClasses = ['Kernel', 'KernelImpl', 'KernelFactory', 'ContextImpl', 'SerializationNode', 'SerializationProxy']
self.nodeByID={} self.nodeByID={}
...@@ -119,9 +127,7 @@ class WrapperGenerator: ...@@ -119,9 +127,7 @@ class WrapperGenerator:
for section in findNodes(classNode, "sectiondef", kind="public-static-func")+findNodes(classNode, "sectiondef", kind="public-func"): 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"): for memberNode in findNodes(section, "memberdef", kind="function", prot="public"):
methodDefinition = getText("definition", memberNode) methodDefinition = getText("definition", memberNode)
shortMethodDefinition = stripOpenMMPrefix(methodDefinition) if methodDefinition in self.skipMethods:
methodName = shortMethodDefinition.split()[-1]
if className+'::'+methodName in self.skipMethods:
continue continue
methodList.append(memberNode) methodList.append(memberNode)
return methodList return methodList
......
...@@ -161,7 +161,7 @@ class AmberPrmtopFile(object): ...@@ -161,7 +161,7 @@ class AmberPrmtopFile(object):
implicitSolventKappa=None, temperature=298.15*u.kelvin, implicitSolventKappa=None, temperature=298.15*u.kelvin,
soluteDielectric=1.0, solventDielectric=78.5, soluteDielectric=1.0, solventDielectric=78.5,
removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005, 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 """Construct an OpenMM System representing the topology described by this
prmtop file. prmtop file.
...@@ -208,6 +208,11 @@ class AmberPrmtopFile(object): ...@@ -208,6 +208,11 @@ class AmberPrmtopFile(object):
turned on for Lennard-Jones interactions. If the switchDistance is 0 turned on for Lennard-Jones interactions. If the switchDistance is 0
or evaluates to boolean False, no switching function will be used. or evaluates to boolean False, no switching function will be used.
Values greater than nonbondedCutoff or less than 0 raise ValueError 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 Returns
------- -------
...@@ -273,7 +278,7 @@ class AmberPrmtopFile(object): ...@@ -273,7 +278,7 @@ class AmberPrmtopFile(object):
nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod], nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod],
flexibleConstraints=False, gbmodel=implicitString, soluteDielectric=soluteDielectric, flexibleConstraints=False, gbmodel=implicitString, soluteDielectric=soluteDielectric,
solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa, solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa,
rigidWater=rigidWater, elements=self.elements) rigidWater=rigidWater, elements=self.elements, gbsaModel=gbsaModel)
if hydrogenMass is not None: if hydrogenMass is not None:
for atom1, atom2 in self.topology.bonds(): for atom1, atom2 in self.topology.bonds():
......
...@@ -117,22 +117,23 @@ class CharmmPsfFile(object): ...@@ -117,22 +117,23 @@ class CharmmPsfFile(object):
This structure has numerous attributes that are lists of the elements of This structure has numerous attributes that are lists of the elements of
this structure, including atoms, bonds, torsions, etc. The attributes are this structure, including atoms, bonds, torsions, etc. The attributes are
- residue_list
- atom_list - residue_list
- bond_list - atom_list
- angle_list - bond_list
- dihedral_list - angle_list
- dihedral_parameter_list - dihedral_list
- improper_list - dihedral_parameter_list
- cmap_list - improper_list
- donor_list # hbonds donors? - cmap_list
- acceptor_list # hbond acceptors? - donor_list # hbonds donors?
- group_list # list of nonbonded interaction groups - acceptor_list # hbond acceptors?
- group_list # list of nonbonded interaction groups
Additional attribute is available if a CharmmParameterSet is loaded into Additional attribute is available if a CharmmParameterSet is loaded into
this structure. this structure.
- urey_bradley_list - urey_bradley_list
The lengths of each of these lists gives the pointers (e.g., natom, nres, The lengths of each of these lists gives the pointers (e.g., natom, nres,
etc.) etc.)
...@@ -678,7 +679,8 @@ class CharmmPsfFile(object): ...@@ -678,7 +679,8 @@ class CharmmPsfFile(object):
hydrogenMass=None, hydrogenMass=None,
ewaldErrorTolerance=0.0005, ewaldErrorTolerance=0.0005,
flexibleConstraints=True, flexibleConstraints=True,
verbose=False): verbose=False,
gbsaModel=None):
"""Construct an OpenMM System representing the topology described by the """Construct an OpenMM System representing the topology described by the
prmtop file. You MUST have loaded a parameter set into this PSF before prmtop file. You MUST have loaded a parameter set into this PSF before
calling createSystem. If not, AttributeError will be raised. ValueError calling createSystem. If not, AttributeError will be raised. ValueError
...@@ -712,7 +714,7 @@ class CharmmPsfFile(object): ...@@ -712,7 +714,7 @@ class CharmmPsfFile(object):
solvent. solvent.
implicitSolventSaltConc : float=0.0*u.moles/u.liter implicitSolventSaltConc : float=0.0*u.moles/u.liter
Salt concentration for GB simulations. Converted to Debye length Salt concentration for GB simulations. Converted to Debye length
`kappa' ``kappa``
temperature : float=298.15*u.kelvin temperature : float=298.15*u.kelvin
Temperature used in the salt concentration-to-kappa conversion for Temperature used in the salt concentration-to-kappa conversion for
GB salt concentration term GB salt concentration term
...@@ -733,10 +735,16 @@ class CharmmPsfFile(object): ...@@ -733,10 +735,16 @@ class CharmmPsfFile(object):
Are our constraints flexible or not? Are our constraints flexible or not?
verbose : bool=False verbose : bool=False
Optionally prints out a running progress report 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 # Load the parameter set
self.loadParameters(params.condense()) self.loadParameters(params.condense())
hasbox = self.topology.getUnitCellDimensions() is not None 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 # Set the cutoff distance in nanometers
cutoff = None cutoff = None
if nonbondedMethod is not ff.NoCutoff: if nonbondedMethod is not ff.NoCutoff:
...@@ -906,8 +914,10 @@ class CharmmPsfFile(object): ...@@ -906,8 +914,10 @@ class CharmmPsfFile(object):
if verbose: print('Adding impropers...') if verbose: print('Adding impropers...')
# Ick. OpenMM does not have an improper torsion class. Need to # Ick. OpenMM does not have an improper torsion class. Need to
# construct one from CustomTorsionForce # construct one from CustomTorsionForce that respects toroidal boundaries
force = mm.CustomTorsionForce('k*(theta-theta0)^2') 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('k')
force.addPerTorsionParameter('theta0') force.addPerTorsionParameter('theta0')
force.setForceGroup(self.IMPROPER_FORCE_GROUP) force.setForceGroup(self.IMPROPER_FORCE_GROUP)
...@@ -1189,19 +1199,19 @@ class CharmmPsfFile(object): ...@@ -1189,19 +1199,19 @@ class CharmmPsfFile(object):
implicitSolventKappa = implicitSolventKappa.value_in_unit( implicitSolventKappa = implicitSolventKappa.value_in_unit(
(1.0/u.nanometer).unit) (1.0/u.nanometer).unit)
if implicitSolvent is HCT: if implicitSolvent is HCT:
gb = GBSAHCTForce(solventDielectric, soluteDielectric, None, gb = GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC1: elif implicitSolvent is OBC1:
gb = GBSAOBC1Force(solventDielectric, soluteDielectric, None, gb = GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC2: elif implicitSolvent is OBC2:
gb = GBSAOBC2Force(solventDielectric, soluteDielectric, None, gb = GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn: elif implicitSolvent is GBn:
gb = GBSAGBnForce(solventDielectric, soluteDielectric, None, gb = GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn2: elif implicitSolvent is GBn2:
gb = GBSAGBn2Force(solventDielectric, soluteDielectric, None, gb = GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
gb_parms = gb.getStandardParameters(self.topology) gb_parms = gb.getStandardParameters(self.topology)
for atom, gb_parm in zip(self.atom_list, gb_parms): for atom, gb_parm in zip(self.atom_list, gb_parms):
......
...@@ -57,7 +57,7 @@ class DCDReporter(object): ...@@ -57,7 +57,7 @@ class DCDReporter(object):
self._reportInterval = reportInterval self._reportInterval = reportInterval
self._append = append self._append = append
if append: if append:
mode = 'a+b' mode = 'r+b'
else: else:
mode = 'wb' mode = 'wb'
self._out = open(file, mode) self._out = open(file, mode)
......
...@@ -47,6 +47,16 @@ from . import element as elem ...@@ -47,6 +47,16 @@ from . import element as elem
from simtk.openmm.app import Topology from simtk.openmm.app import Topology
from simtk.openmm.app.internal.singleton import Singleton 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): def _convertParameterToNumber(param):
if unit.is_quantity(param): if unit.is_quantity(param):
if param.unit.is_compatible(unit.bar): if param.unit.is_compatible(unit.bar):
...@@ -186,11 +196,16 @@ class ForceField(object): ...@@ -186,11 +196,16 @@ class ForceField(object):
trees = [] trees = []
for file in files: for file in files:
tree = None
try: try:
# this handles either filenames or open file-like objects # this handles either filenames or open file-like objects
tree = etree.parse(file) tree = etree.parse(file)
except IOError: 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: except Exception as e:
# Fail with an error message about which file could not be read. # Fail with an error message about which file could not be read.
# TODO: Also handle case where fallback to 'data' directory encounters problems, # TODO: Also handle case where fallback to 'data' directory encounters problems,
...@@ -202,9 +217,24 @@ class ForceField(object): ...@@ -202,9 +217,24 @@ class ForceField(object):
filename = str(file) filename = str(file)
msg += "ForceField.loadFile() encountered an error reading file '%s'\n" % filename msg += "ForceField.loadFile() encountered an error reading file '%s'\n" % filename
raise Exception(msg) raise Exception(msg)
if tree is None:
raise ValueError('Could not locate file "%s"' % file)
trees.append(tree) 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. # Load the atom types.
...@@ -415,17 +445,19 @@ class ForceField(object): ...@@ -415,17 +445,19 @@ class ForceField(object):
generator : function generator : function
A function that will be called when a residue is encountered that does not match an existing forcefield template. 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) 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 forcefield : simtk.openmm.app.ForceField
The ForceField object to which residue templates and/or parameters are to be added. The ForceField object to which residue templates and/or parameters are to be added.
...@@ -508,6 +540,7 @@ class ForceField(object): ...@@ -508,6 +540,7 @@ class ForceField(object):
def __init__(self): def __init__(self):
self.atomType = {} self.atomType = {}
self.atomParameters = {} self.atomParameters = {}
self.atomTemplateIndexes = {}
self.atoms = [] self.atoms = []
self.excludeAtomWith = [] self.excludeAtomWith = []
self.virtualSites = {} self.virtualSites = {}
...@@ -535,6 +568,7 @@ class ForceField(object): ...@@ -535,6 +568,7 @@ class ForceField(object):
for atom, match in zip(residue.atoms(), matches): for atom, match in zip(residue.atoms(), matches):
self.atomType[atom] = template.atoms[match].type self.atomType[atom] = template.atoms[match].type
self.atomParameters[atom] = template.atoms[match].parameters self.atomParameters[atom] = template.atoms[match].parameters
self.atomTemplateIndexes[atom] = match
for site in template.virtualSites: for site in template.virtualSites:
if match == site.index: if match == site.index:
self.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index) self.virtualSites[atom] = (site, [matchAtoms[i].index for i in site.atoms], matchAtoms[site.excludeWith].index)
...@@ -615,10 +649,15 @@ class ForceField(object): ...@@ -615,10 +649,15 @@ class ForceField(object):
numAtoms = 3 numAtoms = 3
self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])] self.weights = [float(attrib['weight12']), float(attrib['weight13']), float(attrib['weightCross'])]
elif self.type == 'localCoords': elif self.type == 'localCoords':
numAtoms = 3 numAtoms = 0
self.originWeights = [float(attrib['wo1']), float(attrib['wo2']), float(attrib['wo3'])] self.originWeights = []
self.xWeights = [float(attrib['wx1']), float(attrib['wx2']), float(attrib['wx3'])] self.xWeights = []
self.yWeights = [float(attrib['wy1']), float(attrib['wy2']), float(attrib['wy3'])] 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'])] self.localPos = [float(attrib['p1']), float(attrib['p2']), float(attrib['p3'])]
else: else:
raise ValueError('Unknown virtual site type: %s' % self.type) raise ValueError('Unknown virtual site type: %s' % self.type)
...@@ -1003,6 +1042,8 @@ class ForceField(object): ...@@ -1003,6 +1042,8 @@ class ForceField(object):
not terminated properly. This option can create ambiguities where multiple not terminated properly. This option can create ambiguities where multiple
templates match the same residue. If that happens, use the residueTemplates templates match the same residue. If that happens, use the residueTemplates
argument to specify which one to use. 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 args
Arbitrary additional keyword arguments may also be specified. Arbitrary additional keyword arguments may also be specified.
This allows extra parameters to be specified that are specific to This allows extra parameters to be specified that are specific to
...@@ -1108,9 +1149,9 @@ class ForceField(object): ...@@ -1108,9 +1149,9 @@ class ForceField(object):
if not unit.is_quantity(hydrogenMass): if not unit.is_quantity(hydrogenMass):
hydrogenMass *= unit.dalton hydrogenMass *= unit.dalton
for atom1, atom2 in topology.bonds(): for atom1, atom2 in topology.bonds():
if atom1.element == elem.hydrogen: if atom1.element is elem.hydrogen:
(atom1, atom2) = (atom2, atom1) (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) transferMass = hydrogenMass-sys.getParticleMass(atom2.index)
sys.setParticleMass(atom2.index, hydrogenMass) sys.setParticleMass(atom2.index, hydrogenMass)
sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass) sys.setParticleMass(atom1.index, sys.getParticleMass(atom1.index)-transferMass)
...@@ -1176,7 +1217,7 @@ class ForceField(object): ...@@ -1176,7 +1217,7 @@ class ForceField(object):
for bond in data.bonds: for bond in data.bonds:
atom1 = data.atoms[bond.atom1] atom1 = data.atoms[bond.atom1]
atom2 = data.atoms[bond.atom2] 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: if rigidWater:
for bond in data.bonds: for bond in data.bonds:
atom1 = data.atoms[bond.atom1] atom1 = data.atoms[bond.atom1]
...@@ -1192,11 +1233,11 @@ class ForceField(object): ...@@ -1192,11 +1233,11 @@ class ForceField(object):
atom2 = data.atoms[angle[1]] atom2 = data.atoms[angle[1]]
atom3 = data.atoms[angle[2]] atom3 = data.atoms[angle[2]]
numH = 0 numH = 0
if atom1.name.startswith('H'): if atom1.element is elem.hydrogen:
numH += 1 numH += 1
if atom3.name.startswith('H'): if atom3.element is elem.hydrogen:
numH += 1 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: else:
data.isAngleConstrained = len(data.angles)*[False] data.isAngleConstrained = len(data.angles)*[False]
if rigidWater: if rigidWater:
...@@ -1221,11 +1262,7 @@ class ForceField(object): ...@@ -1221,11 +1262,7 @@ class ForceField(object):
elif site.type == 'outOfPlane': elif site.type == 'outOfPlane':
sys.setVirtualSite(index, mm.OutOfPlaneSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2])) sys.setVirtualSite(index, mm.OutOfPlaneSite(atoms[0], atoms[1], atoms[2], site.weights[0], site.weights[1], site.weights[2]))
elif site.type == 'localCoords': elif site.type == 'localCoords':
sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms[0], atoms[1], atoms[2], sys.setVirtualSite(index, mm.LocalCoordinatesSite(atoms, site.originWeights, site.xWeights, site.yWeights, site.localPos))
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])))
# Add forces to the System # Add forces to the System
...@@ -1704,6 +1741,108 @@ def _createResidueTemplate(residue): ...@@ -1704,6 +1741,108 @@ def _createResidueTemplate(residue):
template.addExternalBondByName(atom2.name) template.addExternalBondByName(atom2.name)
return template 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 # 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, # 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 # 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): ...@@ -1763,8 +1902,11 @@ class HarmonicBondGenerator(object):
bond.length = self.length[i] bond.length = self.length[i]
if bond.isConstrained: if bond.isConstrained:
data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i]) data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0: if self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i]) # 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 break
parsers["HarmonicBondForce"] = HarmonicBondGenerator.parseElement parsers["HarmonicBondForce"] = HarmonicBondGenerator.parseElement
...@@ -1844,8 +1986,9 @@ class HarmonicAngleGenerator(object): ...@@ -1844,8 +1986,9 @@ class HarmonicAngleGenerator(object):
if l1 is not None and l2 is not None: if l1 is not None and l2 is not None:
length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i])) length = sqrt(l1*l1 + l2*l2 - 2*l1*l2*cos(self.angle[i]))
data.addConstraint(sys, angle[0], angle[2], length) data.addConstraint(sys, angle[0], angle[2], length)
elif self.k[i] != 0: if self.k[i] != 0:
force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i]) if not isConstrained or args.get('flexibleConstraints', False):
force.addAngle(angle[0], angle[1], angle[2], self.angle[i], self.k[i])
break break
parsers["HarmonicAngleForce"] = HarmonicAngleGenerator.parseElement parsers["HarmonicAngleForce"] = HarmonicAngleGenerator.parseElement
...@@ -1863,6 +2006,7 @@ class PeriodicTorsion(object): ...@@ -1863,6 +2006,7 @@ class PeriodicTorsion(object):
self.periodicity = [] self.periodicity = []
self.phase = [] self.phase = []
self.k = [] self.k = []
self.ordering = 'default'
## @private ## @private
class PeriodicTorsionGenerator(object): class PeriodicTorsionGenerator(object):
...@@ -1878,9 +2022,13 @@ class PeriodicTorsionGenerator(object): ...@@ -1878,9 +2022,13 @@ class PeriodicTorsionGenerator(object):
if torsion is not None: if torsion is not None:
self.proper.append(torsion) self.proper.append(torsion)
def registerImproperTorsion(self, parameters): def registerImproperTorsion(self, parameters, ordering='default'):
torsion = self.ff._parseTorsion(parameters) torsion = self.ff._parseTorsion(parameters)
if torsion is not None: 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) self.improper.append(torsion)
@staticmethod @staticmethod
...@@ -1894,7 +2042,10 @@ class PeriodicTorsionGenerator(object): ...@@ -1894,7 +2042,10 @@ class PeriodicTorsionGenerator(object):
for torsion in element.findall('Proper'): for torsion in element.findall('Proper'):
generator.registerProperTorsion(torsion.attrib) generator.registerProperTorsion(torsion.attrib)
for torsion in element.findall('Improper'): 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): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
existing = [sys.getForce(i) for i in range(sys.getNumForces())] existing = [sys.getForce(i) for i in range(sys.getNumForces())]
...@@ -1927,36 +2078,7 @@ class PeriodicTorsionGenerator(object): ...@@ -1927,36 +2078,7 @@ class PeriodicTorsionGenerator(object):
if match.k[i] != 0: if match.k[i] != 0:
force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.periodicity[i], match.phase[i], match.k[i]) force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.periodicity[i], match.phase[i], match.k[i])
for torsion in data.impropers: for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]] match = _matchImproper(data, torsion, self)
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
if match is not None: if match is not None:
(a1, a2, a3, a4, tordef) = match (a1, a2, a3, a4, tordef) = match
for i in range(len(tordef.phase)): for i in range(len(tordef.phase)):
...@@ -1970,12 +2092,16 @@ parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement ...@@ -1970,12 +2092,16 @@ parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement
class RBTorsion(object): class RBTorsion(object):
"""An RBTorsion records the information for a Ryckaert-Bellemans torsion definition.""" """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.types1 = types[0]
self.types2 = types[1] self.types2 = types[1]
self.types3 = types[2] self.types3 = types[2]
self.types4 = types[3] self.types4 = types[3]
self.c = c 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 ## @private
class RBTorsionGenerator(object): class RBTorsionGenerator(object):
...@@ -2001,7 +2127,10 @@ class RBTorsionGenerator(object): ...@@ -2001,7 +2127,10 @@ class RBTorsionGenerator(object):
for torsion in element.findall('Improper'): for torsion in element.findall('Improper'):
types = ff._findAtomTypes(torsion.attrib, 4) types = ff._findAtomTypes(torsion.attrib, 4)
if None not in types: 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): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
existing = [sys.getForce(i) for i in range(sys.getNumForces())] existing = [sys.getForce(i) for i in range(sys.getNumForces())]
...@@ -2032,40 +2161,7 @@ class RBTorsionGenerator(object): ...@@ -2032,40 +2161,7 @@ class RBTorsionGenerator(object):
if match is not None: 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]) 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: for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]] match = _matchImproper(data, torsion, self)
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
if match is not None: if match is not None:
(a1, a2, a3, a4, tordef) = match (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]) 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 ...@@ -2526,12 +2622,16 @@ parsers["CustomAngleForce"] = CustomAngleGenerator.parseElement
class CustomTorsion(object): class CustomTorsion(object):
"""A CustomTorsion records the information for a custom torsion definition.""" """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.types1 = types[0]
self.types2 = types[1] self.types2 = types[1]
self.types3 = types[2] self.types3 = types[2]
self.types4 = types[3] self.types4 = types[3]
self.paramValues = paramValues 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 ## @private
class CustomTorsionGenerator(object): class CustomTorsionGenerator(object):
...@@ -2560,7 +2660,10 @@ class CustomTorsionGenerator(object): ...@@ -2560,7 +2660,10 @@ class CustomTorsionGenerator(object):
for torsion in element.findall('Improper'): for torsion in element.findall('Improper'):
types = ff._findAtomTypes(torsion.attrib, 4) types = ff._findAtomTypes(torsion.attrib, 4)
if None not in types: 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): def createForce(self, sys, data, nonbondedMethod, nonbondedCutoff, args):
force = mm.CustomTorsionForce(self.energy) force = mm.CustomTorsionForce(self.energy)
...@@ -2590,40 +2693,7 @@ class CustomTorsionGenerator(object): ...@@ -2590,40 +2693,7 @@ class CustomTorsionGenerator(object):
if match is not None: if match is not None:
force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.paramValues) force.addTorsion(torsion[0], torsion[1], torsion[2], torsion[3], match.paramValues)
for torsion in data.impropers: for torsion in data.impropers:
type1 = data.atomType[data.atoms[torsion[0]]] match = _matchImproper(data, torsion, self)
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
if match is not None: if match is not None:
(a1, a2, a3, a4, tordef) = match (a1, a2, a3, a4, tordef) = match
force.addTorsion(a1, a2, a3, a4, tordef.paramValues) force.addTorsion(a1, a2, a3, a4, tordef.paramValues)
...@@ -3099,8 +3169,9 @@ class AmoebaBondGenerator(object): ...@@ -3099,8 +3169,9 @@ class AmoebaBondGenerator(object):
bond.length = self.length[i] bond.length = self.length[i]
if bond.isConstrained: if bond.isConstrained:
data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i]) data.addConstraint(sys, bond.atom1, bond.atom2, self.length[i])
elif self.k[i] != 0: if self.k[i] != 0:
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i]) if not bond.isConstrained or args.get('flexibleConstraints', False):
force.addBond(bond.atom1, bond.atom2, self.length[i], self.k[i])
break break
parsers["AmoebaBondForce"] = AmoebaBondGenerator.parseElement parsers["AmoebaBondForce"] = AmoebaBondGenerator.parseElement
...@@ -3227,6 +3298,8 @@ class AmoebaAngleGenerator(object): ...@@ -3227,6 +3298,8 @@ class AmoebaAngleGenerator(object):
force.setAmoebaGlobalAnglePentic(self.pentic) force.setAmoebaGlobalAnglePentic(self.pentic)
force.setAmoebaGlobalAngleSextic(self.sextic) force.setAmoebaGlobalAngleSextic(self.sextic)
DEG_TO_RAD = math.pi / 180
for angleDict in angleList: for angleDict in angleList:
angle = angleDict['angle'] angle = angleDict['angle']
isConstrained = angleDict['isConstrained'] isConstrained = angleDict['isConstrained']
...@@ -3241,8 +3314,8 @@ class AmoebaAngleGenerator(object): ...@@ -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 (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: if isConstrained and self.k[i] != 0.0:
angleDict['idealAngle'] = self.angle[i][0] angleDict['idealAngle'] = self.angle[i][0]
addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys) addAngleConstraint(angle, self.angle[i][0]*DEG_TO_RAD, data, sys)
elif self.k[i] != 0: if self.k[i] != 0 and (not isConstrained or args.get('flexibleConstraints', False)):
lenAngle = len(self.angle[i]) lenAngle = len(self.angle[i])
if (lenAngle > 1): if (lenAngle > 1):
# get k-index by counting number of non-angle hydrogens on the central atom # get k-index by counting number of non-angle hydrogens on the central atom
...@@ -3311,7 +3384,7 @@ class AmoebaAngleGenerator(object): ...@@ -3311,7 +3384,7 @@ class AmoebaAngleGenerator(object):
angleDict['idealAngle'] = self.angle[i][0] angleDict['idealAngle'] = self.angle[i][0]
if (isConstrained and self.k[i] != 0.0): if (isConstrained and self.k[i] != 0.0):
addAngleConstraint(angle, self.angle[i][0]*math.pi/180.0, data, sys) 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]) force.addAngle(angle[0], angle[1], angle[2], angle[3], self.angle[i][0], self.k[i])
break break
...@@ -3963,7 +4036,7 @@ class AmoebaTorsionTorsionGenerator(object): ...@@ -3963,7 +4036,7 @@ class AmoebaTorsionTorsionGenerator(object):
# raise error if atoms E or F not found # raise error if atoms E or F not found
if (atomE == -1 or atomF == -1): 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) raise ValueError(outputString)
# check for different type/mass between atoms E & F # check for different type/mass between atoms E & F
...@@ -5370,7 +5443,7 @@ class AmoebaUreyBradleyGenerator(object): ...@@ -5370,7 +5443,7 @@ class AmoebaUreyBradleyGenerator(object):
force = existing[0] force = existing[0]
for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained): for (angle, isConstrained) in zip(data.angles, data.isAngleConstrained):
if (isConstrained): if (isConstrained and not args.get('flexibleConstraints', False)):
continue continue
type1 = data.atomType[data.atoms[angle[0]]] type1 = data.atomType[data.atoms[angle[0]]]
type2 = data.atomType[data.atoms[angle[1]]] type2 = data.atomType[data.atoms[angle[1]]]
......
...@@ -69,11 +69,14 @@ def _is_gro_coord(line): ...@@ -69,11 +69,14 @@ def _is_gro_coord(line):
@param[in] line The line to be tested @param[in] line The line to be tested
""" """
sline = line.split() # data lines are fixed field
if len(sline) == 6 or len(sline) == 9: fields = []
return all([_isint(sline[2]), _isfloat(sline[3]), _isfloat(sline[4]), _isfloat(sline[5])]) fields.append(line[16:20].strip()) # atom number
elif len(sline) == 5 or len(sline) == 8: fields.append(line[21:28].strip()) # x coord
return all([_isint(line[15:20]), _isfloat(sline[2]), _isfloat(sline[3]), _isfloat(sline[4])]) 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: else:
return 0 return 0
......
...@@ -258,11 +258,16 @@ class GromacsTopFile(object): ...@@ -258,11 +258,16 @@ class GromacsTopFile(object):
def _processDefaults(self, line): def _processDefaults(self, line):
"""Process the [ defaults ] line.""" """Process the [ defaults ] line."""
fields = line.split() fields = line.split()
if len(fields) < 4: if len(fields) < 5:
raise ValueError('Too few fields in [ defaults ] line: '+line) # 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': if fields[0] != '1':
raise ValueError('Unsupported nonbonded type: '+fields[0]) 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]) raise ValueError('Unsupported combination rule: '+fields[1])
if fields[2].lower() == 'no': if fields[2].lower() == 'no':
self._genpairs = False self._genpairs = False
...@@ -570,7 +575,7 @@ class GromacsTopFile(object): ...@@ -570,7 +575,7 @@ class GromacsTopFile(object):
The solvent dielectric constant to use in the implicit solvent The solvent dielectric constant to use in the implicit solvent
model. model.
ewaldErrorTolerance : float=0.0005 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 removeCMMotion : boolean=True
If true, a CMMotionRemover will be added to the System If true, a CMMotionRemover will be added to the System
hydrogenMass : mass=None hydrogenMass : mass=None
...@@ -593,6 +598,11 @@ class GromacsTopFile(object): ...@@ -593,6 +598,11 @@ class GromacsTopFile(object):
raise ValueError('Illegal nonbonded method for a non-periodic system') raise ValueError('Illegal nonbonded method for a non-periodic system')
nb = mm.NonbondedForce() nb = mm.NonbondedForce()
sys.addForce(nb) 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: if implicitSolvent is OBC2:
gb = mm.GBSAOBCForce() gb = mm.GBSAOBCForce()
gb.setSoluteDielectric(soluteDielectric) gb.setSoluteDielectric(soluteDielectric)
...@@ -610,8 +620,10 @@ class GromacsTopFile(object): ...@@ -610,8 +620,10 @@ class GromacsTopFile(object):
mapIndices = {} mapIndices = {}
bondIndices = [] bondIndices = []
topologyAtoms = list(self.topology.atoms()) topologyAtoms = list(self.topology.atoms())
exceptions = [] exclusions = []
pairs = []
fudgeQQ = float(self._defaults[4]) fudgeQQ = float(self._defaults[4])
fudgeLJ = float(self._defaults[3])
# Build a lookup table to let us process dihedrals more quickly. # Build a lookup table to let us process dihedrals more quickly.
...@@ -748,7 +760,7 @@ class GromacsTopFile(object): ...@@ -748,7 +760,7 @@ class GromacsTopFile(object):
dihedralType = fields[4] dihedralType = fields[4]
reversedTypes = types[::-1]+(dihedralType,) reversedTypes = types[::-1]+(dihedralType,)
types = types+(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] paramsList = [fields]
else: else:
# Look for a matching dihedral type. # Look for a matching dihedral type.
...@@ -776,13 +788,16 @@ class GromacsTopFile(object): ...@@ -776,13 +788,16 @@ class GromacsTopFile(object):
elif dihedralType == '2': elif dihedralType == '2':
# Harmonic torsion # Harmonic torsion
k = float(params[6]) k = float(params[6])
phi0 = float(params[5])
if k != 0: if k != 0:
if harmonicTorsion is None: 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('theta0')
harmonicTorsion.addPerTorsionParameter('k') harmonicTorsion.addPerTorsionParameter('k')
sys.addForce(harmonicTorsion) 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: else:
# RB Torsion # RB Torsion
c = [float(x) for x in params[5:11]] c = [float(x) for x in params[5:11]]
...@@ -825,11 +840,18 @@ class GromacsTopFile(object): ...@@ -825,11 +840,18 @@ class GromacsTopFile(object):
for fields in moleculeType.atoms: for fields in moleculeType.atoms:
params = self._atomTypes[fields[1]] params = self._atomTypes[fields[1]]
if len(fields) > 6: if len(fields) > 6:
q = float(fields[6]) q = float(fields[6])
else: else:
q = float(params[4]) 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 implicitSolvent is OBC2:
if fields[1] not in self._implicitTypes: if fields[1] not in self._implicitTypes:
raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1]) raise ValueError('No implicit solvent parameters specified for atom type: '+fields[1])
...@@ -857,19 +879,37 @@ class GromacsTopFile(object): ...@@ -857,19 +879,37 @@ class GromacsTopFile(object):
continue # We'll use the automatically generated parameters continue # We'll use the automatically generated parameters
atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0]) atom1params = nb.getParticleParameters(baseAtomIndex+atoms[0])
atom2params = nb.getParticleParameters(baseAtomIndex+atoms[1]) 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: for fields in moleculeType.exclusions:
atoms = [int(x)-1 for x in fields] atoms = [int(x)-1 for x in fields]
for atom in atoms[1:]: for atom in atoms[1:]:
if atom > atoms[0]: if atom > atoms[0]:
exceptions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom, 0, 0, 0)) exclusions.append((baseAtomIndex+atoms[0], baseAtomIndex+atom))
# Create nonbonded exceptions. # Create nonbonded exceptions.
nb.createExceptionsFromBonds(bondIndices, fudgeQQ, float(self._defaults[3])) nb.createExceptionsFromBonds(bondIndices, fudgeQQ, fudgeLJ)
for exception in exceptions: for exclusion in exclusions:
nb.addException(exception[0], exception[1], exception[2], float(exception[3]), float(exception[4]), True) 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. # Finish configuring the NonbondedForce.
...@@ -882,6 +922,15 @@ class GromacsTopFile(object): ...@@ -882,6 +922,15 @@ class GromacsTopFile(object):
nb.setNonbondedMethod(methodMap[nonbondedMethod]) nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff) nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance) 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. # Adjust masses.
......
...@@ -525,7 +525,7 @@ class PrmtopLoader(object): ...@@ -525,7 +525,7 @@ class PrmtopLoader(object):
iScnb = float(self._raw_data["SCNB_SCALE_FACTOR"][iidx]) iScnb = float(self._raw_data["SCNB_SCALE_FACTOR"][iidx])
except KeyError: except KeyError:
iScnb = 2.0 iScnb = 2.0
returnList.append((iAtom, lAtom, chargeProd, rMin, epsilon, iScee, iScnb)) returnList.append((iAtom, lAtom, chargeProd, rMin, epsilon, iScee, iScnb))
return returnList return returnList
...@@ -579,7 +579,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -579,7 +579,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
soluteDielectric=1.0, solventDielectric=78.5, soluteDielectric=1.0, solventDielectric=78.5,
implicitSolventKappa=0.0*(1/units.nanometer), nonbondedCutoff=None, implicitSolventKappa=0.0*(1/units.nanometer), nonbondedCutoff=None,
nonbondedMethod='NoCutoff', scee=None, scnb=None, mm=None, verbose=False, 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. Create an OpenMM System from an Amber prmtop file.
...@@ -603,6 +604,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -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) 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 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 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 NOTES
...@@ -648,6 +650,9 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -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. " warnings.warn("1-4 scaling parameters in topology file are being ignored. "
"This is not recommended unless you know what you are doing.") "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() has_1264 = 'LENNARD_JONES_CCOEF' in prmtop._raw_data.keys()
if has_1264: if has_1264:
parm_ccoef = [float(x) for x in prmtop._raw_data['LENNARD_JONES_CCOEF']] 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 ...@@ -974,20 +979,20 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
if units.is_quantity(cutoff): if units.is_quantity(cutoff):
cutoff = cutoff.value_in_unit(units.nanometers) cutoff = cutoff.value_in_unit(units.nanometers)
if gbmodel == 'HCT': if gbmodel == 'HCT':
gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa) gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC1': elif gbmodel == 'OBC1':
gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa) gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC2': elif gbmodel == 'OBC2':
if implicitSolventKappa > 0: if implicitSolventKappa > 0:
gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa) gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
else: else:
gb = mm.GBSAOBCForce() gb = mm.GBSAOBCForce()
gb.setSoluteDielectric(soluteDielectric) gb.setSoluteDielectric(soluteDielectric)
gb.setSolventDielectric(solventDielectric) gb.setSolventDielectric(solventDielectric)
elif gbmodel == 'GBn': elif gbmodel == 'GBn':
gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa) gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
elif gbmodel == 'GBn2': elif gbmodel == 'GBn2':
gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, 'ACE', cutoff, implicitSolventKappa) gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa)
else: else:
raise ValueError("Illegal value specified for implicit solvent model") raise ValueError("Illegal value specified for implicit solvent model")
if isinstance(gb, mm.GBSAOBCForce): if isinstance(gb, mm.GBSAOBCForce):
...@@ -1121,7 +1126,7 @@ class AmberAsciiRestart(object): ...@@ -1121,7 +1126,7 @@ class AmberAsciiRestart(object):
self.natom = int(words[0]) self.natom = int(words[0])
except (IndexError, ValueError): except (IndexError, ValueError):
raise TypeError('Unrecognized file type [%s]' % self.filename) raise TypeError('Unrecognized file type [%s]' % self.filename)
if len(words) >= 2: if len(words) >= 2:
self.time = float(words[1]) * units.picoseconds self.time = float(words[1]) * units.picoseconds
...@@ -1290,7 +1295,7 @@ class AmberNetcdfRestart(object): ...@@ -1290,7 +1295,7 @@ class AmberNetcdfRestart(object):
from scipy.io.netcdf import NetCDFFile from scipy.io.netcdf import NetCDFFile
except ImportError: except ImportError:
raise ImportError('scipy is necessary to parse NetCDF restarts') raise ImportError('scipy is necessary to parse NetCDF restarts')
self.filename = filename self.filename = filename
self.velocities = self.boxVectors = self.time = None self.velocities = self.boxVectors = self.time = None
......
...@@ -123,6 +123,8 @@ class PDBFile(object): ...@@ -123,6 +123,8 @@ class PDBFile(object):
# Try to guess the element. # Try to guess the element.
upper = atomName.upper() upper = atomName.upper()
while len(upper) > 1 and upper[0].isdigit():
upper = upper[1:]
if upper.startswith('CL'): if upper.startswith('CL'):
element = elem.chlorine element = elem.chlorine
elif upper.startswith('NA'): elif upper.startswith('NA'):
...@@ -141,7 +143,7 @@ class PDBFile(object): ...@@ -141,7 +143,7 @@ class PDBFile(object):
element = elem.calcium element = elem.calcium
else: else:
try: try:
element = elem.get_by_symbol(atomName[0]) element = elem.get_by_symbol(upper[0])
except KeyError: except KeyError:
pass pass
newAtom = top.addAtom(atomName, element, r, str(atom.serial_number)) 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