-
Notifications
You must be signed in to change notification settings - Fork 10
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
Feature decouple somd 3 #29
base: feature-decouple-somd-3
Are you sure you want to change the base?
Feature decouple somd 3 #29
Conversation
…class _engine_runner_config. Write out the A3FE version, and make the output logs more concise and suitable for debugging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks! Just left a few comments.
self._stream_log_level = stream_log_level | ||
self._set_up_logging() | ||
|
||
def _set_up_logging(self) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can remove logging as it's not used. Thanks
config = self.get_config() | ||
with open(file_path, "w") as f: | ||
_yaml.safe_dump(config, f, default_flow_style=False) | ||
self._logger.info(f"Configuration dumped to {file_path}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can probably remove, thanks
""" | ||
pass | ||
|
||
def __eq__(self, other: object) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to define if this derived from pdyantic base model.
from ..run._logging_formatters import _A3feStreamFormatter | ||
|
||
|
||
class EngineRunnerConfig(ABC): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inherit from base model please.
|
||
from ._engine_runner_config import EngineRunnerConfig as _EngineRunnerConfig | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably shift the base class into the same file - have a think about it - up to you!
@@ -191,8 +192,8 @@ three replicates. Note that this is expected to produce an erroneously favourabl | |||
cfg.runtime_npt = 50 # ps | |||
cfg.ensemble_equilibration_time = 100 # ps | |||
calc = a3.Calculation(ensemble_size = 3) | |||
calc_set.setup(bound_leg_sysprep_config = cfg, free_leg_sysprep_config = cfg) | |||
calc_set.run(adaptive = False, runtime=0.1) # ns | |||
calc.setup(bound_leg_sysprep_config = cfg, free_leg_sysprep_config = cfg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
@@ -140,7 +141,7 @@ Individual Simulation settings | |||
------------------------------- | |||
|
|||
To customise the specifics of how each lambda window is run (e.g. timestep), you can use the ``set_simfile_option`` method. For example, to set the timestep to 2 fs, run | |||
``calc.set_simfile_option("timestep", "2 * femtosecond")``. This will change parameters from the defaults given in ``template_config.cfg`` in the ``input`` directory, and warn | |||
``calc.set_simfile_option("timestep", "2 * femtosecond")``. This will change parameters from the defaults generated by ``engine_config``, and warn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to update this entire function to update the configs for all sub-simulation runners. Please rename it to something more appropriate e.g. set_config_option and test that it updates all configs.
@@ -304,12 +305,12 @@ You can run sets of calculations using the :class:`a3fe.run.CalcSet` class. To d | |||
ABFE with Charged Ligands | |||
************************* | |||
|
|||
Since A3FE 0.2.0, ABFE calculations with charged ligands are supported using a co-alchemical ion approach. The charge of the ligand will be automatically detected, assuming that this is correctly specified in the input sdf. The only change in the input required is that the use of PME, rather than reaction field electrostatics, should be specified in ``template_config.cfg`` as e.g.: | |||
Since A3FE 0.2.0, ABFE calculations with charged ligands are supported using a co-alchemical ion approach. The charge of the ligand will be automatically detected, assuming that this is correctly specified in the input sdf. The only change in the input required is that the use of PME, rather than reaction field electrostatics, should be specified in ``somd_config.cfg`` as e.g.: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please change to SomdEngineConfig (or whatever it was). Thanks
The default `somd_config.cfg` uses reaction field instead of PME. This is faster (around twice as fast for some of our systems) and has been shown to give equivalent results for neutral ligands in RBFE calculations - see https://pubs.acs.org/doi/full/10.1021/acs.jcim.0c01424 . |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please also change to point to the the config class rather than the template. Thanks!
@@ -338,19 +338,6 @@ def run(self, runtime: float = 2.5) -> None: | |||
------- | |||
None | |||
""" | |||
# Need to make sure that runtime is a multiple of the time per cycle |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please ensure that the somd.cfg file is written out before here, to ensure that the most up-to-date options are written out before the simulation is run.
Hi Finlay, I hope you're doing well! So far, the template_config.cfg has been replaced with a Pydantic SomdConfig class, which can now be generated automatically when the simulation requires it. Additionally, we aim to avoid writing the somd.cfg file until it’s ready to be submitted to Slurm. The only place where the file should be written is in the run method of the simulation.py file. The workflow should only interact with the somd_config object, avoiding read from file. Information should flow one way: from the Pydantic classes to the files, without reading data back from files. To support this, I have removed all read_simfile_option calls and definitions, as well as the write_simfile_option function. However, the generated somd.cfg file is still missing some extended parameters, such as morph, top, crdFiles, and restraints. Perhaps in our next meeting, we can discuss this part in detail and explore potential solutions. I’d appreciate any advice you have. Thanks a lot for your support! Best regards |
Hi Roy, I'm doing well thanks, hope you are too! Thanks for all your work - this is looking great! Also thanks for the clear description of your changes and issues - let's discuss this tomorrow. |
a3fe/configuration/engine_config.py
Outdated
### Constraints ### | ||
constraint: str = _Field("hbonds", description="Constraint type, must be hbonds or allbonds") | ||
hydrogen_mass_factor: float = _Field(3.0, alias="hydrogen mass repartitioning factor", description="Hydrogen mass repartitioning factor") | ||
integrator: _Literal["langevinmiddle", "leapfrogverlet"] = _Field("langevinmiddle", description="Integration algorithm") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect!
a3fe/configuration/engine_config.py
Outdated
Set the number of cycles in the SOMD config file. | ||
### Non-Bonded Interactions ### | ||
cutoff_type: _Literal["cutoffperiodic", "PME"] = _Field("cutoffperiodic", description="Type of cutoff to use. Options: PME, cutoffperiodic") | ||
cutoff_distance: float = _Field(12.0, alias="cutoff distance", ge=6.0, le=18.0, description="Cutoff distance in angstroms (6-18). Default 12.0 for cutoffperiodic and 10.0 for PME") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We probably just want one default, otherwise we're going to need some more complex logic and may end up overwriting user input. Let's stick with the RF defaults of RF and 12 A.
a3fe/configuration/engine_config.py
Outdated
ligand_charge: int = _Field(0, description="Net charge of the ligand. If non-zero, must use PME for electrostatics.") | ||
charge_difference: int = _Field(0, description="Charge difference between the ligand and the system. If non-zero, must use co-alchemical ion approach.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the same for ABFE - let's just keep the ligand charge option. We can write it out to the config file as charge_difference = - ligand charge. Thanks
a3fe/configuration/engine_config.py
Outdated
default_factory=_get_default_lambda_array, | ||
description="Lambda array for alchemical perturbation, varies from 0.0 to 1.0 across stages" | ||
) | ||
lambda_val: _Optional[float] = _Field(None, description="Lambda value for current stage") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another example of something that we'd rather not expose to the user, since it varies based on the simulation. We need a good strategy for this in general (haven't got a good solution yet - let me know if you think of one!)
…d only in one place
…d only in one place
a3fe/run/stage.py
Outdated
@@ -175,6 +175,10 @@ def __init__( | |||
self.lam_vals = lambda_values | |||
else: | |||
self.lam_vals = self._get_lam_vals() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please modify this to completely remove any reference to "somd.cfg" which should only be written by the engine config before it is used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check why somd.cfg is appearing in the stage input directories - it shouldn't.
a3fe/run/stage.py
Outdated
|
||
if self.engine_config is not None: | ||
self.engine_config.lambda_values = self.lam_vals | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove all of this logic. Do not allow the user to pass a set of lambda values - this violates the single source of information principle because then the values are also in the config. Please just create a property which gets the lambda values from the engine config.
Please add a similar property for systemprepconfig, then delete the mapping dictionaries and replace with this functionality. Thanks!
…in-Du/a3fe into feature-decouple-somd-3
…s are only stored in one place.
…da stored in right place
for file in _glob.glob(f"{stage_input_dir}/lambda_0.0000/*"): | ||
_shutil.copy(file, stage_input_dir) | ||
if _os.path.basename(file) != "somd.cfg": | ||
_shutil.copy(file, stage_input_dir) | ||
for file in _glob.glob(f"{stage_input_dir}/lambda_*"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Beacuse somd.cfg would be generated by BSS automately, copy exclude somd.cfg if we dont want.
@@ -405,7 +403,8 @@ def failed(self) -> bool: | |||
|
|||
@property | |||
def slurm_output_files(self) -> _List[str]: | |||
return _glob.glob(f"{self.slurm_file_base}*") | |||
"""Get a list of all slurm output files for this simulation.""" | |||
return _glob.glob(_os.path.join(self.output_dir, "slurm-*.out")) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have analyses slurm and simulation slurm result files, just clearly use the correct to do analyse.
if lambda_values is not None: | ||
self.lam_vals = lambda_values | ||
if self.engine_config.lambda_values is None: | ||
self.engine_config.lambda_values = self.get_lambda_values | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the single source of information principle, the values are also in the config files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single source of information - always read the lambda values from the engine config, so make self.lam_vals a property which reads self.engine_config e.g.
@Property
def lam_vals(self):
return self.engine_config.lambda_values
def get_lambda_values(self) -> _List[float]: | ||
"""Return list of lambda values from the engine configuration.""" | ||
return _SomdConfig._from_config_file(_os.path.join(self.input_dir, "somd.cfg")).lambda_values | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A property which gets the lambda values from the engine config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this.
@@ -443,6 +440,9 @@ def test_setup_calc_stages(self, setup_calc): | |||
cfg.lambda_values[leg.leg_type][stage.stage_type] | |||
) | |||
assert lam_vals == expected_lam_vals | |||
|
|||
if leg.leg_type == a3.LegType.BOUND: | |||
assert stage.engine_config.boresch_restraints_dictionary is not None | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test restraint files would be generated from engine_config if bound leg.
@@ -53,7 +51,7 @@ def parameterise_input( | |||
parameterised_system : _BSS._SireWrappers._system.System | |||
Parameterised system. | |||
""" | |||
cfg = _ENGINE_TYPE_TO_SYSPREP_CONFIG[engine_type].load(input_dir, leg_type) | |||
cfg = _SomdSystemPreparationConfig.load(input_dir, leg_type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is no longer generic, but specific to SOMD - please make it generic again by reading the config from the passed engine type enum.
@@ -231,15 +231,15 @@ def setup( | |||
cfg = ( | |||
sysprep_config | |||
if sysprep_config is not None | |||
else _ENGINE_TYPE_TO_SYSPREP_CONFIG[self.engine_type]() | |||
else _SomdSystemPreparationConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please make generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
else self.engine_config.sysprep_config
@@ -673,9 +672,10 @@ def setup_stages( | |||
property_map={"velocity": "foo"}, | |||
) # We will run outside of BSS | |||
|
|||
# Copy input written by BSS to the stage input directory | |||
# Copy input written by BSS to the stage input directory, excluding only somd.cfg | |||
for file in _glob.glob(f"{stage_input_dir}/lambda_0.0000/*"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be clearer (please modify as required)
files = [
file for file in glob.glob(f"{stage_input_dir}/lambda_0.0000/*")
if not file.endswith(".cfg")
]
@@ -405,7 +403,8 @@ def failed(self) -> bool: | |||
|
|||
@property | |||
def slurm_output_files(self) -> _List[str]: | |||
return _glob.glob(f"{self.slurm_file_base}*") | |||
"""Get a list of all slurm output files for this simulation.""" | |||
return _glob.glob(_os.path.join(self.output_dir, "slurm-*.out")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please revert - we've fixed this now!
@@ -171,13 +167,10 @@ def __init__( | |||
) | |||
|
|||
if not self.loaded_from_pickle: | |||
if lambda_values is not None: | |||
self.lam_vals = lambda_values | |||
if self.engine_config.lambda_values is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this
@@ -229,24 +222,29 @@ def lam_windows(self) -> _List[_LamWindow]: | |||
return self._sub_sim_runners | |||
|
|||
@lam_windows.setter | |||
def legs(self, value) -> None: | |||
def lam_windows(self, value) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
@property | ||
def lam_val_weights(self) -> _List[float]: | ||
"""Return the weights for each lambda window. These are calculated | ||
according to how each windows contributes to the overall free energy | ||
estimate, as given by TI and the trapezoidal rule.""" | ||
lam_val_weights = [] | ||
for i, lam_val in enumerate(self.lam_vals): | ||
for i, lam_val in enumerate(self.engine_config.lambda_values): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If lam_vals is a property which reads self.engine_config, no need to change this. This may be clearer though - so up to you.
@@ -1228,7 +1204,7 @@ def update(self, save_name: str = "output_saved") -> None: | |||
self._logger.info("Deleting old lambda windows and creating new ones...") | |||
self._sub_sim_runners = [] | |||
lam_val_weights = self.lam_val_weights | |||
for i, lam_val in enumerate(self.lam_vals): | |||
for i, lam_val in enumerate(self.engine_config.lambda_values): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can also change back (if you want to)
Description
Replace template_config.cfg with Pydantic config
Todos
Remove the template_config.cfg and replace with a Pydantic SomdConfig class (which derived from a general EngineConfig class which can be configured in the code, or else read from a yaml/json file if not supplied.