Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
50 changes: 48 additions & 2 deletions RATapi/utils/convert.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
----------
Expand All @@ -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(
Expand Down
11 changes: 11 additions & 0 deletions tests/test_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
[
Expand Down
Binary file added tests/test_data/R1DoubleBilayerVolumeModel.mat
Binary file not shown.
Loading