ModuleOpticalElement

Provides the class for a general optical element, which serves to connect the “lab-frame”, in which the light rays are traced, to the proper “optic” coordinate frame in which an optic is defined. So this class is what serves to "align" the optics in lab-space, and it provides methods to modify its alignment.

The optic can be a Mirror-object or a Mask-object.

Illustration the OpticalElement-class.

Created in Apr 2020

@author: Anthony Guillaume + Stefan Haessler

  1"""
  2Provides the class for a general optical element, which serves to connect the “lab-frame”,
  3in which the light rays are traced, to the proper “optic” coordinate frame in
  4which an optic is defined. So this class is what serves to "align" the optics in lab-space,
  5and it provides methods to modify its alignment.
  6
  7The optic can be a [Mirror-object](ModuleMirror.html) or a [Mask-object](ModuleMask.html).
  8
  9![Illustration the OpticalElement-class.](OE.svg)
 10
 11
 12
 13Created in Apr 2020
 14
 15@author: Anthony Guillaume + Stefan Haessler
 16"""
 17# %%
 18import ARTcore.ModuleGeometry as mgeo
 19from ARTcore.ModuleGeometry import Origin
 20
 21import numpy as np
 22from abc import ABC, abstractmethod
 23import logging
 24
 25logger = logging.getLogger(__name__)
 26
 27
 28# %%
 29class OpticalElement(ABC):
 30    """
 31    An optical element, can be either a Mirror or a Mask. In the future, one could add Gratings, DispersiveMedium etc...
 32
 33    Attributes: TODO update
 34    ----------
 35        position : np.ndarray
 36            Coordinate vector of the optical element’s center point in the lab frame.
 37            What this center is, depends on the 'type', but it is generally the center of symmetry
 38            of the Support of the optic. It is marked by the point 'P' in the drawings in the
 39            documentation of the Mirror-classes for example.
 40        
 41        orientation: np.quaternion
 42            Orientation of the optic in the lab reference frame. From this, we calculate
 43            the normal, the majoraxis and any other interesting vectors that are of 
 44            interest for a specific OpticalElement subclass. 
 45
 46        normal : np.ndarray
 47            Lab-frame vector pointing against the direction that would be considered
 48            as normal incidence on the optical element.
 49
 50        majoraxis : np.ndarray
 51            Lab-frame vector of another distinguished axis of non-rotationally symmetric optics,
 52            like the major axes of toroidal/elliptical mirrors or the off-axis direction of
 53            off-axis parabolas. This fixes the optical element’s rotation about 'normal'.
 54            It is required to be perpendicular to 'normal', and is usually the x-axis in the
 55            optic's proper coordinate frame.
 56
 57            What this 'majoraxis' is, e.g. for the different kinds of mirrors, is illustrated
 58            in the documentation of the Mirror-classes.
 59
 60            For pure p or s polarization, the light incidence plane should be the
 61            plane spanned by 'normal' and 'majoraxis', so that the incidence angle is varied
 62            by rotating the optical element around the cross product 'normal' x 'majoraxis'.
 63
 64
 65    Methods
 66    ----------
 67        rotate_pitch_by(angle)
 68
 69        rotate_roll_by(angle)
 70
 71        rotate_yaw_by(angle)
 72
 73        rotate_random_by(angle)
 74
 75        shift_along_normal(distance)
 76
 77        shift_along_major(distance)
 78
 79        shift_along_cross(distance)
 80
 81        shift_along_random(distance)
 82
 83    """
 84    # %% Starting 3d orientation definition
 85    _description = "Generic Optical Element"
 86
 87    def __hash__(self):
 88        return super().__hash__()+hash(self._r)+hash(self._q)
 89
 90    @property
 91    def description(self):
 92        """
 93        Return a description of the optical element.
 94        """
 95        return self._description
 96
 97    @property
 98    def r(self):
 99        """
100        Return the offset of the optical element from its reference frame to the lab reference frame.
101        Not that this is **NOT** the position of the optical element's center point. Rather it is the
102        offset of the reference frame of the optical element from the lab reference frame.
103        """
104        return self._r
105
106    @r.setter
107    def r(self, NewPosition):
108        """
109        Set the offset of the optical element from its reference frame to the lab reference frame.
110        """
111        if isinstance(NewPosition, mgeo.Point) and len(NewPosition) == 3:
112            self._r = NewPosition
113        else:
114            raise TypeError("Position must be a 3D mgeo.Point.")
115    @property
116    def q(self):
117        """
118        Return the orientation of the optical element.
119        The orientation is stored as a unit quaternion representing the rotation from the optic's coordinate frame to the lab frame.
120        """
121        return self._q
122    
123    @q.setter
124    def q(self, NewOrientation):
125        """
126        Set the orientation of the optical element.
127        This function normalizes the input quaternion before storing it.
128        If the input is not a quaternion, raise a TypeError.
129        """
130        if isinstance(NewOrientation, np.quaternion):
131            self._q = NewOrientation.normalized()
132        else:
133            raise TypeError("Orientation must be a quaternion.")
134
135    @property
136    def position(self):
137        """
138        Return the position of the basepoint of the optical element. Often it is the same as the optical centre.
139        This position is the one around which all rotations are performed.
140        """
141        return self.r0 + self.r
142    
143    @property
144    def orientation(self):
145        """
146        Return the orientation of the optical element.
147        The utility of this method is unclear.
148        """
149        return self.q
150    
151    @property
152    def basis(self):
153        return self.r0, self.r, self.q
154    # %% Start of other methods
155    def __init__(self):
156        self._r = mgeo.Vector([0.0, 0.0, 0.0])
157        self._q = np.quaternion(1, 0, 0, 0)
158        self.r0 = mgeo.Point([0.0, 0.0, 0.0])
159
160    @abstractmethod
161    def propagate_raylist(self, RayList, alignment=False):
162        """
163        This method is used to propagate a list of rays through the optical element. Implement this method in the subclasses.
164        In the case of a mirror, the method should reflect the rays off the mirror surface.
165        In the case of a mask, the method should block the rays that hit the mask.
166        In the case of a grating, the method should diffract the rays according to the grating equation.
167        """
168        pass
169
170    def add_global_points(self, *args):
171        """
172        Automatically add global properties for point attributes.
173        As arguments it takes the names of the global point attributes and seeks for the corresponding local point attributes.
174        Then it creates a property for the global point attribute.
175
176        Example:
177        Suppose the optical element has an attribute 'center_ref' that is the center of the optical element in the optic's coordinate frame.
178        Then, calling object.add_global_points('center') will create a property 'center' that returns the global position of the center of the optical element.
179        It takes into account the position and orientation of the optical element.
180        """
181        for arg in args:
182            local_attr = f"{arg}_ref"
183            if hasattr(self, local_attr):
184                # Dynamically define a property for the global point
185                setattr(self.__class__, arg, property(self._create_global_point_property(local_attr)))
186
187    def add_global_vectors(self, *args):
188        """
189        Automatically add global properties for vector attributes.
190        As arguments it takes the names of the global vector attributes and seeks for the corresponding local vector attributes.
191        Then it creates a property for the global vector attribute.
192
193        Example:
194        Suppose the optical element has an attribute 'normal_ref' that is the normal of the optical element in the optic's coordinate frame.
195        Then, calling object.add_global_vectors('normal') will create a property 'normal' that returns the global normal of the optical element.
196        """
197        for arg in args:
198            local_attr = f"{arg}_ref"
199            if hasattr(self, local_attr):
200                # Dynamically define a property for the global vector
201                setattr(self.__class__, arg, property(self._create_global_vector_property(local_attr)))
202    
203    def _create_global_point_property(self, local_attr):
204        """
205        Return a function that computes the global point.
206        This is the actual function that is used to create the global point property. It performs the transformation of the local point to the global reference frame.
207        """
208        def global_point(self):
209            # Translate the local point to the global reference frame
210            local_point = getattr(self, local_attr)
211            return local_point.from_basis(*self.basis)
212        return global_point
213
214    def _create_global_vector_property(self, local_attr):
215        """
216        Return a function that computes the global vector.
217        This is the actual function that is used to create the global vector property. It performs the transformation of the local vector to the global reference frame.
218        """
219        def global_vector(self):
220            # Rotate the local vector to the global reference frame
221            local_vector = getattr(self, local_attr)
222            return local_vector.from_basis(*self.basis)
223        return global_vector
224    # %% Starting 3d misalignment definitions
225    # This section needs to be reworked to be more general and to allow for more flexibility in the alignment of the optical elements. TODO
226
227    def rotate_pitch_by(self, angle):
228        """
229        Pitch rotation, i.e. rotates the optical element about the axis ('normal' x 'majoraxis'), by the
230        given angle.
231        If the plane spanned by 'normal' and 'majoraxis' is the incidence plane (normally the case
232        in a "clean alignment" situation for pure p or s polarization), then this is simply a modificaiton
233        of the incidence angle by "angle". But in general, if the optical element has some odd orientation,
234        there is not a direct correspondence.
235
236        Parameters
237        ----------
238            angle : float
239                Rotation angle in *degrees*.
240        """
241        rotation_axis = np.cross(self.support_normal, self.majoraxis)
242        self.q = mgeo.QRotationAroundAxis(rotation_axis, np.deg2rad(angle))*self.q
243
244    def rotate_roll_by(self, angle):
245        """
246        Roll rotation, i.e. rotates the optical element about its 'majoraxis' by the given angle.
247
248        Parameters
249        ----------
250            angle : float
251                Rotation angle in *degrees*.
252        """
253
254        self.q = mgeo.QRotationAroundAxis(self.majoraxis, np.deg2rad(angle))*self.q
255
256    def rotate_yaw_by(self, angle):
257        """
258        Yaw rotation, i.e. rotates the optical element about its 'normal' by the given angle.
259
260        Parameters
261        ----------
262            angle : float
263                Rotation angle in *degrees*.
264        """
265        self.q = mgeo.QRotationAroundAxis(self.support_normal, np.deg2rad(angle))*self.q
266
267    def rotate_random_by(self, angle):
268        """
269        Rotates the optical element about a randomly oriented axis by the given angle.
270
271        Parameters
272        ----------
273            angle : float
274                Rotation angle in *degrees*.
275        """
276
277        self.q = mgeo.QRotationAroundAxis(np.random.random(3), np.deg2rad(angle))*self.q
278
279    def shift_along_normal(self, distance):
280        """
281        Shifts the optical element along its 'normal' by the given distance.
282
283        Parameters
284        ----------
285            distance : float
286                Shift distance in mm.
287        """
288        self.r = self.r +  distance * self.support_normal
289
290    def shift_along_major(self, distance):
291        """
292        Shifts the optical element along its 'majoraxis' by the given distance.
293
294        Parameters
295        ----------
296            distance : float
297                Shift distance in mm.
298        """
299        self.r = self.r +  distance * self.majoraxis
300
301    def shift_along_cross(self, distance):
302        """
303        Shifts the optical element along the axis 'normal'x'majoraxis'
304        (typically normal to the light incidence plane) by the given distance.
305
306        Parameters
307        ----------
308            distance : float
309                Shift distance in mm.
310        """
311        self.r = self.r +  distance * mgeo.Normalize(np.cross(self.support_normal, self.majoraxis))
312
313    def shift_along_random(self, distance):
314        """
315        Shifts the optical element along a random direction by the given distance.
316
317        Parameters
318        ----------
319            distance : float
320                Shift distance in mm.
321        """
322        self.r = self.r + distance * mgeo.Normalize(np.random.random(3))
logger = <Logger ARTcore.ModuleOpticalElement (WARNING)>
class OpticalElement(abc.ABC):
 30class OpticalElement(ABC):
 31    """
 32    An optical element, can be either a Mirror or a Mask. In the future, one could add Gratings, DispersiveMedium etc...
 33
 34    Attributes: TODO update
 35    ----------
 36        position : np.ndarray
 37            Coordinate vector of the optical element’s center point in the lab frame.
 38            What this center is, depends on the 'type', but it is generally the center of symmetry
 39            of the Support of the optic. It is marked by the point 'P' in the drawings in the
 40            documentation of the Mirror-classes for example.
 41        
 42        orientation: np.quaternion
 43            Orientation of the optic in the lab reference frame. From this, we calculate
 44            the normal, the majoraxis and any other interesting vectors that are of 
 45            interest for a specific OpticalElement subclass. 
 46
 47        normal : np.ndarray
 48            Lab-frame vector pointing against the direction that would be considered
 49            as normal incidence on the optical element.
 50
 51        majoraxis : np.ndarray
 52            Lab-frame vector of another distinguished axis of non-rotationally symmetric optics,
 53            like the major axes of toroidal/elliptical mirrors or the off-axis direction of
 54            off-axis parabolas. This fixes the optical element’s rotation about 'normal'.
 55            It is required to be perpendicular to 'normal', and is usually the x-axis in the
 56            optic's proper coordinate frame.
 57
 58            What this 'majoraxis' is, e.g. for the different kinds of mirrors, is illustrated
 59            in the documentation of the Mirror-classes.
 60
 61            For pure p or s polarization, the light incidence plane should be the
 62            plane spanned by 'normal' and 'majoraxis', so that the incidence angle is varied
 63            by rotating the optical element around the cross product 'normal' x 'majoraxis'.
 64
 65
 66    Methods
 67    ----------
 68        rotate_pitch_by(angle)
 69
 70        rotate_roll_by(angle)
 71
 72        rotate_yaw_by(angle)
 73
 74        rotate_random_by(angle)
 75
 76        shift_along_normal(distance)
 77
 78        shift_along_major(distance)
 79
 80        shift_along_cross(distance)
 81
 82        shift_along_random(distance)
 83
 84    """
 85    # %% Starting 3d orientation definition
 86    _description = "Generic Optical Element"
 87
 88    def __hash__(self):
 89        return super().__hash__()+hash(self._r)+hash(self._q)
 90
 91    @property
 92    def description(self):
 93        """
 94        Return a description of the optical element.
 95        """
 96        return self._description
 97
 98    @property
 99    def r(self):
