diff --git a/cpp/RAT b/cpp/RAT index 16f3ebef..aae3dc14 160000 --- a/cpp/RAT +++ b/cpp/RAT @@ -1 +1 @@ -Subproject commit 16f3ebef737f4c85ca601046f7a30ffabee4eb47 +Subproject commit aae3dc141b6a10c6e10dfb47cd62e07a2a11857d diff --git a/ratapi/inputs.py b/ratapi/inputs.py index 890066f9..7d0a8720 100644 --- a/ratapi/inputs.py +++ b/ratapi/inputs.py @@ -302,7 +302,7 @@ def make_problem(project: ratapi.Project) -> ProblemDefinition: problem.numberOfContrasts = len(project.contrasts) problem.geometry = project.geometry problem.useImaginary = project.absorption - problem.repeatLayers = [1] * len(project.contrasts) + problem.repeatLayers = [contrast.repeat_layers for contrast in project.contrasts] problem.contrastBackgroundParams = contrast_background_params problem.contrastBackgroundTypes = contrast_background_types problem.contrastBackgroundActions = [contrast.background_action for contrast in project.contrasts] diff --git a/ratapi/models.py b/ratapi/models.py index e882af65..7d0d4f74 100644 --- a/ratapi/models.py +++ b/ratapi/models.py @@ -163,6 +163,8 @@ class Contrast(RATModel): The name of the instrument resolution for this contrast. resample : bool Whether adaptive resampling should be used for interface microslicing. + repeat_layers : int + For standard layers, the number of times the set of layers defined in the model should be repeated. model : list[str] If this is a standard layers model, this should be a list of layer names that make up the slab model for this contrast. @@ -180,6 +182,7 @@ class Contrast(RATModel): scalefactor: str = "" resolution: str = "" resample: bool = False + repeat_layers: int = Field(default=1, gt=0) model: list[str] = [] @model_validator(mode="before") @@ -208,6 +211,7 @@ def __str__(self): self.scalefactor, self.resolution, self.resample, + self.repeat_layers, model_entry, ] ) @@ -238,6 +242,8 @@ class ContrastWithRatio(RATModel): The name of the instrument resolution for this contrast. resample : bool Whether adaptive resampling should be used for interface microslicing. + repeat_layers : int + For standard layers, the number of times the set of layers defined in the model should be repeated. domain_ratio : str The name of the domain ratio parameter describing how the first domain should be weighted relative to the second. @@ -258,6 +264,7 @@ class ContrastWithRatio(RATModel): scalefactor: str = "" resolution: str = "" resample: bool = False + repeat_layers: int = Field(default=1, gt=0) domain_ratio: str = "" model: list[str] = [] @@ -276,6 +283,8 @@ def __str__(self): self.scalefactor, self.resolution, self.resample, + self.repeat_layers, + self.domain_ratio, model_entry, ] ) diff --git a/ratapi/project.py b/ratapi/project.py index 860f23b7..88555539 100644 --- a/ratapi/project.py +++ b/ratapi/project.py @@ -361,8 +361,8 @@ def model_post_init(self, __context: Any) -> None: """Set up the Class to protect against disallowed modification. We initialise the class handle in the ClassLists for empty data fields, set protected parameters, get names of - all defined parameters, determine the contents of the "model" field in contrasts, - and wrap ClassList routines to control revalidation. + all defined parameters, determine the contents of the "model" field in contrasts, and wrap ClassList routines + to control revalidation. """ # Ensure all ClassLists have the correct _class_handle defined for field in (fields := Project.model_fields): @@ -454,6 +454,33 @@ def set_layers(self) -> "Project": self.layers.data = [] return self + @model_validator(mode="after") + def set_repeat_layers(self) -> "Project": + """If we are not using a standard layers model, warn that the repeat layers setting is not valid.""" + if self.model != LayerModels.StandardLayers: + for contrast in self.contrasts: + if "repeat_layers" in contrast.model_fields_set and contrast.repeat_layers != 1: + warnings.warn( + 'For a custom layers or custom XY calculation, the "repeat_layers" setting for each ' + "contrast is not valid - resetting to 1.", + stacklevel=2, + ) + contrast.repeat_layers = 1 + return self + + @model_validator(mode="after") + def set_resample(self) -> "Project": + """If we are using a custom XY model, warn that the resample setting for each contrast must always be True.""" + if self.model == LayerModels.CustomXY: + for contrast in self.contrasts: + if "resample" in contrast.model_fields_set and contrast.resample is False: + warnings.warn( + 'For a custom XY calculation, "resample" must be True for each contrast - resetting to True.', + stacklevel=2, + ) + contrast.resample = True + return self + @model_validator(mode="after") def set_calculation(self) -> "Project": """Apply the calc setting to the project.""" diff --git a/tests/test_inputs.py b/tests/test_inputs.py index db5b6646..e7a1560e 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -306,7 +306,7 @@ def custom_xy_problem(test_names, test_checks): problem.contrastResolutionTypes = ["constant"] problem.contrastCustomFiles = [1] problem.contrastDomainRatios = [0] - problem.resample = [False] + problem.resample = [True] problem.dataPresent = [0] problem.data = [np.empty([0, 6])] problem.dataLimits = [[0.0, 0.0]] diff --git a/tests/test_project.py b/tests/test_project.py index c2ea92a2..7683dec9 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -443,6 +443,46 @@ def test_set_layers(project_parameters: dict) -> None: assert project.layers == [] +@pytest.mark.parametrize( + "project_parameters", + [ + ( + { + "model": LayerModels.CustomLayers, + "contrasts": [ratapi.models.Contrast(name="Test Contrast", repeat_layers=2)], + } + ), + ({"model": LayerModels.CustomXY, "contrasts": [ratapi.models.Contrast(name="Test Contrast", repeat_layers=2)]}), + ], +) +def test_set_repeat_layers(project_parameters: dict) -> None: + """If we are using a custom layers of custom XY model, the "resample" field of all the contrasts should always + be 1.""" + with pytest.warns( + match='For a custom layers or custom XY calculation, the "repeat_layers" setting for each ' + "contrast is not valid - resetting to 1." + ): + project = ratapi.Project(**project_parameters) + assert all(contrast.repeat_layers == 1 for contrast in project.contrasts) + + +@pytest.mark.parametrize( + "project_parameters", + [ + ({"model": LayerModels.CustomXY, "contrasts": [ratapi.models.Contrast(name="Test Contrast")]}), + ], +) +def test_set_resample(project_parameters: dict) -> None: + """If we are using a custom XY model, the "resample" field of all the contrasts should always be True.""" + project = ratapi.Project(**project_parameters) + assert all(contrast.resample for contrast in project.contrasts) + with pytest.warns( + match='For a custom XY calculation, "resample" must be True for each contrast - resetting to True.' + ): + project.contrasts.append(name="New Contrast", resample=False) + assert all(contrast.resample for contrast in project.contrasts) + + @pytest.mark.parametrize( ["input_calculation", "input_contrast", "new_calculation", "new_contrast_model", "num_domain_ratios"], [