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
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.
Portions copyright (c) 2012 Stanford University and the Authors.
Portions copyright (c) 2012-2014 Stanford University and the Authors.
Authors: Peter Eastman
Contributors:
Contributors: Jason Swails
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
......@@ -31,51 +31,55 @@ USE OR OTHER DEALINGS IN THE SOFTWARE.
__author__ = "Peter Eastman"
__version__ = "1.0"
from functools import wraps
from simtk.openmm.app.internal import amber_file_parser
from simtk.unit import Quantity, nanometers, picoseconds
import warnings
try:
import numpy
import numpy as np
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):
"""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.
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:
- file (string) the name of the file to load
- loadVelocities (boolean=False) whether to load velocities from the file
- loadBoxVectors (boolean=False) whether to load the periodic box vectors
- loadVelocities (boolean=None) deprecated. Velocities are loaded automatically if present
- loadBoxVectors (boolean=None) deprecated. Box vectors are loaded automatically if present
"""
results = amber_file_parser.readAmberCoordinates(file, read_velocities=loadVelocities, read_box=loadBoxVectors)
if loadVelocities:
## The atom positions read from the inpcrd file
self.positions = results[0]
if loadBoxVectors:
## The periodic box vectors read from the inpcrd file
self.boxVectors = results[1]
## The atom velocities read from the inpcrd file
self.velocities = results[2]
else:
self.velocities = results[1]
elif loadBoxVectors:
self.positions = results[0]
self.boxVectors = results[1]
else:
self.positions = results
self.file = file
if loadVelocities is not None or loadBoxVectors is not None:
warnings.warn('loadVelocities and loadBoxVectors have been '
'deprecated. velocities and box information '
'is loaded automatically if the inpcrd file contains '
'them.', DeprecationWarning)
results = amber_file_parser.readAmberCoordinates(file)
self.positions, self.velocities, self.boxVectors = results
# Cached numpy arrays
self._numpyPositions = None
if loadVelocities:
self._numpyVelocities = None
if loadBoxVectors:
self._numpyBoxVectors = None
@numpy_protector
def getPositions(self, asNumpy=False):
"""Get the atomic positions.
......@@ -84,34 +88,40 @@ class AmberInpcrdFile(object):
"""
if asNumpy:
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.positions
@numpy_protector
def getVelocities(self, asNumpy=False):
"""Get the atomic velocities.
Parameters:
- 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 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.velocities
@numpy_protector
def getBoxVectors(self, asNumpy=False):
"""Get the periodic box vectors.
Parameters:
- 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 self._numpyBoxVectors is None:
self._numpyBoxVectors = []
self._numpyBoxVectors.append(Quantity(numpy.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(numpy.array(self.boxVectors[2].value_in_unit(nanometers)), nanometers))
self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[0].value_in_unit(nanometers)), nanometers))
self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[1].value_in_unit(nanometers)), nanometers))
self._numpyBoxVectors.append(Quantity(np.array(self.boxVectors[2].value_in_unit(nanometers)), nanometers))
return self._numpyBoxVectors
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