# -*- coding: utf-8 -*-
# Copyright 2024 Matthew Fitzpatrick.
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, version 3.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
r"""For creating undistorted geometric shapes.
This module contains classes that represent the intensity patterns of different
undistorted geometric shapes that can be combined to construct intensity
patterns that imitate convergent beam diffraction beam (CBED) patterns. As a
shorthand, we refer to these intensity patterns that imitate CBED patterns as
"fake CBED patterns".
Users can create images of fake CBED patterns using the
:mod:`fakecbed.discretized` module. An image of a fake CBED pattern is formed by
specifying a series of parameters, with the most important parameters being: the
set of intensity patterns of undistorted shapes that determine the undistorted
noiseless non-blurred uncorrupted fake CBED pattern; and a distortion model
which transforms the undistorted noiseless non-blurred uncorrupted fake CBED
pattern into a distorted noiseless non-blurred uncorrupted fake CBED
pattern. The remaining parameters determine whether additional images effects
are applied, like e.g. shot noise or blur effects. Note that in the case of the
aforementioned shapes, we expand the notion of intensity patterns to mean a 2D
real-valued function, i.e. it can be negative. To be clear, we do not apply this
generalized notion of intensity patterns to the fake CBED patterns: in such
cases intensity patterns mean 2D real-valued functions that are strictly
nonnegative.
Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and vertical
coordinates, respectively, of a point in an undistorted image, where
:math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left corner of
the image. Secondly, let :math:`q_{x}` and :math:`q_{y}` be the fractional
horizontal and vertical coordinates, respectively, of a point in a distorted
image, where :math:`\left(q_{x},q_{y}\right)=\left(0,0\right)` is the bottom
left corner of the image. When users specify a distortion model, represented by
an :obj:`distoptica.DistortionModel` object, they also specify a coordinate
transformation which maps a given coordinate pair
:math:`\left(u_{x},u_{y}\right)` to a corresponding coordinate pair
:math:`\left(q_{x},q_{y}\right)`, and implicitly a right-inverse to said
coordinate transformation that maps a coordinate pair
:math:`\left(q_{x},q_{y}\right)` to a corresponding coordinate pair
:math:`\left(u_{x},u_{y}\right)`, when such a relationship exists for
:math:`\left(q_{x},q_{y}\right)`.
"""
#####################################
## Load libraries/packages/modules ##
#####################################
# For accessing attributes of functions.
import inspect
# For randomly selecting items in dictionaries.
import random
# For performing deep copies.
import copy
# For general array handling.
import numpy as np
import torch
# For calculating factorials.
import math
# For validating and converting objects.
import czekitout.check
import czekitout.convert
# For defining classes that support enforced validation, updatability,
# pre-serialization, and de-serialization.
import fancytypes
# For validating, pre-serializing, and de-pre-serializing certain objects.
import distoptica
##################################
## Define classes and functions ##
##################################
# List of public objects in module.
__all__ = ["BaseShape",
"Circle",
"Ellipse",
"Peak",
"Band",
"PlaneWave",
"Arc",
"GenericBlob",
"Orbital",
"Lune",
"NonuniformBoundedShape"]
def _check_and_convert_cartesian_coords(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
u_x, u_y = obj
params["real_torch_matrix"] = u_x
params["name_of_alias_of_real_torch_matrix"] = "u_x"
u_x = _check_and_convert_real_torch_matrix(params)
params["real_torch_matrix"] = u_y
params["name_of_alias_of_real_torch_matrix"] = "u_y"
u_y = _check_and_convert_real_torch_matrix(params)
del params["real_torch_matrix"]
del params["name_of_alias_of_real_torch_matrix"]
if u_x.shape != u_y.shape:
unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
err_msg = unformatted_err_msg.format("u_x", "u_y")
raise ValueError(err_msg)
cartesian_coords = (u_x, u_y)
return cartesian_coords
def _check_and_convert_real_torch_matrix(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
name_of_alias_of_real_torch_matrix = \
params["name_of_alias_of_real_torch_matrix"]
try:
if not isinstance(obj, torch.Tensor):
kwargs = {"obj": obj,
"obj_name": name_of_alias_of_real_torch_matrix}
obj = czekitout.convert.to_real_numpy_matrix(**kwargs)
obj = torch.tensor(obj,
dtype=torch.float32,
device=params["device"])
if len(obj.shape) != 2:
raise
real_torch_matrix = obj.to(device=params["device"], dtype=torch.float32)
except:
unformatted_err_msg = globals()[current_func_name+"_err_msg_1"]
err_msg = unformatted_err_msg.format(name_of_alias_of_real_torch_matrix)
raise TypeError(err_msg)
return real_torch_matrix
def _check_and_convert_device(params):
params["name_of_obj_alias_of_torch_device_obj"] = "device"
device = _check_and_convert_torch_device_obj(params)
del params["name_of_obj_alias_of_torch_device_obj"]
return device
def _check_and_convert_torch_device_obj(params):
obj_name = params["name_of_obj_alias_of_torch_device_obj"]
obj = params[obj_name]
if obj is None:
torch_device_obj = torch.device("cuda"
if torch.cuda.is_available()
else "cpu")
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": (torch.device, type(None))}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
torch_device_obj = obj
return torch_device_obj
def _check_and_convert_skip_validation_and_conversion(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
skip_validation_and_conversion = czekitout.convert.to_bool(**kwargs)
return skip_validation_and_conversion
_default_u_x = ((0.5,),)
_default_u_y = _default_u_x
_default_device = None
_default_skip_validation_and_conversion = False
[docs]class BaseShape(fancytypes.PreSerializableAndUpdatable):
r"""The intensity pattern of an undistorted geometric shape.
See the summary documentation of the module :mod:`fakecbed.shapes` for
additional context.
One cannot construct an instance of the class
:class:`fakecbed.shapes.BaseShape`, only subclasses of itself defined in
:mod:`fakecbed` library.
Parameters
----------
ctor_params : `dict`
The construction parameters of the subclass.
"""
def __init__(self, ctor_params):
if type(self) is BaseShape:
self._eval(u_x=None, u_y=None)
else:
kwargs = ctor_params
kwargs["skip_cls_tests"] = True
fancytypes.PreSerializableAndUpdatable.__init__(self, **kwargs)
return None
[docs] @classmethod
def get_validation_and_conversion_funcs(cls):
validation_and_conversion_funcs = \
cls._validation_and_conversion_funcs_.copy()
return validation_and_conversion_funcs
[docs] @classmethod
def get_pre_serialization_funcs(cls):
pre_serialization_funcs = \
cls._pre_serialization_funcs_.copy()
return pre_serialization_funcs
[docs] @classmethod
def get_de_pre_serialization_funcs(cls):
de_pre_serialization_funcs = \
cls._de_pre_serialization_funcs_.copy()
return de_pre_serialization_funcs
[docs] def eval(self,
u_x=\
_default_u_x,
u_y=\
_default_u_y,
device=\
_default_device,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
r"""Evaluate the intensity pattern of the undistorted shape.
Let :math:`u_{x}` and :math:`u_{y}` be the fractional horizontal and
vertical coordinates, respectively, of a point in an undistorted image.
We adopt the convention where the fractional coordinate pair
:math:`\left(u_{x},u_{y}\right)=\left(0,0\right)` is the bottom left
corner of an image.
Parameters
----------
u_x : `torch.Tensor` (`float`, ndim=2), optional
The fractional horizontal coordinates of the positions at which to
evaluate the intensity pattern of the undistorted shape.
u_y : `torch.Tensor` (`float`, shape=``u_x.shape``), optional
The fractional vertical coordinates of the positions at which to
evaluate the intensity pattern of the undistorted shape.
device : `torch.device` | `None`, optional
This parameter specifies the device to be used to perform
computationally intensive calls to PyTorch functions. If ``device``
is of the type :class:`torch.device`, then ``device`` represents the
device to be used. If ``device`` is set to ``None`` and a GPU device
is available, then a GPU device is to be used. Otherwise, the CPU is
used.
skip_validation_and_conversion : `bool`, optional
If ``skip_validation_and_conversion`` is set to ``False``, then
validations and conversions are performed on the above parameters.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
no validations and conversions are performed on the above
parameters. This option is desired primarily when the user wants to
avoid potentially expensive validation and/or conversion operations.
Returns
-------
result : `torch.Tensor` (`float`, shape=``u_x.shape``)
The values of the intensity pattern at the positions specified by
``u_x`` and ``u_y``. For every pair of nonnegative integers ``(i,
j)`` that does not raise an ``IndexError`` exception upon calling
``result[i, j]``, ``result[i, j]`` is the value of the intensity
pattern at the position ``(u_x[i, j], u_y[i, j])`` of the
undistorted shape.
"""
params = locals()
func_alias = _check_and_convert_skip_validation_and_conversion
skip_validation_and_conversion = func_alias(params)
if (skip_validation_and_conversion == False):
params = {"cartesian_coords": (u_x, u_y), "device": device}
device = _check_and_convert_device(params)
u_x, u_y = _check_and_convert_cartesian_coords(params)
result = self._eval(u_x, u_y)
return result
def _eval(self, u_x, u_y):
raise NotImplementedError(_base_shape_err_msg_1)
def _check_and_convert_center(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
cls_alias = \
distoptica.CoordTransformParams
validation_and_conversion_funcs = \
cls_alias.get_validation_and_conversion_funcs()
validation_and_conversion_func = \
validation_and_conversion_funcs[obj_name]
center = \
validation_and_conversion_func(params)
return center
def _pre_serialize_center(center):
obj_to_pre_serialize = random.choice(list(locals().values()))
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[15:]
cls_alias = \
distoptica.CoordTransformParams
pre_serialization_funcs = \
cls_alias.get_pre_serialization_funcs()
pre_serialization_func = \
pre_serialization_funcs[obj_name]
serializable_rep = \
pre_serialization_func(obj_to_pre_serialize)
return serializable_rep
def _de_pre_serialize_center(serializable_rep):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[18:]
cls_alias = \
distoptica.CoordTransformParams
de_pre_serialization_funcs = \
cls_alias.get_de_pre_serialization_funcs()
de_pre_serialization_func = \
de_pre_serialization_funcs[obj_name]
center = \
de_pre_serialization_func(serializable_rep)
return center
def _check_and_convert_radius(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
radius = czekitout.convert.to_positive_float(**kwargs)
return radius
def _pre_serialize_radius(radius):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_radius(serializable_rep):
radius = serializable_rep
return radius
def _check_and_convert_intra_shape_val(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
intra_shape_val = czekitout.convert.to_float(**kwargs)
return intra_shape_val
def _pre_serialize_intra_shape_val(intra_shape_val):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_intra_shape_val(serializable_rep):
intra_shape_val = serializable_rep
return intra_shape_val
_default_center = (0.5, 0.5)
_default_radius = 0.05
_default_intra_shape_val = 1
[docs]class Circle(BaseShape):
r"""The intensity pattern of a circle.
Let :math:`\left(u_{x;c;\text{C}},u_{y;c;\text{C}}\right)`, and
:math:`R_{\text{C}}` be the center, and the radius of the circle
respectively. Furthermore, let :math:`A_{\text{C}}` be the value of the
intensity pattern inside the circle. The undistorted intensity pattern of
the circle is given by:
.. math ::
\mathcal{I}_{\text{C}}\left(u_{x},u_{y}\right)=
A_{\text{C}}\Theta\left(R_{\text{C}}
-\sqrt{\left(u_{x}-u_{x;c;\text{C}}\right)^{2}
+\left(u_{y}-u_{y;c;\text{C}}\right)^{2}}\right),
:label: intensity_pattern_of_circle__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the circle respectively,
and :math:`\Theta\left(\cdots\right)` is the Heaviside step function.
Parameters
----------
center : `array_like` (`float`, shape=(``2``,)), optional
The center of the circle, :math:`\left(u_{x;c;\text{C}},
u_{y;c;\text{C}}\right)`.
radius : `float`, optional
The radius of the circle, :math:`R_{\text{C}}`. Must be positive.
intra_shape_val : `float`, optional
The value of the intensity pattern inside the circle.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("center",
"radius",
"intra_shape_val")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
center=\
_default_center,
radius=\
_default_radius,
intra_shape_val=\
_default_intra_shape_val,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
u_x_c, u_y_c = self._center
R = self._radius
A = self._intra_shape_val
delta_u_x = u_x-u_x_c
delta_u_y = u_y-u_y_c
u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
result = A * (u_r <= R)
return result
def _check_and_convert_semi_major_axis(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
semi_major_axis = czekitout.convert.to_positive_float(**kwargs)
return semi_major_axis
def _pre_serialize_semi_major_axis(semi_major_axis):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_semi_major_axis(serializable_rep):
semi_major_axis = serializable_rep
return semi_major_axis
def _check_and_convert_eccentricity(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
eccentricity = czekitout.convert.to_nonnegative_float(**kwargs)
if eccentricity > 1:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return eccentricity
def _pre_serialize_eccentricity(eccentricity):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_eccentricity(serializable_rep):
eccentricity = serializable_rep
return eccentricity
def _check_and_convert_rotation_angle(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
rotation_angle = czekitout.convert.to_float(**kwargs) % (2*np.pi)
return rotation_angle
def _pre_serialize_rotation_angle(rotation_angle):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_rotation_angle(serializable_rep):
rotation_angle = serializable_rep
return rotation_angle
_default_semi_major_axis = _default_radius
_default_eccentricity = 0
_default_rotation_angle = 0
[docs]class Ellipse(BaseShape):
r"""The intensity pattern of a ellipse.
Let :math:`\left(u_{x;c;\text{E}},u_{y;c;\text{E}}\right)`,
:math:`a_{\text{E}}`, :math:`e_{\text{E}}`, and :math:`\theta_{\text{E}}` be
the center, the semi-major axis, the eccentricity, and the rotation angle of
the ellipse respectively. Furthermore, let :math:`A_{\text{E}}` be the value
of the intensity pattern inside the ellipse. The undistorted intensity
pattern of the ellipse is given by:
.. math ::
\mathcal{I}_{\text{E}}\left(u_{x},u_{y}\right)=
A_{\text{E}}\Theta\left(\Theta_{\arg;\text{E}}\left(u_{x},
u_{y}\right)\right),
:label: intensity_pattern_of_ellipse__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the ellipse
respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
function, and
.. math ::
&\Theta_{\arg;\text{E}}\left(u_{x},u_{y}\right)\\&\quad=
\left\{ 1-e_{\text{E}}^{2}\right\} a_{\text{E}}^{2}\\
&\quad\quad-\left\{ 1-e_{\text{E}}^{2}\right\}
\left\{ \left[u_{x}-u_{x;c;\text{E}}\right]
\cos\left(\theta_{\text{E}}\right)
-\left[u_{y}-u_{y;c;\text{E}}\right]
\sin\left(\theta_{\text{E}}\right)\right\} ^{2}\\
&\quad\quad-\left\{ \left[u_{x}-
u_{x;c;\text{E}}\right]\sin\left(\theta_{\text{E}}\right)+\left[u_{y}-
u_{y;c;\text{E}}\right]\cos\left(\theta_{\text{E}}\right)\right\}^{2}.
:label: ellipse_support_arg__1
Parameters
----------
center : `array_like` (`float`, shape=(``2``,)), optional
The center of the ellipse, :math:`\left(u_{x;c;\text{E}},
u_{y;c;\text{E}}\right)`.
semi_major_axis : `float`, optional
The semi-major axis of the ellipse, :math:`a_{\text{E}}`. Must be
positive.
eccentricity : `float`, optional
The eccentricity of the ellipse, :math:`e_{\text{E}}`. Must be a
nonnegative number less than or equal to unity.
rotation_angle : `float`, optional
The rotation angle of the ellipse, :math:`\theta_{\text{E}}`.
intra_shape_val : `float`, optional
The value of the intensity pattern inside the ellipse.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("center",
"semi_major_axis",
"eccentricity",
"rotation_angle",
"intra_shape_val")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
center=\
_default_center,
semi_major_axis=\
_default_semi_major_axis,
eccentricity=\
_default_eccentricity,
rotation_angle=\
_default_rotation_angle,
intra_shape_val=\
_default_intra_shape_val,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
A = self._intra_shape_val
support_arg = self._calc_support_arg(u_x, u_y)
one = torch.tensor(1.0, device=support_arg.device)
result = A * torch.heaviside(support_arg, one)
return result
def _calc_support_arg(self, u_x, u_y):
u_x_c, u_y_c = self._center
a = self._semi_major_axis
e = self._eccentricity
theta = torch.tensor(self._rotation_angle, dtype=u_x.dtype)
delta_u_x = u_x-u_x_c
delta_u_y = u_y-u_y_c
e_sq = e*e
a_sq = a*a
b_sq = (1-e_sq)*a_sq
cos_theta = torch.cos(theta)
sin_theta = torch.sin(theta)
delta_u_x_prime = delta_u_x*cos_theta - delta_u_y*sin_theta
delta_u_x_prime_sq = delta_u_x_prime*delta_u_x_prime
delta_u_y_prime = delta_u_x*sin_theta + delta_u_y*cos_theta
delta_u_y_prime_sq = delta_u_y_prime*delta_u_y_prime
support_arg = (b_sq
- (b_sq/a_sq)*delta_u_x_prime_sq
- delta_u_y_prime_sq)
return support_arg
def _check_and_convert_widths(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
widths = czekitout.convert.to_quadruplet_of_positive_floats(**kwargs)
return widths
def _pre_serialize_widths(widths):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_widths(serializable_rep):
widths = serializable_rep
return widths
def _check_and_convert_val_at_center(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
val_at_center = czekitout.convert.to_float(**kwargs)
return val_at_center
def _pre_serialize_val_at_center(val_at_center):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_val_at_center(serializable_rep):
val_at_center = serializable_rep
return val_at_center
def _check_and_convert_functional_form(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
functional_form = czekitout.convert.to_str_from_str_like(**kwargs)
kwargs["obj"] = functional_form
kwargs["accepted_strings"] = ("asymmetric_gaussian",
"asymmetric_exponential",
"asymmetric_lorentzian")
czekitout.check.if_one_of_any_accepted_strings(**kwargs)
return functional_form
def _pre_serialize_functional_form(functional_form):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_functional_form(serializable_rep):
functional_form = serializable_rep
return functional_form
_default_widths = 4*(0.05,)
_default_val_at_center = 1
_default_functional_form = "asymmetric_gaussian"
[docs]class Peak(BaseShape):
r"""The intensity pattern of a peak.
Let :math:`\left(u_{x;c;\text{P}},u_{y;c;\text{P}}\right)`,
:math:`\left(W_{1;1;\text{P}},W_{1;2;\text{P}},
W_{2;1;\text{P}},W_{2;2;\text{P}}\right)`, and :math:`\theta_{\text{P}}` be
the center, the widths factors, and the rotation angle of the peak
respectively. Furthermore, let :math:`A_{\text{P}}` be the value of the
intensity pattern at the center of the peak. The undistorted intensity
pattern of the peak is given by:
.. math ::
\mathcal{I}_{\text{P}}\left(u_{x},u_{y}\right)=
A_{\text{P}}F_{\beta;\text{P}}\left(
\sqrt{\sum_{\alpha=1}^{2}\left\{ \frac{
z_{\alpha;\text{P}}\left(u_{x},u_{y}\right)}{
W_{\alpha;\text{P}}\left(u_{x},u_{y}\right)}\right\}^{2}}\right),
:label: intensity_pattern_of_peak__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the peak respectively,
.. math ::
F_{\beta;\text{P}}\left(\omega\right)=\begin{cases}
e^{-\frac{1}{2}\omega^{2}}, & \text{if }\beta=\text{A.G.},\\
e^{-\omega}, & \text{if }\beta=\text{A.E.},\\
\left(1+\omega^{2}\right)^{-\frac{3}{2}}, & \text{if }\beta=\text{A.L.},
\end{cases}
:label: functional_form_of_peak__1
with A.G., A.E., and A.L. being abbreviations of “asymmetric Gaussian”,
“asymmetric exponential”, and “asymmetric Lorentzian” respectively, and
:math:`\beta` specifying the functional form of the intensity pattern;
.. math ::
z_{\alpha=1;\text{P}}\left(u_{x},u_{y}\right)=
\left(u_{x}-u_{x;c;\text{P}}\right)
\cos\left(\theta_{\text{P}}\right)
-\left(u_{y}
-u_{y;c;\text{P}}\right)\sin\left(\theta_{\text{P}}\right);
:label: z_alpha_peak__1
.. math ::
z_{\alpha=2;\text{P}}\left(u_{x},u_{y}\right)=
\left(u_{x}-u_{x;c;\text{P}}\right)
\sin\left(\theta_{\text{P}}\right)
+\left(u_{y}
-u_{y;c;\text{P}}\right)\cos\left(\theta_{\text{P}}\right);
:label: z_alpha_peak__2
and
.. math ::
W_{\alpha;\text{P}}\left(u_{x},u_{y}\right)=
\sum_{\nu=1}^{2}W_{\alpha;\nu;\text{P}}\left[\left\{\nu-1\right\}
+\left\{ -1\right\}^{\nu+1}
\Theta\left(z_{\alpha;\text{P}}\left(u_{x},
u_{y}\right)\right)\right],
:label: W_alpha_peak__1
with :math:`\Theta\left(\cdots\right)` being the Heaviside step function.
Parameters
----------
center : `array_like` (`float`, shape=(``2``,)), optional
The center of the peak,
:math:`\left(u_{x;c;\text{P}},u_{y;c;\text{P}}\right)`.
widths : `array_like` (`float`, shape=(``4``,)), optional
The width factors of the peak,
:math:`\left(W_{1;1;\text{P}},W_{1;2;\text{P}},
W_{2;1;\text{P}},W_{2;2;\text{P}}\right)`. Must be a quadruplet of
positive numbers.
rotation_angle : `float`, optional
The rotation angle of the peak, :math:`\theta_{\text{P}}`.
val_at_center : `float`, optional
The value of the intensity pattern at the center of the peak,
:math:`A_{\text{P}}`.
functional_form : ``"asymmetric_gaussian"`` | ``"asymmetric_exponential"`` | ``"asymmetric_lorentzian"``, optional
The functional form of the peak. If
``functional_form==asymmetric_gaussian``, then :math:`\beta`, which
appears in Eq. :eq:`functional_form_of_peak__1`, is set to "A.G."; else
if ``functional_form==asymmetric_exponential``, then :math:`\beta` is
set to "A.E."; else :math:`\beta` is set to "A.L.".
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("center",
"widths",
"rotation_angle",
"val_at_center",
"functional_form")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
center=\
_default_center,
widths=\
_default_widths,
rotation_angle=\
_default_rotation_angle,
val_at_center=\
_default_val_at_center,
functional_form=\
_default_functional_form,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
theta = torch.tensor(self._rotation_angle)
self._cos_theta = torch.cos(theta)
self._sin_theta = torch.sin(theta)
functional_form = self._functional_form
if functional_form == "asymmetric_gaussian":
self._eval = self._eval_asymmetric_gaussian
elif functional_form == "asymmetric_exponential":
self._eval = self._eval_asymmetric_exponential
else:
self._eval = self._eval_asymmetric_lorentzian
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval_asymmetric_gaussian(self, u_x, u_y):
u_x_c, u_y_c = self._center
delta_u_x_c = u_x-u_x_c
delta_u_y_c = u_y-u_y_c
cos_theta = self._cos_theta
sin_theta = self._sin_theta
A = self._val_at_center
W_1_1, W_1_2, W_2_1, W_2_2 = self._widths
z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
mask_1 = (z_1 >= 0)
W_1 = W_1_1*mask_1 + W_1_2*(~mask_1)
z_1_over_W_1 = z_1/W_1
z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
mask_2 = (z_2 >= 0)
W_2 = W_2_1*mask_2 + W_2_2*(~mask_2)
z_2_over_W_2 = z_2/W_2
result = A*torch.exp(-0.5*(z_1_over_W_1*z_1_over_W_1
+ z_2_over_W_2*z_2_over_W_2))
return result
def _eval_asymmetric_exponential(self, u_x, u_y):
u_x_c, u_y_c = self._center
delta_u_x_c = u_x-u_x_c
delta_u_y_c = u_y-u_y_c
cos_theta = self._cos_theta
sin_theta = self._sin_theta
A = self._val_at_center
w_1_1, w_1_2, w_2_1, w_2_2 = self._widths
z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
mask_1 = (z_1 >= 0)
w_1 = w_1_1*mask_1 + w_1_2*(~mask_1)
z_1_over_w_1 = z_1/w_1
z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
mask_2 = (z_2 >= 0)
w_2 = w_2_1*mask_2 + w_2_2*(~mask_2)
z_2_over_w_2 = z_2/w_2
result = A*torch.exp(-torch.sqrt(z_1_over_w_1*z_1_over_w_1
+ z_2_over_w_2*z_2_over_w_2))
return result
def _eval_asymmetric_lorentzian(self, u_x, u_y):
u_x_c, u_y_c = self._center
delta_u_x_c = u_x-u_x_c
delta_u_y_c = u_y-u_y_c
cos_theta = self._cos_theta
sin_theta = self._sin_theta
A = self._val_at_center
w_1_1, w_1_2, w_2_1, w_2_2 = self._widths
z_1 = delta_u_x_c*cos_theta - delta_u_y_c*sin_theta
mask_1 = (z_1 >= 0)
w_1 = w_1_1*mask_1 + w_1_2*(~mask_1)
z_1_over_w_1 = z_1/w_1
z_2 = delta_u_x_c*sin_theta + delta_u_y_c*cos_theta
mask_2 = (z_2 >= 0)
w_2 = w_2_1*mask_2 + w_2_2*(~mask_2)
z_2_over_w_2 = z_2/w_2
denom_factor = torch.sqrt(1
+ z_1_over_w_1*z_1_over_w_1
+ z_2_over_w_2*z_2_over_w_2)
result = A / denom_factor / denom_factor / denom_factor
return result
def _check_and_convert_end_pt_1(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
end_pt_1 = czekitout.convert.to_pair_of_floats(**kwargs)
return end_pt_1
def _pre_serialize_end_pt_1(end_pt_1):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_end_pt_1(serializable_rep):
end_pt_1 = serializable_rep
return end_pt_1
def _check_and_convert_end_pt_2(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
end_pt_2 = czekitout.convert.to_pair_of_floats(**kwargs)
return end_pt_2
def _pre_serialize_end_pt_2(end_pt_2):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_end_pt_2(serializable_rep):
end_pt_2 = serializable_rep
return end_pt_2
def _check_and_convert_width(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
width = czekitout.convert.to_positive_float(**kwargs)
return width
def _pre_serialize_width(width):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_width(serializable_rep):
width = serializable_rep
return width
_default_end_pt_1 = (0, 0.5)
_default_end_pt_2 = (1, 0.5)
_default_width = 0.05
[docs]class Band(BaseShape):
r"""The intensity pattern of a band.
Let :math:`\left(u_{x;\text{B};1},u_{y;\text{B};1}\right)`,
:math:`\left(u_{x;\text{B};2},u_{y;\text{B};2}\right)`, and
:math:`W_{\text{B}}` be the first end point, the second end point, and the
width of the band respectively. Furthermore, let :math:`A_{\text{B}}` be the
maximum value of the peak. The undistorted intensity pattern of the band is
given by:
.. math ::
\mathcal{I}_{\text{B}}\left(u_{x},u_{y}\right)=
A_{\text{B}}\Theta\left(\frac{W_{\text{B}}}{2}
-d_{\text{B};1}\left(u_{x},u_{y}\right)\right)\Theta\left(
\frac{L_{\text{B}}}{2}
-d_{\text{B};2}\left(u_{x},u_{y}\right)\right),
:label: intensity_pattern_of_band__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the band respectively,
.. math ::
\Theta\left(\omega\right)=\begin{cases}
1, & \text{if }\omega\ge0,\\
0, & \text{otherwise};
\end{cases}
:label: heaviside_step_function__1
.. math ::
L_{\text{B}}=\sqrt{\left(u_{x;\text{B};2}
-u_{x;\text{B};1}\right)^{2}+\left(u_{y;\text{B};2}
-u_{y;\text{B};1}\right)^{2}};
:label: length_of_band__1
.. math ::
d_{\text{B};1}\left(u_{x},u_{y}\right)=
\frac{a_{\text{B};1}u_{x}+b_{\text{B};1}u_{y}
+c_{\text{B};1}}{\sqrt{a_{\text{B};1}^{2}+b_{\text{B};1}^{2}}};
:label: d_1_of_band__1
with
.. math ::
a_{\text{B};1}=\begin{cases}
u_{y;\text{B};2}-u_{y;\text{B};1},
& \text{if }u_{x;\text{B};1}\neq u_{x;\text{B};2},\\
1, & \text{otherwise},
\end{cases}
:label: a_1_of_band__1
.. math ::
b_{\text{B};1}=u_{x;\text{B};1}-u_{x;\text{B};2},
:label: b_1_of_band__1
.. math ::
c_{\text{B};1}=\begin{cases}
u_{x;\text{B};2}u_{y;\text{B};1}
-u_{x;\text{B};1}u_{y;\text{B};2},
& \text{if }u_{x;\text{B};1}\neq u_{x;\text{B};2},\\
-u_{x;\text{B};1}, & \text{otherwise};
\end{cases}
:label: c_1_of_band__1
.. math ::
d_{\text{B};2}\left(u_{x},u_{y}\right)=
\frac{a_{\text{B};2}u_{x}+b_{\text{B};2}u_{y}
+c_{\text{B};2}}{\sqrt{a_{\text{B};2}^{2}+b_{\text{B};2}^{2}}},
:label: d_2_of_band__1
with
.. math ::
a_{\text{B};2}=\begin{cases}
u_{y;\text{B};4}-u_{y;\text{B};3},
& \text{if }u_{x;\text{B};3}\neq u_{x;\text{B};4},\\
1, & \text{otherwise},
\end{cases}
:label: a_2_of_band__1
.. math ::
u_{x;\text{B};3}=u_{x;\text{B};1}
+\frac{L_{\text{B}}}{2}\cos\left(\theta_{\text{B}}\right),
:label: u_x_3_of_band__1
.. math ::
u_{y;\text{B};3}=u_{y;\text{B};1}
+\frac{L_{\text{B}}}{2}\sin\left(\theta_{\text{B}}\right),
:label: u_y_3_of_band__1
.. math ::
u_{x;\text{B};4}=u_{x;\text{B};3}
+\frac{L_{\text{B}}}{2}\cos\left(\phi_{\text{B}}\right),
:label: u_x_4_of_band__1
.. math ::
u_{y;\text{B};4}=u_{y;\text{B};3}
+\frac{L_{\text{B}}}{2}\sin\left(\phi_{\text{B}}\right),
:label: u_y_4_of_band__1
.. math ::
\theta_{\text{B}}=\tan^{-1}\left(\frac{u_{y;\text{B};2}
-u_{y;\text{B};1}}{u_{x;\text{B};2}-u_{x;\text{B};1}}\right),
:label: theta_of_band__1
.. math ::
\phi_{\text{B}}=\theta_{\text{B}}+\frac{\pi}{2},
:label: phi_of_band__1
.. math ::
b_{\text{B};2}=u_{x;\text{B};3}-u_{x;\text{B};4},
:label: b_2_of_band__1
.. math ::
c_{\text{B};2}=\begin{cases}
u_{x;\text{B};4}u_{y;\text{B};3}
-u_{x;\text{B};3}u_{y;\text{B};4},
& \text{if }u_{x;\text{B};3}\neq u_{x;\text{B};4},\\
-u_{x;\text{B};3}, & \text{otherwise}.
\end{cases}
:label: c_2_of_band__1
Parameters
----------
end_pt_1 : `array_like` (`float`, shape=(``2``,)), optional
The first end point of the band,
:math:`\left(u_{x;\text{B};1},u_{y;\text{B};1}\right)`.
end_pt_2 : `array_like` (`float`, shape=(``2``,)), optional
The second end point of the band,
:math:`\left(u_{x;\text{B};2},u_{y;\text{B};2}\right)`.
width : `float`, optional
The width of the band, :math:`W_{\text{B}}`. Must be a positve number.
intra_shape_val : `float`, optional
The value of the intensity pattern inside the band.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("end_pt_1",
"end_pt_2",
"width",
"intra_shape_val")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
end_pt_1=\
_default_end_pt_1,
end_pt_2=\
_default_end_pt_2,
width=\
_default_width,
intra_shape_val=\
_default_intra_shape_val,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
u_x_1, u_y_1 = self._end_pt_1
u_x_2, u_y_2 = self._end_pt_2
length = np.sqrt((u_x_2-u_x_1)**2 + (u_y_2-u_y_1)**2)
theta = np.arctan2(u_y_2-u_y_1, u_x_2-u_x_1)
phi = theta + (np.pi/2)
u_x_3 = (u_x_1 + (length/2)*np.cos(theta)).item()
u_y_3 = (u_y_1 + (length/2)*np.sin(theta)).item()
u_x_4 = (u_x_3 + (length/2)*np.cos(phi)).item()
u_y_4 = (u_y_3 + (length/2)*np.sin(phi)).item()
a_1 = u_y_2-u_y_1 if (u_x_1 != u_x_2) else 1
b_1 = u_x_1-u_x_2
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
a_2 = u_y_4-u_y_3 if (u_x_3 != u_x_4) else 1
b_2 = u_x_3-u_x_4
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
self._a_1 = a_1
self._b_1 = b_1
self._c_1 = c_1
self._denom_of_d_1 = np.sqrt(a_1*a_1 + b_1*b_1).item()
self._a_2 = a_2
self._b_2 = b_2
self._c_2 = c_2
self._denom_of_d_2 = np.sqrt(a_2*a_2 + b_2*b_2).item()
self._length = length
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _d_1(self, u_x, u_y):
a_1 = self._a_1
b_1 = self._b_1
c_1 = self._c_1
denom_of_d_1 = self._denom_of_d_1
d_1 = torch.abs(a_1*u_x + b_1*u_y + c_1) / denom_of_d_1
return d_1
def _d_2(self, u_x, u_y):
a_2 = self._a_2
b_2 = self._b_2
c_2 = self._c_2
denom_of_d_2 = self._denom_of_d_2
d_2 = torch.abs(a_2*u_x + b_2*u_y + c_2) / denom_of_d_2
return d_2
def _eval(self, u_x, u_y):
A = self._intra_shape_val
w_over_2 = self._width/2
l_over_2 = self._length/2
d_1 = self._d_1(u_x, u_y)
d_2 = self._d_2(u_x, u_y)
one = torch.tensor(1.0, device=d_1.device)
result = (A
* torch.heaviside(w_over_2 - d_1, one)
* torch.heaviside(l_over_2 - d_2, one))
return result
def _check_and_convert_amplitude(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
amplitude = czekitout.convert.to_float(**kwargs)
return amplitude
def _pre_serialize_amplitude(amplitude):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_amplitude(serializable_rep):
amplitude = serializable_rep
return amplitude
def _check_and_convert_wavelength(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
wavelength = czekitout.convert.to_positive_float(**kwargs)
return wavelength
def _pre_serialize_wavelength(wavelength):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_wavelength(serializable_rep):
wavelength = serializable_rep
return wavelength
def _check_and_convert_propagation_direction(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
propagation_direction = czekitout.convert.to_float(**kwargs) % (2*np.pi)
return propagation_direction
def _pre_serialize_propagation_direction(propagation_direction):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_propagation_direction(serializable_rep):
propagation_direction = serializable_rep
return propagation_direction
def _check_and_convert_phase(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
phase = czekitout.convert.to_float(**kwargs)
return phase
def _pre_serialize_phase(phase):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_phase(serializable_rep):
phase = serializable_rep
return phase
_default_amplitude = 1
_default_wavelength = 0.01
_default_propagation_direction = 0
_default_phase = 0
[docs]class PlaneWave(BaseShape):
r"""The intensity pattern of a plane wave.
Let :math:`A_{\text{PW}}`, :math:`\lambda_{\text{PW}}`,
:math:`\theta_{\text{PW}}`, and :math:`\phi_{\text{PW}}` be the amplitude,
the wavelength, the propagation direction, and the phase of the plane wave
respectively. The undistorted intensity pattern of the plane wave is given
by:
.. math ::
\mathcal{I}_{\text{PW}}\left(u_{x},u_{y}\right)=
A_{\text{PW}}\cos\left(u_{x}k_{x;\text{PW}}+u_{y}k_{y;\text{PW}}
+\phi_{\text{PW}}\right),
:label: intensity_pattern_of_plane_wave__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the plane wave
respectively,
.. math ::
k_{x;\text{PW}}=\frac{2\pi}{\lambda_{\text{PW}}}
\cos\left(\theta_{\text{PW}}\right),
:label: k_x_of_plane_wave__1
and
.. math ::
k_{y;\text{PW}}=\frac{2\pi}{\lambda_{\text{PW}}}
\sin\left(\theta_{\text{PW}}\right).
:label: k_y_of_plane_wave__1
Parameters
----------
amplitude : `float`, optional
The amplitude of the plane wave, :math:`A_{\text{PW}}`.
wavelength : `float`, optional
The wavelength of the plane wave, :math:`\lambda_{\text{PW}}`. Must be a
positve number.
propagation_direction : `float`, optional
The propagation direction of the plane wave, :math:`\theta_{\text{PW}}`.
phase : `float`, optional
The phase of the plane wave, :math:`\phi_{\text{PW}}`.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("amplitude",
"wavelength",
"propagation_direction",
"phase")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
amplitude=\
_default_amplitude,
wavelength=\
_default_wavelength,
propagation_direction=\
_default_propagation_direction,
phase=\
_default_phase,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
L = self._wavelength
theta = self._propagation_direction
self._k_x = (2*np.pi/L)*np.cos(theta).item()
self._k_y = (2*np.pi/L)*np.sin(theta).item()
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
A = self._amplitude
phi = self._phase
k_x = self._k_x
k_y = self._k_y
result = A*torch.cos(u_x*k_x+u_y*k_y + phi)
return result
def _check_and_convert_midpoint_angle(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
midpoint_angle = czekitout.convert.to_float(**kwargs) % (2*np.pi)
return midpoint_angle
def _pre_serialize_midpoint_angle(midpoint_angle):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_midpoint_angle(serializable_rep):
midpoint_angle = serializable_rep
return midpoint_angle
def _check_and_convert_subtending_angle(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
subtending_angle = min(abs(czekitout.convert.to_float(**kwargs)), (2*np.pi))
return subtending_angle
def _pre_serialize_subtending_angle(subtending_angle):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_subtending_angle(serializable_rep):
subtending_angle = serializable_rep
return subtending_angle
def _check_and_convert_radial_range(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
func_alias = czekitout.convert.to_pair_of_positive_floats
kwargs = {"obj": obj, "obj_name": obj_name}
radial_range = func_alias(**kwargs)
if radial_range[0] >= radial_range[1]:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return radial_range
def _pre_serialize_radial_range(radial_range):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_radial_range(serializable_rep):
radial_range = serializable_rep
return radial_range
_default_midpoint_angle = 0
_default_subtending_angle = np.pi/4
_default_radial_range = (0.10, 0.15)
[docs]class Arc(BaseShape):
r"""The intensity pattern of a circular arc.
Let :math:`\left(u_{x;c;\text{A}},u_{y;c;\text{A}}\right)`,
:math:`\theta_{\text{A}}`, :math:`\phi_{\text{A}}`, and
:math:`\left(R_{\text{A};1},R_{\text{A};2}\right)` be the circle center, the
midpoint angle, the subtending angle, and the radial range of the circular
arc respectively. Furthermore, let :math:`A_{\text{A}}` be the value of the
intensity pattern inside the arc. The undistorted intensity pattern of the
circular arc is given by:
.. math ::
\mathcal{I}_{\text{A}}\left(u_{x},u_{y}\right)&=
A_{\text{A}}\\&\quad\mathop{\times}
\Theta\left(\left|\frac{\phi_{\text{A}}}{2}\right|
-u_{\theta;\text{A}}\right)\\
&\quad\mathop{\times}\Theta\left(\left|\frac{\phi_{\text{A}}}{2}\right|
+u_{\theta;\text{A}}\right)\\
&\quad\mathop{\times}\Theta\left(u_{r;\text{A}}
-R_{\text{A};1}\right)\\
&\quad\mathop{\times}\Theta\left(R_{\text{A};2}
-u_{r;\text{A}}\right),
:label: intensity_pattern_of_arc__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the circular arc
respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
function,
.. math ::
u_{r;\text{A}}=\sqrt{\left(u_{x}-u_{x;c;\text{A}}\right)^{2}
+\left(u_{y}-u_{y;c;\text{A}}\right)^{2}},
:label: u_r_UA__1
and
.. math ::
u_{\theta;\text{A}}=\left\{ \tan^{-1}\left(\frac{u_{y}
-u_{y;c;\text{A}}}{u_{x}-u_{x;c;\text{A}}}\right)
-\theta_{\text{A}}\right\} \mod 2\pi.
:label: u_theta_UA__1
Parameters
----------
center : `array_like` (`float`, shape=(``2``,)), optional
The circle center, :math:`\left(u_{x;c;\text{A}},
u_{y;c;\text{A}}\right)`.
midpoint_angle : `float`, optional
The midpoint angle of the circular arc, :math:`\theta_{\text{A}}`.
subtending_angle : `float`, optional
The subtending angle of the circular arc, :math:`\phi_{\text{A}}`.
radial_range : `array_like` (`float`, shape=(2,)), optional
The radial range of the circular arc,
:math:`\left(R_{\text{A};1},R_{\text{A};2}\right)`, where
``radial_range[0]`` and ``radial_range{1]`` are :math:`R_{\text{A};1}`
and :math:`R_{\text{A};2}` respectively. ``radial_range`` must satisfy
``0<radial_range[0]<radial_range[1]``.
intra_shape_val : `float`, optional
The value of the intensity pattern inside the circular arc.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("center",
"midpoint_angle",
"subtending_angle",
"radial_range",
"intra_shape_val")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
center=\
_default_center,
midpoint_angle=\
_default_midpoint_angle,
subtending_angle=\
_default_subtending_angle,
radial_range=\
_default_radial_range,
intra_shape_val=\
_default_intra_shape_val,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
u_x_c, u_y_c = self._center
theta = self._midpoint_angle
phi = self._subtending_angle
R_1, R_2 = self._radial_range
A = self._intra_shape_val
delta_u_x = u_x-u_x_c
delta_u_y = u_y-u_y_c
phi_over_2 = phi/2
u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
u_theta = ((torch.atan2(delta_u_y, delta_u_x)-theta+phi_over_2)
% (2*np.pi))
one = torch.tensor(1.0, device=u_x.device)
result = (A
* torch.heaviside(phi-u_theta, one)
* torch.heaviside(u_r-R_1, one)
* torch.heaviside(R_2-u_r, one))
return result
def _check_and_convert_radial_reference_pt(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
radial_reference_pt = czekitout.convert.to_pair_of_floats(**kwargs)
return radial_reference_pt
def _pre_serialize_radial_reference_pt(radial_reference_pt):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_radial_reference_pt(serializable_rep):
radial_reference_pt = serializable_rep
return radial_reference_pt
def _check_and_convert_radial_amplitudes(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
func_alias = czekitout.convert.to_tuple_of_nonnegative_floats
radial_amplitudes = func_alias(**kwargs)
num_radial_amplitudes = len(radial_amplitudes)
if num_radial_amplitudes == 0:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
partial_amplitude_sum = sum(radial_amplitudes[1:])
if radial_amplitudes[0] <= partial_amplitude_sum:
err_msg = globals()[current_func_name+"_err_msg_2"]
raise ValueError(err_msg)
return radial_amplitudes
def _pre_serialize_radial_amplitudes(radial_amplitudes):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_radial_amplitudes(serializable_rep):
radial_amplitudes = serializable_rep
return radial_amplitudes
def _check_and_convert_radial_phases(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
radial_phases = czekitout.convert.to_tuple_of_floats(**kwargs)
radial_phases = tuple(radial_phase%(2*np.pi)
for radial_phase
in radial_phases)
radial_amplitudes = _check_and_convert_radial_amplitudes(params)
num_radial_phases = len(radial_phases)
num_radial_amplitudes = len(radial_amplitudes)
if num_radial_phases+1 != num_radial_amplitudes:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return radial_phases
def _pre_serialize_radial_phases(radial_phases):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_radial_phases(serializable_rep):
radial_phases = serializable_rep
return radial_phases
_default_radial_reference_pt = (0.5, 0.5)
_default_radial_amplitudes = (0.1,)
_default_radial_phases = tuple()
[docs]class GenericBlob(BaseShape):
r"""The intensity pattern of a generic blob.
Let :math:`\left(u_{x;c;\text{GB}},u_{y;c;\text{GB}}\right)`,
:math:`N_{\text{GB}}`, :math:`\left\{ \phi_{\text{GB};n}\right\}
_{n=0}^{N_{\text{GB}}-1}`, and :math:`\left\{ D_{\text{GB};n}\right\}
_{n=0}^{N_{\text{GB}}}` be the radial reference point, the number of radial
phases, the radial phases, and the radial amplitudes of the generic blob
respectively. Furthermore, let :math:`A_{\text{GB}}` be the value of the
intensity pattern inside the generic blob. The undistorted intensity pattern
of the generic blob is given by:
.. math ::
\mathcal{I}_{\text{GB}}\left(u_{x},u_{y}\right)=
A_{\text{GB}}\Theta\left(
R_{\text{GB}}\left(u_{\theta;\text{UA}}\right)-u_{r;\text{GB}}\right),
:label: intensity_pattern_of_generic_blob__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the generic blob
respectively, :math:`\Theta\left(\cdots\right)` is the Heaviside step
function,
.. math ::
u_{r;\text{GB}}=\sqrt{\left(u_{x}-u_{x;c;\text{GB}}\right)^{2}
+\left(u_{y}-u_{y;c;\text{GB}}\right)^{2}};
:label: u_r_UGB__1
.. math ::
u_{\theta;\text{GB}}=
\tan^{-1}\left(\frac{u_{y}
-u_{y;c;\text{GB}}}{u_{x}-u_{x;c;\text{GB}}}\right);
:label: u_theta_UGB__1
and
.. math ::
R_{\text{GB}}\left(u_{\theta;\text{GB}}\right)&=
D_{\text{GB};0}\\&\quad\mathop{+}\min\left(1,N_{\text{GB}}\right)
\sum_{n=1}^{N_{\text{GB}}}D_{\text{GB};n}
\cos\left(nu_{\theta;\text{GB}}-\phi_{\text{GB};n-1}\right),
:label: R_UGB__1
with
.. math ::
D_{\text{GB};n} \ge 0,
\quad\forall n\in\left\{ 1,\ldots,N_{\text{GB}}\right\},
:label: D_UGB_n__1
and
.. math ::
D_{\text{GB};0}>\sum_{n=1}^{N_{\text{GB}}}D_{\text{GB};n}.
:label: D_UGB_n__2
Parameters
----------
radial_reference_pt : `array_like` (`float`, shape=(``2``,)), optional
The radial reference point, :math:`\left(u_{x;c;\text{GB}},
u_{y;c;\text{GB}}\right)`.
radial_amplitudes : `array_like` (`float`, shape=(``2``,)), optional
The radial amplitudes,
:math:`\left\{ D_{\text{GB};n}\right\} _{n=0}^{N_{\text{GB}}}`.
radial_phases : `array_like` (`float`, shape=(``len(radial_amplitudes)-1``,)), optional
The radial phases,
:math:`\left\{\phi_{\text{GB};n}\right\}_{n=0}^{N_{\text{GB}}-1}`.
intra_shape_val : `float`, optional
The value of the intensity pattern inside the generic blob.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("radial_reference_pt",
"radial_amplitudes",
"radial_phases",
"intra_shape_val")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
radial_reference_pt=\
_default_radial_reference_pt,
radial_amplitudes=\
_default_radial_amplitudes,
radial_phases=\
_default_radial_phases,
intra_shape_val=\
_default_intra_shape_val,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
u_x_c, u_y_c = self._radial_reference_pt
D = self._radial_amplitudes
phi = self._radial_phases
A = self._intra_shape_val
delta_u_x = u_x-u_x_c
delta_u_y = u_y-u_y_c
u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
u_theta = torch.atan2(delta_u_y, delta_u_x) % (2*np.pi)
N = len(phi)
one = torch.tensor(1.0, device=u_x.device)
R = D[0]*torch.ones_like(u_theta)
for n in range(1, N+1):
R += D[n]*torch.cos(n*u_theta - phi[n-1])
result = A * torch.heaviside(R-u_r, one)
return result
def _check_and_convert_principal_quantum_number(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
principal_quantum_number = czekitout.convert.to_positive_int(**kwargs)
return principal_quantum_number
def _pre_serialize_principal_quantum_number(principal_quantum_number):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_principal_quantum_number(serializable_rep):
principal_quantum_number = serializable_rep
return principal_quantum_number
def _check_and_convert_azimuthal_quantum_number(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
azimuthal_quantum_number = czekitout.convert.to_nonnegative_int(**kwargs)
principal_quantum_number = \
_check_and_convert_principal_quantum_number(params)
if azimuthal_quantum_number >= principal_quantum_number:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return azimuthal_quantum_number
def _pre_serialize_azimuthal_quantum_number(azimuthal_quantum_number):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_azimuthal_quantum_number(serializable_rep):
azimuthal_quantum_number = serializable_rep
return azimuthal_quantum_number
def _check_and_convert_magnetic_quantum_number(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
magnetic_quantum_number = czekitout.convert.to_int(**kwargs)
azimuthal_quantum_number = \
_check_and_convert_azimuthal_quantum_number(params)
if abs(magnetic_quantum_number) > azimuthal_quantum_number:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise ValueError(err_msg)
return magnetic_quantum_number
def _pre_serialize_magnetic_quantum_number(magnetic_quantum_number):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_magnetic_quantum_number(serializable_rep):
magnetic_quantum_number = serializable_rep
return magnetic_quantum_number
def _check_and_convert_effective_size(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
effective_size = czekitout.convert.to_positive_float(**kwargs)
return effective_size
def _pre_serialize_effective_size(effective_size):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_effective_size(serializable_rep):
effective_size = serializable_rep
return effective_size
def _check_and_convert_renormalization_factor(params):
current_func_name = inspect.stack()[0][3]
obj_name = current_func_name[19:]
kwargs = {"obj": params[obj_name], "obj_name": obj_name}
renormalization_factor = czekitout.convert.to_float(**kwargs)
return renormalization_factor
def _pre_serialize_renormalization_factor(renormalization_factor):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize
return serializable_rep
def _de_pre_serialize_renormalization_factor(serializable_rep):
renormalization_factor = serializable_rep
return renormalization_factor
_default_principal_quantum_number = 1
_default_azimuthal_quantum_number = 0
_default_magnetic_quantum_number = 0
_default_effective_size = 0.1
_default_renormalization_factor = 1.0
[docs]class Orbital(BaseShape):
r"""The intensity pattern of a hydrogen-like atomic orbital.
Let :math:`\left(u_{x;c;\text{O}},u_{y;c;\text{O}}\right)`,
:math:`n_{\text{O}}`, :math:`l_{\text{O}}`, :math:`m_{\text{O}}`,
:math:`a_{0;\text{O}}^{*}`, and :math:`\theta_{\text{O}}` be the center, the
principal quantum number, the azimuthal quantum number, the magnetic quantum
number, the effective size, and the rotation angle of the hydrogen-like
atomic orbital respectively. Furthermore, let :math:`A_{\text{O}}` be the
renormalization factor of the intensity pattern. The undistorted intensity
pattern of the orbital is given by:
.. math ::
\mathcal{I}_{\text{O}}\left(u_{x},u_{y}\right)=
A_{\text{O}}\left|\psi_{n_{\text{O}},l_{\text{O}},m_{O}}\left(
u_{r;\text{O}},u_{\theta;O},0\right)\right|^{2},
:label: intensity_pattern_of_orbital__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the orbital
respectively,
.. math ::
u_{r;\text{O}}=\sqrt{\left(u_{x}-u_{x;c;\text{O}}\right)^{2}
+\left(u_{y}-u_{y;c;\text{O}}\right)^{2}},
:label: u_r_O__1
.. math ::
u_{\theta;\text{O}}=\tan^{-1}\left(\frac{u_{y}-u_{y;c;\text{O}}}{
u_{x}-u_{x;c;\text{O}}}\right)-\theta_{\text{O}};
:label: u_theta_O__1
.. math ::
\psi_{n_{\text{O}},l_{\text{O}},m_{O}}\left(u_{r;\text{O}},u_{\theta;O},
u_{\phi;\text{O}}\right)&=\sqrt{\left\{ \frac{2}{n_{\text{O}}
a_{0;\text{O}}^{*}}\right\} ^{3}\frac{\left(
n_{\text{O}}-l_{\text{O}}-1\right)!}{2n_{\text{O}}\left(
n_{\text{O}}+l_{\text{O}}\right)!}}\\&\quad\mathop{\times}
e^{-u_{\rho;\text{O}}/2}u_{\rho;\text{O}}^{l_{\text{O}}}
L_{n_{\text{O}}-l_{\text{O}}-1}^{\left(2l_{\text{O}}+1\right)}\left(
u_{\rho;\text{O}}\right)\\&\quad\mathop{\times}
Y_{l_{\text{O}}}^{m_{\text{O}}}\left(u_{\theta;\text{O}},
u_{\phi;\text{O}}\right),
:label: psi__1
with
.. math ::
u_{\rho;\text{O}}=\frac{2u_{r;\text{O}}}{n_{\text{O}}
a_{0;\text{O}}^{*}},
:label: u_rho_O__1
:math:`L_{n_{\text{O}}-l_{\text{O}}-1}^{2l_{\text{O}}+1}\left(
u_{\rho;\text{O}}\right)` being the generalized Laguerre polynomial of
degree :math:`n_{\text{O}}-l_{\text{O}}-1`, and
:math:`Y_{l_{\text{O}}}^{m_{\text{O}}}\left(u_{\theta;\text{O}},
u_{\phi;\text{O}}\right)` is the spherical harmonic function of degree
:math:`l_{\text{O}}` and order :math:`m_{\text{O}}`.
Parameters
----------
center : `array_like` (`float`, shape=(``2``,)), optional
The center of the hydrogen-like atomic orbital,
:math:`\left(u_{x;c;\text{O}}, u_{y;c;\text{O}}\right)`.
principal_quantum_number : `int`, optional
The principal quantum number of the hydrogen-like atomic orbital,
:math:`n_{\text{O}}`. Must be a positve number.
azimuthal_quantum_number : `int`, optional
The azimuthal quantum number of the hydrogen-like atomic orbital,
:math:`l_{\text{O}}`. Must be a nonnegative number satisfying
``azimuthal_quantum_number < principal_quantum_number``.
magnetic_quantum_number : `int`, optional
The magnetic quantum number of the hydrogen-like atomic orbital,
:math:`m_{\text{O}}`. Must satisfy ``abs(magnetic_quantum_number) <=
azimuthal_quantum_number``.
effective_size : `float`, optional
The effective size of the hydrogen-like atomic orbital,
:math:`a_{0;\text{O}}^{*}`. Must be a positive number.
renormalization_factor : `float`, optional
The renormalization factor of the hydrogen-like atomic orbital,
:math:`A_{\text{O}}`.
rotation_angle : `float`, optional
The rotation angle of the hydrogen-like atomic orbital,
:math:`\theta_{\text{O}}`.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("center",
"principal_quantum_number",
"azimuthal_quantum_number",
"magnetic_quantum_number",
"effective_size",
"renormalization_factor",
"rotation_angle")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
center=\
_default_center,
principal_quantum_number=\
_default_principal_quantum_number,
azimuthal_quantum_number=\
_default_azimuthal_quantum_number,
magnetic_quantum_number=\
_default_magnetic_quantum_number,
effective_size=\
_default_effective_size,
renormalization_factor=\
_default_renormalization_factor,
rotation_angle=\
_default_rotation_angle,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
n = self._principal_quantum_number
l = self._azimuthal_quantum_number
m = self._magnetic_quantum_number
a = self._effective_size
A = self._renormalization_factor
self._pre_factor = (A
* ((2/(n*a))**3
* math.factorial(n-l-1)
/ (2*n*math.factorial(n+l)))
* ((2*l+1)
* math.factorial(l-m)
/ math.factorial(l+m)
/ (4*np.pi)))
self._generalized_laguerre_polynomial_coeffs = \
self._calc_generalized_laguerre_polynomial_coeffs()
unformatted_method_name = \
"_calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v{}"
if l <= n-l-1:
method_name = \
unformatted_method_name.format(1)
else:
method_name = \
unformatted_method_name.format(2)
method_alias = \
getattr(self, method_name)
self._calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l = \
method_alias
self._associated_legendre_polynomial_coeffs = \
self._calc_associated_legendre_polynomial_coeffs()
return None
def _calc_generalized_laguerre_polynomial_coeffs(self):
n = self._principal_quantum_number
l = self._azimuthal_quantum_number
polynomial_degree = n-l-1
alpha = 2*l+1
coeff = 1
numerator = polynomial_degree+alpha
denominator = polynomial_degree
for k in range(polynomial_degree):
coeff *= (numerator/denominator)
numerator -= 1
denominator -= 1
generalized_laguerre_polynomial_coeffs = (coeff,)
for i in range(polynomial_degree):
i_plus_1 = i+1
coeff = -((polynomial_degree-i)
/ (i_plus_1 * (alpha+i_plus_1))
* generalized_laguerre_polynomial_coeffs[-1])
generalized_laguerre_polynomial_coeffs += (coeff,)
return generalized_laguerre_polynomial_coeffs
def _calc_associated_legendre_polynomial_coeffs(self):
l = self._azimuthal_quantum_number
m = self._magnetic_quantum_number
abs_m = abs(m)
r = (l-abs_m)//2
coeff = (-1)**r
numerator = 2*l - 2*r
denominator = l - r
for k in range(0, l-r):
coeff *= (numerator/denominator)
numerator -= 1
denominator -= 1
denominator = r
for k in range(l-r, l):
coeff *= (numerator/denominator)
numerator -= 1
denominator -= 1
denominator = 1
for k in range(l, 2*l-2*r):
coeff *= (numerator/denominator)
numerator -= 1
coeff /= 2**l
associated_legendre_polynomial_coeffs = (coeff,)
temp_1 = r
temp_2 = l-r+1
temp_3 = 2*l-2*r+1
temp_4 = l-abs_m-2*r+1
for i in range(r):
coeff = -((temp_1/temp_2)
* (temp_3/temp_4)
* ((temp_3+1)/(temp_4+1))
* associated_legendre_polynomial_coeffs[-1])
associated_legendre_polynomial_coeffs += (coeff,)
temp_1 -= 1
temp_2 += 1
temp_3 += 2
temp_4 += 2
return associated_legendre_polynomial_coeffs
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
u_x_c, u_y_c = self._center
n = self._principal_quantum_number
l = self._azimuthal_quantum_number
a = self._effective_size
theta = self._rotation_angle
pre_factor = self._pre_factor
delta_u_x = u_x-u_x_c
delta_u_y = u_y-u_y_c
u_r = torch.sqrt(delta_u_x*delta_u_x + delta_u_y*delta_u_y)
u_rho = (2/n/a)*u_r
u_theta = torch.atan2(delta_u_y, delta_u_x) - theta
cos_u_theta = torch.cos(u_theta)
method_name = ("_calc_generalized_laguerre_polynomial"
"_and_u_rho_to_power_of_2l")
method_alias = getattr(self, method_name)
L, u_rho_to_power_of_2l = method_alias(u_rho)
L_sq = L*L
method_name = "_calc_associated_legendre_polynomial_sq"
method_alias = getattr(self, method_name)
P_sq = method_alias(cos_u_theta)
result = (pre_factor
* torch.exp(-u_rho)
* u_rho_to_power_of_2l
* L_sq
* P_sq)
return result
def _calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v1(
self, u_rho):
coeffs = self._generalized_laguerre_polynomial_coeffs
n = self._principal_quantum_number
l = self._azimuthal_quantum_number
polynomial_degree = n-l-1
power_of_u_rho = torch.ones_like(u_rho)
generalized_laguerre_polynomial = coeffs[0]*power_of_u_rho
for i in range(1, l+1):
power_of_u_rho *= u_rho
generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
u_rho_to_power_of_2l = power_of_u_rho*power_of_u_rho
for i in range(l+1, polynomial_degree+1):
power_of_u_rho *= u_rho
generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
return generalized_laguerre_polynomial, u_rho_to_power_of_2l
def _calc_generalized_laguerre_polynomial_and_u_rho_to_power_of_2l_v2(
self, u_rho):
coeffs = self._generalized_laguerre_polynomial_coeffs
n = self._principal_quantum_number
l = self._azimuthal_quantum_number
polynomial_degree = n-l-1
power_of_u_rho = torch.ones_like(u_rho)
generalized_laguerre_polynomial = coeffs[0]*power_of_u_rho
for i in range(1, polynomial_degree+1):
power_of_u_rho *= u_rho
generalized_laguerre_polynomial += coeffs[i]*power_of_u_rho
for i in range(polynomial_degree+1, l+1):
power_of_u_rho *= u_rho
u_rho_to_power_of_2l = power_of_u_rho*power_of_u_rho
return generalized_laguerre_polynomial, u_rho_to_power_of_2l
def _calc_associated_legendre_polynomial_sq(self, cos_u_theta):
coeffs = self._associated_legendre_polynomial_coeffs
l = self._azimuthal_quantum_number
m = self._magnetic_quantum_number
abs_m = abs(m)
r = (l-abs_m)//2
x = cos_u_theta
x_sq = x*x
power_of_x = torch.ones_like(x)
for _ in range(l-abs_m-2*r):
power_of_x *= x
y_sq = 1-x_sq
associated_legendre_polynomial_sq = coeffs[0]*power_of_x
for i in range(1, r+1):
power_of_x *= x_sq
associated_legendre_polynomial_sq += coeffs[i]*power_of_x
associated_legendre_polynomial_sq *= associated_legendre_polynomial_sq
for i in range(abs_m):
associated_legendre_polynomial_sq *= y_sq
return associated_legendre_polynomial_sq
def _check_and_convert_bg_ellipse(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (Ellipse, Circle, type(None))
if isinstance(obj, accepted_types[-1]):
bg_ellipse = accepted_types[0]()
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
bg_ellipse = copy.deepcopy(obj)
return bg_ellipse
def _pre_serialize_bg_ellipse(bg_ellipse):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_bg_ellipse(serializable_rep):
if "radius" in serializable_rep:
bg_ellipse = Circle.de_pre_serialize(serializable_rep)
else:
bg_ellipse = Ellipse.de_pre_serialize(serializable_rep)
return bg_ellipse
def _check_and_convert_fg_ellipse(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (Ellipse, Circle, type(None))
if isinstance(obj, accepted_types[-1]):
fg_ellipse = accepted_types[0]()
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
fg_ellipse = copy.deepcopy(obj)
return fg_ellipse
def _pre_serialize_fg_ellipse(fg_ellipse):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_fg_ellipse(serializable_rep):
if "radius" in serializable_rep:
fg_ellipse = Circle.de_pre_serialize(serializable_rep)
else:
fg_ellipse = Ellipse.de_pre_serialize(serializable_rep)
return fg_ellipse
_default_bg_ellipse = None
_default_fg_ellipse = None
[docs]class Lune(BaseShape):
r"""The intensity pattern of a lune.
Let :math:`\mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right)` and
:math:`\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)` be the intensity
patterns of the background and the foreground ellipses respectively, the
latter of which is used to mask the former to form the lune. The undistorted
intensity pattern of the lune is given by:
.. math ::
\mathcal{I}_{\text{L}}\left(u_{x},u_{y}\right)=\begin{cases}
\mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right),
& \text{if }\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)=0,\\
0, & \text{otherwise},
\end{cases}
:label: intensity_pattern_of_lune__1
where :math:`u_{x}` and :math:`u_{y}` are fractional horizontal and vertical
coordinates of the undistorted intensity pattern of the lune respectively.
Parameters
----------
bg_ellipse : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | `None`, optional
The intensity pattern of the background ellipse,
:math:`\mathcal{I}_{\text{BE}}\left(u_{x},u_{y}\right)`. If
``bg_ellipse`` is set to ``None``, then the parameter will be reassigned
to the value ``fakecbed.shapes.Ellipse()``.
fg_ellipse : :class:`fakecbed.shapes.Circle` | :class:`fakecbed.shapes.Ellipse` | `None`, optional
The intensity pattern of the foreground ellipse,
:math:`\mathcal{I}_{\text{FE}}\left(u_{x},u_{y}\right)`. If
``fg_ellipse`` is set to ``None``, then the parameter will be reassigned
to the value ``fakecbed.shapes.Ellipse()``.
skip_validation_and_conversion : `bool`, optional
Let ``validation_and_conversion_funcs`` and ``core_attrs`` denote the
attributes :attr:`~fancytypes.Checkable.validation_and_conversion_funcs`
and :attr:`~fancytypes.Checkable.core_attrs` respectively, both of which
being `dict` objects.
Let ``params_to_be_mapped_to_core_attrs`` denote the `dict`
representation of the constructor parameters excluding the parameter
``skip_validation_and_conversion``, where each `dict` key ``key`` is a
different constructor parameter name, excluding the name
``"skip_validation_and_conversion"``, and
``params_to_be_mapped_to_core_attrs[key]`` would yield the value of the
constructor parameter with the name given by ``key``.
If ``skip_validation_and_conversion`` is set to ``False``, then for each
key ``key`` in ``params_to_be_mapped_to_core_attrs``,
``core_attrs[key]`` is set to ``validation_and_conversion_funcs[key]
(params_to_be_mapped_to_core_attrs)``.
Otherwise, if ``skip_validation_and_conversion`` is set to ``True``,
then ``core_attrs`` is set to
``params_to_be_mapped_to_core_attrs.copy()``. This option is desired
primarily when the user wants to avoid potentially expensive deep copies
and/or conversions of the `dict` values of
``params_to_be_mapped_to_core_attrs``, as it is guaranteed that no
copies or conversions are made in this case.
"""
ctor_param_names = ("bg_ellipse",
"fg_ellipse")
kwargs = {"namespace_as_dict": globals(),
"ctor_param_names": ctor_param_names}
_validation_and_conversion_funcs_ = \
fancytypes.return_validation_and_conversion_funcs(**kwargs)
_pre_serialization_funcs_ = \
fancytypes.return_pre_serialization_funcs(**kwargs)
_de_pre_serialization_funcs_ = \
fancytypes.return_de_pre_serialization_funcs(**kwargs)
del ctor_param_names, kwargs
def __init__(self,
bg_ellipse=\
_default_bg_ellipse,
fg_ellipse=\
_default_fg_ellipse,
skip_validation_and_conversion=\
_default_skip_validation_and_conversion):
ctor_params = {key: val
for key, val in locals().items()
if (key not in ("self", "__class__"))}
BaseShape.__init__(self, ctor_params)
self.execute_post_core_attrs_update_actions()
return None
def execute_post_core_attrs_update_actions(self):
self_core_attrs = self.get_core_attrs(deep_copy=False)
for self_core_attr_name in self_core_attrs:
attr_name = "_"+self_core_attr_name
attr = self_core_attrs[self_core_attr_name]
setattr(self, attr_name, attr)
return None
[docs] def update(self, new_core_attr_subset_candidate):
super().update(new_core_attr_subset_candidate)
self.execute_post_core_attrs_update_actions()
return None
def _eval(self, u_x, u_y):
bg_ellipse = self._bg_ellipse
fg_ellipse = self._fg_ellipse
result = bg_ellipse.eval(u_x, u_y) * (fg_ellipse.eval(u_x, u_y) == 0)
return result
def _check_and_convert_support(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (Circle, Ellipse, Band, Arc, GenericBlob, Lune, type(None))
if isinstance(obj, accepted_types[-1]):
support = accepted_types[0]()
else:
kwargs = {"obj": obj,
"obj_name": obj_name,
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
support = copy.deepcopy(obj)
return support
def _pre_serialize_support(support):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = obj_to_pre_serialize.pre_serialize()
return serializable_rep
def _de_pre_serialize_support(serializable_rep):
if "radius" in serializable_rep:
support = Circle.de_pre_serialize(serializable_rep)
elif "eccentricity" in serializable_rep:
support = Ellipse.de_pre_serialize(serializable_rep)
elif "end_pt_1" in serializable_rep:
support = Band.de_pre_serialize(serializable_rep)
elif "subtending_angle" in serializable_rep:
support = Arc.de_pre_serialize(serializable_rep)
elif "radial_amplitudes" in serializable_rep:
support = GenericBlob.de_pre_serialize(serializable_rep)
else:
support = Lune.de_pre_serialize(serializable_rep)
return support
def _check_and_convert_intra_support_shapes(params):
current_func_name = inspect.stack()[0][3]
char_idx = 19
obj_name = current_func_name[char_idx:]
obj = params[obj_name]
accepted_types = (Circle,
Ellipse,
Peak,
Band,
PlaneWave,
Arc,
GenericBlob,
Orbital,
Lune,
NonuniformBoundedShape)
try:
for intra_support_shape in obj:
kwargs = {"obj": intra_support_shape,
"obj_name": "intra_support_shape",
"accepted_types": accepted_types}
czekitout.check.if_instance_of_any_accepted_types(**kwargs)
except:
err_msg = globals()[current_func_name+"_err_msg_1"]
raise TypeError(err_msg)
intra_support_shapes = copy.deepcopy(obj)
return intra_support_shapes
def _pre_serialize_intra_support_shapes(intra_support_shapes):
obj_to_pre_serialize = random.choice(list(locals().values()))
serializable_rep = tuple()
for elem in obj_to_pre_serialize:
serializable_rep += (elem.pre_serialize(),)
return serializable_rep
def _de_pre_serialize_intra_support_shapes(serializable_rep):
intra_support_shapes = tuple()
for pre_serialized_intra_support_shape in serializable_rep:
if "radius" in pre_serialized_intra_support_shape:
cls_alias = Circle
elif "eccentricity" in pre_serialized_intra_support_shape:
cls_alias = Ellipse
elif "functional_form" in pre_serialized_intra_support_shape:
cls_alias = Peak
elif "end_pt_1" in pre_serialized_intra_support_shape:
cls_alias = Band
elif "propagation_direction" in pre_serialized_intra_support_shape:
cls_alias = PlaneWave
elif "subtending_angle" in pre_serialized_intra_support_shape:
cls_alias = Arc
elif "radial_amplitudes" in pre_serialized_intra_support_shape:
cls_alias = GenericBlob
elif "magnetic_quantum_number" in pre_serialized_intra_support_shape:
cls_alias = Orbital
elif "bg_ellipse" in pre_serialized_intra_support_shape:
cls_alias = Lune
else:
cls_alias = NonuniformBoundedShape
intra_support_shape = \
cls_alias.de_pre_serialize(pre_serialized_intra_support_shape)
intra_support_shapes += \
(intra_support_shape,)
return intra_support_shapes
_default_support = None
_default_intra_support_shapes = tuple()
###########################
## Define error messages ##
###########################
_check_and_convert_cartesian_coords_err_msg_1 = \
("The objects ``{}`` and ``{}`` must be real-valued matrices of the same "
"shape.")
_check_and_convert_real_torch_matrix_err_msg_1 = \
("The object ``{}`` must be a real-valued matrix.")
_base_shape_err_msg_1 = \
("Cannot construct instances of the class `fakecbed.shapes.BaseShape`, "
"only subclasses of itself defined in the `fakecbed` library.")
_check_and_convert_eccentricity_err_msg_1 = \
("The object ``eccentricity`` must be a nonnegative number less than or "
"equal to unity.")
_check_and_convert_radial_range_err_msg_1 = \
("The object ``radial_range`` must be a pair of positive real numbers "
"satisfying ``radial_range[0]<radial_range[1]``.")
_check_and_convert_radial_amplitudes_err_msg_1 = \
("The object ``radial_amplitudes`` must be a non-empty sequence of "
"nonnegative real numbers.")
_check_and_convert_radial_amplitudes_err_msg_2 = \
("The object ``radial_amplitudes`` must satisfy ``radial_amplitudes[0] > "
"sum(radial_amplitudes[1:])``.")
_check_and_convert_radial_phases_err_msg_1 = \
("The objects ``radial_phases`` and ``radial_amplitudes`` must satisfy "
"``len(radial_phases)+1 == len(radial_amplitudes)``.")
_check_and_convert_azimuthal_quantum_number_err_msg_1 = \
("The objects ``azimuthal_quantum_number`` and "
"``principal_quantum_number`` must satisfy "
"``azimuthal_quantum_number < principal_quantum_number``.")
_check_and_convert_magnetic_quantum_number_err_msg_1 = \
("The objects ``magnetic_quantum_number`` and "
"``azimuthal_quantum_number`` must satisfy "
"``abs(magnetic_quantum_number) <= azimuthal_quantum_number``.")
_check_and_convert_intra_support_shapes_err_msg_1 = \
("The object ``intra_support_shapes`` must be a sequence of objects of any "
"of the following types: ("
"`fakecbed.shapes.Circle`, "
"`fakecbed.shapes.Ellipse`, "
"`fakecbed.shapes.Peak`, "
"`fakecbed.shapes.Band`, "
"`fakecbed.shapes.PlaneWave`, "
"`fakecbed.shapes.Arc`, "
"`fakecbed.shapes.GenericBlob`, "
"`fakecbed.shapes.Orbital`, "
"`fakecbed.shapes.Lune`, "
"`fakecbed.shapes.NonuniformBoundedShape`).")