100        """
101        Return the offset of the optical element from its reference frame to the lab reference frame.
102        Not that this is **NOT** the position of the optical element's center point. Rather it is the
103        offset of the reference frame of the optical element from the lab reference frame.
104        """
105        return self._r
106
107    @r.setter
108    def r(self, NewPosition):
109        """
110        Set the offset of the optical element from its reference frame to the lab reference frame.
111        """
112        if isinstance(NewPosition, mgeo.Point) and len(NewPosition) == 3:
113            self._r = NewPosition
114        else:
115            raise TypeError("Position must be a 3D mgeo.Point.")
116    @property
117    def q(self):
118        """
119        Return the orientation of the optical element.
120        The orientation is stored as a unit quaternion representing the rotation from the optic's coordinate frame to the lab frame.
121        """
122        return self._q
123    
124    @q.setter
125    def q(self, NewOrientation):
126        """
127        Set the orientation of the optical element.
128        This function normalizes the input quaternion before storing it.
129        If the input is not a quaternion, raise a TypeError.
130        """
131        if isinstance(NewOrientation, np.quaternion):
132            self._q = NewOrientation.normalized()
133        else:
134            raise TypeError("Orientation must be a quaternion.")
135
136    @property
137    def position(self):
138        """
139        Return the position of the basepoint of the optical element. Often it is the same as the optical centre.
140        This position is the one around which all rotations are performed.
141        """
142        return self.r0 + self.r
143    
144    @property
145    def orientation(self):
146        """
147        Return the orientation of the optical element.
148        The utility of this method is unclear.
149        """
150        return self.q
151    
152    @property
153    def basis(self):
154        return self.r0, self.r, self.q
155    # %% Start of other methods
156    def __init__(self):
157        self._r = mgeo.Vector([0.0, 0.0, 0.0])
158        self._q = np.quaternion(1, 0, 0, 0)
159        self.r0 = mgeo.Point([0.0, 0.0, 0.0])
160
161    @abstractmethod
162    def propagate_raylist(self, RayList, alignment=False):
163        """
164        This method is used to propagate a list of rays through the optical element. Implement this method in the subclasses.
165        In the case of a mirror, the method should reflect the rays off the mirror surface.
166        In the case of a mask, the method should block the rays that hit the mask.
167        In the case of a grating, the method should diffract the rays according to the grating equation.
168        """
169        pass
170
171    def add_global_points(self, *args):
172        """
173        Automatically add global properties for point attributes.
174        As arguments it takes the names of the global point attributes and seeks for the corresponding local point attributes.
175        Then it creates a property for the global point attribute.
176
177        Example:
178        Suppose the optical element has an attribute 'center_ref' that is the center of the optical element in the optic's coordinate frame.
179        Then, calling object.add_global_points('center') will create a property 'center' that returns the global position of the center of the optical element.
180        It takes into account the position and orientation of the optical element.
181        """
182        for arg in args:
183            local_attr = f"{arg}_ref"
184            if hasattr(self, local_attr):
185                # Dynamically define a property for the global point
186                setattr(self.__class__, arg, property(self._create_global_point_property(local_attr)))
187
188    def add_global_vectors(self, *args):
189        """
190        Automatically add global properties for vector attributes.
191        As arguments it takes the names of the global vector attributes and seeks for the corresponding local vector attributes.
192        Then it creates a property for the global vector attribute.
193
194        Example:
195        Suppose the optical element has an attribute 'normal_ref' that is the normal of the optical element in the optic's coordinate frame.
196        Then, calling object.add_global_vectors('normal') will create a property 'normal' that returns the global normal of the optical element.
197        """
198        for arg in args:
199            local_attr = f"{arg}_ref"
200            if hasattr(self, local_attr):
201                # Dynamically define a property for the global vector
202                setattr(self.__class__, arg, property(self._create_global_vector_property(local_attr)))
203    
204    def _create_global_point_property(self, local_attr):
205        """
206        Return a function that computes the global point.
207        This is the actual function that is used to create the global point property. It performs the transformation of the local point to the global reference frame.
208        """
209        def global_point(self):
210            # Translate the local point to the global reference frame
211            local_point = getattr(self, local_attr)
212            return local_point.from_basis(*self.basis)
213        return global_point
214
215    def _create_global_vector_property(self, local_attr):
216        """
217        Return a function that computes the global vector.
218        This is the actual function that is used to create the global vector property. It performs the transformation of the local vector to the global reference frame.
219        """
220        def global_vector(self):
221            # Rotate the local vector to the global reference frame
222            local_vector = getattr(self, local_attr)
223            return local_vector.from_basis(*self.basis)
224        return global_vector
225    # %% Starting 3d misalignment definitions
226    # This section needs to be reworked to be more general and to allow for more flexibility in the alignment of the optical elements. TODO
227
228    def rotate_pitch_by(self, angle):
229        """
230        Pitch rotation, i.e. rotates the optical element about the axis ('normal' x 'majoraxis'), by the
231        given angle.
232        If the plane spanned by 'normal' and 'majoraxis' is the incidence plane (normally the case
233        in a "clean alignment" situation for pure p or s polarization), then this is simply a modificaiton
234        of the incidence angle by "angle". But in general, if the optical element has some odd orientation,
235        there is not a direct correspondence.
236
237        Parameters
238        ----------
239            angle : float
240                Rotation angle in *degrees*.
241        """
242        rotation_axis = np.cross(self.support_normal, self.majoraxis)
243        self.q = mgeo.QRotationAroundAxis(rotation_axis, np.deg2rad(angle))*self.q
244
245    def rotate_roll_by(self, angle):
246        """
247        Roll rotation, i.e. rotates the optical element about its 'majoraxis' by the given angle.
248
249        Parameters
250        ----------
251            angle : float
252                Rotation angle in *degrees*.
253        """
254
255        self.q = mgeo.QRotationAroundAxis(self.majoraxis, np.deg2rad(angle))*self.q
256
257    def rotate_yaw_by(self, angle):
258        """
259        Yaw rotation, i.e. rotates the optical element about its 'normal' by the given angle.
260
261        Parameters
262        ----------
263            angle : float
264                Rotation angle in *degrees*.
265        """
266        self.q = mgeo.QRotationAroundAxis(self.support_normal, np.deg2rad(angle))*self.q
267
268    def rotate_random_by(self, angle):
269        """
270        Rotates the optical element about a randomly oriented axis by the given angle.
271
272        Parameters
273        ----------
274            angle : float
275                Rotation angle in *degrees*.
276        """
277
278        self.q = mgeo.QRotationAroundAxis(np.random.random(3), np.deg2rad(angle))*self.q
279
280    def shift_along_normal(self, distance):
281        """
282        Shifts the optical element along its 'normal' by the given distance.
283
284        Parameters
285        ----------
286            distance : float
287                Shift distance in mm.
288        """
289        self.r = self.r +  distance * self.support_normal
290
291    def shift_along_major(self, distance):
292        """
293        Shifts the optical element along its 'majoraxis' by the given distance.
294
295        Parameters
296        ----------
297            distance : float
298                Shift distance in mm.
299        """
300        self.r = self.r +  distance * self.majoraxis
301
302    def shift_along_cross(self, distance):
303        """
304        Shifts the optical element along the axis 'normal'x'majoraxis'
305        (typically normal to the light incidence plane) by the given distance.
306
307        Parameters
308        ----------
309            distance : float
310                Shift distance in mm.
311        """
312        self.r = self.r +  distance * mgeo.Normalize(np.cross(self.support_normal, self.majoraxis))
313
314    def shift_along_random(self, distance):
315        """
316        Shifts the optical element along a random direction by the given distance.
317
318        Parameters
319        ----------
320            distance : float
321                Shift distance in mm.
322        """
323        self.r = self.r + distance * mgeo.Normalize(np.random.random(3))

