diff --git a/ratapi/examples/normal_reflectivity/DSPC_standard_layers.ipynb b/ratapi/examples/normal_reflectivity/DSPC_standard_layers.ipynb index 3110cfe5..060df721 100644 --- a/ratapi/examples/normal_reflectivity/DSPC_standard_layers.ipynb +++ b/ratapi/examples/normal_reflectivity/DSPC_standard_layers.ipynb @@ -85,7 +85,7 @@ " Parameter(name=\"SAM Heads Hydration\", min=10.0, value=45.45, max=50.0, fit=True, prior_type=\"gaussian\", mu=30.0, sigma=3.0),\n", " #\n", " Parameter(name=\"CW Thickness\", min=10.0, value=17.12, max=28.0, fit=True, prior_type=\"uniform\"),\n", - " Parameter(name=\"CW SLD\", min=0.0, value=0.0, max=1e-09, fit=False, prior_type=\"uniform\"),\n", + " Parameter(name=\"CW SLD\", min=0.0, value=0.0, max=0.0, fit=False, prior_type=\"uniform\"),\n", " Parameter(name=\"CW Hydration\", min=99.9, value=100.0, max=100.0, fit=False, prior_type=\"uniform\"),\n", " #\n", " Parameter(name=\"Bilayer Heads Thickness\", min=7.0, value=10.7, max=17.0, fit=True, prior_type=\"gaussian\", mu=10.0, sigma=2.0),\n", @@ -175,8 +175,8 @@ "outputs": [], "source": [ "del problem.scalefactors[0]\n", - "problem.scalefactors.append(name=\"Scalefactor 1\", min=0.05, value=0.10, max=0.2, fit=False)\n", - "problem.scalefactors.append(name=\"Scalefactor 2\", min=0.05, value=0.15, max=0.2, fit=False)\n", + "problem.scalefactors.append(name=\"Scalefactor 1\", min=0.05, value=0.10, max=0.2, fit=True)\n", + "problem.scalefactors.append(name=\"Scalefactor 2\", min=0.05, value=0.15, max=0.2, fit=True)\n", "\n", "# Now deal with the backgrounds\n", "del problem.backgrounds[0]\n", diff --git a/ratapi/examples/normal_reflectivity/DSPC_standard_layers.py b/ratapi/examples/normal_reflectivity/DSPC_standard_layers.py index 6962f036..260de48d 100644 --- a/ratapi/examples/normal_reflectivity/DSPC_standard_layers.py +++ b/ratapi/examples/normal_reflectivity/DSPC_standard_layers.py @@ -22,7 +22,7 @@ def DSPC_standard_layers(): problem.parameters.append(name="SAM Tails Hydration", min=1.0, value=5.252, max=50.0, fit=True) problem.parameters.append(name="SAM Roughness", min=1.0, value=5.64, max=15.0, fit=True) problem.parameters.append(name="CW Thickness", min=10.0, value=17.12, max=28.0, fit=True) - problem.parameters.append(name="CW SLD", min=0.0, value=0.0, max=1e-09, fit=False) + problem.parameters.append(name="CW SLD", min=0.0, value=0.0, max=0.0, fit=False) problem.parameters.append( name="SAM Heads Thickness", @@ -131,8 +131,8 @@ def DSPC_standard_layers(): # Set the scalefactors - use one for each contrast del problem.scalefactors[0] - problem.scalefactors.append(name="Scalefactor 1", min=0.05, value=0.10, max=0.2, fit=False) - problem.scalefactors.append(name="Scalefactor 2", min=0.05, value=0.15, max=0.2, fit=False) + problem.scalefactors.append(name="Scalefactor 1", min=0.05, value=0.10, max=0.2, fit=True) + problem.scalefactors.append(name="Scalefactor 2", min=0.05, value=0.15, max=0.2, fit=True) # Now deal with the backgrounds del problem.backgrounds[0] diff --git a/ratapi/inputs.py b/ratapi/inputs.py index bdfaddad..bdd96d38 100644 --- a/ratapi/inputs.py +++ b/ratapi/inputs.py @@ -3,6 +3,7 @@ import importlib import os import pathlib +import warnings from collections.abc import Callable import numpy as np @@ -10,7 +11,7 @@ import ratapi import ratapi.wrappers from ratapi.rat_core import Checks, Control, NameStore, ProblemDefinition -from ratapi.utils.enums import Calculations, Languages, LayerModels, TypeOptions +from ratapi.utils.enums import Calculations, Languages, LayerModels, Procedures, TypeOptions parameter_field = { "parameters": "params", @@ -137,13 +138,13 @@ def make_input(project: ratapi.Project, controls: ratapi.Controls) -> tuple[Prob The controls object used in the compiled RAT code. """ - problem = make_problem(project) + problem = make_problem(project, controls.procedure != Procedures.Calculate) cpp_controls = make_controls(controls) return problem, cpp_controls -def make_problem(project: ratapi.Project) -> ProblemDefinition: +def make_problem(project: ratapi.Project, validate_range: bool = False) -> ProblemDefinition: """Construct the problem input required for the compiled RAT code. Parameters @@ -351,26 +352,36 @@ def make_problem(project: ratapi.Project) -> ProblemDefinition: problem.domainContrastLayers = [ domain_contrast_model if domain_contrast_model else [] for domain_contrast_model in domain_contrast_models ] - problem.fitParams = [ - param.value - for class_list in ratapi.project.parameter_class_lists - for param in getattr(project, class_list) - if param.fit - ] - problem.fitLimits = [ - [param.min, param.max] - for class_list in ratapi.project.parameter_class_lists - for param in getattr(project, class_list) - if param.fit - ] - problem.priorNames = [ - param.name for class_list in ratapi.project.parameter_class_lists for param in getattr(project, class_list) - ] - problem.priorValues = [ - [prior_id[param.prior_type], param.mu, param.sigma] - for class_list in ratapi.project.parameter_class_lists - for param in getattr(project, class_list) - ] + + fit_params = [] + fit_limits = [] + prior_names = [] + prior_values = [] + problem.checks = Checks() + for class_list in ratapi.project.parameter_class_lists: + field = parameter_field[class_list] + check_list = [] + for param in getattr(project, class_list): + prior_names.append(param.name) + prior_values.append([prior_id[param.prior_type], param.mu, param.sigma]) + check_list.append(int(param.fit)) + if param.fit: + min_range = abs(param.value) * 1e-6 if param.value == 0 else 1e-6 + if validate_range and (param.max - param.min) < min_range: + warnings.warn( + f'{class_list.replace("_", " ").title()} "{param.name}" was removed from the ' + f"fit because its range is too small (< {min_range:g}).", + stacklevel=2, + ) + check_list[-1] = 0 + else: + fit_params.append(param.value) + fit_limits.append([param.min, param.max]) + setattr(problem.checks, field, check_list) + problem.fitParams = fit_params + problem.fitLimits = fit_limits + problem.priorNames = prior_names + problem.priorValues = prior_values # Names problem.names = NameStore() @@ -378,13 +389,6 @@ def make_problem(project: ratapi.Project) -> ProblemDefinition: setattr(problem.names, parameter_field[class_list], [param.name for param in getattr(project, class_list)]) problem.names.contrasts = [contrast.name for contrast in project.contrasts] - # Checks - problem.checks = Checks() - for class_list in ratapi.project.parameter_class_lists: - setattr( - problem.checks, parameter_field[class_list], [int(element.fit) for element in getattr(project, class_list)] - ) - check_indices(problem) return problem diff --git a/ratapi/run.py b/ratapi/run.py index d973d068..9a99224b 100644 --- a/ratapi/run.py +++ b/ratapi/run.py @@ -130,7 +130,9 @@ def run(project, controls): # Update parameter values in project for class_list in ratapi.project.parameter_class_lists: for index, value in enumerate(getattr(problem_definition, parameter_field[class_list])): - getattr(project, class_list)[index].value = value + param = getattr(project, class_list)[index] + param.fit = bool(getattr(problem_definition.checks, parameter_field[class_list])[index]) + param.value = value controls.delete_IPC() diff --git a/tests/test_data/R1DSPCBilayer.mat b/tests/test_data/R1DSPCBilayer.mat index 596eb814..87018fda 100644 Binary files a/tests/test_data/R1DSPCBilayer.mat and b/tests/test_data/R1DSPCBilayer.mat differ diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 242d7480..00a4df7f 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -483,6 +483,19 @@ def test_make_problem(test_project, test_problem, request) -> None: check_problem_equal(problem, test_problem) +def test_make_problem_validate_range(request) -> None: + """The problem should not contain fitted parameters with small range.""" + test_project = request.getfixturevalue("standard_layers_project") + + test_project.scalefactors.set_fields(0, min=10, value=10, max=10, fit=True) + problem = make_problem(test_project) + assert problem.checks.scalefactors[0] == 1 + + with pytest.warns(UserWarning, match="was removed from the fit because its range is too small \(< 1e-06\)"): + problem = make_problem(test_project, True) + assert problem.checks.scalefactors[0] == 0 + + @pytest.mark.parametrize("test_problem", ["standard_layers_problem", "custom_xy_problem", "domains_problem"]) class TestCheckIndices: """Tests for check_indices over a set of three test problems."""