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/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