Skip to content

Commit fb506d1

Browse files
authored
Added esis.optics.Grating class. (#13)
1 parent 6b670f2 commit fb506d1

8 files changed

Lines changed: 510 additions & 0 deletions

File tree

esis/optics/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
"""
44

55
from . import abc
6+
from . import mixins
67
from ._front_apertures import FrontAperture
78
from ._central_obscurations import CentralObscuration
89
from ._primary_mirrors import PrimaryMirror
910
from ._field_stops import FieldStop
11+
from ._gratings import Grating
1012

1113
__all__ = [
1214
"abc",
15+
"mixins",
1316
"FrontAperture",
1417
"CentralObscuration",
1518
"PrimaryMirror",
1619
"FieldStop",
20+
"Grating",
1721
]

esis/optics/_gratings/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from ._gratings import (
2+
AbstractGrating,
3+
Grating,
4+
)
5+
6+
__all__ = [
7+
"AbstractGrating",
8+
"Grating",
9+
]

esis/optics/_gratings/_gratings.py

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
import abc
2+
import dataclasses
3+
import numpy as np
4+
import astropy.units as u
5+
import named_arrays as na
6+
import optika.mixins
7+
from .. import mixins
8+
9+
__all__ = [
10+
"AbstractGrating",
11+
"Grating",
12+
]
13+
14+
15+
@dataclasses.dataclass(eq=False, repr=False)
16+
class AbstractGrating(
17+
optika.mixins.Printable,
18+
optika.mixins.Rollable,
19+
optika.mixins.Yawable,
20+
optika.mixins.Pitchable,
21+
optika.mixins.Translatable,
22+
mixins.CylindricallyTransformable,
23+
):
24+
"""
25+
An interface describing the diffraction gratings of the instrument.
26+
"""
27+
28+
@property
29+
@abc.abstractmethod
30+
def serial_number(self) -> str:
31+
"""
32+
The serial number of this diffraction grating.
33+
"""
34+
35+
@property
36+
@abc.abstractmethod
37+
def manufacturing_number(self) -> str:
38+
"""
39+
An additional number describing this diffraction grating.
40+
"""
41+
42+
@property
43+
@abc.abstractmethod
44+
def angle_input(self) -> u.Quantity | na.AbstractScalar:
45+
"""
46+
The nominal angle of the incident light from the field stop.
47+
"""
48+
49+
@property
50+
@abc.abstractmethod
51+
def angle_output(self) -> u.Quantity | na.AbstractScalar:
52+
"""
53+
The nominal angle of reflected light to the detectors.
54+
"""
55+
56+
@property
57+
@abc.abstractmethod
58+
def sag(self) -> None | optika.sags.AbstractSag:
59+
"""
60+
The sag function of this grating.
61+
"""
62+
63+
@property
64+
@abc.abstractmethod
65+
def material(self) -> None | optika.materials.AbstractMaterial:
66+
"""
67+
The optical material composing this grating.
68+
"""
69+
70+
@property
71+
@abc.abstractmethod
72+
def rulings(self) -> None | optika.rulings.AbstractRulings:
73+
"""
74+
The ruling pattern of this grating.
75+
"""
76+
77+
@property
78+
@abc.abstractmethod
79+
def num_folds(self) -> int:
80+
"""
81+
The order of the rotational symmetry of the optical system.
82+
This determines the aperture wedge angle of this grating.
83+
"""
84+
85+
@property
86+
def angle_aperture(self) -> u.Quantity | na.AbstractScalar:
87+
r"""
88+
The angle of the grating's aperture.
89+
90+
This is equal to :math:`2 \pi / n` radians, where :math:`n` is the
91+
order of the rotational symmetry of the optical system.
92+
"""
93+
return (360 * u.deg) / self.num_folds
94+
95+
@property
96+
@abc.abstractmethod
97+
def halfwidth_inner(self) -> u.Quantity | na.AbstractScalar:
98+
"""
99+
The distance from the apex to the inner edge of the clear aperture.
100+
"""
101+
102+
@property
103+
@abc.abstractmethod
104+
def halfwidth_outer(self) -> u.Quantity | na.AbstractScalar:
105+
"""
106+
The distance from the apex to the outer edge of the clear aperture.
107+
"""
108+
109+
@property
110+
@abc.abstractmethod
111+
def width_border(self) -> u.Quantity | na.AbstractScalar:
112+
"""
113+
The nominal width of the border around the clear aperture.
114+
"""
115+
116+
@property
117+
@abc.abstractmethod
118+
def width_border_inner(self) -> u.Quantity | na.AbstractScalar:
119+
"""
120+
The width of the border between the inner edge of the clear aperture
121+
and the substrate inner edge of the substrate.
122+
"""
123+
124+
@property
125+
@abc.abstractmethod
126+
def clearance(self) -> u.Quantity | na.AbstractScalar:
127+
"""
128+
The minimum distance between adjacent physical gratings.
129+
"""
130+
131+
@property
132+
def transformation(self) -> na.transformations.AbstractTransformation:
133+
rotation = na.transformations.Cartesian3dRotationX(180 * u.deg)
134+
return super().transformation @ rotation
135+
136+
@property
137+
def surface(self) -> optika.surfaces.Surface:
138+
"""
139+
Represent this object as an :mod:`optika` surface.
140+
"""
141+
angle_aperture = self.angle_aperture
142+
halfwidth_inner = self.halfwidth_inner
143+
halfwidth_outer = self.halfwidth_outer
144+
width_border = self.width_border
145+
width_border_inner = self.width_border_inner
146+
clearance = self.clearance / np.sin(angle_aperture / 2)
147+
distance_radial = self.distance_radial
148+
side_border_x = width_border / np.sin(angle_aperture / 2) + clearance
149+
offset_clear = distance_radial - side_border_x
150+
offset_mechanical = distance_radial - clearance
151+
return optika.surfaces.Surface(
152+
name="grating",
153+
sag=self.sag,
154+
material=self.material,
155+
aperture=optika.apertures.IsoscelesTrapezoidalAperture(
156+
x_left=offset_clear - halfwidth_inner,
157+
x_right=offset_clear + halfwidth_outer,
158+
angle=angle_aperture,
159+
transformation=na.transformations.Cartesian3dTranslation(
160+
x=-offset_clear,
161+
),
162+
),
163+
aperture_mechanical=optika.apertures.IsoscelesTrapezoidalAperture(
164+
x_left=offset_mechanical - (halfwidth_inner + width_border_inner),
165+
x_right=offset_mechanical + halfwidth_outer + width_border,
166+
angle=angle_aperture,
167+
transformation=na.transformations.Cartesian3dTranslation(
168+
x=-offset_mechanical,
169+
),
170+
),
171+
rulings=self.rulings,
172+
is_pupil_stop=True,
173+
transformation=self.transformation,
174+
)
175+
176+
177+
@dataclasses.dataclass(eq=False, repr=False)
178+
class Grating(
179+
AbstractGrating,
180+
):
181+
"""
182+
A model of the diffraction gratings of this instrument.
183+
"""
184+
185+
serial_number: str = ""
186+
"""
187+
The serial number of this diffraction grating.
188+
"""
189+
190+
manufacturing_number: str = ""
191+
"""
192+
An additional number describing this diffraction grating.
193+
"""
194+
195+
angle_input: u.Quantity = 0 * u.deg
196+
"""
197+
The nominal angle of the incident light from the field stop.
198+
"""
199+
200+
angle_output: u.Quantity = 0 * u.deg
201+
"""
202+
The nominal angle of reflected light to the detectors.
203+
"""
204+
205+
sag: None | optika.sags.AbstractSag = None
206+
"""
207+
The sag function of this grating.
208+
"""
209+
210+
material: None | optika.materials.AbstractMaterial = None
211+
"""
212+
The optical material composing this grating.
213+
"""
214+
rulings: None | optika.rulings.AbstractRulings = None
215+
"""
216+
The ruling pattern of this grating.
217+
"""
218+
219+
num_folds: int = 0
220+
"""
221+
The order of the rotational symmetry of the optical system.
222+
This determines the aperture wedge angle of this grating.
223+
"""
224+
225+
halfwidth_inner: u.Quantity | na.AbstractScalar = 0 * u.mm
226+
"""
227+
The distance from the apex to the inner edge of the clear aperture.
228+
"""
229+
230+
halfwidth_outer: u.Quantity | na.AbstractScalar = 0 * u.mm
231+
"""
232+
The distance from the apex to the outer edge of the clear aperture.
233+
"""
234+
235+
width_border: u.Quantity | na.AbstractScalar = 0 * u.mm
236+
"""
237+
The nominal width of the border around the clear aperture.
238+
"""
239+
240+
width_border_inner: u.Quantity | na.AbstractScalar = 0 * u.mm
241+
"""
242+
The width of the border between the inner edge of the clear aperture
243+
and the substrate inner edge of the substrate.
244+
"""
245+
246+
clearance: u.Quantity | na.AbstractScalar = 0 * u.mm
247+
"""
248+
The minimum distance between adjacent physical gratings.
249+
"""
250+
251+
distance_radial: u.Quantity | na.AbstractScalar = 0 * u.mm
252+
"""
253+
The distance of this object from the axis of symmetry.
254+
"""
255+
256+
azimuth: u.Quantity | na.AbstractScalar = 0 * u.deg
257+
"""
258+
The angle of rotation about the axis of symmetry.
259+
"""
260+
261+
translation: u.Quantity | na.AbstractCartesian3dVectorArray = 0 * u.mm
262+
"""
263+
A transformation which can arbitrarily translate this object.
264+
"""
265+
266+
pitch: u.Quantity | na.AbstractScalar = 0 * u.deg
267+
"""
268+
The pitch angle of this object.
269+
"""
270+
271+
yaw: u.Quantity | na.AbstractScalar = 0 * u.deg
272+
"""
273+
The yaw angle of this object.
274+
"""
275+
276+
roll: u.Quantity | na.AbstractScalar = 0 * u.deg
277+
"""
278+
The roll angle of this object
279+
"""

0 commit comments

Comments
 (0)