test__differential_evolution.py 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758
  1. """
  2. Unit tests for the differential global minimization algorithm.
  3. """
  4. from multiprocessing.dummy import Pool as ThreadPool
  5. import platform
  6. import warnings
  7. from scipy._lib._gcutils import assert_deallocated
  8. from scipy.optimize._differentialevolution import (DifferentialEvolutionSolver,
  9. _ConstraintWrapper)
  10. from scipy.optimize import differential_evolution, OptimizeResult
  11. from scipy.optimize._constraints import (Bounds, NonlinearConstraint,
  12. LinearConstraint)
  13. from scipy.optimize import rosen, minimize, rosen_der
  14. from scipy.sparse import csr_array
  15. from scipy import stats
  16. import numpy as np
  17. from numpy.testing import (assert_equal, assert_allclose, assert_almost_equal,
  18. assert_string_equal, assert_)
  19. from pytest import raises as assert_raises, warns
  20. import pytest
  21. class TestDifferentialEvolutionSolver:
  22. def setup_method(self):
  23. self.old_seterr = np.seterr(invalid='raise')
  24. self.limits = np.array([[0., 0.],
  25. [2., 2.]])
  26. self.bounds = [(0., 2.), (0., 2.)]
  27. self.dummy_solver = DifferentialEvolutionSolver(self.quadratic,
  28. [(0, 100)])
  29. # dummy_solver2 will be used to test mutation strategies
  30. self.dummy_solver2 = DifferentialEvolutionSolver(self.quadratic,
  31. [(0, 1)],
  32. popsize=7,
  33. mutation=0.5)
  34. # create a population that's only 7 members long
  35. # [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
  36. population = np.atleast_2d(np.arange(0.1, 0.8, 0.1)).T
  37. self.dummy_solver2.population = population
  38. def teardown_method(self):
  39. np.seterr(**self.old_seterr)
  40. def quadratic(self, x):
  41. return x[0]**2
  42. def test__strategy_resolves(self):
  43. # test that the correct mutation function is resolved by
  44. # different requested strategy arguments
  45. solver = DifferentialEvolutionSolver(rosen,
  46. self.bounds,
  47. strategy='best1exp')
  48. assert_equal(solver.strategy, 'best1exp')
  49. assert_equal(solver.mutation_func.__name__, '_best1')
  50. solver = DifferentialEvolutionSolver(rosen,
  51. self.bounds,
  52. strategy='best1bin')
  53. assert_equal(solver.strategy, 'best1bin')
  54. assert_equal(solver.mutation_func.__name__, '_best1')
  55. solver = DifferentialEvolutionSolver(rosen,
  56. self.bounds,
  57. strategy='rand1bin')
  58. assert_equal(solver.strategy, 'rand1bin')
  59. assert_equal(solver.mutation_func.__name__, '_rand1')
  60. solver = DifferentialEvolutionSolver(rosen,
  61. self.bounds,
  62. strategy='rand1exp')
  63. assert_equal(solver.strategy, 'rand1exp')
  64. assert_equal(solver.mutation_func.__name__, '_rand1')
  65. solver = DifferentialEvolutionSolver(rosen,
  66. self.bounds,
  67. strategy='rand2exp')
  68. assert_equal(solver.strategy, 'rand2exp')
  69. assert_equal(solver.mutation_func.__name__, '_rand2')
  70. solver = DifferentialEvolutionSolver(rosen,
  71. self.bounds,
  72. strategy='best2bin')
  73. assert_equal(solver.strategy, 'best2bin')
  74. assert_equal(solver.mutation_func.__name__, '_best2')
  75. solver = DifferentialEvolutionSolver(rosen,
  76. self.bounds,
  77. strategy='rand2bin')
  78. assert_equal(solver.strategy, 'rand2bin')
  79. assert_equal(solver.mutation_func.__name__, '_rand2')
  80. solver = DifferentialEvolutionSolver(rosen,
  81. self.bounds,
  82. strategy='rand2exp')
  83. assert_equal(solver.strategy, 'rand2exp')
  84. assert_equal(solver.mutation_func.__name__, '_rand2')
  85. solver = DifferentialEvolutionSolver(rosen,
  86. self.bounds,
  87. strategy='randtobest1bin')
  88. assert_equal(solver.strategy, 'randtobest1bin')
  89. assert_equal(solver.mutation_func.__name__, '_randtobest1')
  90. solver = DifferentialEvolutionSolver(rosen,
  91. self.bounds,
  92. strategy='randtobest1exp')
  93. assert_equal(solver.strategy, 'randtobest1exp')
  94. assert_equal(solver.mutation_func.__name__, '_randtobest1')
  95. solver = DifferentialEvolutionSolver(rosen,
  96. self.bounds,
  97. strategy='currenttobest1bin')
  98. assert_equal(solver.strategy, 'currenttobest1bin')
  99. assert_equal(solver.mutation_func.__name__, '_currenttobest1')
  100. solver = DifferentialEvolutionSolver(rosen,
  101. self.bounds,
  102. strategy='currenttobest1exp')
  103. assert_equal(solver.strategy, 'currenttobest1exp')
  104. assert_equal(solver.mutation_func.__name__, '_currenttobest1')
  105. def test__mutate1(self):
  106. # strategies */1/*, i.e. rand/1/bin, best/1/exp, etc.
  107. result = np.array([0.05])
  108. trial = self.dummy_solver2._best1(np.array([2, 3, 4, 5, 6]))
  109. assert_allclose(trial, result)
  110. result = np.array([0.25])
  111. trial = self.dummy_solver2._rand1(np.array([2, 3, 4, 5, 6]))
  112. assert_allclose(trial, result)
  113. def test__mutate2(self):
  114. # strategies */2/*, i.e. rand/2/bin, best/2/exp, etc.
  115. # [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
  116. result = np.array([-0.1])
  117. trial = self.dummy_solver2._best2(np.array([2, 3, 4, 5, 6]))
  118. assert_allclose(trial, result)
  119. result = np.array([0.1])
  120. trial = self.dummy_solver2._rand2(np.array([2, 3, 4, 5, 6]))
  121. assert_allclose(trial, result)
  122. def test__randtobest1(self):
  123. # strategies randtobest/1/*
  124. result = np.array([0.15])
  125. trial = self.dummy_solver2._randtobest1(np.array([2, 3, 4, 5, 6]))
  126. assert_allclose(trial, result)
  127. def test__currenttobest1(self):
  128. # strategies currenttobest/1/*
  129. result = np.array([0.1])
  130. trial = self.dummy_solver2._currenttobest1(
  131. 1,
  132. np.array([2, 3, 4, 5, 6])
  133. )
  134. assert_allclose(trial, result)
  135. def test_can_init_with_dithering(self):
  136. mutation = (0.5, 1)
  137. solver = DifferentialEvolutionSolver(self.quadratic,
  138. self.bounds,
  139. mutation=mutation)
  140. assert_equal(solver.dither, list(mutation))
  141. def test_invalid_mutation_values_arent_accepted(self):
  142. func = rosen
  143. mutation = (0.5, 3)
  144. assert_raises(ValueError,
  145. DifferentialEvolutionSolver,
  146. func,
  147. self.bounds,
  148. mutation=mutation)
  149. mutation = (-1, 1)
  150. assert_raises(ValueError,
  151. DifferentialEvolutionSolver,
  152. func,
  153. self.bounds,
  154. mutation=mutation)
  155. mutation = (0.1, np.nan)
  156. assert_raises(ValueError,
  157. DifferentialEvolutionSolver,
  158. func,
  159. self.bounds,
  160. mutation=mutation)
  161. mutation = 0.5
  162. solver = DifferentialEvolutionSolver(func,
  163. self.bounds,
  164. mutation=mutation)
  165. assert_equal(0.5, solver.scale)
  166. assert_equal(None, solver.dither)
  167. def test_invalid_functional(self):
  168. def func(x):
  169. return np.array([np.sum(x ** 2), np.sum(x)])
  170. with assert_raises(
  171. RuntimeError,
  172. match=r"func\(x, \*args\) must return a scalar value"):
  173. differential_evolution(func, [(-2, 2), (-2, 2)])
  174. def test__scale_parameters(self):
  175. trial = np.array([0.3])
  176. assert_equal(30, self.dummy_solver._scale_parameters(trial))
  177. # it should also work with the limits reversed
  178. self.dummy_solver.limits = np.array([[100], [0.]])
  179. assert_equal(30, self.dummy_solver._scale_parameters(trial))
  180. def test__unscale_parameters(self):
  181. trial = np.array([30])
  182. assert_equal(0.3, self.dummy_solver._unscale_parameters(trial))
  183. # it should also work with the limits reversed
  184. self.dummy_solver.limits = np.array([[100], [0.]])
  185. assert_equal(0.3, self.dummy_solver._unscale_parameters(trial))
  186. def test_equal_bounds(self):
  187. with np.errstate(invalid='raise'):
  188. solver = DifferentialEvolutionSolver(
  189. self.quadratic,
  190. bounds=[(2.0, 2.0), (1.0, 3.0)]
  191. )
  192. v = solver._unscale_parameters([2.0, 2.0])
  193. assert_allclose(v, 0.5)
  194. res = differential_evolution(self.quadratic, [(2.0, 2.0), (3.0, 3.0)])
  195. assert_equal(res.x, [2.0, 3.0])
  196. def test__ensure_constraint(self):
  197. trial = np.array([1.1, -100, 0.9, 2., 300., -0.00001])
  198. self.dummy_solver._ensure_constraint(trial)
  199. assert_equal(trial[2], 0.9)
  200. assert_(np.logical_and(trial >= 0, trial <= 1).all())
  201. def test_differential_evolution(self):
  202. # test that the Jmin of DifferentialEvolutionSolver
  203. # is the same as the function evaluation
  204. solver = DifferentialEvolutionSolver(
  205. self.quadratic, [(-2, 2)], maxiter=1, polish=False
  206. )
  207. result = solver.solve()
  208. assert_equal(result.fun, self.quadratic(result.x))
  209. solver = DifferentialEvolutionSolver(
  210. self.quadratic, [(-2, 2)], maxiter=1, polish=True
  211. )
  212. result = solver.solve()
  213. assert_equal(result.fun, self.quadratic(result.x))
  214. def test_best_solution_retrieval(self):
  215. # test that the getter property method for the best solution works.
  216. solver = DifferentialEvolutionSolver(self.quadratic, [(-2, 2)])
  217. result = solver.solve()
  218. assert_allclose(result.x, solver.x, atol=1e-15, rtol=0)
  219. def test_intermediate_result(self):
  220. # Check that intermediate result object passed into the callback
  221. # function contains the expected information and that raising
  222. # `StopIteration` causes the expected behavior.
  223. maxiter = 10
  224. def func(x):
  225. val = rosen(x)
  226. if val < func.val:
  227. func.x = x
  228. func.val = val
  229. return val
  230. func.x = None
  231. func.val = np.inf
  232. def callback(intermediate_result):
  233. callback.nit += 1
  234. callback.intermediate_result = intermediate_result
  235. assert intermediate_result.population.ndim == 2
  236. assert intermediate_result.population.shape[1] == 2
  237. assert intermediate_result.nit == callback.nit
  238. # Check that `x` and `fun` attributes are the best found so far
  239. assert_equal(intermediate_result.x, callback.func.x)
  240. assert_equal(intermediate_result.fun, callback.func.val)
  241. # Check for consistency between `fun`, `population_energies`,
  242. # `x`, and `population`
  243. assert_equal(intermediate_result.fun, rosen(intermediate_result.x))
  244. for i in range(len(intermediate_result.population_energies)):
  245. res = intermediate_result.population_energies[i]
  246. ref = rosen(intermediate_result.population[i])
  247. assert_equal(res, ref)
  248. assert_equal(intermediate_result.x,
  249. intermediate_result.population[0])
  250. assert_equal(intermediate_result.fun,
  251. intermediate_result.population_energies[0])
  252. assert intermediate_result.message == 'in progress'
  253. assert intermediate_result.success is True
  254. assert isinstance(intermediate_result, OptimizeResult)
  255. if callback.nit == maxiter:
  256. raise StopIteration
  257. callback.nit = 0
  258. callback.intermediate_result = None
  259. callback.func = func
  260. bounds = [(0, 2), (0, 2)]
  261. kwargs = dict(func=func, bounds=bounds, rng=838245, polish=False)
  262. res = differential_evolution(**kwargs, callback=callback)
  263. ref = differential_evolution(**kwargs, maxiter=maxiter)
  264. # Check that final `intermediate_result` is equivalent to returned
  265. # result object and that terminating with callback `StopIteration`
  266. # after `maxiter` iterations is equivalent to terminating with
  267. # `maxiter` parameter.
  268. assert res.success is ref.success is False
  269. assert callback.nit == res.nit == maxiter
  270. assert res.message == 'callback function requested stop early'
  271. assert ref.message == 'Maximum number of iterations has been exceeded.'
  272. for field, val in ref.items():
  273. if field in {'message', 'success'}: # checked separately
  274. continue
  275. assert_equal(callback.intermediate_result[field], val)
  276. assert_equal(res[field], val)
  277. # Check that polish occurs after `StopIteration` as advertised
  278. callback.nit = 0
  279. func.val = np.inf
  280. kwargs['polish'] = True
  281. res = differential_evolution(**kwargs, callback=callback)
  282. assert res.fun < ref.fun
  283. def test_callback_terminates(self):
  284. # test that if the callback returns true, then the minimization halts
  285. bounds = [(0, 2), (0, 2)]
  286. expected_msg = 'callback function requested stop early'
  287. def callback_python_true(param, convergence=0.):
  288. return True
  289. result = differential_evolution(
  290. rosen, bounds, callback=callback_python_true
  291. )
  292. assert_string_equal(result.message, expected_msg)
  293. # if callback raises StopIteration then solve should be interrupted
  294. def callback_stop(intermediate_result):
  295. raise StopIteration
  296. result = differential_evolution(rosen, bounds, callback=callback_stop)
  297. assert not result.success
  298. def callback_evaluates_true(param, convergence=0.):
  299. # DE should stop if bool(self.callback) is True
  300. return [10]
  301. result = differential_evolution(rosen, bounds, callback=callback_evaluates_true)
  302. assert_string_equal(result.message, expected_msg)
  303. assert not result.success
  304. def callback_evaluates_false(param, convergence=0.):
  305. return []
  306. result = differential_evolution(rosen, bounds,
  307. callback=callback_evaluates_false)
  308. assert result.success
  309. def test_args_tuple_is_passed(self):
  310. # test that the args tuple is passed to the cost function properly.
  311. bounds = [(-10, 10)]
  312. args = (1., 2., 3.)
  313. def quadratic(x, *args):
  314. if not isinstance(args, tuple):
  315. raise ValueError('args should be a tuple')
  316. return args[0] + args[1] * x + args[2] * x**2.
  317. result = differential_evolution(quadratic,
  318. bounds,
  319. args=args,
  320. polish=True)
  321. assert_almost_equal(result.fun, 2 / 3.)
  322. def test_init_with_invalid_strategy(self):
  323. # test that passing an invalid strategy raises ValueError
  324. func = rosen
  325. bounds = [(-3, 3)]
  326. assert_raises(ValueError,
  327. differential_evolution,
  328. func,
  329. bounds,
  330. strategy='abc')
  331. def test_bounds_checking(self):
  332. # test that the bounds checking works
  333. func = rosen
  334. bounds = [(-3)]
  335. assert_raises(ValueError,
  336. differential_evolution,
  337. func,
  338. bounds)
  339. bounds = [(-3, 3), (3, 4, 5)]
  340. assert_raises(ValueError,
  341. differential_evolution,
  342. func,
  343. bounds)
  344. # test that we can use a new-type Bounds object
  345. result = differential_evolution(rosen, Bounds([0, 0], [2, 2]))
  346. assert_almost_equal(result.x, (1., 1.))
  347. def test_select_samples(self):
  348. # select_samples should return 5 separate random numbers.
  349. limits = np.arange(12., dtype='float64').reshape(2, 6)
  350. bounds = list(zip(limits[0, :], limits[1, :]))
  351. solver = DifferentialEvolutionSolver(None, bounds, popsize=1)
  352. candidate = 0
  353. r1, r2, r3, r4, r5 = solver._select_samples(candidate, 5)
  354. assert_equal(
  355. len(np.unique(np.array([candidate, r1, r2, r3, r4, r5]))), 6)
  356. def test_maxiter_stops_solve(self):
  357. # test that if the maximum number of iterations is exceeded
  358. # the solver stops.
  359. solver = DifferentialEvolutionSolver(rosen, self.bounds, maxiter=1)
  360. result = solver.solve()
  361. assert_equal(result.success, False)
  362. assert_equal(result.message,
  363. 'Maximum number of iterations has been exceeded.')
  364. def test_maxfun_stops_solve(self):
  365. # test that if the maximum number of function evaluations is exceeded
  366. # during initialisation the solver stops
  367. solver = DifferentialEvolutionSolver(rosen, self.bounds, maxfun=1,
  368. polish=False)
  369. result = solver.solve()
  370. assert_equal(result.nfev, 2)
  371. assert_equal(result.success, False)
  372. assert_equal(result.message,
  373. 'Maximum number of function evaluations has '
  374. 'been exceeded.')
  375. # test that if the maximum number of function evaluations is exceeded
  376. # during the actual minimisation, then the solver stops.
  377. # Have to turn polishing off, as this will still occur even if maxfun
  378. # is reached. For popsize=5 and len(bounds)=2, then there are only 10
  379. # function evaluations during initialisation.
  380. solver = DifferentialEvolutionSolver(rosen,
  381. self.bounds,
  382. popsize=5,
  383. polish=False,
  384. maxfun=40)
  385. result = solver.solve()
  386. assert_equal(result.nfev, 41)
  387. assert_equal(result.success, False)
  388. assert_equal(result.message,
  389. 'Maximum number of function evaluations has '
  390. 'been exceeded.')
  391. # now repeat for updating='deferred version
  392. # 47 function evaluations is not a multiple of the population size,
  393. # so maxfun is reached partway through a population evaluation.
  394. solver = DifferentialEvolutionSolver(rosen,
  395. self.bounds,
  396. popsize=5,
  397. polish=False,
  398. maxfun=47,
  399. updating='deferred')
  400. result = solver.solve()
  401. assert_equal(result.nfev, 47)
  402. assert_equal(result.success, False)
  403. assert_equal(result.message,
  404. 'Maximum number of function evaluations has '
  405. 'been reached.')
  406. def test_quadratic(self):
  407. # test the quadratic function from object
  408. solver = DifferentialEvolutionSolver(self.quadratic,
  409. [(-100, 100)],
  410. tol=0.02)
  411. solver.solve()
  412. assert_equal(np.argmin(solver.population_energies), 0)
  413. def test_quadratic_from_diff_ev(self):
  414. # test the quadratic function from differential_evolution function
  415. differential_evolution(self.quadratic,
  416. [(-100, 100)],
  417. tol=0.02,
  418. seed=1)
  419. def test_rng_gives_repeatability(self):
  420. result = differential_evolution(self.quadratic,
  421. [(-100, 100)],
  422. polish=False,
  423. rng=1,
  424. tol=0.5)
  425. result2 = differential_evolution(self.quadratic,
  426. [(-100, 100)],
  427. polish=False,
  428. rng=1,
  429. tol=0.5)
  430. assert_equal(result.x, result2.x)
  431. assert_equal(result.nfev, result2.nfev)
  432. def test_random_generator(self):
  433. # check that np.random.Generator can be used (numpy >= 1.17)
  434. # obtain a np.random.Generator object
  435. rng = np.random.default_rng()
  436. inits = ['random', 'latinhypercube', 'sobol', 'halton']
  437. for init in inits:
  438. differential_evolution(self.quadratic,
  439. [(-100, 100)],
  440. polish=False,
  441. rng=rng,
  442. tol=0.5,
  443. init=init)
  444. def test_exp_runs(self):
  445. # test whether exponential mutation loop runs
  446. solver = DifferentialEvolutionSolver(rosen,
  447. self.bounds,
  448. strategy='best1exp',
  449. maxiter=1)
  450. solver.solve()
  451. def test_gh_4511_regression(self):
  452. # This modification of the differential evolution docstring example
  453. # uses a custom popsize that had triggered an off-by-one error.
  454. # Because we do not care about solving the optimization problem in
  455. # this test, we use maxiter=1 to reduce the testing time.
  456. bounds = [(-5, 5), (-5, 5)]
  457. # result = differential_evolution(rosen, bounds, popsize=1815,
  458. # maxiter=1)
  459. # the original issue arose because of rounding error in arange, with
  460. # linspace being a much better solution. 1815 is quite a large popsize
  461. # to use and results in a long test time (~13s). I used the original
  462. # issue to figure out the lowest number of samples that would cause
  463. # this rounding error to occur, 49.
  464. differential_evolution(rosen, bounds, popsize=49, maxiter=1)
  465. def test_calculate_population_energies(self):
  466. # if popsize is 3, then the overall generation has size (6,)
  467. solver = DifferentialEvolutionSolver(rosen, self.bounds, popsize=3)
  468. solver._calculate_population_energies(solver.population)
  469. solver._promote_lowest_energy()
  470. assert_equal(np.argmin(solver.population_energies), 0)
  471. # initial calculation of the energies should require 6 nfev.
  472. assert_equal(solver._nfev, 6)
  473. def test_iteration(self):
  474. # test that DifferentialEvolutionSolver is iterable
  475. # if popsize is 3, then the overall generation has size (6,)
  476. solver = DifferentialEvolutionSolver(rosen, self.bounds, popsize=3,
  477. maxfun=12)
  478. x, fun = next(solver)
  479. assert_equal(np.size(x, 0), 2)
  480. # 6 nfev are required for initial calculation of energies, 6 nfev are
  481. # required for the evolution of the 6 population members.
  482. assert_equal(solver._nfev, 12)
  483. # the next generation should halt because it exceeds maxfun
  484. assert_raises(StopIteration, next, solver)
  485. # check a proper minimisation can be done by an iterable solver
  486. solver = DifferentialEvolutionSolver(rosen, self.bounds)
  487. _, fun_prev = next(solver)
  488. for i, soln in enumerate(solver):
  489. x_current, fun_current = soln
  490. assert fun_prev >= fun_current
  491. _, fun_prev = x_current, fun_current
  492. # need to have this otherwise the solver would never stop.
  493. if i == 50:
  494. break
  495. def test_convergence(self):
  496. solver = DifferentialEvolutionSolver(rosen, self.bounds, tol=0.2,
  497. polish=False)
  498. solver.solve()
  499. assert_(solver.convergence < 0.2)
  500. def test_maxiter_none_GH5731(self):
  501. # Pre 0.17 the previous default for maxiter and maxfun was None.
  502. # the numerical defaults are now 1000 and np.inf. However, some scripts
  503. # will still supply None for both of those, this will raise a TypeError
  504. # in the solve method.
  505. solver = DifferentialEvolutionSolver(rosen, self.bounds, maxiter=None,
  506. maxfun=None)
  507. solver.solve()
  508. def test_population_initiation(self):
  509. # test the different modes of population initiation
  510. # init must be either 'latinhypercube' or 'random'
  511. # raising ValueError is something else is passed in
  512. assert_raises(ValueError,
  513. DifferentialEvolutionSolver,
  514. *(rosen, self.bounds),
  515. **{'init': 'rubbish'})
  516. solver = DifferentialEvolutionSolver(rosen, self.bounds)
  517. # check that population initiation:
  518. # 1) resets _nfev to 0
  519. # 2) all population energies are np.inf
  520. solver.init_population_random()
  521. assert_equal(solver._nfev, 0)
  522. assert_(np.all(np.isinf(solver.population_energies)))
  523. solver.init_population_lhs()
  524. assert_equal(solver._nfev, 0)
  525. assert_(np.all(np.isinf(solver.population_energies)))
  526. solver.init_population_qmc(qmc_engine='halton')
  527. assert_equal(solver._nfev, 0)
  528. assert_(np.all(np.isinf(solver.population_energies)))
  529. solver = DifferentialEvolutionSolver(rosen, self.bounds, init='sobol')
  530. solver.init_population_qmc(qmc_engine='sobol')
  531. assert_equal(solver._nfev, 0)
  532. assert_(np.all(np.isinf(solver.population_energies)))
  533. # we should be able to initialize with our own array
  534. population = np.linspace(-1, 3, 10).reshape(5, 2)
  535. solver = DifferentialEvolutionSolver(rosen, self.bounds,
  536. init=population,
  537. strategy='best2bin',
  538. atol=0.01, rng=1, popsize=5)
  539. assert_equal(solver._nfev, 0)
  540. assert_(np.all(np.isinf(solver.population_energies)))
  541. assert_(solver.num_population_members == 5)
  542. assert_(solver.population_shape == (5, 2))
  543. # check that the population was initialized correctly
  544. unscaled_population = np.clip(solver._unscale_parameters(population),
  545. 0, 1)
  546. assert_almost_equal(solver.population[:5], unscaled_population)
  547. # population values need to be clipped to bounds
  548. assert_almost_equal(np.min(solver.population[:5]), 0)
  549. assert_almost_equal(np.max(solver.population[:5]), 1)
  550. # shouldn't be able to initialize with an array if it's the wrong shape
  551. # this would have too many parameters
  552. population = np.linspace(-1, 3, 15).reshape(5, 3)
  553. assert_raises(ValueError,
  554. DifferentialEvolutionSolver,
  555. *(rosen, self.bounds),
  556. **{'init': population})
  557. # provide an initial solution
  558. # bounds are [(0, 2), (0, 2)]
  559. x0 = np.random.uniform(low=0.0, high=2.0, size=2)
  560. solver = DifferentialEvolutionSolver(
  561. rosen, self.bounds, x0=x0
  562. )
  563. # parameters are scaled to unit interval
  564. assert_allclose(solver.population[0], x0 / 2.0)
  565. def test_x0(self):
  566. # smoke test that checks that x0 is usable.
  567. res = differential_evolution(rosen, self.bounds, x0=[0.2, 0.8])
  568. assert res.success
  569. # check what happens if some of the x0 lay outside the bounds
  570. with assert_raises(ValueError):
  571. differential_evolution(rosen, self.bounds, x0=[0.2, 2.1])
  572. def test_infinite_objective_function(self):
  573. # Test that there are no problems if the objective function
  574. # returns inf on some runs
  575. def sometimes_inf(x):
  576. if x[0] < .5:
  577. return np.inf
  578. return x[1]
  579. bounds = [(0, 1), (0, 1)]
  580. differential_evolution(sometimes_inf, bounds=bounds, disp=False)
  581. def test_deferred_updating(self):
  582. # check setting of deferred updating, with default workers
  583. bounds = [(0., 2.), (0., 2.)]
  584. solver = DifferentialEvolutionSolver(rosen, bounds, updating='deferred')
  585. assert_(solver._updating == 'deferred')
  586. assert_(solver._mapwrapper._mapfunc is map)
  587. res = solver.solve()
  588. assert res.success
  589. # check that deferred updating works with an exponential crossover
  590. res = differential_evolution(
  591. rosen, bounds, updating='deferred', strategy='best1exp'
  592. )
  593. assert res.success
  594. def test_immediate_updating(self):
  595. # check setting of immediate updating, with default workers
  596. bounds = [(0., 2.), (0., 2.)]
  597. solver = DifferentialEvolutionSolver(rosen, bounds)
  598. assert_(solver._updating == 'immediate')
  599. # should raise a UserWarning because the updating='immediate'
  600. # is being overridden by the workers keyword
  601. with warns(UserWarning):
  602. with DifferentialEvolutionSolver(rosen, bounds, workers=2) as s:
  603. solver.solve()
  604. assert s._updating == 'deferred'
  605. def test_parallel_threads(self):
  606. # smoke test for parallelization with deferred updating
  607. bounds = [(0., 2.), (0., 2.)]
  608. # use threads instead of Process to speed things up for this simple example
  609. with ThreadPool(2) as p, DifferentialEvolutionSolver(
  610. rosen, bounds, updating='deferred', workers=p.map, tol=0.1, popsize=3
  611. ) as solver:
  612. assert solver._mapwrapper.pool is not None
  613. assert solver._updating == 'deferred'
  614. solver.solve()
  615. @pytest.mark.fail_slow(10)
  616. def test_parallel_processes(self):
  617. bounds = [(0., 2.), (0., 2.)]
  618. with DifferentialEvolutionSolver(
  619. rosen, bounds, updating='deferred', workers=2, popsize=3, tol=0.1
  620. ) as solver:
  621. assert solver._mapwrapper.pool is not None
  622. assert solver._updating == 'deferred'
  623. solver.solve()
  624. def test_converged(self):
  625. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)])
  626. solver.solve()
  627. assert_(solver.converged())
  628. def test_constraint_violation_fn(self):
  629. def constr_f(x):
  630. return [x[0] + x[1]]
  631. def constr_f2(x):
  632. return np.array([x[0]**2 + x[1], x[0] - x[1]])
  633. nlc = NonlinearConstraint(constr_f, -np.inf, 1.9)
  634. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  635. constraints=(nlc,))
  636. cv = solver._constraint_violation_fn(np.array([1.0, 1.0]))
  637. assert_almost_equal(cv, 0.1)
  638. nlc2 = NonlinearConstraint(constr_f2, -np.inf, 1.8)
  639. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  640. constraints=(nlc, nlc2))
  641. # for multiple constraints the constraint violations should
  642. # be concatenated.
  643. xs = [(1.2, 1), (2.0, 2.0), (0.5, 0.5)]
  644. vs = [(0.3, 0.64, 0.0), (2.1, 4.2, 0.0), (0, 0, 0)]
  645. for x, v in zip(xs, vs):
  646. cv = solver._constraint_violation_fn(np.array(x))
  647. assert_allclose(cv, np.atleast_2d(v))
  648. # vectorized calculation of a series of solutions
  649. assert_allclose(
  650. solver._constraint_violation_fn(np.array(xs)), np.array(vs)
  651. )
  652. # the following line is used in _calculate_population_feasibilities.
  653. # _constraint_violation_fn returns an (1, M) array when
  654. # x.shape == (N,), i.e. a single solution. Therefore this list
  655. # comprehension should generate (S, 1, M) array.
  656. constraint_violation = np.array([solver._constraint_violation_fn(x)
  657. for x in np.array(xs)])
  658. assert constraint_violation.shape == (3, 1, 3)
  659. # we need reasonable error messages if the constraint function doesn't
  660. # return the right thing
  661. def constr_f3(x):
  662. # returns (S, M), rather than (M, S)
  663. return constr_f2(x).T
  664. nlc2 = NonlinearConstraint(constr_f3, -np.inf, 1.8)
  665. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  666. constraints=(nlc, nlc2),
  667. vectorized=False)
  668. solver.vectorized = True
  669. with pytest.raises(
  670. RuntimeError, match="An array returned from a Constraint"
  671. ):
  672. solver._constraint_violation_fn(np.array(xs))
  673. def test_constraint_population_feasibilities(self):
  674. def constr_f(x):
  675. return [x[0] + x[1]]
  676. def constr_f2(x):
  677. return [x[0]**2 + x[1], x[0] - x[1]]
  678. nlc = NonlinearConstraint(constr_f, -np.inf, 1.9)
  679. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  680. constraints=(nlc,))
  681. # are population feasibilities correct
  682. # [0.5, 0.5] corresponds to scaled values of [1., 1.]
  683. feas, cv = solver._calculate_population_feasibilities(
  684. np.array([[0.5, 0.5], [1., 1.]]))
  685. assert_equal(feas, [False, False])
  686. assert_almost_equal(cv, np.array([[0.1], [2.1]]))
  687. assert cv.shape == (2, 1)
  688. nlc2 = NonlinearConstraint(constr_f2, -np.inf, 1.8)
  689. for vectorize in [False, True]:
  690. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  691. constraints=(nlc, nlc2),
  692. vectorized=vectorize,
  693. updating='deferred')
  694. feas, cv = solver._calculate_population_feasibilities(
  695. np.array([[0.5, 0.5], [0.6, 0.5]]))
  696. assert_equal(feas, [False, False])
  697. assert_almost_equal(cv, np.array([[0.1, 0.2, 0], [0.3, 0.64, 0]]))
  698. feas, cv = solver._calculate_population_feasibilities(
  699. np.array([[0.5, 0.5], [1., 1.]]))
  700. assert_equal(feas, [False, False])
  701. assert_almost_equal(cv, np.array([[0.1, 0.2, 0], [2.1, 4.2, 0]]))
  702. assert cv.shape == (2, 3)
  703. feas, cv = solver._calculate_population_feasibilities(
  704. np.array([[0.25, 0.25], [1., 1.]]))
  705. assert_equal(feas, [True, False])
  706. assert_almost_equal(cv, np.array([[0.0, 0.0, 0.], [2.1, 4.2, 0]]))
  707. assert cv.shape == (2, 3)
  708. def test_constraint_solve(self):
  709. def constr_f(x):
  710. return np.array([x[0] + x[1]])
  711. nlc = NonlinearConstraint(constr_f, -np.inf, 1.9)
  712. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  713. constraints=(nlc,))
  714. # trust-constr warns if the constraint function is linear
  715. with warns(UserWarning):
  716. res = solver.solve()
  717. assert constr_f(res.x) <= 1.9
  718. assert res.success
  719. @pytest.mark.fail_slow(10)
  720. def test_impossible_constraint(self):
  721. def constr_f(x):
  722. return np.array([x[0] + x[1]])
  723. nlc = NonlinearConstraint(constr_f, -np.inf, -1)
  724. solver = DifferentialEvolutionSolver(
  725. rosen, [(0, 2), (0, 2)], constraints=(nlc,), popsize=1, rng=1, maxiter=100
  726. )
  727. # a UserWarning is issued because the 'trust-constr' polishing is
  728. # attempted on the least infeasible solution found.
  729. with warns(UserWarning):
  730. res = solver.solve()
  731. assert res.maxcv > 0
  732. assert not res.success
  733. # test _promote_lowest_energy works when none of the population is
  734. # feasible. In this case, the solution with the lowest constraint
  735. # violation should be promoted.
  736. solver = DifferentialEvolutionSolver(
  737. rosen, [(0, 2), (0, 2)], constraints=(nlc,), polish=False)
  738. next(solver)
  739. assert not solver.feasible.all()
  740. assert not np.isfinite(solver.population_energies).all()
  741. # now swap two of the entries in the population
  742. l = 20
  743. cv = solver.constraint_violation[0]
  744. solver.population_energies[[0, l]] = solver.population_energies[[l, 0]]
  745. solver.population[[0, l], :] = solver.population[[l, 0], :]
  746. solver.constraint_violation[[0, l], :] = (
  747. solver.constraint_violation[[l, 0], :])
  748. solver._promote_lowest_energy()
  749. assert_equal(solver.constraint_violation[0], cv)
  750. def test_accept_trial(self):
  751. # _accept_trial(self, energy_trial, feasible_trial, cv_trial,
  752. # energy_orig, feasible_orig, cv_orig)
  753. def constr_f(x):
  754. return [x[0] + x[1]]
  755. nlc = NonlinearConstraint(constr_f, -np.inf, 1.9)
  756. solver = DifferentialEvolutionSolver(rosen, [(0, 2), (0, 2)],
  757. constraints=(nlc,))
  758. fn = solver._accept_trial
  759. # both solutions are feasible, select lower energy
  760. assert fn(0.1, True, np.array([0.]), 1.0, True, np.array([0.]))
  761. assert (fn(1.0, True, np.array([0.0]), 0.1, True, np.array([0.0])) is False)
  762. assert fn(0.1, True, np.array([0.]), 0.1, True, np.array([0.]))
  763. # trial is feasible, original is not
  764. assert fn(9.9, True, np.array([0.]), 1.0, False, np.array([1.]))
  765. # trial and original are infeasible
  766. # cv_trial have to be <= cv_original to be better
  767. assert (fn(0.1, False, np.array([0.5, 0.5]),
  768. 1.0, False, np.array([1., 1.0])))
  769. assert (fn(0.1, False, np.array([0.5, 0.5]),
  770. 1.0, False, np.array([1., 0.50])))
  771. assert not (fn(1.0, False, np.array([0.5, 0.5]),
  772. 1.0, False, np.array([1.0, 0.4])))
  773. def test_constraint_wrapper(self):
  774. lb = np.array([0, 20, 30])
  775. ub = np.array([0.5, np.inf, 70])
  776. x0 = np.array([1, 2, 3])
  777. pc = _ConstraintWrapper(Bounds(lb, ub), x0)
  778. assert (pc.violation(x0) > 0).any()
  779. assert (pc.violation([0.25, 21, 31]) == 0).all()
  780. # check vectorized Bounds constraint
  781. xs = np.arange(1, 16).reshape(5, 3)
  782. violations = []
  783. for x in xs:
  784. violations.append(pc.violation(x))
  785. np.testing.assert_allclose(pc.violation(xs.T), np.array(violations).T)
  786. x0 = np.array([1, 2, 3, 4])
  787. A = np.array([[1, 2, 3, 4], [5, 0, 0, 6], [7, 0, 8, 0]])
  788. pc = _ConstraintWrapper(LinearConstraint(A, -np.inf, 0), x0)
  789. assert (pc.violation(x0) > 0).any()
  790. assert (pc.violation([-10, 2, -10, 4]) == 0).all()
  791. # check vectorized LinearConstraint, for 7 lots of parameter vectors
  792. # with each parameter vector being 4 long, with 3 constraints
  793. # xs is the same shape as stored in the differential evolution
  794. # population, but it's sent to the violation function as (len(x), M)
  795. xs = np.arange(1, 29).reshape(7, 4)
  796. violations = []
  797. for x in xs:
  798. violations.append(pc.violation(x))
  799. np.testing.assert_allclose(pc.violation(xs.T), np.array(violations).T)
  800. pc = _ConstraintWrapper(LinearConstraint(csr_array(A), -np.inf, 0),
  801. x0)
  802. assert (pc.violation(x0) > 0).any()
  803. assert (pc.violation([-10, 2, -10, 4]) == 0).all()
  804. def fun(x):
  805. return A.dot(x)
  806. nonlinear = NonlinearConstraint(fun, -np.inf, 0)
  807. pc = _ConstraintWrapper(nonlinear, [-10, 2, -10, 4])
  808. assert (pc.violation(x0) > 0).any()
  809. assert (pc.violation([-10, 2, -10, 4]) == 0).all()
  810. def test_constraint_wrapper_violation(self):
  811. def cons_f(x):
  812. # written in vectorised form to accept an array of (N, S)
  813. # returning (M, S)
  814. # where N is the number of parameters,
  815. # S is the number of solution vectors to be examined,
  816. # and M is the number of constraint components
  817. return np.array([x[0] ** 2 + x[1],
  818. x[0] ** 2 - x[1]])
  819. nlc = NonlinearConstraint(cons_f, [-1, -0.8500], [2, 2])
  820. pc = _ConstraintWrapper(nlc, [0.5, 1])
  821. assert np.size(pc.bounds[0]) == 2
  822. xs = [(0.5, 1), (0.5, 1.2), (1.2, 1.2), (0.1, -1.2), (0.1, 2.0)]
  823. vs = [(0, 0), (0, 0.1), (0.64, 0), (0.19, 0), (0.01, 1.14)]
  824. for x, v in zip(xs, vs):
  825. assert_allclose(pc.violation(x), v)
  826. # now check that we can vectorize the constraint wrapper
  827. assert_allclose(pc.violation(np.array(xs).T),
  828. np.array(vs).T)
  829. assert pc.fun(np.array(xs).T).shape == (2, len(xs))
  830. assert pc.violation(np.array(xs).T).shape == (2, len(xs))
  831. assert pc.num_constr == 2
  832. assert pc.parameter_count == 2
  833. def test_matrix_linear_constraint(self):
  834. # gh20041 supplying an np.matrix to construct a LinearConstraint caused
  835. # _ConstraintWrapper to start returning constraint violations of the
  836. # wrong shape.
  837. with warnings.catch_warnings():
  838. warnings.simplefilter("ignore", PendingDeprecationWarning)
  839. matrix = np.matrix([[1, 1, 1, 1.],
  840. [2, 2, 2, 2.]])
  841. lc = LinearConstraint(matrix, 0, 1)
  842. x0 = np.ones(4)
  843. cw = _ConstraintWrapper(lc, x0)
  844. # the shape of the constraint violation should be the same as the number
  845. # of constraints applied.
  846. assert cw.violation(x0).shape == (2,)
  847. # let's try a vectorised violation call.
  848. xtrial = np.arange(4 * 5).reshape(4, 5)
  849. assert cw.violation(xtrial).shape == (2, 5)
  850. @pytest.mark.fail_slow(20)
  851. def test_L1(self):
  852. # Lampinen ([5]) test problem 1
  853. def f(x):
  854. x = np.hstack(([0], x)) # 1-indexed to match reference
  855. fun = np.sum(5*x[1:5]) - 5*x[1:5]@x[1:5] - np.sum(x[5:])
  856. return fun
  857. A = np.zeros((10, 14)) # 1-indexed to match reference
  858. A[1, [1, 2, 10, 11]] = 2, 2, 1, 1
  859. A[2, [1, 10]] = -8, 1
  860. A[3, [4, 5, 10]] = -2, -1, 1
  861. A[4, [1, 3, 10, 11]] = 2, 2, 1, 1
  862. A[5, [2, 11]] = -8, 1
  863. A[6, [6, 7, 11]] = -2, -1, 1
  864. A[7, [2, 3, 11, 12]] = 2, 2, 1, 1
  865. A[8, [3, 12]] = -8, 1
  866. A[9, [8, 9, 12]] = -2, -1, 1
  867. A = A[1:, 1:]
  868. b = np.array([10, 0, 0, 10, 0, 0, 10, 0, 0])
  869. L = LinearConstraint(A, -np.inf, b)
  870. bounds = [(0, 1)]*9 + [(0, 100)]*3 + [(0, 1)]
  871. # using a lower popsize to speed the test up
  872. res = differential_evolution(
  873. f, bounds, strategy='best1bin', rng=12345, constraints=(L,),
  874. popsize=5, tol=0.01
  875. )
  876. x_opt = (1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1)
  877. f_opt = -15
  878. assert_allclose(f(x_opt), f_opt, atol=6e-4)
  879. assert res.success
  880. assert_allclose(res.x, x_opt, atol=6e-4)
  881. assert_allclose(res.fun, f_opt, atol=5e-3)
  882. assert_(np.all(A@res.x <= b))
  883. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  884. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  885. # now repeat the same solve, using the same overall constraints,
  886. # but using a sparse array for the LinearConstraint instead of an
  887. # array
  888. L = LinearConstraint(csr_array(A), -np.inf, b)
  889. # using a lower popsize to speed the test up
  890. res = differential_evolution(
  891. f, bounds, strategy='best1bin', rng=1211134, constraints=(L,),
  892. popsize=2, tol=0.05
  893. )
  894. assert_allclose(f(x_opt), f_opt)
  895. assert res.success
  896. assert_allclose(res.x, x_opt, atol=5e-4)
  897. assert_allclose(res.fun, f_opt, atol=5e-3)
  898. assert_(np.all(A@res.x <= b))
  899. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  900. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  901. # now repeat the same solve, using the same overall constraints,
  902. # but specify half the constraints in terms of LinearConstraint,
  903. # and the other half by NonlinearConstraint
  904. def c1(x):
  905. x = np.hstack(([0], x))
  906. return [2*x[2] + 2*x[3] + x[11] + x[12],
  907. -8*x[3] + x[12]]
  908. def c2(x):
  909. x = np.hstack(([0], x))
  910. return -2*x[8] - x[9] + x[12]
  911. L = LinearConstraint(A[:5, :], -np.inf, b[:5])
  912. L2 = LinearConstraint(A[5:6, :], -np.inf, b[5:6])
  913. N = NonlinearConstraint(c1, -np.inf, b[6:8])
  914. N2 = NonlinearConstraint(c2, -np.inf, b[8:9])
  915. constraints = (L, N, L2, N2)
  916. with warnings.catch_warnings():
  917. warnings.simplefilter("ignore", UserWarning)
  918. res = differential_evolution(
  919. f, bounds, strategy='best1bin', rng=1211134,
  920. constraints=constraints, popsize=2, tol=0.05
  921. )
  922. assert_allclose(res.x, x_opt, atol=6e-4)
  923. assert_allclose(res.fun, f_opt, atol=5e-3)
  924. assert_(np.all(A@res.x <= b))
  925. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  926. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  927. @pytest.mark.fail_slow(10)
  928. def test_L2(self):
  929. # Lampinen ([5]) test problem 2
  930. def f(x):
  931. x = np.hstack(([0], x)) # 1-indexed to match reference
  932. fun = ((x[1]-10)**2 + 5*(x[2]-12)**2 + x[3]**4 + 3*(x[4]-11)**2 +
  933. 10*x[5]**6 + 7*x[6]**2 + x[7]**4 - 4*x[6]*x[7] - 10*x[6] -
  934. 8*x[7])
  935. return fun
  936. def c1(x):
  937. x = np.hstack(([0], x)) # 1-indexed to match reference
  938. return [127 - 2*x[1]**2 - 3*x[2]**4 - x[3] - 4*x[4]**2 - 5*x[5],
  939. 196 - 23*x[1] - x[2]**2 - 6*x[6]**2 + 8*x[7],
  940. 282 - 7*x[1] - 3*x[2] - 10*x[3]**2 - x[4] + x[5],
  941. -4*x[1]**2 - x[2]**2 + 3*x[1]*x[2] - 2*x[3]**2 -
  942. 5*x[6] + 11*x[7]]
  943. N = NonlinearConstraint(c1, 0, np.inf)
  944. bounds = [(-10, 10)]*7
  945. constraints = (N)
  946. with warnings.catch_warnings():
  947. warnings.simplefilter("ignore", UserWarning)
  948. res = differential_evolution(f, bounds, strategy='best1bin',
  949. rng=1234, constraints=constraints)
  950. f_opt = 680.6300599487869
  951. x_opt = (2.330499, 1.951372, -0.4775414, 4.365726,
  952. -0.6244870, 1.038131, 1.594227)
  953. assert_allclose(f(x_opt), f_opt)
  954. assert_allclose(res.fun, f_opt)
  955. assert_allclose(res.x, x_opt, atol=1e-5)
  956. assert res.success
  957. assert_(np.all(np.array(c1(res.x)) >= 0))
  958. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  959. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  960. @pytest.mark.fail_slow(10)
  961. def test_L3(self):
  962. # Lampinen ([5]) test problem 3
  963. def f(x):
  964. x = np.hstack(([0], x)) # 1-indexed to match reference
  965. fun = (x[1]**2 + x[2]**2 + x[1]*x[2] - 14*x[1] - 16*x[2] +
  966. (x[3]-10)**2 + 4*(x[4]-5)**2 + (x[5]-3)**2 + 2*(x[6]-1)**2 +
  967. 5*x[7]**2 + 7*(x[8]-11)**2 + 2*(x[9]-10)**2 +
  968. (x[10] - 7)**2 + 45
  969. )
  970. return fun # maximize
  971. A = np.zeros((4, 11))
  972. A[1, [1, 2, 7, 8]] = -4, -5, 3, -9
  973. A[2, [1, 2, 7, 8]] = -10, 8, 17, -2
  974. A[3, [1, 2, 9, 10]] = 8, -2, -5, 2
  975. A = A[1:, 1:]
  976. b = np.array([-105, 0, -12])
  977. def c1(x):
  978. x = np.hstack(([0], x)) # 1-indexed to match reference
  979. return [3*x[1] - 6*x[2] - 12*(x[9]-8)**2 + 7*x[10],
  980. -3*(x[1]-2)**2 - 4*(x[2]-3)**2 - 2*x[3]**2 + 7*x[4] + 120,
  981. -x[1]**2 - 2*(x[2]-2)**2 + 2*x[1]*x[2] - 14*x[5] + 6*x[6],
  982. -5*x[1]**2 - 8*x[2] - (x[3]-6)**2 + 2*x[4] + 40,
  983. -0.5*(x[1]-8)**2 - 2*(x[2]-4)**2 - 3*x[5]**2 + x[6] + 30]
  984. L = LinearConstraint(A, b, np.inf)
  985. N = NonlinearConstraint(c1, 0, np.inf)
  986. bounds = [(-10, 10)]*10
  987. constraints = (L, N)
  988. with warnings.catch_warnings():
  989. warnings.simplefilter("ignore", UserWarning)
  990. res = differential_evolution(f, bounds, rng=1234,
  991. constraints=constraints, popsize=3)
  992. x_opt = (2.171996, 2.363683, 8.773926, 5.095984, 0.9906548,
  993. 1.430574, 1.321644, 9.828726, 8.280092, 8.375927)
  994. f_opt = 24.3062091
  995. assert_allclose(f(x_opt), f_opt, atol=1e-5)
  996. assert_allclose(res.x, x_opt, atol=1e-6)
  997. assert_allclose(res.fun, f_opt, atol=1e-5)
  998. assert res.success
  999. assert_(np.all(A @ res.x >= b))
  1000. assert_(np.all(np.array(c1(res.x)) >= 0))
  1001. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1002. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1003. @pytest.mark.fail_slow(10)
  1004. def test_L4(self):
  1005. # Lampinen ([5]) test problem 4
  1006. def f(x):
  1007. return np.sum(x[:3])
  1008. A = np.zeros((4, 9))
  1009. A[1, [4, 6]] = 0.0025, 0.0025
  1010. A[2, [5, 7, 4]] = 0.0025, 0.0025, -0.0025
  1011. A[3, [8, 5]] = 0.01, -0.01
  1012. A = A[1:, 1:]
  1013. b = np.array([1, 1, 1])
  1014. def c1(x):
  1015. x = np.hstack(([0], x)) # 1-indexed to match reference
  1016. return [x[1]*x[6] - 833.33252*x[4] - 100*x[1] + 83333.333,
  1017. x[2]*x[7] - 1250*x[5] - x[2]*x[4] + 1250*x[4],
  1018. x[3]*x[8] - 1250000 - x[3]*x[5] + 2500*x[5]]
  1019. L = LinearConstraint(A, -np.inf, 1)
  1020. N = NonlinearConstraint(c1, 0, np.inf)
  1021. bounds = [(100, 10000)] + [(1000, 10000)]*2 + [(10, 1000)]*5
  1022. constraints = (L, N)
  1023. with warnings.catch_warnings():
  1024. warnings.simplefilter("ignore", UserWarning)
  1025. res = differential_evolution(
  1026. f, bounds, strategy='best1bin', rng=1234,
  1027. constraints=constraints, popsize=3, tol=0.05
  1028. )
  1029. f_opt = 7049.248
  1030. x_opt = [579.306692, 1359.97063, 5109.9707, 182.0177, 295.601172,
  1031. 217.9823, 286.416528, 395.601172]
  1032. assert_allclose(f(x_opt), f_opt, atol=0.001)
  1033. assert_allclose(res.fun, f_opt, atol=0.001)
  1034. # use higher tol here for 32-bit Windows, see gh-11693
  1035. if (platform.system() == 'Windows' and np.dtype(np.intp).itemsize < 8):
  1036. assert_allclose(res.x, x_opt, rtol=2.4e-6, atol=0.0035)
  1037. else:
  1038. # tolerance determined from macOS + MKL failure, see gh-12701
  1039. assert_allclose(res.x, x_opt, rtol=5e-6, atol=0.0024)
  1040. assert res.success
  1041. assert_(np.all(A @ res.x <= b))
  1042. assert_(np.all(np.array(c1(res.x)) >= 0))
  1043. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1044. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1045. @pytest.mark.fail_slow(10)
  1046. def test_L5(self):
  1047. # Lampinen ([5]) test problem 5
  1048. def f(x):
  1049. x = np.hstack(([0], x)) # 1-indexed to match reference
  1050. fun = (np.sin(2*np.pi*x[1])**3*np.sin(2*np.pi*x[2]) /
  1051. (x[1]**3*(x[1]+x[2])))
  1052. return -fun # maximize
  1053. def c1(x):
  1054. x = np.hstack(([0], x)) # 1-indexed to match reference
  1055. return [x[1]**2 - x[2] + 1,
  1056. 1 - x[1] + (x[2]-4)**2]
  1057. N = NonlinearConstraint(c1, -np.inf, 0)
  1058. bounds = [(0, 10)]*2
  1059. constraints = (N)
  1060. res = differential_evolution(f, bounds, strategy='rand1bin', rng=1234,
  1061. constraints=constraints)
  1062. x_opt = (1.22797135, 4.24537337)
  1063. f_opt = -0.095825
  1064. assert_allclose(f(x_opt), f_opt, atol=2e-5)
  1065. assert_allclose(res.fun, f_opt, atol=1e-4)
  1066. assert res.success
  1067. assert_(np.all(np.array(c1(res.x)) <= 0))
  1068. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1069. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1070. @pytest.mark.fail_slow(10)
  1071. def test_L6(self):
  1072. # Lampinen ([5]) test problem 6
  1073. def f(x):
  1074. x = np.hstack(([0], x)) # 1-indexed to match reference
  1075. fun = (x[1]-10)**3 + (x[2] - 20)**3
  1076. return fun
  1077. def c1(x):
  1078. x = np.hstack(([0], x)) # 1-indexed to match reference
  1079. return [(x[1]-5)**2 + (x[2] - 5)**2 - 100,
  1080. -(x[1]-6)**2 - (x[2] - 5)**2 + 82.81]
  1081. N = NonlinearConstraint(c1, 0, np.inf)
  1082. bounds = [(13, 100), (0, 100)]
  1083. constraints = (N)
  1084. res = differential_evolution(f, bounds, strategy='rand1bin', rng=1234,
  1085. constraints=constraints, tol=1e-7)
  1086. x_opt = (14.095, 0.84296)
  1087. f_opt = -6961.814744
  1088. assert_allclose(f(x_opt), f_opt, atol=1e-6)
  1089. assert_allclose(res.fun, f_opt, atol=0.001)
  1090. assert_allclose(res.x, x_opt, atol=1e-4)
  1091. assert res.success
  1092. assert_(np.all(np.array(c1(res.x)) >= 0))
  1093. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1094. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1095. def test_L7(self):
  1096. # Lampinen ([5]) test problem 7
  1097. def f(x):
  1098. x = np.hstack(([0], x)) # 1-indexed to match reference
  1099. fun = (5.3578547*x[3]**2 + 0.8356891*x[1]*x[5] +
  1100. 37.293239*x[1] - 40792.141)
  1101. return fun
  1102. def c1(x):
  1103. x = np.hstack(([0], x)) # 1-indexed to match reference
  1104. return [
  1105. 85.334407 + 0.0056858*x[2]*x[5] + 0.0006262*x[1]*x[4] -
  1106. 0.0022053*x[3]*x[5],
  1107. 80.51249 + 0.0071317*x[2]*x[5] + 0.0029955*x[1]*x[2] +
  1108. 0.0021813*x[3]**2,
  1109. 9.300961 + 0.0047026*x[3]*x[5] + 0.0012547*x[1]*x[3] +
  1110. 0.0019085*x[3]*x[4]
  1111. ]
  1112. N = NonlinearConstraint(c1, [0, 90, 20], [92, 110, 25])
  1113. bounds = [(78, 102), (33, 45)] + [(27, 45)]*3
  1114. constraints = (N)
  1115. res = differential_evolution(f, bounds, strategy='rand1bin', rng=1234,
  1116. constraints=constraints)
  1117. # using our best solution, rather than Lampinen/Koziel. Koziel solution
  1118. # doesn't satisfy constraints, Lampinen f_opt just plain wrong.
  1119. x_opt = [78.00000686, 33.00000362, 29.99526064, 44.99999971,
  1120. 36.77579979]
  1121. f_opt = -30665.537578
  1122. assert_allclose(f(x_opt), f_opt)
  1123. assert_allclose(res.x, x_opt, atol=1e-3)
  1124. assert_allclose(res.fun, f_opt, atol=1e-3)
  1125. assert res.success
  1126. assert_(np.all(np.array(c1(res.x)) >= np.array([0, 90, 20])))
  1127. assert_(np.all(np.array(c1(res.x)) <= np.array([92, 110, 25])))
  1128. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1129. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1130. @pytest.mark.xslow
  1131. @pytest.mark.xfail(platform.machine() == 'ppc64le',
  1132. reason="fails on ppc64le")
  1133. def test_L8(self):
  1134. def f(x):
  1135. x = np.hstack(([0], x)) # 1-indexed to match reference
  1136. fun = 3*x[1] + 0.000001*x[1]**3 + 2*x[2] + 0.000002/3*x[2]**3
  1137. return fun
  1138. A = np.zeros((3, 5))
  1139. A[1, [4, 3]] = 1, -1
  1140. A[2, [3, 4]] = 1, -1
  1141. A = A[1:, 1:]
  1142. b = np.array([-.55, -.55])
  1143. def c1(x):
  1144. x = np.hstack(([0], x)) # 1-indexed to match reference
  1145. return [
  1146. 1000*np.sin(-x[3]-0.25) + 1000*np.sin(-x[4]-0.25) +
  1147. 894.8 - x[1],
  1148. 1000*np.sin(x[3]-0.25) + 1000*np.sin(x[3]-x[4]-0.25) +
  1149. 894.8 - x[2],
  1150. 1000*np.sin(x[4]-0.25) + 1000*np.sin(x[4]-x[3]-0.25) +
  1151. 1294.8
  1152. ]
  1153. L = LinearConstraint(A, b, np.inf)
  1154. N = NonlinearConstraint(c1, np.full(3, -0.001), np.full(3, 0.001))
  1155. bounds = [(0, 1200)]*2+[(-.55, .55)]*2
  1156. constraints = (L, N)
  1157. with warnings.catch_warnings():
  1158. warnings.simplefilter("ignore", UserWarning)
  1159. # original Lampinen test was with rand1bin, but that takes a
  1160. # huge amount of CPU time. Changing strategy to best1bin speeds
  1161. # things up a lot
  1162. res = differential_evolution(f, bounds, strategy='best1bin',
  1163. rng=1234, constraints=constraints,
  1164. maxiter=5000)
  1165. x_opt = (679.9453, 1026.067, 0.1188764, -0.3962336)
  1166. f_opt = 5126.4981
  1167. assert_allclose(f(x_opt), f_opt, atol=1e-3)
  1168. assert_allclose(res.x[:2], x_opt[:2], atol=2e-3)
  1169. assert_allclose(res.x[2:], x_opt[2:], atol=2e-3)
  1170. assert_allclose(res.fun, f_opt, atol=2e-2)
  1171. assert res.success
  1172. assert_(np.all(A@res.x >= b))
  1173. assert_(np.all(np.array(c1(res.x)) >= -0.001))
  1174. assert_(np.all(np.array(c1(res.x)) <= 0.001))
  1175. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1176. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1177. @pytest.mark.fail_slow(5)
  1178. def test_L9(self):
  1179. # Lampinen ([5]) test problem 9
  1180. def f(x):
  1181. x = np.hstack(([0], x)) # 1-indexed to match reference
  1182. return x[1]**2 + (x[2]-1)**2
  1183. def c1(x):
  1184. x = np.hstack(([0], x)) # 1-indexed to match reference
  1185. return [x[2] - x[1]**2]
  1186. N = NonlinearConstraint(c1, [-.001], [0.001])
  1187. bounds = [(-1, 1)]*2
  1188. constraints = (N)
  1189. res = differential_evolution(f, bounds, strategy='rand1bin', rng=1234,
  1190. constraints=constraints)
  1191. x_opt = [np.sqrt(2)/2, 0.5]
  1192. f_opt = 0.75
  1193. assert_allclose(f(x_opt), f_opt)
  1194. assert_allclose(np.abs(res.x), x_opt, atol=1e-3)
  1195. assert_allclose(res.fun, f_opt, atol=1e-3)
  1196. assert res.success
  1197. assert_(np.all(np.array(c1(res.x)) >= -0.001))
  1198. assert_(np.all(np.array(c1(res.x)) <= 0.001))
  1199. assert_(np.all(res.x >= np.array(bounds)[:, 0]))
  1200. assert_(np.all(res.x <= np.array(bounds)[:, 1]))
  1201. @pytest.mark.fail_slow(10)
  1202. def test_integrality(self):
  1203. # test fitting discrete distribution to data
  1204. rng = np.random.default_rng(6519843218105)
  1205. dist = stats.nbinom
  1206. shapes = (5, 0.5)
  1207. x = dist.rvs(*shapes, size=10000, random_state=rng)
  1208. def func(p, *args):
  1209. dist, x = args
  1210. # negative log-likelihood function
  1211. ll = -np.log(dist.pmf(x, *p)).sum(axis=-1)
  1212. if np.isnan(ll): # occurs when x is outside of support
  1213. ll = np.inf # we don't want that
  1214. return ll
  1215. integrality = [True, False]
  1216. bounds = [(1, 18), (0, 0.95)]
  1217. res = differential_evolution(func, bounds, args=(dist, x),
  1218. integrality=integrality, polish=False,
  1219. rng=rng)
  1220. # tolerance has to be fairly relaxed for the second parameter
  1221. # because we're fitting a distribution to random variates.
  1222. assert res.x[0] == 5
  1223. assert_allclose(res.x, shapes, rtol=0.025)
  1224. # check that we can still use integrality constraints with polishing
  1225. res2 = differential_evolution(func, bounds, args=(dist, x),
  1226. integrality=integrality, polish=True,
  1227. rng=rng)
  1228. def func2(p, *args):
  1229. n, dist, x = args
  1230. return func(np.array([n, p[0]]), dist, x)
  1231. # compare the DE derived solution to an LBFGSB solution (that doesn't
  1232. # have to find the integral values). Note we're setting x0 to be the
  1233. # output from the first DE result, thereby making the polishing step
  1234. # and this minimisation pretty much equivalent.
  1235. LBFGSB = minimize(func2, res2.x[1], args=(5, dist, x),
  1236. bounds=[(0, 0.95)])
  1237. assert_allclose(res2.x[1], LBFGSB.x)
  1238. assert res2.fun <= res.fun
  1239. def test_integrality_limits(self):
  1240. def f(x):
  1241. return x
  1242. integrality = [True, False, True]
  1243. bounds = [(0.2, 1.1), (0.9, 2.2), (3.3, 4.9)]
  1244. # no integrality constraints
  1245. solver = DifferentialEvolutionSolver(f, bounds=bounds, polish=False,
  1246. integrality=False)
  1247. assert_allclose(solver.limits[0], [0.2, 0.9, 3.3])
  1248. assert_allclose(solver.limits[1], [1.1, 2.2, 4.9])
  1249. # with integrality constraints
  1250. solver = DifferentialEvolutionSolver(f, bounds=bounds, polish=False,
  1251. integrality=integrality)
  1252. assert_allclose(solver.limits[0], [0.5, 0.9, 3.5])
  1253. assert_allclose(solver.limits[1], [1.5, 2.2, 4.5])
  1254. assert_equal(solver.integrality, [True, False, True])
  1255. assert solver.polish is False
  1256. bounds = [(-1.2, -0.9), (0.9, 2.2), (-10.3, 4.1)]
  1257. solver = DifferentialEvolutionSolver(f, bounds=bounds, polish=False,
  1258. integrality=integrality)
  1259. assert_allclose(solver.limits[0], [-1.5, 0.9, -10.5])
  1260. assert_allclose(solver.limits[1], [-0.5, 2.2, 4.5])
  1261. # A lower bound of -1.2 is converted to
  1262. # np.nextafter(np.ceil(-1.2) - 0.5, np.inf)
  1263. # with a similar process to the upper bound. Check that the
  1264. # conversions work
  1265. assert_allclose(np.round(solver.limits[0]), [-1.0, 1.0, -10.0])
  1266. assert_allclose(np.round(solver.limits[1]), [-1.0, 2.0, 4.0])
  1267. bounds = [(-10.2, -8.1), (0.9, 2.2), (-10.9, -9.9999)]
  1268. solver = DifferentialEvolutionSolver(f, bounds=bounds, polish=False,
  1269. integrality=integrality)
  1270. assert_allclose(solver.limits[0], [-10.5, 0.9, -10.5])
  1271. assert_allclose(solver.limits[1], [-8.5, 2.2, -9.5])
  1272. bounds = [(-10.2, -10.1), (0.9, 2.2), (-10.9, -9.9999)]
  1273. with pytest.raises(ValueError, match='One of the integrality'):
  1274. DifferentialEvolutionSolver(f, bounds=bounds, polish=False,
  1275. integrality=integrality)
  1276. @pytest.mark.fail_slow(10)
  1277. def test_vectorized(self):
  1278. def quadratic(x):
  1279. return np.sum(x**2)
  1280. def quadratic_vec(x):
  1281. return np.sum(x**2, axis=0)
  1282. # A vectorized function needs to accept (len(x), S) and return (S,)
  1283. with pytest.raises(RuntimeError, match='The vectorized function'):
  1284. differential_evolution(quadratic, self.bounds,
  1285. vectorized=True, updating='deferred')
  1286. # vectorized overrides the updating keyword, check for warning
  1287. with warns(UserWarning, match="differential_evolution: the 'vector"):
  1288. differential_evolution(quadratic_vec, self.bounds,
  1289. vectorized=True)
  1290. # vectorized defers to the workers keyword, check for warning
  1291. with warns(UserWarning, match="differential_evolution: the 'workers"):
  1292. differential_evolution(quadratic_vec, self.bounds,
  1293. vectorized=True, workers=map,
  1294. updating='deferred')
  1295. ncalls = [0]
  1296. def rosen_vec(x):
  1297. ncalls[0] += 1
  1298. return rosen(x)
  1299. bounds = [(0, 10), (0, 10)]
  1300. res1 = differential_evolution(rosen, bounds, updating='deferred',
  1301. rng=1)
  1302. res2 = differential_evolution(rosen_vec, bounds, vectorized=True,
  1303. updating='deferred', rng=1)
  1304. # the two minimisation runs should be functionally equivalent
  1305. assert_allclose(res1.x, res2.x)
  1306. assert ncalls[0] == res2.nfev
  1307. assert res1.nit == res2.nit
  1308. def test_vectorized_constraints(self):
  1309. def constr_f(x):
  1310. return np.array([x[0] + x[1]])
  1311. def constr_f2(x):
  1312. return np.array([x[0]**2 + x[1], x[0] - x[1]])
  1313. nlc1 = NonlinearConstraint(constr_f, -np.inf, 1.9)
  1314. nlc2 = NonlinearConstraint(constr_f2, (0.9, 0.5), (2.0, 2.0))
  1315. def rosen_vec(x):
  1316. # accept an (len(x0), S) array, returning a (S,) array
  1317. v = 100 * (x[1:] - x[:-1]**2.0)**2.0
  1318. v += (1 - x[:-1])**2.0
  1319. return np.squeeze(v)
  1320. bounds = [(0, 10), (0, 10)]
  1321. res1 = differential_evolution(rosen, bounds, updating='deferred',
  1322. rng=1, constraints=[nlc1, nlc2],
  1323. polish=False)
  1324. res2 = differential_evolution(rosen_vec, bounds, vectorized=True,
  1325. updating='deferred', rng=1,
  1326. constraints=[nlc1, nlc2],
  1327. polish=False)
  1328. # the two minimisation runs should be functionally equivalent
  1329. assert_allclose(res1.x, res2.x)
  1330. def test_polish_function(self):
  1331. # the polishing may be done by a callable
  1332. N = len(self.bounds)
  1333. def pf(fun, x, **kwds):
  1334. pf.res = minimize(fun, x, jac=rosen_der, method='trust-constr', **kwds)
  1335. return pf.res
  1336. pf.res = None
  1337. res = differential_evolution(rosen, self.bounds, polish=pf, maxiter=1, rng=0)
  1338. ref = differential_evolution(rosen, self.bounds, polish=True, maxiter=1, rng=0)
  1339. # res.success will be False because of the small number of iterations
  1340. # The solution produced by DE would be bad after only one iteration.
  1341. # However, we still expect a good answer if the polishing worked
  1342. assert res.jac is not None
  1343. assert_allclose(res.x, np.ones(N), atol=1e-6)
  1344. # test that the `pf` callable is really used; it's not just truthy
  1345. assert res.fun == pf.res.fun
  1346. assert ref.fun != res.fun
  1347. def dummy_pf(func, x, **kwds):
  1348. assert "bounds" in kwds
  1349. assert isinstance(kwds["bounds"], Bounds)
  1350. assert "constraints" in kwds
  1351. return np.ones(N)
  1352. with assert_raises(
  1353. ValueError,
  1354. match="The result from a user defined polishing"
  1355. ):
  1356. differential_evolution(
  1357. rosen,
  1358. self.bounds,
  1359. polish=dummy_pf,
  1360. maxiter=1
  1361. )
  1362. # check that output of polish==False followed by a manual polish
  1363. # is the same as a callable(polish). Limit maxiter on DE so polisher
  1364. # has to do all the work.
  1365. def pf(func, x, **kwds):
  1366. return minimize(func, x, method='L-BFGS-B', **kwds)
  1367. rng = np.random.default_rng(110980928209)
  1368. res = differential_evolution(
  1369. rosen, self.bounds, polish=False, rng=rng, maxiter=1
  1370. )
  1371. res = minimize(rosen, res.x, bounds=self.bounds, method='L-BFGS-B')
  1372. rng = np.random.default_rng(110980928209)
  1373. res2 = differential_evolution(
  1374. rosen, self.bounds, polish=pf, rng=rng, maxiter=1
  1375. )
  1376. # could possibly do assert_allequal, but not sure about bitwise exactness
  1377. # for repeated runs from same starting point
  1378. assert_allclose(res.x, res2.x)
  1379. assert_allclose(res.fun, res2.fun)
  1380. def test_constraint_violation_error_message(self):
  1381. def func(x):
  1382. return np.cos(x[0]) + np.sin(x[1])
  1383. # Intentionally infeasible constraints.
  1384. c0 = NonlinearConstraint(lambda x: x[1] - (x[0]-1)**2, 0, np.inf)
  1385. c1 = NonlinearConstraint(lambda x: x[1] + x[0]**2, -np.inf, 0)
  1386. result = differential_evolution(func,
  1387. bounds=[(-1, 2), (-1, 1)],
  1388. constraints=[c0, c1],
  1389. maxiter=10,
  1390. polish=False,
  1391. rng=864197532)
  1392. assert result.success is False
  1393. # The numerical value in the error message might be sensitive to
  1394. # changes in the implementation. It can be updated if the code is
  1395. # changed. The essential part of the test is that there is a number
  1396. # after the '=', so if necessary, the text could be reduced to, say,
  1397. # "MAXCV = 0.".
  1398. assert "MAXCV = 0." in result.message
  1399. @pytest.mark.fail_slow(20) # fail-slow exception by request - see gh-20806
  1400. def test_strategy_fn(self):
  1401. # examines ability to customize strategy by mimicking one of the
  1402. # in-built strategies
  1403. parameter_count = 4
  1404. popsize = 10
  1405. bounds = [(0, 10.)] * parameter_count
  1406. total_popsize = parameter_count * popsize
  1407. mutation = 0.8
  1408. recombination = 0.7
  1409. calls = [0]
  1410. def custom_strategy_fn(candidate, population, rng=None):
  1411. calls[0] += 1
  1412. trial = np.copy(population[candidate])
  1413. fill_point = rng.choice(parameter_count)
  1414. pool = np.arange(total_popsize)
  1415. rng.shuffle(pool)
  1416. idxs = pool[:2 + 1]
  1417. idxs = idxs[idxs != candidate][:2]
  1418. r0, r1 = idxs[:2]
  1419. bprime = (population[0] + mutation *
  1420. (population[r0] - population[r1]))
  1421. crossovers = rng.uniform(size=parameter_count)
  1422. crossovers = crossovers < recombination
  1423. crossovers[fill_point] = True
  1424. trial = np.where(crossovers, bprime, trial)
  1425. return trial
  1426. solver = DifferentialEvolutionSolver(
  1427. rosen,
  1428. bounds,
  1429. popsize=popsize,
  1430. recombination=recombination,
  1431. mutation=mutation,
  1432. maxiter=2,
  1433. strategy=custom_strategy_fn,
  1434. rng=10,
  1435. polish=False
  1436. )
  1437. assert solver.strategy is custom_strategy_fn
  1438. solver.solve()
  1439. assert calls[0] > 0
  1440. # check custom strategy works with updating='deferred'
  1441. res = differential_evolution(
  1442. rosen, bounds, strategy=custom_strategy_fn, updating='deferred'
  1443. )
  1444. assert res.success
  1445. def custom_strategy_fn(candidate, population, rng=None):
  1446. return np.array([1.0, 2.0])
  1447. with pytest.raises(RuntimeError, match="strategy*"):
  1448. differential_evolution(
  1449. rosen,
  1450. bounds,
  1451. strategy=custom_strategy_fn
  1452. )
  1453. def test_reference_cycles(self):
  1454. with assert_deallocated(DifferentialEvolutionSolver, rosen, [(0, 10)]*2):
  1455. pass