Skip to content

Commit

Permalink
Added optika.rulings.SawtoothRulings class.
Browse files Browse the repository at this point in the history
  • Loading branch information
byrdie committed Mar 24, 2024
1 parent f9f7bb9 commit 8c0baa7
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 0 deletions.
136 changes: 136 additions & 0 deletions optika/rulings/_rulings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"Rulings",
"MeasuredRulings",
"SquareRulings",
"SawtoothRulings",
]


Expand Down Expand Up @@ -287,3 +288,138 @@ def efficiency(
)

return result


@dataclasses.dataclass(eq=False, repr=False)
class SawtoothRulings(
AbstractRulings,
):
r"""
A ruling profile described by a sawtooth wave.
Examples
--------
Compute the 1st-order groove efficiency of sawtooth rulings with a groove
density of 2500 grooves/mm and a groove depth of 15 nm.
.. jupyter-execute::
import numpy as np
import matplotlib.pyplot as plt
import astropy.units as u
import named_arrays as na
import optika
# Define the groove density
density = 2500 / u.mm
# Define the groove depth
depth = 15 * u.nm
# Define ruling model
rulings = optika.rulings.SawtoothRulings(
spacing=1 / density,
depth=depth,
diffraction_order=-1,
)
# Define the wavelengths at which to sample the groove efficiency
wavelength = na.geomspace(100, 1000, axis="wavelength", num=1001) * u.AA
# Define the incidence angles at which to sample the groove efficiency
angle = na.linspace(0, 30, num=3, axis="angle") * u.deg
# Define the light rays incident on the grooves
rays = optika.rays.RayVectorArray(
wavelength=wavelength,
direction=na.Cartesian3dVectorArray(
x=np.sin(angle),
y=0,
z=np.cos(angle),
),
)
# Compute the efficiency of the grooves for the given wavelength
efficiency = rulings.efficiency(
rays=rays,
normal=na.Cartesian3dVectorArray(0, 0, -1),
)
# Plot the groove efficiency as a function of wavelength
fig, ax = plt.subplots()
angle_str = angle.value.astype(str).astype(object)
na.plt.plot(
wavelength,
efficiency,
ax=ax,
axis="wavelength",
label=r"$\theta$ = " + angle_str + f"{angle.unit:latex_inline}",
);
ax.set_xlabel(f"wavelength ({wavelength.unit:latex_inline})");
ax.set_ylabel(f"efficiency");
ax.legend();
"""

spacing: u.Quantity | na.AbstractScalar | AbstractRulingSpacing = MISSING
"""Spacing between adjacent rulings at the given position."""

depth: u.Quantity | na.AbstractScalar = MISSING
"""Depth of the ruling pattern."""

diffraction_order: int | na.AbstractScalar = MISSING
"""The diffraction order to simulate."""

def efficiency(
self,
rays: optika.rays.RayVectorArray,
normal: na.AbstractCartesian3dVectorArray,
) -> float | na.AbstractScalar:
r"""
The fraction of light diffracted into a given order.
Calculated using the expression given in Table 1 of :cite:t:`Magnusson1978`.
Parameters
----------
rays
The light rays incident on the rulings
normal
The vector normal to the surface on which the rulings are placed.
Notes
-----
The theoretical efficiency of thin (wavelength much smaller than
the groove spacing), sawtooth rulings is given by Table 1 of
:cite:t:`Magnusson1978`,
.. math::
\eta_i = [\pi (\gamma + i)]^{-2} \sin^2(\pi \gamma)
where :math:`\eta_i` is the groove efficiency for diffraction order
:math:`i`, :math:`\gamma = \pi d n_1 / \lambda \cos \theta` is the
normalized amplitude of the fundamental grating, :math:`d` is the
thickness of the grating, :math:`n_1` is the amplitude of the fundamental
grating, :math:`\lambda` is the free-space wavelength of the incident
light, and :math:`\theta` is the angle of incidence inside the medium.
"""

normal_rulings = self.spacing_(rays.position).normalized

parallel_rulings = normal.cross(normal_rulings).normalized

direction = rays.direction
direction = direction - direction @ parallel_rulings

wavelength = rays.wavelength
cos_theta = -direction @ normal
d = self.depth
i = self.diffraction_order

gamma = np.pi * d / (wavelength * cos_theta)

result = np.square(np.sin(np.pi * gamma * u.rad) / (np.pi * (gamma + i)))

return result
16 changes: 16 additions & 0 deletions optika/rulings/_rulings_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,19 @@ class TestSquareRulings(
AbstractTestAbstractRulings,
):
pass


@pytest.mark.parametrize(
argnames="a",
argvalues=[
optika.rulings.SawtoothRulings(
spacing=1 * u.um,
depth=10 * u.nm,
diffraction_order=na.ScalarArray(np.array([0, 1, 2]), axes="m"),
),
],
)
class TestSawtoothRulings(
AbstractTestAbstractRulings,
):
pass

0 comments on commit 8c0baa7

Please sign in to comment.