test_constraints.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import pytest
  2. import numpy as np
  3. from numpy.testing import TestCase, assert_array_equal
  4. import scipy.sparse as sps
  5. from scipy.optimize._constraints import (
  6. Bounds, LinearConstraint, NonlinearConstraint, PreparedConstraint,
  7. new_bounds_to_old, old_bound_to_new, strict_bounds)
  8. class TestStrictBounds(TestCase):
  9. def test_scalarvalue_unique_enforce_feasibility(self):
  10. m = 3
  11. lb = 2
  12. ub = 4
  13. enforce_feasibility = False
  14. strict_lb, strict_ub = strict_bounds(lb, ub,
  15. enforce_feasibility,
  16. m)
  17. assert_array_equal(strict_lb, [-np.inf, -np.inf, -np.inf])
  18. assert_array_equal(strict_ub, [np.inf, np.inf, np.inf])
  19. enforce_feasibility = True
  20. strict_lb, strict_ub = strict_bounds(lb, ub,
  21. enforce_feasibility,
  22. m)
  23. assert_array_equal(strict_lb, [2, 2, 2])
  24. assert_array_equal(strict_ub, [4, 4, 4])
  25. def test_vectorvalue_unique_enforce_feasibility(self):
  26. m = 3
  27. lb = [1, 2, 3]
  28. ub = [4, 5, 6]
  29. enforce_feasibility = False
  30. strict_lb, strict_ub = strict_bounds(lb, ub,
  31. enforce_feasibility,
  32. m)
  33. assert_array_equal(strict_lb, [-np.inf, -np.inf, -np.inf])
  34. assert_array_equal(strict_ub, [np.inf, np.inf, np.inf])
  35. enforce_feasibility = True
  36. strict_lb, strict_ub = strict_bounds(lb, ub,
  37. enforce_feasibility,
  38. m)
  39. assert_array_equal(strict_lb, [1, 2, 3])
  40. assert_array_equal(strict_ub, [4, 5, 6])
  41. def test_scalarvalue_vector_enforce_feasibility(self):
  42. m = 3
  43. lb = 2
  44. ub = 4
  45. enforce_feasibility = [False, True, False]
  46. strict_lb, strict_ub = strict_bounds(lb, ub,
  47. enforce_feasibility,
  48. m)
  49. assert_array_equal(strict_lb, [-np.inf, 2, -np.inf])
  50. assert_array_equal(strict_ub, [np.inf, 4, np.inf])
  51. def test_vectorvalue_vector_enforce_feasibility(self):
  52. m = 3
  53. lb = [1, 2, 3]
  54. ub = [4, 6, np.inf]
  55. enforce_feasibility = [True, False, True]
  56. strict_lb, strict_ub = strict_bounds(lb, ub,
  57. enforce_feasibility,
  58. m)
  59. assert_array_equal(strict_lb, [1, -np.inf, 3])
  60. assert_array_equal(strict_ub, [4, np.inf, np.inf])
  61. def test_prepare_constraint_infeasible_x0():
  62. lb = np.array([0, 20, 30])
  63. ub = np.array([0.5, np.inf, 70])
  64. x0 = np.array([1, 2, 3])
  65. enforce_feasibility = np.array([False, True, True], dtype=bool)
  66. bounds = Bounds(lb, ub, enforce_feasibility)
  67. pytest.raises(ValueError, PreparedConstraint, bounds, x0)
  68. pc = PreparedConstraint(Bounds(lb, ub), [1, 2, 3])
  69. assert (pc.violation([1, 2, 3]) > 0).any()
  70. assert (pc.violation([0.25, 21, 31]) == 0).all()
  71. x0 = np.array([1, 2, 3, 4])
  72. A = np.array([[1, 2, 3, 4], [5, 0, 0, 6], [7, 0, 8, 0]])
  73. enforce_feasibility = np.array([True, True, True], dtype=bool)
  74. linear = LinearConstraint(A, -np.inf, 0, enforce_feasibility)
  75. pytest.raises(ValueError, PreparedConstraint, linear, x0)
  76. pc = PreparedConstraint(LinearConstraint(A, -np.inf, 0),
  77. [1, 2, 3, 4])
  78. assert (pc.violation([1, 2, 3, 4]) > 0).any()
  79. assert (pc.violation([-10, 2, -10, 4]) == 0).all()
  80. def fun(x):
  81. return A.dot(x)
  82. def jac(x):
  83. return A
  84. def hess(x, v):
  85. return sps.csr_matrix((4, 4))
  86. nonlinear = NonlinearConstraint(fun, -np.inf, 0, jac, hess,
  87. enforce_feasibility)
  88. pytest.raises(ValueError, PreparedConstraint, nonlinear, x0)
  89. pc = PreparedConstraint(nonlinear, [-10, 2, -10, 4])
  90. assert (pc.violation([1, 2, 3, 4]) > 0).any()
  91. assert (pc.violation([-10, 2, -10, 4]) == 0).all()
  92. def test_violation():
  93. def cons_f(x):
  94. return np.array([x[0] ** 2 + x[1], x[0] ** 2 - x[1]])
  95. nlc = NonlinearConstraint(cons_f, [-1, -0.8500], [2, 2])
  96. pc = PreparedConstraint(nlc, [0.5, 1])
  97. assert_array_equal(pc.violation([0.5, 1]), [0., 0.])
  98. np.testing.assert_almost_equal(pc.violation([0.5, 1.2]), [0., 0.1])
  99. np.testing.assert_almost_equal(pc.violation([1.2, 1.2]), [0.64, 0])
  100. np.testing.assert_almost_equal(pc.violation([0.1, -1.2]), [0.19, 0])
  101. np.testing.assert_almost_equal(pc.violation([0.1, 2]), [0.01, 1.14])
  102. def test_new_bounds_to_old():
  103. lb = np.array([-np.inf, 2, 3])
  104. ub = np.array([3, np.inf, 10])
  105. bounds = [(None, 3), (2, None), (3, 10)]
  106. assert_array_equal(new_bounds_to_old(lb, ub, 3), bounds)
  107. bounds_single_lb = [(-1, 3), (-1, None), (-1, 10)]
  108. assert_array_equal(new_bounds_to_old(-1, ub, 3), bounds_single_lb)
  109. bounds_no_lb = [(None, 3), (None, None), (None, 10)]
  110. assert_array_equal(new_bounds_to_old(-np.inf, ub, 3), bounds_no_lb)
  111. bounds_single_ub = [(None, 20), (2, 20), (3, 20)]
  112. assert_array_equal(new_bounds_to_old(lb, 20, 3), bounds_single_ub)
  113. bounds_no_ub = [(None, None), (2, None), (3, None)]
  114. assert_array_equal(new_bounds_to_old(lb, np.inf, 3), bounds_no_ub)
  115. bounds_single_both = [(1, 2), (1, 2), (1, 2)]
  116. assert_array_equal(new_bounds_to_old(1, 2, 3), bounds_single_both)
  117. bounds_no_both = [(None, None), (None, None), (None, None)]
  118. assert_array_equal(new_bounds_to_old(-np.inf, np.inf, 3), bounds_no_both)
  119. def test_old_bounds_to_new():
  120. bounds = ([1, 2], (None, 3), (-1, None))
  121. lb_true = np.array([1, -np.inf, -1])
  122. ub_true = np.array([2, 3, np.inf])
  123. lb, ub = old_bound_to_new(bounds)
  124. assert_array_equal(lb, lb_true)
  125. assert_array_equal(ub, ub_true)
  126. bounds = [(-np.inf, np.inf), (np.array([1]), np.array([1]))]
  127. lb, ub = old_bound_to_new(bounds)
  128. assert_array_equal(lb, [-np.inf, 1])
  129. assert_array_equal(ub, [np.inf, 1])
  130. class TestBounds:
  131. def test_repr(self):
  132. # so that eval works
  133. from numpy import array, inf # noqa
  134. for args in (
  135. (-1.0, 5.0),
  136. (-1.0, np.inf, True),
  137. (np.array([1.0, -np.inf]), np.array([2.0, np.inf])),
  138. (np.array([1.0, -np.inf]), np.array([2.0, np.inf]),
  139. np.array([True, False])),
  140. ):
  141. bounds = Bounds(*args)
  142. bounds2 = eval(repr(Bounds(*args)))
  143. assert_array_equal(bounds.lb, bounds2.lb)
  144. assert_array_equal(bounds.ub, bounds2.ub)
  145. assert_array_equal(bounds.keep_feasible, bounds2.keep_feasible)
  146. def test_array(self):
  147. # gh13501
  148. b = Bounds(lb=[0.0, 0.0], ub=[1.0, 1.0])
  149. assert isinstance(b.lb, np.ndarray)
  150. assert isinstance(b.ub, np.ndarray)
  151. def test_defaults(self):
  152. b1 = Bounds()
  153. b2 = Bounds(np.asarray(-np.inf), np.asarray(np.inf))
  154. assert b1.lb == b2.lb
  155. assert b1.ub == b2.ub
  156. def test_input_validation(self):
  157. message = "`lb`, `ub`, and `keep_feasible` must be broadcastable."
  158. with pytest.raises(ValueError, match=message):
  159. Bounds([1, 2], [1, 2, 3])
  160. def test_residual(self):
  161. bounds = Bounds(-2, 4)
  162. x0 = [-1, 2]
  163. np.testing.assert_allclose(bounds.residual(x0), ([1, 4], [5, 2]))
  164. class TestLinearConstraint:
  165. def test_defaults(self):
  166. A = np.eye(4)
  167. lc = LinearConstraint(A)
  168. lc2 = LinearConstraint(A, -np.inf, np.inf)
  169. assert_array_equal(lc.lb, lc2.lb)
  170. assert_array_equal(lc.ub, lc2.ub)
  171. def test_input_validation(self):
  172. A = np.eye(4)
  173. message = "`lb`, `ub`, and `keep_feasible` must be broadcastable"
  174. with pytest.raises(ValueError, match=message):
  175. LinearConstraint(A, [1, 2], [1, 2, 3])
  176. A = np.empty((4, 3, 5))
  177. message = "`A` must have exactly two dimensions."
  178. with pytest.raises(ValueError, match=message):
  179. LinearConstraint(A)
  180. def test_residual(self):
  181. A = np.eye(2)
  182. lc = LinearConstraint(A, -2, 4)
  183. x0 = [-1, 2]
  184. np.testing.assert_allclose(lc.residual(x0), ([1, 4], [5, 2]))