An optical element, can be either a Mirror or a Mask. In the future, one could add Gratings, DispersiveMedium etc...

Attributes: TODO update

position : np.ndarray
    Coordinate vector of the optical element’s center point in the lab frame.
    What this center is, depends on the 'type', but it is generally the center of symmetry
    of the Support of the optic. It is marked by the point 'P' in the drawings in the
    documentation of the Mirror-classes for example.

orientation: np.quaternion
    Orientation of the optic in the lab reference frame. From this, we calculate
    the normal, the majoraxis and any other interesting vectors that are of 
    interest for a specific OpticalElement subclass. 

normal : np.ndarray
    Lab-frame vector pointing against the direction that would be considered
    as normal incidence on the optical element.

majoraxis : np.ndarray
    Lab-frame vector of another distinguished axis of non-rotationally symmetric optics,
    like the major axes of toroidal/elliptical mirrors or the off-axis direction of
    off-axis parabolas. This fixes the optical element’s rotation about 'normal'.
    It is required to be perpendicular to 'normal', and is usually the x-axis in the
    optic's proper coordinate frame.

    What this 'majoraxis' is, e.g. for the different kinds of mirrors, is illustrated
    in the documentation of the Mirror-classes.

    For pure p or s polarization, the light incidence plane should be the
    plane spanned by 'normal' and 'majoraxis', so that the incidence angle is varied
    by rotating the optical element around the cross product 'normal' x 'majoraxis'.

