test_array_api.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  1. import pytest
  2. import numpy as np
  3. import numpy.testing as npt
  4. import scipy.sparse
  5. import scipy.sparse.linalg as spla
  6. sparray_types = ('bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil')
  7. sparray_classes = [
  8. getattr(scipy.sparse, f'{T}_array') for T in sparray_types
  9. ]
  10. A = np.array([
  11. [0, 1, 2, 0],
  12. [2, 0, 0, 3],
  13. [1, 4, 0, 0]
  14. ])
  15. B = np.array([
  16. [0, 1],
  17. [2, 0]
  18. ])
  19. X = np.array([
  20. [1, 0, 0, 1],
  21. [2, 1, 2, 0],
  22. [0, 2, 1, 0],
  23. [0, 0, 1, 2]
  24. ], dtype=float)
  25. sparrays = [sparray(A) for sparray in sparray_classes]
  26. square_sparrays = [sparray(B) for sparray in sparray_classes]
  27. eig_sparrays = [sparray(X) for sparray in sparray_classes]
  28. parametrize_sparrays = pytest.mark.parametrize(
  29. "A", sparrays, ids=sparray_types
  30. )
  31. parametrize_square_sparrays = pytest.mark.parametrize(
  32. "B", square_sparrays, ids=sparray_types
  33. )
  34. parametrize_eig_sparrays = pytest.mark.parametrize(
  35. "X", eig_sparrays, ids=sparray_types
  36. )
  37. @parametrize_sparrays
  38. def test_sum(A):
  39. assert not isinstance(A.sum(axis=0), np.matrix), \
  40. "Expected array, got matrix"
  41. assert A.sum(axis=0).shape == (4,)
  42. assert A.sum(axis=1).shape == (3,)
  43. @parametrize_sparrays
  44. def test_mean(A):
  45. assert not isinstance(A.mean(axis=1), np.matrix), \
  46. "Expected array, got matrix"
  47. @parametrize_sparrays
  48. def test_min_max(A):
  49. # Some formats don't support min/max operations, so we skip those here.
  50. if hasattr(A, 'min'):
  51. assert not isinstance(A.min(axis=1), np.matrix), \
  52. "Expected array, got matrix"
  53. if hasattr(A, 'max'):
  54. assert not isinstance(A.max(axis=1), np.matrix), \
  55. "Expected array, got matrix"
  56. if hasattr(A, 'argmin'):
  57. assert not isinstance(A.argmin(axis=1), np.matrix), \
  58. "Expected array, got matrix"
  59. if hasattr(A, 'argmax'):
  60. assert not isinstance(A.argmax(axis=1), np.matrix), \
  61. "Expected array, got matrix"
  62. @parametrize_sparrays
  63. def test_todense(A):
  64. assert not isinstance(A.todense(), np.matrix), \
  65. "Expected array, got matrix"
  66. @parametrize_sparrays
  67. def test_indexing(A):
  68. if A.__class__.__name__[:3] in ('dia', 'coo', 'bsr'):
  69. return
  70. all_res = (
  71. A[1, :],
  72. A[:, 1],
  73. A[1, [1, 2]],
  74. A[[1, 2], 1],
  75. A[[0]],
  76. A[:, [1, 2]],
  77. A[[1, 2], :],
  78. A[1, [[1, 2]]],
  79. A[[[1, 2]], 1],
  80. )
  81. for res in all_res:
  82. assert isinstance(res, scipy.sparse.sparray), \
  83. f"Expected sparse array, got {res._class__.__name__}"
  84. @parametrize_sparrays
  85. def test_dense_addition(A):
  86. X = np.random.random(A.shape)
  87. assert not isinstance(A + X, np.matrix), "Expected array, got matrix"
  88. @parametrize_sparrays
  89. def test_sparse_addition(A):
  90. assert isinstance((A + A), scipy.sparse.sparray), "Expected array, got matrix"
  91. @parametrize_sparrays
  92. def test_elementwise_mul(A):
  93. assert np.all((A * A).todense() == A.power(2).todense())
  94. @parametrize_sparrays
  95. def test_elementwise_rmul(A):
  96. with pytest.raises(TypeError):
  97. None * A
  98. with pytest.raises(ValueError):
  99. np.eye(3) * scipy.sparse.csr_array(np.arange(6).reshape(2, 3))
  100. assert np.all((2 * A) == (A.todense() * 2))
  101. assert np.all((A.todense() * A) == (A.todense() ** 2))
  102. @parametrize_sparrays
  103. def test_matmul(A):
  104. assert np.all((A @ A.T).todense() == A.dot(A.T).todense())
  105. @parametrize_sparrays
  106. def test_power_operator(A):
  107. assert isinstance((A**2), scipy.sparse.sparray), "Expected array, got matrix"
  108. # https://github.com/scipy/scipy/issues/15948
  109. npt.assert_equal((A**2).todense(), (A.todense())**2)
  110. # power of zero is all ones (dense) so helpful msg exception
  111. with pytest.raises(NotImplementedError, match="zero power"):
  112. A**0
  113. @parametrize_sparrays
  114. def test_sparse_divide(A):
  115. assert isinstance(A / A, np.ndarray)
  116. @parametrize_sparrays
  117. def test_sparse_dense_divide(A):
  118. with pytest.warns(RuntimeWarning):
  119. assert isinstance((A / A.todense()), scipy.sparse.sparray)
  120. @parametrize_sparrays
  121. def test_dense_divide(A):
  122. assert isinstance((A / 2), scipy.sparse.sparray), "Expected array, got matrix"
  123. @parametrize_sparrays
  124. def test_no_A_attr(A):
  125. with pytest.raises(AttributeError):
  126. A.A
  127. @parametrize_sparrays
  128. def test_no_H_attr(A):
  129. with pytest.raises(AttributeError):
  130. A.H
  131. @parametrize_sparrays
  132. def test_getrow_getcol(A):
  133. assert isinstance(A._getcol(0), scipy.sparse.sparray)
  134. assert isinstance(A._getrow(0), scipy.sparse.sparray)
  135. # -- linalg --
  136. @parametrize_sparrays
  137. def test_as_linearoperator(A):
  138. L = spla.aslinearoperator(A)
  139. npt.assert_allclose(L * [1, 2, 3, 4], A @ [1, 2, 3, 4])
  140. @parametrize_square_sparrays
  141. def test_inv(B):
  142. if B.__class__.__name__[:3] != 'csc':
  143. return
  144. C = spla.inv(B)
  145. assert isinstance(C, scipy.sparse.sparray)
  146. npt.assert_allclose(C.todense(), np.linalg.inv(B.todense()))
  147. @parametrize_square_sparrays
  148. def test_expm(B):
  149. if B.__class__.__name__[:3] != 'csc':
  150. return
  151. Bmat = scipy.sparse.csc_matrix(B)
  152. C = spla.expm(B)
  153. assert isinstance(C, scipy.sparse.sparray)
  154. npt.assert_allclose(
  155. C.todense(),
  156. spla.expm(Bmat).todense()
  157. )
  158. @parametrize_square_sparrays
  159. def test_expm_multiply(B):
  160. if B.__class__.__name__[:3] != 'csc':
  161. return
  162. npt.assert_allclose(
  163. spla.expm_multiply(B, np.array([1, 2])),
  164. spla.expm(B) @ [1, 2]
  165. )
  166. @parametrize_sparrays
  167. def test_norm(A):
  168. C = spla.norm(A)
  169. npt.assert_allclose(C, np.linalg.norm(A.todense()))
  170. @parametrize_square_sparrays
  171. def test_onenormest(B):
  172. C = spla.onenormest(B)
  173. npt.assert_allclose(C, np.linalg.norm(B.todense(), 1))
  174. @parametrize_square_sparrays
  175. def test_spsolve(B):
  176. if B.__class__.__name__[:3] not in ('csc', 'csr'):
  177. return
  178. npt.assert_allclose(
  179. spla.spsolve(B, [1, 2]),
  180. np.linalg.solve(B.todense(), [1, 2])
  181. )
  182. @pytest.mark.parametrize("fmt",["csr","csc"])
  183. def test_spsolve_triangular(fmt):
  184. arr = [
  185. [1, 0, 0, 0],
  186. [2, 1, 0, 0],
  187. [3, 2, 1, 0],
  188. [4, 3, 2, 1],
  189. ]
  190. if fmt == "csr":
  191. X = scipy.sparse.csr_array(arr)
  192. else:
  193. X = scipy.sparse.csc_array(arr)
  194. spla.spsolve_triangular(X, [1, 2, 3, 4])
  195. @parametrize_square_sparrays
  196. def test_factorized(B):
  197. if B.__class__.__name__[:3] != 'csc':
  198. return
  199. LU = spla.factorized(B)
  200. npt.assert_allclose(
  201. LU(np.array([1, 2])),
  202. np.linalg.solve(B.todense(), [1, 2])
  203. )
  204. @parametrize_square_sparrays
  205. @pytest.mark.parametrize(
  206. "solver",
  207. ["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres", "minres", "qmr",
  208. "gcrotmk", "tfqmr"]
  209. )
  210. def test_solvers(B, solver):
  211. if solver == "minres":
  212. kwargs = {}
  213. else:
  214. kwargs = {'atol': 1e-5}
  215. x, info = getattr(spla, solver)(B, np.array([1, 2]), **kwargs)
  216. assert info >= 0 # no errors, even if perhaps did not converge fully
  217. npt.assert_allclose(x, [1, 1], atol=1e-1)
  218. @parametrize_sparrays
  219. @pytest.mark.parametrize(
  220. "solver",
  221. ["lsqr", "lsmr"]
  222. )
  223. def test_lstsqr(A, solver):
  224. x, *_ = getattr(spla, solver)(A, [1, 2, 3])
  225. npt.assert_allclose(A @ x, [1, 2, 3])
  226. @parametrize_eig_sparrays
  227. def test_eigs(X):
  228. e, v = spla.eigs(X, k=1)
  229. npt.assert_allclose(
  230. X @ v,
  231. e[0] * v
  232. )
  233. @parametrize_eig_sparrays
  234. def test_eigsh(X):
  235. X = X + X.T
  236. e, v = spla.eigsh(X, k=1)
  237. npt.assert_allclose(
  238. X @ v,
  239. e[0] * v
  240. )
  241. @parametrize_eig_sparrays
  242. def test_svds(X):
  243. u, s, vh = spla.svds(X, k=3)
  244. u2, s2, vh2 = np.linalg.svd(X.todense())
  245. s = np.sort(s)
  246. s2 = np.sort(s2[:3])
  247. npt.assert_allclose(s, s2, atol=1e-3)
  248. def test_splu():
  249. X = scipy.sparse.csc_array([
  250. [1, 0, 0, 0],
  251. [2, 1, 0, 0],
  252. [3, 2, 1, 0],
  253. [4, 3, 2, 1],
  254. ])
  255. LU = spla.splu(X)
  256. npt.assert_allclose(
  257. LU.solve(np.array([1, 2, 3, 4])),
  258. np.asarray([1, 0, 0, 0], dtype=np.float64),
  259. rtol=1e-14, atol=3e-16
  260. )
  261. def test_spilu():
  262. X = scipy.sparse.csc_array([
  263. [1, 0, 0, 0],
  264. [2, 1, 0, 0],
  265. [3, 2, 1, 0],
  266. [4, 3, 2, 1],
  267. ])
  268. LU = spla.spilu(X)
  269. npt.assert_allclose(
  270. LU.solve(np.array([1, 2, 3, 4])),
  271. np.asarray([1, 0, 0, 0], dtype=np.float64),
  272. rtol=1e-14, atol=3e-16
  273. )
  274. @pytest.mark.parametrize(
  275. "cls,indices_attrs",
  276. [
  277. (
  278. scipy.sparse.csr_array,
  279. ["indices", "indptr"],
  280. ),
  281. (
  282. scipy.sparse.csc_array,
  283. ["indices", "indptr"],
  284. ),
  285. (
  286. scipy.sparse.coo_array,
  287. ["row", "col"],
  288. ),
  289. ]
  290. )
  291. @pytest.mark.parametrize("expected_dtype", [np.int64, np.int32])
  292. def test_index_dtype_compressed(cls, indices_attrs, expected_dtype):
  293. input_array = scipy.sparse.coo_array(np.arange(9).reshape(3, 3))
  294. coo_tuple = (
  295. input_array.data,
  296. (
  297. input_array.row.astype(expected_dtype),
  298. input_array.col.astype(expected_dtype),
  299. )
  300. )
  301. result = cls(coo_tuple)
  302. for attr in indices_attrs:
  303. assert getattr(result, attr).dtype == expected_dtype
  304. result = cls(coo_tuple, shape=(3, 3))
  305. for attr in indices_attrs:
  306. assert getattr(result, attr).dtype == expected_dtype
  307. if issubclass(cls, scipy.sparse._compressed._cs_matrix):
  308. input_array_csr = input_array.tocsr()
  309. csr_tuple = (
  310. input_array_csr.data,
  311. input_array_csr.indices.astype(expected_dtype),
  312. input_array_csr.indptr.astype(expected_dtype),
  313. )
  314. result = cls(csr_tuple)
  315. for attr in indices_attrs:
  316. assert getattr(result, attr).dtype == expected_dtype
  317. result = cls(csr_tuple, shape=(3, 3))
  318. for attr in indices_attrs:
  319. assert getattr(result, attr).dtype == expected_dtype
  320. def test_default_is_matrix_diags():
  321. m = scipy.sparse.diags([0.0, 1.0, 2.0])
  322. assert not isinstance(m, scipy.sparse.sparray)
  323. def test_default_is_matrix_eye():
  324. m = scipy.sparse.eye(3)
  325. assert not isinstance(m, scipy.sparse.sparray)
  326. def test_default_is_matrix_spdiags():
  327. m = scipy.sparse.spdiags([1.0, 2.0, 3.0], 0, 3, 3)
  328. assert not isinstance(m, scipy.sparse.sparray)
  329. def test_default_is_matrix_identity():
  330. m = scipy.sparse.identity(3)
  331. assert not isinstance(m, scipy.sparse.sparray)
  332. def test_default_is_matrix_kron_dense():
  333. m = scipy.sparse.kron(
  334. np.array([[1, 2], [3, 4]]), np.array([[4, 3], [2, 1]])
  335. )
  336. assert not isinstance(m, scipy.sparse.sparray)
  337. def test_default_is_matrix_kron_sparse():
  338. m = scipy.sparse.kron(
  339. np.array([[1, 2], [3, 4]]), np.array([[1, 0], [0, 0]])
  340. )
  341. assert not isinstance(m, scipy.sparse.sparray)
  342. def test_default_is_matrix_kronsum():
  343. m = scipy.sparse.kronsum(
  344. np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])
  345. )
  346. assert not isinstance(m, scipy.sparse.sparray)
  347. def test_default_is_matrix_random():
  348. m = scipy.sparse.random(3, 3)
  349. assert not isinstance(m, scipy.sparse.sparray)
  350. def test_default_is_matrix_rand():
  351. m = scipy.sparse.rand(3, 3)
  352. assert not isinstance(m, scipy.sparse.sparray)
  353. @pytest.mark.parametrize("fn", (scipy.sparse.hstack, scipy.sparse.vstack))
  354. def test_default_is_matrix_stacks(fn):
  355. """Same idea as `test_default_construction_fn_matrices`, but for the
  356. stacking creation functions."""
  357. A = scipy.sparse.coo_matrix(np.eye(2))
  358. B = scipy.sparse.coo_matrix([[0, 1], [1, 0]])
  359. m = fn([A, B])
  360. assert not isinstance(m, scipy.sparse.sparray)
  361. def test_blocks_default_construction_fn_matrices():
  362. """Same idea as `test_default_construction_fn_matrices`, but for the block
  363. creation function"""
  364. A = scipy.sparse.coo_matrix(np.eye(2))
  365. B = scipy.sparse.coo_matrix([[2], [0]])
  366. C = scipy.sparse.coo_matrix([[3]])
  367. # block diag
  368. m = scipy.sparse.block_diag((A, B, C))
  369. assert not isinstance(m, scipy.sparse.sparray)
  370. # bmat
  371. m = scipy.sparse.bmat([[A, None], [None, C]])
  372. assert not isinstance(m, scipy.sparse.sparray)
  373. def test_format_property():
  374. for fmt in sparray_types:
  375. arr_cls = getattr(scipy.sparse, f"{fmt}_array")
  376. M = arr_cls([[1, 2]])
  377. assert M.format == fmt
  378. assert M._format == fmt
  379. with pytest.raises(AttributeError):
  380. M.format = "qqq"
  381. def test_issparse():
  382. m = scipy.sparse.eye(3)
  383. a = scipy.sparse.csr_array(m)
  384. assert not isinstance(m, scipy.sparse.sparray)
  385. assert isinstance(a, scipy.sparse.sparray)
  386. # Both sparse arrays and sparse matrices should be sparse
  387. assert scipy.sparse.issparse(a)
  388. assert scipy.sparse.issparse(m)
  389. # ndarray and array_likes are not sparse
  390. assert not scipy.sparse.issparse(a.todense())
  391. assert not scipy.sparse.issparse(m.todense())
  392. def test_isspmatrix():
  393. m = scipy.sparse.eye(3)
  394. a = scipy.sparse.csr_array(m)
  395. assert not isinstance(m, scipy.sparse.sparray)
  396. assert isinstance(a, scipy.sparse.sparray)
  397. # Should only be true for sparse matrices, not sparse arrays
  398. assert not scipy.sparse.isspmatrix(a)
  399. assert scipy.sparse.isspmatrix(m)
  400. # ndarray and array_likes are not sparse
  401. assert not scipy.sparse.isspmatrix(a.todense())
  402. assert not scipy.sparse.isspmatrix(m.todense())
  403. @pytest.mark.parametrize(
  404. ("fmt", "fn"),
  405. (
  406. ("bsr", scipy.sparse.isspmatrix_bsr),
  407. ("coo", scipy.sparse.isspmatrix_coo),
  408. ("csc", scipy.sparse.isspmatrix_csc),
  409. ("csr", scipy.sparse.isspmatrix_csr),
  410. ("dia", scipy.sparse.isspmatrix_dia),
  411. ("dok", scipy.sparse.isspmatrix_dok),
  412. ("lil", scipy.sparse.isspmatrix_lil),
  413. ),
  414. )
  415. def test_isspmatrix_format(fmt, fn):
  416. m = scipy.sparse.eye(3, format=fmt)
  417. a = scipy.sparse.csr_array(m).asformat(fmt)
  418. assert not isinstance(m, scipy.sparse.sparray)
  419. assert isinstance(a, scipy.sparse.sparray)
  420. # Should only be true for sparse matrices, not sparse arrays
  421. assert not fn(a)
  422. assert fn(m)
  423. # ndarray and array_likes are not sparse
  424. assert not fn(a.todense())
  425. assert not fn(m.todense())