From 485ae5a15686cab1bac01b534a3be71bb757d2e8 Mon Sep 17 00:00:00 2001 From: Matthew Date: Mon, 6 Nov 2023 15:30:38 +0000 Subject: [PATCH] Added thorough testing for restarting, verifying that all options that cannot be changed between runs throw the relevant error. Options that cannot be changed are the following: 'runtime', 'restart', 'temperature', 'minimise', 'max_threads', 'equilibration_time', 'equilibration_timestep', 'energy_frequency', 'save_trajectory', 'frame_frequency', 'save_velocities', 'checkpoint_frequency', 'platform', 'max_threads', 'max_gpus', 'run_parallel', 'restart', 'save_trajectories', 'write_config', 'log_level', 'log_file', 'supress_overwrite_warning' --- src/somd2/runner/_runner.py | 93 ++++++++++++++++++++++++------- tests/runner/test_restart.py | 103 +++++++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+), 20 deletions(-) diff --git a/src/somd2/runner/_runner.py b/src/somd2/runner/_runner.py index 2b65b43..58e4f23 100644 --- a/src/somd2/runner/_runner.py +++ b/src/somd2/runner/_runner.py @@ -119,20 +119,13 @@ def __init__(self, system, config): if self._config.h_mass_factor > 1: self._repartition_h_mass() - # Check the output directories and create names of output files. - self._check_directory() - - # Save config whenever 'configure' is called to keep it up to date - if self._config.write_config: - _dict_to_yaml( - self._config.as_dict(), - self._config.output_directory, - self._fnames[self._lambda_values[0]]["config"], - ) - # Flag whether this is a GPU simulation. self._is_gpu = self._config.platform in ["cuda", "opencl", "hip"] + # Need to verify before doing any directory checks + if self._config.restart: + self._verify_restart_config() + # Setup proper logging level import sys @@ -145,6 +138,17 @@ def __init__(self, system, config): enqueue=True, ) + # Check the output directories and create names of output files. + self._check_directory() + + # Save config whenever 'configure' is called to keep it up to date + if self._config.write_config: + _dict_to_yaml( + self._config.as_dict(), + self._config.output_directory, + self._fnames[self._lambda_values[0]]["config"], + ) + def __str__(self): """Return a string representation of the object.""" return f"Runner(system={self._system}, config={self._config})" @@ -231,16 +235,65 @@ def _verify_restart_config(self): """ import yaml as _yaml - with open( - self._config.output_directory - / self._fnames[self._lambda_values[0]]["config"] - ) as file: - config = _yaml.safe_load(file) - if config != self._config.as_dict(): - raise ValueError( - "The configuration file does not match the configuration used to create the " - "checkpoint file." + def get_last_config(output_directory): + """ + Returns the last config file in the output directory. + """ + import os as _os + + config_files = [ + file + for file in _os.listdir(output_directory) + if file.endswith(".yaml") and file.startswith("config") + ] + config_files.sort() + return config_files[-1] + + try: + last_config = get_last_config(self._config.output_directory) + except IndexError: + raise IndexError( + f"No config files found in {self._config.output_directory}" ) + with open(self._config.output_directory / last_config) as file: + _logger.debug(f"Opening config file {last_config}") + config = _yaml.safe_load(file) + # Define the subset of settings that are allowed to change after restart + allowed_diffs = [ + "runtime", + "restart", + "temperature", + "minimise", + "max_threads", + "equilibration_time", + "equilibration_timestep", + "energy_frequency", + "save_trajectory", + "frame_frequency", + "save_velocities", + "checkpoint_frequency", + "platform", + "max_threads", + "max_gpus", + "run_parallel", + "restart", + "save_trajectories", + "write_config", + "log_level", + "log_file", + "supress_overwrite_warning", + ] + for key in config.keys(): + if key not in allowed_diffs: + _logger.debug(f"Checking {key}") + _logger.debug( + f"""old value: {config[key]} + new value: {self._config.as_dict()[key]}""" + ) + if config[key] != self._config.as_dict()[key]: + raise ValueError( + f"{key} has changed since the last run. This is not allowed when using the restart option." + ) def get_options(self): """ diff --git a/tests/runner/test_restart.py b/tests/runner/test_restart.py index 7a5af59..0065246 100644 --- a/tests/runner/test_restart.py +++ b/tests/runner/test_restart.py @@ -4,6 +4,7 @@ from somd2.io import * from pathlib import Path import sire as sr +import pytest def test_restart(): @@ -52,6 +53,7 @@ def test_restart(): "max_threads": 1, "num_lambda": 2, "supress_overwrite_warning": True, + "log_level": "DEBUG", } runner2 = Runner(mols, Config(**config_new)) @@ -72,6 +74,107 @@ def test_restart(): assert Path.exists(Path(tmpdir) / "traj_0.dcd") assert Path.exists(Path(tmpdir) / "traj_0_1.dcd") + config_difftimestep = config_new.copy() + config_difftimestep["runtime"] = "36fs" + config_difftimestep["timestep"] = "2fs" + + with pytest.raises(ValueError): + runner_timestep = Runner(mols, Config(**config_difftimestep)) + + config_diffscalefactor = config_new.copy() + config_diffscalefactor["runtime"] = "36fs" + config_diffscalefactor["charge_scale_factor"] = 0.5 + + with pytest.raises(ValueError): + runner_scalefactor = Runner(mols, Config(**config_diffscalefactor)) + + config_diffconstraint = config_new.copy() + config_diffconstraint["runtime"] = "36fs" + config_diffconstraint["constraint"] = "bonds" + + with pytest.raises(ValueError): + runner_constraints = Runner(mols, Config(**config_diffconstraint)) + + config_diffcoulombpower = config_new.copy() + config_diffcoulombpower["runtime"] = "36fs" + config_diffcoulombpower["coulomb_power"] = 0.5 + + with pytest.raises(ValueError): + runner_coulombpower = Runner(mols, Config(**config_diffcoulombpower)) + + config_diffcutofftype = config_new.copy() + config_diffcutofftype["runtime"] = "36fs" + config_diffcutofftype["cutoff_type"] = "rf" + + with pytest.raises(ValueError): + runner_cutofftype = Runner(mols, Config(**config_diffcutofftype)) + + config_diffhmassfactor = config_new.copy() + config_diffhmassfactor["runtime"] = "36fs" + config_diffhmassfactor["h_mass_factor"] = 2.0 + + with pytest.raises(ValueError): + runner_hmassfactor = Runner(mols, Config(**config_diffhmassfactor)) + + config_diffintegrator = config_new.copy() + config_diffintegrator["runtime"] = "36fs" + config_diffintegrator["integrator"] = "verlet" + + with pytest.raises(ValueError): + runner_integrator = Runner(mols, Config(**config_diffintegrator)) + + config_difflambdaschedule = config_new.copy() + config_difflambdaschedule["runtime"] = "36fs" + config_difflambdaschedule["charge_scale_factor"] = 0.5 + config_difflambdaschedule["lambda_schedule"] = "charge_scaled_morph" + + with pytest.raises(ValueError): + runner_lambdaschedule = Runner(mols, Config(**config_difflambdaschedule)) + + config_diffnumlambda = config_new.copy() + config_diffnumlambda["runtime"] = "36fs" + config_diffnumlambda["num_lambda"] = 3 + + with pytest.raises(ValueError): + runner_numlambda = Runner(mols, Config(**config_diffnumlambda)) + + config_diffoutputdirectory = config_new.copy() + config_diffoutputdirectory["runtime"] = "36fs" + config_diffoutputdirectory["output_directory"] = "test" + + with pytest.raises(IndexError): + runner_outputdirectory = Runner(mols, Config(**config_diffoutputdirectory)) + + config_diffperturbableconstraint = config_new.copy() + config_diffperturbableconstraint["runtime"] = "36fs" + config_diffperturbableconstraint["perturbable_constraint"] = "bonds" + + with pytest.raises(ValueError): + runner_perturbableconstraint = Runner( + mols, Config(**config_diffperturbableconstraint) + ) + + config_diffpressure = config_new.copy() + config_diffpressure["runtime"] = "36fs" + config_diffpressure["pressure"] = "1.5 atm" + + with pytest.raises(ValueError): + runner_pressure = Runner(mols, Config(**config_diffpressure)) + + config_diffshiftdelta = config_new.copy() + config_diffshiftdelta["runtime"] = "36fs" + config_diffshiftdelta["shift_delta"] = "3 Angstrom" + + with pytest.raises(ValueError): + runner_shiftdelta = Runner(mols, Config(**config_diffshiftdelta)) + + config_diffswapendstates = config_new.copy() + config_diffswapendstates["runtime"] = "36fs" + config_diffswapendstates["swap_end_states"] = True + + with pytest.raises(ValueError): + runner_swapendstates = Runner(mols, Config(**config_diffswapendstates)) + if __name__ == "__main__": test_restart()