diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 331b79ac..3ae7ec9d 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -20,7 +20,7 @@ concurrency:
jobs:
test:
- name: Test on ${{ matrix.os }}, Python ${{ matrix.python-version }}, Latest openff-toolkit ${{ matrix.latest-openff-toolkit }}
+ name: Test on ${{ matrix.os }}, Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
env:
OE_LICENSE: ${{ github.workspace }}/oe_license.txt
@@ -28,8 +28,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
- python-version: ["3.8", "3.9", "3.10"]
- latest-openff-toolkit: [true, false]
+ python-version: ["3.9", "3.10"] # Add 3.11 in with AmberTools 23
exclude:
- python-version: "3.10"
os: macos-latest
@@ -38,10 +37,10 @@ jobs:
- uses: actions/checkout@v3
- name: Setup Conda Environment
- uses: mamba-org/provision-with-micromamba@main
+ uses: mamba-org/setup-micromamba@v1
with:
environment-file: devtools/conda-envs/test_env.yaml
- extra-specs: |
+ create-args: >-
python=${{ matrix.python-version }}
- name: License OpenEye
@@ -52,16 +51,6 @@ jobs:
env:
SECRET_OE_LICENSE: ${{ secrets.OE_LICENSE }}
- - name: "Install openff-toolkit >= 0.11 API changes"
- if: ${{ matrix.latest-openff-toolkit == true }}
- run: |
- micromamba update -y -c conda-forge "openff-toolkit >=0.11.3"
-
- - name: "Install openff-toolkit < 0.11 API changes"
- if: ${{ matrix.latest-openff-toolkit == false }}
- run: |
- micromamba install -y -c conda-forge "openff-toolkit==0.10.6" "openff-toolkit-base==0.10.6"
-
- name: Install Package
run: |
pip list
@@ -77,10 +66,11 @@ jobs:
- name: Test Installed Package
run: |
- pytest -v -x --log-cli-level $LOGLEVEL $COV_ARGS --durations=20 \
+ pytest -v --log-cli-level $LOGLEVEL $COV_ARGS --durations=20 \
openmmforcefields/tests/test_amber_import.py \
openmmforcefields/tests/test_template_generators.py \
- openmmforcefields/tests/test_system_generator.py
+ openmmforcefields/tests/test_system_generator.py \
+ -k "not TestEspalomaTemplateGenerator"
env:
COV_ARGS: --cov=openmmforcefields --cov-config=setup.cfg --cov-append --cov-report=xml
LOGLEVEL: "INFO"
@@ -106,7 +96,6 @@ jobs:
working-directory: ./charmm
- name: Run docstrings
- if: ${{ matrix.latest-openff-toolkit == true }}
continue-on-error: True
run: |
pytest --doctest-modules openmmforcefields --ignore=openmmforcefields/tests
diff --git a/.github/workflows/espaloma_ci.yaml b/.github/workflows/espaloma_ci.yaml
new file mode 100644
index 00000000..394dffd3
--- /dev/null
+++ b/.github/workflows/espaloma_ci.yaml
@@ -0,0 +1,66 @@
+name: EspalomaCI
+
+on:
+ push:
+ branches:
+ - "main"
+ pull_request:
+ branches:
+ - "main"
+ schedule:
+ - cron: "0 0 * * *"
+
+defaults:
+ run:
+ shell: bash -l {0}
+
+concurrency:
+ group: "${{ github.workflow }}-${{ github.ref }}"
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: Test on ${{ matrix.os }}, Python ${{ matrix.python-version }}, Latest openff-toolkit ${{ matrix.latest-openff-toolkit }}
+ runs-on: ${{ matrix.os }}
+ env:
+ OE_LICENSE: ${{ github.workspace }}/oe_license.txt
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ python-version: ["3.9", "3.10"] # Add 3.11 in with AmberTools 23
+ exclude:
+ - python-version: "3.10"
+ os: macos-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup Conda Environment
+ uses: mamba-org/setup-micromamba@v1
+ with:
+ environment-file: devtools/conda-envs/test_env.yaml
+ create-args: >-
+ python=${{ matrix.python-version }}
+
+ - name: Install Package
+ run: |
+ pip list
+ micromamba list
+ micromamba remove --force openmmforcefields
+ python -m pip install .
+
+ - name: Conda Environment Information
+ run: |
+ micromamba info
+ micromamba list
+ python -c "from openmmforcefields import __version__, __file__; print(__version__, __file__)"
+
+ - name: Test Installed Package
+ run: |
+ pytest -v --log-cli-level $LOGLEVEL $COV_ARGS --durations=20 \
+ -m "espaloma" openmmforcefields/tests --runespaloma
+ env:
+ COV_ARGS: --cov=openmmforcefields --cov-config=setup.cfg --cov-append --cov-report=xml
+ LOGLEVEL: "INFO"
+ KMP_DUPLICATE_LIB_OK: "True"
diff --git a/devtools/conda-envs/test_env.yaml b/devtools/conda-envs/test_env.yaml
index 040ad212..adb60eb2 100644
--- a/devtools/conda-envs/test_env.yaml
+++ b/devtools/conda-envs/test_env.yaml
@@ -14,17 +14,17 @@ dependencies:
# Testing
- pytest
- pytest-cov
- - codecov
+ - pytest-xdist
+ - pytest-randomly
# Requirements for converted force field installer
- openmm >=7.6.0
- - openff-units >=0.1.8
- - openff-amber-ff-ports >=0.0.3
+ - openff-toolkit >=0.11
# Requirements for conversion tools
- pyyaml
- - ambertools >=18.0 # contains sufficiently recent ParmEd
+ - ambertools =22
- lxml
- networkx
@@ -41,6 +41,6 @@ dependencies:
# TODO: Rework this once espaloma is on conda-forge
#
- pytorch >=1.8.0
- - dgl
+ - dgl <1
- qcportal >=0.15.0
- espaloma
diff --git a/openmmforcefields/ffxml/amber/opc3.xml b/openmmforcefields/ffxml/amber/opc3.xml
index 6be08369..f21f3e89 100644
--- a/openmmforcefields/ffxml/amber/opc3.xml
+++ b/openmmforcefields/ffxml/amber/opc3.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/openmmforcefields/generators/template_generators.py b/openmmforcefields/generators/template_generators.py
index cd1d8db5..3f5d2755 100644
--- a/openmmforcefields/generators/template_generators.py
+++ b/openmmforcefields/generators/template_generators.py
@@ -1280,7 +1280,7 @@ def __init__(self, molecules=None, cache=None, forcefield=None, **kwargs):
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")
+ raise ValueError(f"Can't find specified SMIRNOFF force field ({forcefield}) in install paths") from e
# Delete constraints, if present
if 'Constraints' in self._smirnoff_forcefield._parameter_handlers:
diff --git a/openmmforcefields/tests/conftest.py b/openmmforcefields/tests/conftest.py
new file mode 100644
index 00000000..cbbb0949
--- /dev/null
+++ b/openmmforcefields/tests/conftest.py
@@ -0,0 +1,21 @@
+"""Default configuration and objects for tests"""
+
+import pytest
+
+def pytest_addoption(parser):
+ parser.addoption(
+ "--runespaloma", action="store_true", default=False, help="run espaloma tests"
+ )
+
+
+def pytest_configure(config):
+ config.addinivalue_line("markers", "espaloma: mark test as slow to run")
+
+
+def pytest_collection_modifyitems(config, items):
+ skip_slow = pytest.mark.skip(reason="need --runespaloma option to run")
+
+ if not config.getoption("--runespaloma"):
+ for item in items:
+ if "espaloma" in item.keywords:
+ item.add_marker(skip_slow)
\ No newline at end of file
diff --git a/openmmforcefields/tests/test_system_generator.py b/openmmforcefields/tests/test_system_generator.py
index 7e24e40a..c00116ed 100644
--- a/openmmforcefields/tests/test_system_generator.py
+++ b/openmmforcefields/tests/test_system_generator.py
@@ -18,7 +18,90 @@
# Tests
################################################################################
-class TestSystemGenerator(unittest.TestCase):
+@pytest.fixture(scope="class", autouse=True)
+
+
+def test_systems():
+ testsystems = dict()
+ for (system_name, prefix) in [
+ # TODO: Uncomment these after we fix input files
+ ('bace', 'Bace'),
+ # ('cdk1', 'CDK2'),
+ # ('jnk1', 'Jnk1'),
+ # ('mcl1', 'MCL1'),
+ # ('p38', 'p38'),
+ # ('ptp1b', 'PTP1B'),
+ # ('thrombin', 'Thrombin'),
+ # ('tyk2', 'Tyk2'),
+ ]:
+ # Load protein
+ pdb_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_protein.pdb'))
+ pdbfile = PDBFile(pdb_filename)
+
+ # Load molecules
+ sdf_filename = get_data_filename(
+ os.path.join('perses_jacs_systems', system_name, prefix + '_ligands_shifted.sdf'))
+
+ molecules = Molecule.from_file(sdf_filename, allow_undefined_stereo=True)
+ print(f'Read {len(molecules)} molecules from {sdf_filename}')
+ n_molecules = len(molecules)
+
+ # Limit number of molecules for testing
+ MAX_MOLECULES = 10 if not CI else 2
+ if (n_molecules > MAX_MOLECULES):
+ print(f'Limiting to {MAX_MOLECULES} for testing...')
+ n_molecules = MAX_MOLECULES
+ molecules = [molecules[index] for index in range(n_molecules)]
+
+ # Create structures
+ import parmed
+
+ # NOTE: This does not work because parmed does not correctly assign bonds for HID
+ # protein_structure = parmed.load_file(pdb_filename)
+ # NOTE: This is the workaround
+ protein_structure = parmed.openmm.load_topology(pdbfile.topology, xyz=pdbfile.positions)
+
+ molecules_structure = parmed.load_file(sdf_filename)
+ molecules_structure = [molecules_structure[index] for index in range(n_molecules)]
+
+ complex_structures = [(molecules_structure[index] + protein_structure) for index in range(n_molecules)]
+ complex_structures = [molecules_structure[index] for index in range(n_molecules)] # DEBUG
+
+ # Store
+ testsystem = {
+ 'name': system_name,
+ 'protein_pdbfile': pdbfile,
+ 'molecules': molecules,
+ 'complex_structures': complex_structures
+ }
+ testsystems[system_name] = testsystem
+
+ # DEBUG
+ for name, testsystem in testsystems.items():
+ filename = f'testsystem-{name}.pdb'
+ print(filename)
+ structure = testsystem['complex_structures'][0]
+ # structure.save(filename, overwrite=True)
+ with open(filename, 'w') as outfile:
+ PDBFile.writeFile(structure.topology, structure.positions, outfile)
+ testsystem['molecules'][0].to_file(f'testsystem-{name}-molecule.sdf', file_format="SDF")
+ testsystem['molecules'][0].to_file(f'testsystem-{name}-molecule.pdb', file_format="PDB")
+
+ # TODO: Create other test topologies
+ # TODO: Protein-only
+ # TODO: Protein-ligand topology
+ # TODO: Solvated protein-ligand topology
+ # TODO: Host-guest topology
+ # Suppress DEBUG logging from various packages
+
+ import logging
+ for name in ['parmed', 'matplotlib']:
+ logging.getLogger(name).setLevel(logging.WARNING)
+
+ return testsystems
+
+
+class TestSystemGenerator(object):
# AMBER force field combination to test
amber_forcefields = ['amber/protein.ff14SB.xml', 'amber/tip3p_standard.xml', 'amber/tip3p_HFE_multivalent.xml']
@@ -47,83 +130,6 @@ def filter_molecules(self, molecules):
return molecules
- """Base class for SystemGenerator tests."""
- def setUp(self):
- self.testsystems = dict()
- for (system_name, prefix) in [
- # TODO: Uncomment these after we fix input files
- ('bace', 'Bace'),
- #('cdk1', 'CDK2'),
- #('jnk1', 'Jnk1'),
- #('mcl1', 'MCL1'),
- #('p38', 'p38'),
- #('ptp1b', 'PTP1B'),
- #('thrombin', 'Thrombin'),
- #('tyk2', 'Tyk2'),
- ]:
- # Load protein
- pdb_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_protein.pdb'))
- pdbfile = PDBFile(pdb_filename)
-
- # Load molecules
- sdf_filename = get_data_filename(os.path.join('perses_jacs_systems', system_name, prefix + '_ligands_shifted.sdf'))
-
- molecules = Molecule.from_file(sdf_filename, allow_undefined_stereo=True)
- print(f'Read {len(molecules)} molecules from {sdf_filename}')
- n_molecules = len(molecules)
-
- # Limit number of molecules for testing
- MAX_MOLECULES = 10 if not CI else 2
- if (n_molecules > MAX_MOLECULES):
- print(f'Limiting to {MAX_MOLECULES} for testing...')
- n_molecules = MAX_MOLECULES
- molecules = [ molecules[index] for index in range(n_molecules) ]
-
- # Create structures
- import parmed
-
- # NOTE: This does not work because parmed does not correctly assign bonds for HID
- #protein_structure = parmed.load_file(pdb_filename)
- # NOTE: This is the workaround
- protein_structure = parmed.openmm.load_topology(pdbfile.topology, xyz=pdbfile.positions)
-
- molecules_structure = parmed.load_file(sdf_filename)
- molecules_structure = [ molecules_structure[index] for index in range(n_molecules) ]
-
- complex_structures = [ (molecules_structure[index] + protein_structure) for index in range(n_molecules) ]
- complex_structures = [ molecules_structure[index] for index in range(n_molecules) ] # DEBUG
-
- # Store
- testsystem = {
- 'name' : system_name,
- 'protein_pdbfile' : pdbfile,
- 'molecules' : molecules,
- 'complex_structures' : complex_structures
- }
- self.testsystems[system_name] = testsystem
-
- # DEBUG
- for name, testsystem in self.testsystems.items():
- filename = f'testsystem-{name}.pdb'
- print(filename)
- structure = testsystem['complex_structures'][0]
- #structure.save(filename, overwrite=True)
- with open(filename, 'w') as outfile:
- PDBFile.writeFile(structure.topology, structure.positions, outfile)
- testsystem['molecules'][0].to_file(f'testsystem-{name}-molecule.sdf', file_format="SDF")
- testsystem['molecules'][0].to_file(f'testsystem-{name}-molecule.pdb', file_format="PDB")
-
- # TODO: Create other test topologies
- # TODO: Protein-only
- # TODO: Protein-ligand topology
- # TODO: Solvated protein-ligand topology
- # TODO: Host-guest topology
- # Suppress DEBUG logging from various packages
-
- import logging
- for name in ['parmed', 'matplotlib']:
- logging.getLogger(name).setLevel(logging.WARNING)
-
def test_create(self):
"""Test SystemGenerator creation with only OpenMM ffxml force fields"""
# Create an empty system generator
@@ -169,61 +175,69 @@ def test_barostat(self):
assert force.getDefaultTemperature() == temperature
assert force.getFrequency() == frequency
- def test_create_with_template_generator(self):
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_create_with_template_generator(self, small_molecule_forcefield):
"""Test SystemGenerator creation with small molecule residue template generators"""
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a generator that defines AMBER and small molecule force fields
+ # Create a generator that defines AMBER and small molecule force fields
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield)
+
+ # 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 = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield)
-
- # 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 = SystemGenerator(forcefields=self.amber_forcefields,
- cache=cache, small_molecule_forcefield=small_molecule_forcefield)
- del generator
- # Reopen it (with cache still empty)
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- cache=cache, small_molecule_forcefield=small_molecule_forcefield)
- del generator
-
- def test_forcefield_default_kwargs(self):
+ cache=cache, small_molecule_forcefield=small_molecule_forcefield)
+ del generator
+ # Reopen it (with cache still empty)
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ cache=cache, small_molecule_forcefield=small_molecule_forcefield)
+ del generator
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_forcefield_default_kwargs(self, small_molecule_forcefield, test_systems):
"""Test that default forcefield kwargs work correctly"""
from openmm import unit
forcefield_kwargs = dict()
from openmmforcefields.generators import SystemGenerator
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
print(testsystem)
molecules = testsystem['molecules']
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator for this force field
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield,
- forcefield_kwargs=forcefield_kwargs,
- molecules=molecules)
-
- # Parameterize molecules
- for molecule in molecules:
- # Create non-periodic Topology
- nonperiodic_openmm_topology = molecule.to_topology().to_openmm()
- system = generator.create_system(nonperiodic_openmm_topology)
- forces = { force.__class__.__name__ : force for force in system.getForces() }
- assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.NoCutoff, "Expected CutoffNonPeriodic, got {forces['NonbondedForce'].getNonbondedMethod()}"
-
- # Create periodic Topology
- box_vectors = unit.Quantity(np.diag([30, 30, 30]), unit.angstrom)
- periodic_openmm_topology = copy.deepcopy(nonperiodic_openmm_topology)
- periodic_openmm_topology.setPeriodicBoxVectors(box_vectors)
- system = generator.create_system(periodic_openmm_topology)
- forces = { force.__class__.__name__ : force for force in system.getForces() }
- assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.PME, "Expected LJPME, got {forces['NonbondedForce'].getNonbondedMethod()}"
-
- def test_forcefield_kwargs(self):
+ # Create a SystemGenerator for this force field
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield,
+ forcefield_kwargs=forcefield_kwargs,
+ molecules=molecules)
+
+ # Parameterize molecules
+ for molecule in molecules:
+ # Create non-periodic Topology
+ nonperiodic_openmm_topology = molecule.to_topology().to_openmm()
+ system = generator.create_system(nonperiodic_openmm_topology)
+ forces = {force.__class__.__name__: force for force in system.getForces()}
+ assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.NoCutoff, "Expected CutoffNonPeriodic, got {forces['NonbondedForce'].getNonbondedMethod()}"
+
+ # Create periodic Topology
+ box_vectors = unit.Quantity(np.diag([30, 30, 30]), unit.angstrom)
+ periodic_openmm_topology = copy.deepcopy(nonperiodic_openmm_topology)
+ periodic_openmm_topology.setPeriodicBoxVectors(box_vectors)
+ system = generator.create_system(periodic_openmm_topology)
+ forces = {force.__class__.__name__: force for force in system.getForces()}
+ assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.PME, "Expected LJPME, got {forces['NonbondedForce'].getNonbondedMethod()}"
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_forcefield_kwargs(self, small_molecule_forcefield, test_systems):
"""Test that forcefield_kwargs and nonbonded method specifications work correctly"""
from openmm import unit
forcefield_kwargs = { 'hydrogenMass' : 4*unit.amu }
@@ -235,156 +249,162 @@ def test_forcefield_kwargs(self):
generator = SystemGenerator(forcefield_kwargs={'nonbondedMethod':PME})
assert "nonbondedMethod cannot be specified in forcefield_kwargs" in str(excinfo.value)
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
print(testsystem)
molecules = testsystem['molecules']
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator for this force field
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield,
- forcefield_kwargs=forcefield_kwargs,
- periodic_forcefield_kwargs={'nonbondedMethod':LJPME},
- nonperiodic_forcefield_kwargs={'nonbondedMethod':CutoffNonPeriodic},
- molecules=molecules)
-
- # Parameterize molecules
- for molecule in molecules:
- # Create non-periodic Topology
- nonperiodic_openmm_topology = molecule.to_topology().to_openmm()
- system = generator.create_system(nonperiodic_openmm_topology)
- forces = { force.__class__.__name__ : force for force in system.getForces() }
- assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.CutoffNonPeriodic, "Expected CutoffNonPeriodic, got {forces['NonbondedForce'].getNonbondedMethod()}"
-
- # Create periodic Topology
- box_vectors = unit.Quantity(np.diag([30, 30, 30]), unit.angstrom)
- periodic_openmm_topology = copy.deepcopy(nonperiodic_openmm_topology)
- periodic_openmm_topology.setPeriodicBoxVectors(box_vectors)
- system = generator.create_system(periodic_openmm_topology)
- forces = { force.__class__.__name__ : force for force in system.getForces() }
- assert forces['NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.LJPME, "Expected LJPME, got {forces['NonbondedForce'].getNonbondedMethod()}"
-
- def test_parameterize_molecules_from_creation(self):
+ # Create a SystemGenerator for this force field
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield,
+ forcefield_kwargs=forcefield_kwargs,
+ periodic_forcefield_kwargs={'nonbondedMethod': LJPME},
+ nonperiodic_forcefield_kwargs={'nonbondedMethod': CutoffNonPeriodic},
+ molecules=molecules)
+
+ # Parameterize molecules
+ for molecule in molecules:
+ # Create non-periodic Topology
+ nonperiodic_openmm_topology = molecule.to_topology().to_openmm()
+ system = generator.create_system(nonperiodic_openmm_topology)
+ forces = {force.__class__.__name__: force for force in system.getForces()}
+ assert forces[
+ 'NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.CutoffNonPeriodic, "Expected CutoffNonPeriodic, got {forces['NonbondedForce'].getNonbondedMethod()}"
+
+ # Create periodic Topology
+ box_vectors = unit.Quantity(np.diag([30, 30, 30]), unit.angstrom)
+ periodic_openmm_topology = copy.deepcopy(nonperiodic_openmm_topology)
+ periodic_openmm_topology.setPeriodicBoxVectors(box_vectors)
+ system = generator.create_system(periodic_openmm_topology)
+ forces = {force.__class__.__name__: force for force in system.getForces()}
+ assert forces[
+ 'NonbondedForce'].getNonbondedMethod() == openmm.NonbondedForce.LJPME, "Expected LJPME, got {forces['NonbondedForce'].getNonbondedMethod()}"
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_parameterize_molecules_from_creation(self, test_systems, small_molecule_forcefield):
"""Test that SystemGenerator can parameterize pre-specified molecules in vacuum"""
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
print(testsystem)
molecules = testsystem['molecules']
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator for this force field
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield,
- molecules=molecules)
-
- # Parameterize molecules
- for molecule in molecules:
- openmm_topology = molecule.to_topology().to_openmm()
- with Timer() as t1:
- system = generator.create_system(openmm_topology)
- assert system.getNumParticles() == molecule.n_atoms
- # Molecule should now be cached
- with Timer() as t2:
- system = generator.create_system(openmm_topology)
- assert system.getNumParticles() == molecule.n_atoms
- assert (t2.interval() < t1.interval())
-
- def test_parameterize_molecules_specified_during_create_system(self):
+ # Create a SystemGenerator for this force field
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield,
+ molecules=molecules)
+
+ # Parameterize molecules
+ for molecule in molecules:
+ openmm_topology = molecule.to_topology().to_openmm()
+ with Timer() as t1:
+ system = generator.create_system(openmm_topology)
+ assert system.getNumParticles() == molecule.n_atoms
+ # Molecule should now be cached
+ with Timer() as t2:
+ system = generator.create_system(openmm_topology)
+ assert system.getNumParticles() == molecule.n_atoms
+ assert (t2.interval() < t1.interval())
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_parameterize_molecules_specified_during_create_system(self, test_systems, small_molecule_forcefield):
"""Test that SystemGenerator can parameterize molecules specified during create_system"""
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
molecules = testsystem['molecules']
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator for this force field
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield)
-
- # Parameterize molecules
- for molecule in molecules:
- openmm_topology = molecule.to_topology().to_openmm()
- # Specify molecules during system creation
- system = generator.create_system(openmm_topology, molecules=molecules)
-
- def test_add_molecules(self):
- """Test that Molecules can be added to SystemGenerator later"""
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
# Create a SystemGenerator for this force field
generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield)
+ small_molecule_forcefield=small_molecule_forcefield)
+
+ # Parameterize molecules
+ for molecule in molecules:
+ openmm_topology = molecule.to_topology().to_openmm()
+ # Specify molecules during system creation
+ system = generator.create_system(openmm_topology, molecules=molecules)
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_add_molecules(self, test_systems, small_molecule_forcefield):
+ """Test that Molecules can be added to SystemGenerator later"""
+ # Create a SystemGenerator for this force field
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield)
+
+ # Add molecules for each test system separately
+ for name, testsystem in test_systems.items():
+ molecules = testsystem['molecules']
+ # Add molecules
+ generator.add_molecules(molecules)
+
+ # Parameterize molecules
+ for molecule in molecules:
+ openmm_topology = molecule.to_topology().to_openmm()
+ with Timer() as t1:
+ system = generator.create_system(openmm_topology)
+ assert system.getNumParticles() == molecule.n_atoms
+ # Molecule should now be cached
+ with Timer() as t2:
+ system = generator.create_system(openmm_topology)
+ assert system.getNumParticles() == molecule.n_atoms
+ assert (t2.interval() < t1.interval())
+
+ @pytest.mark.parametrize("small_molecule_forcefield", [
+ 'gaff-2.11',
+ 'openff-2.0.0',
+ pytest.param('espaloma-0.2.2', marks=pytest.mark.espaloma)])
+ def test_cache(self, test_systems, small_molecule_forcefield):
+ """Test that SystemGenerator correctly manages a cache"""
+ timing = dict() # timing[(small_molecule_forcefield, smiles)] is the time (in seconds) to parameterize molecule the first time
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ # Create a single shared cache for all force fields
+ cache = os.path.join(tmpdirname, 'db.json')
+ # Test that we can parameterize all molecules for all test systems
+ # Create a SystemGenerator
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield,
+ cache=cache)
# Add molecules for each test system separately
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
molecules = testsystem['molecules']
-
# Add molecules
generator.add_molecules(molecules)
# Parameterize molecules
for molecule in molecules:
openmm_topology = molecule.to_topology().to_openmm()
- with Timer() as t1:
+ with Timer() as timer:
system = generator.create_system(openmm_topology)
assert system.getNumParticles() == molecule.n_atoms
- # Molecule should now be cached
- with Timer() as t2:
- system = generator.create_system(openmm_topology)
- assert system.getNumParticles() == molecule.n_atoms
- assert (t2.interval() < t1.interval())
-
- def test_cache(self):
- """Test that SystemGenerator correctly manages a cache"""
- timing = dict() # timing[(small_molecule_forcefield, smiles)] is the time (in seconds) to parameterize molecule the first time
- with tempfile.TemporaryDirectory() as tmpdirname:
- # Create a single shared cache for all force fields
- cache = os.path.join(tmpdirname, 'db.json')
- # Test that we can parameterize all molecules for all test systems
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield,
- cache=cache)
- # Add molecules for each test system separately
- for name, testsystem in self.testsystems.items():
- molecules = testsystem['molecules']
- # Add molecules
- generator.add_molecules(molecules)
-
- # Parameterize molecules
- for molecule in molecules:
- openmm_topology = molecule.to_topology().to_openmm()
- with Timer() as timer:
- system = generator.create_system(openmm_topology)
- assert system.getNumParticles() == molecule.n_atoms
- # Record time
- timing[(small_molecule_forcefield, molecule.to_smiles())] = timer.interval()
+ # Record time
+ timing[(small_molecule_forcefield, molecule.to_smiles())] = timer.interval()
# Molecules should now be cached; test timing is faster the second time
# Test that we can parameterize all molecules for all test systems
- SMALL_MOLECULE_FORCEFIELDS = SystemGenerator.SMALL_MOLECULE_FORCEFIELDS if not CI else ['gaff-2.11', 'openff-2.0.0', 'espaloma-0.2.2']
- for small_molecule_forcefield in SMALL_MOLECULE_FORCEFIELDS:
- # Create a SystemGenerator
- generator = SystemGenerator(forcefields=self.amber_forcefields,
- small_molecule_forcefield=small_molecule_forcefield,
- cache=cache)
- # Add molecules for each test system separately
- for name, testsystem in self.testsystems.items():
- molecules = testsystem['molecules']
- # We don't need to add molecules that are already defined in the cache
-
- # Parameterize molecules
- for molecule in molecules:
- openmm_topology = molecule.to_topology().to_openmm()
- with Timer() as timer:
- system = generator.create_system(openmm_topology)
- assert system.getNumParticles() == molecule.n_atoms
-
- def test_complex(self):
+ # Create a SystemGenerator
+ generator = SystemGenerator(forcefields=self.amber_forcefields,
+ small_molecule_forcefield=small_molecule_forcefield,
+ cache=cache)
+ # Add molecules for each test system separately
+ for name, testsystem in test_systems.items():
+ molecules = testsystem['molecules']
+ # We don't need to add molecules that are already defined in the cache
+
+ # Parameterize molecules
+ for molecule in molecules:
+ openmm_topology = molecule.to_topology().to_openmm()
+ with Timer() as timer:
+ system = generator.create_system(openmm_topology)
+ assert system.getNumParticles() == molecule.n_atoms
+
+ def test_complex(self, test_systems):
"""Test parameterizing a protein:ligand complex in vacuum"""
- for name, testsystem in self.testsystems.items():
+ for name, testsystem in test_systems.items():
from openmm import unit
print(f'Testing parameterization of {name} in vacuum')
diff --git a/openmmforcefields/tests/test_template_generators.py b/openmmforcefields/tests/test_template_generators.py
index 07d130d3..62cb4264 100644
--- a/openmmforcefields/tests/test_template_generators.py
+++ b/openmmforcefields/tests/test_template_generators.py
@@ -3,6 +3,7 @@
import os
import tempfile
import unittest
+import pytest
import numpy as np
import openmm
@@ -534,6 +535,11 @@ def test_parameterize(self):
for small_molecule_forcefield in self.TEMPLATE_GENERATOR.INSTALLED_FORCEFIELDS:
if "ff14sb" in small_molecule_forcefield:
continue
+ if "tip" in small_molecule_forcefield:
+ continue
+ if "opc" in small_molecule_forcefield:
+ continue
+
print(f'Testing {small_molecule_forcefield}')
# Create a generator that knows about a few molecules
# TODO: Should the generator also load the appropriate force field files into the ForceField object?
@@ -675,7 +681,7 @@ def write_xml(filename, system):
print(f'{key:24} {(template_component_energy/unit.kilocalories_per_mole):20.3f} {(reference_component_energy/unit.kilocalories_per_mole):20.3f} kcal/mol')
print(f'{"TOTAL":24} {(template_energy["total"]/unit.kilocalories_per_mole):20.3f} {(reference_energy["total"]/unit.kilocalories_per_mole):20.3f} kcal/mol')
write_xml('reference_system.xml', reference_system)
- write_xml('template_system.xml', template_system)
+ write_xml('template_system.xml', template_system) # What's this? This variable does not exist
raise Exception(f'Energy deviation for {molecule.to_smiles()} ({delta/unit.kilocalories_per_mole} kcal/mol) exceeds threshold ({ENERGY_DEVIATION_TOLERANCE})')
# Compare forces
@@ -780,6 +786,11 @@ def test_energies(self):
for small_molecule_forcefield in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS:
if "ff14sb" in small_molecule_forcefield:
continue
+ if "tip" in small_molecule_forcefield:
+ continue
+ if "opc" in small_molecule_forcefield:
+ continue
+
print(f'Testing energies for {small_molecule_forcefield}...')
# Create a generator that knows about a few molecules
# TODO: Should the generator also load the appropriate force field files into the ForceField object?
@@ -817,6 +828,11 @@ def test_partial_charges_are_none(self):
for small_molecule_forcefield in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS:
if "ff14sb" in small_molecule_forcefield:
continue
+ if "tip" in small_molecule_forcefield:
+ continue
+ if "opc" in small_molecule_forcefield:
+ continue
+
print(f'Testing energies for {small_molecule_forcefield}...')
# Create a generator that knows about a few molecules
# TODO: Should the generator also load the appropriate force field files into the ForceField object?
@@ -831,6 +847,8 @@ def test_partial_charges_are_none(self):
def test_version(self):
"""Test version"""
+ # This test does not appear to test the version of anything in particular, but it fails sometimes
+ # because old versions of the toolkit can't bring in new versions of some water models
for forcefield in SMIRNOFFTemplateGenerator.INSTALLED_FORCEFIELDS:
generator = SMIRNOFFTemplateGenerator(forcefield=forcefield)
assert generator.forcefield == forcefield
@@ -838,6 +856,7 @@ def test_version(self):
assert os.path.exists(generator.smirnoff_filename)
+@pytest.mark.espaloma
class TestEspalomaTemplateGenerator(TestGAFFTemplateGenerator):
TEMPLATE_GENERATOR = EspalomaTemplateGenerator