mirror of
https://github.com/OpenFreeEnergy/openfe.git
synced 2026-06-04 14:14:22 +08:00
merge conflicts
This commit is contained in:
@@ -14,7 +14,12 @@ import json
|
||||
import logging
|
||||
import pathlib
|
||||
import tempfile
|
||||
from openff.toolkit import Molecule
|
||||
from openff.toolkit import (
|
||||
Molecule, RDKitToolkitWrapper, AmberToolsToolkitWrapper
|
||||
)
|
||||
from openff.toolkit.utils.toolkit_registry import (
|
||||
toolkit_registry_manager, ToolkitRegistry
|
||||
)
|
||||
from openff.units import unit
|
||||
from kartograf.atom_aligner import align_mol_shape
|
||||
from kartograf import KartografAtomMapper
|
||||
@@ -31,11 +36,16 @@ logger = logging.getLogger(__name__)
|
||||
LIGA = "[H]C([H])([H])C([H])([H])C(=O)C([H])([H])C([H])([H])[H]"
|
||||
LIGB = "[H]C([H])([H])C(=O)C([H])([H])C([H])([H])C([H])([H])[H]"
|
||||
|
||||
amber_rdkit = ToolkitRegistry(
|
||||
[RDKitToolkitWrapper(), AmberToolsToolkitWrapper()]
|
||||
)
|
||||
|
||||
|
||||
def get_molecule(smi, name):
|
||||
m = Molecule.from_smiles(smi)
|
||||
m.generate_conformers()
|
||||
m.assign_partial_charges(partial_charge_method="am1bcc")
|
||||
with toolkit_registry_manager(amber_rdkit):
|
||||
m = Molecule.from_smiles(smi)
|
||||
m.generate_conformers()
|
||||
m.assign_partial_charges(partial_charge_method="am1bcc")
|
||||
return openfe.SmallMoleculeComponent.from_openff(m, name=name)
|
||||
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ from openmmtools.states import (SamplerState,
|
||||
create_thermodynamic_state_protocol,)
|
||||
from openmmtools.alchemy import (AlchemicalRegion, AbsoluteAlchemicalFactory,
|
||||
AlchemicalState,)
|
||||
from typing import Dict, List, Optional
|
||||
from typing import Optional
|
||||
from openmm import app
|
||||
from openmm import unit as omm_unit
|
||||
from openmmforcefields.generators import SystemGenerator
|
||||
@@ -50,11 +50,14 @@ from gufe import (
|
||||
from openfe.protocols.openmm_utils.omm_settings import (
|
||||
SettingsBaseModel,
|
||||
)
|
||||
from openfe.protocols.openmm_utils.omm_settings import (
|
||||
BasePartialChargeSettings,
|
||||
)
|
||||
from openfe.protocols.openmm_afe.equil_afe_settings import (
|
||||
BaseSolvationSettings,
|
||||
MultiStateSimulationSettings, OpenMMEngineSettings,
|
||||
IntegratorSettings, LambdaSettings, OutputSettings,
|
||||
ThermoSettings,
|
||||
ThermoSettings, OpenFFPartialChargeSettings,
|
||||
)
|
||||
from openfe.protocols.openmm_rfe._rfe_utils import compute
|
||||
from openfe.protocols.openmm_md.plain_md_methods import PlainMDProtocolUnit
|
||||
@@ -356,12 +359,37 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
)
|
||||
return system_generator
|
||||
|
||||
@staticmethod
|
||||
def _assign_partial_charges(
|
||||
partial_charge_settings: OpenFFPartialChargeSettings,
|
||||
smc_components: dict[SmallMoleculeComponent, OFFMolecule],
|
||||
) -> None:
|
||||
"""
|
||||
Assign partial charges to SMCs.
|
||||
Parameters
|
||||
----------
|
||||
charge_settings : OpenFFPartialChargeSettings
|
||||
Settings for controlling how the partial charges are assigned.
|
||||
smc_components : dict[SmallMoleculeComponent, openff.toolkit.Molecule]
|
||||
Dictionary of OpenFF Molecules to add, keyed by
|
||||
SmallMoleculeComponent.
|
||||
"""
|
||||
for mol in smc_components.values():
|
||||
# don't do this if we have user charges
|
||||
if not (mol.partial_charges is not None and np.any(mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
def _get_modeller(
|
||||
self,
|
||||
protein_component: Optional[ProteinComponent],
|
||||
solvent_component: Optional[SolventComponent],
|
||||
smc_components: dict[SmallMoleculeComponent, OFFMolecule],
|
||||
system_generator: SystemGenerator,
|
||||
partial_charge_settings: BasePartialChargeSettings,
|
||||
solvation_settings: BaseSolvationSettings
|
||||
) -> tuple[app.Modeller, dict[Component, npt.NDArray]]:
|
||||
"""
|
||||
@@ -374,10 +402,14 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
Protein Component, if it exists.
|
||||
solvent_component : Optional[ProteinCompoinent]
|
||||
Solvent Component, if it exists.
|
||||
smc_components : list[openff.toolkit.topology.Molecule]
|
||||
List of openff Molecules to add.
|
||||
smc_components : dict[SmallMoleculeComponent, openff.toolkit.Molecule]
|
||||
Dicationary of OpenFF Molecules to add, keyed by
|
||||
SmallMoleculeComponent.
|
||||
system_generator : openmmforcefields.generator.SystemGenerator
|
||||
System Generator to parameterise this unit.
|
||||
partial_charge_settings : BasePartialChargeSettings
|
||||
Settings detailing how to assign partial charges to the
|
||||
SMCs of the system.
|
||||
solvation_settings : BaseSolvationSettings
|
||||
Settings detailing how to solvate the system.
|
||||
|
||||
@@ -392,20 +424,15 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
if self.verbose:
|
||||
self.logger.info("Parameterizing molecules")
|
||||
|
||||
# Assign partial charges to smcs
|
||||
self._assign_partial_charges(partial_charge_settings, smc_components)
|
||||
|
||||
# force the creation of parameters for the small molecules
|
||||
# this is necessary because we need to have the FF generated ahead
|
||||
# of solvating the system.
|
||||
# Note by default this is cached to ctx.shared/db.json which should
|
||||
# reduce some of the costs.
|
||||
for mol in smc_components.values():
|
||||
# don't do this if we have user charges
|
||||
if not (mol.partial_charges is not None and np.any(mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
system_generator.create_system(
|
||||
mol.to_topology().to_openmm(), molecules=[mol]
|
||||
)
|
||||
@@ -438,7 +465,7 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
parametrized.
|
||||
system_generator : SystemGenerator
|
||||
SystemGenerator object to create a System with.
|
||||
smc_components : list
|
||||
smc_components : list[openff.toolkit.Molecules]
|
||||
A list of openff Molecules to add to the system.
|
||||
|
||||
Returns
|
||||
@@ -902,7 +929,7 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
return None
|
||||
|
||||
def run(self, dry=False, verbose=True,
|
||||
scratch_basepath=None, shared_basepath=None) -> Dict[str, Any]:
|
||||
scratch_basepath=None, shared_basepath=None) -> dict[str, Any]:
|
||||
"""Run the absolute free energy calculation.
|
||||
|
||||
Parameters
|
||||
@@ -914,24 +941,16 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
verbose : bool
|
||||
Verbose output of the simulation progress. Output is provided via
|
||||
INFO level logging.
|
||||
basepath : Pathlike, optional
|
||||
Where to run the calculation, defaults to current working directory
|
||||
scratch_basepath : pathlib.Path
|
||||
Path to the scratch (temporary) directory space.
|
||||
shared_basepath : pathlib.Path
|
||||
Path to the shared (persistent) directory space.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Outputs created in the basepath directory or the debug objects
|
||||
(i.e. sampler) if ``dry==True``.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
solvent : Optional[SolventComponent]
|
||||
SolventComponent to be applied to the system
|
||||
protein : Optional[ProteinComponent]
|
||||
ProteinComponent for the system
|
||||
openff_mols : List[openff.Molecule]
|
||||
List of OpenFF Molecule objects for each SmallMoleculeComponent in
|
||||
the stateA ChemicalSystem
|
||||
"""
|
||||
# 0. Generaly preparation tasks
|
||||
self._prepare(verbose, scratch_basepath, shared_basepath)
|
||||
@@ -948,7 +967,8 @@ class BaseAbsoluteUnit(gufe.ProtocolUnit):
|
||||
# 4. Get modeller
|
||||
system_modeller, comp_resids = self._get_modeller(
|
||||
prot_comp, solv_comp, smc_comps, system_generator,
|
||||
settings['solvation_settings']
|
||||
settings['charge_settings'],
|
||||
settings['solvation_settings'],
|
||||
)
|
||||
|
||||
# 5. Get OpenMM topology, positions and system
|
||||
|
||||
@@ -26,6 +26,7 @@ from openfe.protocols.openmm_utils.omm_settings import (
|
||||
OpenMMSolvationSettings,
|
||||
OpenMMEngineSettings,
|
||||
IntegratorSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
OutputSettings,
|
||||
MDSimulationSettings,
|
||||
MDOutputSettings,
|
||||
@@ -213,3 +214,9 @@ class AbsoluteSolvationSettings(SettingsBaseModel):
|
||||
"""
|
||||
Simulation output settings for the solvent transformation.
|
||||
"""
|
||||
|
||||
partial_charge_settings: OpenFFPartialChargeSettings
|
||||
"""
|
||||
Settings for controlling how to assign partial charges.
|
||||
Currently unused.
|
||||
"""
|
||||
|
||||
@@ -54,6 +54,7 @@ from openfe.protocols.openmm_afe.equil_afe_settings import (
|
||||
MDSimulationSettings, MDOutputSettings,
|
||||
MultiStateSimulationSettings, OpenMMEngineSettings,
|
||||
IntegratorSettings, OutputSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
SettingsBaseModel,
|
||||
)
|
||||
from ..openmm_utils import system_validation, settings_validation
|
||||
@@ -416,6 +417,7 @@ class AbsoluteSolvationProtocol(gufe.Protocol):
|
||||
0.0, 0.0, 0.0, 0.0, 0.0, 0.12, 0.24,
|
||||
0.36, 0.48, 0.6, 0.7, 0.77, 0.85, 1.0],
|
||||
),
|
||||
partial_charge_settings=OpenFFPartialChargeSettings(),
|
||||
solvation_settings=OpenMMSolvationSettings(),
|
||||
vacuum_engine_settings=OpenMMEngineSettings(),
|
||||
solvent_engine_settings=OpenMMEngineSettings(),
|
||||
@@ -770,6 +772,7 @@ class AbsoluteSolvationVacuumUnit(BaseAbsoluteUnit):
|
||||
A dictionary with the following entries:
|
||||
* forcefield_settings : OpenMMSystemGeneratorFFSettings
|
||||
* thermo_settings : ThermoSettings
|
||||
* charge_settings: OpenFFPartialChargeSettings
|
||||
* solvation_settings : OpenMMSolvationSettings
|
||||
* alchemical_settings : AlchemicalSettings
|
||||
* lambda_settings : LambdaSettings
|
||||
@@ -785,6 +788,7 @@ class AbsoluteSolvationVacuumUnit(BaseAbsoluteUnit):
|
||||
settings = {}
|
||||
settings['forcefield_settings'] = prot_settings.vacuum_forcefield_settings
|
||||
settings['thermo_settings'] = prot_settings.thermo_settings
|
||||
settings['charge_settings'] = prot_settings.partial_charge_settings
|
||||
settings['solvation_settings'] = prot_settings.solvation_settings
|
||||
settings['alchemical_settings'] = prot_settings.alchemical_settings
|
||||
settings['lambda_settings'] = prot_settings.lambda_settings
|
||||
@@ -858,6 +862,7 @@ class AbsoluteSolvationSolventUnit(BaseAbsoluteUnit):
|
||||
A dictionary with the following entries:
|
||||
* forcefield_settings : OpenMMSystemGeneratorFFSettings
|
||||
* thermo_settings : ThermoSettings
|
||||
* charge_settings : OpenFFPartialChargeSettings
|
||||
* solvation_settings : OpenMMSolvationSettings
|
||||
* alchemical_settings : AlchemicalSettings
|
||||
* lambda_settings : LambdaSettings
|
||||
@@ -873,6 +878,7 @@ class AbsoluteSolvationSolventUnit(BaseAbsoluteUnit):
|
||||
settings = {}
|
||||
settings['forcefield_settings'] = prot_settings.solvent_forcefield_settings
|
||||
settings['thermo_settings'] = prot_settings.thermo_settings
|
||||
settings['charge_settings'] = prot_settings.partial_charge_settings
|
||||
settings['solvation_settings'] = prot_settings.solvation_settings
|
||||
settings['alchemical_settings'] = prot_settings.alchemical_settings
|
||||
settings['lambda_settings'] = prot_settings.lambda_settings
|
||||
|
||||
@@ -32,8 +32,12 @@ from gufe import (
|
||||
settings, ChemicalSystem, SmallMoleculeComponent,
|
||||
ProteinComponent, SolventComponent
|
||||
)
|
||||
from openfe.protocols.openmm_utils.omm_settings import (
|
||||
BasePartialChargeSettings,
|
||||
)
|
||||
from openfe.protocols.openmm_md.plain_md_settings import (
|
||||
PlainMDProtocolSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
OpenMMSolvationSettings, OpenMMEngineSettings,
|
||||
IntegratorSettings, MDSimulationSettings, MDOutputSettings,
|
||||
)
|
||||
@@ -121,6 +125,7 @@ class PlainMDProtocol(gufe.Protocol):
|
||||
temperature=298.15 * unit.kelvin,
|
||||
pressure=1 * unit.bar,
|
||||
),
|
||||
partial_charge_settings=OpenFFPartialChargeSettings(),
|
||||
solvation_settings=OpenMMSolvationSettings(),
|
||||
engine_settings=OpenMMEngineSettings(),
|
||||
integrator_settings=IntegratorSettings(),
|
||||
@@ -391,10 +396,12 @@ class PlainMDProtocolUnit(gufe.ProtocolUnit):
|
||||
logger.info("running production phase")
|
||||
|
||||
# Setup the reporters
|
||||
|
||||
# TODO: write_interval should probably not be in units of
|
||||
# timestep but time - Issue #716
|
||||
write_interval = output_settings.trajectory_write_interval.m
|
||||
write_interval = settings_validation.divmod_time_and_check(
|
||||
output_settings.trajectory_write_interval,
|
||||
timestep,
|
||||
"trajectory_write_interval",
|
||||
"timestep",
|
||||
)
|
||||
|
||||
checkpoint_interval = settings_validation.get_simsteps(
|
||||
sim_length=output_settings.checkpoint_interval,
|
||||
@@ -428,6 +435,33 @@ class PlainMDProtocolUnit(gufe.ProtocolUnit):
|
||||
if verbose:
|
||||
logger.info(f"Completed simulation in {t1 - t0} seconds")
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _assign_partial_charges(
|
||||
charge_settings: OpenFFPartialChargeSettings,
|
||||
smc_components: dict[SmallMoleculeComponent, OFFMolecule],
|
||||
) -> None:
|
||||
"""
|
||||
Assign partial charges to SMCs.
|
||||
Parameters
|
||||
----------
|
||||
charge_settings : OpenFFPartialChargeSettings
|
||||
Settings for controlling how the partial charges are assigned.
|
||||
smc_components : dict[SmallMoleculeComponent, openff.toolkit.Molecule]
|
||||
Dictionary of OpenFF Molecules to add, keyed by
|
||||
SmallMoleculeComponent.
|
||||
"""
|
||||
for mol in smc_components.values():
|
||||
# don't do this if we have user charges
|
||||
if not (mol.partial_charges is not None and np.any(
|
||||
mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
def run(self, *, dry=False, verbose=True,
|
||||
scratch_basepath=None,
|
||||
shared_basepath=None) -> dict[str, Any]:
|
||||
@@ -473,6 +507,7 @@ class PlainMDProtocolUnit(gufe.ProtocolUnit):
|
||||
forcefield_settings: settings.OpenMMSystemGeneratorFFSettings = protocol_settings.forcefield_settings
|
||||
thermo_settings: settings.ThermoSettings = protocol_settings.thermo_settings
|
||||
solvation_settings: OpenMMSolvationSettings = protocol_settings.solvation_settings
|
||||
charge_settings: BasePartialChargeSettings = protocol_settings.partial_charge_settings
|
||||
sim_settings: MDSimulationSettings = protocol_settings.simulation_settings
|
||||
output_settings: MDOutputSettings = protocol_settings.output_settings
|
||||
timestep = protocol_settings.integrator_settings.timestep
|
||||
@@ -516,16 +551,11 @@ class PlainMDProtocolUnit(gufe.ProtocolUnit):
|
||||
smc_components: dict[SmallMoleculeComponent, OFFMolecule]
|
||||
|
||||
smc_components = {i: i.to_openff() for i in small_mols}
|
||||
for mol in smc_components.values():
|
||||
# don't do this if we have user charges
|
||||
if not (mol.partial_charges is not None and np.any(
|
||||
mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
# Assign partial charges to smcs
|
||||
self._assign_partial_charges(charge_settings, smc_components)
|
||||
|
||||
for mol in smc_components.values():
|
||||
system_generator.create_system(
|
||||
mol.to_topology().to_openmm(), molecules=[mol]
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@ from openfe.protocols.openmm_utils.omm_settings import (
|
||||
OpenMMEngineSettings,
|
||||
MDSimulationSettings,
|
||||
IntegratorSettings, MDOutputSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
)
|
||||
from gufe.settings import SettingsBaseModel
|
||||
try:
|
||||
@@ -39,6 +40,7 @@ class PlainMDProtocolSettings(Settings):
|
||||
|
||||
# Things for creating the systems
|
||||
solvation_settings: OpenMMSolvationSettings
|
||||
partial_charge_settings: OpenFFPartialChargeSettings
|
||||
|
||||
# MD Engine things
|
||||
engine_settings: OpenMMEngineSettings
|
||||
|
||||
@@ -56,6 +56,10 @@ from .equil_rfe_settings import (
|
||||
OpenMMSolvationSettings, AlchemicalSettings, LambdaSettings,
|
||||
MultiStateSimulationSettings, OpenMMEngineSettings,
|
||||
IntegratorSettings, OutputSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
)
|
||||
from openfe.protocols.openmm_utils.omm_settings import (
|
||||
BasePartialChargeSettings,
|
||||
)
|
||||
from ..openmm_utils import (
|
||||
system_validation, settings_validation, system_creation,
|
||||
@@ -455,6 +459,7 @@ class RelativeHybridTopologyProtocol(gufe.Protocol):
|
||||
temperature=298.15 * unit.kelvin,
|
||||
pressure=1 * unit.bar,
|
||||
),
|
||||
partial_charge_settings=OpenFFPartialChargeSettings(),
|
||||
solvation_settings=OpenMMSolvationSettings(),
|
||||
alchemical_settings=AlchemicalSettings(softcore_LJ='gapsys'),
|
||||
lambda_settings=LambdaSettings(),
|
||||
@@ -579,6 +584,33 @@ class RelativeHybridTopologyProtocolUnit(gufe.ProtocolUnit):
|
||||
generation=generation
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _assign_partial_charges(
|
||||
charge_settings: OpenFFPartialChargeSettings,
|
||||
off_small_mols: dict[str, list[tuple[SmallMoleculeComponent, OFFMolecule]]],
|
||||
) -> None:
|
||||
"""
|
||||
Assign partial charges to SMCs.
|
||||
Parameters
|
||||
----------
|
||||
charge_settings : OpenFFPartialChargeSettings
|
||||
Settings for controlling how the partial charges are assigned.
|
||||
off_small_mols : dict[str, list[tuple[SmallMoleculeComponent, OFFMolecule]]]
|
||||
Dictionary of dictionary of OpenFF Molecules to add, keyed by
|
||||
state and SmallMoleculeComponent.
|
||||
"""
|
||||
for smc, mol in chain(off_small_mols['stateA'],
|
||||
off_small_mols['stateB'],
|
||||
off_small_mols['both']):
|
||||
# skip if we already have user charges
|
||||
if not (mol.partial_charges is not None and np.any(mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
|
||||
def run(self, *, dry=False, verbose=True,
|
||||
scratch_basepath=None,
|
||||
shared_basepath=None) -> dict[str, Any]:
|
||||
@@ -629,6 +661,7 @@ class RelativeHybridTopologyProtocolUnit(gufe.ProtocolUnit):
|
||||
thermo_settings: settings.ThermoSettings = protocol_settings.thermo_settings
|
||||
alchem_settings: AlchemicalSettings = protocol_settings.alchemical_settings
|
||||
lambda_settings: LambdaSettings = protocol_settings.lambda_settings
|
||||
charge_settings: BasePartialChargeSettings = protocol_settings.partial_charge_settings
|
||||
solvation_settings: OpenMMSolvationSettings = protocol_settings.solvation_settings
|
||||
sampler_settings: MultiStateSimulationSettings = protocol_settings.simulation_settings
|
||||
output_settings: OutputSettings = protocol_settings.output_settings
|
||||
@@ -702,17 +735,14 @@ class RelativeHybridTopologyProtocolUnit(gufe.ProtocolUnit):
|
||||
# Note: by default this is cached to ctx.shared/db.json so shouldn't
|
||||
# incur too large a cost
|
||||
self.logger.info("Parameterizing molecules")
|
||||
|
||||
# Start by assigning partial charges
|
||||
self._assign_partial_charges(charge_settings, off_small_mols)
|
||||
|
||||
# Then register all the templates
|
||||
for smc, mol in chain(off_small_mols['stateA'],
|
||||
off_small_mols['stateB'],
|
||||
off_small_mols['both']):
|
||||
# skip if we already have user charges
|
||||
if not (mol.partial_charges is not None and np.any(mol.partial_charges)):
|
||||
# due to issues with partial charge generation in ambertools
|
||||
# we default to using the input conformer for charge generation
|
||||
mol.assign_partial_charges(
|
||||
'am1bcc', use_conformers=mol.conformers
|
||||
)
|
||||
|
||||
system_generator.create_system(mol.to_topology().to_openmm(),
|
||||
molecules=[mol])
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ from openfe.protocols.openmm_utils.omm_settings import (
|
||||
OpenMMEngineSettings,
|
||||
OpenMMSolvationSettings,
|
||||
OutputSettings,
|
||||
OpenFFPartialChargeSettings,
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -133,6 +134,9 @@ class RelativeHybridTopologyProtocolSettings(Settings):
|
||||
solvation_settings: OpenMMSolvationSettings
|
||||
"""Settings for solvating the system."""
|
||||
|
||||
partial_charge_settings: OpenFFPartialChargeSettings
|
||||
"""Settings for assigning partial charges to small molecules."""
|
||||
|
||||
# Alchemical settings
|
||||
lambda_settings: LambdaSettings
|
||||
"""
|
||||
|
||||
@@ -64,6 +64,20 @@ class OpenMMSolvationSettings(BaseSolvationSettings):
|
||||
return v
|
||||
|
||||
|
||||
class BasePartialChargeSettings(SettingsBaseModel):
|
||||
"""
|
||||
Base class for partial charge assignment.
|
||||
"""
|
||||
class Config:
|
||||
arbitrary_types_allowed = True
|
||||
|
||||
|
||||
class OpenFFPartialChargeSettings(BasePartialChargeSettings):
|
||||
"""
|
||||
Empty placeholder class, will implement OpenFF partial charge assignment.
|
||||
"""
|
||||
|
||||
|
||||
class OpenMMEngineSettings(SettingsBaseModel):
|
||||
"""OpenMM MD engine settings"""
|
||||
|
||||
@@ -379,7 +393,7 @@ class MDOutputSettings(OutputSettings):
|
||||
# reporter settings
|
||||
production_trajectory_filename = 'simulation.xtc'
|
||||
"""Path to the storage file for analysis. Default 'simulation.xtc'."""
|
||||
trajectory_write_interval = 5000 * unit.timestep
|
||||
trajectory_write_interval: FloatQuantity['picosecond'] = 20 * unit.picosecond
|
||||
"""
|
||||
Frequency to write the xtc file. Default 5000 * unit.timestep.
|
||||
"""
|
||||
|
||||
@@ -102,6 +102,41 @@ def divmod_time(
|
||||
return iterations, remainder
|
||||
|
||||
|
||||
def divmod_time_and_check(numerator: unit.Quantity, denominator: unit.Quantity,
|
||||
numerator_name: str, denominator_name: str) -> int:
|
||||
"""Perform a division of time, failing if there is a remainder
|
||||
|
||||
For example numerator 20.0 ps and denominator 4.0 fs gives 5000
|
||||
|
||||
Parameters
|
||||
----------
|
||||
numerator, denominator : unit.Quantity
|
||||
the division to perform
|
||||
numerator_name, denominator_name : str
|
||||
used for the error generated if there is any remainder
|
||||
|
||||
Returns
|
||||
-------
|
||||
iterations : int
|
||||
the result of the division
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
if the division results in any remainder, will include a formatted error
|
||||
message
|
||||
"""
|
||||
its, rem = divmod_time(numerator, denominator)
|
||||
|
||||
if rem:
|
||||
errmsg = (f"The {numerator_name} ({numerator}) "
|
||||
"does not evenly divide by the "
|
||||
f"{denominator_name} ({denominator})")
|
||||
raise ValueError(errmsg)
|
||||
|
||||
return its
|
||||
|
||||
|
||||
def convert_checkpoint_interval_to_iterations(
|
||||
checkpoint_interval: unit.Quantity,
|
||||
time_per_iteration: unit.Quantity,
|
||||
@@ -152,17 +187,13 @@ def convert_steps_per_iteration(
|
||||
steps_per_iteration : int
|
||||
suitable for input to Integrator
|
||||
"""
|
||||
steps_per_iteration, rem = divmod_time(
|
||||
return divmod_time_and_check(
|
||||
simulation_settings.time_per_iteration,
|
||||
integrator_settings.timestep
|
||||
integrator_settings.timestep,
|
||||
"time_per_iteration",
|
||||
"timestep",
|
||||
)
|
||||
|
||||
if rem:
|
||||
raise ValueError(f"time_per_iteration ({simulation_settings.time_per_iteration}) "
|
||||
f"not divisible by timestep ({integrator_settings.timestep})")
|
||||
|
||||
return steps_per_iteration
|
||||
|
||||
|
||||
def convert_real_time_analysis_iterations(
|
||||
simulation_settings: MultiStateSimulationSettings,
|
||||
@@ -191,25 +222,19 @@ def convert_real_time_analysis_iterations(
|
||||
# option to turn off real time analysis
|
||||
return None, None
|
||||
|
||||
rta_its, rem = divmod_time(
|
||||
rta_its = divmod_time_and_check(
|
||||
simulation_settings.real_time_analysis_interval,
|
||||
simulation_settings.time_per_iteration,
|
||||
"real_time_analysis_interval",
|
||||
"time_per_iteration",
|
||||
)
|
||||
|
||||
if rem:
|
||||
raise ValueError(f"real_time_analysis_interval ({simulation_settings.real_time_analysis_interval}) "
|
||||
f"is not divisible by time_per_iteration ({simulation_settings.time_per_iteration})")
|
||||
|
||||
# convert RTA_minimum_time to iterations
|
||||
rta_min_its, rem = divmod_time(
|
||||
rta_min_its = divmod_time_and_check(
|
||||
simulation_settings.real_time_analysis_minimum_time,
|
||||
simulation_settings.time_per_iteration,
|
||||
"real_time_analysis_minimum_time",
|
||||
"time_per_iteration",
|
||||
)
|
||||
|
||||
if rem:
|
||||
raise ValueError(f"real_time_analysis_minimum_time ({simulation_settings.real_time_analysis_minimum_time}) "
|
||||
f"is not divisible by time_per_iteration ({simulation_settings.time_per_iteration})")
|
||||
|
||||
return rta_its, rta_min_its
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1415,7 +1415,7 @@ class TestProtocolResult:
|
||||
est = protocolresult.get_uncertainty()
|
||||
|
||||
assert est
|
||||
assert est.m == pytest.approx(0.1, abs=0.1)
|
||||
assert est.m == pytest.approx(0.1, abs=0.2)
|
||||
assert isinstance(est, unit.Quantity)
|
||||
assert est.is_compatible_with(unit.kilojoule_per_mole)
|
||||
|
||||
|
||||
@@ -54,8 +54,6 @@ class TestOMMSettingsFromStrings:
|
||||
assert s.equilibration_length == 2.5 * unit.nanosecond
|
||||
assert s.production_length == 10.0 * unit.nanosecond
|
||||
|
||||
# todo: checkpoint_interval IntQuantity
|
||||
|
||||
|
||||
class TestEquilRFESettingsFromString:
|
||||
def test_alchemical_settings(self):
|
||||
|
||||
@@ -412,7 +412,7 @@ def test_convert_steps_per_iteration_failure():
|
||||
timestep='3 fs'
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="not divisible"):
|
||||
with pytest.raises(ValueError, match="does not evenly divide"):
|
||||
settings_validation.convert_steps_per_iteration(sim, inty)
|
||||
|
||||
|
||||
@@ -440,7 +440,7 @@ def test_convert_real_time_analysis_iterations_interval_fail():
|
||||
real_time_analysis_minimum_time='500 ps',
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match='not divisible'):
|
||||
with pytest.raises(ValueError, match='does not evenly divide'):
|
||||
settings_validation.convert_real_time_analysis_iterations(sim)
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ def test_convert_real_time_analysis_iterations_min_interval_fail():
|
||||
real_time_analysis_minimum_time='500.5 ps',
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match='not divisible'):
|
||||
with pytest.raises(ValueError, match='does not evenly divide'):
|
||||
settings_validation.convert_real_time_analysis_iterations(sim)
|
||||
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class TestRelativeHybridTopologyProtocolResult(GufeTokenizableTestsMixin):
|
||||
|
||||
class TestRelativeHybridTopologyProtocol(GufeTokenizableTestsMixin):
|
||||
cls = openmm_rfe.RelativeHybridTopologyProtocol
|
||||
key = "RelativeHybridTopologyProtocol-b6ecbf56c5effbda11d3c10d13f37273"
|
||||
key = "RelativeHybridTopologyProtocol-d4a4ff534eee7c88d43334c18b428927"
|
||||
repr = f"<{key}>"
|
||||
|
||||
@pytest.fixture()
|
||||
|
||||
@@ -49,7 +49,7 @@ def protocol_result(afe_solv_transformation_json):
|
||||
|
||||
class TestAbsoluteSolvationProtocol(GufeTokenizableTestsMixin):
|
||||
cls = openmm_afe.AbsoluteSolvationProtocol
|
||||
key = "AbsoluteSolvationProtocol-1262bf8cac922568528648aff23a6e73"
|
||||
key = "AbsoluteSolvationProtocol-e8f575fbe609d60f7e2cb92391c4b83f"
|
||||
repr = f"<{key}>"
|
||||
|
||||
@pytest.fixture()
|
||||
@@ -85,7 +85,7 @@ class TestAbsoluteSolvationVacuumUnit(GufeTokenizableTestsMixin):
|
||||
|
||||
class TestAbsoluteSolvationProtocolResult(GufeTokenizableTestsMixin):
|
||||
cls = openmm_afe.AbsoluteSolvationProtocolResult
|
||||
key = "AbsoluteSolvationProtocolResult-c1e7524ca8cda3921c2cbd5b512d1cf0"
|
||||
key = "AbsoluteSolvationProtocolResult-9b699ca4772043b564468b815ea0851c"
|
||||
repr = f"<{key}>"
|
||||
|
||||
@pytest.fixture()
|
||||
|
||||
@@ -145,6 +145,7 @@ solvent lig_ejm_46 lig_jmc_28 23.65 0.03
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.parametrize('report', ["", "dg", "ddg"])
|
||||
def test_gather(results_dir, report):
|
||||
expected = {
|
||||
@@ -184,6 +185,7 @@ def test_generate_bad_legs_error_message(include):
|
||||
assert string in msg
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_missing_leg_error(results_dir):
|
||||
file_to_remove = "easy_rbfe_lig_ejm_31_complex_lig_ejm_42_complex.json"
|
||||
(pathlib.Path("results") / file_to_remove).unlink()
|
||||
@@ -197,6 +199,7 @@ def test_missing_leg_error(results_dir):
|
||||
assert "'lig_ejm_42'" in str(result.exception)
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_missing_leg_allow_partial(results_dir):
|
||||
file_to_remove = "easy_rbfe_lig_ejm_31_complex_lig_ejm_42_complex.json"
|
||||
(pathlib.Path("results") / file_to_remove).unlink()
|
||||
|
||||
Reference in New Issue
Block a user