562 lines
14 KiB
Python
562 lines
14 KiB
Python
|
import pytest
|
||
|
import numpy as np
|
||
|
import numpy.testing as npt
|
||
|
import scipy.sparse
|
||
|
import scipy.sparse.linalg as spla
|
||
|
from scipy._lib._util import VisibleDeprecationWarning
|
||
|
|
||
|
|
||
|
sparray_types = ('bsr', 'coo', 'csc', 'csr', 'dia', 'dok', 'lil')
|
||
|
|
||
|
sparray_classes = [
|
||
|
getattr(scipy.sparse, f'{T}_array') for T in sparray_types
|
||
|
]
|
||
|
|
||
|
A = np.array([
|
||
|
[0, 1, 2, 0],
|
||
|
[2, 0, 0, 3],
|
||
|
[1, 4, 0, 0]
|
||
|
])
|
||
|
|
||
|
B = np.array([
|
||
|
[0, 1],
|
||
|
[2, 0]
|
||
|
])
|
||
|
|
||
|
X = np.array([
|
||
|
[1, 0, 0, 1],
|
||
|
[2, 1, 2, 0],
|
||
|
[0, 2, 1, 0],
|
||
|
[0, 0, 1, 2]
|
||
|
], dtype=float)
|
||
|
|
||
|
|
||
|
sparrays = [sparray(A) for sparray in sparray_classes]
|
||
|
square_sparrays = [sparray(B) for sparray in sparray_classes]
|
||
|
eig_sparrays = [sparray(X) for sparray in sparray_classes]
|
||
|
|
||
|
parametrize_sparrays = pytest.mark.parametrize(
|
||
|
"A", sparrays, ids=sparray_types
|
||
|
)
|
||
|
parametrize_square_sparrays = pytest.mark.parametrize(
|
||
|
"B", square_sparrays, ids=sparray_types
|
||
|
)
|
||
|
parametrize_eig_sparrays = pytest.mark.parametrize(
|
||
|
"X", eig_sparrays, ids=sparray_types
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_sum(A):
|
||
|
assert not isinstance(A.sum(axis=0), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
assert A.sum(axis=0).shape == (4,)
|
||
|
assert A.sum(axis=1).shape == (3,)
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_mean(A):
|
||
|
assert not isinstance(A.mean(axis=1), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_min_max(A):
|
||
|
# Some formats don't support min/max operations, so we skip those here.
|
||
|
if hasattr(A, 'min'):
|
||
|
assert not isinstance(A.min(axis=1), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
if hasattr(A, 'max'):
|
||
|
assert not isinstance(A.max(axis=1), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
if hasattr(A, 'argmin'):
|
||
|
assert not isinstance(A.argmin(axis=1), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
if hasattr(A, 'argmax'):
|
||
|
assert not isinstance(A.argmax(axis=1), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_todense(A):
|
||
|
assert not isinstance(A.todense(), np.matrix), \
|
||
|
"Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_indexing(A):
|
||
|
if A.__class__.__name__[:3] in ('dia', 'coo', 'bsr'):
|
||
|
return
|
||
|
|
||
|
with pytest.raises(NotImplementedError):
|
||
|
A[1, :]
|
||
|
|
||
|
with pytest.raises(NotImplementedError):
|
||
|
A[:, 1]
|
||
|
|
||
|
with pytest.raises(NotImplementedError):
|
||
|
A[1, [1, 2]]
|
||
|
|
||
|
with pytest.raises(NotImplementedError):
|
||
|
A[[1, 2], 1]
|
||
|
|
||
|
assert isinstance(A[[0]], scipy.sparse.sparray), \
|
||
|
"Expected sparse array, got sparse matrix"
|
||
|
assert isinstance(A[1, [[1, 2]]], scipy.sparse.sparray), \
|
||
|
"Expected ndarray, got sparse array"
|
||
|
assert isinstance(A[[[1, 2]], 1], scipy.sparse.sparray), \
|
||
|
"Expected ndarray, got sparse array"
|
||
|
assert isinstance(A[:, [1, 2]], scipy.sparse.sparray), \
|
||
|
"Expected sparse array, got something else"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_dense_addition(A):
|
||
|
X = np.random.random(A.shape)
|
||
|
assert not isinstance(A + X, np.matrix), "Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_sparse_addition(A):
|
||
|
assert isinstance((A + A), scipy.sparse.sparray), "Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_elementwise_mul(A):
|
||
|
assert np.all((A * A).todense() == A.power(2).todense())
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_elementwise_rmul(A):
|
||
|
with pytest.raises(TypeError):
|
||
|
None * A
|
||
|
|
||
|
with pytest.raises(ValueError):
|
||
|
np.eye(3) * scipy.sparse.csr_array(np.arange(6).reshape(2, 3))
|
||
|
|
||
|
assert np.all((2 * A) == (A.todense() * 2))
|
||
|
|
||
|
assert np.all((A.todense() * A) == (A.todense() ** 2))
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_matmul(A):
|
||
|
assert np.all((A @ A.T).todense() == A.dot(A.T).todense())
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_power_operator(A):
|
||
|
assert isinstance((A**2), scipy.sparse.sparray), "Expected array, got matrix"
|
||
|
|
||
|
# https://github.com/scipy/scipy/issues/15948
|
||
|
npt.assert_equal((A**2).todense(), (A.todense())**2)
|
||
|
|
||
|
# power of zero is all ones (dense) so helpful msg exception
|
||
|
with pytest.raises(NotImplementedError, match="zero power"):
|
||
|
A**0
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_sparse_divide(A):
|
||
|
assert isinstance(A / A, np.ndarray)
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_sparse_dense_divide(A):
|
||
|
with pytest.warns(RuntimeWarning):
|
||
|
assert isinstance((A / A.todense()), scipy.sparse.sparray)
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_dense_divide(A):
|
||
|
assert isinstance((A / 2), scipy.sparse.sparray), "Expected array, got matrix"
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_no_A_attr(A):
|
||
|
with pytest.warns(VisibleDeprecationWarning):
|
||
|
A.A
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_no_H_attr(A):
|
||
|
with pytest.warns(VisibleDeprecationWarning):
|
||
|
A.H
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_getrow_getcol(A):
|
||
|
assert isinstance(A._getcol(0), scipy.sparse.sparray)
|
||
|
assert isinstance(A._getrow(0), scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
# -- linalg --
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_as_linearoperator(A):
|
||
|
L = spla.aslinearoperator(A)
|
||
|
npt.assert_allclose(L * [1, 2, 3, 4], A @ [1, 2, 3, 4])
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_inv(B):
|
||
|
if B.__class__.__name__[:3] != 'csc':
|
||
|
return
|
||
|
|
||
|
C = spla.inv(B)
|
||
|
|
||
|
assert isinstance(C, scipy.sparse.sparray)
|
||
|
npt.assert_allclose(C.todense(), np.linalg.inv(B.todense()))
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_expm(B):
|
||
|
if B.__class__.__name__[:3] != 'csc':
|
||
|
return
|
||
|
|
||
|
Bmat = scipy.sparse.csc_matrix(B)
|
||
|
|
||
|
C = spla.expm(B)
|
||
|
|
||
|
assert isinstance(C, scipy.sparse.sparray)
|
||
|
npt.assert_allclose(
|
||
|
C.todense(),
|
||
|
spla.expm(Bmat).todense()
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_expm_multiply(B):
|
||
|
if B.__class__.__name__[:3] != 'csc':
|
||
|
return
|
||
|
|
||
|
npt.assert_allclose(
|
||
|
spla.expm_multiply(B, np.array([1, 2])),
|
||
|
spla.expm(B) @ [1, 2]
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
def test_norm(A):
|
||
|
C = spla.norm(A)
|
||
|
npt.assert_allclose(C, np.linalg.norm(A.todense()))
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_onenormest(B):
|
||
|
C = spla.onenormest(B)
|
||
|
npt.assert_allclose(C, np.linalg.norm(B.todense(), 1))
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_spsolve(B):
|
||
|
if B.__class__.__name__[:3] not in ('csc', 'csr'):
|
||
|
return
|
||
|
|
||
|
npt.assert_allclose(
|
||
|
spla.spsolve(B, [1, 2]),
|
||
|
np.linalg.solve(B.todense(), [1, 2])
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_spsolve_triangular():
|
||
|
X = scipy.sparse.csr_array([
|
||
|
[1, 0, 0, 0],
|
||
|
[2, 1, 0, 0],
|
||
|
[3, 2, 1, 0],
|
||
|
[4, 3, 2, 1],
|
||
|
])
|
||
|
spla.spsolve_triangular(X, [1, 2, 3, 4])
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
def test_factorized(B):
|
||
|
if B.__class__.__name__[:3] != 'csc':
|
||
|
return
|
||
|
|
||
|
LU = spla.factorized(B)
|
||
|
npt.assert_allclose(
|
||
|
LU(np.array([1, 2])),
|
||
|
np.linalg.solve(B.todense(), [1, 2])
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_square_sparrays
|
||
|
@pytest.mark.parametrize(
|
||
|
"solver",
|
||
|
["bicg", "bicgstab", "cg", "cgs", "gmres", "lgmres", "minres", "qmr",
|
||
|
"gcrotmk", "tfqmr"]
|
||
|
)
|
||
|
def test_solvers(B, solver):
|
||
|
if solver == "minres":
|
||
|
kwargs = {}
|
||
|
else:
|
||
|
kwargs = {'atol': 1e-5}
|
||
|
|
||
|
x, info = getattr(spla, solver)(B, np.array([1, 2]), **kwargs)
|
||
|
assert info >= 0 # no errors, even if perhaps did not converge fully
|
||
|
npt.assert_allclose(x, [1, 1], atol=1e-1)
|
||
|
|
||
|
|
||
|
@parametrize_sparrays
|
||
|
@pytest.mark.parametrize(
|
||
|
"solver",
|
||
|
["lsqr", "lsmr"]
|
||
|
)
|
||
|
def test_lstsqr(A, solver):
|
||
|
x, *_ = getattr(spla, solver)(A, [1, 2, 3])
|
||
|
npt.assert_allclose(A @ x, [1, 2, 3])
|
||
|
|
||
|
|
||
|
@parametrize_eig_sparrays
|
||
|
def test_eigs(X):
|
||
|
e, v = spla.eigs(X, k=1)
|
||
|
npt.assert_allclose(
|
||
|
X @ v,
|
||
|
e[0] * v
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_eig_sparrays
|
||
|
def test_eigsh(X):
|
||
|
X = X + X.T
|
||
|
e, v = spla.eigsh(X, k=1)
|
||
|
npt.assert_allclose(
|
||
|
X @ v,
|
||
|
e[0] * v
|
||
|
)
|
||
|
|
||
|
|
||
|
@parametrize_eig_sparrays
|
||
|
def test_svds(X):
|
||
|
u, s, vh = spla.svds(X, k=3)
|
||
|
u2, s2, vh2 = np.linalg.svd(X.todense())
|
||
|
s = np.sort(s)
|
||
|
s2 = np.sort(s2[:3])
|
||
|
npt.assert_allclose(s, s2, atol=1e-3)
|
||
|
|
||
|
|
||
|
def test_splu():
|
||
|
X = scipy.sparse.csc_array([
|
||
|
[1, 0, 0, 0],
|
||
|
[2, 1, 0, 0],
|
||
|
[3, 2, 1, 0],
|
||
|
[4, 3, 2, 1],
|
||
|
])
|
||
|
LU = spla.splu(X)
|
||
|
npt.assert_allclose(
|
||
|
LU.solve(np.array([1, 2, 3, 4])),
|
||
|
np.asarray([1, 0, 0, 0], dtype=np.float64),
|
||
|
rtol=1e-14, atol=3e-16
|
||
|
)
|
||
|
|
||
|
|
||
|
def test_spilu():
|
||
|
X = scipy.sparse.csc_array([
|
||
|
[1, 0, 0, 0],
|
||
|
[2, 1, 0, 0],
|
||
|
[3, 2, 1, 0],
|
||
|
[4, 3, 2, 1],
|
||
|
])
|
||
|
LU = spla.spilu(X)
|
||
|
npt.assert_allclose(
|
||
|
LU.solve(np.array([1, 2, 3, 4])),
|
||
|
np.asarray([1, 0, 0, 0], dtype=np.float64),
|
||
|
rtol=1e-14, atol=3e-16
|
||
|
)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
"cls,indices_attrs",
|
||
|
[
|
||
|
(
|
||
|
scipy.sparse.csr_array,
|
||
|
["indices", "indptr"],
|
||
|
),
|
||
|
(
|
||
|
scipy.sparse.csc_array,
|
||
|
["indices", "indptr"],
|
||
|
),
|
||
|
(
|
||
|
scipy.sparse.coo_array,
|
||
|
["row", "col"],
|
||
|
),
|
||
|
]
|
||
|
)
|
||
|
@pytest.mark.parametrize("expected_dtype", [np.int64, np.int32])
|
||
|
def test_index_dtype_compressed(cls, indices_attrs, expected_dtype):
|
||
|
input_array = scipy.sparse.coo_array(np.arange(9).reshape(3, 3))
|
||
|
coo_tuple = (
|
||
|
input_array.data,
|
||
|
(
|
||
|
input_array.row.astype(expected_dtype),
|
||
|
input_array.col.astype(expected_dtype),
|
||
|
)
|
||
|
)
|
||
|
|
||
|
result = cls(coo_tuple)
|
||
|
for attr in indices_attrs:
|
||
|
assert getattr(result, attr).dtype == expected_dtype
|
||
|
|
||
|
result = cls(coo_tuple, shape=(3, 3))
|
||
|
for attr in indices_attrs:
|
||
|
assert getattr(result, attr).dtype == expected_dtype
|
||
|
|
||
|
if issubclass(cls, scipy.sparse._compressed._cs_matrix):
|
||
|
input_array_csr = input_array.tocsr()
|
||
|
csr_tuple = (
|
||
|
input_array_csr.data,
|
||
|
input_array_csr.indices.astype(expected_dtype),
|
||
|
input_array_csr.indptr.astype(expected_dtype),
|
||
|
)
|
||
|
|
||
|
result = cls(csr_tuple)
|
||
|
for attr in indices_attrs:
|
||
|
assert getattr(result, attr).dtype == expected_dtype
|
||
|
|
||
|
result = cls(csr_tuple, shape=(3, 3))
|
||
|
for attr in indices_attrs:
|
||
|
assert getattr(result, attr).dtype == expected_dtype
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_diags():
|
||
|
m = scipy.sparse.diags([0, 1, 2])
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_eye():
|
||
|
m = scipy.sparse.eye(3)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_spdiags():
|
||
|
m = scipy.sparse.spdiags([1, 2, 3], 0, 3, 3)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_identity():
|
||
|
m = scipy.sparse.identity(3)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_kron_dense():
|
||
|
m = scipy.sparse.kron(
|
||
|
np.array([[1, 2], [3, 4]]), np.array([[4, 3], [2, 1]])
|
||
|
)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_kron_sparse():
|
||
|
m = scipy.sparse.kron(
|
||
|
np.array([[1, 2], [3, 4]]), np.array([[1, 0], [0, 0]])
|
||
|
)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_kronsum():
|
||
|
m = scipy.sparse.kronsum(
|
||
|
np.array([[1, 0], [0, 1]]), np.array([[0, 1], [1, 0]])
|
||
|
)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_random():
|
||
|
m = scipy.sparse.random(3, 3)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_default_is_matrix_rand():
|
||
|
m = scipy.sparse.rand(3, 3)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize("fn", (scipy.sparse.hstack, scipy.sparse.vstack))
|
||
|
def test_default_is_matrix_stacks(fn):
|
||
|
"""Same idea as `test_default_construction_fn_matrices`, but for the
|
||
|
stacking creation functions."""
|
||
|
A = scipy.sparse.coo_matrix(np.eye(2))
|
||
|
B = scipy.sparse.coo_matrix([[0, 1], [1, 0]])
|
||
|
m = fn([A, B])
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_blocks_default_construction_fn_matrices():
|
||
|
"""Same idea as `test_default_construction_fn_matrices`, but for the block
|
||
|
creation function"""
|
||
|
A = scipy.sparse.coo_matrix(np.eye(2))
|
||
|
B = scipy.sparse.coo_matrix([[2], [0]])
|
||
|
C = scipy.sparse.coo_matrix([[3]])
|
||
|
|
||
|
# block diag
|
||
|
m = scipy.sparse.block_diag((A, B, C))
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
# bmat
|
||
|
m = scipy.sparse.bmat([[A, None], [None, C]])
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
|
||
|
|
||
|
def test_format_property():
|
||
|
for fmt in sparray_types:
|
||
|
arr_cls = getattr(scipy.sparse, f"{fmt}_array")
|
||
|
M = arr_cls([[1, 2]])
|
||
|
assert M.format == fmt
|
||
|
assert M._format == fmt
|
||
|
with pytest.raises(AttributeError):
|
||
|
M.format = "qqq"
|
||
|
|
||
|
|
||
|
def test_issparse():
|
||
|
m = scipy.sparse.eye(3)
|
||
|
a = scipy.sparse.csr_array(m)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
assert isinstance(a, scipy.sparse.sparray)
|
||
|
|
||
|
# Both sparse arrays and sparse matrices should be sparse
|
||
|
assert scipy.sparse.issparse(a)
|
||
|
assert scipy.sparse.issparse(m)
|
||
|
|
||
|
# ndarray and array_likes are not sparse
|
||
|
assert not scipy.sparse.issparse(a.todense())
|
||
|
assert not scipy.sparse.issparse(m.todense())
|
||
|
|
||
|
|
||
|
def test_isspmatrix():
|
||
|
m = scipy.sparse.eye(3)
|
||
|
a = scipy.sparse.csr_array(m)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
assert isinstance(a, scipy.sparse.sparray)
|
||
|
|
||
|
# Should only be true for sparse matrices, not sparse arrays
|
||
|
assert not scipy.sparse.isspmatrix(a)
|
||
|
assert scipy.sparse.isspmatrix(m)
|
||
|
|
||
|
# ndarray and array_likes are not sparse
|
||
|
assert not scipy.sparse.isspmatrix(a.todense())
|
||
|
assert not scipy.sparse.isspmatrix(m.todense())
|
||
|
|
||
|
|
||
|
@pytest.mark.parametrize(
|
||
|
("fmt", "fn"),
|
||
|
(
|
||
|
("bsr", scipy.sparse.isspmatrix_bsr),
|
||
|
("coo", scipy.sparse.isspmatrix_coo),
|
||
|
("csc", scipy.sparse.isspmatrix_csc),
|
||
|
("csr", scipy.sparse.isspmatrix_csr),
|
||
|
("dia", scipy.sparse.isspmatrix_dia),
|
||
|
("dok", scipy.sparse.isspmatrix_dok),
|
||
|
("lil", scipy.sparse.isspmatrix_lil),
|
||
|
),
|
||
|
)
|
||
|
def test_isspmatrix_format(fmt, fn):
|
||
|
m = scipy.sparse.eye(3, format=fmt)
|
||
|
a = scipy.sparse.csr_array(m).asformat(fmt)
|
||
|
assert not isinstance(m, scipy.sparse.sparray)
|
||
|
assert isinstance(a, scipy.sparse.sparray)
|
||
|
|
||
|
# Should only be true for sparse matrices, not sparse arrays
|
||
|
assert not fn(a)
|
||
|
assert fn(m)
|
||
|
|
||
|
# ndarray and array_likes are not sparse
|
||
|
assert not fn(a.todense())
|
||
|
assert not fn(m.todense())
|