Source code for optimization.variable.forbes_coeff

"""Specialized variable handlers for Forbes polynomial coefficients.

This module provides handlers for Forbes polynomial coefficients,
distinguishing between rotationally symmetric (slope-orthogonal Q) and
freeform (Q-2D) surfaces.

Manuel Fragata Mendes, August 2025
"""

from __future__ import annotations

import warnings

from optiland.geometries.forbes.geometry import (
    ForbesQ2dGeometry,
    ForbesQNormalSlopeGeometry,
)
from optiland.optimization.scaling.identity import IdentityScaler
from optiland.optimization.variable.base import VariableBehavior


[docs] class ForbesQNormalSlopeCoeffVariable(VariableBehavior): """Represents a variable for a Forbes Q (slope-orthogonal) coefficient. This variable targets a coefficient for a specific radial order `n`. Attributes: coeff_number (int): The radial order 'n' of the coefficient. """ def __init__(self, optic, surface_number, coeff_number, scaler=None, **kwargs): """Initializes the ForbesQNormalSlopeCoeffVariable. Args: optic: The optical system. surface_number (int): The surface number to which the coefficient belongs. coeff_number (int): The radial order 'n' of the coefficient. scaler (Scaler, optional): The scaler to use. Defaults to IdentityScaler(). **kwargs: Additional keyword arguments. """ if scaler is None: scaler = IdentityScaler() super().__init__(optic, surface_number, scaler=scaler, **kwargs) self.coeff_number = coeff_number # the radial order 'n'
[docs] def get_value(self): """Gets the value of the nth Q (slope-orthogonal) coefficient. Returns: float: The value of the coefficient. Raises: TypeError: If the geometry is not a ForbesQNormalSlopeGeometry. """ surf = self._surfaces[self.surface_number] geom = surf.geometry if not isinstance(geom, ForbesQNormalSlopeGeometry): raise TypeError("This variable is only for ForbesQNormalSlopeGeometry.") return geom.radial_terms.get(self.coeff_number, 0.0)
[docs] def update_value(self, new_value): """Updates the value of the nth Q (slope-orthogonal) coefficient. Args: new_value (float): The new value for the coefficient. """ surf = self.optic.surfaces[self.surface_number] geom = surf.geometry geom.radial_terms[self.coeff_number] = new_value geom._prepare_coeffs()
def __str__(self): return f"Forbes Q Coeff n={self.coeff_number}, Surface {self.surface_number}"
[docs] class ForbesQ2dCoeffVariable(VariableBehavior): """Represents a variable for a Forbes Q-2D (freeform) coefficient. This variable targets the coefficient `c` corresponding to a specific (type, m, n) term, following the Zemax convention. Attributes: attribute (str): The name of the attribute holding the coefficients. m (int): The azimuthal frequency `m`. n (int): The radial order `n`. term_type (str): The type of term ('cos' or 'sin'). coeff_tuple (tuple): The identifier for the coefficient. """ def __init__(self, optic, surface_number, coeff_tuple, scaler=None, **kwargs): """Initializes the ForbesQ2dCoeffVariable. Args: optic: The optical system. surface_number (int): The surface number to which the coefficient belongs. coeff_tuple (tuple): The identifier for the coefficient, following the Zemax convention: `('a', m, n)` for a cosine term a_n^m, or `('b', m, n)` for a sine term b_n^m. scaler (Scaler, optional): The scaler to use. Defaults to IdentityScaler(). **kwargs: Additional keyword arguments. Raises: ValueError: If `coeff_tuple` is not in the correct format. """ if scaler is None: scaler = IdentityScaler() super().__init__(optic, surface_number, scaler=scaler, **kwargs) self.attribute = "freeform_coeffs" # Parse the Zemax-style key for correct string representation and validation try: term_char, m, n = coeff_tuple self.m = m self.n = n if term_char.lower() == "a": self.term_type = "cos" elif term_char.lower() == "b": self.term_type = "sin" else: raise ValueError("Term type in coeff_tuple must be 'a' or 'b'.") except (ValueError, TypeError, IndexError) as err: raise ValueError( "coeff_tuple for ForbesQ2dCoeffVariable must be a tuple of the " "form ('a', m, n) or ('b', m, n)." ) from err self.coeff_tuple = coeff_tuple
[docs] def get_value(self): """Gets the value of the coefficient for the specified term. Returns: float: The value of the coefficient. Raises: TypeError: If the geometry is not a ForbesQ2dGeometry. """ surf = self._surfaces[self.surface_number] geom = surf.geometry if not isinstance(geom, ForbesQ2dGeometry): raise TypeError("This variable is only for ForbesQ2dGeometry.") return geom.freeform_coeffs.get(self.coeff_tuple, 0.0)
[docs] def update_value(self, new_value): """Updates the value of the coefficient for the specified term. Args: new_value (float): The new value for the coefficient. """ surf = self.optic.surfaces[self.surface_number] geom = surf.geometry geom.freeform_coeffs[self.coeff_tuple] = new_value geom._prepare_coeffs()
def __str__(self): """Returns a string representation of the variable.""" return ( f"Forbes Q-2D Coeff (n={self.n}, m={self.m}, {self.term_type}), " f"Surface {self.surface_number}" )
[docs] class ForbesQbfsCoeffVariable(ForbesQNormalSlopeCoeffVariable): """Deprecated alias for ForbesQNormalSlopeCoeffVariable. .. deprecated:: Use :class:`ForbesQNormalSlopeCoeffVariable` instead. """ def __init__(self, optic, surface_number, coeff_number, scaler=None, **kwargs): warnings.warn( "ForbesQbfsCoeffVariable is deprecated; use " "ForbesQNormalSlopeCoeffVariable instead.", DeprecationWarning, stacklevel=2, ) super().__init__(optic, surface_number, coeff_number, scaler, **kwargs) def __str__(self): return ( f"Forbes Q-bfs Coeff n={self.coeff_number}, Surface {self.surface_number}" )