fakecbed 0.2.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.
fakecbed/shapes.py ADDED
@@ -0,0 +1,3510 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright 2024 Matthew Fitzpatrick.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify it under
5
+ # the terms of the GNU General Public License as published by the Free Software
6
+ # Foundation, version 3.
7
+ #
8
+ # This program is distributed in the hope that it will be useful, but WITHOUT
9
+ # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
11
+ #
12
+ # You should have received a copy of the GNU General Public License along with
13
+ # this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
14
+ r"""For creating undistorted geometric shapes.
15
+
16
+ This module contains classes that represent the intensity patterns of different
17
+ undistorted geometric shapes that can be combined to construct intensity
18
+ patterns that imitate convergent beam diffraction beam (CBED) patterns. As a
19
+ shorthand, we refer to these intensity patterns that imitate CBED patterns as
20
+ "fake CBED patterns".
21
+
22
+ Users can create images of fake CBED patterns using the
23
+ :mod:`fakecbed.discretized` module. An image of a fake CBED pattern is formed by
24
+ specifying a series of parameters, with the most important parameters being: the
25
+ set of intensity patterns of undistorted shapes that determine the undistorted
26
+ noiseless non-blurred uncorrupted fake CBED pattern; and a distortion model
27
+ which transforms the undistorted noiseless non-blurred uncorrupted fake CBED
28
+ pattern into a distorted noiseless non-blurred uncorrupted fake CBED
29
+ pattern. The remaining parameters determine whether additional images effects
30
+ are applied, like e.g. shot noise or blur effects. Note that in the case of the
31
+ aforementioned shapes, we expand the notion of intensity patterns to mean a 2D
32
+ real-valued function, i.e. it can be negative. To be clear, we do not apply this
33
+ generalized notion of intensity patterns to the fake CBED patterns: in such
34
+ cases intensity patterns mean 2D real-valued functions that are strictly
35
+ nonnegative.
36
+
37
+ Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and vertical
38
+ coordinates, respectively, of a point in an undistorted image, where
39
+ :math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left corner of
40
+ the image. Secondly, let :math:`q_{x}` and :math:`q_{y}` be the fractional
41
+ horizontal and vertical coordinates, respectively, of a point in a distorted
42
+ image, where :math:`\left(q_{x},q_{y}\right)=\left(0,0\right)` is the bottom
43
+ left corner of the image. When users specify a distortion model, represented by
44
+ an :obj:`distoptica.DistortionModel` object, they also specify a coordinate
45
+ transformation which maps a given coordinate pair
46
+ :math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
47
+ :math:`\left(q_{x},q_{y}\right)`, and implicitly a right-inverse to said
48
+ coordinate transformation that maps a coordinate pair
49
+ :math:`\left(q_{x},q_{y}\right)` to a corresponding coordinate pair
50
+ :math:`\left(u_{x},u_{y}\right)`, when such a relationship exists for
51
+ :math:`\left(q_{x},q_{y}\right)`.
52
+
53
+ """
54
+
55
+
56
+
57
+ #####################################
58
+ ## Load libraries/packages/modules ##
59
+ #####################################
60
+
61
+ # For accessing attributes of functions.
62
+ import inspect
63
+
64
+ # For randomly selecting items in dictionaries.
65
+ import random
66
+
67
+ # For performing deep copies.
68
+ import copy
69
+
70
+
71
+
72
+ # For general array handling.
73
+ import numpy as np
74
+ import torch
75
+
76
+ # For calculating factorials.
77
+ import math
78
+
79
+ # For validating and converting objects.
80
+ import czekitout.check
81
+ import czekitout.convert
82
+
83
+ # For defining classes that support enforced validation, updatability,
84
+ # pre-serialization, and de-serialization.
85
+ import fancytypes
86
+
87
+ # For validating, pre-serializing, and de-pre-serializing certain objects.
88
+ import distoptica
89
+
90
+
91
+
92
+ ##################################
93
+ ## Define classes and functions ##
94
+ ##################################
95
+
96
+ # List of public objects in module.
97
+ __all__ = ["BaseShape",
98
+ "Circle",
99
+ "Ellipse",
100
+ "Peak",
101
+ "Band",
102
+ "PlaneWave",
103
+ "Arc",
104
+ "GenericBlob",
105
+ "Orbital",
106
+ "Lune",
107
+ "NonuniformBoundedShape"]
108
+
109
+
110
+
111
+ def _check_and_convert_cartesian_coords(params):
112
+ current_func_name = inspect.stack()[0][3]
113
+ char_idx = 19
114
+ obj_name = current_func_name[char_idx:]
115
+ obj = params[obj_name]
116
+
117
+ u_x, u_y = obj
118
+
119
+ params["real_torch_matrix"] = u_x
120
+ params["name_of_alias_of_real_torch_matrix"] = "u_x"
121
+ u_x = _check_and_convert_real_torch_matrix(params)
122
+
123
+ params["real_torch_matrix"] = u_y
124
+ params["name_of_alias_of_real_torch_matrix"] = "u_y"
125
+ u_y = _check_and_convert_real_torch_matrix(params)
126
+
127
+ del params["real_torch_matrix"]
128
+ del params["name_of_alias_of_real_torch_matrix"]
129
+
130
+ if u_x.shape != u_y.shape:
131
+ unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
132
+ err_msg = unformatted_err_msg.format("u_x", "u_y")
133
+ raise ValueError(err_msg)
134
+
135
+ cartesian_coords = (u_x, u_y)
136
+
137
+ return cartesian_coords
138
+
139
+
140
+
141
+ def _check_and_convert_real_torch_matrix(params):
142
+ current_func_name = inspect.stack()[0][3]
143
+ char_idx = 19
144
+ obj_name = current_func_name[char_idx:]
145
+ obj = params[obj_name]
146
+
147
+ name_of_alias_of_real_torch_matrix = \
148
+ params["name_of_alias_of_real_torch_matrix"]
149
+
150
+ try:
151
+ if not isinstance(obj, torch.Tensor):
152
+ kwargs = {"obj": obj,
153
+ "obj_name": name_of_alias_of_real_torch_matrix}
154
+ obj = czekitout.convert.to_real_numpy_matrix(**kwargs)
155
+
156
+ obj = torch.tensor(obj,
157
+ dtype=torch.float32,
158
+ device=params["device"])
159
+
160
+ if len(obj.shape) != 2:
161
+ raise
162
+
163
+ real_torch_matrix = obj.to(device=params["device"], dtype=torch.float32)
164
+
165
+ except:
166
+ unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
167
+ err_msg = unformatted_err_msg.format(name_of_alias_of_real_torch_matrix)
168
+ raise TypeError(err_msg)
169
+
170
+ return real_torch_matrix
171
+
172
+
173
+
174
+ def _check_and_convert_device(params):
175
+ params["name_of_obj_alias_of_torch_device_obj"] = "device"
176
+ device = _check_and_convert_torch_device_obj(params)
177
+
178
+ del params["name_of_obj_alias_of_torch_device_obj"]
179
+
180
+ return device
181
+
182
+
183
+
184
+ def _check_and_convert_torch_device_obj(params):
185
+ obj_name = params["name_of_obj_alias_of_torch_device_obj"]
186
+ obj = params[obj_name]
187
+
188
+ if obj is None:
189
+ torch_device_obj = torch.device("cuda"
190
+ if torch.cuda.is_available()
191
+ else "cpu")
192
+ else:
193
+ kwargs = {"obj": obj,
194
+ "obj_name": obj_name,
195
+ "accepted_types": (torch.device, type(None))}
196
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
197
+ torch_device_obj = obj
198
+
199
+ return torch_device_obj
200
+
201
+
202
+
203
+ def _check_and_convert_skip_validation_and_conversion(params):
204
+ current_func_name = inspect.stack()[0][3]
205
+ obj_name = current_func_name[19:]
206
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
207
+ skip_validation_and_conversion = czekitout.convert.to_bool(**kwargs)
208
+
209
+ return skip_validation_and_conversion
210
+
211
+
212
+
213
+ _default_u_x = ((0.5,),)
214
+ _default_u_y = _default_u_x
215
+ _default_device = None
216
+ _default_skip_validation_and_conversion = False
217
+
218
+
219
+
220
+ class BaseShape(fancytypes.PreSerializableAndUpdatable):
221
+ r"""The intensity pattern of an undistorted geometric shape.
222
+
223
+ See the summary documentation of the module :mod:`fakecbed.shapes` for
224
+ additional context.
225
+
226
+ One cannot construct an instance of the class
227
+ :class:`fakecbed.shapes.BaseShape`, only subclasses of itself defined in
228
+ :mod:`fakecbed` library.
229
+
230
+ Parameters
231
+ ----------
232
+ ctor_params : `dict`
233
+ The construction parameters of the subclass.
234
+
235
+ """
236
+ def __init__(self, ctor_params):
237
+ if type(self) is BaseShape:
238
+ self._eval(u_x=None, u_y=None)
239
+ else:
240
+ kwargs = ctor_params
241
+ kwargs["skip_cls_tests"] = True
242
+ fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
243
+
244
+ return None
245
+
246
+
247
+
248
+ @classmethod
249
+ def get_validation_and_conversion_funcs(cls):
250
+ validation_and_conversion_funcs = \
251
+ cls._validation_and_conversion_funcs_.copy()
252
+
253
+ return validation_and_conversion_funcs
254
+
255
+
256
+
257
+ @classmethod
258
+ def get_pre_serialization_funcs(cls):
259
+ pre_serialization_funcs = \
260
+ cls._pre_serialization_funcs_.copy()
261
+
262
+ return pre_serialization_funcs
263
+
264
+
265
+
266
+ @classmethod
267
+ def get_de_pre_serialization_funcs(cls):
268
+ de_pre_serialization_funcs = \
269
+ cls._de_pre_serialization_funcs_.copy()
270
+
271
+ return de_pre_serialization_funcs
272
+
273
+
274
+
275
+ def eval(self,
276
+ u_x=\
277
+ _default_u_x,
278
+ u_y=\
279
+ _default_u_y,
280
+ device=\
281
+ _default_device,
282
+ skip_validation_and_conversion=\
283
+ _default_skip_validation_and_conversion):
284
+ r"""Evaluate the intensity pattern of the undistorted shape.
285
+
286
+ Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and
287
+ vertical coordinates, respectively, of a point in an undistorted image.
288
+ We adopt the convention where the fractional coordinate pair
289
+ :math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left
290
+ corner of an image.
291
+
292
+ Parameters
293
+ ----------
294
+ u_x : `torch.Tensor` (`float`, ndim=2), optional
295
+ The fractional horizontal coordinates of the positions at which to
296
+ evaluate the intensity pattern of the undistorted shape.
297
+ u_y : `torch.Tensor` (`float`, shape=``u_x.shape``), optional
298
+ The fractional vertical coordinates of the positions at which to
299
+ evaluate the intensity pattern of the undistorted shape.
300
+ device : `torch.device` | `None`, optional
301
+ This parameter specifies the device to be used to perform
302
+ computationally intensive calls to PyTorch functions. If ``device``
303
+ is of the type :class:`torch.device`, then ``device`` represents the
304
+ device to be used. If ``device`` is set to ``None`` and a GPU device
305
+ is available, then a GPU device is to be used. Otherwise, the CPU is
306
+ used.
307
+ skip_validation_and_conversion : `bool`, optional
308
+ If ``skip_validation_and_conversion`` is set to ``False``, then
309
+ validations and conversions are performed on the above parameters.
310
+
311
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
312
+ no validations and conversions are performed on the above
313
+ parameters. This option is desired primarily when the user wants to
314
+ avoid potentially expensive validation and/or conversion operations.
315
+
316
+ Returns
317
+ -------
318
+ result : `torch.Tensor` (`float`, shape=``u_x.shape``)
319
+ The values of the intensity pattern at the positions specified by
320
+ ``u_x`` and ``u_y``. For every pair of nonnegative integers ``(i,
321
+ j)`` that does not raise an ``IndexError`` exception upon calling
322
+ ``result[i, j]``, ``result[i, j]`` is the value of the intensity
323
+ pattern at the position ``(u_x[i, j], u_y[i, j])`` of the
324
+ undistorted shape.
325
+
326
+ """
327
+ params = locals()
328
+
329
+ func_alias = _check_and_convert_skip_validation_and_conversion
330
+ skip_validation_and_conversion = func_alias(params)
331
+
332
+ if (skip_validation_and_conversion == False):
333
+ params = {"cartesian_coords": (u_x, u_y), "device": device}
334
+ device = _check_and_convert_device(params)
335
+ u_x, u_y = _check_and_convert_cartesian_coords(params)
336
+
337
+ result = self._eval(u_x, u_y)
338
+
339
+ return result
340
+
341
+
342
+
343
+ def _eval(self, u_x, u_y):
344
+ raise NotImplementedError(_base_shape_err_msg_1)
345
+
346
+
347
+
348
+ def _check_and_convert_center(params):
349
+ current_func_name = inspect.stack()[0][3]
350
+ obj_name = current_func_name[19:]
351
+
352
+ cls_alias = \
353
+ distoptica.CoordTransformParams
354
+ validation_and_conversion_funcs = \
355
+ cls_alias.get_validation_and_conversion_funcs()
356
+ validation_and_conversion_func = \
357
+ validation_and_conversion_funcs[obj_name]
358
+ center = \
359
+ validation_and_conversion_func(params)
360
+
361
+ return center
362
+
363
+
364
+
365
+ def _pre_serialize_center(center):
366
+ obj_to_pre_serialize = random.choice(list(locals().values()))
367
+ current_func_name = inspect.stack()[0][3]
368
+ obj_name = current_func_name[15:]
369
+
370
+ cls_alias = \
371
+ distoptica.CoordTransformParams
372
+ pre_serialization_funcs = \
373
+ cls_alias.get_pre_serialization_funcs()
374
+ pre_serialization_func = \
375
+ pre_serialization_funcs[obj_name]
376
+ serializable_rep = \
377
+ pre_serialization_func(obj_to_pre_serialize)
378
+
379
+ return serializable_rep
380
+
381
+
382
+
383
+ def _de_pre_serialize_center(serializable_rep):
384
+ current_func_name = inspect.stack()[0][3]
385
+ obj_name = current_func_name[18:]
386
+
387
+ cls_alias = \
388
+ distoptica.CoordTransformParams
389
+ de_pre_serialization_funcs = \
390
+ cls_alias.get_de_pre_serialization_funcs()
391
+ de_pre_serialization_func = \
392
+ de_pre_serialization_funcs[obj_name]
393
+ center = \
394
+ de_pre_serialization_func(serializable_rep)
395
+
396
+ return center
397
+
398
+
399
+
400
+ def _check_and_convert_radius(params):
401
+ current_func_name = inspect.stack()[0][3]
402
+ obj_name = current_func_name[19:]
403
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
404
+ radius = czekitout.convert.to_positive_float(**kwargs)
405
+
406
+ return radius
407
+
408
+
409
+
410
+ def _pre_serialize_radius(radius):
411
+ obj_to_pre_serialize = random.choice(list(locals().values()))
412
+ serializable_rep = obj_to_pre_serialize
413
+
414
+ return serializable_rep
415
+
416
+
417
+
418
+ def _de_pre_serialize_radius(serializable_rep):
419
+ radius = serializable_rep
420
+
421
+ return radius
422
+
423
+
424
+
425
+ def _check_and_convert_intra_shape_val(params):
426
+ current_func_name = inspect.stack()[0][3]
427
+ obj_name = current_func_name[19:]
428
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
429
+ intra_shape_val = czekitout.convert.to_float(**kwargs)
430
+
431
+ return intra_shape_val
432
+
433
+
434
+
435
+ def _pre_serialize_intra_shape_val(intra_shape_val):
436
+ obj_to_pre_serialize = random.choice(list(locals().values()))
437
+ serializable_rep = obj_to_pre_serialize
438
+
439
+ return serializable_rep
440
+
441
+
442
+
443
+ def _de_pre_serialize_intra_shape_val(serializable_rep):
444
+ intra_shape_val = serializable_rep
445
+
446
+ return intra_shape_val
447
+
448
+
449
+
450
+ _default_center = (0.5, 0.5)
451
+ _default_radius = 0.05
452
+ _default_intra_shape_val = 1
453
+
454
+
455
+
456
+ class Circle(BaseShape):
457
+ r"""The intensity pattern of a circle.
458
+
459
+ Let :math:`\left(u_{x;c;\text{C}},u_{y;c;\text{C}}\right)`, and
460
+ :math:`R_{\text{C}}` be the center, and the radius of the circle
461
+ respectively. Furthermore, let :math:`A_{\text{C}}` be the value of the
462
+ intensity pattern inside the circle. The undistorted intensity pattern of
463
+ the circle is given by:
464
+
465
+ .. math ::
466
+ \mathcal{I}_{\text{C}}\left(u_{x},u_{y}\right)=
467
+ A_{\text{C}}\Theta\left(R_{\text{C}}
468
+ -\sqrt{\left(u_{x}-u_{x;c;\text{C}}\right)^{2}
469
+ +\left(u_{y}-u_{y;c;\text{C}}\right)^{2}}\right),
470
+ :label: intensity_pattern_of_circle__1
471
+
472
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
473
+ coordinates of the undistorted intensity pattern of the circle respectively,
474
+ and :math:`\Theta\left(\cdots\right)` is the Heaviside step function.
475
+
476
+ Parameters
477
+ ----------
478
+ center : `array_like` (`float`, shape=(``2``,)), optional
479
+ The center of the circle, :math:`\left(u_{x;c;\text{C}},
480
+ u_{y;c;\text{C}}\right)`.
481
+ radius : `float`, optional
482
+ The radius of the circle, :math:`R_{\text{C}}`. Must be positive.
483
+ intra_shape_val : `float`, optional
484
+ The value of the intensity pattern inside the circle.
485
+ skip_validation_and_conversion : `bool`, optional
486
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
487
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
488
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
489
+ being `dict` objects.
490
+
491
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
492
+ representation of the constructor parameters excluding the parameter
493
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
494
+ different constructor parameter name, excluding the name
495
+ ``"skip_validation_and_conversion"``, and
496
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
497
+ constructor parameter with the name given by ``key``.
498
+
499
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
500
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
501
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
502
+ (params_to_be_mapped_to_core_attrs)``.
503
+
504
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
505
+ then ``core_attrs`` is set to
506
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
507
+ primarily when the user wants to avoid potentially expensive deep copies
508
+ and/or conversions of the `dict` values of
509
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
510
+ copies or conversions are made in this case.
511
+
512
+ """
513
+ ctor_param_names = ("center",
514
+ "radius",
515
+ "intra_shape_val")
516
+ kwargs = {"namespace_as_dict": globals(),
517
+ "ctor_param_names": ctor_param_names}
518
+
519
+ _validation_and_conversion_funcs_ = \
520
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
521
+ _pre_serialization_funcs_ = \
522
+ fancytypes.return_pre_serialization_funcs(**kwargs)
523
+ _de_pre_serialization_funcs_ = \
524
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
525
+
526
+ del ctor_param_names, kwargs
527
+
528
+
529
+
530
+ def __init__(self,
531
+ center=\
532
+ _default_center,
533
+ radius=\
534
+ _default_radius,
535
+ intra_shape_val=\
536
+ _default_intra_shape_val,
537
+ skip_validation_and_conversion=\
538
+ _default_skip_validation_and_conversion):
539
+ ctor_params = {key: val
540
+ for key, val in locals().items()
541
+ if (key not in ("self", "__class__"))}
542
+ BaseShape.__init__(self, ctor_params)
543
+
544
+ self.execute_post_core_attrs_update_actions()
545
+
546
+ return None
547
+
548
+
549
+
550
+ def execute_post_core_attrs_update_actions(self):
551
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
552
+ for self_core_attr_name in self_core_attrs:
553
+ attr_name = "_"+self_core_attr_name
554
+ attr = self_core_attrs[self_core_attr_name]
555
+ setattr(self, attr_name, attr)
556
+
557
+ return None
558
+
559
+
560
+
561
+ def update(self,
562
+ new_core_attr_subset_candidate,
563
+ skip_validation_and_conversion=\
564
+ _default_skip_validation_and_conversion):
565
+ super().update(new_core_attr_subset_candidate,
566
+ skip_validation_and_conversion)
567
+ self.execute_post_core_attrs_update_actions()
568
+
569
+ return None
570
+
571
+
572
+
573
+ def _eval(self, u_x, u_y):
574
+ u_x_c, u_y_c = self._center
575
+ R = self._radius
576
+ A = self._intra_shape_val
577
+
578
+ delta_u_x = u_x-u_x_c
579
+ delta_u_y = u_y-u_y_c
580
+
581
+ u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
582
+
583
+ result = A * (u_r <= R)
584
+
585
+ return result
586
+
587
+
588
+
589
+ def _check_and_convert_semi_major_axis(params):
590
+ current_func_name = inspect.stack()[0][3]
591
+ obj_name = current_func_name[19:]
592
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
593
+ semi_major_axis = czekitout.convert.to_positive_float(**kwargs)
594
+
595
+ return semi_major_axis
596
+
597
+
598
+
599
+ def _pre_serialize_semi_major_axis(semi_major_axis):
600
+ obj_to_pre_serialize = random.choice(list(locals().values()))
601
+ serializable_rep = obj_to_pre_serialize
602
+
603
+ return serializable_rep
604
+
605
+
606
+
607
+ def _de_pre_serialize_semi_major_axis(serializable_rep):
608
+ semi_major_axis = serializable_rep
609
+
610
+ return semi_major_axis
611
+
612
+
613
+
614
+ def _check_and_convert_eccentricity(params):
615
+ current_func_name = inspect.stack()[0][3]
616
+ obj_name = current_func_name[19:]
617
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
618
+ eccentricity = czekitout.convert.to_nonnegative_float(**kwargs)
619
+
620
+ if eccentricity > 1:
621
+ err_msg = globals()[current_func_name+"_err_msg_1"]
622
+ raise ValueError(err_msg)
623
+
624
+ return eccentricity
625
+
626
+
627
+
628
+ def _pre_serialize_eccentricity(eccentricity):
629
+ obj_to_pre_serialize = random.choice(list(locals().values()))
630
+ serializable_rep = obj_to_pre_serialize
631
+
632
+ return serializable_rep
633
+
634
+
635
+
636
+ def _de_pre_serialize_eccentricity(serializable_rep):
637
+ eccentricity = serializable_rep
638
+
639
+ return eccentricity
640
+
641
+
642
+
643
+ def _check_and_convert_rotation_angle(params):
644
+ current_func_name = inspect.stack()[0][3]
645
+ obj_name = current_func_name[19:]
646
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
647
+ rotation_angle = czekitout.convert.to_float(**kwargs) % (2*np.pi)
648
+
649
+ return rotation_angle
650
+
651
+
652
+
653
+ def _pre_serialize_rotation_angle(rotation_angle):
654
+ obj_to_pre_serialize = random.choice(list(locals().values()))
655
+ serializable_rep = obj_to_pre_serialize
656
+
657
+ return serializable_rep
658
+
659
+
660
+
661
+ def _de_pre_serialize_rotation_angle(serializable_rep):
662
+ rotation_angle = serializable_rep
663
+
664
+ return rotation_angle
665
+
666
+
667
+
668
+ _default_semi_major_axis = _default_radius
669
+ _default_eccentricity = 0
670
+ _default_rotation_angle = 0
671
+
672
+
673
+
674
+ class Ellipse(BaseShape):
675
+ r"""The intensity pattern of a ellipse.
676
+
677
+ Let :math:`\left(u_{x;c;\text{E}},u_{y;c;\text{E}}\right)`,
678
+ :math:`a_{\text{E}}`, :math:`e_{\text{E}}`, and :math:`\theta_{\text{E}}` be
679
+ the center, the semi-major axis, the eccentricity, and the rotation angle of
680
+ the ellipse respectively. Furthermore, let :math:`A_{\text{E}}` be the value
681
+ of the intensity pattern inside the ellipse. The undistorted intensity
682
+ pattern of the ellipse is given by:
683
+
684
+ .. math ::
685
+ \mathcal{I}_{\text{E}}\left(u_{x},u_{y}\right)=
686
+ A_{\text{E}}\Theta\left(\Theta_{\arg;\text{E}}\left(u_{x},
687
+ u_{y}\right)\right),
688
+ :label: intensity_pattern_of_ellipse__1
689
+
690
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
691
+ coordinates of the undistorted intensity pattern of the ellipse
692
+ respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
693
+ function, and
694
+
695
+ .. math ::
696
+ &\Theta_{\arg;\text{E}}\left(u_{x},u_{y}\right)\\&\quad=
697
+ \left\{ 1-e_{\text{E}}^{2}\right\} a_{\text{E}}^{2}\\
698
+ &\quad\quad-\left\{ 1-e_{\text{E}}^{2}\right\}
699
+ \left\{ \left[u_{x}-u_{x;c;\text{E}}\right]
700
+ \cos\left(\theta_{\text{E}}\right)
701
+ -\left[u_{y}-u_{y;c;\text{E}}\right]
702
+ \sin\left(\theta_{\text{E}}\right)\right\} ^{2}\\
703
+ &\quad\quad-\left\{ \left[u_{x}-
704
+ u_{x;c;\text{E}}\right]\sin\left(\theta_{\text{E}}\right)+\left[u_{y}-
705
+ u_{y;c;\text{E}}\right]\cos\left(\theta_{\text{E}}\right)\right\}^{2}.
706
+ :label: ellipse_support_arg__1
707
+
708
+ Parameters
709
+ ----------
710
+ center : `array_like` (`float`, shape=(``2``,)), optional
711
+ The center of the ellipse, :math:`\left(u_{x;c;\text{E}},
712
+ u_{y;c;\text{E}}\right)`.
713
+ semi_major_axis : `float`, optional
714
+ The semi-major axis of the ellipse, :math:`a_{\text{E}}`. Must be
715
+ positive.
716
+ eccentricity : `float`, optional
717
+ The eccentricity of the ellipse, :math:`e_{\text{E}}`. Must be a
718
+ nonnegative number less than or equal to unity.
719
+ rotation_angle : `float`, optional
720
+ The rotation angle of the ellipse, :math:`\theta_{\text{E}}`.
721
+ intra_shape_val : `float`, optional
722
+ The value of the intensity pattern inside the ellipse.
723
+ skip_validation_and_conversion : `bool`, optional
724
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
725
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
726
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
727
+ being `dict` objects.
728
+
729
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
730
+ representation of the constructor parameters excluding the parameter
731
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
732
+ different constructor parameter name, excluding the name
733
+ ``"skip_validation_and_conversion"``, and
734
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
735
+ constructor parameter with the name given by ``key``.
736
+
737
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
738
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
739
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
740
+ (params_to_be_mapped_to_core_attrs)``.
741
+
742
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
743
+ then ``core_attrs`` is set to
744
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
745
+ primarily when the user wants to avoid potentially expensive deep copies
746
+ and/or conversions of the `dict` values of
747
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
748
+ copies or conversions are made in this case.
749
+
750
+ """
751
+ ctor_param_names = ("center",
752
+ "semi_major_axis",
753
+ "eccentricity",
754
+ "rotation_angle",
755
+ "intra_shape_val")
756
+ kwargs = {"namespace_as_dict": globals(),
757
+ "ctor_param_names": ctor_param_names}
758
+
759
+ _validation_and_conversion_funcs_ = \
760
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
761
+ _pre_serialization_funcs_ = \
762
+ fancytypes.return_pre_serialization_funcs(**kwargs)
763
+ _de_pre_serialization_funcs_ = \
764
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
765
+
766
+ del ctor_param_names, kwargs
767
+
768
+
769
+
770
+ def __init__(self,
771
+ center=\
772
+ _default_center,
773
+ semi_major_axis=\
774
+ _default_semi_major_axis,
775
+ eccentricity=\
776
+ _default_eccentricity,
777
+ rotation_angle=\
778
+ _default_rotation_angle,
779
+ intra_shape_val=\
780
+ _default_intra_shape_val,
781
+ skip_validation_and_conversion=\
782
+ _default_skip_validation_and_conversion):
783
+ ctor_params = {key: val
784
+ for key, val in locals().items()
785
+ if (key not in ("self", "__class__"))}
786
+ BaseShape.__init__(self, ctor_params)
787
+
788
+ self.execute_post_core_attrs_update_actions()
789
+
790
+ return None
791
+
792
+
793
+
794
+ def execute_post_core_attrs_update_actions(self):
795
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
796
+ for self_core_attr_name in self_core_attrs:
797
+ attr_name = "_"+self_core_attr_name
798
+ attr = self_core_attrs[self_core_attr_name]
799
+ setattr(self, attr_name, attr)
800
+
801
+ return None
802
+
803
+
804
+
805
+ def update(self,
806
+ new_core_attr_subset_candidate,
807
+ skip_validation_and_conversion=\
808
+ _default_skip_validation_and_conversion):
809
+ super().update(new_core_attr_subset_candidate,
810
+ skip_validation_and_conversion)
811
+ self.execute_post_core_attrs_update_actions()
812
+
813
+ return None
814
+
815
+
816
+
817
+ def _eval(self, u_x, u_y):
818
+ A = self._intra_shape_val
819
+ support_arg = self._calc_support_arg(u_x, u_y)
820
+ one = torch.tensor(1.0, device=support_arg.device)
821
+ result = A * torch.heaviside(support_arg, one)
822
+
823
+ return result
824
+
825
+
826
+
827
+ def _calc_support_arg(self, u_x, u_y):
828
+ u_x_c, u_y_c = self._center
829
+ a = self._semi_major_axis
830
+ e = self._eccentricity
831
+ theta = torch.tensor(self._rotation_angle, dtype=u_x.dtype)
832
+
833
+ delta_u_x = u_x-u_x_c
834
+ delta_u_y = u_y-u_y_c
835
+
836
+ e_sq = e*e
837
+ a_sq = a*a
838
+ b_sq = (1-e_sq)*a_sq
839
+
840
+ cos_theta = torch.cos(theta)
841
+ sin_theta = torch.sin(theta)
842
+
843
+ delta_u_x_prime = delta_u_x*cos_theta - delta_u_y*sin_theta
844
+ delta_u_x_prime_sq = delta_u_x_prime*delta_u_x_prime
845
+
846
+ delta_u_y_prime = delta_u_x*sin_theta + delta_u_y*cos_theta
847
+ delta_u_y_prime_sq = delta_u_y_prime*delta_u_y_prime
848
+
849
+ support_arg = (b_sq
850
+ - (b_sq/a_sq)*delta_u_x_prime_sq
851
+ - delta_u_y_prime_sq)
852
+
853
+ return support_arg
854
+
855
+
856
+
857
+ def _check_and_convert_widths(params):
858
+ current_func_name = inspect.stack()[0][3]
859
+ obj_name = current_func_name[19:]
860
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
861
+ widths = czekitout.convert.to_quadruplet_of_positive_floats(**kwargs)
862
+
863
+ return widths
864
+
865
+
866
+
867
+ def _pre_serialize_widths(widths):
868
+ obj_to_pre_serialize = random.choice(list(locals().values()))
869
+ serializable_rep = obj_to_pre_serialize
870
+
871
+ return serializable_rep
872
+
873
+
874
+
875
+ def _de_pre_serialize_widths(serializable_rep):
876
+ widths = serializable_rep
877
+
878
+ return widths
879
+
880
+
881
+
882
+ def _check_and_convert_val_at_center(params):
883
+ current_func_name = inspect.stack()[0][3]
884
+ obj_name = current_func_name[19:]
885
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
886
+ val_at_center = czekitout.convert.to_float(**kwargs)
887
+
888
+ return val_at_center
889
+
890
+
891
+
892
+ def _pre_serialize_val_at_center(val_at_center):
893
+ obj_to_pre_serialize = random.choice(list(locals().values()))
894
+ serializable_rep = obj_to_pre_serialize
895
+
896
+ return serializable_rep
897
+
898
+
899
+
900
+ def _de_pre_serialize_val_at_center(serializable_rep):
901
+ val_at_center = serializable_rep
902
+
903
+ return val_at_center
904
+
905
+
906
+
907
+ def _check_and_convert_functional_form(params):
908
+ current_func_name = inspect.stack()[0][3]
909
+ obj_name = current_func_name[19:]
910
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
911
+ functional_form = czekitout.convert.to_str_from_str_like(**kwargs)
912
+
913
+ kwargs["obj"] = functional_form
914
+ kwargs["accepted_strings"] = ("asymmetric_gaussian",
915
+ "asymmetric_exponential",
916
+ "asymmetric_lorentzian")
917
+ czekitout.check.if_one_of_any_accepted_strings(**kwargs)
918
+
919
+ return functional_form
920
+
921
+
922
+
923
+ def _pre_serialize_functional_form(functional_form):
924
+ obj_to_pre_serialize = random.choice(list(locals().values()))
925
+ serializable_rep = obj_to_pre_serialize
926
+
927
+ return serializable_rep
928
+
929
+
930
+
931
+ def _de_pre_serialize_functional_form(serializable_rep):
932
+ functional_form = serializable_rep
933
+
934
+ return functional_form
935
+
936
+
937
+
938
+ _default_widths = 4*(0.05,)
939
+ _default_val_at_center = 1
940
+ _default_functional_form = "asymmetric_gaussian"
941
+
942
+
943
+
944
+ class Peak(BaseShape):
945
+ r"""The intensity pattern of a peak.
946
+
947
+ Let :math:`\left(u_{x;c;\text{P}},u_{y;c;\text{P}}\right)`,
948
+ :math:`\left(W_{1;1;\text{P}},W_{1;2;\text{P}},
949
+ W_{2;1;\text{P}},W_{2;2;\text{P}}\right)`, and :math:`\theta_{\text{P}}` be
950
+ the center, the widths factors, and the rotation angle of the peak
951
+ respectively. Furthermore, let :math:`A_{\text{P}}` be the value of the
952
+ intensity pattern at the center of the peak. The undistorted intensity
953
+ pattern of the peak is given by:
954
+
955
+ .. math ::
956
+ \mathcal{I}_{\text{P}}\left(u_{x},u_{y}\right)=
957
+ A_{\text{P}}F_{\beta;\text{P}}\left(
958
+ \sqrt{\sum_{\alpha=1}^{2}\left\{ \frac{
959
+ z_{\alpha;\text{P}}\left(u_{x},u_{y}\right)}{
960
+ W_{\alpha;\text{P}}\left(u_{x},u_{y}\right)}\right\}^{2}}\right),
961
+ :label: intensity_pattern_of_peak__1
962
+
963
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
964
+ coordinates of the undistorted intensity pattern of the peak respectively,
965
+
966
+ .. math ::
967
+ F_{\beta;\text{P}}\left(\omega\right)=\begin{cases}
968
+ e^{-\frac{1}{2}\omega^{2}}, & \text{if }\beta=\text{A.G.},\\
969
+ e^{-\omega}, & \text{if }\beta=\text{A.E.},\\
970
+ \left(1+\omega^{2}\right)^{-\frac{3}{2}}, & \text{if }\beta=\text{A.L.},
971
+ \end{cases}
972
+ :label: functional_form_of_peak__1
973
+
974
+ with A.G., A.E., and A.L. being abbreviations of “asymmetric Gaussian”,
975
+ “asymmetric exponential”, and “asymmetric Lorentzian” respectively, and
976
+ :math:`\beta` specifying the functional form of the intensity pattern;
977
+
978
+ .. math ::
979
+ z_{\alpha=1;\text{P}}\left(u_{x},u_{y}\right)=
980
+ \left(u_{x}-u_{x;c;\text{P}}\right)
981
+ \cos\left(\theta_{\text{P}}\right)
982
+ -\left(u_{y}
983
+ -u_{y;c;\text{P}}\right)\sin\left(\theta_{\text{P}}\right);
984
+ :label: z_alpha_peak__1
985
+
986
+ .. math ::
987
+ z_{\alpha=2;\text{P}}\left(u_{x},u_{y}\right)=
988
+ \left(u_{x}-u_{x;c;\text{P}}\right)
989
+ \sin\left(\theta_{\text{P}}\right)
990
+ +\left(u_{y}
991
+ -u_{y;c;\text{P}}\right)\cos\left(\theta_{\text{P}}\right);
992
+ :label: z_alpha_peak__2
993
+
994
+ and
995
+
996
+ .. math ::
997
+ W_{\alpha;\text{P}}\left(u_{x},u_{y}\right)=
998
+ \sum_{\nu=1}^{2}W_{\alpha;\nu;\text{P}}\left[\left\{\nu-1\right\}
999
+ +\left\{ -1\right\}^{\nu+1}
1000
+ \Theta\left(z_{\alpha;\text{P}}\left(u_{x},
1001
+ u_{y}\right)\right)\right],
1002
+ :label: W_alpha_peak__1
1003
+
1004
+ with :math:`\Theta\left(\cdots\right)` being the Heaviside step function.
1005
+
1006
+ Parameters
1007
+ ----------
1008
+ center : `array_like` (`float`, shape=(``2``,)), optional
1009
+ The center of the peak,
1010
+ :math:`\left(u_{x;c;\text{P}},u_{y;c;\text{P}}\right)`.
1011
+ widths : `array_like` (`float`, shape=(``4``,)), optional
1012
+ The width factors of the peak,
1013
+ :math:`\left(W_{1;1;\text{P}},W_{1;2;\text{P}},
1014
+ W_{2;1;\text{P}},W_{2;2;\text{P}}\right)`. Must be a quadruplet of
1015
+ positive numbers.
1016
+ rotation_angle : `float`, optional
1017
+ The rotation angle of the peak, :math:`\theta_{\text{P}}`.
1018
+ val_at_center : `float`, optional
1019
+ The value of the intensity pattern at the center of the peak,
1020
+ :math:`A_{\text{P}}`.
1021
+ functional_form : ``"asymmetric_gaussian"`` | ``"asymmetric_exponential"`` | ``"asymmetric_lorentzian"``, optional
1022
+ The functional form of the peak. If
1023
+ ``functional_form==asymmetric_gaussian``, then :math:`\beta`, which
1024
+ appears in Eq. :eq:`functional_form_of_peak__1`, is set to "A.G."; else
1025
+ if ``functional_form==asymmetric_exponential``, then :math:`\beta` is
1026
+ set to "A.E."; else :math:`\beta` is set to "A.L.".
1027
+ skip_validation_and_conversion : `bool`, optional
1028
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
1029
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
1030
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
1031
+ being `dict` objects.
1032
+
1033
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
1034
+ representation of the constructor parameters excluding the parameter
1035
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
1036
+ different constructor parameter name, excluding the name
1037
+ ``"skip_validation_and_conversion"``, and
1038
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
1039
+ constructor parameter with the name given by ``key``.
1040
+
1041
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
1042
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
1043
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
1044
+ (params_to_be_mapped_to_core_attrs)``.
1045
+
1046
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
1047
+ then ``core_attrs`` is set to
1048
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
1049
+ primarily when the user wants to avoid potentially expensive deep copies
1050
+ and/or conversions of the `dict` values of
1051
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
1052
+ copies or conversions are made in this case.
1053
+
1054
+ """
1055
+ ctor_param_names = ("center",
1056
+ "widths",
1057
+ "rotation_angle",
1058
+ "val_at_center",
1059
+ "functional_form")
1060
+ kwargs = {"namespace_as_dict": globals(),
1061
+ "ctor_param_names": ctor_param_names}
1062
+
1063
+ _validation_and_conversion_funcs_ = \
1064
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
1065
+ _pre_serialization_funcs_ = \
1066
+ fancytypes.return_pre_serialization_funcs(**kwargs)
1067
+ _de_pre_serialization_funcs_ = \
1068
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
1069
+
1070
+ del ctor_param_names, kwargs
1071
+
1072
+
1073
+
1074
+ def __init__(self,
1075
+ center=\
1076
+ _default_center,
1077
+ widths=\
1078
+ _default_widths,
1079
+ rotation_angle=\
1080
+ _default_rotation_angle,
1081
+ val_at_center=\
1082
+ _default_val_at_center,
1083
+ functional_form=\
1084
+ _default_functional_form,
1085
+ skip_validation_and_conversion=\
1086
+ _default_skip_validation_and_conversion):
1087
+ ctor_params = {key: val
1088
+ for key, val in locals().items()
1089
+ if (key not in ("self", "__class__"))}
1090
+ BaseShape.__init__(self, ctor_params)
1091
+
1092
+ self.execute_post_core_attrs_update_actions()
1093
+
1094
+ return None
1095
+
1096
+
1097
+
1098
+ def execute_post_core_attrs_update_actions(self):
1099
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
1100
+ for self_core_attr_name in self_core_attrs:
1101
+ attr_name = "_"+self_core_attr_name
1102
+ attr = self_core_attrs[self_core_attr_name]
1103
+ setattr(self, attr_name, attr)
1104
+
1105
+ theta = torch.tensor(self._rotation_angle)
1106
+ self._cos_theta = torch.cos(theta)
1107
+ self._sin_theta = torch.sin(theta)
1108
+
1109
+ functional_form = self._functional_form
1110
+ if functional_form == "asymmetric_gaussian":
1111
+ self._eval = self._eval_asymmetric_gaussian
1112
+ elif functional_form == "asymmetric_exponential":
1113
+ self._eval = self._eval_asymmetric_exponential
1114
+ else:
1115
+ self._eval = self._eval_asymmetric_lorentzian
1116
+
1117
+ return None
1118
+
1119
+
1120
+
1121
+ def update(self,
1122
+ new_core_attr_subset_candidate,
1123
+ skip_validation_and_conversion=\
1124
+ _default_skip_validation_and_conversion):
1125
+ super().update(new_core_attr_subset_candidate,
1126
+ skip_validation_and_conversion)
1127
+ self.execute_post_core_attrs_update_actions()
1128
+
1129
+ return None
1130
+
1131
+
1132
+
1133
+ def _eval_asymmetric_gaussian(self, u_x, u_y):
1134
+ u_x_c, u_y_c = self._center
1135
+ delta_u_x_c = u_x-u_x_c
1136
+ delta_u_y_c = u_y-u_y_c
1137
+
1138
+ cos_theta = self._cos_theta
1139
+ sin_theta = self._sin_theta
1140
+
1141
+ A = self._val_at_center
1142
+ W_1_1, W_1_2, W_2_1, W_2_2 = self._widths
1143
+
1144
+ z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
1145
+ mask_1 = (z_1 >= 0)
1146
+ W_1 = W_1_1*mask_1 + W_1_2*(~mask_1)
1147
+ z_1_over_W_1 = z_1/W_1
1148
+
1149
+ z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
1150
+ mask_2 = (z_2 >= 0)
1151
+ W_2 = W_2_1*mask_2 + W_2_2*(~mask_2)
1152
+ z_2_over_W_2 = z_2/W_2
1153
+
1154
+ result = A*torch.exp(-0.5*(z_1_over_W_1*z_1_over_W_1
1155
+ + z_2_over_W_2*z_2_over_W_2))
1156
+
1157
+ return result
1158
+
1159
+
1160
+
1161
+ def _eval_asymmetric_exponential(self, u_x, u_y):
1162
+ u_x_c, u_y_c = self._center
1163
+ delta_u_x_c = u_x-u_x_c
1164
+ delta_u_y_c = u_y-u_y_c
1165
+
1166
+ cos_theta = self._cos_theta
1167
+ sin_theta = self._sin_theta
1168
+
1169
+ A = self._val_at_center
1170
+ w_1_1, w_1_2, w_2_1, w_2_2 = self._widths
1171
+
1172
+ z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
1173
+ mask_1 = (z_1 >= 0)
1174
+ w_1 = w_1_1*mask_1 + w_1_2*(~mask_1)
1175
+ z_1_over_w_1 = z_1/w_1
1176
+
1177
+ z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
1178
+ mask_2 = (z_2 >= 0)
1179
+ w_2 = w_2_1*mask_2 + w_2_2*(~mask_2)
1180
+ z_2_over_w_2 = z_2/w_2
1181
+
1182
+ result = A*torch.exp(-torch.sqrt(z_1_over_w_1*z_1_over_w_1
1183
+ + z_2_over_w_2*z_2_over_w_2))
1184
+
1185
+ return result
1186
+
1187
+
1188
+
1189
+ def _eval_asymmetric_lorentzian(self, u_x, u_y):
1190
+ u_x_c, u_y_c = self._center
1191
+ delta_u_x_c = u_x-u_x_c
1192
+ delta_u_y_c = u_y-u_y_c
1193
+
1194
+ cos_theta = self._cos_theta
1195
+ sin_theta = self._sin_theta
1196
+
1197
+ A = self._val_at_center
1198
+ w_1_1, w_1_2, w_2_1, w_2_2 = self._widths
1199
+
1200
+ z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
1201
+ mask_1 = (z_1 >= 0)
1202
+ w_1 = w_1_1*mask_1 + w_1_2*(~mask_1)
1203
+ z_1_over_w_1 = z_1/w_1
1204
+
1205
+ z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
1206
+ mask_2 = (z_2 >= 0)
1207
+ w_2 = w_2_1*mask_2 + w_2_2*(~mask_2)
1208
+ z_2_over_w_2 = z_2/w_2
1209
+
1210
+ denom_factor = torch.sqrt(1
1211
+ + z_1_over_w_1*z_1_over_w_1
1212
+ + z_2_over_w_2*z_2_over_w_2)
1213
+
1214
+ result = A / denom_factor / denom_factor / denom_factor
1215
+
1216
+ return result
1217
+
1218
+
1219
+
1220
+ def _check_and_convert_end_pt_1(params):
1221
+ current_func_name = inspect.stack()[0][3]
1222
+ obj_name = current_func_name[19:]
1223
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1224
+ end_pt_1 = czekitout.convert.to_pair_of_floats(**kwargs)
1225
+
1226
+ return end_pt_1
1227
+
1228
+
1229
+
1230
+ def _pre_serialize_end_pt_1(end_pt_1):
1231
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1232
+ serializable_rep = obj_to_pre_serialize
1233
+
1234
+ return serializable_rep
1235
+
1236
+
1237
+
1238
+ def _de_pre_serialize_end_pt_1(serializable_rep):
1239
+ end_pt_1 = serializable_rep
1240
+
1241
+ return end_pt_1
1242
+
1243
+
1244
+
1245
+ def _check_and_convert_end_pt_2(params):
1246
+ current_func_name = inspect.stack()[0][3]
1247
+ obj_name = current_func_name[19:]
1248
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1249
+ end_pt_2 = czekitout.convert.to_pair_of_floats(**kwargs)
1250
+
1251
+ return end_pt_2
1252
+
1253
+
1254
+
1255
+ def _pre_serialize_end_pt_2(end_pt_2):
1256
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1257
+ serializable_rep = obj_to_pre_serialize
1258
+
1259
+ return serializable_rep
1260
+
1261
+
1262
+
1263
+ def _de_pre_serialize_end_pt_2(serializable_rep):
1264
+ end_pt_2 = serializable_rep
1265
+
1266
+ return end_pt_2
1267
+
1268
+
1269
+
1270
+ def _check_and_convert_width(params):
1271
+ current_func_name = inspect.stack()[0][3]
1272
+ obj_name = current_func_name[19:]
1273
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1274
+ width = czekitout.convert.to_positive_float(**kwargs)
1275
+
1276
+ return width
1277
+
1278
+
1279
+
1280
+ def _pre_serialize_width(width):
1281
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1282
+ serializable_rep = obj_to_pre_serialize
1283
+
1284
+ return serializable_rep
1285
+
1286
+
1287
+
1288
+ def _de_pre_serialize_width(serializable_rep):
1289
+ width = serializable_rep
1290
+
1291
+ return width
1292
+
1293
+
1294
+
1295
+ _default_end_pt_1 = (0, 0.5)
1296
+ _default_end_pt_2 = (1, 0.5)
1297
+ _default_width = 0.05
1298
+
1299
+
1300
+
1301
+ class Band(BaseShape):
1302
+ r"""The intensity pattern of a band.
1303
+
1304
+ Let :math:`\left(u_{x;\text{B};1},u_{y;\text{B};1}\right)`,
1305
+ :math:`\left(u_{x;\text{B};2},u_{y;\text{B};2}\right)`, and
1306
+ :math:`W_{\text{B}}` be the first end point, the second end point, and the
1307
+ width of the band respectively. Furthermore, let :math:`A_{\text{B}}` be the
1308
+ maximum value of the peak. The undistorted intensity pattern of the band is
1309
+ given by:
1310
+
1311
+ .. math ::
1312
+ \mathcal{I}_{\text{B}}\left(u_{x},u_{y}\right)=
1313
+ A_{\text{B}}\Theta\left(\frac{W_{\text{B}}}{2}
1314
+ -d_{\text{B};1}\left(u_{x},u_{y}\right)\right)\Theta\left(
1315
+ \frac{L_{\text{B}}}{2}
1316
+ -d_{\text{B};2}\left(u_{x},u_{y}\right)\right),
1317
+ :label: intensity_pattern_of_band__1
1318
+
1319
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
1320
+ coordinates of the undistorted intensity pattern of the band respectively,
1321
+
1322
+ .. math ::
1323
+ \Theta\left(\omega\right)=\begin{cases}
1324
+ 1, & \text{if }\omega\ge0,\\
1325
+ 0, & \text{otherwise};
1326
+ \end{cases}
1327
+ :label: heaviside_step_function__1
1328
+
1329
+ .. math ::
1330
+ L_{\text{B}}=\sqrt{\left(u_{x;\text{B};2}
1331
+ -u_{x;\text{B};1}\right)^{2}+\left(u_{y;\text{B};2}
1332
+ -u_{y;\text{B};1}\right)^{2}};
1333
+ :label: length_of_band__1
1334
+
1335
+ .. math ::
1336
+ d_{\text{B};1}\left(u_{x},u_{y}\right)=
1337
+ \frac{a_{\text{B};1}u_{x}+b_{\text{B};1}u_{y}
1338
+ +c_{\text{B};1}}{\sqrt{a_{\text{B};1}^{2}+b_{\text{B};1}^{2}}};
1339
+ :label: d_1_of_band__1
1340
+
1341
+ with
1342
+
1343
+ .. math ::
1344
+ a_{\text{B};1}=\begin{cases}
1345
+ u_{y;\text{B};2}-u_{y;\text{B};1},
1346
+ & \text{if }u_{x;\text{B};1}\neq u_{x;\text{B};2},\\
1347
+ 1, & \text{otherwise},
1348
+ \end{cases}
1349
+ :label: a_1_of_band__1
1350
+
1351
+ .. math ::
1352
+ b_{\text{B};1}=u_{x;\text{B};1}-u_{x;\text{B};2},
1353
+ :label: b_1_of_band__1
1354
+
1355
+ .. math ::
1356
+ c_{\text{B};1}=\begin{cases}
1357
+ u_{x;\text{B};2}u_{y;\text{B};1}
1358
+ -u_{x;\text{B};1}u_{y;\text{B};2},
1359
+ & \text{if }u_{x;\text{B};1}\neq u_{x;\text{B};2},\\
1360
+ -u_{x;\text{B};1}, & \text{otherwise};
1361
+ \end{cases}
1362
+ :label: c_1_of_band__1
1363
+
1364
+ .. math ::
1365
+ d_{\text{B};2}\left(u_{x},u_{y}\right)=
1366
+ \frac{a_{\text{B};2}u_{x}+b_{\text{B};2}u_{y}
1367
+ +c_{\text{B};2}}{\sqrt{a_{\text{B};2}^{2}+b_{\text{B};2}^{2}}},
1368
+ :label: d_2_of_band__1
1369
+
1370
+ with
1371
+
1372
+ .. math ::
1373
+ a_{\text{B};2}=\begin{cases}
1374
+ u_{y;\text{B};4}-u_{y;\text{B};3},
1375
+ & \text{if }u_{x;\text{B};3}\neq u_{x;\text{B};4},\\
1376
+ 1, & \text{otherwise},
1377
+ \end{cases}
1378
+ :label: a_2_of_band__1
1379
+
1380
+ .. math ::
1381
+ u_{x;\text{B};3}=u_{x;\text{B};1}
1382
+ +\frac{L_{\text{B}}}{2}\cos\left(\theta_{\text{B}}\right),
1383
+ :label: u_x_3_of_band__1
1384
+
1385
+ .. math ::
1386
+ u_{y;\text{B};3}=u_{y;\text{B};1}
1387
+ +\frac{L_{\text{B}}}{2}\sin\left(\theta_{\text{B}}\right),
1388
+ :label: u_y_3_of_band__1
1389
+
1390
+ .. math ::
1391
+ u_{x;\text{B};4}=u_{x;\text{B};3}
1392
+ +\frac{L_{\text{B}}}{2}\cos\left(\phi_{\text{B}}\right),
1393
+ :label: u_x_4_of_band__1
1394
+
1395
+ .. math ::
1396
+ u_{y;\text{B};4}=u_{y;\text{B};3}
1397
+ +\frac{L_{\text{B}}}{2}\sin\left(\phi_{\text{B}}\right),
1398
+ :label: u_y_4_of_band__1
1399
+
1400
+ .. math ::
1401
+ \theta_{\text{B}}=\tan^{-1}\left(\frac{u_{y;\text{B};2}
1402
+ -u_{y;\text{B};1}}{u_{x;\text{B};2}-u_{x;\text{B};1}}\right),
1403
+ :label: theta_of_band__1
1404
+
1405
+ .. math ::
1406
+ \phi_{\text{B}}=\theta_{\text{B}}+\frac{\pi}{2},
1407
+ :label: phi_of_band__1
1408
+
1409
+ .. math ::
1410
+ b_{\text{B};2}=u_{x;\text{B};3}-u_{x;\text{B};4},
1411
+ :label: b_2_of_band__1
1412
+
1413
+ .. math ::
1414
+ c_{\text{B};2}=\begin{cases}
1415
+ u_{x;\text{B};4}u_{y;\text{B};3}
1416
+ -u_{x;\text{B};3}u_{y;\text{B};4},
1417
+ & \text{if }u_{x;\text{B};3}\neq u_{x;\text{B};4},\\
1418
+ -u_{x;\text{B};3}, & \text{otherwise}.
1419
+ \end{cases}
1420
+ :label: c_2_of_band__1
1421
+
1422
+ Parameters
1423
+ ----------
1424
+ end_pt_1 : `array_like` (`float`, shape=(``2``,)), optional
1425
+ The first end point of the band,
1426
+ :math:`\left(u_{x;\text{B};1},u_{y;\text{B};1}\right)`.
1427
+ end_pt_2 : `array_like` (`float`, shape=(``2``,)), optional
1428
+ The second end point of the band,
1429
+ :math:`\left(u_{x;\text{B};2},u_{y;\text{B};2}\right)`.
1430
+ width : `float`, optional
1431
+ The width of the band, :math:`W_{\text{B}}`. Must be a positve number.
1432
+ intra_shape_val : `float`, optional
1433
+ The value of the intensity pattern inside the band.
1434
+ skip_validation_and_conversion : `bool`, optional
1435
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
1436
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
1437
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
1438
+ being `dict` objects.
1439
+
1440
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
1441
+ representation of the constructor parameters excluding the parameter
1442
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
1443
+ different constructor parameter name, excluding the name
1444
+ ``"skip_validation_and_conversion"``, and
1445
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
1446
+ constructor parameter with the name given by ``key``.
1447
+
1448
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
1449
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
1450
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
1451
+ (params_to_be_mapped_to_core_attrs)``.
1452
+
1453
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
1454
+ then ``core_attrs`` is set to
1455
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
1456
+ primarily when the user wants to avoid potentially expensive deep copies
1457
+ and/or conversions of the `dict` values of
1458
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
1459
+ copies or conversions are made in this case.
1460
+
1461
+ """
1462
+ ctor_param_names = ("end_pt_1",
1463
+ "end_pt_2",
1464
+ "width",
1465
+ "intra_shape_val")
1466
+ kwargs = {"namespace_as_dict": globals(),
1467
+ "ctor_param_names": ctor_param_names}
1468
+
1469
+ _validation_and_conversion_funcs_ = \
1470
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
1471
+ _pre_serialization_funcs_ = \
1472
+ fancytypes.return_pre_serialization_funcs(**kwargs)
1473
+ _de_pre_serialization_funcs_ = \
1474
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
1475
+
1476
+ del ctor_param_names, kwargs
1477
+
1478
+
1479
+
1480
+ def __init__(self,
1481
+ end_pt_1=\
1482
+ _default_end_pt_1,
1483
+ end_pt_2=\
1484
+ _default_end_pt_2,
1485
+ width=\
1486
+ _default_width,
1487
+ intra_shape_val=\
1488
+ _default_intra_shape_val,
1489
+ skip_validation_and_conversion=\
1490
+ _default_skip_validation_and_conversion):
1491
+ ctor_params = {key: val
1492
+ for key, val in locals().items()
1493
+ if (key not in ("self", "__class__"))}
1494
+ BaseShape.__init__(self, ctor_params)
1495
+
1496
+ self.execute_post_core_attrs_update_actions()
1497
+
1498
+ return None
1499
+
1500
+
1501
+
1502
+ def execute_post_core_attrs_update_actions(self):
1503
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
1504
+ for self_core_attr_name in self_core_attrs:
1505
+ attr_name = "_"+self_core_attr_name
1506
+ attr = self_core_attrs[self_core_attr_name]
1507
+ setattr(self, attr_name, attr)
1508
+
1509
+ u_x_1, u_y_1 = self._end_pt_1
1510
+ u_x_2, u_y_2 = self._end_pt_2
1511
+
1512
+ length = np.sqrt((u_x_2-u_x_1)**2 + (u_y_2-u_y_1)**2)
1513
+ theta = np.arctan2(u_y_2-u_y_1, u_x_2-u_x_1)
1514
+ phi = theta + (np.pi/2)
1515
+
1516
+ u_x_3 = (u_x_1 + (length/2)*np.cos(theta)).item()
1517
+ u_y_3 = (u_y_1 + (length/2)*np.sin(theta)).item()
1518
+ u_x_4 = (u_x_3 + (length/2)*np.cos(phi)).item()
1519
+ u_y_4 = (u_y_3 + (length/2)*np.sin(phi)).item()
1520
+
1521
+ a_1 = u_y_2-u_y_1 if (u_x_1 != u_x_2) else 1
1522
+ b_1 = u_x_1-u_x_2
1523
+ c_1 = (u_x_2*u_y_1-u_x_1*u_y_2) if (u_x_1 != u_x_2) else -u_x_1
1524
+
1525
+ a_2 = u_y_4-u_y_3 if (u_x_3 != u_x_4) else 1
1526
+ b_2 = u_x_3-u_x_4
1527
+ c_2 = (u_x_4*u_y_3-u_x_3*u_y_4) if (u_x_3 != u_x_4) else -u_x_3
1528
+
1529
+ self._a_1 = a_1
1530
+ self._b_1 = b_1
1531
+ self._c_1 = c_1
1532
+ self._denom_of_d_1 = np.sqrt(a_1*a_1 + b_1*b_1).item()
1533
+
1534
+ self._a_2 = a_2
1535
+ self._b_2 = b_2
1536
+ self._c_2 = c_2
1537
+ self._denom_of_d_2 = np.sqrt(a_2*a_2 + b_2*b_2).item()
1538
+
1539
+ self._length = length
1540
+
1541
+ return None
1542
+
1543
+
1544
+
1545
+ def update(self,
1546
+ new_core_attr_subset_candidate,
1547
+ skip_validation_and_conversion=\
1548
+ _default_skip_validation_and_conversion):
1549
+ super().update(new_core_attr_subset_candidate,
1550
+ skip_validation_and_conversion)
1551
+ self.execute_post_core_attrs_update_actions()
1552
+
1553
+ return None
1554
+
1555
+
1556
+
1557
+ def _d_1(self, u_x, u_y):
1558
+ a_1 = self._a_1
1559
+ b_1 = self._b_1
1560
+ c_1 = self._c_1
1561
+ denom_of_d_1 = self._denom_of_d_1
1562
+
1563
+ d_1 = torch.abs(a_1*u_x + b_1*u_y + c_1) / denom_of_d_1
1564
+
1565
+ return d_1
1566
+
1567
+
1568
+
1569
+ def _d_2(self, u_x, u_y):
1570
+ a_2 = self._a_2
1571
+ b_2 = self._b_2
1572
+ c_2 = self._c_2
1573
+ denom_of_d_2 = self._denom_of_d_2
1574
+
1575
+ d_2 = torch.abs(a_2*u_x + b_2*u_y + c_2) / denom_of_d_2
1576
+
1577
+ return d_2
1578
+
1579
+
1580
+
1581
+ def _eval(self, u_x, u_y):
1582
+ A = self._intra_shape_val
1583
+ w_over_2 = self._width/2
1584
+ l_over_2 = self._length/2
1585
+
1586
+ d_1 = self._d_1(u_x, u_y)
1587
+ d_2 = self._d_2(u_x, u_y)
1588
+
1589
+ one = torch.tensor(1.0, device=d_1.device)
1590
+
1591
+ result = (A
1592
+ * torch.heaviside(w_over_2 - d_1, one)
1593
+ * torch.heaviside(l_over_2 - d_2, one))
1594
+
1595
+ return result
1596
+
1597
+
1598
+
1599
+ def _check_and_convert_amplitude(params):
1600
+ current_func_name = inspect.stack()[0][3]
1601
+ obj_name = current_func_name[19:]
1602
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1603
+ amplitude = czekitout.convert.to_float(**kwargs)
1604
+
1605
+ return amplitude
1606
+
1607
+
1608
+
1609
+ def _pre_serialize_amplitude(amplitude):
1610
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1611
+ serializable_rep = obj_to_pre_serialize
1612
+
1613
+ return serializable_rep
1614
+
1615
+
1616
+
1617
+ def _de_pre_serialize_amplitude(serializable_rep):
1618
+ amplitude = serializable_rep
1619
+
1620
+ return amplitude
1621
+
1622
+
1623
+
1624
+ def _check_and_convert_wavelength(params):
1625
+ current_func_name = inspect.stack()[0][3]
1626
+ obj_name = current_func_name[19:]
1627
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1628
+ wavelength = czekitout.convert.to_positive_float(**kwargs)
1629
+
1630
+ return wavelength
1631
+
1632
+
1633
+
1634
+ def _pre_serialize_wavelength(wavelength):
1635
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1636
+ serializable_rep = obj_to_pre_serialize
1637
+
1638
+ return serializable_rep
1639
+
1640
+
1641
+
1642
+ def _de_pre_serialize_wavelength(serializable_rep):
1643
+ wavelength = serializable_rep
1644
+
1645
+ return wavelength
1646
+
1647
+
1648
+
1649
+ def _check_and_convert_propagation_direction(params):
1650
+ current_func_name = inspect.stack()[0][3]
1651
+ obj_name = current_func_name[19:]
1652
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1653
+ propagation_direction = czekitout.convert.to_float(**kwargs) % (2*np.pi)
1654
+
1655
+ return propagation_direction
1656
+
1657
+
1658
+
1659
+ def _pre_serialize_propagation_direction(propagation_direction):
1660
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1661
+ serializable_rep = obj_to_pre_serialize
1662
+
1663
+ return serializable_rep
1664
+
1665
+
1666
+
1667
+ def _de_pre_serialize_propagation_direction(serializable_rep):
1668
+ propagation_direction = serializable_rep
1669
+
1670
+ return propagation_direction
1671
+
1672
+
1673
+
1674
+ def _check_and_convert_phase(params):
1675
+ current_func_name = inspect.stack()[0][3]
1676
+ obj_name = current_func_name[19:]
1677
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1678
+ phase = czekitout.convert.to_float(**kwargs)
1679
+
1680
+ return phase
1681
+
1682
+
1683
+
1684
+ def _pre_serialize_phase(phase):
1685
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1686
+ serializable_rep = obj_to_pre_serialize
1687
+
1688
+ return serializable_rep
1689
+
1690
+
1691
+
1692
+ def _de_pre_serialize_phase(serializable_rep):
1693
+ phase = serializable_rep
1694
+
1695
+ return phase
1696
+
1697
+
1698
+
1699
+ _default_amplitude = 1
1700
+ _default_wavelength = 0.01
1701
+ _default_propagation_direction = 0
1702
+ _default_phase = 0
1703
+
1704
+
1705
+
1706
+ class PlaneWave(BaseShape):
1707
+ r"""The intensity pattern of a plane wave.
1708
+
1709
+ Let :math:`A_{\text{PW}}`, :math:`\lambda_{\text{PW}}`,
1710
+ :math:`\theta_{\text{PW}}`, and :math:`\phi_{\text{PW}}` be the amplitude,
1711
+ the wavelength, the propagation direction, and the phase of the plane wave
1712
+ respectively. The undistorted intensity pattern of the plane wave is given
1713
+ by:
1714
+
1715
+ .. math ::
1716
+ \mathcal{I}_{\text{PW}}\left(u_{x},u_{y}\right)=
1717
+ A_{\text{PW}}\cos\left(u_{x}k_{x;\text{PW}}+u_{y}k_{y;\text{PW}}
1718
+ +\phi_{\text{PW}}\right),
1719
+ :label: intensity_pattern_of_plane_wave__1
1720
+
1721
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
1722
+ coordinates of the undistorted intensity pattern of the plane wave
1723
+ respectively,
1724
+
1725
+ .. math ::
1726
+ k_{x;\text{PW}}=\frac{2\pi}{\lambda_{\text{PW}}}
1727
+ \cos\left(\theta_{\text{PW}}\right),
1728
+ :label: k_x_of_plane_wave__1
1729
+
1730
+ and
1731
+
1732
+ .. math ::
1733
+ k_{y;\text{PW}}=\frac{2\pi}{\lambda_{\text{PW}}}
1734
+ \sin\left(\theta_{\text{PW}}\right).
1735
+ :label: k_y_of_plane_wave__1
1736
+
1737
+ Parameters
1738
+ ----------
1739
+ amplitude : `float`, optional
1740
+ The amplitude of the plane wave, :math:`A_{\text{PW}}`.
1741
+ wavelength : `float`, optional
1742
+ The wavelength of the plane wave, :math:`\lambda_{\text{PW}}`. Must be a
1743
+ positve number.
1744
+ propagation_direction : `float`, optional
1745
+ The propagation direction of the plane wave, :math:`\theta_{\text{PW}}`.
1746
+ phase : `float`, optional
1747
+ The phase of the plane wave, :math:`\phi_{\text{PW}}`.
1748
+ skip_validation_and_conversion : `bool`, optional
1749
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
1750
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
1751
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
1752
+ being `dict` objects.
1753
+
1754
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
1755
+ representation of the constructor parameters excluding the parameter
1756
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
1757
+ different constructor parameter name, excluding the name
1758
+ ``"skip_validation_and_conversion"``, and
1759
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
1760
+ constructor parameter with the name given by ``key``.
1761
+
1762
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
1763
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
1764
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
1765
+ (params_to_be_mapped_to_core_attrs)``.
1766
+
1767
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
1768
+ then ``core_attrs`` is set to
1769
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
1770
+ primarily when the user wants to avoid potentially expensive deep copies
1771
+ and/or conversions of the `dict` values of
1772
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
1773
+ copies or conversions are made in this case.
1774
+
1775
+ """
1776
+ ctor_param_names = ("amplitude",
1777
+ "wavelength",
1778
+ "propagation_direction",
1779
+ "phase")
1780
+ kwargs = {"namespace_as_dict": globals(),
1781
+ "ctor_param_names": ctor_param_names}
1782
+
1783
+ _validation_and_conversion_funcs_ = \
1784
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
1785
+ _pre_serialization_funcs_ = \
1786
+ fancytypes.return_pre_serialization_funcs(**kwargs)
1787
+ _de_pre_serialization_funcs_ = \
1788
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
1789
+
1790
+ del ctor_param_names, kwargs
1791
+
1792
+
1793
+
1794
+ def __init__(self,
1795
+ amplitude=\
1796
+ _default_amplitude,
1797
+ wavelength=\
1798
+ _default_wavelength,
1799
+ propagation_direction=\
1800
+ _default_propagation_direction,
1801
+ phase=\
1802
+ _default_phase,
1803
+ skip_validation_and_conversion=\
1804
+ _default_skip_validation_and_conversion):
1805
+ ctor_params = {key: val
1806
+ for key, val in locals().items()
1807
+ if (key not in ("self", "__class__"))}
1808
+ BaseShape.__init__(self, ctor_params)
1809
+
1810
+ self.execute_post_core_attrs_update_actions()
1811
+
1812
+ return None
1813
+
1814
+
1815
+
1816
+ def execute_post_core_attrs_update_actions(self):
1817
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
1818
+ for self_core_attr_name in self_core_attrs:
1819
+ attr_name = "_"+self_core_attr_name
1820
+ attr = self_core_attrs[self_core_attr_name]
1821
+ setattr(self, attr_name, attr)
1822
+
1823
+ L = self._wavelength
1824
+ theta = self._propagation_direction
1825
+
1826
+ self._k_x = (2*np.pi/L)*np.cos(theta).item()
1827
+ self._k_y = (2*np.pi/L)*np.sin(theta).item()
1828
+
1829
+ return None
1830
+
1831
+
1832
+
1833
+ def update(self,
1834
+ new_core_attr_subset_candidate,
1835
+ skip_validation_and_conversion=\
1836
+ _default_skip_validation_and_conversion):
1837
+ super().update(new_core_attr_subset_candidate,
1838
+ skip_validation_and_conversion)
1839
+ self.execute_post_core_attrs_update_actions()
1840
+
1841
+ return None
1842
+
1843
+
1844
+
1845
+ def _eval(self, u_x, u_y):
1846
+ A = self._amplitude
1847
+ phi = self._phase
1848
+ k_x = self._k_x
1849
+ k_y = self._k_y
1850
+
1851
+ result = A*torch.cos(u_x*k_x+u_y*k_y + phi)
1852
+
1853
+ return result
1854
+
1855
+
1856
+
1857
+ def _check_and_convert_midpoint_angle(params):
1858
+ current_func_name = inspect.stack()[0][3]
1859
+ obj_name = current_func_name[19:]
1860
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1861
+ midpoint_angle = czekitout.convert.to_float(**kwargs) % (2*np.pi)
1862
+
1863
+ return midpoint_angle
1864
+
1865
+
1866
+
1867
+ def _pre_serialize_midpoint_angle(midpoint_angle):
1868
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1869
+ serializable_rep = obj_to_pre_serialize
1870
+
1871
+ return serializable_rep
1872
+
1873
+
1874
+
1875
+ def _de_pre_serialize_midpoint_angle(serializable_rep):
1876
+ midpoint_angle = serializable_rep
1877
+
1878
+ return midpoint_angle
1879
+
1880
+
1881
+
1882
+ def _check_and_convert_subtending_angle(params):
1883
+ current_func_name = inspect.stack()[0][3]
1884
+ obj_name = current_func_name[19:]
1885
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
1886
+ subtending_angle = min(abs(czekitout.convert.to_float(**kwargs)), (2*np.pi))
1887
+
1888
+ return subtending_angle
1889
+
1890
+
1891
+
1892
+ def _pre_serialize_subtending_angle(subtending_angle):
1893
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1894
+ serializable_rep = obj_to_pre_serialize
1895
+
1896
+ return serializable_rep
1897
+
1898
+
1899
+
1900
+ def _de_pre_serialize_subtending_angle(serializable_rep):
1901
+ subtending_angle = serializable_rep
1902
+
1903
+ return subtending_angle
1904
+
1905
+
1906
+
1907
+ def _check_and_convert_radial_range(params):
1908
+ current_func_name = inspect.stack()[0][3]
1909
+ char_idx = 19
1910
+ obj_name = current_func_name[char_idx:]
1911
+ obj = params[obj_name]
1912
+
1913
+ func_alias = czekitout.convert.to_pair_of_positive_floats
1914
+ kwargs = {"obj": obj, "obj_name": obj_name}
1915
+ radial_range = func_alias(**kwargs)
1916
+
1917
+ if radial_range[0] >= radial_range[1]:
1918
+ err_msg = globals()[current_func_name+"_err_msg_1"]
1919
+ raise ValueError(err_msg)
1920
+
1921
+ return radial_range
1922
+
1923
+
1924
+
1925
+ def _pre_serialize_radial_range(radial_range):
1926
+ obj_to_pre_serialize = random.choice(list(locals().values()))
1927
+ serializable_rep = obj_to_pre_serialize
1928
+
1929
+ return serializable_rep
1930
+
1931
+
1932
+
1933
+ def _de_pre_serialize_radial_range(serializable_rep):
1934
+ radial_range = serializable_rep
1935
+
1936
+ return radial_range
1937
+
1938
+
1939
+
1940
+ _default_midpoint_angle = 0
1941
+ _default_subtending_angle = np.pi/4
1942
+ _default_radial_range = (0.10, 0.15)
1943
+
1944
+
1945
+
1946
+ class Arc(BaseShape):
1947
+ r"""The intensity pattern of a circular arc.
1948
+
1949
+ Let :math:`\left(u_{x;c;\text{A}},u_{y;c;\text{A}}\right)`,
1950
+ :math:`\theta_{\text{A}}`, :math:`\phi_{\text{A}}`, and
1951
+ :math:`\left(R_{\text{A};1},R_{\text{A};2}\right)` be the circle center, the
1952
+ midpoint angle, the subtending angle, and the radial range of the circular
1953
+ arc respectively. Furthermore, let :math:`A_{\text{A}}` be the value of the
1954
+ intensity pattern inside the arc. The undistorted intensity pattern of the
1955
+ circular arc is given by:
1956
+
1957
+ .. math ::
1958
+ \mathcal{I}_{\text{A}}\left(u_{x},u_{y}\right)&=
1959
+ A_{\text{A}}\\&\quad\mathop{\times}
1960
+ \Theta\left(\left|\frac{\phi_{\text{A}}}{2}\right|
1961
+ -u_{\theta;\text{A}}\right)\\
1962
+ &\quad\mathop{\times}\Theta\left(\left|\frac{\phi_{\text{A}}}{2}\right|
1963
+ +u_{\theta;\text{A}}\right)\\
1964
+ &\quad\mathop{\times}\Theta\left(u_{r;\text{A}}
1965
+ -R_{\text{A};1}\right)\\
1966
+ &\quad\mathop{\times}\Theta\left(R_{\text{A};2}
1967
+ -u_{r;\text{A}}\right),
1968
+ :label: intensity_pattern_of_arc__1
1969
+
1970
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
1971
+ coordinates of the undistorted intensity pattern of the circular arc
1972
+ respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
1973
+ function,
1974
+
1975
+ .. math ::
1976
+ u_{r;\text{A}}=\sqrt{\left(u_{x}-u_{x;c;\text{A}}\right)^{2}
1977
+ +\left(u_{y}-u_{y;c;\text{A}}\right)^{2}},
1978
+ :label: u_r_UA__1
1979
+
1980
+ and
1981
+
1982
+ .. math ::
1983
+ u_{\theta;\text{A}}=\left\{ \tan^{-1}\left(\frac{u_{y}
1984
+ -u_{y;c;\text{A}}}{u_{x}-u_{x;c;\text{A}}}\right)
1985
+ -\theta_{\text{A}}\right\} \mod 2\pi.
1986
+ :label: u_theta_UA__1
1987
+
1988
+ Parameters
1989
+ ----------
1990
+ center : `array_like` (`float`, shape=(``2``,)), optional
1991
+ The circle center, :math:`\left(u_{x;c;\text{A}},
1992
+ u_{y;c;\text{A}}\right)`.
1993
+ midpoint_angle : `float`, optional
1994
+ The midpoint angle of the circular arc, :math:`\theta_{\text{A}}`.
1995
+ subtending_angle : `float`, optional
1996
+ The subtending angle of the circular arc, :math:`\phi_{\text{A}}`.
1997
+ radial_range : `array_like` (`float`, shape=(2,)), optional
1998
+ The radial range of the circular arc,
1999
+ :math:`\left(R_{\text{A};1},R_{\text{A};2}\right)`, where
2000
+ ``radial_range[0]`` and ``radial_range{1]`` are :math:`R_{\text{A};1}`
2001
+ and :math:`R_{\text{A};2}` respectively. ``radial_range`` must satisfy
2002
+ ``0<radial_range[0]<radial_range[1]``.
2003
+ intra_shape_val : `float`, optional
2004
+ The value of the intensity pattern inside the circular arc.
2005
+ skip_validation_and_conversion : `bool`, optional
2006
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
2007
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
2008
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
2009
+ being `dict` objects.
2010
+
2011
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
2012
+ representation of the constructor parameters excluding the parameter
2013
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
2014
+ different constructor parameter name, excluding the name
2015
+ ``"skip_validation_and_conversion"``, and
2016
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
2017
+ constructor parameter with the name given by ``key``.
2018
+
2019
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
2020
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
2021
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
2022
+ (params_to_be_mapped_to_core_attrs)``.
2023
+
2024
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
2025
+ then ``core_attrs`` is set to
2026
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
2027
+ primarily when the user wants to avoid potentially expensive deep copies
2028
+ and/or conversions of the `dict` values of
2029
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
2030
+ copies or conversions are made in this case.
2031
+
2032
+ """
2033
+ ctor_param_names = ("center",
2034
+ "midpoint_angle",
2035
+ "subtending_angle",
2036
+ "radial_range",
2037
+ "intra_shape_val")
2038
+ kwargs = {"namespace_as_dict": globals(),
2039
+ "ctor_param_names": ctor_param_names}
2040
+
2041
+ _validation_and_conversion_funcs_ = \
2042
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
2043
+ _pre_serialization_funcs_ = \
2044
+ fancytypes.return_pre_serialization_funcs(**kwargs)
2045
+ _de_pre_serialization_funcs_ = \
2046
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
2047
+
2048
+ del ctor_param_names, kwargs
2049
+
2050
+
2051
+
2052
+ def __init__(self,
2053
+ center=\
2054
+ _default_center,
2055
+ midpoint_angle=\
2056
+ _default_midpoint_angle,
2057
+ subtending_angle=\
2058
+ _default_subtending_angle,
2059
+ radial_range=\
2060
+ _default_radial_range,
2061
+ intra_shape_val=\
2062
+ _default_intra_shape_val,
2063
+ skip_validation_and_conversion=\
2064
+ _default_skip_validation_and_conversion):
2065
+ ctor_params = {key: val
2066
+ for key, val in locals().items()
2067
+ if (key not in ("self", "__class__"))}
2068
+ BaseShape.__init__(self, ctor_params)
2069
+
2070
+ self.execute_post_core_attrs_update_actions()
2071
+
2072
+ return None
2073
+
2074
+
2075
+
2076
+ def execute_post_core_attrs_update_actions(self):
2077
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
2078
+ for self_core_attr_name in self_core_attrs:
2079
+ attr_name = "_"+self_core_attr_name
2080
+ attr = self_core_attrs[self_core_attr_name]
2081
+ setattr(self, attr_name, attr)
2082
+
2083
+ return None
2084
+
2085
+
2086
+
2087
+ def update(self,
2088
+ new_core_attr_subset_candidate,
2089
+ skip_validation_and_conversion=\
2090
+ _default_skip_validation_and_conversion):
2091
+ super().update(new_core_attr_subset_candidate,
2092
+ skip_validation_and_conversion)
2093
+ self.execute_post_core_attrs_update_actions()
2094
+
2095
+ return None
2096
+
2097
+
2098
+
2099
+ def _eval(self, u_x, u_y):
2100
+ u_x_c, u_y_c = self._center
2101
+ theta = self._midpoint_angle
2102
+ phi = self._subtending_angle
2103
+ R_1, R_2 = self._radial_range
2104
+ A = self._intra_shape_val
2105
+
2106
+ delta_u_x = u_x-u_x_c
2107
+ delta_u_y = u_y-u_y_c
2108
+
2109
+ phi_over_2 = phi/2
2110
+
2111
+ u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
2112
+ u_theta = ((torch.atan2(delta_u_y, delta_u_x)-theta+phi_over_2)
2113
+ % (2*np.pi))
2114
+
2115
+ one = torch.tensor(1.0, device=u_x.device)
2116
+
2117
+ result = (A
2118
+ * torch.heaviside(phi-u_theta, one)
2119
+ * torch.heaviside(u_r-R_1, one)
2120
+ * torch.heaviside(R_2-u_r, one))
2121
+
2122
+ return result
2123
+
2124
+
2125
+
2126
+ def _check_and_convert_radial_reference_pt(params):
2127
+ current_func_name = inspect.stack()[0][3]
2128
+ obj_name = current_func_name[19:]
2129
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2130
+ radial_reference_pt = czekitout.convert.to_pair_of_floats(**kwargs)
2131
+
2132
+ return radial_reference_pt
2133
+
2134
+
2135
+
2136
+ def _pre_serialize_radial_reference_pt(radial_reference_pt):
2137
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2138
+ serializable_rep = obj_to_pre_serialize
2139
+
2140
+ return serializable_rep
2141
+
2142
+
2143
+
2144
+ def _de_pre_serialize_radial_reference_pt(serializable_rep):
2145
+ radial_reference_pt = serializable_rep
2146
+
2147
+ return radial_reference_pt
2148
+
2149
+
2150
+
2151
+ def _check_and_convert_radial_amplitudes(params):
2152
+ current_func_name = inspect.stack()[0][3]
2153
+ obj_name = current_func_name[19:]
2154
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2155
+ func_alias = czekitout.convert.to_tuple_of_nonnegative_floats
2156
+ radial_amplitudes = func_alias(**kwargs)
2157
+
2158
+ num_radial_amplitudes = len(radial_amplitudes)
2159
+ if num_radial_amplitudes == 0:
2160
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2161
+ raise ValueError(err_msg)
2162
+
2163
+ partial_amplitude_sum = sum(radial_amplitudes[1:])
2164
+ if radial_amplitudes[0] <= partial_amplitude_sum:
2165
+ err_msg = globals()[current_func_name+"_err_msg_2"]
2166
+ raise ValueError(err_msg)
2167
+
2168
+ return radial_amplitudes
2169
+
2170
+
2171
+
2172
+ def _pre_serialize_radial_amplitudes(radial_amplitudes):
2173
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2174
+ serializable_rep = obj_to_pre_serialize
2175
+
2176
+ return serializable_rep
2177
+
2178
+
2179
+
2180
+ def _de_pre_serialize_radial_amplitudes(serializable_rep):
2181
+ radial_amplitudes = serializable_rep
2182
+
2183
+ return radial_amplitudes
2184
+
2185
+
2186
+
2187
+ def _check_and_convert_radial_phases(params):
2188
+ current_func_name = inspect.stack()[0][3]
2189
+ obj_name = current_func_name[19:]
2190
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2191
+ radial_phases = czekitout.convert.to_tuple_of_floats(**kwargs)
2192
+ radial_phases = tuple(radial_phase%(2*np.pi)
2193
+ for radial_phase
2194
+ in radial_phases)
2195
+
2196
+ radial_amplitudes = _check_and_convert_radial_amplitudes(params)
2197
+
2198
+ num_radial_phases = len(radial_phases)
2199
+ num_radial_amplitudes = len(radial_amplitudes)
2200
+
2201
+ if num_radial_phases+1 != num_radial_amplitudes:
2202
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2203
+ raise ValueError(err_msg)
2204
+
2205
+ return radial_phases
2206
+
2207
+
2208
+
2209
+ def _pre_serialize_radial_phases(radial_phases):
2210
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2211
+ serializable_rep = obj_to_pre_serialize
2212
+
2213
+ return serializable_rep
2214
+
2215
+
2216
+
2217
+ def _de_pre_serialize_radial_phases(serializable_rep):
2218
+ radial_phases = serializable_rep
2219
+
2220
+ return radial_phases
2221
+
2222
+
2223
+
2224
+ _default_radial_reference_pt = (0.5, 0.5)
2225
+ _default_radial_amplitudes = (0.1,)
2226
+ _default_radial_phases = tuple()
2227
+
2228
+
2229
+
2230
+ class GenericBlob(BaseShape):
2231
+ r"""The intensity pattern of a generic blob.
2232
+
2233
+ Let :math:`\left(u_{x;c;\text{GB}},u_{y;c;\text{GB}}\right)`,
2234
+ :math:`N_{\text{GB}}`, :math:`\left\{ \phi_{\text{GB};n}\right\}
2235
+ _{n=0}^{N_{\text{GB}}-1}`, and :math:`\left\{ D_{\text{GB};n}\right\}
2236
+ _{n=0}^{N_{\text{GB}}}` be the radial reference point, the number of radial
2237
+ phases, the radial phases, and the radial amplitudes of the generic blob
2238
+ respectively. Furthermore, let :math:`A_{\text{GB}}` be the value of the
2239
+ intensity pattern inside the generic blob. The undistorted intensity pattern
2240
+ of the generic blob is given by:
2241
+
2242
+ .. math ::
2243
+ \mathcal{I}_{\text{GB}}\left(u_{x},u_{y}\right)=
2244
+ A_{\text{GB}}\Theta\left(
2245
+ R_{\text{GB}}\left(u_{\theta;\text{UA}}\right)-u_{r;\text{GB}}\right),
2246
+ :label: intensity_pattern_of_generic_blob__1
2247
+
2248
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
2249
+ coordinates of the undistorted intensity pattern of the generic blob
2250
+ respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
2251
+ function,
2252
+
2253
+ .. math ::
2254
+ u_{r;\text{GB}}=\sqrt{\left(u_{x}-u_{x;c;\text{GB}}\right)^{2}
2255
+ +\left(u_{y}-u_{y;c;\text{GB}}\right)^{2}};
2256
+ :label: u_r_UGB__1
2257
+
2258
+ .. math ::
2259
+ u_{\theta;\text{GB}}=
2260
+ \tan^{-1}\left(\frac{u_{y}
2261
+ -u_{y;c;\text{GB}}}{u_{x}-u_{x;c;\text{GB}}}\right);
2262
+ :label: u_theta_UGB__1
2263
+
2264
+ and
2265
+
2266
+ .. math ::
2267
+ R_{\text{GB}}\left(u_{\theta;\text{GB}}\right)&=
2268
+ D_{\text{GB};0}\\&\quad\mathop{+}\min\left(1,N_{\text{GB}}\right)
2269
+ \sum_{n=1}^{N_{\text{GB}}}D_{\text{GB};n}
2270
+ \cos\left(nu_{\theta;\text{GB}}-\phi_{\text{GB};n-1}\right),
2271
+ :label: R_UGB__1
2272
+
2273
+ with
2274
+
2275
+ .. math ::
2276
+ D_{\text{GB};n} \ge 0,
2277
+ \quad\forall n\in\left\{ 1,\ldots,N_{\text{GB}}\right\},
2278
+ :label: D_UGB_n__1
2279
+
2280
+ and
2281
+
2282
+ .. math ::
2283
+ D_{\text{GB};0}>\sum_{n=1}^{N_{\text{GB}}}D_{\text{GB};n}.
2284
+ :label: D_UGB_n__2
2285
+
2286
+ Parameters
2287
+ ----------
2288
+ radial_reference_pt : `array_like` (`float`, shape=(``2``,)), optional
2289
+ The radial reference point, :math:`\left(u_{x;c;\text{GB}},
2290
+ u_{y;c;\text{GB}}\right)`.
2291
+ radial_amplitudes : `array_like` (`float`, ndim=1), optional
2292
+ The radial amplitudes,
2293
+ :math:`\left\{ D_{\text{GB};n}\right\} _{n=0}^{N_{\text{GB}}}`.
2294
+ radial_phases : `array_like` (`float`, shape=(``len(radial_amplitudes)-1``,)), optional
2295
+ The radial phases,
2296
+ :math:`\left\{\phi_{\text{GB};n}\right\}_{n=0}^{N_{\text{GB}}-1}`.
2297
+ intra_shape_val : `float`, optional
2298
+ The value of the intensity pattern inside the generic blob.
2299
+ skip_validation_and_conversion : `bool`, optional
2300
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
2301
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
2302
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
2303
+ being `dict` objects.
2304
+
2305
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
2306
+ representation of the constructor parameters excluding the parameter
2307
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
2308
+ different constructor parameter name, excluding the name
2309
+ ``"skip_validation_and_conversion"``, and
2310
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
2311
+ constructor parameter with the name given by ``key``.
2312
+
2313
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
2314
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
2315
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
2316
+ (params_to_be_mapped_to_core_attrs)``.
2317
+
2318
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
2319
+ then ``core_attrs`` is set to
2320
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
2321
+ primarily when the user wants to avoid potentially expensive deep copies
2322
+ and/or conversions of the `dict` values of
2323
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
2324
+ copies or conversions are made in this case.
2325
+
2326
+ """
2327
+ ctor_param_names = ("radial_reference_pt",
2328
+ "radial_amplitudes",
2329
+ "radial_phases",
2330
+ "intra_shape_val")
2331
+ kwargs = {"namespace_as_dict": globals(),
2332
+ "ctor_param_names": ctor_param_names}
2333
+
2334
+ _validation_and_conversion_funcs_ = \
2335
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
2336
+ _pre_serialization_funcs_ = \
2337
+ fancytypes.return_pre_serialization_funcs(**kwargs)
2338
+ _de_pre_serialization_funcs_ = \
2339
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
2340
+
2341
+ del ctor_param_names, kwargs
2342
+
2343
+
2344
+
2345
+ def __init__(self,
2346
+ radial_reference_pt=\
2347
+ _default_radial_reference_pt,
2348
+ radial_amplitudes=\
2349
+ _default_radial_amplitudes,
2350
+ radial_phases=\
2351
+ _default_radial_phases,
2352
+ intra_shape_val=\
2353
+ _default_intra_shape_val,
2354
+ skip_validation_and_conversion=\
2355
+ _default_skip_validation_and_conversion):
2356
+ ctor_params = {key: val
2357
+ for key, val in locals().items()
2358
+ if (key not in ("self", "__class__"))}
2359
+ BaseShape.__init__(self, ctor_params)
2360
+
2361
+ self.execute_post_core_attrs_update_actions()
2362
+
2363
+ return None
2364
+
2365
+
2366
+
2367
+ def execute_post_core_attrs_update_actions(self):
2368
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
2369
+ for self_core_attr_name in self_core_attrs:
2370
+ attr_name = "_"+self_core_attr_name
2371
+ attr = self_core_attrs[self_core_attr_name]
2372
+ setattr(self, attr_name, attr)
2373
+
2374
+ return None
2375
+
2376
+
2377
+
2378
+ def update(self,
2379
+ new_core_attr_subset_candidate,
2380
+ skip_validation_and_conversion=\
2381
+ _default_skip_validation_and_conversion):
2382
+ super().update(new_core_attr_subset_candidate,
2383
+ skip_validation_and_conversion)
2384
+ self.execute_post_core_attrs_update_actions()
2385
+
2386
+ return None
2387
+
2388
+
2389
+
2390
+ def _eval(self, u_x, u_y):
2391
+ u_x_c, u_y_c = self._radial_reference_pt
2392
+ D = self._radial_amplitudes
2393
+ phi = self._radial_phases
2394
+ A = self._intra_shape_val
2395
+
2396
+ delta_u_x = u_x-u_x_c
2397
+ delta_u_y = u_y-u_y_c
2398
+
2399
+ u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
2400
+ u_theta = torch.atan2(delta_u_y, delta_u_x) % (2*np.pi)
2401
+
2402
+ N = len(phi)
2403
+
2404
+ one = torch.tensor(1.0, device=u_x.device)
2405
+
2406
+ R = D[0]*torch.ones_like(u_theta)
2407
+ for n in range(1, N+1):
2408
+ R += D[n]*torch.cos(n*u_theta - phi[n-1])
2409
+
2410
+ result = A * torch.heaviside(R-u_r, one)
2411
+
2412
+ return result
2413
+
2414
+
2415
+
2416
+ def _check_and_convert_principal_quantum_number(params):
2417
+ current_func_name = inspect.stack()[0][3]
2418
+ obj_name = current_func_name[19:]
2419
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2420
+ principal_quantum_number = czekitout.convert.to_positive_int(**kwargs)
2421
+
2422
+ return principal_quantum_number
2423
+
2424
+
2425
+
2426
+ def _pre_serialize_principal_quantum_number(principal_quantum_number):
2427
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2428
+ serializable_rep = obj_to_pre_serialize
2429
+
2430
+ return serializable_rep
2431
+
2432
+
2433
+
2434
+ def _de_pre_serialize_principal_quantum_number(serializable_rep):
2435
+ principal_quantum_number = serializable_rep
2436
+
2437
+ return principal_quantum_number
2438
+
2439
+
2440
+
2441
+ def _check_and_convert_azimuthal_quantum_number(params):
2442
+ current_func_name = inspect.stack()[0][3]
2443
+ obj_name = current_func_name[19:]
2444
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2445
+ azimuthal_quantum_number = czekitout.convert.to_nonnegative_int(**kwargs)
2446
+
2447
+ principal_quantum_number = \
2448
+ _check_and_convert_principal_quantum_number(params)
2449
+
2450
+ if azimuthal_quantum_number >= principal_quantum_number:
2451
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2452
+ raise ValueError(err_msg)
2453
+
2454
+ return azimuthal_quantum_number
2455
+
2456
+
2457
+
2458
+ def _pre_serialize_azimuthal_quantum_number(azimuthal_quantum_number):
2459
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2460
+ serializable_rep = obj_to_pre_serialize
2461
+
2462
+ return serializable_rep
2463
+
2464
+
2465
+
2466
+ def _de_pre_serialize_azimuthal_quantum_number(serializable_rep):
2467
+ azimuthal_quantum_number = serializable_rep
2468
+
2469
+ return azimuthal_quantum_number
2470
+
2471
+
2472
+
2473
+ def _check_and_convert_magnetic_quantum_number(params):
2474
+ current_func_name = inspect.stack()[0][3]
2475
+ obj_name = current_func_name[19:]
2476
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2477
+ magnetic_quantum_number = czekitout.convert.to_int(**kwargs)
2478
+
2479
+ azimuthal_quantum_number = \
2480
+ _check_and_convert_azimuthal_quantum_number(params)
2481
+
2482
+ if abs(magnetic_quantum_number) > azimuthal_quantum_number:
2483
+ err_msg = globals()[current_func_name+"_err_msg_1"]
2484
+ raise ValueError(err_msg)
2485
+
2486
+ return magnetic_quantum_number
2487
+
2488
+
2489
+
2490
+ def _pre_serialize_magnetic_quantum_number(magnetic_quantum_number):
2491
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2492
+ serializable_rep = obj_to_pre_serialize
2493
+
2494
+ return serializable_rep
2495
+
2496
+
2497
+
2498
+ def _de_pre_serialize_magnetic_quantum_number(serializable_rep):
2499
+ magnetic_quantum_number = serializable_rep
2500
+
2501
+ return magnetic_quantum_number
2502
+
2503
+
2504
+
2505
+ def _check_and_convert_effective_size(params):
2506
+ current_func_name = inspect.stack()[0][3]
2507
+ obj_name = current_func_name[19:]
2508
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2509
+ effective_size = czekitout.convert.to_positive_float(**kwargs)
2510
+
2511
+ return effective_size
2512
+
2513
+
2514
+
2515
+ def _pre_serialize_effective_size(effective_size):
2516
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2517
+ serializable_rep = obj_to_pre_serialize
2518
+
2519
+ return serializable_rep
2520
+
2521
+
2522
+
2523
+ def _de_pre_serialize_effective_size(serializable_rep):
2524
+ effective_size = serializable_rep
2525
+
2526
+ return effective_size
2527
+
2528
+
2529
+
2530
+ def _check_and_convert_renormalization_factor(params):
2531
+ current_func_name = inspect.stack()[0][3]
2532
+ obj_name = current_func_name[19:]
2533
+ kwargs = {"obj": params[obj_name], "obj_name": obj_name}
2534
+ renormalization_factor = czekitout.convert.to_float(**kwargs)
2535
+
2536
+ return renormalization_factor
2537
+
2538
+
2539
+
2540
+ def _pre_serialize_renormalization_factor(renormalization_factor):
2541
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2542
+ serializable_rep = obj_to_pre_serialize
2543
+
2544
+ return serializable_rep
2545
+
2546
+
2547
+
2548
+ def _de_pre_serialize_renormalization_factor(serializable_rep):
2549
+ renormalization_factor = serializable_rep
2550
+
2551
+ return renormalization_factor
2552
+
2553
+
2554
+
2555
+ _default_principal_quantum_number = 1
2556
+ _default_azimuthal_quantum_number = 0
2557
+ _default_magnetic_quantum_number = 0
2558
+ _default_effective_size = 0.1
2559
+ _default_renormalization_factor = 1.0
2560
+
2561
+
2562
+
2563
+ class Orbital(BaseShape):
2564
+ r"""The intensity pattern of a hydrogen-like atomic orbital.
2565
+
2566
+ Let :math:`\left(u_{x;c;\text{O}},u_{y;c;\text{O}}\right)`,
2567
+ :math:`n_{\text{O}}`, :math:`l_{\text{O}}`, :math:`m_{\text{O}}`,
2568
+ :math:`a_{0;\text{O}}^{*}`, and :math:`\theta_{\text{O}}` be the center, the
2569
+ principal quantum number, the azimuthal quantum number, the magnetic quantum
2570
+ number, the effective size, and the rotation angle of the hydrogen-like
2571
+ atomic orbital respectively. Furthermore, let :math:`A_{\text{O}}` be the
2572
+ renormalization factor of the intensity pattern. The undistorted intensity
2573
+ pattern of the orbital is given by:
2574
+
2575
+ .. math ::
2576
+ \mathcal{I}_{\text{O}}\left(u_{x},u_{y}\right)=
2577
+ A_{\text{O}}\left|\psi_{n_{\text{O}},l_{\text{O}},m_{O}}\left(
2578
+ u_{r;\text{O}},u_{\theta;O},0\right)\right|^{2},
2579
+ :label: intensity_pattern_of_orbital__1
2580
+
2581
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
2582
+ coordinates of the undistorted intensity pattern of the orbital
2583
+ respectively,
2584
+
2585
+ .. math ::
2586
+ u_{r;\text{O}}=\sqrt{\left(u_{x}-u_{x;c;\text{O}}\right)^{2}
2587
+ +\left(u_{y}-u_{y;c;\text{O}}\right)^{2}},
2588
+ :label: u_r_O__1
2589
+
2590
+ .. math ::
2591
+ u_{\theta;\text{O}}=\tan^{-1}\left(\frac{u_{y}-u_{y;c;\text{O}}}{
2592
+ u_{x}-u_{x;c;\text{O}}}\right)-\theta_{\text{O}};
2593
+ :label: u_theta_O__1
2594
+
2595
+ .. math ::
2596
+ \psi_{n_{\text{O}},l_{\text{O}},m_{O}}\left(u_{r;\text{O}},u_{\theta;O},
2597
+ u_{\phi;\text{O}}\right)&=\sqrt{\left\{ \frac{2}{n_{\text{O}}
2598
+ a_{0;\text{O}}^{*}}\right\} ^{3}\frac{\left(
2599
+ n_{\text{O}}-l_{\text{O}}-1\right)!}{2n_{\text{O}}\left(
2600
+ n_{\text{O}}+l_{\text{O}}\right)!}}\\&\quad\mathop{\times}
2601
+ e^{-u_{\rho;\text{O}}/2}u_{\rho;\text{O}}^{l_{\text{O}}}
2602
+ L_{n_{\text{O}}-l_{\text{O}}-1}^{\left(2l_{\text{O}}+1\right)}\left(
2603
+ u_{\rho;\text{O}}\right)\\&\quad\mathop{\times}
2604
+ Y_{l_{\text{O}}}^{m_{\text{O}}}\left(u_{\theta;\text{O}},
2605
+ u_{\phi;\text{O}}\right),
2606
+ :label: psi__1
2607
+
2608
+ with
2609
+
2610
+ .. math ::
2611
+ u_{\rho;\text{O}}=\frac{2u_{r;\text{O}}}{n_{\text{O}}
2612
+ a_{0;\text{O}}^{*}},
2613
+ :label: u_rho_O__1
2614
+
2615
+ :math:`L_{n_{\text{O}}-l_{\text{O}}-1}^{2l_{\text{O}}+1}\left(
2616
+ u_{\rho;\text{O}}\right)` being the generalized Laguerre polynomial of
2617
+ degree :math:`n_{\text{O}}-l_{\text{O}}-1`, and
2618
+ :math:`Y_{l_{\text{O}}}^{m_{\text{O}}}\left(u_{\theta;\text{O}},
2619
+ u_{\phi;\text{O}}\right)` is the spherical harmonic function of degree
2620
+ :math:`l_{\text{O}}` and order :math:`m_{\text{O}}`.
2621
+
2622
+ Parameters
2623
+ ----------
2624
+ center : `array_like` (`float`, shape=(``2``,)), optional
2625
+ The center of the hydrogen-like atomic orbital,
2626
+ :math:`\left(u_{x;c;\text{O}}, u_{y;c;\text{O}}\right)`.
2627
+ principal_quantum_number : `int`, optional
2628
+ The principal quantum number of the hydrogen-like atomic orbital,
2629
+ :math:`n_{\text{O}}`. Must be a positve number.
2630
+ azimuthal_quantum_number : `int`, optional
2631
+ The azimuthal quantum number of the hydrogen-like atomic orbital,
2632
+ :math:`l_{\text{O}}`. Must be a nonnegative number satisfying
2633
+ ``azimuthal_quantum_number < principal_quantum_number``.
2634
+ magnetic_quantum_number : `int`, optional
2635
+ The magnetic quantum number of the hydrogen-like atomic orbital,
2636
+ :math:`m_{\text{O}}`. Must satisfy ``abs(magnetic_quantum_number) <=
2637
+ azimuthal_quantum_number``.
2638
+ effective_size : `float`, optional
2639
+ The effective size of the hydrogen-like atomic orbital,
2640
+ :math:`a_{0;\text{O}}^{*}`. Must be a positive number.
2641
+ renormalization_factor : `float`, optional
2642
+ The renormalization factor of the hydrogen-like atomic orbital,
2643
+ :math:`A_{\text{O}}`.
2644
+ rotation_angle : `float`, optional
2645
+ The rotation angle of the hydrogen-like atomic orbital,
2646
+ :math:`\theta_{\text{O}}`.
2647
+ skip_validation_and_conversion : `bool`, optional
2648
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
2649
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
2650
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
2651
+ being `dict` objects.
2652
+
2653
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
2654
+ representation of the constructor parameters excluding the parameter
2655
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
2656
+ different constructor parameter name, excluding the name
2657
+ ``"skip_validation_and_conversion"``, and
2658
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
2659
+ constructor parameter with the name given by ``key``.
2660
+
2661
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
2662
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
2663
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
2664
+ (params_to_be_mapped_to_core_attrs)``.
2665
+
2666
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
2667
+ then ``core_attrs`` is set to
2668
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
2669
+ primarily when the user wants to avoid potentially expensive deep copies
2670
+ and/or conversions of the `dict` values of
2671
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
2672
+ copies or conversions are made in this case.
2673
+
2674
+ """
2675
+ ctor_param_names = ("center",
2676
+ "principal_quantum_number",
2677
+ "azimuthal_quantum_number",
2678
+ "magnetic_quantum_number",
2679
+ "effective_size",
2680
+ "renormalization_factor",
2681
+ "rotation_angle")
2682
+ kwargs = {"namespace_as_dict": globals(),
2683
+ "ctor_param_names": ctor_param_names}
2684
+
2685
+ _validation_and_conversion_funcs_ = \
2686
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
2687
+ _pre_serialization_funcs_ = \
2688
+ fancytypes.return_pre_serialization_funcs(**kwargs)
2689
+ _de_pre_serialization_funcs_ = \
2690
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
2691
+
2692
+ del ctor_param_names, kwargs
2693
+
2694
+
2695
+
2696
+ def __init__(self,
2697
+ center=\
2698
+ _default_center,
2699
+ principal_quantum_number=\
2700
+ _default_principal_quantum_number,
2701
+ azimuthal_quantum_number=\
2702
+ _default_azimuthal_quantum_number,
2703
+ magnetic_quantum_number=\
2704
+ _default_magnetic_quantum_number,
2705
+ effective_size=\
2706
+ _default_effective_size,
2707
+ renormalization_factor=\
2708
+ _default_renormalization_factor,
2709
+ rotation_angle=\
2710
+ _default_rotation_angle,
2711
+ skip_validation_and_conversion=\
2712
+ _default_skip_validation_and_conversion):
2713
+ ctor_params = {key: val
2714
+ for key, val in locals().items()
2715
+ if (key not in ("self", "__class__"))}
2716
+ BaseShape.__init__(self, ctor_params)
2717
+
2718
+ self.execute_post_core_attrs_update_actions()
2719
+
2720
+ return None
2721
+
2722
+
2723
+
2724
+ def execute_post_core_attrs_update_actions(self):
2725
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
2726
+ for self_core_attr_name in self_core_attrs:
2727
+ attr_name = "_"+self_core_attr_name
2728
+ attr = self_core_attrs[self_core_attr_name]
2729
+ setattr(self, attr_name, attr)
2730
+
2731
+ n = self._principal_quantum_number
2732
+ l = self._azimuthal_quantum_number
2733
+ m = self._magnetic_quantum_number
2734
+ a = self._effective_size
2735
+ A = self._renormalization_factor
2736
+
2737
+ self._pre_factor = (A
2738
+ * ((2/(n*a))**3
2739
+ * math.factorial(n-l-1)
2740
+ / (2*n*math.factorial(n+l)))
2741
+ * ((2*l+1)
2742
+ * math.factorial(l-m)
2743
+ / math.factorial(l+m)
2744
+ / (4*np.pi)))
2745
+
2746
+ self._generalized_laguerre_polynomial_coeffs = \
2747
+ self._calc_generalized_laguerre_polynomial_coeffs()
2748
+
2749
+ unformatted_method_name = \
2750
+ "_calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v{}"
2751
+ if l <= n-l-1:
2752
+ method_name = \
2753
+ unformatted_method_name.format(1)
2754
+ else:
2755
+ method_name = \
2756
+ unformatted_method_name.format(2)
2757
+ method_alias = \
2758
+ getattr(self, method_name)
2759
+ self._calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l = \
2760
+ method_alias
2761
+
2762
+ self._associated_legendre_polynomial_coeffs = \
2763
+ self._calc_associated_legendre_polynomial_coeffs()
2764
+
2765
+ return None
2766
+
2767
+
2768
+
2769
+ def _calc_generalized_laguerre_polynomial_coeffs(self):
2770
+ n = self._principal_quantum_number
2771
+ l = self._azimuthal_quantum_number
2772
+
2773
+ polynomial_degree = n-l-1
2774
+ alpha = 2*l+1
2775
+
2776
+ coeff = 1
2777
+ numerator = polynomial_degree+alpha
2778
+ denominator = polynomial_degree
2779
+ for k in range(polynomial_degree):
2780
+ coeff *= (numerator/denominator)
2781
+ numerator -= 1
2782
+ denominator -= 1
2783
+
2784
+ generalized_laguerre_polynomial_coeffs = (coeff,)
2785
+ for i in range(polynomial_degree):
2786
+ i_plus_1 = i+1
2787
+ coeff = -((polynomial_degree-i)
2788
+ / (i_plus_1 * (alpha+i_plus_1))
2789
+ * generalized_laguerre_polynomial_coeffs[-1])
2790
+ generalized_laguerre_polynomial_coeffs += (coeff,)
2791
+
2792
+ return generalized_laguerre_polynomial_coeffs
2793
+
2794
+
2795
+
2796
+ def _calc_associated_legendre_polynomial_coeffs(self):
2797
+ l = self._azimuthal_quantum_number
2798
+ m = self._magnetic_quantum_number
2799
+ abs_m = abs(m)
2800
+ r = (l-abs_m)//2
2801
+
2802
+ coeff = (-1)**r
2803
+ numerator = 2*l - 2*r
2804
+ denominator = l - r
2805
+ for k in range(0, l-r):
2806
+ coeff *= (numerator/denominator)
2807
+ numerator -= 1
2808
+ denominator -= 1
2809
+ denominator = r
2810
+ for k in range(l-r, l):
2811
+ coeff *= (numerator/denominator)
2812
+ numerator -= 1
2813
+ denominator -= 1
2814
+ denominator = 1
2815
+ for k in range(l, 2*l-2*r):
2816
+ coeff *= (numerator/denominator)
2817
+ numerator -= 1
2818
+ coeff /= 2**l
2819
+
2820
+ associated_legendre_polynomial_coeffs = (coeff,)
2821
+ temp_1 = r
2822
+ temp_2 = l-r+1
2823
+ temp_3 = 2*l-2*r+1
2824
+ temp_4 = l-abs_m-2*r+1
2825
+ for i in range(r):
2826
+ coeff = -((temp_1/temp_2)
2827
+ * (temp_3/temp_4)
2828
+ * ((temp_3+1)/(temp_4+1))
2829
+ * associated_legendre_polynomial_coeffs[-1])
2830
+ associated_legendre_polynomial_coeffs += (coeff,)
2831
+ temp_1 -= 1
2832
+ temp_2 += 1
2833
+ temp_3 += 2
2834
+ temp_4 += 2
2835
+
2836
+ return associated_legendre_polynomial_coeffs
2837
+
2838
+
2839
+
2840
+ def update(self,
2841
+ new_core_attr_subset_candidate,
2842
+ skip_validation_and_conversion=\
2843
+ _default_skip_validation_and_conversion):
2844
+ super().update(new_core_attr_subset_candidate,
2845
+ skip_validation_and_conversion)
2846
+ self.execute_post_core_attrs_update_actions()
2847
+
2848
+ return None
2849
+
2850
+
2851
+
2852
+ def _eval(self, u_x, u_y):
2853
+ u_x_c, u_y_c = self._center
2854
+ n = self._principal_quantum_number
2855
+ l = self._azimuthal_quantum_number
2856
+ a = self._effective_size
2857
+ theta = self._rotation_angle
2858
+ pre_factor = self._pre_factor
2859
+
2860
+ delta_u_x = u_x-u_x_c
2861
+ delta_u_y = u_y-u_y_c
2862
+
2863
+ u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
2864
+ u_rho = (2/n/a)*u_r
2865
+
2866
+ u_theta = torch.atan2(delta_u_y, delta_u_x) - theta
2867
+ cos_u_theta = torch.cos(u_theta)
2868
+
2869
+ method_name = ("_calc_generalized_laguerre_polynomial"
2870
+ "_and_u_rho_to_power_of_2l")
2871
+ method_alias = getattr(self, method_name)
2872
+ L, u_rho_to_power_of_2l = method_alias(u_rho)
2873
+ L_sq = L*L
2874
+
2875
+ method_name = "_calc_associated_legendre_polynomial_sq"
2876
+ method_alias = getattr(self, method_name)
2877
+ P_sq = method_alias(cos_u_theta)
2878
+
2879
+ result = (pre_factor
2880
+ * torch.exp(-u_rho)
2881
+ * u_rho_to_power_of_2l
2882
+ * L_sq
2883
+ * P_sq)
2884
+
2885
+ return result
2886
+
2887
+
2888
+
2889
+ def _calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v1(
2890
+ self, u_rho):
2891
+ coeffs = self._generalized_laguerre_polynomial_coeffs
2892
+
2893
+ n = self._principal_quantum_number
2894
+ l = self._azimuthal_quantum_number
2895
+ polynomial_degree = n-l-1
2896
+
2897
+ power_of_u_rho = torch.ones_like(u_rho)
2898
+ generalized_laguerre_polynomial = coeffs[0]*power_of_u_rho
2899
+
2900
+ for i in range(1, l+1):
2901
+ power_of_u_rho *= u_rho
2902
+ generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
2903
+
2904
+ u_rho_to_power_of_2l = power_of_u_rho*power_of_u_rho
2905
+
2906
+ for i in range(l+1, polynomial_degree+1):
2907
+ power_of_u_rho *= u_rho
2908
+ generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
2909
+
2910
+ return generalized_laguerre_polynomial, u_rho_to_power_of_2l
2911
+
2912
+
2913
+
2914
+ def _calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v2(
2915
+ self, u_rho):
2916
+ coeffs = self._generalized_laguerre_polynomial_coeffs
2917
+
2918
+ n = self._principal_quantum_number
2919
+ l = self._azimuthal_quantum_number
2920
+ polynomial_degree = n-l-1
2921
+
2922
+ power_of_u_rho = torch.ones_like(u_rho)
2923
+ generalized_laguerre_polynomial = coeffs[0]*power_of_u_rho
2924
+
2925
+ for i in range(1, polynomial_degree+1):
2926
+ power_of_u_rho *= u_rho
2927
+ generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
2928
+
2929
+ for i in range(polynomial_degree+1, l+1):
2930
+ power_of_u_rho *= u_rho
2931
+
2932
+ u_rho_to_power_of_2l = power_of_u_rho*power_of_u_rho
2933
+
2934
+ return generalized_laguerre_polynomial, u_rho_to_power_of_2l
2935
+
2936
+
2937
+
2938
+ def _calc_associated_legendre_polynomial_sq(self, cos_u_theta):
2939
+ coeffs = self._associated_legendre_polynomial_coeffs
2940
+
2941
+ l = self._azimuthal_quantum_number
2942
+ m = self._magnetic_quantum_number
2943
+ abs_m = abs(m)
2944
+ r = (l-abs_m)//2
2945
+
2946
+ x = cos_u_theta
2947
+ x_sq = x*x
2948
+ power_of_x = torch.ones_like(x)
2949
+ for _ in range(l-abs_m-2*r):
2950
+ power_of_x *= x
2951
+
2952
+ y_sq = 1-x_sq
2953
+
2954
+ associated_legendre_polynomial_sq = coeffs[0]*power_of_x
2955
+ for i in range(1, r+1):
2956
+ power_of_x *= x_sq
2957
+ associated_legendre_polynomial_sq += coeffs[i]*power_of_x
2958
+ associated_legendre_polynomial_sq *= associated_legendre_polynomial_sq
2959
+ for i in range(abs_m):
2960
+ associated_legendre_polynomial_sq *= y_sq
2961
+
2962
+ return associated_legendre_polynomial_sq
2963
+
2964
+
2965
+
2966
+ def _check_and_convert_bg_ellipse(params):
2967
+ current_func_name = inspect.stack()[0][3]
2968
+ char_idx = 19
2969
+ obj_name = current_func_name[char_idx:]
2970
+ obj = params[obj_name]
2971
+
2972
+ accepted_types = (Ellipse, Circle, type(None))
2973
+
2974
+ if isinstance(obj, accepted_types[-1]):
2975
+ bg_ellipse = accepted_types[0]()
2976
+ else:
2977
+ kwargs = {"obj": obj,
2978
+ "obj_name": obj_name,
2979
+ "accepted_types": accepted_types}
2980
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
2981
+ bg_ellipse = copy.deepcopy(obj)
2982
+
2983
+ return bg_ellipse
2984
+
2985
+
2986
+
2987
+ def _pre_serialize_bg_ellipse(bg_ellipse):
2988
+ obj_to_pre_serialize = random.choice(list(locals().values()))
2989
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
2990
+
2991
+ return serializable_rep
2992
+
2993
+
2994
+
2995
+ def _de_pre_serialize_bg_ellipse(serializable_rep):
2996
+ if "radius" in serializable_rep:
2997
+ bg_ellipse = Circle.de_pre_serialize(serializable_rep)
2998
+ else:
2999
+ bg_ellipse = Ellipse.de_pre_serialize(serializable_rep)
3000
+
3001
+ return bg_ellipse
3002
+
3003
+
3004
+
3005
+ def _check_and_convert_fg_ellipse(params):
3006
+ current_func_name = inspect.stack()[0][3]
3007
+ char_idx = 19
3008
+ obj_name = current_func_name[char_idx:]
3009
+ obj = params[obj_name]
3010
+
3011
+ accepted_types = (Ellipse, Circle, type(None))
3012
+
3013
+ if isinstance(obj, accepted_types[-1]):
3014
+ fg_ellipse = accepted_types[0]()
3015
+ else:
3016
+ kwargs = {"obj": obj,
3017
+ "obj_name": obj_name,
3018
+ "accepted_types": accepted_types}
3019
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
3020
+ fg_ellipse = copy.deepcopy(obj)
3021
+
3022
+ return fg_ellipse
3023
+
3024
+
3025
+
3026
+ def _pre_serialize_fg_ellipse(fg_ellipse):
3027
+ obj_to_pre_serialize = random.choice(list(locals().values()))
3028
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
3029
+
3030
+ return serializable_rep
3031
+
3032
+
3033
+
3034
+ def _de_pre_serialize_fg_ellipse(serializable_rep):
3035
+ if "radius" in serializable_rep:
3036
+ fg_ellipse = Circle.de_pre_serialize(serializable_rep)
3037
+ else:
3038
+ fg_ellipse = Ellipse.de_pre_serialize(serializable_rep)
3039
+
3040
+ return fg_ellipse
3041
+
3042
+
3043
+
3044
+ _default_bg_ellipse = None
3045
+ _default_fg_ellipse = None
3046
+
3047
+
3048
+
3049
+ class Lune(BaseShape):
3050
+ r"""The intensity pattern of a lune.
3051
+
3052
+ Let :math:`\mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right)` and
3053
+ :math:`\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)` be the intensity
3054
+ patterns of the background and the foreground ellipses respectively, the
3055
+ latter of which is used to mask the former to form the lune. The undistorted
3056
+ intensity pattern of the lune is given by:
3057
+
3058
+ .. math ::
3059
+ \mathcal{I}_{\text{L}}\left(u_{x},u_{y}\right)=\begin{cases}
3060
+ \mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right),
3061
+ & \text{if }\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)=0,\\
3062
+ 0, & \text{otherwise},
3063
+ \end{cases}
3064
+ :label: intensity_pattern_of_lune__1
3065
+
3066
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
3067
+ coordinates of the undistorted intensity pattern of the lune respectively.
3068
+
3069
+ Parameters
3070
+ ----------
3071
+ bg_ellipse : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | `None`, optional
3072
+ The intensity pattern of the background ellipse,
3073
+ :math:`\mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right)`. If
3074
+ ``bg_ellipse`` is set to ``None``, then the parameter will be reassigned
3075
+ to the value ``fakecbed.shapes.Ellipse()``.
3076
+ fg_ellipse : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | `None`, optional
3077
+ The intensity pattern of the foreground ellipse,
3078
+ :math:`\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)`. If
3079
+ ``fg_ellipse`` is set to ``None``, then the parameter will be reassigned
3080
+ to the value ``fakecbed.shapes.Ellipse()``.
3081
+ skip_validation_and_conversion : `bool`, optional
3082
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
3083
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
3084
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
3085
+ being `dict` objects.
3086
+
3087
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
3088
+ representation of the constructor parameters excluding the parameter
3089
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
3090
+ different constructor parameter name, excluding the name
3091
+ ``"skip_validation_and_conversion"``, and
3092
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
3093
+ constructor parameter with the name given by ``key``.
3094
+
3095
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
3096
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
3097
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
3098
+ (params_to_be_mapped_to_core_attrs)``.
3099
+
3100
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
3101
+ then ``core_attrs`` is set to
3102
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
3103
+ primarily when the user wants to avoid potentially expensive deep copies
3104
+ and/or conversions of the `dict` values of
3105
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
3106
+ copies or conversions are made in this case.
3107
+
3108
+ """
3109
+ ctor_param_names = ("bg_ellipse",
3110
+ "fg_ellipse")
3111
+ kwargs = {"namespace_as_dict": globals(),
3112
+ "ctor_param_names": ctor_param_names}
3113
+
3114
+ _validation_and_conversion_funcs_ = \
3115
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
3116
+ _pre_serialization_funcs_ = \
3117
+ fancytypes.return_pre_serialization_funcs(**kwargs)
3118
+ _de_pre_serialization_funcs_ = \
3119
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
3120
+
3121
+ del ctor_param_names, kwargs
3122
+
3123
+
3124
+
3125
+ def __init__(self,
3126
+ bg_ellipse=\
3127
+ _default_bg_ellipse,
3128
+ fg_ellipse=\
3129
+ _default_fg_ellipse,
3130
+ skip_validation_and_conversion=\
3131
+ _default_skip_validation_and_conversion):
3132
+ ctor_params = {key: val
3133
+ for key, val in locals().items()
3134
+ if (key not in ("self", "__class__"))}
3135
+ BaseShape.__init__(self, ctor_params)
3136
+
3137
+ self.execute_post_core_attrs_update_actions()
3138
+
3139
+ return None
3140
+
3141
+
3142
+
3143
+ def execute_post_core_attrs_update_actions(self):
3144
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
3145
+ for self_core_attr_name in self_core_attrs:
3146
+ attr_name = "_"+self_core_attr_name
3147
+ attr = self_core_attrs[self_core_attr_name]
3148
+ setattr(self, attr_name, attr)
3149
+
3150
+ return None
3151
+
3152
+
3153
+
3154
+ def update(self,
3155
+ new_core_attr_subset_candidate,
3156
+ skip_validation_and_conversion=\
3157
+ _default_skip_validation_and_conversion):
3158
+ super().update(new_core_attr_subset_candidate,
3159
+ skip_validation_and_conversion)
3160
+ self.execute_post_core_attrs_update_actions()
3161
+
3162
+ return None
3163
+
3164
+
3165
+
3166
+ def _eval(self, u_x, u_y):
3167
+ bg_ellipse = self._bg_ellipse
3168
+ fg_ellipse = self._fg_ellipse
3169
+ result = bg_ellipse.eval(u_x, u_y) * (fg_ellipse.eval(u_x, u_y) == 0)
3170
+
3171
+ return result
3172
+
3173
+
3174
+
3175
+ def _check_and_convert_support(params):
3176
+ current_func_name = inspect.stack()[0][3]
3177
+ char_idx = 19
3178
+ obj_name = current_func_name[char_idx:]
3179
+ obj = params[obj_name]
3180
+
3181
+ accepted_types = (Circle, Ellipse, Band, Arc, GenericBlob, Lune, type(None))
3182
+
3183
+ if isinstance(obj, accepted_types[-1]):
3184
+ support = accepted_types[0]()
3185
+ else:
3186
+ kwargs = {"obj": obj,
3187
+ "obj_name": obj_name,
3188
+ "accepted_types": accepted_types}
3189
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
3190
+ support = copy.deepcopy(obj)
3191
+
3192
+ return support
3193
+
3194
+
3195
+
3196
+ def _pre_serialize_support(support):
3197
+ obj_to_pre_serialize = random.choice(list(locals().values()))
3198
+ serializable_rep = obj_to_pre_serialize.pre_serialize()
3199
+
3200
+ return serializable_rep
3201
+
3202
+
3203
+
3204
+ def _de_pre_serialize_support(serializable_rep):
3205
+ if "radius" in serializable_rep:
3206
+ support = Circle.de_pre_serialize(serializable_rep)
3207
+ elif "eccentricity" in serializable_rep:
3208
+ support = Ellipse.de_pre_serialize(serializable_rep)
3209
+ elif "end_pt_1" in serializable_rep:
3210
+ support = Band.de_pre_serialize(serializable_rep)
3211
+ elif "subtending_angle" in serializable_rep:
3212
+ support = Arc.de_pre_serialize(serializable_rep)
3213
+ elif "radial_amplitudes" in serializable_rep:
3214
+ support = GenericBlob.de_pre_serialize(serializable_rep)
3215
+ else:
3216
+ support = Lune.de_pre_serialize(serializable_rep)
3217
+
3218
+ return support
3219
+
3220
+
3221
+
3222
+ def _check_and_convert_intra_support_shapes(params):
3223
+ current_func_name = inspect.stack()[0][3]
3224
+ char_idx = 19
3225
+ obj_name = current_func_name[char_idx:]
3226
+ obj = params[obj_name]
3227
+
3228
+ accepted_types = (Circle,
3229
+ Ellipse,
3230
+ Peak,
3231
+ Band,
3232
+ PlaneWave,
3233
+ Arc,
3234
+ GenericBlob,
3235
+ Orbital,
3236
+ Lune,
3237
+ NonuniformBoundedShape)
3238
+
3239
+ try:
3240
+ for intra_support_shape in obj:
3241
+ kwargs = {"obj": intra_support_shape,
3242
+ "obj_name": "intra_support_shape",
3243
+ "accepted_types": accepted_types}
3244
+ czekitout.check.if_instance_of_any_accepted_types(**kwargs)
3245
+ except:
3246
+ err_msg = globals()[current_func_name+"_err_msg_1"]
3247
+ raise TypeError(err_msg)
3248
+
3249
+ intra_support_shapes = copy.deepcopy(obj)
3250
+
3251
+ return intra_support_shapes
3252
+
3253
+
3254
+
3255
+ def _pre_serialize_intra_support_shapes(intra_support_shapes):
3256
+ obj_to_pre_serialize = random.choice(list(locals().values()))
3257
+ serializable_rep = tuple()
3258
+ for elem in obj_to_pre_serialize:
3259
+ serializable_rep += (elem.pre_serialize(),)
3260
+
3261
+ return serializable_rep
3262
+
3263
+
3264
+
3265
+ def _de_pre_serialize_intra_support_shapes(serializable_rep):
3266
+ intra_support_shapes = tuple()
3267
+
3268
+ for pre_serialized_intra_support_shape in serializable_rep:
3269
+ if "radius" in pre_serialized_intra_support_shape:
3270
+ cls_alias = Circle
3271
+ elif "eccentricity" in pre_serialized_intra_support_shape:
3272
+ cls_alias = Ellipse
3273
+ elif "functional_form" in pre_serialized_intra_support_shape:
3274
+ cls_alias = Peak
3275
+ elif "end_pt_1" in pre_serialized_intra_support_shape:
3276
+ cls_alias = Band
3277
+ elif "propagation_direction" in pre_serialized_intra_support_shape:
3278
+ cls_alias = PlaneWave
3279
+ elif "subtending_angle" in pre_serialized_intra_support_shape:
3280
+ cls_alias = Arc
3281
+ elif "radial_amplitudes" in pre_serialized_intra_support_shape:
3282
+ cls_alias = GenericBlob
3283
+ elif "magnetic_quantum_number" in pre_serialized_intra_support_shape:
3284
+ cls_alias = Orbital
3285
+ elif "bg_ellipse" in pre_serialized_intra_support_shape:
3286
+ cls_alias = Lune
3287
+ else:
3288
+ cls_alias = NonuniformBoundedShape
3289
+
3290
+ intra_support_shape = \
3291
+ cls_alias.de_pre_serialize(pre_serialized_intra_support_shape)
3292
+ intra_support_shapes += \
3293
+ (intra_support_shape,)
3294
+
3295
+ return intra_support_shapes
3296
+
3297
+
3298
+
3299
+ _default_support = None
3300
+ _default_intra_support_shapes = tuple()
3301
+
3302
+
3303
+
3304
+ class NonuniformBoundedShape(BaseShape):
3305
+ r"""The intensity pattern of a nonuniform bounded shape.
3306
+
3307
+ Let :math:`\mathcal{I}_{0;\text{NBS}}\left(u_{x},u_{y}\right)`,
3308
+ :math:`N_{\text{NBS}}`, and :math:`\left\{
3309
+ \mathcal{I}_{k;\text{NBS}}\left(u_{x},u_{y}\right)\right\}
3310
+ _{k=1}^{N_{\text{NBS}}}` be the intensity pattern of the uniform bounded
3311
+ shape supporting the nonuniform bounded shape, the number of intra-support
3312
+ shapes, and the intensity patterns of the intra-support shapes
3313
+ respectively. The undistorted intensity pattern of the nonuniform bounded
3314
+ shape is given by:
3315
+
3316
+ .. math ::
3317
+ \mathcal{I}_{\text{NBS}}\left(u_{x},u_{y}\right)=
3318
+ \mathcal{I}_{0;\text{NBS}}\left(u_{x},u_{y}\right)\left|
3319
+ \sum_{k=1}^{N_{\text{NBS}}}\mathcal{I}_{k;\text{NBS}}\left(u_{x},
3320
+ u_{y}\right)\right|,
3321
+ :label: intensity_pattern_of_nonuniform_bounded_shape__1
3322
+
3323
+ where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
3324
+ coordinates of the undistorted intensity pattern of the nonuniform bounded
3325
+ shape respectively.
3326
+
3327
+ Parameters
3328
+ ----------
3329
+ support : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | :class:`fakecbed.shapes.Band` | :class:`fakecbed.shapes.Arc` | :class:`fakecbed.shapes.GenericBlob` | :class:`fakecbed.shapes.Lune` | `None`, optional
3330
+ The intensity pattern of the uniform bounded shape supporting the
3331
+ nonuniform bounded shape,
3332
+ :math:`\mathcal{I}_{0;\text{NBS}}\left(u_{x},u_{y}\right)`. If
3333
+ ``support`` is set to ``None``, then the parameter will be reassigned to
3334
+ the value ``fakecbed.shapes.Circle()``.
3335
+ intra_support_shapes : `array_like` (`any_shape`, ndim=1), optional
3336
+ The intensity patterns of the intra-support shapes, :math:`\left\{
3337
+ \mathcal{I}_{k;\text{NBS}}\left(u_{x},u_{y}\right)\right\}
3338
+ _{k=1}^{N_{\text{NBS}}}`. Note that `any_shape` means any public class
3339
+ defined in the module :mod:`fakecbed.shapes` that is a subclass of
3340
+ :class:`fakecbed.shapes.BaseShape`.
3341
+ skip_validation_and_conversion : `bool`, optional
3342
+ Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
3343
+ attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
3344
+ and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
3345
+ being `dict` objects.
3346
+
3347
+ Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
3348
+ representation of the constructor parameters excluding the parameter
3349
+ ``skip_validation_and_conversion``, where each `dict` key ``key`` is a
3350
+ different constructor parameter name, excluding the name
3351
+ ``"skip_validation_and_conversion"``, and
3352
+ ``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
3353
+ constructor parameter with the name given by ``key``.
3354
+
3355
+ If ``skip_validation_and_conversion`` is set to ``False``, then for each
3356
+ key ``key`` in ``params_to_be_mapped_to_core_attrs``,
3357
+ ``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
3358
+ (params_to_be_mapped_to_core_attrs)``.
3359
+
3360
+ Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
3361
+ then ``core_attrs`` is set to
3362
+ ``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
3363
+ primarily when the user wants to avoid potentially expensive deep copies
3364
+ and/or conversions of the `dict` values of
3365
+ ``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
3366
+ copies or conversions are made in this case.
3367
+
3368
+ """
3369
+ ctor_param_names = ("support",
3370
+ "intra_support_shapes")
3371
+ kwargs = {"namespace_as_dict": globals(),
3372
+ "ctor_param_names": ctor_param_names}
3373
+
3374
+ _validation_and_conversion_funcs_ = \
3375
+ fancytypes.return_validation_and_conversion_funcs(**kwargs)
3376
+ _pre_serialization_funcs_ = \
3377
+ fancytypes.return_pre_serialization_funcs(**kwargs)
3378
+ _de_pre_serialization_funcs_ = \
3379
+ fancytypes.return_de_pre_serialization_funcs(**kwargs)
3380
+
3381
+ del ctor_param_names, kwargs
3382
+
3383
+
3384
+
3385
+ def __init__(self,
3386
+ support=\
3387
+ _default_support,
3388
+ intra_support_shapes=\
3389
+ _default_intra_support_shapes,
3390
+ skip_validation_and_conversion=\
3391
+ _default_skip_validation_and_conversion):
3392
+ ctor_params = {key: val
3393
+ for key, val in locals().items()
3394
+ if (key not in ("self", "__class__"))}
3395
+ BaseShape.__init__(self, ctor_params)
3396
+
3397
+ self.execute_post_core_attrs_update_actions()
3398
+
3399
+ return None
3400
+
3401
+
3402
+
3403
+ def execute_post_core_attrs_update_actions(self):
3404
+ self_core_attrs = self.get_core_attrs(deep_copy=False)
3405
+ for self_core_attr_name in self_core_attrs:
3406
+ attr_name = "_"+self_core_attr_name
3407
+ attr = self_core_attrs[self_core_attr_name]
3408
+ setattr(self, attr_name, attr)
3409
+
3410
+ return None
3411
+
3412
+
3413
+
3414
+ def update(self,
3415
+ new_core_attr_subset_candidate,
3416
+ skip_validation_and_conversion=\
3417
+ _default_skip_validation_and_conversion):
3418
+ super().update(new_core_attr_subset_candidate,
3419
+ skip_validation_and_conversion)
3420
+ self.execute_post_core_attrs_update_actions()
3421
+
3422
+ return None
3423
+
3424
+
3425
+
3426
+ def _eval(self, u_x, u_y):
3427
+ result = (self._eval_without_support(u_x, u_y)
3428
+ * self._eval_without_intra_support_shapes(u_x, u_y))
3429
+
3430
+ return result
3431
+
3432
+
3433
+
3434
+ def _eval_without_intra_support_shapes(self, u_x, u_y):
3435
+ support = self._support
3436
+ result = support._eval(u_x, u_y)
3437
+
3438
+ return result
3439
+
3440
+
3441
+
3442
+ def _eval_without_support(self, u_x, u_y):
3443
+ intra_support_shapes = self._intra_support_shapes
3444
+
3445
+ result = torch.zeros_like(u_x)
3446
+ for intra_support_shape in intra_support_shapes:
3447
+ result += intra_support_shape._eval(u_x, u_y)
3448
+ result = torch.abs(result)
3449
+
3450
+ return result
3451
+
3452
+
3453
+
3454
+ ###########################
3455
+ ## Define error messages ##
3456
+ ###########################
3457
+
3458
+ _check_and_convert_cartesian_coords_err_msg_1 = \
3459
+ ("The objects ``{}`` and ``{}`` must be real-valued matrices of the same "
3460
+ "shape.")
3461
+
3462
+ _check_and_convert_real_torch_matrix_err_msg_1 = \
3463
+ ("The object ``{}`` must be a real-valued matrix.")
3464
+
3465
+ _base_shape_err_msg_1 = \
3466
+ ("Cannot construct instances of the class `fakecbed.shapes.BaseShape`, "
3467
+ "only subclasses of itself defined in the `fakecbed` library.")
3468
+
3469
+ _check_and_convert_eccentricity_err_msg_1 = \
3470
+ ("The object ``eccentricity`` must be a nonnegative number less than or "
3471
+ "equal to unity.")
3472
+
3473
+ _check_and_convert_radial_range_err_msg_1 = \
3474
+ ("The object ``radial_range`` must be a pair of positive real numbers "
3475
+ "satisfying ``radial_range[0]<radial_range[1]``.")
3476
+
3477
+ _check_and_convert_radial_amplitudes_err_msg_1 = \
3478
+ ("The object ``radial_amplitudes`` must be a non-empty sequence of "
3479
+ "nonnegative real numbers.")
3480
+ _check_and_convert_radial_amplitudes_err_msg_2 = \
3481
+ ("The object ``radial_amplitudes`` must satisfy ``radial_amplitudes[0] > "
3482
+ "sum(radial_amplitudes[1:])``.")
3483
+
3484
+ _check_and_convert_radial_phases_err_msg_1 = \
3485
+ ("The objects ``radial_phases`` and ``radial_amplitudes`` must satisfy "
3486
+ "``len(radial_phases)+1 == len(radial_amplitudes)``.")
3487
+
3488
+ _check_and_convert_azimuthal_quantum_number_err_msg_1 = \
3489
+ ("The objects ``azimuthal_quantum_number`` and "
3490
+ "``principal_quantum_number`` must satisfy "
3491
+ "``azimuthal_quantum_number < principal_quantum_number``.")
3492
+
3493
+ _check_and_convert_magnetic_quantum_number_err_msg_1 = \
3494
+ ("The objects ``magnetic_quantum_number`` and "
3495
+ "``azimuthal_quantum_number`` must satisfy "
3496
+ "``abs(magnetic_quantum_number) <= azimuthal_quantum_number``.")
3497
+
3498
+ _check_and_convert_intra_support_shapes_err_msg_1 = \
3499
+ ("The object ``intra_support_shapes`` must be a sequence of objects of any "
3500
+ "of the following types: ("
3501
+ "`fakecbed.shapes.Circle`, "
3502
+ "`fakecbed.shapes.Ellipse`, "
3503
+ "`fakecbed.shapes.Peak`, "
3504
+ "`fakecbed.shapes.Band`, "
3505
+ "`fakecbed.shapes.PlaneWave`, "
3506
+ "`fakecbed.shapes.Arc`, "
3507
+ "`fakecbed.shapes.GenericBlob`, "
3508
+ "`fakecbed.shapes.Orbital`, "
3509
+ "`fakecbed.shapes.Lune`, "
3510
+ "`fakecbed.shapes.NonuniformBoundedShape`).")