Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
os: [ubuntu-latest, macos-latest]
python-version: ["3.11", "3.12", "3.13"]
amber-conversion: [false]
openmm-version: ["8.5.0"]
openmm-version: ["8.5.1"]

steps:
- uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test_charmm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
matrix:
os: [ubuntu-latest]
python-version: ["3.13"]
openmm-version: ["8.5.0"]
openmm-version: ["8.5.1"]

steps:
- uses: actions/checkout@v6
Expand Down
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ from openmmforcefields.generators import (
GAFFTemplateGenerator,
)

gaff = GAFFTemplateGenerator(molecules=molecule)
gaff = GAFFTemplateGenerator(molecules=molecule, forcefield="gaff-2.2.20")
# Create an OpenMM ForceField object with AMBER ff14SB and TIP3P with compatible ions
from openmm.app import ForceField

Expand All @@ -141,15 +141,7 @@ pdbfile = PDBFile("t4-lysozyme-L99A-with-benzene.pdb")
system = forcefield.createSystem(pdbfile.topology)
```

The latest available GAFF version is used if none is specified.
You can check which GAFF version is in use with

```pycon
>>> gaff.gaff_version
'2.2.20'
````

Create a template generator for a specific GAFF version for multiple molecules read from an SDF file:
Create a template generator for multiple molecules read from an SDF file:

