reflectorch 1.0.0__py3-none-any.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.

Potentially problematic release.


This version of reflectorch might be problematic. Click here for more details.

Files changed (83) hide show
  1. reflectorch/__init__.py +23 -0
  2. reflectorch/data_generation/__init__.py +130 -0
  3. reflectorch/data_generation/dataset.py +196 -0
  4. reflectorch/data_generation/likelihoods.py +86 -0
  5. reflectorch/data_generation/noise.py +371 -0
  6. reflectorch/data_generation/priors/__init__.py +66 -0
  7. reflectorch/data_generation/priors/base.py +61 -0
  8. reflectorch/data_generation/priors/exp_subprior_sampler.py +304 -0
  9. reflectorch/data_generation/priors/independent_priors.py +201 -0
  10. reflectorch/data_generation/priors/multilayer_models.py +311 -0
  11. reflectorch/data_generation/priors/multilayer_structures.py +110 -0
  12. reflectorch/data_generation/priors/no_constraints.py +212 -0
  13. reflectorch/data_generation/priors/parametric_models.py +767 -0
  14. reflectorch/data_generation/priors/parametric_subpriors.py +354 -0
  15. reflectorch/data_generation/priors/params.py +258 -0
  16. reflectorch/data_generation/priors/sampler_strategies.py +306 -0
  17. reflectorch/data_generation/priors/scaler_mixin.py +65 -0
  18. reflectorch/data_generation/priors/subprior_sampler.py +377 -0
  19. reflectorch/data_generation/priors/utils.py +124 -0
  20. reflectorch/data_generation/process_data.py +47 -0
  21. reflectorch/data_generation/q_generator.py +232 -0
  22. reflectorch/data_generation/reflectivity/__init__.py +56 -0
  23. reflectorch/data_generation/reflectivity/abeles.py +81 -0
  24. reflectorch/data_generation/reflectivity/kinematical.py +58 -0
  25. reflectorch/data_generation/reflectivity/memory_eff.py +92 -0
  26. reflectorch/data_generation/reflectivity/numpy_implementations.py +120 -0
  27. reflectorch/data_generation/reflectivity/smearing.py +123 -0
  28. reflectorch/data_generation/scale_curves.py +118 -0
  29. reflectorch/data_generation/smearing.py +67 -0
  30. reflectorch/data_generation/utils.py +154 -0
  31. reflectorch/extensions/__init__.py +6 -0
  32. reflectorch/extensions/jupyter/__init__.py +12 -0
  33. reflectorch/extensions/jupyter/callbacks.py +40 -0
  34. reflectorch/extensions/matplotlib/__init__.py +11 -0
  35. reflectorch/extensions/matplotlib/losses.py +38 -0
  36. reflectorch/inference/__init__.py +22 -0
  37. reflectorch/inference/inference_model.py +734 -0
  38. reflectorch/inference/multilayer_fitter.py +171 -0
  39. reflectorch/inference/multilayer_inference_model.py +193 -0
  40. reflectorch/inference/preprocess_exp/__init__.py +7 -0
  41. reflectorch/inference/preprocess_exp/attenuation.py +36 -0
  42. reflectorch/inference/preprocess_exp/cut_with_q_ratio.py +31 -0
  43. reflectorch/inference/preprocess_exp/footprint.py +81 -0
  44. reflectorch/inference/preprocess_exp/interpolation.py +16 -0
  45. reflectorch/inference/preprocess_exp/normalize.py +21 -0
  46. reflectorch/inference/preprocess_exp/preprocess.py +121 -0
  47. reflectorch/inference/record_time.py +43 -0
  48. reflectorch/inference/sampler_solution.py +56 -0
  49. reflectorch/inference/scipy_fitter.py +171 -0
  50. reflectorch/inference/torch_fitter.py +87 -0
  51. reflectorch/ml/__init__.py +37 -0
  52. reflectorch/ml/basic_trainer.py +286 -0
  53. reflectorch/ml/callbacks.py +86 -0
  54. reflectorch/ml/dataloaders.py +27 -0
  55. reflectorch/ml/loggers.py +38 -0
  56. reflectorch/ml/schedulers.py +246 -0
  57. reflectorch/ml/trainers.py +126 -0
  58. reflectorch/ml/utils.py +9 -0
  59. reflectorch/models/__init__.py +22 -0
  60. reflectorch/models/activations.py +50 -0
  61. reflectorch/models/encoders/__init__.py +27 -0
  62. reflectorch/models/encoders/conv_encoder.py +211 -0
  63. reflectorch/models/encoders/conv_res_net.py +119 -0
  64. reflectorch/models/encoders/fno.py +127 -0
  65. reflectorch/models/encoders/transformers.py +56 -0
  66. reflectorch/models/networks/__init__.py +18 -0
  67. reflectorch/models/networks/mlp_networks.py +256 -0
  68. reflectorch/models/networks/residual_net.py +131 -0
  69. reflectorch/paths.py +33 -0
  70. reflectorch/runs/__init__.py +35 -0
  71. reflectorch/runs/config.py +31 -0
  72. reflectorch/runs/slurm_utils.py +99 -0
  73. reflectorch/runs/train.py +85 -0
  74. reflectorch/runs/utils.py +300 -0
  75. reflectorch/test_config.py +4 -0
  76. reflectorch/train.py +4 -0
  77. reflectorch/train_on_cluster.py +4 -0
  78. reflectorch/utils.py +74 -0
  79. reflectorch-1.0.0.dist-info/LICENSE.txt +621 -0
  80. reflectorch-1.0.0.dist-info/METADATA +115 -0
  81. reflectorch-1.0.0.dist-info/RECORD +83 -0
  82. reflectorch-1.0.0.dist-info/WHEEL +5 -0
  83. reflectorch-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,767 @@
