Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into lammps-constraints
Browse files Browse the repository at this point in the history
  • Loading branch information
mattwthompson committed Feb 15, 2024
2 parents 30dd996 + e7f0298 commit 9258d6b
Show file tree
Hide file tree
Showing 43 changed files with 605 additions and 576 deletions.
11 changes: 4 additions & 7 deletions .github/workflows/beta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
python-version:
- "3.9"
- "3.10"
- "3.11"
openeye:
- true
- false
Expand All @@ -46,9 +47,7 @@ jobs:
python -m pip install . plugins/
- name: Environment Information
run: |
conda info
conda list
run: conda info && conda list

- name: License OpenEye
if: ${{ matrix.openeye == true }}
Expand All @@ -60,13 +59,11 @@ jobs:

- name: Run mypy
continue-on-error: true
run: |
mypy --show-error-codes --namespace-packages -p "openff.interchange"
run: mypy --show-error-codes --namespace-packages -p "openff.interchange"

- name: Run all tests
if: always()
run: |
python -m pytest -v $COV openff/interchange/ -m "slow or not slow"
run: python -m pytest -v $COV openff/interchange/ -m "slow or not slow"

- name: Codecov
uses: codecov/codecov-action@v4
Expand Down
6 changes: 5 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ repos:
hooks:
- id: add-trailing-comma
- repo: https:/psf/black
rev: 24.1.1
rev: 24.2.0
hooks:
- id: black
files: ^openff|plugins|stubs
Expand All @@ -23,6 +23,10 @@ repos:
hooks:
- id: isort
files: ^openff|plugins|stubs
- repo: https:/asottile/yesqa
rev: v1.5.0
hooks:
- id: yesqa
- repo: https:/PyCQA/flake8
rev: 7.0.0
hooks:
Expand Down
5 changes: 2 additions & 3 deletions devtools/conda-envs/beta_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ name: beta-env
channels:
- jaimergp/label/unsupported-cudatoolkit-shim
- conda-forge/label/openmm_rc
- conda-forge/label/openff-models_rc
- conda-forge
- openeye
dependencies:
# Core
- python
- numpy >=1.21
- pydantic =1
- pydantic
- openmm >=7.6
# OpenFF stack
- openff-toolkit >=0.15.2
Expand Down Expand Up @@ -39,7 +38,7 @@ dependencies:
- pytest
- nbval
# Typing
- mypy =1.3
- mypy
- typing-extensions
- types-setuptools
- pandas-stubs >=1.2.0.56
3 changes: 2 additions & 1 deletion devtools/conda-envs/examples_env.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ dependencies:
# OpenFF stack
- openff-toolkit =0.15.2
- openff-models
- openff-nagl ==0.3.1
- openff-nagl ==0.3
- openff-nagl-models ==0.1
- dgl =1
# Optional features
- unyt
- mbuild
Expand Down
2 changes: 1 addition & 1 deletion docs/releasehistory.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Dates are given in YYYY-MM-DD format.

Please note that all releases prior to a version 1.0.0 are considered pre-releases and many API changes will come before a stable release.

## Current development
## 0.3.20 - 2023-02-12

* #891 Adds support for hydrogen mass repartitioning (HMR) in GROMACS export. Note that this implementaiton never modifies masses in waters and requires the system contains no virtual sites.
* #887 Adds support for hydrogen mass repartitioning (HMR) in OpenMM export. Note that this implementaiton never modifies masses in waters and requires the system contains no virtual sites.
Expand Down
2 changes: 1 addition & 1 deletion examples/host-guest/host_guest.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
"version": "3.11.7"
}
},
"nbformat": 4,
Expand Down
256 changes: 4 additions & 252 deletions openff/interchange/_tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,18 @@

import pathlib
import sys
from collections import defaultdict
from typing import DefaultDict, Optional
from typing import Optional

import numpy as np
import pytest
from openff.toolkit import ForceField, Molecule, Topology
from openff.toolkit import Molecule
from openff.toolkit.utils import (
AmberToolsToolkitWrapper,
OpenEyeToolkitWrapper,
RDKitToolkitWrapper,
)
from openff.utilities import get_data_file_path
from openff.utilities.utilities import has_executable, has_package, requires_package
from openff.utilities.utilities import has_executable, has_package

from openff.interchange.drivers.gromacs import _find_gromacs_executable
from openff.interchange.drivers.lammps import _find_lammps_executable

