Source code for thin_film.tolerancing.core

"""Core tolerancing class for thin film stacks.

Manages operands (optical performance metrics) and perturbations, and provides
``evaluate`` / ``reset`` methods consumed by the analysis classes.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, Literal

from optiland.thin_film.optimization.operand.thin_film import ThinFilmOperand

from .perturbation import ThinFilmPerturbation

if TYPE_CHECKING:
    from optiland.thin_film import ThinFilmStack
    from optiland.tolerancing.perturbation import BaseSampler


OpticalProperty = Literal["R", "T", "A"]


[docs] @dataclass class ThinFilmOperandSpec: """Specification for a single operand.""" property: OpticalProperty wavelength_nm: float aoi_deg: float polarization: str target: float | None
[docs] class ThinFilmTolerancing: """Container for thin-film operands and perturbations. Args: stack: The thin film stack to tolerance. """ def __init__(self, stack: ThinFilmStack): self.stack = stack self.operands: list[ThinFilmOperandSpec] = [] self.perturbations: list[ThinFilmPerturbation] = []
[docs] def add_operand( self, property: OpticalProperty, wavelength_nm: float, aoi_deg: float = 0.0, polarization: str = "u", target: float | None = None, ) -> ThinFilmTolerancing: """Add a performance operand. If *target* is ``None`` the current computed value is stored as the nominal reference. Returns: self for method chaining. """ if target is None: target = self._evaluate_property( property, wavelength_nm, aoi_deg, polarization ) self.operands.append( ThinFilmOperandSpec( property=property, wavelength_nm=wavelength_nm, aoi_deg=aoi_deg, polarization=polarization, target=target, ) ) return self
[docs] def add_perturbation( self, layer_index: int, perturbation_type: Literal["thickness", "index"] = "thickness", sampler: BaseSampler | None = None, is_relative: bool = True, ) -> ThinFilmTolerancing: """Add a perturbation to a layer. Returns: self for method chaining. """ if sampler is None: raise ValueError("A sampler must be provided.") self.perturbations.append( ThinFilmPerturbation( stack=self.stack, layer_index=layer_index, perturbation_type=perturbation_type, sampler=sampler, is_relative=is_relative, ) ) return self
[docs] def evaluate(self) -> list[float]: """Evaluate all operands and return their current values.""" return [ self._evaluate_property( op.property, op.wavelength_nm, op.aoi_deg, op.polarization ) for op in self.operands ]
[docs] def reset(self) -> None: """Reset all perturbations to nominal.""" for p in self.perturbations: p.reset()
def _evaluate_property( self, property: OpticalProperty, wavelength_nm: float, aoi_deg: float, polarization: str, ) -> float: func = { "R": ThinFilmOperand.reflectance, "T": ThinFilmOperand.transmittance, "A": ThinFilmOperand.absorptance, }[property] return func(self.stack, wavelength_nm, aoi_deg, polarization)