def _convertBounds(bounds): if bounds is None: raise ValueError('bounds need to be defined') if isinstance(bounds, Bounds): limits = np.array(new_bounds_to_old(bounds.lb, bounds.ub, len(bounds.lb)), dtype=float).T else: limits = np.array(bounds, dtype='float').T if (np.size(limits, 0) != 2 or not np.all(np.isfinite(limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') return limits[0], limits[1]
def test_new_bounds_to_old(): lb = np.array([-np.inf, 2, 3]) ub = np.array([3, np.inf, 10]) bounds = [(None, 3), (2, None), (3, 10)] assert_array_equal(new_bounds_to_old(lb, ub, 3), bounds) bounds_single_lb = [(-1, 3), (-1, None), (-1, 10)] assert_array_equal(new_bounds_to_old(-1, ub, 3), bounds_single_lb) bounds_no_lb = [(None, 3), (None, None), (None, 10)] assert_array_equal(new_bounds_to_old(-np.inf, ub, 3), bounds_no_lb) bounds_single_ub = [(None, 20), (2, 20), (3, 20)] assert_array_equal(new_bounds_to_old(lb, 20, 3), bounds_single_ub) bounds_no_ub = [(None, None), (2, None), (3, None)] assert_array_equal(new_bounds_to_old(lb, np.inf, 3), bounds_no_ub) bounds_single_both = [(1, 2), (1, 2), (1, 2)] assert_array_equal(new_bounds_to_old(1, 2, 3), bounds_single_both) bounds_no_both = [(None, None), (None, None), (None, None)] assert_array_equal(new_bounds_to_old(-np.inf, np.inf, 3), bounds_no_both)
def dual_annealing(func, bounds, args=(), maxiter=1000, minimizer_kwargs=None, initial_temp=5230., restart_temp_ratio=2.e-5, visit=2.62, accept=-5.0, maxfun=1e7, seed=None, no_local_search=False, callback=None, x0=None, local_search_options=None): """ Find the global minimum of a function using Dual Annealing. Parameters ---------- func : callable The objective function to be minimized. Must be in the form ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array and ``args`` is a tuple of any additional fixed parameters needed to completely specify the function. bounds : sequence or `Bounds` Bounds for variables. There are two ways to specify the bounds: 1. Instance of `Bounds` class. 2. Sequence of ``(min, max)`` pairs for each element in `x`. args : tuple, optional Any additional fixed parameters needed to completely specify the objective function. maxiter : int, optional The maximum number of global search iterations. Default value is 1000. minimizer_kwargs : dict, optional Extra keyword arguments to be passed to the local minimizer (`minimize`). Some important options could be: ``method`` for the minimizer method to use and ``args`` for objective function additional arguments. initial_temp : float, optional The initial temperature, use higher values to facilitates a wider search of the energy landscape, allowing dual_annealing to escape local minima that it is trapped in. Default value is 5230. Range is (0.01, 5.e4]. restart_temp_ratio : float, optional During the annealing process, temperature is decreasing, when it reaches ``initial_temp * restart_temp_ratio``, the reannealing process is triggered. Default value of the ratio is 2e-5. Range is (0, 1). visit : float, optional Parameter for visiting distribution. Default value is 2.62. Higher values give the visiting distribution a heavier tail, this makes the algorithm jump to a more distant region. The value range is (1, 3]. accept : float, optional Parameter for acceptance distribution. It is used to control the probability of acceptance. The lower the acceptance parameter, the smaller the probability of acceptance. Default value is -5.0 with a range (-1e4, -5]. maxfun : int, optional Soft limit for the number of objective function calls. If the algorithm is in the middle of a local search, this number will be exceeded, the algorithm will stop just after the local search is done. Default value is 1e7. seed : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional If `seed` is None (or `np.random`), the `numpy.random.RandomState` singleton is used. If `seed` is an int, a new ``RandomState`` instance is used, seeded with `seed`. If `seed` is already a ``Generator`` or ``RandomState`` instance then that instance is used. Specify `seed` for repeatable minimizations. The random numbers generated with this seed only affect the visiting distribution function and new coordinates generation. no_local_search : bool, optional If `no_local_search` is set to True, a traditional Generalized Simulated Annealing will be performed with no local search strategy applied. callback : callable, optional A callback function with signature ``callback(x, f, context)``, which will be called for all minima found. ``x`` and ``f`` are the coordinates and function value of the latest minimum found, and ``context`` has value in [0, 1, 2], with the following meaning: - 0: minimum detected in the annealing process. - 1: detection occurred in the local search process. - 2: detection done in the dual annealing process. If the callback implementation returns True, the algorithm will stop. x0 : ndarray, shape(n,), optional Coordinates of a single N-D starting point. local_search_options : dict, optional Backwards compatible flag for `minimizer_kwargs`, only one of these should be supplied. Returns ------- res : OptimizeResult The optimization result represented as a `OptimizeResult` object. Important attributes are: ``x`` the solution array, ``fun`` the value of the function at the solution, and ``message`` which describes the cause of the termination. See `OptimizeResult` for a description of other attributes. Notes ----- This function implements the Dual Annealing optimization. This stochastic approach derived from [3]_ combines the generalization of CSA (Classical Simulated Annealing) and FSA (Fast Simulated Annealing) [1]_ [2]_ coupled to a strategy for applying a local search on accepted locations [4]_. An alternative implementation of this same algorithm is described in [5]_ and benchmarks are presented in [6]_. This approach introduces an advanced method to refine the solution found by the generalized annealing process. This algorithm uses a distorted Cauchy-Lorentz visiting distribution, with its shape controlled by the parameter :math:`q_{v}` .. math:: g_{q_{v}}(\\Delta x(t)) \\propto \\frac{ \\ \\left[T_{q_{v}}(t) \\right]^{-\\frac{D}{3-q_{v}}}}{ \\ \\left[{1+(q_{v}-1)\\frac{(\\Delta x(t))^{2}} { \\ \\left[T_{q_{v}}(t)\\right]^{\\frac{2}{3-q_{v}}}}}\\right]^{ \\ \\frac{1}{q_{v}-1}+\\frac{D-1}{2}}} Where :math:`t` is the artificial time. This visiting distribution is used to generate a trial jump distance :math:`\\Delta x(t)` of variable :math:`x(t)` under artificial temperature :math:`T_{q_{v}}(t)`. From the starting point, after calling the visiting distribution function, the acceptance probability is computed as follows: .. math:: p_{q_{a}} = \\min{\\{1,\\left[1-(1-q_{a}) \\beta \\Delta E \\right]^{ \\ \\frac{1}{1-q_{a}}}\\}} Where :math:`q_{a}` is a acceptance parameter. For :math:`q_{a}<1`, zero acceptance probability is assigned to the cases where .. math:: [1-(1-q_{a}) \\beta \\Delta E] < 0 The artificial temperature :math:`T_{q_{v}}(t)` is decreased according to .. math:: T_{q_{v}}(t) = T_{q_{v}}(1) \\frac{2^{q_{v}-1}-1}{\\left( \\ 1 + t\\right)^{q_{v}-1}-1} Where :math:`q_{v}` is the visiting parameter. .. versionadded:: 1.2.0 References ---------- .. [1] Tsallis C. Possible generalization of Boltzmann-Gibbs statistics. Journal of Statistical Physics, 52, 479-487 (1998). .. [2] Tsallis C, Stariolo DA. Generalized Simulated Annealing. Physica A, 233, 395-406 (1996). .. [3] Xiang Y, Sun DY, Fan W, Gong XG. Generalized Simulated Annealing Algorithm and Its Application to the Thomson Model. Physics Letters A, 233, 216-220 (1997). .. [4] Xiang Y, Gong XG. Efficiency of Generalized Simulated Annealing. Physical Review E, 62, 4473 (2000). .. [5] Xiang Y, Gubian S, Suomela B, Hoeng J. Generalized Simulated Annealing for Efficient Global Optimization: the GenSA Package for R. The R Journal, Volume 5/1 (2013). .. [6] Mullen, K. Continuous Global Optimization in R. Journal of Statistical Software, 60(6), 1 - 45, (2014). :doi:`10.18637/jss.v060.i06` Examples -------- The following example is a 10-D problem, with many local minima. The function involved is called Rastrigin (https://en.wikipedia.org/wiki/Rastrigin_function) >>> from scipy.optimize import dual_annealing >>> func = lambda x: np.sum(x*x - 10*np.cos(2*np.pi*x)) + 10*np.size(x) >>> lw = [-5.12] * 10 >>> up = [5.12] * 10 >>> ret = dual_annealing(func, bounds=list(zip(lw, up))) >>> ret.x array([-4.26437714e-09, -3.91699361e-09, -1.86149218e-09, -3.97165720e-09, -6.29151648e-09, -6.53145322e-09, -3.93616815e-09, -6.55623025e-09, -6.05775280e-09, -5.00668935e-09]) # random >>> ret.fun 0.000000 """ if isinstance(bounds, Bounds): bounds = new_bounds_to_old(bounds.lb, bounds.ub, len(bounds.lb)) # noqa: E501 if x0 is not None and not len(x0) == len(bounds): raise ValueError('Bounds size does not match x0') lu = list(zip(*bounds)) lower = np.array(lu[0]) upper = np.array(lu[1]) # Check that restart temperature ratio is correct if restart_temp_ratio <= 0. or restart_temp_ratio >= 1.: raise ValueError('Restart temperature ratio has to be in range (0, 1)') # Checking bounds are valid if (np.any(np.isinf(lower)) or np.any(np.isinf(upper)) or np.any(np.isnan(lower)) or np.any(np.isnan(upper))): raise ValueError('Some bounds values are inf values or nan values') # Checking that bounds are consistent if not np.all(lower < upper): raise ValueError('Bounds are not consistent min < max') # Checking that bounds are the same length if not len(lower) == len(upper): raise ValueError('Bounds do not have the same dimensions') # Wrapper for the objective function func_wrapper = ObjectiveFunWrapper(func, maxfun, *args) # Wrapper for the minimizer if local_search_options and minimizer_kwargs: raise ValueError( "dual_annealing only allows either 'minimizer_kwargs' (preferred) or " "'local_search_options' (deprecated); not both!") if local_search_options is not None: warnings.warn( "dual_annealing argument 'local_search_options' is " "deprecated in favor of 'minimizer_kwargs'", category=DeprecationWarning, stacklevel=2) minimizer_kwargs = local_search_options # minimizer_kwargs has to be a dict, not None minimizer_kwargs = minimizer_kwargs or {} minimizer_wrapper = LocalSearchWrapper(bounds, func_wrapper, **minimizer_kwargs) # Initialization of random Generator for reproducible runs if seed provided rand_state = check_random_state(seed) # Initialization of the energy state energy_state = EnergyState(lower, upper, callback) energy_state.reset(func_wrapper, rand_state, x0) # Minimum value of annealing temperature reached to perform # re-annealing temperature_restart = initial_temp * restart_temp_ratio # VisitingDistribution instance visit_dist = VisitingDistribution(lower, upper, visit, rand_state) # Strategy chain instance strategy_chain = StrategyChain(accept, visit_dist, func_wrapper, minimizer_wrapper, rand_state, energy_state) need_to_stop = False iteration = 0 message = [] # OptimizeResult object to be returned optimize_res = OptimizeResult() optimize_res.success = True optimize_res.status = 0 t1 = np.exp((visit - 1) * np.log(2.0)) - 1.0 # Run the search loop while (not need_to_stop): for i in range(maxiter): # Compute temperature for this step s = float(i) + 2.0 t2 = np.exp((visit - 1) * np.log(s)) - 1.0 temperature = initial_temp * t1 / t2 if iteration >= maxiter: message.append("Maximum number of iteration reached") need_to_stop = True break # Need a re-annealing process? if temperature < temperature_restart: energy_state.reset(func_wrapper, rand_state) break # starting strategy chain val = strategy_chain.run(i, temperature) if val is not None: message.append(val) need_to_stop = True optimize_res.success = False break # Possible local search at the end of the strategy chain if not no_local_search: val = strategy_chain.local_search() if val is not None: message.append(val) need_to_stop = True optimize_res.success = False break iteration += 1 # Setting the OptimizeResult values optimize_res.x = energy_state.xbest optimize_res.fun = energy_state.ebest optimize_res.nit = iteration optimize_res.nfev = func_wrapper.nfev optimize_res.njev = func_wrapper.ngev optimize_res.nhev = func_wrapper.nhev optimize_res.message = message return optimize_res
def __init__(self, func, bounds, config, args=(), strategy='best1bin', maxiter=1000, popsize=15, tol=0.01, mutation=(0.5, 1), recombination=0.7, seed=None, maxfun=np.inf, callback=None, disp=False, polish=True, init='latinhypercube', atol=0, updating='immediate', workers=1): self.config = config if strategy in self._binomial: self.mutation_func = getattr(self, self._binomial[strategy]) elif strategy in self._exponential: self.mutation_func = getattr(self, self._exponential[strategy]) else: raise ValueError("Please select a valid mutation strategy") self.strategy = strategy self.callback = callback self.polish = polish # set the updating / parallelisation options if updating in ['immediate', 'deferred']: self._updating = updating # want to use parallelisation, but updating is immediate if workers != 1 and updating == 'immediate': warnings.warn("differential_evolution: the 'workers' keyword has" " overridden updating='immediate' to" " updating='deferred'", UserWarning) self._updating = 'deferred' # an object with a map method. self._mapwrapper = MapWrapper(workers) # relative and absolute tolerances for convergence self.tol, self.atol = tol, atol # Mutation constant should be in [0, 2). If specified as a sequence # then dithering is performed. self.scale = mutation if (not np.all(np.isfinite(mutation)) or np.any(np.array(mutation) >= 2) or np.any(np.array(mutation) < 0)): raise ValueError('The mutation constant must be a float in ' 'U[0, 2), or specified as a tuple(min, max)' ' where min < max and min, max are in U[0, 2).') self.dither = None if hasattr(mutation, '__iter__') and len(mutation) > 1: self.dither = [mutation[0], mutation[1]] self.dither.sort() self.cross_over_probability = recombination # we create a wrapped function to allow the use of map (and Pool.map # in the future) self.func = _FunctionWrapper(func, args) self.args = args # convert tuple of lower and upper bounds to limits # [(low_0, high_0), ..., (low_n, high_n] # -> [[low_0, ..., low_n], [high_0, ..., high_n]] if isinstance(bounds, Bounds): self.limits = np.array(new_bounds_to_old(bounds.lb, bounds.ub, len(bounds.lb)), dtype=float).T else: self.limits = np.array(bounds, dtype='float').T if (np.size(self.limits, 0) != 2 or not np.all(np.isfinite(self.limits))): raise ValueError('bounds should be a sequence containing ' 'real valued (min, max) pairs for each value' ' in x') if maxiter is None: # the default used to be None maxiter = 1000 self.maxiter = maxiter if maxfun is None: # the default used to be None maxfun = np.inf self.maxfun = maxfun # population is scaled to between [0, 1]. # We have to scale between parameter <-> population # save these arguments for _scale_parameter and # _unscale_parameter. This is an optimization self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1]) self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1]) self.parameter_count = np.size(self.limits, 1) self.random_number_generator = check_random_state(seed) # default population initialization is a latin hypercube design, but # there are other population initializations possible. # the minimum is 5 because 'best2bin' requires a population that's at # least 5 long self.num_population_members = max(5, popsize * self.parameter_count) self.population_shape = (self.num_population_members, self.parameter_count) self._nfev = 0 if isinstance(init, string_types): if init == 'latinhypercube': self.init_population_lhs() elif init == 'random': self.init_population_random() else: raise ValueError(self.__init_error_msg) else: self.init_population_array(init) self.disp = disp self.fx_history = [] self.x_best_history = np.zeros((self.maxiter, self.parameter_count))