test_tnc.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. """
  2. Unit tests for TNC optimization routine from tnc.py
  3. """
  4. import pytest
  5. from numpy.testing import assert_allclose, assert_equal
  6. import numpy as np
  7. from math import pow
  8. from scipy import optimize
  9. from scipy.sparse._sputils import matrix
  10. class TestTnc:
  11. """TNC non-linear optimization.
  12. These tests are taken from Prof. K. Schittkowski's test examples
  13. for constrained non-linear programming.
  14. http://www.uni-bayreuth.de/departments/math/~kschittkowski/home.htm
  15. """
  16. def setup_method(self):
  17. # options for minimize
  18. self.opts = {'disp': False, 'maxfun': 200}
  19. # objective functions and Jacobian for each test
  20. def f1(self, x, a=100.0):
  21. return a * pow((x[1] - pow(x[0], 2)), 2) + pow(1.0 - x[0], 2)
  22. def g1(self, x, a=100.0):
  23. dif = [0, 0]
  24. dif[1] = 2 * a * (x[1] - pow(x[0], 2))
  25. dif[0] = -2.0 * (x[0] * (dif[1] - 1.0) + 1.0)
  26. return dif
  27. def fg1(self, x, a=100.0):
  28. return self.f1(x, a), self.g1(x, a)
  29. def f3(self, x):
  30. return x[1] + pow(x[1] - x[0], 2) * 1.0e-5
  31. def g3(self, x):
  32. dif = [0, 0]
  33. dif[0] = -2.0 * (x[1] - x[0]) * 1.0e-5
  34. dif[1] = 1.0 - dif[0]
  35. return dif
  36. def fg3(self, x):
  37. return self.f3(x), self.g3(x)
  38. def f4(self, x):
  39. return pow(x[0] + 1.0, 3) / 3.0 + x[1]
  40. def g4(self, x):
  41. dif = [0, 0]
  42. dif[0] = pow(x[0] + 1.0, 2)
  43. dif[1] = 1.0
  44. return dif
  45. def fg4(self, x):
  46. return self.f4(x), self.g4(x)
  47. def f5(self, x):
  48. return np.sin(x[0] + x[1]) + pow(x[0] - x[1], 2) - \
  49. 1.5 * x[0] + 2.5 * x[1] + 1.0
  50. def g5(self, x):
  51. dif = [0, 0]
  52. v1 = np.cos(x[0] + x[1])
  53. v2 = 2.0*(x[0] - x[1])
  54. dif[0] = v1 + v2 - 1.5
  55. dif[1] = v1 - v2 + 2.5
  56. return dif
  57. def fg5(self, x):
  58. return self.f5(x), self.g5(x)
  59. def f38(self, x):
  60. return (100.0 * pow(x[1] - pow(x[0], 2), 2) +
  61. pow(1.0 - x[0], 2) + 90.0 * pow(x[3] - pow(x[2], 2), 2) +
  62. pow(1.0 - x[2], 2) + 10.1 * (pow(x[1] - 1.0, 2) +
  63. pow(x[3] - 1.0, 2)) +
  64. 19.8 * (x[1] - 1.0) * (x[3] - 1.0)) * 1.0e-5
  65. def g38(self, x):
  66. dif = [0, 0, 0, 0]
  67. dif[0] = (-400.0 * x[0] * (x[1] - pow(x[0], 2)) -
  68. 2.0 * (1.0 - x[0])) * 1.0e-5
  69. dif[1] = (200.0 * (x[1] - pow(x[0], 2)) + 20.2 * (x[1] - 1.0) +
  70. 19.8 * (x[3] - 1.0)) * 1.0e-5
  71. dif[2] = (- 360.0 * x[2] * (x[3] - pow(x[2], 2)) -
  72. 2.0 * (1.0 - x[2])) * 1.0e-5
  73. dif[3] = (180.0 * (x[3] - pow(x[2], 2)) + 20.2 * (x[3] - 1.0) +
  74. 19.8 * (x[1] - 1.0)) * 1.0e-5
  75. return dif
  76. def fg38(self, x):
  77. return self.f38(x), self.g38(x)
  78. def f45(self, x):
  79. return 2.0 - x[0] * x[1] * x[2] * x[3] * x[4] / 120.0
  80. def g45(self, x):
  81. dif = [0] * 5
  82. dif[0] = - x[1] * x[2] * x[3] * x[4] / 120.0
  83. dif[1] = - x[0] * x[2] * x[3] * x[4] / 120.0
  84. dif[2] = - x[0] * x[1] * x[3] * x[4] / 120.0
  85. dif[3] = - x[0] * x[1] * x[2] * x[4] / 120.0
  86. dif[4] = - x[0] * x[1] * x[2] * x[3] / 120.0
  87. return dif
  88. def fg45(self, x):
  89. return self.f45(x), self.g45(x)
  90. # tests
  91. # minimize with method=TNC
  92. def test_minimize_tnc1(self):
  93. x0, bnds = [-2, 1], ([-np.inf, None], [-1.5, None])
  94. xopt = [1, 1]
  95. iterx = [] # to test callback
  96. res = optimize.minimize(self.f1, x0, method='TNC', jac=self.g1,
  97. bounds=bnds, options=self.opts,
  98. callback=iterx.append)
  99. assert_allclose(res.fun, self.f1(xopt), atol=1e-8)
  100. assert_equal(len(iterx), res.nit)
  101. def test_minimize_tnc1b(self):
  102. x0, bnds = matrix([-2, 1]), ([-np.inf, None],[-1.5, None])
  103. xopt = [1, 1]
  104. message = 'Use of `minimize` with `x0.ndim != 1` is deprecated.'
  105. with pytest.warns(DeprecationWarning, match=message):
  106. x = optimize.minimize(self.f1, x0, method='TNC',
  107. bounds=bnds, options=self.opts).x
  108. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-4)
  109. def test_minimize_tnc1c(self):
  110. x0, bnds = [-2, 1], ([-np.inf, None],[-1.5, None])
  111. xopt = [1, 1]
  112. x = optimize.minimize(self.fg1, x0, method='TNC',
  113. jac=True, bounds=bnds,
  114. options=self.opts).x
  115. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8)
  116. def test_minimize_tnc2(self):
  117. x0, bnds = [-2, 1], ([-np.inf, None], [1.5, None])
  118. xopt = [-1.2210262419616387, 1.5]
  119. x = optimize.minimize(self.f1, x0, method='TNC',
  120. jac=self.g1, bounds=bnds,
  121. options=self.opts).x
  122. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8)
  123. def test_minimize_tnc3(self):
  124. x0, bnds = [10, 1], ([-np.inf, None], [0.0, None])
  125. xopt = [0, 0]
  126. x = optimize.minimize(self.f3, x0, method='TNC',
  127. jac=self.g3, bounds=bnds,
  128. options=self.opts).x
  129. assert_allclose(self.f3(x), self.f3(xopt), atol=1e-8)
  130. def test_minimize_tnc4(self):
  131. x0,bnds = [1.125, 0.125], [(1, None), (0, None)]
  132. xopt = [1, 0]
  133. x = optimize.minimize(self.f4, x0, method='TNC',
  134. jac=self.g4, bounds=bnds,
  135. options=self.opts).x
  136. assert_allclose(self.f4(x), self.f4(xopt), atol=1e-8)
  137. def test_minimize_tnc5(self):
  138. x0, bnds = [0, 0], [(-1.5, 4),(-3, 3)]
  139. xopt = [-0.54719755119659763, -1.5471975511965976]
  140. x = optimize.minimize(self.f5, x0, method='TNC',
  141. jac=self.g5, bounds=bnds,
  142. options=self.opts).x
  143. assert_allclose(self.f5(x), self.f5(xopt), atol=1e-8)
  144. def test_minimize_tnc38(self):
  145. x0, bnds = np.array([-3, -1, -3, -1]), [(-10, 10)]*4
  146. xopt = [1]*4
  147. x = optimize.minimize(self.f38, x0, method='TNC',
  148. jac=self.g38, bounds=bnds,
  149. options=self.opts).x
  150. assert_allclose(self.f38(x), self.f38(xopt), atol=1e-8)
  151. def test_minimize_tnc45(self):
  152. x0, bnds = [2] * 5, [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]
  153. xopt = [1, 2, 3, 4, 5]
  154. x = optimize.minimize(self.f45, x0, method='TNC',
  155. jac=self.g45, bounds=bnds,
  156. options=self.opts).x
  157. assert_allclose(self.f45(x), self.f45(xopt), atol=1e-8)
  158. # fmin_tnc
  159. def test_tnc1(self):
  160. fg, x, bounds = self.fg1, [-2, 1], ([-np.inf, None], [-1.5, None])
  161. xopt = [1, 1]
  162. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds, args=(100.0, ),
  163. messages=optimize._tnc.MSG_NONE,
  164. maxfun=200)
  165. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
  166. err_msg="TNC failed with status: " +
  167. optimize._tnc.RCSTRINGS[rc])
  168. def test_tnc1b(self):
  169. x, bounds = [-2, 1], ([-np.inf, None], [-1.5, None])
  170. xopt = [1, 1]
  171. x, nf, rc = optimize.fmin_tnc(self.f1, x, approx_grad=True,
  172. bounds=bounds,
  173. messages=optimize._tnc.MSG_NONE,
  174. maxfun=200)
  175. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-4,
  176. err_msg="TNC failed with status: " +
  177. optimize._tnc.RCSTRINGS[rc])
  178. def test_tnc1c(self):
  179. x, bounds = [-2, 1], ([-np.inf, None], [-1.5, None])
  180. xopt = [1, 1]
  181. x, nf, rc = optimize.fmin_tnc(self.f1, x, fprime=self.g1,
  182. bounds=bounds,
  183. messages=optimize._tnc.MSG_NONE,
  184. maxfun=200)
  185. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
  186. err_msg="TNC failed with status: " +
  187. optimize._tnc.RCSTRINGS[rc])
  188. def test_tnc2(self):
  189. fg, x, bounds = self.fg1, [-2, 1], ([-np.inf, None], [1.5, None])
  190. xopt = [-1.2210262419616387, 1.5]
  191. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  192. messages=optimize._tnc.MSG_NONE,
  193. maxfun=200)
  194. assert_allclose(self.f1(x), self.f1(xopt), atol=1e-8,
  195. err_msg="TNC failed with status: " +
  196. optimize._tnc.RCSTRINGS[rc])
  197. def test_tnc3(self):
  198. fg, x, bounds = self.fg3, [10, 1], ([-np.inf, None], [0.0, None])
  199. xopt = [0, 0]
  200. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  201. messages=optimize._tnc.MSG_NONE,
  202. maxfun=200)
  203. assert_allclose(self.f3(x), self.f3(xopt), atol=1e-8,
  204. err_msg="TNC failed with status: " +
  205. optimize._tnc.RCSTRINGS[rc])
  206. def test_tnc4(self):
  207. fg, x, bounds = self.fg4, [1.125, 0.125], [(1, None), (0, None)]
  208. xopt = [1, 0]
  209. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  210. messages=optimize._tnc.MSG_NONE,
  211. maxfun=200)
  212. assert_allclose(self.f4(x), self.f4(xopt), atol=1e-8,
  213. err_msg="TNC failed with status: " +
  214. optimize._tnc.RCSTRINGS[rc])
  215. def test_tnc5(self):
  216. fg, x, bounds = self.fg5, [0, 0], [(-1.5, 4),(-3, 3)]
  217. xopt = [-0.54719755119659763, -1.5471975511965976]
  218. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  219. messages=optimize._tnc.MSG_NONE,
  220. maxfun=200)
  221. assert_allclose(self.f5(x), self.f5(xopt), atol=1e-8,
  222. err_msg="TNC failed with status: " +
  223. optimize._tnc.RCSTRINGS[rc])
  224. def test_tnc38(self):
  225. fg, x, bounds = self.fg38, np.array([-3, -1, -3, -1]), [(-10, 10)]*4
  226. xopt = [1]*4
  227. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  228. messages=optimize._tnc.MSG_NONE,
  229. maxfun=200)
  230. assert_allclose(self.f38(x), self.f38(xopt), atol=1e-8,
  231. err_msg="TNC failed with status: " +
  232. optimize._tnc.RCSTRINGS[rc])
  233. def test_tnc45(self):
  234. fg, x, bounds = self.fg45, [2] * 5, [(0, 1), (0, 2), (0, 3),
  235. (0, 4), (0, 5)]
  236. xopt = [1, 2, 3, 4, 5]
  237. x, nf, rc = optimize.fmin_tnc(fg, x, bounds=bounds,
  238. messages=optimize._tnc.MSG_NONE,
  239. maxfun=200)
  240. assert_allclose(self.f45(x), self.f45(xopt), atol=1e-8,
  241. err_msg="TNC failed with status: " +
  242. optimize._tnc.RCSTRINGS[rc])
  243. def test_raising_exceptions(self):
  244. # tnc was ported to cython from hand-crafted cpython code
  245. # check that Exception handling works.
  246. def myfunc(x):
  247. raise RuntimeError("myfunc")
  248. def myfunc1(x):
  249. return optimize.rosen(x)
  250. def callback(x):
  251. raise ValueError("callback")
  252. with pytest.raises(RuntimeError):
  253. optimize.minimize(myfunc, [0, 1], method="TNC")
  254. with pytest.raises(ValueError):
  255. optimize.minimize(
  256. myfunc1, [0, 1], method="TNC", callback=callback
  257. )
  258. def test_callback_shouldnt_affect_minimization(self):
  259. # gh14879. The output of a TNC minimization was different depending
  260. # on whether a callback was used or not. The two should be equivalent.
  261. # The issue was that TNC was unscaling/scaling x, and this process was
  262. # altering x in the process. Now the callback uses an unscaled
  263. # temporary copy of x.
  264. def callback(x):
  265. pass
  266. fun = optimize.rosen
  267. bounds = [(0, 10)] * 4
  268. x0 = [1, 2, 3, 4.]
  269. res = optimize.minimize(
  270. fun, x0, bounds=bounds, method="TNC", options={"maxfun": 1000}
  271. )
  272. res2 = optimize.minimize(
  273. fun, x0, bounds=bounds, method="TNC", options={"maxfun": 1000},
  274. callback=callback
  275. )
  276. assert_allclose(res2.x, res.x)
  277. assert_allclose(res2.fun, res.fun)
  278. assert_equal(res2.nfev, res.nfev)
  279. def test_maxiter_depreciations(self):
  280. msg = "'maxiter' has been deprecated in favor of 'maxfun'"
  281. with pytest.warns(DeprecationWarning, match=msg):
  282. optimize.minimize(
  283. self.f1, [1, 3], method="TNC", options={"maxiter": 1}
  284. )