test_interface.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. """Test functions for the sparse.linalg._interface module
  2. """
  3. from functools import partial
  4. from itertools import product
  5. import operator
  6. import pytest
  7. from pytest import raises as assert_raises, warns
  8. from numpy.testing import assert_, assert_equal
  9. import numpy as np
  10. import scipy.sparse as sparse
  11. import scipy.sparse.linalg._interface as interface
  12. from scipy.sparse._sputils import matrix
  13. from scipy._lib._gcutils import assert_deallocated, IS_PYPY
  14. class TestLinearOperator:
  15. def setup_method(self):
  16. self.A = np.array([[1,2,3],
  17. [4,5,6]])
  18. self.B = np.array([[1,2],
  19. [3,4],
  20. [5,6]])
  21. self.C = np.array([[1,2],
  22. [3,4]])
  23. def test_matvec(self):
  24. def get_matvecs(A):
  25. return [{
  26. 'shape': A.shape,
  27. 'matvec': lambda x: np.dot(A, x).reshape(A.shape[0]),
  28. 'rmatvec': lambda x: np.dot(A.T.conj(),
  29. x).reshape(A.shape[1])
  30. },
  31. {
  32. 'shape': A.shape,
  33. 'matvec': lambda x: np.dot(A, x),
  34. 'rmatvec': lambda x: np.dot(A.T.conj(), x),
  35. 'rmatmat': lambda x: np.dot(A.T.conj(), x),
  36. 'matmat': lambda x: np.dot(A, x)
  37. }]
  38. for matvecs in get_matvecs(self.A):
  39. A = interface.LinearOperator(**matvecs)
  40. assert_(A.args == ())
  41. assert_equal(A.matvec(np.array([1,2,3])), [14,32])
  42. assert_equal(A.matvec(np.array([[1],[2],[3]])), [[14],[32]])
  43. assert_equal(A @ np.array([1,2,3]), [14,32])
  44. assert_equal(A @ np.array([[1],[2],[3]]), [[14],[32]])
  45. assert_equal(A.dot(np.array([1,2,3])), [14,32])
  46. assert_equal(A.dot(np.array([[1],[2],[3]])), [[14],[32]])
  47. assert_equal(A.matvec(matrix([[1],[2],[3]])), [[14],[32]])
  48. assert_equal(A @ matrix([[1],[2],[3]]), [[14],[32]])
  49. assert_equal(A.dot(matrix([[1],[2],[3]])), [[14],[32]])
  50. assert_equal((2*A)@[1,1,1], [12,30])
  51. assert_equal((2 * A).rmatvec([1, 1]), [10, 14, 18])
  52. assert_equal((2*A).H.matvec([1,1]), [10, 14, 18])
  53. assert_equal((2*A).adjoint().matvec([1,1]), [10, 14, 18])
  54. assert_equal((2*A)@[[1],[1],[1]], [[12],[30]])
  55. assert_equal((2 * A).matmat([[1], [1], [1]]), [[12], [30]])
  56. assert_equal((A*2)@[1,1,1], [12,30])
  57. assert_equal((A*2)@[[1],[1],[1]], [[12],[30]])
  58. assert_equal((2j*A)@[1,1,1], [12j,30j])
  59. assert_equal((A+A)@[1,1,1], [12, 30])
  60. assert_equal((A + A).rmatvec([1, 1]), [10, 14, 18])
  61. assert_equal((A+A).H.matvec([1,1]), [10, 14, 18])
  62. assert_equal((A+A).adjoint().matvec([1,1]), [10, 14, 18])
  63. assert_equal((A+A)@[[1],[1],[1]], [[12], [30]])
  64. assert_equal((A+A).matmat([[1],[1],[1]]), [[12], [30]])
  65. assert_equal((-A)@[1,1,1], [-6,-15])
  66. assert_equal((-A)@[[1],[1],[1]], [[-6],[-15]])
  67. assert_equal((A-A)@[1,1,1], [0,0])
  68. assert_equal((A - A) @ [[1], [1], [1]], [[0], [0]])
  69. X = np.array([[1, 2], [3, 4]])
  70. # A_asarray = np.array([[1, 2, 3], [4, 5, 6]])
  71. assert_equal((2 * A).rmatmat(X), np.dot((2 * self.A).T, X))
  72. assert_equal((A * 2).rmatmat(X), np.dot((self.A * 2).T, X))
  73. assert_equal((2j * A).rmatmat(X),
  74. np.dot((2j * self.A).T.conj(), X))
  75. assert_equal((A * 2j).rmatmat(X),
  76. np.dot((self.A * 2j).T.conj(), X))
  77. assert_equal((A + A).rmatmat(X),
  78. np.dot((self.A + self.A).T, X))
  79. assert_equal((A + 2j * A).rmatmat(X),
  80. np.dot((self.A + 2j * self.A).T.conj(), X))
  81. assert_equal((-A).rmatmat(X), np.dot((-self.A).T, X))
  82. assert_equal((A - A).rmatmat(X),
  83. np.dot((self.A - self.A).T, X))
  84. assert_equal((2j * A).rmatmat(2j * X),
  85. np.dot((2j * self.A).T.conj(), 2j * X))
  86. z = A+A
  87. assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] is A)
  88. z = 2*A
  89. assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] == 2)
  90. assert_(isinstance(A.matvec([1, 2, 3]), np.ndarray))
  91. assert_(isinstance(A.matvec(np.array([[1],[2],[3]])), np.ndarray))
  92. assert_(isinstance(A @ np.array([1,2,3]), np.ndarray))
  93. assert_(isinstance(A @ np.array([[1],[2],[3]]), np.ndarray))
  94. assert_(isinstance(A.dot(np.array([1,2,3])), np.ndarray))
  95. assert_(isinstance(A.dot(np.array([[1],[2],[3]])), np.ndarray))
  96. assert_(isinstance(A.matvec(matrix([[1],[2],[3]])), np.ndarray))
  97. assert_(isinstance(A @ matrix([[1],[2],[3]]), np.ndarray))
  98. assert_(isinstance(A.dot(matrix([[1],[2],[3]])), np.ndarray))
  99. assert_(isinstance(2*A, interface._ScaledLinearOperator))
  100. assert_(isinstance(2j*A, interface._ScaledLinearOperator))
  101. assert_(isinstance(A+A, interface._SumLinearOperator))
  102. assert_(isinstance(-A, interface._ScaledLinearOperator))
  103. assert_(isinstance(A-A, interface._SumLinearOperator))
  104. assert_(isinstance(A/2, interface._ScaledLinearOperator))
  105. assert_(isinstance(A/2j, interface._ScaledLinearOperator))
  106. assert_(((A * 3) / 3).args[0] is A) # check for simplification
  107. # Test that prefactor is of _ScaledLinearOperator is not mutated
  108. # when the operator is multiplied by a number
  109. result = A @ np.array([1, 2, 3])
  110. B = A * 3
  111. C = A / 5
  112. assert_equal(A @ np.array([1, 2, 3]), result)
  113. assert_((2j*A).dtype == np.complex128)
  114. # Test division by non-scalar
  115. msg = "Can only divide a linear operator by a scalar."
  116. with assert_raises(ValueError, match=msg):
  117. A / np.array([1, 2])
  118. assert_raises(ValueError, A.matvec, np.array([1,2]))
  119. assert_raises(ValueError, A.matvec, np.array([1,2,3,4]))
  120. assert_raises(ValueError, A.matvec, np.array([[1],[2]]))
  121. assert_raises(ValueError, A.matvec, np.array([[1],[2],[3],[4]]))
  122. assert_raises(ValueError, lambda: A@A)
  123. assert_raises(ValueError, lambda: A**2)
  124. for matvecsA, matvecsB in product(get_matvecs(self.A),
  125. get_matvecs(self.B)):
  126. A = interface.LinearOperator(**matvecsA)
  127. B = interface.LinearOperator(**matvecsB)
  128. # AtimesB = np.array([[22, 28], [49, 64]])
  129. AtimesB = self.A.dot(self.B)
  130. X = np.array([[1, 2], [3, 4]])
  131. assert_equal((A @ B).rmatmat(X), np.dot((AtimesB).T, X))
  132. assert_equal((2j * A @ B).rmatmat(X),
  133. np.dot((2j * AtimesB).T.conj(), X))
  134. assert_equal((A@B)@[1,1], [50,113])
  135. assert_equal((A@B)@[[1],[1]], [[50],[113]])
  136. assert_equal((A@B).matmat([[1],[1]]), [[50],[113]])
  137. assert_equal((A @ B).rmatvec([1, 1]), [71, 92])
  138. assert_equal((A @ B).H.matvec([1, 1]), [71, 92])
  139. assert_equal((A @ B).adjoint().matvec([1, 1]), [71, 92])
  140. assert_(isinstance(A@B, interface._ProductLinearOperator))
  141. assert_raises(ValueError, lambda: A+B)
  142. assert_raises(ValueError, lambda: A**2)
  143. z = A@B
  144. assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] is B)
  145. for matvecsC in get_matvecs(self.C):
  146. C = interface.LinearOperator(**matvecsC)
  147. X = np.array([[1, 2], [3, 4]])
  148. assert_equal(C.rmatmat(X), np.dot((self.C).T, X))
  149. assert_equal((C**2).rmatmat(X),
  150. np.dot((np.dot(self.C, self.C)).T, X))
  151. assert_equal((C**2)@[1,1], [17,37])
  152. assert_equal((C**2).rmatvec([1, 1]), [22, 32])
  153. assert_equal((C**2).H.matvec([1, 1]), [22, 32])
  154. assert_equal((C**2).adjoint().matvec([1, 1]), [22, 32])
  155. assert_equal((C**2).matmat([[1],[1]]), [[17],[37]])
  156. assert_(isinstance(C**2, interface._PowerLinearOperator))
  157. def test_matmul(self):
  158. D = {'shape': self.A.shape,
  159. 'matvec': lambda x: np.dot(self.A, x).reshape(self.A.shape[0]),
  160. 'rmatvec': lambda x: np.dot(self.A.T.conj(),
  161. x).reshape(self.A.shape[1]),
  162. 'rmatmat': lambda x: np.dot(self.A.T.conj(), x),
  163. 'matmat': lambda x: np.dot(self.A, x)}
  164. A = interface.LinearOperator(**D)
  165. B = np.array([[1 + 1j, 2, 3],
  166. [4, 5, 6],
  167. [7, 8, 9]])
  168. b = B[0]
  169. assert_equal(operator.matmul(A, b), A * b)
  170. assert_equal(operator.matmul(A, b.reshape(-1, 1)), A * b.reshape(-1, 1))
  171. assert_equal(operator.matmul(A, B), A @ B)
  172. assert_equal(operator.matmul(b, A.H), b * A.H)
  173. assert_equal(operator.matmul(b, A.adjoint()), b * A.adjoint())
  174. assert_equal(operator.matmul(b.reshape(1, -1), A.H), b.reshape(1, -1) * A.H)
  175. assert_equal(operator.matmul(b.reshape(1, -1), A.adjoint()),
  176. b.reshape(1, -1) * A.adjoint())
  177. assert_equal(operator.matmul(B, A.H), B @ A.H)
  178. assert_equal(operator.matmul(B, A.adjoint()), B @ A.adjoint())
  179. assert_raises(ValueError, operator.matmul, A, 2)
  180. assert_raises(ValueError, operator.matmul, 2, A)
  181. class TestAsLinearOperator:
  182. def setup_method(self):
  183. self.cases = []
  184. def make_cases(original, dtype):
  185. cases = []
  186. cases.append((matrix(original, dtype=dtype), original))
  187. cases.append((np.array(original, dtype=dtype), original))
  188. cases.append((sparse.csr_array(original, dtype=dtype), original))
  189. # Test default implementations of _adjoint and _rmatvec, which
  190. # refer to each other.
  191. def mv(x, dtype):
  192. y = original.dot(x)
  193. if len(x.shape) == 2:
  194. y = y.reshape(-1, 1)
  195. return y
  196. def rmv(x, dtype):
  197. return original.T.conj().dot(x)
  198. class BaseMatlike(interface.LinearOperator):
  199. args = ()
  200. def __init__(self, dtype):
  201. self.dtype = np.dtype(dtype)
  202. self.shape = original.shape
  203. def _matvec(self, x):
  204. return mv(x, self.dtype)
  205. class HasRmatvec(BaseMatlike):
  206. args = ()
  207. def _rmatvec(self,x):
  208. return rmv(x, self.dtype)
  209. class HasAdjoint(BaseMatlike):
  210. args = ()
  211. def _adjoint(self):
  212. shape = self.shape[1], self.shape[0]
  213. matvec = partial(rmv, dtype=self.dtype)
  214. rmatvec = partial(mv, dtype=self.dtype)
  215. return interface.LinearOperator(matvec=matvec,
  216. rmatvec=rmatvec,
  217. dtype=self.dtype,
  218. shape=shape)
  219. class HasRmatmat(HasRmatvec):
  220. def _matmat(self, x):
  221. return original.dot(x)
  222. def _rmatmat(self, x):
  223. return original.T.conj().dot(x)
  224. cases.append((HasRmatvec(dtype), original))
  225. cases.append((HasAdjoint(dtype), original))
  226. cases.append((HasRmatmat(dtype), original))
  227. return cases
  228. original = np.array([[1,2,3], [4,5,6]])
  229. self.cases += make_cases(original, np.int32)
  230. self.cases += make_cases(original, np.float32)
  231. self.cases += make_cases(original, np.float64)
  232. self.cases += [(interface.aslinearoperator(M).T, A.T)
  233. for M, A in make_cases(original.T, np.float64)]
  234. self.cases += [(interface.aslinearoperator(M).H, A.T.conj())
  235. for M, A in make_cases(original.T, np.float64)]
  236. self.cases += [(interface.aslinearoperator(M).adjoint(), A.T.conj())
  237. for M, A in make_cases(original.T, np.float64)]
  238. original = np.array([[1, 2j, 3j], [4j, 5j, 6]])
  239. self.cases += make_cases(original, np.complex128)
  240. self.cases += [(interface.aslinearoperator(M).T, A.T)
  241. for M, A in make_cases(original.T, np.complex128)]
  242. self.cases += [(interface.aslinearoperator(M).H, A.T.conj())
  243. for M, A in make_cases(original.T, np.complex128)]
  244. self.cases += [(interface.aslinearoperator(M).adjoint(), A.T.conj())
  245. for M, A in make_cases(original.T, np.complex128)]
  246. def test_basic(self):
  247. for M, A_array in self.cases:
  248. A = interface.aslinearoperator(M)
  249. M,N = A.shape
  250. xs = [np.array([1, 2, 3]),
  251. np.array([[1], [2], [3]])]
  252. ys = [np.array([1, 2]), np.array([[1], [2]])]
  253. if A.dtype == np.complex128:
  254. xs += [np.array([1, 2j, 3j]),
  255. np.array([[1], [2j], [3j]])]
  256. ys += [np.array([1, 2j]), np.array([[1], [2j]])]
  257. x2 = np.array([[1, 4], [2, 5], [3, 6]])
  258. for x in xs:
  259. assert_equal(A.matvec(x), A_array.dot(x))
  260. assert_equal(A @ x, A_array.dot(x))
  261. assert_equal(A.matmat(x2), A_array.dot(x2))
  262. assert_equal(A @ x2, A_array.dot(x2))
  263. for y in ys:
  264. assert_equal(A.rmatvec(y), A_array.T.conj().dot(y))
  265. assert_equal(A.T.matvec(y), A_array.T.dot(y))
  266. assert_equal(A.H.matvec(y), A_array.T.conj().dot(y))
  267. assert_equal(A.adjoint().matvec(y), A_array.T.conj().dot(y))
  268. for y in ys:
  269. if y.ndim < 2:
  270. continue
  271. assert_equal(A.rmatmat(y), A_array.T.conj().dot(y))
  272. assert_equal(A.T.matmat(y), A_array.T.dot(y))
  273. assert_equal(A.H.matmat(y), A_array.T.conj().dot(y))
  274. assert_equal(A.adjoint().matmat(y), A_array.T.conj().dot(y))
  275. if hasattr(M,'dtype'):
  276. assert_equal(A.dtype, M.dtype)
  277. assert_(hasattr(A, 'args'))
  278. def test_dot(self):
  279. for M, A_array in self.cases:
  280. A = interface.aslinearoperator(M)
  281. M,N = A.shape
  282. x0 = np.array([1, 2, 3])
  283. x1 = np.array([[1], [2], [3]])
  284. x2 = np.array([[1, 4], [2, 5], [3, 6]])
  285. assert_equal(A.dot(x0), A_array.dot(x0))
  286. assert_equal(A.dot(x1), A_array.dot(x1))
  287. assert_equal(A.dot(x2), A_array.dot(x2))
  288. def test_repr():
  289. A = interface.LinearOperator(shape=(1, 1), matvec=lambda x: 1)
  290. repr_A = repr(A)
  291. assert_('unspecified dtype' not in repr_A, repr_A)
  292. def test_identity():
  293. ident = interface.IdentityOperator((3, 3))
  294. assert_equal(ident @ [1, 2, 3], [1, 2, 3])
  295. assert_equal(ident.dot(np.arange(9).reshape(3, 3)).ravel(), np.arange(9))
  296. assert_raises(ValueError, ident.matvec, [1, 2, 3, 4])
  297. def test_attributes():
  298. A = interface.aslinearoperator(np.arange(16).reshape(4, 4))
  299. def always_four_ones(x):
  300. x = np.asarray(x)
  301. assert_(x.shape == (3,) or x.shape == (3, 1))
  302. return np.ones(4)
  303. B = interface.LinearOperator(shape=(4, 3), matvec=always_four_ones)
  304. ops = [A, B, A * B, A @ B, A.H, A.adjoint(), A + A, B + B, A**4]
  305. for op in ops:
  306. assert_(hasattr(op, "dtype"))
  307. assert_(hasattr(op, "shape"))
  308. assert_(hasattr(op, "_matvec"))
  309. def matvec(x):
  310. """ Needed for test_pickle as local functions are not pickleable """
  311. return np.zeros(3)
  312. def test_pickle():
  313. import pickle
  314. for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
  315. A = interface.LinearOperator((3, 3), matvec)
  316. s = pickle.dumps(A, protocol=protocol)
  317. B = pickle.loads(s)
  318. for k in A.__dict__:
  319. assert_equal(getattr(A, k), getattr(B, k))
  320. def test_inheritance():
  321. class Empty(interface.LinearOperator):
  322. pass
  323. with warns(RuntimeWarning, match="should implement at least"):
  324. assert_raises(TypeError, Empty)
  325. class Identity(interface.LinearOperator):
  326. def __init__(self, n):
  327. super().__init__(dtype=None, shape=(n, n))
  328. def _matvec(self, x):
  329. return x
  330. id3 = Identity(3)
  331. assert_equal(id3.matvec([1, 2, 3]), [1, 2, 3])
  332. assert_raises(NotImplementedError, id3.rmatvec, [4, 5, 6])
  333. class MatmatOnly(interface.LinearOperator):
  334. def __init__(self, A):
  335. super().__init__(A.dtype, A.shape)
  336. self.A = A
  337. def _matmat(self, x):
  338. return self.A.dot(x)
  339. mm = MatmatOnly(np.random.randn(5, 3))
  340. assert_equal(mm.matvec(np.random.randn(3)).shape, (5,))
  341. def test_dtypes_of_operator_sum():
  342. # gh-6078
  343. mat_complex = np.random.rand(2,2) + 1j * np.random.rand(2,2)
  344. mat_real = np.random.rand(2,2)
  345. complex_operator = interface.aslinearoperator(mat_complex)
  346. real_operator = interface.aslinearoperator(mat_real)
  347. sum_complex = complex_operator + complex_operator
  348. sum_real = real_operator + real_operator
  349. assert_equal(sum_real.dtype, np.float64)
  350. assert_equal(sum_complex.dtype, np.complex128)
  351. def test_no_double_init():
  352. call_count = [0]
  353. def matvec(v):
  354. call_count[0] += 1
  355. return v
  356. # It should call matvec exactly once (in order to determine the
  357. # operator dtype)
  358. interface.LinearOperator((2, 2), matvec=matvec)
  359. assert_equal(call_count[0], 1)
  360. INT_DTYPES = (np.int8, np.int16, np.int32, np.int64)
  361. REAL_DTYPES = (np.float32, np.float64, np.longdouble)
  362. COMPLEX_DTYPES = (np.complex64, np.complex128, np.clongdouble)
  363. INEXACTDTYPES = REAL_DTYPES + COMPLEX_DTYPES
  364. ALLDTYPES = INT_DTYPES + INEXACTDTYPES
  365. @pytest.mark.parametrize("test_dtype", ALLDTYPES)
  366. def test_determine_lo_dtype_from_matvec(test_dtype):
  367. # gh-19209
  368. scalar = np.array(1, dtype=test_dtype)
  369. def mv(v):
  370. return np.array([scalar * v[0], v[1]])
  371. lo = interface.LinearOperator((2, 2), matvec=mv)
  372. assert lo.dtype == np.dtype(test_dtype)
  373. def test_determine_lo_dtype_for_int():
  374. # gh-19209
  375. # test Python int larger than int8 max cast to some int
  376. def mv(v):
  377. return np.array([128 * v[0], v[1]])
  378. lo = interface.LinearOperator((2, 2), matvec=mv)
  379. assert lo.dtype in INT_DTYPES
  380. def test_adjoint_conjugate():
  381. X = np.array([[1j]])
  382. A = interface.aslinearoperator(X)
  383. B = 1j * A
  384. Y = 1j * X
  385. v = np.array([1])
  386. assert_equal(B.dot(v), Y.dot(v))
  387. assert_equal(B.H.dot(v), Y.T.conj().dot(v))
  388. assert_equal(B.adjoint().dot(v), Y.T.conj().dot(v))
  389. def test_ndim():
  390. X = np.array([[1]])
  391. A = interface.aslinearoperator(X)
  392. assert_equal(A.ndim, 2)
  393. def test_transpose_noconjugate():
  394. X = np.array([[1j]])
  395. A = interface.aslinearoperator(X)
  396. B = 1j * A
  397. Y = 1j * X
  398. v = np.array([1])
  399. assert_equal(B.dot(v), Y.dot(v))
  400. assert_equal(B.T.dot(v), Y.T.dot(v))
  401. def test_transpose_multiplication():
  402. class MyMatrix(interface.LinearOperator):
  403. def __init__(self, A):
  404. super().__init__(A.dtype, A.shape)
  405. self.A = A
  406. def _matmat(self, other): return self.A @ other
  407. def _rmatmat(self, other): return self.A.T @ other
  408. A = MyMatrix(np.array([[1, 2], [3, 4]]))
  409. X = np.array([1, 2])
  410. B = np.array([[10, 20], [30, 40]])
  411. X2 = X.reshape(-1, 1)
  412. Y = np.array([[1, 2], [3, 4]])
  413. assert_equal(A @ B, Y @ B)
  414. assert_equal(B.T @ A, B.T @ Y)
  415. assert_equal(A.T @ B, Y.T @ B)
  416. assert_equal(A @ X, Y @ X)
  417. assert_equal(X.T @ A, X.T @ Y)
  418. assert_equal(A.T @ X, Y.T @ X)
  419. assert_equal(A @ X2, Y @ X2)
  420. assert_equal(X2.T @ A, X2.T @ Y)
  421. assert_equal(A.T @ X2, Y.T @ X2)
  422. def test_sparse_matmat_exception():
  423. A = interface.LinearOperator((2, 2), matvec=lambda x: x)
  424. B = sparse.eye_array(2)
  425. msg = "Unable to multiply a LinearOperator with a sparse matrix."
  426. with assert_raises(TypeError, match=msg):
  427. A @ B
  428. with assert_raises(TypeError, match=msg):
  429. B @ A
  430. with assert_raises(ValueError):
  431. A @ np.identity(4)
  432. with assert_raises(ValueError):
  433. np.identity(4) @ A
  434. @pytest.mark.skipif(IS_PYPY, reason="Test not meaningful on PyPy")
  435. def test_MatrixLinearOperator_refcycle():
  436. # gh-10634
  437. # Test that MatrixLinearOperator can be automatically garbage collected
  438. A = np.eye(2)
  439. with assert_deallocated(interface.MatrixLinearOperator, A) as op:
  440. op.adjoint()
  441. del op