diff --git a/src/somd2/config/_config.py b/src/somd2/config/_config.py index 41bb8d4..3ab4449 100644 --- a/src/somd2/config/_config.py +++ b/src/somd2/config/_config.py @@ -688,6 +688,11 @@ def as_dict(self, sire_compatible=False): if value is None and sire_compatible: d[attr_l] = False + # Don't include lambda_schedule_name or perturbed_system_file in the dictionary, + # since these are just helper attributes. + d.pop("_lambda_schedule_name", None) + d.pop("_perturbed_system_file", None) + # Handle the lambda schedule separately so that we can use simplified # keyword options. @@ -716,7 +721,6 @@ def as_dict(self, sire_compatible=False): and self._perturbed_system_file is not None ): d["perturbed_system"] = str(self._perturbed_system_file) - d.pop("perturbed_system_file", None) return d diff --git a/src/somd2/runner/_base.py b/src/somd2/runner/_base.py index 00d4aa7..dcfd78c 100644 --- a/src/somd2/runner/_base.py +++ b/src/somd2/runner/_base.py @@ -1397,7 +1397,6 @@ def _compare_configs(config1, config2): "frame_frequency", "save_velocities", "perturbed_system", - "perturbed_system_file", "platform", "max_threads", "max_gpus", @@ -1434,51 +1433,61 @@ def _compare_configs(config1, config2): # Standard schedules are stored as strings, so we can compare these directly. if v1 == v2: continue - else: - try: - v1 = _Config._from_hex(v1) - except Exception as e: + try: + v1 = _Config._from_hex(v1) + except Exception as e: + raise ValueError( + f"Unable to deserialise lambda schedule from config1: {str(e)}" + ) + try: + v2 = _Config._from_hex(v2) + except Exception as e: + raise ValueError( + f"Unable to deserialise lambda schedule from config2: {str(e)}" + ) + if v1 != v2: + raise ValueError( + f"{key} has changed since the last run. This is not " + "allowed when using the restart option." + ) + continue + + # Restraints are stored as a list of hexadecimal strings of serialised objects. + # We need to deserialise them before comparison. + elif key == "restraints": + if v1 and v2: + if len(v1) != len(v2): raise ValueError( - f"Unable to deserialise lambda schedule from config1: {str(e)}" + f"Number of restraints has changed since the last run " + f"({len(v1)} vs {len(v2)}). This is not allowed when " + "using the restart option." ) + # Deserialise all restraints from both configs. try: - v2 = _Config._from_hex(v2) + deserialized_v1 = [_Config._from_hex(r) for r in v1] except Exception as e: raise ValueError( - f"Unable to deserialise lambda schedule from config2: {str(e)}" + f"Unable to deserialise restraint from config1: {str(e)}" ) - if v1 != v2: + try: + deserialized_v2 = [_Config._from_hex(r) for r in v2] + except Exception as e: raise ValueError( - f"{key} has changed since the last run. This is not " - "allowed when using the restart option." + f"Unable to deserialise restraint from config2: {str(e)}" ) - else: - continue - - # Restraints are stored as a list of hexadecimal strings of serialised objects. - # We need to deserialise them before comparison. - elif key == "restraints": - if v1 and v2: - for r1, r2 in zip(v1, v2): - try: - r1 = _Config._from_hex(r1) - except Exception as e: - raise ValueError( - f"Unable to deserialise restraint from config1: {str(e)}" - ) - try: - r2 = _Config._from_hex(r2) - except Exception as e: - raise ValueError( - f"Unable to deserialise restraint from config2: {str(e)}" - ) - if r1 != r2: + # Match each restraint in v1 against v2, regardless of order. + unmatched = list(deserialized_v2) + for r1 in deserialized_v1: + for i, r2 in enumerate(unmatched): + if r1 == r2: + unmatched.pop(i) + break + else: raise ValueError( f"{key} has changed since the last run. This is not " "allowed when using the restart option." ) - else: - continue + continue # Convert GeneralUnits to strings for comparison. if isinstance(v1, _GeneralUnit):