Source code for pybop.optimisers.scipy_optimisers

from scipy.optimize import minimize, differential_evolution
from .base_optimiser import BaseOptimiser


[docs] class SciPyMinimize(BaseOptimiser): """ Adapts SciPy's minimize function for use as an optimization strategy. This class provides an interface to various scalar minimization algorithms implemented in SciPy, allowing fine-tuning of the optimization process through method selection and option configuration. Parameters ---------- method : str, optional The type of solver to use. If not specified, defaults to 'COBYLA'. bounds : sequence or ``Bounds``, optional Bounds for variables as supported by the selected method. maxiter : int, optional Maximum number of iterations to perform. """ def __init__(self, method=None, bounds=None, maxiter=None): super().__init__() self.method = method self.bounds = bounds self.options = {} self._max_iterations = maxiter if self.method is None: self.method = "COBYLA" # "L-BFGS-B"
[docs] def _runoptimise(self, cost_function, x0, bounds): """ Executes the optimization process using SciPy's minimize function. Parameters ---------- cost_function : callable The objective function to minimize. x0 : array_like Initial guess for the parameters. bounds : sequence or `Bounds` Bounds for the variables. Returns ------- tuple A tuple (x, final_cost) containing the optimized parameters and the value of `cost_function` at the optimum. """ # Add callback storing history of parameter values self.log = [[x0]] def callback(x): self.log.append([x]) # Reformat bounds if bounds is not None: bounds = ( (lower, upper) for lower, upper in zip(bounds["lower"], bounds["upper"]) ) # Set max iterations if self._max_iterations is not None: self.options = {"maxiter": self._max_iterations} else: self.options.pop("maxiter", None) output = minimize( cost_function, x0, method=self.method, bounds=bounds, options=self.options, callback=callback, ) # Get performance statistics x = output.x final_cost = output.fun return x, final_cost
[docs] def needs_sensitivities(self): """ Determines if the optimization algorithm requires gradient information. Returns ------- bool False, indicating that gradient information is not required. """ return False
[docs] def name(self): """ Provides the name of the optimization strategy. Returns ------- str The name 'SciPyMinimize'. """ return "SciPyMinimize"
[docs] class SciPyDifferentialEvolution(BaseOptimiser): """ Adapts SciPy's differential_evolution function for global optimization. This class provides a global optimization strategy based on differential evolution, useful for problems involving continuous parameters and potentially multiple local minima. Parameters ---------- bounds : sequence or ``Bounds`` Bounds for variables. Must be provided as it is essential for differential evolution. strategy : str, optional The differential evolution strategy to use. Defaults to 'best1bin'. maxiter : int, optional Maximum number of iterations to perform. Defaults to 1000. popsize : int, optional The number of individuals in the population. Defaults to 15. """ def __init__(self, bounds=None, strategy="best1bin", maxiter=1000, popsize=15): super().__init__() self.bounds = bounds self.strategy = strategy self._max_iterations = maxiter self.popsize = popsize
[docs] def _runoptimise(self, cost_function, x0=None, bounds=None): """ Executes the optimization process using SciPy's differential_evolution function. Parameters ---------- cost_function : callable The objective function to minimize. x0 : array_like, optional Ignored parameter, provided for API consistency. bounds : sequence or ``Bounds`` Bounds for the variables, required for differential evolution. Returns ------- tuple A tuple (x, final_cost) containing the optimized parameters and the value of ``cost_function`` at the optimum. """ if bounds is None: raise ValueError("Bounds must be specified for differential_evolution.") if x0 is not None: print( "Ignoring x0. Initial conditions are not used for differential_evolution." ) # Add callback storing history of parameter values self.log = [] def callback(x, convergence): self.log.append([x]) # Reformat bounds if necessary if isinstance(bounds, dict): bounds = [ (lower, upper) for lower, upper in zip(bounds["lower"], bounds["upper"]) ] output = differential_evolution( cost_function, bounds, strategy=self.strategy, maxiter=self._max_iterations, popsize=self.popsize, callback=callback, ) # Get performance statistics x = output.x final_cost = output.fun return x, final_cost
[docs] def needs_sensitivities(self): """ Determines if the optimization algorithm requires gradient information. Returns ------- bool False, indicating that gradient information is not required for differential evolution. """ return False
[docs] def name(self): """ Provides the name of the optimization strategy. Returns ------- str The name 'SciPyDifferentialEvolution'. """ return "SciPyDifferentialEvolution"