test_common1d.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. """Test of 1D aspects of sparse array classes"""
  2. import warnings
  3. import pytest
  4. import numpy as np
  5. from numpy.testing import assert_equal, assert_allclose
  6. from numpy.exceptions import ComplexWarning
  7. from scipy.sparse import (
  8. bsr_array, csc_array, dia_array, lil_array,
  9. coo_array, csr_array, dok_array,
  10. )
  11. from scipy.sparse._sputils import supported_dtypes, matrix
  12. spcreators = [coo_array, csr_array, dok_array]
  13. math_dtypes = [np.int64, np.float64, np.complex128]
  14. @pytest.fixture
  15. def dat1d():
  16. return np.array([3, 0, 1, 0], 'd')
  17. @pytest.fixture
  18. def datsp_math_dtypes(dat1d):
  19. dat_dtypes = {dtype: dat1d.astype(dtype) for dtype in math_dtypes}
  20. return {
  21. spcreator: [(dtype, dat, spcreator(dat)) for dtype, dat in dat_dtypes.items()]
  22. for spcreator in spcreators
  23. }
  24. # Test init with 1D dense input
  25. # sparrays which do not plan to support 1D
  26. @pytest.mark.parametrize("spcreator", [bsr_array, csc_array, dia_array, lil_array])
  27. def test_no_1d_support_in_init(spcreator):
  28. with pytest.raises(ValueError, match="arrays don't support 1D input"):
  29. spcreator([0, 1, 2, 3])
  30. # Test init with nD dense input
  31. # sparrays which do not yet support nD
  32. @pytest.mark.parametrize(
  33. "spcreator", [csr_array, dok_array, bsr_array, csc_array, dia_array, lil_array]
  34. )
  35. def test_no_nd_support_in_init(spcreator):
  36. with pytest.raises(ValueError, match="arrays don't.*support 3D"):
  37. spcreator(np.ones((3, 2, 4)))
  38. # Main tests class
  39. @pytest.mark.parametrize("spcreator", spcreators)
  40. class TestCommon1D:
  41. """test common functionality shared by 1D sparse formats"""
  42. def test_create_empty(self, spcreator):
  43. assert_equal(spcreator((3,)).toarray(), np.zeros(3))
  44. assert_equal(spcreator((3,)).nnz, 0)
  45. assert_equal(spcreator((3,)).count_nonzero(), 0)
  46. def test_invalid_shapes(self, spcreator):
  47. with pytest.raises(ValueError, match='elements cannot be negative'):
  48. spcreator((-3,))
  49. def test_repr(self, spcreator, dat1d):
  50. repr(spcreator(dat1d))
  51. def test_str(self, spcreator, dat1d):
  52. str(spcreator(dat1d))
  53. def test_neg(self, spcreator):
  54. A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd')
  55. assert_equal(-A, (-spcreator(A)).toarray())
  56. def test_1d_supported_init(self, spcreator):
  57. A = spcreator([0, 1, 2, 3])
  58. assert A.ndim == 1
  59. def test_reshape_1d_tofrom_row_or_column(self, spcreator):
  60. # add a dimension 1d->2d
  61. x = spcreator([1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5])
  62. y = x.reshape(1, 12)
  63. desired = [[1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5]]
  64. assert_equal(y.toarray(), desired)
  65. # remove a size-1 dimension 2d->1d
  66. x = spcreator(desired)
  67. y = x.reshape(12)
  68. assert_equal(y.toarray(), desired[0])
  69. y2 = x.reshape((12,))
  70. assert y.shape == y2.shape
  71. # make a 2d column into 1d. 2d->1d
  72. y = x.T.reshape(12)
  73. assert_equal(y.toarray(), desired[0])
  74. def test_reshape(self, spcreator):
  75. x = spcreator([1, 0, 7, 0, 0, 0, 0, -3, 0, 0, 0, 5])
  76. y = x.reshape((4, 3))
  77. desired = [[1, 0, 7], [0, 0, 0], [0, -3, 0], [0, 0, 5]]
  78. assert_equal(y.toarray(), desired)
  79. y = x.reshape((12,))
  80. assert y is x
  81. y = x.reshape(12)
  82. assert_equal(y.toarray(), x.toarray())
  83. def test_sum(self, spcreator):
  84. np.random.seed(1234)
  85. dat_1 = np.array([0, 1, 2, 3, -4, 5, -6, 7, 9])
  86. dat_2 = np.random.rand(5)
  87. dat_3 = np.array([])
  88. dat_4 = np.zeros((40,))
  89. arrays = [dat_1, dat_2, dat_3, dat_4]
  90. for dat in arrays:
  91. datsp = spcreator(dat)
  92. with np.errstate(over='ignore'):
  93. assert np.isscalar(datsp.sum())
  94. assert_allclose(dat.sum(), datsp.sum())
  95. assert_allclose(dat.sum(axis=None), datsp.sum(axis=None))
  96. assert_allclose(dat.sum(axis=0), datsp.sum(axis=0))
  97. assert_allclose(dat.sum(axis=-1), datsp.sum(axis=-1))
  98. # test `out` parameter
  99. datsp.sum(axis=0, out=np.zeros(()))
  100. def test_sum_invalid_params(self, spcreator):
  101. out = np.zeros((3,)) # wrong size for out
  102. dat = np.array([0, 1, 2])
  103. datsp = spcreator(dat)
  104. with pytest.raises(ValueError, match='axis out of range'):
  105. datsp.sum(axis=1)
  106. with pytest.raises(ValueError, match='axis out of range'):
  107. datsp.sum(axis=(0, 3))
  108. with pytest.raises(TypeError, match='axis must be an integer'):
  109. datsp.sum(axis=1.5)
  110. with pytest.raises(ValueError, match='output parameter.*wrong.*dimension'):
  111. datsp.sum(axis=0, out=out)
  112. def test_numpy_sum(self, spcreator):
  113. dat = np.array([0, 1, 2])
  114. datsp = spcreator(dat)
  115. dat_sum = np.sum(dat)
  116. datsp_sum = np.sum(datsp)
  117. assert_allclose(dat_sum, datsp_sum)
  118. def test_mean(self, spcreator):
  119. dat = np.array([0, 1, 2])
  120. datsp = spcreator(dat)
  121. assert_allclose(dat.mean(), datsp.mean())
  122. assert np.isscalar(datsp.mean(axis=None))
  123. assert_allclose(dat.mean(axis=None), datsp.mean(axis=None))
  124. assert_allclose(dat.mean(axis=0), datsp.mean(axis=0))
  125. assert_allclose(dat.mean(axis=-1), datsp.mean(axis=-1))
  126. with pytest.raises(ValueError, match='axis'):
  127. datsp.mean(axis=1)
  128. with pytest.raises(ValueError, match='axis'):
  129. datsp.mean(axis=-2)
  130. def test_mean_invalid_params(self, spcreator):
  131. out = np.asarray(np.zeros((1, 3)))
  132. dat = np.array([[0, 1, 2], [3, -4, 5], [-6, 7, 9]])
  133. datsp = spcreator(dat)
  134. with pytest.raises(ValueError, match='axis out of range'):
  135. datsp.mean(axis=3)
  136. with pytest.raises(ValueError, match='axis out of range'):
  137. datsp.mean(axis=(0, 3))
  138. with pytest.raises(TypeError, match='axis must be an integer'):
  139. datsp.mean(axis=1.5)
  140. with pytest.raises(ValueError, match='out.*not match shape'):
  141. datsp.mean(axis=1, out=out)
  142. def test_sum_dtype(self, spcreator):
  143. dat = np.array([0, 1, 2])
  144. datsp = spcreator(dat)
  145. for dtype in supported_dtypes:
  146. dat_sum = dat.sum(dtype=dtype)
  147. datsp_sum = datsp.sum(dtype=dtype)
  148. assert_allclose(dat_sum, datsp_sum)
  149. assert_equal(dat_sum.dtype, datsp_sum.dtype)
  150. def test_mean_dtype(self, spcreator):
  151. dat = np.array([0, 1, 2])
  152. datsp = spcreator(dat)
  153. for dtype in supported_dtypes:
  154. dat_mean = dat.mean(dtype=dtype)
  155. datsp_mean = datsp.mean(dtype=dtype)
  156. assert_allclose(dat_mean, datsp_mean)
  157. assert_equal(dat_mean.dtype, datsp_mean.dtype)
  158. def test_mean_out(self, spcreator):
  159. dat = np.array([0, 1, 2])
  160. datsp = spcreator(dat)
  161. dat_out = np.array(0)
  162. datsp_out = np.array(0)
  163. dat.mean(out=dat_out)
  164. datsp.mean(out=datsp_out)
  165. assert_allclose(dat_out, datsp_out)
  166. dat.mean(axis=0, out=dat_out)
  167. datsp.mean(axis=0, out=datsp_out)
  168. assert_allclose(dat_out, datsp_out)
  169. with pytest.raises(ValueError, match="output parameter.*dimension"):
  170. datsp.mean(out=np.array([0]))
  171. with pytest.raises(ValueError, match="output parameter.*dimension"):
  172. datsp.mean(out=np.array([[0]]))
  173. def test_numpy_mean(self, spcreator):
  174. dat = np.array([0, 1, 2])
  175. datsp = spcreator(dat)
  176. dat_mean = np.mean(dat)
  177. datsp_mean = np.mean(datsp)
  178. assert_allclose(dat_mean, datsp_mean)
  179. assert_equal(dat_mean.dtype, datsp_mean.dtype)
  180. def test_from_array(self, spcreator):
  181. with warnings.catch_warnings():
  182. warnings.simplefilter("ignore", ComplexWarning)
  183. A = np.array([2, 3, 4])
  184. assert_equal(spcreator(A).toarray(), A)
  185. A = np.array([1.0 + 3j, 0, -1])
  186. assert_equal(spcreator(A).toarray(), A)
  187. assert_equal(spcreator(A, dtype='int16').toarray(), A.astype('int16'))
  188. def test_from_list(self, spcreator):
  189. with warnings.catch_warnings():
  190. warnings.simplefilter("ignore", ComplexWarning)
  191. A = [2, 3, 4]
  192. assert_equal(spcreator(A).toarray(), A)
  193. A = [1.0 + 3j, 0, -1]
  194. assert_equal(spcreator(A).toarray(), np.array(A))
  195. assert_equal(
  196. spcreator(A, dtype='int16').toarray(), np.array(A).astype('int16')
  197. )
  198. def test_from_sparse(self, spcreator):
  199. with warnings.catch_warnings():
  200. warnings.simplefilter("ignore", ComplexWarning)
  201. D = np.array([1, 0, 0])
  202. S = coo_array(D)
  203. assert_equal(spcreator(S).toarray(), D)
  204. S = spcreator(D)
  205. assert_equal(spcreator(S).toarray(), D)
  206. D = np.array([1.0 + 3j, 0, -1])
  207. S = coo_array(D)
  208. assert_equal(spcreator(S).toarray(), D)
  209. assert_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16'))
  210. S = spcreator(D)
  211. assert_equal(spcreator(S).toarray(), D)
  212. assert_equal(spcreator(S, dtype='int16').toarray(), D.astype('int16'))
  213. def test_toarray(self, spcreator, dat1d):
  214. datsp = spcreator(dat1d)
  215. # Check C- or F-contiguous (default).
  216. chk = datsp.toarray()
  217. assert_equal(chk, dat1d)
  218. assert chk.flags.c_contiguous == chk.flags.f_contiguous
  219. # Check C-contiguous (with arg).
  220. chk = datsp.toarray(order='C')
  221. assert_equal(chk, dat1d)
  222. assert chk.flags.c_contiguous
  223. assert chk.flags.f_contiguous
  224. # Check F-contiguous (with arg).
  225. chk = datsp.toarray(order='F')
  226. assert_equal(chk, dat1d)
  227. assert chk.flags.c_contiguous
  228. assert chk.flags.f_contiguous
  229. # Check with output arg.
  230. out = np.zeros(datsp.shape, dtype=datsp.dtype)
  231. datsp.toarray(out=out)
  232. assert_equal(out, dat1d)
  233. # Check that things are fine when we don't initialize with zeros.
  234. out[...] = 1.0
  235. datsp.toarray(out=out)
  236. assert_equal(out, dat1d)
  237. # np.dot does not work with sparse matrices (unless scalars)
  238. # so this is testing whether dat1d matches datsp.toarray()
  239. a = np.array([1.0, 2.0, 3.0, 4.0])
  240. dense_dot_dense = np.dot(a, dat1d)
  241. check = np.dot(a, datsp.toarray())
  242. assert_equal(dense_dot_dense, check)
  243. b = np.array([1.0, 2.0, 3.0, 4.0])
  244. dense_dot_dense = np.dot(dat1d, b)
  245. check = np.dot(datsp.toarray(), b)
  246. assert_equal(dense_dot_dense, check)
  247. # Check bool data works.
  248. spbool = spcreator(dat1d, dtype=bool)
  249. arrbool = dat1d.astype(bool)
  250. assert_equal(spbool.toarray(), arrbool)
  251. def test_add(self, spcreator, datsp_math_dtypes):
  252. for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
  253. a = dat.copy()
  254. a[0] = 2.0
  255. b = datsp
  256. c = b + a
  257. assert_equal(c, b.toarray() + a)
  258. # test broadcasting
  259. # Note: cant add nonzero scalar to sparray. Can add len 1 array
  260. c = b + a[0:1]
  261. assert_equal(c, b.toarray() + a[0])
  262. def test_radd(self, spcreator, datsp_math_dtypes):
  263. for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
  264. a = dat.copy()
  265. a[0] = 2.0
  266. b = datsp
  267. c = a + b
  268. assert_equal(c, a + b.toarray())
  269. def test_rsub(self, spcreator, datsp_math_dtypes):
  270. for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
  271. if dtype == np.dtype('bool'):
  272. # boolean array subtraction deprecated in 1.9.0
  273. continue
  274. assert_equal((dat - datsp), [0, 0, 0, 0])
  275. assert_equal((datsp - dat), [0, 0, 0, 0])
  276. assert_equal((0 - datsp).toarray(), -dat)
  277. A = spcreator([1, -4, 0, 2], dtype='d')
  278. assert_equal((dat - A), dat - A.toarray())
  279. assert_equal((A - dat), A.toarray() - dat)
  280. assert_equal(A.toarray() - datsp, A.toarray() - dat)
  281. assert_equal(datsp - A.toarray(), dat - A.toarray())
  282. # test broadcasting
  283. assert_equal(dat[:1] - datsp, dat[:1] - dat)
  284. def test_matmul_basic(self, spcreator):
  285. A = np.array([[2, 0, 3.0], [0, 0, 0], [0, 1, 2]])
  286. v = np.array([1, 0, 3])
  287. Asp = spcreator(A)
  288. vsp = spcreator(v)
  289. # sparse result when both args are sparse and result not scalar
  290. assert_equal((Asp @ vsp).toarray(), A @ v)
  291. assert_equal(A @ vsp, A @ v)
  292. assert_equal(Asp @ v, A @ v)
  293. assert_equal((vsp @ Asp).toarray(), v @ A)
  294. assert_equal(vsp @ A, v @ A)
  295. assert_equal(v @ Asp, v @ A)
  296. assert_equal(vsp @ vsp, v @ v)
  297. assert_equal(v @ vsp, v @ v)
  298. assert_equal(vsp @ v, v @ v)
  299. assert_equal((Asp @ Asp).toarray(), A @ A)
  300. assert_equal(A @ Asp, A @ A)
  301. assert_equal(Asp @ A, A @ A)
  302. def test_matvec(self, spcreator):
  303. A = np.array([2, 0, 3.0])
  304. Asp = spcreator(A)
  305. col = np.array([[1, 2, 3]]).T
  306. assert_allclose(Asp @ col, Asp.toarray() @ col)
  307. assert (A @ np.array([1, 2, 3])).shape == ()
  308. assert Asp @ np.array([1, 2, 3]) == 11
  309. assert (Asp @ np.array([1, 2, 3])).shape == ()
  310. assert (Asp @ np.array([[1], [2], [3]])).shape == (1,)
  311. # check result type
  312. assert isinstance(Asp @ matrix([[1, 2, 3]]).T, np.ndarray)
  313. # ensure exception is raised for improper dimensions
  314. bad_vecs = [np.array([1, 2]), np.array([1, 2, 3, 4]), np.array([[1], [2]])]
  315. for x in bad_vecs:
  316. with pytest.raises(ValueError, match='dimension mismatch'):
  317. Asp @ x
  318. # The current relationship between sparse matrix products and array
  319. # products is as follows:
  320. dot_result = np.dot(Asp.toarray(), [1, 2, 3])
  321. assert_allclose(Asp @ np.array([1, 2, 3]), dot_result)
  322. assert_allclose(Asp @ [[1], [2], [3]], dot_result.T)
  323. # Note that the result of Asp @ x is dense if x has a singleton dimension.
  324. def test_rmatvec(self, spcreator, dat1d):
  325. M = spcreator(dat1d)
  326. assert_allclose([1, 2, 3, 4] @ M, np.dot([1, 2, 3, 4], M.toarray()))
  327. row = np.array([[1, 2, 3, 4]])
  328. assert_allclose(row @ M, row @ M.toarray())
  329. def test_transpose(self, spcreator, dat1d):
  330. for A in [dat1d, np.array([])]:
  331. B = spcreator(A)
  332. assert_equal(B.toarray(), A)
  333. assert_equal(B.transpose().toarray(), A)
  334. assert_equal(B.dtype, A.dtype)
  335. def test_add_dense_to_sparse(self, spcreator, datsp_math_dtypes):
  336. for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
  337. sum1 = dat + datsp
  338. assert_equal(sum1, dat + dat)
  339. sum2 = datsp + dat
  340. assert_equal(sum2, dat + dat)
  341. def test_iterator(self, spcreator):
  342. # test that __iter__ is compatible with NumPy
  343. B = np.arange(5)
  344. A = spcreator(B)
  345. if A.format not in ['coo', 'dia', 'bsr']:
  346. for x, y in zip(A, B):
  347. assert_equal(x, y)
  348. def test_resize(self, spcreator):
  349. # resize(shape) resizes the matrix in-place
  350. D = np.array([1, 0, 3, 4])
  351. S = spcreator(D)
  352. assert S.resize((3,)) is None
  353. assert_equal(S.toarray(), [1, 0, 3])
  354. S.resize((5,))
  355. assert_equal(S.toarray(), [1, 0, 3, 0, 0])