Source code for optimization.optimizer.scipy.differential_evolution

from __future__ import annotations

import warnings
from typing import TYPE_CHECKING, Any

import optiland.backend as be
from scipy import optimize

from ..live_plotter import LiveOptimizationPlotter
from .base import OptimizerGeneric

if TYPE_CHECKING:
    from ...problem import OptimizationProblem


[docs] class DifferentialEvolution(OptimizerGeneric): """Differential Evolution optimizer for solving optimization problems. Args: problem (OptimizationProblem): The optimization problem to be solved. Methods: optimize(maxiter=1000, disp=True, workers=-1): Runs the differential evolution optimization algorithm. """ def __init__(self, problem: OptimizationProblem): """Initializes a new instance of the DifferentialEvolution class. Args: problem (OptimizationProblem): The optimization problem to be solved. """ super().__init__(problem)
[docs] def optimize( self, maxiter: int = 1000, disp: bool = True, plot: bool = False, workers: int = -1, callback: Any = None, ): """Runs the differential evolution optimization algorithm. Args: maxiter (int): Maximum number of iterations. disp (bool): Set to True to display status messages. plot: If True, update live plots during optimization. workers (int): Number of parallel workers to use. Set to -1 to use all available processors. callback (callable): A callable called after each iteration. Returns: result (OptimizeResult): The optimization result. Raises: ValueError: If any variable in the problem does not have bounds. """ # Get initial values in backend format x0_backend = [var.value for var in self.problem.variables] self._x.append(x0_backend) # Store backend values # Convert x0 to NumPy for SciPy x0_numpy = be.to_numpy(x0_backend) bounds = tuple([var.bounds for var in self.problem.variables]) if any(None in bound for bound in bounds): raise ValueError( "Differential evolution requires all variables have bounds.", ) live_plotter: LiveOptimizationPlotter | None = None if plot: live_plotter = LiveOptimizationPlotter(self) live_plotter.initialize() def _wrapped_callback(*args: Any, **kwargs: Any) -> None: if callback is not None: callback(*args, **kwargs) if live_plotter is not None: live_plotter.update() with warnings.catch_warnings(): warnings.simplefilter("ignore", category=RuntimeWarning) updating = "deferred" if workers == -1 else "immediate" result = optimize.differential_evolution( self._fun, bounds=bounds, maxiter=maxiter, x0=x0_numpy, disp=disp, updating=updating, workers=workers, callback=_wrapped_callback, ) # The last function evaluation is not necessarily the lowest. # Update all lens variables to their optimized values for idvar, var in enumerate(self.problem.variables): var.update(result.x[idvar]) self.problem.update_optics() if live_plotter is not None: live_plotter.update() live_plotter.finalize() return result