Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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