```python
molecules = Molecule.from_file("molecules.sdf")
Expand Down
10 changes: 5 additions & 5 deletions openmmforcefields/generators/system_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SystemGenerator:
def __init__(
self,
forcefields=None,
small_molecule_forcefield="openff-2.2.0",
small_molecule_forcefield=None,
forcefield_kwargs=None,
nonperiodic_forcefield_kwargs=None,
periodic_forcefield_kwargs=None,
Expand All @@ -58,7 +58,7 @@ def __init__(
forcefields : list of str, optional, default=None
List of the names of ffxml force field files that will be used in
System creation to attempt to parameterize molecules.
small_molecule_forcefield : str, bytes, file-like object, or iterable, optional, default='openff-2.2.0'
small_molecule_forcefield : str, bytes, file-like object, or iterable, optional
Specification of the force field(s) to use for the template
generator that will attempt to parameterize any molecules that could
not be parameterized by the provided ffxml force fields. Whatever
Expand Down Expand Up @@ -218,7 +218,7 @@ def __init__(
template_generator_kwargs=self.template_generator_kwargs,
)
break
except (ValueError,) as e:
except (ValueError, TypeError) as e:
_logger.debug(f" {template_generator_cls.__name__} cannot load {small_molecule_forcefield}")
_logger.debug(e)
if self.template_generator is None:
Expand All @@ -232,8 +232,8 @@ def __init__(
raise ValueError(msg)
self.forcefield.registerTemplateGenerator(self.template_generator.generator)

# Inform the template generator about any specified molecules
self.add_molecules(molecules)
# Inform the template generator about any specified molecules
self.add_molecules(molecules)

@classproperty
def SMALL_MOLECULE_FORCEFIELDS(cls):
Expand Down
42 changes: 14 additions & 28 deletions openmmforcefields/generators/template_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ class GAFFTemplateGenerator(SmallMoleculeTemplateGenerator):
>>> molecule = Molecule.from_smiles('c1ccccc1')
>>> # Create the GAFF template generator
>>> from openmmforcefields.generators import GAFFTemplateGenerator
>>> template_generator = GAFFTemplateGenerator(molecules=molecule)
>>> template_generator = GAFFTemplateGenerator(molecules=molecule, forcefield='gaff-2.2.20')
>>> # Create an OpenMM ForceField
>>> from openmm.app import ForceField
>>> amber_forcefields = ['amber/protein.ff14SB.xml', 'amber/tip3p_standard.xml', 'amber/tip3p_HFE_multivalent.xml']
Expand All @@ -375,7 +375,7 @@ class GAFFTemplateGenerator(SmallMoleculeTemplateGenerator):

You can optionally create or use a tiny database cache of pre-parameterized molecules:

>>> template_generator = GAFFTemplateGenerator(cache='gaff-molecules.json')
>>> template_generator = GAFFTemplateGenerator(cache='gaff-molecules.json', forcefield='gaff-2.2.20')

Newly parameterized molecules will be written to the cache, saving time next time!

Expand Down Expand Up @@ -406,10 +406,9 @@ def __init__(self, molecules=None, forcefield=None, cache=None, template_generat
Can be a Molecule or a list of Molecule objects.
If specified, these molecules will be recognized and parameterized with antechamber as needed.
The parameters will be cached in case they are encountered again the future.
forcefield : str, optional, default=None
forcefield : str
GAFF force field to use, one of
['gaff-1.4', 'gaff-1.8', 'gaff-1.81', 'gaff-2.1', 'gaff-2.11', 'gaff-2.2.20']
If not specified, the latest GAFF supported version is used.
GAFFTemplateGenerator.INSTALLED_FORCEFIELDS contains a complete up-to-date list of supported force fields.
cache : str, optional, default=None
Filename for global caching of parameters.
Expand All @@ -425,7 +424,7 @@ def __init__(self, molecules=None, forcefield=None, cache=None, template_generat
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('c1ccccc1')
>>> from openmmforcefields.generators import GAFFTemplateGenerator
>>> gaff = GAFFTemplateGenerator(molecules=molecule)
>>> gaff = GAFFTemplateGenerator(molecules=molecule, forcefield='gaff-2.2.20')
>>> from openmm.app import ForceField
>>> amber_forcefields = [
... 'amber/protein.ff14SB.xml',
Expand All @@ -435,13 +434,7 @@ def __init__(self, molecules=None, forcefield=None, cache=None, template_generat
>>> forcefield = ForceField(*amber_forcefields)
>>> forcefield.registerTemplateGenerator(gaff)

The latest GAFF version is used if none is specified.
You can check which GAFF version is in use with

>>> gaff.forcefield
'gaff-2.2.20'

Create a template generator for a specific GAFF version for multiple molecules read from an SDF file:
Create a template generator for multiple molecules read from an SDF file:

>>> molecules = Molecule.from_file('molecules.sdf') # doctest: +SKIP
>>> gaff = GAFFTemplateGenerator(molecules=molecules, forcefield='gaff-2.2.20') # doctest: +SKIP
Expand Down Expand Up @@ -469,11 +462,9 @@ def __init__(self, molecules=None, forcefield=None, cache=None, template_generat
# Initialize molecules and cache
super().__init__(molecules=molecules, cache=cache)

# Use latest supported GAFF version if none is specified
if forcefield is None:
forcefield = self.INSTALLED_FORCEFIELDS[-1]

# Ensure a valid GAFF version is specified
if not isinstance(forcefield, str):
raise TypeError("A GAFF force field name must be provided as a string")
if forcefield not in self.INSTALLED_FORCEFIELDS:
raise ValueError(f"Specified 'forcefield' ({forcefield}) must be one of {self.INSTALLED_FORCEFIELDS}")

Expand Down Expand Up @@ -1567,24 +1558,22 @@ class EspalomaTemplateGenerator(SmallMoleculeTemplateGenerator, OpenMMSystemMixi
Examples
--------

Create a template generator for a single Molecule using the latest Open Force Field Initiative
small molecule force field and register it with ForceField:
Create a template generator for a single Molecule and register it with ForceField:

>>> # Define a Molecule using the OpenFF Molecule object
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('c1ccccc1')
>>> # Create the Espaloma template generator
>>> from openmmforcefields.generators import EspalomaTemplateGenerator
>>> template_generator = EspalomaTemplateGenerator(molecules=molecule)
>>> template_generator = EspalomaTemplateGenerator(molecules=molecule, forcefield='espaloma-0.3.2')
>>> # Create an OpenMM ForceField
>>> from openmm.app import ForceField
>>> amber_forcefields = ['amber/protein.ff14SB.xml', 'amber/tip3p_standard.xml', 'amber/tip3p_HFE_multivalent.xml']
>>> forcefield = ForceField(*amber_forcefields)
>>> # Register the template generator
>>> forcefield.registerTemplateGenerator(template_generator.generator)

Create a template generator for a specific Espaloma release ('espaloma-0.3.2')
and register multiple molecules:
Create a template generator and register multiple molecules:

>>> molecule1 = Molecule.from_smiles('c1ccccc1')
>>> molecule2 = Molecule.from_smiles('CCO')
Expand All @@ -1607,7 +1596,7 @@ class EspalomaTemplateGenerator(SmallMoleculeTemplateGenerator, OpenMMSystemMixi

You can optionally create or use a tiny database cache of pre-parameterized molecules:

>>> template_generator = EspalomaTemplateGenerator(cache='espaloma-molecules.json')
>>> template_generator = EspalomaTemplateGenerator(cache='espaloma-molecules.json', forcefield='espaloma-0.3.2')

Newly parameterized molecules will be written to the cache, saving time next time!
"""
Expand Down Expand Up @@ -1663,7 +1652,7 @@ def __init__(
>>> from openff.toolkit import Molecule
>>> molecule = Molecule.from_smiles('c1ccccc1')
>>> from openmmforcefields.generators import EspalomaTemplateGenerator
>>> espaloma_generator = EspalomaTemplateGenerator(molecules=molecule)
>>> espaloma_generator = EspalomaTemplateGenerator(molecules=molecule, forcefield='espaloma-0.3.2')
>>> from openmm.app import ForceField
>>> amber_forcefields = ['amber/protein.ff14SB.xml', 'amber/tip3p_standard.xml', 'amber/tip3p_HFE_multivalent.xml']
>>> forcefield = ForceField(*amber_forcefields)
Expand Down Expand Up @@ -1710,11 +1699,8 @@ def __init__(
else:
self.ESPALOMA_MODEL_CACHE_PATH = model_cache_path

if forcefield is None:
# Use latest supported Espaloma force field release if none is specified
forcefield = "espaloma-0.3.2"
# TODO: After toolkit provides date-ranked force fields,
# use latest dated version if we can sort by date, such as self.INSTALLED_FORCEFIELDS[-1]
if not isinstance(forcefield, str):
raise TypeError("An Espaloma force field name must be provided as a string")
self._forcefield = forcefield

# Check that espaloma model parameters can be found or locally cached
Expand Down
9 changes: 7 additions & 2 deletions openmmforcefields/tests/test_system_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def test_create(self):
def test_barostat(self, barostat_class, args):
"""Test that different barostats are correctly applied to the system"""
# Create a protein SystemGenerator
generator = SystemGenerator(forcefields=self.amber_forcefields)
generator = SystemGenerator(forcefields=self.amber_forcefields, small_molecule_forcefield="openff-2.3.0")

# Create the barostat
generator.barostat = barostat_class(*args)
Expand Down Expand Up @@ -525,7 +525,12 @@ def test_complex(self, test_systems):
)

# Create a system in vacuum
generator = SystemGenerator(forcefields=self.amber_forcefields, molecules=molecules, cache=cache)
generator = SystemGenerator(
forcefields=self.amber_forcefields,
small_molecule_forcefield="openff-2.3.0",
molecules=molecules,
cache=cache,
)
system = generator.create_system(openmm_topology)
assert system.getNumParticles() == len(complex_structure.atoms)

Expand Down
34 changes: 24 additions & 10 deletions openmmforcefields/tests/test_template_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,13 @@ class TestGAFFTemplateGenerator(TemplateGeneratorBaseCase):
"amber/tip3p_HFE_multivalent.xml",
]

def _make_template_generator(self, **kwargs):
"""
Makes a `GAFFTemplateGenerator` for generic testing.
"""

return GAFFTemplateGenerator(forcefield="gaff-2.2.20", **kwargs)

def test_version(self):
"""Test version"""
for forcefield in GAFFTemplateGenerator.INSTALLED_FORCEFIELDS:
Expand All @@ -658,23 +665,23 @@ def test_version(self):
def test_create(self):
"""Test template generator creation"""
# Create an empty generator
generator = self.TEMPLATE_GENERATOR()
generator = self._make_template_generator()
# Create a generator that knows about a few molecules
generator = self.TEMPLATE_GENERATOR(molecules=self.molecules)
generator = self._make_template_generator(molecules=self.molecules)
# Create a generator that also has a database cache
with tempfile.TemporaryDirectory() as tmpdirname:
cache = os.path.join(tmpdirname, "db.json")
# Create a new database file
generator = self.TEMPLATE_GENERATOR(molecules=self.molecules, cache=cache)
generator = self._make_template_generator(molecules=self.molecules, cache=cache)
del generator
# Reopen it (with cache still empty)
generator = self.TEMPLATE_GENERATOR(molecules=self.molecules, cache=cache)
generator = self._make_template_generator(molecules=self.molecules, cache=cache)
del generator

def test_add_molecules(self):
"""Test that molecules can be added to template generator after its creation"""
# Create a generator that does not know about any molecules
generator = self.TEMPLATE_GENERATOR()
generator = self._make_template_generator()
# Create a ForceField
forcefield = ForceField()
# Register the template generator
Expand Down Expand Up @@ -794,7 +801,7 @@ def test_debug_ffxml(self):
cache = os.path.join(tmpdirname, "db.json")
# Create a generator that only knows about one molecule
molecule = self.molecules[0]
generator = self.TEMPLATE_GENERATOR(molecules=molecule, cache=cache)
generator = self._make_template_generator(molecules=molecule, cache=cache)
# Create a ForceField
forcefield = ForceField()
# Register the template generator
Expand Down Expand Up @@ -838,7 +845,7 @@ def test_add_solvent(self):
pass

# Create a generator that knows about a few molecules
generator = self.TEMPLATE_GENERATOR(molecules=self.molecules)
generator = self._make_template_generator(molecules=self.molecules)
# Add to the forcefield object
forcefield.registerTemplateGenerator(generator.generator)
# Add solvent to a system containing a small molecule
Expand Down Expand Up @@ -882,7 +889,7 @@ def test_jacs_ligands(self):
get_data_filename(os.path.join("perses_jacs_systems", system_name)),
"cache.json",
)
generator = self.TEMPLATE_GENERATOR(molecules=molecules, cache=cache)
generator = self._make_template_generator(molecules=molecules, cache=cache)

# Create a ForceField
forcefield = ForceField()
Expand Down Expand Up @@ -962,7 +969,7 @@ def test_jacs_complexes(self):
get_data_filename(os.path.join("perses_jacs_systems", system_name)),
"cache.json",
)
generator = self.TEMPLATE_GENERATOR(molecules=molecules, cache=cache)
generator = self._make_template_generator(molecules=molecules, cache=cache)

# Create a ForceField
forcefield = ForceField(*self.amber_forcefields)
Expand Down Expand Up @@ -1040,7 +1047,7 @@ def test_parameterize(self):

def test_multiple_registration(self):
"""Test registering the template generator with multiple force fields"""
generator = self.TEMPLATE_GENERATOR(molecules=self.molecules)
generator = self._make_template_generator(molecules=self.molecules)
NUM_FORCEFIELDS = 2 # number of force fields to test
forcefields = list()
for index in range(NUM_FORCEFIELDS):
Expand Down Expand Up @@ -1810,6 +1817,13 @@ def test_charge_warning(self):
class TestEspalomaTemplateGenerator(TemplateGeneratorBaseCase):
TEMPLATE_GENERATOR = EspalomaTemplateGenerator

def _make_template_generator(self, **kwargs):
"""
Makes an `EspalomaTemplateGenerator` for generic testing.
"""

return EspalomaTemplateGenerator(forcefield="espaloma-0.3.2", **kwargs)

def test_retrieve_forcefields(self):
"""Test a force field can be retrieved"""
# Test loading model by specifying version number
Expand Down
Loading