diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 6d6c896a..7f0205b5 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -15,7 +15,7 @@ jobs: env: CIBW_SKIP: 'pp*' CIBW_ARCHS: 'auto64' - CIBW_PROJECT_REQUIRES_PYTHON: '>=3.9' + CIBW_PROJECT_REQUIRES_PYTHON: '>=3.10' CIBW_TEST_REQUIRES: 'pytest' defaults: run: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 99582ff8..8911dfdc 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -20,7 +20,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-13] - version: ["3.9", "3.13"] + version: ["3.10", "3.13"] defaults: run: shell: bash -l {0} @@ -53,5 +53,5 @@ jobs: - name: Install and Test with pytest run: | export PATH="$pythonLocation:$PATH" - python -m pip install -e .[Dev] + python -m pip install -e .[Dev,Orso] pytest tests/ --cov=RATapi --cov-report=term diff --git a/.gitignore b/.gitignore index 093e835f..fde40baa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,17 @@ __pycache__/ .idea .vscode +# direnv +.envrc + # Unit test / coverage reports htmlcov/ .coverage .cache/ +# Temp files +tmp/ + # Build _build docs/.buildinfo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 157a515f..6980c2c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ issue has not been reported already) or submitting a pull request. Create Developer Environment ---------------------------- -This project targets Python 3.9 or later. Install an appropriate version of Python and other dependencies +This project targets Python 3.10 or later. Install an appropriate version of Python and other dependencies Then create a fork of the python-RAT repo, and clone the fork diff --git a/MANIFEST.in b/MANIFEST.in index 086d5f71..a4a255ab 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include README.md -recursive-include cpp/RAT * +recursive-include cpp * prune tests prune */__pycache__ global-exclude .git diff --git a/RATapi/__init__.py b/RATapi/__init__.py index a8ce43b3..3d74927d 100644 --- a/RATapi/__init__.py +++ b/RATapi/__init__.py @@ -1,3 +1,7 @@ +"""RATapi is a Python package for modelling, fitting and optimising reflectivity problems.""" + +from contextlib import suppress + import RATapi.examples as examples from RATapi import events, models from RATapi.classlist import ClassList @@ -6,4 +10,7 @@ from RATapi.run import run from RATapi.utils import convert, plotting +with suppress(ImportError): # orsopy is an optional dependency + from RATapi.utils import orso as orso + __all__ = ["examples", "models", "events", "ClassList", "Controls", "Project", "run", "plotting", "convert"] diff --git a/RATapi/classlist.py b/RATapi/classlist.py index 6c050daa..f0a61d3b 100644 --- a/RATapi/classlist.py +++ b/RATapi/classlist.py @@ -1,6 +1,4 @@ -"""The classlist module. Contains the ClassList class, which defines a list containing instances of a particular -class. -""" +"""The ClassList class, which defines a list containing instances of a particular class.""" import collections import contextlib @@ -24,12 +22,12 @@ class ClassList(collections.UserList, Generic[T]): attribute given in the ClassList's "name_field" attribute (the default is "name"), the ClassList will ensure that all objects within the ClassList have unique values for that attribute. It is then possible to use this attribute of an object in the .remove(), .count(), and .index() routines in place of the full object. Due to the requirement - of unique values of the name_field attribute, the multiplication operators __mul__, __rmul__, and __imul__ have + of unique values of the ``name_field`` attribute, the multiplication operators __mul__, __rmul__, and __imul__ have been disabled, since they cannot allow for unique attribute values by definition. We extend the UserList class to enable objects to be added and modified using just the keyword arguments, enable - the object name_field attribute to be used in place of the full object, and ensure all elements are of the - specified type, with unique name_field attributes defined. + the object ``name_field`` attribute to be used in place of the full object, and ensure all elements are of the + specified type, with unique ``name_field`` attributes defined. Parameters ---------- @@ -171,8 +169,9 @@ def __imul__(self, n: int) -> None: raise TypeError(f"unsupported operand type(s) for *=: '{self.__class__.__name__}' and '{n.__class__.__name__}'") def append(self, obj: T = None, **kwargs) -> None: - """Append a new object to the ClassList using either the object itself, or keyword arguments to set attribute - values. + """Append a new object to the ClassList. + + This method can use the object itself, or can provide attribute values as keyword arguments for a new object. Parameters ---------- @@ -184,7 +183,7 @@ def append(self, obj: T = None, **kwargs) -> None: Raises ------ ValueError - Raised if the input arguments contain a name_field value already defined in the ClassList. + Raised if the input arguments contain a ``name_field`` value already defined in the ClassList. Warnings -------- @@ -216,8 +215,9 @@ def append(self, obj: T = None, **kwargs) -> None: self.data.append(self._class_handle(**kwargs)) def insert(self, index: int, obj: T = None, **kwargs) -> None: - """Insert a new object into the ClassList at a given index using either the object itself, or keyword arguments - to set attribute values. + """Insert a new object at a given index. + + This method can use the object itself, or can provide attribute values as keyword arguments for a new object. Parameters ---------- @@ -231,7 +231,7 @@ def insert(self, index: int, obj: T = None, **kwargs) -> None: Raises ------ ValueError - Raised if the input arguments contain a name_field value already defined in the ClassList. + Raised if the input arguments contain a ``name_field`` value already defined in the ClassList. Warnings -------- @@ -263,20 +263,25 @@ def insert(self, index: int, obj: T = None, **kwargs) -> None: self.data.insert(index, self._class_handle(**kwargs)) def remove(self, item: Union[T, str]) -> None: - """Remove an object from the ClassList using either the object itself or its name_field value.""" + """Remove an object from the ClassList using either the object itself or its ``name_field`` value.""" item = self._get_item_from_name_field(item) self.data.remove(item) def count(self, item: Union[T, str]) -> int: - """Return the number of times an object appears in the ClassList using either the object itself or its - name_field value. + """Return the number of times an object appears in the ClassList. + + This method can use either the object itself or its ``name_field`` value. + """ item = self._get_item_from_name_field(item) return self.data.count(item) def index(self, item: Union[T, str], offset: bool = False, *args) -> int: - """Return the index of a particular object in the ClassList using either the object itself or its - name_field value. If offset is specified, add one to the index. This is used to account for one-based indexing. + """Return the index of a particular object in the ClassList. + + This method can use either the object itself or its ``name_field`` value. + If offset is specified, add one to the index. This is used to account for one-based indexing. + """ item = self._get_item_from_name_field(item) return self.data.index(item, *args) + int(offset) @@ -291,11 +296,33 @@ def extend(self, other: Sequence[T]) -> None: self._check_unique_name_fields(other) self.data.extend(other) - def set_fields(self, index: int, **kwargs) -> None: + def union(self, other: Sequence[T]) -> None: + """Extend the ClassList by a sequence, ignoring input items with names that already exist.""" + if other and not (isinstance(other, Sequence) and not isinstance(other, str)): + other = [other] + + self.extend( + [ + item + for item in other + if hasattr(item, self.name_field) and getattr(item, self.name_field) not in self.get_names() + ] + ) + + def set_fields(self, index: Union[int, slice, str, T], **kwargs) -> None: """Assign the values of an existing object's attributes using keyword arguments.""" self._validate_name_field(kwargs) pydantic_object = False + # Find index if name or object is supplied + if isinstance(index, (str, self._class_handle)): + index = self.index(index) + + # Prioritise changing language to avoid CustomFile validator bug + value = kwargs.pop("language", None) + if value is not None: + kwargs = {"language": value, **kwargs} + if importlib.util.find_spec("pydantic"): # Pydantic is installed, so set up a context manager that will # suppress custom validation errors until all fields have been set. @@ -357,12 +384,12 @@ def __exit__(self, exctype, excinst, exctb): self._class_handle.model_validate(self.data[index]) def get_names(self) -> list[str]: - """Return a list of the values of the name_field attribute of each class object in the list. + """Return a list of the values of the ``name_field`` attribute of each class object in the list. Returns ------- names : list [str] - The value of the name_field attribute of each object in the ClassList. + The value of the ``name_field`` attribute of each object in the ClassList. """ return [getattr(model, self.name_field) for model in self.data if hasattr(model, self.name_field)] @@ -389,8 +416,7 @@ def get_all_matches(self, value: Any) -> list[tuple]: ] def _validate_name_field(self, input_args: dict[str, Any]) -> None: - """Raise a ValueError if the name_field attribute is passed as an object parameter, and its value is already - used within the ClassList. + """Raise a ValueError if the user tries to add an object with a ``name_field`` already in the ClassList. Parameters ---------- @@ -400,7 +426,7 @@ def _validate_name_field(self, input_args: dict[str, Any]) -> None: Raises ------ ValueError - Raised if the input arguments contain a name_field value already defined in the ClassList. + Raised if the input arguments contain a ``name_field`` value already defined in the ClassList. """ names = [name.lower() for name in self.get_names()] @@ -413,8 +439,7 @@ def _validate_name_field(self, input_args: dict[str, Any]) -> None: ) def _check_unique_name_fields(self, input_list: Sequence[T]) -> None: - """Raise a ValueError if any value of the name_field attribute is used more than once in a list of class - objects. + """Raise a ValueError if any value of the ``name_field`` attribute is repeated in a list of class objects. Parameters ---------- @@ -470,17 +495,17 @@ def _check_unique_name_fields(self, input_list: Sequence[T]) -> None: ) def _check_classes(self, input_list: Sequence[T]) -> None: - """Raise a ValueError if any object in a list of objects is not of the type specified by self._class_handle. + """Raise a ValueError if any object in a list of objects is not of the type specified by ``self._class_handle``. Parameters ---------- input_list : iterable - A list of instances of the class given in self._class_handle. + A list of instances of the class given in ``self._class_handle``. Raises ------ ValueError - Raised if the input list contains objects of any type other than that given in self._class_handle. + If the input list contains objects of any type other than that given in ``self._class_handle``. """ error_list = [] @@ -495,18 +520,18 @@ def _check_classes(self, input_list: Sequence[T]) -> None: ) def _get_item_from_name_field(self, value: Union[T, str]) -> Union[T, str]: - """Return the object with the given value of the name_field attribute in the ClassList. + """Return the object with the given value of the ``name_field`` attribute in the ClassList. Parameters ---------- value : T or str - Either an object in the ClassList, or the value of the name_field attribute of an object in the ClassList. + Either an object in the ClassList, or the value of the ``name_field`` for an object in the ClassList. Returns ------- instance : T or str - Either the object with the value of the name_field attribute given by value, or the input value if an - object with that value of the name_field attribute cannot be found. + Either the object with the value of the ``name_field`` attribute given by value, or the input value if an + object with that value of the ``name_field`` attribute cannot be found. """ try: @@ -518,24 +543,27 @@ def _get_item_from_name_field(self, value: Union[T, str]) -> Union[T, str]: @staticmethod def _determine_class_handle(input_list: Sequence[T]): - """When inputting a sequence of object to a ClassList, the _class_handle should be set as the type of the - element which satisfies "issubclass" for all the other elements. + """Determine the class handle from a sequence of objects. + + The ``_class_handle`` of the sequence is the type of the first element in the sequence + which is a subclass of all elements in the sequence. If no such element exists, the handle + is set to be the type of the first element in the list. Parameters ---------- - input_list : Sequence [object] + input_list : Sequence[T] A list of instances to populate the ClassList. Returns ------- class_handle : type - The type object of the element fulfilling the condition of satisfying "issubclass" for all of the other - elements. + The type object of the first element which is a subclass of all of the other + elements, or the first element if no such element exists. """ - for this_element in input_list: - if all([issubclass(type(instance), type(this_element)) for instance in input_list]): - class_handle = type(this_element) + for element in input_list: + if all(issubclass(type(instance), type(element)) for instance in input_list): + class_handle = type(element) break else: class_handle = type(input_list[0]) diff --git a/RATapi/controls.py b/RATapi/controls.py index e874e6f3..d696fb57 100644 --- a/RATapi/controls.py +++ b/RATapi/controls.py @@ -1,3 +1,5 @@ +"""The Controls class for providing RAT algorithm settings.""" + import contextlib import os import tempfile @@ -114,9 +116,9 @@ class Controls(BaseModel, validate_assignment=True, extra="forbid", use_attribut # Dream nSamples: int = Field(20000, ge=0) - """[DREAM] The number of samples in the initial population for each chain.""" + """[DREAM] The total number of function evaluations (number of algorithm generations times number of chains).""" - nChains: int = Field(10, gt=0) + nChains: int = Field(10, gt=1) """[DREAM] The number of Markov chains to use in the algorithm.""" jumpProbability: float = Field(0.5, gt=0.0, lt=1.0) @@ -153,7 +155,7 @@ def warn_setting_incorrect_properties(self, handler: ValidatorFunctionWrapHandle f" controls procedure are:\n " f"{', '.join(fields.get('procedure', []))}\n", } - custom_error_list = custom_pydantic_validation_error(exc.errors(), custom_error_msgs) + custom_error_list = custom_pydantic_validation_error(exc.errors(include_url=False), custom_error_msgs) raise ValidationError.from_exception_data(exc.title, custom_error_list, hide_input=True) from None if isinstance(model_input, validated_self.__class__): @@ -196,19 +198,20 @@ def __str__(self) -> str: return table.get_string() def initialise_IPC(self): - """Setup the inter-process communication file.""" + """Set up the inter-process communication file.""" IPC_obj, self._IPCFilePath = tempfile.mkstemp() os.write(IPC_obj, b"0") os.close(IPC_obj) return None def sendStopEvent(self): - """Sends the stop event via the inter-process communication file. + """Send the stop event via the inter-process communication file. Warnings -------- UserWarning Raised if we try to delete an IPC file that was not initialised. + """ if os.path.isfile(self._IPCFilePath): with open(self._IPCFilePath, "wb") as f: diff --git a/RATapi/events.py b/RATapi/events.py index e3ba19f5..c2348ecc 100644 --- a/RATapi/events.py +++ b/RATapi/events.py @@ -1,3 +1,5 @@ +"""Hooks for connecting to run callback events.""" + import os from typing import Callable, Union @@ -5,8 +7,7 @@ def notify(event_type: EventTypes, data: Union[str, PlotEventData, ProgressEventData]) -> None: - """Calls registered callbacks with the data when event type has - been triggered. + """Call registered callbacks with data when event type has been triggered. Parameters ---------- @@ -22,7 +23,7 @@ def notify(event_type: EventTypes, data: Union[str, PlotEventData, ProgressEvent def get_event_callback(event_type: EventTypes) -> list[Callable[[Union[str, PlotEventData, ProgressEventData]], None]]: - """Returns all callbacks registered for the given event type. + """Return all callbacks registered for the given event type. Parameters ---------- @@ -39,7 +40,7 @@ def get_event_callback(event_type: EventTypes) -> list[Callable[[Union[str, Plot def register(event_type: EventTypes, callback: Callable[[Union[str, PlotEventData, ProgressEventData]], None]) -> None: - """Registers a new callback for the event type. + """Register a new callback for the event type. Parameters ---------- @@ -58,12 +59,14 @@ def register(event_type: EventTypes, callback: Callable[[Union[str, PlotEventDat def clear(key=None, callback=None) -> None: - """Clears all event callbacks or specific callback. + """Clear all event callbacks or specific callback. Parameters ---------- - callback : Callable[[Union[str, PlotEventData, ProgressEventData]], None] - The callback for when the event is triggered. + key : EventTypes, optional + The event type of the callback to clear if given. + callback : Callable[[Union[str, PlotEventData, ProgressEventData]], None], optional + A callback for an event which will be cleared if given. """ if key is None and callback is None: diff --git a/RATapi/examples/__init__.py b/RATapi/examples/__init__.py index d2bb8877..86de2334 100644 --- a/RATapi/examples/__init__.py +++ b/RATapi/examples/__init__.py @@ -1,3 +1,5 @@ +"""Usage examples for the Python RAT API.""" + from RATapi.examples.absorption.absorption import absorption from RATapi.examples.convert_rascal_project.convert_rascal import convert_rascal from RATapi.examples.domains.domains_custom_layers import domains_custom_layers diff --git a/RATapi/examples/absorption/__init__.py b/RATapi/examples/absorption/__init__.py index e69de29b..bf48e7ae 100644 --- a/RATapi/examples/absorption/__init__.py +++ b/RATapi/examples/absorption/__init__.py @@ -0,0 +1 @@ +"""An example of using absorption in a RAT project.""" diff --git a/RATapi/examples/absorption/absorption.py b/RATapi/examples/absorption/absorption.py index ce7407d4..63b1ba3e 100644 --- a/RATapi/examples/absorption/absorption.py +++ b/RATapi/examples/absorption/absorption.py @@ -1,3 +1,5 @@ +"""An example for using absorption in RAT.""" + import pathlib import numpy as np @@ -6,7 +8,17 @@ def absorption(): - """Custom layers model including absorption""" + """Run a custom layers model including absorption. + + RAT allows the use of an imaginary, as well as real part of the SLD. + The effect of this is usually seen below the critical edge, and must sometimes be accounted for. + + This is an example of a Custom Layers project using absorption. used here is Custom Layers. + It analyses a bilayer sample on a permalloy / gold substrate, + measured using polarised neutrons, against D2O and H2O, leading to 4 contrasts in total. + Absorption (i.e. imaginary SLD) is defined for Gold and the Permalloy, + to account for non-flat data below the critical edge. + """ problem = RAT.Project( name="Absorption example", calculation="normal", diff --git a/RATapi/examples/absorption/volume_thiol_bilayer.py b/RATapi/examples/absorption/volume_thiol_bilayer.py index f64f16ff..4398f2b6 100644 --- a/RATapi/examples/absorption/volume_thiol_bilayer.py +++ b/RATapi/examples/absorption/volume_thiol_bilayer.py @@ -1,3 +1,6 @@ +"""Custom layer model file for the absorption example.""" + + def volume_thiol_bilayer(params, bulk_in, bulk_out, contrast): """VolumeThiolBilayer RAT Custom Layer Model File. diff --git a/RATapi/examples/bayes_benchmark/bayes_benchmark.py b/RATapi/examples/bayes_benchmark/bayes_benchmark.py index 2cec2e3e..80ac9a24 100644 --- a/RATapi/examples/bayes_benchmark/bayes_benchmark.py +++ b/RATapi/examples/bayes_benchmark/bayes_benchmark.py @@ -1,4 +1,5 @@ -""" +"""An example script to compare different methods of Bayesian fitting. + This example compares three Bayesian posteriors for a low-dimensional example: a posterior generated by DREAM, one generated by NS, and one calculated directly. @@ -34,6 +35,7 @@ # this is the RasCAL-1 default project # it is a bare D2O substrate def get_project() -> RAT.Project: + """Create the project used as our example.""" return RAT.Project( name="Bare D2O Substrate", calculation="normal", @@ -191,6 +193,7 @@ def calculate_posterior(roughness_index: int, background_index: int) -> float: ------- float The value of exp(-chi^2 / 2) for the given roughness and background values. + """ problem.parameters[0].value = roughness[roughness_index] problem.background_parameters[0].value = background[background_index] @@ -265,6 +268,7 @@ def calculate_posterior(roughness_index: int, background_index: int, scalefactor ------- float The value of exp(-chi^2 / 2) for the given roughness and background values. + """ problem.parameters[0].value = roughness[roughness_index] problem.background_parameters[0].value = background[background_index] @@ -300,6 +304,7 @@ def plot_posterior_comparison( The BayesResults object from a DREAM calculation. calc_results : CalculationResults The results from a direct calculation. + """ num_params = calc_results.distribution.ndim fig, axes = plt.subplots(3, num_params, figsize=(3 * num_params, 9)) diff --git a/RATapi/examples/convert_rascal_project/Model_IIb.py b/RATapi/examples/convert_rascal_project/Model_IIb.py index ae36ba5f..d7c61526 100644 --- a/RATapi/examples/convert_rascal_project/Model_IIb.py +++ b/RATapi/examples/convert_rascal_project/Model_IIb.py @@ -1,7 +1,10 @@ +"""A custom model file for a monolayer volume model.""" + from math import cos, radians def Model_IIb(params, bulk_in, bulk_out, contrast): + """Calculate layer parameters for a monolayer volume model at two deuterations.""" # converted from matlab file Model_IIb.m Roughness, APM, thickHead, theta = params diff --git a/RATapi/examples/convert_rascal_project/__init__.py b/RATapi/examples/convert_rascal_project/__init__.py index e69de29b..0648853e 100644 --- a/RATapi/examples/convert_rascal_project/__init__.py +++ b/RATapi/examples/convert_rascal_project/__init__.py @@ -0,0 +1 @@ +"""An example of converting between RAT and RasCAL-1.""" diff --git a/RATapi/examples/convert_rascal_project/convert_rascal.ipynb b/RATapi/examples/convert_rascal_project/convert_rascal.ipynb index c62be937..fbae2d5b 100644 --- a/RATapi/examples/convert_rascal_project/convert_rascal.ipynb +++ b/RATapi/examples/convert_rascal_project/convert_rascal.ipynb @@ -6,7 +6,7 @@ "source": [ "### RasCAL-1 to RAT\n", "\n", - "RasCAL-1 (R1) project structs can be converted to RAT Project classes, and vice versa. This is done via the functions `r1_to_project_class` and `project_class_to_r1`.\n", + "RasCAL-1 (R1) project structs can be converted to RAT Projects, and vice versa. This is done via the functions `r1_to_project` and `project_to_r1`.\n", "\n", "Converting from R1 to a `Project` is very simple. We use the example R1 project in the file `R1monolayerVolumeModel.mat`, which is a project for analysing a monolayer of DSPC with various deuterations (tail-deuterated, head-deuterated, fully deuterated, hydrogenated)" ] @@ -15,7 +15,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Simply give the file path to the function `r1_to_project_class`, and it returns a RAT `Project` that you can use exactly like any other." + "Simply give the file path to the function `r1_to_project`, and it returns a RAT `Project` that you can use exactly like any other." ] }, { @@ -24,9 +24,9 @@ "metadata": {}, "outputs": [], "source": [ - "from RATapi.utils.convert import r1_to_project_class\n", + "from RATapi.utils.convert import r1_to_project\n", "\n", - "project = r1_to_project_class(\"R1monolayerVolumeModel.mat\")\n", + "project = r1_to_project(\"R1monolayerVolumeModel.mat\")\n", "print(project)" ] }, @@ -108,7 +108,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "`project_class_to_r1` takes parameters `project` and `filename`, which are the `Project` object and filename for the produced .mat file respectively. This .mat file can then be loaded into RasCAL-1.\n", + "`project_to_r1` takes parameters `project` and `filename`, which are the `Project` object and filename for the produced .mat file respectively. This .mat file can then be loaded into RasCAL-1.\n", "\n", "Alternatively, if one sets `return_struct=True`, the struct is returned as a Python dictionary instead of being saved.\n", "\n", @@ -121,14 +121,14 @@ "metadata": {}, "outputs": [], "source": [ - "from RATapi.utils.convert import project_class_to_r1\n", + "from RATapi.utils.convert import project_to_r1\n", "from pprint import pp # for printing the struct\n", "\n", "# save to a file called lipid_bilayer.mat\n", - "project_class_to_r1(lipid_bilayer_project, filename=\"lipid_bilayer.mat\")\n", + "project_to_r1(lipid_bilayer_project, filename=\"lipid_bilayer.mat\")\n", "\n", "# return as a Python dictionary\n", - "struct = project_class_to_r1(lipid_bilayer_project, return_struct=True)\n", + "struct = project_to_r1(lipid_bilayer_project, return_struct=True)\n", "pp(struct)" ] } diff --git a/RATapi/examples/convert_rascal_project/convert_rascal.py b/RATapi/examples/convert_rascal_project/convert_rascal.py index f83cb38c..2a41b61f 100644 --- a/RATapi/examples/convert_rascal_project/convert_rascal.py +++ b/RATapi/examples/convert_rascal_project/convert_rascal.py @@ -1,3 +1,5 @@ +"""An example of how to convert a RasCAL-1 project to RAT.""" + import pathlib from pprint import pp @@ -6,8 +8,25 @@ # convert R1 project to Project class def convert_rascal(mat_filename="lipid_bilayer.mat"): + """Convert a project from RasCAL-1 and a project to RasCAL-1. + + We convert a RasCAL-1 monolayer volume model to RAT, and the DSPC Standard Layers + example to RasCAL-1. + + Parameters + ---------- + mat_filename : str + The filename of the output of the RAT project converted to RasCAL-2. + + Returns + ------- + project, struct + A RasCAL-1 monolayer volume model converted to a RAT project, and the + struct of the DSPC standard layers example converted to a RasCAL-1 struct. + + """ project_path = pathlib.Path(__file__).parent / "R1monolayerVolumeModel.mat" - project = RAT.utils.convert.r1_to_project_class(project_path) + project = RAT.utils.convert.r1_to_project(project_path) # change values if you like, including ones not supported by R1 project.parameters["Head Thickness"].prior_type = "gaussian" @@ -16,10 +35,10 @@ def convert_rascal(mat_filename="lipid_bilayer.mat"): # convert DSPC standard layers example to a struct and save as file lipid_bilayer_project = RAT.examples.DSPC_standard_layers()[0] - RAT.utils.convert.project_class_to_r1(lipid_bilayer_project, filename=mat_filename) + RAT.utils.convert.project_to_r1(lipid_bilayer_project, filename=mat_filename) # convert and return as a Python dictionary - struct = RAT.utils.convert.project_class_to_r1(lipid_bilayer_project, return_struct=True) + struct = RAT.utils.convert.project_to_r1(lipid_bilayer_project, return_struct=True) return project, struct diff --git a/RATapi/examples/data/__init__.py b/RATapi/examples/data/__init__.py index e69de29b..9fc1b9dd 100644 --- a/RATapi/examples/data/__init__.py +++ b/RATapi/examples/data/__init__.py @@ -0,0 +1 @@ +"""Data files used by the examples.""" diff --git a/RATapi/examples/data/c_PLP0011859_q.ort b/RATapi/examples/data/c_PLP0011859_q.ort new file mode 100644 index 00000000..7a7bf1fd --- /dev/null +++ b/RATapi/examples/data/c_PLP0011859_q.ort @@ -0,0 +1,441 @@ +# # ORSO reflectivity data file | 1.1 standard | YAML encoding | https://www.reflectometry.org/ +# # handwritten test file header created to test RAT orsopy integration! +# # example data from refnx: https://refnx.readthedocs.io/en/latest/getting_started.html#fitting-a-neutron-reflectometry-dataset +# data_source: +# owner: +# name: null +# affiliation: null +# measurement: +# instrument_settings: null +# data_files: null +# experiment: +# title: +# probe: neutron +# instrument: None +# start_date: 1970-01-01T00:00:00 +# sample: +# name: film on silicon +# model: +# stack: Si | SiO2 30 | film 250 | D2O +# materials: +# film: +# sld: 2.0e-6 +# roughness: 3 +# reduction: +# software: null +# timestamp: null +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: normal momentum transfer} +# - {name: R, unit: '', physical_quantity: specular reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R () sR sQz +0.00806022 0.709581 0.0850676 0.000331422 +0.00813662 0.862281 0.11237 0.000334619 +0.00826375 0.908647 0.0790047 0.000339939 +0.00837067 0.773292 0.0792728 0.000344412 +0.00845033 1.05797 0.125959 0.000347744 +0.00853083 1.01566 0.113295 0.000351111 +0.00861217 0.734717 0.0611566 0.000354512 +0.00869437 0.769216 0.0617058 0.000357949 +0.00877743 1.11574 0.11273 0.000361421 +0.00886136 0.972303 0.089716 0.000364929 +0.00894616 0.751214 0.0549393 0.000368473 +0.00903185 0.797649 0.0567122 0.000372053 +0.00911844 0.922189 0.0685841 0.000375671 +0.00920593 0.975755 0.0729395 0.000379325 +0.00929432 0.819504 0.0521617 0.000383017 +0.00938364 0.78832 0.0479473 0.000386748 +0.00947389 0.794701 0.0460224 0.000390516 +0.00956508 0.8744 0.0515164 0.000394323 +0.00965721 0.839662 0.0474285 0.00039817 +0.0097503 0.800872 0.0439958 0.000402055 +0.00984436 1.1171 0.073733 0.000405981 +0.00993939 0.888411 0.049541 0.000409947 +0.0100354 0.779129 0.0389873 0.000413953 +0.0101324 0.799968 0.0389974 0.000418001 +0.0102304 0.843124 0.041598 0.00042209 +0.0103294 0.961332 0.0492536 0.00042622 +0.0104868 0.880544 0.0299083 0.000432781 +0.0106327 0.755735 0.0320851 0.000438868 +0.0107359 0.971231 0.0453485 0.00044317 +0.0108401 0.895549 0.0390542 0.000447516 +0.0109454 0.862589 0.0358072 0.000451906 +0.0110518 0.890992 0.0361257 0.000456342 +0.0111593 0.900348 0.0367924 0.000460822 +0.0112679 0.845927 0.0321881 0.000465348 +0.0113776 0.943152 0.0365533 0.000469921 +0.0114884 0.995631 0.0390116 0.00047454 +0.0116004 0.969594 0.0363623 0.000479205 +0.0117135 0.905181 0.032041 0.000483919 +0.0118278 0.893381 0.0306119 0.00048868 +0.0119433 0.919602 0.0314677 0.00049349 +0.0120599 0.918998 0.0306097 0.000498349 +0.0121777 0.781056 0.0235542 0.000503257 +0.0122968 0.864915 0.0272025 0.000508215 +0.012417 0.843516 0.0255015 0.000513223 +0.0125385 0.998418 0.0319722 0.000518283 +0.0126612 0.88126 0.0260273 0.000523393 +0.0127852 0.883569 0.0255862 0.000528556 +0.0129105 0.93767 0.0274651 0.000533771 +0.013037 1.0192 0.0305107 0.000539039 +0.0131648 0.845526 0.0226269 0.00054436 +0.0132939 0.873804 0.0232468 0.000549735 +0.0134243 0.865953 0.0224906 0.000555165 +0.0135561 0.877982 0.0224491 0.00056065 +0.0136892 0.947545 0.0248985 0.00056619 +0.0138237 0.888154 0.0220962 0.000571787 +0.0139595 0.891362 0.0218148 0.00057744 +0.0140967 0.888456 0.0215092 0.00058315 +0.0142353 0.913717 0.0222586 0.000588919 +0.0143753 0.810364 0.0188289 0.000594745 +0.0145168 0.738548 0.0168827 0.000600631 +0.0146597 0.68651 0.0159742 0.000606576 +0.014804 0.58224 0.0135021 0.000612582 +0.0149498 0.446855 0.0100626 0.000618648 +0.015097 0.392461 0.00915549 0.000624775 +0.0152458 0.320517 0.00731959 0.000630965 +0.0153961 0.281006 0.00639909 0.000637217 +0.0155479 0.2401 0.00544239 0.000643532 +0.0157012 0.220881 0.00502437 0.000649911 +0.0158561 0.192033 0.00431441 0.000656354 +0.0160126 0.179849 0.00405159 0.000662863 +0.0161707 0.160069 0.00356202 0.000669437 +0.0163303 0.153129 0.00346777 0.000676078 +0.0164916 0.13422 0.003016 0.000682786 +0.0166545 0.12833 0.00288853 0.000689562 +0.016819 0.124794 0.00286182 0.000696406 +0.0169853 0.109127 0.0024831 0.000703319 +0.0171532 0.104429 0.00235392 0.000710302 +0.0173228 0.094683 0.00209162 0.000717355 +0.0175881 0.0896911 0.00143938 0.000728391 +0.0178419 0.0809144 0.00187278 0.000738945 +0.0180185 0.0746544 0.00171422 0.000746287 +0.0181968 0.0703661 0.00163423 0.000753704 +0.018377 0.0690445 0.0016053 0.000761195 +0.0185589 0.0627055 0.00146146 0.000768762 +0.0187427 0.0593915 0.00140007 0.000776405 +0.0189284 0.0575477 0.00136095 0.000784125 +0.0191159 0.0513833 0.00122668 0.000791923 +0.0193054 0.0492267 0.00117824 0.0007998 +0.0194967 0.0452174 0.00107962 0.000807755 +0.01969 0.0424556 0.00102225 0.000815792 +0.0198852 0.0412613 0.000994781 0.000823909 +0.0200824 0.0352333 0.00087779 0.000832108 +0.0202815 0.0335271 0.000836747 0.000840389 +0.0204827 0.0332684 0.000833959 0.000848754 +0.0206859 0.0316644 0.000789886 0.000857204 +0.0208912 0.02916 0.000749613 0.000865738 +0.0210985 0.0265201 0.000706383 0.000874359 +0.021308 0.0251829 0.000671389 0.000883066 +0.0215195 0.0238757 0.000642693 0.000891861 +0.0217331 0.0228929 0.000633068 0.000900745 +0.021949 0.0208646 0.000593128 0.000909718 +0.0221669 0.0208771 0.00059201 0.000918781 +0.0223871 0.0182228 0.000529205 0.000927936 +0.0226095 0.0177346 0.000525152 0.000937183 +0.0228341 0.0158714 0.000481877 0.000946523 +0.023061 0.0143255 0.000451619 0.000955957 +0.0232902 0.0142776 0.000452249 0.000965486 +0.0235217 0.0126624 0.000419845 0.000975112 +0.0237555 0.0122128 0.000413786 0.000984834 +0.0239917 0.0104608 0.000374532 0.000994654 +0.0242302 0.0106133 0.000381052 0.00100457 +0.0244712 0.00987903 0.000364128 0.00101459 +0.0247145 0.00837203 0.000331393 0.00102471 +0.0249603 0.00767048 0.000307705 0.00103493 +0.0252086 0.00734489 0.000304978 0.00104526 +0.0254594 0.00679865 0.000289868 0.00105569 +0.0257127 0.0059163 0.000267178 0.00106622 +0.0259685 0.00534498 0.000251536 0.00107686 +0.0262269 0.00512265 0.000247412 0.0010876 +0.026488 0.00475031 0.000237953 0.00109846 +0.0267516 0.00430715 0.00022385 0.00110942 +0.0270179 0.00401817 0.000220051 0.0011205 +0.0272868 0.00353915 0.000204653 0.00113168 +0.0275585 0.00381819 0.000207944 0.00114298 +0.0278329 0.00286475 0.000181921 0.0011544 +0.02811 0.0027958 0.000176691 0.00116592 +0.02839 0.0026215 0.000175002 0.00117757 +0.0286727 0.00248477 0.000169456 0.00118933 +0.0289583 0.00242009 0.000170925 0.00120121 +0.0292467 0.00235926 0.00017006 0.0012132 +0.0295381 0.00197856 0.000160021 0.00122532 +0.0298323 0.0019472 0.000158282 0.00123757 +0.0301296 0.00173593 0.000152731 0.00124993 +0.0304298 0.00189459 0.000160305 0.00126242 +0.030733 0.00169668 0.000152574 0.00127504 +0.0310392 0.00179369 0.000159245 0.00128778 +0.0313486 0.00178686 0.000155202 0.00130065 +0.031661 0.00187201 0.000158395 0.00131365 +0.0319766 0.00168818 0.000149984 0.00132678 +0.0322953 0.00173237 0.000154325 0.00134004 +0.0326173 0.0015376 0.000145396 0.00135344 +0.0329424 0.00154134 0.000149756 0.00136697 +0.0332708 0.00170033 0.000157208 0.00138064 +0.0336026 0.00214224 0.000172887 0.00139445 +0.0339376 0.00194402 0.000168689 0.0014084 +0.0342205 0.001914 0.000107714 0.00141748 +0.0346645 0.00198641 0.000143828 0.00143727 +0.0350219 0.00184974 0.000127974 0.00145177 +0.035385 0.00193264 0.000122708 0.00146643 +0.0357162 0.00218899 0.000149598 0.00148083 +0.0360773 0.00231432 0.000150345 0.00149568 +0.0364677 0.00183177 0.000104159 0.00151093 +0.0368263 0.00179715 0.000101292 0.00152596 +0.0371616 0.00227874 0.000141733 0.0015409 +0.0375421 0.00218339 0.00012946 0.00155639 +0.0379404 0.001799 9.62498e-05 0.00157213 +0.0383128 0.00182808 9.73464e-05 0.00158778 +0.038684 0.00194985 0.00010826 0.00160356 +0.0390692 0.00204214 0.000112245 0.00161957 +0.0394734 0.00165946 8.39554e-05 0.00163584 +0.0398633 0.00162031 8.07338e-05 0.00165215 +0.0402658 0.00158862 7.61769e-05 0.00166867 +0.0406608 0.00158251 7.82912e-05 0.0016853 +0.0410701 0.0014299 7.03445e-05 0.00170214 +0.0414781 0.00133612 6.49882e-05 0.00171913 +0.0418744 0.0017219 9.20006e-05 0.00173625 +0.0423049 0.00140489 6.78817e-05 0.00175363 +0.0427317 0.00114648 5.31515e-05 0.00177115 +0.0431546 0.00103013 4.8728e-05 0.00178884 +0.0435835 0.00101864 4.85163e-05 0.0018067 +0.0440155 0.00106025 5.14888e-05 0.00182475 +0.0444549 0.000911561 4.51767e-05 0.00184298 +0.0448983 0.000779536 3.74676e-05 0.00186138 +0.045347 0.000595757 2.86778e-05 0.00187998 +0.0457947 0.000647645 3.28669e-05 0.00189877 +0.0462512 0.000526839 2.66621e-05 0.00191774 +0.0467124 0.000436546 2.24095e-05 0.00193691 +0.0471783 0.000369605 1.92965e-05 0.00195626 +0.0476469 0.000331564 1.80855e-05 0.00197583 +0.0481208 0.000254584 1.44062e-05 0.00199557 +0.0485994 0.00020908 1.31243e-05 0.00201553 +0.0490828 0.000192937 1.26159e-05 0.00203569 +0.049571 0.000130922 9.99222e-06 0.00205605 +0.050064 0.000109823 9.08051e-06 0.00207663 +0.0505619 9.56548e-05 8.37009e-06 0.00209741 +0.0510655 7.92572e-05 7.69913e-06 0.00211839 +0.0515734 7.95605e-05 6.89856e-06 0.0021396 +0.0520864 7.22495e-05 6.10273e-06 0.00216102 +0.0526045 7.82513e-05 6.39136e-06 0.00218266 +0.0531279 8.63712e-05 6.56186e-06 0.00220452 +0.0536565 0.000133162 8.78984e-06 0.0022266 +0.0541905 0.000143632 8.17884e-06 0.00224891 +0.0547298 0.000158682 8.56534e-06 0.00227144 +0.0552745 0.000190007 9.75524e-06 0.00229421 +0.0558247 0.00023542 1.14172e-05 0.00231721 +0.0563804 0.000233932 1.03452e-05 0.00234044 +0.0569417 0.00024339 1.05955e-05 0.0023639 +0.0575087 0.000270858 1.12513e-05 0.00238761 +0.0580813 0.000310966 1.23355e-05 0.00241156 +0.0586597 0.000348327 1.35786e-05 0.00243575 +0.0592439 0.000332957 1.27559e-05 0.0024602 +0.059834 0.000354659 1.32236e-05 0.00248489 +0.06043 0.000354467 1.30661e-05 0.00250983 +0.0610319 0.000385515 1.40893e-05 0.00253502 +0.06164 0.000332509 1.20176e-05 0.00256048 +0.0622541 0.000333087 1.19715e-05 0.00258619 +0.0628744 0.000330307 1.19723e-05 0.00261217 +0.0635009 0.000322469 1.16291e-05 0.00263842 +0.0641337 0.000274462 9.94879e-06 0.00266493 +0.0647728 0.000279243 1.02537e-05 0.00269171 +0.0654184 0.000238938 8.96085e-06 0.00271877 +0.0660704 0.000233028 8.72665e-06 0.00274611 +0.066729 0.000186343 7.23614e-06 0.00277372 +0.0673942 0.000164949 6.6195e-06 0.00280162 +0.068066 0.000133244 5.54157e-06 0.00282981 +0.0687446 0.000115376 4.97162e-06 0.00285828 +0.06943 8.25033e-05 3.95115e-06 0.00288705 +0.0701223 6.71923e-05 3.57018e-06 0.00291611 +0.0708215 5.03322e-05 2.92623e-06 0.00294547 +0.0715278 3.33612e-05 2.5097e-06 0.00297513 +0.0722411 2.29052e-05 2.11638e-06 0.0030051 +0.0729338 1.87944e-05 1.86053e-06 0.00303317 +0.0736683 1.55344e-05 1.79821e-06 0.00306431 +0.0744015 1.82452e-05 1.95184e-06 0.00309508 +0.0751405 2.23562e-05 1.801e-06 0.00312604 +0.0758721 2.77362e-05 1.89014e-06 0.00315615 +0.0766402 3.93101e-05 2.30095e-06 0.00318888 +0.0773964 4.59669e-05 2.3324e-06 0.00322041 +0.0781061 5.54131e-05 2.45926e-06 0.00324807 +0.0788682 6.72605e-05 2.72957e-06 0.00327954 +0.0797953 7.65064e-05 2.77309e-06 0.00331828 +0.0807289 7.79472e-05 2.85741e-06 0.00335684 +0.0815334 8.50542e-05 2.9904e-06 0.00339073 +0.0823318 9.68569e-05 3.2227e-06 0.00342487 +0.0831605 9.4624e-05 3.16621e-06 0.00345952 +0.0840252 8.58564e-05 2.82446e-06 0.00349469 +0.0848648 8.10144e-05 2.66254e-06 0.00353001 +0.0857342 7.65592e-05 2.54978e-06 0.00356583 +0.0865553 7.38543e-05 2.48584e-06 0.00360169 +0.0874372 6.24971e-05 2.19092e-06 0.00363822 +0.0883163 5.49543e-05 2.00127e-06 0.00367507 +0.0891108 5.69454e-05 2.08158e-06 0.00371178 +0.0900416 4.2292e-05 1.72971e-06 0.00374959 +0.0909546 3.36754e-05 1.45891e-06 0.00378764 +0.0918504 2.54564e-05 1.24963e-06 0.00382594 +0.0927435 2.09221e-05 1.15976e-06 0.00386459 +0.0936289 1.53112e-05 1.06014e-06 0.00390356 +0.0945527 1.26565e-05 9.57753e-07 0.0039431 +0.0955178 9.33124e-06 8.62139e-07 0.0039832 +0.0964771 8.90152e-06 7.82897e-07 0.00402366 +0.0974189 1.07915e-05 8.93847e-07 0.0040644 +0.0984259 1.21368e-05 9.11308e-07 0.00410584 +0.0994201 1.5408e-05 9.07777e-07 0.0041476 +0.100425 1.93803e-05 9.60253e-07 0.00418981 +0.101434 2.09116e-05 1.04089e-06 0.00423243 +0.102499 2.51925e-05 1.07837e-06 0.0042757 +0.103498 2.8188e-05 1.13217e-06 0.00431908 +0.104546 2.9981e-05 1.17853e-06 0.0043631 +0.105608 3.05628e-05 1.15049e-06 0.0044076 +0.106682 2.81903e-05 1.09907e-06 0.00445257 +0.107758 2.69263e-05 1.0374e-06 0.00449797 +0.108831 2.54192e-05 1.01256e-06 0.0045438 +0.109916 2.2827e-05 9.3671e-07 0.00459012 +0.11104 1.76917e-05 7.9045e-07 0.00463704 +0.112117 1.54751e-05 7.6254e-07 0.00468423 +0.113244 1.17258e-05 6.68805e-07 0.00473208 +0.114319 1.05027e-05 6.59805e-07 0.00478019 +0.115464 8.12153e-06 5.84703e-07 0.00482905 +0.116645 4.98884e-06 5.26719e-07 0.00487853 +0.117762 4.90999e-06 5.36096e-07 0.00492824 +0.118925 5.30434e-06 5.24084e-07 0.00497863 +0.120129 5.68905e-06 4.91234e-07 0.00502965 +0.121346 6.44471e-06 5.06107e-07 0.00508123 +0.122586 7.45619e-06 5.05803e-07 0.0051334 +0.123823 8.24842e-06 5.0256e-07 0.00518608 +0.125091 1.06749e-05 5.84539e-07 0.00523939 +0.126361 1.13162e-05 5.72228e-07 0.00529323 +0.127643 1.1044e-05 5.76255e-07 0.00534765 +0.128932 1.06258e-05 5.65534e-07 0.00540265 +0.13021 9.33766e-06 5.25472e-07 0.00545814 +0.131547 8.9228e-06 5.00068e-07 0.00551441 +0.132854 6.43611e-06 4.44688e-07 0.00557113 +0.134162 6.18113e-06 4.55625e-07 0.00562844 +0.135515 4.68568e-06 4.03478e-07 0.00568647 +0.136824 4.66754e-06 3.80929e-07 0.00574496 +0.138204 4.2625e-06 3.76301e-07 0.00580426 +0.139589 3.8843e-06 3.57158e-07 0.00586419 +0.140989 3.69948e-06 3.79662e-07 0.00592478 +0.14243 3.65993e-06 3.54338e-07 0.00598611 +0.143851 4.55939e-06 3.66569e-07 0.00604802 +0.145263 4.58088e-06 3.47062e-07 0.00611055 +0.14675 4.82929e-06 3.50392e-07 0.00617393 +0.148225 4.93092e-06 3.5333e-07 0.00623794 +0.14971 4.781e-06 3.61256e-07 0.00630265 +0.151225 4.64484e-06 3.39711e-07 0.00636812 +0.152736 4.36508e-06 3.32459e-07 0.00643425 +0.154261 3.80706e-06 3.25378e-07 0.00650113 +0.155825 3.54415e-06 3.05388e-07 0.00656881 +0.157366 2.63269e-06 2.77355e-07 0.00663715 +0.158941 2.12968e-06 2.57061e-07 0.0067063 +0.160511 2.50908e-06 2.68417e-07 0.00677617 +0.162137 2.60626e-06 2.68354e-07 0.00684694 +0.163755 2.46796e-06 2.61811e-07 0.00691843 +0.165404 2.43769e-06 2.45708e-07 0.00699078 +0.167035 2.81846e-06 2.62635e-07 0.00706385 +0.168736 3.20191e-06 2.67722e-07 0.00713789 +0.17041 3.02096e-06 2.54866e-07 0.00721266 +0.172119 2.39855e-06 2.32464e-07 0.00728832 +0.173839 2.45824e-06 2.38546e-07 0.00736483 +0.175574 2.03366e-06 2.23196e-07 0.00744222 +0.177336 1.68404e-06 2.04828e-07 0.00752051 +0.179087 1.29383e-06 2.00765e-07 0.00759963 +0.180907 1.43967e-06 1.88207e-07 0.00767979 +0.182715 1.5402e-06 1.99998e-07 0.0077608 +0.184535 1.33849e-06 1.86482e-07 0.00784273 +0.186383 1.61599e-06 1.86369e-07 0.00792563 +0.188247 1.49347e-06 1.88386e-07 0.00800949 +0.190126 2.05968e-06 1.9207e-07 0.00809433 +0.192038 1.69015e-06 1.7677e-07 0.00818019 +0.193954 1.48837e-06 1.79274e-07 0.00826701 +0.1959 1.40382e-06 1.74728e-07 0.00835488 +0.19786 1.34689e-06 1.70569e-07 0.00844376 +0.199845 9.4771e-07 1.61779e-07 0.00853372 +0.201833 1.08955e-06 1.73502e-07 0.00862469 +0.203863 1.24635e-06 1.65466e-07 0.0087168 +0.205904 1.22737e-06 1.6449e-07 0.00880998 +0.207959 1.16896e-06 1.58456e-07 0.00890425 +0.210042 1.16172e-06 1.52198e-07 0.00899967 +0.21214 1.21689e-06 1.58854e-07 0.00909622 +0.214257 1.31757e-06 1.54607e-07 0.00919392 +0.216399 1.03279e-06 1.47292e-07 0.00929281 +0.21857 1.03066e-06 1.51473e-07 0.00939292 +0.220781 5.94535e-07 1.51172e-07 0.00949429 +0.222988 7.27996e-07 1.42928e-07 0.00959682 +0.225216 7.80631e-07 1.48525e-07 0.0097006 +0.227467 1.06339e-06 1.4993e-07 0.00980565 +0.229741 6.52697e-07 1.29668e-07 0.00991199 +0.232037 1.07438e-06 1.48453e-07 0.0100196 +0.234356 8.68763e-07 1.46862e-07 0.0101286 +0.236698 9.21868e-07 1.45082e-07 0.0102389 +0.239064 6.47149e-07 1.36809e-07 0.0103506 +0.241454 5.36393e-07 1.35688e-07 0.0104637 +0.243867 6.33717e-07 1.30403e-07 0.0105782 +0.246304 6.2927e-07 1.36557e-07 0.0106942 +0.248766 6.33292e-07 1.22453e-07 0.0108116 +0.251253 1.03705e-06 1.35409e-07 0.0109304 +0.253764 8.25286e-07 1.43161e-07 0.0110508 +0.256301 6.26825e-07 1.19257e-07 0.0111727 +0.258863 5.25259e-07 1.22741e-07 0.0112962 +0.26145 5.21832e-07 1.30918e-07 0.0114212 +0.264064 3.91659e-07 1.2137e-07 0.0115479 +0.266703 4.72439e-07 1.39973e-07 0.0116762 +0.269369 5.59536e-07 1.39151e-07 0.0118061 +0.272062 6.6407e-07 1.44867e-07 0.0119377 +0.274782 4.59378e-07 1.51466e-07 0.012071 +0.277529 3.66961e-07 1.43546e-07 0.0122061 +0.280303 5.31531e-07 1.48308e-07 0.012343 +0.283105 4.28914e-07 1.36145e-07 0.0124816 +0.285935 5.52006e-07 1.41983e-07 0.0126221 +0.288793 5.70264e-07 1.529e-07 0.0127644 +0.29168 5.04731e-07 1.36429e-07 0.0129086 +0.294596 6.61923e-07 1.40793e-07 0.0130548 +0.297541 7.60132e-07 1.60721e-07 0.0132029 +0.300516 4.68527e-07 1.34887e-07 0.0133529 +0.30352 4.4286e-07 1.47134e-07 0.0135051 +0.306554 4.89979e-07 1.4043e-07 0.0136592 +0.309619 3.60163e-07 1.33728e-07 0.0138155 +0.312714 2.90563e-07 1.48062e-07 0.0139739 +0.315841 5.19963e-07 1.51562e-07 0.0141344 +0.318998 4.66665e-07 1.56256e-07 0.0142972 +0.322187 3.88883e-07 1.57011e-07 0.0144622 +0.325408 4.46778e-07 1.55512e-07 0.0146295 +0.328662 3.23885e-07 1.52586e-07 0.0147991 +0.331947 3.78736e-07 1.5818e-07 0.0149711 +0.335266 3.88199e-07 1.53911e-07 0.0151455 +0.338618 4.42877e-07 1.49098e-07 0.0153224 +0.342003 2.47787e-07 1.46321e-07 0.0155017 +0.345423 2.85769e-07 1.35135e-07 0.0156836 +0.348876 4.75964e-07 1.53664e-07 0.0158681 +0.352364 4.49639e-07 1.53956e-07 0.0160552 +0.355887 2.47147e-07 1.44322e-07 0.0162449 +0.359445 2.16764e-07 1.49181e-07 0.0164375 +0.363039 5.20585e-07 1.57369e-07 0.0166327 +0.366669 5.24697e-07 1.54541e-07 0.0168309 +0.370335 3.64403e-07 1.55024e-07 0.0170319 +0.374037 4.59752e-07 1.65308e-07 0.0172358 +0.377777 5.35922e-07 1.82043e-07 0.0174427 +0.381554 4.26807e-07 1.77003e-07 0.0176527 +0.385369 4.03666e-07 1.8111e-07 0.0178658 +0.389222 2.55454e-07 1.59729e-07 0.018082 +0.393113 9.26972e-08 1.65402e-07 0.0183015 +0.397044 1.10672e-07 1.80888e-07 0.0185242 +0.401014 4.52163e-07 1.73191e-07 0.0187503 +0.405023 3.78066e-07 1.51597e-07 0.0189798 +0.409073 3.09136e-07 1.57195e-07 0.0192128 +0.413163 3.41774e-07 1.44818e-07 0.0194493 +0.417294 3.44924e-07 1.59581e-07 0.0196894 +0.421466 2.5184e-07 1.59757e-07 0.0199332 +0.42568 4.01737e-07 1.53814e-07 0.0201807 +0.429936 3.17279e-07 1.6913e-07 0.0204321 +0.434235 5.50631e-07 1.61142e-07 0.0206873 +0.438577 5.0851e-07 1.6499e-07 0.0209465 +0.442962 6.02593e-07 1.73835e-07 0.0212097 +0.447391 4.38454e-07 1.6535e-07 0.0214771 +0.451865 3.38757e-07 1.87639e-07 0.0217487 +0.456383 4.35846e-07 1.97826e-07 0.0220245 +0.460946 3.85579e-07 1.76143e-07 0.0223047 +0.465555 3.83415e-07 1.88454e-07 0.0225894 diff --git a/RATapi/examples/domains/__init__.py b/RATapi/examples/domains/__init__.py index e69de29b..ed955f76 100644 --- a/RATapi/examples/domains/__init__.py +++ b/RATapi/examples/domains/__init__.py @@ -0,0 +1 @@ +"""Examples for how to use RAT with domains models.""" diff --git a/RATapi/examples/domains/alloy_domains.py b/RATapi/examples/domains/alloy_domains.py index f7d9ed4c..33454dcc 100644 --- a/RATapi/examples/domains/alloy_domains.py +++ b/RATapi/examples/domains/alloy_domains.py @@ -1,5 +1,10 @@ +"""Custom model file for the domains custom layers example.""" + + def alloy_domains(params, bulkIn, bulkOut, contrast, domain): - """Simple custom model for testing incoherent summing. + """Calculate custom model layers for a permalloy/gold model with domains. + + Simple custom model for testing incoherent summing. Simple two layer of permalloy / gold, with up/down domains. """ # Split up the parameters diff --git a/RATapi/examples/domains/domains_XY_model.py b/RATapi/examples/domains/domains_XY_model.py index fd1823a4..8aeb8c77 100644 --- a/RATapi/examples/domains/domains_XY_model.py +++ b/RATapi/examples/domains/domains_XY_model.py @@ -1,9 +1,12 @@ +"""Custom model file for the domains custom XY example.""" + import math import numpy as np def domains_XY_model(params, bulk_in, bulk_out, contrast, domain): + """Calculate the SLD profile for a domains custom XY model.""" # Split up the parameters for convenience subRough = params[0] oxideThick = params[1] @@ -51,7 +54,8 @@ def domains_XY_model(params, bulk_in, bulk_out, contrast, domain): def makeLayer(z, prevLaySurf, thickness, height, Sigma_L, Sigma_R): - """This produces a layer, with a defined thickness, height and roughness. + """Produce a layer, with a defined thickness, height and roughness. + Each side of the layer has its own roughness value. """ # Find the edges diff --git a/RATapi/examples/domains/domains_custom_XY.py b/RATapi/examples/domains/domains_custom_XY.py index e069540f..1eb7222d 100644 --- a/RATapi/examples/domains/domains_custom_XY.py +++ b/RATapi/examples/domains/domains_custom_XY.py @@ -1,10 +1,21 @@ +"""An example of using domains with a Custom XY model.""" + import pathlib import RATapi as RAT def domains_custom_XY(): - """Simple example of a layer containing domains using a custom XY model""" + """Calculate an example of a layer containing domains using a custom XY model. + + Domains custom XY models operate in the same way as domains custom layer models, + in that there is an additional input to the custom model + specifying the domain to be calculated. + + This is then used within the function to calculate the correct SLD profile + for each contrast and domain. In this example, we simulate a hydrogenated layer + on a silicon substrate, containing domains of a larger SLD, against D2O, SMW and water. + """ problem = RAT.Project(calculation="domains", model="custom xy", geometry="substrate/liquid") problem.parameters.append(name="Oxide Thickness", min=10.0, value=20.0, max=50.0, fit=True) diff --git a/RATapi/examples/domains/domains_custom_layers.py b/RATapi/examples/domains/domains_custom_layers.py index 7ebf0132..99c2d372 100644 --- a/RATapi/examples/domains/domains_custom_layers.py +++ b/RATapi/examples/domains/domains_custom_layers.py @@ -1,10 +1,18 @@ +"""An example of using domains with custom layer models.""" + import pathlib import RATapi as RAT def domains_custom_layers(): - """An example custom layers domains project involving incoherent summing on a permalloy layer""" + """Calculate an example custom layers domains project involving incoherent summing on a permalloy layer. + + For a custom layers model, rather than being forced to define our layers as [Thick SLD Rough…. etc], + we can parameterise however we like and then use a function to calculate the parameters for each layer. + So for example, if the volume of lipid tails are known (from the literature), + then all we need is the Area per molecule to calculate the layers. + """ problem = RAT.Project(calculation="domains", model="custom layers", geometry="substrate/liquid") # Make some parameters... diff --git a/RATapi/examples/domains/domains_standard_layers.py b/RATapi/examples/domains/domains_standard_layers.py index b1d08778..9e821a8d 100644 --- a/RATapi/examples/domains/domains_standard_layers.py +++ b/RATapi/examples/domains/domains_standard_layers.py @@ -1,7 +1,19 @@ +"""An example using domains with standard layers.""" + import RATapi as RAT def domains_standard_layers(): + """Calculate an example for using standard layers with domains. + + Domains standard layers projects proceed in much the same way as a normal standard layers problem, + except that there is an additional grouping step between layers and contrasts. + + Layers are grouped into ‘Domain Contrasts’. The model for the actual experimental contrast + is built from these domain contrasts rather than from layers. There are exactly + two domains for each contrast, with the the ratio of them controlled by + a fittable ‘domain ratio’ parameter. + """ problem = RAT.Project(calculation="domains") # Define the parameters we need to define our two domains diff --git a/RATapi/examples/extras/two_contrast_example.py b/RATapi/examples/extras/two_contrast_example.py index 8d95f1b8..ca441e14 100644 --- a/RATapi/examples/extras/two_contrast_example.py +++ b/RATapi/examples/extras/two_contrast_example.py @@ -1,10 +1,12 @@ +"""The example project with two contrasts from the user guide.""" + import numpy as np import RATapi as RAT def two_contrast_example(): - # Two contrast example for the user guide + """Generate the two-contrast example project from the user guide.""" problem = RAT.Project(name="DSPC monolayers") parameters = [ RAT.models.Parameter(name="Tails Thickness", min=10, value=20, max=30, fit=True), diff --git a/RATapi/examples/languages/__init__.py b/RATapi/examples/languages/__init__.py index e69de29b..99fd5e1f 100644 --- a/RATapi/examples/languages/__init__.py +++ b/RATapi/examples/languages/__init__.py @@ -0,0 +1 @@ +"""A benchmark for using RAT custom files in three different languages.""" diff --git a/RATapi/examples/languages/custom_bilayer.py b/RATapi/examples/languages/custom_bilayer.py index dc978e31..1777b358 100644 --- a/RATapi/examples/languages/custom_bilayer.py +++ b/RATapi/examples/languages/custom_bilayer.py @@ -1,7 +1,10 @@ +"""A custom bilayer model for the custom file languages benchmark.""" + import numpy as np def custom_bilayer(params, bulk_in, bulk_out, contrast): + """Calculate the layer parameters for a custom bilayer model.""" sub_rough = params[0] oxide_thick = params[1] oxide_hydration = params[2] diff --git a/RATapi/examples/languages/setup_problem.py b/RATapi/examples/languages/setup_problem.py index 979c5ed1..4eea0965 100644 --- a/RATapi/examples/languages/setup_problem.py +++ b/RATapi/examples/languages/setup_problem.py @@ -1,3 +1,5 @@ +"""A custom example problem for the languages benchmark.""" + import pathlib import numpy as np @@ -6,7 +8,7 @@ def make_example_problem(): - """Custom Layers example for Supported DSPC layer. + """Generate a Custom Layers example for Supported DSPC layer. Example of using custom layers to model a DSPC supported bilayer. """ diff --git a/RATapi/examples/normal_reflectivity/DSPC_custom_XY.py b/RATapi/examples/normal_reflectivity/DSPC_custom_XY.py index ab1492da..27b15ffe 100644 --- a/RATapi/examples/normal_reflectivity/DSPC_custom_XY.py +++ b/RATapi/examples/normal_reflectivity/DSPC_custom_XY.py @@ -1,3 +1,5 @@ +"""An example of analysing a Custom XY model.""" + import pathlib import numpy as np @@ -6,7 +8,7 @@ def DSPC_custom_XY(): - r"""Custom XY Example for Supported DSPC layer. + r"""Calculate a Custom XY Example for Supported DSPC layer. In this example, we model the same data (DSPC supported bilayer) as the Custom Layers example, but this time we will use continuous distributions of the volume fractions of each component to build up the SLD profiles (as described in diff --git a/RATapi/examples/normal_reflectivity/DSPC_custom_layers.py b/RATapi/examples/normal_reflectivity/DSPC_custom_layers.py index 508653fb..66a53589 100644 --- a/RATapi/examples/normal_reflectivity/DSPC_custom_layers.py +++ b/RATapi/examples/normal_reflectivity/DSPC_custom_layers.py @@ -1,3 +1,5 @@ +"""Example of using custom layers to model a DSPC supported bilayer.""" + import pathlib import numpy as np @@ -6,10 +8,7 @@ def DSPC_custom_layers(): - """Custom Layers example for Supported DSPC layer. - - Example of using custom layers to model a DSPC supported bilayer. - """ + """Calculate a Custom Layers example for a supported DSPC layer.""" problem = RAT.Project(name="Orso lipid example - custom layers", model="custom layers", geometry="substrate/liquid") # First we need to set up a parameters group. We will be using a pre-prepared custom model file, so we need to add diff --git a/RATapi/examples/normal_reflectivity/DSPC_data_background.py b/RATapi/examples/normal_reflectivity/DSPC_data_background.py index 02987226..e4b6869f 100644 --- a/RATapi/examples/normal_reflectivity/DSPC_data_background.py +++ b/RATapi/examples/normal_reflectivity/DSPC_data_background.py @@ -1,3 +1,5 @@ +"""A standard layers example with a data background.""" + import pathlib import numpy as np @@ -6,7 +8,7 @@ def DSPC_data_background(): - """Standard Layers fit of a DSPC floating bilayer""" + """Calculate a Standard Layers fit of a DSPC floating bilayer with a data background.""" problem = RAT.Project(name="original_dspc_bilayer", model="standard layers", geometry="substrate/liquid") # Set up the relevant parameters diff --git a/RATapi/examples/normal_reflectivity/DSPC_function_background.py b/RATapi/examples/normal_reflectivity/DSPC_function_background.py index 8a691107..54cc9d47 100644 --- a/RATapi/examples/normal_reflectivity/DSPC_function_background.py +++ b/RATapi/examples/normal_reflectivity/DSPC_function_background.py @@ -1,3 +1,5 @@ +"""A standard layers example with a function background.""" + import pathlib import numpy as np @@ -6,7 +8,7 @@ def DSPC_function_background(): - """Standard Layers fit of a DSPC floating bilayer""" + """Calculate a standard Layers fit of a DSPC floating bilayer with a function background.""" problem = RAT.Project(name="original_dspc_bilayer", model="standard layers", geometry="substrate/liquid") # Set up the relevant parameters diff --git a/RATapi/examples/normal_reflectivity/DSPC_standard_layers.py b/RATapi/examples/normal_reflectivity/DSPC_standard_layers.py index 0d0d1181..f4cc2ce4 100644 --- a/RATapi/examples/normal_reflectivity/DSPC_standard_layers.py +++ b/RATapi/examples/normal_reflectivity/DSPC_standard_layers.py @@ -1,3 +1,5 @@ +"""An example of a standard layers model in RAT.""" + import pathlib import numpy as np @@ -6,7 +8,10 @@ def DSPC_standard_layers(): - """Standard Layers fit of a DSPC floating bilayer""" + """Calculate a standard Layers fit of a DSPC floating bilayer. + + The sample consists of a DSPC bilayer, on a silane SAM on Silicon. + """ problem = RAT.Project(name="original_dspc_bilayer", model="standard layers", geometry="substrate/liquid") # Set up the relevant parameters diff --git a/RATapi/examples/normal_reflectivity/__init__.py b/RATapi/examples/normal_reflectivity/__init__.py index e69de29b..382a9167 100644 --- a/RATapi/examples/normal_reflectivity/__init__.py +++ b/RATapi/examples/normal_reflectivity/__init__.py @@ -0,0 +1 @@ +"""Examples of normal reflectivity calculations in RAT.""" diff --git a/RATapi/examples/normal_reflectivity/background_function.py b/RATapi/examples/normal_reflectivity/background_function.py index 6728d6f8..43624a08 100644 --- a/RATapi/examples/normal_reflectivity/background_function.py +++ b/RATapi/examples/normal_reflectivity/background_function.py @@ -1,7 +1,10 @@ +"""A background function for an example.""" + import numpy as np -def backgroundFunction(xdata, params): +def background_function(xdata, params): + """Return the background function for a given set of points in q.""" # Split up the params array Ao = params[0] k = params[1] diff --git a/RATapi/examples/normal_reflectivity/custom_XY_DSPC.py b/RATapi/examples/normal_reflectivity/custom_XY_DSPC.py index 0a8c1167..dc1d1013 100644 --- a/RATapi/examples/normal_reflectivity/custom_XY_DSPC.py +++ b/RATapi/examples/normal_reflectivity/custom_XY_DSPC.py @@ -1,10 +1,12 @@ +"""A custom XY model for a supported DSPC bilayer.""" + import math import numpy as np def custom_XY_DSPC(params, bulk_in, bulk_out, contrast): - """This function makes a model of a supported DSPC bilayer using volume restricted distribution functions.""" + """Calculate the continuous SLD of a supported DSPC bilayer using volume restricted distribution functions.""" # Split up the parameters subRough = params[0] oxideThick = params[1] @@ -118,7 +120,8 @@ def custom_XY_DSPC(params, bulk_in, bulk_out, contrast): def layer(z, prevLaySurf, thickness, height, Sigma_L, Sigma_R): - """This produces a layer, with a defined thickness, height and roughness. + """Produce a layer, with a defined thickness, height and roughness. + Each side of the layer has its own roughness value. """ # Find the edges diff --git a/RATapi/examples/normal_reflectivity/custom_bilayer_DSPC.py b/RATapi/examples/normal_reflectivity/custom_bilayer_DSPC.py index c97cde4e..005147ff 100644 --- a/RATapi/examples/normal_reflectivity/custom_bilayer_DSPC.py +++ b/RATapi/examples/normal_reflectivity/custom_bilayer_DSPC.py @@ -1,8 +1,10 @@ +"""A custom layer model for a DSPC supported bilayer.""" + import numpy as np def custom_bilayer_DSPC(params, bulk_in, bulk_out, contrast): - """CUSTOMBILAYER RAT Custom Layer Model File. + """Calculate layer parameters for a DSPC supported bilayer. This file accepts 3 vectors containing the values for params, bulk in and bulk out. The final parameter is an index of the contrast being calculated. diff --git a/RATapi/examples/orso_integration/orso_integration.ipynb b/RATapi/examples/orso_integration/orso_integration.ipynb new file mode 100644 index 00000000..aa6f0797 --- /dev/null +++ b/RATapi/examples/orso_integration/orso_integration.ipynb @@ -0,0 +1,188 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ``orsopy`` Integration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``python-RAT`` contains some integration with ``orsopy``, allowing for convenient interaction with the ``.ort`` file format. This integration is available through the `RATapi.utils.orso` submodule." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import RATapi.utils.orso" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating models from the ORSO model description language\n", + "\n", + "The [ORSO model description format](https://www.reflectometry.org/advanced_and_expert_level/file_format/simple_model) allows the description of a standard slab model as a one-line string, provided that all the layer materials are defined [in the ORSO SLD database](https://slddb.esss.dk/slddb/).\n", + "\n", + "The function `RATapi.utils.orso.orso_model_to_rat` function can read a model and return an `ORSOSample` dataclass, which gives bulk in and bulk out parameters for the model, a list of all layers defined in the model, and all the parameters needed to define those layers as RAT models. \n", + "\n", + "**Note:** the ORSO format gives the thicknesses of materials in *nanometres*. When we convert them to RAT parameters, the units will be converted to Angstroms.\n", + "\n", + "For example, the string `air | Ni 100 | SiO2 0.5 | Si` describes a 1000 angstrom nickel film backed by a 5 angstrom silicon oxide layer. The bulk-in and bulk-out are air and silicon respectively. The roughnesses and SLDs will be calculated or taken from the ORSO SLD database." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# create the RAT parameters and layers from this model\n", + "sample = RATapi.utils.orso.orso_model_to_rat(\"air | Ni 100 | SiO2 0.5 | Si\")\n", + "print(sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can also set `absorption=True` and the model will account for absorption. For example if we change the nickel film for a boron carbide film and want to account for its relatively high absorption, we can add it to the output:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = RATapi.utils.orso.orso_model_to_rat(\"vacuum | B4C 100 | SiO2 0.5 | Si\", absorption=True)\n", + "print(sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, ORSO supports defining repeated layers using parentheses. For example, if we had a polarising multilayer of 5 repetitions of 70 angstrom silicon and 70 angstrom iron, we could represent it as `air | 5 ( Si 7 | Fe 7 ) | Si`.\n", + "\n", + "RAT will only create the number of layers and parameters necessary, but the `ORSOSample` object's `model` attribute will give a list of layer names with the structure of the model preserved, which can be given as the layer model for a Contrast." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sample = RATapi.utils.orso.orso_model_to_rat(\"air | 5 ( Si 7 | Fe 7 ) | Si\")\n", + "print(sample)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading in data and models from .ort files" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "RAT can also load both data and model information from an .ort file. This is done through the `ORSOProject` object, which takes a file path and can also optionally account for absorption.\n", + "\n", + "The example data file we use here is example data for an unknown film on deposited on silicon." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "data_path = pathlib.Path(\"../data\")\n", + "\n", + "orso_data = RATapi.utils.orso.ORSOProject(data_path / \"c_PLP0011859_q.ort\")\n", + "print(orso_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `ORSOProject` object contains two lists: `ORSOProject.data` and `ORSOProject.samples`. The former is a list of Data objects with each dataset defined in the file, and the latter is a list of `ORSOSample` objects (like above) with model information. Note that if the .ort file does not define a model for a dataset, that index of `ORSOProject.samples` will be None.\n", + "\n", + "It's then easy to access this data to create a RAT `Project` that represents our data." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from RATapi.models import Background, Contrast, Parameter, Resolution\n", + "\n", + "dataset = orso_data.data[0]\n", + "sample = orso_data.samples[0]\n", + "\n", + "project = RATapi.Project(\n", + " name = \"Example Project\",\n", + " geometry = \"substrate/liquid\",\n", + " parameters = sample.parameters,\n", + " bulk_in = [sample.bulk_in],\n", + " bulk_out = [sample.bulk_out],\n", + " scalefactors = [Parameter(name=\"Scalefactor\", min=0, value=0.34, max=1.5)],\n", + " background_parameters = [Parameter(name=\"Background Parameter\", min=0, value=2e-6, max=1)],\n", + " backgrounds = [Background(name=\"Background\", type=\"constant\", source=\"Background Parameter\")],\n", + " resolutions = [Resolution(name=\"Data Resolution\", type=\"data\")],\n", + " data = [dataset],\n", + " layers = sample.layers,\n", + " contrasts = [Contrast(\n", + " name = \"prist4\",\n", + " data = dataset.name,\n", + " background = \"Background\",\n", + " bulk_in = sample.bulk_in.name,\n", + " bulk_out = sample.bulk_out.name,\n", + " scalefactor = \"Scalefactor\",\n", + " resolution = \"Data Resolution\",\n", + " model = sample.model,\n", + " )]\n", + ")\n", + "\n", + "controls = RATapi.Controls()\n", + "project, results = RATapi.run(project, controls)\n", + "RATapi.plotting.plot_ref_sld(project, results)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/RATapi/inputs.py b/RATapi/inputs.py index e747e3ab..06d9f82c 100644 --- a/RATapi/inputs.py +++ b/RATapi/inputs.py @@ -1,4 +1,4 @@ -"""Converts python models to the necessary inputs for the compiled RAT code""" +"""Converts python models to the necessary inputs for the compiled RAT code.""" import importlib import os @@ -10,9 +10,19 @@ import RATapi import RATapi.controls import RATapi.wrappers -from RATapi.rat_core import Checks, Control, Limits, NameStore, Priors, ProblemDefinition +from RATapi.rat_core import Checks, Control, NameStore, ProblemDefinition from RATapi.utils.enums import Calculations, Languages, LayerModels, TypeOptions +parameter_field = { + "parameters": "params", + "bulk_in": "bulkIns", + "bulk_out": "bulkOuts", + "scalefactors": "scalefactors", + "domain_ratios": "domainRatios", + "background_parameters": "backgroundParams", + "resolution_parameters": "resolutionParams", +} + def get_python_handle(file_name: str, function_name: str, path: Union[str, pathlib.Path] = "") -> Callable: """Get the function handle from a function defined in a python module located anywhere within the filesystem. @@ -46,6 +56,7 @@ class FileHandles: ---------- files : ClassList[CustomFile] A list of custom file models. + """ def __init__(self, files=None): @@ -56,8 +67,8 @@ def __iter__(self): self.index = 0 return self - def get_handle(self, index): - """Returns file handle for a given custom file. + def get_handle(self, index: int): + """Return file handle for a given custom file. Parameters ---------- @@ -76,7 +87,15 @@ def get_handle(self, index): return file_handle - def copy(self): + def copy(self) -> "FileHandles": + """Create a copy of the FileHandles object. + + Returns + ------- + FileHandles + The copy of this FileHandles object. + + """ handles = FileHandles() handles.files = [file.copy() for file in self.files] @@ -94,9 +113,8 @@ def __len__(self): return len(self.files) -def make_input(project: RATapi.Project, controls: RATapi.Controls) -> tuple[ProblemDefinition, Limits, Priors, Control]: - """Constructs the inputs required for the compiled RAT code using the data defined in the input project and - controls. +def make_input(project: RATapi.Project, controls: RATapi.Controls) -> tuple[ProblemDefinition, Control]: + """Construct the inputs required for the compiled RAT code using the data defined in the input project and controls. Parameters ---------- @@ -109,68 +127,18 @@ def make_input(project: RATapi.Project, controls: RATapi.Controls) -> tuple[Prob ------- problem : RAT.rat_core.ProblemDefinition The problem input used in the compiled RAT code. - limits : RAT.rat_core.Limits - A list of min/max values for each parameter defined in the project. - priors : RAT.rat_core.Priors - The priors defined for each parameter in the project. cpp_controls : RAT.rat_core.Control The controls object used in the compiled RAT code. """ - parameter_field = { - "parameters": "params", - "bulk_in": "bulkIns", - "bulk_out": "bulkOuts", - "scalefactors": "scalefactors", - "domain_ratios": "domainRatios", - "background_parameters": "backgroundParams", - "resolution_parameters": "resolutionParams", - } - - prior_id = {"uniform": 1, "gaussian": 2, "jeffreys": 3} - - checks = Checks() - limits = Limits() - priors = Priors() - - for class_list in RATapi.project.parameter_class_lists: - setattr(checks, parameter_field[class_list], [int(element.fit) for element in getattr(project, class_list)]) - setattr( - limits, - parameter_field[class_list], - [[element.min, element.max] for element in getattr(project, class_list)], - ) - setattr( - priors, - parameter_field[class_list], - [[element.name, element.prior_type, element.mu, element.sigma] for element in getattr(project, class_list)], - ) - - # Use dummy values for qzshifts - checks.qzshifts = [] - limits.qzshifts = [] - priors.qzshifts = [] - - priors.priorNames = [ - param.name for class_list in RATapi.project.parameter_class_lists for param in getattr(project, class_list) - ] - priors.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) - ] - - if project.model == LayerModels.CustomXY: - controls.calcSldDuringFit = True - - problem = make_problem(project, checks) + problem = make_problem(project) cpp_controls = make_controls(controls) - return problem, limits, priors, cpp_controls + return problem, cpp_controls -def make_problem(project: RATapi.Project, checks: Checks) -> ProblemDefinition: - """Constructs the problem input required for the compiled RAT code. +def make_problem(project: RATapi.Project) -> ProblemDefinition: + """Construct the problem input required for the compiled RAT code. Parameters ---------- @@ -184,6 +152,7 @@ def make_problem(project: RATapi.Project, checks: Checks) -> ProblemDefinition: """ hydrate_id = {"bulk in": 1, "bulk out": 2} + prior_id = {"uniform": 1, "gaussian": 2, "jeffreys": 3} # Set contrast parameters according to model type if project.model == LayerModels.StandardLayers: @@ -312,15 +281,13 @@ def make_problem(project: RATapi.Project, checks: Checks) -> ProblemDefinition: problem.dataPresent = make_data_present(project) problem.dataLimits = data_limits problem.simulationLimits = simulation_limits - problem.oilChiDataPresent = [0] * len(project.contrasts) problem.numberOfContrasts = len(project.contrasts) problem.geometry = project.geometry problem.useImaginary = project.absorption - problem.repeatLayers = [[0, 1]] * len(project.contrasts) # This is marked as "to do" in RAT + problem.repeatLayers = [1] * len(project.contrasts) problem.contrastBackgroundParams = contrast_background_params problem.contrastBackgroundTypes = contrast_background_types problem.contrastBackgroundActions = [contrast.background_action for contrast in project.contrasts] - problem.contrastQzshifts = [1] * len(project.contrasts) # This is marked as "to do" in RAT problem.contrastScalefactors = [ project.scalefactors.index(contrast.scalefactor, True) for contrast in project.contrasts ] @@ -331,7 +298,6 @@ def make_problem(project: RATapi.Project, checks: Checks) -> ProblemDefinition: problem.contrastResolutionTypes = contrast_resolution_types problem.backgroundParams = [param.value for param in project.background_parameters] - problem.qzshifts = [0.0] problem.scalefactors = [param.value for param in project.scalefactors] problem.bulkIns = [param.value for param in project.bulk_in] problem.bulkOuts = [param.value for param in project.bulk_out] @@ -372,40 +338,35 @@ def make_problem(project: RATapi.Project, checks: Checks) -> ProblemDefinition: for param in getattr(project, class_list) if param.fit ] - problem.otherParams = [ - param.value - for class_list in RATapi.project.parameter_class_lists - for param in getattr(project, class_list) - if not param.fit + problem.priorNames = [ + param.name for class_list in RATapi.project.parameter_class_lists for param in getattr(project, class_list) ] - problem.otherLimits = [ - [param.min, param.max] + 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) - if not param.fit ] # Names problem.names = NameStore() - problem.names.params = [param.name for param in project.parameters] - problem.names.backgroundParams = [param.name for param in project.background_parameters] - problem.names.scalefactors = [param.name for param in project.scalefactors] - problem.names.qzshifts = [] # Placeholder for qzshifts - problem.names.bulkIns = [param.name for param in project.bulk_in] - problem.names.bulkOuts = [param.name for param in project.bulk_out] - problem.names.resolutionParams = [param.name for param in project.resolution_parameters] - problem.names.domainRatios = [param.name for param in project.domain_ratios] + for class_list in RATapi.project.parameter_class_lists: + 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 + 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 def make_resample(project: RATapi.Project) -> list[int]: - """Constructs the "resample" field of the problem input required for the compiled RAT code. + """Construct the "resample" field of the problem input required for the compiled RAT code. Parameters ---------- @@ -414,7 +375,7 @@ def make_resample(project: RATapi.Project) -> list[int]: Returns ------- - : list[int] + list[int] The "resample" field of the problem input used in the compiled RAT code. """ @@ -422,7 +383,7 @@ def make_resample(project: RATapi.Project) -> list[int]: def make_data_present(project: RATapi.Project) -> list[int]: - """Constructs the "dataPresent" field of the problem input required for the compiled RAT code. + """Construct the "dataPresent" field of the problem input required for the compiled RAT code. Parameters ---------- @@ -431,7 +392,7 @@ def make_data_present(project: RATapi.Project) -> list[int]: Returns ------- - : list[int] + list[int] The "dataPresent" field of the problem input used in the compiled RAT code. """ @@ -439,8 +400,7 @@ def make_data_present(project: RATapi.Project) -> list[int]: def check_indices(problem: ProblemDefinition) -> None: - """Checks the indices in contrast lists in a ProblemDefinition object lie within the range of the corresponding - parameter lists. + """Check the indices given in a problem's contrasts lie within the range of the corresponding parameter lists. Parameters ---------- @@ -528,14 +488,12 @@ def append_data_background(data: np.array, background: np.array) -> np.array: def make_controls(input_controls: RATapi.Controls) -> Control: - """Converts the controls object to the format required by the compiled RAT code. + """Convert the controls object to the format required by the compiled RAT code. Parameters ---------- input_controls : RAT.Controls The controls model, which defines algorithmic properties. - checks : Rat.rat_core.Checks - States whether or not to fit each parameter defined in the project. Returns ------- diff --git a/RATapi/models.py b/RATapi/models.py index 3d9f7097..e8e207ae 100644 --- a/RATapi/models.py +++ b/RATapi/models.py @@ -74,6 +74,14 @@ def __setattr__(self, name, value): @property def display_fields(self) -> dict: + """The fields which should be visible in a table and their values. + + Returns + ------- + dict + A dictionary of the fields which should be visible in a table and their values. + + """ visible_fields = ["name", "type", "source"] if self.type != TypeOptions.Constant: visible_fields.append("value_1") @@ -111,8 +119,8 @@ class Background(Signal): name: str = Field(default_factory=lambda: f"New Background {next(background_number)}", min_length=1) @model_validator(mode="after") - def warn_parameters(self): - """Raise a warning if the parameters given are not expected for the given type.""" + def check_unsupported_parameters(self): + """Raise an error if the parameters given are not supported for the given type.""" if self.type == TypeOptions.Constant: expected_empty_fields = ["value_1", "value_2", "value_3", "value_4", "value_5"] elif self.type == TypeOptions.Data: @@ -122,10 +130,9 @@ def warn_parameters(self): non_empty_fields = [v for v in expected_empty_fields if getattr(self, v) != ""] if non_empty_fields: - warnings.warn( - "The following values are not recognised by this background type and will be ignored: " - f"{', '.join(non_empty_fields)}", - stacklevel=2, + raise ValueError( + f'The following values are not supported by the "{self.type}" Background type: ' + f"{', '.join(non_empty_fields)}" ) return self @@ -179,7 +186,6 @@ class Contrast(RATModel): @classmethod def domain_ratio_error(cls, data: Any): """If the extra input 'domain_ratio' is given, give a more descriptive error.""" - if isinstance(data, dict) and data.get("domain_ratio", False): raise ValueError( "The Contrast class does not support domain ratios. Use the ContrastWithRatio class instead." @@ -301,17 +307,18 @@ class CustomFile(RATModel): path: pathlib.Path = pathlib.Path(".") def model_post_init(self, __context: Any) -> None: - """If a "filename" is supplied but the "function_name" field is not set, the "function_name" should be set to - the file name without the extension. + """Autogenerate the function name from the filename if not set. + + If a filename is supplied but the ``function_name`` field is not set, the ``function_name`` should be set to + the filename without the extension. + """ if "filename" in self.model_fields_set and "function_name" not in self.model_fields_set: self.function_name = pathlib.Path(self.filename).stem @model_validator(mode="after") def set_matlab_function_name(self): - """If we have a matlab custom function, the "function_name" should be set to the filename without the - extension. - """ + """For a matlab custom function, ``function_name`` should be set to the filename without the extension.""" if self.language == Languages.Matlab and self.function_name != pathlib.Path(self.filename).stem: self.function_name = pathlib.Path(self.filename).stem @@ -340,7 +347,7 @@ class Data(RATModel, arbitrary_types_allowed=True): @field_validator("data") @classmethod def check_data_dimension(cls, data: np.ndarray[float]) -> np.ndarray[float]: - """The data must be a two-dimensional array containing at least three columns.""" + """Ensure the data is be a two-dimensional array containing at least three columns.""" try: data.shape[1] except IndexError: @@ -353,13 +360,15 @@ def check_data_dimension(cls, data: np.ndarray[float]) -> np.ndarray[float]: @field_validator("data_range", "simulation_range") @classmethod def check_min_max(cls, limits: list[float], info: ValidationInfo) -> list[float]: - """The data range and simulation range maximum must be greater than the minimum.""" + """Ensure the data range and simulation range maximum is greater than the minimum.""" if limits[0] > limits[1]: raise ValueError(f'{info.field_name} "min" value is greater than the "max" value') return limits def model_post_init(self, __context: Any) -> None: - """If the "data_range" and "simulation_range" fields are not set, but "data" is supplied, the ranges should be + """Automatically generate ``data_range`` and ``simulation_range`` from the data. + + If the ``data_range`` and ``simulation_range`` fields are not set, but ``data`` is supplied, the ranges are set to the min and max values of the first column (assumed to be q) of the supplied data. """ if self.data.shape[0] > 0: @@ -371,7 +380,9 @@ def model_post_init(self, __context: Any) -> None: @model_validator(mode="after") def check_ranges(self) -> "Data": - """The limits of the "data_range" field must lie within the range of the supplied data, whilst the limits + """Check that ``data_range`` is within the q-range of the data, and ``simulation_range`` is outside it. + + The limits of the "data_range" field must lie within the range of the supplied data, whilst the limits of the "simulation_range" field must lie outside the range of the supplied data. """ if self.data.shape[0] > 0: @@ -474,7 +485,6 @@ class Layer(RATModel, populate_by_name=True): @classmethod def sld_imaginary_error(cls, data: Any): """If the extra input 'sld_imaginary' is given, give a more descriptive error.""" - if isinstance(data, dict) and data.get("SLD_imaginary", False): raise ValueError("The Layer class does not support imaginary SLD. Use the AbsorptionLayer class instead.") @@ -549,20 +559,21 @@ class Parameter(RATModel): @model_validator(mode="after") def check_min_max(self) -> "Parameter": - """The maximum value of a parameter must be greater than the minimum.""" + """Ensure the maximum value of a parameter is greater than the minimum.""" if self.min > self.max: raise ValueError(f"The maximum value {self.max} must be greater than the minimum value {self.min}") return self @model_validator(mode="after") def check_value_in_range(self) -> "Parameter": - """The value of a parameter must lie within its defined bounds.""" + """Ensure the value of a parameter lies within its defined bounds.""" if self.value < self.min or self.value > self.max: raise ValueError(f"value {self.value} is not within the defined range: {self.min} <= value <= {self.max}") return self @property def display_fields(self) -> dict: + """Only display Prior information if ``show_priors`` is true.""" visible_fields = ["name", "min", "value", "max", "fit"] if self.show_priors: visible_fields.append("prior_type") @@ -618,8 +629,8 @@ def validate_unimplemented_resolutions(cls, type: TypeOptions): return type @model_validator(mode="after") - def warn_parameters(self): - """Raise a warning if the parameters given are not expected for the given type.""" + def check_unsupported_parameters(self): + """Raise an error if the parameters given are not supported for the given type.""" if self.type == TypeOptions.Constant: expected_empty_fields = ["value_1", "value_2", "value_3", "value_4", "value_5"] elif self.type == TypeOptions.Data: @@ -629,10 +640,9 @@ def warn_parameters(self): non_empty_fields = [v for v in expected_empty_fields if getattr(self, v) != ""] if non_empty_fields: - warnings.warn( - "The following values are not recognised by this resolution type and will be ignored: " - f"{', '.join(non_empty_fields)}", - stacklevel=2, + raise ValueError( + f'The following values are not supported by the "{self.type}" Resolution type: ' + f"{', '.join(non_empty_fields)}" ) return self diff --git a/RATapi/outputs.py b/RATapi/outputs.py index 0f5aeb31..925db7c5 100644 --- a/RATapi/outputs.py +++ b/RATapi/outputs.py @@ -1,4 +1,4 @@ -"""Converts outputs from the compiled RAT code to python dataclasses""" +"""Converts results from the compiled RAT code to python dataclasses.""" from dataclasses import dataclass from typing import Any, Optional, Union @@ -10,8 +10,9 @@ def get_field_string(field: str, value: Any, array_limit: int): - """Returns a string representation of class fields where large and multidimensional arrays are represented by their - shape. + """Return a string representation of class fields where large arrays are represented by their shape. + + An array will be displayed as just its shape if it is multidimensional or 1D and longer than ``array_limit``. Parameters ---------- @@ -26,6 +27,21 @@ def get_field_string(field: str, value: Any, array_limit: int): ------- field_string : str The string representation of the field in the RAT output class. + + Examples + -------- + >>> get_field_string("data", 130, 5) + "data = 130" + + >>> get_field_string("data", array([1, 2, 3, 4, 5]), 10) + "data = [1 2 3 4 5]" + + >>> get_field_string("data", array([1, 2, 3, 4, 5]), 3) + "data = Data array: [5]," + + >>> get_field_string("data", array([[1, 2, 3], [4, 5, 6]]), 10) + "data = Data array: [2 x 3]," + """ array_text = "Data array: " if isinstance(value, list) and len(value) > 0: @@ -52,6 +68,8 @@ def get_field_string(field: str, value: Any, array_limit: int): class RATResult: + """A mixin class which truncates arrays when the class is displayed.""" + def __str__(self): output = f"{self.__class__.__name__}(\n" for key, value in self.__dict__.items(): @@ -62,12 +80,40 @@ def __str__(self): @dataclass class CalculationResults(RATResult): + """The goodness of fit from the Abeles calculation. + + Parameters + ---------- + chiValues : np.ndarray + The chi-squared value for each contrast. + sumChi : float + The sum of the chiValues array. + + """ + chiValues: np.ndarray sumChi: float @dataclass class ContrastParams(RATResult): + """The experimental parameters for each contrast. + + Parameters + ---------- + scalefactors : np.ndarray + The scalefactor values for each contrast. + bulkIn : np.ndarray + The bulk in values for each contrast. + bulkOut : np.ndarray + The bulk out values for each contrast. + subRoughs : np.ndarray + The substrate roughness values for each contrast. + resample : np.ndarray + An array containing whether each contrast was resampled. + + """ + scalefactors: np.ndarray bulkIn: np.ndarray bulkOut: np.ndarray @@ -77,13 +123,50 @@ class ContrastParams(RATResult): @dataclass class Results: + """The results of a RAT calculation. + + Parameters + ---------- + reflectivity : list + The reflectivity curves for each contrast, + with the same range as the data + (``data_range`` in the contrast's ``Data`` object) + simulation : list + The reflectivity curves for each contrast, + which can be a wider range to allow extrapolation + (``simulation_range`` in the contrast's ``Data`` object). + shiftedData : list + The data with scalefactors and background corrections applied. + backgrounds : list + The background for each contrast defined over the simulation range. + resolutions : list + The resolution for each contrast defined over the simulation range. + sldProfiles : list + The SLD profiles for each contrast. + layers : list + The array of layer parameter values for each contrast. + resampledLayers : list + If resampling is used, the array of layer parameter values for each contrast after resampling has been + performed. + calculationResults : CalculationResults + The chi-squared fit results from the final calculation and fit. + contrastParams : ContrastParams + The experimental parameters for the contrasts. + fitParams : np.ndarray + The best fit value of the parameter with name ``fitNames[i]``. + fitNames : list[str] + The names of the fit parameters, where ``fitNames[i]`` is the name + of the parameter with value given in ``fitParams[i]``. + + """ + reflectivity: list simulation: list shiftedData: list backgrounds: list resolutions: list - layerSlds: list sldProfiles: list + layers: list resampledLayers: list calculationResults: CalculationResults contrastParams: ContrastParams @@ -99,6 +182,28 @@ def __str__(self): @dataclass class PredictionIntervals(RATResult): + """The Bayesian prediction intervals for 95% and 65% confidence. + + For ``reflectivity`` and ``sld``, each list item is an array + with five rows. The rows represent: + + - 0: the 5th percentile; + - 1: the 35th percentile; + - 2: the mean value of the interval; + - 3: the 65th percentile; + - 4: the 95th percentile. + + Parameters + ---------- + reflectivity : list + The prediction interval data for reflectivity of each contrast. + SLD : list + The prediction interval data for SLD of each contrast. + sampleChi : np.ndarray + The value of sumChi at each point of the Markov chain. + + """ + reflectivity: list sld: list sampleChi: np.ndarray @@ -106,6 +211,19 @@ class PredictionIntervals(RATResult): @dataclass class ConfidenceIntervals(RATResult): + """The 65% and 95% confidence intervals for the best fit results. + + Parameters + ---------- + percentile95 : np.ndarray + The 95% confidence intervals for each fit parameter. + percentile65 : np.ndarray + The 65% confidence intervals for each fit parameter. + mean : np.ndarray + The mean values for each fit parameter. + + """ + percentile95: np.ndarray percentile65: np.ndarray mean: np.ndarray @@ -113,6 +231,54 @@ class ConfidenceIntervals(RATResult): @dataclass class DreamParams(RATResult): + """The parameters used by the inner DREAM algorithm. + + Parameters + ---------- + nParams : float + The number of parameters used by the algorithm. + nChains : float + The number of MCMC chains used by the algorithm. + nGenerations : float + The number of DE generations calculated per iteration. + parallel : bool + Whether the algorithm should run chains in parallel. + CPU : float + The number of processor cores used for parallel chains. + jumpProbability : float + A probability range for the size of jumps when performing subspace sampling. + pUnitGamma : float + The probability that the scaling-down factor of jumps will be ignored + and a larger jump will be taken for one iteration. + nCR : float + The number of crossovers performed each iteration. + delta : float + The number of chain mutation pairs proposed each iteration. + steps : float + The number of MCMC steps to perform between conversion checks. + zeta : float + The ergodicity of the algorithm. + outlier : str + What test should be used to detect outliers. + adaptPCR : bool + Whether the crossover probability for differential evolution should be + adapted by the algorithm as it runs. + thinning : float + The thinning rate of each Markov chain (to reduce memory intensity) + epsilon : float + The cutoff threshold for Approximate Bayesian Computation (if used) + ABC : bool + Whether Approximate Bayesian Computation is used. + IO : bool + Whether the algorithm should perform IO writes of the model in parallel. + storeOutput : bool + Whether output model simulations are performed. + R : np.ndarray + An array where row ``i`` is the list of chains + with which chain ``i`` can mutate. + + """ + nParams: float nChains: float nGenerations: float @@ -136,11 +302,47 @@ class DreamParams(RATResult): @dataclass class DreamOutput(RATResult): + """The diagnostic output information from DREAM. + + Parameters + ---------- + allChains : np.ndarray + An ``nGenerations`` x ``nParams + 2`` x ``nChains`` size array, + where ``chain_k = DreamOutput.allChains[:, :, k]`` + is the data of chain ``k`` in the final iteration; + for generation i of the final iteration, ``chain_k[i, j]`` represents: + + - the sampled value of parameter ``j`` for ``j in 0:nParams``; + - the associated log-prior for those sampled values for ``j = nParams + 1``; + - the associated log-likelihood for those sampled values for ``j = nParams + 2``. + + outlierChains : np.ndarray + A two-column array where ``DreamOutput.AR[i, 1]`` is the index of a chain + and ``DreamOutput.AR[i, 0]`` is the length of that chain when it was removed + for being an outlier. + runtime : float + The runtime of the DREAM algorithm in seconds. + iteration : float + The number of iterations performed. + AR : np.ndarray + A two-column array where ``DreamOutput.AR[i, 0]`` is an iteration number + and ``DreamOutput.AR[i, 1]`` is the average acceptance rate of chain step + proposals for that iteration. + R_stat : np.ndarray + An array where ``DreamOutput.R_stat[i, 0]`` is an iteration number and + ``DreamOutput.R_stat[i, j]`` is the convergence statistic for parameter ``j`` + at that iteration (where chains are indexed 1 to ``nParams`` inclusive). + CR : np.ndarray + A four-column array where ``DreamOutput.CR[i, 0]`` is an iteration number, + ``and DreamOutput.CR[i, j]`` is the selection probability of the ``j``'th crossover + value for that iteration. + + """ + allChains: np.ndarray outlierChains: np.ndarray runtime: float iteration: float - modelOutput: float AR: np.ndarray R_stat: np.ndarray CR: np.ndarray @@ -148,6 +350,26 @@ class DreamOutput(RATResult): @dataclass class NestedSamplerOutput(RATResult): + """The output information from the Nested Sampler (ns). + + Parameters + ---------- + logZ : float + The natural logarithm of the evidence Z for the parameter values. + logZErr : float + The estimated uncertainty in the final value of logZ. + nestSamples : np.ndarray + ``NestedSamplerOutput.nestSamples[i, j]`` represents the values + sampled at iteration ``i``, where this value is: + + - the value sampled for parameter ``j``, for ``j`` in ``0:nParams``, + - the minimum log-likelihood for ``j = nParams + 1``. + + postSamples : np.ndarray + The posterior values at the points sampled in ``NestedSamplerOutput.nestSamples``. + + """ + logZ: float logZErr: float nestSamples: np.ndarray @@ -156,6 +378,26 @@ class NestedSamplerOutput(RATResult): @dataclass class BayesResults(Results): + """The results of a Bayesian RAT calculation. + + Parameters + ---------- + predictionIntervals : PredictionIntervals + The prediction intervals. + confidenceIntervals : ConfidenceIntervals + The 65% and 95% confidence intervals for the best fit results. + dreamParams : DreamParams + The parameters used by DREAM, if relevant. + dreamOutput : DreamOutput + The output from DREAM if DREAM was used. + nestedSamplerOutput : NestedSamplerOutput + The output from nested sampling if ns was used. + chain : np.ndarray + The MCMC chains for each parameter. + The ``i``'th column of the array contains the chain for parameter ``fitNames[i]``. + + """ + predictionIntervals: PredictionIntervals confidenceIntervals: ConfidenceIntervals dreamParams: DreamParams @@ -167,9 +409,26 @@ class BayesResults(Results): def make_results( procedure: Procedures, output_results: RATapi.rat_core.OutputResult, - bayes_results: Optional[RATapi.rat_core.BayesResults] = None, + bayes_results: Optional[RATapi.rat_core.OutputBayesResult] = None, ) -> Union[Results, BayesResults]: - """Initialise a python Results or BayesResults object using the outputs from a RAT calculation.""" + """Initialise a python Results or BayesResults object using the outputs from a RAT calculation. + + Parameters + ---------- + procedure : Procedures + The procedure used by the calculation. + output_results : RATapi.rat_core.OutputResult + The C++ output results from the calculation. + bayes_results : Optional[RATapi.rat_core.OutputBayesResult] + The optional extra C++ Bayesian output results from a Bayesian calculation. + + Returns + ------- + Results or BayesResults + A result object containing the results of the calculation, of type + Results for non-Bayesian procedures and BayesResults for Bayesian procedures. + + """ calculation_results = CalculationResults( chiValues=output_results.calculationResults.chiValues, sumChi=output_results.calculationResults.sumChi, @@ -222,7 +481,6 @@ def make_results( outlierChains=bayes_results.dreamOutput.outlierChains, runtime=bayes_results.dreamOutput.runtime, iteration=bayes_results.dreamOutput.iteration, - modelOutput=bayes_results.dreamOutput.modelOutput, AR=bayes_results.dreamOutput.AR, R_stat=bayes_results.dreamOutput.R_stat, CR=bayes_results.dreamOutput.CR, @@ -241,8 +499,8 @@ def make_results( shiftedData=output_results.shiftedData, backgrounds=output_results.backgrounds, resolutions=output_results.resolutions, - layerSlds=output_results.layerSlds, sldProfiles=output_results.sldProfiles, + layers=output_results.layers, resampledLayers=output_results.resampledLayers, calculationResults=calculation_results, contrastParams=contrast_params, @@ -263,8 +521,8 @@ def make_results( shiftedData=output_results.shiftedData, backgrounds=output_results.backgrounds, resolutions=output_results.resolutions, - layerSlds=output_results.layerSlds, sldProfiles=output_results.sldProfiles, + layers=output_results.layers, resampledLayers=output_results.resampledLayers, calculationResults=calculation_results, contrastParams=contrast_params, diff --git a/RATapi/project.py b/RATapi/project.py index 9b38f88e..ae2ed2cc 100644 --- a/RATapi/project.py +++ b/RATapi/project.py @@ -4,6 +4,7 @@ import copy import functools import json +import warnings from enum import Enum from pathlib import Path from textwrap import indent @@ -357,9 +358,11 @@ def check_contrasts(cls, value: ClassList, info: ValidationInfo): return value def model_post_init(self, __context: Any) -> None: - """Initialises the class in the ClassLists for empty data fields, sets protected parameters, gets names of all - defined parameters, determines the contents of the "model" field in contrasts, and wraps ClassList routines to - control revalidation. + """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. """ # Ensure all ClassLists have the correct _class_handle defined for field in (fields := self.model_fields): @@ -439,9 +442,7 @@ def set_domain_ratios(self) -> "Project": @model_validator(mode="after") def set_domain_contrasts(self) -> "Project": - """If we are not running a domains calculation with standard layers, ensure the domain_contrasts component of - the model is empty. - """ + """Ensure ``domain_contrasts`` is empty if we are not running a standard layer domains calculation.""" if not (self.calculation == Calculations.Domains and self.model == LayerModels.StandardLayers): self.domain_contrasts.data = [] return self @@ -486,8 +487,10 @@ def set_calculation(self) -> "Project": @model_validator(mode="after") def set_contrast_model_field(self) -> "Project": - """The contents of the "model" field of "contrasts" depend on the values of the "calculation" and "model_type" - defined in the project. If they have changed, clear the contrast models. + """Clear the contrast models if ``calculation`` or ``model_type`` has changed. + + The contents of the "model" field of "contrasts" depend on the values of the "calculation" and "model_type" + defined in the project. """ model_field = self.get_contrast_model_field() if model_field != self._contrast_model_field: @@ -498,8 +501,10 @@ def set_contrast_model_field(self) -> "Project": @model_validator(mode="after") def check_contrast_model_length(self) -> "Project": - """Given certain values of the "calculation" and "model" defined in the project, the "model" field of - "contrasts" may be constrained in its length. + """Ensure the contrast model isn't too long for a domains, custom layers, or custom XY calculation. + + If a custom model is used, the ``model`` field of the contrast should just be one item long. For + a standard layers domain calculation, it should be exactly two items long. """ if self.model == LayerModels.StandardLayers and self.calculation == Calculations.Domains: for contrast in self.contrasts: @@ -552,7 +557,6 @@ def update_renamed_models(self) -> "Project": for index, param in all_matches: if param in params: setattr(project_field[index], param, new_name) - self._all_names = self.get_all_names() return self @model_validator(mode="after") @@ -561,28 +565,45 @@ def cross_check_model_values(self) -> "Project": values = ["value_1", "value_2", "value_3", "value_4", "value_5"] for field in ["backgrounds", "resolutions"]: self.check_allowed_source(field) - self.check_allowed_values(field, values, getattr(self, f"{field[:-1]}_parameters").get_names()) + self.check_allowed_values( + field, + values, + getattr(self, f"{field[:-1]}_parameters").get_names(), + self._all_names[f"{field[:-1]}_parameters"], + ) self.check_allowed_values( "layers", ["thickness", "SLD", "SLD_real", "SLD_imaginary", "roughness"], self.parameters.get_names(), + self._all_names["parameters"], ) - self.check_allowed_values("contrasts", ["data"], self.data.get_names()) - self.check_allowed_values("contrasts", ["background"], self.backgrounds.get_names()) - self.check_allowed_values("contrasts", ["bulk_in"], self.bulk_in.get_names()) - self.check_allowed_values("contrasts", ["bulk_out"], self.bulk_out.get_names()) - self.check_allowed_values("contrasts", ["scalefactor"], self.scalefactors.get_names()) - self.check_allowed_values("contrasts", ["resolution"], self.resolutions.get_names()) - self.check_allowed_values("contrasts", ["domain_ratio"], self.domain_ratios.get_names()) + self.check_allowed_values("contrasts", ["data"], self.data.get_names(), self._all_names["data"]) + self.check_allowed_values( + "contrasts", ["background"], self.backgrounds.get_names(), self._all_names["backgrounds"] + ) + self.check_allowed_values("contrasts", ["bulk_in"], self.bulk_in.get_names(), self._all_names["bulk_in"]) + self.check_allowed_values("contrasts", ["bulk_out"], self.bulk_out.get_names(), self._all_names["bulk_out"]) + self.check_allowed_values( + "contrasts", ["scalefactor"], self.scalefactors.get_names(), self._all_names["scalefactors"] + ) + self.check_allowed_values( + "contrasts", ["resolution"], self.resolutions.get_names(), self._all_names["resolutions"] + ) + self.check_allowed_values( + "contrasts", ["domain_ratio"], self.domain_ratios.get_names(), self._all_names["domain_ratios"] + ) self.check_contrast_model_allowed_values( "contrasts", getattr(self, self._contrast_model_field).get_names(), + self._all_names[self._contrast_model_field], self._contrast_model_field, ) - self.check_contrast_model_allowed_values("domain_contrasts", self.layers.get_names(), "layers") + self.check_contrast_model_allowed_values( + "domain_contrasts", self.layers.get_names(), self._all_names["layers"], "layers" + ) return self @model_validator(mode="after") @@ -601,6 +622,12 @@ def check_protected_parameters(self) -> "Project": self._protected_parameters = self.get_all_protected_parameters() return self + @model_validator(mode="after") + def update_names(self) -> "Project": + """Following validation, update the list of all parameter names.""" + self._all_names = self.get_all_names() + return self + def __str__(self): output = "" for key, value in self.__dict__.items(): @@ -625,7 +652,9 @@ def get_all_protected_parameters(self): for class_list in parameter_class_lists } - def check_allowed_values(self, attribute: str, field_list: list[str], allowed_values: list[str]) -> None: + def check_allowed_values( + self, attribute: str, field_list: list[str], allowed_values: list[str], previous_values: list[str] + ) -> None: """Check the values of the given fields in the given model are in the supplied list of allowed values. Parameters @@ -636,6 +665,8 @@ def check_allowed_values(self, attribute: str, field_list: list[str], allowed_va The fields of the attribute to be checked for valid values. allowed_values : list [str] The list of allowed values for the fields given in field_list. + previous_values : list [str] + The list of allowed values for the fields given in field_list after the previous validation. Raises ------ @@ -644,14 +675,22 @@ def check_allowed_values(self, attribute: str, field_list: list[str], allowed_va """ class_list = getattr(self, attribute) - for model in class_list: + for index, model in enumerate(class_list): for field in field_list: value = getattr(model, field, "") if value and value not in allowed_values: - raise ValueError( - f'The value "{value}" in the "{field}" field of "{attribute}" must be defined in ' - f'"{values_defined_in[f"{attribute}.{field}"]}".', - ) + if value in previous_values: + raise ValueError( + f'The value "{value}" used in the "{field}" field of {attribute}[{index}] must be defined ' + f'in "{values_defined_in[f"{attribute}.{field}"]}". Please remove "{value}" from ' + f'"{attribute}[{index}].{field}" before attempting to delete it.', + ) + else: + raise ValueError( + f'The value "{value}" used in the "{field}" field of {attribute}[{index}] must be defined ' + f'in "{values_defined_in[f"{attribute}.{field}"]}". Please add "{value}" to ' + f'"{values_defined_in[f"{attribute}.{field}"]}" before including it in "{attribute}".', + ) def check_allowed_source(self, attribute: str) -> None: """Check that the source of a background or resolution is defined in the relevant field for its type. @@ -674,28 +713,40 @@ def check_allowed_source(self, attribute: str) -> None: """ class_list = getattr(self, attribute) - for model in class_list: + for index, model in enumerate(class_list): if model.type == TypeOptions.Constant: allowed_values = getattr(self, f"{attribute[:-1]}_parameters").get_names() + previous_values = self._all_names[f"{attribute[:-1]}_parameters"] elif model.type == TypeOptions.Data: allowed_values = self.data.get_names() + previous_values = self._all_names["data"] else: allowed_values = self.custom_files.get_names() + previous_values = self._all_names["custom_files"] if (value := model.source) != "" and value not in allowed_values: - raise ValueError( - f'The value "{value}" in the "source" field of "{attribute}" must be defined in ' - f'"{values_defined_in[f"{attribute}.{model.type}.source"]}".', - ) + if value in previous_values: + raise ValueError( + f'The value "{value}" used in the "source" field of {attribute}[{index}] must be defined in ' + f'"{values_defined_in[f"{attribute}.{model.type}.source"]}". Please remove "{value}" from ' + f'"{attribute}[{index}].source" before attempting to delete it.', + ) + else: + raise ValueError( + f'The value "{value}" used in the "source" field of {attribute}[{index}] must be defined in ' + f'"{values_defined_in[f"{attribute}.{model.type}.source"]}". Please add "{value}" to ' + f'"{values_defined_in[f"{attribute}.{model.type}.source"]}" before including it in ' + f'"{attribute}".', + ) def check_contrast_model_allowed_values( self, contrast_attribute: str, allowed_values: list[str], + previous_values: list[str], allowed_field: str, ) -> None: - """The contents of the "model" field of "contrasts" and "domain_contrasts" must be defined elsewhere in the - project. + """Ensure the contents of the ``model`` for a contrast or domain contrast exist in the required project fields. Parameters ---------- @@ -703,6 +754,8 @@ def check_contrast_model_allowed_values( The specific contrast attribute of Project being validated (either "contrasts" or "domain_contrasts"). allowed_values : list [str] The list of allowed values for the model of the contrast_attribute. + previous_values : list [str] + The list of allowed values for the model of the contrast_attribute after the previous validation. allowed_field : str The name of the field in the project in which the allowed_values are defined. @@ -713,13 +766,22 @@ def check_contrast_model_allowed_values( """ class_list = getattr(self, contrast_attribute) - for contrast in class_list: - model_values = contrast.model - if model_values and not all(value in allowed_values for value in model_values): - raise ValueError( - f'The values: "{", ".join(str(i) for i in model_values)}" in the "model" field of ' - f'"{contrast_attribute}" must be defined in "{allowed_field}".', - ) + for index, contrast in enumerate(class_list): + if (model_values := contrast.model) and (missing_values := list(set(model_values) - set(allowed_values))): + if all(value in previous_values for value in model_values): + raise ValueError( + f"The value{'s' if len(missing_values) > 1 else ''}: " + f'"{", ".join(str(i) for i in missing_values)}" used in the "model" field of ' + f'{contrast_attribute}[{index}] must be defined in "{allowed_field}". Please remove all ' + f'unnecessary values from "model" before attempting to delete them.', + ) + else: + raise ValueError( + f"The value{'s' if len(missing_values) > 1 else ''}: " + f'"{", ".join(str(i) for i in missing_values)}" used in the "model" field of ' + f'{contrast_attribute}[{index}] must be defined in "{allowed_field}". Please add all ' + f'required values to "{allowed_field}" before including them in "{contrast_attribute}".', + ) def get_contrast_model_field(self): """Get the field used to define the contents of the "model" field in contrasts. @@ -835,17 +897,17 @@ def classlist_script(name, classlist): + "\n)" ) - def save(self, path: Union[str, Path], filename: str = "project"): + def save(self, filepath: Union[str, Path] = "./project.json"): """Save a project to a JSON file. Parameters ---------- - path : str or Path - The path in which the project will be written. - filename : str - The name of the generated project file. + filepath : str or Path + The path to where the project file will be written. """ + filepath = Path(filepath).with_suffix(".json") + json_dict = {} for field in self.model_fields: attr = getattr(self, field) @@ -869,7 +931,7 @@ def make_custom_file_dict(item): "name": item.name, "filename": item.filename, "language": item.language, - "path": str(item.path), + "path": try_relative_to(item.path, filepath), } json_dict["custom_files"] = [make_custom_file_dict(file) for file in attr] @@ -879,8 +941,7 @@ def make_custom_file_dict(item): else: json_dict[field] = attr - file = Path(path, f"{filename.removesuffix('.json')}.json") - file.write_text(json.dumps(json_dict)) + filepath.write_text(json.dumps(json_dict)) @classmethod def load(cls, path: Union[str, Path]) -> "Project": @@ -892,20 +953,26 @@ def load(cls, path: Union[str, Path]) -> "Project": The path to the project file. """ - input = Path(path).read_text() - model_dict = json.loads(input) - for i in range(0, len(model_dict["data"])): - if model_dict["data"][i]["name"] == "Simulation": - model_dict["data"][i]["data"] = np.empty([0, 3]) - del model_dict["data"][i]["data_range"] + path = Path(path) + input_data = path.read_text() + model_dict = json.loads(input_data) + for dataset in model_dict["data"]: + if dataset["name"] == "Simulation": + dataset["data"] = np.empty([0, 3]) + del dataset["data_range"] else: - data = model_dict["data"][i]["data"] - model_dict["data"][i]["data"] = np.array(data) + data = dataset["data"] + dataset["data"] = np.array(data) + + # file paths are saved as relative to the project directory + for file in model_dict["custom_files"]: + if not Path(file["path"]).is_absolute(): + file["path"] = Path(path, file["path"]) return cls.model_validate(model_dict) def _classlist_wrapper(self, class_list: ClassList, func: Callable): - """Defines the function used to wrap around ClassList routines to force revalidation. + """Define the function used to wrap around ClassList routines to force revalidation. Parameters ---------- @@ -923,8 +990,11 @@ def _classlist_wrapper(self, class_list: ClassList, func: Callable): @functools.wraps(func) def wrapped_func(*args, **kwargs): - """Run the given function and then revalidate the "Project" model. If any exception is raised, restore - the previous state of the given ClassList and report details of the exception. + """Run the given function and then revalidate the "Project" model. + + If any exception is raised, restore the previous state of the given ClassList + and report details of the exception. + """ previous_state = copy.deepcopy(class_list.data) return_value = None @@ -933,7 +1003,7 @@ def wrapped_func(*args, **kwargs): Project.model_validate(self) except ValidationError as exc: class_list.data = previous_state - custom_error_list = custom_pydantic_validation_error(exc.errors()) + custom_error_list = custom_pydantic_validation_error(exc.errors(include_url=False)) raise ValidationError.from_exception_data(exc.title, custom_error_list, hide_input=True) from None except (TypeError, ValueError): class_list.data = previous_state @@ -943,3 +1013,33 @@ def wrapped_func(*args, **kwargs): return return_value return wrapped_func + + +def try_relative_to(path: Path, relative_to: Path) -> str: + """Attempt to create a relative path and warn the user if it isn't possible. + + Parameters + ---------- + path : Path + The path to try to find a relative path for. + relative_to: Path + The path to which we find a relative path for ``path``. + + Returns + ------- + str + The relative path if successful, else the absolute path. + + """ + path = Path(path) + relative_to = Path(relative_to) + if path.is_relative_to(relative_to): + return str(path.relative_to(relative_to)) + else: + warnings.warn( + "Could not save custom file path as relative to the project directory, " + "which means that it may not work on other devices. If you would like to share your project, " + "make sure your custom files are in a subfolder of the project save location.", + stacklevel=2, + ) + return str(path.resolve()) diff --git a/RATapi/run.py b/RATapi/run.py index d60b76e0..476daa39 100644 --- a/RATapi/run.py +++ b/RATapi/run.py @@ -1,3 +1,5 @@ +"""The function used to run a RAT algorithm for a given project and controls.""" + import time from tqdm.auto import tqdm @@ -9,13 +11,13 @@ class ProgressBar: - """Creates a progress bar that gets updates from the progress event during a - calculation + """Create a progress bar that gets updates from the progress event during a calculation. Parameters ---------- - display : bool, default: True - Indicates if displaying is allowed + display : bool, default True + Indicates if displaying is allowed + """ def __init__(self, display=True): @@ -35,12 +37,13 @@ def __enter__(self): return self def updateProgress(self, event): - """Callback for the progress event. + """Update the progress bar with progress event data. Parameters ---------- event: ProgressEventData The progress event data. + """ if self.pbar is None: self.pbar = tqdm(**self.tqdm_kwargs) @@ -58,12 +61,13 @@ def __exit__(self, _exc_type, _exc_val, _traceback): class TextOutput: - """Pipes the message event to stdout + """Context manager to pipe message events to stdout. Parameters ---------- display : bool, default: True Indicates if displaying is allowed + """ def __init__(self, display=True): @@ -76,12 +80,13 @@ def __enter__(self): return self def printMessage(self, msg): - """Callback for the message event. + """Print an event message. Parameters ---------- msg: str The event message. + """ print(msg, end="") @@ -104,7 +109,7 @@ def run(project, controls): horizontal_line = "\u2500" * 107 + "\n" display_on = controls.display != Display.Off - problem_definition, limits, priors, cpp_controls = make_input(project, controls) + problem_definition, cpp_controls = make_input(project, controls) if display_on: print("Starting RAT " + horizontal_line) @@ -113,9 +118,7 @@ def run(project, controls): with ProgressBar(display=display_on), TextOutput(display=display_on): problem_definition, output_results, bayes_results = RATapi.rat_core.RATMain( problem_definition, - limits, cpp_controls, - priors, ) end = time.time() diff --git a/RATapi/utils/__init__.py b/RATapi/utils/__init__.py index e69de29b..f229eb5c 100644 --- a/RATapi/utils/__init__.py +++ b/RATapi/utils/__init__.py @@ -0,0 +1 @@ +"""Additional utilities for RATapi.""" diff --git a/RATapi/utils/convert.py b/RATapi/utils/convert.py index e831e905..7c94ab50 100644 --- a/RATapi/utils/convert.py +++ b/RATapi/utils/convert.py @@ -15,7 +15,7 @@ from RATapi.utils.enums import Geometries, Languages, LayerModels -def r1_to_project_class(filename: Union[str, PathLike]) -> Project: +def r1_to_project(filename: Union[str, PathLike]) -> Project: """Read a RasCAL1 project struct as a Python `Project`. Parameters @@ -46,7 +46,8 @@ def r1_to_project_class(filename: Union[str, PathLike]) -> Project: def zip_if_several(*params) -> Union[tuple, list[tuple]]: """Zips parameters if necessary, but can handle single-item parameters. - Examples: + Examples + -------- zip_if_several([1, 2], [3, 4]) = [(1, 3), (2, 4)] zip_if_several(1, 2, 3) = [(1, 2, 3)] @@ -79,6 +80,7 @@ def read_param(names, constrs, values, fits): ------- ClassList A list of all relevant parameters. + """ def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: float) -> tuple[float, float]: @@ -316,7 +318,7 @@ def fix_invalid_constraints(name: str, constrs: tuple[float, float], value: floa return project -def project_class_to_r1( +def project_to_r1( project: Project, filename: Union[str, PathLike] = "RAT_project", return_struct: bool = False ) -> Union[dict, None]: """Convert a RAT Project to a RasCAL1 project struct. @@ -334,6 +336,7 @@ def project_class_to_r1( ------- dict or None If `return_struct` is True, return the r1 struct. Else, return nothing. + """ def convert_parameters( @@ -345,7 +348,7 @@ def convert_parameters( ---------- params: ClassList A list of parameter type from the Project. - names, constrs, values, fits : str + name, constr, value, fit : str The keys for names, constraints, values and whether to fit for a type of parameter. number : str, optional, default "" @@ -355,6 +358,7 @@ def convert_parameters( ------- dict A dict of the relevant struct fields. + """ output = { name: [p.name for p in params], @@ -545,10 +549,10 @@ def convert_parameters( # scipy.io.savemat doesn't do cells properly: # https://github.com/scipy/scipy/issues/3756 # rather than fiddling we just use matlab - eng = wrappers.start_matlab().result() - if eng is None: + loader = wrappers.MatlabWrapper.loader + if loader is None: raise ImportError("matlabengine is not installed.") + eng = loader.result() eng.workspace["problem"] = r1 eng.save(str(filename), "problem", nargout=0) - eng.exit() return None diff --git a/RATapi/utils/custom_errors.py b/RATapi/utils/custom_errors.py index 1778841a..425cf9ef 100644 --- a/RATapi/utils/custom_errors.py +++ b/RATapi/utils/custom_errors.py @@ -9,11 +9,11 @@ def custom_pydantic_validation_error( error_list: list[pydantic_core.ErrorDetails], custom_error_msgs: Optional[dict[str, str]] = None, ) -> list[pydantic_core.ErrorDetails]: - """Run through the list of errors generated from a pydantic ValidationError, substituting the standard error for a - PydanticCustomError for a given set of error types. + """Give Pydantic errors a better custom message with extraneous information removed. - For errors that do not have a custom error message defined, we redefine them using a PydanticCustomError to remove - the url from the error message. + We substitute the standard error for a PydanticCustomError for a given set of error types. + For errors that do not have a custom error message defined, + we redefine them using a PydanticCustomError to remove the url from the error message. Parameters ---------- diff --git a/RATapi/utils/enums.py b/RATapi/utils/enums.py index dd8e8660..550b583f 100644 --- a/RATapi/utils/enums.py +++ b/RATapi/utils/enums.py @@ -1,3 +1,5 @@ +"""The Enum values used in the parameters of various RATapi classes and functions.""" + from typing import Union try: @@ -7,6 +9,8 @@ class RATEnum(StrEnum): + """A subclass of StrEnum with some adjustments for variable spellings.""" + @classmethod def _missing_(cls, value: str): value = value.lower() @@ -22,25 +26,39 @@ def _missing_(cls, value: str): # Controls class Procedures(RATEnum): - """Defines the available options for procedures""" + """The available options for procedures.""" Calculate = "calculate" + """Run an Abelès reflectivity calculation and calculate chi-squared to the data.""" + Simplex = "simplex" + """Run a Nelder-Mead simplex optimisation over the fit parameters.""" + DE = "de" + """Run a Differential Evolution optimisation over the fit parameters.""" + NS = "ns" + """Run Bayesian Nested Sampling over the fit parameters.""" + DREAM = "dream" + """Run the Bayesian DREAM algorithm over the fit parameters.""" class Parallel(RATEnum): - """Defines the available options for parallelization""" + """The available options for parallelisation.""" Single = "single" + """Do not parallelise.""" + Points = "points" + """Split all contrasts into groups of points, and assign a process to each group.""" + Contrasts = "contrasts" + """Assign one process to each contrast.""" class Display(RATEnum): - """Defines the available options for display""" + """The available options for terminal output.""" Off = "off" Iter = "iter" @@ -49,14 +67,29 @@ class Display(RATEnum): class Strategies(RATEnum): - """Defines the available options for differential evolution strategies""" + """The available strategies for generating base vectors in differential evolution.""" Random = "random" + """The base vector is random.""" + LocalToBest = "local to best" + """The base vector is a combination of one randomly-selected local solution + and the best solution of the previous iteration.""" + BestWithJitter = "best jitter" + """The base vector is the best solution of the previous iteration, with a small random perturbation applied.""" + RandomWithPerVectorDither = "vector dither" + """The base vector is random, with a random scaling factor applied to each mutant. + This scaling factor is different for each mutant.""" + RandomWithPerGenerationDither = "generation dither" + """The base vector is random, with a random scaling factor applied to each mutant. + This scaling factor is the same for every mutant, and randomised every generation.""" + RandomEitherOrAlgorithm = "either or" + """The base vector is randomly chosen from either a pure random mutation, + or a pure recombination of parent parameter values.""" @classmethod def _missing_(cls, value: Union[int, str]): @@ -68,60 +101,107 @@ def _missing_(cls, value: Union[int, str]): return super()._missing_(value) def __int__(self): - # as RAT core expects strategy as an integer + """Convert the DE strategy to its hardcoded index in the internal code. + + RAT core expects strategy to be an integer, as this is how it is given to + the internal DE algorithm. + """ return list(Strategies).index(self) + 1 class BoundHandling(RATEnum): - """Defines the available options for bound handling""" + """The available options for bound handling in DREAM.""" Off = "off" + """Allow points to be sampled out of the parameter bounds.""" + Reflect = "reflect" + """If a step passes a boundary, reflect it back across the boundary.""" + Bound = "bound" + """If a step passes a boundary, set it equal to the nearest point on the boundary.""" + Fold = "fold" + """Treat the boundary as periodic and ‘wrap the step around’ to the other side of the space.""" # Models class TypeOptions(RATEnum): + """The types of signal (``Background`` and ``Resolution``).""" + Constant = "constant" + """A uniform constant signal given by a parameter.""" + Data = "data" + """A signal for each q-value given by a dataset.""" + Function = "function" + """A signal defined by a custom function.""" class BackgroundActions(RATEnum): + """Ways that the background can be applied to the model.""" + Add = "add" Subtract = "subtract" class Languages(RATEnum): + """Language options for custom files.""" + Cpp = "cpp" Python = "python" Matlab = "matlab" class Hydration(RATEnum): + """Options for the 'hydrate with' parameter of a Layer.""" + None_ = "none" BulkIn = "bulk in" BulkOut = "bulk out" class Priors(RATEnum): + """Prior distributions for parameters.""" + Uniform = "uniform" + """A uniform distribution over the parameter range.""" + Gaussian = "gaussian" + """A Gaussian distribution centred on the parameter value, + with shape defined by ``mu`` and ``sigma`` for the parameter.""" + + Jeffreys = "jeffreys" + """A Jeffreys' prior distribution over the parameter range.""" # Project class Calculations(RATEnum): + """Types of calculations that can be performed by RAT.""" + Normal = "normal" Domains = "domains" class LayerModels(RATEnum): + """Types of layer model supported by RAT.""" + CustomLayers = "custom layers" + """The layer model is given by a custom function.""" + CustomXY = "custom xy" + """The continuous SLD of the layer model is given by a custom function.""" + StandardLayers = "standard layers" + """The layer model is given by a list of ``Layer``s or ``DomainContrast``s.""" class Geometries(RATEnum): + """Where the substrate roughness is placed.""" + AirSubstrate = "air/substrate" + """The substrate roughness is placed at the end of the stack.""" + SubstrateLiquid = "substrate/liquid" + """The substrate roughness is placed at the beginning of the stack.""" diff --git a/RATapi/utils/orso.py b/RATapi/utils/orso.py new file mode 100644 index 00000000..50550619 --- /dev/null +++ b/RATapi/utils/orso.py @@ -0,0 +1,248 @@ +"""Readers from file formats.""" + +from dataclasses import dataclass +from itertools import count +from pathlib import Path +from textwrap import shorten +from typing import Union + +import orsopy +import prettytable +from orsopy.fileio import load_orso + +from RATapi import ClassList +from RATapi.models import AbsorptionLayer, Data, Layer, Parameter + + +class ORSOProject: + """A class to encapsulate model information and data from an .ort file. + + Parameters + ---------- + filepath : str or Path + The path to the .ort file. + absorption : bool, default None + Whether to account for absorption in the model data. + + """ + + def __init__(self, filepath: Union[str, Path], absorption: bool = False): + ort_data = load_orso(filepath) + datasets = [Data(name=dataset.info.data_source.sample.name, data=dataset.data) for dataset in ort_data] + # orso datasets in the same file can have repeated names! + # but classlists do not allow this + # use this dict to keep track of counts for repeated names + name_counts = {d.name: count(1) for d in datasets} + names = [d.name for d in datasets] + if len(names) > len(list(set(names))): + for i, data in enumerate(datasets): + if data.name in names[:i]: + data.name += f"-{next(name_counts[data.name])}" + self.data = ClassList(datasets) + self.samples = [ + orso_model_to_rat(dataset.info.data_source.sample.model, absorption=absorption) for dataset in ort_data + ] + + def __str__(self): + data_str = f"Data:\n{str(self.data)}\n\n" + if len(self.samples) == 1: + samples_str = f"Sample:\n{str(self.samples[0])}" + else: + table = prettytable.PrettyTable() + table.field_names = ["index", "bulk in", "bulk out", "parameters", "layers", "model"] + for index, sample in enumerate(self.samples): + if sample is None: + row = [index, "", "", "", "", ""] + else: + row = [ + index, + sample.bulk_in.name, + sample.bulk_out.name, + shorten(", ".join([p.name for p in sample.parameters]), width=20, placeholder="..."), + shorten(", ".join([layer.name for layer in sample.layers]), width=20, placeholder="..."), + shorten(str(sample.model), width=20, placeholder="..."), + ] + table.add_row(row) + samples_str = table.get_string() + + return data_str + samples_str + + +@dataclass +class ORSOSample: + """The stack data from an ORSO SampleModel, in RAT models.""" + + bulk_in: Parameter + bulk_out: Parameter + parameters: ClassList[Parameter] + layers: Union[ClassList[Layer], ClassList[AbsorptionLayer]] + model: list[str] + + def __str__(self): + return ( + "Bulk in:\n" + f"{str(self.bulk_in)}\n\n" + "Bulk out:\n" + f"{str(self.bulk_out)}\n\n" + "Parameters:\n" + f"{str(self.parameters)}\n\n" + "Layers:\n" + f"{str(self.layers)}\n\n" + "Model:\n" + f"{self.model}" + ) + + +def orso_model_to_rat( + model: Union[orsopy.fileio.model_language.SampleModel, str], absorption: bool = False +) -> Union[ORSOSample, None]: + """Get information from an ORSO SampleModel object. + + Parameters + ---------- + model : orsopy.fileio.model_language.SampleModel or str + The sample model to turn into a RAT data. If given as a string, + the string is interpreted as a layer stack in ORSO model language. + absorption : bool, default False + Whether to account for absorption in the model. + + Returns + ------- + ORSOSample + A dataclass containing the sample data, or None if the model is None. + + """ + if model is None: + return None + + if isinstance(model, str): + model = orsopy.fileio.model_language.SampleModel(stack=model) + + stack = model.resolve_to_layers() + # if bulk in or out is air, it has SLD predefined + # else we need to grab it from SLDDB + if bulk_in_sld := stack[0].material.sld is None: + bulk_in_sld = stack[0].material.get_sld() + + # resolve_to_layers loses the name of bulk in and out + bulk_in_name = model.stack.split("|")[0].strip() + bulk_in = Parameter( + name=f"{bulk_in_name} SLD", + min=bulk_in_sld.real, + value=bulk_in_sld.real, + max=bulk_in_sld.real, + fit=False, + ) + + if bulk_out_sld := stack[-1].material.sld is None: + bulk_out_sld = stack[-1].material.get_sld() + + bulk_out_name = model.stack.split("|")[-1].strip() + bulk_out = Parameter( + name=f"{bulk_out_name} SLD", + min=bulk_out_sld.real, + value=bulk_out_sld.real, + max=bulk_out_sld.real, + fit=False, + ) + + parameters = ClassList() + layers = ClassList() + contrast_model = [] + + for orso_layer in stack[1:-1]: + name = get_material_name(orso_layer.material, model) + contrast_model.append(name) + layer_params, layer = orso_layer_to_rat_layer(orso_layer, name, absorption) + parameters.union(layer_params) + layers.union(layer) + + return ORSOSample( + bulk_in=bulk_in, + bulk_out=bulk_out, + parameters=parameters, + layers=layers, + model=contrast_model, + ) + + +def orso_layer_to_rat_layer( + layer: orsopy.fileio.model_language.Layer, name: str, absorption: bool = False +) -> tuple[ClassList[Parameter], Layer]: + """Convert an ``orsopy`` layer to a RAT layer. + + Parameters + ---------- + layer : orsopy.fileio.model_language.Layer + An ``orsopy`` Layer. + name : str + The name of the material in the layer. + absorption : bool, default True + Whether absorption should be accounted for in the layer. + + Returns + ------- + ClassList[Parameter], Layer + The parameters required for the RAT layer and the layer itself. + + """ + thickness = layer.thickness.as_unit("angstrom") + roughness = layer.roughness.as_unit("angstrom") + sld = layer.material.get_sld() + + params = ClassList( + [ + Parameter(name=f"{name} Thickness", min=thickness, value=thickness, max=thickness, fit=False), + Parameter(name=f"{name} Roughness", min=roughness, value=roughness, max=roughness, fit=False), + Parameter(name=f"{name} SLD", min=sld.real, value=sld.real, max=sld.real, fit=False), + ] + ) + if absorption: + params.append(Parameter(name=f"{name} SLD imaginary", min=sld.imag, value=sld.imag, max=sld.imag, fit=False)) + layer = AbsorptionLayer( + name=name, + thickness=f"{name} Thickness", + roughness=f"{name} Roughness", + SLD_real=f"{name} SLD", + SLD_imaginary=f"{name} SLD imaginary", + ) + else: + layer = Layer( + name=name, + thickness=f"{name} Thickness", + roughness=f"{name} Roughness", + SLD=f"{name} SLD", + ) + + return params, layer + + +def get_material_name( + material: orsopy.fileio.model_language.Material, model: orsopy.fileio.model_language.SampleModel +) -> str: + """Get the name of a material in the model. + + Layers with custom property definitions may not have a formula, so this adjusts the name for that. + + Parameters + ---------- + material : Material + The material to get the name of. + model : SampleModel + The sample model from which the material came. + + Returns + ------- + str + The name of the material. + + """ + if material.formula is not None: + return material.formula + else: + matching_materials = [k for k, v in model.materials.items() if v == material] + if matching_materials: + return matching_materials[0] + else: + # orsopy should catch that this is the case before we get here, but just in case... + raise ValueError("ORSO model contains layers with undefined materials!") diff --git a/RATapi/utils/plotting.py b/RATapi/utils/plotting.py index 97d5fa47..12ab5068 100644 --- a/RATapi/utils/plotting.py +++ b/RATapi/utils/plotting.py @@ -1,4 +1,4 @@ -"""Plots using the matplotlib library""" +"""Plot results using the matplotlib library.""" import copy from functools import partial, wraps @@ -17,11 +17,11 @@ import RATapi import RATapi.inputs import RATapi.outputs -from RATapi.rat_core import PlotEventData, makeSLDProfileXY +from RATapi.rat_core import PlotEventData, makeSLDProfile def plot_errorbars(ax: Axes, x: np.ndarray, y: np.ndarray, err: np.ndarray, one_sided: bool, color: str): - """Plots the error bars. + """Plot the error bars. Parameters ---------- @@ -55,7 +55,7 @@ def plot_ref_sld_helper( show_grid: bool = False, show_legend: bool = True, ): - """Clears the previous plots and updates the ref and SLD plots. + """Clear the previous plots and updates the ref and SLD plots. Parameters ---------- @@ -79,6 +79,7 @@ def plot_ref_sld_helper( Controls whether the grid is shown show_legend : bool, default: True Controls whether the lengend is shown + Returns ------- fig : matplotlib.pyplot.figure @@ -153,13 +154,12 @@ def plot_ref_sld_helper( layer = data.resampledLayers[i][j] if layers.shape[1] == 4: layer = np.delete(layer, 2, 1) - new_profile = makeSLDProfileXY( + new_profile = makeSLDProfile( layers[0, 1], # Bulk In layers[-1, 1], # Bulk Out - data.subRoughs[i], # roughness layer, - len(layer), - 1.0, + data.subRoughs[i], # roughness + 1, ) sld_plot.plot( @@ -207,7 +207,7 @@ def plot_ref_sld( show_grid: bool = False, show_legend: bool = True, ) -> Union[plt.Figure, None]: - """Plots the reflectivity and SLD profiles. + """Plot the reflectivity and SLD profiles. Parameters ---------- @@ -312,8 +312,7 @@ def plot_ref_sld( class LivePlot: - """Creates a plot that gets updates from the plot event during a - calculation + """Create a plot that gets updates from the plot event during a calculation. Parameters ---------- @@ -335,16 +334,19 @@ def __enter__(self): return self.figure def _setCloseState(self, _): - """Close event handler""" + """Close event handler.""" self.closed = True def plotEvent(self, event): - """Callback for the plot event. + """Plot the figure from plot event data. + + This is a callback for the plot event. Parameters ---------- event: PlotEventData The plot event data. + """ if not self.closed and self.figure.number in plt.get_fignums(): plot_ref_sld_helper(event, self.figure) @@ -356,7 +358,9 @@ def __exit__(self, _exc_type, _exc_val, _traceback): def assert_bayesian(name: str): - """Decorator to ensure the results passed to a function are Bayesian. + """Ensure the results passed to a function are Bayesian. + + This is a decorator. Parameters ---------- @@ -469,9 +473,7 @@ def plot_corner( fig.tight_layout() if return_fig: return fig - fig.show() - if block: - fig.wait_for_close() + plt.show(block=block) @assert_bayesian("Histogram") @@ -581,9 +583,7 @@ def plot_one_hist( if fig is not None: if return_fig: return fig - fig.show() - if block: - fig.wait_for_close() + plt.show(block=block) @assert_bayesian("Contour") @@ -658,23 +658,24 @@ def plot_contour( if fig is not None: if return_fig: return fig - fig.show() - if block: - fig.wait_for_close() + plt.show(block=block) def panel_plot_helper(plot_func: Callable, indices: list[int]) -> matplotlib.figure.Figure: - """Helper function for panel-based plots. + """Generate a panel-based plot from a single plot function. Parameters ---------- plot_func : Callable A function which plots one parameter on an Axes object, given its index. + indices : list[int] + The list of indices to pass into ``plot_func``. Returns ------- matplotlib.figure.Figure A figure containing a grid of plots over the indices in `indices`. + """ nplots = len(indices) nrows, ncols = ceil(sqrt(nplots)), round(sqrt(nplots)) @@ -747,7 +748,6 @@ def plot_hists( If `return_fig` is True, return the figure - otherwise, return nothing. """ - # first convert names to indices if given fitname_to_index = partial(name_to_index, names=results.fitNames) @@ -798,9 +798,7 @@ def validate_dens_type(dens_type: Union[str, None], param: str): ) if return_fig: return fig - fig.show() - if block: - fig.wait_for_close() + plt.show(block=block) @assert_bayesian("Chain") @@ -852,9 +850,7 @@ def plot_one_chain(axes: Axes, i: int): fig = panel_plot_helper(plot_one_chain, params) if return_fig: return fig - fig.show() - if block: - fig.wait_for_close() + plt.show(block=block) def plot_bayes(project: RATapi.Project, results: RATapi.outputs.BayesResults): @@ -865,6 +861,7 @@ def plot_bayes(project: RATapi.Project, results: RATapi.outputs.BayesResults): all parameters. Parameters + ---------- project : Project An instance of the Project class results : Union[Results, BayesResults] diff --git a/RATapi/wrappers.py b/RATapi/wrappers.py index c428c254..ab5f5fa6 100644 --- a/RATapi/wrappers.py +++ b/RATapi/wrappers.py @@ -1,3 +1,5 @@ +"""Wrappers for the interface between RATapi and MATLAB custom files.""" + import pathlib from contextlib import suppress from typing import Callable @@ -9,12 +11,12 @@ def start_matlab(): - """Starts MATLAB asynchronously and returns a future to retrieve the engine later + """Start MATLAB asynchronously and returns a future to retrieve the engine later. Returns ------- future : matlab.engine.futureresult.FutureResult - A future used to get the actual matlab engine + A future used to get the actual matlab engine. """ future = None @@ -48,7 +50,7 @@ def __init__(self, filename: str) -> None: self.function_name = path.stem def getHandle(self) -> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], tuple[ArrayLike, float]]: - """Returns a wrapper for the custom MATLAB function + """Return a wrapper for the custom MATLAB function. Returns ------- @@ -66,12 +68,17 @@ def handle(*args): ) return np.array(output, "float").tolist() else: - output, sub_rough = getattr(self.engine, self.function_name)( + matlab_args = [ np.array(args[0], "float"), # params np.array(args[1], "float"), # bulk in np.array(args[2], "float"), # bulk out float(args[3] + 1), # contrast - float(-1 if len(args) < 5 else args[4] + 1), # domain + ] + if len(args) > 4: + matlab_args.append(float(args[4] + 1)) # domain number + + output, sub_rough = getattr(self.engine, self.function_name)( + *matlab_args, nargout=2, ) return np.array(output, "float").tolist(), float(sub_rough) @@ -95,7 +102,7 @@ def __init__(self, filename, function_name) -> None: self.engine = RATapi.rat_core.DylibEngine(filename, function_name) def getHandle(self) -> Callable[[ArrayLike, ArrayLike, ArrayLike, int, int], tuple[ArrayLike, float]]: - """Returns a wrapper for the custom dynamic library function + """Return a wrapper for the custom dynamic library function. Returns ------- diff --git a/cpp/RAT b/cpp/RAT index e14ea44c..589c9871 160000 --- a/cpp/RAT +++ b/cpp/RAT @@ -1 +1 @@ -Subproject commit e14ea44c57b376e9f73936320b0d460e8bec3779 +Subproject commit 589c987180f33f1e265537044a6497ef51f4ef09 diff --git a/cpp/includes/defines.h b/cpp/includes/defines.h new file mode 100644 index 00000000..02b5fa97 --- /dev/null +++ b/cpp/includes/defines.h @@ -0,0 +1,679 @@ +#ifndef RAT_DEFINES_H +#define RAT_DEFINES_H + +#include +#include +#include + +namespace py = pybind11; + +const std::string docsProgressEventData = R"(The Python binding for the C++ progressEventData struct. +The progress event shows the percentage completion for the calculation. This can be emitted by +the DREAM algorithm only. + +Parameters +---------- +message : str + The title text for the event. +percent : float + The percentage of the calculation completed (as a number between 0 and 1) +)"; + +struct ProgressEventData +{ + std::string message; + double percent; +}; + +const std::string docsPlotEventData = R"(The Python binding for the C++ plotEventData struct. +The plot event data contains intermediate results from the calculation. This can be emitted +by the Simplex and DE algorithms only. + +Parameters +---------- +reflectivity : list + The reflectivity curves for each contrast, with the same range as the data + (``data_range`` in the contrast's ``Data`` object) +shiftedData : list + The data with scalefactors and background corrections applied. +sldProfiles : list + The SLD profiles for each contrast. +resampledLayers : list + If resampling is used, the SLD for each contrast after resampling has been performed. +subRoughs : np.ndarray[np.float] + The substrate roughness values for each contrast. +resample : np.ndarray[np.float] + An array containing whether each contrast was resampled. +dataPresent : np.ndarray[np.float] + Non-zero values indicates if data is present for the contrast. +modelType : str + The model type for the project. +contrastNames : list + The names of all contrasts in the project. +)"; + +struct PlotEventData +{ + py::list reflectivity; + py::list shiftedData; + py::list sldProfiles; + py::list resampledLayers; + py::array_t subRoughs; + py::array_t resample; + py::array_t dataPresent; + std::string modelType; + py::list contrastNames; +}; + + +const std::string docsPredictionIntervals = R"(The Python binding for the C++ predictionIntervals struct. +The Bayesian prediction intervals for 95% and 65% confidence. + +For ``reflectivity`` and ``sld``, each list item is an array +with five rows. The rows represent: + +- 0: the 5th percentile; +- 1: the 35th percentile; +- 2: the mean value of the interval; +- 3: the 65th percentile; +- 4: the 95th percentile. + +Parameters +---------- +reflectivity : list + The prediction interval data for reflectivity of each contrast. +sld : list + The prediction interval data for SLD of each contrast. +sampleChi : np.ndarray[np.float] + The value of sumChi at each point of the Markov chain. +)"; + +struct PredictionIntervals +{ + py::list reflectivity; + py::list sld; + py::array_t sampleChi; +}; + +const std::string docsConfidenceIntervals = R"(The Python binding for the C++ confidenceIntervals struct. +The 65% and 95% confidence intervals for the best fit results. + +Parameters +---------- +percentile95 : np.ndarray[np.float] + The 95% confidence intervals for each fit parameter. +percentile65 : np.ndarray[np.float] + The 65% confidence intervals for each fit parameter. +mean : np.ndarray[np.float] + The mean values for each fit parameter. +)"; + +struct ConfidenceIntervals +{ + py::array_t percentile95; + py::array_t percentile65; + py::array_t mean; +}; + +const std::string docsNestedSamplerOutput = R"(The Python binding for the C++ nestedSamplerOutput struct. +The output information from the Nested Sampler (ns). + +Parameters +---------- +logZ : float + The natural logarithm of the evidence Z for the parameter values. +logZErr : float + The estimated uncertainty in the final value of logZ. +nestSamples : np.ndarray[np.float] + ``NestedSamplerOutput.nestSamples[i, j]`` represents the values + sampled at iteration ``i``, where this value is: + + - the value sampled for parameter ``j``, for ``j`` in ``0:nParams``, + - the minimum log-likelihood for ``j = nParams + 1``. + +postSamples : np.ndarray[np.float] + The posterior values at the points sampled in ``NestedSamplerOutput.nestSamples``. +)"; + +struct NestedSamplerOutput +{ + real_T logZ; + real_T logZErr; + py::array_t nestSamples; + py::array_t postSamples; +}; + +const std::string docsDreamParams = R"(The Python binding for the C++ dreamParams struct. +The parameters used by the inner DREAM algorithm. + +Parameters +---------- +nParams : float + The number of parameters used by the algorithm. +nChains : float + The number of MCMC chains used by the algorithm. +nGenerations : float + The number of DE generations calculated per iteration. +parallel : bool + Whether the algorithm should run chains in parallel. +CPU : float + The number of processor cores used for parallel chains. +jumpProbability : float + A probability range for the size of jumps when performing subspace sampling. +pUnitGamma : float + The probability that the scaling-down factor of jumps will be ignored + and a larger jump will be taken for one iteration. +nCR : float + The number of crossovers performed each iteration. +delta : float + The number of chain mutation pairs proposed each iteration. +steps : float + The number of MCMC steps to perform between conversion checks. +zeta : float + The ergodicity of the algorithm. +outlier : str + What test should be used to detect outliers. +adaptPCR : bool + Whether the crossover probability for differential evolution should be + adapted by the algorithm as it runs. +thinning : float + The thinning rate of each Markov chain (to reduce memory intensity) +epsilon : float + The cutoff threshold for Approximate Bayesian Computation (if used) +ABC : bool + Whether Approximate Bayesian Computation is used. +IO : bool + Whether the algorithm should perform IO writes of the model in parallel. +storeOutput : bool + Whether output model simulations are performed. +R : np.np.ndarray[np.float] + An array where row ``i`` is the list of chains + with which chain ``i`` can mutate. +)"; + +struct DreamParams +{ + real_T nParams; + real_T nChains; + real_T nGenerations; + boolean_T parallel; + real_T CPU; + real_T jumpProbability; + real_T pUnitGamma; + real_T nCR; + real_T delta; + real_T steps; + real_T zeta; + std::string outlier; + boolean_T adaptPCR; + real_T thinning; + real_T epsilon; + boolean_T ABC; + boolean_T IO; + boolean_T storeOutput; + py::array_t R; +}; + +const std::string docsDreamOutput = R"(The Python binding for the C++ DreamOutput struct. +The diagnostic output information from DREAM. + +Parameters +---------- +allChains : np.ndarray[np.float] + An ``nGenerations`` x ``nParams + 2`` x ``nChains`` size array, + where ``chain_k = DreamOutput.allChains[:, :, k]`` + is the data of chain ``k`` in the final iteration; + for generation i of the final iteration, ``chain_k[i, j]`` represents: + + - the sampled value of parameter ``j`` for ``j in 0:nParams``; + - the associated log-prior for those sampled values for ``j = nParams + 1``; + - the associated log-likelihood for those sampled values for ``j = nParams + 2``. + +outlierChains : np.ndarray[np.float] + A two-column array where ``DreamOutput.AR[i, 1]`` is the index of a chain + and ``DreamOutput.AR[i, 0]`` is the length of that chain when it was removed + for being an outlier. +runtime : float + The runtime of the DREAM algorithm in seconds. +iteration : float + The number of iterations performed. +AR : np.ndarray[np.float] + A two-column array where ``DreamOutput.AR[i, 0]`` is an iteration number + and ``DreamOutput.AR[i, 1]`` is the average acceptance rate of chain step + proposals for that iteration. +R_stat : np.ndarray[np.float] + An array where ``DreamOutput.R_stat[i, 0]`` is an iteration number and + ``DreamOutput.R_stat[i, j]`` is the convergence statistic for parameter ``j`` + at that iteration (where chains are indexed 1 to ``nParams`` inclusive). +CR : np.ndarray[np.float] + A four-column array where ``DreamOutput.CR[i, 0]`` is an iteration number, + ``and DreamOutput.CR[i, j]`` is the selection probability of the ``j``'th crossover + value for that iteration. +)"; + +struct DreamOutput +{ + py::array_t allChains; + py::array_t outlierChains; + real_T runtime; + real_T iteration; + py::array_t AR; + py::array_t R_stat; + py::array_t CR; +}; + +const std::string docsOutputBayesResult = R"(The Python binding for the C++ bayesResults struct. +The results of a Bayesian RAT calculation. + +Parameters +---------- +predictionIntervals : RATapi.rat_core.PredictionIntervals + The prediction intervals. +confidenceIntervals : RATapi.rat_core.ConfidenceIntervals + The 65% and 95% confidence intervals for the best fit results. +dreamParams : RATapi.rat_core.DreamParams + The parameters used by DREAM, if relevant. +dreamOutput : RATapi.rat_core.DreamOutput + The output from DREAM if DREAM was used. +nestedSamplerOutput : RATapi.rat_core.NestedSamplerOutput + The output from nested sampling if ns was used. +chain : np.ndarray + The MCMC chains for each parameter. + The ``i``'th column of the array contains the chain for parameter ``fitNames[i]``. +)"; + +struct OutputBayesResult +{ + PredictionIntervals predictionIntervals; + ConfidenceIntervals confidenceIntervals; + DreamParams dreamParams; + DreamOutput dreamOutput; + NestedSamplerOutput nestedSamplerOutput; + py::array_t chain; +}; + +const std::string docsCalculation = R"(The Python binding for the C++ calculationResult struct. +The goodness of fit from the Abeles calculation. + +Parameters +---------- +chiValues : np.ndarray[np.float] + The chi-squared value for each contrast. +sumChi : float + The sum of the chiValues array. +)"; + +struct Calculation +{ + py::array_t chiValues; + real_T sumChi; +}; + +const std::string docsContrastParams = R"(The Python binding for the C++ contrastParams struct. +The experimental parameters for each contrast. + +Parameters +---------- +scalefactors : np.ndarray[np.float] + The scalefactor values for each contrast. +bulkIn : np.ndarray[np.float] + The bulk in values for each contrast. +bulkOut : np.ndarray[np.float] + The bulk out values for each contrast. +subRoughs : np.ndarray[np.float] + The substrate roughness values for each contrast. +resample : np.ndarray[np.float] + An array containing whether each contrast was resampled. +)"; + +struct ContrastParams +{ + py::array_t scalefactors; + py::array_t bulkIn; + py::array_t bulkOut; + py::array_t subRoughs; + py::array_t resample; +}; + +const std::string docsOutputResult = R"(The C++ result struct of a RAT calculation. + +Parameters +---------- +reflectivity : list + The reflectivity curves for each contrast, with the same range as the data + (``data_range`` in the contrast's ``Data`` object) +simulation : list + The reflectivity curves for each contrast, which can be a wider range to allow extrapolation + (``simulation_range`` in the contrast's ``Data`` object). +shiftedData : list + The data with scalefactors and background corrections applied. +backgrounds : list + The background for each contrast defined over the simulation range. +resolutions : list + The resolution for each contrast defined over the simulation range. +sldProfiles : list + The SLD profiles for each contrast. +layers : list + The array of layer parameter values for each contrast. +resampledLayers : list + If resampling is used, the array of layer parameter values for each contrast after resampling has been performed. +calculationResults : RATapi.rat_core.Calculation + The chi-squared fit results from the final calculation and fit. +contrastParams : RATapi.rat_core.ContrastParams + The experimental parameters for the contrasts. +fitParams : np.ndarray[np.float] + The best fit value of the parameter with name ``fitNames[i]``. +fitNames : list[str] + The names of the fit parameters, where ``fitNames[i]`` is the name + of the parameter with value given in ``fitParams[i]``. +)"; + +struct OutputResult { + py::list reflectivity; + py::list simulation; + py::list shiftedData; + py::list backgrounds; + py::list resolutions; + py::list sldProfiles; + py::list layers; + py::list resampledLayers; + Calculation calculationResults {}; + ContrastParams contrastParams {}; + py::array_t fitParams; + py::list fitNames; +}; + +const std::string docsNameStore = R"(The Python binding for the C++ names struct which +contains names of all parameters in the project. + +Parameters +---------- +params : list + Names of params in the problem definition. +backgroundParams : list + Names of backgroundParams in the problem definition. +scalefactors : list + Names of scalefactors in the problem definition. +bulkIns : list + Names of bulkIns in the problem definition. +bulkName: list + Names of bulkOuts in the problem definition. +resolutionParams : list + Names of resolutionParams in the problem definition. +domainRatios : list + Names of domainRatios in the problem definition. +)"; + +struct NameStore { + py::list params; + py::list backgroundParams; + py::list scalefactors; + py::list bulkIns; + py::list bulkOuts; + py::list resolutionParams; + py::list domainRatios; + py::list contrasts; +}; + + +const std::string docsChecks = R"(The Python binding for the C++ checks struct which contains +flags indicating which parameters should be fitted in the project. + +For each attribute, if index ``i`` is non-zero, then parameter ``i`` in that attribute is fitted, e.g. if ``Checks.scalefactors = [0.0, 1.0, 1.0]``, then the second and third scalefactors are fitted and the first is not. + +Parameters +---------- +params : np.ndarray[np.float] + Non-zero values indicates which params is fitted. +backgroundParams : np.ndarray[np.float] + Non-zero values indicates which backgroundParams is fitted. +scalefactors : np.ndarray[np.float] + Non-zero values indicates which scalefactors is fitted. +bulkIns : np.ndarray[np.float] + Non-zero values indicates which bulkIns is fitted. +bulkOuts : np.ndarray[np.float] + Non-zero values indicates which bulkOuts is fitted. +resolutionParams : np.ndarray[np.float] + Non-zero values indicates which resolutionParams is fitted. +domainRatios : np.ndarray[np.float] + Non-zero values indicates which domainRatios is fitted. +)"; + +struct Checks { + py::array_t params; + py::array_t backgroundParams; + py::array_t scalefactors; + py::array_t bulkIns; + py::array_t bulkOuts; + py::array_t resolutionParams; + py::array_t domainRatios; +}; + +const std::string docsProblemDefinition = R"(The Python binding for the C++ problem struct. + +Parameters +---------- +TF : str + The target function for the calculation which can be 'normal' or 'domains'. +resample : np.ndarray[np.float] + If ``resample[i]`` is non-zero, then contrast ``i`` will be resampled. +data : list + Data for each contrast. +dataPresent : np.ndarray[np.float] + If ``dataPresent[i]`` is non-zero, then contrast ``i`` has experimental data. +dataLimits : list + Data limits for each contrast. +simulationLimits : list; + Simulation for each contrast. +numberOfContrasts : int + Number of contrasts. +geometry : str + The geometry to use which can be 'air/substrate' or 'substrate/liquid' +useImaginary : bool + Indicates whether imaginary component is used for the SLD value in layers, i.e. + absorption is set to True for the project. +repeatLayers : list + Information about repeating layers for each contrast. This is currently not being used. +contrastBackgroundParams : list + Indices of backgroundParams used for each contrast +contrastBackgroundTypes : list + Background type for each contrast. +contrastBackgroundActions : list + Background action for each contrast. +contrastScalefactors : np.ndarray[np.float] + Indices of scalefactors used for each contrast. +contrastBulkIns : np.ndarray[np.float] + Indices of BulkIns used for each contrast. +contrastBulkOuts : np.ndarray[np.float] + Indices of BulkIns used for each contrast. +contrastResolutionParams : list + Indices of resolutionParams used for each contrast +contrastResolutionTypes : list + Resolution type for each contrast. +backgroundParams : np.ndarray[np.float] + Background parameter values. +scalefactors : np.ndarray[np.float] + Scalefactors values. +bulkIns : np.ndarray[np.float] + BulkIn values. +bulkOuts : np.ndarray[np.float] + BulkOut values. +resolutionParams : np.ndarray[np.float] + Resolution parameter values. +params : np.ndarray[np.float] + Parameter values. +numberOfLayers : int + Number of layers. +contrastLayers : list + Indices of layers added to the model of each contrast. +layersDetails : list + Indices of parameters in each layer. +customFiles : object + Iterable with custom file functions +modelType : str + The layer model type which can be 'standard layers', 'custom layers', or 'custom xy'. +contrastCustomFiles : np.ndarray[np.float] + Indices of CustomFiles used for each domain contrast +contrastDomainRatios : np.ndarray[np.float] + Indices of DomainRatios used for each domain contrast +domainRatios : np.ndarray[np.float] + Domain ratio values +numberOfDomainContrasts : int + Number of domain contrasts. +domainContrastLayers : list + Indices of layers added to the model of each domain contrast. +fitParams : np.ndarray[np.float] + Values of fitted parameters. +fitLimits : np.ndarray[np.float] + Limits of fitted parameters. +priorNames : list + Parameter names for for all parameters in the problem definition. +priorValues : np.ndarray[np.float] + Prior type, mu, and sigma for all parameters in the problem definition. +names : RATapi.rat_core.NameStore + Names of all parameters. +checks : RATapi.rat_core.Checks + Flags indicating which parameters should be fitted. +)"; + +struct ProblemDefinition { + std::string TF {}; + py::array_t resample; + py::list data; + py::array_t dataPresent; + py::list dataLimits; + py::list simulationLimits; + real_T numberOfContrasts; + std::string geometry {}; + boolean_T useImaginary {}; + py::list repeatLayers; + py::list contrastBackgroundParams; + py::list contrastBackgroundTypes; + py::list contrastBackgroundActions; + py::array_t contrastScalefactors; + py::array_t contrastBulkIns; + py::array_t contrastBulkOuts; + py::list contrastResolutionParams; + py::list contrastResolutionTypes; + py::array_t backgroundParams; + py::array_t scalefactors; + py::array_t bulkIns; + py::array_t bulkOuts; + py::array_t resolutionParams; + py::array_t params; + real_T numberOfLayers {}; + py::list contrastLayers; + py::list layersDetails; + py::object customFiles; + std::string modelType {}; + py::array_t contrastCustomFiles; + py::array_t contrastDomainRatios; + py::array_t domainRatios; + real_T numberOfDomainContrasts {}; + py::list domainContrastLayers; + py::array_t fitParams; + py::array_t fitLimits; + py::list priorNames; + py::array_t priorValues; + NameStore names; + Checks checks {}; +}; + + +const std::string docsControl = R"(The Python binding for the C++ controls struct. + +Parameters +---------- +parallel : str + How the calculation should be parallelised (This uses the Parallel Computing Toolbox). Can be 'single', 'contrasts' or 'points'. +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.) +resampleMinAngle : float + The upper threshold on the angle between three sampled points for resampling, in units of radians over pi. +resampleNPoints : int + The number of initial points to use for resampling. +display : str + How much RAT should print to the terminal. Can be 'off', 'iter', 'notify', or 'final'. +IPCFilePath : str + The path of the inter process communication file. +updateFreq : int + [SIMPLEX, DE] Number of iterations between printing progress updates to the terminal. +updatePlotFreq : int + [SIMPLEX, DE] Number of iterations between updates to live plots. +xTolerance : float + [SIMPLEX] The termination tolerance for step size. +funcTolerance : float + [SIMPLEX] The termination tolerance for change in chi-squared. +maxFuncEvals : int + [SIMPLEX] The maximum number of function evaluations before the algorithm terminates. +maxIterations : int + [SIMPLEX] The maximum number of iterations before the algorithm terminates. +populationSize : int + [DE] The number of candidate solutions that exist at any time. +fWeight : float + [DE] The step size for how different mutations are to their parents. +crossoverProbability : float + [DE] The probability of exchange of parameters between individuals at any iteration. +strategy : int + [DE] The algorithm used to generate new candidates. +targetValue : float + [DE] The value of chi-squared at which the algorithm will terminate. +numGenerations : int + [DE] The maximum number of iterations before the algorithm terminates. +nLive : int + [NS] The number of points to sample. +nMCMC : int + [NS] If non-zero, an MCMC process with ``nMCMC`` chains will be used instead of MultiNest. +propScale : float + [NS] A scaling factor for the ellipsoid generated by MultiNest. +nsTolerance : float + [NS] The tolerance threshold for when the algorithm should terminate. +nSamples : int + [DREAM] The number of samples in the initial population for each chain. +nChains : int + [DREAM] The number of Markov chains to use in the algorithm. +jumpProbability : float + [DREAM] The probability range for the size of jumps in sampling. Larger values mean more variable jumps. +pUnitGamma : float + [DREAM] The probability that the scaling-down factor of jumps will be ignored and a larger jump will be taken. +boundHandling : str + [DREAM] How steps past the space boundaries should be handled. Can be 'off', 'reflect', 'bound', or 'fold'. +adaptPCR : bool + [DREAM] Whether the crossover probability for differential evolution should be adapted during the run. +)"; + +struct Control { + std::string parallel {}; + std::string procedure {}; + std::string display {}; + real_T xTolerance {}; + real_T funcTolerance {}; + real_T maxFuncEvals {}; + real_T maxIterations {}; + real_T populationSize {}; + real_T fWeight {}; + real_T crossoverProbability {}; + real_T targetValue {}; + real_T numGenerations {}; + real_T strategy {}; + real_T nLive {}; + real_T nMCMC {}; + real_T propScale {}; + real_T nsTolerance {}; + boolean_T calcSldDuringFit {}; + real_T resampleMinAngle {}; + real_T resampleNPoints {}; + real_T updateFreq {}; + real_T updatePlotFreq {}; + real_T nSamples {}; + real_T nChains {}; + real_T jumpProbability {}; + real_T pUnitGamma {}; + std::string boundHandling {}; + boolean_T adaptPCR; + std::string IPCFilePath {}; +}; + +#endif // RAT_DEFINES_H \ No newline at end of file diff --git a/cpp/includes/functions.h b/cpp/includes/functions.h new file mode 100644 index 00000000..3ced72fe --- /dev/null +++ b/cpp/includes/functions.h @@ -0,0 +1,500 @@ +#ifndef RAT_FUNCTIONS_H +#define RAT_FUNCTIONS_H + +#include +#include +#include +#include +#include "../RAT/classHandle.hpp" + +namespace py = pybind11; + +template +auto customCaller(std::string identifier, Function f, Args&& ... args) -> decltype((*f)(std::forward(args)...)) +{ + try + { + return (*f)(std::forward(args)...); + } + catch(const std::runtime_error& re) + { + std::string errorMsg; + size_t start_pos = std::string(re.what()).find("$id"); + if(start_pos == std::string::npos) + { + errorMsg = std::string("Error occurred when setting ") + identifier + ". " + re.what(); + } + else + { + errorMsg = re.what(); + errorMsg.replace(start_pos, 3, identifier); + } + + throw std::runtime_error(errorMsg); + } +} + +class Library: public CallbackInterface +{ + public: + + py::function function; + + Library(const py::function function){ + this->function = function; + }; + + + void setOutput(py::tuple& result, std::vector& output, double *outputSize) + { + int nRows = 0, idx = 0; + for (py::handle rowHandle : result[0]) + { + py::list rows = py::cast(rowHandle); + for (py::handle value : rows) + { + output.push_back(py::cast(value)); + idx++; + } + nRows++; + } + + outputSize[0] = nRows; + outputSize[1] = (nRows == 0) ? 0 : idx / nRows; + } + + // Backgrounds + void invoke(std::vector& xdata, std::vector& params, std::vector& output) + { + auto f = py::cast>(this->function); + auto result = f(py::cast(xdata), py::cast(params)); + for (py::handle rowHandle : result) + { + if (py::isinstance(rowHandle)) + output.push_back(py::cast(py::cast(rowHandle)[0])); + else + output.push_back(py::cast(rowHandle)); + + } + }; + + // Domain overload + void invoke(std::vector& params, std::vector& bulkIn, std::vector& bulkOut, + int contrast, int domainNumber, std::vector& output, double *outputSize, double *roughness) + { + auto f = py::cast>(this->function); + auto result = f(py::cast(params), py::cast(bulkIn), py::cast(bulkOut), contrast, domainNumber); + *roughness = py::cast(result[1]); + setOutput(result, output, outputSize); + }; + + // Non-Domain overload + void invoke(std::vector& params, std::vector& bulkIn, std::vector& bulkOut, + int contrast, std::vector& output, double *outputSize, double *roughness) + { + auto f = py::cast>(this->function); + auto result = f(py::cast(params), py::cast(bulkIn), py::cast(bulkOut), contrast); + *roughness = py::cast(result[1]); + setOutput(result, output, outputSize); + }; +}; + + +void stringToRatBoundedArray(std::string value, char_T result_data[], int32_T result_size[2]) +{ + result_size[0] = 1; + result_size[1] = value.length(); + + for (int32_T idx1{0}; idx1 < value.length(); idx1++) { + result_data[idx1] = value[idx1]; + } +} + +void stringToRatCharArray(std::string value, coder::array& result) +{ + result.set_size(1, value.length()); + + for (int32_T idx{0}; idx < value.length(); idx++) { + result[idx] = value[idx]; + } +} + +void stringFromRatBoundedArray(const char_T array_data[], const int32_T array_size[2], std::string& result) +{ + result.resize(array_size[1]); + memcpy(&result[0], array_data, array_size[1]); +} + + +coder::array pyArrayToRatRowArray1d(py::array_t value) +{ + coder::array result; + + py::buffer_info buffer_info = value.request(); + + if (buffer_info.size == 0) + return result; + + if (buffer_info.ndim != 1) + throw std::runtime_error("Expects a 1D numeric array"); + + result.set_size(1, buffer_info.shape[0]); + for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { + result[idx0] = value.at(idx0); + } + + return result; +} + +coder::bounded_array pyArrayToRatBoundedArray(py::array_t value) +{ + coder::bounded_array result {}; + + py::buffer_info buffer_info = value.request(); + + if (buffer_info.size == 0) + return result; + + if (buffer_info.ndim != 1) + throw std::runtime_error("Expects a 1D numeric array"); + + result.size[0] = 1; + result.size[1] = buffer_info.shape[0]; + for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { + result.data[idx0] = value.at(idx0); + } + + return result; +} + +coder::bounded_array pyArrayToRatBoundedArray3(py::array_t value) +{ + coder::bounded_array result {}; + + py::buffer_info buffer_info = value.request(); + + if (buffer_info.size == 0) + return result; + + if (buffer_info.ndim != 1) + throw std::runtime_error("Expects a 1D numeric array"); + + result.size[0] = 1; + result.size[1] = buffer_info.shape[0]; + for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { + result.data[idx0] = value.at(idx0); + } + + return result; +} + +coder::array pyArrayToRatArray2d(py::array_t value) +{ + coder::array result; + + py::buffer_info buffer_info = value.request(); + + if (buffer_info.size == 0) + return result; + + if (buffer_info.ndim != 2) + throw std::runtime_error("Expects a 2D numeric array"); + + result.set_size(buffer_info.shape[0], buffer_info.shape[1]); + + int32_T idx {0}; + for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { + for (int32_T idx1{0}; idx1 < buffer_info.shape[1]; idx1++) { + idx = idx0 + result.size(0) * idx1; + result[idx] = value.at(idx0, idx1); + } + } + + return result; +} + +coder::array pyListToRatCellWrap1(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatArray2d, casted_array); + idx++; + } + + return result; +} + +coder::array pyListToRatCellWrap2(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + if (casted_array.size() != 2) + throw std::runtime_error("Expects a 2D list where each row contains exactly 2 numbers"); + result[idx].f1[0] = casted_array.at(0); + result[idx].f1[1] = casted_array.at(1); + idx++; + } + + return result; +} + + +coder::array pyListToRatCellWrap3(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray3, casted_array); + idx++; + } + + return result; +} + +coder::array pyListToRatCellWrap4(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray3, casted_array); + idx++; + } + + return result; +} + +coder::array pyListToRatCellWrap5(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatRowArray1d, casted_array); + idx++; + } + + return result; +} + +coder::array pyListToRatCellWrap6(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + py::array_t casted_array = py::cast(array); + result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray, casted_array); + idx++; + } + + return result; +} + +coder::array pyListToRatCellWrap01d(py::list values) +{ + coder::array result; + result.set_size(values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + if (py::isinstance(array)) { + std::string name = py::cast(array); + stringToRatBoundedArray(name, result[idx].f1.data, result[idx].f1.size); + idx++; + } + else + throw std::runtime_error("Expects a 1D list of strings"); + } + + return result; +} + +coder::array pyListToRatCellWrap02d(py::list values) +{ + coder::array result; + result.set_size(1, values.size()); + int32_T idx {0}; + for (py::handle array: values) + { + if (py::isinstance(array)) { + std::string name = py::cast(array); + stringToRatBoundedArray(name, result[idx].f1.data, result[idx].f1.size); + idx++; + } + else + throw std::runtime_error("Expects a 1D list of strings"); + } + + return result; +} + +coder::array py_function_array_to_rat_cell_wrap_0(py::object values) +{ + auto handles = py::cast(values); + coder::array result; + result.set_size(1, handles.size()); + int32_T idx {0}; + for (py::handle array: handles) + { + auto func = py::cast(array); + std::string func_ptr = convertPtr2String(new Library(func)); + stringToRatBoundedArray(func_ptr, result[idx].f1.data, result[idx].f1.size); + idx++; + } + + return result; +} +template +py::array_t pyArrayFromRatArray1d(T array, bool isCol=true) +{ + auto size = isCol ? array.size(1) : array.size(0); + auto result_array = py::array_t(size); + std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); + + return result_array; +} + +py::array_t pyArrayFromRatArray2d(coder::array array) +{ + auto result_array = py::array_t({array.size(0), array.size(1)}); + std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); + + return result_array; +} + +py::list pyListFromRatCellWrap01d(coder::array values) +{ + py::list result; + for (int32_T idx0{0}; idx0 < values.size(0); idx0++) { + std::string tmp; + stringFromRatBoundedArray(values[idx0].f1.data, values[idx0].f1.size, tmp); + result.append(tmp); + } + + return result; +} + +py::list pyListFromRatCellWrap02d(coder::array values) +{ + py::list result; + for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { + std::string tmp; + stringFromRatBoundedArray(values[idx0].f1.data, values[idx0].f1.size, tmp); + result.append(tmp); + } + + return result; +} + +py::list pyListFromRatCellWrap2(coder::array values) +{ + py::list result; + + for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { + py::list inner = py::make_tuple(values[idx0].f1[0], values[idx0].f1[1]); + result.append(inner); + } + + return result; +} + +template +py::list pyList1DFromRatCellWrap2D(const T& values) +{ + py::list result; + + for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { + result.append(pyArrayFromRatArray2d(values[idx0].f1)); + } + + return result; +} + +template +py::list pyList1DFromRatCellWrap1D(const T& values) +{ + py::list result; + + for (int32_T idx0{0}; idx0 < values.size(0); idx0++) { + result.append(pyArrayFromRatArray2d(values[idx0].f1)); + } + + return result; +} + +template +py::list pyList2dFromRatCellWrap(const T& values) +{ + py::list result; + int32_T idx {0}; + for (int32_T idx0{0}; idx0 < values.size(0); idx0++) { + py::list inner; + for (int32_T idx1{0}; idx1 < values.size(1); idx1++) { + idx = idx0 + values.size(0) * idx1; + inner.append(pyArrayFromRatArray2d(values[idx].f1)); + } + result.append(inner); + } + + return result; +} + +template +py::list pyListFromBoundedCellWrap(const T& values) +{ + py::list result; + + for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { + auto array = py::array_t({values[idx0].f1.size[0]}); + std::memcpy(array.request().ptr, values[idx0].f1.data, array.nbytes()); + + result.append(array); + } + + return result; +} + +template +py::array_t pyArray1dFromBoundedArray(const T& array) +{ + auto result_array = py::array_t({array.size[0]}); + std::memcpy(result_array.request().ptr, array.data, result_array.nbytes()); + + return result_array; +} + +template +py::array_t pyArray2dFromBoundedArray(const T& array) +{ + auto result_array = py::array_t({array.size[0], array.size[1]}); + std::memcpy(result_array.request().ptr, array.data, result_array.nbytes()); + + return result_array; +} + +py::array_t pyArrayFromRatArray3d(coder::array array) +{ + auto result_array = py::array_t({array.size(0), array.size(1), array.size(2)}); + std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); + + return result_array; +} + +#endif // RAT_FUNCTIONS_H \ No newline at end of file diff --git a/cpp/rat.cpp b/cpp/rat.cpp index 759e7d48..3335e09a 100644 --- a/cpp/rat.cpp +++ b/cpp/rat.cpp @@ -16,104 +16,16 @@ setup_pybind11(cfg) #include "RAT/RATMain_initialize.h" #include "RAT/RATMain_terminate.h" #include "RAT/RATMain_types.h" -#include "RAT/makeSLDProfileXY.h" -#include "RAT/classHandle.hpp" +#include "RAT/makeSLDProfile.h" #include "RAT/dylib.hpp" #include "RAT/events/eventManager.h" +#include "includes/defines.h" +#include "includes/functions.h" namespace py = pybind11; const int DEFAULT_DOMAIN = -1; - -template -auto customCaller(std::string identifier, Function f, Args&& ... args) -> decltype((*f)(std::forward(args)...)) -{ - try - { - return (*f)(std::forward(args)...); - } - catch(const std::runtime_error& re) - { - std::string errorMsg; - size_t start_pos = std::string(re.what()).find("$id"); - if(start_pos == std::string::npos) - { - errorMsg = std::string("Error occurred when setting ") + identifier + ". " + re.what(); - } - else - { - errorMsg = re.what(); - errorMsg.replace(start_pos, 3, identifier); - } - - throw std::runtime_error(errorMsg); - } -} - -class Library: public CallbackInterface -{ - public: - - py::function function; - - Library(const py::function function){ - this->function = function; - }; - - - void setOutput(py::tuple& result, std::vector& output, double *outputSize) - { - int nRows = 0, idx = 0; - for (py::handle rowHandle : result[0]) - { - py::list rows = py::cast(rowHandle); - for (py::handle value : rows) - { - output.push_back(py::cast(value)); - idx++; - } - nRows++; - } - - outputSize[0] = nRows; - outputSize[1] = (nRows == 0) ? 0 : idx / nRows; - } - - // Backgrounds - void invoke(std::vector& xdata, std::vector& params, std::vector& output) - { - auto f = py::cast>(this->function); - auto result = f(py::cast(xdata), py::cast(params)); - for (py::handle rowHandle : result) - { - if (py::isinstance(rowHandle)) - output.push_back(py::cast(py::cast(rowHandle)[0])); - else - output.push_back(py::cast(rowHandle)); - - } - }; - - // Domain overload - void invoke(std::vector& params, std::vector& bulkIn, std::vector& bulkOut, - int contrast, int domainNumber, std::vector& output, double *outputSize, double *roughness) - { - auto f = py::cast>(this->function); - auto result = f(py::cast(params), py::cast(bulkIn), py::cast(bulkOut), contrast, domainNumber); - *roughness = py::cast(result[1]); - setOutput(result, output, outputSize); - }; - - // Non-Domain overload - void invoke(std::vector& params, std::vector& bulkIn, std::vector& bulkOut, - int contrast, std::vector& output, double *outputSize, double *roughness) - { - auto f = py::cast>(this->function); - auto result = f(py::cast(params), py::cast(bulkIn), py::cast(bulkOut), contrast); - *roughness = py::cast(result[1]); - setOutput(result, output, outputSize); - }; -}; +const int DEFAULT_NREPEATS = 1; class DylibEngine { @@ -185,25 +97,6 @@ class DylibEngine }; }; -struct ProgressEventData -{ - std::string message; - double percent; -}; - -struct PlotEventData -{ - py::list reflectivity; - py::list shiftedData; - py::list sldProfiles; - py::list resampledLayers; - py::array_t subRoughs; - py::array_t resample; - py::array_t dataPresent; - std::string modelType; - py::list contrastNames; -}; - class EventBridge { public: @@ -328,537 +221,27 @@ class EventBridge }; }; -struct PredictionIntervals -{ - py::list reflectivity; - py::list sld; - py::array_t sampleChi; -}; - -struct ConfidenceIntervals -{ - py::array_t percentile95; - py::array_t percentile65; - py::array_t mean; -}; - -struct NestedSamplerOutput -{ - real_T logZ; - real_T logZErr; - py::array_t nestSamples; - py::array_t postSamples; -}; - -struct DreamParams -{ - real_T nParams; - real_T nChains; - real_T nGenerations; - boolean_T parallel; - real_T CPU; - real_T jumpProbability; - real_T pUnitGamma; - real_T nCR; - real_T delta; - real_T steps; - real_T zeta; - std::string outlier; - boolean_T adaptPCR; - real_T thinning; - real_T epsilon; - boolean_T ABC; - boolean_T IO; - boolean_T storeOutput; - py::array_t R; -}; - -struct DreamOutput +RAT::b_ParamNames createParamNamesStruct(const NameStore& names) { - py::array_t allChains; - py::array_t outlierChains; - real_T runtime; - real_T iteration; - real_T modelOutput; - py::array_t AR; - py::array_t R_stat; - py::array_t CR; -}; - -struct BayesResults -{ - PredictionIntervals predictionIntervals; - ConfidenceIntervals confidenceIntervals; - DreamParams dreamParams; - DreamOutput dreamOutput; - NestedSamplerOutput nestedSamplerOutput; - py::array_t chain; -}; - -struct Priors -{ - py::list params; - py::list backgroundParams; - py::list scalefactors; - py::list qzshifts; - py::list bulkIns; - py::list bulkOuts; - py::list resolutionParams; - py::list domainRatios; - py::list priorNames; - py::array_t priorValues; -}; - -struct Checks { - py::array_t params; - py::array_t backgroundParams; - py::array_t qzshifts; - py::array_t scalefactors; - py::array_t bulkIns; - py::array_t bulkOuts; - py::array_t resolutionParams; - py::array_t domainRatios; -}; - -struct Calculation -{ - py::array_t chiValues; - real_T sumChi; -}; - -struct ContrastParams -{ - py::array_t scalefactors; - py::array_t bulkIn; - py::array_t bulkOut; - py::array_t subRoughs; - py::array_t resample; -}; - -struct OutputResult { - py::list reflectivity; - py::list simulation; - py::list shiftedData; - py::list backgrounds; - py::list resolutions; - py::list layerSlds; - py::list sldProfiles; - py::list resampledLayers; - Calculation calculationResults {}; - ContrastParams contrastParams {}; - py::array_t fitParams; - py::list fitNames; -}; - -struct Limits { - py::array_t params; - py::array_t backgroundParams; - py::array_t scalefactors; - py::array_t qzshifts; - py::array_t bulkIns; - py::array_t bulkOuts; - py::array_t resolutionParams; - py::array_t domainRatios; -}; - -struct NameStore { - py::list params; - py::list backgroundParams; - py::list scalefactors; - py::list qzshifts; - py::list bulkIns; - py::list bulkOuts; - py::list resolutionParams; - py::list domainRatios; - py::list contrasts; -}; - -struct ProblemDefinition { - std::string TF {}; - py::array_t resample; - py::list data; - py::array_t dataPresent; - py::list dataLimits; - py::list simulationLimits; - py::array_t oilChiDataPresent; - real_T numberOfContrasts; - std::string geometry {}; - boolean_T useImaginary {}; - py::list repeatLayers; - py::list contrastBackgroundParams; - py::list contrastBackgroundTypes; - py::list contrastBackgroundActions; - py::array_t contrastQzshifts; - py::array_t contrastScalefactors; - py::array_t contrastBulkIns; - py::array_t contrastBulkOuts; - py::list contrastResolutionParams; - py::list contrastResolutionTypes; - py::array_t backgroundParams; - py::array_t qzshifts; - py::array_t scalefactors; - py::array_t bulkIns; - py::array_t bulkOuts; - py::array_t resolutionParams; - py::array_t params; - real_T numberOfLayers {}; - py::list contrastLayers; - py::list layersDetails; - py::object customFiles; - std::string modelType {}; - py::array_t contrastCustomFiles; - py::array_t contrastDomainRatios; - py::array_t domainRatios; - real_T numberOfDomainContrasts {}; - py::list domainContrastLayers; - py::array_t fitParams; - py::array_t otherParams; - py::array_t fitLimits; - py::array_t otherLimits; - NameStore names; - Checks checks {}; -}; - -struct Control { - std::string parallel {}; - std::string procedure {}; - std::string display {}; - real_T xTolerance {}; - real_T funcTolerance {}; - real_T maxFuncEvals {}; - real_T maxIterations {}; - real_T populationSize {}; - real_T fWeight {}; - real_T crossoverProbability {}; - real_T targetValue {}; - real_T numGenerations {}; - real_T strategy {}; - real_T nLive {}; - real_T nMCMC {}; - real_T propScale {}; - real_T nsTolerance {}; - boolean_T calcSldDuringFit {}; - real_T resampleMinAngle {}; - real_T resampleNPoints {}; - real_T updateFreq {}; - real_T updatePlotFreq {}; - real_T nSamples {}; - real_T nChains {}; - real_T jumpProbability {}; - real_T pUnitGamma {}; - std::string boundHandling {}; - boolean_T adaptPCR; - std::string IPCFilePath {}; -}; - - -void stringToRatBoundedArray(std::string value, char_T result_data[], int32_T result_size[2]) -{ - result_size[0] = 1; - result_size[1] = value.length(); - - for (int32_T idx1{0}; idx1 < value.length(); idx1++) { - result_data[idx1] = value[idx1]; - } -} - -void stringToRatCharArray(std::string value, coder::array& result) -{ - result.set_size(1, value.length()); - - for (int32_T idx{0}; idx < value.length(); idx++) { - result[idx] = value[idx]; - } -} - -void stringFromRatBoundedArray(const char_T array_data[], const int32_T array_size[2], std::string& result) -{ - result.resize(array_size[1]); - memcpy(&result[0], array_data, array_size[1]); -} - - -coder::array pyArrayToRatRowArray1d(py::array_t value) -{ - coder::array result; - - py::buffer_info buffer_info = value.request(); - - if (buffer_info.size == 0) - return result; - - if (buffer_info.ndim != 1) - throw std::runtime_error("Expects a 1D numeric array"); - - result.set_size(1, buffer_info.shape[0]); - for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { - result[idx0] = value.at(idx0); - } - - return result; -} - -coder::bounded_array pyArrayToRatBoundedArray(py::array_t value) -{ - coder::bounded_array result {}; - - py::buffer_info buffer_info = value.request(); - - if (buffer_info.size == 0) - return result; - - if (buffer_info.ndim != 1) - throw std::runtime_error("Expects a 1D numeric array"); - - result.size[0] = 1; - result.size[1] = buffer_info.shape[0]; - for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { - result.data[idx0] = value.at(idx0); - } - - return result; -} - -coder::bounded_array pyArrayToRatBoundedArray3(py::array_t value) -{ - coder::bounded_array result {}; - - py::buffer_info buffer_info = value.request(); - - if (buffer_info.size == 0) - return result; - - if (buffer_info.ndim != 1) - throw std::runtime_error("Expects a 1D numeric array"); - - result.size[0] = 1; - result.size[1] = buffer_info.shape[0]; - for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { - result.data[idx0] = value.at(idx0); - } - - return result; -} - -coder::array pyArrayToRatArray2d(py::array_t value) -{ - coder::array result; - - py::buffer_info buffer_info = value.request(); - - if (buffer_info.size == 0) - return result; - - if (buffer_info.ndim != 2) - throw std::runtime_error("Expects a 2D numeric array"); - - result.set_size(buffer_info.shape[0], buffer_info.shape[1]); - - int32_T idx {0}; - for (int32_T idx0{0}; idx0 < buffer_info.shape[0]; idx0++) { - for (int32_T idx1{0}; idx1 < buffer_info.shape[1]; idx1++) { - idx = idx0 + result.size(0) * idx1; - result[idx] = value.at(idx0, idx1); - } - } - - return result; -} - -coder::array pyListToUnboundedCell0(py::list values) -{ - coder::array result; - result.set_size(values.size()); - int32_T idx {0}; - for (py::handle list: values) - { - py::list value = py::cast(list); - if (py::len(list) != 4 || !py::isinstance(value[0]) || !py::isinstance(value[1]) || - !py::isinstance(value[2]) || !py::isinstance(value[3])) - throw std::runtime_error("Expects a 2D list where each row must contain 4 elements. " - "Columns 1 and 2 must be strings and Columns 3 and 4 must be numeric arrays"); - - stringToRatBoundedArray(value[0].cast(), result[idx].f1.data, result[idx].f1.size); - stringToRatBoundedArray(value[1].cast(), result[idx].f2.data, result[idx].f2.size); - result[idx].f3 = value[2].cast(); - result[idx].f4 = value[3].cast(); - idx++; - } - - return result; -} - -coder::array pyListToUnboundedCell1(py::list values) -{ - coder::array result; - result.set_size(values.size()); - int32_T idx {0}; - for (py::handle list: values) - { - if (py::isinstance(list)) { - std::string value = py::cast(list); - stringToRatCharArray(value, result[idx].f1); - idx++; - } - else - throw std::runtime_error("Expects a 1D list of strings"); - } - - return result; -} -coder::array pyListToRatCellWrap1(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatArray2d, casted_array); - idx++; - } - - return result; -} - -coder::array pyListToRatCellWrap2(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - if (casted_array.size() != 2) - throw std::runtime_error("Expects a 2D list where each row contains exactly 2 numbers"); - result[idx].f1[0] = casted_array.at(0); - result[idx].f1[1] = casted_array.at(1); - idx++; - } - - return result; -} - - -coder::array pyListToRatCellWrap3(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray3, casted_array); - idx++; - } - - return result; -} - -coder::array pyListToRatCellWrap4(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray3, casted_array); - idx++; - } - - return result; -} - -coder::array pyListToRatCellWrap5(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatRowArray1d, casted_array); - idx++; - } - - return result; -} - -coder::array pyListToRatCellWrap6(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - py::array_t casted_array = py::cast(array); - result[idx].f1 = customCaller("$id[" + std::to_string(idx) +"]", pyArrayToRatBoundedArray, casted_array); - idx++; - } - - return result; -} - -coder::array pyListToRatCellWrap0(py::list values) -{ - coder::array result; - result.set_size(1, values.size()); - int32_T idx {0}; - for (py::handle array: values) - { - if (py::isinstance(array)) { - std::string name = py::cast(array); - stringToRatBoundedArray(name, result[idx].f1.data, result[idx].f1.size); - idx++; - } - else - throw std::runtime_error("Expects a 1D list of strings"); - } - - return result; -} - -coder::array py_function_array_to_rat_cell_wrap_0(py::object values) -{ - auto handles = py::cast(values); - coder::array result; - result.set_size(1, handles.size()); - int32_T idx {0}; - for (py::handle array: handles) - { - auto func = py::cast(array); - std::string func_ptr = convertPtr2String(new Library(func)); - stringToRatBoundedArray(func_ptr, result[idx].f1.data, result[idx].f1.size); - idx++; - } - - return result; -} - -RAT::struct1_T createStruct1(const NameStore& names) -{ - RAT::struct1_T names_struct; - names_struct.params = customCaller("NameStore.params", pyListToRatCellWrap0, names.params); - names_struct.backgroundParams = customCaller("NameStore.backgroundParams", pyListToRatCellWrap0, names.backgroundParams); - names_struct.scalefactors = customCaller("NameStore.scalefactors", pyListToRatCellWrap0, names.scalefactors); - names_struct.qzshifts = customCaller("NameStore.qzshifts", pyListToRatCellWrap0, names.qzshifts); - names_struct.bulkIns = customCaller("NameStore.bulkIns", pyListToRatCellWrap0, names.bulkIns); - names_struct.bulkOuts = customCaller("NameStore.bulkOuts", pyListToRatCellWrap0, names.bulkOuts); - names_struct.resolutionParams = customCaller("NameStore.resolutionParams", pyListToRatCellWrap0, names.resolutionParams); - names_struct.domainRatios = customCaller("NameStore.domainRatios", pyListToRatCellWrap0, names.domainRatios); - names_struct.contrasts = customCaller("NameStore.contrasts", pyListToRatCellWrap0, names.contrasts); + RAT::b_ParamNames names_struct; + names_struct.params = customCaller("NameStore.params", pyListToRatCellWrap02d, names.params); + names_struct.backgroundParams = customCaller("NameStore.backgroundParams", pyListToRatCellWrap02d, names.backgroundParams); + names_struct.scalefactors = customCaller("NameStore.scalefactors", pyListToRatCellWrap02d, names.scalefactors); + names_struct.bulkIns = customCaller("NameStore.bulkIns", pyListToRatCellWrap02d, names.bulkIns); + names_struct.bulkOuts = customCaller("NameStore.bulkOuts", pyListToRatCellWrap02d, names.bulkOuts); + names_struct.resolutionParams = customCaller("NameStore.resolutionParams", pyListToRatCellWrap02d, names.resolutionParams); + names_struct.domainRatios = customCaller("NameStore.domainRatios", pyListToRatCellWrap02d, names.domainRatios); + names_struct.contrasts = customCaller("NameStore.contrasts", pyListToRatCellWrap02d, names.contrasts); return names_struct; } -RAT::struct2_T createStruct2(const Checks& checks) +RAT::CheckFlags createCheckFlagsStruct(const Checks& checks) { - RAT::struct2_T checks_struct; + RAT::CheckFlags checks_struct; checks_struct.params = customCaller("Checks.params", pyArrayToRatRowArray1d, checks.params); checks_struct.backgroundParams = customCaller("Checks.backgroundParams", pyArrayToRatRowArray1d, checks.backgroundParams); checks_struct.scalefactors = customCaller("Checks.scalefactors", pyArrayToRatRowArray1d, checks.scalefactors); - checks_struct.qzshifts = customCaller("Checks.qzshifts", pyArrayToRatRowArray1d, checks.qzshifts); checks_struct.bulkIns = customCaller("Checks.bulkIns", pyArrayToRatRowArray1d, checks.bulkIns); checks_struct.bulkOuts = customCaller("Checks.bulkOuts", pyArrayToRatRowArray1d, checks.bulkOuts); checks_struct.resolutionParams = customCaller("Checks.resolutionParams", pyArrayToRatRowArray1d, checks.resolutionParams); @@ -867,9 +250,9 @@ RAT::struct2_T createStruct2(const Checks& checks) return checks_struct; } -RAT::struct0_T createStruct0(const ProblemDefinition& problem) +RAT::b_ProblemDefinition createProblemDefinitionStruct(const ProblemDefinition& problem) { - RAT::struct0_T problem_struct; + RAT::b_ProblemDefinition problem_struct; stringToRatBoundedArray(problem.TF, problem_struct.TF.data, problem_struct.TF.size); problem_struct.resample = customCaller("Problem.resample", pyArrayToRatRowArray1d, problem.resample); @@ -877,22 +260,19 @@ RAT::struct0_T createStruct0(const ProblemDefinition& problem) problem_struct.dataPresent = customCaller("Problem.dataPresent", pyArrayToRatRowArray1d, problem.dataPresent); problem_struct.dataLimits = customCaller("Problem.dataLimits", pyListToRatCellWrap2, problem.dataLimits); problem_struct.simulationLimits = customCaller("Problem.simulationLimits", pyListToRatCellWrap2, problem.simulationLimits); - problem_struct.oilChiDataPresent = customCaller("Problem.oilChiDataPresent", pyArrayToRatRowArray1d, problem.oilChiDataPresent); problem_struct.numberOfContrasts = problem.numberOfContrasts; stringToRatBoundedArray(problem.geometry, problem_struct.geometry.data, problem_struct.geometry.size); problem_struct.useImaginary = problem.useImaginary; - problem_struct.repeatLayers = customCaller("Problem.repeatLayers", pyListToRatCellWrap2, problem.repeatLayers); + problem_struct.repeatLayers = customCaller("Problem.repeatLayers", pyArrayToRatRowArray1d, problem.repeatLayers); problem_struct.contrastBackgroundParams = customCaller("Problem.contrastBackgroundParams", pyListToRatCellWrap3, problem.contrastBackgroundParams); - problem_struct.contrastBackgroundTypes = customCaller("Problem.contrastBackgroundTypes", pyListToRatCellWrap0, problem.contrastBackgroundTypes); - problem_struct.contrastBackgroundActions = customCaller("Problem.contrastBackgroundActions", pyListToRatCellWrap0, problem.contrastBackgroundActions); - problem_struct.contrastQzshifts = customCaller("Problem.contrastQzshifts", pyArrayToRatRowArray1d, problem.contrastQzshifts); + problem_struct.contrastBackgroundTypes = customCaller("Problem.contrastBackgroundTypes", pyListToRatCellWrap02d, problem.contrastBackgroundTypes); + problem_struct.contrastBackgroundActions = customCaller("Problem.contrastBackgroundActions", pyListToRatCellWrap02d, problem.contrastBackgroundActions); problem_struct.contrastScalefactors = customCaller("Problem.contrastScalefactors", pyArrayToRatRowArray1d, problem.contrastScalefactors); problem_struct.contrastBulkIns = customCaller("Problem.contrastBulkIns", pyArrayToRatRowArray1d, problem.contrastBulkIns); problem_struct.contrastBulkOuts = customCaller("Problem.contrastBulkOuts", pyArrayToRatRowArray1d, problem.contrastBulkOuts); problem_struct.contrastResolutionParams = customCaller("Problem.contrastResolutionParams", pyListToRatCellWrap4, problem.contrastResolutionParams); - problem_struct.contrastResolutionTypes = customCaller("Problem.contrastResolutionTypes", pyListToRatCellWrap0, problem.contrastResolutionTypes); + problem_struct.contrastResolutionTypes = customCaller("Problem.contrastResolutionTypes", pyListToRatCellWrap02d, problem.contrastResolutionTypes); problem_struct.backgroundParams = customCaller("Problem.backgroundParams", pyArrayToRatRowArray1d, problem.backgroundParams); - problem_struct.qzshifts = customCaller("Problem.qzshifts", pyArrayToRatRowArray1d, problem.qzshifts); problem_struct.scalefactors = customCaller("Problem.scalefactors", pyArrayToRatRowArray1d, problem.scalefactors); problem_struct.bulkIns = customCaller("Problem.bulkIns", pyArrayToRatRowArray1d, problem.bulkIns); problem_struct.bulkOuts = customCaller("Problem.bulkOuts", pyArrayToRatRowArray1d, problem.bulkOuts); @@ -909,53 +289,21 @@ RAT::struct0_T createStruct0(const ProblemDefinition& problem) problem_struct.numberOfDomainContrasts = problem.numberOfDomainContrasts; problem_struct.domainContrastLayers = customCaller("Problem.domainContrastLayers", pyListToRatCellWrap5, problem.domainContrastLayers); problem_struct.fitParams = customCaller("Problem.fitParams", pyArrayToRatRowArray1d, problem.fitParams); - problem_struct.otherParams = customCaller("Problem.otherParams", pyArrayToRatRowArray1d, problem.otherParams); problem_struct.fitLimits = customCaller("Problem.fitLimits", pyArrayToRatArray2d, problem.fitLimits); - problem_struct.otherLimits = customCaller("Problem.otherLimits", pyArrayToRatArray2d, problem.otherLimits); + problem_struct.priorNames = customCaller("Problem.priorNames", pyListToRatCellWrap01d, problem.priorNames); + problem_struct.priorValues = customCaller("Problem.priorValues", pyArrayToRatArray2d, problem.priorValues); - problem_struct.names = createStruct1(problem.names); - problem_struct.checks = createStruct2(problem.checks); + problem_struct.names = createParamNamesStruct(problem.names); + problem_struct.checks = createCheckFlagsStruct(problem.checks); return problem_struct; } -RAT::struct3_T createStruct3(const Limits& limits) -{ - RAT::struct3_T limits_struct; - limits_struct.params = customCaller("Limits.params", pyArrayToRatArray2d, limits.params); - limits_struct.backgroundParams = customCaller("Limits.backgroundParams", pyArrayToRatArray2d, limits.backgroundParams); - limits_struct.scalefactors = customCaller("Limits.scalefactors", pyArrayToRatArray2d, limits.scalefactors); - limits_struct.qzshifts = customCaller("Limits.qzshifts", pyArrayToRatArray2d, limits.qzshifts); - limits_struct.bulkIns = customCaller("Limits.bulkIns", pyArrayToRatArray2d, limits.bulkIns); - limits_struct.bulkOuts = customCaller("Limits.bulkOuts", pyArrayToRatArray2d, limits.bulkOuts); - limits_struct.resolutionParams = customCaller("Limits.resolutionParams", pyArrayToRatArray2d, limits.resolutionParams); - limits_struct.domainRatios = customCaller("Limits.domainRatios", pyArrayToRatArray2d, limits.domainRatios); - - return limits_struct; -} - -RAT::struct5_T createStruct5(const Priors& priors) -{ - RAT::struct5_T priors_struct; - priors_struct.params = customCaller("Priors.params", pyListToUnboundedCell0, priors.params); - priors_struct.backgroundParams = customCaller("Priors.backgroundParams", pyListToUnboundedCell0, priors.backgroundParams); - priors_struct.scalefactors = customCaller("Priors.scalefactors", pyListToUnboundedCell0, priors.scalefactors); - priors_struct.qzshifts = customCaller("Priors.qzshifts", pyListToUnboundedCell0, priors.qzshifts); - priors_struct.bulkIns = customCaller("Priors.bulkIns", pyListToUnboundedCell0, priors.bulkIns); - priors_struct.bulkOuts = customCaller("Priors.bulkOuts", pyListToUnboundedCell0, priors.bulkOuts); - priors_struct.resolutionParams = customCaller("Priors.resolutionParams", pyListToUnboundedCell0, priors.resolutionParams); - priors_struct.domainRatios = customCaller("Priors.domainRatios", pyListToUnboundedCell0, priors.domainRatios); - priors_struct.priorNames = customCaller("Priors.priorNames", pyListToUnboundedCell1, priors.priorNames); - priors_struct.priorValues = customCaller("Priors.priorValues", pyArrayToRatArray2d, priors.priorValues); - - return priors_struct; -} - -RAT::struct4_T createStruct4(const Control& control) +RAT::Controls createControlsStruct(const Control& control) { - RAT::struct4_T control_struct; + RAT::Controls control_struct; control_struct.funcTolerance = control.funcTolerance; control_struct.maxFuncEvals = control.maxFuncEvals; control_struct.maxIterations = control.maxIterations; @@ -989,132 +337,7 @@ RAT::struct4_T createStruct4(const Control& control) return control_struct; } - -template -py::array_t pyArrayFromRatArray1d(T array, bool isCol=true) -{ - auto size = isCol ? array.size(1) : array.size(0); - auto result_array = py::array_t(size); - std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); - - return result_array; -} - -py::array_t pyArrayFromRatArray2d(coder::array array) -{ - auto result_array = py::array_t({array.size(0), array.size(1)}); - std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); - - return result_array; -} - -py::list pyListFromRatCellWrap0(coder::array values) -{ - py::list result; - for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { - std::string tmp; - stringFromRatBoundedArray(values[idx0].f1.data, values[idx0].f1.size, tmp); - result.append(tmp); - } - - return result; -} - -py::list pyListFromRatCellWrap2(coder::array values) -{ - py::list result; - - for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { - py::list inner = py::make_tuple(values[idx0].f1[0], values[idx0].f1[1]); - result.append(inner); - } - - return result; -} - -template -py::list pyList1DFromRatCellWrap2D(const T& values) -{ - py::list result; - - for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { - result.append(pyArrayFromRatArray2d(values[idx0].f1)); - } - - return result; -} - -template -py::list pyList1DFromRatCellWrap1D(const T& values) -{ - py::list result; - - for (int32_T idx0{0}; idx0 < values.size(0); idx0++) { - result.append(pyArrayFromRatArray2d(values[idx0].f1)); - } - - return result; -} - -template -py::list pyList2dFromRatCellWrap(const T& values) -{ - py::list result; - int32_T idx {0}; - for (int32_T idx0{0}; idx0 < values.size(0); idx0++) { - py::list inner; - for (int32_T idx1{0}; idx1 < values.size(1); idx1++) { - idx = idx0 + values.size(0) * idx1; - inner.append(pyArrayFromRatArray2d(values[idx].f1)); - } - result.append(inner); - } - - return result; -} - -template -py::list pyListFromBoundedCellWrap(const T& values) -{ - py::list result; - - for (int32_T idx0{0}; idx0 < values.size(1); idx0++) { - auto array = py::array_t({values[idx0].f1.size[0]}); - std::memcpy(array.request().ptr, values[idx0].f1.data, array.nbytes()); - - result.append(array); - } - - return result; -} - -template -py::array_t pyArray1dFromBoundedArray(const T& array) -{ - auto result_array = py::array_t({array.size[0]}); - std::memcpy(result_array.request().ptr, array.data, result_array.nbytes()); - - return result_array; -} - -template -py::array_t pyArray2dFromBoundedArray(const T& array) -{ - auto result_array = py::array_t({array.size[0], array.size[1]}); - std::memcpy(result_array.request().ptr, array.data, result_array.nbytes()); - - return result_array; -} - -py::array_t pyArrayFromRatArray3d(coder::array array) -{ - auto result_array = py::array_t({array.size(0), array.size(1), array.size(2)}); - std::memcpy(result_array.request().ptr, array.data(), result_array.nbytes()); - - return result_array; -} - -OutputResult OutputResultFromStruct6T(const RAT::struct6_T result) +OutputResult OutputResultFromStruct(const RAT::Results result) { // Copy problem to output OutputResult output_result; @@ -1153,26 +376,26 @@ OutputResult OutputResultFromStruct6T(const RAT::struct6_T result) output_result.resolutions.append(array); } - for (int32_T idx0{0}; idx0 < result.layerSlds.size(0); idx0++) { + for (int32_T idx0{0}; idx0 < result.sldProfiles.size(0); idx0++) { py::list inner_list; - for (int32_T idx1{0}; idx1 < result.layerSlds.size(1); idx1++) { - auto tmp = result.layerSlds[idx0 + result.layerSlds.size(0) * idx1]; + for (int32_T idx1{0}; idx1 < result.sldProfiles.size(1); idx1++) { + auto tmp = result.sldProfiles[idx0 + result.sldProfiles.size(0) * idx1]; auto array = py::array_t({tmp.f1.size(0), tmp.f1.size(1)}); std::memcpy(array.request().ptr, tmp.f1.data(), array.nbytes()); inner_list.append(array); } - output_result.layerSlds.append(inner_list); + output_result.sldProfiles.append(inner_list); } - for (int32_T idx0{0}; idx0 < result.sldProfiles.size(0); idx0++) { + for (int32_T idx0{0}; idx0 < result.layers.size(0); idx0++) { py::list inner_list; - for (int32_T idx1{0}; idx1 < result.sldProfiles.size(1); idx1++) { - auto tmp = result.sldProfiles[idx0 + result.sldProfiles.size(0) * idx1]; + for (int32_T idx1{0}; idx1 < result.layers.size(1); idx1++) { + auto tmp = result.layers[idx0 + result.layers.size(0) * idx1]; auto array = py::array_t({tmp.f1.size(0), tmp.f1.size(1)}); std::memcpy(array.request().ptr, tmp.f1.data(), array.nbytes()); inner_list.append(array); } - output_result.sldProfiles.append(inner_list); + output_result.layers.append(inner_list); } for (int32_T idx0{0}; idx0 < result.resampledLayers.size(0); idx0++) { @@ -1224,7 +447,7 @@ OutputResult OutputResultFromStruct6T(const RAT::struct6_T result) return output_result; } -ProblemDefinition problemDefinitionFromStruct0T(const RAT::struct0_T problem) +ProblemDefinition problemDefinitionFromStruct(const RAT::b_ProblemDefinition problem) { ProblemDefinition problem_def; @@ -1234,22 +457,19 @@ ProblemDefinition problemDefinitionFromStruct0T(const RAT::struct0_T problem) problem_def.dataPresent = pyArrayFromRatArray1d>(problem.dataPresent); problem_def.dataLimits = pyListFromRatCellWrap2(problem.dataLimits); problem_def.simulationLimits = pyListFromRatCellWrap2(problem.simulationLimits); - problem_def.oilChiDataPresent = pyArrayFromRatArray1d>(problem.oilChiDataPresent); problem_def.numberOfContrasts = problem.numberOfContrasts; stringFromRatBoundedArray(problem.geometry.data, problem.geometry.size, problem_def.geometry); problem_def.useImaginary = problem.useImaginary; - problem_def.repeatLayers = pyListFromRatCellWrap2(problem.repeatLayers); + problem_def.repeatLayers = pyArrayFromRatArray1d>(problem.repeatLayers); problem_def.contrastBackgroundParams = pyListFromBoundedCellWrap>(problem.contrastBackgroundParams); - problem_def.contrastBackgroundTypes = pyListFromRatCellWrap0(problem.contrastBackgroundTypes); - problem_def.contrastBackgroundActions = pyListFromRatCellWrap0(problem.contrastBackgroundActions); - problem_def.contrastQzshifts = pyArrayFromRatArray1d>(problem.contrastQzshifts); + problem_def.contrastBackgroundTypes = pyListFromRatCellWrap02d(problem.contrastBackgroundTypes); + problem_def.contrastBackgroundActions = pyListFromRatCellWrap02d(problem.contrastBackgroundActions); problem_def.contrastScalefactors = pyArrayFromRatArray1d>(problem.contrastScalefactors); problem_def.contrastBulkIns = pyArrayFromRatArray1d>(problem.contrastBulkIns); problem_def.contrastBulkOuts = pyArrayFromRatArray1d>(problem.contrastBulkOuts); problem_def.contrastResolutionParams = pyListFromBoundedCellWrap>(problem.contrastResolutionParams); - problem_def.contrastResolutionTypes = pyListFromRatCellWrap0(problem.contrastResolutionTypes); + problem_def.contrastResolutionTypes = pyListFromRatCellWrap02d(problem.contrastResolutionTypes); problem_def.backgroundParams = pyArrayFromRatArray1d>(problem.backgroundParams); - problem_def.qzshifts = pyArrayFromRatArray1d>(problem.qzshifts); problem_def.scalefactors = pyArrayFromRatArray1d>(problem.scalefactors); problem_def.bulkIns = pyArrayFromRatArray1d>(problem.bulkIns); problem_def.bulkOuts = pyArrayFromRatArray1d>(problem.bulkOuts); @@ -1266,24 +486,22 @@ ProblemDefinition problemDefinitionFromStruct0T(const RAT::struct0_T problem) problem_def.numberOfDomainContrasts = problem.numberOfDomainContrasts; problem_def.domainContrastLayers = pyList1DFromRatCellWrap2D>(problem.domainContrastLayers); problem_def.fitParams = pyArrayFromRatArray1d>(problem.fitParams); - problem_def.otherParams = pyArrayFromRatArray1d>(problem.otherParams); problem_def.fitLimits = pyArrayFromRatArray2d(problem.fitLimits); - problem_def.otherLimits = pyArrayFromRatArray2d(problem.otherLimits); + problem_def.priorNames = pyListFromRatCellWrap01d(problem.priorNames); + problem_def.priorValues = pyArrayFromRatArray2d(problem.priorValues); - problem_def.names.params = pyListFromRatCellWrap0(problem.names.params); - problem_def.names.backgroundParams = pyListFromRatCellWrap0(problem.names.backgroundParams); - problem_def.names.scalefactors = pyListFromRatCellWrap0(problem.names.scalefactors); - problem_def.names.qzshifts = pyListFromRatCellWrap0(problem.names.qzshifts); - problem_def.names.bulkIns = pyListFromRatCellWrap0(problem.names.bulkIns); - problem_def.names.bulkOuts = pyListFromRatCellWrap0(problem.names.bulkOuts); - problem_def.names.resolutionParams = pyListFromRatCellWrap0(problem.names.resolutionParams); - problem_def.names.domainRatios = pyListFromRatCellWrap0(problem.names.domainRatios); - problem_def.names.contrasts = pyListFromRatCellWrap0(problem.names.contrasts); + problem_def.names.params = pyListFromRatCellWrap02d(problem.names.params); + problem_def.names.backgroundParams = pyListFromRatCellWrap02d(problem.names.backgroundParams); + problem_def.names.scalefactors = pyListFromRatCellWrap02d(problem.names.scalefactors); + problem_def.names.bulkIns = pyListFromRatCellWrap02d(problem.names.bulkIns); + problem_def.names.bulkOuts = pyListFromRatCellWrap02d(problem.names.bulkOuts); + problem_def.names.resolutionParams = pyListFromRatCellWrap02d(problem.names.resolutionParams); + problem_def.names.domainRatios = pyListFromRatCellWrap02d(problem.names.domainRatios); + problem_def.names.contrasts = pyListFromRatCellWrap02d(problem.names.contrasts); problem_def.checks.params = pyArrayFromRatArray1d>(problem.checks.params); problem_def.checks.backgroundParams = pyArrayFromRatArray1d>(problem.checks.backgroundParams); problem_def.checks.scalefactors = pyArrayFromRatArray1d>(problem.checks.scalefactors); - problem_def.checks.qzshifts = pyArrayFromRatArray1d>(problem.checks.qzshifts); problem_def.checks.bulkIns = pyArrayFromRatArray1d>(problem.checks.bulkIns); problem_def.checks.bulkOuts = pyArrayFromRatArray1d>(problem.checks.bulkOuts); problem_def.checks.resolutionParams = pyArrayFromRatArray1d>(problem.checks.resolutionParams); @@ -1292,14 +510,14 @@ ProblemDefinition problemDefinitionFromStruct0T(const RAT::struct0_T problem) return problem_def; } -BayesResults bayesResultsFromStruct9T(const RAT::struct9_T results) +OutputBayesResult OutputBayesResultsFromStruct(const RAT::BayesResults results) { - BayesResults bayesResults; + OutputBayesResult bayesResults; bayesResults.chain = pyArrayFromRatArray2d(results.chain); - bayesResults.predictionIntervals.reflectivity = pyList1DFromRatCellWrap1D>(results.predictionIntervals.reflectivity); - bayesResults.predictionIntervals.sld = pyList2dFromRatCellWrap>(results.predictionIntervals.sld); + bayesResults.predictionIntervals.reflectivity = pyList1DFromRatCellWrap1D>(results.predictionIntervals.reflectivity); + bayesResults.predictionIntervals.sld = pyList2dFromRatCellWrap>(results.predictionIntervals.sld); bayesResults.predictionIntervals.sampleChi = pyArray1dFromBoundedArray>(results.predictionIntervals.sampleChi); bayesResults.confidenceIntervals.percentile95 = pyArrayFromRatArray2d(results.confidenceIntervals.percentile95); @@ -1315,7 +533,6 @@ BayesResults bayesResultsFromStruct9T(const RAT::struct9_T results) bayesResults.dreamOutput.outlierChains = pyArray2dFromBoundedArray>(results.dreamOutput.outlierChains); bayesResults.dreamOutput.runtime = results.dreamOutput.runtime; bayesResults.dreamOutput.iteration = results.dreamOutput.iteration; - bayesResults.dreamOutput.modelOutput = results.dreamOutput.modelOutput; bayesResults.dreamOutput.R_stat = pyArrayFromRatArray2d(results.dreamOutput.R_stat); bayesResults.dreamOutput.CR = pyArrayFromRatArray2d(results.dreamOutput.CR); bayesResults.dreamOutput.AR = pyArray2dFromBoundedArray>(results.dreamOutput.AR); @@ -1343,41 +560,77 @@ BayesResults bayesResultsFromStruct9T(const RAT::struct9_T results) return bayesResults; } -py::tuple RATMain(const ProblemDefinition& problem_def, const Limits& limits, const Control& control, const Priors& priors) +const std::string docsRATMain = R"(Entry point for the main reflectivity computation. + +Parameters +---------- +problem_def : Rat.rat_core.ProblemDefinition + The project input for the RAT calculation. +control : RATapi.rat_core.Control + The controls object for the RAT calculation. + +Returns +------- +out_problem_def : Rat.rat_core.ProblemDefinition + The project input with the updated fit values. +results : Rat.rat_core.OutputResult + The results from a RAT calculation. +bayes_result : Rat.rat_core.OutputBayesResult + The extra results if RAT calculation is Bayesian. +)"; + +py::tuple RATMain(const ProblemDefinition& problem_def, const Control& control) { - RAT::struct0_T problem_def_struct = createStruct0(problem_def); - RAT::struct3_T limits_struct = createStruct3(limits); - RAT::struct4_T control_struct = createStruct4(control); - RAT::struct5_T priors_struct = createStruct5(priors); + RAT::b_ProblemDefinition problem_def_struct = createProblemDefinitionStruct(problem_def); + RAT::Controls control_struct = createControlsStruct(control); // Output - RAT::struct6_T results; - RAT::struct9_T bayesResults; + RAT::Results results; + RAT::BayesResults bayesResults; // Call the entry-point - RAT::RATMain(&problem_def_struct, &limits_struct, &control_struct, &priors_struct, &results, &bayesResults); + RAT::RATMain(&problem_def_struct, &control_struct, &results, &bayesResults); // Copy result to output - auto out_problem_def = problemDefinitionFromStruct0T(problem_def_struct); + auto out_problem_def = problemDefinitionFromStruct(problem_def_struct); out_problem_def.customFiles = problem_def.customFiles.attr("copy")(); return py::make_tuple(out_problem_def, - OutputResultFromStruct6T(results), - bayesResultsFromStruct9T(bayesResults)); + OutputResultFromStruct(results), + OutputBayesResultsFromStruct(bayesResults)); } -py::array_t makeSLDProfileXY(real_T bulk_in, - real_T bulk_out, - real_T ssub, - const py::array_t &layers, - real_T number_of_layers, - real_T repeats) +const std::string docsMakeSLDProfile = R"(Creates the profiles for the SLD plots + +Parameters +---------- +bulk_in : float + Bulk in value for contrast. +bulk_out : float + Bulk out value for contrast. +layers : np.ndarray[np.float] + Array of parameters for each layer in the contrast. +ssub : float + Substrate roughness. +number_of_repeats : int, default: 1 + Number of times the layers are repeated. + +Returns +------- +sld_profile : np.ndarray[np.float] + Computed SLD profile +)"; + +py::array_t makeSLDProfile(real_T bulk_in, + real_T bulk_out, + const py::array_t &layers, + real_T ssub, + int number_of_repeats=DEFAULT_NREPEATS) { coder::array out; coder::array layers_array = pyArrayToRatArray2d(layers); - RAT::makeSLDProfileXY(bulk_in, - bulk_out, - ssub, - layers_array, - number_of_layers, - repeats, - out); + RAT::makeSLDProfile(bulk_in, + bulk_out, + layers_array, + ssub, + number_of_repeats, + out); return pyArrayFromRatArray2d(out); @@ -1422,13 +675,13 @@ PYBIND11_MODULE(rat_core, m) { .def("invoke", overload_cast_&, std::vector&>()(&DylibEngine::invoke), py::arg("xdata"), py::arg("param")); - py::class_(m, "PredictionIntervals") + py::class_(m, "PredictionIntervals", docsPredictionIntervals.c_str()) .def(py::init<>()) .def_readwrite("reflectivity", &PredictionIntervals::reflectivity) .def_readwrite("sld", &PredictionIntervals::sld) .def_readwrite("sampleChi", &PredictionIntervals::sampleChi); - py::class_(m, "PlotEventData") + py::class_(m, "PlotEventData", docsPlotEventData.c_str()) .def(py::init<>()) .def_readwrite("reflectivity", &PlotEventData::reflectivity) .def_readwrite("shiftedData", &PlotEventData::shiftedData) @@ -1465,7 +718,7 @@ PYBIND11_MODULE(rat_core, m) { return evt; })); - py::class_(m, "ProgressEventData") + py::class_(m, "ProgressEventData", docsProgressEventData.c_str()) .def(py::init<>()) .def_readwrite("message", &ProgressEventData::message) .def_readwrite("percent", &ProgressEventData::percent) @@ -1487,7 +740,7 @@ PYBIND11_MODULE(rat_core, m) { return evt; })); - py::class_(m, "ConfidenceIntervals") + py::class_(m, "ConfidenceIntervals", docsConfidenceIntervals.c_str()) .def(py::init<>()) .def_readwrite("percentile95", &ConfidenceIntervals::percentile95) .def_readwrite("percentile65", &ConfidenceIntervals::percentile65) @@ -1515,39 +768,38 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("storeOutput", &DreamParams::storeOutput) .def_readwrite("R", &DreamParams::R); - py::class_(m, "NestedSamplerOutput") + py::class_(m, "NestedSamplerOutput", docsNestedSamplerOutput.c_str()) .def(py::init<>()) .def_readwrite("logZ", &NestedSamplerOutput::logZ) .def_readwrite("logZErr", &NestedSamplerOutput::logZErr) .def_readwrite("nestSamples", &NestedSamplerOutput::nestSamples) .def_readwrite("postSamples", &NestedSamplerOutput::postSamples); - py::class_(m, "DreamOutput") + py::class_(m, "DreamOutput", docsDreamOutput.c_str()) .def(py::init<>()) .def_readwrite("allChains", &DreamOutput::allChains) .def_readwrite("outlierChains", &DreamOutput::outlierChains) .def_readwrite("runtime", &DreamOutput::runtime) .def_readwrite("iteration", &DreamOutput::iteration) - .def_readwrite("modelOutput", &DreamOutput::modelOutput) .def_readwrite("AR", &DreamOutput::AR) .def_readwrite("R_stat", &DreamOutput::R_stat) .def_readwrite("CR", &DreamOutput::CR); - py::class_(m, "BayesResults") + py::class_(m, "OutputBayesResult", docsOutputBayesResult.c_str()) .def(py::init<>()) - .def_readwrite("predictionIntervals", &BayesResults::predictionIntervals) - .def_readwrite("confidenceIntervals", &BayesResults::confidenceIntervals) - .def_readwrite("dreamParams", &BayesResults::dreamParams) - .def_readwrite("dreamOutput", &BayesResults::dreamOutput) - .def_readwrite("nestedSamplerOutput", &BayesResults::nestedSamplerOutput) - .def_readwrite("chain", &BayesResults::chain); - - py::class_(m, "Calculation") + .def_readwrite("predictionIntervals", &OutputBayesResult::predictionIntervals) + .def_readwrite("confidenceIntervals", &OutputBayesResult::confidenceIntervals) + .def_readwrite("dreamParams", &OutputBayesResult::dreamParams) + .def_readwrite("dreamOutput", &OutputBayesResult::dreamOutput) + .def_readwrite("nestedSamplerOutput", &OutputBayesResult::nestedSamplerOutput) + .def_readwrite("chain", &OutputBayesResult::chain); + + py::class_(m, "Calculation", docsCalculation.c_str()) .def(py::init<>()) .def_readwrite("chiValues", &Calculation::chiValues) .def_readwrite("sumChi", &Calculation::sumChi); - py::class_(m, "ContrastParams") + py::class_(m, "ContrastParams", docsContrastParams.c_str()) .def(py::init<>()) .def_readwrite("scalefactors", &ContrastParams::scalefactors) .def_readwrite("bulkIn", &ContrastParams::bulkIn) @@ -1555,27 +807,26 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("subRoughs", &ContrastParams::subRoughs) .def_readwrite("resample", &ContrastParams::resample); - py::class_(m, "OutputResult") + py::class_(m, "OutputResult", docsOutputResult.c_str()) .def(py::init<>()) .def_readwrite("reflectivity", &OutputResult::reflectivity) .def_readwrite("simulation", &OutputResult::simulation) .def_readwrite("shiftedData", &OutputResult::shiftedData) .def_readwrite("backgrounds", &OutputResult::backgrounds) .def_readwrite("resolutions", &OutputResult::resolutions) - .def_readwrite("layerSlds", &OutputResult::layerSlds) .def_readwrite("sldProfiles", &OutputResult::sldProfiles) + .def_readwrite("layers", &OutputResult::layers) .def_readwrite("resampledLayers", &OutputResult::resampledLayers) .def_readwrite("calculationResults", &OutputResult::calculationResults) .def_readwrite("contrastParams", &OutputResult::contrastParams) .def_readwrite("fitParams", &OutputResult::fitParams) .def_readwrite("fitNames", &OutputResult::fitNames); - py::class_(m, "NameStore") + py::class_(m, "NameStore", docsNameStore.c_str()) .def(py::init<>()) .def_readwrite("params", &NameStore::params) .def_readwrite("backgroundParams", &NameStore::backgroundParams) .def_readwrite("scalefactors", &NameStore::scalefactors) - .def_readwrite("qzshifts", &NameStore::qzshifts) .def_readwrite("bulkIns", &NameStore::bulkIns) .def_readwrite("bulkOuts", &NameStore::bulkOuts) .def_readwrite("resolutionParams", &NameStore::resolutionParams) @@ -1584,11 +835,11 @@ PYBIND11_MODULE(rat_core, m) { .def(py::pickle( [](const NameStore &names) { // __getstate__ /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(names.params, names.backgroundParams, names.scalefactors, names.qzshifts, names.bulkIns, names.bulkOuts, names.resolutionParams, + return py::make_tuple(names.params, names.backgroundParams, names.scalefactors, names.bulkIns, names.bulkOuts, names.resolutionParams, names.domainRatios, names.contrasts); }, [](py::tuple t) { // __setstate__ - if (t.size() != 9) + if (t.size() != 8) throw std::runtime_error("Encountered invalid state unpickling NameStore object!"); /* Create a new C++ instance */ @@ -1597,22 +848,20 @@ PYBIND11_MODULE(rat_core, m) { names.params = t[0].cast(); names.backgroundParams = t[1].cast(); names.scalefactors = t[2].cast(); - names.qzshifts = t[3].cast(); - names.bulkIns = t[4].cast(); - names.bulkOuts = t[5].cast(); - names.resolutionParams = t[6].cast(); - names.domainRatios = t[7].cast(); - names.contrasts = t[8].cast(); + names.bulkIns = t[3].cast(); + names.bulkOuts = t[4].cast(); + names.resolutionParams = t[5].cast(); + names.domainRatios = t[6].cast(); + names.contrasts = t[7].cast(); return names; })); - py::class_(m, "Checks") + py::class_(m, "Checks", docsChecks.c_str()) .def(py::init<>()) .def_readwrite("params", &Checks::params) .def_readwrite("backgroundParams", &Checks::backgroundParams) .def_readwrite("scalefactors", &Checks::scalefactors) - .def_readwrite("qzshifts", &Checks::qzshifts) .def_readwrite("bulkIns", &Checks::bulkIns) .def_readwrite("bulkOuts", &Checks::bulkOuts) .def_readwrite("resolutionParams", &Checks::resolutionParams) @@ -1620,11 +869,11 @@ PYBIND11_MODULE(rat_core, m) { .def(py::pickle( [](const Checks &chk) { // __getstate__ /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(chk.params, chk.backgroundParams, chk.scalefactors, chk.qzshifts, chk.bulkIns, chk.bulkOuts, + return py::make_tuple(chk.params, chk.backgroundParams, chk.scalefactors, chk.bulkIns, chk.bulkOuts, chk.resolutionParams, chk.domainRatios); }, [](py::tuple t) { // __setstate__ - if (t.size() != 8) + if (t.size() != 7) throw std::runtime_error("Encountered invalid state unpickling Checks object!"); /* Create a new C++ instance */ @@ -1632,91 +881,16 @@ PYBIND11_MODULE(rat_core, m) { chk.params = t[0].cast>(); chk.backgroundParams = t[1].cast>(); - chk.scalefactors = t[2].cast>(); - chk.qzshifts = t[3].cast>(); - chk.bulkIns = t[4].cast>(); - chk.bulkOuts = t[5].cast>(); - chk.resolutionParams = t[6].cast>(); - chk.domainRatios = t[7].cast>(); + chk.scalefactors = t[2].cast>(); + chk.bulkIns = t[3].cast>(); + chk.bulkOuts = t[4].cast>(); + chk.resolutionParams = t[5].cast>(); + chk.domainRatios = t[6].cast>(); return chk; })); - py::class_(m, "Limits") - .def(py::init<>()) - .def_readwrite("params", &Limits::params) - .def_readwrite("backgroundParams", &Limits::backgroundParams) - .def_readwrite("scalefactors", &Limits::scalefactors) - .def_readwrite("qzshifts", &Limits::qzshifts) - .def_readwrite("bulkIns", &Limits::bulkIns) - .def_readwrite("bulkOuts", &Limits::bulkOuts) - .def_readwrite("resolutionParams", &Limits::resolutionParams) - .def_readwrite("domainRatios", &Limits::domainRatios) - .def(py::pickle( - [](const Limits &lim) { // __getstate__ - /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(lim.params, lim.backgroundParams, lim.scalefactors, lim.qzshifts, lim.bulkIns, lim.bulkOuts, - lim.resolutionParams, lim.domainRatios); - }, - [](py::tuple t) { // __setstate__ - if (t.size() != 8) - throw std::runtime_error("Encountered invalid state unpickling Limits object!"); - - /* Create a new C++ instance */ - Limits lim; - - lim.params = t[0].cast>(); - lim.backgroundParams = t[1].cast>(); - lim.scalefactors = t[2].cast>(); - lim.qzshifts = t[3].cast>(); - lim.bulkIns = t[4].cast>(); - lim.bulkOuts = t[5].cast>(); - lim.resolutionParams = t[6].cast>(); - lim.domainRatios = t[7].cast>(); - - return lim; - })); - - py::class_(m, "Priors") - .def(py::init<>()) - .def_readwrite("params", &Priors::params) - .def_readwrite("backgroundParams", &Priors::backgroundParams) - .def_readwrite("scalefactors", &Priors::scalefactors) - .def_readwrite("qzshifts", &Priors::qzshifts) - .def_readwrite("bulkIns", &Priors::bulkIns) - .def_readwrite("bulkOuts", &Priors::bulkOuts) - .def_readwrite("resolutionParams", &Priors::resolutionParams) - .def_readwrite("domainRatios", &Priors::domainRatios) - .def_readwrite("priorNames", &Priors::priorNames) - .def_readwrite("priorValues", &Priors::priorValues) - .def(py::pickle( - [](const Priors &prior) { // __getstate__ - /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(prior.params, prior.backgroundParams, prior.scalefactors, prior.qzshifts, prior.bulkIns, - prior.bulkOuts, prior.resolutionParams, prior.domainRatios, prior.priorNames, prior.priorValues); - }, - [](py::tuple t) { // __setstate__ - if (t.size() != 10) - throw std::runtime_error("Encountered invalid state unpickling Limits object!"); - - /* Create a new C++ instance */ - Priors prior; - - prior.params = t[0].cast(); - prior.backgroundParams = t[1].cast(); - prior.scalefactors = t[2].cast(); - prior.qzshifts = t[3].cast(); - prior.bulkIns = t[4].cast(); - prior.bulkOuts = t[5].cast(); - prior.resolutionParams = t[6].cast(); - prior.domainRatios = t[7].cast(); - prior.priorNames = t[8].cast(); - prior.priorValues = t[9].cast>(); - - return prior; - })); - - py::class_(m, "Control") + py::class_(m, "Control", docsControl.c_str()) .def(py::init<>()) .def_readwrite("parallel", &Control::parallel) .def_readwrite("procedure", &Control::procedure) @@ -1797,7 +971,7 @@ PYBIND11_MODULE(rat_core, m) { return ctrl; })); - py::class_(m, "ProblemDefinition") + py::class_(m, "ProblemDefinition", docsProblemDefinition.c_str()) .def(py::init<>()) .def_readwrite("TF", &ProblemDefinition::TF) .def_readwrite("resample", &ProblemDefinition::resample) @@ -1805,7 +979,6 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("dataPresent", &ProblemDefinition::dataPresent) .def_readwrite("dataLimits", &ProblemDefinition::dataLimits) .def_readwrite("simulationLimits", &ProblemDefinition::simulationLimits) - .def_readwrite("oilChiDataPresent", &ProblemDefinition::oilChiDataPresent) .def_readwrite("numberOfContrasts", &ProblemDefinition::numberOfContrasts) .def_readwrite("geometry", &ProblemDefinition::geometry) .def_readwrite("useImaginary", &ProblemDefinition::useImaginary) @@ -1813,14 +986,12 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("contrastBackgroundParams", &ProblemDefinition::contrastBackgroundParams) .def_readwrite("contrastBackgroundTypes", &ProblemDefinition::contrastBackgroundTypes) .def_readwrite("contrastBackgroundActions", &ProblemDefinition::contrastBackgroundActions) - .def_readwrite("contrastQzshifts", &ProblemDefinition::contrastQzshifts) .def_readwrite("contrastScalefactors", &ProblemDefinition::contrastScalefactors) .def_readwrite("contrastBulkIns", &ProblemDefinition::contrastBulkIns) .def_readwrite("contrastBulkOuts", &ProblemDefinition::contrastBulkOuts) .def_readwrite("contrastResolutionParams", &ProblemDefinition::contrastResolutionParams) .def_readwrite("contrastResolutionTypes", &ProblemDefinition::contrastResolutionTypes) .def_readwrite("backgroundParams", &ProblemDefinition::backgroundParams) - .def_readwrite("qzshifts", &ProblemDefinition::qzshifts) .def_readwrite("scalefactors", &ProblemDefinition::scalefactors) .def_readwrite("bulkIns", &ProblemDefinition::bulkIns) .def_readwrite("bulkOuts", &ProblemDefinition::bulkOuts) @@ -1837,29 +1008,29 @@ PYBIND11_MODULE(rat_core, m) { .def_readwrite("numberOfDomainContrasts", &ProblemDefinition::numberOfDomainContrasts) .def_readwrite("domainContrastLayers", &ProblemDefinition::domainContrastLayers) .def_readwrite("fitParams", &ProblemDefinition::fitParams) - .def_readwrite("otherParams", &ProblemDefinition::otherParams) .def_readwrite("fitLimits", &ProblemDefinition::fitLimits) - .def_readwrite("otherLimits", &ProblemDefinition::otherLimits) + .def_readwrite("priorNames", &ProblemDefinition::priorNames) + .def_readwrite("priorValues", &ProblemDefinition::priorValues) .def_readwrite("names", &ProblemDefinition::names) .def_readwrite("checks", &ProblemDefinition::checks) .def(py::pickle( [](const ProblemDefinition &p) { // __getstate__ /* Return a tuple that fully encodes the state of the object */ - return py::make_tuple(p.TF, p.resample, p.data, p.dataPresent, p.dataLimits, p.simulationLimits, p.oilChiDataPresent, + return py::make_tuple(p.TF, p.resample, p.data, p.dataPresent, p.dataLimits, p.simulationLimits, p.numberOfContrasts, p.geometry, p.useImaginary, p.repeatLayers, p.contrastBackgroundParams, p.contrastBackgroundTypes, p.contrastBackgroundActions, - p.contrastQzshifts, p.contrastScalefactors, p.contrastBulkIns, p.contrastBulkOuts, - p.contrastResolutionParams, p.contrastResolutionTypes, p.backgroundParams, p.qzshifts, p.scalefactors, - p.bulkIns, p.bulkOuts, p.resolutionParams, p.params, p.numberOfLayers, p.contrastLayers, p.layersDetails, + p.contrastScalefactors, p.contrastBulkIns, p.contrastBulkOuts, p.contrastResolutionParams, + p.contrastResolutionTypes, p.backgroundParams, p.scalefactors, p.bulkIns, p.bulkOuts, + p.resolutionParams, p.params, p.numberOfLayers, p.contrastLayers, p.layersDetails, p.customFiles, p.modelType, p.contrastCustomFiles, p.contrastDomainRatios, p.domainRatios, - p.numberOfDomainContrasts, p.domainContrastLayers, p.fitParams, p.otherParams, p.fitLimits, - p.otherLimits, p.names.params, p.names.backgroundParams, p.names.scalefactors, p.names.qzshifts, - p.names.bulkIns, p.names.bulkOuts, p.names.resolutionParams, p.names.domainRatios, p.names.contrasts, - p.checks.params, p.checks.backgroundParams, p.checks.scalefactors, p.checks.qzshifts, + p.numberOfDomainContrasts, p.domainContrastLayers, p.fitParams, p.fitLimits, p.priorNames, p.priorValues, + p.names.params, p.names.backgroundParams, p.names.scalefactors, p.names.bulkIns, + p.names.bulkOuts, p.names.resolutionParams, p.names.domainRatios, p.names.contrasts, + p.checks.params, p.checks.backgroundParams, p.checks.scalefactors, p.checks.bulkIns, p.checks.bulkOuts, p.checks.resolutionParams, p.checks.domainRatios); }, [](py::tuple t) { // __setstate__ - if (t.size() != 58) + if (t.size() != 53) throw std::runtime_error("Encountered invalid state unpickling ProblemDefinition object!"); /* Create a new C++ instance */ @@ -1871,65 +1042,61 @@ PYBIND11_MODULE(rat_core, m) { p.dataPresent = t[3].cast>(); p.dataLimits = t[4].cast(); p.simulationLimits = t[5].cast(); - p.oilChiDataPresent = t[6].cast>(); - p.numberOfContrasts = t[7].cast(); - p.geometry = t[8].cast(); - p.useImaginary = t[9].cast(); - p.repeatLayers = t[10].cast(); - p.contrastBackgroundParams = t[11].cast(); - p.contrastBackgroundTypes = t[12].cast(); - p.contrastBackgroundActions = t[13].cast(); - p.contrastQzshifts = t[14].cast>(); - p.contrastScalefactors = t[15].cast>(); - p.contrastBulkIns = t[16].cast>(); - p.contrastBulkOuts = t[17].cast>(); - p.contrastResolutionParams = t[18].cast(); - p.contrastResolutionTypes = t[19].cast(); - p.backgroundParams = t[20].cast>(); - p.qzshifts = t[21].cast>(); - p.scalefactors = t[22].cast>(); - p.bulkIns = t[23].cast>(); - p.bulkOuts = t[24].cast>(); - p.resolutionParams = t[25].cast>(); - p.params = t[26].cast>(); - p.numberOfLayers = t[27].cast(); - p.contrastLayers = t[28].cast(); - p.layersDetails = t[29].cast(); - p.customFiles = t[30].cast(); - p.modelType = t[31].cast(); - p.contrastCustomFiles = t[32].cast>(); - p.contrastDomainRatios = t[33].cast>(); - p.domainRatios = t[34].cast>(); - p.numberOfDomainContrasts = t[35].cast(); - p.domainContrastLayers = t[36].cast(); - p.fitParams = t[37].cast>(); - p.otherParams = t[38].cast>(); - p.fitLimits = t[39].cast>(); - p.otherLimits = t[40].cast>(); + p.numberOfContrasts = t[6].cast(); + p.geometry = t[7].cast(); + p.useImaginary = t[8].cast(); + p.repeatLayers = t[9].cast>(); + p.contrastBackgroundParams = t[10].cast(); + p.contrastBackgroundTypes = t[11].cast(); + p.contrastBackgroundActions = t[12].cast(); + p.contrastScalefactors = t[13].cast>(); + p.contrastBulkIns = t[14].cast>(); + p.contrastBulkOuts = t[15].cast>(); + p.contrastResolutionParams = t[16].cast(); + p.contrastResolutionTypes = t[17].cast(); + p.backgroundParams = t[18].cast>(); + p.scalefactors = t[19].cast>(); + p.bulkIns = t[20].cast>(); + p.bulkOuts = t[21].cast>(); + p.resolutionParams = t[22].cast>(); + p.params = t[23].cast>(); + p.numberOfLayers = t[24].cast(); + p.contrastLayers = t[25].cast(); + p.layersDetails = t[26].cast(); + p.customFiles = t[27].cast(); + p.modelType = t[28].cast(); + p.contrastCustomFiles = t[29].cast>(); + p.contrastDomainRatios = t[30].cast>(); + p.domainRatios = t[31].cast>(); + p.numberOfDomainContrasts = t[32].cast(); + p.domainContrastLayers = t[33].cast(); + p.fitParams = t[34].cast>(); + p.fitLimits = t[35].cast>(); + p.priorNames = t[36].cast(); + p.priorValues = t[37].cast>(); - p.names.params = t[41].cast(); - p.names.backgroundParams = t[42].cast(); - p.names.scalefactors = t[43].cast(); - p.names.qzshifts = t[44].cast(); - p.names.bulkIns = t[45].cast(); - p.names.bulkOuts = t[46].cast(); - p.names.resolutionParams = t[47].cast(); - p.names.domainRatios = t[48].cast(); - p.names.contrasts = t[49].cast(); - - p.checks.params = t[50].cast>(); - p.checks.backgroundParams = t[51].cast>(); - p.checks.scalefactors = t[52].cast>(); - p.checks.qzshifts = t[53].cast>(); - p.checks.bulkIns = t[54].cast>(); - p.checks.bulkOuts = t[55].cast>(); - p.checks.resolutionParams = t[56].cast>(); - p.checks.domainRatios = t[57].cast>(); + p.names.params = t[38].cast(); + p.names.backgroundParams = t[39].cast(); + p.names.scalefactors = t[40].cast(); + p.names.bulkIns = t[41].cast(); + p.names.bulkOuts = t[42].cast(); + p.names.resolutionParams = t[43].cast(); + p.names.domainRatios = t[44].cast(); + p.names.contrasts = t[45].cast(); + + p.checks.params = t[46].cast>(); + p.checks.backgroundParams = t[47].cast>(); + p.checks.scalefactors = t[48].cast>(); + p.checks.bulkIns = t[49].cast>(); + p.checks.bulkOuts = t[50].cast>(); + p.checks.resolutionParams = t[51].cast>(); + p.checks.domainRatios = t[52].cast>(); return p; })); - m.def("RATMain", &RATMain, "Entry point for the main reflectivity computation."); + m.def("RATMain", &RATMain, docsRATMain.c_str(), py::arg("problem_def"), py::arg("control")); - m.def("makeSLDProfileXY", &makeSLDProfileXY, "Creates the profiles for the SLD plots"); + m.def("makeSLDProfile", &makeSLDProfile, docsMakeSLDProfile.c_str(), + py::arg("bulk_in"), py::arg("bulk_out"), py::arg("layers"), py::arg("ssub"), py::arg("number_of_repeats") = DEFAULT_NREPEATS); } diff --git a/pyproject.toml b/pyproject.toml index d20e5b8d..35febb57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,9 +11,31 @@ line-length = 120 extend-exclude = ["*.ipynb"] [tool.ruff.lint] -select = ["E", "F", "UP", "B", "SIM", "I"] -ignore = ["SIM103", "SIM108"] +select = ["E", # pycodestyle errors + "F", # pyflakes + "UP", # pyupgrade + "B", # flake8-bugbear + "SIM", # flake8-simplify + "I", # isort + "D"] # pydocstyle + +ignore = ["SIM103", # needless bool + "SIM108", # if-else block instead of ternary operator + "D105", # undocumented __init__ + "D107", # undocumented magic method + "D203", # blank line before class docstring + "D213"] # multi line summary should start at second line + +# ignore docstring lints in the tests and install script +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["D"] +"setup.py" = ["D"] [tool.ruff.lint.flake8-pytest-style] fixture-parentheses = false mark-parentheses = false + +[tool.ruff.lint.pydocstyle] +convention = "numpy" + + diff --git a/requirements.txt b/requirements.txt index 47a268b5..cee9a790 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,5 @@ StrEnum >= 0.4.15; python_version < '3.11' ruff >= 0.4.10 scipy >= 1.13.1 tqdm >= 4.66.5 +orsopy >= 1.2.1 +pint >= 0.24.4 diff --git a/setup.py b/setup.py index a21faae8..f3f294db 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ from setuptools.command.build_clib import build_clib from setuptools.command.build_ext import build_ext -__version__ = "0.0.0.dev4" +__version__ = "0.0.0.dev5" PACKAGE_NAME = "RATapi" with open("README.md") as f: @@ -26,6 +26,7 @@ pybind11.get_include(), pybind11.get_include(True), "cpp/RAT/", + "cpp/includes/", ], language="c++", ), @@ -164,26 +165,25 @@ def build_libraries(self, libraries): cmdclass={"build_clib": BuildClib, "build_ext": BuildExt}, libraries=[libevent], ext_modules=ext_modules, - python_requires=">=3.9", + python_requires=">=3.10", install_requires=[ "numpy >= 1.20", "prettytable >= 3.9.0", "pydantic >= 2.7.2", "matplotlib >= 3.8.3", "scipy >= 1.13.1", - "tqdm>=4.66.5", + "tqdm >= 4.66.5", ], extras_require={ ':python_version < "3.11"': ["StrEnum >= 0.4.15"], "Dev": ["pytest>=7.4.0", "pytest-cov>=4.1.0", "ruff>=0.4.10"], + "Orso": ["orsopy>=1.2.1", "pint>=0.24.4"], "Matlab_latest": ["matlabengine"], - "Matlab_2024a": ["matlabengine == 24.1.*"], + "Matlab_2025a": ["matlabengine == 25.1.*"], + "Matlab_2024b": ["matlabengine == 24.2.2"], + "Matlab_2024a": ["matlabengine == 24.1.4"], "Matlab_2023b": ["matlabengine == 23.2.3"], "Matlab_2023a": ["matlabengine == 9.14.3"], - "Matlab_2022b": ["matlabengine == 9.13.9"], - "Matlab_2022a": ["matlabengine == 9.12.19"], - "Matlab_2021b": ["matlabengine == 9.11.21"], - "Matlab_2021a": ["matlabengine == 9.10.3"], }, zip_safe=False, ) diff --git a/tests/conftest.py b/tests/conftest.py index 0b78d192..c109c92d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -774,36 +774,6 @@ def reflectivity_calculation_output_results(): ] ), ] - results.layerSlds = [ - [ - np.array( - [ - [1.954000e01, 4.001499e-06, 3.000000e00], - [2.266000e01, -6.586988e-08, 3.000000e00], - [8.560000e00, 3.672535e-06, 5.640000e00], - [1.712000e01, 5.980000e-06, 5.640000e00], - [1.070000e01, 3.100365e-06, 6.014000e00], - [1.782000e01, 6.751924e-07, 6.014000e00], - [1.782000e01, 6.751924e-07, 6.014000e00], - [1.070000e01, 3.100365e-06, 6.014000e00], - ], - ), - ], - [ - np.array( - [ - [1.9540000e01, 3.1114020e-06, 3.0000000e00], - [2.2660000e01, -2.6387028e-07, 3.0000000e00], - [8.5600000e00, 1.9590700e-06, 5.6400000e00], - [1.7120000e01, 2.2100000e-06, 5.6400000e00], - [1.0700000e01, 1.7375100e-06, 6.0140000e00], - [1.7820000e01, 1.0164400e-08, 6.0140000e00], - [1.7820000e01, 1.0164400e-08, 6.0140000e00], - [1.0700000e01, 1.7375100e-06, 6.0140000e00], - ], - ), - ], - ] results.sldProfiles = [ [ np.array( @@ -866,6 +836,36 @@ def reflectivity_calculation_output_results(): ), ], ] + results.layers = [ + [ + np.array( + [ + [1.954000e01, 4.001499e-06, 3.000000e00], + [2.266000e01, -6.586988e-08, 3.000000e00], + [8.560000e00, 3.672535e-06, 5.640000e00], + [1.712000e01, 5.980000e-06, 5.640000e00], + [1.070000e01, 3.100365e-06, 6.014000e00], + [1.782000e01, 6.751924e-07, 6.014000e00], + [1.782000e01, 6.751924e-07, 6.014000e00], + [1.070000e01, 3.100365e-06, 6.014000e00], + ], + ), + ], + [ + np.array( + [ + [1.9540000e01, 3.1114020e-06, 3.0000000e00], + [2.2660000e01, -2.6387028e-07, 3.0000000e00], + [8.5600000e00, 1.9590700e-06, 5.6400000e00], + [1.7120000e01, 2.2100000e-06, 5.6400000e00], + [1.0700000e01, 1.7375100e-06, 6.0140000e00], + [1.7820000e01, 1.0164400e-08, 6.0140000e00], + [1.7820000e01, 1.0164400e-08, 6.0140000e00], + [1.0700000e01, 1.7375100e-06, 6.0140000e00], + ], + ), + ], + ] results.resampledLayers = [[np.array([[0.0, 0.0, 0.0]])], [np.array([[0.0, 0.0, 0.0]])]] results.calculationResults = RATapi.rat_core.Calculation() results.calculationResults.chiValues = np.array([202.83057377, 1641.4024969]) @@ -1430,36 +1430,6 @@ def reflectivity_calculation_results(): ] ), ], - layerSlds=[ - [ - np.array( - [ - [1.954000e01, 4.001499e-06, 3.000000e00], - [2.266000e01, -6.586988e-08, 3.000000e00], - [8.560000e00, 3.672535e-06, 5.640000e00], - [1.712000e01, 5.980000e-06, 5.640000e00], - [1.070000e01, 3.100365e-06, 6.014000e00], - [1.782000e01, 6.751924e-07, 6.014000e00], - [1.782000e01, 6.751924e-07, 6.014000e00], - [1.070000e01, 3.100365e-06, 6.014000e00], - ], - ), - ], - [ - np.array( - [ - [1.9540000e01, 3.1114020e-06, 3.0000000e00], - [2.2660000e01, -2.6387028e-07, 3.0000000e00], - [8.5600000e00, 1.9590700e-06, 5.6400000e00], - [1.7120000e01, 2.2100000e-06, 5.6400000e00], - [1.0700000e01, 1.7375100e-06, 6.0140000e00], - [1.7820000e01, 1.0164400e-08, 6.0140000e00], - [1.7820000e01, 1.0164400e-08, 6.0140000e00], - [1.0700000e01, 1.7375100e-06, 6.0140000e00], - ], - ), - ], - ], sldProfiles=[ [ np.array( @@ -1522,6 +1492,36 @@ def reflectivity_calculation_results(): ), ], ], + layers=[ + [ + np.array( + [ + [1.954000e01, 4.001499e-06, 3.000000e00], + [2.266000e01, -6.586988e-08, 3.000000e00], + [8.560000e00, 3.672535e-06, 5.640000e00], + [1.712000e01, 5.980000e-06, 5.640000e00], + [1.070000e01, 3.100365e-06, 6.014000e00], + [1.782000e01, 6.751924e-07, 6.014000e00], + [1.782000e01, 6.751924e-07, 6.014000e00], + [1.070000e01, 3.100365e-06, 6.014000e00], + ], + ), + ], + [ + np.array( + [ + [1.9540000e01, 3.1114020e-06, 3.0000000e00], + [2.2660000e01, -2.6387028e-07, 3.0000000e00], + [8.5600000e00, 1.9590700e-06, 5.6400000e00], + [1.7120000e01, 2.2100000e-06, 5.6400000e00], + [1.0700000e01, 1.7375100e-06, 6.0140000e00], + [1.7820000e01, 1.0164400e-08, 6.0140000e00], + [1.7820000e01, 1.0164400e-08, 6.0140000e00], + [1.0700000e01, 1.7375100e-06, 6.0140000e00], + ], + ), + ], + ], resampledLayers=[[np.array([[0.0, 0.0, 0.0]])], [np.array([[0.0, 0.0, 0.0]])]], calculationResults=RATapi.outputs.CalculationResults( chiValues=np.array([202.83057377, 1641.4024969]), @@ -2091,36 +2091,6 @@ def dream_output_results(): ] ), ] - results.layerSlds = [ - [ - np.array( - [ - [3.15755349e01, 3.35278238e-06, 4.16625659e00], - [3.61791464e01, 7.68327921e-07, 4.16625659e00], - [1.00488530e01, 2.06044530e-06, 2.78042232e01], - [1.08043784e01, 3.29384190e-06, 2.78042232e01], - [2.42251646e01, 2.35556998e-06, 1.55593097e01], - [1.49022278e01, 7.42138004e-07, 1.55593097e01], - [1.49022278e01, 7.42138004e-07, 1.55593097e01], - [2.42251646e01, 2.35556998e-06, 1.55593097e01], - ], - ), - ], - [ - np.array( - [ - [3.15755349e01, 4.11636356e-06, 4.16625659e00], - [3.61791464e01, 1.39268494e-06, 4.16625659e00], - [1.00488530e01, 2.45715680e-06, 2.78042232e01], - [1.08043784e01, 5.26668495e-06, 2.78042232e01], - [2.42251646e01, 3.31348777e-06, 1.55593097e01], - [1.49022278e01, 1.37428245e-06, 1.55593097e01], - [1.49022278e01, 1.37428245e-06, 1.55593097e01], - [2.42251646e01, 3.31348777e-06, 1.55593097e01], - ], - ), - ], - ] results.sldProfiles = [ [ np.array( @@ -2191,6 +2161,36 @@ def dream_output_results(): ), ], ] + results.layers = [ + [ + np.array( + [ + [3.15755349e01, 3.35278238e-06, 4.16625659e00], + [3.61791464e01, 7.68327921e-07, 4.16625659e00], + [1.00488530e01, 2.06044530e-06, 2.78042232e01], + [1.08043784e01, 3.29384190e-06, 2.78042232e01], + [2.42251646e01, 2.35556998e-06, 1.55593097e01], + [1.49022278e01, 7.42138004e-07, 1.55593097e01], + [1.49022278e01, 7.42138004e-07, 1.55593097e01], + [2.42251646e01, 2.35556998e-06, 1.55593097e01], + ], + ), + ], + [ + np.array( + [ + [3.15755349e01, 4.11636356e-06, 4.16625659e00], + [3.61791464e01, 1.39268494e-06, 4.16625659e00], + [1.00488530e01, 2.45715680e-06, 2.78042232e01], + [1.08043784e01, 5.26668495e-06, 2.78042232e01], + [2.42251646e01, 3.31348777e-06, 1.55593097e01], + [1.49022278e01, 1.37428245e-06, 1.55593097e01], + [1.49022278e01, 1.37428245e-06, 1.55593097e01], + [2.42251646e01, 3.31348777e-06, 1.55593097e01], + ], + ), + ], + ] results.resampledLayers = [[np.array([[0.0, 0.0, 0.0]])], [np.array([[0.0, 0.0, 0.0]])]] results.calculationResults = RATapi.rat_core.Calculation() results.calculationResults.chiValues = (np.array([4.6077885, 7.00028098]),) @@ -2253,7 +2253,7 @@ def dream_bayes(): This optimisation used the parameters: nSamples=1, nChains=1. """ - bayes = RATapi.rat_core.BayesResults() + bayes = RATapi.rat_core.OutputBayesResult() bayes.predictionIntervals = RATapi.rat_core.PredictionIntervals() bayes.predictionIntervals.reflectivity = [ np.array( @@ -3984,7 +3984,6 @@ def dream_bayes(): bayes.dreamOutput.outlierChains = np.array([[0.0, 0.0]]) bayes.dreamOutput.runtime = 2.6e-06 bayes.dreamOutput.iteration = 2.0 - bayes.dreamOutput.modelOutput = 0.0 bayes.dreamOutput.AR = np.array([[1.0, np.nan]]) bayes.dreamOutput.R_stat = np.array( [ @@ -4576,36 +4575,6 @@ def dream_results(): ] ), ], - layerSlds=[ - [ - np.array( - [ - [3.15755349e01, 3.35278238e-06, 4.16625659e00], - [3.61791464e01, 7.68327921e-07, 4.16625659e00], - [1.00488530e01, 2.06044530e-06, 2.78042232e01], - [1.08043784e01, 3.29384190e-06, 2.78042232e01], - [2.42251646e01, 2.35556998e-06, 1.55593097e01], - [1.49022278e01, 7.42138004e-07, 1.55593097e01], - [1.49022278e01, 7.42138004e-07, 1.55593097e01], - [2.42251646e01, 2.35556998e-06, 1.55593097e01], - ], - ), - ], - [ - np.array( - [ - [3.15755349e01, 4.11636356e-06, 4.16625659e00], - [3.61791464e01, 1.39268494e-06, 4.16625659e00], - [1.00488530e01, 2.45715680e-06, 2.78042232e01], - [1.08043784e01, 5.26668495e-06, 2.78042232e01], - [2.42251646e01, 3.31348777e-06, 1.55593097e01], - [1.49022278e01, 1.37428245e-06, 1.55593097e01], - [1.49022278e01, 1.37428245e-06, 1.55593097e01], - [2.42251646e01, 3.31348777e-06, 1.55593097e01], - ], - ), - ], - ], sldProfiles=[ [ np.array( @@ -4676,6 +4645,36 @@ def dream_results(): ), ], ], + layers=[ + [ + np.array( + [ + [3.15755349e01, 3.35278238e-06, 4.16625659e00], + [3.61791464e01, 7.68327921e-07, 4.16625659e00], + [1.00488530e01, 2.06044530e-06, 2.78042232e01], + [1.08043784e01, 3.29384190e-06, 2.78042232e01], + [2.42251646e01, 2.35556998e-06, 1.55593097e01], + [1.49022278e01, 7.42138004e-07, 1.55593097e01], + [1.49022278e01, 7.42138004e-07, 1.55593097e01], + [2.42251646e01, 2.35556998e-06, 1.55593097e01], + ], + ), + ], + [ + np.array( + [ + [3.15755349e01, 4.11636356e-06, 4.16625659e00], + [3.61791464e01, 1.39268494e-06, 4.16625659e00], + [1.00488530e01, 2.45715680e-06, 2.78042232e01], + [1.08043784e01, 5.26668495e-06, 2.78042232e01], + [2.42251646e01, 3.31348777e-06, 1.55593097e01], + [1.49022278e01, 1.37428245e-06, 1.55593097e01], + [1.49022278e01, 1.37428245e-06, 1.55593097e01], + [2.42251646e01, 3.31348777e-06, 1.55593097e01], + ], + ), + ], + ], resampledLayers=[[np.array([[0.0, 0.0, 0.0]])], [np.array([[0.0, 0.0, 0.0]])]], calculationResults=RATapi.outputs.CalculationResults( chiValues=np.array([4.6077885, 7.00028098]), @@ -6463,7 +6462,6 @@ def dream_results(): outlierChains=np.array([[0.0, 0.0]]), runtime=2.6e-06, iteration=2.0, - modelOutput=0.0, AR=np.array([[1.0, np.nan]]), R_stat=np.array( [ @@ -6548,7 +6546,6 @@ def dream_results(): @pytest.fixture def r1_default_project(): """The Project corresponding to the data in R1defaultProject.mat.""" - project = RATapi.Project( name="defaultProject", calculation="normal", diff --git a/tests/test_classlist.py b/tests/test_classlist.py index 2a9aa881..3fa874ab 100644 --- a/tests/test_classlist.py +++ b/tests/test_classlist.py @@ -729,6 +729,7 @@ def test_extend_empty_classlist(extended_list: Sequence, one_name_class_list: Cl assert isinstance(extended_list[-1], class_list._class_handle) +@pytest.mark.parametrize("index", [0, "Alice", InputAttributes(name="Alice")]) @pytest.mark.parametrize( ["new_values", "expected_classlist"], [ @@ -739,10 +740,12 @@ def test_extend_empty_classlist(extended_list: Sequence, one_name_class_list: Cl ), ], ) -def test_set_fields(two_name_class_list: ClassList, new_values: dict[str, Any], expected_classlist: ClassList) -> None: +def test_set_fields( + two_name_class_list: ClassList, index: Union[int, str], new_values: dict[str, Any], expected_classlist: ClassList +) -> None: """We should be able to set field values in an element of a ClassList using keyword arguments.""" class_list = two_name_class_list - class_list.set_fields(0, **new_values) + class_list.set_fields(index, **new_values) assert class_list == expected_classlist @@ -829,7 +832,8 @@ def test__validate_name_field(two_name_class_list: ClassList, input_dict: dict[s ) def test__validate_name_field_not_unique(two_name_class_list: ClassList, input_dict: dict[str, Any]) -> None: """We should raise a ValueError if we input values containing a name_field defined in an object in the ClassList, - accounting for case sensitivity.""" + accounting for case sensitivity. + """ with pytest.raises( ValueError, match=f"Input arguments contain the {two_name_class_list.name_field} " diff --git a/tests/test_controls.py b/tests/test_controls.py index 73b776bd..5c4400a3 100644 --- a/tests/test_controls.py +++ b/tests/test_controls.py @@ -859,7 +859,7 @@ def test_dream_nSamples_error(self, value: int) -> None: @pytest.mark.parametrize("value", [-5, 0]) def test_dream_nChains_error(self, value: int) -> None: """Tests the nChains setter error in Dream class.""" - with pytest.raises(pydantic.ValidationError, match="Input should be greater than 0"): + with pytest.raises(pydantic.ValidationError, match="Input should be greater than 1"): self.dream.nChains = value def test_control_class_dream_str(self, table_str) -> None: diff --git a/tests/test_convert.py b/tests/test_convert.py index e636fc9a..8dc3ecdb 100644 --- a/tests/test_convert.py +++ b/tests/test_convert.py @@ -8,7 +8,7 @@ import pytest import RATapi -from RATapi.utils.convert import project_class_to_r1, r1_to_project_class +from RATapi.utils.convert import project_to_r1, r1_to_project TEST_DIR_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data") @@ -58,9 +58,9 @@ def dspc_bilayer(): ], ) @pytest.mark.parametrize("path_type", [os.path.join, pathlib.Path]) -def test_r1_to_project_class(file, project, path_type, request): +def test_r1_to_project(file, project, path_type, request): """Test that R1 to Project class conversion returns the expected Project.""" - output_project = r1_to_project_class(path_type(TEST_DIR_PATH, file)) + output_project = r1_to_project(path_type(TEST_DIR_PATH, file)) expected_project = request.getfixturevalue(project) # assert statements have to be more careful due to R1 missing features @@ -83,7 +83,7 @@ def test_r1_to_project_class(file, project, path_type, request): def test_r1_involution(project, request, monkeypatch): """Test that converting a Project to an R1 struct and back returns the same project.""" original_project = request.getfixturevalue(project) - r1_struct = project_class_to_r1(original_project, return_struct=True) + r1_struct = project_to_r1(original_project, return_struct=True) # rather than writing the struct to a file and reading the file, just directly # hand the struct over @@ -93,7 +93,7 @@ def mock_load(ignored_filename, **ignored_settings): monkeypatch.setattr("RATapi.utils.convert.loadmat", mock_load, raising=True) - converted_project = r1_to_project_class(project) + converted_project = r1_to_project(project) for class_list in RATapi.project.class_lists: assert getattr(converted_project, class_list) == getattr(original_project, class_list) @@ -105,7 +105,7 @@ def test_invalid_constraints(): match=r"The parameter (.+) 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")) + output_project = r1_to_project(pathlib.Path(TEST_DIR_PATH, "R1DoubleBilayerVolumeModel.mat")) assert output_project.background_parameters[0].min == output_project.background_parameters[0].value @@ -117,7 +117,7 @@ def test_matlab_save(path_type, request): project = request.getfixturevalue("r1_default_project") with tempfile.TemporaryDirectory() as temp: matfile = path_type(temp, "testfile.mat") - project_class_to_r1(project, filename=matfile) - converted_project = r1_to_project_class(matfile) + project_to_r1(project, filename=matfile) + converted_project = r1_to_project(matfile) assert project == converted_project diff --git a/tests/test_data/bare_substrate.json b/tests/test_data/bare_substrate.json new file mode 100644 index 00000000..65e405c2 --- /dev/null +++ b/tests/test_data/bare_substrate.json @@ -0,0 +1 @@ +{"name": "substrate", "calculation": "normal", "model": "standard layers", "geometry": "air/substrate", "absorption": false, "parameters": [{"name": "Substrate Roughness", "min": 1.0, "value": 3.0, "max": 5.0, "fit": true, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "bulk_in": [{"name": "air SLD", "min": 4.42927130586151e-09, "value": 4.42927130586151e-09, "max": 4.42927130586151e-09, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "bulk_out": [{"name": "D2O SLD", "min": 6.360408603667384e-06, "value": 6.360408603667384e-06, "max": 6.360408603667384e-06, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "scalefactors": [{"name": "Scalefactor 1", "min": 0.02, "value": 0.23, "max": 0.25, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "domain_ratios": [], "background_parameters": [{"name": "Background Param 1", "min": 1e-07, "value": 1e-06, "max": 1e-05, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "backgrounds": [{"name": "Background 1", "type": "constant", "source": "Background Param 1", "value_1": "", "value_2": "", "value_3": "", "value_4": "", "value_5": ""}], "resolution_parameters": [{"name": "Resolution Param 1", "min": 0.01, "value": 0.03, "max": 0.05, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "resolutions": [{"name": "Resolution 1", "type": "constant", "source": "Resolution Param 1", "value_1": "", "value_2": "", "value_3": "", "value_4": "", "value_5": ""}], "custom_files": [], "data": [{"name": "Simulation", "data": [], "data_range": [], "simulation_range": [0.005, 0.7]}, {"name": "D2O substrate", "data": [[0.048866, 0.00012343, 1.3213e-06, 0.03], [0.051309, 0.00010063, 1.0803e-06, 0.03], [0.053874, 8.2165e-05, 8.8779e-07, 0.03], [0.056568, 6.4993e-05, 7.2018e-07, 0.03], [0.059396, 5.3958e-05, 6.0015e-07, 0.03], [0.062366, 4.359e-05, 5.0129e-07, 0.03], [0.065485, 3.578e-05, 4.1957e-07, 0.03], [0.068759, 2.913e-05, 3.5171e-07, 0.03], [0.072197, 2.3481e-05, 3.0586e-07, 0.03], [0.075807, 1.8906e-05, 2.6344e-07, 0.03], [0.079597, 1.4642e-05, 2.2314e-07, 0.03], [0.083577, 1.1589e-05, 1.8938e-07, 0.03], [0.087756, 9.5418e-06, 1.622e-07, 0.03], [0.092143, 7.5694e-06, 1.3809e-07, 0.03], [0.096751, 6.3831e-06, 1.2097e-07, 0.03], [0.10159, 5.0708e-06, 1.0333e-07, 0.03], [0.10667, 4.1041e-06, 8.9548e-08, 0.03], [0.112, 3.4253e-06, 7.983e-08, 0.03], [0.1176, 2.8116e-06, 7.1554e-08, 0.03], [0.12348, 2.3767e-06, 6.3738e-08, 0.03], [0.12966, 1.9241e-06, 5.6586e-08, 0.03], [0.13614, 1.5642e-06, 5.2778e-08, 0.03], [0.14294, 1.2922e-06, 4.973e-08, 0.03], [0.15009, 1.1694e-06, 5.1175e-08, 0.03], [0.1576, 9.7837e-07, 5.0755e-08, 0.03], [0.16548, 8.9138e-07, 5.3542e-08, 0.03], [0.17375, 7.942e-07, 5.4857e-08, 0.03], [0.18244, 7.9131e-07, 5.8067e-08, 0.03], [0.19156, 6.5358e-07, 5.7717e-08, 0.03], [0.20114, 6.297e-07, 5.7951e-08, 0.03], [0.21119, 5.013e-07, 5.5262e-08, 0.03], [0.22175, 5.0218e-07, 5.6461e-08, 0.03], [0.23284, 3.9299e-07, 5.0685e-08, 0.03], [0.24448, 3.5324e-07, 5.0194e-08, 0.03], [0.25671, 4.4475e-07, 5.6485e-08, 0.03], [0.26954, 5.1338e-07, 6.2247e-08, 0.03], [0.28302, 3.4918e-07, 4.9745e-08, 0.03], [0.29717, 4.3037e-07, 5.5488e-08, 0.03], [0.31203, 4.0099e-07, 5.3591e-08, 0.03], [0.32763, 3.8397e-07, 5.1303e-08, 0.03], [0.34401, 3.0995e-07, 4.5965e-08, 0.03], [0.36121, 3.9357e-07, 5.0135e-08, 0.03], [0.37927, 3.0997e-07, 4.368e-08, 0.03], [0.39824, 2.9656e-07, 4.2432e-08, 0.03], [0.41815, 2.1909e-07, 3.6117e-08, 0.03], [0.43906, 2.3153e-07, 3.6307e-08, 0.03], [0.46101, 3.3428e-07, 4.3874e-08, 0.03], [0.48406, 2.3441e-07, 3.7488e-08, 0.03], [0.50826, 1.5496e-07, 3.0585e-08, 0.03], [0.53368, 2.4708e-07, 3.9376e-08, 0.03], [0.56036, 2.2157e-07, 3.8258e-08, 0.03], [0.58838, 2.2798e-07, 4.6976e-08, 0.03], [0.61169, 6.0272e-07, 2.3239e-07, 0.03]], "data_range": [0.048866, 0.61169], "simulation_range": [0.048866, 0.61169]}], "layers": [], "domain_contrasts": [], "contrasts": []} \ No newline at end of file diff --git a/tests/test_data/bare_substrate.ort b/tests/test_data/bare_substrate.ort new file mode 100644 index 00000000..7e4ffb52 --- /dev/null +++ b/tests/test_data/bare_substrate.ort @@ -0,0 +1,83 @@ +# # ORSO reflectivity data file | 1.1 standard | YAML encoding | https://www.reflectometry.org/ +# # handwritten test file header created to test RAT orsopy integration! +# data_source: +# owner: +# name: null +# affiliation: null +# measurement: +# instrument_settings: null +# data_files: null +# experiment: +# title: Bare D2O substrate +# probe: neutron +# instrument: None +# start_date: 1970-01-01T00:00:00 +# sample: +# name: D2O substrate +# model: +# stack: air | D2O +# reduction: +# software: null +# timestamp: null +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: normal momentum transfer} +# - {name: R, unit: '', physical_quantity: specular reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R () sR sQz +4.8866e-02 1.2343e-04 1.3213e-06 0.03 +5.1309e-02 1.0063e-04 1.0803e-06 0.03 +5.3874e-02 8.2165e-05 8.8779e-07 0.03 +5.6568e-02 6.4993e-05 7.2018e-07 0.03 +5.9396e-02 5.3958e-05 6.0015e-07 0.03 +6.2366e-02 4.3590e-05 5.0129e-07 0.03 +6.5485e-02 3.5780e-05 4.1957e-07 0.03 +6.8759e-02 2.9130e-05 3.5171e-07 0.03 +7.2197e-02 2.3481e-05 3.0586e-07 0.03 +7.5807e-02 1.8906e-05 2.6344e-07 0.03 +7.9597e-02 1.4642e-05 2.2314e-07 0.03 +8.3577e-02 1.1589e-05 1.8938e-07 0.03 +8.7756e-02 9.5418e-06 1.6220e-07 0.03 +9.2143e-02 7.5694e-06 1.3809e-07 0.03 +9.6751e-02 6.3831e-06 1.2097e-07 0.03 +1.0159e-01 5.0708e-06 1.0333e-07 0.03 +1.0667e-01 4.1041e-06 8.9548e-08 0.03 +1.1200e-01 3.4253e-06 7.9830e-08 0.03 +1.1760e-01 2.8116e-06 7.1554e-08 0.03 +1.2348e-01 2.3767e-06 6.3738e-08 0.03 +1.2966e-01 1.9241e-06 5.6586e-08 0.03 +1.3614e-01 1.5642e-06 5.2778e-08 0.03 +1.4294e-01 1.2922e-06 4.9730e-08 0.03 +1.5009e-01 1.1694e-06 5.1175e-08 0.03 +1.5760e-01 9.7837e-07 5.0755e-08 0.03 +1.6548e-01 8.9138e-07 5.3542e-08 0.03 +1.7375e-01 7.9420e-07 5.4857e-08 0.03 +1.8244e-01 7.9131e-07 5.8067e-08 0.03 +1.9156e-01 6.5358e-07 5.7717e-08 0.03 +2.0114e-01 6.2970e-07 5.7951e-08 0.03 +2.1119e-01 5.0130e-07 5.5262e-08 0.03 +2.2175e-01 5.0218e-07 5.6461e-08 0.03 +2.3284e-01 3.9299e-07 5.0685e-08 0.03 +2.4448e-01 3.5324e-07 5.0194e-08 0.03 +2.5671e-01 4.4475e-07 5.6485e-08 0.03 +2.6954e-01 5.1338e-07 6.2247e-08 0.03 +2.8302e-01 3.4918e-07 4.9745e-08 0.03 +2.9717e-01 4.3037e-07 5.5488e-08 0.03 +3.1203e-01 4.0099e-07 5.3591e-08 0.03 +3.2763e-01 3.8397e-07 5.1303e-08 0.03 +3.4401e-01 3.0995e-07 4.5965e-08 0.03 +3.6121e-01 3.9357e-07 5.0135e-08 0.03 +3.7927e-01 3.0997e-07 4.3680e-08 0.03 +3.9824e-01 2.9656e-07 4.2432e-08 0.03 +4.1815e-01 2.1909e-07 3.6117e-08 0.03 +4.3906e-01 2.3153e-07 3.6307e-08 0.03 +4.6101e-01 3.3428e-07 4.3874e-08 0.03 +4.8406e-01 2.3441e-07 3.7488e-08 0.03 +5.0826e-01 1.5496e-07 3.0585e-08 0.03 +5.3368e-01 2.4708e-07 3.9376e-08 0.03 +5.6036e-01 2.2157e-07 3.8258e-08 0.03 +5.8838e-01 2.2798e-07 4.6976e-08 0.03 +6.1169e-01 6.0272e-07 2.3239e-07 0.03 + + diff --git a/tests/test_data/prist.json b/tests/test_data/prist.json new file mode 100644 index 00000000..3b36893e --- /dev/null +++ b/tests/test_data/prist.json @@ -0,0 +1 @@ +{"name": "Example Project", "calculation": "normal", "model": "standard layers", "geometry": "air/substrate", "absorption": false, "parameters": [{"name": "Substrate Roughness", "min": 1.0, "value": 3.0, "max": 5.0, "fit": true, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Al2O3 Thickness", "min": 20.0, "value": 20.0, "max": 20.0, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Al2O3 Roughness", "min": 3.0, "value": 3.0, "max": 3.0, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Al2O3 SLD", "min": 5.715275837467511e-06, "value": 5.715275837467511e-06, "max": 5.715275837467511e-06, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Ti0.27Co0.73 Thickness", "min": 93.0, "value": 93.0, "max": 93.0, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Ti0.27Co0.73 Roughness", "min": 3.0, "value": 3.0, "max": 3.0, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}, {"name": "Ti0.27Co0.73 SLD", "min": 6.720584953375607e-07, "value": 6.720584953375607e-07, "max": 6.720584953375607e-07, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "bulk_in": [{"name": "vacuum SLD", "min": 0.0, "value": 0.0, "max": 0.0, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "bulk_out": [{"name": "Si SLD", "min": 2.07370547828562e-06, "value": 2.07370547828562e-06, "max": 2.07370547828562e-06, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "scalefactors": [{"name": "Scalefactor", "min": 0.0, "value": 1.0, "max": 1.5, "fit": true, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "domain_ratios": [], "background_parameters": [{"name": "Background Parameter", "min": 0.0, "value": 0.025, "max": 1.0, "fit": true, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "backgrounds": [{"name": "Background", "type": "constant", "source": "Background Parameter", "value_1": "", "value_2": "", "value_3": "", "value_4": "", "value_5": ""}], "resolution_parameters": [{"name": "Resolution Param 1", "min": 0.01, "value": 0.03, "max": 0.05, "fit": false, "prior_type": "uniform", "mu": 0.0, "sigma": Infinity, "show_priors": false}], "resolutions": [{"name": "Data Resolution", "type": "data", "source": "", "value_1": "", "value_2": "", "value_3": "", "value_4": "", "value_5": ""}], "custom_files": [], "data": [{"name": "Simulation", "data": [], "data_range": [], "simulation_range": [0.005, 0.7]}, {"name": "prist4", "data": [[0.0021999999999999997, 0.004724119389721505, 0.004724119389721505, 0.00019999999999999987], [0.0026, 0.0, 0.0, 0.0002000000000000001], [0.003, 0.0049441079014990305, 0.0028632759811688313, 0.0002000000000000001], [0.0034000000000000002, 0.00539571466421478, 0.0027033018099334193, 0.00019999999999999987], [0.0038, 0.011948517768746966, 0.0037903477990210636, 0.0002000000000000001], [0.004200000000000001, 0.016302332669940297, 0.003972317564845323, 0.0002000000000000001], [0.0046, 0.02265216487165058, 0.0045763672802481776, 0.0002000000000000001], [0.005, 0.023020814468949415, 0.00425372711337262, 0.00019999999999999966], [0.0054, 0.027305557750665344, 0.004428700971127359, 0.0002000000000000001], [0.0058, 0.019457945835850306, 0.0037817426451169783, 0.0002000000000000001], [0.006200000000000001, 0.027645506109865266, 0.004267993991409073, 0.0002000000000000001], [0.0066, 0.024223034347324195, 0.0037655052701117517, 0.0002000000000000001], [0.007, 0.028408308819418325, 0.0037377941755602716, 0.00019999999999999966], [0.0074, 0.057460495308053604, 0.0052154049057105455, 0.0002000000000000001], [0.0078, 0.1489883351950982, 0.00754789840356179, 0.0002000000000000001], [0.008199999999999999, 0.3520480929443021, 0.011146891694516669, 0.00019999999999999966], [0.0086, 0.5317657760789548, 0.01351725179531962, 0.00019999999999999966], [0.009, 0.7914500646501588, 0.016276277463585134, 0.00020000000000000052], [0.009399999999999999, 0.9680331047846104, 0.01782748799306378, 0.00019999999999999966], [0.0098, 1.0324643310179094, 0.018104900945963596, 0.00020000000000000052], [0.010204081632653062, 0.6287661683321234, 0.013873373556929145, 0.00020408163265306107], [0.010620574760516453, 0.3506955915110479, 0.010136613757789808, 0.00021241149521033023], [0.011054067607884473, 0.23477751708069897, 0.008109962639928676, 0.000221081352157689], [0.011505254040859348, 0.18964302712880826, 0.007180573014831645, 0.00023010508081718756], [0.011974856246608712, 0.14285208497806512, 0.006016224926747713, 0.00023949712493217482], [0.012463625889327434, 0.12188457497836602, 0.005435279585343197, 0.00024927251778654805], [0.012972345313381616, 0.10530945504618572, 0.005027517945960155, 0.00025944690626763297], [0.013501828795560456, 0.08038795087657985, 0.004197556276825506, 0.0002700365759112089], [0.014052923848440476, 0.07313672279549625, 0.003958200398418697, 0.00028105847696880976], [0.01462651257694825, 0.06129841978151465, 0.0034783001152817426, 0.0002925302515389659], [0.01522351309029308, 0.06240905936586468, 0.003479800062460076, 0.000304470261805862], [0.015844880971529533, 0.05651280096554304, 0.0032223775371921573, 0.00031689761943059103], [0.016491610807102167, 0.05044073793552524, 0.00290210672478378, 0.0003298322161420439], [0.017164737778820625, 0.0502775250441711, 0.002825286449304508, 0.00034329475557641313], [0.0178653393208133, 0.05168970509722787, 0.002811814341605395, 0.0003573067864162654], [0.018594536844111803, 0.0530076187705834, 0.0027685413804410695, 0.00037189073688223724], [0.019353497531626573, 0.0490707566771454, 0.0025677593751218504, 0.00038706995063253133], [0.020143436206386842, 0.04687883170966378, 0.0024119133732668076, 0.0004028687241277376], [0.020965617276035284, 0.04919955377214455, 0.0024276503561561644, 0.0004193123455207056], [0.021821356756689787, 0.05204111270310913, 0.00245561515567379, 0.00043642713513379616], [0.022712024379411822, 0.04925178172610669, 0.0023335505076022252, 0.00045424048758823873], [0.02363904578265312, 0.04317260894774357, 0.00211385396560445, 0.0004727809156530611], [0.024603904794189984, 0.0383043276355005, 0.0019158755178575817, 0.0004920780958838009], [0.02560814580619774, 0.04444220010645997, 0.002018641932709427, 0.0005121629161239545], [0.026653376247267036, 0.039587001147536376, 0.0018488058581743547, 0.0005330675249453423], [0.027741269155318757, 0.036677858215866437, 0.0017473224966445143, 0.0005548253831063766], [0.028873565855535847, 0.037663512267048754, 0.0017682821318945074, 0.0005774713171107174], [0.030052078747598535, 0.036755569252809664, 0.0017350440273063527, 0.0006010415749519698], [0.03127869420668419, 0.03854639441776167, 0.0017965558626918197, 0.0006255738841336837], [0.032555375602875386, 0.032866785139061716, 0.0016217721152892931, 0.000651107512057509], [0.03388416644380907, 0.0315131968037535, 0.0016061232808323794, 0.0006776833288761816], [0.0352671936455972, 0.030280345918169513, 0.0015885947028228238, 0.0007053438729119448], [0.03670667093725423, 0.025036982048446406, 0.0014176844815086535, 0.0007341334187450851], [0.038204902404080934, 0.02083180456328542, 0.001280336546816776, 0.0007640980480816198], [0.03976428617567608, 0.019376976329704945, 0.0012712203272554497, 0.0007952857235135231], [0.04138731826447918, 0.016729675515039004, 0.0011715497338181137, 0.000827746365289584], [0.04307659656098854, 0.013996303090353096, 0.0010654383375290077, 0.0008615319312197701], [0.0448348249920493, 0.010621480384334052, 0.0009246807782300395, 0.0008966964998409904], [0.04666481784886764, 0.007820332463525126, 0.0008093102454836752, 0.000933296356977354], [0.04856950429167857, 0.0060428022894521575, 0.0007183835413384203, 0.0009713900858335685], [0.050551933038277694, 0.004095892971484393, 0.0005841670446995313, 0.0010110386607655557], [0.05261527724392169, 0.005111525161941947, 0.0006658003030884043, 0.0010523055448784409], [0.05476283958040829, 0.0020207116612746484, 0.0004143866059231477, 0.0010952567916081637], [0.05699805752246577, 0.001711568931414378, 0.00038529038925611664, 0.0011399611504493146], [0.05932450884991336, 0.0008496305883131249, 0.00027095330976641694, 0.001186490176998272], [0.06174591737439962, 0.0008142116035177235, 0.00027251395576638693, 0.0012349183474879948], [0.06426615889988532, 8.92042382623096e-05, 8.920423826322552e-05, 0.0012853231779977048], [0.06688926742641126, 9.339736346561163e-05, 9.339736346489978e-05, 0.0013377853485282282], [0.06961944160708111, 0.00040599169078880156, 0.0002030320824188672, 0.0013923888321416208], [0.07246105146859462, 0.0002183257352075823, 0.00015439324988622013, 0.0014492210293718943], [0.07541864540608828, 0.00012684339163879, 0.00012684339163797725, 0.001508372908121773], [0.07849695746347965, 0.0, 0.0, 0.001569939149269589], [0.08170091491096862, 0.0, 0.0, 0.0016340182982193738], [0.08503564613182449, 0.0004165748322677754, 0.00024090333523406435, 0.001700712922636495], [0.08850648883108263, 0.0, 0.0, 0.0017701297766216512], [0.09211899857929008, 0.0, 0.0, 0.001842379971585803], [0.0958789577049754, 0.0, 0.0, 0.0019175791540995135], [0.09979238455007644, 0.0, 0.0, 0.001995847691001529], [0.10386554310314079, 0.0, 0.0, 0.0020773108620628228]], "data_range": [0.0021999999999999997, 0.10386554310314079], "simulation_range": [0.0021999999999999997, 0.10386554310314079]}], "layers": [{"name": "Al2O3", "thickness": "Al2O3 Thickness", "SLD": "Al2O3 SLD", "roughness": "Al2O3 Roughness", "hydration": "", "hydrate_with": "bulk out"}, {"name": "Ti0.27Co0.73", "thickness": "Ti0.27Co0.73 Thickness", "SLD": "Ti0.27Co0.73 SLD", "roughness": "Ti0.27Co0.73 Roughness", "hydration": "", "hydrate_with": "bulk out"}], "domain_contrasts": [], "contrasts": [{"name": "prist4", "data": "prist4", "background": "Background", "background_action": "add", "bulk_in": "vacuum SLD", "bulk_out": "Si SLD", "scalefactor": "Scalefactor", "resolution": "Data Resolution", "resample": false, "model": ["Al2O3", "Ti0.27Co0.73"]}]} \ No newline at end of file diff --git a/tests/test_data/prist5_10K_m_025.Rqz.ort b/tests/test_data/prist5_10K_m_025.Rqz.ort new file mode 100644 index 00000000..5b60b8fc --- /dev/null +++ b/tests/test_data/prist5_10K_m_025.Rqz.ort @@ -0,0 +1,124 @@ +# # ORSO reflectivity data file | 1.0 standard | YAML encoding | https://www.reflectometry.org/ +# data_source: +# owner: +# name: Artur Glavic +# affiliation: null +# contact: b'' +# experiment: +# title: Structural evolution of the CO2/Water interface +# instrument: Amor +# start_date: 2023-11-29T10:12:45 +# probe: neutron +# facility: SINQ@PSI +# proposalID: '20230368' +# sample: +# name: prist4 +# sample_parameters: +# tempMean: {magnitude: -9999.0} +# model: +# stack: vacuum | Al2O3 2 | Ti0.27Co0.73 9.3 | Si +# measurement: +# instrument_settings: +# incident_angle: {min: 0.0950000000000002, max: 1.495, unit: deg} +# wavelength: {min: 3.0, max: 12.0, unit: angstrom} +# mu: {magnitude: 0.25, unit: deg, comment: sample angle to referece direction} +# nu: {magnitude: 1.0450000000000002, unit: deg, comment: detector angle to referece +# direction} +# data_files: +# - file: raw/amor2023n000848.hdf +# timestamp: 2023-11-29T10:12:45 +# amor_monitor: 1792.443302905 +# scheme: angle- and energy-dispersive +# references: [] +# amor_monitor: 1792.443302905 +# reduction: +# software: {name: eos, version: '2.0'} +# timestamp: 2023-11-29T10:57:48.480339 +# computer: amor.psi.ch +# call: eos.py -a 0.04 -F 0.0093,0.0101 -n 848 prist5_10K_m_025 +# data_set: 0 +# columns: +# - {name: Qz, unit: 1/angstrom, physical_quantity: normal momentum transfer} +# - {name: R, unit: '', physical_quantity: specular reflectivity} +# - {error_of: R, error_type: uncertainty, value_is: sigma} +# - {error_of: Qz, error_type: resolution, value_is: sigma} +# # Qz (1/angstrom) R () sR sQz +2.1999999999999997e-03 4.7241193897215048e-03 4.7241193897215048e-03 1.9999999999999987e-04 +2.5999999999999999e-03 0.0000000000000000e+00 0.0000000000000000e+00 2.0000000000000009e-04 +3.0000000000000001e-03 4.9441079014990305e-03 2.8632759811688313e-03 2.0000000000000009e-04 +3.4000000000000002e-03 5.3957146642147798e-03 2.7033018099334193e-03 1.9999999999999987e-04 +3.8000000000000000e-03 1.1948517768746966e-02 3.7903477990210636e-03 2.0000000000000009e-04 +4.2000000000000006e-03 1.6302332669940297e-02 3.9723175648453228e-03 2.0000000000000009e-04 +4.5999999999999999e-03 2.2652164871650581e-02 4.5763672802481776e-03 2.0000000000000009e-04 +5.0000000000000001e-03 2.3020814468949415e-02 4.2537271133726200e-03 1.9999999999999966e-04 +5.4000000000000003e-03 2.7305557750665344e-02 4.4287009711273589e-03 2.0000000000000009e-04 +5.7999999999999996e-03 1.9457945835850306e-02 3.7817426451169783e-03 2.0000000000000009e-04 +6.2000000000000006e-03 2.7645506109865266e-02 4.2679939914090732e-03 2.0000000000000009e-04 +6.6000000000000000e-03 2.4223034347324195e-02 3.7655052701117517e-03 2.0000000000000009e-04 +7.0000000000000001e-03 2.8408308819418325e-02 3.7377941755602716e-03 1.9999999999999966e-04 +7.4000000000000003e-03 5.7460495308053604e-02 5.2154049057105455e-03 2.0000000000000009e-04 +7.7999999999999996e-03 1.4898833519509819e-01 7.5478984035617898e-03 2.0000000000000009e-04 +8.1999999999999990e-03 3.5204809294430212e-01 1.1146891694516669e-02 1.9999999999999966e-04 +8.6000000000000000e-03 5.3176577607895481e-01 1.3517251795319620e-02 1.9999999999999966e-04 +8.9999999999999993e-03 7.9145006465015877e-01 1.6276277463585134e-02 2.0000000000000052e-04 +9.3999999999999986e-03 9.6803310478461035e-01 1.7827487993063780e-02 1.9999999999999966e-04 +9.7999999999999997e-03 1.0324643310179094e+00 1.8104900945963596e-02 2.0000000000000052e-04 +1.0204081632653062e-02 6.2876616833212340e-01 1.3873373556929145e-02 2.0408163265306107e-04 +1.0620574760516453e-02 3.5069559151104790e-01 1.0136613757789808e-02 2.1241149521033023e-04 +1.1054067607884473e-02 2.3477751708069897e-01 8.1099626399286761e-03 2.2108135215768900e-04 +1.1505254040859348e-02 1.8964302712880826e-01 7.1805730148316446e-03 2.3010508081718756e-04 +1.1974856246608712e-02 1.4285208497806512e-01 6.0162249267477130e-03 2.3949712493217482e-04 +1.2463625889327434e-02 1.2188457497836602e-01 5.4352795853431970e-03 2.4927251778654805e-04 +1.2972345313381616e-02 1.0530945504618572e-01 5.0275179459601553e-03 2.5944690626763297e-04 +1.3501828795560456e-02 8.0387950876579850e-02 4.1975562768255060e-03 2.7003657591120889e-04 +1.4052923848440476e-02 7.3136722795496253e-02 3.9582003984186967e-03 2.8105847696880976e-04 +1.4626512576948251e-02 6.1298419781514651e-02 3.4783001152817426e-03 2.9253025153896592e-04 +1.5223513090293080e-02 6.2409059365864682e-02 3.4798000624600761e-03 3.0447026180586197e-04 +1.5844880971529533e-02 5.6512800965543043e-02 3.2223775371921573e-03 3.1689761943059103e-04 +1.6491610807102167e-02 5.0440737935525240e-02 2.9021067247837801e-03 3.2983221614204389e-04 +1.7164737778820625e-02 5.0277525044171101e-02 2.8252864493045079e-03 3.4329475557641313e-04 +1.7865339320813300e-02 5.1689705097227867e-02 2.8118143416053952e-03 3.5730678641626538e-04 +1.8594536844111803e-02 5.3007618770583402e-02 2.7685413804410695e-03 3.7189073688223724e-04 +1.9353497531626573e-02 4.9070756677145402e-02 2.5677593751218504e-03 3.8706995063253133e-04 +2.0143436206386842e-02 4.6878831709663782e-02 2.4119133732668076e-03 4.0286872412773761e-04 +2.0965617276035284e-02 4.9199553772144550e-02 2.4276503561561644e-03 4.1931234552070561e-04 +2.1821356756689787e-02 5.2041112703109127e-02 2.4556151556737898e-03 4.3642713513379616e-04 +2.2712024379411822e-02 4.9251781726106690e-02 2.3335505076022252e-03 4.5424048758823873e-04 +2.3639045782653120e-02 4.3172608947743572e-02 2.1138539656044499e-03 4.7278091565306109e-04 +2.4603904794189984e-02 3.8304327635500499e-02 1.9158755178575817e-03 4.9207809588380086e-04 +2.5608145806197739e-02 4.4442200106459967e-02 2.0186419327094269e-03 5.1216291612395451e-04 +2.6653376247267036e-02 3.9587001147536376e-02 1.8488058581743547e-03 5.3306752494534232e-04 +2.7741269155318757e-02 3.6677858215866437e-02 1.7473224966445143e-03 5.5482538310637659e-04 +2.8873565855535847e-02 3.7663512267048754e-02 1.7682821318945074e-03 5.7747131711071743e-04 +3.0052078747598535e-02 3.6755569252809664e-02 1.7350440273063527e-03 6.0104157495196979e-04 +3.1278694206684193e-02 3.8546394417761670e-02 1.7965558626918197e-03 6.2557388413368373e-04 +3.2555375602875386e-02 3.2866785139061716e-02 1.6217721152892931e-03 6.5110751205750897e-04 +3.3884166443809073e-02 3.1513196803753502e-02 1.6061232808323794e-03 6.7768332887618160e-04 +3.5267193645597203e-02 3.0280345918169513e-02 1.5885947028228238e-03 7.0534387291194475e-04 +3.6706670937254229e-02 2.5036982048446406e-02 1.4176844815086535e-03 7.3413341874508514e-04 +3.8204902404080934e-02 2.0831804563285421e-02 1.2803365468167761e-03 7.6409804808161980e-04 +3.9764286175676081e-02 1.9376976329704945e-02 1.2712203272554497e-03 7.9528572351352314e-04 +4.1387318264479181e-02 1.6729675515039004e-02 1.1715497338181137e-03 8.2774636528958404e-04 +4.3076596560988542e-02 1.3996303090353096e-02 1.0654383375290077e-03 8.6153193121977015e-04 +4.4834824992049299e-02 1.0621480384334052e-02 9.2468077823003954e-04 8.9669649984099042e-04 +4.6664817848867640e-02 7.8203324635251256e-03 8.0931024548367521e-04 9.3329635697735405e-04 +4.8569504291678570e-02 6.0428022894521575e-03 7.1838354133842033e-04 9.7139008583356848e-04 +5.0551933038277694e-02 4.0958929714843932e-03 5.8416704469953131e-04 1.0110386607655557e-03 +5.2615277243921690e-02 5.1115251619419473e-03 6.6580030308840434e-04 1.0523055448784409e-03 +5.4762839580408292e-02 2.0207116612746484e-03 4.1438660592314769e-04 1.0952567916081637e-03 +5.6998057522465770e-02 1.7115689314143780e-03 3.8529038925611664e-04 1.1399611504493146e-03 +5.9324508849913360e-02 8.4963058831312487e-04 2.7095330976641694e-04 1.1864901769982721e-03 +6.1745917374399620e-02 8.1421160351772355e-04 2.7251395576638693e-04 1.2349183474879948e-03 +6.4266158899885323e-02 8.9204238262309603e-05 8.9204238263225523e-05 1.2853231779977048e-03 +6.6889267426411256e-02 9.3397363465611626e-05 9.3397363464899779e-05 1.3377853485282282e-03 +6.9619441607081112e-02 4.0599169078880156e-04 2.0303208241886721e-04 1.3923888321416208e-03 +7.2461051468594620e-02 2.1832573520758229e-04 1.5439324988622013e-04 1.4492210293718943e-03 +7.5418645406088280e-02 1.2684339163878999e-04 1.2684339163797725e-04 1.5083729081217731e-03 +7.8496957463479650e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.5699391492695891e-03 +8.1700914910968619e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.6340182982193738e-03 +8.5035646131824488e-02 4.1657483226777538e-04 2.4090333523406435e-04 1.7007129226364950e-03 +8.8506488831082628e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.7701297766216512e-03 +9.2118998579290082e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.8423799715858030e-03 +9.5878957704975398e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.9175791540995135e-03 +9.9792384550076441e-02 0.0000000000000000e+00 0.0000000000000000e+00 1.9958476910015288e-03 +1.0386554310314079e-01 0.0000000000000000e+00 0.0000000000000000e+00 2.0773108620628228e-03 diff --git a/tests/test_inputs.py b/tests/test_inputs.py index 32fc4835..e7efd7b4 100644 --- a/tests/test_inputs.py +++ b/tests/test_inputs.py @@ -9,7 +9,7 @@ import RATapi import RATapi.wrappers from RATapi.inputs import FileHandles, check_indices, make_controls, make_input, make_problem -from RATapi.rat_core import Checks, Control, Limits, NameStore, Priors, ProblemDefinition +from RATapi.rat_core import Checks, Control, NameStore, ProblemDefinition from RATapi.utils.enums import ( BackgroundActions, BoundHandling, @@ -118,7 +118,6 @@ def test_names(): names.params = ["Substrate Roughness", "Test Thickness", "Test SLD", "Test Roughness"] names.backgroundParams = ["Background Param 1"] names.scalefactors = ["Scalefactor 1"] - names.qzshifts = [] names.bulkIns = ["SLD Air"] names.bulkOuts = ["SLD D2O"] names.resolutionParams = ["Resolution Param 1"] @@ -135,7 +134,6 @@ def test_checks(): checks.params = np.array([1, 0, 0, 0]) checks.backgroundParams = np.array([0]) checks.scalefactors = np.array([0]) - checks.qzshifts = np.array([]) checks.bulkIns = np.array([0]) checks.bulkOuts = np.array([0]) checks.resolutionParams = np.array([0]) @@ -155,14 +153,12 @@ def standard_layers_problem(test_names, test_checks): problem.params = [3.0, 0.0, 0.0, 0.0] problem.bulkIns = [0.0] problem.bulkOuts = [6.35e-06] - problem.qzshifts = [] problem.scalefactors = [0.23] problem.domainRatios = [] problem.backgroundParams = [1e-06] problem.resolutionParams = [0.03] problem.contrastBulkIns = [1] problem.contrastBulkOuts = [1] - problem.contrastQzshifts = [] problem.contrastScalefactors = [1] problem.contrastBackgroundParams = [[1]] problem.contrastBackgroundActions = [BackgroundActions.Add] @@ -176,26 +172,36 @@ def standard_layers_problem(test_names, test_checks): problem.data = [np.array([[1.0, 1.0, 1.0, 0.0, 0.0, 0.0]])] problem.dataLimits = [[1.0, 1.0]] problem.simulationLimits = [[1.0, 1.0]] - problem.oilChiDataPresent = [0] problem.numberOfContrasts = 1 problem.numberOfLayers = 1 - problem.repeatLayers = [[0, 1]] + problem.repeatLayers = [1] problem.layersDetails = [[2, 3, 4, float("NaN"), 2]] problem.contrastLayers = [[1]] problem.numberOfDomainContrasts = 0 problem.domainContrastLayers = [] problem.fitParams = [3.0] - problem.otherParams = [0.0, 0.0, 0.0, 1e-06, 0.23, 0.0, 6.35e-06, 0.03] problem.fitLimits = [[1.0, 5.0]] - problem.otherLimits = [ - [0.0, 0.0], - [0.0, 0.0], - [0.0, 0.0], - [1e-07, 1e-05], - [0.02, 0.25], - [0.0, 0.0], - [6.2e-06, 6.35e-06], - [0.01, 0.05], + problem.priorNames = [ + "Substrate Roughness", + "Test Thickness", + "Test SLD", + "Test Roughness", + "Background Param 1", + "Scalefactor 1", + "SLD Air", + "SLD D2O", + "Resolution Param 1", + ] + problem.priorValues = [ + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], ] problem.customFiles = FileHandles([]) problem.names = test_names @@ -215,14 +221,12 @@ def domains_problem(test_names, test_checks): problem.params = [3.0, 0.0, 0.0, 0.0] problem.bulkIns = [0.0] problem.bulkOuts = [6.35e-06] - problem.qzshifts = [] problem.scalefactors = [0.23] problem.domainRatios = [0.5] problem.backgroundParams = [1e-06] problem.resolutionParams = [0.03] problem.contrastBulkIns = [1] problem.contrastBulkOuts = [1] - problem.contrastQzshifts = [] problem.contrastScalefactors = [1] problem.contrastBackgroundParams = [[1]] problem.contrastBackgroundActions = [BackgroundActions.Add] @@ -236,27 +240,38 @@ def domains_problem(test_names, test_checks): problem.data = [np.array([[1.0, 1.0, 1.0, 0.0, 0.0, 0.0]])] problem.dataLimits = [[1.0, 1.0]] problem.simulationLimits = [[1.0, 1.0]] - problem.oilChiDataPresent = [0] problem.numberOfContrasts = 1 problem.numberOfLayers = 1 - problem.repeatLayers = [[0, 1]] + problem.repeatLayers = [1] problem.layersDetails = [[2, 3, 4, float("NaN"), 2]] problem.contrastLayers = [[2, 1]] problem.numberOfDomainContrasts = 2 problem.domainContrastLayers = [[1], [1]] problem.fitParams = [3.0] - problem.otherParams = [0.0, 0.0, 0.0, 1e-06, 0.23, 0.0, 6.35e-06, 0.03, 0.5] problem.fitLimits = [[1.0, 5.0]] - problem.otherLimits = [ - [0.0, 0.0], - [0.0, 0.0], - [0.0, 0.0], - [1e-07, 1e-05], - [0.02, 0.25], - [0.0, 0.0], - [6.2e-06, 6.35e-06], - [0.01, 0.05], - [0.4, 0.6], + problem.priorNames = [ + "Substrate Roughness", + "Test Thickness", + "Test SLD", + "Test Roughness", + "Background Param 1", + "Scalefactor 1", + "SLD Air", + "SLD D2O", + "Resolution Param 1", + "Domain Ratio 1", + ] + problem.priorValues = [ + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], + [1, 0.0, np.inf], ] problem.customFiles = FileHandles([]) problem.names = test_names @@ -277,14 +292,12 @@ def custom_xy_problem(test_names, test_checks): problem.params = [3.0, 0.0, 0.0, 0.0] problem.bulkIns = [0.0] problem.bulkOuts = [6.35e-06] - problem.qzshifts = [] problem.scalefactors = [0.23] problem.domainRatios = [] problem.backgroundParams = [1e-06] problem.resolutionParams = [0.03] problem.contrastBulkIns = [1] problem.contrastBulkOuts = [1] - problem.contrastQzshifts = [] problem.contrastScalefactors = [1] problem.contrastBackgroundParams = [[1]] problem.contrastBackgroundActions = [BackgroundActions.Add] @@ -298,8 +311,7 @@ def custom_xy_problem(test_names, test_checks): problem.data = [np.empty([0, 6])] problem.dataLimits = [[0.0, 0.0]] problem.simulationLimits = [[0.005, 0.7]] - problem.oilChiDataPresent = [0] - problem.repeatLayers = [[0, 1]] + problem.repeatLayers = [1] problem.layersDetails = [] problem.contrastLayers = [[]] problem.numberOfContrasts = 1 @@ -307,120 +319,8 @@ def custom_xy_problem(test_names, test_checks): problem.numberOfDomainContrasts = 0 problem.domainContrastLayers = [] problem.fitParams = [3.0] - problem.otherParams = [0.0, 0.0, 0.0, 1e-06, 0.23, 0.0, 6.35e-06, 0.03] problem.fitLimits = [[1.0, 5.0]] - problem.otherLimits = [ - [0.0, 0.0], - [0.0, 0.0], - [0.0, 0.0], - [1e-07, 1e-05], - [0.02, 0.25], - [0.0, 0.0], - [6.2e-06, 6.35e-06], - [0.01, 0.05], - ] - problem.customFiles = FileHandles( - [RATapi.models.CustomFile(name="Test Custom File", filename="cpp_test.dll", language="cpp")] - ) - problem.names = test_names - problem.checks = test_checks - - return problem - - -@pytest.fixture -def normal_limits(): - """The expected limits object from "standard_layers_project" and "custom_xy_project".""" - limits = Limits() - limits.params = [[1.0, 5.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] - limits.backgroundParams = [[1e-7, 1e-5]] - limits.scalefactors = [[0.02, 0.25]] - limits.qzshifts = [] - limits.bulkIns = [[0.0, 0.0]] - limits.bulkOuts = [[6.2e-6, 6.35e-6]] - limits.resolutionParams = [[0.01, 0.05]] - limits.domainRatios = [] - - return limits - - -@pytest.fixture -def domains_limits(): - """The expected limits object from "domains_project".""" - limits = Limits() - limits.params = [[1.0, 5.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]] - limits.backgroundParams = [[1e-7, 1e-5]] - limits.scalefactors = [[0.02, 0.25]] - limits.qzshifts = [] - limits.bulkIns = [[0.0, 0.0]] - limits.bulkOuts = [[6.2e-6, 6.35e-6]] - limits.resolutionParams = [[0.01, 0.05]] - limits.domainRatios = [[0.4, 0.6]] - - return limits - - -@pytest.fixture -def normal_priors(): - """The expected priors object from "standard_layers_project" and "custom_xy_project".""" - priors = Priors() - priors.params = [ - ["Substrate Roughness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test Thickness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test SLD", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test Roughness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ] - priors.backgroundParams = [["Background Param 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.scalefactors = [["Scalefactor 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.qzshifts = [] - priors.bulkIns = [["SLD Air", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.bulkOuts = [["SLD D2O", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.resolutionParams = [["Resolution Param 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.domainRatios = [] - priors.priorNames = [ - "Substrate Roughness", - "Test Thickness", - "Test SLD", - "Test Roughness", - "Background Param 1", - "Scalefactor 1", - "SLD Air", - "SLD D2O", - "Resolution Param 1", - ] - priors.priorValues = [ - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - [1, 0.0, np.inf], - ] - - return priors - - -@pytest.fixture -def domains_priors(): - """The expected priors object from "domains_project".""" - priors = Priors() - priors.params = [ - ["Substrate Roughness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test Thickness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test SLD", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ["Test Roughness", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf], - ] - priors.backgroundParams = [["Background Param 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.scalefactors = [["Scalefactor 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.qzshifts = [] - priors.bulkIns = [["SLD Air", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.bulkOuts = [["SLD D2O", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.resolutionParams = [["Resolution Param 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.domainRatios = [["Domain Ratio 1", RATapi.utils.enums.Priors.Uniform, 0.0, np.inf]] - priors.priorNames = [ + problem.priorNames = [ "Substrate Roughness", "Test Thickness", "Test SLD", @@ -430,10 +330,8 @@ def domains_priors(): "SLD Air", "SLD D2O", "Resolution Param 1", - "Domain Ratio 1", ] - priors.priorValues = [ - [1, 0.0, np.inf], + problem.priorValues = [ [1, 0.0, np.inf], [1, 0.0, np.inf], [1, 0.0, np.inf], @@ -444,8 +342,13 @@ def domains_priors(): [1, 0.0, np.inf], [1, 0.0, np.inf], ] + problem.customFiles = FileHandles( + [RATapi.models.CustomFile(name="Test Custom File", filename="cpp_test.dll", language="cpp")] + ) + problem.names = test_names + problem.checks = test_checks - return priors + return problem @pytest.fixture @@ -494,7 +397,7 @@ def custom_xy_controls(): controls = Control() controls.procedure = Procedures.Calculate controls.parallel = Parallel.Single - controls.calcSldDuringFit = True + controls.calcSldDuringFit = False controls.resampleMinAngle = 0.9 controls.resampleNPoints = 50.0 controls.display = Display.Iter @@ -525,86 +428,56 @@ def custom_xy_controls(): @pytest.mark.parametrize( - ["test_project", "test_problem", "test_limits", "test_priors", "test_controls"], + ["test_project", "test_problem", "test_controls"], [ ( "standard_layers_project", "standard_layers_problem", - "normal_limits", - "normal_priors", "standard_layers_controls", ), ( "custom_xy_project", "custom_xy_problem", - "normal_limits", - "normal_priors", "custom_xy_controls", ), ( "domains_project", "domains_problem", - "domains_limits", - "domains_priors", "standard_layers_controls", ), ], ) -def test_make_input(test_project, test_problem, test_limits, test_priors, test_controls, request) -> None: - """When converting the "project" and "controls", we should obtain the five input objects required for the compiled +def test_make_input(test_project, test_problem, test_controls, request) -> None: + """When converting the "project" and "controls", we should obtain the two input objects required for the compiled RAT code. """ test_project = request.getfixturevalue(test_project) test_problem = request.getfixturevalue(test_problem) - test_limits = request.getfixturevalue(test_limits) - test_priors = request.getfixturevalue(test_priors) test_controls = request.getfixturevalue(test_controls) - parameter_fields = [ - "params", - "backgroundParams", - "scalefactors", - "qzshifts", - "bulkIns", - "bulkOuts", - "resolutionParams", - "domainRatios", - ] + problem, controls = make_input(test_project, RATapi.Controls()) - problem, limits, priors, controls = make_input(test_project, RATapi.Controls()) problem = pickle.loads(pickle.dumps(problem)) check_problem_equal(problem, test_problem) - limits = pickle.loads(pickle.dumps(limits)) - for limit_field in parameter_fields: - assert np.all(getattr(limits, limit_field) == getattr(test_limits, limit_field)) - - priors = pickle.loads(pickle.dumps(priors)) - for prior_field in parameter_fields: - assert getattr(priors, prior_field) == getattr(test_priors, prior_field) - - assert priors.priorNames == test_priors.priorNames - assert np.all(priors.priorValues == test_priors.priorValues) - controls = pickle.loads(pickle.dumps(controls)) check_controls_equal(controls, test_controls) @pytest.mark.parametrize( - ["test_project", "test_check", "test_problem"], + ["test_project", "test_problem"], [ - ("standard_layers_project", "test_checks", "standard_layers_problem"), - ("custom_xy_project", "test_checks", "custom_xy_problem"), - ("domains_project", "test_checks", "domains_problem"), + ("standard_layers_project", "standard_layers_problem"), + ("custom_xy_project", "custom_xy_problem"), + ("domains_project", "domains_problem"), ], ) -def test_make_problem(test_project, test_problem, test_check, request) -> None: +def test_make_problem(test_project, test_problem, request) -> None: """The problem object should contain the relevant parameters defined in the input project object.""" test_project = request.getfixturevalue(test_project) test_problem = request.getfixturevalue(test_problem) - test_check = request.getfixturevalue(test_check) - problem = make_problem(test_project, test_check) + problem = make_problem(test_project) check_problem_equal(problem, test_problem) @@ -749,12 +622,12 @@ def check_problem_equal(actual_problem, expected_problem) -> None: "numberOfContrasts", "numberOfLayers", "numberOfDomainContrasts", + "priorNames", ] array_fields = [ "params", "backgroundParams", - "qzshifts", "scalefactors", "bulkIns", "bulkOuts", @@ -762,7 +635,6 @@ def check_problem_equal(actual_problem, expected_problem) -> None: "domainRatios", "contrastBackgroundParams", "contrastBackgroundActions", - "contrastQzshifts", "contrastScalefactors", "contrastBulkIns", "contrastBulkOuts", @@ -772,20 +644,17 @@ def check_problem_equal(actual_problem, expected_problem) -> None: "dataPresent", "dataLimits", "simulationLimits", - "oilChiDataPresent", "repeatLayers", "contrastLayers", "domainContrastLayers", "fitParams", - "otherParams", "fitLimits", - "otherLimits", + "priorValues", ] checks_fields = [ "params", "backgroundParams", "scalefactors", - "qzshifts", "bulkIns", "bulkOuts", "resolutionParams", diff --git a/tests/test_models.py b/tests/test_models.py index aa133c79..703e1f38 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -334,23 +334,50 @@ def test_contrast_bad_ratio(): RATapi.models.Contrast(name="My Contrast", domain_ratio="bad ratio") -@pytest.mark.parametrize("model", [RATapi.models.Background, RATapi.models.Resolution]) -@pytest.mark.filterwarnings("ignore:The following values are not recognised by this*:UserWarning") -def test_type_change_clear(model): +@pytest.mark.parametrize( + ["model", "type", "values"], + [ + (RATapi.models.Background, "function", ["val1", "val2", "val3", "val4", "val5"]), + (RATapi.models.Resolution, "constant", ["", "", "", "", ""]), + ], +) +def test_type_change_clear(model, type, values): """If the type of a background or resolution is changed, it should wipe the other fields and warn the user.""" - model_instance = model( name="Test", - type="constant", + type=type, source="src", - value_1="val1", - value_2="val2", - value_3="val3", - value_4="val4", - value_5="val5", + value_1=values[0], + value_2=values[1], + value_3=values[2], + value_4=values[3], + value_5=values[4], ) with pytest.warns(UserWarning, match="Changing the type of Test clears its source and value fields."): model_instance.type = "data" for attr in ["source", "value_1", "value_2", "value_3", "value_4", "value_5"]: assert getattr(model_instance, attr) == "" + + +@pytest.mark.parametrize( + ["model", "signal_type", "values"], + [ + (RATapi.models.Background, "constant", ["value_1", "value_2", "value_3", "value_4", "value_5"]), + (RATapi.models.Background, "data", ["value_2", "value_3", "value_4", "value_5"]), + (RATapi.models.Resolution, "constant", ["value_1", "value_2", "value_3", "value_4", "value_5"]), + (RATapi.models.Resolution, "data", ["value_1", "value_2", "value_3", "value_4", "value_5"]), + ], +) +def test_unsupported_parameters_error(model, signal_type, values): + """If a value is inputted for an unsupported field for a particular type of background or resolution then we should + raise an error.""" + for value in values: + with pytest.raises( + pydantic.ValidationError, + match=( + f"1 validation error for {model.__name__}\n Value error, The following values are not supported" + f' by the "{signal_type}" {model.__name__} type: {value}' + ), + ): + model(**{"type": signal_type, value: "unsupported"}) diff --git a/tests/test_orso_utils.py b/tests/test_orso_utils.py new file mode 100644 index 00000000..958abd88 --- /dev/null +++ b/tests/test_orso_utils.py @@ -0,0 +1,119 @@ +"""Tests for the RATapi.utils.orso module.""" + +import os +from io import StringIO +from pathlib import Path + +import numpy as np +import pytest +from orsopy.fileio.model_language import SampleModel + +from RATapi.examples.bayes_benchmark.bayes_benchmark import get_project +from RATapi.project import Project +from RATapi.utils.orso import ORSOProject, orso_model_to_rat + +TEST_DIR_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test_data") + + +@pytest.fixture +def bare_subs(): + """The bare substrate project from the Bayes Benchmark example.""" + return get_project() + + +@pytest.fixture +def prist(): + """The project from the model data from prist5_10K_m_025.Rqz.ort""" + return Project.load(Path(TEST_DIR_PATH, "prist.json")) + + +@pytest.mark.parametrize( + "model", + [ + "air | FeO2 0.75 | Fe 10 | Si", + "vacuum | 5 (O3 3 | He2 4) | SiO2 0.75 | Si", + "Si | 5 (O2 2 | 3 (D2O 1 | H2O 1)) | air", + ], +) +@pytest.mark.parametrize("absorption", [True, False]) +def test_orso_model_to_rat(model, absorption): + """Test that orso_model_to_rat gives the expected parameters, layers and model.""" + + expected = SampleModel(model).resolve_to_layers()[1:-1] + expected_layers = [layer.material.formula for layer in expected] + expected_thicknesses = {layer.material.formula: layer.thickness for layer in expected} + expected_roughnesses = {layer.material.formula: layer.roughness for layer in expected} + actual = orso_model_to_rat(model, absorption=absorption) + + assert actual.model == expected_layers + + for layer in actual.layers: + assert layer.name in expected_layers + for layer in expected_layers: + assert layer in [actual_layer.name for actual_layer in actual.layers] + + expected_parameters = [] + # get set of parameters + for layer in set(expected_layers): + expected_parameters.extend([f"{layer} Thickness", f"{layer} Roughness", f"{layer} SLD"]) + if absorption: + expected_parameters.append(f"{layer} SLD imaginary") + + assert actual.parameters[f"{layer} Thickness"].value == expected_thicknesses[layer].as_unit("angstrom") + assert actual.parameters[f"{layer} Roughness"].value == expected_roughnesses[layer].as_unit("angstrom") + + assert set(p.name for p in actual.parameters) == set(expected_parameters) + + +@pytest.mark.parametrize( + "test_data", + [ + "bare_substrate.ort", + "prist5_10K_m_025.Rqz.ort", + ], +) +def test_load_ort_data(test_data): + """Test that .ort data is loaded correctly.""" + # manually get the test data for comparison + data_strings = [""] + parsing_data = False + with Path(TEST_DIR_PATH, test_data).open() as file: + for line in file: + if line[0] == "#": + if parsing_data: + parsing_data = False + data_strings.append("") + else: + continue + else: + parsing_data = True + data_strings[-1] += line + + expected_data = list(map(lambda s: np.loadtxt(StringIO(s)), data_strings)) + actual_data = ORSOProject(Path(TEST_DIR_PATH, test_data)).data + + assert len(actual_data) == len(expected_data) + for actual_dataset, expected_dataset in zip(actual_data, expected_data): + np.testing.assert_array_equal(actual_dataset.data, expected_dataset) + + +@pytest.mark.parametrize( + "test_data, expected_data", + [ + ["bare_substrate.ort", "bare_substrate.json"], + ["prist5_10K_m_025.Rqz.ort", "prist.json"], + ], +) +def test_load_ort_project(test_data, expected_data): + """Test that a project with model data is loaded correctly.""" + ort_data = ORSOProject(Path(TEST_DIR_PATH, test_data)) + sample = ort_data.samples[0] + exp_project = Project.load(Path(TEST_DIR_PATH, expected_data)) + + for class_list in ["bulk_in", "bulk_out"]: + assert getattr(sample, class_list) == getattr(exp_project, class_list)[0] + assert sample.parameters == exp_project.parameters[1:] + assert sample.layers == exp_project.layers + + for data, exp_data in zip(ort_data.data, exp_project.data[1:]): + np.testing.assert_array_equal(data.data, exp_data.data) diff --git a/tests/test_outputs.py b/tests/test_outputs.py index 7d9a0afd..37a4c631 100644 --- a/tests/test_outputs.py +++ b/tests/test_outputs.py @@ -24,8 +24,8 @@ def reflectivity_calculation_str(): "shiftedData = [Data array: [21 x 3], Data array: [21 x 3]],\n" "backgrounds = [Data array: [82 x 3], Data array: [82 x 3]],\n" "resolutions = [Data array: [82 x 2], Data array: [82 x 2]],\n" - "layerSlds = [[Data array: [8 x 3]], [Data array: [8 x 3]]],\n" "sldProfiles = [[Data array: [25 x 2], Data array: [25 x 2]]],\n" + "layers = [[Data array: [8 x 3]], [Data array: [8 x 3]]],\n" "resampledLayers = [[Data array: [1 x 3]], [Data array: [1 x 3]]],\n" "calculationResults = CalculationResults(\n" "\tchiValues = [ 202.83057377 1641.4024969 ],\n" @@ -59,8 +59,8 @@ def dream_str(): "shiftedData = [Data array: [21 x 3], Data array: [21 x 3]],\n" "backgrounds = [Data array: [82 x 3], Data array: [82 x 3]],\n" "resolutions = [Data array: [82 x 2], Data array: [82 x 2]],\n" - "layerSlds = [[Data array: [8 x 3]], [Data array: [8 x 3]]],\n" "sldProfiles = [[Data array: [29 x 2], Data array: [29 x 2]]],\n" + "layers = [[Data array: [8 x 3]], [Data array: [8 x 3]]],\n" "resampledLayers = [[Data array: [1 x 3]], [Data array: [1 x 3]]],\n" "calculationResults = CalculationResults(\n" "\tchiValues = [4.6077885 7.00028098],\n" @@ -119,7 +119,6 @@ def dream_str(): "\toutlierChains = Data array: [1 x 2],\n" "\truntime = 2.6e-06,\n" "\titeration = 2.0,\n" - "\tmodelOutput = 0.0,\n" "\tAR = Data array: [1 x 2],\n" "\tR_stat = Data array: [1 x 19],\n" "\tCR = Data array: [1 x 4],\n" diff --git a/tests/test_plotting.py b/tests/test_plotting.py index a1954834..d907ba16 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -73,8 +73,8 @@ def bayes_fig(request) -> plt.figure: @pytest.mark.parametrize("fig", [False, True], indirect=True) -def test_figure_axis_formating(fig: plt.figure) -> None: - """Tests the axis formating of the figure.""" +def test_figure_axis_formatting(fig: plt.figure) -> None: + """Tests the axis formatting of the figure.""" ref_plot = fig.axes[0] sld_plot = fig.axes[1] @@ -140,9 +140,9 @@ def test_ref_sld_bayes(fig, bayes_fig, bayes): assert any(isinstance(comp, PolyCollection) for comp in components) -@patch("RATapi.utils.plotting.makeSLDProfileXY") +@patch("RATapi.utils.plotting.makeSLDProfile") def test_sld_profile_function_call(mock: MagicMock) -> None: - """Tests the makeSLDProfileXY function called with + """Tests the makeSLDProfile function called with correct args. """ RATplot.plot_ref_sld_helper(data()) @@ -150,24 +150,21 @@ def test_sld_profile_function_call(mock: MagicMock) -> None: assert mock.call_count == 3 assert mock.call_args_list[0].args[0] == 2.07e-06 assert mock.call_args_list[0].args[1] == 6.28e-06 - assert mock.call_args_list[0].args[2] == 0.0 - assert mock.call_args_list[0].args[4] == 82 - assert mock.call_args_list[0].args[5] == 1.0 + assert mock.call_args_list[0].args[3] == 0.0 + assert mock.call_args_list[0].args[4] == 1 assert mock.call_args_list[1].args[0] == 2.07e-06 assert mock.call_args_list[1].args[1] == 1.83e-06 - assert mock.call_args_list[1].args[2] == 0.0 - assert mock.call_args_list[1].args[4] == 128 - assert mock.call_args_list[1].args[5] == 1.0 + assert mock.call_args_list[1].args[3] == 0.0 + assert mock.call_args_list[1].args[4] == 1 assert mock.call_args_list[2].args[0] == 2.07e-06 assert mock.call_args_list[2].args[1] == -5.87e-07 - assert mock.call_args_list[2].args[2] == 0.0 - assert mock.call_args_list[2].args[4] == 153 - assert mock.call_args_list[2].args[5] == 1.0 + assert mock.call_args_list[2].args[3] == 0.0 + assert mock.call_args_list[2].args[4] == 1 -@patch("RATapi.utils.plotting.makeSLDProfileXY") +@patch("RATapi.utils.plotting.makeSLDProfile") def test_live_plot(mock: MagicMock) -> None: plot_data = data() @@ -180,21 +177,18 @@ def test_live_plot(mock: MagicMock) -> None: assert mock.call_count == 3 assert mock.call_args_list[0].args[0] == 2.07e-06 assert mock.call_args_list[0].args[1] == 6.28e-06 - assert mock.call_args_list[0].args[2] == 0.0 - assert mock.call_args_list[0].args[4] == 82 - assert mock.call_args_list[0].args[5] == 1.0 + assert mock.call_args_list[0].args[3] == 0.0 + assert mock.call_args_list[0].args[4] == 1 assert mock.call_args_list[1].args[0] == 2.07e-06 assert mock.call_args_list[1].args[1] == 1.83e-06 - assert mock.call_args_list[1].args[2] == 0.0 - assert mock.call_args_list[1].args[4] == 128 - assert mock.call_args_list[1].args[5] == 1.0 + assert mock.call_args_list[1].args[3] == 0.0 + assert mock.call_args_list[1].args[4] == 1 assert mock.call_args_list[2].args[0] == 2.07e-06 assert mock.call_args_list[2].args[1] == -5.87e-07 - assert mock.call_args_list[2].args[2] == 0.0 - assert mock.call_args_list[2].args[4] == 153 - assert mock.call_args_list[2].args[5] == 1.0 + assert mock.call_args_list[2].args[3] == 0.0 + assert mock.call_args_list[2].args[4] == 1 @patch("RATapi.utils.plotting.plot_ref_sld_helper") diff --git a/tests/test_project.py b/tests/test_project.py index ee2136ba..e82a84da 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1,7 +1,9 @@ """Test the project module.""" import copy +import re import tempfile +import warnings from pathlib import Path from typing import Callable @@ -139,7 +141,7 @@ def test_classlists(test_project) -> None: """The ClassLists in the "Project" model should contain instances of the models given by the dictionary "model_in_classlist". """ - for model in (fields := RATapi.Project.model_fields): + for model in (fields := test_project.model_fields): if get_origin(fields[model].annotation) == RATapi.ClassList: class_list = getattr(test_project, model) assert class_list._class_handle == get_args(fields[model].annotation)[0] @@ -172,11 +174,9 @@ def test_initialise_wrong_classes(input_model: Callable, model_params: dict) -> """If the "Project" model is initialised with incorrect classes, we should raise a ValidationError.""" with pytest.raises( pydantic.ValidationError, - match=( - "1 validation error for Project\nparameters\n" - " Value error, This ClassList only supports elements of type Parameter. In the input list:\n" - f" index 0 is of type {input_model.__name__}" - ), + match="1 validation error for Project\nparameters\n " + "Value error, This ClassList only supports elements of type Parameter. In the input list:\n" + f" index 0 is of type {input_model.__name__}", ): RATapi.Project(parameters=RATapi.ClassList(input_model(**model_params))) @@ -200,8 +200,8 @@ def test_initialise_wrong_layers( with pytest.raises( pydantic.ValidationError, match=f"1 validation error for Project\nlayers\n Value error, " - f'"The layers attribute contains {input_model.__name__}s, but the absorption parameter is {absorption}. ' - f'The attribute should be a ClassList of {actual_model_name} instead."', + f'"The layers attribute contains {input_model.__name__}s, but the absorption parameter is ' + f'{absorption}. The attribute should be a ClassList of {actual_model_name} instead."', ): RATapi.Project(absorption=absorption, layers=RATapi.ClassList(input_model(**model_params))) @@ -235,8 +235,8 @@ def test_initialise_ambiguous_layers(absorption: bool, model: RATapi.models.RATM def test_initialise_wrong_contrasts( input_model: RATapi.models.RATModel, calculation: Calculations, actual_model_name: str ) -> None: - """If the "Project" model is initialised with the incorrect contrast model given the value of calculation, we should - raise a ValidationError. + """If the "Project" model is initialised with the incorrect contrast model given the value of calculation, we + should raise a ValidationError. """ word = "without" if calculation == Calculations.Domains else "with" with pytest.raises( @@ -352,8 +352,8 @@ def test_assign_wrong_layers( with pytest.raises( pydantic.ValidationError, match=f"1 validation error for Project\nlayers\n Value error, " - f'"The layers attribute contains {wrong_input_model.__name__}s, but the absorption parameter is {absorption}. ' - f'The attribute should be a ClassList of {actual_model_name} instead."', + f'"The layers attribute contains {wrong_input_model.__name__}s, but the absorption parameter is ' + f'{absorption}. The attribute should be a ClassList of {actual_model_name} instead."', ): project.layers = RATapi.ClassList(wrong_input_model(**model_params)) @@ -372,8 +372,8 @@ def test_assign_wrong_contrasts(wrong_input_model: Callable, calculation: Calcul with pytest.raises( pydantic.ValidationError, match=f"1 validation error for Project\ncontrasts\n" - f' Value error, "The contrasts attribute contains contrasts {word} ratio, ' - f'but the calculation is {calculation}"', + f' Value error, "The contrasts attribute contains contrasts {word} ratio, but the calculation is ' + f'{calculation}"', ): project.contrasts = RATapi.ClassList(wrong_input_model()) @@ -607,8 +607,8 @@ def test_check_protected_parameters(delete_operation) -> None: with pytest.raises( pydantic.ValidationError, - match="1 validation error for Project\n Value error, Can't delete" - " the protected parameters: Substrate Roughness", + match="1 validation error for Project\n Value error, " + "Can't delete the protected parameters: Substrate Roughness", ): eval(delete_operation) @@ -660,15 +660,18 @@ def test_rename_models(test_project, model: str, fields: list[str]) -> None: ], ) def test_allowed_backgrounds(background_type, expected_field) -> None: - """If the source field of the Background model are set to values that are not specified in the background - parameters, we should raise a ValidationError. + """ + If the source field of the Background model is set to a value that is not specified in the appropriate ClassList, + we should raise a ValidationError. """ test_background = RATapi.models.Background(type=background_type, source="undefined") with pytest.raises( pydantic.ValidationError, - match="1 validation error for Project\n Value error, The value " - '"undefined" in the "source" field of "backgrounds" must be ' - f'defined in "{expected_field}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "source" field of ' + f'backgrounds[0] must be defined in "{expected_field}". Please add "undefined" to "{expected_field}" ' + f'before including it in "backgrounds".' + ), ): RATapi.Project(backgrounds=RATapi.ClassList(test_background)) @@ -689,9 +692,11 @@ def test_allowed_layers(field: str) -> None: with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "layers" must be ' - f'defined in "parameters".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" ' + f'field of layers[0] must be defined in "parameters". Please add "undefined" to "parameters" ' + f'before including it in "layers".' + ), ): RATapi.Project( absorption=False, @@ -723,9 +728,11 @@ def test_allowed_absorption_layers(field: str) -> None: with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "layers" must be ' - f'defined in "parameters".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" field of ' + f'layers[0] must be defined in "parameters". Please add "undefined" to "parameters" before including it ' + f'in "layers".' + ), ): RATapi.Project( absorption=True, @@ -755,9 +762,11 @@ def test_allowed_resolutions(resolution_type, expected_field) -> None: test_resolution = RATapi.models.Resolution(type=resolution_type, source="undefined") with pytest.raises( pydantic.ValidationError, - match="1 validation error for Project\n Value error, The value " - '"undefined" in the "source" field of "resolutions" must be ' - f'defined in "{expected_field}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "source" field of ' + f'resolutions[0] must be defined in "{expected_field}". Please add "undefined" to "{expected_field}" ' + f'before including it in "resolutions".' + ), ): RATapi.Project(resolutions=RATapi.ClassList(test_resolution)) @@ -780,9 +789,11 @@ def test_allowed_contrasts(field: str, model_name: str) -> None: test_contrast = RATapi.models.Contrast(**{field: "undefined"}) with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "contrasts" must be ' - f'defined in "{model_name}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" field of ' + f'contrasts[0] must be defined in "{model_name}". Please add "undefined" to "{model_name}" before ' + f'including it in "contrasts".' + ), ): RATapi.Project(calculation=Calculations.Normal, contrasts=RATapi.ClassList(test_contrast)) @@ -806,9 +817,11 @@ def test_allowed_contrasts_with_ratio(field: str, model_name: str) -> None: test_contrast = RATapi.models.ContrastWithRatio(**{field: "undefined"}) with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "contrasts" must be ' - f'defined in "{model_name}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" field of ' + f'contrasts[0] must be defined in "{model_name}". Please add "undefined" to "{model_name}" before ' + f'including it in "contrasts".' + ), ): RATapi.Project(calculation=Calculations.Domains, contrasts=RATapi.ClassList(test_contrast)) @@ -863,11 +876,14 @@ def test_allowed_contrast_models( """If any value in the model field of the contrasts is set to a value not specified in the appropriate part of the project, we should raise a ValidationError. """ + missing_values = list(set(test_contrast.model)) with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The values: " - f'"{", ".join(test_contrast.model)}" in the "model" field of ' - f'"contrasts" must be defined in "{field_name}".', + match=re.escape( + f"1 validation error for Project\n Value error, The value{'s' if len(missing_values) > 1 else ''}: " + f'"{", ".join(missing_values)}" used in the "model" field of contrasts[0] must be defined in ' + f'"{field_name}". Please add all required values to "{field_name}" before including them in "contrasts".' + ), ): RATapi.Project(calculation=input_calc, model=input_model, contrasts=RATapi.ClassList(test_contrast)) @@ -879,9 +895,11 @@ def test_allowed_domain_contrast_models() -> None: test_contrast = RATapi.models.DomainContrast(name="Test Domain Contrast", model=["undefined"]) with pytest.raises( pydantic.ValidationError, - match="1 validation error for Project\n Value error, The values: " - '"undefined" in the "model" field of "domain_contrasts" must be ' - 'defined in "layers".', + match=re.escape( + '1 validation error for Project\n Value error, The value: "undefined" used in the "model" field of ' + 'domain_contrasts[0] must be defined in "layers". Please add all required values to "layers" before ' + 'including them in "domain_contrasts".' + ), ): RATapi.Project(calculation=Calculations.Domains, domain_contrasts=RATapi.ClassList(test_contrast)) @@ -933,10 +951,11 @@ def test_get_all_protected_parameters(test_project) -> None: ) def test_check_allowed_values(test_value: str) -> None: """We should not raise an error if string values are defined and on the list of allowed values.""" + allowed_values = ["Substrate Roughness"] project = RATapi.Project.model_construct( layers=RATapi.ClassList(RATapi.models.Layer(**dict(layer_params, roughness=test_value))) ) - assert project.check_allowed_values("layers", ["roughness"], ["Substrate Roughness"]) is None + assert project.check_allowed_values("layers", ["roughness"], allowed_values, allowed_values) is None @pytest.mark.parametrize( @@ -950,11 +969,15 @@ def test_check_allowed_values_not_on_list(test_value: str) -> None: project = RATapi.Project.model_construct( layers=RATapi.ClassList(RATapi.models.Layer(**dict(layer_params, roughness=test_value))) ) + allowed_values = ["Substrate Roughness"] with pytest.raises( ValueError, - match=f'The value "{test_value}" in the "roughness" field of "layers" must be defined in "parameters".', + match=re.escape( + f'The value "{test_value}" used in the "roughness" field of layers[0] must be defined in "parameters". ' + f'Please add "{test_value}" to "parameters" before including it in "layers".' + ), ): - project.check_allowed_values("layers", ["roughness"], ["Substrate Roughness"]) + project.check_allowed_values("layers", ["roughness"], allowed_values, allowed_values) @pytest.mark.parametrize( @@ -1001,8 +1024,11 @@ def test_check_allowed_background_resolution_values_not_on_constant_list(test_va ) with pytest.raises( ValueError, - match=f'The value "{test_value}" in the "source" field of "backgrounds" must be ' - f'defined in "background_parameters".', + match=re.escape( + f'The value "{test_value}" used in the "source" field of backgrounds[0] must be defined in ' + f'"background_parameters". Please add "{test_value}" to "background_parameters" before including it in ' + f'"backgrounds".' + ), ): project.check_allowed_source( "backgrounds", @@ -1025,7 +1051,10 @@ def test_check_allowed_background_resolution_values_on_data_list(test_value: str ) with pytest.raises( ValueError, - match=f'The value "{test_value}" in the "source" field of "backgrounds" must be defined in "data".', + match=re.escape( + f'The value "{test_value}" used in the "source" field of backgrounds[0] must be defined in "data". Please ' + f'add "{test_value}" to "data" before including it in "backgrounds".' + ), ): project.check_allowed_source("backgrounds") @@ -1044,14 +1073,15 @@ def test_check_contrast_model_allowed_values(test_values: list[str]) -> None: project = RATapi.Project.model_construct( contrasts=RATapi.ClassList(RATapi.models.Contrast(name="Test Contrast", model=test_values)), ) - assert project.check_contrast_model_allowed_values("contrasts", ["Test Layer"], "layers") is None + assert project.check_contrast_model_allowed_values("contrasts", ["Test Layer"], ["Test Layer"], "layers") is None @pytest.mark.parametrize( "test_values", [ - ["Undefined Param"], - ["Test Layer", "Undefined Param"], + ["Undefined Param 1"], + ["Test Layer", "Undefined Param 1"], + ["Undefined Param 1 ", "Test Layer", "Undefined Param 2"], ], ) def test_check_allowed_contrast_model_not_on_list(test_values: list[str]) -> None: @@ -1061,12 +1091,46 @@ def test_check_allowed_contrast_model_not_on_list(test_values: list[str]) -> Non project = RATapi.Project.model_construct( contrasts=RATapi.ClassList(RATapi.models.Contrast(name="Test Contrast", model=test_values)), ) + allowed_values = ["Test Layer"] + missing_values = list(set(test_values) - set(allowed_values)) with pytest.raises( ValueError, - match=f'The values: "{", ".join(str(i) for i in test_values)}" in the "model" field ' - f'of "contrasts" must be defined in "layers".', + match=re.escape( + f'The value{"s" if len(missing_values) > 1 else ""}: "{", ".join(str(i) for i in missing_values)}" used ' + f'in the "model" field of contrasts[0] must be defined in "layers". Please add all required values to ' + f'"layers" before including them in "contrasts".' + ), ): - project.check_contrast_model_allowed_values("contrasts", ["Test Layer"], "layers") + project.check_contrast_model_allowed_values("contrasts", allowed_values, allowed_values, "layers") + + +@pytest.mark.parametrize( + "test_values", + [ + ["Undefined Param 1"], + ["Test Layer", "Undefined Param 1"], + ["Undefined Param 1", "Test Layer", "Undefined Param 2"], + ], +) +def test_check_allowed_contrast_model_removed_from_list(test_values: list[str]) -> None: + """If string values are defined in a non-empty list and any of them have been removed from the list of allowed + values we should raise a ValueError. + """ + project = RATapi.Project.model_construct( + contrasts=RATapi.ClassList(RATapi.models.Contrast(name="Test Contrast", model=test_values)), + ) + previous_values = ["Test Layer", "Undefined Param 1", "Undefined Param 2"] + allowed_values = ["Test Layer"] + missing_values = list(set(test_values) - set(allowed_values)) + with pytest.raises( + ValueError, + match=re.escape( + f'The value{"s" if len(missing_values) > 1 else ""}: "{", ".join(str(i) for i in missing_values)}" used ' + f'in the "model" field of contrasts[0] must be defined in "layers". Please remove all unnecessary values ' + f'from "model" before attempting to delete them.' + ), + ): + project.check_contrast_model_allowed_values("contrasts", allowed_values, previous_values, "layers") @pytest.mark.parametrize( @@ -1136,17 +1200,7 @@ def test_write_script_wrong_extension(test_project, extension: str) -> None: ["class_list", "model_type", "field"], [ ("backgrounds", "constant", "source"), - ("backgrounds", "", "value_1"), - ("backgrounds", "", "value_2"), - ("backgrounds", "", "value_3"), - ("backgrounds", "", "value_4"), - ("backgrounds", "", "value_5"), ("resolutions", "constant", "source"), - ("resolutions", "", "value_1"), - ("resolutions", "", "value_2"), - ("resolutions", "", "value_3"), - ("resolutions", "", "value_4"), - ("resolutions", "", "value_5"), ("layers", "", "thickness"), ("layers", "", "SLD"), ("layers", "", "roughness"), @@ -1164,15 +1218,18 @@ def test_wrap_set(test_project, class_list: str, model_type: str, field: str) -> test_attribute = getattr(test_project, class_list) orig_class_list = copy.deepcopy(test_attribute) class_list_str = f"{class_list}{f'.{model_type}' if model_type else ''}.{field}" + index = 0 with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "{class_list}" must be ' - f"defined in " - f'"{RATapi.project.values_defined_in[class_list_str]}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" field of ' + f'{class_list}[{index}] must be defined in "{RATapi.project.values_defined_in[class_list_str]}". Please ' + f'add "undefined" to "{RATapi.project.values_defined_in[class_list_str]}" before including it in ' + f'"{class_list}".' + ), ): - test_attribute.set_fields(0, **{field: "undefined"}) + test_attribute.set_fields(index, **{field: "undefined"}) # Ensure invalid model was not changed assert test_attribute == orig_class_list @@ -1198,12 +1255,17 @@ def test_wrap_del(test_project, class_list: str, parameter: str, field: str) -> orig_class_list = copy.deepcopy(test_attribute) index = test_attribute.index(parameter) + sub_attribute_name = RATapi.project.model_names_used_in[class_list][0].attribute + sub_attribute = getattr(test_project, sub_attribute_name) + sub_index = [i for i, _ in enumerate(sub_attribute) if getattr(sub_attribute[i], field) == parameter][0] + with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"{parameter}" in the "{field}" field of ' - f'"{RATapi.project.model_names_used_in[class_list][0].attribute}" ' - f'must be defined in "{class_list}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "{parameter}" used in the "{field}" field of ' + f'{sub_attribute_name}[{sub_index}] must be defined in "{class_list}". Please remove "{parameter}" from ' + f'"{sub_attribute_name}[{sub_index}].{field}" before attempting to delete it.' + ), ): del test_attribute[index] @@ -1215,17 +1277,7 @@ def test_wrap_del(test_project, class_list: str, parameter: str, field: str) -> ["class_list", "model_type", "field", "model_params"], [ ("backgrounds", "constant", "source", {}), - ("backgrounds", "", "value_1", {}), - ("backgrounds", "", "value_2", {}), - ("backgrounds", "", "value_3", {}), - ("backgrounds", "", "value_4", {}), - ("backgrounds", "", "value_5", {}), ("resolutions", "constant", "source", {}), - ("resolutions", "", "value_1", {}), - ("resolutions", "", "value_2", {}), - ("resolutions", "", "value_3", {}), - ("resolutions", "", "value_4", {}), - ("resolutions", "", "value_5", {}), ("layers", "", "thickness", layer_params), ("layers", "", "SLD", layer_params), ("layers", "", "roughness", layer_params), @@ -1247,10 +1299,12 @@ def test_wrap_iadd(test_project, class_list: str, model_type: str, field: str, m with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "{class_list}" must be ' - f"defined in " - f'"{RATapi.project.values_defined_in[class_list_str]}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" ' + f"field of {class_list}[{len(test_attribute)}] must be defined in " + f'"{RATapi.project.values_defined_in[class_list_str]}". Please add "undefined" to ' + f'"{RATapi.project.values_defined_in[class_list_str]}" before including it in "{class_list}".' + ), ): test_attribute += [input_model(**{**model_params, field: "undefined"})] @@ -1262,17 +1316,7 @@ def test_wrap_iadd(test_project, class_list: str, model_type: str, field: str, m ["class_list", "model_type", "field", "model_params"], [ ("backgrounds", "constant", "source", {}), - ("backgrounds", "", "value_1", {}), - ("backgrounds", "", "value_2", {}), - ("backgrounds", "", "value_3", {}), - ("backgrounds", "", "value_4", {}), - ("backgrounds", "", "value_5", {}), ("resolutions", "constant", "source", {}), - ("resolutions", "", "value_1", {}), - ("resolutions", "", "value_2", {}), - ("resolutions", "", "value_3", {}), - ("resolutions", "", "value_4", {}), - ("resolutions", "", "value_5", {}), ("layers", "", "thickness", layer_params), ("layers", "", "SLD", layer_params), ("layers", "", "roughness", layer_params), @@ -1295,10 +1339,12 @@ def test_wrap_append(test_project, class_list: str, model_type: str, field: str, with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "{class_list}" must be ' - f"defined in " - f'"{RATapi.project.values_defined_in[class_list_str]}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" ' + f"field of {class_list}[{len(test_attribute)}] must be defined in " + f'"{RATapi.project.values_defined_in[class_list_str]}". Please add "undefined" to ' + f'"{RATapi.project.values_defined_in[class_list_str]}" before including it in "{class_list}".' + ), ): test_attribute.append(input_model(**{**model_params, field: "undefined"})) @@ -1310,17 +1356,7 @@ def test_wrap_append(test_project, class_list: str, model_type: str, field: str, ["class_list", "model_type", "field", "model_params"], [ ("backgrounds", "constant", "source", {}), - ("backgrounds", "", "value_1", {}), - ("backgrounds", "", "value_2", {}), - ("backgrounds", "", "value_3", {}), - ("backgrounds", "", "value_4", {}), - ("backgrounds", "", "value_5", {}), ("resolutions", "constant", "source", {}), - ("resolutions", "", "value_1", {}), - ("resolutions", "", "value_2", {}), - ("resolutions", "", "value_3", {}), - ("resolutions", "", "value_4", {}), - ("resolutions", "", "value_5", {}), ("layers", "", "thickness", layer_params), ("layers", "", "SLD", layer_params), ("layers", "", "roughness", layer_params), @@ -1339,15 +1375,18 @@ def test_wrap_insert(test_project, class_list: str, model_type: str, field: str, orig_class_list = copy.deepcopy(test_attribute) input_model = model_classes[class_list] class_list_str = f"{class_list}{f'.{model_type}' if model_type else ''}.{field}" + index = 0 with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "{class_list}" must be ' - f"defined in " - f'"{RATapi.project.values_defined_in[class_list_str]}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" ' + f"field of {class_list}[{index}] must be defined in " + f'"{RATapi.project.values_defined_in[class_list_str]}". Please add "undefined" to ' + f'"{RATapi.project.values_defined_in[class_list_str]}" before including it in "{class_list}".' + ), ): - test_attribute.insert(0, input_model(**{**model_params, field: "undefined"})) + test_attribute.insert(index, input_model(**{**model_params, field: "undefined"})) # Ensure invalid model was not inserted assert test_attribute == orig_class_list @@ -1410,12 +1449,17 @@ def test_wrap_pop(test_project, class_list: str, parameter: str, field: str) -> orig_class_list = copy.deepcopy(test_attribute) index = test_attribute.index(parameter) + sub_attribute_name = RATapi.project.model_names_used_in[class_list][0].attribute + sub_attribute = getattr(test_project, sub_attribute_name) + sub_index = [i for i, _ in enumerate(sub_attribute) if getattr(sub_attribute[i], field) == parameter][0] + with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"{parameter}" in the "{field}" field of ' - f'"{RATapi.project.model_names_used_in[class_list][0].attribute}" ' - f'must be defined in "{class_list}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "{parameter}" used in the "{field}" field of ' + f'{sub_attribute_name}[{sub_index}] must be defined in "{class_list}". Please remove "{parameter}" from ' + f'"{sub_attribute_name}[{sub_index}].{field}" before attempting to delete it.' + ), ): test_attribute.pop(index) @@ -1442,12 +1486,17 @@ def test_wrap_remove(test_project, class_list: str, parameter: str, field: str) test_attribute = getattr(test_project, class_list) orig_class_list = copy.deepcopy(test_attribute) + sub_attribute_name = RATapi.project.model_names_used_in[class_list][0].attribute + sub_attribute = getattr(test_project, sub_attribute_name) + sub_index = [i for i, _ in enumerate(sub_attribute) if getattr(sub_attribute[i], field) == parameter][0] + with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"{parameter}" in the "{field}" field of ' - f'"{RATapi.project.model_names_used_in[class_list][0].attribute}" ' - f'must be defined in "{class_list}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "{parameter}" used in the "{field}" field of ' + f'{sub_attribute_name}[{sub_index}] must be defined in "{class_list}". Please remove "{parameter}" from ' + f'"{sub_attribute_name}[{sub_index}].{field}" before attempting to delete it.' + ), ): test_attribute.remove(parameter) @@ -1474,12 +1523,17 @@ def test_wrap_clear(test_project, class_list: str, parameter: str, field: str) - test_attribute = getattr(test_project, class_list) orig_class_list = copy.deepcopy(test_attribute) + sub_attribute_name = RATapi.project.model_names_used_in[class_list][0].attribute + sub_attribute = getattr(test_project, sub_attribute_name) + sub_index = [i for i, _ in enumerate(sub_attribute) if getattr(sub_attribute[i], field) == parameter][0] + with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"{parameter}" in the "{field}" field of ' - f'"{RATapi.project.model_names_used_in[class_list][0].attribute}" ' - f'must be defined in "{class_list}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "{parameter}" used in the "{field}" field of ' + f'{sub_attribute_name}[{sub_index}] must be defined in "{class_list}". Please remove "{parameter}" from ' + f'"{sub_attribute_name}[{sub_index}].{field}" before attempting to delete it.' + ), ): test_attribute.clear() @@ -1491,17 +1545,7 @@ def test_wrap_clear(test_project, class_list: str, parameter: str, field: str) - ["class_list", "model_type", "field", "model_params"], [ ("backgrounds", "constant", "source", {}), - ("backgrounds", "", "value_1", {}), - ("backgrounds", "", "value_2", {}), - ("backgrounds", "", "value_3", {}), - ("backgrounds", "", "value_4", {}), - ("backgrounds", "", "value_5", {}), ("resolutions", "constant", "source", {}), - ("resolutions", "", "value_1", {}), - ("resolutions", "", "value_2", {}), - ("resolutions", "", "value_3", {}), - ("resolutions", "", "value_4", {}), - ("resolutions", "", "value_5", {}), ("layers", "", "thickness", layer_params), ("layers", "", "SLD", layer_params), ("layers", "", "roughness", layer_params), @@ -1523,10 +1567,12 @@ def test_wrap_extend(test_project, class_list: str, model_type: str, field: str, with pytest.raises( pydantic.ValidationError, - match=f"1 validation error for Project\n Value error, The value " - f'"undefined" in the "{field}" field of "{class_list}" must be ' - f"defined in " - f'"{RATapi.project.values_defined_in[class_list_str]}".', + match=re.escape( + f'1 validation error for Project\n Value error, The value "undefined" used in the "{field}" ' + f"field of {class_list}[{len(test_attribute)}] must be defined in " + f'"{RATapi.project.values_defined_in[class_list_str]}". Please add "undefined" to ' + f'"{RATapi.project.values_defined_in[class_list_str]}" before including it in "{class_list}".' + ), ): test_attribute.extend([input_model(**{**model_params, field: "undefined"})]) @@ -1556,8 +1602,42 @@ def test_save_load(project, request): original_project = request.getfixturevalue(project) with tempfile.TemporaryDirectory() as tmp: - original_project.save(tmp) - converted_project = RATapi.Project.load(Path(tmp, "project.json")) - - for field in RATapi.Project.model_fields: + # ignore relative path warnings + path = Path(tmp, "project.json") + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + original_project.save(path) + converted_project = RATapi.Project.load(path) + + # resolve custom files in case the original project had unresolvable relative paths + for file in original_project.custom_files: + file.path = file.path.resolve() + + for field in original_project.model_fields: assert getattr(converted_project, field) == getattr(original_project, field) + + +def test_relative_paths(): + """Test that ``try_relative_to`` correctly creates relative paths to subfolders.""" + + with tempfile.TemporaryDirectory() as tmp: + data_path = Path(tmp, "data/myfile.dat") + + assert Path(RATapi.project.try_relative_to(data_path, tmp)) == Path("data/myfile.dat") + + +def test_relative_paths_warning(): + """Test that we get a warning for trying to walk up paths.""" + + data_path = "/tmp/project/data/mydata.dat" + relative_path = "/tmp/project/project_path/myproj.dat" + + with pytest.warns( + match="Could not save custom file path as relative to the project directory, " + "which means that it may not work on other devices. If you would like to share your project, " + "make sure your custom files are in a subfolder of the project save location.", + ): + assert ( + Path(RATapi.project.try_relative_to(data_path, relative_path)) + == Path("/tmp/project/data/mydata.dat").resolve() + ) diff --git a/tests/test_run.py b/tests/test_run.py index 85f7b64c..a3242d45 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -56,14 +56,12 @@ def reflectivity_calculation_problem(): ) problem.bulkIns = np.array([2.073e-06]) problem.bulkOuts = np.array([5.98e-06, 2.21e-06]) - problem.qzshifts = np.array([0.0]) problem.scalefactors = np.array([0.1, 0.15]) problem.domainRatios = np.array([]) problem.backgroundParams = np.array([2.23e-06, 3.38e-06]) problem.resolutionParams = np.array([0.03]) problem.contrastBulkIns = np.array([1.0, 1.0]) problem.contrastBulkOuts = np.array([1.0, 2.0]) - problem.contrastQzshifts = np.array([0.0, 0.0]) problem.contrastScalefactors = np.array([1.0, 2.0]) problem.contrastBackgroundParams = [[1.0], [2.0]] problem.contrastBackgroundTypes = ["constant", "constant"] @@ -76,10 +74,9 @@ def reflectivity_calculation_problem(): problem.dataPresent = np.array([1.0, 1.0]) problem.dataLimits = [[0.011403, 0.59342], [0.011403, 0.59342]] problem.simulationLimits = [[0.011403, 0.59342], [0.011403, 0.59342]] - problem.oilChiDataPresent = np.array([0.0, 0.0]) problem.numberOfContrasts = 2.0 problem.numberOfLayers = 6.0 - problem.repeatLayers = [[0.0, 1.0], [0.0, 1.0]] + problem.repeatLayers = [1.0, 1.0] problem.layersDetails = [ np.array([2.0]), np.array([4.0]), @@ -116,21 +113,6 @@ def reflectivity_calculation_problem(): 2.210e-06, ], ) - problem.otherParams = np.array( - [ - 3.390e-06, - -4.010e-07, - 0.000e00, - 1.750e-06, - 1.470e-06, - -4.610e-07, - 1.000e02, - 1.000e-01, - 1.500e-01, - 2.073e-06, - 3.000e-02, - ], - ) problem.fitLimits = np.array( [ [1.00e00, 1.00e01], @@ -153,21 +135,6 @@ def reflectivity_calculation_problem(): [1.00e-06, 4.99e-06], ], ) - problem.otherLimits = np.array( - [ - [3.39e-06, 3.41e-06], - [-5.00e-07, -3.00e-07], - [0.00e00, 1.00e-09], - [1.00e-07, 2.00e-06], - [5.00e-07, 1.50e-06], - [-5.00e-07, 0.00e00], - [9.99e01, 1.00e02], - [5.00e-02, 2.00e-01], - [5.00e-02, 2.00e-01], - [2.00e-06, 2.10e-06], - [1.00e-02, 5.00e-02], - ], - ) problem.names.params = [ "Substrate Roughness", "Oxide Thickness", @@ -193,7 +160,6 @@ def reflectivity_calculation_problem(): ] problem.names.backgroundParams = ["Background parameter D2O", "Background parameter SMW"] problem.names.scalefactors = ["Scalefactor 1", "Scalefactor 2"] - problem.names.qzshifts = [] problem.names.bulkIns = ["Silicon"] problem.names.bulkOuts = ["D2O", "SMW"] problem.names.resolutionParams = ["Resolution Param 1"] @@ -204,7 +170,6 @@ def reflectivity_calculation_problem(): ) problem.checks.backgroundParams = np.array([1.0, 1.0]) problem.checks.scalefactors = np.array([0.0, 0.0]) - problem.checks.qzshifts = np.array([]) problem.checks.bulkIns = np.array([0.0]) problem.checks.bulkOuts = np.array([1.0, 1.0]) problem.checks.resolutionParams = np.array([0.0]) @@ -252,14 +217,12 @@ def dream_problem(): ) problem.bulkIns = np.array([2.073e-06]) problem.bulkOuts = np.array([6.01489149e-06, 1.59371685e-06]) - problem.qzshifts = np.array([0.0]) problem.scalefactors = np.array([0.1, 0.15]) problem.domainRatios = np.array([]) problem.backgroundParams = np.array([2.37113128e-06, 1.99006694e-06]) problem.resolutionParams = np.array([0.03]) problem.contrastBulkIns = np.array([1.0, 1.0]) problem.contrastBulkOuts = np.array([1.0, 2.0]) - problem.contrastQzshifts = np.array([0.0, 0.0]) problem.contrastScalefactors = np.array([1.0, 2.0]) problem.contrastBackgroundParams = [[1.0], [2.0]] problem.contrastBackgroundTypes = ["constant", "constant"] @@ -272,10 +235,9 @@ def dream_problem(): problem.dataPresent = np.array([1.0, 1.0]) problem.dataLimits = [[0.011403, 0.59342], [0.011403, 0.59342]] problem.simulationLimits = [[0.011403, 0.59342], [0.011403, 0.59342]] - problem.oilChiDataPresent = np.array([0.0, 0.0]) problem.numberOfContrasts = 2.0 problem.numberOfLayers = 6.0 - problem.repeatLayers = [[0.0, 1.0], [0.0, 1.0]] + problem.repeatLayers = [1.0, 1.0] problem.layersDetails = [ np.array([2.0]), np.array([4.0]), @@ -312,22 +274,6 @@ def dream_problem(): 1.59371685e-06, ], ) - problem.otherParams = np.array( - [ - 3.390e-06, - -4.010e-07, - 0.000e00, - 1.750e-06, - 1.470e-06, - -4.610e-07, - 1.000e02, - 1.000e-01, - 1.500e-01, - 2.073e-06, - 3.000e-02, - 0.000e00, - ], - ) problem.fitLimits = np.array( [ [1.00e00, 1.00e01], @@ -350,22 +296,6 @@ def dream_problem(): [1.00e-06, 4.99e-06], ], ) - problem.otherLimits = np.array( - [ - [3.39e-06, 3.41e-06], - [-5.00e-07, -3.00e-07], - [0.00e00, 1.00e-09], - [1.00e-07, 2.00e-06], - [5.00e-07, 1.50e-06], - [-5.00e-07, 0.00e00], - [9.99e01, 1.00e02], - [5.00e-02, 2.00e-01], - [5.00e-02, 2.00e-01], - [2.00e-06, 2.10e-06], - [1.00e-02, 5.00e-02], - [0.00e00, 0.00e00], - ], - ) problem.names.params = [ "Substrate Roughness", "Oxide Thickness", @@ -391,7 +321,6 @@ def dream_problem(): ] problem.names.backgroundParams = ["Background parameter D2O", "Background parameter SMW"] problem.names.scalefactors = ["Scalefactor 1", "Scalefactor 2"] - problem.names.qzshifts = [] problem.names.bulkIns = ["Silicon"] problem.names.bulkOuts = ["D2O", "SMW"] problem.names.resolutionParams = ["Resolution Param 1"] @@ -402,7 +331,6 @@ def dream_problem(): ) problem.checks.backgroundParams = np.array([1.0, 1.0]) problem.checks.scalefactors = np.array([0.0, 0.0]) - problem.checks.qzshifts = np.array([]) problem.checks.bulkIns = np.array([0.0]) problem.checks.bulkOuts = np.array([1.0, 1.0]) problem.checks.resolutionParams = np.array([0.0]) diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index a50127e4..2ced5ad0 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -27,7 +27,7 @@ def test_matlab_wrapper() -> None: mocked_engine.demo.return_value = ([2], 5) result = handle([1], [2], [3], 0) assert result == ([2], 5) - assert wrapper.engine.demo.call_args[0] == ([1], [2], [3], 1, -1) + assert wrapper.engine.demo.call_args[0] == ([1], [2], [3], 1) mocked_engine.demo.assert_called_once() mocked_engine.demo.return_value = ([3, 1], 7) diff --git a/tests/utils.py b/tests/utils.py index a9411aac..40524fd0 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -31,7 +31,7 @@ def check_results_equal(actual_results, expected_results) -> None: "CalculationResults" and "ContrastParams". """ list_fields = ["reflectivity", "simulation", "shiftedData", "backgrounds", "resolutions"] - double_list_fields = ["layerSlds", "sldProfiles", "resampledLayers"] + double_list_fields = ["sldProfiles", "layers", "resampledLayers"] contrast_param_fields = [ "scalefactors", "bulkIn", @@ -109,7 +109,7 @@ def check_bayes_fields_equal(actual_results, expected_results) -> None: "IO", "storeOutput", ], - "dreamOutput": ["runtime", "iteration", "modelOutput"], + "dreamOutput": ["runtime", "iteration"], "nestedSamplerOutput": ["logZ"], }