Methods

rotate_pitch_by(angle)

rotate_roll_by(angle)

rotate_yaw_by(angle)

rotate_random_by(angle)

shift_along_normal(distance)

shift_along_major(distance)

shift_along_cross(distance)

shift_along_random(distance)
description
91    @property
92    def description(self):
93        """
94        Return a description of the optical element.
95        """
96        return self._description

Return a description of the optical element.

r
 98    @property
 99    def r(self):
100        """
101        Return the offset of the optical element from its reference frame to the lab reference frame.
102        Not that this is **NOT** the position of the optical element's center point. Rather it is the
103        offset of the reference frame of the optical element from the lab reference frame.
104        """
105        return self._r

Return the offset of the optical element from its reference frame to the lab reference frame. Not that this is NOT the position of the optical element's center point. Rather it is the offset of the reference frame of the optical element from the lab reference frame.

q
116    @property
117    def q(self):
118        """
119        Return the orientation of the optical element.
120        The orientation is stored as a unit quaternion representing the rotation from the optic's coordinate frame to the lab frame.
121        """
122        return self._q

Return the orientation of the optical element. The orientation is stored as a unit quaternion representing the rotation from the optic's coordinate frame to the lab frame.

position
136    @property
137    def position(self):
138        """
139        Return the position of the basepoint of the optical element. Often it is the same as the optical centre.
140        This position is the one around which all rotations are performed.
141        """
142        return self.r0 + self.r