1
+ from typing import Tuple, Dict, List
2
+
3
+ import torch
4
+ from torch import Tensor
5
+
6
+ from reflectorch.data_generation.reflectivity import (
7
+ reflectivity,
8
+ abeles_memory_eff,
9
+ kinematical_approximation,
10
+ )
11
+ from reflectorch.data_generation.utils import (
12
+ get_param_labels,
13
+ get_param_labels_absorption_model,
14
+ )
15
+ from reflectorch.data_generation.priors.sampler_strategies import (
16
+ SamplerStrategy,
17
+ BasicSamplerStrategy,
18
+ ConstrainedRoughnessSamplerStrategy,
19
+ ConstrainedRoughnessAndImgSldSamplerStrategy,
20
+ )
21
+
22
+ __all__ = [
23
+ "MULTILAYER_MODELS",
24
+ "ParametricModel",
25
+ ]
26
+
27
+
28
+ class ParametricModel(object):
29
+ """Base class for parameterizations of the SLD profile.
30
+
31
+ Args:
32
+ max_num_layers (int): the number of layers
33
+ """
34
+ NAME: str = ''
35
+ PARAMETER_NAMES: Tuple[str, ...]
36
+
37
+ def __init__(self, max_num_layers: int, **kwargs):
38
+ self.max_num_layers = max_num_layers
39
+ self._sampler_strategy = self._init_sampler_strategy(**kwargs)
40
+
41
+ def _init_sampler_strategy(self, **kwargs):
42
+ return BasicSamplerStrategy(**kwargs)
43
+
44
+ @property
45
+ def param_dim(self) -> int:
46
+ """get the number of parameters
47
+
48
+ Returns:
49
+ int:
50
+ """
51
+ return len(self.PARAMETER_NAMES)
52
+
53
+ @property
54
+ def sampler_strategy(self) -> SamplerStrategy:
55
+ """get the sampler strategy
56
+
57
+ Returns:
58
+ SamplerStrategy:
59
+ """
60
+ return self._sampler_strategy
61
+
62
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
63
+ """computes the reflectivity curves
64
+
65
+ Args:
66
+ q: the reciprocal space (q) positions
67
+ parametrized_model (Tensor): the values of the parameters
68
+
69
+ Returns:
70
+ Tensor: the computed reflectivity curves
71
+ """
72
+ params = self.to_standard_params(parametrized_model)
73
+ return reflectivity(q, **params, **kwargs)
74
+
75
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
76
+ raise NotImplementedError
77
+
78
+ def from_standard_params(self, params: dict) -> Tensor:
79
+ raise NotImplementedError
80
+
81
+ def scale_with_q(self, parametrized_model: Tensor, q_ratio: float) -> Tensor:
82
+ raise NotImplementedError
83
+
84
+ def init_bounds(self,
85
+ param_ranges: Dict[str, Tuple[float, float]],
86
+ bound_width_ranges: Dict[str, Tuple[float, float]],
87
+ device=None,
88
+ dtype=None,
89
+ ) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
90
+ """initializes arrays storing individually the upper and lower bounds from the dictionaries of parameter and bound width ranges
91
+
92
+ Args:
93
+ param_ranges (Dict[str, Tuple[float, float]]): parameter ranges
94
+ bound_width_ranges (Dict[str, Tuple[float, float]]): bound width ranges
95
+ device (optional): the Pytorch device. Defaults to None.
96
+ dtype (optional): the Pytorch datatype. Defaults to None.
97
+
98
+ Returns:
99
+ Tuple[Tensor, Tensor, Tensor, Tensor]:
100
+ """
101
+ ordered_bounds = [param_ranges[k] for k in self.PARAMETER_NAMES]
102
+ delta_bounds = [bound_width_ranges[k] for k in self.PARAMETER_NAMES]
103
+
104
+ min_bounds, max_bounds = torch.tensor(ordered_bounds, device=device, dtype=dtype).T[:, None]
105
+ min_deltas, max_deltas = torch.tensor(delta_bounds, device=device, dtype=dtype).T[:, None]
106
+
107
+ return min_bounds, max_bounds, min_deltas, max_deltas
108
+
109
+ def get_param_labels(self) -> List[str]:
110
+ """get the list with the name of the parameters
111
+
112
+ Returns:
113
+ List[str]:
114
+ """
115
+ return list(self.PARAMETER_NAMES)
116
+
117
+ def sample(self, batch_size: int,
118
+ total_min_bounds: Tensor,
119
+ total_max_bounds: Tensor,
120
+ total_min_delta: Tensor,
121
+ total_max_delta: Tensor,
122
+ ):
123
+ """samples the parameter values and their prior bounds
124
+
125
+ Args:
126
+ batch_size (int): the batch size
127
+ total_min_bounds (Tensor): lower bounds of the parameter ranges
128
+ total_max_bounds (Tensor): upper bounds of the parameter ranges
129
+ total_min_delta (Tensor): lower widths of the subprior intervals
130
+ total_max_delta (Tensor): upper widths of the subprior intervals
131
+
132
+ Returns:
133
+ Tensor: sampled parameters
134
+ """
135
+ return self.sampler_strategy.sample(
136
+ batch_size,
137
+ total_min_bounds,
138
+ total_max_bounds,
139
+ total_min_delta,
140
+ total_max_delta,
141
+ )
142
+
143
+
144
+ class StandardModel(ParametricModel):
145
+ """Parameterization for the standard box model. The parameters are the thicknesses, roughnesses and real sld values of the layers."""
146
+ NAME = 'standard_model'
147
+
148
+ PARAMETER_NAMES = (
149
+ "thicknesses",
150
+ "roughnesses",
151
+ "slds",
152
+ )
153
+
154
+ @property
155
+ def param_dim(self) -> int:
156
+ return 3 * self.max_num_layers + 2
157
+
158
+ def _init_sampler_strategy(self,
159
+ constrained_roughness: bool = True,
160
+ max_thickness_share: float = 0.5,
161
+ **kwargs):
162
+ if constrained_roughness:
163
+ num_params = self.param_dim
164
+ thickness_mask = torch.zeros(num_params, dtype=torch.bool)
165
+ roughness_mask = torch.zeros(num_params, dtype=torch.bool)
166
+ thickness_mask[:self.max_num_layers] = True
167
+ roughness_mask[self.max_num_layers:2 * self.max_num_layers + 1] = True
168
+ return ConstrainedRoughnessSamplerStrategy(
169
+ thickness_mask, roughness_mask,
170
+ max_thickness_share=max_thickness_share,
171
+ **kwargs
172
+ )
173
+ else:
174
+ return BasicSamplerStrategy(**kwargs)
175
+
176
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
177
+ return self._params2dict(parametrized_model)
178
+
179
+ def init_bounds(self,
180
+ param_ranges: Dict[str, Tuple[float, float]],
181
+ bound_width_ranges: Dict[str, Tuple[float, float]],
182
+ device=None,
183
+ dtype=None,
184
+ ) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
185
+
186
+ other_ranges = [param_ranges[k] for k in self.PARAMETER_NAMES[3:]]
187
+ other_delta_bounds = [bound_width_ranges[k] for k in self.PARAMETER_NAMES[3:]]
188
+
189
+ ordered_bounds = (
190
+ [param_ranges["thicknesses"]] * self.max_num_layers +
191
+ [param_ranges["roughnesses"]] * (self.max_num_layers + 1) +
192
+ [param_ranges["slds"]] * (self.max_num_layers + 1) +
193
+ other_ranges
194
+ )
195
+ delta_bounds = (
196
+ [bound_width_ranges["thicknesses"]] * self.max_num_layers +
197
+ [bound_width_ranges["roughnesses"]] * (self.max_num_layers + 1) +
198
+ [bound_width_ranges["slds"]] * (self.max_num_layers + 1) +
199
+ other_delta_bounds
200
+ )
201
+
202
+ min_bounds, max_bounds = torch.tensor(ordered_bounds, device=device, dtype=dtype).T[:, None]
203
+ min_deltas, max_deltas = torch.tensor(delta_bounds, device=device, dtype=dtype).T[:, None]
204
+
205
+ return min_bounds, max_bounds, min_deltas, max_deltas
206
+
207
+ def get_param_labels(self) -> List[str]:
208
+ return get_param_labels(self.max_num_layers)
209
+
210
+ @staticmethod
211
+ def _params2dict(parametrized_model: Tensor):
212
+ num_params = parametrized_model.shape[-1]
213
+ num_layers = (num_params - 2) // 3
214
+ assert num_layers * 3 + 2 == num_params
215
+
216
+ d, sigma, sld = torch.split(
217
+ parametrized_model, [num_layers, num_layers + 1, num_layers + 1], -1
218
+ )
219
+ params = dict(
220
+ thickness=d,
221
+ roughness=sigma,
222
+ sld=sld
223
+ )
224
+
225
+ return params
226
+
227
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
228
+ return reflectivity(
229
+ q, **self._params2dict(parametrized_model), **kwargs
230
+ )
231
+
232
+
233
+ class ModelWithAbsorption(StandardModel):
234
+ """Parameterization for the box model in which the imaginary sld values of the layers are additional parameters."""
235
+ NAME = 'model_with_absorption'
236
+
237
+ PARAMETER_NAMES = (
238
+ "thicknesses",
239
+ "roughnesses",
240
+ "slds",
241
+ "islds",
242
+ )
243
+
244
+ @property
245
+ def param_dim(self) -> int:
246
+ return 4 * self.max_num_layers + 3
247
+
248
+ def _init_sampler_strategy(self,
249
+ constrained_roughness: bool = True,
250
+ constrained_isld: bool = True,
251
+ max_thickness_share: float = 0.5,
252
+ max_sld_share: float = 0.2,
253
+ **kwargs):
254
+ if constrained_roughness:
255
+ num_params = self.param_dim
256
+ thickness_mask = torch.zeros(num_params, dtype=torch.bool)
257
+ roughness_mask = torch.zeros(num_params, dtype=torch.bool)
258
+ thickness_mask[:self.max_num_layers] = True
259
+ roughness_mask[self.max_num_layers:2 * self.max_num_layers + 1] = True
260
+
261
+ if constrained_isld:
262
+ sld_mask = torch.zeros(num_params, dtype=torch.bool)
263
+ isld_mask = torch.zeros(num_params, dtype=torch.bool)
264
+ sld_mask[2 * self.max_num_layers + 1:3 * self.max_num_layers + 2] = True
265
+ isld_mask[3 * self.max_num_layers + 2:] = True
266
+ return ConstrainedRoughnessAndImgSldSamplerStrategy(
267
+ thickness_mask, roughness_mask, sld_mask, isld_mask,
268
+ max_thickness_share=max_thickness_share, max_sld_share=max_sld_share
269
+ )
270
+ else:
271
+ return ConstrainedRoughnessSamplerStrategy(
272
+ thickness_mask, roughness_mask,
273
+ max_thickness_share=max_thickness_share,
274
+ **kwargs
275
+ )
276
+ else:
277
+ return BasicSamplerStrategy(**kwargs)
278
+
279
+ def init_bounds(self,
280
+ param_ranges: Dict[str, Tuple[float, float]],
281
+ bound_width_ranges: Dict[str, Tuple[float, float]],
282
+ device=None,
283
+ dtype=None,
284
+ ) -> Tuple[Tensor, Tensor, Tensor, Tensor]:
285
+ other_ranges = [param_ranges[k] for k in self.PARAMETER_NAMES[4:]]
286
+ other_delta_bounds = [bound_width_ranges[k] for k in self.PARAMETER_NAMES[4:]]
287
+
288
+ ordered_bounds = (
289
+ [param_ranges["thicknesses"]] * self.max_num_layers +
290
+ [param_ranges["roughnesses"]] * (self.max_num_layers + 1) +
291
+ [param_ranges["slds"]] * (self.max_num_layers + 1) +
292
+ [param_ranges["islds"]] * (self.max_num_layers + 1) +
293
+ other_ranges
294
+ )
295
+ delta_bounds = (
296
+ [bound_width_ranges["thicknesses"]] * self.max_num_layers +
297
+ [bound_width_ranges["roughnesses"]] * (self.max_num_layers + 1) +
298
+ [bound_width_ranges["slds"]] * (self.max_num_layers + 1) +
299
+ [bound_width_ranges["islds"]] * (self.max_num_layers + 1) +
300
+ other_delta_bounds
301
+ )
302
+
303
+ min_bounds, max_bounds = torch.tensor(ordered_bounds, device=device, dtype=dtype).T[:, None]
304
+ min_deltas, max_deltas = torch.tensor(delta_bounds, device=device, dtype=dtype).T[:, None]
305
+
306
+ return min_bounds, max_bounds, min_deltas, max_deltas
307
+
308
+ def get_param_labels(self) -> List[str]:
309
+ return get_param_labels_absorption_model(self.max_num_layers)
310
+
311
+ @staticmethod
312
+ def _params2dict(parametrized_model: Tensor):
313
+ num_params = parametrized_model.shape[-1]
314
+ num_layers = (num_params - 3) // 4
315
+ assert num_layers * 4 + 3 == num_params
316
+
317
+ d, sigma, sld, isld = torch.split(
318
+ parametrized_model, [num_layers, num_layers + 1, num_layers + 1, num_layers + 1], -1
319
+ )
320
+ params = dict(
321
+ thickness=d,
322
+ roughness=sigma,
323
+ sld=sld + 1j * isld
324
+ )
325
+
326
+ return params
327
+
328
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
329
+ return reflectivity(
330
+ q, **self._params2dict(parametrized_model), **kwargs
331
+ )
332
+
333
+
334
+ class ModelWithShifts(StandardModel):
335
+ """Variant of the standard box model parameterization in which two additional parameters are considered: the shift in the q positions (additive) and the shift in
336
+ intensity (multiplicative, or additive in log domain)."""
337
+ NAME = 'model_with_shifts'
338
+
339
+ PARAMETER_NAMES = (
340
+ "thicknesses",
341
+ "roughnesses",
342
+ "slds",
343
+ "q_shift",
344
+ "norm_shift",
345
+ )
346
+
347
+ @property
348
+ def param_dim(self) -> int:
349
+ return 3 * self.max_num_layers + 4
350
+
351
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
352
+ params = self._params2dict(parametrized_model)
353
+ params.pop('q_shift')
354
+ params.pop('norm_shift')
355
+
356
+ return params
357
+
358
+ def get_param_labels(self) -> List[str]:
359
+ return get_param_labels(self.max_num_layers) + [r"$\Delta q$ (Å$^{{-1}}$)", r"$\Delta I$"]
360
+
361
+ @staticmethod
362
+ def _params2dict(parametrized_model: Tensor):
363
+ num_params = parametrized_model.shape[-1]
364
+ num_layers = (num_params - 4) // 3
365
+ assert num_layers * 3 + 4 == num_params
366
+
367
+ d, sigma, sld, q_shift, norm_shift = torch.split(
368
+ parametrized_model, [num_layers, num_layers + 1, num_layers + 1, 1, 1], -1
369
+ )
370
+ params = dict(
371
+ thickness=d,
372
+ roughness=sigma,
373
+ sld=sld,
374
+ q_shift=q_shift,
375
+ norm_shift=norm_shift,
376
+ )
377
+
378
+ return params
379
+
380
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
381
+ return reflectivity_with_shifts(
382
+ q, **self._params2dict(parametrized_model), **kwargs
383
+ )
384
+
385
+ def reflectivity_with_shifts(q, thickness, roughness, sld, q_shift, norm_shift, **kwargs):
386
+ q = torch.atleast_2d(q) + q_shift
387
+ return reflectivity(q, thickness, roughness, sld, **kwargs) * norm_shift
388
+
389
+ class NoFresnelModel(StandardModel):
390
+ NAME = 'no_fresnel_model'
391
+
392
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
393
+ return kinematical_approximation(
394
+ q, **self._params2dict(parametrized_model), apply_fresnel=False, **kwargs
395
+ )
396
+
397
+
398
+ class BasicMultilayerModel1(ParametricModel):
399
+ NAME = 'repeating_multilayer_v1'
400
+
401
+ PARAMETER_NAMES = (
402
+ "d_full_rel",
403
+ "rel_sigmas",
404
+ "d_block",
405
+ "s_block_rel",
406
+ "r_block",
407
+ "dr",
408
+ "d3_rel",
409
+ "s3_rel",
410
+ "r3",
411
+ "d_sio2",
412
+ "s_sio2",
413
+ "s_si",
414
+ "r_sio2",
415
+ "r_si",
416
+ )
417
+
418
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
419
+ return multilayer_model1(parametrized_model, self.max_num_layers)
420
+
421
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
422
+ params = self.to_standard_params(parametrized_model)
423
+ return reflectivity(q, abeles_func=abeles_memory_eff, **params, **kwargs)
424
+
425
+
426
+ class BasicMultilayerModel2(BasicMultilayerModel1):
427
+ NAME = 'repeating_multilayer_v2'
428
+
429
+ PARAMETER_NAMES = (
430
+ "d_full_rel",
431
+ "rel_sigmas",
432
+ "dr_sigmoid_rel_pos",
433
+ "dr_sigmoid_rel_width",
434
+ "d_block",
435
+ "s_block_rel",
436
+ "r_block",
437
+ "dr",
438
+ "d3_rel",
439
+ "s3_rel",
440
+ "r3",
441
+ "d_sio2",
442
+ "s_sio2",
443
+ "s_si",
444
+ "r_sio2",
445
+ "r_si",
446
+ )
447
+
448
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
449
+ return multilayer_model2(parametrized_model, self.max_num_layers)
450
+
451
+
452
+ class BasicMultilayerModel3(BasicMultilayerModel1):
453
+ """Parameterization for a thin film composed of repeating identical monolayers, each monolayer consisting of two boxes with distinct SLDs.
454
+ A sigmoid envelope modulating the SLD profile of the monolayers defines the film thickness and the roughness at the top interface.
455
+ A second sigmoid envelope can be used to modulate the amplitude of the monolayer SLDs as a function of the displacement from the position of the first sigmoid.
456
+ These two sigmoids allow one to model a thin film that is coherently ordered up to a certain coherent thickness and gets incoherently ordered or amorphous toward the top of the film.
457
+ In addition, a layer between the substrate and the multilayer (”phase layer”) is introduced to account for the interface structure,
458
+ which does not necessarily have to be identical to the multilayer period.
459
+ """
460
+
461
+ NAME = 'repeating_multilayer_v3'
462
+
463
+ PARAMETER_NAMES = (
464
+ "d_full_rel",
465
+ "rel_sigmas",
466
+ "dr_sigmoid_rel_pos",
467
+ "dr_sigmoid_rel_width",
468
+ "d_block1_rel",
469
+ "d_block",
470
+ "s_block_rel",
471
+ "r_block",
472
+ "dr",
473
+ "d3_rel",
474
+ "s3_rel",
475
+ "r3",
476
+ "d_sio2",
477
+ "s_sio2",
478
+ "s_si",
479
+ "r_sio2",
480
+ "r_si",
481
+ )
482
+
483
+ def to_standard_params(self, parametrized_model: Tensor) -> dict:
484
+ return multilayer_model3(parametrized_model, self.max_num_layers)
485
+
486
+
487
+ class MultilayerModel1WithShifts(BasicMultilayerModel1):
488
+ NAME = 'repeating_multilayer_v1_with_shifts'
489
+
490
+ PARAMETER_NAMES = (
491
+ "d_full_rel",
492
+ "rel_sigmas",
493
+ "d_block",
494
+ "s_block_rel",
495
+ "r_block",
496
+ "dr",
497
+ "d3_rel",
498
+ "s3_rel",
499
+ "r3",
500
+ "d_sio2",
501
+ "s_sio2",
502
+ "s_si",
503
+ "r_sio2",
504
+ "r_si",
505
+ "q_shift",
506
+ "norm_shift",
507
+ )
508
+
509
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
510
+ q_shift, norm_shift = parametrized_model[..., -2:].T[..., None]
511
+ return reflectivity_with_shifts(
512
+ q, q_shift=q_shift, norm_shift=norm_shift, abeles_func=abeles_memory_eff,
513
+ **self.to_standard_params(parametrized_model), **kwargs
514
+ )
515
+
516
+
517
+ class MultilayerModel3WithShifts(BasicMultilayerModel3):
518
+ NAME = 'repeating_multilayer_v3_with_shifts'
519
+
520
+ PARAMETER_NAMES = (
521
+ "d_full_rel",
522
+ "rel_sigmas",
523
+ "dr_sigmoid_rel_pos",
524
+ "dr_sigmoid_rel_width",
525
+ "d_block1_rel",
526
+ "d_block",
527
+ "s_block_rel",
528
+ "r_block",
529
+ "dr",
530
+ "d3_rel",
531
+ "s3_rel",
532
+ "r3",
533
+ "d_sio2",
534
+ "s_sio2",
535
+ "s_si",
536
+ "r_sio2",
537
+ "r_si",
538
+ "q_shift",
539
+ "norm_shift",
540
+ )
541
+
542
+ def reflectivity(self, q, parametrized_model: Tensor, **kwargs) -> Tensor:
543
+ q_shift, norm_shift = parametrized_model[..., -2:].T[..., None]
544
+ return reflectivity_with_shifts(
545
+ q, q_shift=q_shift, norm_shift=norm_shift, abeles_func=abeles_memory_eff,
546
+ **self.to_standard_params(parametrized_model), **kwargs
547
+ )
548
+
549
+
550
+ MULTILAYER_MODELS = {
551
+ 'standard_model': StandardModel,
552
+ 'model_with_absorption': ModelWithAbsorption,
553
+ 'model_with_shifts': ModelWithShifts,
554
+ 'no_fresnel_model': NoFresnelModel,
555
+ 'repeating_multilayer_v1': BasicMultilayerModel1,
556
+ 'repeating_multilayer_v2': BasicMultilayerModel2,
557
+ 'repeating_multilayer_v3': BasicMultilayerModel3,
558
+ 'repeating_multilayer_v1_with_shifts': MultilayerModel1WithShifts,
559
+ 'repeating_multilayer_v3_with_shifts': MultilayerModel3WithShifts,
560
+ }
561
+
562
+
563
+ def multilayer_model1(parametrized_model: Tensor, d_full_rel_max: int = 30) -> dict:
564
+ n = d_full_rel_max
565
+
566
+ (
567
+ d_full_rel,
568
+ rel_sigmas,
569
+ d_block,
570
+ s_block_rel,
571
+ r_block,
572
+ dr,
573
+ d3_rel,
574
+ s3_rel,
575
+ r3,
576
+ d_sio2,
577
+ s_sio2,
578
+ s_si,
579
+ r_sio2,
580
+ r_si,
581
+ *_,
582
+ ) = parametrized_model.T
583
+
584
+ batch_size = parametrized_model.shape[0]
585
+
586
+ r_positions = 2 * n - torch.arange(2 * n, dtype=dr.dtype, device=dr.device)[None].repeat(batch_size, 1)
587
+
588
+ r_modulations = torch.sigmoid(-(r_positions - 2 * d_full_rel[..., None]) / rel_sigmas[..., None])
589
+
590
+ r_block = r_block[:, None].repeat(1, n)
591
+ dr = dr[:, None].repeat(1, n)
592
+
593
+ sld_blocks = torch.stack([r_block, r_block + dr], -1).flatten(1)
594
+
595
+ sld_blocks = r_modulations * sld_blocks
596
+
597
+ d3 = d3_rel * d_block
598
+
599
+ thicknesses = torch.cat(
600
+ [(d_block / 2)[:, None].repeat(1, n * 2), d3[:, None], d_sio2[:, None]], -1
601
+ )
602
+
603
+ s_block = s_block_rel * d_block
604
+
605
+ roughnesses = torch.cat(
606
+ [s_block[:, None].repeat(1, n * 2), (s3_rel * d3)[:, None], s_sio2[:, None], s_si[:, None]], -1
607
+ )
608
+
609
+ slds = torch.cat(
610
+ [sld_blocks, r3[:, None], r_sio2[:, None], r_si[:, None]], -1
611
+ )
612
+
613
+ params = dict(
614
+ thickness=thicknesses,
615
+ roughness=roughnesses,
616
+ sld=slds
617
+ )
618
+ return params
619
+
620
+
621
+ def multilayer_model2(parametrized_model: Tensor, d_full_rel_max: int = 30) -> dict:
622
+ n = d_full_rel_max
623
+
624
+ (
625
+ d_full_rel,
626
+ rel_sigmas,
627
+ dr_sigmoid_rel_pos,
628
+ dr_sigmoid_rel_width,
629
+ d_block,
630
+ s_block_rel,
631
+ r_block,
632
+ dr,
633
+ d3_rel,
634
+ s3_rel,
635
+ r3,
636
+ d_sio2,
637
+ s_sio2,
638
+ s_si,
639
+ r_sio2,
640
+ r_si,
641
+ *_,
642
+ ) = parametrized_model.T
643
+
644
+ batch_size = parametrized_model.shape[0]
645
+
646
+ r_positions = 2 * n - torch.arange(2 * n, dtype=dr.dtype, device=dr.device)[None].repeat(batch_size, 1)
647
+
648
+ r_modulations = torch.sigmoid(-(r_positions - 2 * d_full_rel[..., None]) / rel_sigmas[..., None])
649
+
650
+ r_block = r_block[:, None].repeat(1, n)
651
+ dr = dr[:, None].repeat(1, n)
652
+
653
+ dr_positions = r_positions[:, ::2]
654
+
655
+ dr_modulations = torch.sigmoid(
656
+ -(dr_positions - (2 * d_full_rel * dr_sigmoid_rel_pos)[..., None]) / dr_sigmoid_rel_width[..., None]
657
+ )
658
+
659
+ dr = dr * dr_modulations
660
+
661
+ sld_blocks = torch.stack([r_block, r_block + dr], -1).flatten(1)
662
+
663
+ sld_blocks = r_modulations * sld_blocks
664
+
665
+ d3 = d3_rel * d_block
666
+
667
+ thicknesses = torch.cat(
668
+ [(d_block / 2)[:, None].repeat(1, n * 2), d3[:, None], d_sio2[:, None]], -1
669
+ )
670
+
671
+ s_block = s_block_rel * d_block
672
+
673
+ roughnesses = torch.cat(
674
+ [s_block[:, None].repeat(1, n * 2), (s3_rel * d3)[:, None], s_sio2[:, None], s_si[:, None]], -1
675
+ )
676
+
677
+ slds = torch.cat(
678
+ [sld_blocks, r3[:, None], r_sio2[:, None], r_si[:, None]], -1
679
+ )
680
+
681
+ params = dict(
682
+ thickness=thicknesses,
683
+ roughness=roughnesses,
684
+ sld=slds
685
+ )
686
+ return params
687
+
688
+
689
+ def multilayer_model3(parametrized_model: Tensor, d_full_rel_max: int = 30):
690
+ n = d_full_rel_max
691
+
692
+ (
693
+ d_full_rel,
694
+ rel_sigmas,
695
+ dr_sigmoid_rel_pos,
696
+ dr_sigmoid_rel_width,
697
+ d_block1_rel,
698
+ d_block,
699
+ s_block_rel,
700
+ r_block,
701
+ dr,
702
+ d3_rel,
703
+ s3_rel,
704
+ r3,
705
+ d_sio2,
706
+ s_sio2,
707
+ s_si,
708
+ r_sio2,
709
+ r_si,
710
+ *_,
711
+ ) = parametrized_model.T
712
+
713
+ batch_size = parametrized_model.shape[0]
714
+
715
+ r_positions = 2 * n - torch.arange(2 * n, dtype=dr.dtype, device=dr.device)[None].repeat(batch_size, 1)
716
+
717
+ r_modulations = torch.sigmoid(
718
+ -(
719
+ r_positions - 2 * d_full_rel[..., None]
720
+ ) / rel_sigmas[..., None]
721
+ )
722
+
723
+ dr_positions = r_positions[:, ::2]
724
+
725
+ dr_modulations = dr[..., None] * (1 - torch.sigmoid(
726
+ -(
727
+ dr_positions - 2 * d_full_rel[..., None] + 2 * dr_sigmoid_rel_pos[..., None]
728
+ ) / dr_sigmoid_rel_width[..., None]
729
+ ))
730
+
731
+ r_block = r_block[..., None].repeat(1, n)
732
+ dr = dr[..., None].repeat(1, n)
733
+
734
+ sld_blocks = torch.stack(
735
+ [
736
+ r_block + dr_modulations * (1 - d_block1_rel[..., None]),
737
+ r_block + dr - dr_modulations * d_block1_rel[..., None]
738
+ ], -1).flatten(1)
739
+
740
+ sld_blocks = r_modulations * sld_blocks
741
+
742
+ d3 = d3_rel * d_block
743
+
744
+ d1, d2 = d_block * d_block1_rel, d_block * (1 - d_block1_rel)
745
+
746
+ thickness_blocks = torch.stack([d1[:, None].repeat(1, n), d2[:, None].repeat(1, n)], -1).flatten(1)
747
+
748
+ thicknesses = torch.cat(
749
+ [thickness_blocks, d3[:, None], d_sio2[:, None]], -1
750
+ )
751
+
752
+ s_block = s_block_rel * d_block
753
+
754
+ roughnesses = torch.cat(
755
+ [s_block[:, None].repeat(1, n * 2), (s3_rel * d3)[:, None], s_sio2[:, None], s_si[:, None]], -1
756
+ )
757
+
758
+ slds = torch.cat(
759
+ [sld_blocks, r3[:, None], r_sio2[:, None], r_si[:, None]], -1
760
+ )
761
+
762
+ params = dict(
763
+ thickness=thicknesses,
764
+ roughness=roughnesses,
765
+ sld=slds
766
+ )
767
+ return params