"""
Classes with geometrical shapes.
* Shape: Base class for all possible geometric shapes.
* SurfaceShape: Base class to caracterize the shape (sphere, flat, etc.) of the optical element surface
* BoundaryShape: Base class to characterize the optical element dimensions (rectangle, etc.).
Additional classes help to define flags:
* Convexity: NONE = -1, UPWARD = 0, DOWNWARD = 1
* Direction: TANGENTIAL = 0, SAGITTAL = 1
* Side: SOURCE = 0, IMAGE = 1
"""
import numpy
from syned.syned_object import SynedObject
from collections import OrderedDict
[docs]class Convexity:
NONE = -1
UPWARD = 0
DOWNWARD = 1
[docs]class Direction:
TANGENTIAL = 0
SAGITTAL = 1
[docs]class Side:
SOURCE = 0
IMAGE = 1
[docs]class Shape(SynedObject):
"""
Constructor.
"""
[docs] def __init__(self):
SynedObject.__init__(self)
[docs]class SurfaceShape(Shape):
"""
Constructor.
Parameters
----------
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
"""
[docs] def __init__(self, convexity = Convexity.UPWARD):
Shape.__init__(self)
self._convexity = convexity
[docs] def get_convexity(self):
"""
Gets the convexity flag.
Returns
-------
int
NONE = -1, UPWARD = 0, DOWNWARD = 1
"""
return self._convexity
[docs]class BoundaryShape(Shape):
"""
Constructor.
"""
[docs] def __init__(self):
Shape.__init__(self)
[docs] def get_boundaries(self):
"""
Returns the boundary shape. It must be defined in the children classes.
Raises
------
NotImplementedError
"""
raise NotImplementedError()
#############################
# Subclasses for SurfaceShape
#############################
[docs]class Cylinder(SynedObject):
"""
Defines that a surface shape is cylindrical in one direction.
Usage: must be used with double inheritance in other classes (e.g. ParabolicCylinder).
It should not be used standalone.
Parameters
----------
cylinder_direction : int, optional
TANGENTIAL = 0, SAGITTAL = 1.
"""
[docs] def __init__(self, cylinder_direction=Direction.TANGENTIAL):
self._cylinder_direction = cylinder_direction
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._add_support_text([
("cylinder_direction" , "(0=tangential, 1=sagittal)", " " ),
] )
[docs] def get_cylinder_direction(self):
"""
Returns the cylinder direction.
Returns
-------
int
TANGENTIAL = 0, SAGITTAL = 1.
"""
return self._cylinder_direction
[docs]class Conic(SurfaceShape):
"""
Defines a conic surface shape expresses via the 10 conic coeffcients.
Parameters
----------
conic_coefficients : list, optional
A list with the 10 coefficients.
"""
[docs] def __init__(self,
conic_coefficients=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):
SurfaceShape.__init__(self, convexity=Convexity.NONE)
# stored as numpy array, not as list, to avoid in i/o to interpret the items as syned objects.
self._conic_coefficients = numpy.array(conic_coefficients)
self._set_support_text([
("conic_coefficients" , "Conic coeffs. ", " " ),
] )
[docs] def get_conic_coefficients(self):
"""
Returns the coefficients.
Returns
-------
list
A list with the 10 coefficients.
"""
return list(self._conic_coefficients)
[docs]class Plane(SurfaceShape):
"""
Defines a plane surface shape.
"""
[docs] def __init__(self):
SurfaceShape.__init__(self, convexity=Convexity.NONE)
[docs]class Sphere(SurfaceShape):
"""
Defines an spherical surface.
Parameters
----------
radius : float
The sphere radius.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
"""
[docs] def __init__(self, radius=1.0, convexity=Convexity.UPWARD):
SurfaceShape.__init__(self, convexity=convexity)
self._radius = radius
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("radius" , "Sphere radius ", "m" ),
("convexity" , "(0=upwards, 1=downwards)", " "),
] )
[docs] @classmethod
def create_sphere_from_radius(cls, radius=0.0, convexity=Convexity.UPWARD):
"""
Defines an spherical surface.
Parameters
----------
radius : float
The sphere radius.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Sphere
"""
return Sphere(radius, convexity)
[docs] @classmethod
def create_sphere_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
"""
Defines an spherical surface.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Sphere
"""
sphere = Sphere(convexity=convexity)
sphere.initialize_from_p_q(p, q, grazing_angle)
return sphere
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Defines an spherical surface.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
self._radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
[docs] @classmethod
def get_radius_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
"""
Calculates the radius of the sphere from factory parameters (1/p+1/q=2/(R sin theta_grazing)))
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
float
the calculated radius.
"""
# 1/p + 1/q = 2/(R cos(pi/2 - gr.a.))
return (2*p*q/(p+q))/numpy.sin(grazing_angle)
[docs] def get_radius(self):
"""
Returns the radius of the sphere.
Returns
-------
float
The radius of the sphere.
"""
return self._radius
[docs]class SphericalCylinder(Sphere, Cylinder):
"""
Constructor.
Parameters
----------
radius : float
the radius of the circular section.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
"""
[docs] def __init__(self,
radius=1.0,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
Sphere.__init__(self, radius, convexity)
Cylinder.__init__(self, cylinder_direction)
[docs] @classmethod
def create_spherical_cylinder_from_radius(cls, radius=0.0, convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Creates a spherical cylinder.
Parameters
----------
radius : float
the radius of the circular section.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of SphericalCylinder
"""
return SphericalCylinder(radius, convexity, cylinder_direction)
[docs] @classmethod
def create_spherical_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of SphericalCylinder
"""
spherical_cylinder = SphericalCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
spherical_cylinder.initialize_from_p_q(p, q, grazing_angle)
return spherical_cylinder
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Calculates and sets the radius of curvature in the corresponding direction (tangential or sagittal.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
if self._cylinder_direction == Direction.TANGENTIAL:
self._radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
elif self._cylinder_direction == Direction.SAGITTAL:
self._radius = SphericalCylinder.get_radius_from_p_q_sagittal(p, q, grazing_angle)
[docs] @classmethod
def get_radius_from_p_q_sagittal(cls, p=2.0, q=1.0, grazing_angle=0.003):
"""
Calculates the sagittal radius from the factory parameters (1/p + 1/q = 2 sin(grazing_angle)/Rs).
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
float
The calculated radius.
"""
# 1/p + 1/q = 2 cos(pi/2 - gr.a.)/r
return (2*p*q/(p+q))*numpy.sin(grazing_angle)
[docs]class Ellipsoid(SurfaceShape):
"""
Constructor.
Ellipsoid: Revolution ellipsoid (rotation around major axis).
It is defined with three parameters: axes of the ellipse and an additional parameter
defining the position of the origin of the mirror. This additional parameter can be "p", "x0", "y0"
or the angle beta from the ellipsoid center (tan(beta)=y0/x0). For simplicity, we store "p" in syned.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
References
----------
Conic Surfaces and Transformations for X-Ray Beamline Optics Modeling,
Manuel Sanchez del Rio and Kenneth Goldberg
(2024) https://arxiv.org/abs/2406.04079
"""
[docs] def __init__(self, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
SurfaceShape.__init__(self, convexity)
self._min_axis = min_axis
self._maj_axis = maj_axis
self._p_focus = p_focus
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("min_axis" , "Ellipse major axis ", "m" ),
("maj_axis" , "Ellipse minor axis ", "m"),
("p_focus" , "Ellipse p (source-focus to pole) ", "m"),
("convexity" , "(0=upwards, 1=downwards)", " "),
] )
[docs] @classmethod
def create_ellipsoid_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
"""
Creates an ellipsoid.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Ellipsoid
"""
return Ellipsoid(min_axis, maj_axis, p_focus, convexity)
[docs] @classmethod
def create_ellipsoid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
"""
Creates an ellipsoid from factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Ellipsoid
"""
ellipsoid = Ellipsoid(convexity=convexity)
ellipsoid.initialize_from_p_q(p, q, grazing_angle)
return ellipsoid
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Sets the ellipsoid parameters as calculated from the factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
self._min_axis, self._maj_axis = Ellipsoid.get_axis_from_p_q(p, q, grazing_angle)
self._p_focus = p
[docs] def initialize_from_shadow_parameters(self, axmaj=2.0, axmin=1.0, ell_the=0.003, convexity=Convexity.UPWARD):
"""
Sets the ellipsoid parameters as calculated from the parameters used in SHADOW.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
ell_the : float, optional
the angle beta from the ellipsoid center in rads.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
"""
tanbeta2 = numpy.tan(ell_the) ** 2
y = axmaj * axmin / numpy.sqrt(axmin ** 2 + axmaj ** 2 * tanbeta2)
z = y * numpy.tan(ell_the)
c = numpy.sqrt(axmaj ** 2 - axmin ** 2)
p = numpy.sqrt( (y + c)**2 + z**2)
self.__init__(axmin, axmaj, p, convexity)
[docs] def get_axes(self):
"""
Returns the ellipsoid axes.
Note that the third axis of the ellipsoid is the same as the minor axis (revolution ellipsoid).
Returns
-------
tuple
(minor_axis, major_axis)
"""
return self._min_axis, self._maj_axis
[docs] def get_p_q(self, grazing_angle=None):
"""
Returns p and q for a given grazing angle.
Parameters
----------
grazing_angle : None, float
The grazing angle in rad. If None it calculates it using a,b,p.
** This is not used, as it is calculated from the ellipsoid parameters **
Returns
-------
tuple
(p, q)
"""
return self.get_p_focus(), self.get_q_focus()
# semiaxes etc
[docs] def get_a(self):
"""
Returns a = half of the major axis.
Returns
-------
float
"""
return 0.5 * self._maj_axis
[docs] def get_b(self):
"""
Returns b = half of the minor axis.
Returns
-------
float
"""
return 0.5 * self._min_axis
[docs] def get_c(self):
"""
Returns c = sqrt(a^2 - b^2).
Returns
-------
float
"""
return numpy.sqrt(self.get_a()**2 - self.get_b()**2)
[docs] def get_p_focus(self):
"""
Returns p (=p_focus).
Returns
-------
float
"""
return self._p_focus
[docs] def get_q_focus(self):
"""
Returns q.
Returns
-------
float
"""
return 2 * self.get_a() - self.get_p_focus()
[docs] def get_eccentricity(self):
"""
returns the eccentricity e = c / a.
Returns
-------
float
"""
return self.get_c() / self.get_a()
[docs] def get_grazing_angle(self):
"""
Returns the grazing angle.
Returns
-------
float
"""
return numpy.arcsin(self.get_b() / numpy.sqrt(self.get_p_focus() * self.get_q_focus()))
[docs] def get_mirror_center(self):
"""
Returns the coordinates of the mirror pole or center.
Returns
-------
tuple
(coor_along_axis_maj, coor_along_axis_min).
"""
coor_along_axis_maj = (self.get_p_focus()**2 - self.get_q_focus()**2) / (4 * self.get_c())
coor_along_axis_min = self.get_b() * numpy.sqrt(1 - (coor_along_axis_maj / self.get_a())**2)
return coor_along_axis_maj, coor_along_axis_min
[docs] def get_angle_pole_from_origin(self):
"""
Return the angle from pole to origin (beta).
Returns
-------
float
"""
x1, x2 = self.get_mirror_center()
return numpy.arctan(x2 / x1)
[docs] @classmethod
def get_axis_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
"""
Calculates the ellipse axes from the factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
tuple
(minor_axis, major_axis).
"""
# see calculation of ellipse axis in shadow_kernel.f90 row 3605
min_axis = 2*numpy.sqrt(p*q)*numpy.sin(grazing_angle)
maj_axis = (p + q)
return min_axis, maj_axis
[docs] @classmethod
def get_p_q_from_axis(cls, min_axis=2.0, maj_axis=1.0, grazing_angle=0.003):
"""
Calculates the p and q values from axis and grazing angle.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
tuple
(p, q).
"""
a = maj_axis / 2
b = min_axis / 2
p = a + numpy.sqrt(a**2 - (b / numpy.sin(grazing_angle))**2)
q = maj_axis - p
return p, q
[docs]class EllipticalCylinder(Ellipsoid, Cylinder):
"""
Constructor.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
"""
[docs] def __init__(self,
min_axis=0.0,
maj_axis=0.0,
p_focus=0.0,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
Ellipsoid.__init__(self, min_axis, maj_axis, p_focus, convexity)
Cylinder.__init__(self, cylinder_direction)
[docs] @classmethod
def create_elliptical_cylinder_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0,
convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Returns an EllipticalCylinder instance from main parameters.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of EllipticalCylinder
"""
return EllipticalCylinder(min_axis, maj_axis, p_focus, convexity, cylinder_direction)
[docs] @classmethod
def create_elliptical_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Returns an EllipticalCylinder instance from factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of EllipticalCylinder
"""
elliptical_cylinder = EllipticalCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
elliptical_cylinder.initialize_from_p_q(p, q, grazing_angle)
return elliptical_cylinder
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Sets the ellipsoid parameters for given factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
super().initialize_from_p_q(p, q, grazing_angle)
[docs] def get_p_q(self, grazing_angle=None):
"""
Returns p and q distances for a given grazing angle.
Parameters
----------
grazing_angle : float, None
The grazing angle in rad. If None it calculates it using a,b,p.
** This is not longer used, as it is calculated from the Ellipse parameters **
Returns
-------
tuple
(p, q).
"""
if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
return super().get_p_q()
[docs]class Hyperboloid(SurfaceShape):
"""
Constructor.
Hyperboloid: Revolution hyperboloid (two sheets: rotation around major axis).
It is defined with three parameters: axes of the hyperbola and an additional parameter
defining the position of the origin of the mirror. This additional parameter can be "p", "x0", "y0"
or the angle beta from the ellipsoid center (tan(beta)=y0/x0). For simplicity, we store "p" in syned.
Parameters
----------
min_axis : float, optional
the hyperbola minor axis.
maj_axis : float, optional
the hyperbola major axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
References
----------
Some equations can be found here: https://github.com/srio/shadow3-docs/blob/master/doc/conics.pdf
"""
[docs] def __init__(self, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
SurfaceShape.__init__(self, convexity)
self._min_axis = min_axis
self._maj_axis = maj_axis
self._p_focus = p_focus
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("min_axis" , "Hyperbola major axis ", "m" ),
("maj_axis" , "Hyperbola minor axis ", "m"),
("p_focus" , "Hyperbola p (source-focus to pole) ", "m"),
("convexity" , "(0=upwards, 1=downwards)", " "),
] )
[docs] @classmethod
def create_hyperboloid_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0, convexity=Convexity.UPWARD):
"""
Creates an hyperboloid from main parameters.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the angle beta from the hyperbola center in rads.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Hyperboloid
"""
return Hyperboloid(min_axis, maj_axis, p_focus, convexity)
[docs] @classmethod
def create_hyperboloid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, convexity=Convexity.UPWARD):
"""
Creates an hyperboloid from factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Hyoerboloid
"""
hyperboloid = Hyperboloid(convexity=convexity)
hyperboloid.initialize_from_p_q(p, q, grazing_angle)
return hyperboloid
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Sets the hyperboloid parameters as calculated from the factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
self._min_axis, self._maj_axis = Hyperboloid.get_axis_from_p_q(p, q, grazing_angle)
self._p_focus = p
[docs] def get_axes(self):
"""
Returns the hyperboloid axes.
Note that the third axis of the ellipsoid is the same as the minor axis (revolution ellipsoid).
Returns
-------
tuple
(minor_axis, major_axis)
"""
return self._min_axis, self._maj_axis
[docs] def get_p_q(self, grazing_angle=None):
"""
Returns p and q for a given grazing angle.
Parameters
----------
grazing_angle : float, None
The grazing angle in rad. If None it calculates it using a,b,p.
** This is not longer used as it is calculates from the Hyperbola parameters **
Returns
-------
tuple
(p, q)
"""
return self.get_p_focus(), self.get_q_focus()
# semiaxes etc
[docs] def get_a(self):
"""
Returns a = half of the major axis.
Returns
-------
float
"""
return 0.5 * self._maj_axis
[docs] def get_b(self):
"""
Returns b = half of the minor axis.
Returns
-------
float
"""
return 0.5 * self._min_axis
[docs] def get_c(self):
"""
Returns c = sqrt(a^2 + b^2).
Returns
-------
float
"""
return numpy.sqrt(self.get_a()**2 + self.get_b()**2)
[docs] def get_p_focus(self):
"""
Returns p (=p_focus).
Returns
-------
float
"""
return self._p_focus
[docs] def get_q_focus(self):
"""
Returns q.
Returns
-------
float
"""
if self.get_p_focus() > 2 * self.get_a():
return self.get_p_focus() - 2 * self.get_a() # p > a
else:
return self.get_p_focus() + 2 * self.get_a() # p < a
[docs] def get_eccentricity(self):
"""
returns the eccentricity e = c / a.
Returns
-------
float
"""
return self.get_c / self.get_a()
[docs] def get_grazing_angle(self):
"""
Returns the grazing angle.
Returns
-------
float
"""
return 0.5 * numpy.arccos( (4 * self.get_c()**2 - self.get_p_focus()**2 - self.get_q_focus()**2 ) / (-2 * self.get_p_focus() * self.get_q_focus()))
[docs] def get_mirror_center(self):
"""
Returns the coordinates of the mirror pole or center.
Returns
-------
tuple
(coor_along_axis_maj, coor_along_axis_min).
"""
coor_along_axis_maj = (self.get_p_focus()**2 - self.get_q_focus()**2) / (4 * self.get_c())
coor_along_axis_min = self.get_b() * numpy.sqrt((coor_along_axis_maj / self.get_a())**2 - 1)
return coor_along_axis_maj, coor_along_axis_min
[docs] @classmethod
def get_axis_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, branch_sign=None):
"""
Calculates the hyperbola axes from the factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
branch_sign : None, int
+1 (positive) when p > q; or -1 (negative) when p < q.If None, it is internally calculated.
Returns
-------
tuple
(minor_axis, major_axis).
"""
if branch_sign is None:
if p > q:
branch_sign = +1
else:
branch_sign = -1
ah = (p - q) * branch_sign / 2
ch = 0.5 * numpy.sqrt(p**2 + q**2 - 2 * p * q * numpy.cos(2 * grazing_angle))
bh = numpy.sqrt(ch**2 - ah**2)
maj_axis = 2 * ah
min_axis = 2 * bh
return min_axis, maj_axis
[docs] @classmethod
def get_p_q_from_axis(cls, min_axis=2.0, maj_axis=1.0, grazing_angle=0.003, branch_sign=+1):
"""
Calculates the p and q values from axis and grazing angle.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
tuple
(p, q).
"""
bh = min_axis / 2
ah = maj_axis / 2
ch = numpy.sqrt(bh**2 + ah**2)
c2t = numpy.cos(2 * grazing_angle)
# p > q; q = p - 2a
# 4 c**2 = p**2 + q**2 - 2 p q c2t ; solve quadratic
if branch_sign > 0:
A = 0.5 * (1 - c2t)
B = ah * (-1 + c2t)
C = ah**2 - ch**2
p1 = (-B + numpy.sqrt(B**2 - 4 * A * C)) / 2 / A
q1 = p1 - 2 * ah
else:
A = 0.5 * (1 - c2t)
B = ah * (1 - c2t)
C = ah**2 - ch**2
p1 = (-B + numpy.sqrt(B**2 - 4 * A * C)) / 2 / A
q1 = p1 + 2 * ah
return p1, q1
[docs]class HyperbolicCylinder(Hyperboloid, Cylinder):
"""
Constructor.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
"""
[docs] def __init__(self,
min_axis=0.0,
maj_axis=0.0,
p_focus=0.0,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
Hyperboloid.__init__(self, min_axis, maj_axis, p_focus, convexity)
Cylinder.__init__(self, cylinder_direction)
[docs] @classmethod
def create_hyperbolic_cylinder_from_axes(cls, min_axis=0.0, maj_axis=0.0, p_focus=0.0,
convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Returns an HyperbolicCylinder instance from main parameters.
Parameters
----------
min_axis : float, optional
the ellipse minor axis.
maj_axis : float, optional
the ellipse majot axis.
p_focus : float, optional
the distance from the first focus (source position) to the mirror pole.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of HyperbolicCylinder
"""
return HyperbolicCylinder(min_axis, maj_axis, p_focus, convexity, cylinder_direction)
[docs] @classmethod
def create_hyperbolic_cylinder_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
convexity=Convexity.UPWARD, cylinder_direction=Direction.TANGENTIAL):
"""
Returns an HyperbolicCylinder instance from factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of HyperbolicCylinder
"""
hyperbolic_cylinder = HyperbolicCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
hyperbolic_cylinder.initialize_from_p_q(p, q, grazing_angle)
return hyperbolic_cylinder
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Sets the hyperboloid parameters for given factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
super().initialize_from_p_q(p, q, grazing_angle)
[docs] def get_p_q(self, grazing_angle=None):
"""
Returns p and q distances for a given grazing angle.
Parameters
----------
grazing_angle : float
The grazing angle in rad. If None it calculates it using a,b,p.
** This is not longer used as it is calculates from the Hyperbola parameters **
Returns
-------
tuple
(p, q).
"""
if self._cylinder_direction == Direction.SAGITTAL: raise NotImplementedError("Operation not possible for SAGITTAL direction")
return super().get_p_q()
[docs]class Paraboloid(SurfaceShape):
"""
Constructor.
Paraboloid: Revolution paraboloid (rotation around symmetry axis).
It is defined with three parameters: the parabola_parameter and two more parameters
defining the position of the origin of the mirror.
The parabola_parameter = 2 * focal_length = - 0.5 * ccc_9 / ccc_2
The additional parameter can be the focal distances
("p" or "q", one is infinity), "x0", "y0" or the grazing angle.
Here, we selected the at_infinity and the finite focal distance p or q or distance from
the mirror pole to focus (pole to focus).
The parabola equation is:
ccc_2 y^2 + ccc_9 z = 0 or
y^2 = -ccc_9/ccc_2 z = 2 parabola_parameter z = 4 focal_length z
The focus is at (0, 0, focal_length).
The directrix is at (0, 0, -focal_length).
The distance from the directrix to focus is 2 * focal_length.
The radius of curvature at the vertex is 2 * focal_length.
Parameters
----------
parabola_parameter : float, optional
parabola_parameter = 2 * focal_length = - 0.5 * ccc_9 / ccc_2. Equation: y^2 = 2 parabola_parameter z.
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
pole_to_focus : float, optional
The p distance.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
References
----------
https://en.wikipedia.org/wiki/Parabola
https://doi.org/10.1107/S1600577522004593
https://arxiv.org/abs/2406.04079
"""
[docs] def __init__(self,
parabola_parameter=0.0,
at_infinity=Side.SOURCE,
pole_to_focus=None,
convexity=Convexity.UPWARD):
SurfaceShape.__init__(self, convexity)
self._parabola_parameter = parabola_parameter
self._at_infinity = at_infinity
self._pole_to_focus = pole_to_focus
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("parabola_parameter" , "Parabola parameter ", "m" ),
("at_infinity" , "(0=source, 1=image)", " " ),
("pole_to_focus" , "pole to focus", "m"),
("convexity" , "(0=upwards, 1=downwards)", " "),
] )
[docs] @classmethod
def create_paraboloid_from_parabola_parameter(cls, parabola_parameter=0.0, at_infinity=Side.SOURCE,
pole_to_focus=None, convexity=Convexity.UPWARD):
"""
Create a paraboloid.
Parameters
----------
parabola_parameter : float, optional
parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
pole_to_focus : float, optional
The p distance.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Paraboloid
"""
return Paraboloid(parabola_parameter, at_infinity=at_infinity, pole_to_focus=pole_to_focus, convexity=convexity)
[docs] @classmethod
def create_paraboloid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003,
at_infinity=Side.SOURCE, convexity=Convexity.UPWARD):
"""
Creates a paraboloid from the factory parameters.
Parameters
----------
p : float
The distance p (used if at_infinity=Side.IMAGE)
q : float
The distance q (used if at_infinity=Side.SOURCE)
grazing_angle : float
The distance p
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
Returns
-------
instance of Paraboloid
"""
paraboloid = Paraboloid(convexity=convexity)
paraboloid.initialize_from_p_q(p, q, grazing_angle=grazing_angle, at_infinity=at_infinity)
return paraboloid
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
"""
Sets the paraboloid parameters as calculated from the factory parameters.
Parameters
----------
p : float
The distance p (used if at_infinity=Side.IMAGE)
q : float
The distance q (used if at_infinity=Side.SOURCE)
grazing_angle : float
The distance p
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
Returns
-------
instance of Paraboloid
"""
self._parabola_parameter = Paraboloid.get_parabola_parameter_from_p_q(p=p, q=q, grazing_angle=grazing_angle, at_infinity=at_infinity)
self._at_infinity = at_infinity
if at_infinity == Side.SOURCE:
self._pole_to_focus = q
elif at_infinity == Side.IMAGE:
self._pole_to_focus = p
[docs] @classmethod
def get_parabola_parameter_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
"""
Calculates the parabola parameter from the factory parameters.
Parameters
----------
p : float
The distance p (used if at_infinity=Side.IMAGE)
q : float
The distance q (used if at_infinity=Side.SOURCE)
grazing_angle : float
The distance p
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
Returns
-------
float
The parabola parameter.
"""
if at_infinity == Side.IMAGE:
return 2*p*(numpy.sin(grazing_angle))**2
elif at_infinity == Side.SOURCE:
return 2*q*(numpy.sin(grazing_angle))**2
[docs] def get_parabola_parameter(self):
"""
Returns the parabola parameter.
Returns
-------
float
"""
return self._parabola_parameter
[docs] def get_at_infinity(self):
"""
Returns the "at_infinity" flag.
Returns
-------
int (as defined by Side)
SOURCE = 0, IMAGE = 1.
"""
return self._at_infinity
[docs] def get_pole_to_focus(self):
"""
Returns the distance from focus to pole.
Returns
-------
float
"""
return self._pole_to_focus
[docs] def get_grazing_angle(self):
"""
Returns the grazing angle.
Returns
-------
float
"""
return numpy.arcsin( numpy.sqrt( self.get_parabola_parameter() / (2 * self.get_pole_to_focus())))
[docs]class ParabolicCylinder(Paraboloid, Cylinder):
"""
Constructor.
Parameters
----------
parabola_parameter : float, optional
parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
pole_to_focus : float, optional
The p distance.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
"""
[docs] def __init__(self,
parabola_parameter=0.0,
at_infinity=Side.SOURCE,
pole_to_focus=None,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
Paraboloid.__init__(self, parabola_parameter=parabola_parameter, at_infinity=at_infinity,
pole_to_focus=pole_to_focus, convexity=convexity)
Cylinder.__init__(self, cylinder_direction)
[docs] @classmethod
def create_parabolic_cylinder_from_parabola_parameter(cls,
parabola_parameter=0.0,
at_infinity=Side.SOURCE,
pole_to_focus=None,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
"""
Returns a ParabolicCylinder instance.
Parameters
----------
parabola_parameter : float, optional
parabola_parameter = 2 * focal_distance = - 0.5 * ccc_9 / ccc_2.
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
pole_to_focus : float, optional
The p distance.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of ParabolicCylinder
"""
return ParabolicCylinder(parabola_parameter, at_infinity, pole_to_focus, convexity, cylinder_direction)
[docs] @classmethod
def create_parabolic_cylinder_from_p_q(cls,
p=2.0,
q=1.0,
grazing_angle=0.003,
at_infinity=Side.SOURCE,
convexity=Convexity.UPWARD,
cylinder_direction=Direction.TANGENTIAL):
"""
Returns a ParabolicCylinder instance from factory parameters.
Parameters
----------
p : float
The distance p (used if at_infinity=Side.IMAGE)
q : float
The distance q (used if at_infinity=Side.SOURCE)
grazing_angle : float
The distance p
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
convexity : int (as defined by Convexity), optional
NONE = -1, UPWARD = 0, DOWNWARD = 1.
cylinder_direction : int (as defined by Direction), optional
TANGENTIAL = 0, SAGITTAL = 1.
Returns
-------
instance of ParabolicCylinder
"""
parabolic_cylinder = ParabolicCylinder(convexity=convexity, cylinder_direction=cylinder_direction)
parabolic_cylinder.initialize_from_p_q(p, q, grazing_angle, at_infinity)
return parabolic_cylinder
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003, at_infinity=Side.SOURCE):
"""
Sets the parameters calculated from factory parameters.
Parameters
----------
p : float
The distance p (used if at_infinity=Side.IMAGE)
q : float
The distance q (used if at_infinity=Side.SOURCE)
grazing_angle : float
The distance p
at_infinity : int (as defined by Side), optional
SOURCE = 0, IMAGE = 1.
Returns
-------
instance of ParabolicCylinder
"""
if self._cylinder_direction == Direction.SAGITTAL:
raise NotImplementedError("Operation not possible for SAGITTAL direction")
return super().initialize_from_p_q(p, q, grazing_angle, at_infinity)
[docs]class Toroid(SurfaceShape):
"""
Creator.
Parameters
----------
min_radius : float, optional
The toroid minor radius
maj_radius : float, optional
The toroid major radius. Note that this is the "optical" major radius at the farest surface from the center
of the toroid. Indeed, it corresponds to the "toroid major radius" plus the min_radius.
"""
[docs] def __init__(self, min_radius=0.0, maj_radius=0.0):
SurfaceShape.__init__(self, convexity=Convexity.NONE)
self._min_radius = min_radius
self._maj_radius = maj_radius
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("min_radius" , "Minor radius r ", "m" ),
("maj_radius" , "Major (optical) radius R (R=Ro+r)", "m" ),
] )
[docs] @classmethod
def create_toroid_from_radii(cls, min_radius=0.0, maj_radius=0.0):
"""
returns a Toroid from main parameters (radii).
Parameters
----------
min_radius : float, optional
The toroid minor radius
maj_radius : float, optional
The toroid major radius. Note that this is the "optical" major radius at the farest surface from the center
of the toroid. Indeed, it corresponds to the "toroid major radius" plus the min_radius.
Returns
-------
instance of Toroid
"""
return Toroid(min_radius, maj_radius)
[docs] @classmethod
def create_toroid_from_p_q(cls, p=2.0, q=1.0, grazing_angle=0.003):
"""
returns a Toroid from factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
Returns
-------
instance of Toroid
"""
R = 2 / numpy.sin(grazing_angle) * p * q / (p + q)
r = 2 * numpy.sin(grazing_angle) * p * q / (p + q)
return Toroid(min_radius=r, maj_radius=R)
[docs] def get_radii(self):
"""
Returns the radii.
Returns
-------
tuple
(min_radius, maj_radius).
"""
return self._min_radius, self._maj_radius
[docs] def get_min_radius(self):
"""
Returns the minor radius.
Returns
-------
float
"""
return self._min_radius
[docs] def get_maj_radius(self):
"""
Returns the major (optical) radius.
Returns
-------
float
"""
return self._maj_radius
[docs] def initialize_from_p_q(self, p=2.0, q=1.0, grazing_angle=0.003):
"""
Sets the parameters calculated from the factory parameters.
Parameters
----------
p : float, optional
distance source-optical element.
q : float, optional
distance optical element to focus.
grazing_angle : float, optional
grazing angle in rad.
"""
self._maj_radius = Sphere.get_radius_from_p_q(p, q, grazing_angle)
self._min_radius = SphericalCylinder.get_radius_from_p_q_sagittal(p, q, grazing_angle)
# FROM SHADOW3:
#! C
#! C NOTE : The major radius is the in reality the radius of the torus
#! C max. circle. The true major radius is then
#! C
# R_MAJ = R_MAJ - R_MIN
self._maj_radius -= self._min_radius
# This is exactly the same as OasysSurfaceData
# class OasysSurfaceData(object):
[docs]class NumericalMesh(SurfaceShape):
"""
Implements an optical surface from a numerical mesh.
Constructor.
Parameters
----------
xx : numpy array, optional
The x vector.
yy : numpy array, optional
The y vector.
zz : numpy array, optional
The z (2D) array.
surface_data_file : str, optional
a file name from where the dara may come.
Notes
-----
This is exactly the same as OasysSurfaceData class OasysSurfaceData(object), with added methods.
"""
[docs] def __init__(self,
xx=None,
yy=None,
zz=None,
surface_data_file=None):
self._xx = xx
self._yy = yy
self._zz = zz
self._surface_data_file=surface_data_file
[docs] def has_surface_data(self):
"""
Returns True is data is loaded.
Returns
-------
boolean
"""
return not (self._xx is None or self._yy is None or self._zz is None)
[docs] def has_surface_data_file(self):
"""
Returns True is data file is set.
Returns
-------
boolean
"""
return not self._surface_data_file is None
##############################
# subclasses for BoundaryShape
##############################
[docs]class Rectangle(BoundaryShape):
"""
Constructor.
Parameters
----------
x_left : float, optional
The coordinate (signed) of the minimum (left) along the X axis.
x_right : float, optional
The coordinate (signed) of the maximum (right) along the X axis.
y_bottom : float, optional
The coordinate (signed) of the minimum (left) along the Y axis.
y_top : float, optional
The coordinate (signed) of the maximum (right) along the Y axis.
"""
[docs] def __init__(self, x_left=-0.010, x_right=0.010, y_bottom=-0.020, y_top=0.020):
super().__init__()
self._x_left = x_left
self._x_right = x_right
self._y_bottom = y_bottom
self._y_top = y_top
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("x_left" , "x (width) minimum (signed) ", "m" ),
("x_right" , "x (width) maximum (signed) ", "m" ),
("y_bottom" , "y (length) minimum (signed) ", "m" ),
("y_top" , "y (length) maximum (signed) ", "m" ),
] )
[docs] def get_boundaries(self):
"""
Return the rectangle coordinates.
Returns
-------
tuple
(x_left, x_right, y_bottom, y_top).
"""
return self._x_left, self._x_right, self._y_bottom, self._y_top
[docs] def set_boundaries(self,x_left=-0.010, x_right=0.010, y_bottom=-0.020, y_top=0.020):
"""
Sets the rectangle coordinates.
Parameters
----------
x_left : float, optional
The coordinate (signed) of the minimum (left) along the X axis.
x_right : float, optional
The coordinate (signed) of the maximum (right) along the X axis.
y_bottom : float, optional
The coordinate (signed) of the minimum (left) along the Y axis.
y_top : float, optional
The coordinate (signed) of the maximum (right) along the Y axis.
"""
self._x_left = x_left
self._x_right = x_right
self._y_bottom = y_bottom
self._y_top = y_top
[docs] def set_width_and_length(self,width=10e-3,length=30e-3):
"""
Sets the rectangle parameters from width and length (centered at the origin).
Parameters
----------
width : float, optional
The rectangle width.
length : float, optional
The rectangle length.
"""
self._x_left = -0.5 * width
self._x_right = 0.5 * width
self._y_bottom = -0.5 * length
self._y_top = 0.5 * length
[docs]class Ellipse(BoundaryShape):
"""
Constructor.
Parameters
----------
a_axis_min : float, optional
The coordinate (signed) of the minimum (left) along the major axis.
a_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the major axis.
b_axis_min : float, optional
The coordinate (signed) of the minimum (left) along the minor axis.
b_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the minor axis.
"""
[docs] def __init__(self, a_axis_min=-10e-6, a_axis_max=10e-6, b_axis_min=-5e-6, b_axis_max=5e-6):
super().__init__()
self._a_axis_min = a_axis_min
self._a_axis_max = a_axis_max
self._b_axis_min = b_axis_min
self._b_axis_max = b_axis_max
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("a_axis_min" , "x (width) axis starts (signed) ", "m" ),
("a_axis_max" , "x (width) axis ends (signed) ", "m" ),
("b_axis_min" , "y (length) axis starts (signed) ", "m" ),
("b_axis_max" , "y (length) axis ends (signed) ", "m" ),
] )
[docs] def get_boundaries(self):
"""
Returns the coordinates of the ellipse.
Returns
-------
tuple
(a_axis_min, a_axis_max, b_axis_min, b_axis_max).
"""
return self._a_axis_min, self._a_axis_max, self._b_axis_min, self._b_axis_max
[docs] def get_axis(self):
"""
Returns the length of the ellipse axes.
Returns
-------
tuple
(a_length, b_length).
"""
return numpy.abs(self._a_axis_max - self._a_axis_min), numpy.abs(self._b_axis_max - self._b_axis_min)
[docs]class TwoEllipses(BoundaryShape):
"""
Constructor.
Parameters
----------
a1_axis_min : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
a1_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
b1_axis_min : float, optional
TThe coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
b1_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
a2_axis_min : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
a2_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
b2_axis_min : float, optional
TThe coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
b2_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
"""
[docs] def __init__(self,
a1_axis_min=-10e-6, a1_axis_max=10e-6, b1_axis_min=-5e-6, b1_axis_max=5e-6,
a2_axis_min=-20e-6, a2_axis_max=20e-6, b2_axis_min=-8e-6, b2_axis_max=8e-6):
super().__init__()
self._a1_axis_min = a1_axis_min
self._a1_axis_max = a1_axis_max
self._b1_axis_min = b1_axis_min
self._b1_axis_max = b1_axis_max
self._a2_axis_min = a2_axis_min
self._a2_axis_max = a2_axis_max
self._b2_axis_min = b2_axis_min
self._b2_axis_max = b2_axis_max
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("a1_axis_min", "x (width) axis 1 starts (signed) ", "m" ),
("a1_axis_max", "x (width) axis 1 ends (signed) ", "m" ),
("b1_axis_min", "y (length) axis 1 starts (signed) ", "m" ),
("b1_axis_max", "y (length) axis 1 ends (signed) ", "m" ),
("a2_axis_min", "x (width) axis 2 starts (signed) ", "m"),
("a2_axis_max", "x (width) axis 2 ends (signed) ", "m"),
("b2_axis_min", "y (length) axis 2 starts (signed) ", "m"),
("b2_axis_max", "y (length) axis 2 ends (signed) ", "m"),
] )
[docs] def get_boundaries(self):
"""
Return the coordinates of the ellipses.
Returns
-------
tuple
(a1_axis_min, a1_axis_max, b1_axis_min, b1_axis_max, a2_axis_min, a2_axis_max, b2_axis_min, b2_axis_max).
"""
return \
self._a1_axis_min, self._a1_axis_max, self._b1_axis_min, self._b1_axis_max, \
self._a2_axis_min, self._a2_axis_max, self._b2_axis_min, self._b2_axis_max
[docs] def get_axis(self):
"""
Returns the lengths of the axes of the two ellipses.
Returns
-------
tuple
(a1_length, b1_length, a2_length, b2_length).
"""
return \
numpy.abs(self._a1_axis_max - self._a1_axis_min), numpy.abs(self._b1_axis_max - self._b2_axis_min), \
numpy.abs(self._a2_axis_max - self._a2_axis_min), numpy.abs(self._b2_axis_max - self._b2_axis_min)
[docs]class Circle(BoundaryShape):
"""
Constructor.
Parameters
----------
radius : float
The radius of the circle.
x_center : float
The x coordinate of the center of the circle.
y_center : float
The y coordinate of the center of the circle.
"""
[docs] def __init__(self,radius=50e-6,x_center=0.0,y_center=0.0):
super().__init__()
self._radius = radius
self._x_center = x_center
self._y_center = y_center
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("radius" , "radius ", "m" ),
("x_center" , "x center (signed) ", "m" ),
("y_center" , "y center (signed) ", "m" ),
] )
[docs] def get_boundaries(self):
"""
Returns the circle parameters.
Returns
-------
tuple
(radius, x_center, y_center).
"""
return self._radius, self._x_center, self._y_center
[docs] def set_boundaries(self, radius=1.0, x_center=0.0, y_center=0.0):
"""
Sets the circle parameters.
Parameters
----------
radius : float
The radius of the circle.
x_center : float
The x coordinate of the center of the circle.
y_center : float
The y coordinate of the center of the circle.
"""
self._radius = radius
self._x_center = x_center
self._y_center = y_center
[docs] def get_radius(self):
"""
Returns the radius of the circle.
Returns
-------
float
"""
return self._radius
[docs] def get_center(self):
"""
Returns the coordinates of the circle.
Returns
-------
list
[x_center, y_center]
"""
return [self._x_center,self._y_center]
[docs]class Polygon(BoundaryShape):
"""
Constructor.
Parameters
----------
x : list, optional
A list with the X coordinates of the patch vertices.
y : list, optional
A list with the Y coordinates of the patch vertices.
"""
[docs] def __init__(self,x=[],y=[]):
super().__init__()
self._x = numpy.array(x)
self._y = numpy.array(y)
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("x" , "x vertices ", "m" ),
("y" , "y vertices ", "m" ),
] )
[docs] def get_boundaries(self):
"""
Returns the coordinates of the patch vertices.
Returns
-------
tuple
(list_of_x_coordinates, list_of_y_coordinates).
"""
return self._x, self._y
[docs] def set_boundaries(self, x, y):
"""
Sets the coordinates of the patch vertices.
Parameters
----------
x : list
A list with the X coordinates of the patch vertices.
y : list
A list with the Y coordinates of the patch vertices.
"""
self._x = numpy.array(x)
self._y = numpy.array(y)
[docs] def get_number_of_vertices(self):
"""
Returns the number of vertices.
Returns
-------
int
"""
n = numpy.array(self._x).size
if (numpy.abs(self._x[0] - self._x[-1]) < 1e-10) and (numpy.abs(self._y[0] - self._y[-1]) < 1e-10):
# print(">>>>> same first and last point")
n -= 1
return n
[docs] def get_polygon(self):
"""
Returns the vertices arranges as a polugon.
Returns
-------
list
[[x0,y0], [x1,y1], ...]
"""
polygon = []
for i in range(self.get_number_of_vertices()):
polygon.append([self._x[i], self._y[i]])
return polygon
[docs] def check_inside_vector(self, x0, y0):
"""
Checks if a set of points are inside the patch (closed as polygon).
Parameters
----------
x0 : numpy array
The X coordinates of the points to check.
y0 : numpy array
The Y coordinates of the points to check.
Returns
-------
numpy array
0=No, 1=Yes (inside).
References
----------
https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
"""
# see https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
poly = self.get_polygon()
n = len(poly)
x = numpy.array(x0)
y = numpy.array(y0)
inside = numpy.zeros(x.size, numpy.bool_)
p2x = 0.0
p2y = 0.0
xints = 0.0
p1x, p1y = poly[0]
for i in range(n + 1):
p2x, p2y = poly[i % n]
idx = numpy.nonzero((y > min(p1y, p2y)) & (y <= max(p1y, p2y)) & (x <= max(p1x, p2x)))[0]
if len(idx > 0): # added intuitively by srio TODO: make some tests to compare with self.check_insize
if p1y != p2y:
xints = (y[idx] - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
if p1x == p2x:
inside[idx] = ~inside[idx]
else:
idxx = idx[x[idx] <= xints]
inside[idxx] = ~inside[idxx]
p1x, p1y = p2x, p2y
return inside
[docs] def check_inside(self, x, y):
"""
Checks if a set of points are inside the patch (closed as polygon).
Parameters
----------
x0 : list
The X coordinates of the points to check.
y0 : list
The Y coordinates of the points to check.
Returns
-------
numpy array
0=No, 1=Yes (inside).
References
----------
https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
"""
return [self.check_inside_one_point(xi, yi) for xi, yi in zip(x, y)]
[docs] def check_inside_one_point(self, x0, y0):
"""
Checks if a single point is inside the patch (closed as polygon).
Parameters
----------
x0 : float
The X coordinate pf the point to check.
y0 : float
The Y coordinate pf the point to check.
Returns
-------
boolean
References
----------
https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
"""
# see https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
poly = self.get_polygon()
x = x0
y = y0
n = len(poly)
inside = False
p2x = 0.0
p2y = 0.0
xints = 0.0
p1x, p1y = poly[0]
for i in range(n + 1):
p2x, p2y = poly[i % n]
if y > min(p1y, p2y):
if y <= max(p1y, p2y):
if x <= max(p1x, p2x):
if p1y != p2y:
xints = (y - p1y) * (p2x - p1x) / (p2y - p1y) + p1x
if p1x == p2x or x <= xints:
inside = not inside
p1x, p1y = p2x, p2y
return inside
[docs] def check_outside(self, x0, y0):
"""
Checks if a set of points are outside the patch (closed as polygon).
Parameters
----------
x0 : list
The X coordinates of the points to check.
y0 : list
The Y coordinates of the points to check.
Returns
-------
numpy array
0=No, 1=Yes (outside).
References
----------
https://stackoverflow.com/questions/36399381/whats-the-fastest-way-of-checking-if-a-point-is-inside-a-polygon-in-python
"""
inside = self.check_inside(x0, y0)
if isinstance(inside, list):
out = []
for item in inside:
out.append(not(item))
else:
out = not(inside)
return out
[docs]class MultiplePatch(BoundaryShape):
"""
Constructor.
Parameters
----------
patch_list : list
A list of patches (each one can be a Circle, Rectangle, Polygon, etc.)
"""
[docs] def __init__(self, patch_list=None):
super().__init__()
if patch_list is None:
self._patch_list = []
else:
self._patch_list = patch_list
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("patch_list", "Multiple Patch", ""),
])
# overwrites the SynedObject method for dealing with list
[docs] def to_dictionary(self):
"""
Gets the dictionary with the multiple patch parameters.
Returns
-------
dict
"""
dict_to_save = OrderedDict()
dict_to_save.update({"CLASS_NAME":self.__class__.__name__})
dict_to_save["patch_list"] = [el.to_dictionary() for el in self._patch_list]
return dict_to_save
[docs] def reset(self):
"""
Removes all existing patches.
"""
self._patch_list = []
[docs] def get_number_of_patches(self):
"""
Returns the number of stored patches.
Returns
-------
int
"""
return len(self._patch_list)
[docs] def get_boundaries(self):
"""
Returns a list with the concatenated boundaries of the sotred patches.
Returns
-------
list
"""
boundaries_list = []
for i in range(self.get_number_of_patches()):
boundaries_list.extend(list(self._patch_list[i].get_boundaries()))
return tuple(boundaries_list)
[docs] def append_patch(self,patch=BoundaryShape()):
"""
Append a patch.
Parameters
----------
patch : instance of Rectangle, Circle, etc.
"""
self._patch_list.append(patch)
[docs] def append_rectangle(self,x_left=-0.010,x_right=0.010,y_bottom=-0.020,y_top=0.020):
"""
Appends a rectangle.
Parameters
----------
x_left : float, optional
The coordinate (signed) of the minimum (left) along the X axis.
x_right : float, optional
The coordinate (signed) of the maximum (right) along the X axis.
y_bottom : float, optional
The coordinate (signed) of the minimum (left) along the Y axis.
y_top : float, optional
The coordinate (signed) of the maximum (right) along the Y axis.
"""
self.append_patch(Rectangle(x_left=x_left, x_right=x_right, y_bottom=y_bottom, y_top=y_top))
[docs] def append_circle(self,radius, x_center=0.0, y_center=0.0):
"""
Appends a circle.
Parameters
----------
radius : float
The radius of the circle.
x_center : float
The x coordinate of the center of the circle.
y_center : float
The y coordinate of the center of the circle.
"""
self.append_patch(Circle(radius, x_center=x_center, y_center=y_center))
[docs] def append_ellipse(self,a_axis_min, a_axis_max, b_axis_min, b_axis_max):
"""
Appends an ellipse.
Parameters
----------
a_axis_min : float, optional
The coordinate (signed) of the minimum (left) along the major axis.
a_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the major axis.
b_axis_min : float, optional
TThe coordinate (signed) of the minimum (left) along the minor axis.
b_axis_max : float, optional
The coordinate (signed) of the maximum (right) along the minor axis.
"""
self.append_patch(Ellipse(a_axis_min, a_axis_max, b_axis_min, b_axis_max))
[docs] def append_polygon(self,x, y):
"""
Appends a polygon.
Parameters
----------
x : list
The polygon X coordinates.
y : list
The polygon Y coordinates.
Returns
-------
"""
self.append_patch(Polygon(x, y))
[docs] def get_patches(self):
"""
Returns a list with the patches.
Returns
-------
list
"""
return self._patch_list
[docs] def get_patch(self, index):
"""
Returns the patch corresponding to a given index.
Parameters
----------
index : int
The index of the wanted patch.
Returns
-------
instance of BoundaryShape (Circle, Rectangle, etc.).
"""
return self.get_patches()[index]
[docs] def get_name_of_patch(self,index):
"""
Returns the name of the patch with a given index.
Parameters
----------
index : int
The index of the wanted patch.
Returns
-------
str
"""
return self._patch_list[index].__class__.__name__
[docs]class DoubleRectangle(MultiplePatch):
"""
Constructor.
Parameters
----------
x_left1 : float, optional
The coordinate (signed) of the minimum (left) along the X axis of rectangle 1.
x_right1 : float, optional
The coordinate (signed) of the maximum (right) along the X axis of rectangle 1.
y_bottom1 : float, optional
The coordinate (signed) of the minimum (left) along the Y axis of rectangle 1.
y_top1 : float, optional
The coordinate (signed) of the maximum (right) along the Y axis of rectangle 1.
x_left2 : float, optional
The coordinate (signed) of the minimum (left) along the X axis of rectangle 2.
x_right2 : float, optional
The coordinate (signed) of the maximum (right) along the X axis of rectangle 2.
y_bottom2 : float, optional
The coordinate (signed) of the minimum (left) along the Y axis of rectangle 2.
y_top2 : float, optional
The coordinate (signed) of the maximum (right) along the Y axis of rectangle 2.
"""
[docs] def __init__(self, x_left1=-0.010, x_right1=0.0, y_bottom1=-0.020, y_top1=0.0,
x_left2=-0.010, x_right2=0.010, y_bottom2=-0.001, y_top2=0.020):
super().__init__()
self.reset()
self.append_patch(Rectangle(x_left=x_left1, x_right=x_right1, y_bottom=y_bottom1, y_top=y_top1))
self.append_patch(Rectangle(x_left=x_left2, x_right=x_right2, y_bottom=y_bottom2, y_top=y_top2))
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("x_left1" , "x (width) minimum (signed) ", "m" ),
("x_right1" , "x (width) maximum (signed) ", "m" ),
("y_bottom1" , "y (length) minimum (signed) ", "m" ),
("y_top1" , "y (length) maximum (signed) ", "m" ),
("x_left2" , "x (width) minimum (signed) ", "m" ),
("x_right2" , "x (width) maximum (signed) ", "m" ),
("y_bottom2" , "y (length) minimum (signed) ", "m" ),
("y_top2" , "y (length) maximum (signed) ", "m" ),
] )
[docs] def set_boundaries(self,x_left1=-0.010, x_right1=0.0, y_bottom1=-0.020, y_top1=0.0,
x_left2=-0.010, x_right2=0.010, y_bottom2=-0.001, y_top2=0.020):
self._patch_list[0].set_boundaries(x_left1, x_right1, y_bottom1, y_top1)
self._patch_list[1].set_boundaries(x_left2, x_right2, y_bottom2, y_top2)
[docs]class DoubleEllipse(MultiplePatch):
"""
Constructor.
Parameters
----------
a_axis_min1 : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
a_axis_max1 : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
b_axis_min1 : float, optional
The coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
b_axis_max1 : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
a_axis_min2 : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
a_axis_max2 : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
b_axis_min2 : float, optional
The coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
b_axis_max2 : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
"""
[docs] def __init__(self, a_axis_min1=-0.010, a_axis_max1=0.0, b_axis_min1=-0.020, b_axis_max1=0.0,
a_axis_min2=-0.010, a_axis_max2=0.010, b_axis_min2=-0.001, b_axis_max2=0.020):
super().__init__()
self.reset()
self.append_patch(Ellipse(a_axis_min1, a_axis_max1, b_axis_min1, b_axis_max1))
self.append_patch(Ellipse(a_axis_min2, a_axis_max2, b_axis_min2, b_axis_max2))
self._set_support_text([
("a_axis_min1" , "x (width) axis starts (signed) ", "m" ),
("a_axis_max1" , "x (width) axis ends (signed) ", "m" ),
("b_axis_min1" , "y (length) axis starts (signed) ", "m" ),
("b_axis_max1" , "y (length) axis ends (signed) ", "m" ),
("a_axis_min2" , "x (width) axis starts (signed) ", "m" ),
("a_axis_max2" , "x (width) axis ends (signed) ", "m" ),
("b_axis_min2" , "y (length) axis starts (signed) ", "m" ),
("b_axis_max2" , "y (length) axis ends (signed) ", "m" ),
] )
[docs] def set_boundaries(self,a_axis_min1=-0.010, a_axis_max1=0.0, b_axis_min1=-0.020, b_axis_max1=0.0,
a_axis_min2=-0.010, a_axis_max2=0.010, b_axis_min2=-0.001, b_axis_max2=0.020):
"""
Sets the coordinates of the ellipses.
Parameters
----------
a_axis_min1 : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 1.
a_axis_max1 : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 1.
b_axis_min1 : float, optional
The coordinate (signed) of the minimum (left) along the minor axis of ellipse 1.
b_axis_max1 : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 1.
a_axis_min2 : float, optional
The coordinate (signed) of the minimum (left) along the major axis of ellipse 2.
a_axis_max2 : float, optional
The coordinate (signed) of the maximum (right) along the major axis of ellipse 2.
b_axis_min2 : float, optional
The coordinate (signed) of the minimum (left) along the minor axis of ellipse 2.
b_axis_max2 : float, optional
The coordinate (signed) of the maximum (right) along the minor axis of ellipse 2.
"""
self._patch_list[0].set_boundaries(a_axis_min1,a_axis_max1,b_axis_min1,b_axis_max1)
self._patch_list[1].set_boundaries(a_axis_min2,a_axis_max2,b_axis_min2,b_axis_max2)
[docs]class DoubleCircle(MultiplePatch):
"""
Constructor.
Parameters
----------
radius1 : float
The radius of the circle 1.
x_center1 : float
The x coordinate of the center of the circle 1.
y_center1 : float
The y coordinate of the center of the circle 1.
radius2 : float
The radius of the circle 2.
x_center2 : float
The x coordinate of the center of the circle 2.
y_center2 : float
The y coordinate of the center of the circle 2.
"""
[docs] def __init__(self, radius1=50e-6,x_center1=0.0,y_center1=0.0,
radius2=50e-6,x_center2=100e-6,y_center2=100e-6):
super().__init__()
self.reset()
self.append_patch(Circle(radius1,x_center1,y_center1))
self.append_patch(Circle(radius2,x_center2,y_center2))
# support text containg name of variable, help text and unit. Will be stored in self._support_dictionary
self._set_support_text([
("radius1" , "radius ", "m" ),
("x_center1" , "x center (signed) ", "m" ),
("y_center1" , "y center (signed) ", "m" ),
("radius2" , "radius ", "m" ),
("x_center2" , "x center (signed) ", "m" ),
("y_center2" , "y center (signed) ", "m" ),
] )
[docs] def set_boundaries(self,radius1=50e-6,x_center1=0.0,y_center1=0.0,
radius2=50e-6,x_center2=100e-6,y_center2=100e-6):
"""
Sets the coordinates of the circles.
Parameters
----------
radius1 : float
The radius of the circle 1.
x_center1 : float
The x coordinate of the center of the circle 1.
y_center1 : float
The y coordinate of the center of the circle 1.
radius2 : float
The radius of the circle 2.
x_center2 : float
The x coordinate of the center of the circle 2.
y_center2 : float
The y coordinate of the center of the circle 2.
"""
self._patch_list[0].set_boundaries(radius1,x_center1,y_center1)
self._patch_list[1].set_boundaries(radius2,x_center2,y_center2)
if __name__=="__main__":
if 0:
p = 20
q = 10
theta_graz = 0.003
#
# sphere
#
# sph = Sphere()
sph = Sphere.create_sphere_from_p_q(10, 10, 0.021)
print(sph.info())
#
# Ellipsoid
#
ell = Ellipsoid()
ell.initialize_from_p_q(p, q, theta_graz)
#
# toroid
#
par = Toroid.create_toroid_from_p_q(p=p, q=q, grazing_angle=theta_graz)
print("inputs p, q, theta_graz: ", p, q, theta_graz)
radii = par.get_radii()
print("toroid radii: ", radii )
R = 2 / numpy.sin(theta_graz) * p * q / (p + q)
r = 2 * numpy.sin(theta_graz) * p * q / (p + q)
assert ((radii[0] - R) < 1e-10 )
assert ((radii[0] - r) < 1e-10 )
print(par.info())
#
# paraboloid
#
at_infinity = Side.SOURCE
par = Paraboloid.create_paraboloid_from_p_q(p=p, q=q, grazing_angle=theta_graz, at_infinity=at_infinity, convexity=Convexity.UPWARD)
print("inputs p, q, theta_graz: ", p, q, theta_graz, at_infinity)
print ("parabola p or q: ",par.get_pole_to_focus())
print("parabola par: ", par.get_parabola_parameter())
print("parabola grazing_angle: ", par.get_grazing_angle())
if par.get_at_infinity() == Side.SOURCE:
assert (numpy.abs(q - par.get_pole_to_focus()) < 1e-10 )
else:
assert (numpy.abs(p - par.get_pole_to_focus()) < 1e-10)
assert (numpy.abs(theta_graz - par.get_grazing_angle()) < 1e-10)
print(par.info())
#
# parabolic cylinder: TODO: check that the info is not good for double inheritage
#
a = Cylinder()
print(a.info())
print(a.to_dictionary())
parC = ParabolicCylinder(par, a)
print(parC.info())
#
# some other checks...
#
if 0:
# conic coeffs.
ccc = Conic()
print(ccc.get_conic_coefficients())
print(ccc.info())
ccc.to_json("tmp.json")
from syned.util.json_tools import load_from_json_file
tmp = load_from_json_file("tmp.json")
print("returned class: ",type(tmp))
print(ccc.to_dictionary())
print(tmp.to_dictionary())
# from deepdiff import DeepDiff # use this because == gives an error
# assert (len(DeepDiff(ccc.to_dictionary(), tmp.to_dictionary())) == 0)
# circle
circle = Circle(3.0)
print(circle.get_radius(),circle.get_center())
print(circle.get_boundaries())
# patches
patches = MultiplePatch()
patches.append_rectangle(-0.02,-0.01,-0.001,0.001)
patches.append_rectangle(0.01,0.02,-0.001,0.001)
patches.append_polygon([-0.02,-0.02,0.02,0.02], [-0.02,0.02,0.02,-0.02])
print(patches.get_number_of_patches(),patches.get_boundaries())
for patch in patches.get_patches():
print(patch.info())
print("Patch 0 is: ",patches.get_name_of_patch(0))
print("Patch 1 is: ",patches.get_name_of_patch(1))
print(patches.get_boundaries())
# double rectangle
double_rectangle = DoubleRectangle()
double_rectangle.set_boundaries(-0.02,-0.01,-0.001,0.001,0.01,0.02,-0.001,0.001)
print("Rectangle 0 is: ",double_rectangle.get_name_of_patch(0))
print("Rectangle 1 is: ",double_rectangle.get_name_of_patch(1))
print(double_rectangle.get_boundaries())
# polygon
angle = numpy.linspace(0, 2 * numpy.pi, 5)
x = numpy.sin(angle) + 0.5
y = numpy.cos(angle) + 0.5
poly = Polygon(x=x, y=y)
print(poly.info())
print("vertices: ", poly.get_number_of_vertices())
if False:
from srxraylib.plot.gol import plot,set_qt
set_qt()
plot(x,y)
print(poly.get_polygon())
print("inside? : ", poly.check_inside([0.5,0],[0.5,5]))
print("outside? : ", poly.check_outside([0.5, 0], [0.5, 5]))
# multiple patches
patches = MultiplePatch()
patches.append_polygon(numpy.array([-1,-1,1,1]),numpy.array([-1,1,1,-1]))
x = [-0.00166557, 0.12180897, -0.11252591, -0.12274196, 0.00586896, -0.12999401, -0.12552975, -0.0377907, -0.01094828, -0.13689862]
y = [ 0.16279557, -0.00085991, 0.01349174, -0.01371226, 0.01480265, -0.04810334, 0.07198068, -0.03725407, 0.13301309, -0.00296213]
x = numpy.array(x)
y = numpy.array(y)
patch = patches.get_patch(0)
# print(patch.check_inside(x,y))
for i in range(x.size):
tmp = patch.check_inside_one_point(x[i], y[i])
print(x[i], y[i], tmp )
print("inside? : ", patch.check_inside(x, y), type(patch.check_inside(x, y)))
print("inside? : ", patch.check_inside_vector(x, y), type(patch.check_inside_vector(x, y)))
if 1: # checking Hyperbolas
theta_graz = 0.003
#
# Ellipsoid
#
print("===============================================")
p = 20
q = 10
ell = Ellipsoid()
ell.initialize_from_p_q(p, q, theta_graz)
print(ell.info())
print("a, b, c: ", ell.get_a(), ell.get_b(), ell.get_c())
print("p, q, theta: ", ell.get_p_focus(), ell.get_q_focus(), ell.get_grazing_angle())
print("center: ", ell.get_mirror_center())
p2, q2 = ell.get_p_q()
print(p, p2, q, q2)
print("===============================================")
p = 10
q = 20
ell = Ellipsoid()
ell.initialize_from_p_q(p, q, theta_graz)
print(ell.info())
print("a, b, c: ", ell.get_a(), ell.get_b(), ell.get_c())
print("p, q, theta: ", ell.get_p_focus(), ell.get_q_focus(), ell.get_grazing_angle())
print("center: ", ell.get_mirror_center())
p2, q2 = ell.get_p_q()
print(p, p2, q, q2)
print("===============================================")
#
# Hyperboloid
p = 20
q = 10
hyp = Hyperboloid()
hyp.initialize_from_p_q(p, q, theta_graz)
print(hyp.info())
print("a, b, c: ", hyp.get_a(), hyp.get_b(), hyp.get_c())
print("p, q, theta: ", hyp.get_p_focus(), hyp.get_q_focus(), hyp.get_grazing_angle())
print("center: ", hyp.get_mirror_center())
p1, q1 = hyp.get_p_q()
print(p, p1, q, q1)
print("===============================================")
# now p < q (swap p, q)
p = 10
q = 20
hyp = Hyperboloid()
hyp.initialize_from_p_q(p, q, theta_graz)
print(hyp.info())
print("a, b, c: ", hyp.get_a(), hyp.get_b(), hyp.get_c())
print("p, q, theta: ", hyp.get_p_focus(), hyp.get_q_focus(), hyp.get_grazing_angle())
print("center: ", hyp.get_mirror_center())
p1, q1 = hyp.get_p_q()
print(p, p1, q, q1)
print("===============================================")