Return the position of the basepoint of the optical element. Often it is the same as the optical centre. This position is the one around which all rotations are performed.

orientation
144    @property
145    def orientation(self):
146        """
147        Return the orientation of the optical element.
148        The utility of this method is unclear.
149        """
150        return self.q

Return the orientation of the optical element. The utility of this method is unclear.

basis
152    @property
153    def basis(self):
154        return self.r0, self.r, self.q
r0
@abstractmethod
def propagate_raylist(self, RayList, alignment=False):
161    @abstractmethod
162    def propagate_raylist(self, RayList, alignment=False):
163        """
164        This method is used to propagate a list of rays through the optical element. Implement this method in the subclasses.
165        In the case of a mirror, the method should reflect the rays off the mirror surface.
166        In the case of a mask, the method should block the rays that hit the mask.
167        In the case of a grating, the method should diffract the rays according to the grating equation.
168        """
169        pass

This method is used to propagate a list of rays through the optical element. Implement this method in the subclasses. In the case of a mirror, the method should reflect the rays off the mirror surface. In the case of a mask, the method should block the rays that hit the mask. In the case of a grating, the method should diffract the rays according to the grating equation.

def add_global_points(self, *args):
171    def add_global_points(self, *args):
172        """
173        Automatically add global properties for point attributes.
174        As arguments it takes the names of the global point attributes and seeks for the corresponding local point attributes.
175        Then it creates a property for the global point attribute.
176
177        Example:
178        Suppose the optical element has an attribute 'center_ref' that is the center of the optical element in the optic's coordinate frame.
179        Then, calling object.add_global_points('center') will create a property 'center' that returns the global position of the center of the optical element.
180        It takes into account the position and orientation of the optical element.
181        """
182        for arg in args:
183            local_attr = f"{arg}_ref"
184            if hasattr(self, local_attr):
185                # Dynamically define a property for the global point
186                setattr(self.__class__, arg, property(self._create_global_point_property(local_attr)))

