From f1cb71d24df9078eb8fb452d04fd1ae273e7000f Mon Sep 17 00:00:00 2001 From: Roy Smart Date: Sat, 7 Oct 2023 21:15:11 -0600 Subject: [PATCH] Added `optika.apertures.IsoscelesTrapezoidalAperture`. --- optika/_tests/test_apertures.py | 45 +++++++++++++++ optika/apertures.py | 98 +++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) diff --git a/optika/_tests/test_apertures.py b/optika/_tests/test_apertures.py index ca4e537..e190715 100644 --- a/optika/_tests/test_apertures.py +++ b/optika/_tests/test_apertures.py @@ -226,3 +226,48 @@ class TestOctagonalAperture( AbstractTestAbstractOctagonalAperture, ): pass + + +class AbstractTestAbstractIsoscelesTrapezoidalAperture( + AbstractTestAbstractPolygonalAperture, +): + def test_x_left(self, a: optika.apertures.AbstractIsoscelesTrapezoidalAperture): + assert isinstance(na.as_named_array(a.x_left), na.AbstractScalar) + + def test_x_right(self, a: optika.apertures.AbstractIsoscelesTrapezoidalAperture): + assert isinstance(na.as_named_array(a.x_right), na.AbstractScalar) + + def test_angle(self, a: optika.apertures.AbstractIsoscelesTrapezoidalAperture): + assert isinstance(na.as_named_array(a.angle), na.AbstractScalar) + + +x_left_parameterization = [ + 25 * u.mm, + na.linspace(25, 30, axis="x_left", num=2) * u.mm, +] + + +@pytest.mark.parametrize( + argnames="a", + argvalues=[ + optika.apertures.IsoscelesTrapezoidalAperture( + x_left=x_left, + x_right=50 * u.mm, + angle=45 * u.deg, + samples_wire=21, + active=active, + inverted=inverted, + transformation=transformation, + kwargs_plot=kwargs_plot, + ) + for x_left in x_left_parameterization + for active in active_parameterization + for inverted in inverted_parameterization + for transformation in transform_parameterization + for kwargs_plot in test_plotting.kwargs_plot_parameterization + ], +) +class TestIsoscelesTrapezoidalAperture( + AbstractTestAbstractIsoscelesTrapezoidalAperture, +): + pass diff --git a/optika/apertures.py b/optika/apertures.py index abef4d0..ddae5ad 100644 --- a/optika/apertures.py +++ b/optika/apertures.py @@ -16,6 +16,8 @@ "AbstractRegularPolygonalAperture", "AbstractOctagonalAperture", "OctagonalAperture", + "AbstractIsoscelesTrapezoidalAperture", + "IsoscelesTrapezoidalAperture", ] @@ -564,3 +566,99 @@ class OctagonalAperture( inverted: bool | na.AbstractScalar = False transformation: None | na.transformations.AbstractTransformation = None kwargs_plot: None | dict = None + + +@dataclasses.dataclass(eq=False, repr=False) +class AbstractIsoscelesTrapezoidalAperture( + AbstractPolygonalAperture, +): + @property + @abc.abstractmethod + def x_left(self) -> na.ScalarLike: + """:math:`x` coordinate of the left base of the trapezoid""" + + @property + @abc.abstractmethod + def x_right(self) -> na.ScalarLike: + """:math:`x` coordinate of the right base of the trapezoid""" + + @property + @abc.abstractmethod + def angle(self) -> na.ScalarLike: + """angle between the two legs of the trapezoid""" + + @property + def vertices(self) -> na.Cartesian3dVectorArray: + x_left = self.x_left + x_right = self.x_right + angle = self.angle + + m = np.tan(angle / 2) + left = na.Cartesian3dVectorArray( + x=x_left, + y=m * x_left, + z=0 * x_left, + ) + right = na.Cartesian3dVectorArray( + x=x_right, + y=m * x_right, + z=0 * x_right, + ) + + upper = na.stack([left, right], axis="vertex") + + lower = upper[dict(vertex=slice(None, None, -1))] + lower = lower * na.Cartesian3dVectorArray(1, -1, 1) + + result = na.concatenate([upper, lower], axis="vertex") + + if self.transformation is not None: + result = self.transformation(result) + + return result + + +@dataclasses.dataclass(eq=False, repr=False) +class IsoscelesTrapezoidalAperture( + AbstractIsoscelesTrapezoidalAperture, +): + """ + This aperture is useful if you want to break a circular aperture up + into different sectors. + + .. jupyter-execute:: + + import matplotlib.pyplot as plt + import astropy.units as u + import astropy.visualization + import named_arrays as na + import optika + + num_sectors = 8 + + roll = na.linspace(0, 360, axis="roll", num=num_sectors, endpoint=False) * u.deg + + aperture = optika.apertures.IsoscelesTrapezoidalAperture( + x_left=10 * u.mm, + x_right=40 * u.mm, + angle=(360 * u.deg) / num_sectors, + transformation=na.transformations.TransformationList([ + na.transformations.Cartesian3dTranslation(x=5 * u.mm), + na.transformations.Cartesian3dRotationZ(roll), + ]) + ) + + with astropy.visualization.quantity_support(): + plt.figure() + plt.gca().set_aspect("equal") + aperture.plot(components=("x", "y"), color="black") + """ + + x_left: na.ScalarLike = 0 * u.mm + x_right: na.ScalarLike = 0 * u.mm + angle: na.ScalarLike = 0 * u.deg + samples_wire: int = 101 + active: bool | na.AbstractScalar = True + inverted: bool | na.AbstractScalar = False + transformation: None | na.transformations.AbstractTransformation = None + kwargs_plot: None | dict = None