339 lines
12 KiB
Python
339 lines
12 KiB
Python
"""Test of 1D arithmetic operations"""
|
|
|
|
import pytest
|
|
|
|
import numpy as np
|
|
from numpy.testing import assert_equal, assert_allclose
|
|
|
|
from scipy.sparse import coo_array, csr_array
|
|
from scipy.sparse._sputils import isscalarlike
|
|
|
|
|
|
spcreators = [coo_array, csr_array]
|
|
math_dtypes = [np.int64, np.float64, np.complex128]
|
|
|
|
|
|
def toarray(a):
|
|
if isinstance(a, np.ndarray) or isscalarlike(a):
|
|
return a
|
|
return a.toarray()
|
|
|
|
@pytest.fixture
|
|
def dat1d():
|
|
return np.array([3, 0, 1, 0], 'd')
|
|
|
|
|
|
@pytest.fixture
|
|
def datsp_math_dtypes(dat1d):
|
|
dat_dtypes = {dtype: dat1d.astype(dtype) for dtype in math_dtypes}
|
|
return {
|
|
sp: [(dtype, dat, sp(dat)) for dtype, dat in dat_dtypes.items()]
|
|
for sp in spcreators
|
|
}
|
|
|
|
|
|
@pytest.mark.parametrize("spcreator", spcreators)
|
|
class TestArithmetic1D:
|
|
def test_empty_arithmetic(self, spcreator):
|
|
shape = (5,)
|
|
for mytype in [
|
|
np.dtype('int32'),
|
|
np.dtype('float32'),
|
|
np.dtype('float64'),
|
|
np.dtype('complex64'),
|
|
np.dtype('complex128'),
|
|
]:
|
|
a = spcreator(shape, dtype=mytype)
|
|
b = a + a
|
|
c = 2 * a
|
|
assert isinstance(a @ a.tocsr(), np.ndarray)
|
|
assert isinstance(a @ a.tocoo(), np.ndarray)
|
|
for m in [a, b, c]:
|
|
assert m @ m == a.toarray() @ a.toarray()
|
|
assert m.dtype == mytype
|
|
assert toarray(m).dtype == mytype
|
|
|
|
def test_abs(self, spcreator):
|
|
A = np.array([-1, 0, 17, 0, -5, 0, 1, -4, 0, 0, 0, 0], 'd')
|
|
assert_equal(abs(A), abs(spcreator(A)).toarray())
|
|
|
|
def test_round(self, spcreator):
|
|
A = np.array([-1.35, 0.56, 17.25, -5.98], 'd')
|
|
Asp = spcreator(A)
|
|
assert_equal(np.around(A, decimals=1), round(Asp, ndigits=1).toarray())
|
|
|
|
def test_elementwise_power(self, spcreator):
|
|
A = np.array([-4, -3, -2, -1, 0, 1, 2, 3, 4], 'd')
|
|
Asp = spcreator(A)
|
|
assert_equal(np.power(A, 2), Asp.power(2).toarray())
|
|
|
|
# element-wise power function needs a scalar power
|
|
with pytest.raises(NotImplementedError, match='input is not scalar'):
|
|
spcreator(A).power(A)
|
|
|
|
def test_real(self, spcreator):
|
|
D = np.array([1 + 3j, 2 - 4j])
|
|
A = spcreator(D)
|
|
assert_equal(A.real.toarray(), D.real)
|
|
|
|
def test_imag(self, spcreator):
|
|
D = np.array([1 + 3j, 2 - 4j])
|
|
A = spcreator(D)
|
|
assert_equal(A.imag.toarray(), D.imag)
|
|
|
|
def test_mul_scalar(self, spcreator, datsp_math_dtypes):
|
|
for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
|
|
assert_equal(dat * 2, (datsp * 2).toarray())
|
|
assert_equal(dat * 17.3, (datsp * 17.3).toarray())
|
|
|
|
def test_rmul_scalar(self, spcreator, datsp_math_dtypes):
|
|
for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
|
|
assert_equal(2 * dat, (2 * datsp).toarray())
|
|
assert_equal(17.3 * dat, (17.3 * datsp).toarray())
|
|
|
|
def test_sub(self, spcreator, datsp_math_dtypes):
|
|
for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
|
|
if dtype == np.dtype('bool'):
|
|
# boolean array subtraction deprecated in 1.9.0
|
|
continue
|
|
|
|
assert_equal((datsp - datsp).toarray(), np.zeros(4))
|
|
assert_equal((datsp - 0).toarray(), dat)
|
|
|
|
A = spcreator([1, -4, 0, 2], dtype='d')
|
|
assert_equal((datsp - A).toarray(), dat - A.toarray())
|
|
assert_equal((A - datsp).toarray(), A.toarray() - dat)
|
|
|
|
# test broadcasting
|
|
assert_equal(datsp.toarray() - dat[0], dat - dat[0])
|
|
|
|
def test_add0(self, spcreator, datsp_math_dtypes):
|
|
for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
|
|
# Adding 0 to a sparse matrix
|
|
assert_equal((datsp + 0).toarray(), dat)
|
|
# use sum (which takes 0 as a starting value)
|
|
sumS = sum([k * datsp for k in range(1, 3)])
|
|
sumD = sum([k * dat for k in range(1, 3)])
|
|
assert_allclose(sumS.toarray(), sumD)
|
|
|
|
def test_elementwise_multiply(self, spcreator):
|
|
# real/real
|
|
A = np.array([4, 0, 9])
|
|
B = np.array([0, 7, -1])
|
|
Asp = spcreator(A)
|
|
Bsp = spcreator(B)
|
|
assert_allclose(Asp.multiply(Bsp).toarray(), A * B) # sparse/sparse
|
|
assert_allclose(Asp.multiply(B).toarray(), A * B) # sparse/dense
|
|
|
|
# complex/complex
|
|
C = np.array([1 - 2j, 0 + 5j, -1 + 0j])
|
|
D = np.array([5 + 2j, 7 - 3j, -2 + 1j])
|
|
Csp = spcreator(C)
|
|
Dsp = spcreator(D)
|
|
assert_allclose(Csp.multiply(Dsp).toarray(), C * D) # sparse/sparse
|
|
assert_allclose(Csp.multiply(D).toarray(), C * D) # sparse/dense
|
|
|
|
# real/complex
|
|
assert_allclose(Asp.multiply(Dsp).toarray(), A * D) # sparse/sparse
|
|
assert_allclose(Asp.multiply(D).toarray(), A * D) # sparse/dense
|
|
|
|
def test_elementwise_multiply_broadcast(self, spcreator):
|
|
A = np.array([4])
|
|
B = np.array([[-9]])
|
|
C = np.array([1, -1, 0])
|
|
D = np.array([[7, 9, -9]])
|
|
E = np.array([[3], [2], [1]])
|
|
F = np.array([[8, 6, 3], [-4, 3, 2], [6, 6, 6]])
|
|
G = [1, 2, 3]
|
|
H = np.ones((3, 4))
|
|
J = H.T
|
|
K = np.array([[0]])
|
|
L = np.array([[[1, 2], [0, 1]]])
|
|
|
|
# Some arrays can't be cast as spmatrices (A, C, L) so leave
|
|
# them out.
|
|
Asp = spcreator(A)
|
|
Csp = spcreator(C)
|
|
Gsp = spcreator(G)
|
|
# 2d arrays
|
|
Bsp = spcreator(B)
|
|
Dsp = spcreator(D)
|
|
Esp = spcreator(E)
|
|
Fsp = spcreator(F)
|
|
Hsp = spcreator(H)
|
|
Hspp = spcreator(H[0, None])
|
|
Jsp = spcreator(J)
|
|
Jspp = spcreator(J[:, 0, None])
|
|
Ksp = spcreator(K)
|
|
|
|
matrices = [A, B, C, D, E, F, G, H, J, K, L]
|
|
spmatrices = [Asp, Bsp, Csp, Dsp, Esp, Fsp, Gsp, Hsp, Hspp, Jsp, Jspp, Ksp]
|
|
sp1dmatrices = [Asp, Csp, Gsp]
|
|
|
|
# sparse/sparse
|
|
for i in sp1dmatrices:
|
|
for j in spmatrices:
|
|
try:
|
|
dense_mult = i.toarray() * j.toarray()
|
|
except ValueError:
|
|
with pytest.raises(ValueError, match='inconsistent shapes'):
|
|
i.multiply(j)
|
|
continue
|
|
sp_mult = i.multiply(j)
|
|
assert_allclose(sp_mult.toarray(), dense_mult)
|
|
|
|
# sparse/dense
|
|
for i in sp1dmatrices:
|
|
for j in matrices:
|
|
try:
|
|
dense_mult = i.toarray() * j
|
|
except TypeError:
|
|
continue
|
|
except ValueError:
|
|
matchme = 'broadcast together|inconsistent shapes'
|
|
with pytest.raises(ValueError, match=matchme):
|
|
i.multiply(j)
|
|
continue
|
|
sp_mult = i.multiply(j)
|
|
assert_allclose(toarray(sp_mult), dense_mult)
|
|
|
|
def test_elementwise_divide(self, spcreator, dat1d):
|
|
datsp = spcreator(dat1d)
|
|
expected = np.array([1, np.nan, 1, np.nan])
|
|
actual = datsp / datsp
|
|
# need assert_array_equal to handle nan values
|
|
np.testing.assert_array_equal(actual, expected)
|
|
|
|
denom = spcreator([1, 0, 0, 4], dtype='d')
|
|
expected = [3, np.nan, np.inf, 0]
|
|
np.testing.assert_array_equal(datsp / denom, expected)
|
|
|
|
# complex
|
|
A = np.array([1 - 2j, 0 + 5j, -1 + 0j])
|
|
B = np.array([5 + 2j, 7 - 3j, -2 + 1j])
|
|
Asp = spcreator(A)
|
|
Bsp = spcreator(B)
|
|
assert_allclose(Asp / Bsp, A / B)
|
|
|
|
# integer
|
|
A = np.array([1, 2, 3])
|
|
B = np.array([0, 1, 2])
|
|
Asp = spcreator(A)
|
|
Bsp = spcreator(B)
|
|
with np.errstate(divide='ignore'):
|
|
assert_equal(Asp / Bsp, A / B)
|
|
|
|
# mismatching sparsity patterns
|
|
A = np.array([0, 1])
|
|
B = np.array([1, 0])
|
|
Asp = spcreator(A)
|
|
Bsp = spcreator(B)
|
|
with np.errstate(divide='ignore', invalid='ignore'):
|
|
assert_equal(Asp / Bsp, A / B)
|
|
|
|
def test_pow(self, spcreator):
|
|
A = np.array([1, 0, 2, 0])
|
|
B = spcreator(A)
|
|
|
|
# unusual exponents
|
|
with pytest.raises(ValueError, match='negative integer powers'):
|
|
B**-1
|
|
with pytest.raises(NotImplementedError, match='zero power'):
|
|
B**0
|
|
|
|
for exponent in [1, 2, 3, 2.2]:
|
|
ret_sp = B**exponent
|
|
ret_np = A**exponent
|
|
assert_equal(ret_sp.toarray(), ret_np)
|
|
assert_equal(ret_sp.dtype, ret_np.dtype)
|
|
|
|
def test_dot_scalar(self, spcreator, dat1d):
|
|
A = spcreator(dat1d)
|
|
scalar = 10
|
|
actual = A.dot(scalar)
|
|
expected = A * scalar
|
|
|
|
assert_allclose(actual.toarray(), expected.toarray())
|
|
|
|
def test_matmul(self, spcreator):
|
|
Msp = spcreator([2, 0, 3.0])
|
|
B = spcreator(np.array([[0, 1], [1, 0], [0, 2]], 'd'))
|
|
col = np.array([[1, 2, 3]]).T
|
|
|
|
# check sparse @ dense 2d column
|
|
assert_allclose(Msp @ col, Msp.toarray() @ col)
|
|
|
|
# check sparse1d @ sparse2d, sparse1d @ dense2d, dense1d @ sparse2d
|
|
assert_allclose((Msp @ B).toarray(), (Msp @ B).toarray())
|
|
assert_allclose(Msp.toarray() @ B, (Msp @ B).toarray())
|
|
assert_allclose(Msp @ B.toarray(), (Msp @ B).toarray())
|
|
|
|
# check sparse1d @ dense1d, sparse1d @ sparse1d
|
|
V = np.array([0, 0, 1])
|
|
assert_allclose(Msp @ V, Msp.toarray() @ V)
|
|
|
|
Vsp = spcreator(V)
|
|
Msp_Vsp = Msp @ Vsp
|
|
assert isinstance(Msp_Vsp, np.ndarray)
|
|
assert Msp_Vsp.shape == ()
|
|
|
|
# output is 0-dim ndarray
|
|
assert_allclose(np.array(3), Msp_Vsp)
|
|
assert_allclose(np.array(3), Msp.toarray() @ Vsp)
|
|
assert_allclose(np.array(3), Msp @ Vsp.toarray())
|
|
assert_allclose(np.array(3), Msp.toarray() @ Vsp.toarray())
|
|
|
|
# check error on matrix-scalar
|
|
with pytest.raises(ValueError, match='Scalar operands are not allowed'):
|
|
Msp @ 1
|
|
with pytest.raises(ValueError, match='Scalar operands are not allowed'):
|
|
1 @ Msp
|
|
|
|
def test_sub_dense(self, spcreator, datsp_math_dtypes):
|
|
# subtracting a dense matrix to/from a sparse matrix
|
|
for dtype, dat, datsp in datsp_math_dtypes[spcreator]:
|
|
if dtype == np.dtype('bool'):
|
|
# boolean array subtraction deprecated in 1.9.0
|
|
continue
|
|
|
|
# Manually add to avoid upcasting from scalar
|
|
# multiplication.
|
|
sum1 = (dat + dat + dat) - datsp
|
|
assert_equal(sum1, dat + dat)
|
|
sum2 = (datsp + datsp + datsp) - dat
|
|
assert_equal(sum2, dat + dat)
|
|
|
|
def test_size_zero_matrix_arithmetic(self, spcreator):
|
|
# Test basic matrix arithmetic with shapes like 0, (1, 0), (0, 3), etc.
|
|
mat = np.array([])
|
|
a = mat.reshape(0)
|
|
d = mat.reshape((1, 0))
|
|
f = np.ones([5, 5])
|
|
|
|
asp = spcreator(a)
|
|
dsp = spcreator(d)
|
|
# bad shape for addition
|
|
with pytest.raises(ValueError, match='inconsistent shapes'):
|
|
asp.__add__(dsp)
|
|
|
|
# matrix product.
|
|
assert_equal(asp.dot(asp), np.dot(a, a))
|
|
|
|
# bad matrix products
|
|
with pytest.raises(ValueError, match='dimension mismatch'):
|
|
asp.dot(f)
|
|
|
|
# elemente-wise multiplication
|
|
assert_equal(asp.multiply(asp).toarray(), np.multiply(a, a))
|
|
|
|
assert_equal(asp.multiply(a).toarray(), np.multiply(a, a))
|
|
|
|
assert_equal(asp.multiply(6).toarray(), np.multiply(a, 6))
|
|
|
|
# bad element-wise multiplication
|
|
with pytest.raises(ValueError, match='inconsistent shapes'):
|
|
asp.multiply(f)
|
|
|
|
# Addition
|
|
assert_equal(asp.__add__(asp).toarray(), a.__add__(a))
|