Commit 1c938ceb authored by Jason Swails's avatar Jason Swails
Browse files

Merge branch 'master' into amber-switching

 Conflicts:
	wrappers/python/simtk/openmm/app/amberprmtopfile.py

In fixing the merge conflict, I went ahead and fixed up the switchDistance logic
to match what I did in CharmmPsfFile.
parents a1113e7b 167ae8a0
...@@ -44,9 +44,12 @@ class PDBReporter(object): ...@@ -44,9 +44,12 @@ class PDBReporter(object):
def __init__(self, file, reportInterval): def __init__(self, file, reportInterval):
"""Create a PDBReporter. """Create a PDBReporter.
Parameters: Parameters
- file (string) The file to write to ----------
- reportInterval (int) The interval (in time steps) at which to write frames file : string
The file to write to
reportInterval : int
The interval (in time steps) at which to write frames
""" """
self._reportInterval = reportInterval self._reportInterval = reportInterval
self._out = open(file, 'w') self._out = open(file, 'w')
...@@ -56,11 +59,18 @@ class PDBReporter(object): ...@@ -56,11 +59,18 @@ class PDBReporter(object):
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
"""Get information about the next report this object will generate. """Get information about the next report this object will generate.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
Returns: A five element tuple. The first element is the number of steps until the simulation : Simulation
next report. The remaining elements specify whether that report will require The Simulation to generate a report for
positions, velocities, forces, and energies respectively.
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
""" """
steps = self._reportInterval - simulation.currentStep%self._reportInterval steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, True, False, False, False) return (steps, True, False, False, False)
...@@ -68,9 +78,12 @@ class PDBReporter(object): ...@@ -68,9 +78,12 @@ class PDBReporter(object):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if self._nextModel == 0: if self._nextModel == 0:
PDBFile.writeHeader(simulation.topology, self._out) PDBFile.writeHeader(simulation.topology, self._out)
...@@ -95,9 +108,12 @@ class PDBxReporter(PDBReporter): ...@@ -95,9 +108,12 @@ class PDBxReporter(PDBReporter):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if self._nextModel == 0: if self._nextModel == 0:
PDBxFile.writeHeader(simulation.topology, self._out) PDBxFile.writeHeader(simulation.topology, self._out)
......
...@@ -53,10 +53,14 @@ class PDBxFile(object): ...@@ -53,10 +53,14 @@ class PDBxFile(object):
def __init__(self, file): def __init__(self, file):
"""Load a PDBx/mmCIF file. """Load a PDBx/mmCIF file.
The atom positions and Topology can be retrieved by calling getPositions() and getTopology(). The atom positions and Topology can be retrieved by calling
getPositions() and getTopology().
Parameters:
- file (string) the name of the file to load. Alternatively you can pass an open file object. Parameters
----------
file : string
the name of the file to load. Alternatively you can pass an open
file object.
""" """
top = Topology() top = Topology()
## The Topology read from the PDBx/mmCIF file ## The Topology read from the PDBx/mmCIF file
...@@ -193,10 +197,14 @@ class PDBxFile(object): ...@@ -193,10 +197,14 @@ class PDBxFile(object):
def getPositions(self, asNumpy=False, frame=0): def getPositions(self, asNumpy=False, frame=0):
"""Get the atomic positions. """Get the atomic positions.
Parameters: Parameters
- asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s ----------
- frame (int=0) the index of the frame for which to get positions asNumpy : bool=False
""" if true, the values are returned as a numpy array instead of a list
of Vec3s
frame : int=0
the index of the frame for which to get positions
"""
if asNumpy: if asNumpy:
if self._numpyPositions is None: if self._numpyPositions is None:
self._numpyPositions = [None]*len(self._positions) self._numpyPositions = [None]*len(self._positions)
...@@ -210,14 +218,21 @@ class PDBxFile(object): ...@@ -210,14 +218,21 @@ class PDBxFile(object):
entry=None): entry=None):
"""Write a PDBx/mmCIF file containing a single model. """Write a PDBx/mmCIF file containing a single model.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write to The Topology defining the model to write
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating positions : list
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of The list of atomic positions to write
the PDBx/mmCIF format. Otherwise, the output file will be invalid. file : file=stdout
- entry (str=None) The entry ID to assign to the CIF file A file to write to
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDBx/mmCIF format. Otherwise, the output file will be invalid.
entry : str=None
The entry ID to assign to the CIF file
""" """
PDBxFile.writeHeader(topology, file, entry) PDBxFile.writeHeader(topology, file, entry)
PDBxFile.writeModel(topology, positions, file, keepIds=keepIds) PDBxFile.writeModel(topology, positions, file, keepIds=keepIds)
...@@ -226,10 +241,14 @@ class PDBxFile(object): ...@@ -226,10 +241,14 @@ class PDBxFile(object):
def writeHeader(topology, file=sys.stdout, entry=None): def writeHeader(topology, file=sys.stdout, entry=None):
"""Write out the header for a PDBx/mmCIF file. """Write out the header for a PDBx/mmCIF file.
Parameters: Parameters
- topology (Topology) The Topology defining the molecular system being written ----------
- file (file=stdout) A file to write the file to topology : Topology
- entry (str=None) The entry ID to assign to the CIF file The Topology defining the molecular system being written
file : file=stdout
A file to write the file to
entry : str=None
The entry ID to assign to the CIF file
""" """
if entry is not None: if entry is not None:
print('data_%s' % entry, file=file) print('data_%s' % entry, file=file)
...@@ -280,14 +299,21 @@ class PDBxFile(object): ...@@ -280,14 +299,21 @@ class PDBxFile(object):
def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False): def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False):
"""Write out a model to a PDBx/mmCIF file. """Write out a model to a PDBx/mmCIF file.
Parameters: Parameters
- topology (Topology) The Topology defining the model to write ----------
- positions (list) The list of atomic positions to write topology : Topology
- file (file=stdout) A file to write the model to The Topology defining the model to write
- modelIndex (int=1) The model number of this frame positions : list
- keepIds (bool=False) If True, keep the residue and chain IDs specified in the Topology rather than generating The list of atomic positions to write
new ones. Warning: It is up to the caller to make sure these are valid IDs that satisfy the requirements of file : file=stdout
the PDBx/mmCIF format. Otherwise, the output file will be invalid. A file to write the model to
modelIndex : int=1
The model number of this frame
keepIds : bool=False
If True, keep the residue and chain IDs specified in the Topology
rather than generating new ones. Warning: It is up to the caller to
make sure these are valid IDs that satisfy the requirements of the
PDBx/mmCIF format. Otherwise, the output file will be invalid.
""" """
if len(list(topology.atoms())) != len(positions): if len(list(topology.atoms())) != len(positions):
raise ValueError('The number of positions must match the number of atoms') raise ValueError('The number of positions must match the number of atoms')
......
...@@ -55,13 +55,19 @@ class Simulation(object): ...@@ -55,13 +55,19 @@ class Simulation(object):
def __init__(self, topology, system, integrator, platform=None, platformProperties=None): def __init__(self, topology, system, integrator, platform=None, platformProperties=None):
"""Create a Simulation. """Create a Simulation.
Parameters: Parameters
- topology (Topology) A Topology describing the the system to simulate ----------
- system (System) The OpenMM System object to simulate topology : Topology
- integrator (Integrator) The OpenMM Integrator to use for simulating the System A Topology describing the the system to simulate
- platform (Platform=None) If not None, the OpenMM Platform to use system : System
- platformProperties (map=None) If not None, a set of platform-specific properties to pass The OpenMM System object to simulate
to the Context's constructor integrator : Integrator
The OpenMM Integrator to use for simulating the System
platform : Platform=None
If not None, the OpenMM Platform to use
platformProperties : map=None
If not None, a set of platform-specific properties to pass to the
Context's constructor
""" """
## The Topology describing the system being simulated ## The Topology describing the system being simulated
self.topology = topology self.topology = topology
...@@ -84,35 +90,48 @@ class Simulation(object): ...@@ -84,35 +90,48 @@ class Simulation(object):
def minimizeEnergy(self, tolerance=10*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=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 tolerance : energy=10*kilojoules/mole
until the results converge without regard to how many iterations it takes. 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.
""" """
mm.LocalEnergyMinimizer.minimize(self.context, tolerance, maxIterations) mm.LocalEnergyMinimizer.minimize(self.context, tolerance, maxIterations)
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."""
self._simulate(endStep=self.currentStep+steps) self._simulate(endStep=self.currentStep+steps)
def runForClockTime(self, time, checkpointFile=None, stateFile=None, checkpointInterval=None): 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. """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 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, 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 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 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. you can resume even if the simulation is interrupted before the time limit is reached.
Parameters: 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 time : time
simulation (and optionally at regular intervals before then) by passing this to saveCheckpoint(). the amount of time to run for. If no units are specified, it is
- stateFile (string or file=None) if specified, a state file will be written at the end of the assumed to be a number of hours.
simulation (and optionally at regular intervals before then) by passing this to saveState(). checkpointFile : string or file=None
- checkpointInterval (time=None) if specified, checkpoints and/or states will be written at regular intervals if specified, a checkpoint file will be written at the end of the
during the simulation, in addition to writing a final version at the end. If no units are specified, this is simulation (and optionally at regular intervals before then) by
assumed to be in hours. 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): if unit.is_quantity(time):
time = time.value_in_unit(unit.hours) time = time.value_in_unit(unit.hours)
...@@ -131,7 +150,7 @@ class Simulation(object): ...@@ -131,7 +150,7 @@ class Simulation(object):
self.saveCheckpoint(checkpointFile) self.saveCheckpoint(checkpointFile)
if stateFile is not None: if stateFile is not None:
self.saveState(stateFile) self.saveState(stateFile)
def _simulate(self, endStep=None, endTime=None): def _simulate(self, endStep=None, endTime=None):
if endStep is None: if endStep is None:
endStep = sys.maxsize endStep = sys.maxsize
...@@ -174,30 +193,36 @@ class Simulation(object): ...@@ -174,30 +193,36 @@ class Simulation(object):
def saveCheckpoint(self, file): def saveCheckpoint(self, file):
"""Save a checkpoint of the simulation to a 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. 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 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 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. 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 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 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. identical hardware. If you need a more portable way to resume simulations, consider using saveState() instead.
Parameters: Parameters
- file (string or file) a File-like object to write the checkpoint to, or alternatively a filename ----------
file : string or file
a File-like object to write the checkpoint to, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'wb') as f: with open(file, 'wb') as f:
f.write(self.context.createCheckpoint()) f.write(self.context.createCheckpoint())
else: else:
file.write(self.context.createCheckpoint()) file.write(self.context.createCheckpoint())
def loadCheckpoint(self, file): def loadCheckpoint(self, file):
"""Load a checkpoint file that was created with saveCheckpoint(). """Load a checkpoint file that was created with saveCheckpoint().
Parameters: Parameters
- file (string or file) a File-like object to load the checkpoint from, or alternatively a filename ----------
file : string or file
a File-like object to load the checkpoint from, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'rb') as f: with open(file, 'rb') as f:
...@@ -207,18 +232,21 @@ class Simulation(object): ...@@ -207,18 +232,21 @@ class Simulation(object):
def saveState(self, file): def saveState(self, file):
"""Save the current state of the simulation to a 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, 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 including positions, velocities, and parameters. Reloading the State will put the Simulation back into
approximately the same state it had before. approximately the same state it had before.
Unlike saveCheckpoint(), this does not store internal data such as the states of random number generators. 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 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 with the original Simulation. On the other hand, this means it is portable across different Platforms or
hardware. hardware.
Parameters: Parameters
- file (string or file) a File-like object to write the state to, or alternatively a filename ----------
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) state = self.context.getState(getPositions=True, getVelocities=True, getParameters=True)
xml = mm.XmlSerializer.serialize(state) xml = mm.XmlSerializer.serialize(state)
...@@ -227,12 +255,15 @@ class Simulation(object): ...@@ -227,12 +255,15 @@ class Simulation(object):
f.write(xml) f.write(xml)
else: else:
file.write(xml) file.write(xml)
def loadState(self, file): def loadState(self, file):
"""Load a State file that was created with saveState(). """Load a State file that was created with saveState().
Parameters: Parameters
- file (string or file) a File-like object to load the state from, or alternatively a filename ----------
file : string or file
a File-like object to load the state from, or alternatively a
filename
""" """
if isinstance(file, str): if isinstance(file, str):
with open(file, 'r') as f: with open(file, 'r') as f:
......
...@@ -60,31 +60,54 @@ class StateDataReporter(object): ...@@ -60,31 +60,54 @@ class StateDataReporter(object):
progress=False, remainingTime=False, speed=False, elapsedTime=False, separator=',', systemMass=None, totalSteps=None): progress=False, remainingTime=False, speed=False, elapsedTime=False, separator=',', systemMass=None, totalSteps=None):
"""Create a StateDataReporter. """Create a StateDataReporter.
Parameters: Parameters
- file (string or file) The file to write to, specified as a file name or file object ----------
- reportInterval (int) The interval (in time steps) at which to write frames file : string or file
- step (boolean=False) Whether to write the current step index to the file The file to write to, specified as a file name or file object
- time (boolean=False) Whether to write the current time to the file reportInterval : int
- potentialEnergy (boolean=False) Whether to write the potential energy to the file The interval (in time steps) at which to write frames
- kineticEnergy (boolean=False) Whether to write the kinetic energy to the file step : bool=False
- totalEnergy (boolean=False) Whether to write the total energy to the file Whether to write the current step index to the file
- temperature (boolean=False) Whether to write the instantaneous temperature to the file time : bool=False
- volume (boolean=False) Whether to write the periodic box volume to the file Whether to write the current time to the file
- density (boolean=False) Whether to write the system density to the file potentialEnergy : bool=False
- progress (boolean=False) Whether to write current progress (percent completion) to the file. Whether to write the potential energy to the file
If this is True, you must also specify totalSteps. kineticEnergy : bool=False
- remainingTime (boolean=False) Whether to write an estimate of the remaining clock time until Whether to write the kinetic energy to the file
completion to the file. If this is True, you must also specify totalSteps. totalEnergy : bool=False
- speed (bool=False) Whether to write an estimate of the simulation speed in ns/day to the file Whether to write the total energy to the file
- elapsedTime (bool=False) Whether to write the elapsed time of the simulation in seconds to the file. temperature : bool=False
- separator (string=',') The separator to use between columns in the file Whether to write the instantaneous temperature to the file
- systemMass (mass=None) The total mass to use for the system when reporting density. If this is volume : bool=False
None (the default), the system mass is computed by summing the masses of all particles. This Whether to write the periodic box volume to the file
parameter is useful when the particle masses do not reflect their actual physical mass, such as density : bool=False
when some particles have had their masses set to 0 to immobilize them. Whether to write the system density to the file
- totalSteps (int=None) The total number of steps that will be included in the simulation. This progress : bool=False
is required if either progress or remainingTime is set to True, and defines how many steps will Whether to write current progress (percent completion) to the file.
indicate 100% completion. If this is True, you must also specify totalSteps.
remainingTime : bool=False
Whether to write an estimate of the remaining clock time until
completion to the file. If this is True, you must also specify
totalSteps.
speed : bool=False
Whether to write an estimate of the simulation speed in ns/day to
the file
elapsedTime : bool=False
Whether to write the elapsed time of the simulation in seconds to
the file.
separator : string=','
The separator to use between columns in the file
systemMass : mass=None
The total mass to use for the system when reporting density. If
this is None (the default), the system mass is computed by summing
the masses of all particles. This parameter is useful when the
particle masses do not reflect their actual physical mass, such as
when some particles have had their masses set to 0 to immobilize
them.
totalSteps : int=None
The total number of steps that will be included in the simulation.
This is required if either progress or remainingTime is set to True,
and defines how many steps will indicate 100% completion.
""" """
self._reportInterval = reportInterval self._reportInterval = reportInterval
self._openedFile = isinstance(file, str) self._openedFile = isinstance(file, str)
...@@ -129,11 +152,18 @@ class StateDataReporter(object): ...@@ -129,11 +152,18 @@ class StateDataReporter(object):
def describeNextReport(self, simulation): def describeNextReport(self, simulation):
"""Get information about the next report this object will generate. """Get information about the next report this object will generate.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
Returns: A five element tuple. The first element is the number of steps until the simulation : Simulation
next report. The remaining elements specify whether that report will require The Simulation to generate a report for
positions, velocities, forces, and energies respectively.
Returns
-------
tuple
A five element tuple. The first element is the number of steps
until the next report. The remaining elements specify whether
that report will require positions, velocities, forces, and
energies respectively.
""" """
steps = self._reportInterval - simulation.currentStep%self._reportInterval steps = self._reportInterval - simulation.currentStep%self._reportInterval
return (steps, self._needsPositions, self._needsVelocities, self._needsForces, self._needEnergy) return (steps, self._needsPositions, self._needsVelocities, self._needsForces, self._needEnergy)
...@@ -141,9 +171,12 @@ class StateDataReporter(object): ...@@ -141,9 +171,12 @@ class StateDataReporter(object):
def report(self, simulation, state): def report(self, simulation, state):
"""Generate a report. """Generate a report.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
""" """
if not self._hasInitialized: if not self._hasInitialized:
self._initializeConstants(simulation) self._initializeConstants(simulation)
...@@ -174,11 +207,16 @@ class StateDataReporter(object): ...@@ -174,11 +207,16 @@ class StateDataReporter(object):
def _constructReportValues(self, simulation, state): def _constructReportValues(self, simulation, state):
"""Query the simulation for the current state of our observables of interest. """Query the simulation for the current state of our observables of interest.
Parameters: Parameters
- simulation (Simulation) The Simulation to generate a report for ----------
- state (State) The current state of the simulation simulation : Simulation
The Simulation to generate a report for
state : State
The current state of the simulation
Returns: A list of values summarizing the current state of Returns
-------
A list of values summarizing the current state of
the simulation, to be printed or saved. Each element in the list the simulation, to be printed or saved. Each element in the list
corresponds to one of the columns in the resulting CSV file. corresponds to one of the columns in the resulting CSV file.
""" """
......
...@@ -62,19 +62,43 @@ class Topology(object): ...@@ -62,19 +62,43 @@ class Topology(object):
def __repr__(self): def __repr__(self):
nchains = len(self._chains) nchains = len(self._chains)
nres = sum(1 for r in self.residues()) nres = self._numResidues
natom = sum(1 for a in self.atoms()) natom = self._numAtoms
nbond = len(self._bonds) nbond = len(self._bonds)
return '<%s; %d chains, %d residues, %d atoms, %d bonds>' % ( return '<%s; %d chains, %d residues, %d atoms, %d bonds>' % (
type(self).__name__, nchains, nres, natom, nbond) type(self).__name__, nchains, nres, natom, nbond)
def getNumAtoms(self):
"""Return the number of atoms in the Topology.
"""
natom = self._numAtoms
return natom
def getNumResidues(self):
"""Return the number of residues in the Topology.
"""
nres = self._numResidues
return nres
def getNumChains(self):
"""Return the number of chains in the Topology.
"""
nchain = len(self._chains)
return nchain
def addChain(self, id=None): def addChain(self, id=None):
"""Create a new Chain and add it to the Topology. """Create a new Chain and add it to the Topology.
Parameters: Parameters
- id (string=None) An optional identifier for the chain. If this is omitted, an id ----------
is generated based on the chain index. id : string=None
Returns: the newly created Chain An optional identifier for the chain. If this is omitted, an id is
generated based on the chain index.
Returns
-------
Chain
the newly created Chain
""" """
if id is None: if id is None:
id = str(len(self._chains)+1) id = str(len(self._chains)+1)
...@@ -85,12 +109,20 @@ class Topology(object): ...@@ -85,12 +109,20 @@ class Topology(object):
def addResidue(self, name, chain, id=None): def addResidue(self, name, chain, id=None):
"""Create a new Residue and add it to the Topology. """Create a new Residue and add it to the Topology.
Parameters: Parameters
- name (string) The name of the residue to add ----------
- chain (Chain) The Chain to add it to name : string
- id (string=None) An optional identifier for the residue. If this is omitted, an id The name of the residue to add
is generated based on the residue index. chain : Chain
Returns: the newly created Residue The Chain to add it to
id : string=None
An optional identifier for the residue. If this is omitted, an id
is generated based on the residue index.
Returns
-------
Residue
the newly created Residue
""" """
if id is None: if id is None:
id = str(self._numResidues+1) id = str(self._numResidues+1)
...@@ -102,13 +134,22 @@ class Topology(object): ...@@ -102,13 +134,22 @@ class Topology(object):
def addAtom(self, name, element, residue, id=None): def addAtom(self, name, element, residue, id=None):
"""Create a new Atom and add it to the Topology. """Create a new Atom and add it to the Topology.
Parameters: Parameters
- name (string) The name of the atom to add ----------
- element (Element) The element of the atom to add name : string
- residue (Residue) The Residue to add it to The name of the atom to add
- id (string=None) An optional identifier for the atom. If this is omitted, an id element : Element
is generated based on the atom index. The element of the atom to add
Returns: the newly created Atom residue : Residue
The Residue to add it to
id : string=None
An optional identifier for the atom. If this is omitted, an id is
generated based on the atom index.
Returns
-------
Atom
the newly created Atom
""" """
if id is None: if id is None:
id = str(self._numAtoms+1) id = str(self._numAtoms+1)
...@@ -120,9 +161,12 @@ class Topology(object): ...@@ -120,9 +161,12 @@ class Topology(object):
def addBond(self, atom1, atom2): def addBond(self, atom1, atom2):
"""Create a new bond and add it to the Topology. """Create a new bond and add it to the Topology.
Parameters: Parameters
- atom1 (Atom) The first Atom connected by the bond ----------
- atom2 (Atom) The second Atom connected by the bond atom1 : Atom
The first Atom connected by the bond
atom2 : Atom
The second Atom connected by the bond
""" """
self._bonds.append((atom1, atom2)) self._bonds.append((atom1, atom2))
...@@ -256,10 +300,13 @@ class Topology(object): ...@@ -256,10 +300,13 @@ class Topology(object):
self.addBond(atomMaps[fromResidue][fromAtom], atomMaps[toResidue][toAtom]) self.addBond(atomMaps[fromResidue][fromAtom], atomMaps[toResidue][toAtom])
def createDisulfideBonds(self, positions): def createDisulfideBonds(self, positions):
"""Identify disulfide bonds based on proximity and add them to the Topology. """Identify disulfide bonds based on proximity and add them to the
Topology.
Parameters: Parameters
- positions (list) The list of atomic positions based on which to identify bonded atoms ----------
positions : list
The list of atomic positions based on which to identify bonded atoms
""" """
def isCyx(res): def isCyx(res):
names = [atom.name for atom in res._atoms] names = [atom.name for atom in res._atoms]
......
...@@ -10,7 +10,7 @@ Portions copyright (c) 2013-2015 Stanford University and the Authors. ...@@ -10,7 +10,7 @@ Portions copyright (c) 2013-2015 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors:
Permission is hereby granted, free of charge, to any person obtaining a Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, the rights to use, copy, modify, merge, publish, distribute, sublicense,
...@@ -36,49 +36,52 @@ from simtk.openmm import CustomIntegrator ...@@ -36,49 +36,52 @@ from simtk.openmm import CustomIntegrator
class MTSIntegrator(CustomIntegrator): class MTSIntegrator(CustomIntegrator):
"""MTSIntegrator implements the rRESPA multiple time step integration algorithm. """MTSIntegrator implements the rRESPA multiple time step integration algorithm.
This integrator allows different forces to be evaluated at different frequencies, This integrator allows different forces to be evaluated at different frequencies,
for example to evaluate the expensive, slowly changing forces less frequently than for example to evaluate the expensive, slowly changing forces less frequently than
the inexpensive, quickly changing forces. the inexpensive, quickly changing forces.
To use it, you must first divide your forces into two or more groups (by calling To use it, you must first divide your forces into two or more groups (by calling
setForceGroup() on them) that should be evaluated at different frequencies. When setForceGroup() on them) that should be evaluated at different frequencies. When
you create the integrator, you provide a tuple for each group specifying the index you create the integrator, you provide a tuple for each group specifying the index
of the force group and the frequency (as a fraction of the outermost time step) at of the force group and the frequency (as a fraction of the outermost time step) at
which to evaluate it. For example: which to evaluate it. For example:
<pre> <pre>
integrator = MTSIntegrator(4*femtoseconds, [(0,1), (1,2), (2,8)]) integrator = MTSIntegrator(4*femtoseconds, [(0,1), (1,2), (2,8)])
</pre> </pre>
This specifies that the outermost time step is 4 fs, so each step of the integrator This specifies that the outermost time step is 4 fs, so each step of the integrator
will advance time by that much. It also says that force group 0 should be evaluated will advance time by that much. It also says that force group 0 should be evaluated
once per time step, force group 1 should be evaluated twice per time step (every 2 fs), once per time step, force group 1 should be evaluated twice per time step (every 2 fs),
and force group 2 should be evaluated eight times per time step (every 0.5 fs). and force group 2 should be evaluated eight times per time step (every 0.5 fs).
A common use of this algorithm is to evaluate reciprocal space nonbonded interactions A common use of this algorithm is to evaluate reciprocal space nonbonded interactions
less often than the bonded and direct space nonbonded interactions. The following less often than the bonded and direct space nonbonded interactions. The following
example looks up the NonbondedForce, sets the reciprocal space interactions to their example looks up the NonbondedForce, sets the reciprocal space interactions to their
own force group, and then creates an integrator that evaluates them once every 4 fs, own force group, and then creates an integrator that evaluates them once every 4 fs,
but all other interactions every 2 fs. but all other interactions every 2 fs.
<pre> <pre>
nonbonded = [f for f in system.getForces() if isinstance(f, NonbondedForce)][0] nonbonded = [f for f in system.getForces() if isinstance(f, NonbondedForce)][0]
nonbonded.setReciprocalSpaceForceGroup(1) nonbonded.setReciprocalSpaceForceGroup(1)
integrator = MTSIntegrator(4*femtoseconds, [(1,1), (0,2)]) integrator = MTSIntegrator(4*femtoseconds, [(1,1), (0,2)])
</pre> </pre>
For details, see Tuckerman et al., J. Chem. Phys. 97(3) pp. 1990-2001 (1992). For details, see Tuckerman et al., J. Chem. Phys. 97(3) pp. 1990-2001 (1992).
""" """
def __init__(self, dt, groups): def __init__(self, dt, groups):
"""Create an MTSIntegrator. """Create an MTSIntegrator.
Parameters: Parameters
- dt (time) The largest (outermost) integration time step to use ----------
- groups (list) A list of tuples defining the force groups. The first element of each dt : time
tuple is the force group index, and the second element is the number of times that force The largest (outermost) integration time step to use
group should be evaluated in one time step. groups : list
A list of tuples defining the force groups. The first element of
each tuple is the force group index, and the second element is the
number of times that force group should be evaluated in one time step.
""" """
if len(groups) == 0: if len(groups) == 0:
raise ValueError("No force groups specified") raise ValueError("No force groups specified")
...@@ -88,7 +91,7 @@ class MTSIntegrator(CustomIntegrator): ...@@ -88,7 +91,7 @@ class MTSIntegrator(CustomIntegrator):
self.addUpdateContextState(); self.addUpdateContextState();
self._createSubsteps(1, groups) self._createSubsteps(1, groups)
self.addConstrainVelocities(); self.addConstrainVelocities();
def _createSubsteps(self, parentSubsteps, groups): def _createSubsteps(self, parentSubsteps, groups):
group, substeps = groups[0] group, substeps = groups[0]
stepsPerParentStep = substeps/parentSubsteps stepsPerParentStep = substeps/parentSubsteps
......
...@@ -54,9 +54,12 @@ class Unit(object): ...@@ -54,9 +54,12 @@ class Unit(object):
def __init__(self, base_or_scaled_units): def __init__(self, base_or_scaled_units):
"""Create a new Unit. """Create a new Unit.
Parameters: Parameters
- self (Unit) The newly created Unit. ----------
- base_or_scaled_units (dict) Keys are BaseUnits or ScaledUnits. Values are exponents (numbers). self : Unit
The newly created Unit.
base_or_scaled_units : dict
Keys are BaseUnits or ScaledUnits. Values are exponents (numbers).
""" """
# Unit contents are of two types: BaseUnits and ScaledUnits # Unit contents are of two types: BaseUnits and ScaledUnits
self._top_base_units = {} self._top_base_units = {}
...@@ -389,7 +392,8 @@ class Unit(object): ...@@ -389,7 +392,8 @@ class Unit(object):
Strips off any ScaledUnits in the Unit, leaving only BaseUnits. Strips off any ScaledUnits in the Unit, leaving only BaseUnits.
Parameters Parameters
- system: a dictionary of (BaseDimension, BaseUnit) pairs ----------
system : a dictionary of (BaseDimension, BaseUnit) pairs
""" """
return system.express_unit(self) return system.express_unit(self)
...@@ -583,7 +587,7 @@ class UnitSystem(object): ...@@ -583,7 +587,7 @@ class UnitSystem(object):
Parameters Parameters
---------- ----------
units: ``list`` units : list
List of base units from which to construct the unit system List of base units from which to construct the unit system
""" """
def __init__(self, units): def __init__(self, units):
...@@ -678,7 +682,7 @@ def is_unit(x): ...@@ -678,7 +682,7 @@ def is_unit(x):
Returns True if x is a Unit, False otherwise. Returns True if x is a Unit, False otherwise.
Examples Examples
--------
>>> is_unit(16) >>> is_unit(16)
False False
""" """
......
...@@ -45,7 +45,7 @@ using namespace OpenMM; ...@@ -45,7 +45,7 @@ using namespace OpenMM;
%} %}
%feature("autodoc", "1"); %feature("autodoc", "0");
%nodefaultctor; %nodefaultctor;
%include features.i %include features.i
...@@ -61,49 +61,3 @@ using namespace OpenMM; ...@@ -61,49 +61,3 @@ using namespace OpenMM;
# namespace # namespace
__all__ = [k for k in locals().keys() if not (k.endswith('_swigregister') or k.startswith('_'))] __all__ = [k for k in locals().keys() if not (k.endswith('_swigregister') or k.startswith('_'))]
%} %}
/*
%extend OpenMM::XmlSerializer {
%template(XmlSerializer_serialize_AndersenThermostat) XmlSerializer::serialize<AndersenThermostat>;
%template(XmlSerializer_serialize_RBTorsionForce) XmlSerializer::serialize<RBTorsionForce>;
%template(XmlSerializer_serialize_CMAPTorsionForce) XmlSerializer::serialize<CMAPTorsionForce>;
%template(XmlSerializer_serialize_CMMotionRemover) XmlSerializer::serialize<CMMotionRemover>;
%template(XmlSerializer_serialize_CustomAngleForce) XmlSerializer::serialize<CustomAngleForce>;
%template(XmlSerializer_serialize_CustomBondForce) XmlSerializer::serialize<CustomBondForce>;
%template(XmlSerializer_serialize_CustomExternalForce) XmlSerializer::serialize<CustomExternalForce>;
%template(XmlSerializer_serialize_CustomGBForce) XmlSerializer::serialize<CustomGBForce>;
%template(XmlSerializer_serialize_CustomHbondForce) XmlSerializer::serialize<CustomHbondForce>;
%template(XmlSerializer_serialize_CustomNonbondedForce) XmlSerializer::serialize<CustomNonbondedForce>;
%template(XmlSerializer_serialize_CustomTorsionForce) XmlSerializer::serialize<CustomTorsionForce>;
%template(XmlSerializer_serialize_GBSAOBCForce) XmlSerializer::serialize<GBSAOBCForce>;
%template(XmlSerializer_serialize_GBVIForce) XmlSerializer::serialize<GBVIForce>;
%template(XmlSerializer_serialize_HarmonicAngleForce) XmlSerializer::serialize<HarmonicAngleForce>;
%template(XmlSerializer_serialize_HarmonicBondForce) XmlSerializer::serialize<HarmonicBondForce>;
%template(XmlSerializer_serialize_MonteCarloBarostat) XmlSerializer::serialize<MonteCarloBarostat>;
%template(XmlSerializer_serialize_MonteCarloAnisotropicBarostat) XmlSerializer::serialize<MonteCarloAnisotropicBarostat>;
%template(XmlSerializer_serialize_NonbondedForce) XmlSerializer::serialize<NonbondedForce>;
%template(XmlSerializer_serialize_RBTorsionForce) XmlSerializer::serialize<RBTorsionForce>;
%template(XmlSerializer_serialize_System) XmlSerializer::serialize<System>;
%template(XmlSerializer_deserialize_AndersenThermostat) XmlSerializer::deserialize<AndersenThermostat>;
%template(XmlSerializer_deserialize_RBTorsionForce) XmlSerializer::deserialize<RBTorsionForce>;
%template(XmlSerializer_deserialize_CMAPTorsionForce) XmlSerializer::deserialize<CMAPTorsionForce>;
%template(XmlSerializer_deserialize_CMMotionRemover) XmlSerializer::deserialize<CMMotionRemover>;
%template(XmlSerializer_deserialize_CustomAngleForce) XmlSerializer::deserialize<CustomAngleForce>;
%template(XmlSerializer_deserialize_CustomBondForce) XmlSerializer::deserialize<CustomBondForce>;
%template(XmlSerializer_deserialize_CustomExternalForce) XmlSerializer::deserialize<CustomExternalForce>;
%template(XmlSerializer_deserialize_CustomGBForce) XmlSerializer::deserialize<CustomGBForce>;
%template(XmlSerializer_deserialize_CustomHbondForce) XmlSerializer::deserialize<CustomHbondForce>;
%template(XmlSerializer_deserialize_CustomNonbondedForce) XmlSerializer::deserialize<CustomNonbondedForce>;
%template(XmlSerializer_deserialize_CustomTorsionForce) XmlSerializer::deserialize<CustomTorsionForce>;
%template(XmlSerializer_deserialize_GBSAOBCForce) XmlSerializer::deserialize<GBSAOBCForce>;
%template(XmlSerializer_deserialize_GBVIForce) XmlSerializer::deserialize<GBVIForce>;
%template(XmlSerializer_deserialize_HarmonicAngleForce) XmlSerializer::deserialize<HarmonicAngleForce>;
%template(XmlSerializer_deserialize_HarmonicBondForce) XmlSerializer::deserialize<HarmonicBondForce>;
%template(XmlSerializer_deserialize_MonteCarloBarostat) XmlSerializer::deserialize<MonteCarloBarostat>;
%template(XmlSerializer_deserialize_MonteCarloAnisotropicBarostat) XmlSerializer::deserialize<MonteCarloAnisotropicBarostat>;
%template(XmlSerializer_deserialize_NonbondedForce) XmlSerializer::deserialize<NonbondedForce>;
%template(XmlSerializer_deserialize_RBTorsionForce) XmlSerializer::deserialize<RBTorsionForce>;
%template(XmlSerializer_deserialize_System) XmlSerializer::deserialize<System>;
};
*/
...@@ -49,8 +49,7 @@ def striphtmltags(s): ...@@ -49,8 +49,7 @@ def striphtmltags(s):
s = s.replace('<i>', '_').replace('</i>', '_') s = s.replace('<i>', '_').replace('</i>', '_')
s = s.replace('<b>', '*').replace('</b>', '*') s = s.replace('<b>', '*').replace('</b>', '*')
s = re.sub('\s*(<ul>.*</ul>\s*)', replace_ul_tags, s, flags=re.MULTILINE | re.DOTALL) s = re.sub('\s*(<ul>.*?</ul>\s*)', replace_ul_tags, s, flags=re.MULTILINE | re.DOTALL)
return s return s
def trimToSingleSpace(text): def trimToSingleSpace(text):
...@@ -146,6 +145,20 @@ def getClassMethodList(classNode, skipMethods): ...@@ -146,6 +145,20 @@ def getClassMethodList(classNode, skipMethods):
return methodList return methodList
def docstringTypemap(cpptype):
"""Translate a C++ type to Python for inclusion in the Python docstrings.
This doesn't need to be perfectly accurate -- it's not used for generating
the actual swig wrapper code. It's only used for generating the docstrings.
"""
pytype = cpptype
if pytype.startswith('const '):
pytype = pytype[6:]
if pytype.startswith('std::'):
pytype = pytype[5:]
pytype = pytype.strip('&')
return pytype.strip()
class SwigInputBuilder: class SwigInputBuilder:
def __init__(self, def __init__(self,
inputDirname, inputDirname,
...@@ -243,23 +256,43 @@ class SwigInputBuilder: ...@@ -243,23 +256,43 @@ class SwigInputBuilder:
forceSubclassList.append(shortClassName) forceSubclassList.append(shortClassName)
elif baseName == 'OpenMM::Integrator': elif baseName == 'OpenMM::Integrator':
integratorSubclassList.append(shortClassName) integratorSubclassList.append(shortClassName)
self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce") self.fOut.write("%factory(OpenMM::Force& OpenMM::System::getForce")
for name in sorted(forceSubclassList): for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Force* OpenMM::Force::__copy__")
for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Force* OpenMM_XmlSerializer__deserializeForce") self.fOut.write("%factory(OpenMM::Force* OpenMM_XmlSerializer__deserializeForce")
for name in sorted(forceSubclassList): for name in sorted(forceSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator* OpenMM::Integrator::__copy__")
for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator* OpenMM_XmlSerializer__deserializeIntegrator") self.fOut.write("%factory(OpenMM::Integrator* OpenMM_XmlSerializer__deserializeIntegrator")
for name in sorted(integratorSubclassList): for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::Integrator& OpenMM::Context::getIntegrator") self.fOut.write("%factory(OpenMM::Integrator& OpenMM::Context::getIntegrator")
for name in sorted(integratorSubclassList): for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name) self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n") self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite);\n\n")
self.fOut.write("%factory(OpenMM::Integrator& OpenMM::CompoundIntegrator::getIntegrator")
for name in sorted(integratorSubclassList):
self.fOut.write(",\n OpenMM::%s" % name)
self.fOut.write(");\n\n")
self.fOut.write("%factory(OpenMM::VirtualSite& OpenMM::System::getVirtualSite, OpenMM::TwoParticleAverageSite, OpenMM::ThreeParticleAverageSite, OpenMM::OutOfPlaneSite, OpenMM::LocalCoordinatesSite);\n\n")
self.fOut.write("\n") self.fOut.write("\n")
def writeGlobalConstants(self): def writeGlobalConstants(self):
...@@ -345,7 +378,6 @@ class SwigInputBuilder: ...@@ -345,7 +378,6 @@ class SwigInputBuilder:
self.fOut.write("\n%s};\n" % INDENT) self.fOut.write("\n%s};\n" % INDENT)
if len(enumNodes)>0: self.fOut.write("\n") if len(enumNodes)>0: self.fOut.write("\n")
def writeMethods(self, classNode): def writeMethods(self, classNode):
methodList=getClassMethodList(classNode, self.skipMethods) methodList=getClassMethodList(classNode, self.skipMethods)
...@@ -432,35 +464,44 @@ class SwigInputBuilder: ...@@ -432,35 +464,44 @@ class SwigInputBuilder:
(shortClassName, memberNode, (shortClassName, memberNode,
shortMethDefinition, methName, shortMethDefinition, methName,
isConstructors, isDestructor, templateType, templateName) = items isConstructors, isDestructor, templateType, templateName) = items
paramList=findNodes(memberNode, 'param') paramList = findNodes(memberNode, 'param')
#write pythonprepend blocks # write pythonprepend blocks
mArgsstring = getText("argsstring", memberNode) mArgsstring = getText("argsstring", memberNode)
if self.fOutPythonprepend and \ if self.fOutPythonprepend and \
len(paramList) and \ len(paramList) and \
mArgsstring.find('=0')<0: mArgsstring.find('=0') < 0:
key=(shortClassName, methName) text = '''
if key in self.configModule.STEAL_OWNERSHIP: %pythonprepend OpenMM::{shortClassName}::{methName}{mArgsstring} %{{{{{{0}}
for argNum in self.configModule.STEAL_OWNERSHIP[key]: %}}}}'''.format(shortClassName=shortClassName, methName=methName, mArgsstring=mArgsstring)
if self.SWIG_COMPACT_ARGUMENTS: textInside = ''
argName = 'args[%s]' % argNum key = (shortClassName, methName)
else: for argNum in self.configModule.STEAL_OWNERSHIP.get(key, []):
argName = getText('declname', paramList[argNum]) if self.SWIG_COMPACT_ARGUMENTS:
argName = 'args[%s]' % argNum
else:
argName = getText('declname', paramList[argNum])
text = ''' textInside += '''
%pythonprepend OpenMM::{shortClassName}::{methName}{mArgsstring} %{{
if not {argName}.thisown: if not {argName}.thisown:
s = ("the %s object does not own its corresponding OpenMM object" s = ("the %s object does not own its corresponding OpenMM object"
% self.__class__.__name__) % self.__class__.__name__)
raise Exception(s) raise Exception(s)'''.format(argName=argName)
%}}'''.format(argName=argName, shortClassName=shortClassName, methName=methName, mArgsstring=mArgsstring) for argNum in self.configModule.REQUIRE_ORDERED_SET.get(key, []):
self.fOutPythonprepend.write(text) if self.SWIG_COMPACT_ARGUMENTS:
argName = 'args[%s]' % argNum
else:
argName = getText('declname', paramList[argNum])
textInside += '''
{argName} = list({argName})'''.format(argName=argName)
if textInside:
self.fOutPythonprepend.write(text.format(textInside))
#write pythonappend blocks # write pythonappend blocks
if self.fOutPythonappend \ if self.fOutPythonappend \
and mArgsstring.find('=0')<0: and mArgsstring.find('=0') < 0:
key=(shortClassName, methName) key = (shortClassName, methName)
#print "key %s %s \n" % (shortClassName, methName) #print "key %s %s \n" % (shortClassName, methName)
addText='' addText=''
returnType = getText("type", memberNode) returnType = getText("type", memberNode)
...@@ -548,32 +589,58 @@ class SwigInputBuilder: ...@@ -548,32 +589,58 @@ class SwigInputBuilder:
(shortClassName, memberNode, (shortClassName, memberNode,
shortMethDefinition, methName, shortMethDefinition, methName,
isConstructors, isDestructor, templateType, templateName ) = items isConstructors, isDestructor, templateType, templateName ) = items
if self.fOutDocstring: if self.fOutDocstring:
for dNode in findNodes(memberNode, 'detaileddescription'): signatureParams = findNodes(memberNode, 'param')
dString="" assert len(findNodes(memberNode, 'detaileddescription')) == 1
try: dNode = findNodes(memberNode, 'detaileddescription')[0]
description=getText('para', dNode)
description.strip() try:
if description: description=getText('para', dNode)
dString=description description.strip()
except IndexError: except IndexError:
pass description = ''
params = findNodes(dNode, 'para/parameterlist/parameteritem') params = findNodes(dNode, 'para/parameterlist/parameteritem')
if len(params) > 0:
dString="%s\n Parameters:" % dString paramString = ['Parameters', '----------']
for pNode in params: returnString = ['Returns', '-------']
argName = getText('parameternamelist/parametername', pNode)
if len(params) > 0:
if len(signatureParams) != len(params):
raise ValueError('docstring in %s.%s does not match the signature' % (shortClassName, methName))
for pNode, pSignatureNode in zip(params, signatureParams):
parameterNameNode = findNodes(pNode, 'parameternamelist/parametername')[0]
argDoc = getText('parameterdescription/para', pNode) argDoc = getText('parameterdescription/para', pNode)
dString="%s\n - %s %s" % (dString, argName, argDoc) argName = getNodeText(parameterNameNode)
dString.strip() argType = docstringTypemap(getText('type', pSignatureNode))
if dString:
dString=re.sub(r'([^\\])"', r'\g<1>\"', dString)
s = '%%feature("docstring") OpenMM::%s::%s "%s";' \
% (shortClassName, methName, dString)
self.fOutDocstring.write("%s\n" % s)
self.fOutDocstring.write("\n\n")
#print "Done write Docstring info\n"
isOutput = parameterNameNode.get('direction') == 'out'
if isOutput:
returnString.extend(['%s : %s' % (argName, argType), ' %s' % argDoc])
else:
paramString.extend(['%s : %s' % (argName, argType), ' %s' % argDoc])
returnSection = findNodes(dNode, 'para/simplesect')
if len(returnSection) > 0:
returnNode = returnSection[0]
if returnNode.get('kind') == 'return':
argType = getNodeText(findNodes(memberNode, 'type')[0])
argType = docstringTypemap(argType)
returnString.extend([argType, ' %s' % getNodeText(returnNode).strip()])
dString = '\n'.join(
([description] + [''] if len(description) > 0 else []) +
(paramString + [''] if len(paramString) > 2 else []) +
(returnString if len(returnString) > 2 else [])).strip()
if dString:
dString = re.sub(r'([^\\])"', r'\g<1>\"', dString)
s = '%%feature("docstring") OpenMM::%s::%s "%s";' \
% (shortClassName, methName, dString)
self.fOutDocstring.write("%s\n" % s)
self.fOutDocstring.write("\n\n")
def writeSwigFile(self): def writeSwigFile(self):
......
...@@ -43,7 +43,6 @@ SKIP_METHODS = [('State',), ...@@ -43,7 +43,6 @@ SKIP_METHODS = [('State',),
('CalcCustomTorsionForceKernel',), ('CalcCustomTorsionForceKernel',),
('CalcForcesAndEnergyKernel',), ('CalcForcesAndEnergyKernel',),
('CalcGBSAOBCForceKernel',), ('CalcGBSAOBCForceKernel',),
('CalcGBVIForceKernel',),
('CalcHarmonicAngleForceKernel',), ('CalcHarmonicAngleForceKernel',),
('CalcHarmonicBondForceKernel',), ('CalcHarmonicBondForceKernel',),
('CalcKineticEnergyKernel',), ('CalcKineticEnergyKernel',),
...@@ -141,8 +140,15 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0], ...@@ -141,8 +140,15 @@ STEAL_OWNERSHIP = {("Platform", "registerPlatform") : [0],
("CustomHbondForce", "addTabulatedFunction") : [1], ("CustomHbondForce", "addTabulatedFunction") : [1],
("CustomCompoundBondForce", "addTabulatedFunction") : [1], ("CustomCompoundBondForce", "addTabulatedFunction") : [1],
("CustomManyParticleForce", "addTabulatedFunction") : [1], ("CustomManyParticleForce", "addTabulatedFunction") : [1],
("CompoundIntegrator", "addIntegrator") : [0],
} }
REQUIRE_ORDERED_SET = {("CustomNonbondedForce", "addInteractionGroup") : [0, 1],
("CustomNonbondedForce", "setInteractionGroupParameters") : [1, 2],
}
# This is a list of units to attach to return values and method args. # This is a list of units to attach to return values and method args.
# Indexed by (ClassName, MethodsName) # Indexed by (ClassName, MethodsName)
UNITS = { UNITS = {
...@@ -308,6 +314,7 @@ UNITS = { ...@@ -308,6 +314,7 @@ UNITS = {
("AmoebaWcaDispersionForce", "getShctd") : ( None, ()), ("AmoebaWcaDispersionForce", "getShctd") : ( None, ()),
("Context", "getParameter") : (None, ()), ("Context", "getParameter") : (None, ()),
("Context", "getParameters") : (None, ()),
("Context", "getMolecules") : (None, ()), ("Context", "getMolecules") : (None, ()),
("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')), ("CMAPTorsionForce", "getMapParameters") : (None, (None, 'unit.kilojoule_per_mole')),
("CMAPTorsionForce", "getTorsionParameters") : (None, ()), ("CMAPTorsionForce", "getTorsionParameters") : (None, ()),
...@@ -385,14 +392,6 @@ UNITS = { ...@@ -385,14 +392,6 @@ UNITS = {
: (None, ('unit.elementary_charge', : (None, ('unit.elementary_charge',
'unit.nanometer', None)), 'unit.nanometer', None)),
("GBSAOBCForce", "getSurfaceAreaEnergy") : ('unit.kilojoule_per_mole/unit.nanometer/unit.nanometer', ()), ("GBSAOBCForce", "getSurfaceAreaEnergy") : ('unit.kilojoule_per_mole/unit.nanometer/unit.nanometer', ()),
("GBVIForce", "getBornRadiusScalingMethod") : (None, ()),
("GBVIForce", "getQuinticLowerLimitFactor") : (None, ()),
("GBVIForce", "getQuinticUpperBornRadiusLimit") : ('unit.nanometer', ()),
("GBVIForce", "getBondParameters")
: (None, (None, None, 'unit.nanometer')),
("GBVIForce", "getParticleParameters")
: (None, ('unit.elementary_charge',
'unit.nanometer', 'unit.kilojoule_per_mole')),
("HarmonicAngleForce", "getAngleParameters") ("HarmonicAngleForce", "getAngleParameters")
: (None, (None, None, None, 'unit.radian', : (None, (None, None, None, 'unit.radian',
'unit.kilojoule_per_mole/(unit.radian*unit.radian)')), 'unit.kilojoule_per_mole/(unit.radian*unit.radian)')),
......
%inline %{
typedef int bitmask32t;
%}
%typemap(in) bitmask32t %{
$1 = 0;
#if PY_VERSION_HEX >= 0x03000000
if (PyLong_Check($input)) {
unsigned long u = PyLong_AsUnsignedLongMask($input);
#else
if (PyInt_Check($input)) {
unsigned long u = PyInt_AsUnsignedLongMask($input);
#endif
// 64-bit Windows has 32-bit longs, but other platforms have
// 64-bit longs
$1 = u & 0xffffffff;
} else {
PyErr_SetString(PyExc_ValueError, "in method $symname, argument $argnum could not be converted to type $type");
SWIG_fail;
}
%}
%extend OpenMM::Context { %extend OpenMM::Context {
PyObject *_getStateAsLists(int getPositions, PyObject *_getStateAsLists(int getPositions,
int getVelocities, int getVelocities,
...@@ -5,7 +29,7 @@ ...@@ -5,7 +29,7 @@
int getEnergy, int getEnergy,
int getParameters, int getParameters,
int enforcePeriodic, int enforcePeriodic,
int groups) { bitmask32t groups) {
State state; State state;
PyThreadState* _savePythonThreadState = PyEval_SaveThread(); PyThreadState* _savePythonThreadState = PyEval_SaveThread();
int types = 0; int types = 0;
...@@ -27,54 +51,53 @@ ...@@ -27,54 +51,53 @@
%pythoncode %{ %pythoncode %{
def getState(self, def getState(self, getPositions=False, getVelocities=False,
getPositions=False, getForces=False, getEnergy=False, getParameters=False,
getVelocities=False, enforcePeriodicBox=False, groups=-1):
getForces=False, """Get a State object recording the current state information stored in this context.
getEnergy=False,
getParameters=False, Parameters
enforcePeriodicBox=False, ----------
groups=-1): getPositions : bool=False
""" whether to store particle positions in the State
getState(self, getVelocities : bool=False
getPositions = False, whether to store particle velocities in the State
getVelocities = False, getForces : bool=False
getForces = False, whether to store the forces acting on particles in the State
getEnergy = False, getEnergy : bool=False
getParameters = False, whether to store potential and kinetic energy in the State
enforcePeriodicBox = False, getParameter : bool=False
groups = -1) whether to store context parameters in the State
-> State enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
Get a State object recording the current state information stored in this context. is stored in the Context, regardless of periodic boundary conditions.
If true, particle positions will be translated so the center of
Parameters: every molecule lies in the same periodic box.
- getPositions (bool=False) whether to store particle positions in the State groups : set={0,1,2,...,31}
- getVelocities (bool=False) whether to store particle velocities in the State a set of indices for which force groups to include when computing
- getForces (bool=False) whether to store the forces acting on particles in the State forces and energies. The default value includes all groups. groups
- getEnergy (bool=False) whether to store potential and kinetic energy in the State can also be passed as an unsigned integer interpreted as a bitmask,
- getParameter (bool=False) whether to store context parameters in the State in which case group i will be included if (groups&(1<<i)) != 0.
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box.
- groups (int=-1) a set of bit flags for which force groups to include when computing forces and energies. Group i will be included if (groups&(1<<i)) != 0. The default value includes all groups.
""" """
getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
if getPositions: getP=1 (getPositions, getVelocities, getForces, getEnergy, getParameters,
else: getP=0 enforcePeriodicBox))
if getVelocities: getV=1
else: getV=0 try:
if getForces: getF=1 # is the input integer-like?
else: getF=0 groups_mask = int(groups)
if getEnergy: getE=1 except TypeError:
else: getE=0 if isinstance(groups, set):
if getParameters: getPa=1 # nope, okay, then it should be an set
else: getPa=0 groups_mask = functools.reduce(operator.or_,
if enforcePeriodicBox: enforcePeriodic=1 ((1<<x) & 0xffffffff for x in groups))
else: enforcePeriodic=0 else:
raise TypeError('%s is neither an int nor set' % groups)
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \ forceList, paramMap) = \
self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups) self._getStateAsLists(getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -83,11 +106,11 @@ ...@@ -83,11 +106,11 @@
periodicBoxVectorsList=periodicBoxVectorsList, periodicBoxVectorsList=periodicBoxVectorsList,
paramMap=paramMap) paramMap=paramMap)
return state return state
def setState(self, state): def setState(self, state):
""" """
setState(Context self, State state) setState(Context self, State state)
Copy information from a State object into this Context. This restores the Context to Copy information from a State object into this Context. This restores the Context to
approximately the same state it was in when the State was created. If the State does not include approximately the same state it was in when the State was created. If the State does not include
a piece of information (e.g. positions or velocities), that aspect of the Context is a piece of information (e.g. positions or velocities), that aspect of the Context is
...@@ -108,7 +131,7 @@ ...@@ -108,7 +131,7 @@
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.
...@@ -185,48 +208,51 @@ Parameters: ...@@ -185,48 +208,51 @@ Parameters:
getParameters=False, getParameters=False,
enforcePeriodicBox=False, enforcePeriodicBox=False,
groups=-1): groups=-1):
"""Get a State object recording the current state information about one copy of the system.
Parameters
----------
copy : int
the index of the copy for which to retrieve state information
getPositions : bool=False
whether to store particle positions in the State
getVelocities : bool=False
whether to store particle velocities in the State
getForces : bool=False
whether to store the forces acting on particles in the State
getEnergy : bool=False
whether to store potential and kinetic energy in the State
getParameter : bool=False
whether to store context parameters in the State
enforcePeriodicBox : bool=False
if false, the position of each particle will be whatever position
is stored in the Context, regardless of periodic boundary conditions.
If true, particle positions will be translated so the center of
every molecule lies in the same periodic box.
groups : set={0,1,2,...,31}
a set of indices for which force groups to include when computing
forces and energies. The default value includes all groups. groups
can also be passed as an unsigned integer interpreted as a bitmask,
in which case group i will be included if (groups&(1<<i)) != 0.
""" """
getState(self, getP, getV, getF, getE, getPa, enforcePeriodic = map(bool,
copy, (getPositions, getVelocities, getForces, getEnergy, getParameters,
getPositions = False, enforcePeriodicBox))
getVelocities = False,
getForces = False, try:
getEnergy = False, # is the input integer-like?
getParameters = False, groups_mask = int(groups)
enforcePeriodicBox = False, except TypeError:
groups = -1) if isinstance(groups, set):
-> State groups_mask = functools.reduce(operator.or_,
((1<<x) & 0xffffffff for x in groups))
Get a State object recording the current state information about one copy of the system. else:
raise TypeError('%s is neither an int nor set' % groups)
Parameters:
- copy (int) the index of the copy for which to retrieve state information
- getPositions (bool=False) whether to store particle positions in the State
- getVelocities (bool=False) whether to store particle velocities in the State
- getForces (bool=False) whether to store the forces acting on particles in the State
- getEnergy (bool=False) whether to store potential and kinetic energy in the State
- getParameter (bool=False) whether to store context parameters in the State
- enforcePeriodicBox (bool=False) if false, the position of each particle will be whatever position is stored in the Context, regardless of periodic boundary conditions. If true, particle positions will be translated so the center of every molecule lies in the same periodic box.
- groups (int=-1) a set of bit flags for which force groups to include when computing forces and energies. Group i will be included if (groups&(1<<i)) != 0. The default value includes all groups.
"""
if getPositions: getP=1
else: getP=0
if getVelocities: getV=1
else: getV=0
if getForces: getF=1
else: getF=0
if getEnergy: getE=1
else: getE=0
if getParameters: getPa=1
else: getPa=0
if enforcePeriodicBox: enforcePeriodic=1
else: enforcePeriodic=0
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = \ forceList, paramMap) = \
self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups) self._getStateAsLists(copy, getP, getV, getF, getE, getPa, enforcePeriodic, groups_mask)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -299,7 +325,7 @@ Parameters: ...@@ -299,7 +325,7 @@ Parameters:
ss << inputString; ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::System>(ss); return OpenMM::XmlSerializer::deserialize<OpenMM::System>(ss);
} }
static std::string _serializeForce(const OpenMM::Force* object) { static std::string _serializeForce(const OpenMM::Force* object) {
std::stringstream ss; std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::Force>(object, "Force", ss); OpenMM::XmlSerializer::serialize<OpenMM::Force>(object, "Force", ss);
...@@ -312,7 +338,7 @@ Parameters: ...@@ -312,7 +338,7 @@ Parameters:
ss << inputString; ss << inputString;
return OpenMM::XmlSerializer::deserialize<OpenMM::Force>(ss); return OpenMM::XmlSerializer::deserialize<OpenMM::Force>(ss);
} }
static std::string _serializeIntegrator(const OpenMM::Integrator* object) { static std::string _serializeIntegrator(const OpenMM::Integrator* object) {
std::stringstream ss; std::stringstream ss;
OpenMM::XmlSerializer::serialize<OpenMM::Integrator>(object, "Integrator", ss); OpenMM::XmlSerializer::serialize<OpenMM::Integrator>(object, "Integrator", ss);
...@@ -327,8 +353,8 @@ Parameters: ...@@ -327,8 +353,8 @@ Parameters:
} }
static std::string _serializeStateAsLists( static std::string _serializeStateAsLists(
const std::vector<Vec3>& pos, const std::vector<Vec3>& pos,
const std::vector<Vec3>& vel, const std::vector<Vec3>& vel,
const std::vector<Vec3>& forces, const std::vector<Vec3>& forces,
double kineticEnergy, double kineticEnergy,
double potentialEnergy, double potentialEnergy,
...@@ -341,7 +367,7 @@ Parameters: ...@@ -341,7 +367,7 @@ Parameters:
OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer); OpenMM::XmlSerializer::serialize<OpenMM::State>(&myState, "State", buffer);
return buffer.str(); return buffer.str();
} }
static PyObject* _deserializeStringIntoLists(const std::string &stateAsString) { static PyObject* _deserializeStringIntoLists(const std::string &stateAsString) {
std::stringstream ss; std::stringstream ss;
ss << stateAsString; ss << stateAsString;
...@@ -369,7 +395,7 @@ Parameters: ...@@ -369,7 +395,7 @@ Parameters:
try: try:
velocities = pythonState.getVelocities().value_in_unit(unit.nanometers/unit.picoseconds) velocities = pythonState.getVelocities().value_in_unit(unit.nanometers/unit.picoseconds)
types |= 2 types |= 2
except: except:
pass pass
try: try:
forces = pythonState.getForces().value_in_unit(unit.kilojoules_per_mole/unit.nanometers) forces = pythonState.getForces().value_in_unit(unit.kilojoules_per_mole/unit.nanometers)
...@@ -390,14 +416,14 @@ Parameters: ...@@ -390,14 +416,14 @@ Parameters:
time = pythonState.getTime().value_in_unit(unit.picoseconds) time = pythonState.getTime().value_in_unit(unit.picoseconds)
boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers) boxVectors = pythonState.getPeriodicBoxVectors().value_in_unit(unit.nanometers)
string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types) string = XmlSerializer._serializeStateAsLists(positions, velocities, forces, kineticEnergy, potentialEnergy, time, boxVectors, params, types)
return string return string
@staticmethod @staticmethod
def _deserializeState(pythonString): def _deserializeState(pythonString):
(simTime, periodicBoxVectorsList, energy, coordList, velList, (simTime, periodicBoxVectorsList, energy, coordList, velList,
forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString) forceList, paramMap) = XmlSerializer._deserializeStringIntoLists(pythonString)
state = State(simTime=simTime, state = State(simTime=simTime,
energy=energy, energy=energy,
coordList=coordList, coordList=coordList,
...@@ -450,6 +476,14 @@ Parameters: ...@@ -450,6 +476,14 @@ Parameters:
%extend OpenMM::Force { %extend OpenMM::Force {
%pythoncode %{ %pythoncode %{
def __getstate__(self):
serializationString = XmlSerializer.serialize(self)
return serializationString
def __setstate__(self, serializationString):
system = XmlSerializer.deserialize(serializationString)
self.this = system.this
def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return self.__copy__() return self.__copy__()
%} %}
......
...@@ -8,6 +8,8 @@ except ImportError: ...@@ -8,6 +8,8 @@ except ImportError:
import copy import copy
import sys import sys
import math import math
import functools
import operator
RMIN_PER_SIGMA=math.pow(2, 1/6.0) RMIN_PER_SIGMA=math.pow(2, 1/6.0)
RVDW_PER_SIGMA=math.pow(2, 1/6.0)/2.0 RVDW_PER_SIGMA=math.pow(2, 1/6.0)/2.0
if sys.version_info[0] == 2: if sys.version_info[0] == 2:
......
%fragment("Py_StripOpenMMUnits", "header") { %fragment("Vec3_to_PyVec3", "header") {
/**
* Convert an OpenMM::Vec3 into a Python simtk.openmm.Vec3 object
*
* Returns a new reference.
*/
PyObject* Vec3_to_PyVec3(const OpenMM::Vec3& v) {
static PyObject *__s_mm = NULL;
static PyObject *__s_Vec3 = NULL;
if (__s_mm == NULL) {
__s_mm = PyImport_AddModule("simtk.openmm");
__s_Vec3 = PyObject_GetAttrString(__s_mm, "Vec3");
}
PyObject* tuple = Py_BuildValue("(d,d,d)", v[0], v[1], v[2]);
PyObject* PyVec3 = PyObject_CallObject(__s_Vec3, tuple);
Py_DECREF(tuple);
return PyVec3;
}
}
static PyObject *__s_Quantity = NULL; %fragment("Py_StripOpenMMUnits", "header") {
static PyObject *__s_md_unit_system_tuple = NULL;
static PyObject *__s_bar_tuple = NULL;
/**
* Strip any OpenMM units of an input PyObject.
*
* This is equivalent to the following Python code
*
* >>> from simtk import unit
* >>> if isinstance(input, unit.Quantity)
* ... if input.is_compatible(unit.bar)
* ... return input.value_in_unit(unit.bar)
* ... return input.value_in_unit_system(unit.md_input_system)
* ... return input
*
* Returns a new reference.
*/
PyObject* Py_StripOpenMMUnits(PyObject *input) { PyObject* Py_StripOpenMMUnits(PyObject *input) {
static PyObject *__s_Quantity = NULL;
static PyObject *__s_md_unit_system_tuple = NULL;
static PyObject *__s_bar_tuple = NULL;
if (__s_Quantity == NULL) { if (__s_Quantity == NULL) {
PyObject* module = NULL; PyObject* module = NULL;
module = PyImport_ImportModule("simtk.unit"); module = PyImport_ImportModule("simtk.unit");
...@@ -412,19 +446,12 @@ int Py_SequenceToVecVecVecDouble(PyObject* obj, std::vector<std::vector<std::vec ...@@ -412,19 +446,12 @@ int Py_SequenceToVecVecVecDouble(PyObject* obj, std::vector<std::vector<std::vec
} }
%typemap(argout) std::vector<Vec3>& { %typemap(argout, fragment="Vec3_to_PyVec3") std::vector<Vec3>& {
int i, n; int n = (*$1).size();
PyObject *pyList; PyObject * pyList = PyList_New(n);
for (int i=0; i<n; i++) {
n=(*$1).size();
pyList=PyList_New(n);
PyObject* mm = PyImport_AddModule("simtk.openmm");
PyObject* vec3 = PyObject_GetAttrString(mm, "Vec3");
for (i=0; i<n; i++) {
OpenMM::Vec3& v = (*$1).at(i); OpenMM::Vec3& v = (*$1).at(i);
PyObject* args = Py_BuildValue("(d,d,d)", v[0], v[1], v[2]); PyObject* pyVec = Vec3_to_PyVec3(v);
PyObject* pyVec = PyObject_CallObject(vec3, args);
Py_DECREF(args);
PyList_SET_ITEM(pyList, i, pyVec); PyList_SET_ITEM(pyList, i, pyVec);
} }
$result = pyList; $result = pyList;
...@@ -436,41 +463,20 @@ int Py_SequenceToVecVecVecDouble(PyObject* obj, std::vector<std::vector<std::vec ...@@ -436,41 +463,20 @@ int Py_SequenceToVecVecVecDouble(PyObject* obj, std::vector<std::vector<std::vec
} }
%typemap(out, fragment="Vec3_to_PyVec3") Vec3 {
$result = Vec3_to_PyVec3(*$1);
%typemap(out) Vec3 {
PyObject* mm = PyImport_AddModule("simtk.openmm");
PyObject* vec3 = PyObject_GetAttrString(mm, "Vec3");
PyObject* args = Py_BuildValue("(d,d,d)", ($1)[0], ($1)[1], ($1)[2]);
$result = PyObject_CallObject(vec3, args);
Py_DECREF(args);
} }
%typemap(out) const Vec3& { %typemap(out, fragment="Vec3_to_PyVec3") const Vec3& {
PyObject* mm = PyImport_AddModule("simtk.openmm"); $result = Vec3_to_PyVec3(*$1);
PyObject* vec3 = PyObject_GetAttrString(mm, "Vec3");
PyObject* args = Py_BuildValue("(d,d,d)", (*$1)[0], (*$1)[1], (*$1)[2]);
$result = PyObject_CallObject(vec3, args);
Py_DECREF(args);
} }
/* Convert C++ (Vec3&, Vec3&, Vec3&) object to python tuple or tuples */ /* Convert C++ (Vec3&, Vec3&, Vec3&) object to python tuple or tuples */
%typemap(argout) (Vec3& a, Vec3& b, Vec3& c) { %typemap(argout, fragment="Vec3_to_PyVec3") (Vec3& a, Vec3& b, Vec3& c) {
// %typemap(argout) (Vec3& a, Vec3& b, Vec3& c) PyObject* pyVec1 = Vec3_to_PyVec3(*$1);
PyObject* mm = PyImport_AddModule("simtk.openmm"); PyObject* pyVec2 = Vec3_to_PyVec3(*$2);
PyObject* vec3 = PyObject_GetAttrString(mm, "Vec3"); PyObject* pyVec3 = Vec3_to_PyVec3(*$3);
PyObject* args1 = Py_BuildValue("(d,d,d)", (*$1)[0], (*$1)[1], (*$1)[2]);
PyObject* args2 = Py_BuildValue("(d,d,d)", (*$2)[0], (*$2)[1], (*$2)[2]);
PyObject* args3 = Py_BuildValue("(d,d,d)", (*$3)[0], (*$3)[1], (*$3)[2]);
PyObject* pyVec1 = PyObject_CallObject(vec3, args1);
PyObject* pyVec2 = PyObject_CallObject(vec3, args2);
PyObject* pyVec3 = PyObject_CallObject(vec3, args3);
Py_DECREF(args1);
Py_DECREF(args2);
Py_DECREF(args3);
Py_DECREF(mm);
Py_DECREF(vec3);
PyObject *o, *o2, *o3; PyObject *o, *o2, *o3;
o = Py_BuildValue("[N, N, N]", pyVec1, pyVec2, pyVec3); o = Py_BuildValue("[N, N, N]", pyVec1, pyVec2, pyVec3);
if ((!$result) || ($result == Py_None)) { if ((!$result) || ($result == Py_None)) {
......
...@@ -4,6 +4,7 @@ from simtk.openmm.app import * ...@@ -4,6 +4,7 @@ from simtk.openmm.app import *
from simtk.openmm import * from simtk.openmm import *
from simtk.unit import * from simtk.unit import *
import simtk.openmm.app.element as elem import simtk.openmm.app.element as elem
import warnings
class TestCharmmFiles(unittest.TestCase): class TestCharmmFiles(unittest.TestCase):
...@@ -90,7 +91,6 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -90,7 +91,6 @@ class TestCharmmFiles(unittest.TestCase):
def test_NBFIX(self): def test_NBFIX(self):
"""Tests CHARMM systems with NBFIX Lennard-Jones modifications""" """Tests CHARMM systems with NBFIX Lennard-Jones modifications"""
import warnings
warnings.filterwarnings('ignore', category=CharmmPSFWarning) warnings.filterwarnings('ignore', category=CharmmPSFWarning)
psf = CharmmPsfFile('systems/ala3_solv.psf') psf = CharmmPsfFile('systems/ala3_solv.psf')
crd = CharmmCrdFile('systems/ala3_solv.crd') crd = CharmmCrdFile('systems/ala3_solv.crd')
...@@ -122,6 +122,34 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -122,6 +122,34 @@ class TestCharmmFiles(unittest.TestCase):
self.assertEqual(len(list(psf.topology.residues())), 20169) self.assertEqual(len(list(psf.topology.residues())), 20169)
self.assertEqual(len(list(psf.topology.bonds())), 46634) self.assertEqual(len(list(psf.topology.bonds())), 46634)
def testSystemOptions(self):
""" Test various options in CharmmPsfFile.createSystem """
warnings.filterwarnings('ignore', category=CharmmPSFWarning)
psf = CharmmPsfFile('systems/ala3_solv.psf')
crd = CharmmCrdFile('systems/ala3_solv.crd')
params = CharmmParameterSet('systems/par_all36_prot.prm',
'systems/toppar_water_ions.str')
# Box dimensions (found from bounding box)
psf.setBox(32.7119500*angstroms, 32.9959600*angstroms, 33.0071500*angstroms)
# Check some illegal options
self.assertRaises(ValueError, lambda:
psf.createSystem(params, nonbondedMethod=5))
self.assertRaises(TypeError, lambda:
psf.createSystem(params, nonbondedMethod=PME,
nonbondedCutoff=1*radian)
)
self.assertRaises(TypeError, lambda:
psf.createSystem(params, nonbondedMethod=PME,
switchDistance=1*radian)
)
# Check what should be some legal options
psf.createSystem(params, nonbondedMethod=PME, switchDistance=0.8,
nonbondedCutoff=1.2)
psf.createSystem(params, nonbondedMethod=PME, switchDistance=0.8,
nonbondedCutoff=1.2*nanometer)
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]
......
...@@ -6,6 +6,10 @@ from simtk.unit import * ...@@ -6,6 +6,10 @@ from simtk.unit import *
import simtk.openmm.app.element as elem import simtk.openmm.app.element as elem
import simtk.openmm.app.forcefield as forcefield import simtk.openmm.app.forcefield as forcefield
import math import math
if sys.version_info >= (3, 0):
from io import StringIO
else:
from cStringIO import StringIO
class TestForceField(unittest.TestCase): class TestForceField(unittest.TestCase):
"""Test the ForceField.createSystem() method.""" """Test the ForceField.createSystem() method."""
...@@ -144,7 +148,8 @@ class TestForceField(unittest.TestCase): ...@@ -144,7 +148,8 @@ class TestForceField(unittest.TestCase):
context = Context(system, integrator) context = Context(system, integrator)
context.setPositions(pdb.positions) context.setPositions(pdb.positions)
state1 = context.getState(getForces=True) state1 = context.getState(getForces=True)
state2 = XmlSerializer.deserialize(open('systems/lysozyme-implicit-forces.xml').read()) with open('systems/lysozyme-implicit-forces.xml') as input:
state2 = XmlSerializer.deserialize(input.read())
numDifferences = 0 numDifferences = 0
for f1, f2, in zip(state1.getForces().value_in_unit(kilojoules_per_mole/nanometer), state2.getForces().value_in_unit(kilojoules_per_mole/nanometer)): for f1, f2, in zip(state1.getForces().value_in_unit(kilojoules_per_mole/nanometer), state2.getForces().value_in_unit(kilojoules_per_mole/nanometer)):
diff = norm(f1-f2) diff = norm(f1-f2)
...@@ -197,7 +202,51 @@ class TestForceField(unittest.TestCase): ...@@ -197,7 +202,51 @@ class TestForceField(unittest.TestCase):
for i in range(3): for i in range(3):
self.assertEqual(vectors[i], self.pdb1.topology.getPeriodicBoxVectors()[i]) self.assertEqual(vectors[i], self.pdb1.topology.getPeriodicBoxVectors()[i])
self.assertEqual(vectors[i], system.getDefaultPeriodicBoxVectors()[i]) self.assertEqual(vectors[i], system.getDefaultPeriodicBoxVectors()[i])
def test_ResidueAttributes(self):
"""Test a ForceField that gets per-particle parameters from residue attributes."""
xml = """
<ForceField>
<AtomTypes>
<Type name="tip3p-O" class="OW" element="O" mass="15.99943"/>
<Type name="tip3p-H" class="HW" element="H" mass="1.007947"/>
</AtomTypes>
<Residues>
<Residue name="HOH">
<Atom name="O" type="tip3p-O" charge="-0.834"/>
<Atom name="H1" type="tip3p-H" charge="0.417"/>
<Atom name="H2" type="tip3p-H" charge="0.417"/>
<Bond from="0" to="1"/>
<Bond from="0" to="2"/>
</Residue>
</Residues>
<NonbondedForce coulomb14scale="0.833333" lj14scale="0.5">
<UseAttributeFromResidue name="charge"/>
<Atom type="tip3p-O" sigma="0.315" epsilon="0.635"/>
<Atom type="tip3p-H" sigma="1" epsilon="0"/>
</NonbondedForce>
</ForceField>"""
ff = ForceField(StringIO(xml))
# Build a water box.
modeller = Modeller(Topology(), [])
modeller.addSolvent(ff, boxSize=Vec3(3, 3, 3)*nanometers)
# Create a system and make sure all nonbonded parameters are correct.
system = ff.createSystem(modeller.topology)
nonbonded = [f for f in system.getForces() if isinstance(f, NonbondedForce)][0]
atoms = list(modeller.topology.atoms())
for i in range(len(atoms)):
params = nonbonded.getParticleParameters(i)
if atoms[i].element == elem.oxygen:
self.assertEqual(params[0], -0.834*elementary_charge)
self.assertEqual(params[1], 0.315*nanometers)
self.assertEqual(params[2], 0.635*kilojoule_per_mole)
else:
self.assertEqual(params[0], 0.417*elementary_charge)
self.assertEqual(params[1], 1.0*nanometers)
self.assertEqual(params[2], 0.0*kilojoule_per_mole)
class AmoebaTestForceField(unittest.TestCase): class AmoebaTestForceField(unittest.TestCase):
"""Test the ForceField.createSystem() method with the AMOEBA forcefield.""" """Test the ForceField.createSystem() method with the AMOEBA forcefield."""
...@@ -270,6 +319,22 @@ class AmoebaTestForceField(unittest.TestCase): ...@@ -270,6 +319,22 @@ class AmoebaTestForceField(unittest.TestCase):
self.assertAlmostEqual(constraints[(0,2)], hoDist) self.assertAlmostEqual(constraints[(0,2)], hoDist)
self.assertAlmostEqual(constraints[(1,2)], hohDist) self.assertAlmostEqual(constraints[(1,2)], hohDist)
def test_Forces(self):
"""Compute forces and compare them to ones generated with a previous version of OpenMM to ensure they haven't changed."""
pdb = PDBFile('systems/alanine-dipeptide-implicit.pdb')
forcefield = ForceField('amoeba2013.xml', 'amoeba2013_gk.xml')
system = forcefield.createSystem(pdb.topology, polarization='direct')
integrator = VerletIntegrator(0.001)
context = Context(system, integrator)
context.setPositions(pdb.positions)
state1 = context.getState(getForces=True)
with open('systems/alanine-dipeptide-amoeba-forces.xml') as input:
state2 = XmlSerializer.deserialize(input.read())
for f1, f2, in zip(state1.getForces().value_in_unit(kilojoules_per_mole/nanometer), state2.getForces().value_in_unit(kilojoules_per_mole/nanometer)):
diff = norm(f1-f2)
self.assertTrue(diff < 0.1 or diff/norm(f1) < 1e-3)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
import unittest
import itertools
import simtk.openmm as mm
class TestForceGroups(unittest.TestCase):
def setUp(self):
system = mm.System()
system.addParticle(1.0)
for i in range(32):
force = mm.CustomExternalForce(str(i))
force.addParticle(0, [])
force.setForceGroup(i)
system.addForce(force)
platform = mm.Platform.getPlatformByName('Reference')
context = mm.Context(system, mm.VerletIntegrator(0), platform)
context.setPositions([(0,0,0)])
self.context = context
def test1(self):
n = 32
for (i,j) in itertools.combinations(range(n), 2):
groups = 1<<i | 1<<j
e_0 = self.context.getState(getEnergy=True, groups=groups).getPotentialEnergy()._value
e_1 = self.context.getState(getEnergy=True, groups={i,j}).getPotentialEnergy()._value
e_ref = i+j
self.assertEqual(e_0, e_ref)
self.assertEqual(e_1, e_ref)
def test2(self):
with self.assertRaises(TypeError):
# groups must be an int or set
self.context.getState(getEnergy=True, groups=(1, 2))
def test3(self):
e_0 = self.context.getState(getEnergy=True, groups=-1).getPotentialEnergy()._value
e_ref = sum(range(32))
self.assertEqual(e_0, e_ref)
if __name__ == '__main__':
unittest.main()
...@@ -3,6 +3,10 @@ from validateModeller import * ...@@ -3,6 +3,10 @@ from validateModeller import *
from simtk.openmm.app import * from simtk.openmm.app import *
from simtk.openmm import * from simtk.openmm import *
from simtk.unit import * from simtk.unit import *
if sys.version_info >= (3, 0):
from io import StringIO
else:
from cStringIO import StringIO
class TestModeller(unittest.TestCase): class TestModeller(unittest.TestCase):
""" Test the Modeller class. """ """ Test the Modeller class. """
...@@ -397,44 +401,46 @@ class TestModeller(unittest.TestCase): ...@@ -397,44 +401,46 @@ 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)
# set up modeller with no solvent
modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
# add 5 Cl- ions to the original topology for neutralize in (True, False):
topology_toAdd = Topology() # set up modeller with no solvent
newChain = topology_toAdd.addChain() modeller = Modeller(topology_start, self.positions)
for i in range(5): modeller.deleteWater()
topology_toAdd.addResidue('CL', newChain)
residues = [residue for residue in topology_toAdd.residues()]
for i in range(5):
topology_toAdd.addAtom('Cl',Element.getBySymbol('Cl'), residues[i])
positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar)
topology_after = modeller.getTopology()
water_count = 0 # add 5 Cl- ions to the original topology
sodium_count = 0 topology_toAdd = Topology()
chlorine_count = 0 newChain = topology_toAdd.addChain()
for residue in topology_after.residues(): for i in range(5):
if residue.name=='HOH': topology_toAdd.addResidue('CL', newChain)
water_count += 1 residues = [residue for residue in topology_toAdd.residues()]
elif residue.name=='NA': for i in range(5):
sodium_count += 1 topology_toAdd.addAtom('Cl',Element.getBySymbol('Cl'), residues[i])
elif residue.name=='CL': positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
chlorine_count += 1 Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar, neutralize=neutralize)
topology_after = modeller.getTopology()
total_water_ions = water_count+sodium_count+chlorine_count water_count = 0
expected_ion_fraction = 1.0*molar/(55.4*molar) sodium_count = 0
expected_ions = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5 chlorine_count = 0
self.assertEqual(sodium_count, expected_ions) for residue in topology_after.residues():
self.assertEqual(chlorine_count, expected_ions) if residue.name=='HOH':
water_count += 1
elif residue.name=='NA':
sodium_count += 1
elif residue.name=='CL':
chlorine_count += 1
total_water_ions = water_count+sodium_count+chlorine_count
expected_ion_fraction = 1.0*molar/(55.4*molar)
expected_chlorine = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5
expected_sodium = expected_chlorine if neutralize else expected_chlorine-5
self.assertEqual(sodium_count, expected_sodium)
self.assertEqual(chlorine_count, expected_chlorine)
def test_addSolventPositiveSolvent(self): def test_addSolventPositiveSolvent(self):
""" Test the addSolvent() method; test adding ions to a positively charged solvent. """ """ Test the addSolvent() method; test adding ions to a positively charged solvent. """
...@@ -442,42 +448,44 @@ class TestModeller(unittest.TestCase): ...@@ -442,42 +448,44 @@ 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 for neutralize in (True, False):
modeller = Modeller(topology_start, self.positions) # set up modeller with no solvent
modeller.deleteWater() modeller = Modeller(topology_start, self.positions)
modeller.deleteWater()
# add 5 Na+ ions to the original topology # add 5 Na+ ions to the original topology
topology_toAdd = Topology() topology_toAdd = Topology()
newChain = topology_toAdd.addChain() newChain = topology_toAdd.addChain()
for i in range(5): for i in range(5):
topology_toAdd.addResidue('NA', newChain) topology_toAdd.addResidue('NA', newChain)
residues = [residue for residue in topology_toAdd.residues()] residues = [residue for residue in topology_toAdd.residues()]
for i in range(5): for i in range(5):
topology_toAdd.addAtom('Na',Element.getBySymbol('Na'), residues[i]) topology_toAdd.addAtom('Na',Element.getBySymbol('Na'), residues[i])
positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0), positions_toAdd = [Vec3(1.0,1.2,1.5), Vec3(1.7,1.0,1.4), Vec3(1.5,2.0,1.0),
Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers Vec3(2.0,2.0,2.0), Vec3(2.0,1.5,1.0)]*nanometers
# positions_toAdd doesn't need to change # positions_toAdd doesn't need to change
modeller.add(topology_toAdd, positions_toAdd) modeller.add(topology_toAdd, positions_toAdd)
modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar) modeller.addSolvent(self.forcefield, ionicStrength=1.0*molar, neutralize=neutralize)
topology_after = modeller.getTopology() topology_after = modeller.getTopology()
water_count = 0 water_count = 0
sodium_count = 0 sodium_count = 0
chlorine_count = 0 chlorine_count = 0
for residue in topology_after.residues(): for residue in topology_after.residues():
if residue.name=='HOH': if residue.name=='HOH':
water_count += 1 water_count += 1
elif residue.name=='NA': elif residue.name=='NA':
sodium_count += 1 sodium_count += 1
elif residue.name=='CL': elif residue.name=='CL':
chlorine_count += 1 chlorine_count += 1
total_water_ions = water_count+sodium_count+chlorine_count total_water_ions = water_count+sodium_count+chlorine_count
expected_ion_fraction = 1.0*molar/(55.4*molar) expected_ion_fraction = 1.0*molar/(55.4*molar)
expected_ions = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5 expected_sodium = math.floor((total_water_ions-10)*expected_ion_fraction/2+0.5)+5
self.assertEqual(sodium_count, expected_ions) expected_chlorine = expected_sodium if neutralize else expected_sodium-5
self.assertEqual(chlorine_count, expected_ions) self.assertEqual(sodium_count, expected_sodium)
self.assertEqual(chlorine_count, expected_chlorine)
def test_addSolventIons(self): def test_addSolventIons(self):
""" Test the addSolvent() method with all possible choices for positive and negative ions. """ """ Test the addSolvent() method with all possible choices for positive and negative ions. """
...@@ -915,6 +923,135 @@ class TestModeller(unittest.TestCase): ...@@ -915,6 +923,135 @@ class TestModeller(unittest.TestCase):
self.assertEqual(1, len(ep)) self.assertEqual(1, len(ep))
def test_addVirtualSites(self):
"""Test adding extra particles defined by virtual sites."""
xml = """
<ForceField>
<AtomTypes>
<Type name="C" class="C" element="C" mass="10"/>
<Type name="N" class="N" element="N" mass="10"/>
<Type name="O" class="O" element="O" mass="10"/>
<Type name="V" class="V" mass="0.0"/>
</AtomTypes>
<Residues>
<Residue name="Test">
<Atom name="C" type="C"/>
<Atom name="N" type="N"/>
<Atom name="O" type="O"/>
<Atom name="V1" type="V"/>
<Atom name="V2" type="V"/>
<Atom name="V3" type="V"/>
<Atom name="V4" type="V"/>
<VirtualSite type="average2" index="3" atom1="0" atom2="1" weight1="0.7" weight2="0.3"/>
<VirtualSite type="average3" index="4" atom1="0" atom2="1" atom3="2" weight1="0.2" weight2="0.3" weight3="0.5"/>
<VirtualSite type="outOfPlane" index="5" atom1="0" atom2="1" atom3="2" weight12="0.1" weight13="-0.2" weightCross="0.8"/>
<VirtualSite type="localCoords" index="6" atom1="0" atom2="1" atom3="2" wo1="0.1" wo2="0.5" wo3="0.4" wx1="1" wx2="-0.6" wx3="-0.4" wy1="0.1" wy2="0.9" wy3="-1" p1="-0.5" p2="0.4" p3="1.1"/>
</Residue>
</Residues>
</ForceField>"""
ff = ForceField(StringIO(xml))
# Create the three real atoms.
topology = Topology()
chain = topology.addChain()
residue = topology.addResidue('Test', chain)
topology.addAtom('C', element.carbon, residue)
topology.addAtom('N', element.nitrogen, residue)
topology.addAtom('V', element.oxygen, residue)
# Add the virtual sites.
modeller = Modeller(topology, [Vec3(0.1, 0.2, 0.3), Vec3(1.0, 0.9, 0.8), Vec3(1.5, 1.1, 0.7)]*nanometers)
modeller.addExtraParticles(ff)
top = modeller.topology
pos = modeller.positions
# Check that the correct particles were added.
self.assertEqual(len(pos), 7)
for atom, elem in zip(top.atoms(), [element.carbon, element.nitrogen, element.oxygen, None, None, None, None]):
self.assertEqual(elem, atom.element)
# Check that the positions were calculated correctly.
system = ff.createSystem(top)
integ = VerletIntegrator(1.0)
context = Context(system, integ)
context.setPositions(pos)
context.computeVirtualSites()
state = context.getState(getPositions=True)
for p1, p2 in zip (pos, state.getPositions()):
self.assertVecAlmostEqual(p1.value_in_unit(nanometers), p2.value_in_unit(nanometers), 1e-6)
def test_multiSiteIon(self):
"""Test adding extra particles whose positions are determined based on bonds."""
xml = """
<ForceField>
<AtomTypes>
<Type name="Zn" class="Zn" element="Zn" mass="53.380"/>
<Type name="DA" class="DA" mass="3.0"/>
</AtomTypes>
<Residues>
<Residue name="ZN">
<Atom name="ZN" type="Zn"/>
<Atom name="D1" type="DA"/>
<Atom name="D2" type="DA"/>
<Atom name="D3" type="DA"/>
<Atom name="D4" type="DA"/>
<Bond from="0" to="2"/>
<Bond from="0" to="1"/>
<Bond from="0" to="3"/>
<Bond from="0" to="4"/>
<Bond from="1" to="2"/>
<Bond from="1" to="3"/>
<Bond from="1" to="4"/>
<Bond from="2" to="4"/>
<Bond from="2" to="3"/>
<Bond from="3" to="4"/>
</Residue>
</Residues>
<HarmonicBondForce>
<Bond class1="DA" class2="Zn" length="0.09" k="535552.0"/>
<Bond class1="DA" class2="DA" length="0.147" k="535552.0"/>
</HarmonicBondForce>
</ForceField>"""
ff = ForceField(StringIO(xml))
# Create two zinc atoms.
topology = Topology()
chain = topology.addChain()
residue = topology.addResidue('ZN', chain)
topology.addAtom('ZN', element.zinc, residue)
residue = topology.addResidue('ZN', chain)
topology.addAtom('ZN', element.zinc, residue)
# Add the extra particles.
modeller = Modeller(topology, [Vec3(0.5, 1.0, 1.5), Vec3(2.0, 2.0, 0.0)]*nanometers)
modeller.addExtraParticles(ff)
top = modeller.topology
pos = modeller.positions
# Check that the correct particles were added.
self.assertEqual(len(pos), 10)
for i, atom in enumerate(top.atoms()):
self.assertEqual(element.zinc if i in (0,5) else None, atom.element)
# Check that the positions in the first residue are reasonable.
center = Vec3(0.5, 1.0, 1.5)*nanometers
self.assertEqual(center, modeller.positions[0])
for i in range(1, 5):
for j in range(i):
dist = norm(pos[i]-pos[j])
expectedDist = 0.09 if j == 0 else 0.147
self.assertTrue(dist > (expectedDist-0.01)*nanometers and dist < (expectedDist+0.01)*nanometers)
def assertVecAlmostEqual(self, p1, p2, tol=1e-7): def assertVecAlmostEqual(self, p1, p2, tol=1e-7):
scale = max(1.0, norm(p1),) scale = max(1.0, norm(p1),)
for i in range(3): for i in range(3):
......
...@@ -3,13 +3,14 @@ from validateConstraints import * ...@@ -3,13 +3,14 @@ from validateConstraints import *
from simtk.openmm.app import * from simtk.openmm.app import *
from simtk.openmm import * from simtk.openmm import *
from simtk.unit import * from simtk.unit import *
import simtk.openmm
import simtk.openmm.app.element as elem import simtk.openmm.app.element as elem
import simtk.openmm.app.forcefield as forcefield import simtk.openmm.app.forcefield as forcefield
import copy import copy
import pickle import pickle
class TestPickle(unittest.TestCase): class TestPickle(unittest.TestCase):
"""Pickling / deepcopy of OpenMM state and integrator objects.""" """Pickling / deepcopy of OpenMM objects."""
def setUp(self): def setUp(self):
"""Set up the tests by loading the input pdb files and force field """Set up the tests by loading the input pdb files and force field
...@@ -26,28 +27,46 @@ class TestPickle(unittest.TestCase): ...@@ -26,28 +27,46 @@ class TestPickle(unittest.TestCase):
self.pdb2 = PDBFile('systems/alanine-dipeptide-implicit.pdb') self.pdb2 = PDBFile('systems/alanine-dipeptide-implicit.pdb')
self.forcefield2 = ForceField('amber99sb.xml', 'amber99_obc.xml') self.forcefield2 = ForceField('amber99sb.xml', 'amber99_obc.xml')
def check_copy(self, object, object_copy):
"""Check that an object's copy is an accurate replica."""
# Check class name is same.
self.assertEqual(object.__class__.__name__, object_copy.__class__.__name__)
# Check serialized contents are the same.
self.assertEqual(XmlSerializer.serialize(object), XmlSerializer.serialize(object_copy))
def test_deepcopy(self): def test_deepcopy(self):
"""Test that serialization/deserialization works (via deepcopy).""" """Test that serialization/deserialization works (via deepcopy)."""
# Create system, integrator, and state.
system = self.forcefield1.createSystem(self.pdb1.topology) system = self.forcefield1.createSystem(self.pdb1.topology)
integrator = VerletIntegrator(2*femtosecond) integrator = VerletIntegrator(2*femtosecond)
context = Context(system, integrator) context = Context(system, integrator)
context.setPositions(self.pdb1.positions) context.setPositions(self.pdb1.positions)
state = context.getState(getPositions=True, getForces=True, getEnergy=True) state = context.getState(getPositions=True, getForces=True, getEnergy=True)
system2 = copy.deepcopy(system) #
integrator2 = copy.deepcopy(integrator) # Test deepcopy
state2 = copy.deepcopy(state) #
str_state = pickle.dumps(state)
str_integrator = pickle.dumps(integrator)
state3 = pickle.loads(str_state)
context.setState(state3)
self.check_copy(system, copy.deepcopy(system))
self.check_copy(integrator, copy.deepcopy(integrator))
self.check_copy(state, copy.deepcopy(state))
for force_index in range(system.getNumForces()):
force = system.getForce(force_index)
force_copy = copy.deepcopy(force)
self.check_copy(force, force_copy)
del context, integrator #
# Test pickle
#
self.check_copy(system, pickle.loads(pickle.dumps(system)))
self.check_copy(integrator, pickle.loads(pickle.dumps(integrator)))
self.check_copy(state, pickle.loads(pickle.dumps(state)))
for force_index in range(system.getNumForces()):
force = system.getForce(force_index)
force_copy = pickle.loads(pickle.dumps(force))
self.check_copy(force, force_copy)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
......
import unittest
import simtk.openmm as mm
class TestSwigWrappers(unittest.TestCase):
def test_1(self):
# This tests for a refcounting bug in the swig wrappers
# that was previously problematic.
# See https://github.com/pandegroup/openmm/issues/1214
for cycle in range(10):
system = mm.System()
system.getDefaultPeriodicBoxVectors()
if __name__ == '__main__':
unittest.main()
import sys
import unittest
from simtk.openmm.app import *
from simtk.openmm import *
from simtk.unit import *
import simtk.openmm.app.element as elem
if sys.version_info >= (3, 0):
from io import StringIO
else:
from cStringIO import StringIO
class TestTopology(unittest.TestCase):
"""Test the Topology object"""
def check_pdbfile(self, pdbfilename, natoms, nres, nchains):
"""Check that a PDB file has the specified number of atoms, residues, and chains."""
pdb = PDBFile(pdbfilename)
top = pdb.topology
self.assertEqual(pdb.topology.getNumAtoms(), natoms)
self.assertEqual(pdb.topology.getNumResidues(), nres)
self.assertEqual(pdb.topology.getNumChains(), nchains)
def test_getters(self):
"""Test getters for number of atoms, residues, chains."""
self.check_pdbfile('systems/1T2Y.pdb', 271, 25, 1)
if __name__ == '__main__':
unittest.main()
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