Optimization Framework ====================== Optimization in Optiland allows users to improve the performance of optical systems by adjusting design parameters to minimize (or maximize) a merit function. The framework supports a wide range of optimizers and a flexible system for defining operands and variables. The framework integrates tightly with the `Optic` class. The Optiland optimization framework includes the following components: - **Operands**: Quantitative metrics for evaluating optical system performance or properties (e.g., RMS spot size, wavefront error, etc.). - **Variables**: System parameters that can be adjusted (e.g., surface curvatures, separations). - **Scaling**: Methods to scale optimization variables to a (roughly) common range for improved convergence and performance. - **Optimization Problem Class**: Encapsulates the problem definition, linking operands and variables. - **Optimizers**: Algorithms for solving optimization problems, such as gradient-based methods or evolutionary strategies. .. figure:: ../_static/cooke_triplet_lens_optimization_evolution.gif :width: 60% :align: center .. figure:: ../_static/cooke_triplet_merit_function_evolution.gif :width: 60% :align: center .. raw:: html
Components Explained -------------------- 1. **Optimization Problem**: - The `OptimizationProblem` class orchestrates the optimization process. - Key responsibilities include: - Adding **operands** to define the merit function. - Adding **variables** to define the parameters to optimize. - Computing the overall objective function value. - Optionally batching compatible ray operands to avoid redundant traces. - Providing both squared terms (`fun_array`) and residual vectors (`residual_vector`) for least-squares optimizers. 2. **Optimizers**: - A base `Optimizer` class wraps `scipy.optimize.minimize` and provides a unified interface. - Built-in optimizers include: - **Dual Annealing** (global) - **Differential Evolution** (global) - **Basin Hopping** (global) - **SHGO** (global) - **Least Squares** (local) - **Orthogonal Descent** (local) - **Nelder-Mead**, **Powell**, **BFGS**, **L-BFGS-B**, **COBYLA**, etc. (local optimization, from `scipy.optimize.minimize`) - Users can subclass the base optimizer for custom methods. 3. **Operands and Variables**: - **Operands**: Define individual contributions to the merit function. Examples: - RMS Spot Size - Wavefront Error - Focal Length - **Variables**: Define the parameters to optimize, such as: - Radius of curvature - Conic constants - Material refractive indices - Surface tilts and decenters - Geometry parameters (e.g., freeform coefficients) 4. **Scaling**: - Scaling methods help improve optimization performance by normalizing variable ranges. - Built-in scalers include: - Linear Scaling - Logarithmic Scaling - Power Scaling - Reciprocal Scaling .. note:: The optimization framework is written in a modular way, allowing users to easily extend the framework with custom optimizers, operands, and variables. .. raw:: html Typical Optimization Process ---------------------------- 1. **Set Up the Problem**. Create an instance of `OptimizationProblem`: .. code:: python from optiland.optimization import OptimizationProblem problem = OptimizationProblem() 2. **Add Operands**. Add operands to define the merit function: .. code:: python input_data = {'optic': lens} # Add focal length operand problem.add_operand(operand_type='f2', target=50, weight=1, input_data=input_data) 3. **Add Variables**. Define the parameters to optimize: .. code:: python # Add radius of curvature variable for second surface problem.add_variable(lens, 'radius', surface_number=2) 4. **(Optional) Configure Batched Ray Evaluation**. Batching is enabled by default. If you need to compare with legacy per-operand behavior, disable it explicitly: .. code:: python # Squared weighted deltas (used by many optimizers) merit = problem.sum_squared() # Unsquared weighted deltas (useful for least-squares style methods) residuals = problem.residual_vector() # Disable batching to opt out (legacy per-operand evaluation) problem.disable_batching() 5. **Choose an Optimizer**. Select an optimizer and run the optimization: .. code:: python from optiland.optimization import OptimizerGeneric optimizer = OptimizerGeneric(problem) result = optimizer.optimize() 6. **Review Results**. Print optimization results and visualize performance: .. code:: python problem.info() # print optimization problem details print(result) # standard output from scipy.optimize.minimize lens.draw() # Plot the lens in 2D Batched Ray Evaluation Internals -------------------------------- The batching path is implemented by `optiland.optimization.batched_evaluator.BatchedRayEvaluator` and is integrated into `OptimizationProblem` by default. You can opt out with `disable_batching()` and re-enable with `enable_batching()`. When enabled, the evaluator performs three steps: 1. Group compatible operands that can share the same trace call. 2. Execute the minimum required set of `trace_generic` and `trace` calls. 3. Extract per-operand values from shared traced arrays while preserving backend behavior and autograd. Current batching support includes: - **Single-ray (`trace_generic`) operands** such as `real_x_intercept`, `real_y_intercept`, `real_z_intercept`, local-coordinate intercept variants, direction cosines (`real_L`, `real_M`, `real_N`), and `AOI`. - **Distribution (`trace`) operands** for `rms_spot_size` when trace parameters match. Operands that are not currently batchable are evaluated through the standard direct path, so mixed merit functions are fully supported. For PyTorch workflows, this design keeps gradients valid because values are extracted by tensor indexing from traced data (without detaching). For NumPy workflows, behavior remains numerically equivalent to the standard per-operand evaluation path. .. figure:: ../_static/cooke_triplet_starting_point.png :width: 80% :align: center .. figure:: ../_static/cooke_triplet_optimized.png :width: 80% :align: center .. raw:: html Understanding Operands ---------------------- Operands represent individual components of the merit function. To find the inputs required for a specific operand: - Refer to the operand registry in the Operand module, or the API documentation. - Use operand-specific documentation for parameter details. For example, the RMS spot size requires a field as an input, while the focal length does not. All operands require a target value, weight, and an `Optic` instance. Note that the ``weight`` of an operand dictates its contribution relative to other operands in the merit function. For operands evaluated across the pupil or across a spectrum (e.g., standard RMS Spot Size), the merit function also automatically accounts for the intrinsic ``weight`` assigned to the ``Field`` and ``Wavelength`` objects defined in the ``Optic``. .. raw:: html Extending Optimization ---------------------- Custom operands, variables and optimization algorithms can be added by subclassing the appropriate base classes. For example: - Subclass VariableBehavior to create a new variable type, then register it within the Variable class. - Define a new operand function and register it within the Operand module. - Subclass OptimizerGeneric to create a new optimization algorithm. .. tip:: See the :ref:`Learning Guide