Automatically add global properties for point attributes. As arguments it takes the names of the global point attributes and seeks for the corresponding local point attributes. Then it creates a property for the global point attribute.

Example: Suppose the optical element has an attribute 'center_ref' that is the center of the optical element in the optic's coordinate frame. Then, calling object.add_global_points('center') will create a property 'center' that returns the global position of the center of the optical element. It takes into account the position and orientation of the optical element.

def add_global_vectors(self, *args):
188    def add_global_vectors(self, *args):
189        """
190        Automatically add global properties for vector attributes.
191        As arguments it takes the names of the global vector attributes and seeks for the corresponding local vector attributes.
192        Then it creates a property for the global vector attribute.
193
194        Example:
195        Suppose the optical element has an attribute 'normal_ref' that is the normal of the optical element in the optic's coordinate frame.
196        Then, calling object.add_global_vectors('normal') will create a property 'normal' that returns the global normal of the optical element.
197        """
198        for arg in args:
199            local_attr = f"{arg}_ref"
200            if hasattr(self, local_attr):
201                # Dynamically define a property for the global vector
202                setattr(self.__class__, arg, property(self._create_global_vector_property(local_attr)))

Automatically add global properties for vector attributes. As arguments it takes the names of the global vector attributes and seeks for the corresponding local vector attributes. Then it creates a property for the global vector attribute.

