_differentialevolution.py 88 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037
  1. """
  2. differential_evolution: The differential evolution global optimization algorithm
  3. Added by Andrew Nelson 2014
  4. """
  5. from functools import partial
  6. import warnings
  7. import numpy as np
  8. from scipy.optimize import OptimizeResult, minimize
  9. from scipy.optimize._constraints import (Bounds, new_bounds_to_old,
  10. NonlinearConstraint, LinearConstraint)
  11. from scipy.optimize._optimize import _status_message, _wrap_callback
  12. from scipy._lib._util import (check_random_state, MapWrapper, _FunctionWrapper,
  13. rng_integers, _transition_to_rng)
  14. from scipy._lib._sparse import issparse
  15. __all__ = ['differential_evolution']
  16. _MACHEPS = np.finfo(np.float64).eps
  17. @_transition_to_rng("seed", position_num=9)
  18. def differential_evolution(func, bounds, args=(), strategy='best1bin',
  19. maxiter=1000, popsize=15, tol=0.01,
  20. mutation=(0.5, 1), recombination=0.7, rng=None,
  21. callback=None, disp=False, polish=True,
  22. init='latinhypercube', atol=0, updating='immediate',
  23. workers=1, constraints=(), x0=None, *,
  24. integrality=None, vectorized=False):
  25. r"""Finds the global minimum of a multivariate function.
  26. The differential evolution method [1]_ is stochastic in nature. It does
  27. not use gradient methods to find the minimum, and can search large areas
  28. of candidate space, but often requires larger numbers of function
  29. evaluations than conventional gradient-based techniques.
  30. The algorithm is due to Storn and Price [2]_.
  31. Parameters
  32. ----------
  33. func : callable
  34. The objective function to be minimized. Must be in the form
  35. ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array
  36. and ``args`` is a tuple of any additional fixed parameters needed to
  37. completely specify the function. The number of parameters, N, is equal
  38. to ``len(x)``.
  39. bounds : sequence or `Bounds`
  40. Bounds for variables. There are two ways to specify the bounds:
  41. 1. Instance of `Bounds` class.
  42. 2. ``(min, max)`` pairs for each element in ``x``, defining the
  43. finite lower and upper bounds for the optimizing argument of
  44. `func`.
  45. The total number of bounds is used to determine the number of
  46. parameters, N. If there are parameters whose bounds are equal the total
  47. number of free parameters is ``N - N_equal``.
  48. args : tuple, optional
  49. Any additional fixed parameters needed to
  50. completely specify the objective function.
  51. strategy : {str, callable}, optional
  52. The differential evolution strategy to use. Should be one of:
  53. - 'best1bin'
  54. - 'best1exp'
  55. - 'rand1bin'
  56. - 'rand1exp'
  57. - 'rand2bin'
  58. - 'rand2exp'
  59. - 'randtobest1bin'
  60. - 'randtobest1exp'
  61. - 'currenttobest1bin'
  62. - 'currenttobest1exp'
  63. - 'best2exp'
  64. - 'best2bin'
  65. The default is 'best1bin'. Strategies that may be implemented are
  66. outlined in 'Notes'.
  67. Alternatively the differential evolution strategy can be customized by
  68. providing a callable that constructs a trial vector. The callable must
  69. have the form ``strategy(candidate: int, population: np.ndarray, rng=None)``,
  70. where ``candidate`` is an integer specifying which entry of the
  71. population is being evolved, ``population`` is an array of shape
  72. ``(S, N)`` containing all the population members (where S is the
  73. total population size), and ``rng`` is the random number generator
  74. being used within the solver.
  75. ``candidate`` will be in the range ``[0, S)``.
  76. ``strategy`` must return a trial vector with shape ``(N,)``. The
  77. fitness of this trial vector is compared against the fitness of
  78. ``population[candidate]``.
  79. .. versionchanged:: 1.12.0
  80. Customization of evolution strategy via a callable.
  81. maxiter : int, optional
  82. The maximum number of generations over which the entire population is
  83. evolved. The maximum number of function evaluations (with no polishing)
  84. is: ``(maxiter + 1) * popsize * (N - N_equal)``
  85. popsize : int, optional
  86. A multiplier for setting the total population size. The population has
  87. ``popsize * (N - N_equal)`` individuals. This keyword is overridden if
  88. an initial population is supplied via the `init` keyword. When using
  89. ``init='sobol'`` the population size is calculated as the next power
  90. of 2 after ``popsize * (N - N_equal)``.
  91. tol : float, optional
  92. Relative tolerance for convergence, the solving stops when
  93. ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``,
  94. where and `atol` and `tol` are the absolute and relative tolerance
  95. respectively.
  96. mutation : float or tuple(float, float), optional
  97. The mutation constant. In the literature this is also known as
  98. differential weight, being denoted by :math:`F`.
  99. If specified as a float it should be in the range [0, 2).
  100. If specified as a tuple ``(min, max)`` dithering is employed. Dithering
  101. randomly changes the mutation constant on a generation by generation
  102. basis. The mutation constant for that generation is taken from
  103. ``U[min, max)``. Dithering can help speed convergence significantly.
  104. Increasing the mutation constant increases the search radius, but will
  105. slow down convergence.
  106. recombination : float, optional
  107. The recombination constant, should be in the range [0, 1]. In the
  108. literature this is also known as the crossover probability, being
  109. denoted by CR. Increasing this value allows a larger number of mutants
  110. to progress into the next generation, but at the risk of population
  111. stability.
  112. rng : `numpy.random.Generator`, optional
  113. Pseudorandom number generator state. When `rng` is None, a new
  114. `numpy.random.Generator` is created using entropy from the
  115. operating system. Types other than `numpy.random.Generator` are
  116. passed to `numpy.random.default_rng` to instantiate a ``Generator``.
  117. disp : bool, optional
  118. Prints the evaluated `func` at every iteration.
  119. callback : callable, optional
  120. A callable called after each iteration. Has the signature::
  121. callback(intermediate_result: OptimizeResult)
  122. where ``intermediate_result`` is a keyword parameter containing an
  123. `OptimizeResult` with attributes ``x`` and ``fun``, the best solution
  124. found so far and the objective function. Note that the name
  125. of the parameter must be ``intermediate_result`` for the callback
  126. to be passed an `OptimizeResult`.
  127. The callback also supports a signature like::
  128. callback(x, convergence: float=val)
  129. ``val`` represents the fractional value of the population convergence.
  130. When ``val`` is greater than ``1.0``, the function halts.
  131. Introspection is used to determine which of the signatures is invoked.
  132. Global minimization will halt if the callback raises ``StopIteration``
  133. or returns ``True``; any polishing is still carried out.
  134. .. versionchanged:: 1.12.0
  135. callback accepts the ``intermediate_result`` keyword.
  136. polish : {bool, callable}, optional
  137. If True (default), then `scipy.optimize.minimize` with the `L-BFGS-B`
  138. method is used to polish the best population member at the end, which
  139. can improve the minimization slightly. If a constrained problem is
  140. being studied then the `trust-constr` method is used instead. For large
  141. problems with many constraints, polishing can take a long time due to
  142. the Jacobian computations.
  143. Alternatively supply a callable that has a `minimize`-like signature,
  144. ``polish_func(func, x0, **kwds)`` and returns an `OptimizeResult`. This
  145. allows the user to have fine control over how the polishing occurs.
  146. `bounds` and `constraints` will be present in ``kwds``. Extra keywords
  147. could be supplied to `polish_func` using `functools.partial`. It is the
  148. user's responsibility to ensure that the polishing function obeys
  149. bounds, any constraints (including integrality constraints), and that
  150. appropriate attributes are set in the `OptimizeResult`, such as ``fun``,
  151. ``x``, ``nfev``, ``jac``.
  152. .. versionchanged:: 1.15.0
  153. If `workers` is specified then the map-like callable that wraps
  154. `func` is supplied to `minimize` instead of it using `func`
  155. directly. This allows the caller to control how and where the
  156. invocations actually run.
  157. .. versionchanged:: 1.17.0
  158. A callable obeying the `minimize` signature can be supplied to
  159. polish the best population member.
  160. init : str or array-like, optional
  161. Specify which type of population initialization is performed. Should be
  162. one of:
  163. - 'latinhypercube'
  164. - 'sobol'
  165. - 'halton'
  166. - 'random'
  167. - array specifying the initial population. The array should have
  168. shape ``(S, N)``, where S is the total population size and N is
  169. the number of parameters.
  170. `init` is clipped to `bounds` before use.
  171. The default is 'latinhypercube'. Latin Hypercube sampling tries to
  172. maximize coverage of the available parameter space.
  173. 'sobol' and 'halton' are superior alternatives and maximize even more
  174. the parameter space. 'sobol' will enforce an initial population
  175. size which is calculated as the next power of 2 after
  176. ``popsize * (N - N_equal)``. 'halton' has no requirements but is a bit
  177. less efficient. See `scipy.stats.qmc` for more details.
  178. 'random' initializes the population randomly - this has the drawback
  179. that clustering can occur, preventing the whole of parameter space
  180. being covered. Use of an array to specify a population could be used,
  181. for example, to create a tight bunch of initial guesses in an location
  182. where the solution is known to exist, thereby reducing time for
  183. convergence.
  184. atol : float, optional
  185. Absolute tolerance for convergence, the solving stops when
  186. ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``,
  187. where and `atol` and `tol` are the absolute and relative tolerance
  188. respectively.
  189. updating : {'immediate', 'deferred'}, optional
  190. If ``'immediate'``, the best solution vector is continuously updated
  191. within a single generation [4]_. This can lead to faster convergence as
  192. trial vectors can take advantage of continuous improvements in the best
  193. solution.
  194. With ``'deferred'``, the best solution vector is updated once per
  195. generation. Only ``'deferred'`` is compatible with parallelization or
  196. vectorization, and the `workers` and `vectorized` keywords can
  197. over-ride this option.
  198. .. versionadded:: 1.2.0
  199. workers : int or map-like callable, optional
  200. If `workers` is an int the population is subdivided into `workers`
  201. sections and evaluated in parallel
  202. (uses `multiprocessing.Pool <multiprocessing>`).
  203. Supply -1 to use all available CPU cores.
  204. Alternatively supply a map-like callable, such as
  205. `multiprocessing.Pool.map` for evaluating the population in parallel.
  206. This evaluation is carried out as ``workers(func, iterable)``.
  207. This option will override the `updating` keyword to
  208. ``updating='deferred'`` if ``workers != 1``.
  209. This option overrides the `vectorized` keyword if ``workers != 1``.
  210. Requires that `func` be pickleable.
  211. .. versionadded:: 1.2.0
  212. constraints : {NonLinearConstraint, LinearConstraint, Bounds}
  213. Constraints on the solver, over and above those applied by the `bounds`
  214. kwd. Uses the approach by Lampinen [5]_.
  215. .. versionadded:: 1.4.0
  216. x0 : None or array-like, optional
  217. Provides an initial guess to the minimization. Once the population has
  218. been initialized this vector replaces the first (best) member. This
  219. replacement is done even if `init` is given an initial population.
  220. ``x0.shape == (N,)``.
  221. .. versionadded:: 1.7.0
  222. integrality : 1-D array, optional
  223. For each decision variable, a boolean value indicating whether the
  224. decision variable is constrained to integer values. The array is
  225. broadcast to ``(N,)``.
  226. If any decision variables are constrained to be integral, they will not
  227. be changed during polishing.
  228. Only integer values lying between the lower and upper bounds are used.
  229. If there are no integer values lying between the bounds then a
  230. `ValueError` is raised.
  231. .. versionadded:: 1.9.0
  232. vectorized : bool, optional
  233. If ``vectorized is True``, `func` is sent an `x` array with
  234. ``x.shape == (N, S)``, and is expected to return an array of shape
  235. ``(S,)``, where `S` is the number of solution vectors to be calculated.
  236. If constraints are applied, each of the functions used to construct
  237. a `Constraint` object should accept an `x` array with
  238. ``x.shape == (N, S)``, and return an array of shape ``(M, S)``, where
  239. `M` is the number of constraint components.
  240. This option is an alternative to the parallelization offered by
  241. `workers`, and may help in optimization speed by reducing interpreter
  242. overhead from multiple function calls. This keyword is ignored if
  243. ``workers != 1``.
  244. This option will override the `updating` keyword to
  245. ``updating='deferred'``.
  246. See the notes section for further discussion on when to use
  247. ``'vectorized'``, and when to use ``'workers'``.
  248. .. versionadded:: 1.9.0
  249. Returns
  250. -------
  251. res : OptimizeResult
  252. The optimization result represented as a `OptimizeResult` object.
  253. Important attributes are: ``x`` the solution array, ``success`` a
  254. Boolean flag indicating if the optimizer exited successfully,
  255. ``message`` which describes the cause of the termination,
  256. ``population`` the solution vectors present in the population, and
  257. ``population_energies`` the value of the objective function for each
  258. entry in ``population``.
  259. See `OptimizeResult` for a description of other attributes. If `polish`
  260. was employed, and a lower minimum was obtained by the polishing, then
  261. OptimizeResult also contains the ``jac`` attribute.
  262. If the eventual solution does not satisfy the applied constraints
  263. ``success`` will be `False`.
  264. Notes
  265. -----
  266. Differential evolution is a stochastic population based method that is
  267. useful for global optimization problems. At each pass through the
  268. population the algorithm mutates each candidate solution by mixing with
  269. other candidate solutions to create a trial candidate. There are several
  270. strategies [3]_ for creating trial candidates, which suit some problems
  271. more than others. The 'best1bin' strategy is a good starting point for
  272. many systems. In this strategy two members of the population are randomly
  273. chosen. Their difference is used to mutate the best member (the 'best' in
  274. 'best1bin'), :math:`x_0`, so far:
  275. .. math::
  276. b' = x_0 + F \cdot (x_{r_0} - x_{r_1})
  277. where :math:`F` is the `mutation` parameter.
  278. A trial vector is then constructed. Starting with a randomly chosen ith
  279. parameter the trial is sequentially filled (in modulo) with parameters
  280. from ``b'`` or the original candidate. The choice of whether to use ``b'``
  281. or the original candidate is made with a binomial distribution (the 'bin'
  282. in 'best1bin') - a random number in [0, 1) is generated. If this number is
  283. less than the `recombination` constant then the parameter is loaded from
  284. ``b'``, otherwise it is loaded from the original candidate. A randomly
  285. selected parameter is always loaded from ``b'``. For binomial crossover,
  286. this is a single random parameter. For exponential crossover, this is the
  287. starting point of a consecutive sequence of parameters from ``b'``. Once
  288. the trial candidate is built its fitness is assessed. If the trial is
  289. better than the original candidate then it takes its place. If it is
  290. also better than the best overall candidate it also replaces that.
  291. The other strategies available are outlined in Qiang and
  292. Mitchell (2014) [3]_.
  293. - ``rand1`` : :math:`b' = x_{r_0} + F \cdot (x_{r_1} - x_{r_2})`
  294. - ``rand2`` : :math:`b' = x_{r_0} + F \cdot (x_{r_1} + x_{r_2} - x_{r_3} - x_{r_4})`
  295. - ``best1`` : :math:`b' = x_0 + F \cdot (x_{r_0} - x_{r_1})`
  296. - ``best2`` : :math:`b' = x_0 + F \cdot (x_{r_0} + x_{r_1} - x_{r_2} - x_{r_3})`
  297. - ``currenttobest1`` : :math:`b' = x_i + F \cdot (x_0 - x_i + x_{r_0} - x_{r_1})`
  298. - ``randtobest1`` : :math:`b' = x_{r_0} + F \cdot (x_0 - x_{r_0} + x_{r_1} - x_{r_2})`
  299. where the integers :math:`r_0, r_1, r_2, r_3, r_4` are chosen randomly
  300. from the interval [0, NP) with `NP` being the total population size and
  301. the original candidate having index `i`. The user can fully customize the
  302. generation of the trial candidates by supplying a callable to ``strategy``.
  303. To improve your chances of finding a global minimum use higher `popsize`
  304. values, with higher `mutation` and (dithering), but lower `recombination`
  305. values. This has the effect of widening the search radius, but slowing
  306. convergence.
  307. By default the best solution vector is updated continuously within a single
  308. iteration (``updating='immediate'``). This is a modification [4]_ of the
  309. original differential evolution algorithm which can lead to faster
  310. convergence as trial vectors can immediately benefit from improved
  311. solutions. To use the original Storn and Price behaviour, updating the best
  312. solution once per iteration, set ``updating='deferred'``.
  313. The ``'deferred'`` approach is compatible with both parallelization and
  314. vectorization (``'workers'`` and ``'vectorized'`` keywords). These may
  315. improve minimization speed by using computer resources more efficiently.
  316. The ``'workers'`` distribute calculations over multiple processors. By
  317. default the Python `multiprocessing` module is used, but other approaches
  318. are also possible, such as the Message Passing Interface (MPI) used on
  319. clusters [6]_ [7]_. The overhead from these approaches (creating new
  320. Processes, etc) may be significant, meaning that computational speed
  321. doesn't necessarily scale with the number of processors used.
  322. Parallelization is best suited to computationally expensive objective
  323. functions. If the objective function is less expensive, then
  324. ``'vectorized'`` may aid by only calling the objective function once per
  325. iteration, rather than multiple times for all the population members; the
  326. interpreter overhead is reduced.
  327. .. versionadded:: 0.15.0
  328. References
  329. ----------
  330. .. [1] Differential evolution, Wikipedia,
  331. http://en.wikipedia.org/wiki/Differential_evolution
  332. .. [2] Storn, R and Price, K, Differential Evolution - a Simple and
  333. Efficient Heuristic for Global Optimization over Continuous Spaces,
  334. Journal of Global Optimization, 1997, 11, 341 - 359.
  335. .. [3] Qiang, J., Mitchell, C., A Unified Differential Evolution Algorithm
  336. for Global Optimization, 2014, https://www.osti.gov/servlets/purl/1163659
  337. .. [4] Wormington, M., Panaccione, C., Matney, K. M., Bowen, D. K., -
  338. Characterization of structures from X-ray scattering data using
  339. genetic algorithms, Phil. Trans. R. Soc. Lond. A, 1999, 357,
  340. 2827-2848
  341. .. [5] Lampinen, J., A constraint handling approach for the differential
  342. evolution algorithm. Proceedings of the 2002 Congress on
  343. Evolutionary Computation. CEC'02 (Cat. No. 02TH8600). Vol. 2. IEEE,
  344. 2002.
  345. .. [6] https://mpi4py.readthedocs.io/en/stable/
  346. .. [7] https://schwimmbad.readthedocs.io/en/latest/
  347. Examples
  348. --------
  349. Let us consider the problem of minimizing the Rosenbrock function. This
  350. function is implemented in `rosen` in `scipy.optimize`.
  351. >>> import numpy as np
  352. >>> from scipy.optimize import rosen, differential_evolution
  353. >>> bounds = [(0,2), (0, 2), (0, 2), (0, 2), (0, 2)]
  354. >>> result = differential_evolution(rosen, bounds)
  355. >>> result.x, result.fun
  356. (array([1., 1., 1., 1., 1.]), 1.9216496320061384e-19)
  357. Now repeat, but with parallelization.
  358. >>> result = differential_evolution(rosen, bounds, updating='deferred',
  359. ... workers=2)
  360. >>> result.x, result.fun
  361. (array([1., 1., 1., 1., 1.]), 1.9216496320061384e-19)
  362. Let's do a constrained minimization.
  363. >>> from scipy.optimize import LinearConstraint, Bounds
  364. We add the constraint that the sum of ``x[0]`` and ``x[1]`` must be less
  365. than or equal to 1.9. This is a linear constraint, which may be written
  366. ``A @ x <= 1.9``, where ``A = array([[1, 1]])``. This can be encoded as
  367. a `LinearConstraint` instance:
  368. >>> lc = LinearConstraint([[1, 1]], -np.inf, 1.9)
  369. Specify limits using a `Bounds` object.
  370. >>> bounds = Bounds([0., 0.], [2., 2.])
  371. >>> result = differential_evolution(rosen, bounds, constraints=lc,
  372. ... rng=1)
  373. >>> result.x, result.fun
  374. (array([0.96632622, 0.93367155]), 0.0011352416852625719)
  375. Next find the minimum of the Ackley function
  376. (https://en.wikipedia.org/wiki/Test_functions_for_optimization).
  377. >>> def ackley(x):
  378. ... arg1 = -0.2 * np.sqrt(0.5 * (x[0] ** 2 + x[1] ** 2))
  379. ... arg2 = 0.5 * (np.cos(2. * np.pi * x[0]) + np.cos(2. * np.pi * x[1]))
  380. ... return -20. * np.exp(arg1) - np.exp(arg2) + 20. + np.e
  381. >>> bounds = [(-5, 5), (-5, 5)]
  382. >>> result = differential_evolution(ackley, bounds, rng=1)
  383. >>> result.x, result.fun
  384. (array([0., 0.]), 4.440892098500626e-16)
  385. The Ackley function is written in a vectorized manner, so the
  386. ``'vectorized'`` keyword can be employed. Note the reduced number of
  387. function evaluations.
  388. >>> result = differential_evolution(
  389. ... ackley, bounds, vectorized=True, updating='deferred', rng=1
  390. ... )
  391. >>> result.x, result.fun
  392. (array([0., 0.]), 4.440892098500626e-16)
  393. The final polishing step can be customised by providing a callable that
  394. mimics the `minimize` interface. The user is responsible for ensuring that
  395. the minimizer obeys any bounds and constraints.
  396. >>> from functools import partial
  397. >>> from scipy.optimize import minimize
  398. >>> # supply extra parameters to the polishing function using partial
  399. >>> polish_func = partial(minimize, method="SLSQP")
  400. >>> result = differential_evolution(
  401. ... ackley, bounds, vectorized=True, updating='deferred', rng=1,
  402. ... polish=polish_func
  403. ... )
  404. >>> result.x, result.fun
  405. (array([0., 0.]), 4.440892098500626e-16)
  406. The following custom strategy function mimics 'best1bin':
  407. >>> def custom_strategy_fn(candidate, population, rng=None):
  408. ... parameter_count = population.shape[-1]
  409. ... mutation, recombination = 0.7, 0.9
  410. ... trial = np.copy(population[candidate])
  411. ... fill_point = rng.choice(parameter_count)
  412. ...
  413. ... pool = np.arange(len(population))
  414. ... rng.shuffle(pool)
  415. ...
  416. ... # two unique random numbers that aren't the same, and
  417. ... # aren't equal to candidate.
  418. ... idxs = []
  419. ... while len(idxs) < 2 and len(pool) > 0:
  420. ... idx = pool[0]
  421. ... pool = pool[1:]
  422. ... if idx != candidate:
  423. ... idxs.append(idx)
  424. ...
  425. ... r0, r1 = idxs[:2]
  426. ...
  427. ... bprime = (population[0] + mutation *
  428. ... (population[r0] - population[r1]))
  429. ...
  430. ... crossovers = rng.uniform(size=parameter_count)
  431. ... crossovers = crossovers < recombination
  432. ... crossovers[fill_point] = True
  433. ... trial = np.where(crossovers, bprime, trial)
  434. ... return trial
  435. """# noqa: E501
  436. # using a context manager means that any created Pool objects are
  437. # cleared up.
  438. with DifferentialEvolutionSolver(func, bounds, args=args,
  439. strategy=strategy,
  440. maxiter=maxiter,
  441. popsize=popsize, tol=tol,
  442. mutation=mutation,
  443. recombination=recombination,
  444. rng=rng, polish=polish,
  445. callback=callback,
  446. disp=disp, init=init, atol=atol,
  447. updating=updating,
  448. workers=workers,
  449. constraints=constraints,
  450. x0=x0,
  451. integrality=integrality,
  452. vectorized=vectorized) as solver:
  453. ret = solver.solve()
  454. return ret
  455. class DifferentialEvolutionSolver:
  456. """This class implements the differential evolution solver
  457. Parameters
  458. ----------
  459. func : callable
  460. The objective function to be minimized. Must be in the form
  461. ``f(x, *args)``, where ``x`` is the argument in the form of a 1-D array
  462. and ``args`` is a tuple of any additional fixed parameters needed to
  463. completely specify the function. The number of parameters, N, is equal
  464. to ``len(x)``.
  465. bounds : sequence or `Bounds`
  466. Bounds for variables. There are two ways to specify the bounds:
  467. 1. Instance of `Bounds` class.
  468. 2. ``(min, max)`` pairs for each element in ``x``, defining the
  469. finite lower and upper bounds for the optimizing argument of
  470. `func`.
  471. The total number of bounds is used to determine the number of
  472. parameters, N. If there are parameters whose bounds are equal the total
  473. number of free parameters is ``N - N_equal``.
  474. args : tuple, optional
  475. Any additional fixed parameters needed to
  476. completely specify the objective function.
  477. strategy : {str, callable}, optional
  478. The differential evolution strategy to use. Should be one of:
  479. - 'best1bin'
  480. - 'best1exp'
  481. - 'rand1bin'
  482. - 'rand1exp'
  483. - 'rand2bin'
  484. - 'rand2exp'
  485. - 'randtobest1bin'
  486. - 'randtobest1exp'
  487. - 'currenttobest1bin'
  488. - 'currenttobest1exp'
  489. - 'best2exp'
  490. - 'best2bin'
  491. The default is 'best1bin'. Strategies that may be
  492. implemented are outlined in 'Notes'.
  493. Alternatively the differential evolution strategy can be customized
  494. by providing a callable that constructs a trial vector. The callable
  495. must have the form
  496. ``strategy(candidate: int, population: np.ndarray, rng=None)``,
  497. where ``candidate`` is an integer specifying which entry of the
  498. population is being evolved, ``population`` is an array of shape
  499. ``(S, N)`` containing all the population members (where S is the
  500. total population size), and ``rng`` is the random number generator
  501. being used within the solver.
  502. ``candidate`` will be in the range ``[0, S)``.
  503. ``strategy`` must return a trial vector with shape ``(N,)``. The
  504. fitness of this trial vector is compared against the fitness of
  505. ``population[candidate]``.
  506. maxiter : int, optional
  507. The maximum number of generations over which the entire population is
  508. evolved. The maximum number of function evaluations (with no polishing)
  509. is: ``(maxiter + 1) * popsize * (N - N_equal)``
  510. popsize : int, optional
  511. A multiplier for setting the total population size. The population has
  512. ``popsize * (N - N_equal)`` individuals. This keyword is overridden if
  513. an initial population is supplied via the `init` keyword. When using
  514. ``init='sobol'`` the population size is calculated as the next power
  515. of 2 after ``popsize * (N - N_equal)``.
  516. tol : float, optional
  517. Relative tolerance for convergence, the solving stops when
  518. ``np.std(population_energies) <= atol + tol * np.abs(np.mean(population_energies))``,
  519. where and `atol` and `tol` are the absolute and relative tolerance
  520. respectively.
  521. mutation : float or tuple(float, float), optional
  522. The mutation constant. In the literature this is also known as
  523. differential weight, being denoted by F.
  524. If specified as a float it should be in the range [0, 2].
  525. If specified as a tuple ``(min, max)`` dithering is employed. Dithering
  526. randomly changes the mutation constant on a generation by generation
  527. basis. The mutation constant for that generation is taken from
  528. U[min, max). Dithering can help speed convergence significantly.
  529. Increasing the mutation constant increases the search radius, but will
  530. slow down convergence.
  531. recombination : float, optional
  532. The recombination constant, should be in the range [0, 1]. In the
  533. literature this is also known as the crossover probability, being
  534. denoted by CR. Increasing this value allows a larger number of mutants
  535. to progress into the next generation, but at the risk of population
  536. stability.
  537. rng : {None, int, `numpy.random.Generator`}, optional
  538. ..versionchanged:: 1.15.0
  539. As part of the `SPEC-007 <https://scientific-python.org/specs/spec-0007/>`_
  540. transition from use of `numpy.random.RandomState` to
  541. `numpy.random.Generator` this keyword was changed from `seed` to `rng`.
  542. For an interim period both keywords will continue to work (only specify
  543. one of them). After the interim period using the `seed` keyword will emit
  544. warnings. The behavior of the `seed` and `rng` keywords is outlined below.
  545. If `rng` is passed by keyword, types other than `numpy.random.Generator` are
  546. passed to `numpy.random.default_rng` to instantiate a `Generator`.
  547. If `rng` is already a `Generator` instance, then the provided instance is
  548. used.
  549. If this argument is passed by position or `seed` is passed by keyword, the
  550. behavior is:
  551. - If `seed` is None (or `np.random`), the `numpy.random.RandomState`
  552. singleton is used.
  553. - If `seed` is an int, a new `RandomState` instance is used,
  554. seeded with `seed`.
  555. - If `seed` is already a `Generator` or `RandomState` instance then
  556. that instance is used.
  557. Specify `seed`/`rng` for repeatable minimizations.
  558. disp : bool, optional
  559. Prints the evaluated `func` at every iteration.
  560. callback : callable, optional
  561. A callable called after each iteration. Has the signature:
  562. ``callback(intermediate_result: OptimizeResult)``
  563. where ``intermediate_result`` is a keyword parameter containing an
  564. `OptimizeResult` with attributes ``x`` and ``fun``, the best solution
  565. found so far and the objective function. Note that the name
  566. of the parameter must be ``intermediate_result`` for the callback
  567. to be passed an `OptimizeResult`.
  568. The callback also supports a signature like:
  569. ``callback(x, convergence: float=val)``
  570. ``val`` represents the fractional value of the population convergence.
  571. When ``val`` is greater than ``1.0``, the function halts.
  572. Introspection is used to determine which of the signatures is invoked.
  573. Global minimization will halt if the callback raises ``StopIteration``
  574. or returns ``True``; any polishing is still carried out.
  575. .. versionchanged:: 1.12.0
  576. callback accepts the ``intermediate_result`` keyword.
  577. polish : {bool, callable}, optional
  578. If True (default), then `scipy.optimize.minimize` with the `L-BFGS-B`
  579. method is used to polish the best population member at the end, which
  580. can improve the minimization slightly. If a constrained problem is
  581. being studied then the `trust-constr` method is used instead. For large
  582. problems with many constraints, polishing can take a long time due to
  583. the Jacobian computations.
  584. Alternatively supply a callable that has a `minimize`-like signature,
  585. ``polish_func(func, x0, **kwds)`` and returns an `OptimizeResult`. This
  586. allows the user to have fine control over how the polishing occurs.
  587. `bounds` and `constraints` will be present in ``kwds``. Extra keywords
  588. could be supplied to `polish_func` using `functools.partial`. It is the
  589. user's responsibility to ensure that the polishing function obeys
  590. bounds, any constraints (including integrality constraints), and that
  591. appropriate attributes are set in the `OptimizeResult`, such as ``fun``,
  592. ```x``, ``nfev``, ``jac``.
  593. maxfun : int, optional
  594. Set the maximum number of function evaluations. However, it probably
  595. makes more sense to set `maxiter` instead.
  596. init : str or array-like, optional
  597. Specify which type of population initialization is performed. Should be
  598. one of:
  599. - 'latinhypercube'
  600. - 'sobol'
  601. - 'halton'
  602. - 'random'
  603. - array specifying the initial population. The array should have
  604. shape ``(S, N)``, where S is the total population size and
  605. N is the number of parameters.
  606. `init` is clipped to `bounds` before use.
  607. The default is 'latinhypercube'. Latin Hypercube sampling tries to
  608. maximize coverage of the available parameter space.
  609. 'sobol' and 'halton' are superior alternatives and maximize even more
  610. the parameter space. 'sobol' will enforce an initial population
  611. size which is calculated as the next power of 2 after
  612. ``popsize * (N - N_equal)``. 'halton' has no requirements but is a bit
  613. less efficient. See `scipy.stats.qmc` for more details.
  614. 'random' initializes the population randomly - this has the drawback
  615. that clustering can occur, preventing the whole of parameter space
  616. being covered. Use of an array to specify a population could be used,
  617. for example, to create a tight bunch of initial guesses in an location
  618. where the solution is known to exist, thereby reducing time for
  619. convergence.
  620. atol : float, optional
  621. Absolute tolerance for convergence, the solving stops when
  622. ``np.std(pop) <= atol + tol * np.abs(np.mean(population_energies))``,
  623. where and `atol` and `tol` are the absolute and relative tolerance
  624. respectively.
  625. updating : {'immediate', 'deferred'}, optional
  626. If ``'immediate'``, the best solution vector is continuously updated
  627. within a single generation [4]_. This can lead to faster convergence as
  628. trial vectors can take advantage of continuous improvements in the best
  629. solution.
  630. With ``'deferred'``, the best solution vector is updated once per
  631. generation. Only ``'deferred'`` is compatible with parallelization or
  632. vectorization, and the `workers` and `vectorized` keywords can
  633. over-ride this option.
  634. workers : int or map-like callable, optional
  635. If `workers` is an int the population is subdivided into `workers`
  636. sections and evaluated in parallel
  637. (uses `multiprocessing.Pool <multiprocessing>`).
  638. Supply `-1` to use all cores available to the Process.
  639. Alternatively supply a map-like callable, such as
  640. `multiprocessing.Pool.map` for evaluating the population in parallel.
  641. This evaluation is carried out as ``workers(func, iterable)``.
  642. This option will override the `updating` keyword to
  643. `updating='deferred'` if `workers != 1`.
  644. Requires that `func` be pickleable.
  645. constraints : {NonLinearConstraint, LinearConstraint, Bounds}
  646. Constraints on the solver, over and above those applied by the `bounds`
  647. kwd. Uses the approach by Lampinen.
  648. x0 : None or array-like, optional
  649. Provides an initial guess to the minimization. Once the population has
  650. been initialized this vector replaces the first (best) member. This
  651. replacement is done even if `init` is given an initial population.
  652. ``x0.shape == (N,)``.
  653. integrality : 1-D array, optional
  654. For each decision variable, a boolean value indicating whether the
  655. decision variable is constrained to integer values. The array is
  656. broadcast to ``(N,)``.
  657. If any decision variables are constrained to be integral, they will not
  658. be changed during polishing.
  659. Only integer values lying between the lower and upper bounds are used.
  660. If there are no integer values lying between the bounds then a
  661. `ValueError` is raised.
  662. vectorized : bool, optional
  663. If ``vectorized is True``, `func` is sent an `x` array with
  664. ``x.shape == (N, S)``, and is expected to return an array of shape
  665. ``(S,)``, where `S` is the number of solution vectors to be calculated.
  666. If constraints are applied, each of the functions used to construct
  667. a `Constraint` object should accept an `x` array with
  668. ``x.shape == (N, S)``, and return an array of shape ``(M, S)``, where
  669. `M` is the number of constraint components.
  670. This option is an alternative to the parallelization offered by
  671. `workers`, and may help in optimization speed. This keyword is
  672. ignored if ``workers != 1``.
  673. This option will override the `updating` keyword to
  674. ``updating='deferred'``.
  675. """ # noqa: E501
  676. # Dispatch of mutation strategy method (binomial or exponential).
  677. _binomial = {'best1bin': '_best1',
  678. 'randtobest1bin': '_randtobest1',
  679. 'currenttobest1bin': '_currenttobest1',
  680. 'best2bin': '_best2',
  681. 'rand2bin': '_rand2',
  682. 'rand1bin': '_rand1'}
  683. _exponential = {'best1exp': '_best1',
  684. 'rand1exp': '_rand1',
  685. 'randtobest1exp': '_randtobest1',
  686. 'currenttobest1exp': '_currenttobest1',
  687. 'best2exp': '_best2',
  688. 'rand2exp': '_rand2'}
  689. __combined = _binomial | _exponential
  690. __init_error_msg = ("The population initialization method must be one of "
  691. "'latinhypercube' or 'random', or an array of shape "
  692. "(S, N) where N is the number of parameters and S>5")
  693. def __init__(self, func, bounds, args=(),
  694. strategy='best1bin', maxiter=1000, popsize=15,
  695. tol=0.01, mutation=(0.5, 1), recombination=0.7, rng=None,
  696. maxfun=np.inf, callback=None, disp=False, polish=True,
  697. init='latinhypercube', atol=0, updating='immediate',
  698. workers=1, constraints=(), x0=None, *, integrality=None,
  699. vectorized=False):
  700. if callable(strategy):
  701. # a callable strategy is going to be stored in self.strategy anyway
  702. pass
  703. elif strategy not in self.__combined:
  704. raise ValueError("Please select a valid mutation strategy")
  705. self.strategy = strategy
  706. self.callback = _wrap_callback(callback, "differential_evolution")
  707. self.polish = polish
  708. # set the updating / parallelisation options
  709. if updating in ['immediate', 'deferred']:
  710. self._updating = updating
  711. self.vectorized = vectorized
  712. # want to use parallelisation, but updating is immediate
  713. if workers != 1 and updating == 'immediate':
  714. warnings.warn("differential_evolution: the 'workers' keyword has"
  715. " overridden updating='immediate' to"
  716. " updating='deferred'", UserWarning, stacklevel=2)
  717. self._updating = 'deferred'
  718. if vectorized and workers != 1:
  719. warnings.warn("differential_evolution: the 'workers' keyword"
  720. " overrides the 'vectorized' keyword", stacklevel=2)
  721. self.vectorized = vectorized = False
  722. if vectorized and updating == 'immediate':
  723. warnings.warn("differential_evolution: the 'vectorized' keyword"
  724. " has overridden updating='immediate' to updating"
  725. "='deferred'", UserWarning, stacklevel=2)
  726. self._updating = 'deferred'
  727. # an object with a map method.
  728. if vectorized:
  729. def maplike_for_vectorized_func(func, x):
  730. # send an array (N, S) to the user func,
  731. # expect to receive (S,). Transposition is required because
  732. # internally the population is held as (S, N)
  733. return np.atleast_1d(func(x.T))
  734. workers = maplike_for_vectorized_func
  735. self._mapwrapper = MapWrapper(workers)
  736. # relative and absolute tolerances for convergence
  737. self.tol, self.atol = tol, atol
  738. # Mutation constant should be in [0, 2). If specified as a sequence
  739. # then dithering is performed.
  740. self.scale = mutation
  741. if (not np.all(np.isfinite(mutation)) or
  742. np.any(np.array(mutation) >= 2) or
  743. np.any(np.array(mutation) < 0)):
  744. raise ValueError('The mutation constant must be a float in '
  745. 'U[0, 2), or specified as a tuple(min, max)'
  746. ' where min < max and min, max are in U[0, 2).')
  747. self.dither = None
  748. if hasattr(mutation, '__iter__') and len(mutation) > 1:
  749. self.dither = [mutation[0], mutation[1]]
  750. self.dither.sort()
  751. self.cross_over_probability = recombination
  752. # we create a wrapped function to allow the use of map (and Pool.map
  753. # in the future)
  754. self.original_func = func
  755. self.func = _FunctionWrapper(func, args)
  756. self.args = args
  757. # convert tuple of lower and upper bounds to limits
  758. # [(low_0, high_0), ..., (low_n, high_n]
  759. # -> [[low_0, ..., low_n], [high_0, ..., high_n]]
  760. if isinstance(bounds, Bounds):
  761. self.limits = np.array(new_bounds_to_old(bounds.lb,
  762. bounds.ub,
  763. len(bounds.lb)),
  764. dtype=float).T
  765. else:
  766. self.limits = np.array(bounds, dtype='float').T
  767. if (np.size(self.limits, 0) != 2 or not
  768. np.all(np.isfinite(self.limits))):
  769. raise ValueError('bounds should be a sequence containing finite '
  770. 'real valued (min, max) pairs for each value'
  771. ' in x')
  772. if maxiter is None: # the default used to be None
  773. maxiter = 1000
  774. self.maxiter = maxiter
  775. if maxfun is None: # the default used to be None
  776. maxfun = np.inf
  777. self.maxfun = maxfun
  778. # population is scaled to between [0, 1].
  779. # We have to scale between parameter <-> population
  780. # save these arguments for _scale_parameter and
  781. # _unscale_parameter. This is an optimization
  782. self.__scale_arg1 = 0.5 * (self.limits[0] + self.limits[1])
  783. self.__scale_arg2 = np.fabs(self.limits[0] - self.limits[1])
  784. with np.errstate(divide='ignore'):
  785. # if lb == ub then the following line will be 1/0, which is why
  786. # we ignore the divide by zero warning. The result from 1/0 is
  787. # inf, so replace those values by 0.
  788. self.__recip_scale_arg2 = 1 / self.__scale_arg2
  789. self.__recip_scale_arg2[~np.isfinite(self.__recip_scale_arg2)] = 0
  790. self.parameter_count = np.size(self.limits, 1)
  791. self.random_number_generator = check_random_state(rng)
  792. # Which parameters are going to be integers?
  793. if np.any(integrality):
  794. # # user has provided a truth value for integer constraints
  795. integrality = np.broadcast_to(
  796. integrality,
  797. self.parameter_count
  798. )
  799. integrality = np.asarray(integrality, bool)
  800. # For integrality parameters change the limits to only allow
  801. # integer values lying between the limits.
  802. lb, ub = np.copy(self.limits)
  803. lb = np.ceil(lb)
  804. ub = np.floor(ub)
  805. if not (lb[integrality] <= ub[integrality]).all():
  806. # there's a parameter that doesn't have an integer value
  807. # lying between the limits
  808. raise ValueError("One of the integrality constraints does not"
  809. " have any possible integer values between"
  810. " the lower/upper bounds.")
  811. nlb = np.nextafter(lb[integrality] - 0.5, np.inf)
  812. nub = np.nextafter(ub[integrality] + 0.5, -np.inf)
  813. self.integrality = integrality
  814. self.limits[0, self.integrality] = nlb
  815. self.limits[1, self.integrality] = nub
  816. else:
  817. self.integrality = False
  818. # check for equal bounds
  819. eb = self.limits[0] == self.limits[1]
  820. eb_count = np.count_nonzero(eb)
  821. # default population initialization is a latin hypercube design, but
  822. # there are other population initializations possible.
  823. # the minimum is 5 because 'best2bin' requires a population that's at
  824. # least 5 long
  825. # 202301 - reduced population size to account for parameters with
  826. # equal bounds. If there are no varying parameters set N to at least 1
  827. self.num_population_members = max(
  828. 5,
  829. popsize * max(1, self.parameter_count - eb_count)
  830. )
  831. self.population_shape = (self.num_population_members,
  832. self.parameter_count)
  833. self._nfev = 0
  834. # check first str otherwise will fail to compare str with array
  835. if isinstance(init, str):
  836. if init == 'latinhypercube':
  837. self.init_population_lhs()
  838. elif init == 'sobol':
  839. # must be Ns = 2**m for Sobol'
  840. n_s = int(2 ** np.ceil(np.log2(self.num_population_members)))
  841. self.num_population_members = n_s
  842. self.population_shape = (self.num_population_members,
  843. self.parameter_count)
  844. self.init_population_qmc(qmc_engine='sobol')
  845. elif init == 'halton':
  846. self.init_population_qmc(qmc_engine='halton')
  847. elif init == 'random':
  848. self.init_population_random()
  849. else:
  850. raise ValueError(self.__init_error_msg)
  851. else:
  852. self.init_population_array(init)
  853. if x0 is not None:
  854. # scale to within unit interval and
  855. # ensure parameters are within bounds.
  856. x0_scaled = self._unscale_parameters(np.asarray(x0))
  857. if ((x0_scaled > 1.0) | (x0_scaled < 0.0)).any():
  858. raise ValueError(
  859. "Some entries in x0 lay outside the specified bounds"
  860. )
  861. self.population[0] = x0_scaled
  862. # infrastructure for constraints
  863. self.constraints = constraints
  864. self._wrapped_constraints = []
  865. if hasattr(constraints, '__len__'):
  866. # sequence of constraints, this will also deal with default
  867. # keyword parameter
  868. for c in constraints:
  869. self._wrapped_constraints.append(
  870. _ConstraintWrapper(c, self.x)
  871. )
  872. else:
  873. self._wrapped_constraints = [
  874. _ConstraintWrapper(constraints, self.x)
  875. ]
  876. self.total_constraints = np.sum(
  877. [c.num_constr for c in self._wrapped_constraints]
  878. )
  879. self.constraint_violation = np.zeros((self.num_population_members, 1))
  880. self.feasible = np.ones(self.num_population_members, bool)
  881. # an array to shuffle when selecting candidates. Create it here
  882. # rather than repeatedly creating it in _select_samples.
  883. self._random_population_index = np.arange(self.num_population_members)
  884. self.disp = disp
  885. @property
  886. def mutation_func(self):
  887. return getattr(self, self.__combined[self.strategy])
  888. def init_population_lhs(self):
  889. """
  890. Initializes the population with Latin Hypercube Sampling.
  891. Latin Hypercube Sampling ensures that each parameter is uniformly
  892. sampled over its range.
  893. """
  894. rng = self.random_number_generator
  895. # Each parameter range needs to be sampled uniformly. The scaled
  896. # parameter range ([0, 1)) needs to be split into
  897. # `self.num_population_members` segments, each of which has the following
  898. # size:
  899. segsize = 1.0 / self.num_population_members
  900. # Within each segment we sample from a uniform random distribution.
  901. # We need to do this sampling for each parameter.
  902. samples = (segsize * rng.uniform(size=self.population_shape)
  903. # Offset each segment to cover the entire parameter range [0, 1)
  904. + np.linspace(0., 1., self.num_population_members,
  905. endpoint=False)[:, np.newaxis])
  906. # Create an array for population of candidate solutions.
  907. self.population = np.zeros_like(samples)
  908. # Initialize population of candidate solutions by permutation of the
  909. # random samples.
  910. for j in range(self.parameter_count):
  911. order = rng.permutation(range(self.num_population_members))
  912. self.population[:, j] = samples[order, j]
  913. # reset population energies
  914. self.population_energies = np.full(self.num_population_members,
  915. np.inf)
  916. # reset number of function evaluations counter
  917. self._nfev = 0
  918. def init_population_qmc(self, qmc_engine):
  919. """Initializes the population with a QMC method.
  920. QMC methods ensures that each parameter is uniformly
  921. sampled over its range.
  922. Parameters
  923. ----------
  924. qmc_engine : str
  925. The QMC method to use for initialization. Can be one of
  926. ``latinhypercube``, ``sobol`` or ``halton``.
  927. """
  928. from scipy.stats import qmc
  929. rng = self.random_number_generator
  930. # Create an array for population of candidate solutions.
  931. if qmc_engine == 'latinhypercube':
  932. sampler = qmc.LatinHypercube(d=self.parameter_count, seed=rng)
  933. elif qmc_engine == 'sobol':
  934. sampler = qmc.Sobol(d=self.parameter_count, seed=rng)
  935. elif qmc_engine == 'halton':
  936. sampler = qmc.Halton(d=self.parameter_count, seed=rng)
  937. else:
  938. raise ValueError(self.__init_error_msg)
  939. self.population = sampler.random(n=self.num_population_members)
  940. # reset population energies
  941. self.population_energies = np.full(self.num_population_members,
  942. np.inf)
  943. # reset number of function evaluations counter
  944. self._nfev = 0
  945. def init_population_random(self):
  946. """
  947. Initializes the population at random. This type of initialization
  948. can possess clustering, Latin Hypercube sampling is generally better.
  949. """
  950. rng = self.random_number_generator
  951. self.population = rng.uniform(size=self.population_shape)
  952. # reset population energies
  953. self.population_energies = np.full(self.num_population_members,
  954. np.inf)
  955. # reset number of function evaluations counter
  956. self._nfev = 0
  957. def init_population_array(self, init):
  958. """
  959. Initializes the population with a user specified population.
  960. Parameters
  961. ----------
  962. init : np.ndarray
  963. Array specifying subset of the initial population. The array should
  964. have shape (S, N), where N is the number of parameters.
  965. The population is clipped to the lower and upper bounds.
  966. """
  967. # make sure you're using a float array
  968. popn = np.asarray(init, dtype=np.float64)
  969. if (np.size(popn, 0) < 5 or
  970. popn.shape[1] != self.parameter_count or
  971. len(popn.shape) != 2):
  972. raise ValueError("The population supplied needs to have shape"
  973. " (S, len(x)), where S > 4.")
  974. # scale values and clip to bounds, assigning to population
  975. self.population = np.clip(self._unscale_parameters(popn), 0, 1)
  976. self.num_population_members = np.size(self.population, 0)
  977. self.population_shape = (self.num_population_members,
  978. self.parameter_count)
  979. # reset population energies
  980. self.population_energies = np.full(self.num_population_members,
  981. np.inf)
  982. # reset number of function evaluations counter
  983. self._nfev = 0
  984. @property
  985. def x(self):
  986. """
  987. The best solution from the solver
  988. """
  989. return self._scale_parameters(self.population[0])
  990. @property
  991. def convergence(self):
  992. """
  993. The standard deviation of the population energies divided by their
  994. mean.
  995. """
  996. if np.any(np.isinf(self.population_energies)):
  997. return np.inf
  998. return (np.std(self.population_energies) /
  999. (np.abs(np.mean(self.population_energies)) + _MACHEPS))
  1000. def converged(self):
  1001. """
  1002. Return True if the solver has converged.
  1003. """
  1004. if np.any(np.isinf(self.population_energies)):
  1005. return False
  1006. return (np.std(self.population_energies) <=
  1007. self.atol +
  1008. self.tol * np.abs(np.mean(self.population_energies)))
  1009. def solve(self):
  1010. """
  1011. Runs the DifferentialEvolutionSolver.
  1012. Returns
  1013. -------
  1014. res : OptimizeResult
  1015. The optimization result represented as a `OptimizeResult` object.
  1016. Important attributes are: ``x`` the solution array, ``success`` a
  1017. Boolean flag indicating if the optimizer exited successfully,
  1018. ``message`` which describes the cause of the termination,
  1019. ``population`` the solution vectors present in the population, and
  1020. ``population_energies`` the value of the objective function for
  1021. each entry in ``population``.
  1022. See `OptimizeResult` for a description of other attributes. If
  1023. `polish` was employed, and a lower minimum was obtained by the
  1024. polishing, then OptimizeResult also contains the ``jac`` attribute.
  1025. If the eventual solution does not satisfy the applied constraints
  1026. ``success`` will be `False`.
  1027. """
  1028. nit, warning_flag = 0, False
  1029. status_message = _status_message['success']
  1030. # The population may have just been initialized (all entries are
  1031. # np.inf). If it has you have to calculate the initial energies.
  1032. # Although this is also done in the evolve generator it's possible
  1033. # that someone can set maxiter=0, at which point we still want the
  1034. # initial energies to be calculated (the following loop isn't run).
  1035. if np.all(np.isinf(self.population_energies)):
  1036. self.feasible, self.constraint_violation = (
  1037. self._calculate_population_feasibilities(self.population))
  1038. # only work out population energies for feasible solutions
  1039. self.population_energies[self.feasible] = (
  1040. self._calculate_population_energies(
  1041. self.population[self.feasible]))
  1042. self._promote_lowest_energy()
  1043. # do the optimization.
  1044. for nit in range(1, self.maxiter + 1):
  1045. # evolve the population by a generation
  1046. try:
  1047. next(self)
  1048. except StopIteration:
  1049. warning_flag = True
  1050. if self._nfev > self.maxfun:
  1051. status_message = _status_message['maxfev']
  1052. elif self._nfev == self.maxfun:
  1053. status_message = ('Maximum number of function evaluations'
  1054. ' has been reached.')
  1055. break
  1056. if self.disp:
  1057. print(f"differential_evolution step {nit}: f(x)="
  1058. f" {self.population_energies[0]}"
  1059. )
  1060. if self.callback:
  1061. c = self.tol / (self.convergence + _MACHEPS)
  1062. res = self._result(nit=nit, message="in progress")
  1063. res.convergence = c
  1064. try:
  1065. warning_flag = bool(self.callback(res))
  1066. except StopIteration:
  1067. warning_flag = True
  1068. if warning_flag:
  1069. status_message = 'callback function requested stop early'
  1070. # should the solver terminate?
  1071. if warning_flag or self.converged():
  1072. break
  1073. else:
  1074. status_message = _status_message['maxiter']
  1075. warning_flag = True
  1076. DE_result = self._result(
  1077. nit=nit, message=status_message, warning_flag=warning_flag
  1078. )
  1079. if self.polish and not np.all(self.integrality):
  1080. # can't polish if all the parameters are integers
  1081. if np.any(self.integrality):
  1082. # set the lower/upper bounds equal so that any integrality
  1083. # constraints work.
  1084. limits, integrality = self.limits, self.integrality
  1085. limits[0, integrality] = DE_result.x[integrality]
  1086. limits[1, integrality] = DE_result.x[integrality]
  1087. polish_method = 'L-BFGS-B'
  1088. if self._wrapped_constraints:
  1089. polish_method = 'trust-constr'
  1090. constr_violation = self._constraint_violation_fn(DE_result.x)
  1091. if np.any(constr_violation > 0.):
  1092. warnings.warn("differential evolution didn't find a "
  1093. "solution satisfying the constraints, "
  1094. "attempting to polish from the least "
  1095. "infeasible solution",
  1096. UserWarning, stacklevel=2)
  1097. pf = self.polish
  1098. _f = self.original_func
  1099. if not callable(pf):
  1100. pf = partial(minimize, method=polish_method)
  1101. def _f(x):
  1102. return list(self._mapwrapper(self.func, np.atleast_2d(x)))[0]
  1103. if self.disp:
  1104. print(f"Polishing solution with '{polish_method}'")
  1105. result = pf(
  1106. _f,
  1107. np.copy(DE_result.x),
  1108. bounds=Bounds(lb=self.limits[0], ub=self.limits[1]),
  1109. constraints=self.constraints
  1110. )
  1111. if not isinstance(result, OptimizeResult):
  1112. raise ValueError(
  1113. "The result from a user defined polishing function "
  1114. "should return an OptimizeResult."
  1115. )
  1116. self._nfev += result.get("nfev", 0)
  1117. DE_result.nfev = self._nfev
  1118. # Polishing solution is only accepted if there is an improvement in
  1119. # cost function, the polishing was successful and the solution lies
  1120. # within the bounds.
  1121. if (result.fun < DE_result.fun and
  1122. result.success and
  1123. np.all(result.x <= self.limits[1]) and
  1124. np.all(self.limits[0] <= result.x)):
  1125. DE_result.fun = result.fun
  1126. DE_result.x = result.x
  1127. DE_result.jac = result.get("jac", None)
  1128. # to keep internal state consistent
  1129. self.population_energies[0] = result.fun
  1130. self.population[0] = self._unscale_parameters(result.x)
  1131. if self._wrapped_constraints:
  1132. DE_result.constr = [c.violation(DE_result.x) for
  1133. c in self._wrapped_constraints]
  1134. DE_result.constr_violation = np.max(
  1135. np.concatenate(DE_result.constr))
  1136. DE_result.maxcv = DE_result.constr_violation
  1137. if DE_result.maxcv > 0:
  1138. # if the result is infeasible then success must be False
  1139. DE_result.success = False
  1140. DE_result.message = ("The solution does not satisfy the "
  1141. f"constraints, MAXCV = {DE_result.maxcv}")
  1142. return DE_result
  1143. def _result(self, **kwds):
  1144. # form an intermediate OptimizeResult
  1145. nit = kwds.get('nit', None)
  1146. message = kwds.get('message', None)
  1147. warning_flag = kwds.get('warning_flag', False)
  1148. result = OptimizeResult(
  1149. x=self.x,
  1150. fun=self.population_energies[0],
  1151. nfev=self._nfev,
  1152. nit=nit,
  1153. message=message,
  1154. success=(warning_flag is not True),
  1155. population=self._scale_parameters(self.population),
  1156. population_energies=self.population_energies
  1157. )
  1158. if self._wrapped_constraints:
  1159. result.constr = [c.violation(result.x)
  1160. for c in self._wrapped_constraints]
  1161. result.constr_violation = np.max(np.concatenate(result.constr))
  1162. result.maxcv = result.constr_violation
  1163. if result.maxcv > 0:
  1164. result.success = False
  1165. return result
  1166. def _calculate_population_energies(self, population):
  1167. """
  1168. Calculate the energies of a population.
  1169. Parameters
  1170. ----------
  1171. population : ndarray
  1172. An array of parameter vectors normalised to [0, 1] using lower
  1173. and upper limits. Has shape ``(np.size(population, 0), N)``.
  1174. Returns
  1175. -------
  1176. energies : ndarray
  1177. An array of energies corresponding to each population member. If
  1178. maxfun will be exceeded during this call, then the number of
  1179. function evaluations will be reduced and energies will be
  1180. right-padded with np.inf. Has shape ``(np.size(population, 0),)``
  1181. """
  1182. num_members = np.size(population, 0)
  1183. # S is the number of function evals left to stay under the
  1184. # maxfun budget
  1185. S = min(num_members, self.maxfun - self._nfev)
  1186. energies = np.full(num_members, np.inf)
  1187. parameters_pop = self._scale_parameters(population)
  1188. try:
  1189. calc_energies = list(
  1190. self._mapwrapper(self.func, parameters_pop[0:S])
  1191. )
  1192. calc_energies = np.squeeze(calc_energies)
  1193. except (TypeError, ValueError) as e:
  1194. # wrong number of arguments for _mapwrapper
  1195. # or wrong length returned from the mapper
  1196. raise RuntimeError(
  1197. "The map-like callable must be of the form f(func, iterable), "
  1198. "returning a sequence of numbers the same length as 'iterable'"
  1199. ) from e
  1200. if calc_energies.size != S:
  1201. if self.vectorized:
  1202. raise RuntimeError("The vectorized function must return an"
  1203. " array of shape (S,) when given an array"
  1204. " of shape (len(x), S)")
  1205. raise RuntimeError("func(x, *args) must return a scalar value")
  1206. energies[0:S] = calc_energies
  1207. if self.vectorized:
  1208. self._nfev += 1
  1209. else:
  1210. self._nfev += S
  1211. return energies
  1212. def _promote_lowest_energy(self):
  1213. # swaps 'best solution' into first population entry
  1214. idx = np.arange(self.num_population_members)
  1215. feasible_solutions = idx[self.feasible]
  1216. if feasible_solutions.size:
  1217. # find the best feasible solution
  1218. idx_t = np.argmin(self.population_energies[feasible_solutions])
  1219. l = feasible_solutions[idx_t]
  1220. else:
  1221. # no solution was feasible, use 'best' infeasible solution, which
  1222. # will violate constraints the least
  1223. l = np.argmin(np.sum(self.constraint_violation, axis=1))
  1224. self.population_energies[[0, l]] = self.population_energies[[l, 0]]
  1225. self.population[[0, l], :] = self.population[[l, 0], :]
  1226. self.feasible[[0, l]] = self.feasible[[l, 0]]
  1227. self.constraint_violation[[0, l], :] = (
  1228. self.constraint_violation[[l, 0], :])
  1229. def _constraint_violation_fn(self, x):
  1230. """
  1231. Calculates total constraint violation for all the constraints, for a
  1232. set of solutions.
  1233. Parameters
  1234. ----------
  1235. x : ndarray
  1236. Solution vector(s). Has shape (S, N), or (N,), where S is the
  1237. number of solutions to investigate and N is the number of
  1238. parameters.
  1239. Returns
  1240. -------
  1241. cv : ndarray
  1242. Total violation of constraints. Has shape ``(S, M)``, where M is
  1243. the total number of constraint components (which is not necessarily
  1244. equal to len(self._wrapped_constraints)).
  1245. """
  1246. # how many solution vectors you're calculating constraint violations
  1247. # for
  1248. S = np.size(x) // self.parameter_count
  1249. _out = np.zeros((S, self.total_constraints))
  1250. offset = 0
  1251. for con in self._wrapped_constraints:
  1252. # the input/output of the (vectorized) constraint function is
  1253. # {(N, S), (N,)} --> (M, S)
  1254. # The input to _constraint_violation_fn is (S, N) or (N,), so
  1255. # transpose to pass it to the constraint. The output is transposed
  1256. # from (M, S) to (S, M) for further use.
  1257. c = con.violation(x.T).T
  1258. # The shape of c should be (M,), (1, M), or (S, M). Check for
  1259. # those shapes, as an incorrect shape indicates that the
  1260. # user constraint function didn't return the right thing, and
  1261. # the reshape operation will fail. Intercept the wrong shape
  1262. # to give a reasonable error message. I'm not sure what failure
  1263. # modes an inventive user will come up with.
  1264. if c.shape[-1] != con.num_constr or (S > 1 and c.shape[0] != S):
  1265. raise RuntimeError("An array returned from a Constraint has"
  1266. " the wrong shape. If `vectorized is False`"
  1267. " the Constraint should return an array of"
  1268. " shape (M,). If `vectorized is True` then"
  1269. " the Constraint must return an array of"
  1270. " shape (M, S), where S is the number of"
  1271. " solution vectors and M is the number of"
  1272. " constraint components in a given"
  1273. " Constraint object.")
  1274. # the violation function may return a 1D array, but is it a
  1275. # sequence of constraints for one solution (S=1, M>=1), or the
  1276. # value of a single constraint for a sequence of solutions
  1277. # (S>=1, M=1)
  1278. c = np.reshape(c, (S, con.num_constr))
  1279. _out[:, offset:offset + con.num_constr] = c
  1280. offset += con.num_constr
  1281. return _out
  1282. def _calculate_population_feasibilities(self, population):
  1283. """
  1284. Calculate the feasibilities of a population.
  1285. Parameters
  1286. ----------
  1287. population : ndarray
  1288. An array of parameter vectors normalised to [0, 1] using lower
  1289. and upper limits. Has shape ``(np.size(population, 0), N)``.
  1290. Returns
  1291. -------
  1292. feasible, constraint_violation : ndarray, ndarray
  1293. Boolean array of feasibility for each population member, and an
  1294. array of the constraint violation for each population member.
  1295. constraint_violation has shape ``(np.size(population, 0), M)``,
  1296. where M is the number of constraints.
  1297. """
  1298. num_members = np.size(population, 0)
  1299. if not self._wrapped_constraints:
  1300. # shortcut for no constraints
  1301. return np.ones(num_members, bool), np.zeros((num_members, 1))
  1302. # (S, N)
  1303. parameters_pop = self._scale_parameters(population)
  1304. if self.vectorized:
  1305. # (S, M)
  1306. constraint_violation = np.array(
  1307. self._constraint_violation_fn(parameters_pop)
  1308. )
  1309. else:
  1310. # (S, 1, M)
  1311. constraint_violation = np.array([self._constraint_violation_fn(x)
  1312. for x in parameters_pop])
  1313. # if you use the list comprehension in the line above it will
  1314. # create an array of shape (S, 1, M), because each iteration
  1315. # generates an array of (1, M). In comparison the vectorized
  1316. # version returns (S, M). It's therefore necessary to remove axis 1
  1317. constraint_violation = constraint_violation[:, 0]
  1318. feasible = ~(np.sum(constraint_violation, axis=1) > 0)
  1319. return feasible, constraint_violation
  1320. def __iter__(self):
  1321. return self
  1322. def __enter__(self):
  1323. return self
  1324. def __exit__(self, *args):
  1325. return self._mapwrapper.__exit__(*args)
  1326. def _accept_trial(self, energy_trial, feasible_trial, cv_trial,
  1327. energy_orig, feasible_orig, cv_orig):
  1328. """
  1329. Trial is accepted if:
  1330. * it satisfies all constraints and provides a lower or equal objective
  1331. function value, while both the compared solutions are feasible
  1332. - or -
  1333. * it is feasible while the original solution is infeasible,
  1334. - or -
  1335. * it is infeasible, but provides a lower or equal constraint violation
  1336. for all constraint functions.
  1337. This test corresponds to section III of Lampinen [1]_.
  1338. Parameters
  1339. ----------
  1340. energy_trial : float
  1341. Energy of the trial solution
  1342. feasible_trial : float
  1343. Feasibility of trial solution
  1344. cv_trial : array-like
  1345. Excess constraint violation for the trial solution
  1346. energy_orig : float
  1347. Energy of the original solution
  1348. feasible_orig : float
  1349. Feasibility of original solution
  1350. cv_orig : array-like
  1351. Excess constraint violation for the original solution
  1352. Returns
  1353. -------
  1354. accepted : bool
  1355. """
  1356. if feasible_orig and feasible_trial:
  1357. return energy_trial <= energy_orig
  1358. elif feasible_trial and not feasible_orig:
  1359. return True
  1360. elif not feasible_trial and (cv_trial <= cv_orig).all():
  1361. # cv_trial < cv_orig would imply that both trial and orig are not
  1362. # feasible
  1363. return True
  1364. return False
  1365. def __next__(self):
  1366. """
  1367. Evolve the population by a single generation
  1368. Returns
  1369. -------
  1370. x : ndarray
  1371. The best solution from the solver.
  1372. fun : float
  1373. Value of objective function obtained from the best solution.
  1374. """
  1375. # the population may have just been initialized (all entries are
  1376. # np.inf). If it has you have to calculate the initial energies
  1377. if np.all(np.isinf(self.population_energies)):
  1378. self.feasible, self.constraint_violation = (
  1379. self._calculate_population_feasibilities(self.population))
  1380. # only need to work out population energies for those that are
  1381. # feasible
  1382. self.population_energies[self.feasible] = (
  1383. self._calculate_population_energies(
  1384. self.population[self.feasible]))
  1385. self._promote_lowest_energy()
  1386. if self.dither is not None:
  1387. self.scale = self.random_number_generator.uniform(self.dither[0],
  1388. self.dither[1])
  1389. if self._updating == 'immediate':
  1390. # update best solution immediately
  1391. for candidate in range(self.num_population_members):
  1392. if self._nfev > self.maxfun:
  1393. raise StopIteration
  1394. # create a trial solution
  1395. trial = self._mutate(candidate)
  1396. # ensuring that it's in the range [0, 1)
  1397. self._ensure_constraint(trial)
  1398. # scale from [0, 1) to the actual parameter value
  1399. parameters = self._scale_parameters(trial)
  1400. # determine the energy of the objective function
  1401. if self._wrapped_constraints:
  1402. cv = self._constraint_violation_fn(parameters)
  1403. feasible = False
  1404. energy = np.inf
  1405. if not np.sum(cv) > 0:
  1406. # solution is feasible
  1407. feasible = True
  1408. energy = self.func(parameters)
  1409. self._nfev += 1
  1410. else:
  1411. feasible = True
  1412. cv = np.atleast_2d([0.])
  1413. energy = self.func(parameters)
  1414. self._nfev += 1
  1415. # compare trial and population member
  1416. if self._accept_trial(energy, feasible, cv,
  1417. self.population_energies[candidate],
  1418. self.feasible[candidate],
  1419. self.constraint_violation[candidate]):
  1420. self.population[candidate] = trial
  1421. self.population_energies[candidate] = np.squeeze(energy)
  1422. self.feasible[candidate] = feasible
  1423. self.constraint_violation[candidate] = cv
  1424. # if the trial candidate is also better than the best
  1425. # solution then promote it.
  1426. if self._accept_trial(energy, feasible, cv,
  1427. self.population_energies[0],
  1428. self.feasible[0],
  1429. self.constraint_violation[0]):
  1430. self._promote_lowest_energy()
  1431. elif self._updating == 'deferred':
  1432. # update best solution once per generation
  1433. if self._nfev >= self.maxfun:
  1434. raise StopIteration
  1435. # 'deferred' approach, vectorised form.
  1436. # create trial solutions
  1437. trial_pop = self._mutate_many(
  1438. np.arange(self.num_population_members)
  1439. )
  1440. # enforce bounds
  1441. self._ensure_constraint(trial_pop)
  1442. # determine the energies of the objective function, but only for
  1443. # feasible trials
  1444. feasible, cv = self._calculate_population_feasibilities(trial_pop)
  1445. trial_energies = np.full(self.num_population_members, np.inf)
  1446. # only calculate for feasible entries
  1447. trial_energies[feasible] = self._calculate_population_energies(
  1448. trial_pop[feasible])
  1449. # which solutions are 'improved'?
  1450. loc = [self._accept_trial(*val) for val in
  1451. zip(trial_energies, feasible, cv, self.population_energies,
  1452. self.feasible, self.constraint_violation)]
  1453. loc = np.array(loc)
  1454. self.population = np.where(loc[:, np.newaxis],
  1455. trial_pop,
  1456. self.population)
  1457. self.population_energies = np.where(loc,
  1458. trial_energies,
  1459. self.population_energies)
  1460. self.feasible = np.where(loc,
  1461. feasible,
  1462. self.feasible)
  1463. self.constraint_violation = np.where(loc[:, np.newaxis],
  1464. cv,
  1465. self.constraint_violation)
  1466. # make sure the best solution is updated if updating='deferred'.
  1467. # put the lowest energy into the best solution position.
  1468. self._promote_lowest_energy()
  1469. return self.x, self.population_energies[0]
  1470. def _scale_parameters(self, trial):
  1471. """Scale from a number between 0 and 1 to parameters."""
  1472. # trial either has shape (N, ) or (L, N), where L is the number of
  1473. # solutions being scaled
  1474. scaled = self.__scale_arg1 + (trial - 0.5) * self.__scale_arg2
  1475. if np.count_nonzero(self.integrality):
  1476. i = np.broadcast_to(self.integrality, scaled.shape)
  1477. scaled[i] = np.round(scaled[i])
  1478. return scaled
  1479. def _unscale_parameters(self, parameters):
  1480. """Scale from parameters to a number between 0 and 1."""
  1481. return (parameters - self.__scale_arg1) * self.__recip_scale_arg2 + 0.5
  1482. def _ensure_constraint(self, trial):
  1483. """Make sure the parameters lie between the limits."""
  1484. mask = np.bitwise_or(trial > 1, trial < 0)
  1485. if oob := np.count_nonzero(mask):
  1486. trial[mask] = self.random_number_generator.uniform(size=oob)
  1487. def _mutate_custom(self, candidate):
  1488. rng = self.random_number_generator
  1489. msg = (
  1490. "strategy must have signature"
  1491. " f(candidate: int, population: np.ndarray, rng=None) returning an"
  1492. " array of shape (N,)"
  1493. )
  1494. _population = self._scale_parameters(self.population)
  1495. if not len(np.shape(candidate)):
  1496. # single entry in population
  1497. trial = self.strategy(candidate, _population, rng=rng)
  1498. if trial.shape != (self.parameter_count,):
  1499. raise RuntimeError(msg)
  1500. else:
  1501. S = candidate.shape[0]
  1502. trial = np.array(
  1503. [self.strategy(c, _population, rng=rng) for c in candidate],
  1504. dtype=float
  1505. )
  1506. if trial.shape != (S, self.parameter_count):
  1507. raise RuntimeError(msg)
  1508. return self._unscale_parameters(trial)
  1509. def _mutate_many(self, candidates):
  1510. """Create trial vectors based on a mutation strategy."""
  1511. rng = self.random_number_generator
  1512. S = len(candidates)
  1513. if callable(self.strategy):
  1514. return self._mutate_custom(candidates)
  1515. trial = np.copy(self.population[candidates])
  1516. samples = np.array([self._select_samples(c, 5) for c in candidates])
  1517. if self.strategy in ['currenttobest1exp', 'currenttobest1bin']:
  1518. bprime = self.mutation_func(candidates, samples)
  1519. else:
  1520. bprime = self.mutation_func(samples)
  1521. fill_point = rng_integers(rng, self.parameter_count, size=S)
  1522. crossovers = rng.uniform(size=(S, self.parameter_count))
  1523. crossovers = crossovers < self.cross_over_probability
  1524. if self.strategy in self._binomial:
  1525. # A randomly selected parameter is always from the bprime vector for
  1526. # binomial crossover. The fill_point ensures at least one parameter
  1527. # comes from bprime, preventing the possibility of no mutation
  1528. # influence in the trial vector.
  1529. i = np.arange(S)
  1530. crossovers[i, fill_point[i]] = True
  1531. trial = np.where(crossovers, bprime, trial)
  1532. return trial
  1533. elif self.strategy in self._exponential:
  1534. # For exponential crossover, fill_point determines the starting index
  1535. # for a consecutive sequence of parameters from bprime. The sequence
  1536. # continues until a crossover probability check fails. The starting
  1537. # index is always from the bprime vector ensuring at least one
  1538. # parameter comes from bprime.
  1539. crossovers[..., 0] = True
  1540. for j in range(S):
  1541. i = 0
  1542. init_fill = fill_point[j]
  1543. while (i < self.parameter_count and crossovers[j, i]):
  1544. trial[j, init_fill] = bprime[j, init_fill]
  1545. init_fill = (init_fill + 1) % self.parameter_count
  1546. i += 1
  1547. return trial
  1548. def _mutate(self, candidate):
  1549. """Create a trial vector based on a mutation strategy."""
  1550. rng = self.random_number_generator
  1551. if callable(self.strategy):
  1552. return self._mutate_custom(candidate)
  1553. fill_point = rng_integers(rng, self.parameter_count)
  1554. samples = self._select_samples(candidate, 5)
  1555. trial = np.copy(self.population[candidate])
  1556. if self.strategy in ['currenttobest1exp', 'currenttobest1bin']:
  1557. bprime = self.mutation_func(candidate, samples)
  1558. else:
  1559. bprime = self.mutation_func(samples)
  1560. crossovers = rng.uniform(size=self.parameter_count)
  1561. crossovers = crossovers < self.cross_over_probability
  1562. if self.strategy in self._binomial:
  1563. # A randomly selected parameter is always from the bprime vector for
  1564. # binomial crossover. The fill_point ensures at least one parameter
  1565. # comes from bprime, preventing the possibility of no mutation
  1566. # influence in the trial vector.
  1567. crossovers[fill_point] = True
  1568. trial = np.where(crossovers, bprime, trial)
  1569. return trial
  1570. elif self.strategy in self._exponential:
  1571. # For exponential crossover, fill_point determines the starting index
  1572. # for a consecutive sequence of parameters from bprime. The sequence
  1573. # continues until a crossover probability check fails. The starting
  1574. # index is always from the bprime vector ensuring at least one
  1575. # parameter comes from bprime.
  1576. i = 0
  1577. crossovers[0] = True
  1578. while i < self.parameter_count and crossovers[i]:
  1579. trial[fill_point] = bprime[fill_point]
  1580. fill_point = (fill_point + 1) % self.parameter_count
  1581. i += 1
  1582. return trial
  1583. def _best1(self, samples):
  1584. """best1bin, best1exp"""
  1585. # samples.shape == (S, 5)
  1586. # or
  1587. # samples.shape(5,)
  1588. r0, r1 = samples[..., :2].T
  1589. return (self.population[0] + self.scale *
  1590. (self.population[r0] - self.population[r1]))
  1591. def _rand1(self, samples):
  1592. """rand1bin, rand1exp"""
  1593. r0, r1, r2 = samples[..., :3].T
  1594. return (self.population[r0] + self.scale *
  1595. (self.population[r1] - self.population[r2]))
  1596. def _randtobest1(self, samples):
  1597. """randtobest1bin, randtobest1exp"""
  1598. r0, r1, r2 = samples[..., :3].T
  1599. bprime = np.copy(self.population[r0])
  1600. bprime += self.scale * (self.population[0] - bprime)
  1601. bprime += self.scale * (self.population[r1] -
  1602. self.population[r2])
  1603. return bprime
  1604. def _currenttobest1(self, candidate, samples):
  1605. """currenttobest1bin, currenttobest1exp"""
  1606. r0, r1 = samples[..., :2].T
  1607. bprime = (self.population[candidate] + self.scale *
  1608. (self.population[0] - self.population[candidate] +
  1609. self.population[r0] - self.population[r1]))
  1610. return bprime
  1611. def _best2(self, samples):
  1612. """best2bin, best2exp"""
  1613. r0, r1, r2, r3 = samples[..., :4].T
  1614. bprime = (self.population[0] + self.scale *
  1615. (self.population[r0] + self.population[r1] -
  1616. self.population[r2] - self.population[r3]))
  1617. return bprime
  1618. def _rand2(self, samples):
  1619. """rand2bin, rand2exp"""
  1620. r0, r1, r2, r3, r4 = samples[..., :5].T
  1621. bprime = (self.population[r0] + self.scale *
  1622. (self.population[r1] + self.population[r2] -
  1623. self.population[r3] - self.population[r4]))
  1624. return bprime
  1625. def _select_samples(self, candidate, number_samples):
  1626. """
  1627. obtain random integers from range(self.num_population_members),
  1628. without replacement. You can't have the original candidate either.
  1629. """
  1630. self.random_number_generator.shuffle(self._random_population_index)
  1631. idxs = self._random_population_index[:number_samples + 1]
  1632. return idxs[idxs != candidate][:number_samples]
  1633. class _ConstraintWrapper:
  1634. """Object to wrap/evaluate user defined constraints.
  1635. Very similar in practice to `PreparedConstraint`, except that no evaluation
  1636. of jac/hess is performed (explicit or implicit).
  1637. If created successfully, it will contain the attributes listed below.
  1638. Parameters
  1639. ----------
  1640. constraint : {`NonlinearConstraint`, `LinearConstraint`, `Bounds`}
  1641. Constraint to check and prepare.
  1642. x0 : array_like
  1643. Initial vector of independent variables, shape (N,)
  1644. Attributes
  1645. ----------
  1646. fun : callable
  1647. Function defining the constraint wrapped by one of the convenience
  1648. classes.
  1649. bounds : 2-tuple
  1650. Contains lower and upper bounds for the constraints --- lb and ub.
  1651. These are converted to ndarray and have a size equal to the number of
  1652. the constraints.
  1653. Notes
  1654. -----
  1655. _ConstraintWrapper.fun and _ConstraintWrapper.violation can get sent
  1656. arrays of shape (N, S) or (N,), where S is the number of vectors of shape
  1657. (N,) to consider constraints for.
  1658. """
  1659. def __init__(self, constraint, x0):
  1660. self.constraint = constraint
  1661. if isinstance(constraint, NonlinearConstraint):
  1662. def fun(x):
  1663. x = np.asarray(x)
  1664. return np.atleast_1d(constraint.fun(x))
  1665. elif isinstance(constraint, LinearConstraint):
  1666. def fun(x):
  1667. if issparse(constraint.A):
  1668. A = constraint.A
  1669. else:
  1670. A = np.atleast_2d(constraint.A)
  1671. res = A.dot(x)
  1672. # x either has shape (N, S) or (N)
  1673. # (M, N) x (N, S) --> (M, S)
  1674. # (M, N) x (N,) --> (M,)
  1675. # However, if (M, N) is a matrix then:
  1676. # (M, N) * (N,) --> (M, 1), we need this to be (M,)
  1677. if x.ndim == 1 and res.ndim == 2:
  1678. # deal with case that constraint.A is an np.matrix
  1679. # see gh20041
  1680. res = np.asarray(res)[:, 0]
  1681. return res
  1682. elif isinstance(constraint, Bounds):
  1683. def fun(x):
  1684. return np.asarray(x)
  1685. else:
  1686. raise ValueError("`constraint` of an unknown type is passed.")
  1687. self.fun = fun
  1688. lb = np.asarray(constraint.lb, dtype=float)
  1689. ub = np.asarray(constraint.ub, dtype=float)
  1690. x0 = np.asarray(x0)
  1691. # find out the number of constraints
  1692. f0 = fun(x0)
  1693. self.num_constr = m = f0.size
  1694. self.parameter_count = x0.size
  1695. if lb.ndim == 0:
  1696. lb = np.resize(lb, m)
  1697. if ub.ndim == 0:
  1698. ub = np.resize(ub, m)
  1699. self.bounds = (lb, ub)
  1700. def __call__(self, x):
  1701. return np.atleast_1d(self.fun(x))
  1702. def violation(self, x):
  1703. """How much the constraint is exceeded by.
  1704. Parameters
  1705. ----------
  1706. x : array-like
  1707. Vector of independent variables, (N, S), where N is number of
  1708. parameters and S is the number of solutions to be investigated.
  1709. Returns
  1710. -------
  1711. excess : array-like
  1712. How much the constraint is exceeded by, for each of the
  1713. constraints specified by `_ConstraintWrapper.fun`.
  1714. Has shape (M, S) where M is the number of constraint components.
  1715. """
  1716. # expect ev to have shape (num_constr, S) or (num_constr,)
  1717. ev = self.fun(np.asarray(x))
  1718. try:
  1719. excess_lb = np.maximum(self.bounds[0] - ev.T, 0)
  1720. excess_ub = np.maximum(ev.T - self.bounds[1], 0)
  1721. except ValueError as e:
  1722. raise RuntimeError("An array returned from a Constraint has"
  1723. " the wrong shape. If `vectorized is False`"
  1724. " the Constraint should return an array of"
  1725. " shape (M,). If `vectorized is True` then"
  1726. " the Constraint must return an array of"
  1727. " shape (M, S), where S is the number of"
  1728. " solution vectors and M is the number of"
  1729. " constraint components in a given"
  1730. " Constraint object.") from e
  1731. v = (excess_lb + excess_ub).T
  1732. return v