test_constraint_conversion.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. from __future__ import division, print_function, absolute_import
  2. """
  3. Unit test for constraint conversion
  4. """
  5. import numpy as np
  6. from numpy.testing import (assert_, assert_array_almost_equal,
  7. assert_allclose, assert_equal, TestCase)
  8. import pytest
  9. from scipy._lib._numpy_compat import suppress_warnings
  10. from scipy.optimize import (NonlinearConstraint, LinearConstraint, Bounds,
  11. OptimizeWarning, minimize, BFGS)
  12. from .test_minimize_constrained import (Maratos, HyperbolicIneq, Rosenbrock,
  13. IneqRosenbrock, EqIneqRosenbrock,
  14. BoundedRosenbrock, Elec)
  15. from scipy._lib._numpy_compat import _assert_warns, suppress_warnings
  16. class TestOldToNew(object):
  17. x0 = (2, 0)
  18. bnds = ((0, None), (0, None))
  19. method = "trust-constr"
  20. def test_constraint_dictionary_1(self):
  21. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
  22. cons = ({'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
  23. {'type': 'ineq', 'fun': lambda x: -x[0] - 2 * x[1] + 6},
  24. {'type': 'ineq', 'fun': lambda x: -x[0] + 2 * x[1] + 2})
  25. with suppress_warnings() as sup:
  26. sup.filter(UserWarning, "delta_grad == 0.0")
  27. res = minimize(fun, self.x0, method=self.method,
  28. bounds=self.bnds, constraints=cons)
  29. assert_allclose(res.x, [1.4, 1.7], rtol=1e-4)
  30. assert_allclose(res.fun, 0.8, rtol=1e-4)
  31. def test_constraint_dictionary_2(self):
  32. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
  33. cons = {'type': 'eq',
  34. 'fun': lambda x, p1, p2: p1*x[0] - p2*x[1],
  35. 'args': (1, 1.1),
  36. 'jac': lambda x, p1, p2: np.array([[p1, -p2]])}
  37. with suppress_warnings() as sup:
  38. sup.filter(UserWarning, "delta_grad == 0.0")
  39. res = minimize(fun, self.x0, method=self.method,
  40. bounds=self.bnds, constraints=cons)
  41. assert_allclose(res.x, [1.7918552, 1.62895927])
  42. assert_allclose(res.fun, 1.3857466063348418)
  43. def test_constraint_dictionary_3(self):
  44. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2
  45. cons = [{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
  46. NonlinearConstraint(lambda x: x[0] - x[1], 0, 0)]
  47. with suppress_warnings() as sup:
  48. sup.filter(UserWarning, "delta_grad == 0.0")
  49. res = minimize(fun, self.x0, method=self.method,
  50. bounds=self.bnds, constraints=cons)
  51. assert_allclose(res.x, [1.75, 1.75], rtol=1e-4)
  52. assert_allclose(res.fun, 1.125, rtol=1e-4)
  53. class TestNewToOld(object):
  54. def test_multiple_constraint_objects(self):
  55. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
  56. x0 = [2, 0, 1]
  57. coni = [] # only inequality constraints (can use cobyla)
  58. methods = ["slsqp", "cobyla", "trust-constr"]
  59. # mixed old and new
  60. coni.append([{'type': 'ineq', 'fun': lambda x: x[0] - 2 * x[1] + 2},
  61. NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
  62. coni.append([LinearConstraint([1, -2, 0], -2, np.inf),
  63. NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
  64. coni.append([NonlinearConstraint(lambda x: x[0] - 2 * x[1] + 2, 0, np.inf),
  65. NonlinearConstraint(lambda x: x[0] - x[1], -1, 1)])
  66. for con in coni:
  67. funs = {}
  68. for method in methods:
  69. with suppress_warnings() as sup:
  70. sup.filter(UserWarning)
  71. result = minimize(fun, x0, method=method, constraints=con)
  72. funs[method] = result.fun
  73. assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-4)
  74. assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-4)
  75. def test_individual_constraint_objects(self):
  76. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
  77. x0 = [2, 0, 1]
  78. cone = [] # with equality constraints (can't use cobyla)
  79. coni = [] # only inequality constraints (can use cobyla)
  80. methods = ["slsqp", "cobyla", "trust-constr"]
  81. # nonstandard data types for constraint equality bounds
  82. cone.append(NonlinearConstraint(lambda x: x[0] - x[1], 1, 1))
  83. cone.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], [1.21]))
  84. cone.append(NonlinearConstraint(lambda x: x[0] - x[1],
  85. 1.21, np.array([1.21])))
  86. # multiple equalities
  87. cone.append(NonlinearConstraint(
  88. lambda x: [x[0] - x[1], x[1] - x[2]],
  89. 1.21, 1.21)) # two same equalities
  90. cone.append(NonlinearConstraint(
  91. lambda x: [x[0] - x[1], x[1] - x[2]],
  92. [1.21, 1.4], [1.21, 1.4])) # two different equalities
  93. cone.append(NonlinearConstraint(
  94. lambda x: [x[0] - x[1], x[1] - x[2]],
  95. [1.21, 1.21], 1.21)) # equality specified two ways
  96. cone.append(NonlinearConstraint(
  97. lambda x: [x[0] - x[1], x[1] - x[2]],
  98. [1.21, -np.inf], [1.21, np.inf])) # equality + unbounded
  99. # nonstandard data types for constraint inequality bounds
  100. coni.append(NonlinearConstraint(lambda x: x[0] - x[1], 1.21, np.inf))
  101. coni.append(NonlinearConstraint(lambda x: x[0] - x[1], [1.21], np.inf))
  102. coni.append(NonlinearConstraint(lambda x: x[0] - x[1],
  103. 1.21, np.array([np.inf])))
  104. coni.append(NonlinearConstraint(lambda x: x[0] - x[1], -np.inf, -3))
  105. coni.append(NonlinearConstraint(lambda x: x[0] - x[1],
  106. np.array(-np.inf), -3))
  107. # multiple inequalities/equalities
  108. coni.append(NonlinearConstraint(
  109. lambda x: [x[0] - x[1], x[1] - x[2]],
  110. 1.21, np.inf)) # two same inequalities
  111. cone.append(NonlinearConstraint(
  112. lambda x: [x[0] - x[1], x[1] - x[2]],
  113. [1.21, -np.inf], [1.21, 1.4])) # mixed equality/inequality
  114. coni.append(NonlinearConstraint(
  115. lambda x: [x[0] - x[1], x[1] - x[2]],
  116. [1.1, .8], [1.2, 1.4])) # bounded above and below
  117. coni.append(NonlinearConstraint(
  118. lambda x: [x[0] - x[1], x[1] - x[2]],
  119. [-1.2, -1.4], [-1.1, -.8])) # - bounded above and below
  120. # quick check of LinearConstraint class (very little new code to test)
  121. cone.append(LinearConstraint([1, -1, 0], 1.21, 1.21))
  122. cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]], 1.21, 1.21))
  123. cone.append(LinearConstraint([[1, -1, 0], [0, 1, -1]],
  124. [1.21, -np.inf], [1.21, 1.4]))
  125. for con in coni:
  126. funs = {}
  127. for method in methods:
  128. with suppress_warnings() as sup:
  129. sup.filter(UserWarning)
  130. result = minimize(fun, x0, method=method, constraints=con)
  131. funs[method] = result.fun
  132. assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3)
  133. assert_allclose(funs['cobyla'], funs['trust-constr'], rtol=1e-3)
  134. for con in cone:
  135. funs = {}
  136. for method in methods[::2]: # skip cobyla
  137. with suppress_warnings() as sup:
  138. sup.filter(UserWarning)
  139. result = minimize(fun, x0, method=method, constraints=con)
  140. funs[method] = result.fun
  141. assert_allclose(funs['slsqp'], funs['trust-constr'], rtol=1e-3)
  142. class TestNewToOldSLSQP(object):
  143. method = 'slsqp'
  144. elec = Elec(n_electrons=2)
  145. elec.x_opt = np.array([-0.58438468, 0.58438466, 0.73597047,
  146. -0.73597044, 0.34180668, -0.34180667])
  147. brock = BoundedRosenbrock()
  148. brock.x_opt = [0, 0]
  149. list_of_problems = [Maratos(),
  150. HyperbolicIneq(),
  151. Rosenbrock(),
  152. IneqRosenbrock(),
  153. EqIneqRosenbrock(),
  154. elec,
  155. brock
  156. ]
  157. def test_list_of_problems(self):
  158. for prob in self.list_of_problems:
  159. with suppress_warnings() as sup:
  160. sup.filter(UserWarning)
  161. result = minimize(prob.fun, prob.x0,
  162. method=self.method,
  163. bounds=prob.bounds,
  164. constraints=prob.constr)
  165. assert_array_almost_equal(result.x, prob.x_opt, decimal=3)
  166. def test_warn_mixed_constraints(self):
  167. # warns about inefficiency of mixed equality/inequality constraints
  168. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
  169. cons = NonlinearConstraint(lambda x: [x[0]**2 - x[1], x[1] - x[2]],
  170. [1.1, .8], [1.1, 1.4])
  171. bnds = ((0, None), (0, None), (0, None))
  172. with suppress_warnings() as sup:
  173. sup.filter(UserWarning, "delta_grad == 0.0")
  174. _assert_warns(OptimizeWarning, minimize, fun, (2, 0, 1),
  175. method=self.method, bounds=bnds, constraints=cons)
  176. def test_warn_ignored_options(self):
  177. # warns about constraint options being ignored
  178. fun = lambda x: (x[0] - 1)**2 + (x[1] - 2.5)**2 + (x[2] - 0.75)**2
  179. x0 = (2, 0, 1)
  180. if self.method == "slsqp":
  181. bnds = ((0, None), (0, None), (0, None))
  182. else:
  183. bnds = None
  184. cons = NonlinearConstraint(lambda x: x[0], 2, np.inf)
  185. res = minimize(fun, x0, method=self.method,
  186. bounds=bnds, constraints=cons)
  187. # no warnings without constraint options
  188. assert_allclose(res.fun, 1)
  189. cons = LinearConstraint([1, 0, 0], 2, np.inf)
  190. res = minimize(fun, x0, method=self.method,
  191. bounds=bnds, constraints=cons)
  192. # no warnings without constraint options
  193. assert_allclose(res.fun, 1)
  194. cons = []
  195. cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
  196. keep_feasible=True))
  197. cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
  198. hess=BFGS()))
  199. cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
  200. finite_diff_jac_sparsity=42))
  201. cons.append(NonlinearConstraint(lambda x: x[0]**2, 2, np.inf,
  202. finite_diff_rel_step=42))
  203. cons.append(LinearConstraint([1, 0, 0], 2, np.inf,
  204. keep_feasible=True))
  205. for con in cons:
  206. _assert_warns(OptimizeWarning, minimize, fun, x0,
  207. method=self.method, bounds=bnds, constraints=cons)
  208. class TestNewToOldCobyla(object):
  209. method = 'cobyla'
  210. list_of_problems = [
  211. Elec(n_electrons=2),
  212. Elec(n_electrons=4),
  213. ]
  214. @pytest.mark.slow
  215. def test_list_of_problems(self):
  216. for prob in self.list_of_problems:
  217. with suppress_warnings() as sup:
  218. sup.filter(UserWarning)
  219. truth = minimize(prob.fun, prob.x0,
  220. method='trust-constr',
  221. bounds=prob.bounds,
  222. constraints=prob.constr)
  223. result = minimize(prob.fun, prob.x0,
  224. method=self.method,
  225. bounds=prob.bounds,
  226. constraints=prob.constr)
  227. assert_allclose(result.fun, truth.fun, rtol=1e-3)