test_constraint_conversion.py 12 KB

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