Skip to content

Commit

Permalink
TST: Add some round-trip tests between GROMACS files
Browse files Browse the repository at this point in the history
  • Loading branch information
mattwthompson committed Dec 3, 2024
1 parent 2ea3d50 commit 92e6e3a
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 21 deletions.
21 changes: 21 additions & 0 deletions openff/interchange/_tests/_gromacs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Helpers for testing GROMACS interoperability."""

from openff.utilities import temporary_cd

from openff.interchange import Interchange
from openff.interchange.components.mdconfig import get_smirnoff_defaults
from openff.interchange.drivers import get_gromacs_energies


def gmx_roundtrip(state: Interchange, apply_smirnoff_defaults: bool = False):
with temporary_cd():
state.to_gromacs(prefix="state", decimal=8)
new_state = Interchange.from_gromacs(topology_file="state.top", gro_file="state.gro")

get_smirnoff_defaults(periodic=True).apply(new_state)

# TODO: More helpful handling of failures, i.e.
# * Detect differences in positions
# * Detect differences in box vectors
# * Detect differences in non-bonded settings
get_gromacs_energies(state).compare(get_gromacs_energies(new_state))
48 changes: 32 additions & 16 deletions openff/interchange/_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,16 @@ def gbsa_force_field() -> ForceField:
)


@pytest.fixture
def ff14sb() -> ForceField:
return ForceField("ff14sb_off_impropers_0.0.3.offxml")


@pytest.fixture
def ligand():
return MoleculeWithConformer.from_smiles("CC[C@@](/C=C\\[H])(C=C)O", allow_undefined_stereo=True)


@pytest.fixture
def basic_top() -> Topology:
topology = MoleculeWithConformer.from_smiles("C").to_topology()
Expand Down Expand Up @@ -469,27 +479,33 @@ def ethanol_top(ethanol):


@pytest.fixture
def mainchain_ala():
molecule = Molecule.from_file(
get_data_file_path("proteins/MainChain_ALA.sdf", "openff.toolkit"),
def alanine_dipeptide() -> Topology:
return Topology.from_pdb(
get_data_file_path(
"proteins/MainChain_ALA_ALA.pdb",
"openff.toolkit",
),
)
molecule._add_default_hierarchy_schemes()
molecule.perceive_residues()
molecule.perceive_hierarchy()

return molecule


@pytest.fixture
def mainchain_arg():
molecule = Molecule.from_file(
get_data_file_path("proteins/MainChain_ARG.sdf", "openff.toolkit"),
)
molecule._add_default_hierarchy_schemes()
molecule.perceive_residues()
molecule.perceive_hierarchy()
def mainchain_ala() -> Molecule:
return Topology.from_pdb(
get_data_file_path(
"proteins/MainChain_ALA.pdb",
"openff.toolkit",
),
).molecule(0)

return molecule

@pytest.fixture
def mainchain_arg() -> Molecule:
return Topology.from_pdb(
get_data_file_path(
"proteins/MainChain_ARG.pdb",
"openff.toolkit",
),
).molecule(0)


@pytest.fixture
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Interoperability tests with GROMACS."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from openff.toolkit import Quantity

from openff.interchange._tests._gromacs import gmx_roundtrip


def test_ligand_vacuum(ligand, sage_unconstrained, monkeypatch):
monkeypatch.setenv("INTERCHANGE_EXPERIMENTAL", 1)

topology = ligand.to_topology()
topology.box_vectors = Quantity([4, 4, 4], "nanometer")

gmx_roundtrip(sage_unconstrained.create_interchange(topology))


def test_water_dimer(water_dimer, tip3p, monkeypatch):
monkeypatch.setenv("INTERCHANGE_EXPERIMENTAL", 1)

gmx_roundtrip(tip3p.create_interchange(water_dimer))


def test_alanine_dipeptide(alanine_dipeptide, ff14sb, monkeypatch):
monkeypatch.setenv("INTERCHANGE_EXPERIMENTAL", 1)

gmx_roundtrip(ff14sb.create_interchange(alanine_dipeptide))
4 changes: 0 additions & 4 deletions openff/interchange/_tests/unit_tests/drivers/test_openmm.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,6 @@ class TestReportWithPlugins:
pytest.importorskip("smirnoff_plugins")
pytest.importorskip("openeye")

@pytest.fixture
def ligand(self):
return MoleculeWithConformer.from_smiles("CC[C@@](/C=C\\[H])(C=C)O")

@pytest.fixture
def de_force_field(self) -> ForceField:
return ForceField(
Expand Down
5 changes: 4 additions & 1 deletion openff/interchange/components/mdconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,12 +462,15 @@ def _infer_constraints(interchange: "Interchange") -> str:

if num_constraints == num_h_bonds:
return "h-bonds"
elif num_constraints == len(interchange["Bonds"].key_map):
elif num_constraints == num_bonds:
return "all-bonds"
elif num_constraints == (num_bonds + num_angles):
return "all-angles"

else:
# TODO: Rigid waters may not have bond and angle parameters, but still have 3 constraints
# per molecule. There should be a better way to process these, but it's non-trivial
# to detect water molecules in a performance, scalable way without false positives.
warnings.warn(
"Ambiguous failure while processing constraints. Constraining h-bonds as a stopgap.",
)
Expand Down
2 changes: 2 additions & 0 deletions openff/interchange/drivers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from openff.interchange.drivers.gromacs import get_gromacs_energies
from openff.interchange.drivers.lammps import get_lammps_energies
from openff.interchange.drivers.openmm import get_openmm_energies
from openff.interchange.drivers.report import EnergyReport

__all__ = [
"EnergyReport",
"get_all_energies",
"get_amber_energies",
"get_gromacs_energies",
Expand Down

0 comments on commit 92e6e3a

Please sign in to comment.