test__dual_annealing.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # Dual annealing unit tests implementation.
  2. # Copyright (c) 2018 Sylvain Gubian <sylvain.gubian@pmi.com>,
  3. # Yang Xiang <yang.xiang@pmi.com>
  4. # Author: Sylvain Gubian, PMP S.A.
  5. """
  6. Unit tests for the dual annealing global optimizer
  7. """
  8. from scipy.optimize import dual_annealing
  9. from scipy.optimize._dual_annealing import VisitingDistribution
  10. from scipy.optimize._dual_annealing import ObjectiveFunWrapper
  11. from scipy.optimize._dual_annealing import EnergyState
  12. from scipy.optimize._dual_annealing import LocalSearchWrapper
  13. from scipy.optimize import rosen, rosen_der
  14. import numpy as np
  15. from numpy.testing import (assert_equal, TestCase, assert_allclose,
  16. assert_array_less)
  17. from pytest import raises as assert_raises
  18. from scipy._lib._util import check_random_state
  19. class TestDualAnnealing(TestCase):
  20. def setUp(self):
  21. # A function that returns always infinity for initialization tests
  22. self.weirdfunc = lambda x: np.inf
  23. # 2-D bounds for testing function
  24. self.ld_bounds = [(-5.12, 5.12)] * 2
  25. # 4-D bounds for testing function
  26. self.hd_bounds = self.ld_bounds * 4
  27. # Number of values to be generated for testing visit function
  28. self.nbtestvalues = 5000
  29. self.high_temperature = 5230
  30. self.low_temperature = 0.1
  31. self.qv = 2.62
  32. self.seed = 1234
  33. self.rs = check_random_state(self.seed)
  34. self.nb_fun_call = 0
  35. self.ngev = 0
  36. def tearDown(self):
  37. pass
  38. def callback(self, x, f, context):
  39. # For testing callback mechanism. Should stop for e <= 1 as
  40. # the callback function returns True
  41. if f <= 1.0:
  42. return True
  43. def func(self, x, args=()):
  44. # Using Rastrigin function for performing tests
  45. if args:
  46. shift = args
  47. else:
  48. shift = 0
  49. y = np.sum((x - shift) ** 2 - 10 * np.cos(2 * np.pi * (
  50. x - shift))) + 10 * np.size(x) + shift
  51. self.nb_fun_call += 1
  52. return y
  53. def rosen_der_wrapper(self, x, args=()):
  54. self.ngev += 1
  55. return rosen_der(x, *args)
  56. def test_visiting_stepping(self):
  57. lu = list(zip(*self.ld_bounds))
  58. lower = np.array(lu[0])
  59. upper = np.array(lu[1])
  60. dim = lower.size
  61. vd = VisitingDistribution(lower, upper, self.qv, self.rs)
  62. values = np.zeros(dim)
  63. x_step_low = vd.visiting(values, 0, self.high_temperature)
  64. # Make sure that only the first component is changed
  65. assert_equal(np.not_equal(x_step_low, 0), True)
  66. values = np.zeros(dim)
  67. x_step_high = vd.visiting(values, dim, self.high_temperature)
  68. # Make sure that component other than at dim has changed
  69. assert_equal(np.not_equal(x_step_high[0], 0), True)
  70. def test_visiting_dist_high_temperature(self):
  71. lu = list(zip(*self.ld_bounds))
  72. lower = np.array(lu[0])
  73. upper = np.array(lu[1])
  74. vd = VisitingDistribution(lower, upper, self.qv, self.rs)
  75. values = np.zeros(self.nbtestvalues)
  76. for i in np.arange(self.nbtestvalues):
  77. values[i] = vd.visit_fn(self.high_temperature)
  78. # Visiting distribution is a distorted version of Cauchy-Lorentz
  79. # distribution, and as no 1st and higher moments (no mean defined,
  80. # no variance defined).
  81. # Check that big tails values are generated
  82. assert_array_less(np.min(values), 1e-10)
  83. assert_array_less(1e+10, np.max(values))
  84. def test_reset(self):
  85. owf = ObjectiveFunWrapper(self.weirdfunc)
  86. lu = list(zip(*self.ld_bounds))
  87. lower = np.array(lu[0])
  88. upper = np.array(lu[1])
  89. es = EnergyState(lower, upper)
  90. assert_raises(ValueError, es.reset, owf, check_random_state(None))
  91. def test_low_dim(self):
  92. ret = dual_annealing(
  93. self.func, self.ld_bounds, seed=self.seed)
  94. assert_allclose(ret.fun, 0., atol=1e-12)
  95. def test_high_dim(self):
  96. ret = dual_annealing(self.func, self.hd_bounds)
  97. assert_allclose(ret.fun, 0., atol=1e-12)
  98. def test_low_dim_no_ls(self):
  99. ret = dual_annealing(self.func, self.ld_bounds,
  100. no_local_search=True)
  101. assert_allclose(ret.fun, 0., atol=1e-4)
  102. def test_high_dim_no_ls(self):
  103. ret = dual_annealing(self.func, self.hd_bounds,
  104. no_local_search=True)
  105. assert_allclose(ret.fun, 0., atol=1e-4)
  106. def test_nb_fun_call(self):
  107. ret = dual_annealing(self.func, self.ld_bounds)
  108. assert_equal(self.nb_fun_call, ret.nfev)
  109. def test_nb_fun_call_no_ls(self):
  110. ret = dual_annealing(self.func, self.ld_bounds,
  111. no_local_search=True)
  112. assert_equal(self.nb_fun_call, ret.nfev)
  113. def test_max_reinit(self):
  114. assert_raises(ValueError, dual_annealing, self.weirdfunc,
  115. self.ld_bounds)
  116. def test_reproduce(self):
  117. seed = 1234
  118. res1 = dual_annealing(self.func, self.ld_bounds, seed=seed)
  119. res2 = dual_annealing(self.func, self.ld_bounds, seed=seed)
  120. res3 = dual_annealing(self.func, self.ld_bounds, seed=seed)
  121. # If we have reproducible results, x components found has to
  122. # be exactly the same, which is not the case with no seeding
  123. assert_equal(res1.x, res2.x)
  124. assert_equal(res1.x, res3.x)
  125. def test_bounds_integrity(self):
  126. wrong_bounds = [(-5.12, 5.12), (1, 0), (5.12, 5.12)]
  127. assert_raises(ValueError, dual_annealing, self.func,
  128. wrong_bounds)
  129. def test_bound_validity(self):
  130. invalid_bounds = [(-5, 5), (-np.inf, 0), (-5, 5)]
  131. assert_raises(ValueError, dual_annealing, self.func,
  132. invalid_bounds)
  133. invalid_bounds = [(-5, 5), (0, np.inf), (-5, 5)]
  134. assert_raises(ValueError, dual_annealing, self.func,
  135. invalid_bounds)
  136. invalid_bounds = [(-5, 5), (0, np.nan), (-5, 5)]
  137. assert_raises(ValueError, dual_annealing, self.func,
  138. invalid_bounds)
  139. def test_max_fun_ls(self):
  140. ret = dual_annealing(self.func, self.ld_bounds, maxfun=100)
  141. ls_max_iter = min(max(
  142. len(self.ld_bounds) * LocalSearchWrapper.LS_MAXITER_RATIO,
  143. LocalSearchWrapper.LS_MAXITER_MIN),
  144. LocalSearchWrapper.LS_MAXITER_MAX)
  145. assert ret.nfev <= 100 + ls_max_iter
  146. def test_max_fun_no_ls(self):
  147. ret = dual_annealing(self.func, self.ld_bounds,
  148. no_local_search=True, maxfun=500)
  149. assert ret.nfev <= 500
  150. def test_maxiter(self):
  151. ret = dual_annealing(self.func, self.ld_bounds, maxiter=700)
  152. assert ret.nit <= 700
  153. # Testing that args are passed correctly for dual_annealing
  154. def test_fun_args_ls(self):
  155. ret = dual_annealing(self.func, self.ld_bounds,
  156. args=((3.14159, )))
  157. assert_allclose(ret.fun, 3.14159, atol=1e-6)
  158. # Testing that args are passed correctly for pure simulated annealing
  159. def test_fun_args_no_ls(self):
  160. ret = dual_annealing(self.func, self.ld_bounds,
  161. args=((3.14159, )), no_local_search=True)
  162. assert_allclose(ret.fun, 3.14159, atol=1e-4)
  163. def test_callback_stop(self):
  164. # Testing that callback make the algorithm stop for
  165. # fun value <= 1.0 (see callback method)
  166. ret = dual_annealing(self.func, self.ld_bounds,
  167. callback=self.callback)
  168. assert ret.fun <= 1.0
  169. assert 'stop early' in ret.message[0]
  170. def test_neldermed_ls_minimizer(self):
  171. minimizer_opts = {
  172. 'method': 'Nelder-Mead',
  173. }
  174. ret = dual_annealing(self.func, self.ld_bounds,
  175. local_search_options=minimizer_opts)
  176. assert_allclose(ret.fun, 0., atol=1e-6)
  177. def test_powell_ls_minimizer(self):
  178. minimizer_opts = {
  179. 'method': 'Powell',
  180. }
  181. ret = dual_annealing(self.func, self.ld_bounds,
  182. local_search_options=minimizer_opts)
  183. assert_allclose(ret.fun, 0., atol=1e-8)
  184. def test_cg_ls_minimizer(self):
  185. minimizer_opts = {
  186. 'method': 'CG',
  187. }
  188. ret = dual_annealing(self.func, self.ld_bounds,
  189. local_search_options=minimizer_opts)
  190. assert_allclose(ret.fun, 0., atol=1e-8)
  191. def test_bfgs_ls_minimizer(self):
  192. minimizer_opts = {
  193. 'method': 'BFGS',
  194. }
  195. ret = dual_annealing(self.func, self.ld_bounds,
  196. local_search_options=minimizer_opts)
  197. assert_allclose(ret.fun, 0., atol=1e-8)
  198. def test_tnc_ls_minimizer(self):
  199. minimizer_opts = {
  200. 'method': 'TNC',
  201. }
  202. ret = dual_annealing(self.func, self.ld_bounds,
  203. local_search_options=minimizer_opts)
  204. assert_allclose(ret.fun, 0., atol=1e-8)
  205. def test_colyba_ls_minimizer(self):
  206. minimizer_opts = {
  207. 'method': 'COBYLA',
  208. }
  209. ret = dual_annealing(self.func, self.ld_bounds,
  210. local_search_options=minimizer_opts)
  211. assert_allclose(ret.fun, 0., atol=1e-5)
  212. def test_slsqp_ls_minimizer(self):
  213. minimizer_opts = {
  214. 'method': 'SLSQP',
  215. }
  216. ret = dual_annealing(self.func, self.ld_bounds,
  217. local_search_options=minimizer_opts)
  218. assert_allclose(ret.fun, 0., atol=1e-7)
  219. def test_wrong_restart_temp(self):
  220. assert_raises(ValueError, dual_annealing, self.func,
  221. self.ld_bounds, restart_temp_ratio=1)
  222. assert_raises(ValueError, dual_annealing, self.func,
  223. self.ld_bounds, restart_temp_ratio=0)
  224. def test_gradient_gnev(self):
  225. minimizer_opts = {
  226. 'jac': self.rosen_der_wrapper,
  227. }
  228. ret = dual_annealing(rosen, self.ld_bounds,
  229. local_search_options=minimizer_opts)
  230. assert ret.njev == self.ngev