1# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. 2# 3# This source code is licensed under the MIT license found in the 4# LICENSE file in the root directory of this source tree. 5 6import numpy as np 7from scipy import optimize as scipyoptimize 8import nevergrad.common.typing as tp 9from nevergrad.parametrization import parameter as p 10from . import base 11from .base import IntOrParameter 12from . import recaster 13 14 15class _ScipyMinimizeBase(recaster.SequentialRecastOptimizer): 16 def __init__( 17 self, 18 parametrization: IntOrParameter, 19 budget: tp.Optional[int] = None, 20 num_workers: int = 1, 21 *, 22 method: str = "Nelder-Mead", 23 random_restart: bool = False, 24 ) -> None: 25 super().__init__(parametrization, budget=budget, num_workers=num_workers) 26 self.multirun = 1 # work in progress 27 self.initial_guess: tp.Optional[tp.ArrayLike] = None 28 # configuration 29 assert method in ["Nelder-Mead", "COBYLA", "SLSQP", "Powell"], f"Unknown method '{method}'" 30 self.method = method 31 self.random_restart = random_restart 32 33 # def _internal_tell_not_asked(self, x: base.ArrayLike, value: float) -> None: 34 def _internal_tell_not_asked(self, candidate: p.Parameter, value: float) -> None: 35 """Called whenever calling "tell" on a candidate that was not "asked". 36 Defaults to the standard tell pipeline. 37 """ # We do not do anything; this just updates the current best. 38 39 def get_optimization_function(self) -> tp.Callable[[tp.Callable[[tp.ArrayLike], float]], tp.ArrayLike]: 40 # create a different sub-instance, so that the current instance is not referenced by the thread 41 # (consequence: do not create a thread at initialization, or we get a thread explosion) 42 subinstance = self.__class__( 43 parametrization=self.parametrization, 44 budget=self.budget, 45 num_workers=self.num_workers, 46 method=self.method, 47 random_restart=self.random_restart, 48 ) 49 subinstance.archive = self.archive 50 subinstance.current_bests = self.current_bests 51 return subinstance._optimization_function 52 53 def _optimization_function(self, objective_function: tp.Callable[[tp.ArrayLike], float]) -> tp.ArrayLike: 54 # pylint:disable=unused-argument 55 budget = np.inf if self.budget is None else self.budget 56 best_res = np.inf 57 best_x: np.ndarray = self.current_bests["average"].x # np.zeros(self.dimension) 58 if self.initial_guess is not None: 59 best_x = np.array(self.initial_guess, copy=True) # copy, just to make sure it is not modified 60 remaining = budget - self._num_ask 61 while remaining > 0: # try to restart if budget is not elapsed 62 options: tp.Dict[str, int] = {} if self.budget is None else {"maxiter": remaining} 63 res = scipyoptimize.minimize( 64 objective_function, 65 best_x if not self.random_restart else self._rng.normal(0.0, 1.0, self.dimension), 66 method=self.method, 67 options=options, 68 tol=0, 69 ) 70 if res.fun < best_res: 71 best_res = res.fun 72 best_x = res.x 73 remaining = budget - self._num_ask 74 return best_x 75 76 77class ScipyOptimizer(base.ConfiguredOptimizer): 78 """Wrapper over Scipy optimizer implementations, in standard ask and tell format. 79 This is actually an import from scipy-optimize, including Sequential Quadratic Programming, 80 81 Parameters 82 ---------- 83 method: str 84 Name of the method to use among: 85 86 - Nelder-Mead 87 - COBYLA 88 - SQP (or SLSQP): very powerful e.g. in continuous noisy optimization. It is based on 89 approximating the objective function by quadratic models. 90 - Powell 91 random_restart: bool 92 whether to restart at a random point if the optimizer converged but the budget is not entirely 93 spent yet (otherwise, restarts from best point) 94 95 Note 96 ---- 97 These optimizers do not support asking several candidates in a row 98 """ 99 100 recast = True 101 no_parallelization = True 102 103 # pylint: disable=unused-argument 104 def __init__(self, *, method: str = "Nelder-Mead", random_restart: bool = False) -> None: 105 super().__init__(_ScipyMinimizeBase, locals()) 106 107 108NelderMead = ScipyOptimizer(method="Nelder-Mead").set_name("NelderMead", register=True) 109Powell = ScipyOptimizer(method="Powell").set_name("Powell", register=True) 110RPowell = ScipyOptimizer(method="Powell", random_restart=True).set_name("RPowell", register=True) 111Cobyla = ScipyOptimizer(method="COBYLA").set_name("Cobyla", register=True) 112RCobyla = ScipyOptimizer(method="COBYLA", random_restart=True).set_name("RCobyla", register=True) 113SQP = ScipyOptimizer(method="SLSQP").set_name("SQP", register=True) 114SLSQP = SQP # Just so that people who are familiar with SLSQP naming are not lost. 115RSQP = ScipyOptimizer(method="SLSQP", random_restart=True).set_name("RSQP", register=True) 116RSLSQP = RSQP # Just so that people who are familiar with SLSQP naming are not lost. 117