_differentialevolution.py 72 KB

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