"""
Aberrations Module
This module computes first- and third-order aberrations of optical systems.
The aberration calculations are based on the algorithms outlined in
Modern Optical Engineering by Warren Smith (Chapter 6.3).
Kramer Harrison, 2023
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import optiland.backend as be
if TYPE_CHECKING:
from collections.abc import Callable
from optiland._types import BEArray
from optiland.optic import Optic
[docs]
class Aberrations:
"""Class for computation of optical aberrations.
This class provides methods to compute first- and third-order aberrations
of a general optical system, using an instance of an optic.Optic object.
Args:
optic (Optic): An instance of the optical system to be analyzed.
"""
def __init__(self, optic: Optic):
self.optic = optic
[docs]
def third_order(self) -> tuple[BEArray, ...]:
"""
Compute all third-order aberrations and first-order color terms.
Returns:
A tuple containing the following `be.ndarray` instances:
- TSC: Third-order transverse spherical aberration.
- SC: Third-order longitudinal spherical aberration.
- CC: Third-order sagittal coma.
- TCC: Third-order tangential coma (3×CC).
- TAC: Third-order transverse astigmatism.
- AC: Third-order longitudinal astigmatism.
- TPC: Third-order transverse Petzval sum.
- PC: Third-order longitudinal Petzval sum.
- DC: Third-order distortion.
- TAchC: First-order transverse axial color.
- LchC: First-order longitudinal axial color.
- TchC: First-order lateral color.
- S: Seidel aberration coefficients.
"""
self._precalculations()
# Compute aberration terms over surfaces 1 to N-2
TSC = self._compute_over_surfaces(self._TSC_term)
CC = self._compute_over_surfaces(self._CC_term)
TAC = self._compute_over_surfaces(self._TAC_term)
TPC = self._compute_over_surfaces(self._TPC_term)
DC = self._compute_over_surfaces(self._DC_term)
TAchC = self._compute_over_surfaces(self._TAchC_term)
TchC = self._compute_over_surfaces(self._TchC_term)
# Compute derived terms using the computed aberration values
SC = -TSC / self._ua[-1]
AC = -TAC / self._ua[-1]
PC = -TPC / self._ua[-1]
LchC = -TAchC / self._ua[-1]
S = self._sum_seidels(TSC, CC, TAC, TPC, DC)
TCC = CC * 3
return (
TSC.flatten(),
SC.flatten(),
CC.flatten(),
TCC.flatten(),
TAC.flatten(),
AC.flatten(),
TPC.flatten(),
PC.flatten(),
DC.flatten(),
TAchC.flatten(),
LchC.flatten(),
TchC.flatten(),
S,
)
[docs]
def seidels(self) -> BEArray:
"""
Compute the Seidel aberration coefficients.
Returns:
Array of Seidel aberration coefficients.
"""
self._precalculations()
TSC = self._compute_over_surfaces(self._TSC_term)
CC = self._compute_over_surfaces(self._CC_term)
TAC = self._compute_over_surfaces(self._TAC_term)
TPC = self._compute_over_surfaces(self._TPC_term)
DC = self._compute_over_surfaces(self._DC_term)
S = self._sum_seidels(TSC, CC, TAC, TPC, DC)
return S.squeeze()
[docs]
def TSC(self) -> BEArray:
"""
Compute third-order transverse spherical aberration.
Returns:
Third-order transverse spherical aberration.
"""
self._precalculations()
return self._compute_over_surfaces(self._TSC_term).flatten()
[docs]
def SC(self) -> BEArray:
"""
Compute third-order longitudinal spherical aberration.
Returns:
Third-order longitudinal spherical aberration.
"""
self._precalculations()
TSC = self._compute_over_surfaces(self._TSC_term)
SC = -TSC / self._ua[-1]
return SC.flatten()
[docs]
def CC(self) -> BEArray:
"""
Compute third-order sagittal coma.
Returns:
Third-order sagittal coma.
"""
self._precalculations()
return self._compute_over_surfaces(self._CC_term).flatten()
[docs]
def TCC(self) -> BEArray:
"""
Compute third-order tangential coma.
Returns:
Third-order tangential coma.
"""
return (self.CC() * 3).flatten()
[docs]
def TAC(self) -> BEArray:
"""
Compute third-order transverse astigmatism.
Returns:
Third-order transverse astigmatism.
"""
self._precalculations()
return self._compute_over_surfaces(self._TAC_term).flatten()
[docs]
def AC(self) -> BEArray:
"""
Compute third-order longitudinal astigmatism.
Returns:
Third-order longitudinal astigmatism.
"""
self._precalculations()
TAC = self._compute_over_surfaces(self._TAC_term)
AC = -TAC / self._ua[-1]
return AC.flatten()
[docs]
def TPC(self) -> BEArray:
"""
Compute third-order transverse Petzval sum.
Returns:
Third-order transverse Petzval sum.
"""
self._precalculations()
return self._compute_over_surfaces(self._TPC_term).flatten()
[docs]
def PC(self) -> BEArray:
"""
Compute third-order longitudinal Petzval sum.
Returns:
Third-order longitudinal Petzval sum.
"""
self._precalculations()
TPC = self._compute_over_surfaces(self._TPC_term)
PC = -TPC / self._ua[-1]
return PC.flatten()
[docs]
def DC(self) -> BEArray:
"""
Compute third-order distortion.
Returns:
Third-order distortion.
"""
self._precalculations()
return self._compute_over_surfaces(self._DC_term).flatten()
[docs]
def TAchC(self) -> BEArray:
"""
Compute first-order transverse axial color.
Returns:
First-order transverse axial color.
"""
self._precalculations()
return self._compute_over_surfaces(self._TAchC_term).flatten()
[docs]
def LchC(self) -> BEArray:
"""
Compute first-order longitudinal axial color.
Returns:
First-order longitudinal axial color.
"""
self._precalculations()
TAchC = self._compute_over_surfaces(self._TAchC_term)
LchC = -TAchC / self._ua[-1]
return LchC.flatten()
[docs]
def TchC(self) -> BEArray:
"""
Compute first-order lateral color.
Returns:
First-order lateral color.
"""
self._precalculations()
return self._compute_over_surfaces(self._TchC_term).flatten()
def _compute_over_surfaces(self, term_func: Callable) -> BEArray:
"""
Compute a given aberration term over all relevant surfaces.
Args:
term_func (callable): Function that computes a term for a given
surface index.
Returns:
Array of computed term values over surfaces 1 to N-2.
"""
terms = [term_func(k) for k in range(1, self._N - 1)]
return be.array(terms)
def _precalculations(self):
"""
Perform all necessary precalculations for aberration computations.
This method computes and stores common parameters required by the
various aberration term methods.
"""
self._inv: float = self.optic.paraxial.invariant() # Lagrange invariant
self._on_axis = be.isclose(self._inv, be.array(0.0))
# Refractive indices for all surfaces
self._n: BEArray = self.optic.surfaces.n(self.optic.primary_wavelength)
self._N: int = self.optic.surfaces.num_surfaces
self._C = 1 / self.optic.surfaces.radii
self._ya, self._ua = self.optic.paraxial.marginal_ray()
self._yb, self._ub = self.optic.paraxial.chief_ray()
self._hp = self._inv / (self._n[-1] * self._ua[-1])
self._dn = self.optic.surfaces.n(0.4861) - self.optic.surfaces.n(0.6563)
i_list = []
ip_list = []
B_list = []
Bp_list = []
for k in range(1, self._N - 1):
i_val = (self._C[k] * self._ya[k] + self._ua[k - 1])[0]
ip_val = (self._C[k] * self._yb[k] + self._ub[k - 1])[0]
i_list.append(i_val)
ip_list.append(ip_val)
if self._on_axis:
B_list.append(0)
Bp_list.append(0)
else:
denom = 2 * self._n[k] * self._inv
B_val = (
self._n[k - 1]
* (self._n[k] - self._n[k - 1])
* self._ya[k]
* (self._ua[k] + i_val)
/ denom
)[0]
Bp_val = (
self._n[k - 1]
* (self._n[k] - self._n[k - 1])
* self._yb[k]
* (self._ub[k] + ip_val)
/ denom
)[0]
B_list.append(B_val)
Bp_list.append(Bp_val)
self._i = be.array(i_list)
self._ip = be.array(ip_list)
self._B = be.array(B_list)
self._Bp = be.array(Bp_list)
def _TSC_on_axis_term(self, k: int) -> float:
"""
Compute third-order transverse spherical aberration term for surface k
when the system is on-axis.
Args:
k: Surface index.
Returns:
Computed transverse spherical aberration term.
"""
i_val = self._C[k] * self._ya[k] + self._ua[k - 1]
term = (
self._n[k - 1]
* (self._n[k] - self._n[k - 1])
* self._ya[k]
* (self._ua[k] + i_val)
* i_val**2
)
return term / (2 * self._n[k] * self._n[-1] * self._ua[-1])
def _TSC_term(self, k: int) -> float:
"""
Compute third-order transverse spherical aberration term for surface k.
Args:
k: Surface index.
Returns:
Computed transverse spherical aberration term.
"""
if self._on_axis:
return self._TSC_on_axis_term(k)
return self._B[k - 1] * self._i[k - 1] ** 2 * self._hp
def _CC_term(self, k: int) -> float:
"""
Compute third-order sagittal coma term for surface k.
Args:
k: Surface index.
Returns:
Computed sagittal coma term.
"""
return self._B[k - 1] * self._i[k - 1] * self._ip[k - 1] * self._hp
def _TAC_term(self, k: int) -> float:
"""
Compute third-order transverse astigmatism term for surface k.
Args:
k: Surface index.
Returns:
Computed transverse astigmatism term.
"""
return self._B[k - 1] * self._ip[k - 1] ** 2 * self._hp
def _TPC_term(self, k: int) -> BEArray:
"""
Compute third-order transverse Petzval sum term for surface k.
Args:
k: Surface index.
Returns:
Computed transverse Petzval sum term.
"""
return (
(self._n[k] - self._n[k - 1])
* self._C[k]
* self._hp
* self._inv
/ (2 * self._n[k] * self._n[k - 1])
)
def _DC_term(self, k: int) -> BEArray:
"""
Compute third-order distortion term for surface k.
Args:
k: Surface index.
Returns:
Computed distortion term.
"""
return self._hp * (
self._Bp[k - 1] * self._i[k - 1] * self._ip[k - 1]
+ 0.5 * (self._ub[k] ** 2 - self._ub[k - 1] ** 2)
)
def _TAchC_term(self, k: int) -> BEArray:
"""
Compute first-order transverse axial color term for surface k.
Args:
k: Surface index.
Returns:
Computed transverse axial color term.
"""
return (
-self._ya[k - 1]
* self._i[k - 1]
/ (self._n[-1] * self._ua[-1])
* (self._dn[k - 1] - self._n[k - 1] / self._n[k] * self._dn[k])
)
def _TchC_term(self, k: int) -> BEArray:
"""
Compute first-order lateral color term for surface k.
Args:
k: Surface index.
Returns:
Computed lateral color term.
"""
return (
-self._ya[k - 1]
* self._ip[k - 1]
/ (self._n[-1] * self._ua[-1])
* (self._dn[k - 1] - self._n[k - 1] / self._n[k] * self._dn[k])
)
def _sum_seidels(
self,
TSC: BEArray,
CC: BEArray,
TAC: BEArray,
TPC: BEArray,
DC: BEArray,
) -> BEArray:
"""
Sum the Seidel aberration coefficients from the individual terms.
Args:
TSC: Transverse spherical aberration terms.
CC: Sagittal coma terms.
TAC: Transverse astigmatism terms.
TPC: Transverse Petzval sum terms.
DC: Distortion terms.
Returns:
Array of Seidel aberration coefficients.
"""
factor = self._n[-1] * self._ua[-1] * 2
return be.array(
[
-be.sum(TSC) * factor,
-be.sum(CC) * factor,
-be.sum(TAC) * factor,
-be.sum(TPC) * factor,
-be.sum(DC) * factor,
]
)