ratapi 0.0.0.dev16__cp315-cp315-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. ratapi/__init__.py +42 -0
  2. ratapi/classlist.py +620 -0
  3. ratapi/controls.py +258 -0
  4. ratapi/eventManager.dll +0 -0
  5. ratapi/events.py +88 -0
  6. ratapi/examples/__init__.py +25 -0
  7. ratapi/examples/absorption/__init__.py +1 -0
  8. ratapi/examples/absorption/absorption.py +172 -0
  9. ratapi/examples/absorption/volume_thiol_bilayer.py +144 -0
  10. ratapi/examples/convert_rascal_project/Model_IIb.py +90 -0
  11. ratapi/examples/convert_rascal_project/__init__.py +1 -0
  12. ratapi/examples/convert_rascal_project/convert_rascal.py +49 -0
  13. ratapi/examples/data/D2O_spin_down.dat +100 -0
  14. ratapi/examples/data/D2O_spin_up.dat +101 -0
  15. ratapi/examples/data/DSPC_D2O.dat +82 -0
  16. ratapi/examples/data/DSPC_SMW.dat +82 -0
  17. ratapi/examples/data/H2O_spin_down.dat +102 -0
  18. ratapi/examples/data/H2O_spin_up.dat +102 -0
  19. ratapi/examples/data/__init__.py +1 -0
  20. ratapi/examples/data/c_PLP0016596.dat +146 -0
  21. ratapi/examples/data/c_PLP0016601.dat +97 -0
  22. ratapi/examples/data/c_PLP0016607.dat +104 -0
  23. ratapi/examples/data/d2o_background_data.dat +82 -0
  24. ratapi/examples/domains/__init__.py +1 -0
  25. ratapi/examples/domains/alloy_domains.py +34 -0
  26. ratapi/examples/domains/domains_XY_model.py +75 -0
  27. ratapi/examples/domains/domains_custom_XY.py +86 -0
  28. ratapi/examples/domains/domains_custom_layers.py +60 -0
  29. ratapi/examples/domains/domains_standard_layers.py +92 -0
  30. ratapi/examples/languages/__init__.py +1 -0
  31. ratapi/examples/languages/custom_bilayer.py +72 -0
  32. ratapi/examples/languages/run_custom_file_languages.py +41 -0
  33. ratapi/examples/languages/setup_problem.py +130 -0
  34. ratapi/examples/normal_reflectivity/DSPC_custom_XY.py +149 -0
  35. ratapi/examples/normal_reflectivity/DSPC_custom_layers.py +130 -0
  36. ratapi/examples/normal_reflectivity/DSPC_data_background.py +220 -0
  37. ratapi/examples/normal_reflectivity/DSPC_function_background.py +219 -0
  38. ratapi/examples/normal_reflectivity/DSPC_standard_layers.py +210 -0
  39. ratapi/examples/normal_reflectivity/__init__.py +1 -0
  40. ratapi/examples/normal_reflectivity/background_function.py +16 -0
  41. ratapi/examples/normal_reflectivity/custom_XY_DSPC.py +141 -0
  42. ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py +89 -0
  43. ratapi/inputs.py +603 -0
  44. ratapi/models.py +717 -0
  45. ratapi/outputs.py +821 -0
  46. ratapi/project.py +1091 -0
  47. ratapi/rat_core.cp315-win_amd64.pyd +0 -0
  48. ratapi/run.py +142 -0
  49. ratapi/utils/__init__.py +1 -0
  50. ratapi/utils/convert.py +597 -0
  51. ratapi/utils/custom_errors.py +40 -0
  52. ratapi/utils/enums.py +203 -0
  53. ratapi/utils/matlab.py +254 -0
  54. ratapi/utils/orso.py +247 -0
  55. ratapi/utils/plotting.py +1316 -0
  56. ratapi/wrappers.py +147 -0
  57. ratapi-0.0.0.dev16.dist-info/DELVEWHEEL +2 -0
  58. ratapi-0.0.0.dev16.dist-info/METADATA +61 -0
  59. ratapi-0.0.0.dev16.dist-info/RECORD +63 -0
  60. ratapi-0.0.0.dev16.dist-info/WHEEL +5 -0
  61. ratapi-0.0.0.dev16.dist-info/top_level.txt +1 -0
  62. ratapi.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
  63. ratapi.libs/vcomp140-f96f3a14d88d8846f31f3ab38a490304.dll +0 -0
