Commit d3fd7d5a authored by Jason Swails's avatar Jason Swails
Browse files

Rewrite Amber inpcrd/restart file handling. Among the improvements:

    - existence of velocities and box vectors is auto-detected, so
      loadVelocities and loadBoxVectors are unnecessary
    - recognizes NetCDF-format Amber restart files (which can be written by
      tleap, sander, pmemd, and cpptraj).
    - Restart files coordinate/velocity/box fields have a hard width of 12
      characters and does not ensure that fields are whitespace delimited. This
      commit will correctly parse (valid) Amber restart files in which a
      particular coordinate is <= -100 A or >= 1000 A (not uncommon for restart
      files from sander/pmemd).
    - File format (ASCII vs. NetCDF) is auto-detected.

I deprecated the loadVelocities and loadBoxVectors arguments, since the behavior
does not change (and is correct) whether these variables are set or not. A
DeprecationWarning is emitted if users specify these variables.
parent 0b8201b2
...@@ -6,9 +6,9 @@ Simbios, the NIH National Center for Physics-Based Simulation of ...@@ -6,9 +6,9 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org. Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2012 Stanford University and the Authors. Portions copyright (c) 2012-2014 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"),
...@@ -31,87 +31,97 @@ USE OR OTHER DEALINGS IN THE SOFTWARE. ...@@ -31,87 +31,97 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
__author__ = "Peter Eastman" __author__ = "Peter Eastman"
__version__ = "1.0" __version__ = "1.0"
from functools import wraps
from simtk.openmm.app.internal import amber_file_parser from simtk.openmm.app.internal import amber_file_parser
from simtk.unit import Quantity, nanometers, picoseconds from simtk.unit import Quantity, nanometers, picoseconds
import warnings
try: try:
import numpy import numpy as np
except: except:
pass np = None
def numpy_protector(func):
"""
Decorator to emit useful error messages if users try to request numpy
processing if numpy is not available. Raises ImportError if numpy could not
be found
"""
@wraps(func)
def wrapper(self, asNumpy=False):
if asNumpy and np is None:
raise ImportError('Could not import numpy. Cannot set asNumpy=True')
return func(self, asNumpy=asNumpy)
return wrapper
class AmberInpcrdFile(object): class AmberInpcrdFile(object):
"""AmberInpcrdFile parses an AMBER inpcrd file and loads the data stored in it.""" """AmberInpcrdFile parses an AMBER inpcrd file and loads the data stored in it."""
def __init__(self, file, loadVelocities=False, loadBoxVectors=False): def __init__(self, file, loadVelocities=None, loadBoxVectors=None):
"""Load an inpcrd file. """Load an inpcrd file.
An inpcrd file contains atom positions and, optionally, velocities and periodic box dimensions. An inpcrd file contains atom positions and, optionally, velocities and periodic box dimensions.
Unfortunately, it is sometimes impossible to determine from the file itself exactly what data
it contains. You therefore must specify in advance what data to load. It is stored into this
object's "positions", "velocities", and "boxVectors" fields.
Parameters: Parameters:
- file (string) the name of the file to load - file (string) the name of the file to load
- loadVelocities (boolean=False) whether to load velocities from the file - loadVelocities (boolean=None) deprecated. Velocities are loaded automatically if present
- loadBoxVectors (boolean=False) whether to load the periodic box vectors - loadBoxVectors (boolean=None) deprecated. Box vectors are loaded automatically if present
""" """
results = amber_file_parser.readAmberCoordinates(file, read_velocities=loadVelocities, read_box=loadBoxVectors) self.file = file
if loadVelocities: if loadVelocities is not None or loadBoxVectors is not None:
## The atom positions read from the inpcrd file warnings.warn('loadVelocities and loadBoxVectors have been '
self.positions = results[0] 'deprecated. velocities and box information '
if loadBoxVectors: 'is loaded automatically if the inpcrd file contains '
## The periodic box vectors read from the inpcrd file 'them.', DeprecationWarning)
self.boxVectors = results[1] results = amber_file_parser.readAmberCoordinates(file)
## The atom velocities read from the inpcrd file self.positions, self.velocities, self.boxVectors = results
self.velocities = results[2] # Cached numpy arrays
else:
self.velocities = results[1]
elif loadBoxVectors:
self.positions = results[0]
self.boxVectors = results[1]
else:
self.positions = results
self._numpyPositions = None self._numpyPositions = None
if loadVelocities: self._numpyVelocities = None
self._numpyVelocities = None self._numpyBoxVectors = None
if loadBoxVectors:
self._numpyBoxVectors = None
@numpy_protector
def getPositions(self, asNumpy=False): def getPositions(self, asNumpy=False):
"""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 - asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s
""" """
if asNumpy: if asNumpy:
if self._numpyPositions is None: if self._numpyPositions is None:
self._numpyPositions = Quantity(numpy.array(self.positions.value_in_unit(nanometers)), nanometers) self._numpyPositions = Quantity(np.array(self.positions.value_in_unit(nanometers)), nanometers)
return self._numpyPositions return self._numpyPositions
return self.positions return self.positions
@numpy_protector
def getVelocities(self, asNumpy=False): def getVelocities(self, asNumpy=False):
"""Get the atomic velocities. """Get the atomic velocities.
Parameters: Parameters:
- asNumpy (boolean=False) if true, the vectors are returned as numpy arrays instead of Vec3s - asNumpy (boolean=False) if true, the vectors are returned as numpy arrays instead of Vec3s
""" """
if self.velocities is None:
raise AttributeError('velocities not found in %s' % self.file)
if asNumpy: if asNumpy:
if self._numpyVelocities is None: if self._numpyVelocities is None:
self._numpyVelocities = Quantity(numpy.array(self.velocities.value_in_unit(nanometers/picoseconds)), nanometers/picoseconds) self._numpyVelocities = Quantity(np.array(self.velocities.value_in_unit(nanometers/picoseconds)), nanometers/picoseconds)
return self._numpyVelocities return self._numpyVelocities
return self.velocities return self.velocities
@numpy_protector
def getBoxVectors(self, asNumpy=False): def getBoxVectors(self, asNumpy=False):
"""Get the periodic box vectors. """Get the periodic box vectors.
Parameters: Parameters:
- asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s - asNumpy (boolean=False) if true, the values are returned as a numpy array instead of a list of Vec3s
""" """
if self.boxVectors is None:
raise AttributeError('Box information not found in %s' % self.file)
if asNumpy: if asNumpy:
if self._numpyBoxVectors is None: if self._numpyBoxVectors is None:
self._numpyBoxVectors = [] self._numpyBoxVectors = []
self._numpyBoxVectors.append(Quantity(numpy.array(self.boxVectors[0].value_in_unit(nanometers)), nanometers)) self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[0].value_in_unit(nanometers)), nanometers))
self._numpyBoxVectors.append(Quantity(numpy.array(self.boxVectors[1].value_in_unit(nanometers)), nanometers)) self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[1].value_in_unit(nanometers)), nanometers))
self._numpyBoxVectors.append(Quantity(numpy.array(self.boxVectors[2].value_in_unit(nanometers)), nanometers)) self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[2].value_in_unit(nanometers)), nanometers))
return self._numpyBoxVectors return self._numpyBoxVectors
return self.boxVectors return self.boxVectors
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