test_minimize_constrained.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850
  1. import warnings
  2. import numpy as np
  3. import pytest
  4. from scipy.linalg import block_diag
  5. from scipy.sparse import csc_array
  6. from numpy.testing import (assert_array_almost_equal,
  7. assert_array_less, assert_)
  8. from scipy.optimize import (NonlinearConstraint,
  9. LinearConstraint,
  10. Bounds,
  11. minimize,
  12. BFGS,
  13. SR1,
  14. rosen)
  15. class Maratos:
  16. """Problem 15.4 from Nocedal and Wright
  17. The following optimization problem:
  18. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  19. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  20. """
  21. def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
  22. rads = degrees/180*np.pi
  23. self.x0 = [np.cos(rads), np.sin(rads)]
  24. self.x_opt = np.array([1.0, 0.0])
  25. self.constr_jac = constr_jac
  26. self.constr_hess = constr_hess
  27. self.bounds = None
  28. def fun(self, x):
  29. return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  30. def grad(self, x):
  31. return np.array([4*x[0]-1, 4*x[1]])
  32. def hess(self, x):
  33. return 4*np.eye(2)
  34. @property
  35. def constr(self):
  36. def fun(x):
  37. return x[0]**2 + x[1]**2
  38. if self.constr_jac is None:
  39. def jac(x):
  40. return [[2*x[0], 2*x[1]]]
  41. else:
  42. jac = self.constr_jac
  43. if self.constr_hess is None:
  44. def hess(x, v):
  45. return 2*v[0]*np.eye(2)
  46. else:
  47. hess = self.constr_hess
  48. return NonlinearConstraint(fun, 1, 1, jac, hess)
  49. class MaratosTestArgs:
  50. """Problem 15.4 from Nocedal and Wright
  51. The following optimization problem:
  52. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  53. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  54. """
  55. def __init__(self, a, b, degrees=60, constr_jac=None, constr_hess=None):
  56. rads = degrees/180*np.pi
  57. self.x0 = [np.cos(rads), np.sin(rads)]
  58. self.x_opt = np.array([1.0, 0.0])
  59. self.constr_jac = constr_jac
  60. self.constr_hess = constr_hess
  61. self.a = a
  62. self.b = b
  63. self.bounds = None
  64. def _test_args(self, a, b):
  65. if self.a != a or self.b != b:
  66. raise ValueError()
  67. def fun(self, x, a, b):
  68. self._test_args(a, b)
  69. return 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  70. def grad(self, x, a, b):
  71. self._test_args(a, b)
  72. return np.array([4*x[0]-1, 4*x[1]])
  73. def hess(self, x, a, b):
  74. self._test_args(a, b)
  75. return 4*np.eye(2)
  76. @property
  77. def constr(self):
  78. def fun(x):
  79. return x[0]**2 + x[1]**2
  80. if self.constr_jac is None:
  81. def jac(x):
  82. return [[4*x[0], 4*x[1]]]
  83. else:
  84. jac = self.constr_jac
  85. if self.constr_hess is None:
  86. def hess(x, v):
  87. return 2*v[0]*np.eye(2)
  88. else:
  89. hess = self.constr_hess
  90. return NonlinearConstraint(fun, 1, 1, jac, hess)
  91. class MaratosGradInFunc:
  92. """Problem 15.4 from Nocedal and Wright
  93. The following optimization problem:
  94. minimize 2*(x[0]**2 + x[1]**2 - 1) - x[0]
  95. Subject to: x[0]**2 + x[1]**2 - 1 = 0
  96. """
  97. def __init__(self, degrees=60, constr_jac=None, constr_hess=None):
  98. rads = degrees/180*np.pi
  99. self.x0 = [np.cos(rads), np.sin(rads)]
  100. self.x_opt = np.array([1.0, 0.0])
  101. self.constr_jac = constr_jac
  102. self.constr_hess = constr_hess
  103. self.bounds = None
  104. def fun(self, x):
  105. return (2*(x[0]**2 + x[1]**2 - 1) - x[0],
  106. np.array([4*x[0]-1, 4*x[1]]))
  107. @property
  108. def grad(self):
  109. return True
  110. def hess(self, x):
  111. return 4*np.eye(2)
  112. @property
  113. def constr(self):
  114. def fun(x):
  115. return x[0]**2 + x[1]**2
  116. if self.constr_jac is None:
  117. def jac(x):
  118. return [[4*x[0], 4*x[1]]]
  119. else:
  120. jac = self.constr_jac
  121. if self.constr_hess is None:
  122. def hess(x, v):
  123. return 2*v[0]*np.eye(2)
  124. else:
  125. hess = self.constr_hess
  126. return NonlinearConstraint(fun, 1, 1, jac, hess)
  127. class HyperbolicIneq:
  128. """Problem 15.1 from Nocedal and Wright
  129. The following optimization problem:
  130. minimize 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
  131. Subject to: 1/(x[0] + 1) - x[1] >= 1/4
  132. x[0] >= 0
  133. x[1] >= 0
  134. """
  135. def __init__(self, constr_jac=None, constr_hess=None):
  136. self.x0 = [0, 0]
  137. self.x_opt = [1.952823, 0.088659]
  138. self.constr_jac = constr_jac
  139. self.constr_hess = constr_hess
  140. self.bounds = Bounds(0, np.inf)
  141. def fun(self, x):
  142. return 1/2*(x[0] - 2)**2 + 1/2*(x[1] - 1/2)**2
  143. def grad(self, x):
  144. return [x[0] - 2, x[1] - 1/2]
  145. def hess(self, x):
  146. return np.eye(2)
  147. @property
  148. def constr(self):
  149. def fun(x):
  150. return 1/(x[0] + 1) - x[1]
  151. if self.constr_jac is None:
  152. def jac(x):
  153. return [[-1/(x[0] + 1)**2, -1]]
  154. else:
  155. jac = self.constr_jac
  156. if self.constr_hess is None:
  157. def hess(x, v):
  158. return 2*v[0]*np.array([[1/(x[0] + 1)**3, 0],
  159. [0, 0]])
  160. else:
  161. hess = self.constr_hess
  162. return NonlinearConstraint(fun, 0.25, np.inf, jac, hess)
  163. class Rosenbrock:
  164. """Rosenbrock function.
  165. The following optimization problem:
  166. minimize sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)
  167. """
  168. def __init__(self, n=2, random_state=0):
  169. rng = np.random.RandomState(random_state)
  170. self.x0 = rng.uniform(-1, 1, n)
  171. self.x_opt = np.ones(n)
  172. self.bounds = None
  173. def fun(self, x):
  174. x = np.asarray(x)
  175. r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0,
  176. axis=0)
  177. return r
  178. def grad(self, x):
  179. x = np.asarray(x)
  180. xm = x[1:-1]
  181. xm_m1 = x[:-2]
  182. xm_p1 = x[2:]
  183. der = np.zeros_like(x)
  184. der[1:-1] = (200 * (xm - xm_m1**2) -
  185. 400 * (xm_p1 - xm**2) * xm - 2 * (1 - xm))
  186. der[0] = -400 * x[0] * (x[1] - x[0]**2) - 2 * (1 - x[0])
  187. der[-1] = 200 * (x[-1] - x[-2]**2)
  188. return der
  189. def hess(self, x):
  190. x = np.atleast_1d(x)
  191. H = np.diag(-400 * x[:-1], 1) - np.diag(400 * x[:-1], -1)
  192. diagonal = np.zeros(len(x), dtype=x.dtype)
  193. diagonal[0] = 1200 * x[0]**2 - 400 * x[1] + 2
  194. diagonal[-1] = 200
  195. diagonal[1:-1] = 202 + 1200 * x[1:-1]**2 - 400 * x[2:]
  196. H = H + np.diag(diagonal)
  197. return H
  198. @property
  199. def constr(self):
  200. return ()
  201. class IneqRosenbrock(Rosenbrock):
  202. """Rosenbrock subject to inequality constraints.
  203. The following optimization problem:
  204. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  205. subject to: x[0] + 2 x[1] <= 1
  206. Taken from matlab ``fmincon`` documentation.
  207. """
  208. def __init__(self, random_state=0):
  209. Rosenbrock.__init__(self, 2, random_state)
  210. self.x0 = [-1, -0.5]
  211. self.x_opt = [0.5022, 0.2489]
  212. self.bounds = None
  213. @property
  214. def constr(self):
  215. A = [[1, 2]]
  216. b = 1
  217. return LinearConstraint(A, -np.inf, b)
  218. class BoundedRosenbrock(Rosenbrock):
  219. """Rosenbrock subject to inequality constraints.
  220. The following optimization problem:
  221. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  222. subject to: -2 <= x[0] <= 0
  223. 0 <= x[1] <= 2
  224. Taken from matlab ``fmincon`` documentation.
  225. """
  226. def __init__(self, random_state=0):
  227. Rosenbrock.__init__(self, 2, random_state)
  228. self.x0 = [-0.2, 0.2]
  229. self.x_opt = None
  230. self.bounds = Bounds([-2, 0], [0, 2])
  231. class EqIneqRosenbrock(Rosenbrock):
  232. """Rosenbrock subject to equality and inequality constraints.
  233. The following optimization problem:
  234. minimize sum(100.0*(x[1] - x[0]**2)**2.0 + (1 - x[0])**2)
  235. subject to: x[0] + 2 x[1] <= 1
  236. 2 x[0] + x[1] = 1
  237. Taken from matlab ``fimincon`` documentation.
  238. """
  239. def __init__(self, random_state=0):
  240. Rosenbrock.__init__(self, 2, random_state)
  241. self.x0 = [-1, -0.5]
  242. self.x_opt = [0.41494, 0.17011]
  243. self.bounds = None
  244. @property
  245. def constr(self):
  246. A_ineq = [[1, 2]]
  247. b_ineq = 1
  248. A_eq = [[2, 1]]
  249. b_eq = 1
  250. return (LinearConstraint(A_ineq, -np.inf, b_ineq),
  251. LinearConstraint(A_eq, b_eq, b_eq))
  252. class Elec:
  253. """Distribution of electrons on a sphere.
  254. Problem no 2 from COPS collection [2]_. Find
  255. the equilibrium state distribution (of minimal
  256. potential) of the electrons positioned on a
  257. conducting sphere.
  258. References
  259. ----------
  260. .. [1] E. D. Dolan, J. J. Mor\'{e}, and T. S. Munson,
  261. "Benchmarking optimization software with COPS 3.0.",
  262. Argonne National Lab., Argonne, IL (US), 2004.
  263. """
  264. def __init__(self, n_electrons=200, random_state=0,
  265. constr_jac=None, constr_hess=None):
  266. self.n_electrons = n_electrons
  267. self.rng = np.random.RandomState(random_state)
  268. # Initial Guess
  269. phi = self.rng.uniform(0, 2 * np.pi, self.n_electrons)
  270. theta = self.rng.uniform(-np.pi, np.pi, self.n_electrons)
  271. x = np.cos(theta) * np.cos(phi)
  272. y = np.cos(theta) * np.sin(phi)
  273. z = np.sin(theta)
  274. self.x0 = np.hstack((x, y, z))
  275. self.x_opt = None
  276. self.constr_jac = constr_jac
  277. self.constr_hess = constr_hess
  278. self.bounds = None
  279. def _get_cordinates(self, x):
  280. x_coord = x[:self.n_electrons]
  281. y_coord = x[self.n_electrons:2 * self.n_electrons]
  282. z_coord = x[2 * self.n_electrons:]
  283. return x_coord, y_coord, z_coord
  284. def _compute_coordinate_deltas(self, x):
  285. x_coord, y_coord, z_coord = self._get_cordinates(x)
  286. dx = x_coord[:, None] - x_coord
  287. dy = y_coord[:, None] - y_coord
  288. dz = z_coord[:, None] - z_coord
  289. return dx, dy, dz
  290. def fun(self, x):
  291. dx, dy, dz = self._compute_coordinate_deltas(x)
  292. with np.errstate(divide='ignore'):
  293. dm1 = (dx**2 + dy**2 + dz**2) ** -0.5
  294. dm1[np.diag_indices_from(dm1)] = 0
  295. return 0.5 * np.sum(dm1)
  296. def grad(self, x):
  297. dx, dy, dz = self._compute_coordinate_deltas(x)
  298. with np.errstate(divide='ignore'):
  299. dm3 = (dx**2 + dy**2 + dz**2) ** -1.5
  300. dm3[np.diag_indices_from(dm3)] = 0
  301. grad_x = -np.sum(dx * dm3, axis=1)
  302. grad_y = -np.sum(dy * dm3, axis=1)
  303. grad_z = -np.sum(dz * dm3, axis=1)
  304. return np.hstack((grad_x, grad_y, grad_z))
  305. def hess(self, x):
  306. dx, dy, dz = self._compute_coordinate_deltas(x)
  307. d = (dx**2 + dy**2 + dz**2) ** 0.5
  308. with np.errstate(divide='ignore'):
  309. dm3 = d ** -3
  310. dm5 = d ** -5
  311. i = np.arange(self.n_electrons)
  312. dm3[i, i] = 0
  313. dm5[i, i] = 0
  314. Hxx = dm3 - 3 * dx**2 * dm5
  315. Hxx[i, i] = -np.sum(Hxx, axis=1)
  316. Hxy = -3 * dx * dy * dm5
  317. Hxy[i, i] = -np.sum(Hxy, axis=1)
  318. Hxz = -3 * dx * dz * dm5
  319. Hxz[i, i] = -np.sum(Hxz, axis=1)
  320. Hyy = dm3 - 3 * dy**2 * dm5
  321. Hyy[i, i] = -np.sum(Hyy, axis=1)
  322. Hyz = -3 * dy * dz * dm5
  323. Hyz[i, i] = -np.sum(Hyz, axis=1)
  324. Hzz = dm3 - 3 * dz**2 * dm5
  325. Hzz[i, i] = -np.sum(Hzz, axis=1)
  326. H = np.vstack((
  327. np.hstack((Hxx, Hxy, Hxz)),
  328. np.hstack((Hxy, Hyy, Hyz)),
  329. np.hstack((Hxz, Hyz, Hzz))
  330. ))
  331. return H
  332. @property
  333. def constr(self):
  334. def fun(x):
  335. x_coord, y_coord, z_coord = self._get_cordinates(x)
  336. return x_coord**2 + y_coord**2 + z_coord**2 - 1
  337. if self.constr_jac is None:
  338. def jac(x):
  339. x_coord, y_coord, z_coord = self._get_cordinates(x)
  340. Jx = 2 * np.diag(x_coord)
  341. Jy = 2 * np.diag(y_coord)
  342. Jz = 2 * np.diag(z_coord)
  343. return csc_array(np.hstack((Jx, Jy, Jz)))
  344. else:
  345. jac = self.constr_jac
  346. if self.constr_hess is None:
  347. def hess(x, v):
  348. D = 2 * np.diag(v)
  349. return block_diag(D, D, D)
  350. else:
  351. hess = self.constr_hess
  352. return NonlinearConstraint(fun, -np.inf, 0, jac, hess)
  353. class TestTrustRegionConstr:
  354. list_of_problems = [Maratos(),
  355. Maratos(constr_hess='2-point'),
  356. Maratos(constr_hess=SR1()),
  357. Maratos(constr_jac='2-point', constr_hess=SR1()),
  358. MaratosGradInFunc(),
  359. HyperbolicIneq(),
  360. HyperbolicIneq(constr_hess='3-point'),
  361. HyperbolicIneq(constr_hess=BFGS()),
  362. HyperbolicIneq(constr_jac='3-point',
  363. constr_hess=BFGS()),
  364. Rosenbrock(),
  365. IneqRosenbrock(),
  366. EqIneqRosenbrock(),
  367. BoundedRosenbrock(),
  368. Elec(n_electrons=2),
  369. Elec(n_electrons=2, constr_hess='2-point'),
  370. Elec(n_electrons=2, constr_hess=SR1()),
  371. Elec(n_electrons=2, constr_jac='3-point',
  372. constr_hess=SR1())]
  373. @pytest.mark.parametrize('prob', list_of_problems)
  374. @pytest.mark.parametrize('grad', ('prob.grad', '3-point', False))
  375. @pytest.mark.parametrize('hess', ("prob.hess", '3-point', lambda: SR1(),
  376. lambda: BFGS(exception_strategy='damp_update'),
  377. lambda: BFGS(exception_strategy='skip_update')))
  378. def test_list_of_problems(self, prob, grad, hess):
  379. grad = prob.grad if grad == "prob.grad" else grad
  380. hess = hess() if callable(hess) else hess
  381. hess = prob.hess if hess == "prob.hess" else hess
  382. # Remove exceptions
  383. if (grad in {'2-point', '3-point', 'cs', False} and
  384. hess in {'2-point', '3-point', 'cs'}):
  385. pytest.skip("Numerical Hessian needs analytical gradient")
  386. if prob.grad is True and grad in {'3-point', False}:
  387. pytest.skip("prob.grad incompatible with grad in {'3-point', False}")
  388. sensitive = (isinstance(prob, BoundedRosenbrock) and grad == '3-point'
  389. and isinstance(hess, BFGS))
  390. if sensitive:
  391. pytest.xfail("Seems sensitive to initial conditions w/ Accelerate")
  392. with warnings.catch_warnings():
  393. warnings.filterwarnings("ignore", "delta_grad == 0.0", UserWarning)
  394. result = minimize(prob.fun, prob.x0,
  395. method='trust-constr',
  396. jac=grad, hess=hess,
  397. bounds=prob.bounds,
  398. constraints=prob.constr)
  399. if prob.x_opt is not None:
  400. assert_array_almost_equal(result.x, prob.x_opt,
  401. decimal=5)
  402. # gtol
  403. if result.status == 1:
  404. assert_array_less(result.optimality, 1e-8)
  405. # xtol
  406. if result.status == 2:
  407. assert_array_less(result.tr_radius, 1e-8)
  408. if result.method == "tr_interior_point":
  409. assert_array_less(result.barrier_parameter, 1e-8)
  410. # check for max iter
  411. message = f"Invalid termination condition: {result.status}."
  412. assert result.status not in {0, 3}, message
  413. def test_default_jac_and_hess(self):
  414. def fun(x):
  415. return (x - 1) ** 2
  416. bounds = [(-2, 2)]
  417. res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr')
  418. assert_array_almost_equal(res.x, 1, decimal=5)
  419. def test_default_hess(self):
  420. def fun(x):
  421. return (x - 1) ** 2
  422. bounds = [(-2, 2)]
  423. res = minimize(fun, x0=[-1.5], bounds=bounds, method='trust-constr',
  424. jac='2-point')
  425. assert_array_almost_equal(res.x, 1, decimal=5)
  426. def test_no_constraints(self):
  427. prob = Rosenbrock()
  428. result = minimize(prob.fun, prob.x0,
  429. method='trust-constr',
  430. jac=prob.grad, hess=prob.hess)
  431. result1 = minimize(prob.fun, prob.x0,
  432. method='L-BFGS-B',
  433. jac='2-point')
  434. result2 = minimize(prob.fun, prob.x0,
  435. method='L-BFGS-B',
  436. jac='3-point')
  437. assert_array_almost_equal(result.x, prob.x_opt, decimal=5)
  438. assert_array_almost_equal(result1.x, prob.x_opt, decimal=5)
  439. assert_array_almost_equal(result2.x, prob.x_opt, decimal=5)
  440. def test_hessp(self):
  441. prob = Maratos()
  442. def hessp(x, p):
  443. H = prob.hess(x)
  444. return H.dot(p)
  445. result = minimize(prob.fun, prob.x0,
  446. method='trust-constr',
  447. jac=prob.grad, hessp=hessp,
  448. bounds=prob.bounds,
  449. constraints=prob.constr)
  450. if prob.x_opt is not None:
  451. assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
  452. # gtol
  453. if result.status == 1:
  454. assert_array_less(result.optimality, 1e-8)
  455. # xtol
  456. if result.status == 2:
  457. assert_array_less(result.tr_radius, 1e-8)
  458. if result.method == "tr_interior_point":
  459. assert_array_less(result.barrier_parameter, 1e-8)
  460. # max iter
  461. if result.status in (0, 3):
  462. raise RuntimeError("Invalid termination condition.")
  463. def test_args(self):
  464. prob = MaratosTestArgs("a", 234)
  465. result = minimize(prob.fun, prob.x0, ("a", 234),
  466. method='trust-constr',
  467. jac=prob.grad, hess=prob.hess,
  468. bounds=prob.bounds,
  469. constraints=prob.constr)
  470. if prob.x_opt is not None:
  471. assert_array_almost_equal(result.x, prob.x_opt, decimal=2)
  472. # gtol
  473. if result.status == 1:
  474. assert_array_less(result.optimality, 1e-8)
  475. # xtol
  476. if result.status == 2:
  477. assert_array_less(result.tr_radius, 1e-8)
  478. if result.method == "tr_interior_point":
  479. assert_array_less(result.barrier_parameter, 1e-8)
  480. # max iter
  481. if result.status in (0, 3):
  482. raise RuntimeError("Invalid termination condition.")
  483. def test_raise_exception(self):
  484. prob = Maratos()
  485. message = "Whenever the gradient is estimated via finite-differences"
  486. with pytest.raises(ValueError, match=message):
  487. minimize(prob.fun, prob.x0, method='trust-constr', jac='2-point',
  488. hess='2-point', constraints=prob.constr)
  489. def test_issue_9044(self):
  490. # https://github.com/scipy/scipy/issues/9044
  491. # Test the returned `OptimizeResult` contains keys consistent with
  492. # other solvers.
  493. def callback(x, info):
  494. assert_('nit' in info)
  495. assert_('niter' in info)
  496. result = minimize(lambda x: x**2, [0], jac=lambda x: 2*x,
  497. hess=lambda x: 2, callback=callback,
  498. method='trust-constr')
  499. assert_(result.get('success'))
  500. assert_(result.get('nit', -1) == 1)
  501. # Also check existence of the 'niter' attribute, for backward
  502. # compatibility
  503. assert_(result.get('niter', -1) == 1)
  504. def test_issue_15093(self):
  505. # scipy docs define bounds as inclusive, so it shouldn't be
  506. # an issue to set x0 on the bounds even if keep_feasible is
  507. # True. Previously, trust-constr would treat bounds as
  508. # exclusive.
  509. x0 = np.array([0., 0.5])
  510. def obj(x):
  511. x1 = x[0]
  512. x2 = x[1]
  513. return x1 ** 2 + x2 ** 2
  514. bounds = Bounds(np.array([0., 0.]), np.array([1., 1.]),
  515. keep_feasible=True)
  516. with warnings.catch_warnings():
  517. warnings.filterwarnings("ignore", "delta_grad == 0.0", UserWarning)
  518. result = minimize(
  519. method='trust-constr',
  520. fun=obj,
  521. x0=x0,
  522. bounds=bounds)
  523. assert result['success']
  524. class TestEmptyConstraint:
  525. """
  526. Here we minimize x^2+y^2 subject to x^2-y^2>1.
  527. The actual minimum is at (0, 0) which fails the constraint.
  528. Therefore we will find a minimum on the boundary at (+/-1, 0).
  529. When minimizing on the boundary, optimize uses a set of
  530. constraints that removes the constraint that sets that
  531. boundary. In our case, there's only one constraint, so
  532. the result is an empty constraint.
  533. This tests that the empty constraint works.
  534. """
  535. def test_empty_constraint(self):
  536. def function(x):
  537. return x[0]**2 + x[1]**2
  538. def functionjacobian(x):
  539. return np.array([2.*x[0], 2.*x[1]])
  540. def functionhvp(x, v):
  541. return 2.*v
  542. def constraint(x):
  543. return np.array([x[0]**2 - x[1]**2])
  544. def constraintjacobian(x):
  545. return np.array([[2*x[0], -2*x[1]]])
  546. def constraintlcoh(x, v):
  547. return np.array([[2., 0.], [0., -2.]]) * v[0]
  548. constraint = NonlinearConstraint(constraint, 1., np.inf,
  549. constraintjacobian, constraintlcoh)
  550. startpoint = [1., 2.]
  551. bounds = Bounds([-np.inf, -np.inf], [np.inf, np.inf])
  552. result = minimize(
  553. function,
  554. startpoint,
  555. method='trust-constr',
  556. jac=functionjacobian,
  557. hessp=functionhvp,
  558. constraints=[constraint],
  559. bounds=bounds,
  560. )
  561. assert_array_almost_equal(abs(result.x), np.array([1, 0]), decimal=4)
  562. def test_bug_11886():
  563. def opt(x):
  564. return x[0]**2+x[1]**2
  565. with warnings.catch_warnings():
  566. warnings.simplefilter("ignore", PendingDeprecationWarning)
  567. A = np.matrix(np.diag([1, 1]))
  568. lin_cons = LinearConstraint(A, -1, np.inf)
  569. # just checking that there are no errors
  570. minimize(opt, 2*[1], constraints = lin_cons)
  571. def test_gh11649():
  572. # trust - constr error when attempting to keep bound constrained solutions
  573. # feasible. Algorithm attempts to go outside bounds when evaluating finite
  574. # differences. (don't give objective an analytic gradient)
  575. bnds = Bounds(lb=[-1, -1], ub=[1, 1], keep_feasible=True)
  576. def assert_inbounds(x):
  577. assert np.all(x >= bnds.lb)
  578. assert np.all(x <= bnds.ub)
  579. def obj(x):
  580. assert_inbounds(x)
  581. return np.exp(x[0])*(4*x[0]**2 + 2*x[1]**2 + 4*x[0]*x[1] + 2*x[1] + 1)
  582. def nce(x):
  583. assert_inbounds(x)
  584. return x[0]**2 + x[1]
  585. def nce_jac(x):
  586. return np.array([2*x[0], 1])
  587. def nci(x):
  588. assert_inbounds(x)
  589. return x[0]*x[1]
  590. x0 = np.array((0.99, -0.99))
  591. nlcs = [NonlinearConstraint(nci, -10, np.inf),
  592. NonlinearConstraint(nce, 1, 1, jac=nce_jac)]
  593. res = minimize(fun=obj, x0=x0, method='trust-constr',
  594. bounds=bnds, constraints=nlcs)
  595. assert_inbounds(res.x)
  596. assert nlcs[0].lb < nlcs[0].fun(res.x) < nlcs[0].ub
  597. def test_gh20665_too_many_constraints():
  598. # gh-20665 reports a confusing error message when there are more equality
  599. # constraints than variables. Check that the error message is improved.
  600. message = "...more equality constraints than independent variables..."
  601. with pytest.raises(ValueError, match=message):
  602. x0 = np.ones((2,))
  603. A_eq, b_eq = np.arange(6).reshape((3, 2)), np.ones((3,))
  604. g = NonlinearConstraint(lambda x: A_eq @ x, lb=b_eq, ub=b_eq)
  605. minimize(rosen, x0, method='trust-constr', constraints=[g])
  606. # no error with `SVDFactorization`
  607. with warnings.catch_warnings():
  608. warnings.simplefilter("ignore", UserWarning)
  609. minimize(rosen, x0, method='trust-constr', constraints=[g],
  610. options={'factorization_method': 'SVDFactorization'})
  611. def test_issue_18882():
  612. def lsf(u):
  613. u1, u2 = u
  614. a, b = [3.0, 4.0]
  615. return 1.0 + u1**2 / a**2 - u2**2 / b**2
  616. def of(u):
  617. return np.sum(u**2)
  618. with warnings.catch_warnings():
  619. warnings.filterwarnings("ignore", "delta_grad == 0.0", UserWarning)
  620. warnings.filterwarnings("ignore", "Singular Jacobian matrix.", UserWarning)
  621. res = minimize(
  622. of,
  623. [0.0, 0.0],
  624. method="trust-constr",
  625. constraints=NonlinearConstraint(lsf, 0, 0),
  626. )
  627. assert (not res.success) and (res.constr_violation > 1e-8)
  628. class TestBoundedNelderMead:
  629. @pytest.mark.parametrize('bounds, x_opt',
  630. [(Bounds(-np.inf, np.inf), Rosenbrock().x_opt),
  631. (Bounds(-np.inf, -0.8), [-0.8, -0.8]),
  632. (Bounds(3.0, np.inf), [3.0, 9.0]),
  633. (Bounds([3.0, 1.0], [4.0, 5.0]), [3., 5.]),
  634. ])
  635. def test_rosen_brock_with_bounds(self, bounds, x_opt):
  636. prob = Rosenbrock()
  637. with warnings.catch_warnings():
  638. msg = "Initial guess is not within the specified bounds"
  639. warnings.filterwarnings("ignore", msg, UserWarning)
  640. result = minimize(prob.fun, [-10, -10],
  641. method='Nelder-Mead',
  642. bounds=bounds)
  643. assert np.less_equal(bounds.lb, result.x).all()
  644. assert np.less_equal(result.x, bounds.ub).all()
  645. assert np.allclose(prob.fun(result.x), result.fun)
  646. assert np.allclose(result.x, x_opt, atol=1.e-3)
  647. def test_equal_all_bounds(self):
  648. prob = Rosenbrock()
  649. bounds = Bounds([4.0, 5.0], [4.0, 5.0])
  650. with warnings.catch_warnings():
  651. warnings.filterwarnings(
  652. "ignore",
  653. "Initial guess is not within the specified bounds",
  654. UserWarning)
  655. result = minimize(prob.fun, [-10, 8],
  656. method='Nelder-Mead',
  657. bounds=bounds)
  658. assert np.allclose(result.x, [4.0, 5.0])
  659. def test_equal_one_bounds(self):
  660. prob = Rosenbrock()
  661. bounds = Bounds([4.0, 5.0], [4.0, 20.0])
  662. with warnings.catch_warnings():
  663. warnings.filterwarnings(
  664. "ignore",
  665. "Initial guess is not within the specified bounds",
  666. UserWarning)
  667. result = minimize(prob.fun, [-10, 8],
  668. method='Nelder-Mead',
  669. bounds=bounds)
  670. assert np.allclose(result.x, [4.0, 16.0])
  671. def test_invalid_bounds(self):
  672. prob = Rosenbrock()
  673. message = 'An upper bound is less than the corresponding lower bound.'
  674. with pytest.raises(ValueError, match=message):
  675. bounds = Bounds([-np.inf, 1.0], [4.0, -5.0])
  676. minimize(prob.fun, [-10, 3],
  677. method='Nelder-Mead',
  678. bounds=bounds)
  679. @pytest.mark.xfail(reason="Failing on Azure Linux and macOS builds, "
  680. "see gh-13846")
  681. def test_outside_bounds_warning(self):
  682. prob = Rosenbrock()
  683. message = "Initial guess is not within the specified bounds"
  684. with pytest.warns(UserWarning, match=message):
  685. bounds = Bounds([-np.inf, 1.0], [4.0, 5.0])
  686. minimize(prob.fun, [-10, 8],
  687. method='Nelder-Mead',
  688. bounds=bounds)