ratapi/controls.py ADDED
@@ -0,0 +1,258 @@
1
+ """The Controls class for providing RAT algorithm settings."""
2
+
3
+ import contextlib
4
+ import os
5
+ import tempfile
6
+ import warnings
7
+ from pathlib import Path
8
+
9
+ import prettytable
10
+ from pydantic import (
11
+ BaseModel,
12
+ Field,
13
+ ValidationError,
14
+ ValidatorFunctionWrapHandler,
15
+ model_serializer,
16
+ model_validator,
17
+ )
18
+
19
+ from ratapi.utils.custom_errors import custom_pydantic_validation_error
20
+ from ratapi.utils.enums import BoundHandling, Display, Parallel, Procedures, Strategies
21
+
22
+ common_fields = [
23
+ "procedure",
24
+ "parallel",
25
+ "numSimulationPoints",
26
+ "resampleMinAngle",
27
+ "resampleNPoints",
28
+ "display",
29
+ ]
30
+ update_fields = ["updateFreq", "updatePlotFreq"]
31
+ fields = {
32
+ "calculate": common_fields,
33
+ "simplex": [*common_fields, "xTolerance", "funcTolerance", "maxFuncEvals", "maxIterations", *update_fields],
34
+ "de": [
35
+ *common_fields,
36
+ "populationSize",
37
+ "fWeight",
38
+ "crossoverProbability",
39
+ "strategy",
40
+ "targetValue",
41
+ "numGenerations",
42
+ *update_fields,
43
+ ],
44
+ "ns": [*common_fields, "nLive", "nMCMC", "propScale", "nsTolerance"],
45
+ "dream": [*common_fields, "nSamples", "nChains", "jumpProbability", "pUnitGamma", "boundHandling", "adaptPCR"],
46
+ }
47
+
48
+
49
+ class Controls(BaseModel, validate_assignment=True, extra="forbid", use_attribute_docstrings=True):
50
+ """The full set of controls parameters for all five procedures that are required for the compiled RAT code."""
51
+
52
+ # All Procedures
53
+ procedure: Procedures = Procedures.Calculate
54
+ """Which procedure RAT should execute. Can be 'calculate', 'simplex', 'de', 'ns', or 'dream'."""
55
+
56
+ parallel: Parallel = Parallel.Single
57
+ """How the calculation should be parallelised. Can be 'single', 'contrasts' or 'points'."""
58
+
59
+ numSimulationPoints: int = Field(500, ge=2)
60
+ """The number of points used for reflectivity simulations where no data is supplied."""
61
+
62
+ resampleMinAngle: float = Field(0.9, le=1, gt=0)
63
+ """The upper threshold on the angle between three sampled points for resampling, in units of radians over pi."""
64
+
65
+ resampleNPoints: int = Field(50, gt=0)
66
+ """The number of initial points to use for resampling."""
67
+
68
+ display: Display = Display.Iter
69
+ """How much RAT should print to the terminal. Can be 'off', 'iter', or 'final'."""
70
+
71
+ # Simplex
72
+ xTolerance: float = Field(1.0e-6, gt=0.0)
73
+ """[SIMPLEX] The termination tolerance for step size."""
74
+
75
+ funcTolerance: float = Field(1.0e-6, gt=0.0)
76
+ """[SIMPLEX] The termination tolerance for change in chi-squared."""
77
+
78
+ maxFuncEvals: int = Field(10000, gt=0)
79
+ """[SIMPLEX] The maximum number of function evaluations before the algorithm terminates."""
80
+
81
+ maxIterations: int = Field(1000, gt=0)
82
+ """[SIMPLEX] The maximum number of iterations before the algorithm terminates."""
83
+
84
+ # Simplex and DE
85
+ updateFreq: int = 1
86
+ """[SIMPLEX, DE] Number of iterations between printing progress updates to the terminal."""
87
+
88
+ updatePlotFreq: int = 20
89
+ """[SIMPLEX, DE] Number of iterations between updates to live plots."""
90
+
91
+ # DE
92
+ populationSize: int = Field(20, ge=1)
93
+ """[DE] The number of candidate solutions that exist at any time."""
94
+
95
+ fWeight: float = Field(0.5, gt=0.0)
96
+ """[DE] The step size for how different mutations are to their parents."""
97
+
98
+ crossoverProbability: float = Field(0.8, gt=0.0, lt=1.0)
99
+ """[DE] The probability of exchange of parameters between individuals at any iteration."""
100
+
101
+ strategy: Strategies = Strategies.RandomWithPerVectorDither
102
+ """[DE] The algorithm used to generate new candidates."""
103
+
104
+ targetValue: float = Field(1.0, ge=1.0)
105
+ """[DE] The value of chi-squared at which the algorithm will terminate."""
106
+
107
+ numGenerations: int = Field(500, ge=1)
108
+ """[DE] The maximum number of iterations before the algorithm terminates."""
109
+
110
+ # NS
111
+ nLive: int = Field(150, ge=1)
112
+ """[NS] The number of points to sample."""
113
+
114
+ nMCMC: int = Field(0, ge=0)
115
+ """[NS] If non-zero, an MCMC process with ``nMCMC`` chains will be used instead of MultiNest."""
116
+
117
+ propScale: float = Field(0.1, gt=0.0, lt=1.0)
118
+ """[NS] A scaling factor for the ellipsoid generated by MultiNest."""
119
+
120
+ nsTolerance: float = Field(0.1, ge=0.0)
121
+ """[NS] The tolerance threshold for when the algorithm should terminate."""
122
+
123
+ # Dream
124
+ nSamples: int = Field(20000, ge=0)
125
+ """[DREAM] The total number of function evaluations (number of algorithm generations times number of chains)."""
126
+
127
+ nChains: int = Field(10, gt=1)
128
+ """[DREAM] The number of Markov chains to use in the algorithm."""
129
+
130
+ jumpProbability: float = Field(0.5, gt=0.0, lt=1.0)
131
+ """[DREAM] The probability range for the size of jumps in sampling. Larger values mean more variable jumps."""
132
+
133
+ pUnitGamma: float = Field(0.2, gt=0.0, lt=1.0)
134
+ """[DREAM] The probability that the scaling-down factor of jumps will be ignored and a larger jump will be taken."""
135
+
136
+ boundHandling: BoundHandling = BoundHandling.Reflect
137
+ """[DREAM] How steps past the space boundaries should be handled. Can be 'off', 'reflect', 'bound', or 'fold'."""
138
+
139
+ adaptPCR: bool = True
140
+ """[DREAM] Whether the crossover probability for differential evolution should be adapted during the run."""
141
+
142
+ # Private field for IPC file
143
+ _IPCFilePath: str = ""
144
+
145
+ @model_validator(mode="wrap")
146
+ def warn_setting_incorrect_properties(self, handler: ValidatorFunctionWrapHandler) -> "Controls":
147
+ """Raise a warning if the user sets fields that apply to other procedures."""
148
+ model_input = self
149
+ try:
150
+ input_dict = model_input.__dict__
151
+ except AttributeError:
152
+ input_dict = model_input
153
+
154
+ validated_self = None
155
+ try:
156
+ validated_self = handler(self)
157
+ except ValidationError as exc:
158
+ procedure = input_dict.get("procedure", Procedures.Calculate)
159
+ custom_error_msgs = {
160
+ "extra_forbidden": f'Extra inputs are not permitted. The fields for the "{procedure}"'
161
+ f" controls procedure are:\n "
162
+ f"{', '.join(fields.get('procedure', []))}\n",
163
+ }
164
+ custom_error_list = custom_pydantic_validation_error(exc.errors(include_url=False), custom_error_msgs)
165
+ raise ValidationError.from_exception_data(exc.title, custom_error_list, hide_input=True) from None
166
+
167
+ if isinstance(model_input, validated_self.__class__):
168
+ # This is for changing fields in a defined model
169
+ changed_fields = [key for key in input_dict if input_dict[key] != validated_self.__dict__[key]]
170
+ elif isinstance(model_input, dict):
171
+ # This is for a newly-defined model
172
+ changed_fields = input_dict.keys()
173
+ else:
174
+ raise ValueError('The input to the "Controls" model is invalid.')
175
+
176
+ new_procedure = validated_self.procedure
177
+ allowed_fields = fields[new_procedure]
178
+ for field in changed_fields:
179
+ if field not in allowed_fields:
180
+ incorrect_procedures = [key for (key, value) in fields.items() if field in value]
181
+ warnings.warn(
182
+ f'\nThe current controls procedure is "{new_procedure}", but the property'
183
+ f' "{field}" applies instead to the {", ".join(incorrect_procedures)} procedure.\n\n'
184
+ f' The fields for the "{new_procedure}" controls procedure are:\n'
185
+ f" {', '.join(fields[new_procedure])}\n",
186
+ stacklevel=2,
187
+ )
188
+
189
+ return validated_self
190
+
191
+ @model_serializer
192
+ def serialize(self):
193
+ """Filter fields so only those applying to the chosen procedure are serialized."""
194
+ return {model_field: getattr(self, model_field) for model_field in fields[self.procedure]}
195
+
196
+ def __repr__(self) -> str:
197
+ fields_repr = ", ".join(repr(v) if a is None else f"{a}={v!r}" for a, v in self.model_dump().items())
198
+ return f"{self.__repr_name__()}({fields_repr})"
199
+
200
+ def __str__(self) -> str:
201
+ table = prettytable.PrettyTable()
202
+ table.field_names = ["Property", "Value"]
203
+ table.add_rows([[k, v] for k, v in self.model_dump().items()])
204
+ return table.get_string()
205
+
206
+ def initialise_IPC(self):
207
+ """Set up the inter-process communication file."""
208
+ IPC_obj, self._IPCFilePath = tempfile.mkstemp()
209
+ os.write(IPC_obj, b"\x00")
210
+ os.close(IPC_obj)
211
+ return None
212
+
213
+ def sendStopEvent(self):
214
+ """Send the stop event via the inter-process communication file.
215
+
216
+ Warnings
217
+ --------
218
+ UserWarning
219
+ Raised if we try to delete an IPC file that was not initialised.
220
+
221
+ """
222
+ if os.path.isfile(self._IPCFilePath):
223
+ with open(self._IPCFilePath, "wb") as f:
224
+ f.write(b"\x01")
225
+ else:
226
+ warnings.warn("An IPC file was not initialised.", UserWarning, stacklevel=2)
227
+ return None
228
+
229
+ def delete_IPC(self):
230
+ """Delete the inter-process communication file."""
231
+ with contextlib.suppress(FileNotFoundError):
232
+ os.remove(self._IPCFilePath)
233
+ self._IPCFilePath = ""
234
+ return None
235
+
236
+ def save(self, filepath: str | Path = "./controls.json"):
237
+ """Save a controls object to a JSON file.
238
+
239
+ Parameters
240
+ ----------
241
+ filepath : str or Path
242
+ The path to where the controls file will be written.
243
+ """
244
+ filepath = Path(filepath).with_suffix(".json")
245
+ filepath.write_text(self.model_dump_json())
246
+
247
+ @classmethod
248
+ def load(cls, path: str | Path) -> "Controls":
249
+ """Load a controls object from file.
250
+
251
+ Parameters
252
+ ----------
253
+ path : str or Path
254
+ The path to the controls object file.
255
+
256
+ """
257
+ file = Path(path)
258
+ return cls.model_validate_json(file.read_text())
Binary file
ratapi/events.py ADDED
@@ -0,0 +1,88 @@
1
+ """Hooks for connecting to run callback events."""
2
+
3
+ import os
4
+ from collections.abc import Callable
5
+
6
+ from ratapi.rat_core import EventBridge, EventTypes, PlotEventData, ProgressEventData
7
+
8
+
9
+ def notify(event_type: EventTypes, data: str | PlotEventData | ProgressEventData) -> None:
10
+ """Call registered callbacks with data when event type has been triggered.
11
+
12
+ Parameters
13
+ ----------
14
+ event_type : EventTypes
15
+ The event type that was triggered.
16
+ data : str or PlotEventData or ProgressEventData
17
+ The data sent by the event. The message event data is a string.
18
+
19
+ """
20
+ callbacks = __event_callbacks[event_type]
21
+ for callback in callbacks:
22
+ callback(data)
23
+
24
+
25
+ def get_event_callback(event_type: EventTypes) -> list[Callable[[str | PlotEventData | ProgressEventData], None]]:
26
+ """Return all callbacks registered for the given event type.
27
+
28
+ Parameters
29
+ ----------
30
+ event_type : EventTypes
31
+ The event type.
32
+
33
+ Returns
34
+ -------
35
+ callback : Callable[[Union[str, PlotEventData, ProgressEventData]], None]
36
+ The callback for the event type.
37
+
38
+ """
39
+ return list(__event_callbacks[event_type])
40
+
41
+
42
+ def register(event_type: EventTypes, callback: Callable[[str | PlotEventData | ProgressEventData], None]) -> None:
43
+ """Register a new callback for the event type.
44
+
45
+ Parameters
46
+ ----------
47
+ event_type : EventTypes
48
+ The event type to register.
49
+ callback : Callable[[Union[str, PlotEventData, ProgressEventData]], None]
50
+ The callback for when the event is triggered.
51
+
52
+ """
53
+ if not isinstance(event_type, EventTypes):
54
+ raise ValueError("event_type must be a events.EventTypes enum")
55
+
56
+ if len(__event_callbacks[event_type]) == 0:
57
+ __event_impl.register(event_type)
58
+ __event_callbacks[event_type].add(callback)
59
+
60
+
61
+ def clear(key=None, callback=None) -> None:
62
+ """Clear all event callbacks or specific callback.
63
+
64
+ Parameters
65
+ ----------
66
+ key : EventTypes, optional
67
+ The event type of the callback to clear if given.
68
+ callback : Callable[[Union[str, PlotEventData, ProgressEventData]], None], optional
69
+ A callback for an event which will be cleared if given.
70
+
71
+ """
72
+ if key is None and callback is None:
73
+ for key in __event_callbacks:
74
+ __event_callbacks[key] = set()
75
+ elif key is not None and callback is not None:
76
+ __event_callbacks[key].remove(callback)
77
+
78
+ for value in __event_callbacks.values():
79
+ if value:
80
+ break
81
+ else:
82
+ __event_impl.clear()
83
+
84
+
85
+ dir_path = os.path.dirname(os.path.realpath(__file__))
86
+ os.environ["RAT_PATH"] = os.path.join(dir_path, "")
87
+ __event_impl = EventBridge(notify)
88
+ __event_callbacks = {EventTypes.Message: set(), EventTypes.Plot: set(), EventTypes.Progress: set()}
@@ -0,0 +1,25 @@
1
+ """Usage examples for the Python RAT API."""
2
+
3
+ from ratapi.examples.absorption.absorption import absorption
4
+ from ratapi.examples.convert_rascal_project.convert_rascal import convert_rascal
5
+ from ratapi.examples.domains.domains_custom_layers import domains_custom_layers
6
+ from ratapi.examples.domains.domains_custom_XY import domains_custom_XY
7
+ from ratapi.examples.domains.domains_standard_layers import domains_standard_layers
8
+ from ratapi.examples.normal_reflectivity.DSPC_custom_layers import DSPC_custom_layers
9
+ from ratapi.examples.normal_reflectivity.DSPC_custom_XY import DSPC_custom_XY
10
+ from ratapi.examples.normal_reflectivity.DSPC_data_background import DSPC_data_background
11
+ from ratapi.examples.normal_reflectivity.DSPC_function_background import DSPC_function_background
12
+ from ratapi.examples.normal_reflectivity.DSPC_standard_layers import DSPC_standard_layers
13
+
14
+ __all__ = [
15
+ "absorption",
16
+ "domains_custom_layers",
17
+ "domains_custom_XY",
18
+ "domains_standard_layers",
19
+ "DSPC_custom_layers",
20
+ "DSPC_custom_XY",
21
+ "DSPC_standard_layers",
22
+ "DSPC_data_background",
23
+ "DSPC_function_background",
24
+ "convert_rascal",
25
+ ]
@@ -0,0 +1 @@
1
+ """An example of using absorption in a RAT project."""
@@ -0,0 +1,172 @@
1
+ """An example for using absorption in RAT."""
2
+
3
+ import pathlib
4
+
5
+ import numpy as np
6
+
7
+ import ratapi as RAT
8
+
9
+
10
+ def absorption():
11
+ """Run a custom layers model including absorption.
12
+
13
+ RAT allows the use of an imaginary, as well as real part of the SLD.
14
+ The effect of this is usually seen below the critical edge, and must sometimes be accounted for.
15
+
16
+ This is an example of a Custom Layers project using absorption. used here is Custom Layers.
17
+ It analyses a bilayer sample on a permalloy / gold substrate,
18
+ measured using polarised neutrons, against D2O and H2O, leading to 4 contrasts in total.
19
+ Absorption (i.e. imaginary SLD) is defined for Gold and the Permalloy,
20
+ to account for non-flat data below the critical edge.
21
+ """
22
+ problem = RAT.Project(
23
+ name="Absorption example",
24
+ calculation="normal",
25
+ model="custom layers",
26
+ geometry="substrate/liquid",
27
+ absorption=True,
28
+ )
29
+
30
+ # Add the required parameters (substrate roughness is already there by default)
31
+ problem.parameters.append(name="Alloy Thickness", min=100.0, value=135.6, max=200.0, fit=True)
32
+ problem.parameters.append(name="Alloy SLD up", min=6.0e-6, value=9.87e-6, max=1.2e-5, fit=True)
33
+ problem.parameters.append(name="Alloy SLD imaginary up", min=1.0e-9, value=4.87e-8, max=1.0e-7, fit=True)
34
+ problem.parameters.append(name="Alloy SLD down", min=6.0e-6, value=7.05e-6, max=1.3e-5, fit=True)
35
+ problem.parameters.append(name="Alloy SLD imaginary down", min=1.0e-9, value=4.87e-8, max=1.0e-7, fit=True)
36
+ problem.parameters.append(name="Alloy Roughness", min=2.0, value=5.71, max=10.0, fit=True)
37
+ problem.parameters.append(name="Gold Thickness", min=100.0, value=154.7, max=200.0, fit=True)
38
+ problem.parameters.append(name="Gold Roughness", min=0.1, value=5.42, max=10.0, fit=True)
39
+ problem.parameters.append(name="Gold SLD", min=4.0e-6, value=4.49e-6, max=5.0e-6, fit=True)
40
+ problem.parameters.append(name="Gold SLD imaginary", min=1.0e-9, value=4.20e-8, max=1.0e-7, fit=True)
41
+
42
+ problem.parameters.append(name="Thiol APM", min=40.0, value=56.27, max=100.0, fit=True)
43
+ problem.parameters.append(name="Thiol Head Hydration", min=20.0, value=30.0, max=50.0, fit=True)
44
+ problem.parameters.append(name="Thiol Coverage", min=0.5, value=0.9, max=1.0, fit=True)
45
+
46
+ problem.parameters.append(name="CW Thickness", min=1.0, value=12.87, max=25.0, fit=True)
47
+ problem.parameters.append(name="Bilayer APM", min=48.0, value=65.86, max=90.0, fit=True)
48
+ problem.parameters.append(name="Bilayer Head Hydration", min=20.0, value=30.0, max=50.0, fit=True)
49
+ problem.parameters.append(name="Bilayer Roughness", min=1.0, value=3.87, max=10.0, fit=True)
50
+ problem.parameters.append(name="Bilayer Coverage", min=0.5, value=0.94, max=1.0, fit=True)
51
+
52
+ # Change the existing Bulk In parameter to be Silicon
53
+ problem.bulk_in.set_fields(0, name="Silicon", min=2.0e-6, value=2.073e-6, max=2.1e-6)
54
+
55
+ # We need 2 bulk outs - D2O and H2O
56
+ problem.bulk_out.set_fields(0, name="D2O", min=5.8e-06, value=6.21e-06, max=6.35e-06, fit=True)
57
+ problem.bulk_out.append(name="H2O", min=-5.6e-07, value=-3.15e-07, max=0.0, fit=True)
58
+
59
+ # Use a different scalefactor for each dataset
60
+ del problem.scalefactors[0]
61
+ problem.scalefactors.append(name="Scalefactor 1", min=0.5, value=1, max=1.5, fit=True)
62
+ problem.scalefactors.append(name="Scalefactor 2", min=0.5, value=1, max=1.5, fit=True)
63
+ problem.scalefactors.append(name="Scalefactor 3", min=0.5, value=1, max=1.5, fit=True)
64
+ problem.scalefactors.append(name="Scalefactor 4", min=0.5, value=1, max=1.5, fit=True)
65
+
66
+ # Similarly, use an individual background for each dataset
67
+ del problem.backgrounds[0]
68
+ del problem.background_parameters[0]
69
+
70
+ problem.background_parameters.append(
71
+ name="Background parameter 1", min=5.0e-08, value=7.88e-06, max=9.0e-05, fit=True
72
+ )
73
+ problem.background_parameters.append(
74
+ name="Background parameter 2", min=1.0e-08, value=5.46e-06, max=9.0e-05, fit=True
75
+ )
76
+ problem.background_parameters.append(
77
+ name="Background parameter 3", min=1.0e-06, value=9.01e-06, max=9.0e-05, fit=True
78
+ )
79
+ problem.background_parameters.append(
80
+ name="Background parameter 4", min=1.0e-06, value=5.61e-06, max=9.0e-05, fit=True
81
+ )
82
+
83
+ problem.backgrounds.append(name="Background 1", type="constant", source="Background parameter 1")
84
+ problem.backgrounds.append(name="Background 2", type="constant", source="Background parameter 2")
85
+ problem.backgrounds.append(name="Background 3", type="constant", source="Background parameter 3")
86
+ problem.backgrounds.append(name="Background 4", type="constant", source="Background parameter 4")
87
+
88
+ # Make the resolution fittable
89
+ problem.resolution_parameters.set_fields(0, fit=True)
90
+
91
+ # Now add the data we need
92
+ data_path = pathlib.Path(__file__).parents[1] / "data"
93
+
94
+ data_1 = np.loadtxt(data_path / "D2O_spin_down.dat")
95
+ problem.data.append(name="D2O_dn", data=data_1)
96
+
97
+ data_2 = np.loadtxt(data_path / "D2O_spin_up.dat")
98
+ problem.data.append(name="D2O_up", data=data_2)
99
+
100
+ data_3 = np.loadtxt(data_path / "H2O_spin_down.dat")
101
+ problem.data.append(name="H2O_dn", data=data_3)
102
+
103
+ data_4 = np.loadtxt(data_path / "H2O_spin_up.dat")
104
+ problem.data.append(name="H2O_up", data=data_4)
105
+
106
+ # Add the custom file
107
+ problem.custom_files.append(
108
+ name="DPPC absorption",
109
+ filename="volume_thiol_bilayer.py",
110
+ language="python",
111
+ path=pathlib.Path(__file__).parent,
112
+ )
113
+
114
+ # Finally add the contrasts
115
+ problem.contrasts.append(
116
+ name="D2O Down",
117
+ data="D2O_dn",
118
+ background="Background 1",
119
+ bulk_in="Silicon",
120
+ bulk_out="D2O",
121
+ scalefactor="Scalefactor 1",
122
+ resolution="Resolution 1",
123
+ resample=True,
124
+ model=["DPPC absorption"],
125
+ )
126
+
127
+ problem.contrasts.append(
128
+ name="D2O Up",
129
+ data="D2O_up",
130
+ background="Background 2",
131
+ bulk_in="Silicon",
132
+ bulk_out="D2O",
133
+ scalefactor="Scalefactor 2",
134
+ resolution="Resolution 1",
135
+ resample=True,
136
+ model=["DPPC absorption"],
137
+ )
138
+
139
+ problem.contrasts.append(
140
+ name="H2O Down",
141
+ data="H2O_dn",
142
+ background="Background 3",
143
+ bulk_in="Silicon",
144
+ bulk_out="H2O",
145
+ scalefactor="Scalefactor 3",
146
+ resolution="Resolution 1",
147
+ resample=True,
148
+ model=["DPPC absorption"],
149
+ )
150
+
151
+ problem.contrasts.append(
152
+ name="H2O Up",
153
+ data="H2O_up",
154
+ background="Background 4",
155
+ bulk_in="Silicon",
156
+ bulk_out="H2O",
157
+ scalefactor="Scalefactor 4",
158
+ resolution="Resolution 1",
159
+ resample=True,
160
+ model=["DPPC absorption"],
161
+ )
162
+
163
+ # Now make a controls block and run the code
164
+ controls = RAT.Controls(parallel="contrasts", resampleNPoints=150)
165
+ problem, results = RAT.run(problem, controls)
166
+
167
+ return problem, results
168
+
169
+
170
+ if __name__ == "__main__":
171
+ problem, results = absorption()
172
+ RAT.plotting.plot_ref_sld(problem, results, True)