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.
- ratapi/__init__.py +42 -0
- ratapi/classlist.py +620 -0
- ratapi/controls.py +258 -0
- ratapi/eventManager.dll +0 -0
- ratapi/events.py +88 -0
- ratapi/examples/__init__.py +25 -0
- ratapi/examples/absorption/__init__.py +1 -0
- ratapi/examples/absorption/absorption.py +172 -0
- ratapi/examples/absorption/volume_thiol_bilayer.py +144 -0
- ratapi/examples/convert_rascal_project/Model_IIb.py +90 -0
- ratapi/examples/convert_rascal_project/__init__.py +1 -0
- ratapi/examples/convert_rascal_project/convert_rascal.py +49 -0
- ratapi/examples/data/D2O_spin_down.dat +100 -0
- ratapi/examples/data/D2O_spin_up.dat +101 -0
- ratapi/examples/data/DSPC_D2O.dat +82 -0
- ratapi/examples/data/DSPC_SMW.dat +82 -0
- ratapi/examples/data/H2O_spin_down.dat +102 -0
- ratapi/examples/data/H2O_spin_up.dat +102 -0
- ratapi/examples/data/__init__.py +1 -0
- ratapi/examples/data/c_PLP0016596.dat +146 -0
- ratapi/examples/data/c_PLP0016601.dat +97 -0
- ratapi/examples/data/c_PLP0016607.dat +104 -0
- ratapi/examples/data/d2o_background_data.dat +82 -0
- ratapi/examples/domains/__init__.py +1 -0
- ratapi/examples/domains/alloy_domains.py +34 -0
- ratapi/examples/domains/domains_XY_model.py +75 -0
- ratapi/examples/domains/domains_custom_XY.py +86 -0
- ratapi/examples/domains/domains_custom_layers.py +60 -0
- ratapi/examples/domains/domains_standard_layers.py +92 -0
- ratapi/examples/languages/__init__.py +1 -0
- ratapi/examples/languages/custom_bilayer.py +72 -0
- ratapi/examples/languages/run_custom_file_languages.py +41 -0
- ratapi/examples/languages/setup_problem.py +130 -0
- ratapi/examples/normal_reflectivity/DSPC_custom_XY.py +149 -0
- ratapi/examples/normal_reflectivity/DSPC_custom_layers.py +130 -0
- ratapi/examples/normal_reflectivity/DSPC_data_background.py +220 -0
- ratapi/examples/normal_reflectivity/DSPC_function_background.py +219 -0
- ratapi/examples/normal_reflectivity/DSPC_standard_layers.py +210 -0
- ratapi/examples/normal_reflectivity/__init__.py +1 -0
- ratapi/examples/normal_reflectivity/background_function.py +16 -0
- ratapi/examples/normal_reflectivity/custom_XY_DSPC.py +141 -0
- ratapi/examples/normal_reflectivity/custom_bilayer_DSPC.py +89 -0
- ratapi/inputs.py +603 -0
- ratapi/models.py +717 -0
- ratapi/outputs.py +821 -0
- ratapi/project.py +1091 -0
- ratapi/rat_core.cp315-win_amd64.pyd +0 -0
- ratapi/run.py +142 -0
- ratapi/utils/__init__.py +1 -0
- ratapi/utils/convert.py +597 -0
- ratapi/utils/custom_errors.py +40 -0
- ratapi/utils/enums.py +203 -0
- ratapi/utils/matlab.py +254 -0
- ratapi/utils/orso.py +247 -0
- ratapi/utils/plotting.py +1316 -0
- ratapi/wrappers.py +147 -0
- ratapi-0.0.0.dev16.dist-info/DELVEWHEEL +2 -0
- ratapi-0.0.0.dev16.dist-info/METADATA +61 -0
- ratapi-0.0.0.dev16.dist-info/RECORD +63 -0
- ratapi-0.0.0.dev16.dist-info/WHEEL +5 -0
- ratapi-0.0.0.dev16.dist-info/top_level.txt +1 -0
- ratapi.libs/msvcp140-a4c2229bdc2a2a630acdc095b4d86008.dll +0 -0
- ratapi.libs/vcomp140-f96f3a14d88d8846f31f3ab38a490304.dll +0 -0
ratapi/inputs.py
ADDED
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
"""Converts python models to the necessary inputs for the compiled RAT code."""
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import os
|
|
5
|
+
import pathlib
|
|
6
|
+
import warnings
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
import ratapi
|
|
12
|
+
import ratapi.wrappers
|
|
13
|
+
from ratapi.rat_core import Checks, Control, NameStore, ProblemDefinition
|
|
14
|
+
from ratapi.utils.enums import Calculations, Languages, LayerModels, Procedures, TypeOptions
|
|
15
|
+
|
|
16
|
+
parameter_field = {
|
|
17
|
+
"parameters": "params",
|
|
18
|
+
"bulk_in": "bulkIns",
|
|
19
|
+
"bulk_out": "bulkOuts",
|
|
20
|
+
"scalefactors": "scalefactors",
|
|
21
|
+
"domain_ratios": "domainRatios",
|
|
22
|
+
"background_parameters": "backgroundParams",
|
|
23
|
+
"resolution_parameters": "resolutionParams",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_python_handle(file_name: str, function_name: str, path: str | pathlib.Path = "") -> Callable:
|
|
28
|
+
"""Get the function handle from a function defined in a python module located anywhere within the filesystem.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
file_name : str
|
|
33
|
+
The name of the file containing the function of interest.
|
|
34
|
+
function_name : str
|
|
35
|
+
The name of the function we wish to obtain the handle for within the module.
|
|
36
|
+
path : str
|
|
37
|
+
The path to the file containing the function (default is "", which represent the working directory).
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
handle : Callable
|
|
42
|
+
The handle of the function defined in the python module file.
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
spec = importlib.util.spec_from_file_location(pathlib.Path(file_name).stem, os.path.join(path, file_name))
|
|
46
|
+
custom_module = importlib.util.module_from_spec(spec)
|
|
47
|
+
spec.loader.exec_module(custom_module)
|
|
48
|
+
handle = getattr(custom_module, function_name)
|
|
49
|
+
return handle
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class FileHandles:
|
|
53
|
+
"""Class to defer creation of custom file handles.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
files : ClassList[CustomFile]
|
|
58
|
+
A list of custom file models.
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, files=None):
|
|
63
|
+
self.index = 0
|
|
64
|
+
self.files = [] if files is None else [file.model_dump() for file in files]
|
|
65
|
+
|
|
66
|
+
def __iter__(self):
|
|
67
|
+
self.index = 0
|
|
68
|
+
return self
|
|
69
|
+
|
|
70
|
+
def get_handle(self, index: int):
|
|
71
|
+
"""Return file handle for a given custom file.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
index : int
|
|
76
|
+
The index of the custom file.
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
custom_file = self.files[index]
|
|
80
|
+
full_path = os.path.join(custom_file["path"], custom_file["filename"])
|
|
81
|
+
|
|
82
|
+
if not os.path.isfile(full_path):
|
|
83
|
+
raise FileNotFoundError(f"The custom file ({custom_file['name']}) does not have a valid path.")
|
|
84
|
+
|
|
85
|
+
if not custom_file["function_name"] and custom_file["language"] != Languages.Matlab:
|
|
86
|
+
raise ValueError(f"The custom file ({custom_file['name']}) does not have a valid function name.")
|
|
87
|
+
|
|
88
|
+
if custom_file["language"] == Languages.Python:
|
|
89
|
+
file_handle = get_python_handle(custom_file["filename"], custom_file["function_name"], custom_file["path"])
|
|
90
|
+
elif custom_file["language"] == Languages.Matlab:
|
|
91
|
+
file_handle = ratapi.wrappers.MatlabWrapper(full_path).getHandle()
|
|
92
|
+
elif custom_file["language"] == Languages.Cpp:
|
|
93
|
+
file_handle = ratapi.wrappers.DylibWrapper(full_path, custom_file["function_name"]).getHandle()
|
|
94
|
+
|
|
95
|
+
return file_handle
|
|
96
|
+
|
|
97
|
+
def copy(self) -> "FileHandles":
|
|
98
|
+
"""Create a copy of the FileHandles object.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
FileHandles
|
|
103
|
+
The copy of this FileHandles object.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
handles = FileHandles()
|
|
107
|
+
handles.files = [file.copy() for file in self.files]
|
|
108
|
+
|
|
109
|
+
return handles
|
|
110
|
+
|
|
111
|
+
def __next__(self):
|
|
112
|
+
if self.index < len(self.files):
|
|
113
|
+
custom_file = self.get_handle(self.index)
|
|
114
|
+
self.index += 1
|
|
115
|
+
return custom_file
|
|
116
|
+
else:
|
|
117
|
+
raise StopIteration
|
|
118
|
+
|
|
119
|
+
def __len__(self):
|
|
120
|
+
return len(self.files)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def make_input(project: ratapi.Project, controls: ratapi.Controls) -> tuple[ProblemDefinition, Control]:
|
|
124
|
+
"""Construct the inputs required for the compiled RAT code using the data defined in the input project and controls.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
project : RAT.Project
|
|
129
|
+
The project model, which defines the physical system under study.
|
|
130
|
+
controls : RAT.Controls
|
|
131
|
+
The controls model, which defines algorithmic properties.
|
|
132
|
+
|
|
133
|
+
Returns
|
|
134
|
+
-------
|
|
135
|
+
problem : RAT.rat_core.ProblemDefinition
|
|
136
|
+
The problem input used in the compiled RAT code.
|
|
137
|
+
cpp_controls : RAT.rat_core.Control
|
|
138
|
+
The controls object used in the compiled RAT code.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
problem = make_problem(project, controls.procedure != Procedures.Calculate)
|
|
142
|
+
cpp_controls = make_controls(controls)
|
|
143
|
+
|
|
144
|
+
return problem, cpp_controls
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def make_problem(project: ratapi.Project, validate_range: bool = False) -> ProblemDefinition:
|
|
148
|
+
"""Construct the problem input required for the compiled RAT code.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
project : RAT.Project
|
|
153
|
+
The project model, which defines the physical system under study.
|
|
154
|
+
validate_range : bool, default True
|
|
155
|
+
Whether parameter range should be validated.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
problem : RAT.rat_core.ProblemDefinition
|
|
160
|
+
The problem input used in the compiled RAT code.
|
|
161
|
+
|
|
162
|
+
"""
|
|
163
|
+
prior_id = {"uniform": 1, "gaussian": 2, "jeffreys": 3}
|
|
164
|
+
|
|
165
|
+
# Ensure all contrast fields are properly defined
|
|
166
|
+
for contrast in project.contrasts:
|
|
167
|
+
contrast_fields = ["data", "background", "bulk_in", "bulk_out", "scalefactor", "resolution"]
|
|
168
|
+
|
|
169
|
+
if project.calculation == Calculations.Domains:
|
|
170
|
+
contrast_fields.append("domain_ratio")
|
|
171
|
+
|
|
172
|
+
for field in contrast_fields:
|
|
173
|
+
if getattr(contrast, field) == "":
|
|
174
|
+
raise ValueError(
|
|
175
|
+
f'In the input project, the "{field}" field of contrast "{contrast.name}" does not have a '
|
|
176
|
+
f"value defined. A value must be supplied before running the project."
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
# Ensure backgrounds and resolutions have a source defined
|
|
180
|
+
background = project.backgrounds[contrast.background]
|
|
181
|
+
resolution = project.resolutions[contrast.resolution]
|
|
182
|
+
if background.source == "":
|
|
183
|
+
raise ValueError(
|
|
184
|
+
f"All backgrounds must have a source defined. For a {background.type} type background, "
|
|
185
|
+
f"the source must be defined in "
|
|
186
|
+
f'"{ratapi.project.values_defined_in[f"backgrounds.{background.type}.source"]}"'
|
|
187
|
+
)
|
|
188
|
+
if resolution.source == "" and resolution.type != TypeOptions.Data:
|
|
189
|
+
raise ValueError(
|
|
190
|
+
f"Constant resolutions must have a source defined. The source must be defined in "
|
|
191
|
+
f'"{ratapi.project.values_defined_in[f"resolutions.{resolution.type}.source"]}"'
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# Set contrast parameters according to model type
|
|
195
|
+
if project.model == LayerModels.StandardLayers:
|
|
196
|
+
if project.calculation == Calculations.Domains:
|
|
197
|
+
contrast_models = [
|
|
198
|
+
[project.domain_contrasts.index(domain_contrast, True) for domain_contrast in contrast.model]
|
|
199
|
+
for contrast in project.contrasts
|
|
200
|
+
]
|
|
201
|
+
else:
|
|
202
|
+
contrast_models = [
|
|
203
|
+
[project.layers.index(layer, True) for layer in contrast.model] for contrast in project.contrasts
|
|
204
|
+
]
|
|
205
|
+
else:
|
|
206
|
+
contrast_models = [[]] * len(project.contrasts)
|
|
207
|
+
|
|
208
|
+
# Set contrast parameters according to model type
|
|
209
|
+
if project.model == LayerModels.StandardLayers:
|
|
210
|
+
contrast_custom_files = [float("NaN")] * len(project.contrasts)
|
|
211
|
+
else:
|
|
212
|
+
contrast_custom_files = [project.custom_files.index(contrast.model[0], True) for contrast in project.contrasts]
|
|
213
|
+
|
|
214
|
+
# Get details of defined layers
|
|
215
|
+
layer_details = get_layer_details(project)
|
|
216
|
+
|
|
217
|
+
contrast_background_params = []
|
|
218
|
+
contrast_background_types = []
|
|
219
|
+
all_data = []
|
|
220
|
+
data_limits = []
|
|
221
|
+
simulation_limits = []
|
|
222
|
+
contrast_resolution_params = []
|
|
223
|
+
contrast_resolution_types = []
|
|
224
|
+
|
|
225
|
+
# Set data, background and resolution for each contrast
|
|
226
|
+
for contrast in project.contrasts:
|
|
227
|
+
# Set data
|
|
228
|
+
data_index = project.data.index(contrast.data)
|
|
229
|
+
data = project.data[data_index].data
|
|
230
|
+
data_range = project.data[data_index].data_range
|
|
231
|
+
simulation_range = project.data[data_index].simulation_range
|
|
232
|
+
|
|
233
|
+
if data_range:
|
|
234
|
+
data_limits.append(data_range)
|
|
235
|
+
else:
|
|
236
|
+
data_limits.append([0.0, 0.0])
|
|
237
|
+
|
|
238
|
+
if simulation_range:
|
|
239
|
+
simulation_limits.append(simulation_range)
|
|
240
|
+
else:
|
|
241
|
+
simulation_limits.append([0.0, 0.0])
|
|
242
|
+
|
|
243
|
+
# Set background parameters
|
|
244
|
+
background = project.backgrounds[contrast.background]
|
|
245
|
+
contrast_background_types.append(background.type)
|
|
246
|
+
contrast_background_param = []
|
|
247
|
+
|
|
248
|
+
if background.type == TypeOptions.Data:
|
|
249
|
+
# Background data is appended to contrast data so empty index
|
|
250
|
+
if background.value_1 != "":
|
|
251
|
+
contrast_background_param.append(project.background_parameters.index(background.value_1))
|
|
252
|
+
# If we are using a data background, we add the background data to the contrast data
|
|
253
|
+
data = append_data_background(data, project.data[background.source].data)
|
|
254
|
+
|
|
255
|
+
elif background.type == TypeOptions.Function:
|
|
256
|
+
contrast_background_param.append(project.custom_files.index(background.source, True))
|
|
257
|
+
contrast_background_param.extend(
|
|
258
|
+
[
|
|
259
|
+
project.background_parameters.index(value, True)
|
|
260
|
+
for value in [
|
|
261
|
+
background.value_1,
|
|
262
|
+
background.value_2,
|
|
263
|
+
background.value_3,
|
|
264
|
+
background.value_4,
|
|
265
|
+
background.value_5,
|
|
266
|
+
]
|
|
267
|
+
if value != ""
|
|
268
|
+
]
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
else:
|
|
272
|
+
contrast_background_param.append(project.background_parameters.index(background.source, True))
|
|
273
|
+
|
|
274
|
+
contrast_background_params.append(contrast_background_param)
|
|
275
|
+
|
|
276
|
+
# Set resolution parameters
|
|
277
|
+
resolution = project.resolutions[contrast.resolution]
|
|
278
|
+
contrast_resolution_types.append(resolution.type)
|
|
279
|
+
contrast_resolution_param = []
|
|
280
|
+
if resolution.type == TypeOptions.Function:
|
|
281
|
+
contrast_resolution_param.append(project.custom_files.index(resolution.source, True))
|
|
282
|
+
contrast_resolution_param.extend(
|
|
283
|
+
[
|
|
284
|
+
project.resolution_parameters.index(value, True)
|
|
285
|
+
for value in [
|
|
286
|
+
resolution.value_1,
|
|
287
|
+
resolution.value_2,
|
|
288
|
+
resolution.value_3,
|
|
289
|
+
resolution.value_4,
|
|
290
|
+
resolution.value_5,
|
|
291
|
+
]
|
|
292
|
+
if value != ""
|
|
293
|
+
]
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
elif resolution.type == TypeOptions.Constant:
|
|
297
|
+
contrast_resolution_param.append(project.resolution_parameters.index(resolution.source, True))
|
|
298
|
+
|
|
299
|
+
contrast_resolution_params.append(contrast_resolution_param)
|
|
300
|
+
|
|
301
|
+
# Contrast data has exactly six columns to include background data if relevant
|
|
302
|
+
all_data.append(np.column_stack((data, np.zeros((data.shape[0], 6 - data.shape[1])))))
|
|
303
|
+
|
|
304
|
+
problem = ProblemDefinition()
|
|
305
|
+
|
|
306
|
+
problem.TF = project.calculation
|
|
307
|
+
problem.resample = make_resample(project)
|
|
308
|
+
problem.data = all_data
|
|
309
|
+
problem.dataPresent = make_data_present(project)
|
|
310
|
+
problem.dataLimits = data_limits
|
|
311
|
+
problem.simulationLimits = simulation_limits
|
|
312
|
+
problem.numberOfContrasts = len(project.contrasts)
|
|
313
|
+
problem.geometry = project.geometry
|
|
314
|
+
problem.useImaginary = project.absorption
|
|
315
|
+
problem.repeatLayers = [contrast.repeat_layers for contrast in project.contrasts]
|
|
316
|
+
problem.contrastBackgroundParams = contrast_background_params
|
|
317
|
+
problem.contrastBackgroundTypes = contrast_background_types
|
|
318
|
+
problem.contrastBackgroundActions = [contrast.background_action for contrast in project.contrasts]
|
|
319
|
+
problem.contrastScalefactors = [
|
|
320
|
+
project.scalefactors.index(contrast.scalefactor, True) for contrast in project.contrasts
|
|
321
|
+
]
|
|
322
|
+
problem.contrastBulkIns = [project.bulk_in.index(contrast.bulk_in, True) for contrast in project.contrasts]
|
|
323
|
+
problem.contrastBulkOuts = [project.bulk_out.index(contrast.bulk_out, True) for contrast in project.contrasts]
|
|
324
|
+
|
|
325
|
+
problem.contrastResolutionParams = contrast_resolution_params
|
|
326
|
+
problem.contrastResolutionTypes = contrast_resolution_types
|
|
327
|
+
|
|
328
|
+
problem.backgroundParams = [param.value for param in project.background_parameters]
|
|
329
|
+
problem.scalefactors = [param.value for param in project.scalefactors]
|
|
330
|
+
problem.bulkIns = [param.value for param in project.bulk_in]
|
|
331
|
+
problem.bulkOuts = [param.value for param in project.bulk_out]
|
|
332
|
+
problem.resolutionParams = [param.value for param in project.resolution_parameters]
|
|
333
|
+
problem.params = [param.value for param in project.parameters]
|
|
334
|
+
problem.numberOfLayers = len(project.layers)
|
|
335
|
+
problem.contrastLayers = [contrast_model if contrast_model else [] for contrast_model in contrast_models]
|
|
336
|
+
problem.layersDetails = layer_details if project.model == LayerModels.StandardLayers else []
|
|
337
|
+
problem.customFiles = FileHandles(project.custom_files)
|
|
338
|
+
problem.modelType = project.model
|
|
339
|
+
problem.contrastCustomFiles = contrast_custom_files
|
|
340
|
+
|
|
341
|
+
problem.contrastDomainRatios = [
|
|
342
|
+
project.domain_ratios.index(contrast.domain_ratio, True) if hasattr(contrast, "domain_ratio") else 0
|
|
343
|
+
for contrast in project.contrasts
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
problem.domainRatios = [param.value for param in project.domain_ratios]
|
|
347
|
+
problem.numberOfDomainContrasts = len(project.domain_contrasts)
|
|
348
|
+
|
|
349
|
+
domain_contrast_models = [
|
|
350
|
+
[project.layers.index(layer, True) for layer in domain_contrast.model]
|
|
351
|
+
for domain_contrast in project.domain_contrasts
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
problem.domainContrastLayers = [
|
|
355
|
+
domain_contrast_model if domain_contrast_model else [] for domain_contrast_model in domain_contrast_models
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
fit_params = []
|
|
359
|
+
fit_limits = []
|
|
360
|
+
prior_names = []
|
|
361
|
+
prior_values = []
|
|
362
|
+
problem.checks = Checks()
|
|
363
|
+
for class_list in ratapi.project.parameter_class_lists:
|
|
364
|
+
field = parameter_field[class_list]
|
|
365
|
+
check_list = []
|
|
366
|
+
for param in getattr(project, class_list):
|
|
367
|
+
prior_names.append(param.name)
|
|
368
|
+
prior_values.append([prior_id[param.prior_type], param.mu, param.sigma])
|
|
369
|
+
check_list.append(int(param.fit))
|
|
370
|
+
if param.fit:
|
|
371
|
+
min_range = 1e-6 if param.value == 0 else abs(param.value) * 1e-6
|
|
372
|
+
if validate_range and (param.max - param.min) < min_range:
|
|
373
|
+
warnings.warn(
|
|
374
|
+
f'{class_list.replace("_", " ").title()} "{param.name}" was removed from the '
|
|
375
|
+
f"fit because its range is too small (< {min_range:g}).",
|
|
376
|
+
stacklevel=2,
|
|
377
|
+
)
|
|
378
|
+
check_list[-1] = 0
|
|
379
|
+
else:
|
|
380
|
+
fit_params.append(param.value)
|
|
381
|
+
fit_limits.append([param.min, param.max])
|
|
382
|
+
setattr(problem.checks, field, check_list)
|
|
383
|
+
problem.fitParams = fit_params
|
|
384
|
+
problem.fitLimits = fit_limits
|
|
385
|
+
problem.priorNames = prior_names
|
|
386
|
+
problem.priorValues = prior_values
|
|
387
|
+
|
|
388
|
+
# Names
|
|
389
|
+
problem.names = NameStore()
|
|
390
|
+
for class_list in ratapi.project.parameter_class_lists:
|
|
391
|
+
setattr(problem.names, parameter_field[class_list], [param.name for param in getattr(project, class_list)])
|
|
392
|
+
problem.names.contrasts = [contrast.name for contrast in project.contrasts]
|
|
393
|
+
|
|
394
|
+
check_indices(problem)
|
|
395
|
+
|
|
396
|
+
return problem
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def get_layer_details(project: ratapi.Project) -> list[int]:
|
|
400
|
+
"""Get parameter indices for all layers defined in the project."""
|
|
401
|
+
hydrate_id = {"bulk in": 0, "bulk out": 1}
|
|
402
|
+
layer_details = []
|
|
403
|
+
|
|
404
|
+
# Get the thickness, SLD, roughness fields from the appropriate model
|
|
405
|
+
if project.absorption:
|
|
406
|
+
layer_fields = list(ratapi.models.AbsorptionLayer.model_fields.keys())[1:-2]
|
|
407
|
+
else:
|
|
408
|
+
layer_fields = list(ratapi.models.Layer.model_fields.keys())[1:-2]
|
|
409
|
+
|
|
410
|
+
for layer in project.layers:
|
|
411
|
+
for field in layer_fields:
|
|
412
|
+
if getattr(layer, field) == "":
|
|
413
|
+
raise ValueError(
|
|
414
|
+
f'In the input project, the "{field}" field of layer {layer.name} does not have a value '
|
|
415
|
+
f"defined. A value must be supplied before running the project."
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
layer_params = [project.parameters.index(getattr(layer, attribute), True) for attribute in list(layer_fields)]
|
|
419
|
+
|
|
420
|
+
layer_params.append(project.parameters.index(layer.hydration, True) if layer.hydration else float("NaN"))
|
|
421
|
+
layer_params.append(hydrate_id[layer.hydrate_with])
|
|
422
|
+
|
|
423
|
+
layer_details.append(layer_params)
|
|
424
|
+
|
|
425
|
+
return layer_details
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def make_resample(project: ratapi.Project) -> list[int]:
|
|
429
|
+
"""Construct the "resample" field of the problem input required for the compiled RAT code.
|
|
430
|
+
|
|
431
|
+
Parameters
|
|
432
|
+
----------
|
|
433
|
+
project : RAT.Project
|
|
434
|
+
The project model, which defines the physical system under study.
|
|
435
|
+
|
|
436
|
+
Returns
|
|
437
|
+
-------
|
|
438
|
+
list[int]
|
|
439
|
+
The "resample" field of the problem input used in the compiled RAT code.
|
|
440
|
+
|
|
441
|
+
"""
|
|
442
|
+
return [contrast.resample for contrast in project.contrasts]
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def make_data_present(project: ratapi.Project) -> list[int]:
|
|
446
|
+
"""Construct the "dataPresent" field of the problem input required for the compiled RAT code.
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
project : RAT.Project
|
|
451
|
+
The project model, which defines the physical system under study.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
list[int]
|
|
456
|
+
The "dataPresent" field of the problem input used in the compiled RAT code.
|
|
457
|
+
|
|
458
|
+
"""
|
|
459
|
+
return [1 if project.data[contrast.data].data.size != 0 else 0 for contrast in project.contrasts]
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def check_indices(problem: ProblemDefinition) -> None:
|
|
463
|
+
"""Check the indices given in a problem's contrasts lie within the range of the corresponding parameter lists.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
problem : RAT.rat_core.ProblemDefinition
|
|
468
|
+
The problem input used in the compiled RAT code.
|
|
469
|
+
|
|
470
|
+
"""
|
|
471
|
+
index_list = {
|
|
472
|
+
"scalefactors": "contrastScalefactors",
|
|
473
|
+
"bulkIns": "contrastBulkIns",
|
|
474
|
+
"bulkOuts": "contrastBulkOuts",
|
|
475
|
+
"domainRatios": "contrastDomainRatios",
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
# Check the indices -- note we have switched to 1-based indexing at this point
|
|
479
|
+
for params in index_list:
|
|
480
|
+
param_list = getattr(problem, params)
|
|
481
|
+
if len(param_list) > 0:
|
|
482
|
+
elements = [
|
|
483
|
+
element
|
|
484
|
+
for element in getattr(problem, index_list[params])
|
|
485
|
+
if (element != -1) and not (0 < element <= len(param_list))
|
|
486
|
+
]
|
|
487
|
+
if elements:
|
|
488
|
+
raise IndexError(
|
|
489
|
+
f'The problem field "{index_list[params]}" contains: {", ".join(str(i) for i in elements)}'
|
|
490
|
+
f', which lie{"s" * (len(elements) == 1)} outside of the range of "{params}"',
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# backgroundParams has a different structure, so is handled separately:
|
|
494
|
+
# it is of type list[list[int]], where each list[int] is the indices for
|
|
495
|
+
# source, value_1, value_2, value_3, value_4, value_5 where they are defined
|
|
496
|
+
# e.g. for a data background with offset it is [source value_1], for a function
|
|
497
|
+
# with 3 values it is [source value_1 value_2 value_3], etc.
|
|
498
|
+
|
|
499
|
+
source_param_lists = {
|
|
500
|
+
"constant": "backgroundParams",
|
|
501
|
+
"data": "backgroundParams",
|
|
502
|
+
"function": "customFiles",
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
for i, background_data in enumerate(problem.contrastBackgroundParams):
|
|
506
|
+
background_type = problem.contrastBackgroundTypes[i]
|
|
507
|
+
|
|
508
|
+
# check source param is in range of the relevant parameter list
|
|
509
|
+
param_list = getattr(problem, source_param_lists[background_type])
|
|
510
|
+
# For data, background data is appended to the contrast data so first index could
|
|
511
|
+
# either be empty or be the index of an offset.
|
|
512
|
+
if background_data and not 0 < background_data[0] <= len(param_list):
|
|
513
|
+
first_entry_name = "data offset" if background_type == "data" else "source"
|
|
514
|
+
raise IndexError(
|
|
515
|
+
f'Entry {i} of contrastBackgroundParams has type "{background_type}" '
|
|
516
|
+
f"and {first_entry_name} index {background_data[0]}, "
|
|
517
|
+
f'which is outside the range of "{source_param_lists[background_type]}".'
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# check value params are in range for background params
|
|
521
|
+
if len(background_data) > 1:
|
|
522
|
+
elements = [element for element in background_data[1:] if not 0 < element <= len(problem.backgroundParams)]
|
|
523
|
+
if elements:
|
|
524
|
+
raise IndexError(
|
|
525
|
+
f"Entry {i} of contrastBackgroundParams contains: {', '.join(str(i) for i in elements)}"
|
|
526
|
+
f', which lie{"s" * (len(elements) == 1)} outside of the range of "backgroundParams"',
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def append_data_background(data: np.array, background: np.array) -> np.array:
|
|
531
|
+
"""Append background data to contrast data.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
data : np.array
|
|
536
|
+
The contrast data to which we are appending a background.
|
|
537
|
+
background : np.array
|
|
538
|
+
The background data to append to the contrast.
|
|
539
|
+
|
|
540
|
+
Returns
|
|
541
|
+
-------
|
|
542
|
+
np.array
|
|
543
|
+
The contrast data with background data appended as two additional columns.
|
|
544
|
+
|
|
545
|
+
"""
|
|
546
|
+
if not np.allclose(data[:, 0], background[:, 0]):
|
|
547
|
+
raise ValueError("The q-values of the data and background must be equal.")
|
|
548
|
+
|
|
549
|
+
return np.hstack((data, np.zeros((data.shape[0], 4 - data.shape[1])), background[:, 1:]))
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def make_controls(input_controls: ratapi.Controls) -> Control:
|
|
553
|
+
"""Convert the controls object to the format required by the compiled RAT code.
|
|
554
|
+
|
|
555
|
+
Parameters
|
|
556
|
+
----------
|
|
557
|
+
input_controls : RAT.Controls
|
|
558
|
+
The controls model, which defines algorithmic properties.
|
|
559
|
+
|
|
560
|
+
Returns
|
|
561
|
+
-------
|
|
562
|
+
controls : RAT.rat_core.Control
|
|
563
|
+
The controls object used in the compiled RAT code.
|
|
564
|
+
|
|
565
|
+
"""
|
|
566
|
+
controls = Control()
|
|
567
|
+
|
|
568
|
+
controls.procedure = input_controls.procedure
|
|
569
|
+
controls.parallel = input_controls.parallel
|
|
570
|
+
controls.numSimulationPoints = input_controls.numSimulationPoints
|
|
571
|
+
controls.resampleMinAngle = input_controls.resampleMinAngle
|
|
572
|
+
controls.resampleNPoints = input_controls.resampleNPoints
|
|
573
|
+
controls.display = input_controls.display
|
|
574
|
+
# Simplex
|
|
575
|
+
controls.xTolerance = input_controls.xTolerance
|
|
576
|
+
controls.funcTolerance = input_controls.funcTolerance
|
|
577
|
+
controls.maxFuncEvals = input_controls.maxFuncEvals
|
|
578
|
+
controls.maxIterations = input_controls.maxIterations
|
|
579
|
+
controls.updateFreq = input_controls.updateFreq
|
|
580
|
+
controls.updatePlotFreq = input_controls.updatePlotFreq
|
|
581
|
+
# DE
|
|
582
|
+
controls.populationSize = input_controls.populationSize
|
|
583
|
+
controls.fWeight = input_controls.fWeight
|
|
584
|
+
controls.crossoverProbability = input_controls.crossoverProbability
|
|
585
|
+
controls.strategy = int(input_controls.strategy) # RAT core expects strategy as an integer
|
|
586
|
+
controls.targetValue = input_controls.targetValue
|
|
587
|
+
controls.numGenerations = input_controls.numGenerations
|
|
588
|
+
# NS
|
|
589
|
+
controls.nLive = input_controls.nLive
|
|
590
|
+
controls.nMCMC = input_controls.nMCMC
|
|
591
|
+
controls.propScale = input_controls.propScale
|
|
592
|
+
controls.nsTolerance = input_controls.nsTolerance
|
|
593
|
+
# Dream
|
|
594
|
+
controls.nSamples = input_controls.nSamples
|
|
595
|
+
controls.nChains = input_controls.nChains
|
|
596
|
+
controls.jumpProbability = input_controls.jumpProbability
|
|
597
|
+
controls.pUnitGamma = input_controls.pUnitGamma
|
|
598
|
+
controls.boundHandling = input_controls.boundHandling
|
|
599
|
+
controls.adaptPCR = input_controls.adaptPCR
|
|
600
|
+
|
|
601
|
+
controls.IPCFilePath = input_controls._IPCFilePath
|
|
602
|
+
|
|
603
|
+
return controls
|