From 39dbb6025aafa9a3ad01fe4c583dc5eea9ea085d Mon Sep 17 00:00:00 2001 From: Josh Horton Date: Tue, 11 Jul 2023 18:07:23 +0100 Subject: [PATCH 1/3] allow parsing of offxml strings --- .../generators/template_generators.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/openmmforcefields/generators/template_generators.py b/openmmforcefields/generators/template_generators.py index d3bc641a..a4d21082 100644 --- a/openmmforcefields/generators/template_generators.py +++ b/openmmforcefields/generators/template_generators.py @@ -1275,14 +1275,19 @@ def __init__(self, molecules=None, cache=None, forcefield=None): # Create ForceField object import openff.toolkit.typing.engines.smirnoff - try: - filename = forcefield - if not filename.endswith('.offxml'): - filename += '.offxml' + + # check for an installed force field + available_force_fields = openff.toolkit.typing.engines.smirnoff.get_available_force_fields() + if (filename := forcefield + ".offxml") in available_force_fields or (filename := forcefield) in available_force_fields: self._smirnoff_forcefield = openff.toolkit.typing.engines.smirnoff.ForceField(filename) - except Exception as e: - _logger.error(e) - raise ValueError(f"Can't find specified SMIRNOFF force field ({forcefield}) in install paths") + + # just try parsing the input and let openff handle the error + else: + try: + self._smirnoff_forcefield = openff.toolkit.typing.engines.smirnoff.ForceField(forcefield) + except Exception as e: + _logger.error(e) + raise ValueError(f"Can't find specified SMIRNOFF force field ({forcefield}) in install paths or parse the input") # Delete constraints, if present if 'Constraints' in self._smirnoff_forcefield._parameter_handlers: From 9e23767ff24a271f155a11b714420e5f522bdc59 Mon Sep 17 00:00:00 2001 From: Josh Horton Date: Fri, 14 Jul 2023 11:37:34 +0100 Subject: [PATCH 2/3] add bespoke smirnoff force field test --- .../tests/test_template_generators.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openmmforcefields/tests/test_template_generators.py b/openmmforcefields/tests/test_template_generators.py index 07d130d3..7094e35c 100644 --- a/openmmforcefields/tests/test_template_generators.py +++ b/openmmforcefields/tests/test_template_generators.py @@ -1,12 +1,15 @@ import copy import logging import os +import pytest import tempfile import unittest import numpy as np import openmm from openff.toolkit.topology import Molecule +from openff.toolkit.typing.engines.smirnoff import ForceField as OFFForceField +from openff.units import unit as OFFUnit from openmm.app import PME, ForceField, Modeller, NoCutoff, PDBFile from openmmforcefields.generators import ( @@ -837,6 +840,40 @@ def test_version(self): assert generator.smirnoff_filename.endswith(forcefield + '.offxml') assert os.path.exists(generator.smirnoff_filename) + def test_bespoke_force_field(self): + """ + Make sure a molecule can be parameterised using a bespoke force field passed as a string to + the template generator. + """ + + custom_sage = OFFForceField("openff-2.0.0.offxml") + # Create a simple molecule with one bond type + ethane = Molecule.from_smiles("C") + # Label ethane to get the bond type (not hard coded incase this changes in future) + bond_parameter = custom_sage.label_molecules(ethane.to_topology())[0]["Bonds"][(0, 1)] + # Edit the bond parameter + bonds = custom_sage.get_parameter_handler("Bonds") + new_parameter = bonds[bond_parameter.smirks] + new_parameter.length = 2 * OFFUnit.angstrom + + # Use the custom sage passed as string to build a template and an openmm system + generator = SMIRNOFFTemplateGenerator(molecules=ethane, forcefield=custom_sage.to_string()) + + # Create a ForceField + openmm_forcefield = openmm.app.ForceField() + # Register the template generator + openmm_forcefield.registerTemplateGenerator(generator.generator) + # Use OpenMM app to generate the system + openmm_system = openmm_forcefield.createSystem(ethane.to_topology().to_openmm(), removeCMMotion=False, + nonbondedMethod=NoCutoff) + + # Check the bond length has been updated + forces = {force.__class__.__name__: force for force in openmm_system.getForces()} + bond_force = forces["HarmonicBondForce"] + for i in range(bond_force.getNumBonds()): + _, _, length, _ = bond_force.getBondParameters(i) + assert pytest.approx(length.value_in_unit(openmm.unit.angstrom)) == 2 + class TestEspalomaTemplateGenerator(TestGAFFTemplateGenerator): TEMPLATE_GENERATOR = EspalomaTemplateGenerator From 2a656de6b1d93205d49da7360dd99af8b86c8da5 Mon Sep 17 00:00:00 2001 From: Josh Horton Date: Thu, 27 Jul 2023 09:55:16 +0100 Subject: [PATCH 3/3] update error --- openmmforcefields/generators/template_generators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmmforcefields/generators/template_generators.py b/openmmforcefields/generators/template_generators.py index 3d2e5b0d..cd202447 100644 --- a/openmmforcefields/generators/template_generators.py +++ b/openmmforcefields/generators/template_generators.py @@ -1301,7 +1301,7 @@ def __init__(self, molecules=None, cache=None, forcefield=None): self._smirnoff_forcefield = openff.toolkit.typing.engines.smirnoff.ForceField(forcefield) except Exception as e: _logger.error(e) - raise ValueError(f"Can't find specified SMIRNOFF force field ({forcefield}) in install paths") from e + raise ValueError(f"Can't find specified SMIRNOFF force field ({forcefield}) in install paths or parse the input as a string.") from e # Delete constraints, if present if 'Constraints' in self._smirnoff_forcefield._parameter_handlers: