test_lsq_linear.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. import pytest
  2. import numpy as np
  3. from numpy.linalg import lstsq
  4. from numpy.testing import assert_allclose, assert_equal, assert_
  5. from scipy.sparse import rand, coo_matrix
  6. from scipy.sparse.linalg import aslinearoperator
  7. from scipy.optimize import lsq_linear
  8. A = np.array([
  9. [0.171, -0.057],
  10. [-0.049, -0.248],
  11. [-0.166, 0.054],
  12. ])
  13. b = np.array([0.074, 1.014, -0.383])
  14. class BaseMixin:
  15. def setup_method(self):
  16. self.rnd = np.random.RandomState(0)
  17. def test_dense_no_bounds(self):
  18. for lsq_solver in self.lsq_solvers:
  19. res = lsq_linear(A, b, method=self.method, lsq_solver=lsq_solver)
  20. assert_allclose(res.x, lstsq(A, b, rcond=-1)[0])
  21. assert_allclose(res.x, res.unbounded_sol[0])
  22. def test_dense_bounds(self):
  23. # Solutions for comparison are taken from MATLAB.
  24. lb = np.array([-1, -10])
  25. ub = np.array([1, 0])
  26. unbounded_sol = lstsq(A, b, rcond=-1)[0]
  27. for lsq_solver in self.lsq_solvers:
  28. res = lsq_linear(A, b, (lb, ub), method=self.method,
  29. lsq_solver=lsq_solver)
  30. assert_allclose(res.x, lstsq(A, b, rcond=-1)[0])
  31. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  32. lb = np.array([0.0, -np.inf])
  33. for lsq_solver in self.lsq_solvers:
  34. res = lsq_linear(A, b, (lb, np.inf), method=self.method,
  35. lsq_solver=lsq_solver)
  36. assert_allclose(res.x, np.array([0.0, -4.084174437334673]),
  37. atol=1e-6)
  38. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  39. lb = np.array([-1, 0])
  40. for lsq_solver in self.lsq_solvers:
  41. res = lsq_linear(A, b, (lb, np.inf), method=self.method,
  42. lsq_solver=lsq_solver)
  43. assert_allclose(res.x, np.array([0.448427311733504, 0]),
  44. atol=1e-15)
  45. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  46. ub = np.array([np.inf, -5])
  47. for lsq_solver in self.lsq_solvers:
  48. res = lsq_linear(A, b, (-np.inf, ub), method=self.method,
  49. lsq_solver=lsq_solver)
  50. assert_allclose(res.x, np.array([-0.105560998682388, -5]))
  51. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  52. ub = np.array([-1, np.inf])
  53. for lsq_solver in self.lsq_solvers:
  54. res = lsq_linear(A, b, (-np.inf, ub), method=self.method,
  55. lsq_solver=lsq_solver)
  56. assert_allclose(res.x, np.array([-1, -4.181102129483254]))
  57. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  58. lb = np.array([0, -4])
  59. ub = np.array([1, 0])
  60. for lsq_solver in self.lsq_solvers:
  61. res = lsq_linear(A, b, (lb, ub), method=self.method,
  62. lsq_solver=lsq_solver)
  63. assert_allclose(res.x, np.array([0.005236663400791, -4]))
  64. assert_allclose(res.unbounded_sol[0], unbounded_sol)
  65. def test_np_matrix(self):
  66. # gh-10711
  67. with np.testing.suppress_warnings() as sup:
  68. sup.filter(PendingDeprecationWarning)
  69. A = np.matrix([[20, -4, 0, 2, 3], [10, -2, 1, 0, -1]])
  70. k = np.array([20, 15])
  71. s_t = lsq_linear(A, k)
  72. def test_dense_rank_deficient(self):
  73. A = np.array([[-0.307, -0.184]])
  74. b = np.array([0.773])
  75. lb = [-0.1, -0.1]
  76. ub = [0.1, 0.1]
  77. for lsq_solver in self.lsq_solvers:
  78. res = lsq_linear(A, b, (lb, ub), method=self.method,
  79. lsq_solver=lsq_solver)
  80. assert_allclose(res.x, [-0.1, -0.1])
  81. assert_allclose(res.unbounded_sol[0], lstsq(A, b, rcond=-1)[0])
  82. A = np.array([
  83. [0.334, 0.668],
  84. [-0.516, -1.032],
  85. [0.192, 0.384],
  86. ])
  87. b = np.array([-1.436, 0.135, 0.909])
  88. lb = [0, -1]
  89. ub = [1, -0.5]
  90. for lsq_solver in self.lsq_solvers:
  91. res = lsq_linear(A, b, (lb, ub), method=self.method,
  92. lsq_solver=lsq_solver)
  93. assert_allclose(res.optimality, 0, atol=1e-11)
  94. assert_allclose(res.unbounded_sol[0], lstsq(A, b, rcond=-1)[0])
  95. def test_full_result(self):
  96. lb = np.array([0, -4])
  97. ub = np.array([1, 0])
  98. res = lsq_linear(A, b, (lb, ub), method=self.method)
  99. assert_allclose(res.x, [0.005236663400791, -4])
  100. assert_allclose(res.unbounded_sol[0], lstsq(A, b, rcond=-1)[0])
  101. r = A.dot(res.x) - b
  102. assert_allclose(res.cost, 0.5 * np.dot(r, r))
  103. assert_allclose(res.fun, r)
  104. assert_allclose(res.optimality, 0.0, atol=1e-12)
  105. assert_equal(res.active_mask, [0, -1])
  106. assert_(res.nit < 15)
  107. assert_(res.status == 1 or res.status == 3)
  108. assert_(isinstance(res.message, str))
  109. assert_(res.success)
  110. # This is a test for issue #9982.
  111. def test_almost_singular(self):
  112. A = np.array(
  113. [[0.8854232310355122, 0.0365312146937765, 0.0365312146836789],
  114. [0.3742460132129041, 0.0130523214078376, 0.0130523214077873],
  115. [0.9680633871281361, 0.0319366128718639, 0.0319366128718388]])
  116. b = np.array(
  117. [0.0055029366538097, 0.0026677442422208, 0.0066612514782381])
  118. result = lsq_linear(A, b, method=self.method)
  119. assert_(result.cost < 1.1e-8)
  120. def test_large_rank_deficient(self):
  121. np.random.seed(0)
  122. n, m = np.sort(np.random.randint(2, 1000, size=2))
  123. m *= 2 # make m >> n
  124. A = 1.0 * np.random.randint(-99, 99, size=[m, n])
  125. b = 1.0 * np.random.randint(-99, 99, size=[m])
  126. bounds = 1.0 * np.sort(np.random.randint(-99, 99, size=(2, n)), axis=0)
  127. bounds[1, :] += 1.0 # ensure up > lb
  128. # Make the A matrix strongly rank deficient by replicating some columns
  129. w = np.random.choice(n, n) # Select random columns with duplicates
  130. A = A[:, w]
  131. x_bvls = lsq_linear(A, b, bounds=bounds, method='bvls').x
  132. x_trf = lsq_linear(A, b, bounds=bounds, method='trf').x
  133. cost_bvls = np.sum((A @ x_bvls - b)**2)
  134. cost_trf = np.sum((A @ x_trf - b)**2)
  135. assert_(abs(cost_bvls - cost_trf) < cost_trf*1e-10)
  136. def test_convergence_small_matrix(self):
  137. A = np.array([[49.0, 41.0, -32.0],
  138. [-19.0, -32.0, -8.0],
  139. [-13.0, 10.0, 69.0]])
  140. b = np.array([-41.0, -90.0, 47.0])
  141. bounds = np.array([[31.0, -44.0, 26.0],
  142. [54.0, -32.0, 28.0]])
  143. x_bvls = lsq_linear(A, b, bounds=bounds, method='bvls').x
  144. x_trf = lsq_linear(A, b, bounds=bounds, method='trf').x
  145. cost_bvls = np.sum((A @ x_bvls - b)**2)
  146. cost_trf = np.sum((A @ x_trf - b)**2)
  147. assert_(abs(cost_bvls - cost_trf) < cost_trf*1e-10)
  148. class SparseMixin:
  149. def test_sparse_and_LinearOperator(self):
  150. m = 5000
  151. n = 1000
  152. A = rand(m, n, random_state=0)
  153. b = self.rnd.randn(m)
  154. res = lsq_linear(A, b)
  155. assert_allclose(res.optimality, 0, atol=1e-6)
  156. A = aslinearoperator(A)
  157. res = lsq_linear(A, b)
  158. assert_allclose(res.optimality, 0, atol=1e-6)
  159. def test_sparse_bounds(self):
  160. m = 5000
  161. n = 1000
  162. A = rand(m, n, random_state=0)
  163. b = self.rnd.randn(m)
  164. lb = self.rnd.randn(n)
  165. ub = lb + 1
  166. res = lsq_linear(A, b, (lb, ub))
  167. assert_allclose(res.optimality, 0.0, atol=1e-6)
  168. res = lsq_linear(A, b, (lb, ub), lsmr_tol=1e-13,
  169. lsmr_maxiter=1500)
  170. assert_allclose(res.optimality, 0.0, atol=1e-6)
  171. res = lsq_linear(A, b, (lb, ub), lsmr_tol='auto')
  172. assert_allclose(res.optimality, 0.0, atol=1e-6)
  173. def test_sparse_ill_conditioned(self):
  174. # Sparse matrix with condition number of ~4 million
  175. data = np.array([1., 1., 1., 1. + 1e-6, 1.])
  176. row = np.array([0, 0, 1, 2, 2])
  177. col = np.array([0, 2, 1, 0, 2])
  178. A = coo_matrix((data, (row, col)), shape=(3, 3))
  179. # Get the exact solution
  180. exact_sol = lsq_linear(A.toarray(), b, lsq_solver='exact')
  181. # Default lsmr arguments should not fully converge the solution
  182. default_lsmr_sol = lsq_linear(A, b, lsq_solver='lsmr')
  183. with pytest.raises(AssertionError, match=""):
  184. assert_allclose(exact_sol.x, default_lsmr_sol.x)
  185. # By increasing the maximum lsmr iters, it will converge
  186. conv_lsmr = lsq_linear(A, b, lsq_solver='lsmr', lsmr_maxiter=10)
  187. assert_allclose(exact_sol.x, conv_lsmr.x)
  188. class TestTRF(BaseMixin, SparseMixin):
  189. method = 'trf'
  190. lsq_solvers = ['exact', 'lsmr']
  191. class TestBVLS(BaseMixin):
  192. method = 'bvls'
  193. lsq_solvers = ['exact']
  194. class TestErrorChecking:
  195. def test_option_lsmr_tol(self):
  196. # Should work with a positive float, string equal to 'auto', or None
  197. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol=1e-2)
  198. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol='auto')
  199. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol=None)
  200. # Should raise error with negative float, strings
  201. # other than 'auto', and integers
  202. err_message = "`lsmr_tol` must be None, 'auto', or positive float."
  203. with pytest.raises(ValueError, match=err_message):
  204. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol=-0.1)
  205. with pytest.raises(ValueError, match=err_message):
  206. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol='foo')
  207. with pytest.raises(ValueError, match=err_message):
  208. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_tol=1)
  209. def test_option_lsmr_maxiter(self):
  210. # Should work with positive integers or None
  211. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_maxiter=1)
  212. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_maxiter=None)
  213. # Should raise error with 0 or negative max iter
  214. err_message = "`lsmr_maxiter` must be None or positive integer."
  215. with pytest.raises(ValueError, match=err_message):
  216. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_maxiter=0)
  217. with pytest.raises(ValueError, match=err_message):
  218. _ = lsq_linear(A, b, lsq_solver='lsmr', lsmr_maxiter=-1)