Commit 59bccb15 authored by peastman's avatar peastman
Browse files

Merge branch 'master' into pthreads

Conflicts:
	libraries/pthreads/include/pthread.h
parents 6cf75568 4bdbcf4d
...@@ -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) 2013-2014 Stanford University and the Authors. * * Portions copyright (c) 2013-2015 Stanford University and the Authors. *
* Authors: Peter Eastman * * Authors: Peter Eastman *
* Contributors: * * Contributors: *
* * * *
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
#include "openmm/internal/vectorize.h" #include "openmm/internal/vectorize.h"
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <sstream>
using namespace OpenMM; using namespace OpenMM;
using namespace std; using namespace std;
...@@ -353,6 +354,9 @@ static void* threadBody(void* args) { ...@@ -353,6 +354,9 @@ static void* threadBody(void* args) {
void CpuCalcPmeReciprocalForceKernel::initialize(int xsize, int ysize, int zsize, int numParticles, double alpha) { void CpuCalcPmeReciprocalForceKernel::initialize(int xsize, int ysize, int zsize, int numParticles, double alpha) {
if (!hasInitializedThreads) { if (!hasInitializedThreads) {
numThreads = getNumProcessors(); numThreads = getNumProcessors();
char* threadsEnv = getenv("OPENMM_CPU_THREADS");
if (threadsEnv != NULL)
stringstream(threadsEnv) >> numThreads;
fftwf_init_threads(); fftwf_init_threads();
hasInitializedThreads = true; hasInitializedThreads = true;
} }
......
...@@ -178,6 +178,8 @@ void verifyDerivative(const string& expression, const string& expectedDeriv) { ...@@ -178,6 +178,8 @@ void verifyDerivative(const string& expression, const string& expectedDeriv) {
verifySameValue(computed, expected, 2.0, 3.0); verifySameValue(computed, expected, 2.0, 3.0);
verifySameValue(computed, expected, -2.0, 3.0); verifySameValue(computed, expected, -2.0, 3.0);
verifySameValue(computed, expected, 2.0, -3.0); verifySameValue(computed, expected, 2.0, -3.0);
verifySameValue(computed, expected, 0.0, -3.0);
verifySameValue(computed, expected, 2.0, 0.0);
} }
/** /**
...@@ -249,6 +251,8 @@ int main() { ...@@ -249,6 +251,8 @@ int main() {
verifyEvaluation("step(x-3)+y*step(x)", 2.0, 3.0, 3.0); verifyEvaluation("step(x-3)+y*step(x)", 2.0, 3.0, 3.0);
verifyEvaluation("floor(x)", -2.1, 3.0, -3.0); verifyEvaluation("floor(x)", -2.1, 3.0, -3.0);
verifyEvaluation("ceil(x)", -2.1, 3.0, -2.0); verifyEvaluation("ceil(x)", -2.1, 3.0, -2.0);
verifyEvaluation("select(x, 1.0, y)", 0.3, 2.0, 1.0);
verifyEvaluation("select(x, 1.0, y)", 0.0, 2.0, 2.0);
verifyInvalidExpression("1..2"); verifyInvalidExpression("1..2");
verifyInvalidExpression("1*(2+3"); verifyInvalidExpression("1*(2+3");
verifyInvalidExpression("5++4"); verifyInvalidExpression("5++4");
...@@ -282,6 +286,7 @@ int main() { ...@@ -282,6 +286,7 @@ int main() {
verifyDerivative("max(5, x^2)", "(1-step(5-x^2))*2*x"); verifyDerivative("max(5, x^2)", "(1-step(5-x^2))*2*x");
verifyDerivative("abs(3*x)", "step(3*x)*3+(1-step(3*x))*-3"); verifyDerivative("abs(3*x)", "step(3*x)*3+(1-step(3*x))*-3");
verifyDerivative("floor(x)+0.5*x*ceil(x)", "0.5*ceil(x)"); verifyDerivative("floor(x)+0.5*x*ceil(x)", "0.5*ceil(x)");
verifyDerivative("select(x, x^2, 3*x)", "select(x, 2*x, 3)");
testCustomFunction("custom(x, y)/2", "x*y"); testCustomFunction("custom(x, y)/2", "x*y");
testCustomFunction("custom(x^2, 1)+custom(2, y-1)", "2*x^2+4*(y-1)"); testCustomFunction("custom(x^2, 1)+custom(2, y-1)", "2*x^2+4*(y-1)");
cout << Parser::parse("x*x").optimize() << endl; cout << Parser::parse("x*x").optimize() << endl;
......
...@@ -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) 2014 Stanford University and the Authors. * * Portions copyright (c) 2014-2015 Stanford University and the Authors. *
* Authors: Peter Eastman * * Authors: Peter Eastman *
* Contributors: * * Contributors: *
* * * *
...@@ -148,6 +148,7 @@ void testMathFunctions() { ...@@ -148,6 +148,7 @@ void testMathFunctions() {
ASSERT_VEC4_EQUAL(min(f1, f2), 0.4, 1.2, -1.2, -5.0); ASSERT_VEC4_EQUAL(min(f1, f2), 0.4, 1.2, -1.2, -5.0);
ASSERT_VEC4_EQUAL(max(f1, f2), 1.1, 1.9, 1.3, -3.8); ASSERT_VEC4_EQUAL(max(f1, f2), 1.1, 1.9, 1.3, -3.8);
ASSERT_VEC4_EQUAL(sqrt(fvec4(1.5, 3.1, 4.0, 15.0)), sqrt(1.5), sqrt(3.1), sqrt(4.0), sqrt(15.0)); ASSERT_VEC4_EQUAL(sqrt(fvec4(1.5, 3.1, 4.0, 15.0)), sqrt(1.5), sqrt(3.1), sqrt(4.0), sqrt(15.0));
ASSERT_VEC4_EQUAL(rsqrt(fvec4(1.5, 3.1, 4.0, 15.0)), 1.0/sqrt(1.5), 1.0/sqrt(3.1), 1.0/sqrt(4.0), 1.0/sqrt(15.0));
ASSERT_EQUAL_TOL(f1[0]*f2[0]+f1[1]*f2[1]+f1[2]*f2[2], dot3(f1, f2), 1e-6); ASSERT_EQUAL_TOL(f1[0]*f2[0]+f1[1]*f2[1]+f1[2]*f2[2], dot3(f1, f2), 1e-6);
ASSERT_EQUAL_TOL(f1[0]*f2[0]+f1[1]*f2[1]+f1[2]*f2[2]+f1[3]*f2[3], dot4(f1, f2), 1e-6); ASSERT_EQUAL_TOL(f1[0]*f2[0]+f1[1]*f2[1]+f1[2]*f2[2]+f1[3]*f2[3], dot4(f1, f2), 1e-6);
ASSERT(any(f1 > 0.5)); ASSERT(any(f1 > 0.5));
......
...@@ -85,7 +85,7 @@ version = '%(version)s' ...@@ -85,7 +85,7 @@ version = '%(version)s'
full_version = '%(full_version)s' full_version = '%(full_version)s'
git_revision = '%(git_revision)s' git_revision = '%(git_revision)s'
release = %(isrelease)s release = %(isrelease)s
openmm_library_path = '%(path)s' openmm_library_path = r'%(path)s'
if not release: if not release:
version = full_version version = full_version
......
...@@ -37,6 +37,7 @@ from __future__ import division ...@@ -37,6 +37,7 @@ from __future__ import division
from functools import wraps from functools import wraps
from math import pi, cos, sin, sqrt from math import pi, cos, sin, sqrt
import os import os
import re
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
...@@ -99,6 +100,8 @@ class _ZeroDict(dict): ...@@ -99,6 +100,8 @@ class _ZeroDict(dict):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_resre = re.compile(r'(\d+)([a-zA-Z]*)')
class CharmmPsfFile(object): class CharmmPsfFile(object):
""" """
A chemical structure instantiated from CHARMM files. A chemical structure instantiated from CHARMM files.
...@@ -192,11 +195,13 @@ class CharmmPsfFile(object): ...@@ -192,11 +195,13 @@ class CharmmPsfFile(object):
atom_list = AtomList() atom_list = AtomList()
for i in xrange(natom): for i in xrange(natom):
words = psfsections['NATOM'][1][i].split() words = psfsections['NATOM'][1][i].split()
atid = int(words[0])
if atid != i + 1:
raise CharmmPSFError('Nonsequential atoms detected!')
system = words[1] system = words[1]
resid = conv(words[2], int, 'residue number') rematch = _resre.match(words[2])
if not rematch:
raise RuntimeError('Could not parse residue number %s' %
words[2])
resid, inscode = rematch.groups()
resid = int(resid)
resname = words[3] resname = words[3]
name = words[4] name = words[4]
attype = words[5] attype = words[5]
...@@ -209,7 +214,7 @@ class CharmmPsfFile(object): ...@@ -209,7 +214,7 @@ class CharmmPsfFile(object):
mass = conv(words[7], float, 'atomic mass') mass = conv(words[7], float, 'atomic mass')
props = words[8:] props = words[8:]
atom = residue_list.add_atom(system, resid, resname, name, atom = residue_list.add_atom(system, resid, resname, name,
attype, charge, mass, props) attype, charge, mass, inscode, props)
atom_list.append(atom) atom_list.append(atom)
atom_list.assign_indexes() atom_list.assign_indexes()
atom_list.changed = False atom_list.changed = False
...@@ -443,175 +448,6 @@ class CharmmPsfFile(object): ...@@ -443,175 +448,6 @@ class CharmmPsfFile(object):
line = psf.readline().strip() line = psf.readline().strip()
return title, pointers, data return title, pointers, data
def writePsf(self, dest, vmd=False):
"""
Writes a PSF file from the stored molecule
Parameters:
- dest (str or file-like) : The place to write the output PSF file.
If it has a "write" attribute, it will be used to print the
PSF file. Otherwise, it will be treated like a string and a
file will be opened, printed, then closed
- vmd (bool) : If True, it will write out a PSF in the format that
VMD prints it in (i.e., no NUMLP/NUMLPH or MOLNT sections)
Example:
>>> cs = CharmmPsfFile('testfiles/test.psf')
>>> cs.writePsf('testfiles/test2.psf')
"""
# See if this is an extended format
ext = 'EXT' in self.flags
own_handle = False
# Index the atoms and residues
self.residue_list.assign_indexes()
self.atom_list.assign_indexes()
if not hasattr(dest, 'write'):
own_handle = True
dest = open(dest, 'w')
# Assign the formats we need to write with
if ext:
atmfmt1 = ('%10d %-8s %-8i %-8s %-8s %4d %10.6f %13.4f' + 11*' ')
atmfmt2 = ('%10d %-8s %-8i %-8s %-8s %-4s %10.6f %13.4f' + 11*' ')
intfmt = '%10d' # For pointers
else:
atmfmt1 = ('%8d %-4s %-4i %-4s %-4s %4d %10.6f %13.4f' + 11*' ')
atmfmt2 = ('%8d %-4s %-4i %-4s %-4s %-4s %10.6f %13.4f' + 11*' ')
intfmt = '%8d' # For pointers
# Now print the header then the title
dest.write('PSF ' + ' '.join(self.flags) + '\n')
dest.write('\n')
dest.write(intfmt % len(self.title) + ' !NTITLE\n')
dest.write('\n'.join(self.title) + '\n\n')
# Now time for the atoms
dest.write(intfmt % len(self.atom_list) + ' !NATOM\n')
# atmfmt1 is for CHARMM format (i.e., atom types are integers)
# atmfmt is for XPLOR format (i.e., atom types are strings)
for i, atom in enumerate(self.atom_list):
if isinstance(atom.attype, str):
fmt = atmfmt2
else:
fmt = atmfmt1
atmstr = fmt % (i+1, atom.system, atom.residue.resnum,
atom.residue.resname, atom.name, atom.attype,
atom.charge, atom.mass)
dest.write(atmstr + ' '.join(atom.props) + '\n')
dest.write('\n')
# Bonds
dest.write(intfmt % len(self.bond_list) + ' !NBOND: bonds\n')
for i, bond in enumerate(self.bond_list):
dest.write((intfmt*2) % (bond.atom1.idx+1, bond.atom2.idx+1))
if i % 4 == 3: # Write 4 bonds per line
dest.write('\n')
# See if we need to terminate
if len(self.bond_list) % 4 != 0 or len(self.bond_list) == 0:
dest.write('\n')
dest.write('\n')
# Angles
dest.write(intfmt % len(self.angle_list) + ' !NTHETA: angles\n')
for i, angle in enumerate(self.angle_list):
dest.write((intfmt*3) % (angle.atom1.idx+1, angle.atom2.idx+1,
angle.atom3.idx+1)
)
if i % 3 == 2: # Write 3 angles per line
dest.write('\n')
# See if we need to terminate
if len(self.angle_list) % 3 != 0 or len(self.angle_list) == 0:
dest.write('\n')
dest.write('\n')
# Dihedrals
dest.write(intfmt % len(self.dihedral_list) + ' !NPHI: dihedrals\n')
for i, dih in enumerate(self.dihedral_list):
dest.write((intfmt*4) % (dih.atom1.idx+1, dih.atom2.idx+1,
dih.atom3.idx+1, dih.atom4.idx+1)
)
if i % 2 == 1: # Write 2 dihedrals per line
dest.write('\n')
# See if we need to terminate
if len(self.dihedral_list) % 2 != 0 or len(self.dihedral_list) == 0:
dest.write('\n')
dest.write('\n')
# Impropers
dest.write(intfmt % len(self.improper_list) + ' !NIMPHI: impropers\n')
for i, imp in enumerate(self.improper_list):
dest.write((intfmt*4) % (imp.atom1.idx+1, imp.atom2.idx+1,
imp.atom3.idx+1, imp.atom4.idx+1)
)
if i % 2 == 1: # Write 2 dihedrals per line
dest.write('\n')
# See if we need to terminate
if len(self.improper_list) % 2 != 0 or len(self.improper_list) == 0:
dest.write('\n')
dest.write('\n')
# Donor section
dest.write(intfmt % len(self.donor_list) + ' !NDON: donors\n')
for i, don in enumerate(self.donor_list):
dest.write((intfmt*2) % (don.atom1.idx+1, don.atom2.idx+1))
if i % 4 == 3: # 4 donors per line
dest.write('\n')
if len(self.donor_list) % 4 != 0 or len(self.donor_list) == 0:
dest.write('\n')
dest.write('\n')
# Acceptor section
dest.write(intfmt % len(self.acceptor_list) + ' !NACC: acceptors\n')
for i, acc in enumerate(self.acceptor_list):
dest.write((intfmt*2) % (acc.atom1.idx+1, acc.atom2.idx+1))
if i % 4 == 3: # 4 donors per line
dest.write('\n')
if len(self.acceptor_list) % 4 != 0 or len(self.acceptor_list) == 0:
dest.write('\n')
dest.write('\n')
# NNB section ??
dest.write(intfmt % 0 + ' !NNB\n\n')
for i in range(len(self.atom_list)):
dest.write(intfmt % 0)
if i % 8 == 7: # Write 8 0's per line
dest.write('\n')
if len(self.atom_list) % 8 != 0: dest.write('\n')
dest.write('\n')
# Group section
dest.write((intfmt*2) % (len(self.group_list), self.group_list.nst2))
dest.write(' !NGRP NST2\n')
for i, gp in enumerate(self.group_list):
dest.write((intfmt*3) % (gp.bs, gp.type, gp.move))
if i % 3 == 2: dest.write('\n')
if len(self.group_list) % 3 != 0 or len(self.group_list) == 0:
dest.write('\n')
dest.write('\n')
# The next two sections are never found in VMD prmtops...
if not vmd:
# Molecule section; first set molecularity
set_molecules(self.atom_list)
mollist = [a.marked for a in self.atom_list]
dest.write(intfmt % max(mollist) + ' !MOLNT\n')
for i, atom in enumerate(self.atom_list):
dest.write(intfmt % atom.marked)
if i % 8 == 7: dest.write('\n')
if len(self.atom_list) % 8 != 0: dest.write('\n')
dest.write('\n')
# NUMLP/NUMLPH section
dest.write((intfmt*2) % (0, 0) + ' !NUMLP NUMLPH\n')
dest.write('\n')
# CMAP section
dest.write(intfmt % len(self.cmap_list) + ' !NCRTERM: cross-terms\n')
for i, cmap in enumerate(self.cmap_list):
dest.write((intfmt*4) % (cmap.atom1.idx+1, cmap.atom2.idx+1,
cmap.atom3.idx+1, cmap.atom4.idx+1)
)
if cmap.consecutive:
dest.write((intfmt*4) % (cmap.atom2.idx+1, cmap.atom3.idx+1,
cmap.atom4.idx+1, cmap.atom5.idx+1)
)
else:
dest.write((intfmt*4) % (cmap.atom5.idx+1, cmap.atom6.idx+1,
cmap.atom7.idx+1, cmap.atom8.idx+1)
)
dest.write('\n')
# Done!
# If we opened our own handle, close it
if own_handle:
dest.close()
def loadParameters(self, parmset): def loadParameters(self, parmset):
""" """
Loads parameters from a parameter set that was loaded via CHARMM RTF, Loads parameters from a parameter set that was loaded via CHARMM RTF,
...@@ -776,13 +612,13 @@ class CharmmPsfFile(object): ...@@ -776,13 +612,13 @@ class CharmmPsfFile(object):
last_residue = None last_residue = None
# Add each chain (separate 'system's) and residue # Add each chain (separate 'system's) and residue
for atom in self.atom_list: for atom in self.atom_list:
resid = '%d%s' % (atom.residue.idx, atom.residue.inscode)
if atom.system != last_chain: if atom.system != last_chain:
chain = topology.addChain(atom.system) chain = topology.addChain(atom.system)
last_chain = atom.system last_chain = atom.system
if atom.residue.idx != last_residue: if resid != last_residue:
last_residue = atom.residue.idx last_residue = resid
residue = topology.addResidue(atom.residue.resname, chain, residue = topology.addResidue(atom.residue.resname, chain, resid)
str(atom.residue.idx))
if atom.type is not None: if atom.type is not None:
# This is the most reliable way of determining the element # This is the most reliable way of determining the element
atomic_num = atom.type.atomic_number atomic_num = atom.type.atomic_number
......
...@@ -31,7 +31,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -31,7 +31,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
__author__ = "Christopher M. Bruns" __author__ = "Christopher M. Bruns"
__version__ = "1.0" __version__ = "1.0"
from collections import OrderedDict
from simtk.unit import daltons, is_quantity from simtk.unit import daltons, is_quantity
import copy_reg import copy_reg
...@@ -47,6 +47,7 @@ class Element(object): ...@@ -47,6 +47,7 @@ class Element(object):
_elements_by_symbol = {} _elements_by_symbol = {}
_elements_by_atomic_number = {} _elements_by_atomic_number = {}
_elements_by_mass = None
def __init__(self, number, name, symbol, mass): def __init__(self, number, name, symbol, mass):
"""Create a new element """Create a new element
...@@ -67,8 +68,11 @@ class Element(object): ...@@ -67,8 +68,11 @@ class Element(object):
self._mass = mass self._mass = mass
# Index this element in a global table # Index this element in a global table
s = symbol.strip().upper() s = symbol.strip().upper()
## If we add a new element, we need to re-hash elements by mass
Element._elements_by_mass = None
assert s not in Element._elements_by_symbol if s in Element._elements_by_symbol:
raise ValueError('Duplicate element symbol %s' % s)
Element._elements_by_symbol[s] = self Element._elements_by_symbol[s] = self
if number in Element._elements_by_atomic_number: if number in Element._elements_by_atomic_number:
other_element = Element._elements_by_atomic_number[number] other_element = Element._elements_by_atomic_number[number]
...@@ -96,20 +100,45 @@ class Element(object): ...@@ -96,20 +100,45 @@ class Element(object):
""" """
Get the element whose mass is CLOSEST to the requested mass. This method Get the element whose mass is CLOSEST to the requested mass. This method
should not be used for repartitioned masses should not be used for repartitioned masses
Parameters
----------
mass : float or Quantity
Mass of the atom to find the element for. Units assumed to be
daltons if not specified
Returns
-------
element : Element
The element whose atomic mass is closest to the input mass
""" """
# Assume masses are in daltons if they are not units # Assume masses are in daltons if they are not units
if not is_quantity(mass): if is_quantity(mass):
mass = mass * daltons mass = mass.value_in_unit(daltons)
if mass < 0:
raise ValueError('Invalid Higgs field')
# If this is our first time calling getByMass (or we added an element
# since the last call), re-generate the ordered by-mass dict cache
if Element._elements_by_mass is None:
Element._elements_by_mass = OrderedDict()
for elem in sorted(Element._elements_by_symbol.values(),
key=lambda x: x.mass):
Element._elements_by_mass[elem.mass] = elem
diff = mass diff = mass
best_guess = None best_guess = None
for key in Element._elements_by_atomic_number: for elemmass, element in Element._elements_by_mass.iteritems():
element = Element._elements_by_atomic_number[key] massdiff = abs(elemmass._value - mass)
massdiff = abs(element.mass - mass)
if massdiff < diff: if massdiff < diff:
best_guess = element best_guess = element
diff = massdiff diff = massdiff
if elemmass._value > mass:
# Elements are only getting heavier, so bail out early
return best_guess
# This really should only happen if we wanted ununoctium or something
# bigger... won't really happen but still make sure we return an Element
return best_guess return best_guess
@property @property
...@@ -145,6 +174,9 @@ def _pickle_element(element): ...@@ -145,6 +174,9 @@ def _pickle_element(element):
copy_reg.pickle(Element, _pickle_element) copy_reg.pickle(Element, _pickle_element)
# NOTE: getElementByMass assumes all masses are Quantity instances with unit
# "daltons". All elements need to obey this assumption, or that method will
# fail. No checking is done in getElementByMass for performance reasons
hydrogen = Element( 1, "hydrogen", "H", 1.007947*daltons) hydrogen = Element( 1, "hydrogen", "H", 1.007947*daltons)
deuterium = Element( 1, "deuterium", "D", 2.01355321270*daltons) deuterium = Element( 1, "deuterium", "D", 2.01355321270*daltons)
helium = Element( 2, "helium", "He", 4.003*daltons) helium = Element( 2, "helium", "He", 4.003*daltons)
......
...@@ -127,6 +127,10 @@ class PrmtopLoader(object): ...@@ -127,6 +127,10 @@ class PrmtopLoader(object):
tag, self._prmtopVersion = line.rstrip().split(None, 1) tag, self._prmtopVersion = line.rstrip().split(None, 1)
elif line.startswith('%FLAG'): elif line.startswith('%FLAG'):
tag, flag = line.rstrip().split(None, 1) tag, flag = line.rstrip().split(None, 1)
if flag == 'CTITLE':
raise TypeError('CHAMBER-style topology files are not supported here. '
'Consider using the CHARMM files directly with CharmmPsfFile '
'or ParmEd (where CHAMBER topologies are supported)')
self._flags.append(flag) self._flags.append(flag)
self._raw_data[flag] = [] self._raw_data[flag] = []
elif line.startswith('%FORMAT'): elif line.startswith('%FORMAT'):
......
...@@ -375,12 +375,13 @@ class AtomList(TrackedList): ...@@ -375,12 +375,13 @@ class AtomList(TrackedList):
class Residue(object): class Residue(object):
""" Residue class """ """ Residue class """
def __init__(self, resname, idx): def __init__(self, resname, idx, inscode=''):
self.resname = resname self.resname = resname
self.idx = idx self.idx = idx
self.atoms = [] self.atoms = []
self.resnum = None # Numbered based on SYSTEM name self.resnum = None # Numbered based on SYSTEM name
self.system = None self.system = None
self.inscode = inscode
def add_atom(self, atom): def add_atom(self, atom):
if self.system is None: if self.system is None:
...@@ -416,7 +417,7 @@ class Residue(object): ...@@ -416,7 +417,7 @@ class Residue(object):
return self.resname == thing.resname and self.idx == thing.idx return self.resname == thing.resname and self.idx == thing.idx
if isinstance(thing, tuple) or isinstance(thing, list): if isinstance(thing, tuple) or isinstance(thing, list):
# Must be resnum, resname # Must be resnum, resname
return thing == (self.resname, self.idx) return thing == (self.resname, self.idx, self.inscode)
return False # No other type can be equal. return False # No other type can be equal.
def __ne__(self, thing): def __ne__(self, thing):
...@@ -447,7 +448,7 @@ class ResidueList(list): ...@@ -447,7 +448,7 @@ class ResidueList(list):
resnum += 1 resnum += 1
def add_atom(self, system, resnum, resname, name, def add_atom(self, system, resnum, resname, name,
attype, charge, mass, props=None): attype, charge, mass, inscode, props=None):
""" """
Adds an atom to the list of residues. If the residue is not the same as Adds an atom to the list of residues. If the residue is not the same as
the last residue that was created, a new Residue is created and added the last residue that was created, a new Residue is created and added
...@@ -461,25 +462,22 @@ class ResidueList(list): ...@@ -461,25 +462,22 @@ class ResidueList(list):
- attype (int or str) : Type of the atom - attype (int or str) : Type of the atom
- charge (float) : Partial atomic charge of the atom - charge (float) : Partial atomic charge of the atom
- mass (float) : Mass (amu) of the atom - mass (float) : Mass (amu) of the atom
- inscode (str) : Insertion code, if it is specified
Returns: Returns:
The Atom instance created and added to the list of residues The Atom instance created and added to the list of residues
""" """
if self._last_residue is None: lr = self._last_residue
res = self._last_residue = Residue(resname, resnum) if lr is None:
res = self._last_residue = Residue(resname, resnum, inscode)
list.append(self, res) list.append(self, res)
elif (self._last_residue != (resname, resnum) or elif (lr.resname != resname or lr.idx != resname or
system != self._last_residue.system): lr.inscode != inscode or system != lr.system):
if (self._last_residue.idx == resnum and res = self._last_residue = Residue(resname, resnum, inscode)
self._last_residue.system == system): res.system = system
lresname = self._last_residue.resname
warnings.warn('Residue %d split into separate residues %s '
'and %s' % (resnum, lresname, resname),
SplitResidueWarning)
res = self._last_residue = Residue(resname, resnum)
list.append(self, res) list.append(self, res)
else: else:
res = self._last_residue res = lr
atom = Atom(system, name, attype, float(charge), float(mass), props) atom = Atom(system, name, attype, float(charge), float(mass), props)
res.add_atom(atom) res.add_atom(atom)
return atom return atom
......
...@@ -240,7 +240,7 @@ class Modeller(object): ...@@ -240,7 +240,7 @@ class Modeller(object):
self.topology = newTopology self.topology = newTopology
self.positions = newPositions self.positions = newPositions
def addSolvent(self, forcefield, model='tip3p', boxSize=None, boxVectors=None, padding=None, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar): def addSolvent(self, forcefield, model='tip3p', boxSize=None, boxVectors=None, padding=None, numAdded=None, positiveIon='Na+', negativeIon='Cl-', ionicStrength=0*molar):
"""Add solvent (both water and ions) to the model to fill a rectangular box. """Add solvent (both water and ions) to the model to fill a rectangular box.
The algorithm works as follows: The algorithm works as follows:
...@@ -250,11 +250,15 @@ class Modeller(object): ...@@ -250,11 +250,15 @@ class Modeller(object):
randomly selecting a water molecule and replacing it with the ion. randomly selecting a water molecule and replacing it with the ion.
4. Ion pairs are added to give the requested total ionic strength. 4. Ion pairs are added to give the requested total ionic strength.
The box size can be specified in four ways. First, you can explicitly give the vectors defining the periodic box to The box size can be specified in any of several ways:
use. Alternatively, for a rectangular box you can simply give the dimensions of the unit cell. Third, you can
give a padding distance. The largest dimension of the solute (along the x, y, or z axis) is determined, and a cubic 1. You can explicitly give the vectors defining the periodic box to use.
box of size (largest dimension)+2*padding is used. Finally, if neither box vectors, box size, nor padding distance is specified, 2. Alternatively, for a rectangular box you can simply give the dimensions of the unit cell.
the existing Topology's box vectors are used. 3. You can give a padding distance. The largest dimension of the solute (along the x, y, or z axis) is determined, and a cubic
box of size (largest dimension)+2*padding is used.
4. You can specify the total number of molecules (both waters and ions) to add. A cubic box is then created whose size is
just large enough hold the specified amount of solvent.
5. Finally, if none of the above options is specified, the existing Topology's box vectors are used.
Parameters: Parameters:
- forcefield (ForceField) the ForceField to use for determining van der Waals radii and atomic charges - forcefield (ForceField) the ForceField to use for determining van der Waals radii and atomic charges
...@@ -262,14 +266,43 @@ class Modeller(object): ...@@ -262,14 +266,43 @@ class Modeller(object):
- boxSize (Vec3=None) the size of the box to fill with water - boxSize (Vec3=None) the size of the box to fill with water
- boxVectors (tuple of Vec3=None) the vectors defining the periodic box to fill with water - boxVectors (tuple of Vec3=None) the vectors defining the periodic box to fill with water
- padding (distance=None) the padding distance to use - padding (distance=None) the padding distance to use
- numAdded (int=None) the total number of molecules (waters and ions) to add
- positiveIon (string='Na+') the type of positive ion to add. Allowed values are 'Cs+', 'K+', 'Li+', 'Na+', and 'Rb+' - positiveIon (string='Na+') the type of positive ion to add. Allowed values are 'Cs+', 'K+', 'Li+', 'Na+', and 'Rb+'
- negativeIon (string='Cl-') the type of negative ion to add. Allowed values are 'Cl-', 'Br-', 'F-', and 'I-'. Be aware - negativeIon (string='Cl-') the type of negative ion to add. Allowed values are 'Cl-', 'Br-', 'F-', and 'I-'. Be aware
that not all force fields support all ion types. that not all force fields support all ion types.
- ionicStrength (concentration=0*molar) the total concentration of ions (both positive and negative) to add. This - ionicStrength (concentration=0*molar) the total concentration of ions (both positive and negative) to add. This
does not include ions that are added to neutralize the system. does not include ions that are added to neutralize the system.
""" """
if len([x for x in (boxSize, boxVectors, padding, numAdded) if x is not None]) > 1:
raise ValueError('At most one of the following arguments may be specified: boxSize, boxVectors, padding, numAdded')
# Load the pre-equilibrated water box.
vdwRadiusPerSigma = 0.5612310241546864907
if model == 'tip3p':
waterRadius = 0.31507524065751241*vdwRadiusPerSigma
elif model == 'spce':
waterRadius = 0.31657195050398818*vdwRadiusPerSigma
elif model == 'tip4pew':
waterRadius = 0.315365*vdwRadiusPerSigma
elif model == 'tip5p':
waterRadius = 0.312*vdwRadiusPerSigma
else:
raise ValueError('Unknown water model: %s' % model)
pdb = PDBFile(os.path.join(os.path.dirname(__file__), 'data', model+'.pdb'))
pdbTopology = pdb.getTopology()
pdbPositions = pdb.getPositions().value_in_unit(nanometer)
pdbResidues = list(pdbTopology.residues())
pdbBoxSize = pdbTopology.getUnitCellDimensions().value_in_unit(nanometer)
# Pick a unit cell size. # Pick a unit cell size.
if numAdded is not None:
# Select a padding distance which is guaranteed to give more than the specified number of molecules.
padding = 1.1*(numAdded/((len(pdbResidues)/pdbBoxSize[0]**3)*8))**(1.0/3.0)
if padding < 0.5:
padding = 0.5 # Ensure we have enough when adding very small numbers of molecules
if boxVectors is not None: if boxVectors is not None:
if is_quantity(boxVectors[0]): if is_quantity(boxVectors[0]):
boxVectors = (boxVectors[0].value_in_unit(nanometer), boxVectors[1].value_in_unit(nanometer), boxVectors[2].value_in_unit(nanometer)) boxVectors = (boxVectors[0].value_in_unit(nanometer), boxVectors[1].value_in_unit(nanometer), boxVectors[2].value_in_unit(nanometer))
...@@ -305,25 +338,6 @@ class Modeller(object): ...@@ -305,25 +338,6 @@ class Modeller(object):
positiveElement = posIonElements[positiveIon] positiveElement = posIonElements[positiveIon]
negativeElement = negIonElements[negativeIon] negativeElement = negIonElements[negativeIon]
# Load the pre-equilibrated water box.
vdwRadiusPerSigma = 0.5612310241546864907
if model == 'tip3p':
waterRadius = 0.31507524065751241*vdwRadiusPerSigma
elif model == 'spce':
waterRadius = 0.31657195050398818*vdwRadiusPerSigma
elif model == 'tip4pew':
waterRadius = 0.315365*vdwRadiusPerSigma
elif model == 'tip5p':
waterRadius = 0.312*vdwRadiusPerSigma
else:
raise ValueError('Unknown water model: %s' % model)
pdb = PDBFile(os.path.join(os.path.dirname(__file__), 'data', model+'.pdb'))
pdbTopology = pdb.getTopology()
pdbPositions = pdb.getPositions().value_in_unit(nanometer)
pdbResidues = list(pdbTopology.residues())
pdbBoxSize = pdbTopology.getUnitCellDimensions().value_in_unit(nanometer)
# Have the ForceField build a System for the solute from which we can determine van der Waals radii. # Have the ForceField build a System for the solute from which we can determine van der Waals radii.
system = forcefield.createSystem(self.topology) system = forcefield.createSystem(self.topology)
...@@ -424,27 +438,42 @@ class Modeller(object): ...@@ -424,27 +438,42 @@ class Modeller(object):
addedWaters.append((residue.index, atomPos)) addedWaters.append((residue.index, atomPos))
# There could be clashes between water molecules at the box edges. Find ones to remove. if numAdded is not None:
# We added many more waters than we actually want. Sort them based on distance to the nearest box edge and
upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff) # only keep the ones in the middle.
lowerCutoff = center-box/2+Vec3(waterCutoff, waterCutoff, waterCutoff)
lowerSkinPositions = [pos for index, pos in addedWaters if pos[0] < lowerCutoff[0] or pos[1] < lowerCutoff[1] or pos[2] < lowerCutoff[2]] lowerBound = center-box/2
filteredWaters = [] upperBound = center+box/2
cells = {} distToEdge = (min(min(pos-lowerBound), min(upperBound-pos)) for index, pos in addedWaters)
for i in range(len(lowerSkinPositions)): sortedIndex = [i[0] for i in sorted(enumerate(distToEdge), key=lambda x: -x[1])]
cell = tuple((int(floor(lowerSkinPositions[i][j]/cellSize[j]))%numCells[j] for j in range(3))) addedWaters = [addedWaters[i] for i in sortedIndex[:numAdded]]
if cell in cells:
cells[cell].append(i) # Compute a new periodic box size.
else:
cells[cell] = [i] maxSize = max(max((pos[i] for index, pos in addedWaters))-min((pos[i] for index, pos in addedWaters)) for i in range(3))
for entry in addedWaters: newTopology.setUnitCellDimensions(Vec3(maxSize, maxSize, maxSize))
pos = entry[1] else:
if pos[0] < upperCutoff[0] and pos[1] < upperCutoff[1] and pos[2] < upperCutoff[2]: # There could be clashes between water molecules at the box edges. Find ones to remove.
filteredWaters.append(entry)
else: upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff)
if not any((periodicDistance(lowerSkinPositions[i], pos) < waterCutoff and norm(lowerSkinPositions[i]-pos) > waterCutoff for i in neighbors(pos))): lowerCutoff = center-box/2+Vec3(waterCutoff, waterCutoff, waterCutoff)
lowerSkinPositions = [pos for index, pos in addedWaters if pos[0] < lowerCutoff[0] or pos[1] < lowerCutoff[1] or pos[2] < lowerCutoff[2]]
filteredWaters = []
cells = {}
for i in range(len(lowerSkinPositions)):
cell = tuple((int(floor(lowerSkinPositions[i][j]/cellSize[j]))%numCells[j] for j in range(3)))
if cell in cells:
cells[cell].append(i)
else:
cells[cell] = [i]
for entry in addedWaters:
pos = entry[1]
if pos[0] < upperCutoff[0] and pos[1] < upperCutoff[1] and pos[2] < upperCutoff[2]:
filteredWaters.append(entry) filteredWaters.append(entry)
addedWaters = filteredWaters else:
if not any((periodicDistance(lowerSkinPositions[i], pos) < waterCutoff and norm(lowerSkinPositions[i]-pos) > waterCutoff for i in neighbors(pos))):
filteredWaters.append(entry)
addedWaters = filteredWaters
# Add ions to neutralize the system. # Add ions to neutralize the system.
......
...@@ -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) 2012 Stanford University and the Authors. Portions copyright (c) 2012-2015 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors:
...@@ -33,6 +33,8 @@ __version__ = "1.0" ...@@ -33,6 +33,8 @@ __version__ = "1.0"
import simtk.openmm as mm import simtk.openmm as mm
import simtk.unit as unit import simtk.unit as unit
import sys
from datetime import datetime, timedelta
class Simulation(object): class Simulation(object):
"""Simulation provides a simplified API for running simulations with OpenMM and reporting results. """Simulation provides a simplified API for running simulations with OpenMM and reporting results.
...@@ -78,11 +80,11 @@ class Simulation(object): ...@@ -78,11 +80,11 @@ class Simulation(object):
else: else:
self.context = mm.Context(system, integrator, platform, platformProperties) self.context = mm.Context(system, integrator, platform, platformProperties)
def minimizeEnergy(self, tolerance=1*unit.kilojoule/unit.mole, maxIterations=0): def minimizeEnergy(self, tolerance=10*unit.kilojoule/unit.mole, maxIterations=0):
"""Perform a local energy minimization on the system. """Perform a local energy minimization on the system.
Parameters: Parameters:
- tolerance (energy=1*kilojoule/mole) The energy tolerance to which the system should be minimized - tolerance (energy=10*kilojoules/mole) The energy tolerance to which the system should be minimized
- maxIterations (int=0) The maximum number of iterations to perform. If this is 0, minimization is continued - maxIterations (int=0) The maximum number of iterations to perform. If this is 0, minimization is continued
until the results converge without regard to how many iterations it takes. until the results converge without regard to how many iterations it takes.
""" """
...@@ -90,10 +92,51 @@ class Simulation(object): ...@@ -90,10 +92,51 @@ class Simulation(object):
def step(self, steps): def step(self, steps):
"""Advance the simulation by integrating a specified number of time steps.""" """Advance the simulation by integrating a specified number of time steps."""
stepTo = self.currentStep+steps self._simulate(endStep=self.currentStep+steps)
def runForClockTime(self, time, checkpointFile=None, stateFile=None, checkpointInterval=None):
"""Advance the simulation by integrating time steps until a fixed amount of clock time has elapsed.
This is useful when you have a limited amount of computer time available, and want to run the longest simulation
possible in that time. This method will continue taking time steps until the specified clock time has elapsed,
then return. It also can automatically write out a checkpoint and/or state file before returning, so you can
later resume the simulation. Another option allows it to write checkpoints or states at regular intervals, so
you can resume even if the simulation is interrupted before the time limit is reached.
Parameters:
- time (time) the amount of time to run for. If no units are specified, it is assumed to be a number of hours.
- checkpointFile (string or file=None) if specified, a checkpoint file will be written at the end of the
simulation (and optionally at regular intervals before then) by passing this to saveCheckpoint().
- stateFile (string or file=None) if specified, a state file will be written at the end of the
simulation (and optionally at regular intervals before then) by passing this to saveState().
- checkpointInterval (time=None) if specified, checkpoints and/or states will be written at regular intervals
during the simulation, in addition to writing a final version at the end. If no units are specified, this is
assumed to be in hours.
"""
if unit.is_quantity(time):
time = time.value_in_unit(unit.hours)
if unit.is_quantity(checkpointInterval):
checkpointInterval = checkpointInterval.value_in_unit(unit.hours)
endTime = datetime.now()+timedelta(hours=time)
while (datetime.now() < endTime):
if checkpointInterval is None:
nextTime = endTime
else:
nextTime = datetime.now()+timedelta(hours=checkpointInterval)
if nextTime > endTime:
nextTime = endTime
self._simulate(endTime=nextTime)
if checkpointFile is not None:
self.saveCheckpoint(checkpointFile)
if stateFile is not None:
self.saveState(stateFile)
def _simulate(self, endStep=None, endTime=None):
if endStep is None:
endStep = sys.maxint
nextReport = [None]*len(self.reporters) nextReport = [None]*len(self.reporters)
while self.currentStep < stepTo: while self.currentStep < endStep:
nextSteps = stepTo-self.currentStep nextSteps = endStep-self.currentStep
anyReport = False anyReport = False
for i, reporter in enumerate(self.reporters): for i, reporter in enumerate(self.reporters):
nextReport[i] = reporter.describeNextReport(self) nextReport[i] = reporter.describeNextReport(self)
...@@ -104,6 +147,8 @@ class Simulation(object): ...@@ -104,6 +147,8 @@ class Simulation(object):
while stepsToGo > 10: while stepsToGo > 10:
self.integrator.step(10) # Only take 10 steps at a time, to give Python more chances to respond to a control-c. self.integrator.step(10) # Only take 10 steps at a time, to give Python more chances to respond to a control-c.
stepsToGo -= 10 stepsToGo -= 10
if endTime is not None and datetime.now() >= endTime:
return
self.integrator.step(stepsToGo) self.integrator.step(stepsToGo)
self.currentStep += nextSteps self.currentStep += nextSteps
if anyReport: if anyReport:
...@@ -126,3 +171,71 @@ class Simulation(object): ...@@ -126,3 +171,71 @@ class Simulation(object):
if next[0] == nextSteps: if next[0] == nextSteps:
reporter.report(self, state) reporter.report(self, state)
def saveCheckpoint(self, file):
"""Save a checkpoint of the simulation to a file.
The output is a binary file that contains a complete representation of the current state of the Simulation.
It includes both publicly visible data such as the particle positions and velocities, and also internal data
such as the states of random number generators. Reloading the checkpoint will put the Simulation back into
precisely the same state it had before, so it can be exactly continued.
A checkpoint file is highly specific to the Simulation it was created from. It can only be loaded into
another Simulation that has an identical System, uses the same Platform and OpenMM version, and is running on
identical hardware. If you need a more portable way to resume simulations, consider using saveState() instead.
Parameters:
- file (string or file) a File-like object to write the checkpoint to, or alternatively a filename
"""
if isinstance(file, str):
with open(file, 'wb') as f:
f.write(self.context.createCheckpoint())
else:
file.write(self.context.createCheckpoint())
def loadCheckpoint(self, file):
"""Load a checkpoint file that was created with saveCheckpoint().
Parameters:
- file (string or file) a File-like object to load the checkpoint from, or alternatively a filename
"""
if isinstance(file, str):
with open(file, 'rb') as f:
self.context.loadCheckpoint(f.read())
else:
self.context.loadCheckpoint(file.read())
def saveState(self, file):
"""Save the current state of the simulation to a file.
The output is an XML file containing a serialized State object. It includes all publicly visible data,
including positions, velocities, and parameters. Reloading the State will put the Simulation back into
approximately the same state it had before.
Unlike saveCheckpoint(), this does not store internal data such as the states of random number generators.
Therefore, you should not expect the following trajectory to be identical to what would have been produced
with the original Simulation. On the other hand, this means it is portable across different Platforms or
hardware.
Parameters:
- file (string or file) a File-like object to write the state to, or alternatively a filename
"""
state = self.context.getState(getPositions=True, getVelocities=True, getParameters=True)
xml = mm.XmlSerializer.serialize(state)
if isinstance(file, str):
with open(file, 'w') as f:
f.write(xml)
else:
file.write(xml)
def loadState(self, file):
"""Load a State file that was created with saveState().
Parameters:
- file (string or file) a File-like object to load the state from, or alternatively a filename
"""
if isinstance(file, str):
with open(file, 'r') as f:
xml = f.read()
else:
xml = file.read()
self.context.setState(mm.XmlSerializer.deserialize(xml))
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
} }
%pythoncode { %pythoncode %{
def getState(self, def getState(self,
getPositions=False, getPositions=False,
getVelocities=False, getVelocities=False,
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
if state._paramMap is not None: if state._paramMap is not None:
for param in state._paramMap: for param in state._paramMap:
self.setParameter(param, state._paramMap[param]) self.setParameter(param, state._paramMap[param])
} %}
%feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context. %feature("docstring") createCheckpoint "Create a checkpoint recording the current state of the Context.
This should be treated as an opaque block of binary data. See loadCheckpoint() for more details. This should be treated as an opaque block of binary data. See loadCheckpoint() for more details.
...@@ -175,7 +175,7 @@ Parameters: ...@@ -175,7 +175,7 @@ Parameters:
} }
%pythoncode { %pythoncode %{
def getState(self, def getState(self,
copy, copy,
getPositions=False, getPositions=False,
...@@ -235,11 +235,11 @@ Parameters: ...@@ -235,11 +235,11 @@ Parameters:
periodicBoxVectorsList=periodicBoxVectorsList, periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap) paramMap=paramMap)
return state return state
} %}
} }
%extend OpenMM::NonbondedForce { %extend OpenMM::NonbondedForce {
%pythoncode { %pythoncode %{
def addParticle_usingRVdw(self, charge, rVDW, epsilon): def addParticle_usingRVdw(self, charge, rVDW, epsilon):
"""Add particle using elemetrary charge. Rvdw and epsilon, """Add particle using elemetrary charge. Rvdw and epsilon,
which is consistent with AMBER parameter file usage. which is consistent with AMBER parameter file usage.
...@@ -260,11 +260,11 @@ Parameters: ...@@ -260,11 +260,11 @@ Parameters:
""" """
return self.addException(particle1, particle2, return self.addException(particle1, particle2,
chargeProd, rMin/RMIN_PER_SIGMA, epsilon) chargeProd, rMin/RMIN_PER_SIGMA, epsilon)
} %}
} }
%extend OpenMM::System { %extend OpenMM::System {
%pythoncode { %pythoncode %{
def __getstate__(self): def __getstate__(self):
serializationString = XmlSerializer.serializeSystem(self) serializationString = XmlSerializer.serializeSystem(self)
return serializationString return serializationString
...@@ -275,7 +275,7 @@ Parameters: ...@@ -275,7 +275,7 @@ Parameters:
def getForces(self): def getForces(self):
"""Get the list of Forces in this System""" """Get the list of Forces in this System"""
return [self.getForce(i) for i in range(self.getNumForces())] return [self.getForce(i) for i in range(self.getNumForces())]
} %}
} }
%extend OpenMM::XmlSerializer { %extend OpenMM::XmlSerializer {
...@@ -345,7 +345,7 @@ Parameters: ...@@ -345,7 +345,7 @@ Parameters:
return obj; return obj;
} }
%pythoncode { %pythoncode %{
@staticmethod @staticmethod
def _serializeState(pythonState): def _serializeState(pythonState):
positions = [] positions = []
...@@ -417,7 +417,6 @@ Parameters: ...@@ -417,7 +417,6 @@ Parameters:
@staticmethod @staticmethod
def deserialize(inputString): def deserialize(inputString):
"""Reconstruct an object that has been serialized as XML.""" """Reconstruct an object that has been serialized as XML."""
# Look for the first tag to figure out what type of object it is.
import re import re
match = re.search("<([^?]\S*)", inputString) match = re.search("<([^?]\S*)", inputString)
if match is None: if match is None:
...@@ -432,7 +431,7 @@ Parameters: ...@@ -432,7 +431,7 @@ Parameters:
if type == "State": if type == "State":
return XmlSerializer._deserializeState(inputString) return XmlSerializer._deserializeState(inputString)
raise ValueError("Unsupported object type") raise ValueError("Unsupported object type")
} %}
} }
%extend OpenMM::CustomIntegrator { %extend OpenMM::CustomIntegrator {
...@@ -444,7 +443,7 @@ Parameters: ...@@ -444,7 +443,7 @@ Parameters:
} }
%extend OpenMM::Force { %extend OpenMM::Force {
%pythoncode { %pythoncode %{
def __copy__(self): def __copy__(self):
copy = self.__class__.__new__(self.__class__) copy = self.__class__.__new__(self.__class__)
copy.__init__(self) copy.__init__(self)
...@@ -452,11 +451,11 @@ Parameters: ...@@ -452,11 +451,11 @@ Parameters:
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self.__copy__() return self.__copy__()
} %}
} }
%extend OpenMM::Integrator { %extend OpenMM::Integrator {
%pythoncode { %pythoncode %{
def __getstate__(self): def __getstate__(self):
serializationString = XmlSerializer.serialize(self) serializationString = XmlSerializer.serialize(self)
return serializationString return serializationString
...@@ -464,5 +463,5 @@ Parameters: ...@@ -464,5 +463,5 @@ Parameters:
def __setstate__(self, serializationString): def __setstate__(self, serializationString):
system = XmlSerializer.deserialize(serializationString) system = XmlSerializer.deserialize(serializationString)
self.this = system.this self.this = system.this
} %}
} }
...@@ -6,3 +6,12 @@ ...@@ -6,3 +6,12 @@
%include pythonprepend.i %include pythonprepend.i
%include pythonappend.i %include pythonappend.i
%include typemaps.i %include typemaps.i
/* SWIG 3.x resolved a bug in which all wrapped C++ functions took *args as its
* default argument list. OpenMM then exploited this bug by doing stuff like
* passing args to stripUnits (and all added code assumed that the arguments
* were in an "args" list). So in order to restore this arguably buggy behavior
* from SWIG 2, enable the "compactdefaultargs" feature globally.
*
* See https://github.com/swig/swig/issues/387
*/
%feature("compactdefaultargs");
...@@ -316,7 +316,17 @@ class TestAmberPrmtopFile(unittest.TestCase): ...@@ -316,7 +316,17 @@ class TestAmberPrmtopFile(unittest.TestCase):
simulation.reporters.append(DCDReporter(fname, 1)) # This is an explicit test for the bugs in issue #850 simulation.reporters.append(DCDReporter(fname, 1)) # This is an explicit test for the bugs in issue #850
simulation.step(5) simulation.step(5)
os.remove(fname) os.remove(fname)
def testChamber(self):
""" Tests that Chamber prmtops fail with proper error message """
self.assertRaises(TypeError, lambda: AmberPrmtopFile('systems/ala3_solv.parm7'))
try:
parm = AmberPrmtopFile('systems/ala3_solv.parm7')
# Should not make it past here
self.assertTrue(False)
except TypeError as e:
# Make sure it says something about chamber
self.assertTrue('chamber' in str(e).lower())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
...@@ -115,6 +115,13 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -115,6 +115,13 @@ class TestCharmmFiles(unittest.TestCase):
ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole) ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole)
self.assertAlmostEqual(ene, 15490.0033559, delta=0.05) self.assertAlmostEqual(ene, 15490.0033559, delta=0.05)
def test_InsCode(self):
""" Test the parsing of PSF files that contain insertion codes in their residue numbers """
psf = CharmmPsfFile('systems/4TVP-dmj_wat-ion.psf')
self.assertEqual(len(list(psf.topology.atoms())), 66264)
self.assertEqual(len(list(psf.topology.residues())), 20169)
self.assertEqual(len(list(psf.topology.bonds())), 46634)
def test_ImplicitSolventForces(self): def test_ImplicitSolventForces(self):
"""Compute forces for different implicit solvent types, and compare them to ones generated with a previous version of OpenMM to ensure they haven't changed.""" """Compute forces for different implicit solvent types, and compare them to ones generated with a previous version of OpenMM to ensure they haven't changed."""
solventType = [HCT, OBC1, OBC2, GBn, GBn2] solventType = [HCT, OBC1, OBC2, GBn, GBn2]
......
import pickle import pickle
import unittest import random
from simtk.unit import dalton from simtk.unit import dalton, is_quantity
from simtk.openmm.app import element from simtk.openmm.app import element
import unittest
class TestElement(unittest.TestCase): class TestElement(unittest.TestCase):
def test_immutable(self): def test_immutable(self):
...@@ -14,18 +15,37 @@ class TestElement(unittest.TestCase): ...@@ -14,18 +15,37 @@ class TestElement(unittest.TestCase):
newsulfur = pickle.loads(pickle.dumps(element.sulfur)) newsulfur = pickle.loads(pickle.dumps(element.sulfur))
# make sure that a new object is not created during the pickle/unpickle # make sure that a new object is not created during the pickle/unpickle
# cycle # cycle
assert element.sulfur == newsulfur self.assertEqual(element.sulfur, newsulfur)
assert element.sulfur is newsulfur self.assertTrue(element.sulfur is newsulfur)
assert id(element.sulfur) == id(newsulfur)
def test_attributes(self): def test_attributes(self):
assert element.hydrogen.atomic_number == 1 self.assertEqual(element.hydrogen.atomic_number, 1)
assert element.hydrogen.symbol == 'H' self.assertEqual(element.hydrogen.symbol, 'H')
assert element.hydrogen.name == 'hydrogen' self.assertEqual(element.hydrogen.name, 'hydrogen')
assert element.hydrogen.mass == 1.007947 * dalton self.assertEqual(element.hydrogen.mass, 1.007947 * dalton)
def test_getByMass(self):
""" Tests the getByMass method """
def exhaustive_search(mass):
"""
Searches through all element symbols and finds the one with the
smallest mass difference
"""
min_diff = mass
closest_element = None
for symbol, elem in element.Element._elements_by_symbol.items():
diff = abs(elem.mass._value - mass)
if diff < min_diff:
min_diff = diff
closest_element = elem
return closest_element
# Check 5000 random numbers between 0 and 300
for i in range(5000):
mass = random.random() * 300
elem = element.Element.getByMass(mass)
self.assertTrue(elem is exhaustive_search(mass))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
\ No newline at end of file
...@@ -308,7 +308,7 @@ class TestModeller(unittest.TestCase): ...@@ -308,7 +308,7 @@ class TestModeller(unittest.TestCase):
self.assertTrue(len(matoms)==0 and len(m1atoms)==1 and len(m2atoms)==1) self.assertTrue(len(matoms)==0 and len(m1atoms)==1 and len(m2atoms)==1)
def test_addSolventPeriodicBox(self): def test_addSolventPeriodicBox(self):
""" Test the addSolvent() method; test that the four ways of passing in the periodic box all work. """ """ Test the addSolvent() method; test that the five ways of passing in the periodic box all work. """
# First way of passing in periodic box vectors: set it in the original topology. # First way of passing in periodic box vectors: set it in the original topology.
topology_start = self.pdb.topology topology_start = self.pdb.topology
...@@ -358,6 +358,14 @@ class TestModeller(unittest.TestCase): ...@@ -358,6 +358,14 @@ class TestModeller(unittest.TestCase):
self.assertVecAlmostEqual(dim3[0]/nanometers, Vec3(2.8802, 0, 0)) self.assertVecAlmostEqual(dim3[0]/nanometers, Vec3(2.8802, 0, 0))
self.assertVecAlmostEqual(dim3[1]/nanometers, Vec3(0, 2.8802, 0)) self.assertVecAlmostEqual(dim3[1]/nanometers, Vec3(0, 2.8802, 0))
self.assertVecAlmostEqual(dim3[2]/nanometers, Vec3(0, 0, 2.8802)) self.assertVecAlmostEqual(dim3[2]/nanometers, Vec3(0, 0, 2.8802))
# Fifth way: specify a number of molecules to add instead of a box size
topology_start = self.pdb.topology
modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
numInitial = len(list(modeller.topology.residues()))
modeller.addSolvent(self.forcefield, numAdded=1000)
self.assertEqual(numInitial+1000, len(list(modeller.topology.residues())))
def test_addSolventNeutralSolvent(self): def test_addSolventNeutralSolvent(self):
""" Test the addSolvent() method; test adding ions to neutral solvent. """ """ Test the addSolvent() method; test adding ions to neutral solvent. """
...@@ -389,7 +397,7 @@ class TestModeller(unittest.TestCase): ...@@ -389,7 +397,7 @@ class TestModeller(unittest.TestCase):
def test_addSolventNegativeSolvent(self): def test_addSolventNegativeSolvent(self):
""" Test the addSolvent() method; test adding ions to a negatively charged solvent. """ """ Test the addSolvent() method; test adding ions to a negatively charged solvent. """
topology_start = self.pdb.topology topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers) topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
...@@ -476,7 +484,7 @@ class TestModeller(unittest.TestCase): ...@@ -476,7 +484,7 @@ class TestModeller(unittest.TestCase):
topology_start = self.pdb.topology topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers) topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
# set up modeller with no solvent # set up modeller with no solvent
modeller = Modeller(topology_start, self.positions) modeller = Modeller(topology_start, self.positions)
modeller.deleteWater() modeller.deleteWater()
...@@ -490,7 +498,7 @@ class TestModeller(unittest.TestCase): ...@@ -490,7 +498,7 @@ class TestModeller(unittest.TestCase):
modeller = Modeller(topology_nowater, positions_nowater) modeller = Modeller(topology_nowater, positions_nowater)
modeller.addSolvent(self.forcefield, positiveIon=positiveIon, ionicStrength=1.0*molar) modeller.addSolvent(self.forcefield, positiveIon=positiveIon, ionicStrength=1.0*molar)
topology_after = modeller.getTopology() topology_after = modeller.getTopology()
water_count = 0 water_count = 0
positive_ion_count = 0 positive_ion_count = 0
chlorine_count = 0 chlorine_count = 0
...@@ -514,7 +522,7 @@ class TestModeller(unittest.TestCase): ...@@ -514,7 +522,7 @@ class TestModeller(unittest.TestCase):
modeller.addSolvent(self.forcefield, negativeIon=negativeIon, ionicStrength=1.0*molar) modeller.addSolvent(self.forcefield, negativeIon=negativeIon, ionicStrength=1.0*molar)
topology_after = modeller.getTopology() topology_after = modeller.getTopology()
water_count = 0 water_count = 0
sodium_count = 0 sodium_count = 0
negative_ion_count = 0 negative_ion_count = 0
...@@ -543,7 +551,7 @@ class TestModeller(unittest.TestCase): ...@@ -543,7 +551,7 @@ class TestModeller(unittest.TestCase):
# remove hydrogens from the topology # remove hydrogens from the topology
toDelete = [atom for atom in topology_start.atoms() if atom.element==Element.getBySymbol('H')] toDelete = [atom for atom in topology_start.atoms() if atom.element==Element.getBySymbol('H')]
modeller.delete(toDelete) modeller.delete(toDelete)
# Create a variants list to force the one histidine to be of the right variation. # Create a variants list to force the one histidine to be of the right variation.
residues = [residue for residue in topology_start.residues()] residues = [residue for residue in topology_start.residues()]
variants = [None]*len(residues) variants = [None]*len(residues)
...@@ -557,7 +565,7 @@ class TestModeller(unittest.TestCase): ...@@ -557,7 +565,7 @@ class TestModeller(unittest.TestCase):
topology_after = modeller.getTopology() topology_after = modeller.getTopology()
validate_equivalence(self, topology_start, topology_after) validate_equivalence(self, topology_start, topology_after)
def test_addHydrogensPdb3(self): def test_addHydrogensPdb3(self):
""" Test the addHydrogens() method on the metallothionein pdb file. """ """ Test the addHydrogens() method on the metallothionein pdb file. """
...@@ -650,7 +658,7 @@ class TestModeller(unittest.TestCase): ...@@ -650,7 +658,7 @@ class TestModeller(unittest.TestCase):
# There should be extra hydrogens on the CYS residues. Assert that they exist # There should be extra hydrogens on the CYS residues. Assert that they exist
# on modeller2, then delete them and validate that the topologies match. # on modeller2, then delete them and validate that the topologies match.
# These are the indices of the hydrogens to delete from CYS to make CYX. # These are the indices of the hydrogens to delete from CYS to make CYX.
index_list_CYS = [31, 49, 110, 135, 171, 193, 229] index_list_CYS = [31, 49, 110, 135, 171, 193, 229]
atoms = [atom for atom in topology2.atoms()] atoms = [atom for atom in topology2.atoms()]
......
import unittest
import tempfile
from datetime import datetime, timedelta
from simtk.openmm import *
from simtk.openmm.app import *
from simtk.unit import *
class TestSimulation(unittest.TestCase):
"""Test the Simulation class"""
def testCheckpointing(self):
"""Test that checkpointing works correctly."""
pdb = PDBFile('systems/alanine-dipeptide-implicit.pdb')
ff = ForceField('amber99sb.xml', 'tip3p.xml')
system = ff.createSystem(pdb.topology)
integrator = VerletIntegrator(0.001*picoseconds)
# Create a Simulation.
simulation = Simulation(pdb.topology, system, integrator, Platform.getPlatformByName('Reference'))
simulation.context.setPositions(pdb.positions)
simulation.context.setVelocitiesToTemperature(300*kelvin)
initialState = simulation.context.getState(getPositions=True, getVelocities=True)
# Create a checkpoint.
filename = tempfile.mktemp()
simulation.saveCheckpoint(filename)
# Take a few steps so the positions and velocities will be different.
simulation.step(2)
state = simulation.context.getState(getPositions=True, getVelocities=True)
self.assertNotEqual(initialState.getPositions(), state.getPositions())
self.assertNotEqual(initialState.getVelocities(), state.getVelocities())
# Reload the checkpoint and see if it resets them correctly.
simulation.loadCheckpoint(filename)
state = simulation.context.getState(getPositions=True, getVelocities=True)
self.assertEqual(initialState.getPositions(), state.getPositions())
self.assertEqual(initialState.getVelocities(), state.getVelocities())
def testSaveState(self):
"""Test that saving States works correctly."""
pdb = PDBFile('systems/alanine-dipeptide-implicit.pdb')
ff = ForceField('amber99sb.xml', 'tip3p.xml')
system = ff.createSystem(pdb.topology)
integrator = VerletIntegrator(0.001*picoseconds)
# Create a Simulation.
simulation = Simulation(pdb.topology, system, integrator, Platform.getPlatformByName('Reference'))
simulation.context.setPositions(pdb.positions)
simulation.context.setVelocitiesToTemperature(300*kelvin)
initialState = simulation.context.getState(getPositions=True, getVelocities=True)
# Create a state.
filename = tempfile.mktemp()
simulation.saveState(filename)
# Take a few steps so the positions and velocities will be different.
simulation.step(2)
state = simulation.context.getState(getPositions=True, getVelocities=True)
self.assertNotEqual(initialState.getPositions(), state.getPositions())
self.assertNotEqual(initialState.getVelocities(), state.getVelocities())
# Reload the state and see if it resets them correctly.
simulation.loadState(filename)
state = simulation.context.getState(getPositions=True, getVelocities=True)
self.assertEqual(initialState.getPositions(), state.getPositions())
self.assertEqual(initialState.getVelocities(), state.getVelocities())
def testStep(self):
"""Test the step() method."""
pdb = PDBFile('systems/alanine-dipeptide-implicit.pdb')
ff = ForceField('amber99sb.xml', 'tip3p.xml')
system = ff.createSystem(pdb.topology)
integrator = VerletIntegrator(0.001*picoseconds)
# Create a Simulation.
simulation = Simulation(pdb.topology, system, integrator, Platform.getPlatformByName('Reference'))
simulation.context.setPositions(pdb.positions)
simulation.context.setVelocitiesToTemperature(300*kelvin)
self.assertEqual(0, simulation.currentStep)
self.assertEqual(0*picoseconds, simulation.context.getState().getTime())
# Take some steps and verify the simulation has advanced by the correct amount.
simulation.step(23)
self.assertEqual(23, simulation.currentStep)
self.assertAlmostEqual(0.023, simulation.context.getState().getTime().value_in_unit(picoseconds))
def testRunForClockTime(self):
"""Test the runForClockTime() method."""
pdb = PDBFile('systems/alanine-dipeptide-implicit.pdb')
ff = ForceField('amber99sb.xml', 'tip3p.xml')
system = ff.createSystem(pdb.topology)
integrator = VerletIntegrator(0.001*picoseconds)
# Create a Simulation.
simulation = Simulation(pdb.topology, system, integrator, Platform.getPlatformByName('Reference'))
simulation.context.setPositions(pdb.positions)
simulation.context.setVelocitiesToTemperature(300*kelvin)
self.assertEqual(0, simulation.currentStep)
self.assertEqual(0*picoseconds, simulation.context.getState().getTime())
# Run for five seconds, the save both a checkpoint and a state.
checkpointFile = tempfile.mktemp()
stateFile = tempfile.mktemp()
startTime = datetime.now()
simulation.runForClockTime(5*seconds, checkpointFile=checkpointFile, stateFile=stateFile)
endTime = datetime.now()
# Make sure at least five seconds have elapsed, but no more than ten.
self.assertTrue(endTime >= startTime+timedelta(seconds=5))
self.assertTrue(endTime < startTime+timedelta(seconds=10))
# Load the checkpoint and state and make sure they are both correct.
velocities = simulation.context.getState(getVelocities=True).getVelocities()
simulation.context.setVelocitiesToTemperature(300*kelvin)
simulation.loadCheckpoint(checkpointFile)
self.assertEqual(velocities, simulation.context.getState(getVelocities=True).getVelocities())
simulation.context.setVelocitiesToTemperature(300*kelvin)
simulation.loadState(stateFile)
self.assertEqual(velocities, simulation.context.getState(getVelocities=True).getVelocities())
if __name__ == '__main__':
unittest.main()
This diff is collapsed.
This diff is collapsed.
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