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