test_lsmr.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. """
  2. Copyright (C) 2010 David Fong and Michael Saunders
  3. Distributed under the same license as SciPy
  4. Testing Code for LSMR.
  5. 03 Jun 2010: First version release with lsmr.py
  6. David Chin-lung Fong clfong@stanford.edu
  7. Institute for Computational and Mathematical Engineering
  8. Stanford University
  9. Michael Saunders saunders@stanford.edu
  10. Systems Optimization Laboratory
  11. Dept of MS&E, Stanford University.
  12. """
  13. from numpy import array, arange, eye, zeros, ones, sqrt, transpose, hstack
  14. from numpy.linalg import norm
  15. from numpy.testing import assert_allclose
  16. import pytest
  17. from scipy.sparse import coo_matrix
  18. from scipy.sparse.linalg._interface import aslinearoperator
  19. from scipy.sparse.linalg import lsmr
  20. from .test_lsqr import G, b
  21. class TestLSMR:
  22. def setup_method(self):
  23. self.n = 10
  24. self.m = 10
  25. def assertCompatibleSystem(self, A, xtrue):
  26. Afun = aslinearoperator(A)
  27. b = Afun.matvec(xtrue)
  28. x = lsmr(A, b)[0]
  29. assert norm(x - xtrue) == pytest.approx(0, abs=1e-5)
  30. def testIdentityACase1(self):
  31. A = eye(self.n)
  32. xtrue = zeros((self.n, 1))
  33. self.assertCompatibleSystem(A, xtrue)
  34. def testIdentityACase2(self):
  35. A = eye(self.n)
  36. xtrue = ones((self.n,1))
  37. self.assertCompatibleSystem(A, xtrue)
  38. def testIdentityACase3(self):
  39. A = eye(self.n)
  40. xtrue = transpose(arange(self.n,0,-1))
  41. self.assertCompatibleSystem(A, xtrue)
  42. def testBidiagonalA(self):
  43. A = lowerBidiagonalMatrix(20,self.n)
  44. xtrue = transpose(arange(self.n,0,-1))
  45. self.assertCompatibleSystem(A,xtrue)
  46. def testScalarB(self):
  47. A = array([[1.0, 2.0]])
  48. b = 3.0
  49. x = lsmr(A, b)[0]
  50. assert norm(A.dot(x) - b) == pytest.approx(0)
  51. def testComplexX(self):
  52. A = eye(self.n)
  53. xtrue = transpose(arange(self.n, 0, -1) * (1 + 1j))
  54. self.assertCompatibleSystem(A, xtrue)
  55. def testComplexX0(self):
  56. A = 4 * eye(self.n) + ones((self.n, self.n))
  57. xtrue = transpose(arange(self.n, 0, -1))
  58. b = aslinearoperator(A).matvec(xtrue)
  59. x0 = zeros(self.n, dtype=complex)
  60. x = lsmr(A, b, x0=x0)[0]
  61. assert norm(x - xtrue) == pytest.approx(0, abs=1e-5)
  62. def testComplexA(self):
  63. A = 4 * eye(self.n) + 1j * ones((self.n, self.n))
  64. xtrue = transpose(arange(self.n, 0, -1).astype(complex))
  65. self.assertCompatibleSystem(A, xtrue)
  66. def testComplexB(self):
  67. A = 4 * eye(self.n) + ones((self.n, self.n))
  68. xtrue = transpose(arange(self.n, 0, -1) * (1 + 1j))
  69. b = aslinearoperator(A).matvec(xtrue)
  70. x = lsmr(A, b)[0]
  71. assert norm(x - xtrue) == pytest.approx(0, abs=1e-5)
  72. def testColumnB(self):
  73. A = eye(self.n)
  74. b = ones((self.n, 1))
  75. x = lsmr(A, b)[0]
  76. assert norm(A.dot(x) - b.ravel()) == pytest.approx(0)
  77. def testInitialization(self):
  78. # Test that the default setting is not modified
  79. x_ref, _, itn_ref, normr_ref, *_ = lsmr(G, b)
  80. assert_allclose(norm(b - G@x_ref), normr_ref, atol=1e-6)
  81. # Test passing zeros yields similiar result
  82. x0 = zeros(b.shape)
  83. x = lsmr(G, b, x0=x0)[0]
  84. assert_allclose(x, x_ref)
  85. # Test warm-start with single iteration
  86. x0 = lsmr(G, b, maxiter=1)[0]
  87. x, _, itn, normr, *_ = lsmr(G, b, x0=x0)
  88. assert_allclose(norm(b - G@x), normr, atol=1e-6)
  89. # NOTE(gh-12139): This doesn't always converge to the same value as
  90. # ref because error estimates will be slightly different when calculated
  91. # from zeros vs x0 as a result only compare norm and itn (not x).
  92. # x generally converges 1 iteration faster because it started at x0.
  93. # itn == itn_ref means that lsmr(x0) took an extra iteration see above.
  94. # -1 is technically possible but is rare (1 in 100000) so it's more
  95. # likely to be an error elsewhere.
  96. assert itn - itn_ref in (0, 1)
  97. # If an extra iteration is performed normr may be 0, while normr_ref
  98. # may be much larger.
  99. assert normr < normr_ref * (1 + 1e-6)
  100. class TestLSMRReturns:
  101. def setup_method(self):
  102. self.n = 10
  103. self.A = lowerBidiagonalMatrix(20, self.n)
  104. self.xtrue = transpose(arange(self.n, 0, -1))
  105. self.Afun = aslinearoperator(self.A)
  106. self.b = self.Afun.matvec(self.xtrue)
  107. self.x0 = ones(self.n)
  108. self.x00 = self.x0.copy()
  109. self.returnValues = lsmr(self.A, self.b)
  110. self.returnValuesX0 = lsmr(self.A, self.b, x0=self.x0)
  111. def test_unchanged_x0(self):
  112. x, istop, itn, normr, normar, normA, condA, normx = self.returnValuesX0
  113. assert_allclose(self.x00, self.x0)
  114. def testNormr(self):
  115. x, istop, itn, normr, normar, normA, condA, normx = self.returnValues
  116. assert norm(self.b - self.Afun.matvec(x)) == pytest.approx(normr)
  117. def testNormar(self):
  118. x, istop, itn, normr, normar, normA, condA, normx = self.returnValues
  119. assert (norm(self.Afun.rmatvec(self.b - self.Afun.matvec(x)))
  120. == pytest.approx(normar))
  121. def testNormx(self):
  122. x, istop, itn, normr, normar, normA, condA, normx = self.returnValues
  123. assert norm(x) == pytest.approx(normx)
  124. def lowerBidiagonalMatrix(m, n):
  125. # This is a simple example for testing LSMR.
  126. # It uses the leading m*n submatrix from
  127. # A = [ 1
  128. # 1 2
  129. # 2 3
  130. # 3 4
  131. # ...
  132. # n ]
  133. # suitably padded by zeros.
  134. #
  135. # 04 Jun 2010: First version for distribution with lsmr.py
  136. if m <= n:
  137. row = hstack((arange(m, dtype=int),
  138. arange(1, m, dtype=int)))
  139. col = hstack((arange(m, dtype=int),
  140. arange(m-1, dtype=int)))
  141. data = hstack((arange(1, m+1, dtype=float),
  142. arange(1,m, dtype=float)))
  143. return coo_matrix((data, (row, col)), shape=(m,n))
  144. else:
  145. row = hstack((arange(n, dtype=int),
  146. arange(1, n+1, dtype=int)))
  147. col = hstack((arange(n, dtype=int),
  148. arange(n, dtype=int)))
  149. data = hstack((arange(1, n+1, dtype=float),
  150. arange(1,n+1, dtype=float)))
  151. return coo_matrix((data,(row, col)), shape=(m,n))
  152. def lsmrtest(m, n, damp):
  153. """Verbose testing of lsmr"""
  154. A = lowerBidiagonalMatrix(m,n)
  155. xtrue = arange(n,0,-1, dtype=float)
  156. Afun = aslinearoperator(A)
  157. b = Afun.matvec(xtrue)
  158. atol = 1.0e-7
  159. btol = 1.0e-7
  160. conlim = 1.0e+10
  161. itnlim = 10*n
  162. show = 1
  163. x, istop, itn, normr, normar, norma, conda, normx \
  164. = lsmr(A, b, damp, atol, btol, conlim, itnlim, show)
  165. j1 = min(n,5)
  166. j2 = max(n-4,1)
  167. print(' ')
  168. print('First elements of x:')
  169. str = ['%10.4f' % (xi) for xi in x[0:j1]]
  170. print(''.join(str))
  171. print(' ')
  172. print('Last elements of x:')
  173. str = ['%10.4f' % (xi) for xi in x[j2-1:]]
  174. print(''.join(str))
  175. r = b - Afun.matvec(x)
  176. r2 = sqrt(norm(r)**2 + (damp*norm(x))**2)
  177. print(' ')
  178. str = 'normr (est.) %17.10e' % (normr)
  179. str2 = 'normr (true) %17.10e' % (r2)
  180. print(str)
  181. print(str2)
  182. print(' ')
  183. if __name__ == "__main__":
  184. lsmrtest(20,10,0)