Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions optika/apertures/_apertures.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,40 @@ def wire(self, num: None | int = None) -> na.Cartesian3dVectorArray:
result = self.transformation(result)
return result

def grid(
self,
normalized: na.AbstractCartesian2dVectorArray,
) -> na.Cartesian3dVectorArray:
"""
Map a grid of normalized coordinates in :math:`[-1, 1]` onto points
filling the interior of the aperture.

Unlike :meth:`wire`, which samples the boundary, this samples the
interior, and is useful for filling the aperture with rays. The
``x`` component is mapped to the radius (vertex to rim) and the ``y``
component to the polar angle (``angle_start`` to ``angle_stop``).

Parameters
----------
normalized
A grid of normalized coordinates, where each component is in the
interval :math:`[-1, 1]`.
"""
unit_radius = na.unit(self.radius)
radius = self.radius * (normalized.x + 1) / 2
angle = (
self.angle_start
+ (self.angle_stop - self.angle_start) * (normalized.y + 1) / 2
)
result = na.Cartesian3dVectorArray(
x=radius * np.cos(angle),
y=radius * np.sin(angle),
z=0 * unit_radius if unit_radius is not None else 0,
)
if self.transformation is not None:
result = self.transformation(result)
return result


@dataclasses.dataclass(eq=False, repr=False)
class EllipticalAperture(
Expand Down Expand Up @@ -985,6 +1019,34 @@ def vertices(self):
result.z = result.z * unit
return result

def grid(
self,
normalized: na.AbstractCartesian2dVectorArray,
) -> na.Cartesian3dVectorArray:
"""
Map a grid of normalized coordinates in :math:`[-1, 1]` onto points
filling the interior of the aperture.

Unlike :meth:`wire`, which samples the boundary, this samples the
interior, and is useful for filling the aperture with rays.

Parameters
----------
normalized
A grid of normalized coordinates, where each component is in the
interval :math:`[-1, 1]`.
"""
half_width = na.asanyarray(self.half_width, like=na.Cartesian2dVectorArray())
unit = na.unit(half_width.x)
result = na.Cartesian3dVectorArray(
x=half_width.x * normalized.x,
y=half_width.y * normalized.y,
z=0 * unit if unit is not None else 0,
)
if self.transformation is not None:
result = self.transformation(result)
return result


@dataclasses.dataclass(eq=False, repr=False)
class AbstractRegularPolygonalAperture(
Expand Down
21 changes: 21 additions & 0 deletions optika/apertures/_apertures_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,27 @@ def test_decentered(self, a: optika.apertures.RectangularAperture):
assert not np.any(b(point_outside))


def test_grid_transformation():
# grid() must apply the aperture's transformation to the sampled points
shift = na.transformations.Cartesian3dTranslation(x=5 * u.mm)
center = na.Cartesian2dVectorArray(x=0.0, y=0.0)
for aperture in (
optika.apertures.RectangularAperture(
half_width=na.Cartesian2dVectorArray(10, 4) * u.mm,
transformation=shift,
),
optika.apertures.CircularSectorAperture(
radius=125 * u.mm,
angle_start=90 * u.deg,
angle_stop=180 * u.deg,
transformation=shift,
),
):
grid = aperture.grid(center)
assert isinstance(grid, na.AbstractCartesian3dVectorArray)
assert np.all(aperture(grid))


class AbstractTestAbstractRegularPolygonalAperture(
AbstractTestAbstractPolygonalAperture,
):
Expand Down
Loading