Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding LambdaSettings object for AFE #681

Merged
merged 29 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3b221ad
Adding LambdaSettings object
hannahbaumann Jan 8, 2024
fff204f
change tests to have new lambda_settings
hannahbaumann Jan 8, 2024
95ee8b7
Validate n_replicas equals number windows
hannahbaumann Jan 8, 2024
3e6e73b
Change number lambda windows test
hannahbaumann Jan 8, 2024
75e36ea
small changes
hannahbaumann Jan 11, 2024
d6f4b70
list[float]
hannahbaumann Jan 11, 2024
4def050
adapt doc string lambda_x lists
hannahbaumann Jan 11, 2024
bf4d3b0
Add LambdaSettings to rfe
hannahbaumann Jan 11, 2024
2bc510b
Remove lambda_windows and lambda_functions from AlchemicalSettings
hannahbaumann Jan 11, 2024
8c94a91
PEP8 fixes
hannahbaumann Jan 11, 2024
d915504
Change errormsg validator
hannahbaumann Jan 11, 2024
babfa16
Add updated results json file
hannahbaumann Jan 11, 2024
774af86
Add validators and tests for monotonical lambda pathways and naked ch…
hannahbaumann Jan 11, 2024
ef55cc9
add autopydantic entries for LambdaSettings
hannahbaumann Jan 12, 2024
3a1a177
Move validator length lambda windows to protocol
hannahbaumann Jan 15, 2024
9c07d76
Validate the lambda schedule in the protocol
hannahbaumann Jan 16, 2024
7e44518
Change keys
hannahbaumann Jan 30, 2024
ad51ce8
Merge changes
hannahbaumann Jan 30, 2024
cf56f51
Change LambdaSettings defaults AHFE
hannahbaumann Jan 30, 2024
3d843eb
Changes validate_lambda_settings
hannahbaumann Jan 30, 2024
2694992
Change doc string LambdaSettings
hannahbaumann Jan 30, 2024
03c137a
Add warning for non-zero lambda_restraints in AHFE
hannahbaumann Jan 31, 2024
2ecbcc7
Remove extra line
hannahbaumann Jan 31, 2024
a82a679
adapt gen-serialized-results for new LambdaSettings
hannahbaumann Jan 31, 2024
29cc6d0
new MD results json
hannahbaumann Jan 31, 2024
ecee8a1
add new results json files ahfe and rhfe
hannahbaumann Jan 31, 2024
58c7ba8
Allow some variation in DG results json
hannahbaumann Feb 1, 2024
afd7a41
Update keys rfe and afe protocols
hannahbaumann Feb 1, 2024
e497cf5
Merge branch 'main' into lambda_settings_afe
richardjgowers Feb 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions openfe/protocols/openmm_afe/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
from openfe.protocols.openmm_afe.equil_afe_settings import (
SolvationSettings,
AlchemicalSamplerSettings, OpenMMEngineSettings,
IntegratorSettings, SimulationSettings,
IntegratorSettings, SimulationSettings, LambdaSettings,
)
from openfe.protocols.openmm_rfe._rfe_utils import compute
from ..openmm_utils import (
Expand Down Expand Up @@ -233,6 +233,7 @@ def _handle_settings(self):
* system_settings : SystemSettings
* solvation_settings : SolvationSettings
* alchemical_settings : AlchemicalSettings
* lambda_settings : LambdaSettings
* sampler_settings : AlchemicalSamplerSettings
* engine_settings : OpenMMEngineSettings
* integrator_settings : IntegratorSettings
Expand Down Expand Up @@ -406,14 +407,16 @@ def _get_lambda_schedule(
LambdaProtocol
"""
lambdas = dict()
n_elec = settings['alchemical_settings'].lambda_elec_windows
n_vdw = settings['alchemical_settings'].lambda_vdw_windows + 1
lambdas['lambda_electrostatics'] = np.concatenate(
[np.linspace(1, 0, n_elec), np.linspace(0, 0, n_vdw)[1:]]
)
lambdas['lambda_sterics'] = np.concatenate(
[np.linspace(1, 1, n_elec), np.linspace(1, 0, n_vdw)[1:]]
)

lambda_elec = settings['lambda_settings'].lambda_elec
lambda_vdw = settings['lambda_settings'].lambda_vdw

# Reverse lambda schedule since in AbsoluteAlchemicalFactory 1
# means fully interacting, not stateB
lambda_elec = [1-x for x in lambda_elec]
IAlibay marked this conversation as resolved.
Show resolved Hide resolved
lambda_vdw = [1-x for x in lambda_vdw]
lambdas['lambda_electrostatics'] = lambda_elec
lambdas['lambda_sterics'] = lambda_vdw

n_replicas = settings['sampler_settings'].n_replicas

Expand Down Expand Up @@ -761,11 +764,9 @@ def _run_simulation(
# minimize
if self.verbose:
self.logger.info("minimizing systems")

sampler.minimize(
max_iterations=settings['simulation_settings'].minimization_steps
)

# equilibrate
if self.verbose:
self.logger.info("equilibrating systems")
Expand All @@ -775,7 +776,6 @@ def _run_simulation(
# production
if self.verbose:
self.logger.info("running production phase")

sampler.extend(int(prod_steps / mc_steps)) # type: ignore

if self.verbose:
Expand Down
71 changes: 58 additions & 13 deletions openfe/protocols/openmm_afe/equil_afe_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,52 @@
class AlchemicalSettings(SettingsBaseModel):
"""Settings for the alchemical protocol

These settings describe the lambda schedule and the creation of the
hybrid system.
Empty place holder for right now.
"""

lambda_elec_windows = 12
"""Number of lambda electrostatic alchemical steps, default 12"""
lambda_vdw_windows = 12
"""Number of lambda vdw alchemical steps, default 12"""

@validator('lambda_elec_windows', 'lambda_vdw_windows')
def must_be_positive(cls, v):
if v <= 0:
errmsg = ("Number of lambda steps must be positive ")
raise ValueError(errmsg)
class LambdaSettings(SettingsBaseModel):
"""Settings for lambda schedule
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add more details about what the settings class does?

Defines lists of floats to control various aspects of the alchemical transformation.

Notes
--------
* In all cases a lambda value of 0 defines a fully interacting state A and a non-interacting state B, whilst a value of 1 defines a fully interacting state B and a non-interacting state A.
* ``lambda_elec``, `lambda_vdw``, and ``lambda_restraints`` must all be of the same length, defining all the windows of the transformation.

"""
lambda_elec: list[float] = [0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
"""
List of floats of lambda values for the electrostatics.
Zero means state A and 1 means state B.
Length of this list needs to match length of lambda_vdw and lambda_restraints.
"""
lambda_vdw: list[float] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5,
0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0]
"""
List of floats of lambda values for the van der Waals.
Zero means state A and 1 means state B.
Length of this list needs to match length of lambda_elec and lambda_restraints.
"""
lambda_restraints: list[float] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
"""
List of floats of lambda values for the restraints.
hannahbaumann marked this conversation as resolved.
Show resolved Hide resolved
Zero means state A and 1 means state B.
Length of this list needs to match length of lambda_vdw and lambda_elec.
"""

@validator('lambda_elec', 'lambda_vdw', 'lambda_restraints')
def must_be_between_0_and_1(cls, v):
richardjgowers marked this conversation as resolved.
Show resolved Hide resolved
for window in v:
if not 0 <= window <= 1:
errmsg = "Lambda windows must be between 0 and 1."
raise ValueError(errmsg)
return v

@validator('lambda_elec')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There may be a better way to compare different list lengths here?

Copy link
Contributor Author

@hannahbaumann hannahbaumann Jan 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validator currently would lead to errors when changing the settings from the default settings (since when changing lambda_elec the list of lambda_vdw has still the old length and therefore would raise an error. One could move the validator into the protocol instead, or do you have other suggestions @richardjgowers ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there might be a limit to what's possible in the @validator and instead you want a protocol function. The nice thing about these (when possible) is it fails fast & early.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the check to the _get_lambda_schedule function, or would it be better to have a separate _validator function there?

def must_have_equal_number_elec_vdw_restraints_windows(cls, v, values):
if 'lambda_vdw' in values and 'lambda_restraints' in values:
lambdas = [v, values['lambda_vdw'], values['lambda_restraints']]
it = iter(lambdas)
the_len = len(next(it))
if not all(len(l) == the_len for l in it):
raise ValueError('Components elec, vdw and restraints must have '
'equal amount of lambda windows')
return v


Expand Down Expand Up @@ -91,11 +123,16 @@ class Config:
# Alchemical settings
alchemical_settings: AlchemicalSettings
"""
Alchemical protocol settings including lambda windows.
Alchemical protocol settings.
"""
lambda_settings: LambdaSettings
"""
Settings for controlling the lambda schedule for the different components
(vdw, elec, restraints).
"""
alchemsampler_settings: AlchemicalSamplerSettings
"""
Settings for controling how we sample alchemical space, including the
Settings for controlling how we sample alchemical space, including the
number of repeats.
"""

Expand Down Expand Up @@ -129,3 +166,11 @@ class Config:
Simulation control settings, including simulation lengths and
record-keeping for the solvent transformation.
"""

def must_have_same_number_windows(cls, values):
n_elec = len(values['lambda_settings']['lambda_elec'])
n_replicas = values['alchemsampler_settings']['n_replicas']
if n_elec != n_replicas:
errmsg = "Number of lambda windows must equal number of replicas."
raise ValueError(errmsg)
return values
9 changes: 7 additions & 2 deletions openfe/protocols/openmm_afe/equil_solvation_afe_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
)
from openfe.protocols.openmm_afe.equil_afe_settings import (
AbsoluteSolvationSettings, SystemSettings,
SolvationSettings, AlchemicalSettings,
SolvationSettings, AlchemicalSettings, LambdaSettings,
AlchemicalSamplerSettings, OpenMMEngineSettings,
IntegratorSettings, SimulationSettings,
SettingsBaseModel,
Expand Down Expand Up @@ -404,8 +404,9 @@ def _default_settings(cls):
solvent_system_settings=SystemSettings(),
vacuum_system_settings=SystemSettings(nonbonded_method='nocutoff'),
alchemical_settings=AlchemicalSettings(),
lambda_settings=LambdaSettings(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be reasonable here to change the default to match what we usually run as a default when doing AHFEs, i.e. 14 windows?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, had changed that in the other PR, but not here, added this now!

alchemsampler_settings=AlchemicalSamplerSettings(
n_replicas=24,
n_replicas=20,
),
solvation_settings=SolvationSettings(),
vacuum_engine_settings=OpenMMEngineSettings(),
Expand Down Expand Up @@ -655,6 +656,7 @@ def _handle_settings(self) -> dict[str, SettingsBaseModel]:
* system_settings : SystemSettings
* solvation_settings : SolvationSettings
* alchemical_settings : AlchemicalSettings
* lambda_settings : LambdaSettings
* sampler_settings : AlchemicalSamplerSettings
* engine_settings : OpenMMEngineSettings
* integrator_settings : IntegratorSettings
Expand All @@ -668,6 +670,7 @@ def _handle_settings(self) -> dict[str, SettingsBaseModel]:
settings['system_settings'] = prot_settings.vacuum_system_settings
settings['solvation_settings'] = prot_settings.solvation_settings
settings['alchemical_settings'] = prot_settings.alchemical_settings
settings['lambda_settings'] = prot_settings.lambda_settings
settings['sampler_settings'] = prot_settings.alchemsampler_settings
settings['engine_settings'] = prot_settings.vacuum_engine_settings
settings['integrator_settings'] = prot_settings.integrator_settings
Expand Down Expand Up @@ -739,6 +742,7 @@ def _handle_settings(self) -> dict[str, SettingsBaseModel]:
* system_settings : SystemSettings
* solvation_settings : SolvationSettings
* alchemical_settings : AlchemicalSettings
* lambda_settings : LambdaSettings
* sampler_settings : AlchemicalSamplerSettings
* engine_settings : OpenMMEngineSettings
* integrator_settings : IntegratorSettings
Expand All @@ -752,6 +756,7 @@ def _handle_settings(self) -> dict[str, SettingsBaseModel]:
settings['system_settings'] = prot_settings.solvent_system_settings
settings['solvation_settings'] = prot_settings.solvation_settings
settings['alchemical_settings'] = prot_settings.alchemical_settings
settings['lambda_settings'] = prot_settings.lambda_settings
settings['sampler_settings'] = prot_settings.alchemsampler_settings
settings['engine_settings'] = prot_settings.solvent_engine_settings
settings['integrator_settings'] = prot_settings.integrator_settings
Expand Down
8 changes: 5 additions & 3 deletions openfe/protocols/openmm_rfe/equil_rfe_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@

from .equil_rfe_settings import (
RelativeHybridTopologyProtocolSettings, SystemSettings,
SolvationSettings, AlchemicalSettings,
SolvationSettings, AlchemicalSettings, LambdaSettings,
AlchemicalSamplerSettings, OpenMMEngineSettings,
IntegratorSettings, SimulationSettings
)
Expand Down Expand Up @@ -456,6 +456,7 @@ def _default_settings(cls):
system_settings=SystemSettings(),
solvation_settings=SolvationSettings(),
alchemical_settings=AlchemicalSettings(),
lambda_settings=LambdaSettings(),
alchemical_sampler_settings=AlchemicalSamplerSettings(),
engine_settings=OpenMMEngineSettings(),
integrator_settings=IntegratorSettings(),
Expand Down Expand Up @@ -626,6 +627,7 @@ def run(self, *, dry=False, verbose=True,
forcefield_settings: settings.OpenMMSystemGeneratorFFSettings = protocol_settings.forcefield_settings
thermo_settings: settings.ThermoSettings = protocol_settings.thermo_settings
alchem_settings: AlchemicalSettings = protocol_settings.alchemical_settings
lambda_settings: LambdaSettings = protocol_settings.lambda_settings
system_settings: SystemSettings = protocol_settings.system_settings
solvation_settings: SolvationSettings = protocol_settings.solvation_settings
sampler_settings: AlchemicalSamplerSettings = protocol_settings.alchemical_sampler_settings
Expand Down Expand Up @@ -793,8 +795,8 @@ def run(self, *, dry=False, verbose=True,
# TODO - this should be exposed to users, maybe we should offer the
# ability to print the schedule directly in settings?
lambdas = _rfe_utils.lambdaprotocol.LambdaProtocol(
functions=alchem_settings.lambda_functions,
windows=alchem_settings.lambda_windows
functions=lambda_settings.lambda_functions,
windows=lambda_settings.lambda_windows
)

# PR #125 temporarily pin lambda schedule spacing to n_replicas
Expand Down
27 changes: 20 additions & 7 deletions openfe/protocols/openmm_rfe/equil_rfe_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,33 @@
from pydantic import validator # type: ignore[assignment]


class AlchemicalSettings(SettingsBaseModel):
class LambdaSettings(SettingsBaseModel):
class Config:
extra = 'ignore'
arbitrary_types_allowed = True

"""Settings for the alchemical protocol

This describes the lambda schedule and the creation of the
hybrid system.
"""Settings for the lambda protocol
This describes the lambda schedule.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This describes the lambda schedule.
Lambda schedule settings.
Settings controlling the lambda schedule, these include the switching function type, and the number of windows.

Or something like that.

"""
# Lambda settings
lambda_functions = 'default'
"""
Key of which switching functions to use for alchemical mutation.
Default 'default'.
"""
lambda_windows = 11
"""Number of lambda windows to calculate. Default 11."""


class AlchemicalSettings(SettingsBaseModel):
class Config:
extra = 'ignore'
arbitrary_types_allowed = True

"""Settings for the alchemical protocol

This describes the creation of the hybrid system.
"""

unsampled_endstates = False
"""
Whether to have extra unsampled endstate windows for long range
Expand Down Expand Up @@ -117,9 +126,13 @@ class Config:
"""Settings for solvating the system."""

# Alchemical settings
lambda_settings: LambdaSettings
"""
Lambda protocol settings including lambda windows and lambda functions.
"""
alchemical_settings: AlchemicalSettings
"""
Alchemical protocol settings including lambda windows and soft core scaling.
Alchemical protocol settings including soft core scaling.
"""
alchemical_sampler_settings: AlchemicalSamplerSettings
"""
Expand Down
13 changes: 9 additions & 4 deletions openfe/tests/protocols/test_openmm_afe_slow.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ def test_openmm_run_engine(platform,
s.integrator_settings.n_steps = 5 * unit.timestep
s.vacuum_simulation_settings.checkpoint_interval = 5 * unit.timestep
s.solvent_simulation_settings.checkpoint_interval = 5 * unit.timestep
s.alchemsampler_settings.n_replicas = 14
s.alchemical_settings.lambda_elec_windows = 5
s.alchemical_settings.lambda_vdw_windows = 9
s.alchemsampler_settings.n_replicas = 20
s.lambda_settings.lambda_elec = \
[0.0, 0.25, 0.5, 0.75, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
s.lambda_settings.lambda_vdw = \
[0.0, 0.0, 0.0, 0.0, 0.0, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5,
0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 1.0]


protocol = openmm_afe.AbsoluteSolvationProtocol(
settings=s,
Expand Down Expand Up @@ -103,4 +108,4 @@ def test_openmm_run_engine(platform,
states = results.get_replica_states()
assert len(states.items()) == 2
assert len(states['solvent']) == 1
assert states['solvent'][0].shape[1] == 14
assert states['solvent'][0].shape[1] == 20
27 changes: 19 additions & 8 deletions openfe/tests/protocols/test_openmm_afe_solvation_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,28 @@ def test_create_default_settings():


@pytest.mark.parametrize('val', [
{'elec': 0, 'vdw': 5},
{'elec': -2, 'vdw': 5},
{'elec': 5, 'vdw': -2},
{'elec': 5, 'vdw': 0},
{'elec': [0.0, -1], 'vdw': [0.0, 1.0], 'restraints': [0.0, 1.0]},
{'elec': [0.0, 1.5], 'vdw': [0.0, 1.5], 'restraints': [-0.1, 1.0]}
])
def test_incorrect_window_settings(val, default_settings):
errmsg = "lambda steps must be positive"
alchem_settings = default_settings.alchemical_settings
errmsg = "Lambda windows must be between 0 and 1."
lambda_settings = default_settings.lambda_settings
with pytest.raises(ValueError, match=errmsg):
alchem_settings.lambda_elec_windows = val['elec']
alchem_settings.lambda_vdw_windows = val['vdw']
lambda_settings.lambda_elec = val['elec']
lambda_settings.lambda_vdw = val['vdw']
lambda_settings.lambda_restraints = val['restraints']

@pytest.mark.parametrize('val', [
{'elec': [0.0, 0.1, 1.0], 'vdw': [0.0, 1.0], 'restraints': [0.0, 1.0]},
])
def test_incorrect_number_windows(val, default_settings):
errmsg = "Components elec, vdw and restraints must have equal amount " \
"of lambda windows"
hannahbaumann marked this conversation as resolved.
Show resolved Hide resolved
lambda_settings = default_settings.lambda_settings
with pytest.raises(ValueError, match=errmsg):
lambda_settings.lambda_elec = val['elec']
lambda_settings.lambda_vdw = val['vdw']
lambda_settings.lambda_restraints = val['restraints']


def test_create_default_protocol(default_settings):
Expand Down
Loading