Source code for geometries.odd_asphere

"""Odd Asphere Geometry

The Odd Asphere geometry represents a surface defined by an odd asphere
polynomial superimposed on a base conic surface. The surface is defined as:

z = r^2 / (R * (1 + sqrt(1 - (1 + k) * r^2 / R^2))) + sum(Ci * r^i)

where
- r^2 = x^2 + y^2
- R is the radius of curvature
- k is the conic constant
- Ci are the aspheric coefficients

Kramer Harrison, 2025
"""

from __future__ import annotations

import warnings

import optiland.backend as be
from optiland.geometries.even_asphere import EvenAsphere


[docs] class OddAsphere(EvenAsphere): """Represents an odd asphere geometry defined as: z = r^2 / (R * (1 + sqrt(1 - (1 + k) * r^2 / R^2))) + sum(Ci * r^i) where - r^2 = x^2 + y^2 - R is the radius of curvature - k is the conic constant - Ci are the aspheric coefficients - i is an integer from 1 to n Args: coordinate_system (CoordinateSystem): The coordinate system of the geometry. radius (float): The radius of curvature of the base sphere. conic (float, optional): The conic constant of the base sphere. Defaults to 0.0. tol (float, optional): Tolerance for Newton-Raphson iteration. Defaults to 1e-10. max_iter (int, optional): Maximum iterations for Newton-Raphson. Defaults to 100. coefficients (list[float], optional): A list of odd aspheric coefficients C_i, where the term is C_i * r^i. The list index corresponds to i-1 (e.g., coefficients[0] is C_1 for r^1). Defaults to an empty list (no aspheric contribution). Attributes: c (list[float]): List of aspheric coefficients. """ def __init__( self, coordinate_system, radius, conic=0.0, tol=1e-10, max_iter=100, coefficients=None, ): if coefficients is None: coefficients = [] super().__init__(coordinate_system, radius, conic, tol, max_iter, coefficients) self.order = 1 # used for optimization scaling def __str__(self): return "Odd Asphere"
[docs] def scale(self, scale_factor: float): """Scale the geometry parameters. Args: scale_factor (float): The factor by which to scale the geometry. """ # Skip EvenAsphere.scale as it incorrectly scales coefficients for OddAsphere # Call the next class in MRO after EvenAsphere (NewtonRaphson -> Standard) super(EvenAsphere, self).scale(scale_factor) for i in range(len(self.coefficients)): # C_i' = C_i * s^(1 - (i+1)) self.coefficients[i] *= scale_factor ** (1 - (i + 1))
[docs] def sag(self, x=0, y=0): """Calculates the sag of the asphere at the given coordinates. Args: x (float or be.ndarray, optional): The x-coordinate(s). Defaults to 0. y (float or be.ndarray, optional): The y-coordinate(s). Defaults to 0. Returns: be.ndarray or float: The sag value(s) at the given coordinates. """ r2 = be.array(x**2 + y**2) r = be.sqrt(r2) z = r2 / (self.radius * (1 + be.sqrt(1 - (1 + self.k) * r2 / self.radius**2))) for i, Ci in enumerate(self.coefficients): z = z + Ci * r ** (i + 1) return z
def _surface_normal(self, x, y): """Calculates the surface normal of the asphere at the given x and y position. Args: x (be.ndarray): The x-coordinate(s) at which to calculate the normal. y (be.ndarray): The y-coordinate(s) at which to calculate the normal. Returns: tuple[be.ndarray, be.ndarray, be.ndarray]: The surface normal components (nx, ny, nz). """ r2 = x**2 + y**2 r = be.sqrt(r2) denom = self.radius * be.sqrt(1 - (1 + self.k) * r2 / self.radius**2) dfdx = x / denom dfdy = y / denom with warnings.catch_warnings(): warnings.simplefilter("ignore") for i, Ci in enumerate(self.coefficients): x_term = (i + 1) * x * Ci * r ** (i - 1) y_term = (i + 1) * y * Ci * r ** (i - 1) x_term[~be.isfinite(x_term)] = 0 y_term[~be.isfinite(y_term)] = 0 dfdx = dfdx + x_term dfdy = dfdy + y_term mag = be.sqrt(dfdx**2 + dfdy**2 + 1) nx = dfdx / mag ny = dfdy / mag nz = -1 / mag return nx, ny, nz