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 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* 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 *
* Contributors: *
* *
......@@ -38,6 +38,7 @@
#include "openmm/internal/vectorize.h"
#include <cmath>
#include <cstring>
#include <sstream>
using namespace OpenMM;
using namespace std;
......@@ -353,6 +354,9 @@ static void* threadBody(void* args) {
void CpuCalcPmeReciprocalForceKernel::initialize(int xsize, int ysize, int zsize, int numParticles, double alpha) {
if (!hasInitializedThreads) {
numThreads = getNumProcessors();
char* threadsEnv = getenv("OPENMM_CPU_THREADS");
if (threadsEnv != NULL)
stringstream(threadsEnv) >> numThreads;
fftwf_init_threads();
hasInitializedThreads = true;
}
......
......@@ -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, 0.0, -3.0);
verifySameValue(computed, expected, 2.0, 0.0);
}
/**
......@@ -249,6 +251,8 @@ int main() {
verifyEvaluation("step(x-3)+y*step(x)", 2.0, 3.0, 3.0);
verifyEvaluation("floor(x)", -2.1, 3.0, -3.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+3");
verifyInvalidExpression("5++4");
......@@ -282,6 +286,7 @@ int main() {
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("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^2, 1)+custom(2, y-1)", "2*x^2+4*(y-1)");
cout << Parser::parse("x*x").optimize() << endl;
......
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2014 Stanford University and the Authors. *
* Portions copyright (c) 2014-2015 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -148,6 +148,7 @@ void testMathFunctions() {
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(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]+f1[3]*f2[3], dot4(f1, f2), 1e-6);
ASSERT(any(f1 > 0.5));
......
......@@ -85,7 +85,7 @@ version = '%(version)s'
full_version = '%(full_version)s'
git_revision = '%(git_revision)s'
release = %(isrelease)s
openmm_library_path = '%(path)s'
openmm_library_path = r'%(path)s'
if not release:
version = full_version
......
......@@ -37,6 +37,7 @@ from __future__ import division
from functools import wraps
from math import pi, cos, sin, sqrt
import os
import re
import simtk.openmm as mm
from simtk.openmm.vec3 import Vec3
import simtk.unit as u
......@@ -99,6 +100,8 @@ class _ZeroDict(dict):
# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
_resre = re.compile(r'(\d+)([a-zA-Z]*)')
class CharmmPsfFile(object):
"""
A chemical structure instantiated from CHARMM files.
......@@ -192,11 +195,13 @@ class CharmmPsfFile(object):
atom_list = AtomList()
for i in xrange(natom):
words = psfsections['NATOM'][1][i].split()
atid = int(words[0])
if atid != i + 1:
raise CharmmPSFError('Nonsequential atoms detected!')
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]
name = words[4]
attype = words[5]
......@@ -209,7 +214,7 @@ class CharmmPsfFile(object):
mass = conv(words[7], float, 'atomic mass')
props = words[8:]
atom = residue_list.add_atom(system, resid, resname, name,
attype, charge, mass, props)
attype, charge, mass, inscode, props)
atom_list.append(atom)
atom_list.assign_indexes()
atom_list.changed = False
......@@ -443,175 +448,6 @@ class CharmmPsfFile(object):
line = psf.readline().strip()
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):
"""
Loads parameters from a parameter set that was loaded via CHARMM RTF,
......@@ -776,13 +612,13 @@ class CharmmPsfFile(object):
last_residue = None
# Add each chain (separate 'system's) and residue
for atom in self.atom_list:
resid = '%d%s' % (atom.residue.idx, atom.residue.inscode)
if atom.system != last_chain:
chain = topology.addChain(atom.system)
last_chain = atom.system
if atom.residue.idx != last_residue:
last_residue = atom.residue.idx
residue = topology.addResidue(atom.residue.resname, chain,
str(atom.residue.idx))
if resid != last_residue:
last_residue = resid
residue = topology.addResidue(atom.residue.resname, chain, resid)
if atom.type is not None:
# This is the most reliable way of determining the element
atomic_num = atom.type.atomic_number
......
......@@ -31,7 +31,7 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
__author__ = "Christopher M. Bruns"
__version__ = "1.0"
from collections import OrderedDict
from simtk.unit import daltons, is_quantity
import copy_reg
......@@ -47,6 +47,7 @@ class Element(object):
_elements_by_symbol = {}
_elements_by_atomic_number = {}
_elements_by_mass = None
def __init__(self, number, name, symbol, mass):
"""Create a new element
......@@ -67,8 +68,11 @@ class Element(object):
self._mass = mass
# Index this element in a global table
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
if number in Element._elements_by_atomic_number:
other_element = Element._elements_by_atomic_number[number]
......@@ -96,20 +100,45 @@ class Element(object):
"""
Get the element whose mass is CLOSEST to the requested mass. This method
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
if not is_quantity(mass):
mass = mass * daltons
if is_quantity(mass):
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
best_guess = None
for key in Element._elements_by_atomic_number:
element = Element._elements_by_atomic_number[key]
massdiff = abs(element.mass - mass)
for elemmass, element in Element._elements_by_mass.iteritems():
massdiff = abs(elemmass._value - mass)
if massdiff < diff:
best_guess = element
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
@property
......@@ -145,6 +174,9 @@ def _pickle_element(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)
deuterium = Element( 1, "deuterium", "D", 2.01355321270*daltons)
helium = Element( 2, "helium", "He", 4.003*daltons)
......
......@@ -127,6 +127,10 @@ class PrmtopLoader(object):
tag, self._prmtopVersion = line.rstrip().split(None, 1)
elif line.startswith('%FLAG'):
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._raw_data[flag] = []
elif line.startswith('%FORMAT'):
......
......@@ -375,12 +375,13 @@ class AtomList(TrackedList):
class Residue(object):
""" Residue class """
def __init__(self, resname, idx):
def __init__(self, resname, idx, inscode=''):
self.resname = resname
self.idx = idx
self.atoms = []
self.resnum = None # Numbered based on SYSTEM name
self.system = None
self.inscode = inscode
def add_atom(self, atom):
if self.system is None:
......@@ -416,7 +417,7 @@ class Residue(object):
return self.resname == thing.resname and self.idx == thing.idx
if isinstance(thing, tuple) or isinstance(thing, list):
# 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.
def __ne__(self, thing):
......@@ -447,7 +448,7 @@ class ResidueList(list):
resnum += 1
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
the last residue that was created, a new Residue is created and added
......@@ -461,25 +462,22 @@ class ResidueList(list):
- attype (int or str) : Type of the atom
- charge (float) : Partial atomic charge of the atom
- mass (float) : Mass (amu) of the atom
- inscode (str) : Insertion code, if it is specified
Returns:
The Atom instance created and added to the list of residues
"""
if self._last_residue is None:
res = self._last_residue = Residue(resname, resnum)
lr = self._last_residue
if lr is None:
res = self._last_residue = Residue(resname, resnum, inscode)
list.append(self, res)
elif (self._last_residue != (resname, resnum) or
system != self._last_residue.system):
if (self._last_residue.idx == resnum and
self._last_residue.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)
elif (lr.resname != resname or lr.idx != resname or
lr.inscode != inscode or system != lr.system):
res = self._last_residue = Residue(resname, resnum, inscode)
res.system = system
list.append(self, res)
else:
res = self._last_residue
res = lr
atom = Atom(system, name, attype, float(charge), float(mass), props)
res.add_atom(atom)
return atom
......
......@@ -240,7 +240,7 @@ class Modeller(object):
self.topology = newTopology
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.
The algorithm works as follows:
......@@ -250,11 +250,15 @@ class Modeller(object):
randomly selecting a water molecule and replacing it with the ion.
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
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
box of size (largest dimension)+2*padding is used. Finally, if neither box vectors, box size, nor padding distance is specified,
the existing Topology's box vectors are used.
The box size can be specified in any of several ways:
1. You can explicitly give the vectors defining the periodic box to use.
2. Alternatively, for a rectangular box you can simply give the dimensions of the unit cell.
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:
- forcefield (ForceField) the ForceField to use for determining van der Waals radii and atomic charges
......@@ -262,14 +266,43 @@ class Modeller(object):
- 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
- 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+'
- 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.
- 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.
"""
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.
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 is_quantity(boxVectors[0]):
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):
positiveElement = posIonElements[positiveIon]
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.
system = forcefield.createSystem(self.topology)
......@@ -424,27 +438,42 @@ class Modeller(object):
addedWaters.append((residue.index, atomPos))
# There could be clashes between water molecules at the box edges. Find ones to remove.
upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff)
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)
else:
if not any((periodicDistance(lowerSkinPositions[i], pos) < waterCutoff and norm(lowerSkinPositions[i]-pos) > waterCutoff for i in neighbors(pos))):
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
# only keep the ones in the middle.
lowerBound = center-box/2
upperBound = center+box/2
distToEdge = (min(min(pos-lowerBound), min(upperBound-pos)) for index, pos in addedWaters)
sortedIndex = [i[0] for i in sorted(enumerate(distToEdge), key=lambda x: -x[1])]
addedWaters = [addedWaters[i] for i in sortedIndex[:numAdded]]
# Compute a new periodic box size.
maxSize = max(max((pos[i] for index, pos in addedWaters))-min((pos[i] for index, pos in addedWaters)) for i in range(3))
newTopology.setUnitCellDimensions(Vec3(maxSize, maxSize, maxSize))
else:
# There could be clashes between water molecules at the box edges. Find ones to remove.
upperCutoff = center+box/2-Vec3(waterCutoff, waterCutoff, waterCutoff)
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)
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.
......
......@@ -6,7 +6,7 @@ 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) 2012 Stanford University and the Authors.
Portions copyright (c) 2012-2015 Stanford University and the Authors.
Authors: Peter Eastman
Contributors:
......@@ -33,6 +33,8 @@ __version__ = "1.0"
import simtk.openmm as mm
import simtk.unit as unit
import sys
from datetime import datetime, timedelta
class Simulation(object):
"""Simulation provides a simplified API for running simulations with OpenMM and reporting results.
......@@ -78,11 +80,11 @@ class Simulation(object):
else:
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.
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
until the results converge without regard to how many iterations it takes.
"""
......@@ -90,10 +92,51 @@ class Simulation(object):
def step(self, 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)
while self.currentStep < stepTo:
nextSteps = stepTo-self.currentStep
while self.currentStep < endStep:
nextSteps = endStep-self.currentStep
anyReport = False
for i, reporter in enumerate(self.reporters):
nextReport[i] = reporter.describeNextReport(self)
......@@ -104,6 +147,8 @@ class Simulation(object):
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.
stepsToGo -= 10
if endTime is not None and datetime.now() >= endTime:
return
self.integrator.step(stepsToGo)
self.currentStep += nextSteps
if anyReport:
......@@ -126,3 +171,71 @@ class Simulation(object):
if next[0] == nextSteps:
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 @@
}
%pythoncode {
%pythoncode %{
def getState(self,
getPositions=False,
getVelocities=False,
......@@ -107,7 +107,7 @@
if state._paramMap is not None:
for param in state._paramMap:
self.setParameter(param, state._paramMap[param])
}
%}
%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.
......@@ -175,7 +175,7 @@ Parameters:
}
%pythoncode {
%pythoncode %{
def getState(self,
copy,
getPositions=False,
......@@ -235,11 +235,11 @@ Parameters:
periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap)
return state
}
%}
}
%extend OpenMM::NonbondedForce {
%pythoncode {
%pythoncode %{
def addParticle_usingRVdw(self, charge, rVDW, epsilon):
"""Add particle using elemetrary charge. Rvdw and epsilon,
which is consistent with AMBER parameter file usage.
......@@ -260,11 +260,11 @@ Parameters:
"""
return self.addException(particle1, particle2,
chargeProd, rMin/RMIN_PER_SIGMA, epsilon)
}
%}
}
%extend OpenMM::System {
%pythoncode {
%pythoncode %{
def __getstate__(self):
serializationString = XmlSerializer.serializeSystem(self)
return serializationString
......@@ -275,7 +275,7 @@ Parameters:
def getForces(self):
"""Get the list of Forces in this System"""
return [self.getForce(i) for i in range(self.getNumForces())]
}
%}
}
%extend OpenMM::XmlSerializer {
......@@ -345,7 +345,7 @@ Parameters:
return obj;
}
%pythoncode {
%pythoncode %{
@staticmethod
def _serializeState(pythonState):
positions = []
......@@ -417,7 +417,6 @@ Parameters:
@staticmethod
def deserialize(inputString):
"""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
match = re.search("<([^?]\S*)", inputString)
if match is None:
......@@ -432,7 +431,7 @@ Parameters:
if type == "State":
return XmlSerializer._deserializeState(inputString)
raise ValueError("Unsupported object type")
}
%}
}
%extend OpenMM::CustomIntegrator {
......@@ -444,7 +443,7 @@ Parameters:
}
%extend OpenMM::Force {
%pythoncode {
%pythoncode %{
def __copy__(self):
copy = self.__class__.__new__(self.__class__)
copy.__init__(self)
......@@ -452,11 +451,11 @@ Parameters:
def __deepcopy__(self, memo):
return self.__copy__()
}
%}
}
%extend OpenMM::Integrator {
%pythoncode {
%pythoncode %{
def __getstate__(self):
serializationString = XmlSerializer.serialize(self)
return serializationString
......@@ -464,5 +463,5 @@ Parameters:
def __setstate__(self, serializationString):
system = XmlSerializer.deserialize(serializationString)
self.this = system.this
}
%}
}
......@@ -6,3 +6,12 @@
%include pythonprepend.i
%include pythonappend.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):
simulation.reporters.append(DCDReporter(fname, 1)) # This is an explicit test for the bugs in issue #850
simulation.step(5)
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__':
unittest.main()
......@@ -115,6 +115,13 @@ class TestCharmmFiles(unittest.TestCase):
ene = state.getPotentialEnergy().value_in_unit(kilocalories_per_mole)
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):
"""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]
......
import pickle
import unittest
from simtk.unit import dalton
import random
from simtk.unit import dalton, is_quantity
from simtk.openmm.app import element
import unittest
class TestElement(unittest.TestCase):
def test_immutable(self):
......@@ -14,18 +15,37 @@ class TestElement(unittest.TestCase):
newsulfur = pickle.loads(pickle.dumps(element.sulfur))
# make sure that a new object is not created during the pickle/unpickle
# cycle
assert element.sulfur == newsulfur
assert element.sulfur is newsulfur
assert id(element.sulfur) == id(newsulfur)
self.assertEqual(element.sulfur, newsulfur)
self.assertTrue(element.sulfur is newsulfur)
def test_attributes(self):
assert element.hydrogen.atomic_number == 1
assert element.hydrogen.symbol == 'H'
assert element.hydrogen.name == 'hydrogen'
assert element.hydrogen.mass == 1.007947 * dalton
self.assertEqual(element.hydrogen.atomic_number, 1)
self.assertEqual(element.hydrogen.symbol, 'H')
self.assertEqual(element.hydrogen.name, 'hydrogen')
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__':
unittest.main()
\ No newline at end of file
......@@ -308,7 +308,7 @@ class TestModeller(unittest.TestCase):
self.assertTrue(len(matoms)==0 and len(m1atoms)==1 and len(m2atoms)==1)
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.
topology_start = self.pdb.topology
......@@ -358,6 +358,14 @@ class TestModeller(unittest.TestCase):
self.assertVecAlmostEqual(dim3[0]/nanometers, Vec3(2.8802, 0, 0))
self.assertVecAlmostEqual(dim3[1]/nanometers, Vec3(0, 2.8802, 0))
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):
""" Test the addSolvent() method; test adding ions to neutral solvent. """
......@@ -389,7 +397,7 @@ class TestModeller(unittest.TestCase):
def test_addSolventNegativeSolvent(self):
""" Test the addSolvent() method; test adding ions to a negatively charged solvent. """
topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
......@@ -476,7 +484,7 @@ class TestModeller(unittest.TestCase):
topology_start = self.pdb.topology
topology_start.setUnitCellDimensions(Vec3(3.5, 3.5, 3.5)*nanometers)
# set up modeller with no solvent
modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
......@@ -490,7 +498,7 @@ class TestModeller(unittest.TestCase):
modeller = Modeller(topology_nowater, positions_nowater)
modeller.addSolvent(self.forcefield, positiveIon=positiveIon, ionicStrength=1.0*molar)
topology_after = modeller.getTopology()
water_count = 0
positive_ion_count = 0
chlorine_count = 0
......@@ -514,7 +522,7 @@ class TestModeller(unittest.TestCase):
modeller.addSolvent(self.forcefield, negativeIon=negativeIon, ionicStrength=1.0*molar)
topology_after = modeller.getTopology()
water_count = 0
sodium_count = 0
negative_ion_count = 0
......@@ -543,7 +551,7 @@ class TestModeller(unittest.TestCase):
# remove hydrogens from the topology
toDelete = [atom for atom in topology_start.atoms() if atom.element==Element.getBySymbol('H')]
modeller.delete(toDelete)
# Create a variants list to force the one histidine to be of the right variation.
residues = [residue for residue in topology_start.residues()]
variants = [None]*len(residues)
......@@ -557,7 +565,7 @@ class TestModeller(unittest.TestCase):
topology_after = modeller.getTopology()
validate_equivalence(self, topology_start, topology_after)
def test_addHydrogensPdb3(self):
""" Test the addHydrogens() method on the metallothionein pdb file. """
......@@ -650,7 +658,7 @@ class TestModeller(unittest.TestCase):
# There should be extra hydrogens on the CYS residues. Assert that they exist
# 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.
index_list_CYS = [31, 49, 110, 135, 171, 193, 229]
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