Example: Suppose the optical element has an attribute 'normal_ref' that is the normal of the optical element in the optic's coordinate frame. Then, calling object.add_global_vectors('normal') will create a property 'normal' that returns the global normal of the optical element.

def rotate_pitch_by(self, angle):
228    def rotate_pitch_by(self, angle):
229        """
230        Pitch rotation, i.e. rotates the optical element about the axis ('normal' x 'majoraxis'), by the
231        given angle.
232        If the plane spanned by 'normal' and 'majoraxis' is the incidence plane (normally the case
233        in a "clean alignment" situation for pure p or s polarization), then this is simply a modificaiton
234        of the incidence angle by "angle". But in general, if the optical element has some odd orientation,
235        there is not a direct correspondence.
236
237        Parameters
238        ----------
239            angle : float
240                Rotation angle in *degrees*.
241        """
242        rotation_axis = np.cross(self.support_normal, self.majoraxis)
243        self.q = mgeo.QRotationAroundAxis(rotation_axis, np.deg2rad(angle))*self.q

Pitch rotation, i.e. rotates the optical element about the axis ('normal' x 'majoraxis'), by the given angle. If the plane spanned by 'normal' and 'majoraxis' is the incidence plane (normally the case in a "clean alignment" situation for pure p or s polarization), then this is simply a modificaiton of the incidence angle by "angle". But in general, if the optical element has some odd orientation, there is not a direct correspondence.

