Skip to content

Commit

Permalink
Merge pull request #120 from openmm/find-installed-openff
Browse files Browse the repository at this point in the history
Auto-detect installed SMIRNOFF force fields
  • Loading branch information
jchodera authored Jun 16, 2020
2 parents 32f8913 + de63cba commit 32a6dea
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 7 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Newly parameterized molecules will be written to the cache, saving time next tim
## Using the Open Force Field Initiative SMIRNOFF small molecule force fields

The `openmmforcefields` package includes a [residue template generator](http://docs.openmm.org/latest/userguide/application.html#adding-residue-template-generators) for [the OpenMM `ForceField` class](http://docs.openmm.org/latest/api-python/generated/simtk.openmm.app.forcefield.ForceField.html#simtk.openmm.app.forcefield.ForceField) that automatically generates OpenMM residue templates for small molecules lacking parameters using the [Open Force Field Initiative](http://openforcefield.org) [SMIRNOFF](https://open-forcefield-toolkit.readthedocs.io/en/0.6.0/smirnoff.html) small molecule force fields.
This includes the [`openff-1.0.0` ("Parsley")](https://openforcefield.org/news/introducing-openforcefield-1.0/) small molecule force field.
This includes the [`openff-1.0.0` ("Parsley")](https://openforcefield.org/news/introducing-openforcefield-1.0/) small molecule force field, as well as newer versions of this force field.

The `SMIRNOFFTemplateGenerator` residue template generator operates in a manner very similar to `GAFFTemplateGenerator`, so we only highlight its differences here.

Expand Down Expand Up @@ -269,7 +269,9 @@ See the corresponding directories for information on how to use the provided con

# Changelog

##
## 0.7.3 Bugfix release: Compatibility with openforcefield toolkit 0.7.0 and auto-detection of installed openforcefield force fields
* [(PR #119)](https://github.com/openmm/openmmforcefields/pull/119) Handle `None` partial charges in openforcefield `Molecule` objects (needed in `openforcefield` toolkit 0.7.0)
* [(PR #120)](https://github.com/openmm/openmmforcefields/pull/120) Auto-detect installed SMIRNOFF force fields

## 0.7.2 Bugfix release: More error checking; OpenMM 7.4.2 minimum version requirement

Expand Down
48 changes: 45 additions & 3 deletions openmmforcefields/generators/template_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,10 @@ def _check_for_errors(self, outputtext, other_errors=None, ignore_errors=None):
# Open Force Field Initiative SMIRNOFF specific OpenMM ForceField template generation utilities
################################################################################

class ClassProperty(property):
def __get__(self, cls, owner):
return self.fget.__get__(None, owner)()

class SMIRNOFFTemplateGenerator(SmallMoleculeTemplateGenerator):
"""
OpenMM ForceField residue template generator for Open Force Field Initiative SMIRNOFF
Expand Down Expand Up @@ -910,9 +914,6 @@ class SMIRNOFFTemplateGenerator(SmallMoleculeTemplateGenerator):
Newly parameterized molecules will be written to the cache, saving time next time!
"""
# TODO: Automatically populate this at import by examining plugin directories in order of semantic version
INSTALLED_FORCEFIELDS = ['smirnoff99Frosst-1.1.0', 'openff-1.0.0']

def __init__(self, molecules=None, cache=None, forcefield=None):
"""
Create a SMIRNOFFTemplateGenerator with some openforcefield toolkit molecules
Expand Down Expand Up @@ -1011,6 +1012,41 @@ def __init__(self, molecules=None, cache=None, forcefield=None):
# Cache a copy of the OpenMM System generated for each molecule for testing purposes
self._system_cache = dict()

@ClassProperty
@classmethod
def INSTALLED_FORCEFIELDS(cls):
"""Return a list of the offxml files shipped with the openforcefield package.
Returns
-------
file_names : str
The file names of available force fields
.. todo ::
Replace this with an API call once this issue is addressed:
https://github.com/openforcefield/openforcefield/issues/477
"""
# TODO: Replace this method once there is a public API in the openforcefield toolkit for doing this
# TODO: Impose some sort of ordering by preference?

from openforcefield.utils import get_data_file_path
from openforcefield.typing.engines.smirnoff.forcefield import _get_installed_offxml_dir_paths
from glob import glob

file_names = list()
for dir_path in _get_installed_offxml_dir_paths():
file_pattern = os.path.join(dir_path, '*.offxml')
file_paths = [file_path for file_path in glob(file_pattern)]
for file_path in file_paths:
basename = os.path.basename(file_path)
root, ext = os.path.splitext(basename)
# Only add variants without '_unconstrained'
if '_unconstrained' not in root:
file_names.append(root)
return file_names

def _search_paths(self, filename):
"""Search registered openforcefield plugin directories
Expand All @@ -1023,6 +1059,12 @@ def _search_paths(self, filename):
-------
fullpath : str
Full path to identified file, or None if no file found
.. todo ::
Replace this with an API call once this issue is addressed:
https://github.com/openforcefield/openforcefield/issues/477
"""
# TODO: Replace this method once there is a public API in the openforcefield toolkit for doing this

Expand Down
10 changes: 8 additions & 2 deletions openmmforcefields/tests/test_template_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,12 @@ def propagate_dynamics(self, molecule, system):

return new_molecule

def test_INSTALLED_FORCEFIELDS(self):
"""Test INSTALLED_FORCEFIELDS contains expected force fields"""
assert 'openff-1.1.0' in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS
assert 'smirnoff99Frosst-1.1.0' in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS
assert 'openff_unconstrained-1.1.0' not in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS

def test_energies(self):
"""Test potential energies match between openforcefield and OpenMM ForceField"""
# DEBUG
Expand Down Expand Up @@ -742,7 +748,7 @@ def test_energies(self):


def test_partial_charges_are_none(self):
"""Test parameterizing a small molecule with `partial_charges=None` instead
"""Test parameterizing a small molecule with `partial_charges=None` instead
of zeros (happens frequently in OFFTK>=0.7.0)"""
from openforcefield.topology import Molecule
molecule = Molecule.from_smiles('C=O')
Expand All @@ -763,7 +769,7 @@ def test_partial_charges_are_none(self):
from simtk.openmm.app import NoCutoff
openmm_system = openmm_forcefield.createSystem(molecule.to_topology().to_openmm(), removeCMMotion=False, onbondedMethod=NoCutoff)
smirnoff_system = generator.get_openmm_system(molecule)

def test_version(self):
"""Test version"""
for forcefield in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS:
Expand Down

0 comments on commit 32a6dea

Please sign in to comment.