if sys.version_info >= (3, 10):
from importlib import resources
Expand Down Expand Up @@ -98,94 +94,8 @@ def from_mapped_smiles(self, smiles, name="", **kwargs):
return molecule


class _BaseTest:
@pytest.fixture(autouse=True)
def _initdir(self, tmpdir):
tmpdir.chdir()

@pytest.fixture()
def ethanol_top(self, ethanol):
"""Fixture that builds a simple four ethanol topology."""
return Topology.from_molecules(4 * [ethanol])

@pytest.fixture()
def mainchain_ala(self):
molecule = Molecule.from_file(
get_data_file_path("proteins/MainChain_ALA.sdf", "openff.toolkit"),
)
molecule._add_default_hierarchy_schemes()
molecule.perceive_residues()
molecule.perceive_hierarchy()

return molecule

@pytest.fixture()
def mainchain_arg(self):
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()

return molecule

@pytest.fixture()
def two_peptides(self, mainchain_ala, mainchain_arg):
return Topology.from_molecules([mainchain_ala, mainchain_arg])

xml_ff_bo_bonds = """<?xml version='1.0' encoding='ASCII'?>
<SMIRNOFF version="0.3" aromaticity_model="OEAroModel_MDL">
<Bonds version="0.3" fractional_bondorder_method="AM1-Wiberg" fractional_bondorder_interpolation="linear">
<Bond smirks="[#6:1]~[#8:2]" id="bbo1"
k_bondorder1="100.0*kilocalories_per_mole/angstrom**2"
k_bondorder2="1000.0*kilocalories_per_mole/angstrom**2"
length_bondorder1="1.5*angstrom"
length_bondorder2="1.0*angstrom"/>
</Bonds>
</SMIRNOFF>
"""

@pytest.fixture()
def methane(self):
return Molecule.from_smiles("C")

@pytest.fixture()
def parsley(self):
return ForceField("openff-1.0.0.offxml")

@pytest.fixture()
def hydrogen_cyanide(self):
return Molecule.from_mapped_smiles("[H:1][C:2]#[N:3]")

@pytest.fixture()
def hydrogen_cyanide_reversed(self):
return Molecule.from_mapped_smiles("[H:3][C:2]#[N:1]")

@pytest.fixture()
def hexane_diol(self):
molecule = Molecule.from_smiles("OCCCCCCO")
molecule.assign_partial_charges(partial_charge_method="gasteiger")
molecule.partial_charges.m
return molecule

@pytest.fixture()
def hydrogen_chloride(self):
return Molecule.from_mapped_smiles("[Cl:1][H:2]")

@pytest.fixture()
def formaldehyde(self):
return Molecule.from_mapped_smiles("[H:3][C:1]([H:4])=[O:2]")

@pytest.fixture()
def acetaldehyde(self):
return Molecule.from_mapped_smiles(
"[C:1]([C:2](=[O:3])[H:7])([H:4])([H:5])[H:6]",
)


HAS_GROMACS = _find_gromacs_executable() is not None
HAS_LAMMPS = _find_lammps_executable() is not None
HAS_LAMMPS = has_package("lammps")
HAS_SANDER = has_executable("sander")

needs_gmx = pytest.mark.skipif(not HAS_GROMACS, reason="Needs GROMACS")
Expand All @@ -203,161 +113,3 @@ def acetaldehyde(self):
HAS_SANDER,
reason="sander needs to NOT be installed",
)


def _get_charges_from_openff_interchange(interchange):
charges_ = [*interchange["Electrostatics"].charges.values()]
charges = np.asarray([charge.magnitude for charge in charges_])
return charges


def _create_torsion_dict(torsion_force) -> dict[tuple[int], list[tuple]]:
torsions: DefaultDict = defaultdict(list)

for i in range(torsion_force.getNumTorsions()):
p1, p2, p3, p4, periodicity, phase, k = torsion_force.getTorsionParameters(i)
key = (p1, p2, p3, p4)
torsions[key]
torsions[key].append((periodicity, phase, k))

return torsions


def _create_bond_dict(bond_force):
bonds = dict()

for i in range(bond_force.getNumBonds()):
p1, p2, length, k = bond_force.getBondParameters(i)
key = (p1, p2)
bonds[key] = (length, k)

return bonds


def _create_angle_dict(angle_force):
angles = dict()

for i in range(angle_force.getNumAngles()):
p1, p2, p3, theta, k = angle_force.getAngleParameters(i)
key = (p1, p2, p3)
angles[key] = (theta, k)

return angles


