"""Core Tolerancing Module
This module contains the core classes for the tolerancing module. The
Tolerancing class is the main class for defining a tolerancing problem. It
allows the user to add operands, perturbations, and compensators to an optical
system.
Kramer Harrison, 2024
"""
from __future__ import annotations
from optiland.optimization.operand import Operand
from optiland.tolerancing.compensator import CompensatorOptimizer
from optiland.tolerancing.perturbation import BaseSampler, Perturbation
[docs]
class OperandEvaluator:
"""Evaluates a set of named operands against an Optic instance.
Args:
operands: List of Operand objects to evaluate.
"""
def __init__(self, operands: list[Operand]) -> None:
self._operands = operands
[docs]
def evaluate(self) -> list[float]:
"""Return evaluated values for the current optic state.
Returns:
List of operand values in the same order as the operand list.
"""
return [float(op.value) for op in self._operands]
[docs]
class Tolerancing:
"""A class representing a tolerancing problem. This class is the core of the
tolerancing module. It allows the user to define a tolerancing problem by
adding operands (metrics), perturbations, and compensators to an optical
system.
Args:
optic: The optic object to be toleranced.
method: The method to use for optimization. Defaults to 'generic'.
tol: The tolerance for the compensator optimizer. Defaults to 1e-5.
Attributes:
optic: The optic object to be toleranced.
method: The method to use for optimization. Defaults to 'generic'.
tol: The tolerance for the compensator optimizer. Defaults to 1e-5.
_optic_nominal: A deep copy of the optic object representing its
nominal state.
operands: A list of operands in the tolerancing problem.
perturbations: A list of perturbations applied to the optic.
compensator: An optimizer for compensating the perturbations.
Methods:
__init__(self, optic): Initializes a Tolerancing object with the given
optic.
add_operand(self, operand_type, input_data={}): Adds an operand to the
tolerancing problem.
add_perturbation(self, variable_type, perturbation, **kwargs): Adds a
perturbation to the optic.
add_compensator(self, variable_type, **kwargs): Adds a compensator
variable to the optimizer.
apply_compensators(self): Applies compensators to the optic.
evaluate(self): Evaluates the operands in the tolerancing problem.
reset(self): Resets the optic to its nominal state.
"""
def __init__(self, optic, method="generic", tol=1e-5):
self.optic = optic
self.method = method
self.tol = tol
self.operands = []
self.perturbations = []
self.compensator = CompensatorOptimizer(method=method, tol=tol)
[docs]
def add_operand(
self,
operand_type: str,
input_data: dict = None,
target: float = None,
weight: float = 1.0,
min_val: float = None,
max_val: float = None,
):
"""Add an operand to the tolerancing problem.
Args:
operand_type (str): The type of the operand.
target (float): The target value of the operand (equality operand).
If target and both bounds are omitted, the current operand
value is captured as the target. When bounds are provided,
target remains None so compensation uses the allowed range.
min_val (float): The operand should stay above this
value (inequality operand).
max_val (float): The operand should stay below this
value (inequality operand).
weight (float): The weight of the operand.
input_data (dict): Additional input data for the operand.
"""
if input_data is None:
input_data = {}
new_operand = Operand(
operand_type,
target,
min_val,
max_val,
weight,
input_data,
)
self.operands.append(new_operand)
[docs]
def add_perturbation(self, variable_type: str, sampler: BaseSampler, **kwargs):
"""Add a perturbation to the optic.
Args:
variable_type: The type of the variable to be perturbed, such as
"radius", "thickness", etc. See the variable module for more
information.
sampler: The sampler object used to generate perturbation values.
**kwargs: Additional keyword arguments for the variable.
"""
perturbation = Perturbation(self.optic, variable_type, sampler, **kwargs)
self.perturbations.append(perturbation)
[docs]
def add_compensator(self, variable_type: str, **kwargs):
"""Add a compensator variable to the optimizer.
Args:
variable_type: The type of the variable to be used for
compensation, such as "thickness", etc. See the variable
module for more information.
**kwargs: Additional keyword arguments for the variable.
"""
self.compensator.add_variable(self.optic, variable_type, **kwargs)
[docs]
def apply_compensators(self):
"""Apply compensators to the optic."""
result = {}
if self.compensator.has_variables:
# add operands to the optimization problem (for compensation)
self.compensator.operands = self.operands
# run optimizer for compensating the perturbations
self.compensator.run()
# undo scaling and record the optimized values
result = {}
for i, var in enumerate(self.compensator.variables):
result[f"C{i}: {var!s}"] = float(var.variable.inverse_scale(var.value))
return result
[docs]
def evaluate(self) -> list[float]:
"""Evaluate the operands."""
return OperandEvaluator(self.operands).evaluate()
[docs]
def reset(self):
"""Reset the optic to its initial state."""
for perturbation in self.perturbations:
perturbation.reset()
for compensator in self.compensator.variables:
compensator.reset()