Make CUDA the default compute platform (#1576)

* Make CUDA the default compute platform
This commit is contained in:
Irfan Alibay
2025-10-20 14:10:34 +01:00
committed by GitHub
parent 3ebf7e326a
commit 7f93af79ec
8 changed files with 320 additions and 257 deletions

24
news/cuda.rst Normal file
View File

@@ -0,0 +1,24 @@
**Added:**
* <news item>
**Changed:**
* The default platform for OpenMM-based Protocols is now CUDA and will fail
by default on a non-Nvidia GPU enabled system.
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View File

@@ -314,10 +314,13 @@ class OpenMMEngineSettings(SettingsBaseModel):
* In the future make precision and deterministic forces user defined too.
"""
compute_platform: Optional[str] = None
compute_platform: Optional[str] = 'cuda'
"""
OpenMM compute platform to perform MD integration with. If ``None``, will
choose fastest available platform. Default ``None``.
choose fastest available platform.
Allowed platforms are; ``cuda``, ``opencl``, ``cpu``.
Default ``cuda``.
"""
gpu_device_index: Optional[list[int]] = None
"""
@@ -332,6 +335,15 @@ class OpenMMEngineSettings(SettingsBaseModel):
Default ``None``.
"""
@field_validator('compute_platform')
def supported_sampler(cls, v):
supported = ['cpu', 'opencl', 'cuda']
if v is not None and v.lower() not in supported:
errmsg = ("Only the following OpenMM compute backends are "
f"supported: {supported}")
raise ValueError(errmsg)
return v
class IntegratorSettings(SettingsBaseModel):
"""Settings for the `LangevinDynamicsMove integrator <https://openmmtools.readthedocs.io/en/latest/api/generated/openmmtools.mcmc.LangevinDynamicsMove.html>`_.

View File

@@ -29,6 +29,15 @@ from openfe.protocols.openmm_utils.charge_generation import (
)
@pytest.fixture()
def protocol_dry_settings():
settings = AbsoluteSolvationProtocol.default_settings()
settings.vacuum_engine_settings.compute_platform = None
settings.solvent_engine_settings.compute_platform = None
settings.protocol_repeats = 1
return settings
@pytest.fixture()
def default_settings():
return AbsoluteSolvationProtocol.default_settings()
@@ -55,14 +64,16 @@ def test_serialize_protocol(default_settings):
@pytest.mark.parametrize('method', [
'repex', 'sams', 'independent', 'InDePeNdENT'
])
def test_dry_run_vac_benzene(benzene_modifications,
method, tmpdir):
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.vacuum_simulation_settings.sampler_method = method
def test_dry_run_vac_benzene(
benzene_modifications,
method,
protocol_dry_settings,
tmpdir
):
protocol_dry_settings.vacuum_simulation_settings.sampler_method = method
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -98,13 +109,10 @@ def test_dry_run_vac_benzene(benzene_modifications,
assert not vac_sampler.is_periodic
def test_confgen_fail_AFE(benzene_modifications, tmpdir):
def test_confgen_fail_AFE(benzene_modifications, protocol_dry_settings, tmpdir):
# check system parametrisation works even if confgen fails
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -134,13 +142,13 @@ def test_confgen_fail_AFE(benzene_modifications, tmpdir):
assert vac_sampler
def test_dry_run_solv_benzene(benzene_modifications, tmpdir):
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.solvent_output_settings.output_indices = "resname UNK"
def test_dry_run_solv_benzene(
benzene_modifications, protocol_dry_settings, tmpdir
):
protocol_dry_settings.solvent_output_settings.output_indices = "resname UNK"
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -179,24 +187,24 @@ def test_dry_run_solv_benzene(benzene_modifications, tmpdir):
assert pdb.n_atoms == 12
def test_dry_run_solv_benzene_tip4p(benzene_modifications, tmpdir):
s = AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.vacuum_forcefield_settings.forcefields = [
def test_dry_run_solv_benzene_tip4p(
benzene_modifications, protocol_dry_settings, tmpdir
):
protocol_dry_settings.vacuum_forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
"amber/phosaa10.xml", # Handles THE TPO
]
s.solvent_forcefield_settings.forcefields = [
protocol_dry_settings.solvent_forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
"amber/phosaa10.xml", # Handles THE TPO
]
s.solvation_settings.solvent_model = 'tip4pew'
s.integrator_settings.reassign_velocities = True
protocol_dry_settings.solvation_settings.solvent_model = 'tip4pew'
protocol_dry_settings.integrator_settings.reassign_velocities = True
protocol = AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -226,13 +234,12 @@ def test_dry_run_solv_benzene_tip4p(benzene_modifications, tmpdir):
def test_dry_run_solv_benzene_noncubic(
benzene_modifications, tmpdir
benzene_modifications, protocol_dry_settings, tmpdir
):
s = AbsoluteSolvationProtocol.default_settings()
s.solvation_settings.solvent_padding = 1.5 * offunit.nanometer
s.solvation_settings.box_shape = 'dodecahedron'
protocol_dry_settings.solvation_settings.solvent_padding = 1.5 * offunit.nanometer
protocol_dry_settings.solvation_settings.box_shape = 'dodecahedron'
protocol = AbsoluteSolvationProtocol(settings=s)
protocol = AbsoluteSolvationProtocol(settings=protocol_dry_settings)
stateA = ChemicalSystem({
'benzene': benzene_modifications['benzene'],
@@ -276,17 +283,16 @@ def test_dry_run_solv_benzene_noncubic(
)
def test_dry_run_solv_user_charges_benzene(benzene_modifications, tmpdir):
def test_dry_run_solv_user_charges_benzene(
benzene_modifications, protocol_dry_settings, tmpdir
):
"""
Create a test system with fictitious user supplied charges and
ensure that they are properly passed through to the constructed
alchemical system.
"""
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
def assign_fictitious_charges(offmol):
@@ -385,19 +391,18 @@ def test_dry_run_solv_user_charges_benzene(benzene_modifications, tmpdir):
),
])
def test_dry_run_charge_backends(
CN_molecule, tmpdir, method, backend, ref_key, am1bcc_ref_charges
CN_molecule, tmpdir, method, backend, ref_key,
protocol_dry_settings, am1bcc_ref_charges
):
"""
Check that partial charge generation with different backends
works as expected.
"""
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.partial_charge_settings.partial_charge_method = method
s.partial_charge_settings.off_toolkit_backend = backend
s.partial_charge_settings.nagl_model = 'openff-gnn-am1bcc-0.1.0-rc.1.pt'
protocol_dry_settings.partial_charge_settings.partial_charge_method = method
protocol_dry_settings.partial_charge_settings.off_toolkit_backend = backend
protocol_dry_settings.partial_charge_settings.nagl_model = 'openff-gnn-am1bcc-0.1.0-rc.1.pt'
protocol = openmm_afe.AbsoluteSolvationProtocol(settings=s)
protocol = openmm_afe.AbsoluteSolvationProtocol(settings=protocol_dry_settings)
# Create ChemicalSystems
stateA = ChemicalSystem({
@@ -441,14 +446,12 @@ def test_dry_run_charge_backends(
)
def test_high_timestep(benzene_modifications, tmpdir):
s = AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.solvent_forcefield_settings.hydrogen_mass = 1.0
s.vacuum_forcefield_settings.hydrogen_mass = 1.0
def test_high_timestep(benzene_modifications, protocol_dry_settings, tmpdir):
protocol_dry_settings.solvent_forcefield_settings.hydrogen_mass = 1.0
protocol_dry_settings.vacuum_forcefield_settings.hydrogen_mass = 1.0
protocol = AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -474,11 +477,10 @@ def test_high_timestep(benzene_modifications, tmpdir):
@pytest.fixture
def benzene_solvation_dag(benzene_modifications):
s = AbsoluteSolvationProtocol.default_settings()
def benzene_solvation_dag(benzene_modifications, protocol_dry_settings):
protocol_dry_settings.protocol_repeats = 3
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({
@@ -672,20 +674,21 @@ class TestProtocolResult:
[[100 * offunit.picosecond, None],
[None, None],
[None, 100 * offunit.picosecond]])
def test_dry_run_vacuum_write_frequency(benzene_modifications,
positions_write_frequency,
velocities_write_frequency,
tmpdir):
s = openmm_afe.AbsoluteSolvationProtocol.default_settings()
s.protocol_repeats = 1
s.solvent_output_settings.output_indices = "resname UNK"
s.solvent_output_settings.positions_write_frequency = positions_write_frequency
s.solvent_output_settings.velocities_write_frequency = velocities_write_frequency
s.vacuum_output_settings.positions_write_frequency = positions_write_frequency
s.vacuum_output_settings.velocities_write_frequency = velocities_write_frequency
def test_dry_run_vacuum_write_frequency(
benzene_modifications,
positions_write_frequency,
velocities_write_frequency,
protocol_dry_settings,
tmpdir
):
protocol_dry_settings.solvent_output_settings.output_indices = "resname UNK"
protocol_dry_settings.solvent_output_settings.positions_write_frequency = positions_write_frequency
protocol_dry_settings.solvent_output_settings.velocities_write_frequency = velocities_write_frequency
protocol_dry_settings.vacuum_output_settings.positions_write_frequency = positions_write_frequency
protocol_dry_settings.vacuum_output_settings.velocities_write_frequency = velocities_write_frequency
protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
settings=protocol_dry_settings,
)
stateA = ChemicalSystem({

View File

@@ -26,6 +26,14 @@ import logging
from openfe.tests.conftest import HAS_ESPALOMA
@pytest.fixture()
def vac_settings():
settings = PlainMDProtocol.default_settings()
settings.forcefield_settings.nonbonded_method = 'nocutoff'
settings.engine_settings.compute_platform = None
return settings
def test_create_default_settings():
settings = PlainMDProtocol.default_settings()
@@ -87,10 +95,7 @@ def test_create_independent_repeat_ids(benzene_system):
assert len(repeat_ids) == 6
def test_dry_run_default_vacuum(benzene_vacuum_system, tmpdir):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_default_vacuum(benzene_vacuum_system, vac_settings, tmpdir):
protocol = PlainMDProtocol(
settings=vac_settings,
@@ -112,10 +117,8 @@ def test_dry_run_default_vacuum(benzene_vacuum_system, tmpdir):
protocol.settings.thermo_settings.temperature)).barostat is None
def test_dry_run_logger_output(benzene_vacuum_system, tmpdir, caplog):
def test_dry_run_logger_output(benzene_vacuum_system, vac_settings, tmpdir, caplog):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.simulation_settings.equilibration_length_nvt = 1 * unit.picosecond
vac_settings.simulation_settings.equilibration_length = 1 * unit.picosecond
vac_settings.simulation_settings.production_length = 1 * unit.picosecond
@@ -143,10 +146,8 @@ def test_dry_run_logger_output(benzene_vacuum_system, tmpdir, caplog):
assert "running production phase" in messages
def test_dry_run_ffcache_none_vacuum(benzene_vacuum_system, tmpdir):
def test_dry_run_ffcache_none_vacuum(benzene_vacuum_system, vac_settings, tmpdir):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.output_settings.forcefield_cache = None
protocol = PlainMDProtocol(
@@ -166,9 +167,7 @@ def test_dry_run_ffcache_none_vacuum(benzene_vacuum_system, tmpdir):
dag_unit.run(dry=True)['debug']['system']
def test_dry_run_gaff_vacuum(benzene_vacuum_system, tmpdir):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_gaff_vacuum(benzene_vacuum_system, vac_settings, tmpdir):
vac_settings.forcefield_settings.small_molecule_forcefield = 'gaff-2.11'
protocol = PlainMDProtocol(
@@ -187,9 +186,7 @@ def test_dry_run_gaff_vacuum(benzene_vacuum_system, tmpdir):
@pytest.mark.skipif(not HAS_ESPALOMA, reason='espaloma is not available')
def test_dry_run_espaloma_vacuum(benzene_vacuum_system, tmpdir):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_espaloma_vacuum(benzene_vacuum_system, vac_settings, tmpdir):
vac_settings.forcefield_settings.small_molecule_forcefield = 'espaloma-0.3.2'
protocol = PlainMDProtocol(
@@ -230,10 +227,8 @@ def test_dry_run_espaloma_vacuum(benzene_vacuum_system, tmpdir):
),
])
def test_dry_run_charge_backends(
CN_molecule, tmpdir, method, backend, ref_key, am1bcc_ref_charges
CN_molecule, tmpdir, method, backend, ref_key, vac_settings, am1bcc_ref_charges
):
vac_settings = PlainMDProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.partial_charge_settings.partial_charge_method = method
vac_settings.partial_charge_settings.off_toolkit_backend = backend
vac_settings.partial_charge_settings.nagl_model = "openff-gnn-am1bcc-0.1.0-rc.1.pt"
@@ -268,6 +263,7 @@ def test_dry_many_molecules_solvent(
A basic test flushing "will it work if you pass multiple molecules"
"""
settings = PlainMDProtocol.default_settings()
settings.engine_settings.compute_platform = None
protocol = PlainMDProtocol(
settings=settings,
@@ -357,6 +353,7 @@ def test_dry_run_ligand_tip4p(benzene_system, tmpdir):
environment (waters)
"""
settings = PlainMDProtocol.default_settings()
settings.engine_settings.compute_platform = None
settings.forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
@@ -386,6 +383,7 @@ def test_dry_run_ligand_tip4p(benzene_system, tmpdir):
def test_dry_run_complex(benzene_complex_system, tmpdir):
# this will be very time consuming
settings = PlainMDProtocol.default_settings()
settings.engine_settings.compute_platform = None
protocol = PlainMDProtocol(
settings=settings,

View File

@@ -44,6 +44,23 @@ from openfe.protocols.openmm_utils.charge_generation import (
)
@pytest.fixture()
def vac_settings():
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.forcefield_settings.nonbonded_method = 'nocutoff'
settings.engine_settings.compute_platform = None
settings.protocol_repeats = 1
return settings
@pytest.fixture()
def solv_settings():
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.engine_settings.compute_platform = None
settings.protocol_repeats = 1
return settings
def test_compute_platform_warn():
with pytest.warns(UserWarning, match="Non-CUDA platform selected: CPU"):
omm_compute.get_openmm_platform('CPU')
@@ -188,13 +205,11 @@ def test_validate_alchemical_components_missing_alchem_comp(
@pytest.mark.parametrize('method', [
'repex', 'sams', 'independent', 'InDePeNdENT'
])
def test_dry_run_default_vacuum(benzene_vacuum_system, toluene_vacuum_system,
benzene_to_toluene_mapping, method, tmpdir, benzene_toluene_topology):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_default_vacuum(
benzene_vacuum_system, toluene_vacuum_system, benzene_to_toluene_mapping,
method, vac_settings, tmpdir, benzene_toluene_topology
):
vac_settings.simulation_settings.sampler_method = method
vac_settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=vac_settings,
@@ -248,10 +263,10 @@ def test_dry_run_default_vacuum(benzene_vacuum_system, toluene_vacuum_system,
assert_allclose(new_positions.value_in_unit(omm_unit.angstrom), htf._new_positions.value_in_unit(omm_unit.angstrom))
def test_dry_run_gaff_vacuum(benzene_vacuum_system, toluene_vacuum_system,
benzene_to_toluene_mapping, tmpdir):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_gaff_vacuum(
benzene_vacuum_system, toluene_vacuum_system, benzene_to_toluene_mapping,
vac_settings, tmpdir
):
vac_settings.forcefield_settings.small_molecule_forcefield = 'gaff-2.11'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
@@ -271,16 +286,17 @@ def test_dry_run_gaff_vacuum(benzene_vacuum_system, toluene_vacuum_system,
@pytest.mark.slow
def test_dry_many_molecules_solvent(
benzene_many_solv_system, toluene_many_solv_system,
benzene_to_toluene_mapping, tmpdir
benzene_many_solv_system,
toluene_many_solv_system,
benzene_to_toluene_mapping,
solv_settings,
tmpdir
):
"""
A basic test flushing "will it work if you pass multiple molecules"
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
# create DAG from protocol and take first (and only) work unit from within
@@ -361,7 +377,7 @@ $$$$
"""
def test_dry_core_element_change(tmpdir):
def test_dry_core_element_change(vac_settings, tmpdir):
benz = openfe.SmallMoleculeComponent(Chem.MolFromMolBlock(BENZ, removeHs=False))
pyr = openfe.SmallMoleculeComponent(Chem.MolFromMolBlock(PYRIDINE, removeHs=False))
@@ -371,11 +387,8 @@ def test_dry_core_element_change(tmpdir):
{0: 0, 1: 10, 2: 1, 3: 2, 4: 3, 5: 4, 6: 5, 8: 9, 9: 8, 10: 7, 11: 6}
)
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.forcefield_settings.nonbonded_method = 'nocutoff'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=vac_settings,
)
dag = protocol.create(
@@ -403,16 +416,16 @@ def test_dry_core_element_change(tmpdir):
@pytest.mark.parametrize('method', ['repex', 'sams', 'independent'])
def test_dry_run_ligand(benzene_system, toluene_system,
benzene_to_toluene_mapping, method, tmpdir):
def test_dry_run_ligand(
benzene_system, toluene_system,
benzene_to_toluene_mapping, method, solv_settings, tmpdir
):
# this might be a bit time consuming
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.simulation_settings.sampler_method = method
settings.protocol_repeats = 1
settings.output_settings.output_indices = 'resname UNK'
solv_settings.simulation_settings.sampler_method = method
solv_settings.output_settings.output_indices = 'resname UNK'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
dag = protocol.create(
stateA=benzene_system,
@@ -434,15 +447,14 @@ def test_dry_run_ligand(benzene_system, toluene_system,
assert pdb.n_atoms == 16
def test_confgen_mocked_fail(benzene_system, toluene_system,
benzene_to_toluene_mapping, tmpdir):
def test_confgen_mocked_fail(
benzene_system, toluene_system,
benzene_to_toluene_mapping, solv_settings, tmpdir
):
"""
Check that even if conformer generation fails, we can still perform a sim
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(settings=settings)
protocol = openmm_rfe.RelativeHybridTopologyProtocol(settings=solv_settings)
dag = protocol.create(stateA=benzene_system, stateB=toluene_system,
mapping=benzene_to_toluene_mapping)
@@ -464,6 +476,7 @@ def tip4p_hybrid_factory(
Hybrid system with virtual sites in the environment (waters)
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.engine_settings.compute_platform = None
settings.forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
@@ -578,17 +591,17 @@ def test_tip4p_check_vsite_parameters(tip4p_hybrid_factory):
0.9 * unit.nanometer]
)
def test_dry_run_ligand_system_cutoff(
cutoff, benzene_system, toluene_system, benzene_to_toluene_mapping, tmpdir
cutoff, benzene_system, toluene_system, benzene_to_toluene_mapping,
solv_settings, tmpdir
):
"""
Test that the right nonbonded cutoff is propagated to the hybrid system.
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.solvation_settings.solvent_padding = 1.5 * unit.nanometer
settings.forcefield_settings.nonbonded_cutoff = cutoff
solv_settings.solvation_settings.solvent_padding = 1.5 * unit.nanometer
solv_settings.forcefield_settings.nonbonded_cutoff = cutoff
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
dag = protocol.create(
stateA=benzene_system,
@@ -633,11 +646,9 @@ def test_dry_run_ligand_system_cutoff(
),
])
def test_dry_run_charge_backends(
CN_molecule, tmpdir, method, backend, ref_key, am1bcc_ref_charges
CN_molecule, tmpdir, method, backend, ref_key,
vac_settings, am1bcc_ref_charges
):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.protocol_repeats = 1
vac_settings.partial_charge_settings.partial_charge_method = method
vac_settings.partial_charge_settings.off_toolkit_backend = backend
vac_settings.partial_charge_settings.nagl_model = 'openff-gnn-am1bcc-0.1.0-rc.1.pt'
@@ -702,16 +713,12 @@ def test_dry_run_charge_backends(
@pytest.mark.flaky(reruns=3) # bad minimisation can happen
def test_dry_run_user_charges(benzene_modifications, tmpdir):
def test_dry_run_user_charges(benzene_modifications, vac_settings, tmpdir):
"""
Create a hybrid system with a set of fictitious user supplied charges
and ensure that they are properly passed through to the constructed
hybrid topology.
"""
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=vac_settings,
)
@@ -828,26 +835,27 @@ def test_dry_run_user_charges(benzene_modifications, tmpdir):
np.testing.assert_allclose(c_offsets[i], c_exp)
def test_virtual_sites_no_reassign(benzene_system, toluene_system,
benzene_to_toluene_mapping, tmpdir):
def test_virtual_sites_no_reassign(
benzene_system, toluene_system,
benzene_to_toluene_mapping, solv_settings, tmpdir
):
"""
Because of some as-of-yet not fully identified issue, not reassigning
velocities will cause systems to NaN.
See https://github.com/choderalab/openmmtools/issues/695
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.forcefield_settings.forcefields = [
solv_settings.forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
"amber/phosaa10.xml", # Handles THE TPO
]
settings.solvation_settings.solvent_padding = 1.0 * unit.nanometer
settings.forcefield_settings.nonbonded_cutoff = 0.9 * unit.nanometer
settings.solvation_settings.solvent_model = 'tip4pew'
settings.integrator_settings.reassign_velocities = False
solv_settings.solvation_settings.solvent_padding = 1.0 * unit.nanometer
solv_settings.forcefield_settings.nonbonded_cutoff = 0.9 * unit.nanometer
solv_settings.solvation_settings.solvent_model = 'tip4pew'
solv_settings.integrator_settings.reassign_velocities = False
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
dag = protocol.create(
stateA=benzene_system,
@@ -862,15 +870,16 @@ def test_virtual_sites_no_reassign(benzene_system, toluene_system,
dag_unit.run(dry=True)
def test_dodecahdron_ligand_box(benzene_system, toluene_system,
benzene_to_toluene_mapping, tmpdir):
def test_dodecahdron_ligand_box(
benzene_system, toluene_system, benzene_to_toluene_mapping,
solv_settings, tmpdir
):
"""
Test that a hybrid system with a dodechadron is built properly.
"""
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.solvation_settings.solvent_padding = 1.5 * unit.nanometer
settings.solvation_settings.box_shape = 'dodecahedron'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(settings=settings)
solv_settings.solvation_settings.solvent_padding = 1.5 * unit.nanometer
solv_settings.solvation_settings.box_shape = 'dodecahedron'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(settings=solv_settings)
dag = protocol.create(
stateA=benzene_system,
@@ -902,16 +911,16 @@ def test_dodecahdron_ligand_box(benzene_system, toluene_system,
@pytest.mark.slow
@pytest.mark.parametrize('method', ['repex', 'sams', 'independent'])
def test_dry_run_complex(benzene_complex_system, toluene_complex_system,
benzene_to_toluene_mapping, method, tmpdir):
def test_dry_run_complex(
benzene_complex_system, toluene_complex_system,
benzene_to_toluene_mapping, method, solv_settings, tmpdir
):
# this will be very time consuming
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.simulation_settings.sampler_method = method
settings.protocol_repeats = 1
settings.output_settings.output_indices = 'protein or resname UNK'
solv_settings.simulation_settings.sampler_method = method
solv_settings.output_settings.output_indices = 'protein or resname UNK'
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -945,15 +954,17 @@ def test_lambda_schedule(windows):
assert len(lambdas.lambda_schedule) == windows
def test_hightimestep(benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping, tmpdir):
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.forcefield_settings.hydrogen_mass = 1.0
settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_hightimestep(
benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
vac_settings,
tmpdir
):
vac_settings.forcefield_settings.hydrogen_mass = 1.0
p = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=vac_settings,
)
dag = p.create(
@@ -969,15 +980,17 @@ def test_hightimestep(benzene_vacuum_system,
dag_unit.run(dry=True)
def test_n_replicas_not_n_windows(benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping, tmpdir):
def test_n_replicas_not_n_windows(
benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
vac_settings,
tmpdir
):
# For PR #125 we pin such that the number of lambda windows
# equals the numbers of replicas used - TODO: remove limitation
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
# default lambda windows is 11
settings.simulation_settings.n_replicas = 13
settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.simulation_settings.n_replicas = 13
errmsg = ("Number of replicas 13 does not equal the number of "
"lambda windows 11")
@@ -985,7 +998,7 @@ def test_n_replicas_not_n_windows(benzene_vacuum_system,
with tmpdir.as_cwd():
with pytest.raises(ValueError, match=errmsg):
p = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=vac_settings,
)
dag = p.create(
stateA=benzene_vacuum_system,
@@ -1178,11 +1191,10 @@ def test_element_change_warning(atom_mapping_basic_test_files):
)
def test_ligand_overlap_warning(benzene_vacuum_system, toluene_vacuum_system,
benzene_to_toluene_mapping, tmpdir):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_ligand_overlap_warning(
benzene_vacuum_system, toluene_vacuum_system, benzene_to_toluene_mapping,
vac_settings, tmpdir
):
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=vac_settings,
)
@@ -1521,13 +1533,14 @@ def tyk2_xml(tmp_path_factory):
29: 23, 30: 24, 31: 25, 32: 26, 33: 27}
)
settings: openmm_rfe.RelativeHybridTopologyProtocolSettings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.forcefield_settings.small_molecule_forcefield = 'openff-2.0.0'
settings.forcefield_settings.nonbonded_method = 'nocutoff'
settings.forcefield_settings.hydrogen_mass = 3.0
settings.protocol_repeats = 1
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.engine_settings.compute_platform = None
vac_settings.forcefield_settings.small_molecule_forcefield = 'openff-2.0.0'
vac_settings.forcefield_settings.hydrogen_mass = 3.0
vac_settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(settings)
protocol = openmm_rfe.RelativeHybridTopologyProtocol(vac_settings)
dag = protocol.create(
stateA=openfe.ChemicalSystem({'ligand': lig23}),
@@ -2070,7 +2083,9 @@ def _assert_total_charge(system, atom_classes, chgA, chgB):
assert chgB == pytest.approx(np.sum(stateB_charges))
def test_dry_run_alchemwater_solvent(benzene_to_benzoic_mapping, tmpdir):
def test_dry_run_alchemwater_solvent(
benzene_to_benzoic_mapping, solv_settings, tmpdir
):
stateA_system = openfe.ChemicalSystem(
{'ligand': benzene_to_benzoic_mapping.componentA,
'solvent': openfe.SolventComponent()}
@@ -2079,7 +2094,6 @@ def test_dry_run_alchemwater_solvent(benzene_to_benzoic_mapping, tmpdir):
{'ligand': benzene_to_benzoic_mapping.componentB,
'solvent': openfe.SolventComponent()}
)
solv_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
solv_settings.alchemical_settings.explicit_charge_correction = True
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=solv_settings,
@@ -2116,7 +2130,7 @@ def test_dry_run_alchemwater_solvent(benzene_to_benzoic_mapping, tmpdir):
])
def test_dry_run_complex_alchemwater_totcharge(
mapping_name, chgA, chgB, correction, core_atoms,
new_uniq, old_uniq, tmpdir, request, T4_protein_component,
new_uniq, old_uniq, tmpdir, request, T4_protein_component, solv_settings
):
mapping = request.getfixturevalue(mapping_name)
solvent = openfe.SolventComponent()
@@ -2131,13 +2145,12 @@ def test_dry_run_complex_alchemwater_totcharge(
'protein': T4_protein_component}
)
settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
settings.solvation_settings.solvent_padding="0.9 nm"
settings.solvation_settings.box_shape="dodecahedron"
settings.alchemical_settings.explicit_charge_correction = correction
solv_settings.solvation_settings.solvent_padding="0.9 nm"
solv_settings.solvation_settings.box_shape="dodecahedron"
solv_settings.alchemical_settings.explicit_charge_correction = correction
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=settings,
settings=solv_settings,
)
# create DAG from protocol and take first (and only) work unit from within
@@ -2174,19 +2187,17 @@ def test_structural_analysis_error(tmpdir):
[[100 * unit.picosecond, None],
[None, None],
[None, 100 * unit.picosecond]])
def test_dry_run_vacuum_write_frequency(benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
positions_write_frequency,
velocities_write_frequency,
tmpdir,
):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
def test_dry_run_vacuum_write_frequency(
benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
positions_write_frequency,
velocities_write_frequency,
vac_settings,
tmpdir,
):
vac_settings.output_settings.positions_write_frequency = positions_write_frequency
vac_settings.output_settings.velocities_write_frequency = velocities_write_frequency
vac_settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=vac_settings,
@@ -2216,19 +2227,18 @@ def test_dry_run_vacuum_write_frequency(benzene_vacuum_system,
@pytest.mark.parametrize('positions_write_frequency,velocities_write_frequency',
[[100.1 * unit.picosecond, 100 * unit.picosecond],
[100 * unit.picosecond, 100.1 * unit.picosecond]])
def test_pos_write_frequency_not_divisible(benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
positions_write_frequency,
velocities_write_frequency,
tmpdir,
):
def test_pos_write_frequency_not_divisible(
benzene_vacuum_system,
toluene_vacuum_system,
benzene_to_toluene_mapping,
positions_write_frequency,
velocities_write_frequency,
tmpdir,
vac_settings,
):
vac_settings = openmm_rfe.RelativeHybridTopologyProtocol.default_settings()
vac_settings.forcefield_settings.nonbonded_method = 'nocutoff'
vac_settings.output_settings.positions_write_frequency = positions_write_frequency
vac_settings.output_settings.velocities_write_frequency = velocities_write_frequency
vac_settings.protocol_repeats = 1
protocol = openmm_rfe.RelativeHybridTopologyProtocol(
settings=vac_settings,

View File

@@ -49,9 +49,19 @@ EPSILON0 = (
ONE_4PI_EPS0 = 1 / (4 * np.pi * EPSILON0) * EPSILON0.unit * 10.0 # nm -> angstrom
@pytest.fixture()
def protocol_dry_settings():
# a set of settings for dry run tests
s = SepTopProtocol.default_settings()
s.engine_settings.compute_platform = None
s.protocol_repeats = 1
return s
@pytest.fixture()
def default_settings():
return SepTopProtocol.default_settings()
s = SepTopProtocol.default_settings()
return s
def test_create_default_settings():
@@ -637,10 +647,9 @@ class TestNonbondedInteractions:
def benzene_toluene_dag(
benzene_complex_system,
toluene_complex_system,
default_settings,
protocol_dry_settings,
):
default_settings.protocol_repeats = 1
protocol = SepTopProtocol(settings=default_settings)
protocol = SepTopProtocol(settings=protocol_dry_settings)
return protocol.create(
stateA=benzene_complex_system,
@@ -710,18 +719,17 @@ def test_dry_run_methods(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
method,
):
default_settings.solvent_simulation_settings.sampler_method = method
default_settings.complex_simulation_settings.sampler_method = method
default_settings.protocol_repeats = 1
default_settings.complex_output_settings.output_indices = 'resname UNK'
default_settings.solvent_output_settings.output_indices = 'resname UNK'
protocol_dry_settings.solvent_simulation_settings.sampler_method = method
protocol_dry_settings.complex_simulation_settings.sampler_method = method
protocol_dry_settings.complex_output_settings.output_indices = 'resname UNK'
protocol_dry_settings.solvent_output_settings.output_indices = 'resname UNK'
protocol = SepTopProtocol(
settings=default_settings,
settings=protocol_dry_settings,
)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -765,16 +773,16 @@ def test_dry_run_ligand_system_pressure(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
"""
Test that the right nonbonded cutoff is propagated to the system.
"""
# openfe settings requires openff/pint units
default_settings.thermo_settings.pressure = pressure * offunit.bar
protocol_dry_settings.thermo_settings.pressure = pressure * offunit.bar
protocol = SepTopProtocol(
settings=default_settings,
settings=protocol_dry_settings,
)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -801,22 +809,21 @@ def test_virtual_sites_no_reassign(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
"""
Test that an error is raised when not reassigning velocities
in a system with virtual site.
"""
default_settings.protocol_repeats = 1
default_settings.forcefield_settings.forcefields = [
protocol_dry_settings.forcefield_settings.forcefields = [
"amber/ff14SB.xml",
"amber/tip4pew_standard.xml", # FF with VS
]
default_settings.solvent_solvation_settings.solvent_model = 'tip4pew'
default_settings.integrator_settings.reassign_velocities = False
protocol_dry_settings.solvent_solvation_settings.solvent_model = 'tip4pew'
protocol_dry_settings.integrator_settings.reassign_velocities = False
protocol = SepTopProtocol(
settings=default_settings,
settings=protocol_dry_settings,
)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -841,18 +848,18 @@ def test_dry_run_ligand_system_cutoff(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
"""
Test that the right nonbonded cutoff is propagated to the system.
"""
default_settings.solvent_solvation_settings.solvent_padding = (
protocol_dry_settings.solvent_solvation_settings.solvent_padding = (
1.9 * offunit.nanometer
)
default_settings.forcefield_settings.nonbonded_cutoff = cutoff
protocol_dry_settings.forcefield_settings.nonbonded_cutoff = cutoff
protocol = SepTopProtocol(
settings=default_settings,
settings=protocol_dry_settings,
)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -881,18 +888,17 @@ def test_dry_run_benzene_toluene_tip4p(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
default_settings.protocol_repeats = 1
default_settings.forcefield_settings.forcefields = [
protocol_dry_settings.forcefield_settings.forcefields = [
"amber/ff14SB.xml", # ff14SB protein force field
"amber/tip4pew_standard.xml", # FF we are testsing with the fun VS
"amber/phosaa10.xml", # Handles THE TPO
]
default_settings.solvent_solvation_settings.solvent_model = "tip4pew"
default_settings.integrator_settings.reassign_velocities = True
protocol_dry_settings.solvent_solvation_settings.solvent_model = "tip4pew"
protocol_dry_settings.integrator_settings.reassign_velocities = True
protocol = SepTopProtocol(settings=default_settings)
protocol = SepTopProtocol(settings=protocol_dry_settings)
# Create DAG from protocol, get the solvent units
# and eventually dry run the first solvent unit
@@ -926,15 +932,14 @@ def test_dry_run_benzene_toluene_noncubic(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
default_settings.protocol_repeats = 1
default_settings.solvent_solvation_settings.solvent_padding = (
protocol_dry_settings.solvent_solvation_settings.solvent_padding = (
1.5 * offunit.nanometer
)
default_settings.solvent_solvation_settings.box_shape = "dodecahedron"
protocol_dry_settings.solvent_solvation_settings.box_shape = "dodecahedron"
protocol = SepTopProtocol(settings=default_settings)
protocol = SepTopProtocol(settings=protocol_dry_settings)
# Create DAG from protocol, get the vacuum and solvent units
# and eventually dry run the first solvent unit
@@ -977,16 +982,14 @@ def test_dry_run_solv_user_charges_benzene_toluene(
benzene_modifications,
T4_protein_component,
tmpdir,
default_settings,
protocol_dry_settings,
):
"""
Create a test system with fictitious user supplied charges and
ensure that they are properly passed through to the constructed
alchemical system.
"""
default_settings.protocol_repeats = 1
protocol = SepTopProtocol(settings=default_settings)
protocol = SepTopProtocol(settings=protocol_dry_settings)
def assign_fictitious_charges(offmol):
"""
@@ -1091,13 +1094,12 @@ def test_high_timestep(
benzene_complex_system,
toluene_complex_system,
tmpdir,
default_settings,
protocol_dry_settings,
):
default_settings.protocol_repeats = 1
default_settings.forcefield_settings.hydrogen_mass = 1.0
default_settings.forcefield_settings.hydrogen_mass = 1.0
protocol_dry_settings.forcefield_settings.hydrogen_mass = 1.0
protocol_dry_settings.forcefield_settings.hydrogen_mass = 1.0
protocol = SepTopProtocol(settings=default_settings)
protocol = SepTopProtocol(settings=protocol_dry_settings)
dag = protocol.create(
stateA=benzene_complex_system,
@@ -1117,14 +1119,14 @@ def T4L_xml(
benzene_complex_system,
toluene_complex_system,
tmp_path_factory,
default_settings,
protocol_dry_settings,
):
# Fixing the number of solvent molecules in the solvent settings
# to test against reference xml
default_settings.solvent_solvation_settings.solvent_padding = None
default_settings.solvent_solvation_settings.number_of_solvent_molecules = 364
default_settings.forcefield_settings.small_molecule_forcefield = "openff-2.1.1"
protocol = SepTopProtocol(settings=default_settings)
protocol_dry_settings.solvent_solvation_settings.solvent_padding = None
protocol_dry_settings.solvent_solvation_settings.number_of_solvent_molecules = 364
protocol_dry_settings.forcefield_settings.small_molecule_forcefield = "openff-2.1.1"
protocol = SepTopProtocol(settings=protocol_dry_settings)
dag = protocol.create(
stateA=benzene_complex_system,

View File

@@ -22,7 +22,9 @@ import mdtraj as md
@pytest.fixture()
def default_settings():
return SepTopProtocol.default_settings()
s = SepTopProtocol.default_settings()
s.engine_settings.compute_platform = None
return s
def compare_energies(alchemical_system, positions):

View File

@@ -94,3 +94,15 @@ class TestOpenMMSolvationSettings:
with pytest.raises(ValueError, match="must be a 1-D array"):
s.box_size = np.array([[1, 2, 3], [1, 2, 3]]) * unit.angstrom
class TestOpenMMEngineSettings:
@pytest.mark.parametrize('platform', ['CUDA', 'OpenCL', 'cPu'])
def test_ok_platforms(self, platform):
s = omm_settings.OpenMMEngineSettings(compute_platform=platform)
assert isinstance(s, omm_settings.OpenMMEngineSettings)
def test_fail_platform(self):
with pytest.raises(ValueError, match="OpenMM compute backends"):
_ = omm_settings.OpenMMEngineSettings(compute_platform="foo")