test_minimize_constrained.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. from __future__ import division, print_function, absolute_import
  2. import numpy as np
  3. import pytest
  4. from scipy.linalg import block_diag
  5. from scipy.sparse import csc_matrix
  6. from numpy.testing import (TestCase, assert_array_almost_equal,
  7. assert_array_less, assert_allclose, assert_)
  8. from pytest import raises
  9. from scipy.optimize import (NonlinearConstraint,
  10. LinearConstraint,
  11. Bounds,
  12. minimize,
  13. BFGS,
  14. SR1)
  15. from scipy._lib._numpy_compat import suppress_warnings
  16. class Maratos:
  17. """Problem 15.4 from Nocedal and Wright
  18. The following optimization problem:
  19. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  20. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  21. """
  22. def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
  23. rads = degrees/180*np.pi
  24. self.x0 = [np.cos(rads), np.sin(rads)]
  25. self.x_opt = np.array([1.0, 0.0])
  26. self.constr_jac = constr_jac
  27. self.constr_hess = constr_hess
  28. self.bounds = None
  29. def fun(self, x):
  30. return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  31. def grad(self, x):
  32. return np.array([4*x[0]-1, 4*x[1]])
  33. def hess(self, x):
  34. return 4*np.eye(2)
  35. @property
  36. def constr(self):
  37. def fun(x):
  38. return x[0]**2 + x[1]**2
  39. if self.constr_jac is None:
  40. def jac(x):
  41. return [[2*x[0], 2*x[1]]]
  42. else:
  43. jac = self.constr_jac
  44. if self.constr_hess is None:
  45. def hess(x, v):
  46. return 2*v[0]*np.eye(2)
  47. else:
  48. hess = self.constr_hess
  49. return NonlinearConstraint(fun, 1, 1, jac, hess)
  50. class MaratosTestArgs:
  51. """Problem 15.4 from Nocedal and Wright
  52. The following optimization problem:
  53. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  54. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  55. """
  56. def __init__(self, a, b, degrees=60, constr_jac=None, constr_hess=None):
  57. rads = degrees/180*np.pi
  58. self.x0 = [np.cos(rads), np.sin(rads)]
  59. self.x_opt = np.array([1.0, 0.0])
  60. self.constr_jac = constr_jac
  61. self.constr_hess = constr_hess
  62. self.a = a
  63. self.b = b
  64. self.bounds = None
  65. def _test_args(self, a, b):
  66. if self.a != a or self.b != b:
  67. raise ValueError()
  68. def fun(self, x, a, b):
  69. self._test_args(a, b)
  70. return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  71. def grad(self, x, a, b):
  72. self._test_args(a, b)
  73. return np.array([4*x[0]-1, 4*x[1]])
  74. def hess(self, x, a, b):
  75. self._test_args(a, b)
  76. return 4*np.eye(2)
  77. @property
  78. def constr(self):
  79. def fun(x):
  80. return x[0]**2 + x[1]**2
  81. if self.constr_jac is None:
  82. def jac(x):
  83. return [[4*x[0], 4*x[1]]]
  84. else:
  85. jac = self.constr_jac
  86. if self.constr_hess is None:
  87. def hess(x, v):
  88. return 2*v[0]*np.eye(2)
  89. else:
  90. hess = self.constr_hess
  91. return NonlinearConstraint(fun, 1, 1, jac, hess)
  92. class MaratosGradInFunc:
  93. """Problem 15.4 from Nocedal and Wright
  94. The following optimization problem:
  95. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  96. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  97. """
  98. def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
  99. rads = degrees/180*np.pi
  100. self.x0 = [np.cos(rads), np.sin(rads)]
  101. self.x_opt = np.array([1.0, 0.0])
  102. self.constr_jac = constr_jac
  103. self.constr_hess = constr_hess
  104. self.bounds = None
  105. def fun(self, x):
  106. return (2*(x[0]**2 + x[1]**2 - 1) - x[0],
  107. np.array([4*x[0]-1, 4*x[1]]))
  108. @property
  109. def grad(self):
  110. return True
  111. def hess(self, x):
  112. return 4*np.eye(2)
  113. @property
  114. def constr(self):
  115. def fun(x):
  116. return x[0]**2 + x[1]**2
  117. if self.constr_jac is None:
  118. def jac(x):
  119. return [[4*x[0], 4*x[1]]]
  120. else:
  121. jac = self.constr_jac
  122. if self.constr_hess is None:
  123. def hess(x, v):
  124. return 2*v[0]*np.eye(2)
  125. else:
  126. hess = self.constr_hess
  127. return NonlinearConstraint(fun, 1, 1, jac, hess)
  128. class HyperbolicIneq:
  129. """Problem 15.1 from Nocedal and Wright
  130. The following optimization problem:
  131. minimize 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
  132. Subject to: 1/(x[0] + 1) - x[1] >= 1/4
  133. x[0] >= 0
  134. x[1] >= 0
  135. """
  136. def __init__(self, constr_jac=None, constr_hess=None):
  137. self.x0 = [0, 0]
  138. self.x_opt = [1.952823, 0.088659]
  139. self.constr_jac = constr_jac
  140. self.constr_hess = constr_hess
  141. self.bounds = Bounds(0, np.inf)
  142. def fun(self, x):
  143. return 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
  144. def grad(self, x):
  145. return [x[0] - 2, x[1] - 1/2]
  146. def hess(self, x):
  147. return np.eye(2)
  148. @property
  149. def constr(self):
  150. def fun(x):
  151. return 1/(x[0] + 1) - x[1]
  152. if self.constr_jac is None:
  153. def jac(x):
  154. return [[-1/(x[0] + 1)**2, -1]]
  155. else:
  156. jac = self.constr_jac
  157. if self.constr_hess is None:
  158. def hess(x, v):
  159. return 2*v[0]*np.array([[1/(x[0] + 1)**3, 0],
  160. [0, 0]])
  161. else:
  162. hess = self.constr_hess
  163. return NonlinearConstraint(fun, 0.25, np.inf, jac, hess)
  164. class Rosenbrock:
  165. """Rosenbrock function.
  166. The following optimization problem:
  167. minimize sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
  168. """
  169. def __init__(self, n=2, random_state=0):
  170. rng = np.random.RandomState(random_state)
  171. self.x0 = rng.uniform(-1, 1, n)
  172. self.x_opt = np.ones(n)
  173. self.bounds = None
  174. def fun(self, x):
  175. x = np.asarray(x)
  176. r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0,
  177. axis=0)
  178. return r
  179. def grad(self, x):
  180. x = np.asarray(x)
  181. xm = x[1:-1]
  182. xm_m1 = x[:-2]
  183. xm_p1 = x[2:]
  184. der = np.zeros_like(x)
  185. der[1:-1] = (200 * (xm - xm_m1**2) -
  186. 400 * (xm_p1 - xm**2) * xm - 2 * (1 - xm))
  187. der[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
  188. der[-1] = 200 * (x[-1] - x[-2]**2)
  189. return der
  190. def hess(self, x):
  191. x = np.atleast_1d(x)
  192. H = np.diag(-400 * x[:-1], 1) - np.diag(400 * x[:-1], -1)
  193. diagonal = np.zeros(len(x), dtype=x.dtype)
  194. diagonal[0] = 1200 * x[0]**2 - 400 * x[1] + 2
  195. diagonal[-1] = 200
  196. diagonal[1:-1] = 202 + 1200 * x[1:-1]**2 - 400 * x[2:]
  197. H = H + np.diag(diagonal)
  198. return H
  199. @property
  200. def constr(self):
  201. return ()
  202. class IneqRosenbrock(Rosenbrock):
  203. """Rosenbrock subject to inequality constraints.
  204. The following optimization problem:
  205. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  206. subject to: x[0] + 2 x[1] <= 1
  207. Taken from matlab ``fmincon`` documentation.
  208. """
  209. def __init__(self, random_state=0):
  210. Rosenbrock.__init__(self, 2, random_state)
  211. self.x0 = [-1, -0.5]
  212. self.x_opt = [0.5022, 0.2489]
  213. self.bounds = None
  214. @property
  215. def constr(self):
  216. A = [[1, 2]]
  217. b = 1
  218. return LinearConstraint(A, -np.inf, b)
  219. class BoundedRosenbrock(Rosenbrock):
  220. """Rosenbrock subject to inequality constraints.
  221. The following optimization problem:
  222. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  223. subject to: -2 <= x[0] <= 0
  224. 0 <= x[1] <= 2
  225. Taken from matlab ``fmincon`` documentation.
  226. """
  227. def __init__(self, random_state=0):
  228. Rosenbrock.__init__(self, 2, random_state)
  229. self.x0 = [-0.2, 0.2]
  230. self.x_opt = None
  231. self.bounds = Bounds([-2, 0], [0, 2])
  232. class EqIneqRosenbrock(Rosenbrock):
  233. """Rosenbrock subject to equality and inequality constraints.
  234. The following optimization problem:
  235. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  236. subject to: x[0] + 2 x[1] <= 1
  237. 2 x[0] + x[1] = 1
  238. Taken from matlab ``fimincon`` documentation.
  239. """
  240. def __init__(self, random_state=0):
  241. Rosenbrock.__init__(self, 2, random_state)
  242. self.x0 = [-1, -0.5]
  243. self.x_opt = [0.41494, 0.17011]
  244. self.bounds = None
  245. @property
  246. def constr(self):
  247. A_ineq = [[1, 2]]
  248. b_ineq = 1
  249. A_eq = [[2, 1]]
  250. b_eq = 1
  251. return (LinearConstraint(A_ineq, -np.inf, b_ineq),
  252. LinearConstraint(A_eq, b_eq, b_eq))
  253. class Elec:
  254. """Distribution of electrons on a sphere.
  255. Problem no 2 from COPS collection [2]_. Find
  256. the equilibrium state distribution (of minimal
  257. potential) of the electrons positioned on a
  258. conducting sphere.
  259. References
  260. ----------
  261. .. [1] E. D. Dolan, J. J. Mor\'{e}, and T. S. Munson,
  262. "Benchmarking optimization software with COPS 3.0.",
  263. Argonne National Lab., Argonne, IL (US), 2004.
  264. """
  265. def __init__(self, n_electrons=200, random_state=0,
  266. constr_jac=None, constr_hess=None):
  267. self.n_electrons = n_electrons
  268. self.rng = np.random.RandomState(random_state)
  269. # Initial Guess
  270. phi = self.rng.uniform(0, 2 * np.pi, self.n_electrons)
  271. theta = self.rng.uniform(-np.pi, np.pi, self.n_electrons)
  272. x = np.cos(theta) * np.cos(phi)
  273. y = np.cos(theta) * np.sin(phi)
  274. z = np.sin(theta)
  275. self.x0 = np.hstack((x, y, z))
  276. self.x_opt = None
  277. self.constr_jac = constr_jac
  278. self.constr_hess = constr_hess
  279. self.bounds = None
  280. def _get_cordinates(self, x):
  281. x_coord = x[:self.n_electrons]
  282. y_coord = x[self.n_electrons:2 * self.n_electrons]
  283. z_coord = x[2 * self.n_electrons:]
  284. return x_coord, y_coord, z_coord
  285. def _compute_coordinate_deltas(self, x):
  286. x_coord, y_coord, z_coord = self._get_cordinates(x)
  287. dx = x_coord[:, None] - x_coord
  288. dy = y_coord[:, None] - y_coord
  289. dz = z_coord[:, None] - z_coord
  290. return dx, dy, dz
  291. def fun(self, x):
  292. dx, dy, dz = self._compute_coordinate_deltas(x)
  293. with np.errstate(divide='ignore'):
  294. dm1 = (dx**2 + dy**2 + dz**2) ** -0.5
  295. dm1[np.diag_indices_from(dm1)] = 0
  296. return 0.5 * np.sum(dm1)
  297. def grad(self, x):
  298. dx, dy, dz = self._compute_coordinate_deltas(x)
  299. with np.errstate(divide='ignore'):
  300. dm3 = (dx**2 + dy**2 + dz**2) ** -1.5
  301. dm3[np.diag_indices_from(dm3)] = 0
  302. grad_x = -np.sum(dx * dm3, axis=1)
  303. grad_y = -np.sum(dy * dm3, axis=1)
  304. grad_z = -np.sum(dz * dm3, axis=1)
  305. return np.hstack((grad_x, grad_y, grad_z))
  306. def hess(self, x):
  307. dx, dy, dz = self._compute_coordinate_deltas(x)
  308. d = (dx**2 + dy**2 + dz**2) ** 0.5
  309. with np.errstate(divide='ignore'):
  310. dm3 = d ** -3
  311. dm5 = d ** -5
  312. i = np.arange(self.n_electrons)
  313. dm3[i, i] = 0
  314. dm5[i, i] = 0
  315. Hxx = dm3 - 3 * dx**2 * dm5
  316. Hxx[i, i] = -np.sum(Hxx, axis=1)
  317. Hxy = -3 * dx * dy * dm5
  318. Hxy[i, i] = -np.sum(Hxy, axis=1)
  319. Hxz = -3 * dx * dz * dm5
  320. Hxz[i, i] = -np.sum(Hxz, axis=1)
  321. Hyy = dm3 - 3 * dy**2 * dm5
  322. Hyy[i, i] = -np.sum(Hyy, axis=1)
  323. Hyz = -3 * dy * dz * dm5
  324. Hyz[i, i] = -np.sum(Hyz, axis=1)
  325. Hzz = dm3 - 3 * dz**2 * dm5
  326. Hzz[i, i] = -np.sum(Hzz, axis=1)
  327. H = np.vstack((
  328. np.hstack((Hxx, Hxy, Hxz)),
  329. np.hstack((Hxy, Hyy, Hyz)),
  330. np.hstack((Hxz, Hyz, Hzz))
  331. ))
  332. return H
  333. @property
  334. def constr(self):
  335. def fun(x):
  336. x_coord, y_coord, z_coord = self._get_cordinates(x)
  337. return x_coord**2 + y_coord**2 + z_coord**2 - 1
  338. if self.constr_jac is None:
  339. def jac(x):
  340. x_coord, y_coord, z_coord = self._get_cordinates(x)
  341. Jx = 2 * np.diag(x_coord)
  342. Jy = 2 * np.diag(y_coord)
  343. Jz = 2 * np.diag(z_coord)
  344. return csc_matrix(np.hstack((Jx, Jy, Jz)))
  345. else:
  346. jac = self.constr_jac
  347. if self.constr_hess is None:
  348. def hess(x, v):
  349. D = 2 * np.diag(v)
  350. return block_diag(D, D, D)
  351. else:
  352. hess = self.constr_hess
  353. return NonlinearConstraint(fun, -np.inf, 0, jac, hess)
  354. class TestTrustRegionConstr(TestCase):
  355. @pytest.mark.slow
  356. def test_list_of_problems(self):
  357. list_of_problems = [Maratos(),
  358. Maratos(constr_hess='2-point'),
  359. Maratos(constr_hess=SR1()),
  360. Maratos(constr_jac='2-point', constr_hess=SR1()),
  361. MaratosGradInFunc(),
  362. HyperbolicIneq(),
  363. HyperbolicIneq(constr_hess='3-point'),
  364. HyperbolicIneq(constr_hess=BFGS()),
  365. HyperbolicIneq(constr_jac='3-point',
  366. constr_hess=BFGS()),
  367. Rosenbrock(),
  368. IneqRosenbrock(),
  369. EqIneqRosenbrock(),
  370. BoundedRosenbrock(),
  371. Elec(n_electrons=2),
  372. Elec(n_electrons=2, constr_hess='2-point'),
  373. Elec(n_electrons=2, constr_hess=SR1()),
  374. Elec(n_electrons=2, constr_jac='3-point',
  375. constr_hess=SR1())]
  376. for prob in list_of_problems:
  377. for grad in (prob.grad, '3-point', False):
  378. for hess in (prob.hess,
  379. '3-point',
  380. SR1(),
  381. BFGS(exception_strategy='damp_update'),
  382. BFGS(exception_strategy='skip_update')):
  383. # Remove exceptions
  384. if grad in ('2-point', '3-point', 'cs', False) and \
  385. hess in ('2-point', '3-point', 'cs'):
  386. continue
  387. if prob.grad is True and grad in ('3-point', False):
  388. continue
  389. with suppress_warnings() as sup:
  390. sup.filter(UserWarning, "delta_grad == 0.0")
  391. result = minimize(prob.fun, prob.x0,
  392. method='trust-constr',
  393. jac=grad, hess=hess,
  394. bounds=prob.bounds,
  395. constraints=prob.constr)
  396. if prob.x_opt is not None:
  397. assert_array_almost_equal(result.x, prob.x_opt,
  398. decimal=5)
  399. # gtol
  400. if result.status == 1:
  401. assert_array_less(result.optimality, 1e-8)
  402. # xtol
  403. if result.status == 2:
  404. assert_array_less(result.tr_radius, 1e-8)
  405. if result.method == "tr_interior_point":
  406. assert_array_less(result.barrier_parameter, 1e-8)
  407. # max iter
  408. if result.status in (0, 3):
  409. raise RuntimeError("Invalid termination condition.")
  410. def test_default_jac_and_hess(self):
  411. def fun(x):
  412. return (x - 1) ** 2
  413. bounds = [(-2, 2)]
  414. res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr')
  415. assert_array_almost_equal(res.x, 1, decimal=5)
  416. def test_default_hess(self):
  417. def fun(x):
  418. return (x - 1) ** 2
  419. bounds = [(-2, 2)]
  420. res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr',
  421. jac='2-point')
  422. assert_array_almost_equal(res.x, 1, decimal=5)
  423. def test_no_constraints(self):
  424. prob = Rosenbrock()
  425. result = minimize(prob.fun, prob.x0,
  426. method='trust-constr',
  427. jac=prob.grad, hess=prob.hess)
  428. result1 = minimize(prob.fun, prob.x0,
  429. method='L-BFGS-B',
  430. jac='2-point')
  431. with pytest.warns(UserWarning):
  432. result2 = minimize(prob.fun, prob.x0,
  433. method='L-BFGS-B',
  434. jac='3-point')
  435. assert_array_almost_equal(result.x, prob.x_opt, decimal=5)
  436. assert_array_almost_equal(result1.x, prob.x_opt, decimal=5)
  437. assert_array_almost_equal(result2.x, prob.x_opt, decimal=5)
  438. def test_hessp(self):
  439. prob = Maratos()
  440. def hessp(x, p):
  441. H = prob.hess(x)
  442. return H.dot(p)
  443. result = minimize(prob.fun, prob.x0,
  444. method='trust-constr',
  445. jac=prob.grad, hessp=hessp,
  446. bounds=prob.bounds,
  447. constraints=prob.constr)
  448. if prob.x_opt is not None:
  449. assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
  450. # gtol
  451. if result.status == 1:
  452. assert_array_less(result.optimality, 1e-8)
  453. # xtol
  454. if result.status == 2:
  455. assert_array_less(result.tr_radius, 1e-8)
  456. if result.method == "tr_interior_point":
  457. assert_array_less(result.barrier_parameter, 1e-8)
  458. # max iter
  459. if result.status in (0, 3):
  460. raise RuntimeError("Invalid termination condition.")
  461. def test_args(self):
  462. prob = MaratosTestArgs("a", 234)
  463. result = minimize(prob.fun, prob.x0, ("a", 234),
  464. method='trust-constr',
  465. jac=prob.grad, hess=prob.hess,
  466. bounds=prob.bounds,
  467. constraints=prob.constr)
  468. if prob.x_opt is not None:
  469. assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
  470. # gtol
  471. if result.status == 1:
  472. assert_array_less(result.optimality, 1e-8)
  473. # xtol
  474. if result.status == 2:
  475. assert_array_less(result.tr_radius, 1e-8)
  476. if result.method == "tr_interior_point":
  477. assert_array_less(result.barrier_parameter, 1e-8)
  478. # max iter
  479. if result.status in (0, 3):
  480. raise RuntimeError("Invalid termination condition.")
  481. def test_raise_exception(self):
  482. prob = Maratos()
  483. raises(ValueError, minimize, prob.fun, prob.x0, method='trust-constr',
  484. jac='2-point', hess='2-point', constraints=prob.constr)
  485. def test_issue_9044(self):
  486. # https://github.com/scipy/scipy/issues/9044
  487. # Test the returned `OptimizeResult` contains keys consistent with
  488. # other solvers.
  489. def callback(x, info):
  490. assert_('nit' in info)
  491. assert_('niter' in info)
  492. result = minimize(lambda x: x**2, [0], jac=lambda x: 2*x,
  493. hess=lambda x: 2, callback=callback,
  494. method='trust-constr')
  495. assert_(result.get('success'))
  496. assert_(result.get('nit', -1) == 1)
  497. # Also check existence of the 'niter' attribute, for backward
  498. # compatibility
  499. assert_(result.get('niter', -1) == 1)