test_slsqp.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. """
  2. Unit test for SLSQP optimization.
  3. """
  4. from __future__ import division, print_function, absolute_import
  5. import pytest
  6. from numpy.testing import (assert_, assert_array_almost_equal,
  7. assert_allclose, assert_equal)
  8. from pytest import raises as assert_raises
  9. import numpy as np
  10. from scipy.optimize import fmin_slsqp, minimize, NonlinearConstraint, Bounds
  11. class MyCallBack(object):
  12. """pass a custom callback function
  13. This makes sure it's being used.
  14. """
  15. def __init__(self):
  16. self.been_called = False
  17. self.ncalls = 0
  18. def __call__(self, x):
  19. self.been_called = True
  20. self.ncalls += 1
  21. class TestSLSQP(object):
  22. """
  23. Test SLSQP algorithm using Example 14.4 from Numerical Methods for
  24. Engineers by Steven Chapra and Raymond Canale.
  25. This example maximizes the function f(x) = 2*x*y + 2*x - x**2 - 2*y**2,
  26. which has a maximum at x=2, y=1.
  27. """
  28. def setup_method(self):
  29. self.opts = {'disp': False}
  30. def fun(self, d, sign=1.0):
  31. """
  32. Arguments:
  33. d - A list of two elements, where d[0] represents x and d[1] represents y
  34. in the following equation.
  35. sign - A multiplier for f. Since we want to optimize it, and the scipy
  36. optimizers can only minimize functions, we need to multiply it by
  37. -1 to achieve the desired solution
  38. Returns:
  39. 2*x*y + 2*x - x**2 - 2*y**2
  40. """
  41. x = d[0]
  42. y = d[1]
  43. return sign*(2*x*y + 2*x - x**2 - 2*y**2)
  44. def jac(self, d, sign=1.0):
  45. """
  46. This is the derivative of fun, returning a numpy array
  47. representing df/dx and df/dy.
  48. """
  49. x = d[0]
  50. y = d[1]
  51. dfdx = sign*(-2*x + 2*y + 2)
  52. dfdy = sign*(2*x - 4*y)
  53. return np.array([dfdx, dfdy], float)
  54. def fun_and_jac(self, d, sign=1.0):
  55. return self.fun(d, sign), self.jac(d, sign)
  56. def f_eqcon(self, x, sign=1.0):
  57. """ Equality constraint """
  58. return np.array([x[0] - x[1]])
  59. def fprime_eqcon(self, x, sign=1.0):
  60. """ Equality constraint, derivative """
  61. return np.array([[1, -1]])
  62. def f_eqcon_scalar(self, x, sign=1.0):
  63. """ Scalar equality constraint """
  64. return self.f_eqcon(x, sign)[0]
  65. def fprime_eqcon_scalar(self, x, sign=1.0):
  66. """ Scalar equality constraint, derivative """
  67. return self.fprime_eqcon(x, sign)[0].tolist()
  68. def f_ieqcon(self, x, sign=1.0):
  69. """ Inequality constraint """
  70. return np.array([x[0] - x[1] - 1.0])
  71. def fprime_ieqcon(self, x, sign=1.0):
  72. """ Inequality constraint, derivative """
  73. return np.array([[1, -1]])
  74. def f_ieqcon2(self, x):
  75. """ Vector inequality constraint """
  76. return np.asarray(x)
  77. def fprime_ieqcon2(self, x):
  78. """ Vector inequality constraint, derivative """
  79. return np.identity(x.shape[0])
  80. # minimize
  81. def test_minimize_unbounded_approximated(self):
  82. # Minimize, method='SLSQP': unbounded, approximated jacobian.
  83. res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
  84. method='SLSQP', options=self.opts)
  85. assert_(res['success'], res['message'])
  86. assert_allclose(res.x, [2, 1])
  87. def test_minimize_unbounded_given(self):
  88. # Minimize, method='SLSQP': unbounded, given jacobian.
  89. res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
  90. jac=self.jac, method='SLSQP', options=self.opts)
  91. assert_(res['success'], res['message'])
  92. assert_allclose(res.x, [2, 1])
  93. def test_minimize_bounded_approximated(self):
  94. # Minimize, method='SLSQP': bounded, approximated jacobian.
  95. with np.errstate(invalid='ignore'):
  96. res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
  97. bounds=((2.5, None), (None, 0.5)),
  98. method='SLSQP', options=self.opts)
  99. assert_(res['success'], res['message'])
  100. assert_allclose(res.x, [2.5, 0.5])
  101. assert_(2.5 <= res.x[0])
  102. assert_(res.x[1] <= 0.5)
  103. def test_minimize_unbounded_combined(self):
  104. # Minimize, method='SLSQP': unbounded, combined function and jacobian.
  105. res = minimize(self.fun_and_jac, [-1.0, 1.0], args=(-1.0, ),
  106. jac=True, method='SLSQP', options=self.opts)
  107. assert_(res['success'], res['message'])
  108. assert_allclose(res.x, [2, 1])
  109. def test_minimize_equality_approximated(self):
  110. # Minimize with method='SLSQP': equality constraint, approx. jacobian.
  111. res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
  112. constraints={'type': 'eq',
  113. 'fun': self.f_eqcon,
  114. 'args': (-1.0, )},
  115. method='SLSQP', options=self.opts)
  116. assert_(res['success'], res['message'])
  117. assert_allclose(res.x, [1, 1])
  118. def test_minimize_equality_given(self):
  119. # Minimize with method='SLSQP': equality constraint, given jacobian.
  120. res = minimize(self.fun, [-1.0, 1.0], jac=self.jac,
  121. method='SLSQP', args=(-1.0,),
  122. constraints={'type': 'eq', 'fun':self.f_eqcon,
  123. 'args': (-1.0, )},
  124. options=self.opts)
  125. assert_(res['success'], res['message'])
  126. assert_allclose(res.x, [1, 1])
  127. def test_minimize_equality_given2(self):
  128. # Minimize with method='SLSQP': equality constraint, given jacobian
  129. # for fun and const.
  130. res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
  131. jac=self.jac, args=(-1.0,),
  132. constraints={'type': 'eq',
  133. 'fun': self.f_eqcon,
  134. 'args': (-1.0, ),
  135. 'jac': self.fprime_eqcon},
  136. options=self.opts)
  137. assert_(res['success'], res['message'])
  138. assert_allclose(res.x, [1, 1])
  139. def test_minimize_equality_given_cons_scalar(self):
  140. # Minimize with method='SLSQP': scalar equality constraint, given
  141. # jacobian for fun and const.
  142. res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
  143. jac=self.jac, args=(-1.0,),
  144. constraints={'type': 'eq',
  145. 'fun': self.f_eqcon_scalar,
  146. 'args': (-1.0, ),
  147. 'jac': self.fprime_eqcon_scalar},
  148. options=self.opts)
  149. assert_(res['success'], res['message'])
  150. assert_allclose(res.x, [1, 1])
  151. def test_minimize_inequality_given(self):
  152. # Minimize with method='SLSQP': inequality constraint, given jacobian.
  153. res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
  154. jac=self.jac, args=(-1.0, ),
  155. constraints={'type': 'ineq',
  156. 'fun': self.f_ieqcon,
  157. 'args': (-1.0, )},
  158. options=self.opts)
  159. assert_(res['success'], res['message'])
  160. assert_allclose(res.x, [2, 1], atol=1e-3)
  161. def test_minimize_inequality_given_vector_constraints(self):
  162. # Minimize with method='SLSQP': vector inequality constraint, given
  163. # jacobian.
  164. res = minimize(self.fun, [-1.0, 1.0], jac=self.jac,
  165. method='SLSQP', args=(-1.0,),
  166. constraints={'type': 'ineq',
  167. 'fun': self.f_ieqcon2,
  168. 'jac': self.fprime_ieqcon2},
  169. options=self.opts)
  170. assert_(res['success'], res['message'])
  171. assert_allclose(res.x, [2, 1])
  172. def test_minimize_bound_equality_given2(self):
  173. # Minimize with method='SLSQP': bounds, eq. const., given jac. for
  174. # fun. and const.
  175. res = minimize(self.fun, [-1.0, 1.0], method='SLSQP',
  176. jac=self.jac, args=(-1.0, ),
  177. bounds=[(-0.8, 1.), (-1, 0.8)],
  178. constraints={'type': 'eq',
  179. 'fun': self.f_eqcon,
  180. 'args': (-1.0, ),
  181. 'jac': self.fprime_eqcon},
  182. options=self.opts)
  183. assert_(res['success'], res['message'])
  184. assert_allclose(res.x, [0.8, 0.8], atol=1e-3)
  185. assert_(-0.8 <= res.x[0] <= 1)
  186. assert_(-1 <= res.x[1] <= 0.8)
  187. # fmin_slsqp
  188. def test_unbounded_approximated(self):
  189. # SLSQP: unbounded, approximated jacobian.
  190. res = fmin_slsqp(self.fun, [-1.0, 1.0], args=(-1.0, ),
  191. iprint = 0, full_output = 1)
  192. x, fx, its, imode, smode = res
  193. assert_(imode == 0, imode)
  194. assert_array_almost_equal(x, [2, 1])
  195. def test_unbounded_given(self):
  196. # SLSQP: unbounded, given jacobian.
  197. res = fmin_slsqp(self.fun, [-1.0, 1.0], args=(-1.0, ),
  198. fprime = self.jac, iprint = 0,
  199. full_output = 1)
  200. x, fx, its, imode, smode = res
  201. assert_(imode == 0, imode)
  202. assert_array_almost_equal(x, [2, 1])
  203. def test_equality_approximated(self):
  204. # SLSQP: equality constraint, approximated jacobian.
  205. res = fmin_slsqp(self.fun,[-1.0,1.0], args=(-1.0,),
  206. eqcons = [self.f_eqcon],
  207. iprint = 0, full_output = 1)
  208. x, fx, its, imode, smode = res
  209. assert_(imode == 0, imode)
  210. assert_array_almost_equal(x, [1, 1])
  211. def test_equality_given(self):
  212. # SLSQP: equality constraint, given jacobian.
  213. res = fmin_slsqp(self.fun, [-1.0, 1.0],
  214. fprime=self.jac, args=(-1.0,),
  215. eqcons = [self.f_eqcon], iprint = 0,
  216. full_output = 1)
  217. x, fx, its, imode, smode = res
  218. assert_(imode == 0, imode)
  219. assert_array_almost_equal(x, [1, 1])
  220. def test_equality_given2(self):
  221. # SLSQP: equality constraint, given jacobian for fun and const.
  222. res = fmin_slsqp(self.fun, [-1.0, 1.0],
  223. fprime=self.jac, args=(-1.0,),
  224. f_eqcons = self.f_eqcon,
  225. fprime_eqcons = self.fprime_eqcon,
  226. iprint = 0,
  227. full_output = 1)
  228. x, fx, its, imode, smode = res
  229. assert_(imode == 0, imode)
  230. assert_array_almost_equal(x, [1, 1])
  231. def test_inequality_given(self):
  232. # SLSQP: inequality constraint, given jacobian.
  233. res = fmin_slsqp(self.fun, [-1.0, 1.0],
  234. fprime=self.jac, args=(-1.0, ),
  235. ieqcons = [self.f_ieqcon],
  236. iprint = 0, full_output = 1)
  237. x, fx, its, imode, smode = res
  238. assert_(imode == 0, imode)
  239. assert_array_almost_equal(x, [2, 1], decimal=3)
  240. def test_bound_equality_given2(self):
  241. # SLSQP: bounds, eq. const., given jac. for fun. and const.
  242. res = fmin_slsqp(self.fun, [-1.0, 1.0],
  243. fprime=self.jac, args=(-1.0, ),
  244. bounds = [(-0.8, 1.), (-1, 0.8)],
  245. f_eqcons = self.f_eqcon,
  246. fprime_eqcons = self.fprime_eqcon,
  247. iprint = 0, full_output = 1)
  248. x, fx, its, imode, smode = res
  249. assert_(imode == 0, imode)
  250. assert_array_almost_equal(x, [0.8, 0.8], decimal=3)
  251. assert_(-0.8 <= x[0] <= 1)
  252. assert_(-1 <= x[1] <= 0.8)
  253. def test_scalar_constraints(self):
  254. # Regression test for gh-2182
  255. x = fmin_slsqp(lambda z: z**2, [3.],
  256. ieqcons=[lambda z: z[0] - 1],
  257. iprint=0)
  258. assert_array_almost_equal(x, [1.])
  259. x = fmin_slsqp(lambda z: z**2, [3.],
  260. f_ieqcons=lambda z: [z[0] - 1],
  261. iprint=0)
  262. assert_array_almost_equal(x, [1.])
  263. def test_integer_bounds(self):
  264. # This should not raise an exception
  265. fmin_slsqp(lambda z: z**2 - 1, [0], bounds=[[0, 1]], iprint=0)
  266. def test_obj_must_return_scalar(self):
  267. # Regression test for Github Issue #5433
  268. # If objective function does not return a scalar, raises ValueError
  269. with assert_raises(ValueError):
  270. fmin_slsqp(lambda x: [0, 1], [1, 2, 3])
  271. def test_obj_returns_scalar_in_list(self):
  272. # Test for Github Issue #5433 and PR #6691
  273. # Objective function should be able to return length-1 Python list
  274. # containing the scalar
  275. fmin_slsqp(lambda x: [0], [1, 2, 3], iprint=0)
  276. def test_callback(self):
  277. # Minimize, method='SLSQP': unbounded, approximated jacobian. Check for callback
  278. callback = MyCallBack()
  279. res = minimize(self.fun, [-1.0, 1.0], args=(-1.0, ),
  280. method='SLSQP', callback=callback, options=self.opts)
  281. assert_(res['success'], res['message'])
  282. assert_(callback.been_called)
  283. assert_equal(callback.ncalls, res['nit'])
  284. def test_inconsistent_linearization(self):
  285. # SLSQP must be able to solve this problem, even if the
  286. # linearized problem at the starting point is infeasible.
  287. # Linearized constraints are
  288. #
  289. # 2*x0[0]*x[0] >= 1
  290. #
  291. # At x0 = [0, 1], the second constraint is clearly infeasible.
  292. # This triggers a call with n2==1 in the LSQ subroutine.
  293. x = [0, 1]
  294. f1 = lambda x: x[0] + x[1] - 2
  295. f2 = lambda x: x[0]**2 - 1
  296. sol = minimize(
  297. lambda x: x[0]**2 + x[1]**2,
  298. x,
  299. constraints=({'type':'eq','fun': f1},
  300. {'type':'ineq','fun': f2}),
  301. bounds=((0,None), (0,None)),
  302. method='SLSQP')
  303. x = sol.x
  304. assert_allclose(f1(x), 0, atol=1e-8)
  305. assert_(f2(x) >= -1e-8)
  306. assert_(sol.success, sol)
  307. def test_regression_5743(self):
  308. # SLSQP must not indicate success for this problem,
  309. # which is infeasible.
  310. x = [1, 2]
  311. sol = minimize(
  312. lambda x: x[0]**2 + x[1]**2,
  313. x,
  314. constraints=({'type':'eq','fun': lambda x: x[0]+x[1]-1},
  315. {'type':'ineq','fun': lambda x: x[0]-2}),
  316. bounds=((0,None), (0,None)),
  317. method='SLSQP')
  318. assert_(not sol.success, sol)
  319. def test_gh_6676(self):
  320. def func(x):
  321. return (x[0] - 1)**2 + 2*(x[1] - 1)**2 + 0.5*(x[2] - 1)**2
  322. sol = minimize(func, [0, 0, 0], method='SLSQP')
  323. assert_(sol.jac.shape == (3,))
  324. def test_invalid_bounds(self):
  325. # Raise correct error when lower bound is greater than upper bound.
  326. # See Github issue 6875.
  327. bounds_list = [
  328. ((1, 2), (2, 1)),
  329. ((2, 1), (1, 2)),
  330. ((2, 1), (2, 1)),
  331. ((np.inf, 0), (np.inf, 0)),
  332. ((1, -np.inf), (0, 1)),
  333. ]
  334. for bounds in bounds_list:
  335. with assert_raises(ValueError):
  336. minimize(self.fun, [-1.0, 1.0], bounds=bounds, method='SLSQP')
  337. def test_bounds_clipping(self):
  338. #
  339. # SLSQP returns bogus results for initial guess out of bounds, gh-6859
  340. #
  341. def f(x):
  342. return (x[0] - 1)**2
  343. sol = minimize(f, [10], method='slsqp', bounds=[(None, 0)])
  344. assert_(sol.success)
  345. assert_allclose(sol.x, 0, atol=1e-10)
  346. sol = minimize(f, [-10], method='slsqp', bounds=[(2, None)])
  347. assert_(sol.success)
  348. assert_allclose(sol.x, 2, atol=1e-10)
  349. sol = minimize(f, [-10], method='slsqp', bounds=[(None, 0)])
  350. assert_(sol.success)
  351. assert_allclose(sol.x, 0, atol=1e-10)
  352. sol = minimize(f, [10], method='slsqp', bounds=[(2, None)])
  353. assert_(sol.success)
  354. assert_allclose(sol.x, 2, atol=1e-10)
  355. sol = minimize(f, [-0.5], method='slsqp', bounds=[(-1, 0)])
  356. assert_(sol.success)
  357. assert_allclose(sol.x, 0, atol=1e-10)
  358. sol = minimize(f, [10], method='slsqp', bounds=[(-1, 0)])
  359. assert_(sol.success)
  360. assert_allclose(sol.x, 0, atol=1e-10)
  361. def test_infeasible_initial(self):
  362. # Check SLSQP behavior with infeasible initial point
  363. def f(x):
  364. x, = x
  365. return x*x - 2*x + 1
  366. cons_u = [{'type': 'ineq', 'fun': lambda x: 0 - x}]
  367. cons_l = [{'type': 'ineq', 'fun': lambda x: x - 2}]
  368. cons_ul = [{'type': 'ineq', 'fun': lambda x: 0 - x},
  369. {'type': 'ineq', 'fun': lambda x: x + 1}]
  370. sol = minimize(f, [10], method='slsqp', constraints=cons_u)
  371. assert_(sol.success)
  372. assert_allclose(sol.x, 0, atol=1e-10)
  373. sol = minimize(f, [-10], method='slsqp', constraints=cons_l)
  374. assert_(sol.success)
  375. assert_allclose(sol.x, 2, atol=1e-10)
  376. sol = minimize(f, [-10], method='slsqp', constraints=cons_u)
  377. assert_(sol.success)
  378. assert_allclose(sol.x, 0, atol=1e-10)
  379. sol = minimize(f, [10], method='slsqp', constraints=cons_l)
  380. assert_(sol.success)
  381. assert_allclose(sol.x, 2, atol=1e-10)
  382. sol = minimize(f, [-0.5], method='slsqp', constraints=cons_ul)
  383. assert_(sol.success)
  384. assert_allclose(sol.x, 0, atol=1e-10)
  385. sol = minimize(f, [10], method='slsqp', constraints=cons_ul)
  386. assert_(sol.success)
  387. assert_allclose(sol.x, 0, atol=1e-10)
  388. def test_inconsistent_inequalities(self):
  389. # gh-7618
  390. def cost(x):
  391. return -1 * x[0] + 4 * x[1]
  392. def ineqcons1(x):
  393. return x[1] - x[0] - 1
  394. def ineqcons2(x):
  395. return x[0] - x[1]
  396. # The inequalities are inconsistent, so no solution can exist:
  397. #
  398. # x1 >= x0 + 1
  399. # x0 >= x1
  400. x0 = (1,5)
  401. bounds = ((-5, 5), (-5, 5))
  402. cons = (dict(type='ineq', fun=ineqcons1), dict(type='ineq', fun=ineqcons2))
  403. res = minimize(cost, x0, method='SLSQP', bounds=bounds, constraints=cons)
  404. assert_(not res.success)
  405. def test_new_bounds_type(self):
  406. f = lambda x: x[0]**2 + x[1]**2
  407. bounds = Bounds([1, 0], [np.inf, np.inf])
  408. sol = minimize(f, [0, 0], method='slsqp', bounds=bounds)
  409. assert_(sol.success)
  410. assert_allclose(sol.x, [1, 0])
  411. def test_nested_minimization(self):
  412. class NestedProblem():
  413. def __init__(self):
  414. self.F_outer_count = 0
  415. def F_outer(self, x):
  416. self.F_outer_count += 1
  417. if self.F_outer_count > 1000:
  418. raise Exception("Nested minimization failed to terminate.")
  419. inner_res = minimize(self.F_inner, (3, 4), method="SLSQP")
  420. assert_(inner_res.success)
  421. assert_allclose(inner_res.x, [1, 1])
  422. return x[0]**2 + x[1]**2 + x[2]**2
  423. def F_inner(self, x):
  424. return (x[0] - 1)**2 + (x[1] - 1)**2
  425. def solve(self):
  426. outer_res = minimize(self.F_outer, (5, 5, 5), method="SLSQP")
  427. assert_(outer_res.success)
  428. assert_allclose(outer_res.x, [0, 0, 0])
  429. problem = NestedProblem()
  430. problem.solve()