Skip to content

Commit

Permalink
Converter for OpenMM (#2917)
Browse files Browse the repository at this point in the history
* Topology from omm topology and simulation objects. Coordinates from omm simulation

* Remove debug lines

* Move omm topology parsing code outside class for re-use in multiple omm reader objects

* Change openmm trajectory reader into single frame reader

* Add coordinates conversions for openmm modeller and pdbfile classes

* Add topology parsing for openmm pdbfile and modeller objects

* Tests for openmmtopology parser

* Revert init

* Test openmm topology parsing on pdb file with connections, add atomtypes

* Use guesser for guessing atomtypes from atomnames

* Off by one errors for 1-indexed resids

* Set frame to 0 for first timestep

* Unwrap simtk quantity for time

* Coordinates tests for openmm pdbfile, modeller, simulation objects

* update error messsage

* Update documentation

* Try updating some yaml files

* Include datafiles and tests for pdbx files

* More documentation for pdbx

* Typo in format

* Blacken code

* Refactor so generic application objects share the same readers and functions

* Adjust line length

* Revert datafiles without black formatting

* Add pdbx file to datafiles, update changelog

* Update docstring with proper versionadded tag

* Missing comma in list

* Add pytest importorskip for testing openmm topology c onversion

* Merge conflict

* Remove unnecessary imports, more documentation for openmm application layer objects, additional openmm test from a basic openmm simulation made from scratch

* Example usage in documentation

* Convert simtk quanitties into default internal units

* Update travis with extra variables for openmm, installing only on py37 tests

* Revert back to atomtype guessing

* Use proper openmm atom naming conventions for unit test

* Modify appveyor config to specify openmm on py36 vs py38

* Correct syntax for appveyor env vars

* Change appveyor var syntax

* Empty string for conda openmm var

* Merge conflict

* Update GHA conda min dependencies for openmm on conda-forge

* Remove inplace flag for converting energies, not used anywhere in openmm conversions

* Update appveyor to pull openmm from conda-forge

* Use simtk syntax for unit conversions

* - Sanitize box angles when converting from OpenMM unitcell to MDA unitcell
- Add xfail to pickle single frame reader for openmm tests
- Use elements to specify atomtypes from OpenMM topology

* Make sure OpenMM appears in the documentation

* Add the stubs to trigger the build and link to the documentation of
  the new modules
* Fix issues with references in the docstrings

* PEP8 fixes for OpenMM reader and parser

Co-authored-by: Oliver Beckstein <[email protected]>
Co-authored-by: Jonathan Barnoud <[email protected]>
  • Loading branch information
3 people authored May 5, 2021
1 parent 79183bf commit 7f2ce3d
Show file tree
Hide file tree
Showing 16 changed files with 873 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .disabled-travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ env:
- SETUP_CMD="${PYTEST_FLAGS}"
- BUILD_CMD="pip install -e package/ && (cd testsuite/ && python setup.py build)"
- CONDA_MIN_DEPENDENCIES="mmtf-python biopython networkx cython matplotlib scipy griddataformats hypothesis gsd codecov"
- CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0 tidynamics>=1.0.0 rdkit>=2020.03.1 h5py"
- CONDA_DEPENDENCIES="${CONDA_MIN_DEPENDENCIES} seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0 tidynamics>=1.0.0 rdkit>=2020.03.1 h5py openmm"
- CONDA_CHANNELS='biobuilds conda-forge'
- CONDA_CHANNEL_PRIORITY=True
- PIP_DEPENDENCIES="duecredit parmed"
Expand All @@ -43,7 +43,7 @@ env:
matrix:
# Run a coverage test for most versions
- CODECOV="true" SETUP_CMD="${PYTEST_FLAGS} --cov=MDAnalysis"
- PYTHON_VERSION=3.8 CODECOV="true" SETUP_CMD="${PYTEST_FLAGS} --cov=MDAnalysis"
- PYTHON_VERSION=3.8 CODECOV="true" SETUP_CMD="${PYTEST_FLAGS} --cov=MDAnalysis"
- PYTHON_VERSION=3.7 CODECOV="true" SETUP_CMD="${PYTEST_FLAGS} --cov=MDAnalysis"
- NUMPY_VERSION=1.16.0
- NUMPY_VERSION=dev EVENT_TYPE="cron"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gh-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ defaults:
shell: bash -l {0}

env:
MDA_CONDA_MIN_DEPS: "pip pytest==6.1.2 mmtf-python biopython networkx cython matplotlib-base scipy griddataformats hypothesis gsd codecov threadpoolctl"
MDA_CONDA_MIN_DEPS: "pip pytest==6.1.2 mmtf-python biopython networkx cython matplotlib-base scipy griddataformats hypothesis gsd codecov threadpoolctl openmm"
MDA_CONDA_EXTRA_DEPS: "seaborn>=0.7.0 clustalw=2.1 netcdf4 scikit-learn joblib>=0.12 chemfiles tqdm>=4.43.0 tidynamics>=1.0.0 rdkit>=2020.03.1 h5py"
MDA_PIP_MIN_DEPS: 'coveralls coverage<5 pytest-cov pytest-xdist'
MDA_PIP_EXTRA_DEPS: 'duecredit parmed'
Expand Down
1 change: 1 addition & 0 deletions package/AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ Chronological list of authors
- Henry Kobin
- Kosuke Kudo
- Sulay Shah
- Alexander Yang

External code
-------------
Expand Down
3 changes: 2 additions & 1 deletion package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ The rules for this file:
calcraven,xiki-tempula, mieczyslaw, manuel.nuno.melo, PicoCentauri,
hanatok, rmeli, aditya-kamath, tirkarthi, LeonardoBarneschi, hejamu,
biogen98, orioncohen, z3y50n, hp115, ojeda-e, thadanipaarth, HenryKobin,
1ut, sulays, PicoCentauri
1ut, sulays, PicoCentauri, ahy3nz

* 2.0.0

Expand Down Expand Up @@ -214,6 +214,7 @@ Changes
``analysis.helix_analysis`` (PR #2929)
* Move water bridge analysis from hbonds to hydrogenbonds (Issue #2739 PR #2913)
* `threadpoolctl` is required for installation (Issue #2860)
* Added OpenMM coordinate and topology converters (Issue #2863, PR #2917)

Deprecations
* In 2.1.0 the TRZReader will default to a dt of 1.0 ps when failing to
Expand Down
198 changes: 198 additions & 0 deletions package/MDAnalysis/coordinates/OpenMM.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# -*- Mode: python; tab-width: 4; indent-tabs-mode:nil; coding:utf-8 -*-
# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
#
# MDAnalysis --- https://www.mdanalysis.org
# Copyright (c) 2006-2017 The MDAnalysis Development Team and contributors
# (see the file AUTHORS for the full list of names)
#
# Released under the GNU Public Licence, v2 or any higher version
#
# Please cite your use of MDAnalysis in published work:
#
# R. J. Gowers, M. Linke, J. Barnoud, T. J. E. Reddy, M. N. Melo, S. L. Seyler,
# D. L. Dotson, J. Domanski, S. Buchoux, I. M. Kenney, and O. Beckstein.
# MDAnalysis: A Python package for the rapid analysis of molecular dynamics
# simulations. In S. Benthall and S. Rostrup editors, Proceedings of the 15th
# Python in Science Conference, pages 102-109, Austin, TX, 2016. SciPy.
# doi: 10.25080/majora-629e541a-00e
#
# N. Michaud-Agrawal, E. J. Denning, T. B. Woolf, and O. Beckstein.
# MDAnalysis: A Toolkit for the Analysis of Molecular Dynamics Simulations.
# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
#

"""OpenMM structure I/O --- :mod:`MDAnalysis.coordinates.OpenMM`
================================================================
Read coordinates data from a
`OpenMM <http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.simulation.Simulation.html#simtk.openmm.app.simulation.Simulation>`_
:class:`simtk.openmm.app.simulation.Simulation` with :class:`OpenMMReader`
into a MDAnalysis Universe.
Also converts other objects within the
`OpenMM Application Layer <http://docs.openmm.org/latest/api-python/app.html>`_:
- `simtk.openmm.app.pdbfile.PDBFile <http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.pdbfile.PDBFile.html#simtk.openmm.app.pdbfile.PDBFile>`_
- `simtk.openmm.app.modeller.Modeller <http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.modeller.Modeller.html#simtk.openmm.app.modeller.Modeller>`_
- `simtk.openmm.app.pdbxfile.PDBxFile <http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.pdbxfile.PDBxFile.html#simtk.openmm.app.pdbxfile.PDBxFile>`_
Example
-------
OpenMM can read various file formats into OpenMM objects.
MDAnalysis can then convert some of these OpenMM objects into MDAnalysis Universe objects.
>>> import simtk.openmm.app as app
>>> import MDAnalysis as mda
>>> from MDAnalysis.tests.datafiles import PDBX
>>> pdbxfile = app.PDBxFile(PDBX)
>>> mda.Universe(pdbxfile)
<Universe with 60 atoms>
Classes
-------
.. autoclass:: OpenMMSimulationReader
:members:
.. autoclass:: OpenMMAppReader
:members:
"""


import numpy as np

from . import base
from .. import units


class OpenMMSimulationReader(base.SingleFrameReaderBase):
"""Reader for OpenMM Simulation objects
.. versionadded:: 2.0.0
"""

format = "OPENMMSIMULATION"
units = {"time": "ps", "length": "nm", "velocity": "nm/ps",
"force": "kJ/(mol*nm)", "energy": "kJ/mol"}

@staticmethod
def _format_hint(thing):
"""Can this reader read *thing*?
"""
try:
from simtk.openmm.app import Simulation
except ImportError:
return False
else:
return isinstance(thing, Simulation)

def _read_first_frame(self):
self.n_atoms = self.filename.topology.getNumAtoms()

self.ts = self._mda_timestep_from_omm_context()

if self.convert_units:
self.convert_pos_from_native(self.ts._pos)
self.ts.triclinic_dimensions = self.convert_pos_from_native(
self.ts.triclinic_dimensions, inplace=False
)
self.ts.dimensions[3:] = _sanitize_box_angles(self.ts.dimensions[3:])
self.convert_velocities_from_native(self.ts._velocities)
self.convert_forces_from_native(self.ts._forces)
self.convert_time_from_native(self.ts.dt)

def _mda_timestep_from_omm_context(self):
""" Construct Timestep object from OpenMM context """
import simtk.unit as u

state = self.filename.context.getState(-1, getVelocities=True,
getForces=True, getEnergy=True)

n_atoms = self.filename.context.getSystem().getNumParticles()

ts = self._Timestep(n_atoms, **self._ts_kwargs)
ts.frame = 0
ts.data["time"] = state.getTime()._value
ts.data["potential_energy"] = (
state.getPotentialEnergy().in_units_of(u.kilojoule/u.mole)
)
ts.data["kinetic_energy"] = (
state.getKineticEnergy().in_units_of(u.kilojoule/u.mole)
)
ts.triclinic_dimensions = state.getPeriodicBoxVectors(
asNumpy=True)._value
ts.dimensions[3:] = _sanitize_box_angles(ts.dimensions[3:])
ts.positions = state.getPositions(asNumpy=True)._value
ts.velocities = state.getVelocities(asNumpy=True)._value
ts.forces = state.getForces(asNumpy=True)._value

return ts


class OpenMMAppReader(base.SingleFrameReaderBase):
"""Reader for OpenMM Application layer objects
See also `the object definition in the OpenMM Application layer <http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.simulation.Simulation.html#simtk.openmm.app.simulation.Simulation>`_
.. versionadded:: 2.0.0
"""

format = "OPENMMAPP"
units = {"time": "ps", "length": "nm"}

@staticmethod
def _format_hint(thing):
"""Can this reader read *thing*?
"""
try:
from simtk.openmm import app
except ImportError:
return False
else:
return isinstance(thing, (app.PDBFile, app.Modeller,
app.PDBxFile))

def _read_first_frame(self):
self.n_atoms = self.filename.topology.getNumAtoms()

self.ts = self._mda_timestep_from_omm_app()

if self.convert_units:
self.convert_pos_from_native(self.ts._pos)
self.ts.triclinic_dimensions = self.convert_pos_from_native(
self.ts.triclinic_dimensions, inplace=False
)
self.ts.dimensions[3:] = _sanitize_box_angles(self.ts.dimensions[3:])

def _mda_timestep_from_omm_app(self):
""" Construct Timestep object from OpenMM Application object """

omm_object = self.filename
n_atoms = omm_object.topology.getNumAtoms()

ts = self._Timestep(n_atoms, **self._ts_kwargs)
ts.frame = 0
if omm_object.topology.getPeriodicBoxVectors() is not None:
ts.triclinic_dimensions = np.array(
omm_object.topology.getPeriodicBoxVectors()._value
)
ts.dimensions[3:] = _sanitize_box_angles(ts.dimensions[3:])
ts.positions = np.array(omm_object.getPositions()._value)

return ts


def _sanitize_box_angles(angles):
""" Ensure box angles correspond to first quadrant
See `discussion on unitcell angles <https://github.com/MDAnalysis/mdanalysis/pull/2917/files#r620558575>`_
"""
inverted = 180 - angles

return np.min(np.array([angles, inverted]), axis=0)
1 change: 1 addition & 0 deletions package/MDAnalysis/coordinates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ class can choose an appropriate reader automatically.
from . import INPCRD
from . import LAMMPS
from . import MOL2
from . import OpenMM
from . import ParmEd
from . import PDB
from . import PDBQT
Expand Down
Loading

0 comments on commit 7f2ce3d

Please sign in to comment.