From 06a7acb2879e9edc51fef9bf40f3861264578599 Mon Sep 17 00:00:00 2001 From: Paul Sharp <44529197+DrPaulSharp@users.noreply.github.com> Date: Tue, 24 Jun 2025 15:56:45 +0100 Subject: [PATCH] Adds controls option "numSimulationPoints" --- cpp/RAT | 2 +- cpp/includes/defines.h | 3 + cpp/rat.cpp | 29 +++++----- ratapi/controls.py | 13 ++++- ratapi/inputs.py | 1 + tests/test_controls.py | 127 +++++++++++++++++++++++------------------ tests/test_inputs.py | 5 +- 7 files changed, 108 insertions(+), 72 deletions(-) diff --git a/cpp/RAT b/cpp/RAT index 589c9871..cf81c8d0 160000 --- a/cpp/RAT +++ b/cpp/RAT @@ -1 +1 @@ -Subproject commit 589c987180f33f1e265537044a6497ef51f4ef09 +Subproject commit cf81c8d00f0d0348cbeb360446362f8381093203 diff --git a/cpp/includes/defines.h b/cpp/includes/defines.h index 02b5fa97..9e8a5ff1 100644 --- a/cpp/includes/defines.h +++ b/cpp/includes/defines.h @@ -590,6 +590,8 @@ procedure : str Which procedure RAT should execute. Can be 'calculate', 'simplex', 'de', 'ns', or 'dream'. calcSldDuringFit : bool Whether SLD will be calculated during fit (for live plotting etc.) +numSimulationPoints : int + The number of points used for a reflectivity simulation where no data is present. resampleMinAngle : float The upper threshold on the angle between three sampled points for resampling, in units of radians over pi. resampleNPoints : int @@ -663,6 +665,7 @@ struct Control { real_T propScale {}; real_T nsTolerance {}; boolean_T calcSldDuringFit {}; + real_T numSimulationPoints {}; real_T resampleMinAngle {}; real_T resampleNPoints {}; real_T updateFreq {}; diff --git a/cpp/rat.cpp b/cpp/rat.cpp index 3335e09a..ddec2782 100644 --- a/cpp/rat.cpp +++ b/cpp/rat.cpp @@ -318,6 +318,7 @@ RAT::Controls createControlsStruct(const Control& control) control_struct.propScale = control.propScale; control_struct.nsTolerance = control.nsTolerance; control_struct.calcSldDuringFit = control.calcSldDuringFit; + control_struct.numSimulationPoints = control.numSimulationPoints; control_struct.updateFreq = control.updateFreq; control_struct.updatePlotFreq = control.updatePlotFreq; control_struct.nSamples = control.nSamples; @@ -910,6 +911,7 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("propScale", &Control::propScale) .def_readwrite("nsTolerance", &Control::nsTolerance) .def_readwrite("calcSldDuringFit", &Control::calcSldDuringFit) + .def_readwrite("numSimulationPoints", &Control::numSimulationPoints) .def_readwrite("resampleMinAngle", &Control::resampleMinAngle) .def_readwrite("resampleNPoints", &Control::resampleNPoints) .def_readwrite("updateFreq", &Control::updateFreq) @@ -927,12 +929,12 @@ PYBIND11_MODULE(rat_core, m) { return py::make_tuple(ctrl.parallel, ctrl.procedure, ctrl.display, ctrl.xTolerance, ctrl.funcTolerance, ctrl.maxFuncEvals, ctrl.maxIterations, ctrl.populationSize, ctrl.fWeight, ctrl.crossoverProbability, ctrl.targetValue, ctrl.numGenerations, ctrl.strategy, ctrl.nLive, ctrl.nMCMC, ctrl.propScale, - ctrl.nsTolerance, ctrl.calcSldDuringFit, ctrl.resampleMinAngle, ctrl.resampleNPoints, + ctrl.nsTolerance, ctrl.calcSldDuringFit, ctrl.numSimulationPoints, ctrl.resampleMinAngle, ctrl.resampleNPoints, ctrl.updateFreq, ctrl.updatePlotFreq, ctrl.nSamples, ctrl.nChains, ctrl.jumpProbability, ctrl.pUnitGamma, ctrl.boundHandling, ctrl.adaptPCR, ctrl.IPCFilePath); }, [](py::tuple t) { // __setstate__ - if (t.size() != 29) + if (t.size() != 30) throw std::runtime_error("Encountered invalid state unpickling ProblemDefinition object!"); /* Create a new C++ instance */ @@ -956,17 +958,18 @@ PYBIND11_MODULE(rat_core, m) { ctrl.propScale = t[15].cast(); ctrl.nsTolerance = t[16].cast(); ctrl.calcSldDuringFit = t[17].cast(); - ctrl.resampleMinAngle = t[18].cast(); - ctrl.resampleNPoints = t[19].cast(); - ctrl.updateFreq = t[20].cast(); - ctrl.updatePlotFreq = t[21].cast(); - ctrl.nSamples = t[22].cast(); - ctrl.nChains = t[23].cast(); - ctrl.jumpProbability = t[24].cast(); - ctrl.pUnitGamma = t[25].cast(); - ctrl.boundHandling = t[26].cast(); - ctrl.adaptPCR = t[27].cast(); - ctrl.IPCFilePath = t[28].cast(); + ctrl.numSimulationPoints = t[18].cast(); + ctrl.resampleMinAngle = t[19].cast(); + ctrl.resampleNPoints = t[20].cast(); + ctrl.updateFreq = t[21].cast(); + ctrl.updatePlotFreq = t[22].cast(); + ctrl.nSamples = t[23].cast(); + ctrl.nChains = t[24].cast(); + ctrl.jumpProbability = t[25].cast(); + ctrl.pUnitGamma = t[26].cast(); + ctrl.boundHandling = t[27].cast(); + ctrl.adaptPCR = t[28].cast(); + ctrl.IPCFilePath = t[29].cast(); return ctrl; })); diff --git a/ratapi/controls.py b/ratapi/controls.py index d035a32d..917d78eb 100644 --- a/ratapi/controls.py +++ b/ratapi/controls.py @@ -20,7 +20,15 @@ from ratapi.utils.custom_errors import custom_pydantic_validation_error from ratapi.utils.enums import BoundHandling, Display, Parallel, Procedures, Strategies -common_fields = ["procedure", "parallel", "calcSldDuringFit", "resampleMinAngle", "resampleNPoints", "display"] +common_fields = [ + "procedure", + "parallel", + "calcSldDuringFit", + "numSimulationPoints", + "resampleMinAngle", + "resampleNPoints", + "display", +] update_fields = ["updateFreq", "updatePlotFreq"] fields = { "calculate": common_fields, @@ -53,6 +61,9 @@ class Controls(BaseModel, validate_assignment=True, extra="forbid", use_attribut calcSldDuringFit: bool = False """Whether SLD will be calculated during fit (for live plotting etc.)""" + numSimulationPoints: int = Field(500, ge=2) + """The number of points used for reflectivity simulations where no data is supplied.""" + resampleMinAngle: float = Field(0.9, le=1, gt=0) """The upper threshold on the angle between three sampled points for resampling, in units of radians over pi.""" diff --git a/ratapi/inputs.py b/ratapi/inputs.py index 5e5341d0..34b695d3 100644 --- a/ratapi/inputs.py +++ b/ratapi/inputs.py @@ -553,6 +553,7 @@ def make_controls(input_controls: ratapi.Controls) -> Control: controls.procedure = input_controls.procedure controls.parallel = input_controls.parallel controls.calcSldDuringFit = input_controls.calcSldDuringFit + controls.numSimulationPoints = input_controls.numSimulationPoints controls.resampleMinAngle = input_controls.resampleMinAngle controls.resampleNPoints = input_controls.resampleNPoints controls.display = input_controls.display diff --git a/tests/test_controls.py b/tests/test_controls.py index 75f5d705..3e668795 100644 --- a/tests/test_controls.py +++ b/tests/test_controls.py @@ -55,16 +55,17 @@ def setup_class(self): @pytest.fixture def table_str(self): table_str = ( - "+------------------+-----------+\n" - "| Property | Value |\n" - "+------------------+-----------+\n" - "| procedure | calculate |\n" - "| parallel | single |\n" - "| calcSldDuringFit | False |\n" - "| resampleMinAngle | 0.9 |\n" - "| resampleNPoints | 50 |\n" - "| display | iter |\n" - "+------------------+-----------+" + "+---------------------+-----------+\n" + "| Property | Value |\n" + "+---------------------+-----------+\n" + "| procedure | calculate |\n" + "| parallel | single |\n" + "| calcSldDuringFit | False |\n" + "| numSimulationPoints | 500 |\n" + "| resampleMinAngle | 0.9 |\n" + "| resampleNPoints | 50 |\n" + "| display | iter |\n" + "+---------------------+-----------+" ) return table_str @@ -74,6 +75,7 @@ def table_str(self): [ ("parallel", Parallel.Single), ("calcSldDuringFit", False), + ("numSimulationPoints", 500), ("resampleMinAngle", 0.9), ("resampleNPoints", 50), ("display", Display.Iter), @@ -89,6 +91,7 @@ def test_calculate_property_values(self, control_property: str, value: Any) -> N [ ("parallel", Parallel.Points), ("calcSldDuringFit", True), + ("numSimulationPoints", 10), ("resampleMinAngle", 0.2), ("resampleNPoints", 1), ("display", Display.Notify), @@ -212,22 +215,23 @@ def setup_class(self): @pytest.fixture def table_str(self): table_str = ( - "+------------------+---------+\n" - "| Property | Value |\n" - "+------------------+---------+\n" - "| procedure | simplex |\n" - "| parallel | single |\n" - "| calcSldDuringFit | False |\n" - "| resampleMinAngle | 0.9 |\n" - "| resampleNPoints | 50 |\n" - "| display | iter |\n" - "| xTolerance | 1e-06 |\n" - "| funcTolerance | 1e-06 |\n" - "| maxFuncEvals | 10000 |\n" - "| maxIterations | 1000 |\n" - "| updateFreq | 1 |\n" - "| updatePlotFreq | 20 |\n" - "+------------------+---------+" + "+---------------------+---------+\n" + "| Property | Value |\n" + "+---------------------+---------+\n" + "| procedure | simplex |\n" + "| parallel | single |\n" + "| calcSldDuringFit | False |\n" + "| numSimulationPoints | 500 |\n" + "| resampleMinAngle | 0.9 |\n" + "| resampleNPoints | 50 |\n" + "| display | iter |\n" + "| xTolerance | 1e-06 |\n" + "| funcTolerance | 1e-06 |\n" + "| maxFuncEvals | 10000 |\n" + "| maxIterations | 1000 |\n" + "| updateFreq | 1 |\n" + "| updatePlotFreq | 20 |\n" + "+---------------------+---------+" ) return table_str @@ -237,6 +241,7 @@ def table_str(self): [ ("parallel", Parallel.Single), ("calcSldDuringFit", False), + ("numSimulationPoints", 500), ("resampleMinAngle", 0.9), ("resampleNPoints", 50), ("display", Display.Iter), @@ -258,6 +263,7 @@ def test_simplex_property_values(self, control_property: str, value: Any) -> Non [ ("parallel", Parallel.Points), ("calcSldDuringFit", True), + ("numSimulationPoints", 10), ("resampleMinAngle", 0.2), ("resampleNPoints", 1), ("display", Display.Notify), @@ -375,6 +381,7 @@ def table_str(self): "| procedure | de |\n" "| parallel | single |\n" "| calcSldDuringFit | False |\n" + "| numSimulationPoints | 500 |\n" "| resampleMinAngle | 0.9 |\n" "| resampleNPoints | 50 |\n" "| display | iter |\n" @@ -396,6 +403,7 @@ def table_str(self): [ ("parallel", Parallel.Single), ("calcSldDuringFit", False), + ("numSimulationPoints", 500), ("resampleMinAngle", 0.9), ("resampleNPoints", 50), ("display", Display.Iter), @@ -417,6 +425,7 @@ def test_de_property_values(self, control_property: str, value: Any) -> None: [ ("parallel", Parallel.Points), ("calcSldDuringFit", True), + ("numSimulationPoints", 10), ("resampleMinAngle", 0.2), ("resampleNPoints", 1), ("display", Display.Notify), @@ -542,20 +551,21 @@ def setup_class(self): @pytest.fixture def table_str(self): table_str = ( - "+------------------+--------+\n" - "| Property | Value |\n" - "+------------------+--------+\n" - "| procedure | ns |\n" - "| parallel | single |\n" - "| calcSldDuringFit | False |\n" - "| resampleMinAngle | 0.9 |\n" - "| resampleNPoints | 50 |\n" - "| display | iter |\n" - "| nLive | 150 |\n" - "| nMCMC | 0 |\n" - "| propScale | 0.1 |\n" - "| nsTolerance | 0.1 |\n" - "+------------------+--------+" + "+---------------------+--------+\n" + "| Property | Value |\n" + "+---------------------+--------+\n" + "| procedure | ns |\n" + "| parallel | single |\n" + "| calcSldDuringFit | False |\n" + "| numSimulationPoints | 500 |\n" + "| resampleMinAngle | 0.9 |\n" + "| resampleNPoints | 50 |\n" + "| display | iter |\n" + "| nLive | 150 |\n" + "| nMCMC | 0 |\n" + "| propScale | 0.1 |\n" + "| nsTolerance | 0.1 |\n" + "+---------------------+--------+" ) return table_str @@ -565,6 +575,7 @@ def table_str(self): [ ("parallel", Parallel.Single), ("calcSldDuringFit", False), + ("numSimulationPoints", 500), ("resampleMinAngle", 0.9), ("resampleNPoints", 50), ("display", Display.Iter), @@ -584,6 +595,7 @@ def test_ns_property_values(self, control_property: str, value: Any) -> None: [ ("parallel", Parallel.Points), ("calcSldDuringFit", True), + ("numSimulationPoints", 10), ("resampleMinAngle", 0.2), ("resampleNPoints", 1), ("display", Display.Notify), @@ -708,22 +720,23 @@ def setup_class(self): @pytest.fixture def table_str(self): table_str = ( - "+------------------+---------+\n" - "| Property | Value |\n" - "+------------------+---------+\n" - "| procedure | dream |\n" - "| parallel | single |\n" - "| calcSldDuringFit | False |\n" - "| resampleMinAngle | 0.9 |\n" - "| resampleNPoints | 50 |\n" - "| display | iter |\n" - "| nSamples | 20000 |\n" - "| nChains | 10 |\n" - "| jumpProbability | 0.5 |\n" - "| pUnitGamma | 0.2 |\n" - "| boundHandling | reflect |\n" - "| adaptPCR | True |\n" - "+------------------+---------+" + "+---------------------+---------+\n" + "| Property | Value |\n" + "+---------------------+---------+\n" + "| procedure | dream |\n" + "| parallel | single |\n" + "| calcSldDuringFit | False |\n" + "| numSimulationPoints | 500 |\n" + "| resampleMinAngle | 0.9 |\n" + "| resampleNPoints | 50 |\n" + "| display | iter |\n" + "| nSamples | 20000 |\n" + "| nChains | 10 |\n" + "| jumpProbability | 0.5 |\n" + "| pUnitGamma | 0.2 |\n" + "| boundHandling | reflect |\n" + "| adaptPCR | True |\n" + "+---------------------+---------+" ) return table_str @@ -733,6 +746,7 @@ def table_str(self): [ ("parallel", Parallel.Single), ("calcSldDuringFit", False), + ("numSimulationPoints", 500), ("resampleMinAngle", 0.9), ("resampleNPoints", 50), ("display", Display.Iter), @@ -754,6 +768,7 @@ def test_dream_property_values(self, control_property: str, value: Any) -> None: [ ("parallel", Parallel.Points), ("calcSldDuringFit", True), + ("numSimulationPoints", 10), ("resampleMinAngle", 0.2), ("resampleNPoints", 1), ("display", Display.Notify), diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 4e65501a..2c207928 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -360,8 +360,9 @@ def standard_layers_controls(): controls.procedure = Procedures.Calculate controls.parallel = Parallel.Single controls.calcSldDuringFit = False + controls.numSimulationPoints = 500 controls.resampleMinAngle = 0.9 - controls.resampleNPoints = 50.0 + controls.resampleNPoints = 50 controls.display = Display.Iter controls.xTolerance = 1.0e-6 controls.funcTolerance = 1.0e-6 @@ -398,6 +399,7 @@ def custom_xy_controls(): controls.procedure = Procedures.Calculate controls.parallel = Parallel.Single controls.calcSldDuringFit = False + controls.numSimulationPoints = 500 controls.resampleMinAngle = 0.9 controls.resampleNPoints = 50.0 controls.display = Display.Iter @@ -756,6 +758,7 @@ def check_controls_equal(actual_controls, expected_controls) -> None: "procedure", "parallel", "calcSldDuringFit", + "numSimulationPoints", "resampleMinAngle", "resampleNPoints", "display",