Parameters

angle : float
    Rotation angle in *degrees*.
def rotate_roll_by(self, angle):
245    def rotate_roll_by(self, angle):
246        """
247        Roll rotation, i.e. rotates the optical element about its 'majoraxis' by the given angle.
248
249        Parameters
250        ----------
251            angle : float
252                Rotation angle in *degrees*.
253        """
254
255        self.q = mgeo.QRotationAroundAxis(self.majoraxis, np.deg2rad(angle))*self.q

Roll rotation, i.e. rotates the optical element about its 'majoraxis' by the given angle.

Parameters

angle : float
    Rotation angle in *degrees*.
def rotate_yaw_by(self, angle):
257    def rotate_yaw_by(self, angle):
258        """
259        Yaw rotation, i.e. rotates the optical element about its 'normal' by the given angle.
260
261        Parameters
262        ----------
263            angle : float
264                Rotation angle in *degrees*.
265        """
266        self.q = mgeo.QRotationAroundAxis(self.support_normal, np.deg2rad(angle))*self.q

Yaw rotation, i.e. rotates the optical element about its 'normal' by the given angle.

Parameters

angle : float
    Rotation angle in *degrees*.
def rotate_random_by(self, angle):
268    def rotate_random_by(self, angle):
269        """
270        Rotates the optical element about a randomly oriented axis by the given angle.
271
272        Parameters
273        ----------
274            angle : float
275                Rotation angle in *degrees*.
276        """
277
278        self.q = mgeo.QRotationAroundAxis(np.random.random(3), np.deg2rad(angle))*self.q

Rotates the optical element about a randomly oriented axis by the given angle.

Parameters

angle : float
    Rotation angle in *degrees*.
def shift_along_normal(self, distance):
280    def shift_along_normal(self, distance):
281        """
282        Shifts the optical element along its 'normal' by the given distance.
283
284        Parameters
285        ----------
286            distance : float
287                Shift distance in mm.
288        """
289        self.r = self.r +  distance * self.support_normal

Shifts the optical element along its 'normal' by the given distance.

Parameters

distance : float
    Shift distance in mm.
def shift_along_major(self, distance):
291    def shift_along_major(self, distance):
292        """
293        Shifts the optical element along its 'majoraxis' by the given distance.
294
295        Parameters
296        ----------
297            distance : float
298                Shift distance in mm.
299        """
300        self.r = self.r +  distance * self.majoraxis

Shifts the optical element along its 'majoraxis' by the given distance.

Parameters

distance : float
    Shift distance in mm.
def shift_along_cross(self, distance):
302    def shift_along_cross(self, distance):
303        """
304        Shifts the optical element along the axis 'normal'x'majoraxis'
305        (typically normal to the light incidence plane) by the given distance.
306
307        Parameters
308        ----------
309            distance : float
310                Shift distance in mm.
311        """
312        self.r = self.r +  distance * mgeo.Normalize(np.cross(self.support_normal, self.majoraxis))

Shifts the optical element along the axis 'normal'x'majoraxis' (typically normal to the light incidence plane) by the given distance.

Parameters

distance : float
    Shift distance in mm.
def shift_along_random(self, distance):
314    def shift_along_random(self, distance):
315        """
316        Shifts the optical element along a random direction by the given distance.
317
318        Parameters
319        ----------
320            distance : float
321                Shift distance in mm.
322        """
323        self.r = self.r + distance * mgeo.Normalize(np.random.random(3))

Shifts the optical element along a random direction by the given distance.

Parameters

distance : float
    Shift distance in mm.