From e15087ee77a0432344bdf4774eeab477dacddacd Mon Sep 17 00:00:00 2001 From: alexhroom Date: Tue, 14 Jan 2025 14:06:30 +0000 Subject: [PATCH] added constraint adjustment --- RATapi/utils/convert.py | 50 +++++++++++++++++- tests/test_convert.py | 11 ++++ .../test_data/R1DoubleBilayerVolumeModel.mat | Bin 0 -> 11117 bytes 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 tests/test_data/R1DoubleBilayerVolumeModel.mat diff --git a/RATapi/utils/convert.py b/RATapi/utils/convert.py index 1934dc63..2afdc32e 100644 --- a/RATapi/utils/convert.py +++ b/RATapi/utils/convert.py @@ -1,6 +1,7 @@ """Utilities for converting input files to Python `Project`s.""" import json +import warnings from collections.abc import Iterable from os import PathLike from pathlib import Path @@ -67,7 +68,7 @@ def zip_if_several(*params) -> Union[tuple, list[tuple]]: return [params] def read_param(names, constrs, values, fits): - """Read in a parameter list from the relevant keys. + """Read in a parameter list from the relevant keys, and fix constraints for non-fit parameters. Parameters ---------- @@ -77,10 +78,55 @@ def read_param(names, constrs, values, fits): Returns ------- - list + ClassList A list of all relevant parameters. """ + def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: float) -> tuple[float, float]: + """Check that constraints are valid and fix them if they aren't. + + RasCAL-1 allowed the constraints of non-fit parameters to be invalid, which means + we need to fix them here so that the project is valid. + + Parameters + ---------- + name: str + The name of the parameter. + constrs : tuple[float, float] + The constraints of the parameter (min and max, respectively) + value : float + The value of the parameter. + + Returns + ------- + tuple[float, float] + The adjusted constraints (identical to constrs if constraints were valid) + + """ + new_constrs = (min(constrs[0], value), max(constrs[1], value)) + if new_constrs[0] != constrs[0] or new_constrs[1] != constrs[1]: + warnings.warn( + f"The parameter {name} has invalid constraints," + " these have been adjusted to satisfy the current value of the parameter.", + stacklevel=2, + ) + return new_constrs + + # adjust invalid constraints + # if just one item in the classlist, these objects won't be in lists + if not isinstance(fit := mat_project[fits], Iterable): + if not fit: + mat_project[constrs] = fix_invalid_constraints( + mat_project[names], mat_project[constrs], mat_project[values] + ) + # else they will be iterable + else: + for i, fit in enumerate(mat_project[fits]): + if not fit: + mat_project[constrs][i] = fix_invalid_constraints( + mat_project[names][i], mat_project[constrs][i], mat_project[values][i] + ) + return ClassList( [ Parameter( diff --git a/tests/test_convert.py b/tests/test_convert.py index eef85c7e..32a0a5c0 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -99,6 +99,17 @@ def mock_load(ignored_filename, **ignored_settings): assert getattr(converted_project, class_list) == getattr(original_project, class_list) +def test_invalid_constraints(): + """Test that invalid constraints are fixed where necessary.""" + with pytest.warns( + match="The parameter Background parameter 1 has invalid constraints," + " these have been adjusted to satisfy the current value of the parameter." + ): + output_project = r1_to_project_class(pathlib.Path(TEST_DIR_PATH, "R1DoubleBilayerVolumeModel.mat")) + + assert output_project.background_parameters[0].min == output_project.background_parameters[0].value + + @pytest.mark.parametrize( "project", [ diff --git a/tests/test_data/R1DoubleBilayerVolumeModel.mat b/tests/test_data/R1DoubleBilayerVolumeModel.mat new file mode 100644 index 0000000000000000000000000000000000000000..c65b170add51820f07828bc7cedc26cf201cdf8e GIT binary patch literal 11117 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 ABme*a literal 0 HcmV?d00001