Commit 4c00b312 authored by peastman's avatar peastman
Browse files

Merge pull request #1081 from swails/pdbx

Add PDBxReporter class
parents 300566f3 c885f3d7
""" """
OpenMM Application OpenMM Application
""" """
from __future__ import absolute_import
__docformat__ = "epytext en" __docformat__ = "epytext en"
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
...@@ -10,26 +12,26 @@ __license__ = "MIT" ...@@ -10,26 +12,26 @@ __license__ = "MIT"
__maintainer__ = "Peter Eastman" __maintainer__ = "Peter Eastman"
__email__ = "peastman@stanford.edu" __email__ = "peastman@stanford.edu"
from topology import Topology, Chain, Residue, Atom from .topology import Topology, Chain, Residue, Atom
from pdbfile import PDBFile from .pdbfile import PDBFile
from pdbxfile import PDBxFile from .pdbxfile import PDBxFile
from forcefield import ForceField from .forcefield import ForceField
from simulation import Simulation from .simulation import Simulation
from pdbreporter import PDBReporter from .pdbreporter import PDBReporter, PDBxReporter
from amberprmtopfile import AmberPrmtopFile, HCT, OBC1, OBC2, GBn, GBn2 from .amberprmtopfile import AmberPrmtopFile, HCT, OBC1, OBC2, GBn, GBn2
from amberinpcrdfile import AmberInpcrdFile from .amberinpcrdfile import AmberInpcrdFile
from dcdfile import DCDFile from .dcdfile import DCDFile
from gromacsgrofile import GromacsGroFile from .gromacsgrofile import GromacsGroFile
from gromacstopfile import GromacsTopFile from .gromacstopfile import GromacsTopFile
from dcdreporter import DCDReporter from .dcdreporter import DCDReporter
from modeller import Modeller from .modeller import Modeller
from statedatareporter import StateDataReporter from .statedatareporter import StateDataReporter
from element import Element from .element import Element
from desmonddmsfile import DesmondDMSFile from .desmonddmsfile import DesmondDMSFile
from checkpointreporter import CheckpointReporter from .checkpointreporter import CheckpointReporter
from charmmcrdfiles import CharmmCrdFile, CharmmRstFile from .charmmcrdfiles import CharmmCrdFile, CharmmRstFile
from charmmparameterset import CharmmParameterSet from .charmmparameterset import CharmmParameterSet
from charmmpsffile import CharmmPsfFile, CharmmPSFWarning from .charmmpsffile import CharmmPsfFile, CharmmPSFWarning
# Enumerated values # Enumerated values
......
...@@ -28,6 +28,7 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR ...@@ -28,6 +28,7 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE. USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
from __future__ import print_function, division, absolute_import
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
...@@ -39,12 +40,13 @@ from copy import copy ...@@ -39,12 +40,13 @@ from copy import copy
from datetime import date from datetime import date
from simtk.openmm import Vec3, Platform from simtk.openmm import Vec3, Platform
from simtk.openmm.app.internal.pdbstructure import PdbStructure from simtk.openmm.app.internal.pdbstructure import PdbStructure
from simtk.openmm.app.internal.unitcell import computeLengthsAndAngles
from simtk.openmm.app import Topology from simtk.openmm.app import Topology
from simtk.unit import nanometers, angstroms, is_quantity, norm, Quantity, dot from simtk.unit import nanometers, angstroms, is_quantity, norm, Quantity, dot
import element as elem from . import element as elem
try: try:
import numpy import numpy
except: except ImportError:
pass pass
class PDBFile(object): class PDBFile(object):
...@@ -252,17 +254,13 @@ class PDBFile(object): ...@@ -252,17 +254,13 @@ class PDBFile(object):
- topology (Topology) The Topology defining the molecular system being written - topology (Topology) The Topology defining the molecular system being written
- file (file=stdout) A file to write the file to - file (file=stdout) A file to write the file to
""" """
print >>file, "REMARK 1 CREATED WITH OPENMM %s, %s" % (Platform.getOpenMMVersion(), str(date.today())) print("REMARK 1 CREATED WITH OPENMM %s, %s" % (Platform.getOpenMMVersion(), str(date.today())), file=file)
vectors = topology.getPeriodicBoxVectors() vectors = topology.getPeriodicBoxVectors()
if vectors is not None: if vectors is not None:
(a, b, c) = vectors.value_in_unit(angstroms) a, b, c, alpha, beta, gamma = computeLengthsAndAngles(vectors)
a_length = norm(a) RAD_TO_DEG = 180/math.pi
b_length = norm(b) print("CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f P 1 1 " % (
c_length = norm(c) a*10, b*10, c*10, alpha*RAD_TO_DEG, beta*RAD_TO_DEG, gamma*RAD_TO_DEG), file=file)
alpha = math.acos(dot(b, c)/(b_length*c_length))*180.0/math.pi
beta = math.acos(dot(c, a)/(c_length*a_length))*180.0/math.pi
gamma = math.acos(dot(a, b)/(a_length*b_length))*180.0/math.pi
print >>file, "CRYST1%9.3f%9.3f%9.3f%7.2f%7.2f%7.2f P 1 1 " % (a_length, b_length, c_length, alpha, beta, gamma)
@staticmethod @staticmethod
def writeModel(topology, positions, file=sys.stdout, modelIndex=None, keepIds=False): def writeModel(topology, positions, file=sys.stdout, modelIndex=None, keepIds=False):
...@@ -288,7 +286,7 @@ class PDBFile(object): ...@@ -288,7 +286,7 @@ class PDBFile(object):
atomIndex = 1 atomIndex = 1
posIndex = 0 posIndex = 0
if modelIndex is not None: if modelIndex is not None:
print >>file, "MODEL %4d" % modelIndex print("MODEL %4d" % modelIndex, file=file)
for (chainIndex, chain) in enumerate(topology.chains()): for (chainIndex, chain) in enumerate(topology.chains()):
if keepIds: if keepIds:
chainName = chain.id chainName = chain.id
...@@ -320,14 +318,14 @@ class PDBFile(object): ...@@ -320,14 +318,14 @@ class PDBFile(object):
atomIndex%100000, atomName, resName, chainName, resId, _format_83(coords[0]), atomIndex%100000, atomName, resName, chainName, resId, _format_83(coords[0]),
_format_83(coords[1]), _format_83(coords[2]), symbol) _format_83(coords[1]), _format_83(coords[2]), symbol)
assert len(line) == 80, 'Fixed width overflow detected' assert len(line) == 80, 'Fixed width overflow detected'
print >>file, line print(line, file=file)
posIndex += 1 posIndex += 1
atomIndex += 1 atomIndex += 1
if resIndex == len(residues)-1: if resIndex == len(residues)-1:
print >>file, "TER %5d %3s %s%4s" % (atomIndex, resName, chainName, resId) print("TER %5d %3s %s%4s" % (atomIndex, resName, chainName, resId), file=file)
atomIndex += 1 atomIndex += 1
if modelIndex is not None: if modelIndex is not None:
print >>file, "ENDMDL" print("ENDMDL", file=file)
@staticmethod @staticmethod
def writeFooter(topology, file=sys.stdout): def writeFooter(topology, file=sys.stdout):
...@@ -381,13 +379,13 @@ class PDBFile(object): ...@@ -381,13 +379,13 @@ class PDBFile(object):
for index1 in sorted(atomBonds): for index1 in sorted(atomBonds):
bonded = atomBonds[index1] bonded = atomBonds[index1]
while len(bonded) > 4: while len(bonded) > 4:
print >>file, "CONECT%5d%5d%5d%5d" % (index1, bonded[0], bonded[1], bonded[2]) print("CONECT%5d%5d%5d%5d" % (index1, bonded[0], bonded[1], bonded[2]), file=file)
del bonded[:4] del bonded[:4]
line = "CONECT%5d" % index1 line = "CONECT%5d" % index1
for index2 in bonded: for index2 in bonded:
line = "%s%5d" % (line, index2) line = "%s%5d" % (line, index2)
print >>file, line print(line, file=file)
print >>file, "END" print("END", file=file)
def _format_83(f): def _format_83(f):
......
...@@ -32,7 +32,7 @@ __author__ = "Peter Eastman" ...@@ -32,7 +32,7 @@ __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
import simtk.openmm as mm import simtk.openmm as mm
from simtk.openmm.app import PDBFile from simtk.openmm.app import PDBFile, PDBxFile
class PDBReporter(object): class PDBReporter(object):
"""PDBReporter outputs a series of frames from a Simulation to a PDB file. """PDBReporter outputs a series of frames from a Simulation to a PDB file.
...@@ -77,8 +77,34 @@ class PDBReporter(object): ...@@ -77,8 +77,34 @@ class PDBReporter(object):
self._nextModel += 1 self._nextModel += 1
PDBFile.writeModel(simulation.topology, state.getPositions(), self._out, self._nextModel) PDBFile.writeModel(simulation.topology, state.getPositions(), self._out, self._nextModel)
self._nextModel += 1 self._nextModel += 1
if hasattr(self._out, 'flush') and callable(self._out.flush):
self._out.flush()
def __del__(self): def __del__(self):
if self._topology is not None: if self._topology is not None:
PDBFile.writeFooter(self._topology, self._out) PDBFile.writeFooter(self._topology, self._out)
self._out.close() self._out.close()
class PDBxReporter(PDBReporter):
"""PDBxReporter outputs a series of frames from a Simulation to a PDBx/mmCIF file.
To use it, create a PDBxReporter, then add it to the Simulation's list of reporters.
"""
def report(self, simulation, state):
"""Generate a report.
Parameters:
- simulation (Simulation) The Simulation to generate a report for
- state (State) The current state of the simulation
"""
if self._nextModel == 0:
PDBxFile.writeHeader(simulation.topology, self._out)
self._nextModel += 1
PDBxFile.writeModel(simulation.topology, state.getPositions(), self._out, self._nextModel)
self._nextModel += 1
if hasattr(self._out, 'flush') and callable(self._out.flush):
self._out.flush()
def __del__(self):
self._out.close()
""" """
pdbfile.py: Used for loading PDB files. pdbxfile.py: Used for loading PDBx/mmCIF files.
This is part of the OpenMM molecular simulation toolkit originating from This is part of the OpenMM molecular simulation toolkit originating from
Simbios, the NIH National Center for Physics-Based Simulation of Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org. Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2014-2015 Stanford University and the Authors. Portions copyright (c) 2015 Stanford University and the Authors.
Authors: Peter Eastman Authors: Peter Eastman
Contributors: Contributors: Jason Swails
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"),
...@@ -28,18 +28,20 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR ...@@ -28,18 +28,20 @@ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE. USE OR OTHER DEALINGS IN THE SOFTWARE.
""" """
from __future__ import division, absolute_import, print_function
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "2.0"
import os
import sys import sys
import math import math
from simtk.openmm import Vec3 from simtk.openmm import Vec3, Platform
from datetime import date
from simtk.openmm.app.internal.pdbx.reader.PdbxReader import PdbxReader from simtk.openmm.app.internal.pdbx.reader.PdbxReader import PdbxReader
from simtk.openmm.app.internal.unitcell import computePeriodicBoxVectors from simtk.openmm.app.internal.unitcell import computePeriodicBoxVectors, computeLengthsAndAngles
from simtk.openmm.app import Topology from simtk.openmm.app import Topology
from simtk.unit import nanometers, angstroms, is_quantity, norm, Quantity from simtk.unit import nanometers, angstroms, is_quantity, norm, Quantity, dot
import element as elem from . import element as elem
try: try:
import numpy import numpy
except: except:
...@@ -202,3 +204,120 @@ class PDBxFile(object): ...@@ -202,3 +204,120 @@ class PDBxFile(object):
self._numpyPositions[frame] = Quantity(numpy.array(self._positions[frame].value_in_unit(nanometers)), nanometers) self._numpyPositions[frame] = Quantity(numpy.array(self._positions[frame].value_in_unit(nanometers)), nanometers)
return self._numpyPositions[frame] return self._numpyPositions[frame]
return self._positions[frame] return self._positions[frame]
@staticmethod
def writeFile(topology, positions, file=sys.stdout, keepIds=False,
entry=None):
"""Write a PDBx/mmCIF file containing a single model.
Parameters:
- topology (Topology) The Topology defining the model to write
- positions (list) The list of atomic positions to write
- file (file=stdout) 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.writeModel(topology, positions, file, keepIds=keepIds)
@staticmethod
def writeHeader(topology, file=sys.stdout, entry=None):
"""Write out the header for a PDBx/mmCIF file.
Parameters:
- topology (Topology) 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:
print('data_%s' % entry, file=file)
else:
print('data_cell', file=file)
print("# Created with OpenMM %s, %s" % (Platform.getOpenMMVersion(), str(date.today())), file=file)
print('#', file=file)
vectors = topology.getPeriodicBoxVectors()
if vectors is not None:
a, b, c, alpha, beta, gamma = computeLengthsAndAngles(vectors)
RAD_TO_DEG = 180/math.pi
print('_cell.length_a %10.4f' % (a*10), file=file)
print('_cell.length_b %10.4f' % (b*10), file=file)
print('_cell.length_c %10.4f' % (c*10), file=file)
print('_cell.angle_alpha %10.4f' % (alpha*RAD_TO_DEG), file=file)
print('_cell.angle_beta %10.4f' % (beta*RAD_TO_DEG), file=file)
print('_cell.angle_gamma %10.4f' % (gamma*RAD_TO_DEG), file=file)
print('##', file=file)
print('loop_', file=file)
print('_atom_site.group_PDB', file=file)
print('_atom_site.id', file=file)
print('_atom_site.type_symbol', file=file)
print('_atom_site.label_atom_id', file=file)
print('_atom_site.label_alt_id', file=file)
print('_atom_site.label_comp_id', file=file)
print('_atom_site.label_asym_id', file=file)
print('_atom_site.label_entity_id', file=file)
print('_atom_site.label_seq_id', file=file)
print('_atom_site.pdbx_PDB_ins_code', file=file)
print('_atom_site.Cartn_x', file=file)
print('_atom_site.Cartn_y', file=file)
print('_atom_site.Cartn_z', file=file)
print('_atom_site.occupancy', file=file)
print('_atom_site.B_iso_or_equiv', file=file)
print('_atom_site.Cartn_x_esd', file=file)
print('_atom_site.Cartn_y_esd', file=file)
print('_atom_site.Cartn_z_esd', file=file)
print('_atom_site.occupancy_esd', file=file)
print('_atom_site.B_iso_or_equiv_esd', file=file)
print('_atom_site.pdbx_formal_charge', file=file)
print('_atom_site.auth_seq_id', file=file)
print('_atom_site.auth_comp_id', file=file)
print('_atom_site.auth_asym_id', file=file)
print('_atom_site.auth_atom_id', file=file)
print('_atom_site.pdbx_PDB_model_num', file=file)
@staticmethod
def writeModel(topology, positions, file=sys.stdout, modelIndex=1, keepIds=False):
"""Write out a model to a PDBx/mmCIF file.
Parameters:
- topology (Topology) The Topology defining the model to write
- positions (list) The list of atomic positions to write
- file (file=stdout) 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):
raise ValueError('The number of positions must match the number of atoms')
if is_quantity(positions):
positions = positions.value_in_unit(angstroms)
if any(math.isnan(norm(pos)) for pos in positions):
raise ValueError('Particle position is NaN')
if any(math.isinf(norm(pos)) for pos in positions):
raise ValueError('Particle position is infinite')
atomIndex = 1
posIndex = 0
for (chainIndex, chain) in enumerate(topology.chains()):
if keepIds:
chainName = chain.id
else:
chainName = chr(ord('A')+chainIndex%26)
residues = list(chain.residues())
for (resIndex, res) in enumerate(residues):
if keepIds:
resId = res.id
else:
resId = resIndex + 1
for atom in res.atoms():
coords = positions[posIndex]
if atom.element is not None:
symbol = atom.element.symbol
else:
symbol = '?'
line = "ATOM %5d %-3s %-4s . %-4s %s ? %5s . %10.4f %10.4f %10.4f 0.0 0.0 ? ? ? ? ? . %5s %4s %s %4s %5d"
print(line % (atomIndex, symbol, atom.name, res.name, chainName, resId, coords[0], coords[1], coords[2],
resId, res.name, chainName, atom.name, modelIndex), file=file)
posIndex += 1
atomIndex += 1
...@@ -3,6 +3,7 @@ from simtk.openmm.app import * ...@@ -3,6 +3,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 os
class TestPdbxFile(unittest.TestCase): class TestPdbxFile(unittest.TestCase):
"""Test the PDBx/mmCIF file parser""" """Test the PDBx/mmCIF file parser"""
...@@ -48,6 +49,69 @@ class TestPdbxFile(unittest.TestCase): ...@@ -48,6 +49,69 @@ class TestPdbxFile(unittest.TestCase):
diff = abs(p1[i]-p2[i])/scale diff = abs(p1[i]-p2[i])/scale
self.assertTrue(diff < tol) self.assertTrue(diff < tol)
def testReporterImplicit(self):
""" Tests the PDBxReporter without PBC """
parm = AmberPrmtopFile('systems/alanine-dipeptide-implicit.prmtop')
system = parm.createSystem()
sim = Simulation(parm.topology, system, VerletIntegrator(1*femtoseconds),
Platform.getPlatformByName('Reference'))
sim.context.setPositions(PDBFile('systems/alanine-dipeptide-implicit.pdb').getPositions())
sim.reporters.append(PDBxReporter('test.cif', 1))
sim.step(10)
pdb = PDBxFile('test.cif')
self.assertEqual(len(list(pdb.topology.atoms())), len(list(parm.topology.atoms())))
self.assertEqual(len(list(pdb.topology.residues())), len(list(parm.topology.residues())))
for res1, res2 in zip(pdb.topology.residues(), parm.topology.residues()):
self.assertEqual(res1.name, res2.name)
for atom1, atom2 in zip(res1.atoms(), res2.atoms()):
self.assertEqual(atom1.name, atom2.name)
positions = pdb.getPositions(frame=9)
self.assertFalse(all(x1 == x2 for x1, x2 in zip(positions, pdb.getPositions(frame=0))))
# There should only be 10 frames (0 through 9)
self.assertRaises(IndexError, lambda: pdb.getPositions(frame=10))
self.assertIs(pdb.topology.getPeriodicBoxVectors(), None)
os.unlink('test.cif')
def assertAlmostEqualVec(self, vec1, vec2, *args, **kwargs):
if is_quantity(vec1):
vec1 = vec1.value_in_unit_system(md_unit_system)
if is_quantity(vec2):
vec2 = vec2.value_in_unit_system(md_unit_system)
for x, y in zip(vec1, vec2):
self.assertAlmostEqual(x, y, *args, **kwargs)
def testReporterExplicit(self):
""" Tests the PDBxReporter with PBC """
parm = AmberPrmtopFile('systems/alanine-dipeptide-explicit.prmtop')
system = parm.createSystem(nonbondedCutoff=1.0, nonbondedMethod=PME)
sim = Simulation(parm.topology, system, VerletIntegrator(1*femtoseconds),
Platform.getPlatformByName('Reference'))
orig_pdb = PDBFile('systems/alanine-dipeptide-explicit.pdb')
sim.context.setPositions(orig_pdb.getPositions())
sim.context.setPeriodicBoxVectors(*parm.topology.getPeriodicBoxVectors())
sim.reporters.append(PDBxReporter('test.cif', 1))
sim.step(10)
pdb = PDBxFile('test.cif')
self.assertEqual(len(list(pdb.topology.atoms())), len(list(parm.topology.atoms())))
self.assertEqual(len(list(pdb.topology.residues())), len(list(parm.topology.residues())))
for res1, res2 in zip(pdb.topology.residues(), parm.topology.residues()):
self.assertEqual(res1.name, res2.name)
for atom1, atom2 in zip(res1.atoms(), res2.atoms()):
self.assertEqual(atom1.name, atom2.name)
positions = pdb.getPositions(frame=9)
self.assertFalse(all(x1 == x2 for x1, x2 in zip(positions, pdb.getPositions(frame=0))))
# There should only be 10 frames (0 through 9)
self.assertRaises(IndexError, lambda: pdb.getPositions(frame=10))
self.assertAlmostEqualVec(parm.topology.getPeriodicBoxVectors()[0],
pdb.topology.getPeriodicBoxVectors()[0],
places=5)
self.assertAlmostEqualVec(parm.topology.getPeriodicBoxVectors()[1],
pdb.topology.getPeriodicBoxVectors()[1],
places=5)
self.assertAlmostEqualVec(parm.topology.getPeriodicBoxVectors()[2],
pdb.topology.getPeriodicBoxVectors()[2],
places=5)
os.unlink('test.cif')
if __name__ == '__main__': if __name__ == '__main__':
unittest.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