@requires_package("openmm")
def _compare_individual_torsions(x, y):
assert x[0] == y[0]
assert x[1] == y[1]
assert (x[2] - y[2]) < 1e-15 * openmm.unit.kilojoule_per_mole


def _compare_torsion_forces(force1, force2):
sorted1 = _create_torsion_dict(torsion_force=force1)
sorted2 = _create_torsion_dict(torsion_force=force2)

assert sum(len(v) for v in sorted1.values()) == force1.getNumTorsions()
assert sum(len(v) for v in sorted2.values()) == force2.getNumTorsions()
assert len(sorted1) == len(sorted2)

for key in sorted1:
for i in range(len(sorted1[key])):
_compare_individual_torsions(sorted1[key][i], sorted2[key][i])


@requires_package("openmm")
def _compare_bond_forces(force1, force2):
assert force1.getNumBonds() == force2.getNumBonds()

bonds1 = _create_bond_dict(force1)
bonds2 = _create_bond_dict(force2)

for key in bonds1:
length_diff = bonds2[key][0] - bonds1[key][0]
assert (
abs(length_diff) < 1e-15 * openmm.unit.nanometer
), f"Bond lengths differ by {length_diff}"
k_diff = bonds2[key][1] - bonds1[key][1]
assert abs(k_diff) < 1e-9 * kj_nm2_mol, f"bond k differ by {k_diff}"


@requires_package("openmm")
def _compare_angle_forces(force1, force2):
assert force1.getNumAngles() == force2.getNumAngles()

angles1 = _create_angle_dict(force1)
angles2 = _create_angle_dict(force2)

for key in angles1:
angle_diff = angles2[key][0] - angles1[key][0]
assert (
abs(angle_diff) < 1e-15 * openmm.unit.radian
), f"angles differ by {angle_diff}"
k_diff = angles2[key][1] - angles1[key][1]
assert abs(k_diff) < 1e-10 * kj_rad2_mol, f"angle k differ by {k_diff}"


def _compare_nonbonded_settings(force1, force2):
for attr in dir(force1):
if not attr.startswith("get") or attr in [
"getExceptionParameterOffset",
"getExceptionParameters",
"getGlobalParameterDefaultValue",
"getGlobalParameterName",
"getLJPMEParametersInContext",
"getPMEParametersInContext",
"getParticleParameterOffset",
"getParticleParameters",
"getForceGroup",
]:
continue
assert getattr(force1, attr)() == getattr(force2, attr)(), attr


@requires_package("openmm")
def _compare_nonbonded_parameters(force1, force2):
assert (
force1.getNumParticles() == force2.getNumParticles()
), "found different number of particles"

for i in range(force1.getNumParticles()):
q1, sig1, eps1 = force1.getParticleParameters(i)
q2, sig2, eps2 = force2.getParticleParameters(i)
assert (
abs(q2 - q1) < 1e-8 * openmm.unit.elementary_charge
), f"charge mismatch in particle {i}: {q1} vs {q2}"
assert (
abs(sig2 - sig1) < 1e-12 * openmm.unit.nanometer
), f"sigma mismatch in particle {i}: {sig1} vs {sig2}"
assert (
abs(eps2 - eps1) < 1e-12 * openmm.unit.kilojoule_per_mole
), f"epsilon mismatch in particle {i}: {eps1} vs {eps2}"


@requires_package("openmm")
def _compare_exceptions(force1, force2):
assert (
force1.getNumExceptions() == force2.getNumExceptions()
), "found different number of exceptions"

for i in range(force1.getNumExceptions()):
_, _, q1, sig1, eps1 = force1.getExceptionParameters(i)
_, _, q2, sig2, eps2 = force2.getExceptionParameters(i)
assert (
abs(q2 - q1) < 1e-12 * openmm.unit.elementary_charge**2
), f"charge mismatch in exception {i}"
assert (
abs(sig2 - sig1) < 1e-12 * openmm.unit.nanometer
), f"sigma mismatch in exception {i}"
assert (
abs(eps2 - eps1) < 1e-12 * openmm.unit.kilojoule_per_mole
), f"epsilon mismatch in exception {i}"


@requires_package("openmm")
def _get_force(openmm_sys: "openmm.System", force_type):
forces = [f for f in openmm_sys.getForces() if type(f) is force_type]

if len(forces) > 1:
raise NotImplementedError("Not yet able to process duplicate forces types")
return forces[0]
Loading

0 comments on commit 9258d6b

Please sign in to comment.