Commit 5a06df78 authored by tic20's avatar tic20
Browse files
parents 8dd60914 a9223eea
...@@ -163,12 +163,12 @@ void testRandomSeed() { ...@@ -163,12 +163,12 @@ void testRandomSeed() {
Context context(system, integrator, platform); Context context(system, integrator, platform);
context.setPositions(positions); context.setPositions(positions);
context.setVelocities(velocities); context.setVelocities(velocities);
integrator.step(10); integrator.step(20);
State state1 = context.getState(State::Positions); State state1 = context.getState(State::Positions);
context.reinitialize(); context.reinitialize();
context.setPositions(positions); context.setPositions(positions);
context.setVelocities(velocities); context.setVelocities(velocities);
integrator.step(10); integrator.step(20);
State state2 = context.getState(State::Positions); State state2 = context.getState(State::Positions);
// Try twice with a different random seed. // Try twice with a different random seed.
...@@ -177,12 +177,12 @@ void testRandomSeed() { ...@@ -177,12 +177,12 @@ void testRandomSeed() {
context.reinitialize(); context.reinitialize();
context.setPositions(positions); context.setPositions(positions);
context.setVelocities(velocities); context.setVelocities(velocities);
integrator.step(10); integrator.step(20);
State state3 = context.getState(State::Positions); State state3 = context.getState(State::Positions);
context.reinitialize(); context.reinitialize();
context.setPositions(positions); context.setPositions(positions);
context.setVelocities(velocities); context.setVelocities(velocities);
integrator.step(10); integrator.step(20);
State state4 = context.getState(State::Positions); State state4 = context.getState(State::Positions);
// Compare the results. // Compare the results.
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
#include "openmm/HarmonicBondForce.h" #include "openmm/HarmonicBondForce.h"
#include "openmm/NonbondedForce.h" #include "openmm/NonbondedForce.h"
#include "openmm/System.h" #include "openmm/System.h"
#include "openmm/BAOABLangevinIntegrator.h" #include "openmm/LangevinMiddleIntegrator.h"
#include "SimTKOpenMMRealType.h" #include "SimTKOpenMMRealType.h"
#include "sfmt/SFMT.h" #include "sfmt/SFMT.h"
#include <iostream> #include <iostream>
...@@ -49,7 +49,7 @@ void testSingleBond() { ...@@ -49,7 +49,7 @@ void testSingleBond() {
System system; System system;
system.addParticle(2.0); system.addParticle(2.0);
system.addParticle(2.0); system.addParticle(2.0);
BAOABLangevinIntegrator integrator(0, 0.1, 0.01); LangevinMiddleIntegrator integrator(0, 0.1, 0.01);
HarmonicBondForce* forceField = new HarmonicBondForce(); HarmonicBondForce* forceField = new HarmonicBondForce();
forceField->addBond(0, 1, 1.5, 1); forceField->addBond(0, 1, 1.5, 1);
system.addForce(forceField); system.addForce(forceField);
...@@ -93,7 +93,7 @@ void testTemperature() { ...@@ -93,7 +93,7 @@ void testTemperature() {
const double temp = 100.0; const double temp = 100.0;
System system; System system;
system.setDefaultPeriodicBoxVectors(Vec3(5, 0, 0), Vec3(0, 5, 0), Vec3(0, 0, 5)); system.setDefaultPeriodicBoxVectors(Vec3(5, 0, 0), Vec3(0, 5, 0), Vec3(0, 0, 5));
BAOABLangevinIntegrator integrator(temp, 3.0, 0.01); LangevinMiddleIntegrator integrator(temp, 3.0, 0.01);
NonbondedForce* forceField = new NonbondedForce(); NonbondedForce* forceField = new NonbondedForce();
forceField->setNonbondedMethod(NonbondedForce::CutoffPeriodic); forceField->setNonbondedMethod(NonbondedForce::CutoffPeriodic);
for (int i = 0; i < numParticles; ++i) { for (int i = 0; i < numParticles; ++i) {
...@@ -130,7 +130,7 @@ void testConstraints() { ...@@ -130,7 +130,7 @@ void testConstraints() {
const int numConstraints = 5; const int numConstraints = 5;
const double temp = 100.0; const double temp = 100.0;
System system; System system;
BAOABLangevinIntegrator integrator(temp, 2.0, 0.01); LangevinMiddleIntegrator integrator(temp, 2.0, 0.01);
integrator.setConstraintTolerance(1e-5); integrator.setConstraintTolerance(1e-5);
NonbondedForce* forceField = new NonbondedForce(); NonbondedForce* forceField = new NonbondedForce();
for (int i = 0; i < numParticles; ++i) { for (int i = 0; i < numParticles; ++i) {
...@@ -181,7 +181,7 @@ void testConstrainedMasslessParticles() { ...@@ -181,7 +181,7 @@ void testConstrainedMasslessParticles() {
vector<Vec3> positions(2); vector<Vec3> positions(2);
positions[0] = Vec3(-1, 0, 0); positions[0] = Vec3(-1, 0, 0);
positions[1] = Vec3(1, 0, 0); positions[1] = Vec3(1, 0, 0);
BAOABLangevinIntegrator integrator(300.0, 2.0, 0.01); LangevinMiddleIntegrator integrator(300.0, 2.0, 0.01);
bool failed = false; bool failed = false;
try { try {
// This should throw an exception. // This should throw an exception.
...@@ -208,7 +208,7 @@ void testRandomSeed() { ...@@ -208,7 +208,7 @@ void testRandomSeed() {
const int numParticles = 8; const int numParticles = 8;
const double temp = 100.0; const double temp = 100.0;
System system; System system;
BAOABLangevinIntegrator integrator(temp, 2.0, 0.01); LangevinMiddleIntegrator integrator(temp, 2.0, 0.01);
NonbondedForce* forceField = new NonbondedForce(); NonbondedForce* forceField = new NonbondedForce();
for (int i = 0; i < numParticles; ++i) { for (int i = 0; i < numParticles; ++i) {
system.addParticle(2.0); system.addParticle(2.0);
......
...@@ -354,6 +354,44 @@ void testPeriodic() { ...@@ -354,6 +354,44 @@ void testPeriodic() {
ASSERT_EQUAL_TOL(2*ONE_4PI_EPS0*(1.0)*(1.0+krf*1.0-crf), state.getPotentialEnergy(), TOL); ASSERT_EQUAL_TOL(2*ONE_4PI_EPS0*(1.0)*(1.0+krf*1.0-crf), state.getPotentialEnergy(), TOL);
} }
void testPeriodicExceptions() {
System system;
system.addParticle(1.0);
system.addParticle(1.0);
VerletIntegrator integrator(0.01);
NonbondedForce* nonbonded = new NonbondedForce();
nonbonded->addParticle(1.0, 1, 0);
nonbonded->addParticle(1.0, 1, 0);
nonbonded->addException(0, 1, 1.0, 1.0, 0.0);
nonbonded->setNonbondedMethod(NonbondedForce::CutoffPeriodic);
const double cutoff = 2.0;
nonbonded->setCutoffDistance(cutoff);
system.setDefaultPeriodicBoxVectors(Vec3(4, 0, 0), Vec3(0, 4, 0), Vec3(0, 0, 4));
system.addForce(nonbonded);
Context context(system, integrator, platform);
vector<Vec3> positions(2);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(3, 0, 0);
context.setPositions(positions);
State state = context.getState(State::Forces | State::Energy);
vector<Vec3> forces = state.getForces();
double force = ONE_4PI_EPS0/(3*3);
ASSERT_EQUAL_VEC(Vec3(-force, 0, 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(force, 0, 0), forces[1], TOL);
ASSERT_EQUAL_TOL(ONE_4PI_EPS0/3, state.getPotentialEnergy(), TOL);
// Now make exceptions periodic and see if it changes correctly.
nonbonded->setExceptionsUsePeriodicBoundaryConditions(true);
context.reinitialize(true);
state = context.getState(State::Forces | State::Energy);
forces = state.getForces();
force = ONE_4PI_EPS0/(1*1);
ASSERT_EQUAL_VEC(Vec3(force, 0, 0), forces[0], TOL);
ASSERT_EQUAL_VEC(Vec3(-force, 0, 0), forces[1], TOL);
ASSERT_EQUAL_TOL(ONE_4PI_EPS0/1, state.getPotentialEnergy(), TOL);
}
void testTriclinic() { void testTriclinic() {
System system; System system;
system.addParticle(1.0); system.addParticle(1.0);
...@@ -809,6 +847,7 @@ int main(int argc, char* argv[]) { ...@@ -809,6 +847,7 @@ int main(int argc, char* argv[]) {
testCutoff(); testCutoff();
testCutoff14(); testCutoff14();
testPeriodic(); testPeriodic();
testPeriodicExceptions();
testTriclinic(); testTriclinic();
testLargeSystem(); testLargeSystem();
testDispersionCorrection(); testDispersionCorrection();
......
/* -------------------------------------------------------------------------- *
* 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) 2019 Stanford University and the Authors. *
* Authors: Andreas Krämer and Andrew C. Simmonett *
* 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/Context.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/HarmonicBondForce.h"
#include "openmm/NonbondedForce.h"
#include "openmm/System.h"
#include "openmm/NoseHooverIntegrator.h"
#include "openmm/VirtualSite.h"
#include "SimTKOpenMMRealType.h"
#include "sfmt/SFMT.h"
#include <iostream>
#include <vector>
using namespace OpenMM;
using namespace std;
const double TOL = 1e-5;
void testVVSingleBond() {
System system;
system.addParticle(2.0);
system.addParticle(2.0);
NoseHooverIntegrator integrator(0.01);
HarmonicBondForce* forceField = new HarmonicBondForce();
forceField->addBond(0, 1, 1.5, 1);
system.addForce(forceField);
Context context(system, integrator, platform);
vector<Vec3> positions(2);
positions[0] = Vec3(-1, 0, 0);
positions[1] = Vec3(1, 0, 0);
context.setPositions(positions);
// This is simply a harmonic oscillator, so compare it to the analytical solution.
const double freq = 1.0;
State state = context.getState(State::Energy);
const double initialEnergy = state.getKineticEnergy()+state.getPotentialEnergy();
for (int i = 0; i < 1000; ++i) {
state = context.getState(State::Positions | State::Velocities | State::Energy);
double time = state.getTime();
double expectedDist = 1.5+0.5*std::cos(freq*time);
ASSERT_EQUAL_VEC(Vec3(-0.5*expectedDist, 0, 0), state.getPositions()[0], 0.02);
ASSERT_EQUAL_VEC(Vec3(0.5*expectedDist, 0, 0), state.getPositions()[1], 0.02);
double expectedSpeed = -0.5*freq*std::sin(freq*time);
ASSERT_EQUAL_VEC(Vec3(-0.5*expectedSpeed, 0, 0), state.getVelocities()[0], 0.02);
ASSERT_EQUAL_VEC(Vec3(0.5*expectedSpeed, 0, 0), state.getVelocities()[1], 0.02);
double energy = state.getKineticEnergy()+state.getPotentialEnergy();
ASSERT_EQUAL_TOL(initialEnergy, energy, 0.01);
integrator.step(1);
}
ASSERT_EQUAL_TOL(10.0, context.getState(0).getTime(), 1e-5);
}
void testVVConstraints() {
const int numParticles = 8;
const int numConstraints = 5;
System system;
NoseHooverIntegrator integrator(0.001);
integrator.setConstraintTolerance(1e-5);
NonbondedForce* forceField = new NonbondedForce();
for (int i = 0; i < numParticles; ++i) {
system.addParticle(i%2 == 0 ? 5.0 : 10.0);
forceField->addParticle((i%2 == 0 ? 0.2 : -0.2), 0.5, 5.0);
}
system.addConstraint(0, 1, 1.0);
system.addConstraint(1, 2, 1.0);
system.addConstraint(2, 3, 1.0);
system.addConstraint(4, 5, 1.0);
system.addConstraint(6, 7, 1.0);
system.addForce(forceField);
Context context(system, integrator, platform);
vector<Vec3> positions(numParticles);
vector<Vec3> velocities(numParticles);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
for (int i = 0; i < numParticles; ++i) {
positions[i] = Vec3(i/2, (i+1)/2, 0);
velocities[i] = Vec3(genrand_real2(sfmt)-0.5, genrand_real2(sfmt)-0.5, genrand_real2(sfmt)-0.5);
}
context.setPositions(positions);
context.setVelocities(velocities);
// Simulate it and see whether the constraints remain satisfied.
double initialEnergy = 0.0;
for (int i = 0; i < 1000; ++i) {
State state = context.getState(State::Positions | State::Energy | State::Velocities | State::Forces);
for (int j = 0; j < numConstraints; ++j) {
int particle1, particle2;
double distance;
system.getConstraintParameters(j, particle1, particle2, distance);
Vec3 p1 = state.getPositions()[particle1];
Vec3 p2 = state.getPositions()[particle2];
double dist = std::sqrt((p1[0]-p2[0])*(p1[0]-p2[0])+(p1[1]-p2[1])*(p1[1]-p2[1])+(p1[2]-p2[2])*(p1[2]-p2[2]));
ASSERT_EQUAL_TOL(distance, dist, 1e-4);
}
double energy = state.getPotentialEnergy()+state.getKineticEnergy();
if (i == 1)
initialEnergy = energy;
else if (i > 1)
ASSERT_EQUAL_TOL(initialEnergy, energy, 0.01);
integrator.step(1);
}
}
void testVVConstrainedClusters() {
const int numParticles = 7;
System system;
NoseHooverIntegrator integrator(0.001);
integrator.setConstraintTolerance(1e-5);
NonbondedForce* forceField = new NonbondedForce();
for (int i = 0; i < numParticles; ++i) {
system.addParticle(i > 1 ? 1.0 : 10.0);
forceField->addParticle((i%2 == 0 ? 0.2 : -0.2), 0.5, 5.0);
}
system.addConstraint(0, 1, 1.0);
system.addConstraint(0, 2, 1.0);
system.addConstraint(0, 3, 1.0);
system.addConstraint(0, 4, 1.0);
system.addConstraint(1, 5, 1.0);
system.addConstraint(1, 6, 1.0);
system.addConstraint(2, 3, sqrt(2.0));
system.addConstraint(2, 4, sqrt(2.0));
system.addConstraint(3, 4, sqrt(2.0));
system.addConstraint(5, 6, sqrt(2.0));
system.addForce(forceField);
Context context(system, integrator, platform);
vector<Vec3> positions(numParticles);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(1, 0, 0);
positions[2] = Vec3(-1, 0, 0);
positions[3] = Vec3(0, 1, 0);
positions[4] = Vec3(0, 0, 1);
positions[5] = Vec3(2, 0, 0);
positions[6] = Vec3(1, 1, 0);
vector<Vec3> velocities(numParticles);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
for (int i = 0; i < numParticles; ++i)
velocities[i] = Vec3(genrand_real2(sfmt)-0.5, genrand_real2(sfmt)-0.5, genrand_real2(sfmt)-0.5);
context.setPositions(positions);
context.setVelocities(velocities);
// Simulate it and see whether the constraints remain satisfied.
double initialEnergy = 0.0;
for (int i = 0; i < 1000; ++i) {
State state = context.getState(State::Positions | State::Energy | State::Velocities | State::Forces);
for (int j = 0; j < system.getNumConstraints(); ++j) {
int particle1, particle2;
double distance;
system.getConstraintParameters(j, particle1, particle2, distance);
Vec3 p1 = state.getPositions()[particle1];
Vec3 p2 = state.getPositions()[particle2];
double dist = std::sqrt((p1[0]-p2[0])*(p1[0]-p2[0])+(p1[1]-p2[1])*(p1[1]-p2[1])+(p1[2]-p2[2])*(p1[2]-p2[2]));
ASSERT_EQUAL_TOL(distance, dist, 2e-5);
}
double energy = state.getPotentialEnergy()+state.getKineticEnergy();
if (i == 1)
initialEnergy = energy;
else if (i > 1)
ASSERT_EQUAL_TOL(initialEnergy, energy, 0.01);
integrator.step(1);
}
}
void testVVConstrainedMasslessParticles() {
System system;
system.addParticle(0.0);
system.addParticle(1.0);
system.addConstraint(0, 1, 1.5);
vector<Vec3> positions(2);
positions[0] = Vec3(-1, 0, 0);
positions[1] = Vec3(1, 0, 0);
NoseHooverIntegrator integrator(0.01);
bool failed = false;
try {
// This should throw an exception.
Context context(system, integrator, platform);
}
catch (exception& ex) {
failed = true;
}
ASSERT(failed);
// Now make both particles massless, which should work.
system.setParticleMass(1, 0.0);
Context context(system, integrator, platform);
context.setPositions(positions);
context.setVelocitiesToTemperature(300.0);
integrator.step(1);
State state = context.getState(State::Velocities);
ASSERT_EQUAL(0.0, state.getVelocities()[0][0]);
}
/**
* Make sure that virtual sites are updated correctly
*/
void testThreeParticleVirtualSite() {
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(0.0);
system.setVirtualSite(3, new ThreeParticleAverageSite(0, 1, 2, 0.2, 0.3, 0.5));
CustomExternalForce* forceField = new CustomExternalForce("-a*x");
system.addForce(forceField);
forceField->addPerParticleParameter("a");
vector<double> params(1);
params[0] = 0.1;
forceField->addParticle(0, params);
params[0] = 0.2;
forceField->addParticle(1, params);
params[0] = 0.3;
forceField->addParticle(2, params);
params[0] = 0.4;
forceField->addParticle(3, params);
NoseHooverIntegrator integrator(0.002);
Context context(system, integrator, platform);
vector<Vec3> positions(4);
positions[0] = Vec3(0, 0, 0);
positions[1] = Vec3(1, 0, 0);
positions[2] = Vec3(0, 1, 0);
context.setPositions(positions);
context.applyConstraints(0.0001);
for (int i = 0; i < 1000; i++) {
State state = context.getState(State::Positions | State::Forces);
const vector<Vec3>& pos = state.getPositions();
ASSERT_EQUAL_VEC(pos[0]*0.2+pos[1]*0.3+pos[2]*0.5, pos[3], 1e-5);
ASSERT_EQUAL_VEC(Vec3(0.1+0.4*0.2, 0, 0), state.getForces()[0], 1e-5);
ASSERT_EQUAL_VEC(Vec3(0.2+0.4*0.3, 0, 0), state.getForces()[1], 1e-5);
ASSERT_EQUAL_VEC(Vec3(0.3+0.4*0.5, 0, 0), state.getForces()[2], 1e-5);
integrator.step(1);
}
}
void runPlatformTests();
int main(int argc, char* argv[]) {
try {
initializeTests(argc, argv);
testVVSingleBond();
testVVConstraints();
testVVConstrainedClusters();
testVVConstrainedMasslessParticles();
testThreeParticleVirtualSite();
runPlatformTests();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
/* -------------------------------------------------------------------------- *
* 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) 2019 Stanford University and the Authors. *
* Authors: Andreas Krämer and Andrew C. Simmonett *
* 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/NoseHooverChain.h"
#include "openmm/NoseHooverIntegrator.h"
#include "openmm/Context.h"
#include "openmm/State.h"
#include "openmm/HarmonicBondForce.h"
#include "openmm/NonbondedForce.h"
#include "openmm/CustomExternalForce.h"
#include "openmm/System.h"
#include "SimTKOpenMMRealType.h"
#include "sfmt/SFMT.h"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace OpenMM;
using namespace std;
void testHarmonicOscillator() {
const double mass = 1.0;
double temperature = 300;
double frequency = 1;
double mts = 1, ys = 1, chain_length = 3;
System system;
system.addParticle(mass);
vector<Vec3> positions(1);
positions[0] = Vec3(0.5,0.5,0.5);
vector<Vec3> velocities(1);
velocities[0] = Vec3(0, 0, 0);
auto harmonic_restraint = new CustomExternalForce("0.5*(x^2+y^2+z^2)");
harmonic_restraint->addParticle(0);
system.addForce(harmonic_restraint);
NoseHooverIntegrator integrator(0.001);
integrator.addThermostat(temperature, frequency, chain_length, mts, ys);
Context context(system, integrator, platform);
context.setPositions(positions);
context.setVelocities(velocities);
double mean_temperature=0;
// equilibration
integrator.step(2000);
for (size_t i=0; i < 2500; i++){
integrator.step(10);
State state = context.getState(State::Energy | State::Positions | State::Velocities);
double kinetic_energy = state.getKineticEnergy();
double temp = kinetic_energy/(0.5*3*BOLTZ);
mean_temperature = (i*mean_temperature + temp)/(i+1);
double PE = state.getPotentialEnergy();
double time = state.getTime();
double energy = kinetic_energy + PE + integrator.computeHeatBathEnergy();
}
ASSERT_EQUAL_TOL(temperature, mean_temperature, 0.02);
}
int makeDimerBox(System& system, std::vector<Vec3>& positions, bool constrain=true, int numMolecules=20, double bondLength=0.1){
double boxLength = 2; // nm
Vec3 a(boxLength, 0.0, 0.0);
Vec3 b(0.0, boxLength, 0.0);
Vec3 c(0.0, 0.0, boxLength);
double mass = 20;
double bondForceConstant = 30000; //0.001;
int numDOF = 0;
NonbondedForce* forceField = new NonbondedForce();
HarmonicBondForce* bondForce = new HarmonicBondForce();
for(int molecule = 0; molecule < numMolecules; ++molecule) {
int particle1 = system.addParticle(mass);
int particle2 = system.addParticle(mass);
forceField->addParticle(0.0, 0.1, 1.0);
forceField->addParticle(0.0, 0.1, 1.0);
forceField->addException(particle1, particle2, 0, 0, 0);
bondForce->addBond(particle1, particle2, bondLength, bondForceConstant);
numDOF += 6;
if (constrain) {
system.addConstraint(particle1, particle2, bondLength);
numDOF -= 1;
}
}
forceField->setCutoffDistance(.99*boxLength/2);
forceField->setSwitchingDistance(.88*boxLength/2);
forceField->setUseSwitchingFunction(true);
forceField->setUseDispersionCorrection(false);
forceField->setNonbondedMethod(NonbondedForce::CutoffPeriodic);
system.addForce(forceField);
system.addForce(bondForce);
system.setDefaultPeriodicBoxVectors(a, b, c);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
for (int i = 0; i < numMolecules; i++) {
while (true) {
Vec3 pos = Vec3(boxLength*genrand_real2(sfmt), boxLength*genrand_real2(sfmt), boxLength*genrand_real2(sfmt));
Vec3 pos1 = pos + Vec3(0,0, bondLength/2);
Vec3 pos2 = pos + Vec3(0,0,-bondLength/2);
double minDist = 2*boxLength;
for (int j = 0; j < i; j++) {
Vec3 delta = pos1-positions[j];
minDist = std::min(minDist, sqrt(delta.dot(delta)));
delta = pos2-positions[j];
minDist = std::min(minDist, sqrt(delta.dot(delta)));
}
if (minDist > 0.15) {
positions[2*i+0] = pos1;
positions[2*i+1] = pos2;
break;
}
}
}
return numDOF;
}
void testDimerBox(bool constrain=true) {
// Check conservation of system + bath energy for a harmonic oscillator
int numMolecules = 20;
double bondLength = 0.1;
double bondLengthSquared = bondLength * bondLength;
System system;
std::vector<Vec3> positions(numMolecules*2);
int numDOF = makeDimerBox(system, positions, constrain, numMolecules, bondLength);
bool simpleConstruct = true;
double temperature = 300; // kelvin
double collisionFrequency = 200; // 1/ps
int numMTS = 3;
int numYS = 3;
int chainLength = 5;
auto integrator = simpleConstruct ? NoseHooverIntegrator(temperature, collisionFrequency, 0.001, chainLength, numMTS, numYS)
: NoseHooverIntegrator(0.001);
if (!simpleConstruct)
integrator.addThermostat(temperature, collisionFrequency, chainLength, numMTS, numYS);
Context context(system, integrator, platform);
context.setPositions(positions);
context.setVelocitiesToTemperature(temperature);
int nSteps = 1500;
double mean_temp = 0.0;
std::vector<double> energies(nSteps);
for (int i = 0; i < nSteps; ++i) {
integrator.step(1);
State state = context.getState(State::Energy | (constrain ? State::Positions : 0));
if (constrain) {
auto positions = state.getPositions();
for(int i = 0; i < numMolecules; ++i) {
Vec3 delta = positions[2*i+1] - positions[2*i];
double dR2 = delta.dot(delta);
ASSERT_EQUAL_TOL(bondLengthSquared, dR2, 1e-4);
}
}
double KE = state.getKineticEnergy();
double PE = state.getPotentialEnergy();
double time = state.getTime();
double instantaneous_temperature = 2 * KE / (BOLTZ * numDOF);
mean_temp = (i*mean_temp + instantaneous_temperature)/(i+1);
double energy = KE + PE + integrator.computeHeatBathEnergy();
energies[i] = energy;
}
double sum = std::accumulate(energies.begin(), energies.end(), 0.0);
double mean = sum / energies.size();
double sq_sum = std::inner_product(energies.begin(), energies.end(), energies.begin(), 0.0);
double std = std::sqrt(sq_sum / energies.size() - mean * mean);
double relative_std = std / mean;
// Check mean temperature
ASSERT_USUALLY_EQUAL_TOL(temperature, mean_temp, 1e-2);
// Check fluctuation of conserved (total bath + system) energy
ASSERT_USUALLY_EQUAL_TOL(relative_std, 0, 5e-4);
}
void testCheckpoints() {
// Create a system with Drude-like particles to be thermostated as a pair, as well as another
// particle to be thermostated independently, to test all integrator features.
double timeStep = 0.001;
NoseHooverIntegrator integrator(timeStep), newIntegrator(timeStep);
System system;
double mass = 1;
system.addParticle(8*mass);
system.addParticle(mass);
system.addParticle(5*mass);
HarmonicBondForce* force = new HarmonicBondForce();
force->addBond(0, 1, 0.1, 50.0);
force->addBond(0, 2, 0.1, 50.0);
system.addForce(force);
double kineticEnergy = 1e6;
double temperature=300, collisionFrequency=1, chainLength=3, numMTS=3, numYS=3;
chainLength = 10;
integrator.addSubsystemThermostat(std::vector<int>{2}, std::vector<std::pair<int,int>>{{0,1}}, temperature, collisionFrequency, temperature, collisionFrequency,
chainLength, numMTS, numYS);
newIntegrator.addSubsystemThermostat(std::vector<int>{2}, std::vector<std::pair<int,int>>{{0,1}}, temperature, collisionFrequency, temperature, collisionFrequency,
chainLength, numMTS, numYS);
Context context(system, integrator, platform);
Context newContext(system, newIntegrator, platform);
std::vector<Vec3> positions(3);
std::vector<Vec3> velocities(3);
positions[1] = {0.1, 0.0, 0.0};
velocities[1] = {0.1,0.2,-0.2};
positions[2] = {-0.1, 0.001, 0.001};
velocities[2] = {-0.1,0.2,-0.2};
context.setPositions(positions);
context.setVelocities(velocities);
// Run a short simulation and checkpoint..
integrator.step(500);
std::stringstream checkpoint;
context.createCheckpoint(checkpoint);
// Now continue the simulation
integrator.step(5);
// And try the same, starting from the checkpoint
newContext.loadCheckpoint(checkpoint);
newIntegrator.step(5);
State state1 = context.getState(State::Positions | State::Velocities);
State state2 = newContext.getState(State::Positions | State::Velocities);
ASSERT_EQUAL_VEC(state1.getPositions()[0], state2.getPositions()[0], 1e-6);
ASSERT_EQUAL_VEC(state1.getPositions()[1], state2.getPositions()[1], 1e-6);
ASSERT_EQUAL_VEC(state1.getVelocities()[0], state2.getVelocities()[0], 1e-6);
ASSERT_EQUAL_VEC(state1.getVelocities()[1], state2.getVelocities()[1], 1e-6);
}
void testAPIChangeNumParticles() {
bool constrain = true;
int numMolecules = 20;
double bondLength = 0.1;
double bondLengthSquared = bondLength * bondLength;
System system;
std::vector<Vec3> positions(numMolecules*2);
int numDOF = makeDimerBox(system, positions, constrain, numMolecules, bondLength);
}
void runPlatformTests();
int main(int argc, char* argv[]) {
try {
initializeTests(argc, argv);
testHarmonicOscillator();
bool constrain;
constrain = false; testDimerBox(constrain);
constrain = true; testDimerBox(constrain);
testCheckpoints();
runPlatformTests();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
...@@ -76,7 +76,12 @@ class WrapperGenerator: ...@@ -76,7 +76,12 @@ class WrapperGenerator:
'static std::vector<std::string> OpenMM::Platform::loadPluginsFromDirectory', 'static std::vector<std::string> OpenMM::Platform::loadPluginsFromDirectory',
'Vec3 OpenMM::LocalCoordinatesSite::getOriginWeights', 'Vec3 OpenMM::LocalCoordinatesSite::getOriginWeights',
'Vec3 OpenMM::LocalCoordinatesSite::getXWeights', 'Vec3 OpenMM::LocalCoordinatesSite::getXWeights',
'Vec3 OpenMM::LocalCoordinatesSite::getYWeights'] 'Vec3 OpenMM::LocalCoordinatesSite::getYWeights',
'std::vector<double> OpenMM::NoseHooverChain::getYoshidaSuzukiWeights',
'const std::vector<int>& OpenMM::NoseHooverIntegrator::getAllThermostatedIndividualParticles',
'const std::vector<std::tuple<int, int, double> >& OpenMM::NoseHooverIntegrator::getAllThermostatedPairs',
'virtual void OpenMM::NoseHooverIntegrator::stateChanged'
]
self.hideClasses = ['Kernel', 'KernelImpl', 'KernelFactory', 'ContextImpl', 'SerializationNode', 'SerializationProxy'] self.hideClasses = ['Kernel', 'KernelImpl', 'KernelFactory', 'ContextImpl', 'SerializationNode', 'SerializationProxy']
self.nodeByID={} self.nodeByID={}
...@@ -165,6 +170,7 @@ class CHeaderGenerator(WrapperGenerator): ...@@ -165,6 +170,7 @@ class CHeaderGenerator(WrapperGenerator):
'std::vector< std::string >': 'OpenMM_StringArray', 'std::vector< std::string >': 'OpenMM_StringArray',
'std::vector< Vec3 >': 'OpenMM_Vec3Array', 'std::vector< Vec3 >': 'OpenMM_Vec3Array',
'std::vector< std::pair< int, int > >': 'OpenMM_BondArray', 'std::vector< std::pair< int, int > >': 'OpenMM_BondArray',
'const std::vector< std::pair< int, int > >': 'OpenMM_BondArray',
'std::map< std::string, double >': 'OpenMM_ParameterArray', 'std::map< std::string, double >': 'OpenMM_ParameterArray',
'std::map< std::string, std::string >': 'OpenMM_PropertyArray', 'std::map< std::string, std::string >': 'OpenMM_PropertyArray',
'std::vector< double >': 'OpenMM_DoubleArray', 'std::vector< double >': 'OpenMM_DoubleArray',
......
...@@ -184,7 +184,7 @@ def buildKeywordDictionary(major_version_num=MAJOR_VERSION_NUM, ...@@ -184,7 +184,7 @@ def buildKeywordDictionary(major_version_num=MAJOR_VERSION_NUM,
if not openmm_lib_path: if not openmm_lib_path:
reportError("Set OPENMM_LIB_PATH to point to the lib directory for OpenMM") reportError("Set OPENMM_LIB_PATH to point to the lib directory for OpenMM")
extra_compile_args=[] extra_compile_args=['-std=c++11']
extra_link_args=[] extra_link_args=[]
if platform.system() == "Windows": if platform.system() == "Windows":
define_macros.append( ('WIN32', None) ) define_macros.append( ('WIN32', None) )
......
...@@ -8,7 +8,7 @@ Structures at Stanford, funded under the NIH Roadmap for Medical Research, ...@@ -8,7 +8,7 @@ Structures at Stanford, funded under the NIH Roadmap for Medical Research,
grant U54 GM072970. See https://simtk.org. This code was originally part of grant U54 GM072970. See https://simtk.org. This code was originally part of
the ParmEd program and was ported for use with OpenMM. the ParmEd program and was ported for use with OpenMM.
Copyright (c) 2014-2016 the Authors Copyright (c) 2014-2020 the Authors
Author: Jason M. Swails Author: Jason M. Swails
Contributors: Jing Huang Contributors: Jing Huang
...@@ -41,7 +41,7 @@ import sys ...@@ -41,7 +41,7 @@ import sys
import simtk.openmm as mm import simtk.openmm as mm
from simtk.openmm.vec3 import Vec3 from simtk.openmm.vec3 import Vec3
import simtk.unit as u import simtk.unit as u
from simtk.openmm.app import (forcefield as ff, Topology, element) from simtk.openmm.app import (forcefield as ff, Topology, element, PDBFile)
from simtk.openmm.app.amberprmtopfile import HCT, OBC1, OBC2, GBn, GBn2 from simtk.openmm.app.amberprmtopfile import HCT, OBC1, OBC2, GBn, GBn2
from simtk.openmm.app.internal.customgbforces import (GBSAHCTForce, from simtk.openmm.app.internal.customgbforces import (GBSAHCTForce,
GBSAOBC1Force, GBSAOBC2Force, GBSAGBnForce, GBSAGBn2Force) GBSAOBC1Force, GBSAOBC2Force, GBSAGBnForce, GBSAGBn2Force)
...@@ -164,6 +164,7 @@ class CharmmPsfFile(object): ...@@ -164,6 +164,7 @@ class CharmmPsfFile(object):
CMAP_FORCE_GROUP = 5 CMAP_FORCE_GROUP = 5
NONBONDED_FORCE_GROUP = 6 NONBONDED_FORCE_GROUP = 6
GB_FORCE_GROUP = 6 GB_FORCE_GROUP = 6
DRUDE_FORCE_GROUP = 7
@_catchindexerror @_catchindexerror
def __init__(self, psf_name, periodicBoxVectors=None, unitCellDimensions=None): def __init__(self, psf_name, periodicBoxVectors=None, unitCellDimensions=None):
...@@ -218,6 +219,7 @@ class CharmmPsfFile(object): ...@@ -218,6 +219,7 @@ class CharmmPsfFile(object):
atom_list = AtomList() atom_list = AtomList()
if IsDrudePSF: if IsDrudePSF:
drudeconsts_list = TrackedList() drudeconsts_list = TrackedList()
PDBFile._loadNameReplacementTables()
for i in xrange(natom): for i in xrange(natom):
words = psfsections['NATOM'][1][i].split() words = psfsections['NATOM'][1][i].split()
system = words[1] system = words[1]
...@@ -238,6 +240,12 @@ class CharmmPsfFile(object): ...@@ -238,6 +240,12 @@ class CharmmPsfFile(object):
charge = conv(words[6], float, 'partial charge') charge = conv(words[6], float, 'partial charge')
mass = conv(words[7], float, 'atomic mass') mass = conv(words[7], float, 'atomic mass')
props = words[8:] props = words[8:]
if resname in PDBFile._residueNameReplacements:
resname = PDBFile._residueNameReplacements[resname]
if resname in PDBFile._atomNameReplacements:
atomReplacements = PDBFile._atomNameReplacements[resname]
if name in atomReplacements:
name = atomReplacements[name]
atom = residue_list.add_atom(system, resid, resname, name, atom = residue_list.add_atom(system, resid, resname, name,
attype, charge, mass, inscode, props) attype, charge, mass, inscode, props)
atom_list.append(atom) atom_list.append(atom)
...@@ -466,6 +474,37 @@ class CharmmPsfFile(object): ...@@ -466,6 +474,37 @@ class CharmmPsfFile(object):
else: else:
self.box_vectors = periodicBoxVectors self.box_vectors = periodicBoxVectors
def _build_exclusion_list(self):
pair_12_set = set()
pair_13_set = set()
pair_14_set = set()
for bond in self.bond_list:
a1, a2 = bond.atom1, bond.atom2
pair = (min(a1.idx, a2.idx), max(a1.idx, a2.idx),)
pair_12_set.add(pair)
for bond in self.bond_list:
a2, a3 = bond.atom1, bond.atom2
for a1 in a2.bond_partners:
pair = (min(a1.idx, a3.idx), max(a1.idx, a3.idx),)
if a1 != a3:
pair_13_set.add(pair)
for a4 in a3.bond_partners:
pair = (min(a2.idx, a4.idx), max(a2.idx, a4.idx),)
if a2 != a4:
pair_13_set.add(pair)
for bond in self.bond_list:
a2, a3 = bond.atom1, bond.atom2
for a1 in a2.bond_partners:
for a4 in a3.bond_partners:
pair = (min(a1.idx, a4.idx), max(a1.idx, a4.idx),)
if a1 != a4:
pair_14_set.add(pair)
# in case there are 3,4,5-member rings
self.pair_12_list = list(sorted(pair_12_set))
self.pair_13_list = list(sorted(pair_13_set - pair_12_set))
self.pair_14_list = list(sorted(pair_14_set - pair_13_set.union(pair_12_set)))
@staticmethod @staticmethod
def _convert(string, type, message): def _convert(string, type, message):
"""Converts a string to a specific type, making sure to raise """Converts a string to a specific type, making sure to raise
...@@ -1320,31 +1359,29 @@ class CharmmPsfFile(object): ...@@ -1320,31 +1359,29 @@ class CharmmPsfFile(object):
# now, add the actual force to the system # now, add the actual force to the system
system.addForce(nbtforce) system.addForce(nbtforce)
# build 1-2, 1-3 and 1-4 pairs from connectivity
if verbose:
print('Build exclusion list...')
self._build_exclusion_list()
if verbose:
print('\tNumber of 1-2 pairs: %i' % len(self.pair_12_list))
print('\tNumber of 1-3 pairs: %i' % len(self.pair_13_list))
print('\tNumber of 1-4 pairs: %i' % len(self.pair_14_list))
# Add 1-4 interactions # Add 1-4 interactions
excluded_atom_pairs = set() # save these pairs so we don't zero them out
sigma_scale = 2**(-1/6) sigma_scale = 2**(-1/6)
nbxmod = abs(params.nbxmod) nbxmod = abs(params.nbxmod)
if nbxmod == 4:
for ia1, ia4 in self.pair_14_list:
force.addException(ia1, ia4, 0.0, 0.1, 0.0)
if nbxmod == 5: if nbxmod == 5:
for tor in self.dihedral_parameter_list: for ia1, ia4 in self.pair_14_list:
# First check to see if atoms 1 and 4 are already excluded because atom1 = self.atom_list[ia1]
# they are 1-2 or 1-3 pairs (would happen in 6-member rings or atom4 = self.atom_list[ia4]
# fewer). Then check that they're not already added as exclusions charge_prod = (atom1.charge * atom4.charge)
if tor.atom1 in tor.atom4.bond_partners: continue epsilon = sqrt(abs(atom1.type.epsilon_14 * atom4.type.epsilon_14)) * ene_conv
if tor.atom1 in tor.atom4.angle_partners: continue sigma = (atom1.type.rmin_14 + atom4.type.rmin_14) * (length_conv * sigma_scale)
key = min((tor.atom1.idx, tor.atom4.idx), force.addException(ia1, ia4, charge_prod, sigma, epsilon)
(tor.atom4.idx, tor.atom1.idx))
if key in excluded_atom_pairs: continue # multiterm...
charge_prod = (tor.atom1.charge * tor.atom4.charge)
epsilon = (sqrt(abs(tor.atom1.type.epsilon_14) * ene_conv *
abs(tor.atom4.type.epsilon_14) * ene_conv))
sigma = (tor.atom1.type.rmin_14 + tor.atom4.type.rmin_14) * (
length_conv * sigma_scale)
force.addException(tor.atom1.idx, tor.atom4.idx,
charge_prod, sigma, epsilon)
excluded_atom_pairs.add(
min((tor.atom1.idx, tor.atom4.idx),
(tor.atom4.idx, tor.atom1.idx))
)
# Add excluded atoms # Add excluded atoms
# Drude and lonepairs will be excluded based on their parent atoms # Drude and lonepairs will be excluded based on their parent atoms
...@@ -1368,33 +1405,24 @@ class CharmmPsfFile(object): ...@@ -1368,33 +1405,24 @@ class CharmmPsfFile(object):
for i in range(len(excludeterm)): for i in range(len(excludeterm)):
for j in range(i): for j in range(i):
force.addException(excludeterm[j], excludeterm[i], 0.0, 0.1, 0.0) force.addException(excludeterm[j], excludeterm[i], 0.0, 0.1, 0.0)
# Exclude all bonds and angles, as well as the lonepair/Drude attached onto them # Exclude 1-2 and 1-3 pairs as well as the lonepair/Drude attached onto them
for atom in self.atom_list: if nbxmod > 1:
if nbxmod > 1: for ia1, ia2 in self.pair_12_list:
for atom2 in atom.bond_partners: for excludeatom in [ia1]+parent_exclude_list[ia1]:
if atom2.idx > atom.idx: for excludeatom2 in [ia2]+parent_exclude_list[ia2]:
for excludeatom in [atom.idx]+parent_exclude_list[atom.idx]: force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0)
for excludeatom2 in [atom2.idx]+parent_exclude_list[atom2.idx]: if nbxmod > 2:
force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0) for ia1, ia3 in self.pair_13_list:
if nbxmod > 2: for excludeatom in [ia1]+parent_exclude_list[ia1]:
for atom2 in atom.angle_partners: for excludeatom2 in [ia3]+parent_exclude_list[ia3]:
if atom2.idx > atom.idx: force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0)
for excludeatom in [atom.idx]+parent_exclude_list[atom.idx]:
for excludeatom2 in [atom2.idx]+parent_exclude_list[atom2.idx]:
force.addException(excludeatom, excludeatom2, 0.0, 0.1, 0.0)
if nbxmod > 3:
for atom2 in atom.dihedral_partners:
if atom2.idx <= atom.idx: continue
if ((atom.idx, atom2.idx) in excluded_atom_pairs):
continue
force.addException(atom.idx, atom2.idx, 0.0, 0.1, 0.0)
system.addForce(force) system.addForce(force)
# Add Drude particles (Drude force) # Add Drude particles (Drude force)
if has_drude_particle: if has_drude_particle:
if verbose: print('Adding Drude force and Thole screening...') if verbose: print('Adding Drude force and Thole screening...')
drudeforce = mm.DrudeForce() drudeforce = mm.DrudeForce()
drudeforce.setForceGroup(7) drudeforce.setForceGroup(self.DRUDE_FORCE_GROUP)
for pair in self.drudepair_list: for pair in self.drudepair_list:
parentatom=pair[0] parentatom=pair[0]
drudeatom=pair[1] drudeatom=pair[1]
...@@ -1423,24 +1451,16 @@ class CharmmPsfFile(object): ...@@ -1423,24 +1451,16 @@ class CharmmPsfFile(object):
particleMap = {} particleMap = {}
for i in range(drudeforce.getNumParticles()): for i in range(drudeforce.getNumParticles()):
particleMap[drudeforce.getParticleParameters(i)[0]] = i particleMap[drudeforce.getParticleParameters(i)[0]] = i
for bond in self.bond_list: # Apply thole screening for 1-2 and 1-3 pairs
alpha1 = self.drudeconsts_list[bond.atom1.idx][0] for ia1, ia2 in self.pair_12_list + self.pair_13_list:
alpha2 = self.drudeconsts_list[bond.atom2.idx][0] alpha1 = self.drudeconsts_list[ia1][0]
if abs(alpha1) > TINY and abs(alpha2) > TINY: # both are Drude parent atoms alpha2 = self.drudeconsts_list[ia2][0]
thole1 = self.drudeconsts_list[bond.atom1.idx][1]
thole2 = self.drudeconsts_list[bond.atom2.idx][1]
drude1 = bond.atom1.idx + 1 # CHARMM psf has hard-coded rule that the Drude is next to its parent
drude2 = bond.atom2.idx + 1
drudeforce.addScreenedPair(particleMap[drude1], particleMap[drude2], thole1+thole2)
for ang in self.angle_list:
alpha1 = self.drudeconsts_list[ang.atom1.idx][0]
alpha2 = self.drudeconsts_list[ang.atom3.idx][0]
if abs(alpha1) > TINY and abs(alpha2) > TINY: # both are Drude parent atoms if abs(alpha1) > TINY and abs(alpha2) > TINY: # both are Drude parent atoms
thole1 = self.drudeconsts_list[ang.atom1.idx][1] thole1 = self.drudeconsts_list[ia1][1]
thole2 = self.drudeconsts_list[ang.atom3.idx][1] thole2 = self.drudeconsts_list[ia2][1]
drude1 = ang.atom1.idx + 1 # CHARMM psf has hard-coded rule that the Drude is next to its parent drude1 = ia1 + 1 # CHARMM psf has hard-coded rule that the Drude is next to its parent
drude2 = ang.atom3.idx + 1 drude2 = ia2 + 1
drudeforce.addScreenedPair(particleMap[drude1], particleMap[drude2], thole1+thole2) drudeforce.addScreenedPair(particleMap[drude1], particleMap[drude2], thole1+thole2)
# If we needed a CustomNonbondedForce, map all of the exceptions from # If we needed a CustomNonbondedForce, map all of the exceptions from
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -2983,7 +2983,7 @@ ...@@ -2983,7 +2983,7 @@
<ExternalBond from="14" /> <ExternalBond from="14" />
<ExternalBond from="0" /> <ExternalBond from="0" />
</Residue> </Residue>
<Residue name="HOH"> <Residue name="HOH" rigidWater="false">
<Atom name="H1" type="403" /> <Atom name="H1" type="403" />
<Atom name="H2" type="403" /> <Atom name="H2" type="403" />
<Atom name="O" type="402" /> <Atom name="O" type="402" />
......
...@@ -1756,7 +1756,7 @@ ...@@ -1756,7 +1756,7 @@
<ExternalBond from="14" /> <ExternalBond from="14" />
<ExternalBond from="0" /> <ExternalBond from="0" />
</Residue> </Residue>
<Residue name="HOH"> <Residue name="HOH" rigidWater="false">
<Atom name="H1" type="248" /> <Atom name="H1" type="248" />
<Atom name="H2" type="248" /> <Atom name="H2" type="248" />
<Atom name="O" type="247" /> <Atom name="O" type="247" />
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<Type name="381" class="74" element="H" mass="1.008"/> <Type name="381" class="74" element="H" mass="1.008"/>
</AtomTypes> </AtomTypes>
<Residues> <Residues>
<Residue name="HOH"> <Residue name="HOH" rigidWater="false">
<Atom name="H1" type="381"/> <Atom name="H1" type="381"/>
<Atom name="H2" type="381"/> <Atom name="H2" type="381"/>
<Atom name="O" type="380"/> <Atom name="O" type="380"/>
......
...@@ -267,6 +267,8 @@ class ForceField(object): ...@@ -267,6 +267,8 @@ class ForceField(object):
template = ForceField._TemplateData(resName) template = ForceField._TemplateData(resName)
if 'override' in residue.attrib: if 'override' in residue.attrib:
template.overrideLevel = int(residue.attrib['override']) template.overrideLevel = int(residue.attrib['override'])
if 'rigidWater' in residue.attrib:
template.rigidWater = (residue.attrib['rigidWater'].lower() == 'true')
atomIndices = template.atomIndices atomIndices = template.atomIndices
for ia, atom in enumerate(residue.findall('Atom')): for ia, atom in enumerate(residue.findall('Atom')):
params = {} params = {}
...@@ -597,6 +599,7 @@ class ForceField(object): ...@@ -597,6 +599,7 @@ class ForceField(object):
self.bonds = [] self.bonds = []
self.externalBonds = [] self.externalBonds = []
self.overrideLevel = 0 self.overrideLevel = 0
self.rigidWater = True
def getAtomIndexByName(self, atom_name): def getAtomIndexByName(self, atom_name):
"""Look up an atom index by atom name, providing a helpful error message if not found.""" """Look up an atom index by atom name, providing a helpful error message if not found."""
...@@ -637,7 +640,7 @@ class ForceField(object): ...@@ -637,7 +640,7 @@ class ForceField(object):
def areParametersIdentical(self, template2, matchingAtoms, matchingAtoms2): def areParametersIdentical(self, template2, matchingAtoms, matchingAtoms2):
"""Get whether this template and another one both assign identical atom types and parameters to all atoms. """Get whether this template and another one both assign identical atom types and parameters to all atoms.
Parameters Parameters
---------- ----------
template2: _TemplateData template2: _TemplateData
...@@ -1054,7 +1057,7 @@ class ForceField(object): ...@@ -1054,7 +1057,7 @@ class ForceField(object):
return [templates, unique_unmatched_residues] return [templates, unique_unmatched_residues]
def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer, def createSystem(self, topology, nonbondedMethod=NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, removeCMMotion=True, hydrogenMass=None, residueTemplates=dict(), constraints=None, rigidWater=None, removeCMMotion=True, hydrogenMass=None, residueTemplates=dict(),
ignoreExternalBonds=False, switchDistance=None, flexibleConstraints=False, **args): ignoreExternalBonds=False, switchDistance=None, flexibleConstraints=False, **args):
"""Construct an OpenMM System representing a Topology with this force field. """Construct an OpenMM System representing a Topology with this force field.
...@@ -1070,9 +1073,10 @@ class ForceField(object): ...@@ -1070,9 +1073,10 @@ class ForceField(object):
constraints : object=None constraints : object=None
Specifies which bonds and angles should be implemented with constraints. Specifies which bonds and angles should be implemented with constraints.
Allowed values are None, HBonds, AllBonds, or HAngles. Allowed values are None, HBonds, AllBonds, or HAngles.
rigidWater : boolean=True rigidWater : boolean=None
If true, water molecules will be fully rigid regardless of the value If true, water molecules will be fully rigid regardless of the value
passed for the constraints argument passed for the constraints argument. If None (the default), it uses the
default behavior for this force field's water model.
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
...@@ -1112,6 +1116,7 @@ class ForceField(object): ...@@ -1112,6 +1116,7 @@ class ForceField(object):
data.atoms = list(topology.atoms()) data.atoms = list(topology.atoms())
for atom in data.atoms: for atom in data.atoms:
data.excludeAtomWith.append([]) data.excludeAtomWith.append([])
rigidResidue = [False]*topology.getNumResidues()
# Make a list of all bonds # Make a list of all bonds
...@@ -1149,6 +1154,13 @@ class ForceField(object): ...@@ -1149,6 +1154,13 @@ class ForceField(object):
unmatchedResidues.append(res) unmatchedResidues.append(res)
else: else:
data.recordMatchedAtomParameters(res, template, matches) data.recordMatchedAtomParameters(res, template, matches)
if res.name == 'HOH':
# Determine whether this should be a rigid water.
if rigidWater is None and template is not None:
rigidResidue[res.index] = template.rigidWater
elif rigidWater:
rigidResidue[res.index] = True
# Try to apply patches to find matches for any unmatched residues. # Try to apply patches to find matches for any unmatched residues.
...@@ -1272,12 +1284,11 @@ class ForceField(object): ...@@ -1272,12 +1284,11 @@ class ForceField(object):
atom1 = data.atoms[bond.atom1] atom1 = data.atoms[bond.atom1]
atom2 = data.atoms[bond.atom2] atom2 = data.atoms[bond.atom2]
bond.isConstrained = atom1.element is elem.hydrogen or atom2.element is elem.hydrogen bond.isConstrained = atom1.element is elem.hydrogen or atom2.element is elem.hydrogen
if rigidWater: 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] if rigidResidue[atom1.residue.index] and rigidResidue[atom2.residue.index]:
if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH': bond.isConstrained = True
bond.isConstrained = True
# Identify angles that should be implemented with constraints # Identify angles that should be implemented with constraints
...@@ -1294,14 +1305,13 @@ class ForceField(object): ...@@ -1294,14 +1305,13 @@ class ForceField(object):
data.isAngleConstrained.append(numH == 2 or (numH == 1 and atom2.element is elem.oxygen)) 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: for i in range(len(data.angles)):
for i in range(len(data.angles)): angle = data.angles[i]
angle = data.angles[i] atom1 = data.atoms[angle[0]]
atom1 = data.atoms[angle[0]] atom2 = data.atoms[angle[1]]
atom2 = data.atoms[angle[1]] atom3 = data.atoms[angle[2]]
atom3 = data.atoms[angle[2]] if rigidResidue[atom1.residue.index] and rigidResidue[atom2.residue.index] and rigidResidue[atom3.residue.index]:
if atom1.residue.name == 'HOH' and atom2.residue.name == 'HOH' and atom3.residue.name == 'HOH': data.isAngleConstrained[i] = True
data.isAngleConstrained[i] = True
# Add virtual sites # Add virtual sites
...@@ -1844,6 +1854,16 @@ def _matchImproper(data, torsion, generator): ...@@ -1844,6 +1854,16 @@ def _matchImproper(data, torsion, generator):
(a2, a3) = (a3, a2) (a2, a3) = (a3, a2)
match = (a2, a3, torsion[0], a4, tordef) match = (a2, a3, torsion[0], a4, tordef)
break break
elif tordef.ordering == 'smirnoff':
# topology atom indexes
a1 = torsion[0]
a2 = torsion[t2[1]]
a3 = torsion[t3[1]]
a4 = torsion[t4[1]]
# enforce exact match
match = (a1, a2, a3, a4, tordef)
break
return match return match
...@@ -2035,7 +2055,7 @@ class PeriodicTorsionGenerator(object): ...@@ -2035,7 +2055,7 @@ class PeriodicTorsionGenerator(object):
def registerImproperTorsion(self, parameters, ordering='default'): 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']: if ordering in ['default', 'charmm', 'amber', 'smirnoff']:
torsion.ordering = ordering torsion.ordering = ordering
else: else:
raise ValueError('Illegal ordering type %s for improper torsion %s' % (ordering, torsion)) raise ValueError('Illegal ordering type %s for improper torsion %s' % (ordering, torsion))
...@@ -2119,7 +2139,13 @@ class PeriodicTorsionGenerator(object): ...@@ -2119,7 +2139,13 @@ class PeriodicTorsionGenerator(object):
(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)):
if tordef.k[i] != 0: if tordef.k[i] != 0:
force.addTorsion(a1, a2, a3, a4, tordef.periodicity[i], tordef.phase[i], tordef.k[i]) if tordef.ordering == 'smirnoff':
# Add all torsions in trefoil
force.addTorsion(a1, a2, a3, a4, tordef.periodicity[i], tordef.phase[i], tordef.k[i])
force.addTorsion(a1, a3, a4, a2, tordef.periodicity[i], tordef.phase[i], tordef.k[i])
force.addTorsion(a1, a4, a2, a3, tordef.periodicity[i], tordef.phase[i], tordef.k[i])
else:
force.addTorsion(a1, a2, a3, a4, tordef.periodicity[i], tordef.phase[i], tordef.k[i])
parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement parsers["PeriodicTorsionForce"] = PeriodicTorsionGenerator.parseElement
......
...@@ -592,7 +592,8 @@ class GromacsTopFile(object): ...@@ -592,7 +592,8 @@ class GromacsTopFile(object):
top.addBond(atoms[int(fields[0])-1], atoms[int(fields[1])-1]) top.addBond(atoms[int(fields[0])-1], atoms[int(fields[1])-1])
def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer, def createSystem(self, nonbondedMethod=ff.NoCutoff, nonbondedCutoff=1.0*unit.nanometer,
constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None): constraints=None, rigidWater=True, implicitSolvent=None, soluteDielectric=1.0, solventDielectric=78.5,
ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None, switchDistance=None):
"""Construct an OpenMM System representing the topology described by this """Construct an OpenMM System representing the topology described by this
top file. top file.
...@@ -627,6 +628,9 @@ class GromacsTopFile(object): ...@@ -627,6 +628,9 @@ class GromacsTopFile(object):
The mass to use for hydrogen atoms bound to heavy atoms. Any mass The mass to use for hydrogen atoms bound to heavy atoms. Any mass
added to a hydrogen is subtracted from the heavy atom to keep their added to a hydrogen is subtracted from the heavy atom to keep their
total mass the same. total mass the same.
switchDistance : float=None
The distance at which the potential energy switching function is turned on for
Lennard-Jones interactions. If this is None, no switching function will be used.
Returns Returns
------- -------
...@@ -1091,6 +1095,9 @@ class GromacsTopFile(object): ...@@ -1091,6 +1095,9 @@ class GromacsTopFile(object):
nb.setNonbondedMethod(methodMap[nonbondedMethod]) nb.setNonbondedMethod(methodMap[nonbondedMethod])
nb.setCutoffDistance(nonbondedCutoff) nb.setCutoffDistance(nonbondedCutoff)
nb.setEwaldErrorTolerance(ewaldErrorTolerance) nb.setEwaldErrorTolerance(ewaldErrorTolerance)
if switchDistance is not None:
nb.setUseSwitchingFunction(True)
nb.setSwitchingDistance(switchDistance)
if self._defaults[1] in ('1', '3'): if self._defaults[1] in ('1', '3'):
methodMap = {ff.NoCutoff:mm.CustomNonbondedForce.NoCutoff, methodMap = {ff.NoCutoff:mm.CustomNonbondedForce.NoCutoff,
ff.CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic, ff.CutoffNonPeriodic:mm.CustomNonbondedForce.CutoffNonPeriodic,
...@@ -1100,6 +1107,9 @@ class GromacsTopFile(object): ...@@ -1100,6 +1107,9 @@ class GromacsTopFile(object):
ff.LJPME:mm.CustomNonbondedForce.CutoffPeriodic} ff.LJPME:mm.CustomNonbondedForce.CutoffPeriodic}
lj.setNonbondedMethod(methodMap[nonbondedMethod]) lj.setNonbondedMethod(methodMap[nonbondedMethod])
lj.setCutoffDistance(nonbondedCutoff) lj.setCutoffDistance(nonbondedCutoff)
if switchDistance is not None:
lj.setUseSwitchingFunction(True)
lj.setSwitchingDistance(switchDistance)
if has_nbfix_terms: if has_nbfix_terms:
if self._defaults[1] != '2': if self._defaults[1] != '2':
...@@ -1175,6 +1185,9 @@ class GromacsTopFile(object): ...@@ -1175,6 +1185,9 @@ class GromacsTopFile(object):
cforce.setCutoffDistance(nonbondedCutoff) cforce.setCutoffDistance(nonbondedCutoff)
else: else:
raise ValueError('Unrecognized nonbonded method') raise ValueError('Unrecognized nonbonded method')
if switchDistance is not None:
cforce.setUseSwitchingFunction(True)
cforce.setSwitchingDistance(switchDistance)
for i in lj_idx_list: for i in lj_idx_list:
cforce.addParticle((i - 1,)) # adjust for indexing from 0 cforce.addParticle((i - 1,)) # adjust for indexing from 0
......
...@@ -1004,7 +1004,7 @@ class Modeller(object): ...@@ -1004,7 +1004,7 @@ class Modeller(object):
del context del context
return actualVariants return actualVariants
def addExtraParticles(self, forcefield): def addExtraParticles(self, forcefield, ignoreExternalBonds=False):
"""Add missing extra particles to the model that are required by a force """Add missing extra particles to the model that are required by a force
field. field.
...@@ -1023,6 +1023,10 @@ class Modeller(object): ...@@ -1023,6 +1023,10 @@ class Modeller(object):
---------- ----------
forcefield : ForceField forcefield : ForceField
the ForceField defining what extra particles should be present the ForceField defining what extra particles should be present
ignoreExternalBonds : boolean=False
If true, ignore external bonds when matching residues to templates.
This is useful when the Topology represents one piece of a larger
molecule, so chains are not terminated properly.
""" """
# Create copies of all residue templates that have had all extra points removed. # Create copies of all residue templates that have had all extra points removed.
...@@ -1090,7 +1094,7 @@ class Modeller(object): ...@@ -1090,7 +1094,7 @@ class Modeller(object):
signature = _createResidueSignature([atom.element for atom in residue.atoms()]) signature = _createResidueSignature([atom.element for atom in residue.atoms()])
if signature in forcefield._templateSignatures: if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]: for t in forcefield._templateSignatures[signature]:
if compiled.matchResidueToTemplate(residue, t, bondedToAtom, False) is not None: if compiled.matchResidueToTemplate(residue, t, bondedToAtom, ignoreExternalBonds) is not None:
matchFound = True matchFound = True
if matchFound: if matchFound:
# Just copy the residue over. # Just copy the residue over.
...@@ -1109,7 +1113,7 @@ class Modeller(object): ...@@ -1109,7 +1113,7 @@ class Modeller(object):
if signature in forcefield._templateSignatures: if signature in forcefield._templateSignatures:
for t in forcefield._templateSignatures[signature]: for t in forcefield._templateSignatures[signature]:
if t in templatesNoEP: if t in templatesNoEP:
matches = compiled.matchResidueToTemplate(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, False) matches = compiled.matchResidueToTemplate(residueNoEP, templatesNoEP[t], bondedToAtomNoEP, ignoreExternalBonds)
if matches is not None: if matches is not None:
template = t; template = t;
# Record the corresponding atoms. # Record the corresponding atoms.
...@@ -1255,9 +1259,9 @@ class Modeller(object): ...@@ -1255,9 +1259,9 @@ class Modeller(object):
adds whole copies of the pre-equilibrated membrane patch, so the box dimensions will always be adds whole copies of the pre-equilibrated membrane patch, so the box dimensions will always be
integer multiples of the patch size. That may lead to a larger membrane than what you requested. integer multiples of the patch size. That may lead to a larger membrane than what you requested.
This method has built in support for POPC and POPE lipids. You can also build other types of This method has built in support for POPC, POPE, DLPC, DLPE, DMPC, DOPC and DPPC lipids.
membranes by providing a pre-equilibrated, solvated membrane patch that can be tiled in the XY You can also build other types of membranes by providing a pre-equilibrated, solvated membrane patch
plane to form the membrane. that can be tiled in the XY plane to form the membrane.
Parameters Parameters
---------- ----------
...@@ -1285,7 +1289,7 @@ class Modeller(object): ...@@ -1285,7 +1289,7 @@ class Modeller(object):
""" """
if 'topology' in dir(lipidType) and 'positions' in dir(lipidType): if 'topology' in dir(lipidType) and 'positions' in dir(lipidType):
patch = lipidType patch = lipidType
elif lipidType.upper() in ('POPC', 'POPE'): elif lipidType.upper() in ('POPC', 'POPE', 'DLPC', 'DLPE', 'DMPC', 'DOPC', 'DPPC'):
patch = PDBFile(os.path.join(os.path.dirname(__file__), 'data', lipidType.upper()+'.pdb')) patch = PDBFile(os.path.join(os.path.dirname(__file__), 'data', lipidType.upper()+'.pdb'))
else: else:
raise ValueError('Unsupported lipid type: '+lipidType) raise ValueError('Unsupported lipid type: '+lipidType)
......
...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of ...@@ -6,7 +6,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
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) 2015-2019 Stanford University and the Authors. Portions copyright (c) 2015-2020 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Jason Swails Contributors: Jason Swails
...@@ -98,6 +98,16 @@ class PDBxFile(object): ...@@ -98,6 +98,16 @@ class PDBxFile(object):
chainIdCol = atomData.getAttributeIndex('auth_asym_id') chainIdCol = atomData.getAttributeIndex('auth_asym_id')
if chainIdCol == -1: if chainIdCol == -1:
chainIdCol = atomData.getAttributeIndex('label_asym_id') chainIdCol = atomData.getAttributeIndex('label_asym_id')
altChainIdCol = -1
else:
altChainIdCol = atomData.getAttributeIndex('label_asym_id')
if altChainIdCol != -1:
# Figure out which column is best to use for chain IDs.
idSet = set(row[chainIdCol] for row in atomData.getRowList())
altIdSet = set(row[altChainIdCol] for row in atomData.getRowList())
if len(altIdSet) > len(idSet):
chainIdCol, altChainIdCol = (altChainIdCol, chainIdCol)
elementCol = atomData.getAttributeIndex('type_symbol') elementCol = atomData.getAttributeIndex('type_symbol')
altIdCol = atomData.getAttributeIndex('label_alt_id') altIdCol = atomData.getAttributeIndex('label_alt_id')
modelCol = atomData.getAttributeIndex('pdbx_PDB_model_num') modelCol = atomData.getAttributeIndex('pdbx_PDB_model_num')
...@@ -105,7 +115,9 @@ class PDBxFile(object): ...@@ -105,7 +115,9 @@ class PDBxFile(object):
yCol = atomData.getAttributeIndex('Cartn_y') yCol = atomData.getAttributeIndex('Cartn_y')
zCol = atomData.getAttributeIndex('Cartn_z') zCol = atomData.getAttributeIndex('Cartn_z')
lastChainId = None lastChainId = None
lastAltChainId = None
lastResId = None lastResId = None
lastInsertionCode = ''
atomTable = {} atomTable = {}
atomsInResidue = set() atomsInResidue = set()
models = [] models = []
...@@ -123,17 +135,26 @@ class PDBxFile(object): ...@@ -123,17 +135,26 @@ class PDBxFile(object):
if modelIndex == 0: if modelIndex == 0:
# This row defines a new atom. # This row defines a new atom.
if lastChainId != row[chainIdCol]: if resInsertionCol == -1:
insertionCode = ''
else:
insertionCode = row[resInsertionCol]
if insertionCode in ('.', '?'):
insertionCode = ''
if lastChainId != row[chainIdCol] or (altChainIdCol != -1 and lastAltChainId != row[altChainIdCol]):
# The start of a new chain. # The start of a new chain.
chain = top.addChain(row[chainIdCol]) chain = top.addChain(row[chainIdCol])
lastChainId = row[chainIdCol] lastChainId = row[chainIdCol]
lastResId = None lastResId = None
if lastResId != row[resNumCol] or lastChainId != row[chainIdCol] or (lastResId == '.' and row[atomNameCol] in atomsInResidue): if altChainIdCol != -1:
lastAltChainId = row[altChainIdCol]
if lastResId != row[resNumCol] or lastChainId != row[chainIdCol] or lastInsertionCode != insertionCode or (lastResId == '.' and row[atomNameCol] in atomsInResidue):
# The start of a new residue. # The start of a new residue.
resId = (None if resNumCol == -1 else row[resNumCol]) resId = (None if resNumCol == -1 else row[resNumCol])
resIC = ('' if resInsertionCol == -1 else row[resInsertionCol]) resIC = insertionCode
res = top.addResidue(row[resNameCol], chain, resId, resIC) res = top.addResidue(row[resNameCol], chain, resId, resIC)
lastResId = row[resNumCol] lastResId = row[resNumCol]
lastInsertionCode = insertionCode
atomsInResidue.clear() atomsInResidue.clear()
element = None element = None
try: try:
...@@ -386,6 +407,8 @@ class PDBxFile(object): ...@@ -386,6 +407,8 @@ class PDBxFile(object):
raise ValueError('Particle position is NaN') raise ValueError('Particle position is NaN')
if any(math.isinf(norm(pos)) for pos in positions): if any(math.isinf(norm(pos)) for pos in positions):
raise ValueError('Particle position is infinite') raise ValueError('Particle position is infinite')
nonHeterogens = PDBFile._standardResidues[:]
nonHeterogens.remove('HOH')
atomIndex = 1 atomIndex = 1
posIndex = 0 posIndex = 0
for (chainIndex, chain) in enumerate(topology.chains()): for (chainIndex, chain) in enumerate(topology.chains()):
...@@ -401,14 +424,18 @@ class PDBxFile(object): ...@@ -401,14 +424,18 @@ class PDBxFile(object):
else: else:
resId = resIndex + 1 resId = resIndex + 1
resIC = '.' resIC = '.'
if res.name in nonHeterogens:
recordName = "ATOM"
else:
recordName = "HETATM"
for atom in res.atoms(): for atom in res.atoms():
coords = positions[posIndex] coords = positions[posIndex]
if atom.element is not None: if atom.element is not None:
symbol = atom.element.symbol symbol = atom.element.symbol
else: else:
symbol = '?' symbol = '?'
line = "ATOM %5d %-3s %-4s . %-4s %s ? %5s %s %10.4f %10.4f %10.4f 0.0 0.0 ? ? ? ? ? . %5s %4s %s %4s %5d" line = "%s %5d %-3s %-4s . %-4s %s ? %5s %s %10.4f %10.4f %10.4f 0.0 0.0 ? ? ? ? ? . %5s %4s %s %4s %5d"
print(line % (atomIndex, symbol, atom.name, res.name, chainName, resId, resIC, coords[0], coords[1], coords[2], print(line % (recordName, atomIndex, symbol, atom.name, res.name, chainName, resId, resIC, coords[0], coords[1], coords[2],
resId, res.name, chainName, atom.name, modelIndex), file=file) resId, res.name, chainName, atom.name, modelIndex), file=file)
posIndex += 1 posIndex += 1
atomIndex += 1 atomIndex += 1
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