reconnect moved files to git repo

This commit is contained in:
root
2025-08-01 04:33:03 -04:00
commit 5d3c35492d
23190 changed files with 4750716 additions and 0 deletions

View File

@ -0,0 +1,349 @@
"""Test functions for the sparse.linalg._expm_multiply module."""
from functools import partial
from itertools import product
import numpy as np
import pytest
from numpy.testing import (assert_allclose, assert_, assert_equal,
suppress_warnings)
from scipy.sparse import SparseEfficiencyWarning
from scipy.sparse.linalg import aslinearoperator
import scipy.linalg
from scipy.sparse.linalg import expm as sp_expm
from scipy.sparse.linalg._expm_multiply import (_theta, _compute_p_max,
_onenormest_matrix_power, expm_multiply, _expm_multiply_simple,
_expm_multiply_interval)
from scipy._lib._util import np_long
IMPRECISE = {np.single, np.csingle}
REAL_DTYPES = {np.intc, np_long, np.longlong,
np.float32, np.float64, np.longdouble}
COMPLEX_DTYPES = {np.complex64, np.complex128, np.clongdouble}
# use sorted list to ensure fixed order of tests
DTYPES = sorted(REAL_DTYPES ^ COMPLEX_DTYPES, key=str)
def estimated(func):
"""If trace is estimated, it should warn.
We warn that estimation of trace might impact performance.
All result have to be correct nevertheless!
"""
def wrapped(*args, **kwds):
with pytest.warns(UserWarning,
match="Trace of LinearOperator not available"):
return func(*args, **kwds)
return wrapped
def less_than_or_close(a, b):
return np.allclose(a, b) or (a < b)
class TestExpmActionSimple:
"""
These tests do not consider the case of multiple time steps in one call.
"""
def test_theta_monotonicity(self):
pairs = sorted(_theta.items())
for (m_a, theta_a), (m_b, theta_b) in zip(pairs[:-1], pairs[1:]):
assert_(theta_a < theta_b)
def test_p_max_default(self):
m_max = 55
expected_p_max = 8
observed_p_max = _compute_p_max(m_max)
assert_equal(observed_p_max, expected_p_max)
def test_p_max_range(self):
for m_max in range(1, 55+1):
p_max = _compute_p_max(m_max)
assert_(p_max*(p_max - 1) <= m_max + 1)
p_too_big = p_max + 1
assert_(p_too_big*(p_too_big - 1) > m_max + 1)
def test_onenormest_matrix_power(self):
np.random.seed(1234)
n = 40
nsamples = 10
for i in range(nsamples):
A = scipy.linalg.inv(np.random.randn(n, n))
for p in range(4):
if not p:
M = np.identity(n)
else:
M = np.dot(M, A)
estimated = _onenormest_matrix_power(A, p)
exact = np.linalg.norm(M, 1)
assert_(less_than_or_close(estimated, exact))
assert_(less_than_or_close(exact, 3*estimated))
def test_expm_multiply(self):
np.random.seed(1234)
n = 40
k = 3
nsamples = 10
for i in range(nsamples):
A = scipy.linalg.inv(np.random.randn(n, n))
B = np.random.randn(n, k)
observed = expm_multiply(A, B)
expected = np.dot(sp_expm(A), B)
assert_allclose(observed, expected)
observed = estimated(expm_multiply)(aslinearoperator(A), B)
assert_allclose(observed, expected)
traceA = np.trace(A)
observed = expm_multiply(aslinearoperator(A), B, traceA=traceA)
assert_allclose(observed, expected)
def test_matrix_vector_multiply(self):
np.random.seed(1234)
n = 40
nsamples = 10
for i in range(nsamples):
A = scipy.linalg.inv(np.random.randn(n, n))
v = np.random.randn(n)
observed = expm_multiply(A, v)
expected = np.dot(sp_expm(A), v)
assert_allclose(observed, expected)
observed = estimated(expm_multiply)(aslinearoperator(A), v)
assert_allclose(observed, expected)
def test_scaled_expm_multiply(self):
np.random.seed(1234)
n = 40
k = 3
nsamples = 10
for i, t in product(range(nsamples), [0.2, 1.0, 1.5]):
with np.errstate(invalid='ignore'):
A = scipy.linalg.inv(np.random.randn(n, n))
B = np.random.randn(n, k)
observed = _expm_multiply_simple(A, B, t=t)
expected = np.dot(sp_expm(t*A), B)
assert_allclose(observed, expected)
observed = estimated(_expm_multiply_simple)(
aslinearoperator(A), B, t=t
)
assert_allclose(observed, expected)
def test_scaled_expm_multiply_single_timepoint(self):
np.random.seed(1234)
t = 0.1
n = 5
k = 2
A = np.random.randn(n, n)
B = np.random.randn(n, k)
observed = _expm_multiply_simple(A, B, t=t)
expected = sp_expm(t*A).dot(B)
assert_allclose(observed, expected)
observed = estimated(_expm_multiply_simple)(
aslinearoperator(A), B, t=t
)
assert_allclose(observed, expected)
def test_sparse_expm_multiply(self):
np.random.seed(1234)
n = 40
k = 3
nsamples = 10
for i in range(nsamples):
A = scipy.sparse.rand(n, n, density=0.05)
B = np.random.randn(n, k)
observed = expm_multiply(A, B)
with suppress_warnings() as sup:
sup.filter(SparseEfficiencyWarning,
"splu converted its input to CSC format")
sup.filter(SparseEfficiencyWarning,
"spsolve is more efficient when sparse b is in the"
" CSC matrix format")
expected = sp_expm(A).dot(B)
assert_allclose(observed, expected)
observed = estimated(expm_multiply)(aslinearoperator(A), B)
assert_allclose(observed, expected)
def test_complex(self):
A = np.array([
[1j, 1j],
[0, 1j]], dtype=complex)
B = np.array([1j, 1j])
observed = expm_multiply(A, B)
expected = np.array([
1j * np.exp(1j) + 1j * (1j*np.cos(1) - np.sin(1)),
1j * np.exp(1j)], dtype=complex)
assert_allclose(observed, expected)
observed = estimated(expm_multiply)(aslinearoperator(A), B)
assert_allclose(observed, expected)
class TestExpmActionInterval:
@pytest.mark.fail_slow(5)
def test_sparse_expm_multiply_interval(self):
np.random.seed(1234)
start = 0.1
stop = 3.2
n = 40
k = 3
endpoint = True
for num in (14, 13, 2):
A = scipy.sparse.rand(n, n, density=0.05)
B = np.random.randn(n, k)
v = np.random.randn(n)
for target in (B, v):
X = expm_multiply(A, target, start=start, stop=stop,
num=num, endpoint=endpoint)
samples = np.linspace(start=start, stop=stop,
num=num, endpoint=endpoint)
with suppress_warnings() as sup:
sup.filter(SparseEfficiencyWarning,
"splu converted its input to CSC format")
sup.filter(SparseEfficiencyWarning,
"spsolve is more efficient when sparse b is in"
" the CSC matrix format")
for solution, t in zip(X, samples):
assert_allclose(solution, sp_expm(t*A).dot(target))
@pytest.mark.fail_slow(5)
def test_expm_multiply_interval_vector(self):
np.random.seed(1234)
interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True}
for num, n in product([14, 13, 2], [1, 2, 5, 20, 40]):
A = scipy.linalg.inv(np.random.randn(n, n))
v = np.random.randn(n)
samples = np.linspace(num=num, **interval)
X = expm_multiply(A, v, num=num, **interval)
for solution, t in zip(X, samples):
assert_allclose(solution, sp_expm(t*A).dot(v))
# test for linear operator with unknown trace -> estimate trace
Xguess = estimated(expm_multiply)(aslinearoperator(A), v,
num=num, **interval)
# test for linear operator with given trace
Xgiven = expm_multiply(aslinearoperator(A), v, num=num, **interval,
traceA=np.trace(A))
# test robustness for linear operator with wrong trace
Xwrong = expm_multiply(aslinearoperator(A), v, num=num, **interval,
traceA=np.trace(A)*5)
for sol_guess, sol_given, sol_wrong, t in zip(Xguess, Xgiven,
Xwrong, samples):
correct = sp_expm(t*A).dot(v)
assert_allclose(sol_guess, correct)
assert_allclose(sol_given, correct)
assert_allclose(sol_wrong, correct)
@pytest.mark.fail_slow(5)
def test_expm_multiply_interval_matrix(self):
np.random.seed(1234)
interval = {'start': 0.1, 'stop': 3.2, 'endpoint': True}
for num, n, k in product([14, 13, 2], [1, 2, 5, 20, 40], [1, 2]):
A = scipy.linalg.inv(np.random.randn(n, n))
B = np.random.randn(n, k)
samples = np.linspace(num=num, **interval)
X = expm_multiply(A, B, num=num, **interval)
for solution, t in zip(X, samples):
assert_allclose(solution, sp_expm(t*A).dot(B))
X = estimated(expm_multiply)(aslinearoperator(A), B, num=num,
**interval)
for solution, t in zip(X, samples):
assert_allclose(solution, sp_expm(t*A).dot(B))
def test_sparse_expm_multiply_interval_dtypes(self):
# Test A & B int
A = scipy.sparse.diags(np.arange(5),format='csr', dtype=int)
B = np.ones(5, dtype=int)
Aexpm = scipy.sparse.diags(np.exp(np.arange(5)),format='csr')
assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B))
# Test A complex, B int
A = scipy.sparse.diags(-1j*np.arange(5),format='csr', dtype=complex)
B = np.ones(5, dtype=int)
Aexpm = scipy.sparse.diags(np.exp(-1j*np.arange(5)),format='csr')
assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B))
# Test A int, B complex
A = scipy.sparse.diags(np.arange(5),format='csr', dtype=int)
B = np.full(5, 1j, dtype=complex)
Aexpm = scipy.sparse.diags(np.exp(np.arange(5)),format='csr')
assert_allclose(expm_multiply(A,B,0,1)[-1], Aexpm.dot(B))
def test_expm_multiply_interval_status_0(self):
self._help_test_specific_expm_interval_status(0)
def test_expm_multiply_interval_status_1(self):
self._help_test_specific_expm_interval_status(1)
def test_expm_multiply_interval_status_2(self):
self._help_test_specific_expm_interval_status(2)
def _help_test_specific_expm_interval_status(self, target_status):
np.random.seed(1234)
start = 0.1
stop = 3.2
num = 13
endpoint = True
n = 5
k = 2
nrepeats = 10
nsuccesses = 0
for num in [14, 13, 2] * nrepeats:
A = np.random.randn(n, n)
B = np.random.randn(n, k)
status = _expm_multiply_interval(A, B,
start=start, stop=stop, num=num, endpoint=endpoint,
status_only=True)
if status == target_status:
X, status = _expm_multiply_interval(A, B,
start=start, stop=stop, num=num, endpoint=endpoint,
status_only=False)
assert_equal(X.shape, (num, n, k))
samples = np.linspace(start=start, stop=stop,
num=num, endpoint=endpoint)
for solution, t in zip(X, samples):
assert_allclose(solution, sp_expm(t*A).dot(B))
nsuccesses += 1
if not nsuccesses:
msg = 'failed to find a status-' + str(target_status) + ' interval'
raise Exception(msg)
@pytest.mark.parametrize("dtype_a", DTYPES)
@pytest.mark.parametrize("dtype_b", DTYPES)
@pytest.mark.parametrize("b_is_matrix", [False, True])
def test_expm_multiply_dtype(dtype_a, dtype_b, b_is_matrix):
"""Make sure `expm_multiply` handles all numerical dtypes correctly."""
assert_allclose_ = (partial(assert_allclose, rtol=1.2e-3, atol=1e-5)
if {dtype_a, dtype_b} & IMPRECISE else assert_allclose)
rng = np.random.default_rng(1234)
# test data
n = 7
b_shape = (n, 3) if b_is_matrix else (n, )
if dtype_a in REAL_DTYPES:
A = scipy.linalg.inv(rng.random([n, n])).astype(dtype_a)
else:
A = scipy.linalg.inv(
rng.random([n, n]) + 1j*rng.random([n, n])
).astype(dtype_a)
if dtype_b in REAL_DTYPES:
B = (2*rng.random(b_shape)).astype(dtype_b)
else:
B = (rng.random(b_shape) + 1j*rng.random(b_shape)).astype(dtype_b)
# single application
sol_mat = expm_multiply(A, B)
sol_op = estimated(expm_multiply)(aslinearoperator(A), B)
direct_sol = np.dot(sp_expm(A), B)
assert_allclose_(sol_mat, direct_sol)
assert_allclose_(sol_op, direct_sol)
sol_op = expm_multiply(aslinearoperator(A), B, traceA=np.trace(A))
assert_allclose_(sol_op, direct_sol)
# for time points
interval = {'start': 0.1, 'stop': 3.2, 'num': 13, 'endpoint': True}
samples = np.linspace(**interval)
X_mat = expm_multiply(A, B, **interval)
X_op = estimated(expm_multiply)(aslinearoperator(A), B, **interval)
for sol_mat, sol_op, t in zip(X_mat, X_op, samples):
direct_sol = sp_expm(t*A).dot(B)
assert_allclose_(sol_mat, direct_sol)
assert_allclose_(sol_op, direct_sol)

View File

@ -0,0 +1,481 @@
"""Test functions for the sparse.linalg._interface module
"""
from functools import partial
from itertools import product
import operator
from pytest import raises as assert_raises, warns
from numpy.testing import assert_, assert_equal
import numpy as np
import scipy.sparse as sparse
import scipy.sparse.linalg._interface as interface
from scipy.sparse._sputils import matrix
class TestLinearOperator:
def setup_method(self):
self.A = np.array([[1,2,3],
[4,5,6]])
self.B = np.array([[1,2],
[3,4],
[5,6]])
self.C = np.array([[1,2],
[3,4]])
def test_matvec(self):
def get_matvecs(A):
return [{
'shape': A.shape,
'matvec': lambda x: np.dot(A, x).reshape(A.shape[0]),
'rmatvec': lambda x: np.dot(A.T.conj(),
x).reshape(A.shape[1])
},
{
'shape': A.shape,
'matvec': lambda x: np.dot(A, x),
'rmatvec': lambda x: np.dot(A.T.conj(), x),
'rmatmat': lambda x: np.dot(A.T.conj(), x),
'matmat': lambda x: np.dot(A, x)
}]
for matvecs in get_matvecs(self.A):
A = interface.LinearOperator(**matvecs)
assert_(A.args == ())
assert_equal(A.matvec(np.array([1,2,3])), [14,32])
assert_equal(A.matvec(np.array([[1],[2],[3]])), [[14],[32]])
assert_equal(A * np.array([1,2,3]), [14,32])
assert_equal(A * np.array([[1],[2],[3]]), [[14],[32]])
assert_equal(A.dot(np.array([1,2,3])), [14,32])
assert_equal(A.dot(np.array([[1],[2],[3]])), [[14],[32]])
assert_equal(A.matvec(matrix([[1],[2],[3]])), [[14],[32]])
assert_equal(A * matrix([[1],[2],[3]]), [[14],[32]])
assert_equal(A.dot(matrix([[1],[2],[3]])), [[14],[32]])
assert_equal((2*A)*[1,1,1], [12,30])
assert_equal((2 * A).rmatvec([1, 1]), [10, 14, 18])
assert_equal((2*A).H.matvec([1,1]), [10, 14, 18])
assert_equal((2*A)*[[1],[1],[1]], [[12],[30]])
assert_equal((2 * A).matmat([[1], [1], [1]]), [[12], [30]])
assert_equal((A*2)*[1,1,1], [12,30])
assert_equal((A*2)*[[1],[1],[1]], [[12],[30]])
assert_equal((2j*A)*[1,1,1], [12j,30j])
assert_equal((A+A)*[1,1,1], [12, 30])
assert_equal((A + A).rmatvec([1, 1]), [10, 14, 18])
assert_equal((A+A).H.matvec([1,1]), [10, 14, 18])
assert_equal((A+A)*[[1],[1],[1]], [[12], [30]])
assert_equal((A+A).matmat([[1],[1],[1]]), [[12], [30]])
assert_equal((-A)*[1,1,1], [-6,-15])
assert_equal((-A)*[[1],[1],[1]], [[-6],[-15]])
assert_equal((A-A)*[1,1,1], [0,0])
assert_equal((A - A) * [[1], [1], [1]], [[0], [0]])
X = np.array([[1, 2], [3, 4]])
# A_asarray = np.array([[1, 2, 3], [4, 5, 6]])
assert_equal((2 * A).rmatmat(X), np.dot((2 * self.A).T, X))
assert_equal((A * 2).rmatmat(X), np.dot((self.A * 2).T, X))
assert_equal((2j * A).rmatmat(X),
np.dot((2j * self.A).T.conj(), X))
assert_equal((A * 2j).rmatmat(X),
np.dot((self.A * 2j).T.conj(), X))
assert_equal((A + A).rmatmat(X),
np.dot((self.A + self.A).T, X))
assert_equal((A + 2j * A).rmatmat(X),
np.dot((self.A + 2j * self.A).T.conj(), X))
assert_equal((-A).rmatmat(X), np.dot((-self.A).T, X))
assert_equal((A - A).rmatmat(X),
np.dot((self.A - self.A).T, X))
assert_equal((2j * A).rmatmat(2j * X),
np.dot((2j * self.A).T.conj(), 2j * X))
z = A+A
assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] is A)
z = 2*A
assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] == 2)
assert_(isinstance(A.matvec([1, 2, 3]), np.ndarray))
assert_(isinstance(A.matvec(np.array([[1],[2],[3]])), np.ndarray))
assert_(isinstance(A * np.array([1,2,3]), np.ndarray))
assert_(isinstance(A * np.array([[1],[2],[3]]), np.ndarray))
assert_(isinstance(A.dot(np.array([1,2,3])), np.ndarray))
assert_(isinstance(A.dot(np.array([[1],[2],[3]])), np.ndarray))
assert_(isinstance(A.matvec(matrix([[1],[2],[3]])), np.ndarray))
assert_(isinstance(A * matrix([[1],[2],[3]]), np.ndarray))
assert_(isinstance(A.dot(matrix([[1],[2],[3]])), np.ndarray))
assert_(isinstance(2*A, interface._ScaledLinearOperator))
assert_(isinstance(2j*A, interface._ScaledLinearOperator))
assert_(isinstance(A+A, interface._SumLinearOperator))
assert_(isinstance(-A, interface._ScaledLinearOperator))
assert_(isinstance(A-A, interface._SumLinearOperator))
assert_(isinstance(A/2, interface._ScaledLinearOperator))
assert_(isinstance(A/2j, interface._ScaledLinearOperator))
assert_(((A * 3) / 3).args[0] is A) # check for simplification
# Test that prefactor is of _ScaledLinearOperator is not mutated
# when the operator is multiplied by a number
result = A @ np.array([1, 2, 3])
B = A * 3
C = A / 5
assert_equal(A @ np.array([1, 2, 3]), result)
assert_((2j*A).dtype == np.complex128)
# Test division by non-scalar
msg = "Can only divide a linear operator by a scalar."
with assert_raises(ValueError, match=msg):
A / np.array([1, 2])
assert_raises(ValueError, A.matvec, np.array([1,2]))
assert_raises(ValueError, A.matvec, np.array([1,2,3,4]))
assert_raises(ValueError, A.matvec, np.array([[1],[2]]))
assert_raises(ValueError, A.matvec, np.array([[1],[2],[3],[4]]))
assert_raises(ValueError, lambda: A*A)
assert_raises(ValueError, lambda: A**2)
for matvecsA, matvecsB in product(get_matvecs(self.A),
get_matvecs(self.B)):
A = interface.LinearOperator(**matvecsA)
B = interface.LinearOperator(**matvecsB)
# AtimesB = np.array([[22, 28], [49, 64]])
AtimesB = self.A.dot(self.B)
X = np.array([[1, 2], [3, 4]])
assert_equal((A * B).rmatmat(X), np.dot((AtimesB).T, X))
assert_equal((2j * A * B).rmatmat(X),
np.dot((2j * AtimesB).T.conj(), X))
assert_equal((A*B)*[1,1], [50,113])
assert_equal((A*B)*[[1],[1]], [[50],[113]])
assert_equal((A*B).matmat([[1],[1]]), [[50],[113]])
assert_equal((A * B).rmatvec([1, 1]), [71, 92])
assert_equal((A * B).H.matvec([1, 1]), [71, 92])
assert_(isinstance(A*B, interface._ProductLinearOperator))
assert_raises(ValueError, lambda: A+B)
assert_raises(ValueError, lambda: A**2)
z = A*B
assert_(len(z.args) == 2 and z.args[0] is A and z.args[1] is B)
for matvecsC in get_matvecs(self.C):
C = interface.LinearOperator(**matvecsC)
X = np.array([[1, 2], [3, 4]])
assert_equal(C.rmatmat(X), np.dot((self.C).T, X))
assert_equal((C**2).rmatmat(X),
np.dot((np.dot(self.C, self.C)).T, X))
assert_equal((C**2)*[1,1], [17,37])
assert_equal((C**2).rmatvec([1, 1]), [22, 32])
assert_equal((C**2).H.matvec([1, 1]), [22, 32])
assert_equal((C**2).matmat([[1],[1]]), [[17],[37]])
assert_(isinstance(C**2, interface._PowerLinearOperator))
def test_matmul(self):
D = {'shape': self.A.shape,
'matvec': lambda x: np.dot(self.A, x).reshape(self.A.shape[0]),
'rmatvec': lambda x: np.dot(self.A.T.conj(),
x).reshape(self.A.shape[1]),
'rmatmat': lambda x: np.dot(self.A.T.conj(), x),
'matmat': lambda x: np.dot(self.A, x)}
A = interface.LinearOperator(**D)
B = np.array([[1 + 1j, 2, 3],
[4, 5, 6],
[7, 8, 9]])
b = B[0]
assert_equal(operator.matmul(A, b), A * b)
assert_equal(operator.matmul(A, b.reshape(-1, 1)), A * b.reshape(-1, 1))
assert_equal(operator.matmul(A, B), A * B)
assert_equal(operator.matmul(b, A.H), b * A.H)
assert_equal(operator.matmul(b.reshape(1, -1), A.H), b.reshape(1, -1) * A.H)
assert_equal(operator.matmul(B, A.H), B * A.H)
assert_raises(ValueError, operator.matmul, A, 2)
assert_raises(ValueError, operator.matmul, 2, A)
class TestAsLinearOperator:
def setup_method(self):
self.cases = []
def make_cases(original, dtype):
cases = []
cases.append((matrix(original, dtype=dtype), original))
cases.append((np.array(original, dtype=dtype), original))
cases.append((sparse.csr_matrix(original, dtype=dtype), original))
# Test default implementations of _adjoint and _rmatvec, which
# refer to each other.
def mv(x, dtype):
y = original.dot(x)
if len(x.shape) == 2:
y = y.reshape(-1, 1)
return y
def rmv(x, dtype):
return original.T.conj().dot(x)
class BaseMatlike(interface.LinearOperator):
args = ()
def __init__(self, dtype):
self.dtype = np.dtype(dtype)
self.shape = original.shape
def _matvec(self, x):
return mv(x, self.dtype)
class HasRmatvec(BaseMatlike):
args = ()
def _rmatvec(self,x):
return rmv(x, self.dtype)
class HasAdjoint(BaseMatlike):
args = ()
def _adjoint(self):
shape = self.shape[1], self.shape[0]
matvec = partial(rmv, dtype=self.dtype)
rmatvec = partial(mv, dtype=self.dtype)
return interface.LinearOperator(matvec=matvec,
rmatvec=rmatvec,
dtype=self.dtype,
shape=shape)
class HasRmatmat(HasRmatvec):
def _matmat(self, x):
return original.dot(x)
def _rmatmat(self, x):
return original.T.conj().dot(x)
cases.append((HasRmatvec(dtype), original))
cases.append((HasAdjoint(dtype), original))
cases.append((HasRmatmat(dtype), original))
return cases
original = np.array([[1,2,3], [4,5,6]])
self.cases += make_cases(original, np.int32)
self.cases += make_cases(original, np.float32)
self.cases += make_cases(original, np.float64)
self.cases += [(interface.aslinearoperator(M).T, A.T)
for M, A in make_cases(original.T, np.float64)]
self.cases += [(interface.aslinearoperator(M).H, A.T.conj())
for M, A in make_cases(original.T, np.float64)]
original = np.array([[1, 2j, 3j], [4j, 5j, 6]])
self.cases += make_cases(original, np.complex128)
self.cases += [(interface.aslinearoperator(M).T, A.T)
for M, A in make_cases(original.T, np.complex128)]
self.cases += [(interface.aslinearoperator(M).H, A.T.conj())
for M, A in make_cases(original.T, np.complex128)]
def test_basic(self):
for M, A_array in self.cases:
A = interface.aslinearoperator(M)
M,N = A.shape
xs = [np.array([1, 2, 3]),
np.array([[1], [2], [3]])]
ys = [np.array([1, 2]), np.array([[1], [2]])]
if A.dtype == np.complex128:
xs += [np.array([1, 2j, 3j]),
np.array([[1], [2j], [3j]])]
ys += [np.array([1, 2j]), np.array([[1], [2j]])]
x2 = np.array([[1, 4], [2, 5], [3, 6]])
for x in xs:
assert_equal(A.matvec(x), A_array.dot(x))
assert_equal(A * x, A_array.dot(x))
assert_equal(A.matmat(x2), A_array.dot(x2))
assert_equal(A * x2, A_array.dot(x2))
for y in ys:
assert_equal(A.rmatvec(y), A_array.T.conj().dot(y))
assert_equal(A.T.matvec(y), A_array.T.dot(y))
assert_equal(A.H.matvec(y), A_array.T.conj().dot(y))
for y in ys:
if y.ndim < 2:
continue
assert_equal(A.rmatmat(y), A_array.T.conj().dot(y))
assert_equal(A.T.matmat(y), A_array.T.dot(y))
assert_equal(A.H.matmat(y), A_array.T.conj().dot(y))
if hasattr(M,'dtype'):
assert_equal(A.dtype, M.dtype)
assert_(hasattr(A, 'args'))
def test_dot(self):
for M, A_array in self.cases:
A = interface.aslinearoperator(M)
M,N = A.shape
x0 = np.array([1, 2, 3])
x1 = np.array([[1], [2], [3]])
x2 = np.array([[1, 4], [2, 5], [3, 6]])
assert_equal(A.dot(x0), A_array.dot(x0))
assert_equal(A.dot(x1), A_array.dot(x1))
assert_equal(A.dot(x2), A_array.dot(x2))
def test_repr():
A = interface.LinearOperator(shape=(1, 1), matvec=lambda x: 1)
repr_A = repr(A)
assert_('unspecified dtype' not in repr_A, repr_A)
def test_identity():
ident = interface.IdentityOperator((3, 3))
assert_equal(ident * [1, 2, 3], [1, 2, 3])
assert_equal(ident.dot(np.arange(9).reshape(3, 3)).ravel(), np.arange(9))
assert_raises(ValueError, ident.matvec, [1, 2, 3, 4])
def test_attributes():
A = interface.aslinearoperator(np.arange(16).reshape(4, 4))
def always_four_ones(x):
x = np.asarray(x)
assert_(x.shape == (3,) or x.shape == (3, 1))
return np.ones(4)
B = interface.LinearOperator(shape=(4, 3), matvec=always_four_ones)
for op in [A, B, A * B, A.H, A + A, B + B, A**4]:
assert_(hasattr(op, "dtype"))
assert_(hasattr(op, "shape"))
assert_(hasattr(op, "_matvec"))
def matvec(x):
""" Needed for test_pickle as local functions are not pickleable """
return np.zeros(3)
def test_pickle():
import pickle
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
A = interface.LinearOperator((3, 3), matvec)
s = pickle.dumps(A, protocol=protocol)
B = pickle.loads(s)
for k in A.__dict__:
assert_equal(getattr(A, k), getattr(B, k))
def test_inheritance():
class Empty(interface.LinearOperator):
pass
with warns(RuntimeWarning, match="should implement at least"):
assert_raises(TypeError, Empty)
class Identity(interface.LinearOperator):
def __init__(self, n):
super().__init__(dtype=None, shape=(n, n))
def _matvec(self, x):
return x
id3 = Identity(3)
assert_equal(id3.matvec([1, 2, 3]), [1, 2, 3])
assert_raises(NotImplementedError, id3.rmatvec, [4, 5, 6])
class MatmatOnly(interface.LinearOperator):
def __init__(self, A):
super().__init__(A.dtype, A.shape)
self.A = A
def _matmat(self, x):
return self.A.dot(x)
mm = MatmatOnly(np.random.randn(5, 3))
assert_equal(mm.matvec(np.random.randn(3)).shape, (5,))
def test_dtypes_of_operator_sum():
# gh-6078
mat_complex = np.random.rand(2,2) + 1j * np.random.rand(2,2)
mat_real = np.random.rand(2,2)
complex_operator = interface.aslinearoperator(mat_complex)
real_operator = interface.aslinearoperator(mat_real)
sum_complex = complex_operator + complex_operator
sum_real = real_operator + real_operator
assert_equal(sum_real.dtype, np.float64)
assert_equal(sum_complex.dtype, np.complex128)
def test_no_double_init():
call_count = [0]
def matvec(v):
call_count[0] += 1
return v
# It should call matvec exactly once (in order to determine the
# operator dtype)
interface.LinearOperator((2, 2), matvec=matvec)
assert_equal(call_count[0], 1)
def test_adjoint_conjugate():
X = np.array([[1j]])
A = interface.aslinearoperator(X)
B = 1j * A
Y = 1j * X
v = np.array([1])
assert_equal(B.dot(v), Y.dot(v))
assert_equal(B.H.dot(v), Y.T.conj().dot(v))
def test_ndim():
X = np.array([[1]])
A = interface.aslinearoperator(X)
assert_equal(A.ndim, 2)
def test_transpose_noconjugate():
X = np.array([[1j]])
A = interface.aslinearoperator(X)
B = 1j * A
Y = 1j * X
v = np.array([1])
assert_equal(B.dot(v), Y.dot(v))
assert_equal(B.T.dot(v), Y.T.dot(v))
def test_sparse_matmat_exception():
A = interface.LinearOperator((2, 2), matvec=lambda x: x)
B = sparse.identity(2)
msg = "Unable to multiply a LinearOperator with a sparse matrix."
with assert_raises(TypeError, match=msg):
A @ B
with assert_raises(TypeError, match=msg):
B @ A
with assert_raises(ValueError):
A @ np.identity(4)
with assert_raises(ValueError):
np.identity(4) @ A

View File

@ -0,0 +1,592 @@
#
# Created by: Pearu Peterson, March 2002
#
""" Test functions for scipy.linalg._matfuncs module
"""
import math
import numpy as np
from numpy import array, eye, exp, random
from numpy.testing import (
assert_allclose, assert_, assert_array_almost_equal, assert_equal,
assert_array_almost_equal_nulp, suppress_warnings)
from scipy.sparse import csc_matrix, csc_array, SparseEfficiencyWarning
from scipy.sparse._construct import eye as speye
from scipy.sparse.linalg._matfuncs import (expm, _expm,
ProductOperator, MatrixPowerOperator,
_onenorm_matrix_power_nnm, matrix_power)
from scipy.sparse._sputils import matrix
from scipy.linalg import logm
from scipy.special import factorial, binom
import scipy.sparse
import scipy.sparse.linalg
def _burkardt_13_power(n, p):
"""
A helper function for testing matrix functions.
Parameters
----------
n : integer greater than 1
Order of the square matrix to be returned.
p : non-negative integer
Power of the matrix.
Returns
-------
out : ndarray representing a square matrix
A Forsythe matrix of order n, raised to the power p.
"""
# Input validation.
if n != int(n) or n < 2:
raise ValueError('n must be an integer greater than 1')
n = int(n)
if p != int(p) or p < 0:
raise ValueError('p must be a non-negative integer')
p = int(p)
# Construct the matrix explicitly.
a, b = divmod(p, n)
large = np.power(10.0, -n*a)
small = large * np.power(10.0, -n)
return np.diag([large]*(n-b), b) + np.diag([small]*b, b-n)
def test_onenorm_matrix_power_nnm():
np.random.seed(1234)
for n in range(1, 5):
for p in range(5):
M = np.random.random((n, n))
Mp = np.linalg.matrix_power(M, p)
observed = _onenorm_matrix_power_nnm(M, p)
expected = np.linalg.norm(Mp, 1)
assert_allclose(observed, expected)
def test_matrix_power():
np.random.seed(1234)
row, col = np.random.randint(0, 4, size=(2, 6))
data = np.random.random(size=(6,))
Amat = csc_matrix((data, (row, col)), shape=(4, 4))
A = csc_array((data, (row, col)), shape=(4, 4))
Adense = A.toarray()
for power in (2, 5, 6):
Apow = matrix_power(A, power).toarray()
Amat_pow = (Amat**power).toarray()
Adense_pow = np.linalg.matrix_power(Adense, power)
assert_allclose(Apow, Adense_pow)
assert_allclose(Apow, Amat_pow)
class TestExpM:
def test_zero_ndarray(self):
a = array([[0.,0],[0,0]])
assert_array_almost_equal(expm(a),[[1,0],[0,1]])
def test_zero_sparse(self):
a = csc_matrix([[0.,0],[0,0]])
assert_array_almost_equal(expm(a).toarray(),[[1,0],[0,1]])
def test_zero_matrix(self):
a = matrix([[0.,0],[0,0]])
assert_array_almost_equal(expm(a),[[1,0],[0,1]])
def test_misc_types(self):
A = expm(np.array([[1]]))
assert_allclose(expm(((1,),)), A)
assert_allclose(expm([[1]]), A)
assert_allclose(expm(matrix([[1]])), A)
assert_allclose(expm(np.array([[1]])), A)
assert_allclose(expm(csc_matrix([[1]])).toarray(), A)
B = expm(np.array([[1j]]))
assert_allclose(expm(((1j,),)), B)
assert_allclose(expm([[1j]]), B)
assert_allclose(expm(matrix([[1j]])), B)
assert_allclose(expm(csc_matrix([[1j]])).toarray(), B)
def test_bidiagonal_sparse(self):
A = csc_matrix([
[1, 3, 0],
[0, 1, 5],
[0, 0, 2]], dtype=float)
e1 = math.exp(1)
e2 = math.exp(2)
expected = np.array([
[e1, 3*e1, 15*(e2 - 2*e1)],
[0, e1, 5*(e2 - e1)],
[0, 0, e2]], dtype=float)
observed = expm(A).toarray()
assert_array_almost_equal(observed, expected)
def test_padecases_dtype_float(self):
for dtype in [np.float32, np.float64]:
for scale in [1e-2, 1e-1, 5e-1, 1, 10]:
A = scale * eye(3, dtype=dtype)
observed = expm(A)
expected = exp(scale, dtype=dtype) * eye(3, dtype=dtype)
assert_array_almost_equal_nulp(observed, expected, nulp=100)
def test_padecases_dtype_complex(self):
for dtype in [np.complex64, np.complex128]:
for scale in [1e-2, 1e-1, 5e-1, 1, 10]:
A = scale * eye(3, dtype=dtype)
observed = expm(A)
expected = exp(scale, dtype=dtype) * eye(3, dtype=dtype)
assert_array_almost_equal_nulp(observed, expected, nulp=100)
def test_padecases_dtype_sparse_float(self):
# float32 and complex64 lead to errors in spsolve/UMFpack
dtype = np.float64
for scale in [1e-2, 1e-1, 5e-1, 1, 10]:
a = scale * speye(3, 3, dtype=dtype, format='csc')
e = exp(scale, dtype=dtype) * eye(3, dtype=dtype)
with suppress_warnings() as sup:
sup.filter(SparseEfficiencyWarning, "Changing the sparsity structure")
exact_onenorm = _expm(a, use_exact_onenorm=True).toarray()
inexact_onenorm = _expm(a, use_exact_onenorm=False).toarray()
assert_array_almost_equal_nulp(exact_onenorm, e, nulp=100)
assert_array_almost_equal_nulp(inexact_onenorm, e, nulp=100)
def test_padecases_dtype_sparse_complex(self):
# float32 and complex64 lead to errors in spsolve/UMFpack
dtype = np.complex128
for scale in [1e-2, 1e-1, 5e-1, 1, 10]:
a = scale * speye(3, 3, dtype=dtype, format='csc')
e = exp(scale) * eye(3, dtype=dtype)
with suppress_warnings() as sup:
sup.filter(SparseEfficiencyWarning, "Changing the sparsity structure")
assert_array_almost_equal_nulp(expm(a).toarray(), e, nulp=100)
def test_logm_consistency(self):
random.seed(1234)
for dtype in [np.float64, np.complex128]:
for n in range(1, 10):
for scale in [1e-4, 1e-3, 1e-2, 1e-1, 1, 1e1, 1e2]:
# make logm(A) be of a given scale
A = (eye(n) + random.rand(n, n) * scale).astype(dtype)
if np.iscomplexobj(A):
A = A + 1j * random.rand(n, n) * scale
assert_array_almost_equal(expm(logm(A)), A)
def test_integer_matrix(self):
Q = np.array([
[-3, 1, 1, 1],
[1, -3, 1, 1],
[1, 1, -3, 1],
[1, 1, 1, -3]])
assert_allclose(expm(Q), expm(1.0 * Q))
def test_integer_matrix_2(self):
# Check for integer overflows
Q = np.array([[-500, 500, 0, 0],
[0, -550, 360, 190],
[0, 630, -630, 0],
[0, 0, 0, 0]], dtype=np.int16)
assert_allclose(expm(Q), expm(1.0 * Q))
Q = csc_matrix(Q)
assert_allclose(expm(Q).toarray(), expm(1.0 * Q).toarray())
def test_triangularity_perturbation(self):
# Experiment (1) of
# Awad H. Al-Mohy and Nicholas J. Higham (2012)
# Improved Inverse Scaling and Squaring Algorithms
# for the Matrix Logarithm.
A = np.array([
[3.2346e-1, 3e4, 3e4, 3e4],
[0, 3.0089e-1, 3e4, 3e4],
[0, 0, 3.221e-1, 3e4],
[0, 0, 0, 3.0744e-1]],
dtype=float)
A_logm = np.array([
[-1.12867982029050462e+00, 9.61418377142025565e+04,
-4.52485573953179264e+09, 2.92496941103871812e+14],
[0.00000000000000000e+00, -1.20101052953082288e+00,
9.63469687211303099e+04, -4.68104828911105442e+09],
[0.00000000000000000e+00, 0.00000000000000000e+00,
-1.13289322264498393e+00, 9.53249183094775653e+04],
[0.00000000000000000e+00, 0.00000000000000000e+00,
0.00000000000000000e+00, -1.17947533272554850e+00]],
dtype=float)
assert_allclose(expm(A_logm), A, rtol=1e-4)
# Perturb the upper triangular matrix by tiny amounts,
# so that it becomes technically not upper triangular.
random.seed(1234)
tiny = 1e-17
A_logm_perturbed = A_logm.copy()
A_logm_perturbed[1, 0] = tiny
with suppress_warnings() as sup:
sup.filter(RuntimeWarning, "Ill-conditioned.*")
A_expm_logm_perturbed = expm(A_logm_perturbed)
rtol = 1e-4
atol = 100 * tiny
assert_(not np.allclose(A_expm_logm_perturbed, A, rtol=rtol, atol=atol))
def test_burkardt_1(self):
# This matrix is diagonal.
# The calculation of the matrix exponential is simple.
#
# This is the first of a series of matrix exponential tests
# collected by John Burkardt from the following sources.
#
# Alan Laub,
# Review of "Linear System Theory" by Joao Hespanha,
# SIAM Review,
# Volume 52, Number 4, December 2010, pages 779--781.
#
# Cleve Moler and Charles Van Loan,
# Nineteen Dubious Ways to Compute the Exponential of a Matrix,
# Twenty-Five Years Later,
# SIAM Review,
# Volume 45, Number 1, March 2003, pages 3--49.
#
# Cleve Moler,
# Cleve's Corner: A Balancing Act for the Matrix Exponential,
# 23 July 2012.
#
# Robert Ward,
# Numerical computation of the matrix exponential
# with accuracy estimate,
# SIAM Journal on Numerical Analysis,
# Volume 14, Number 4, September 1977, pages 600--610.
exp1 = np.exp(1)
exp2 = np.exp(2)
A = np.array([
[1, 0],
[0, 2],
], dtype=float)
desired = np.array([
[exp1, 0],
[0, exp2],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_2(self):
# This matrix is symmetric.
# The calculation of the matrix exponential is straightforward.
A = np.array([
[1, 3],
[3, 2],
], dtype=float)
desired = np.array([
[39.322809708033859, 46.166301438885753],
[46.166301438885768, 54.711576854329110],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_3(self):
# This example is due to Laub.
# This matrix is ill-suited for the Taylor series approach.
# As powers of A are computed, the entries blow up too quickly.
exp1 = np.exp(1)
exp39 = np.exp(39)
A = np.array([
[0, 1],
[-39, -40],
], dtype=float)
desired = np.array([
[
39/(38*exp1) - 1/(38*exp39),
-np.expm1(-38) / (38*exp1)],
[
39*np.expm1(-38) / (38*exp1),
-1/(38*exp1) + 39/(38*exp39)],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_4(self):
# This example is due to Moler and Van Loan.
# The example will cause problems for the series summation approach,
# as well as for diagonal Pade approximations.
A = np.array([
[-49, 24],
[-64, 31],
], dtype=float)
U = np.array([[3, 1], [4, 2]], dtype=float)
V = np.array([[1, -1/2], [-2, 3/2]], dtype=float)
w = np.array([-17, -1], dtype=float)
desired = np.dot(U * np.exp(w), V)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_5(self):
# This example is due to Moler and Van Loan.
# This matrix is strictly upper triangular
# All powers of A are zero beyond some (low) limit.
# This example will cause problems for Pade approximations.
A = np.array([
[0, 6, 0, 0],
[0, 0, 6, 0],
[0, 0, 0, 6],
[0, 0, 0, 0],
], dtype=float)
desired = np.array([
[1, 6, 18, 36],
[0, 1, 6, 18],
[0, 0, 1, 6],
[0, 0, 0, 1],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_6(self):
# This example is due to Moler and Van Loan.
# This matrix does not have a complete set of eigenvectors.
# That means the eigenvector approach will fail.
exp1 = np.exp(1)
A = np.array([
[1, 1],
[0, 1],
], dtype=float)
desired = np.array([
[exp1, exp1],
[0, exp1],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_7(self):
# This example is due to Moler and Van Loan.
# This matrix is very close to example 5.
# Mathematically, it has a complete set of eigenvectors.
# Numerically, however, the calculation will be suspect.
exp1 = np.exp(1)
eps = np.spacing(1)
A = np.array([
[1 + eps, 1],
[0, 1 - eps],
], dtype=float)
desired = np.array([
[exp1, exp1],
[0, exp1],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_8(self):
# This matrix was an example in Wikipedia.
exp4 = np.exp(4)
exp16 = np.exp(16)
A = np.array([
[21, 17, 6],
[-5, -1, -6],
[4, 4, 16],
], dtype=float)
desired = np.array([
[13*exp16 - exp4, 13*exp16 - 5*exp4, 2*exp16 - 2*exp4],
[-9*exp16 + exp4, -9*exp16 + 5*exp4, -2*exp16 + 2*exp4],
[16*exp16, 16*exp16, 4*exp16],
], dtype=float) * 0.25
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_9(self):
# This matrix is due to the NAG Library.
# It is an example for function F01ECF.
A = np.array([
[1, 2, 2, 2],
[3, 1, 1, 2],
[3, 2, 1, 2],
[3, 3, 3, 1],
], dtype=float)
desired = np.array([
[740.7038, 610.8500, 542.2743, 549.1753],
[731.2510, 603.5524, 535.0884, 542.2743],
[823.7630, 679.4257, 603.5524, 610.8500],
[998.4355, 823.7630, 731.2510, 740.7038],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_10(self):
# This is Ward's example #1.
# It is defective and nonderogatory.
A = np.array([
[4, 2, 0],
[1, 4, 1],
[1, 1, 4],
], dtype=float)
assert_allclose(sorted(scipy.linalg.eigvals(A)), (3, 3, 6))
desired = np.array([
[147.8666224463699, 183.7651386463682, 71.79703239999647],
[127.7810855231823, 183.7651386463682, 91.88256932318415],
[127.7810855231824, 163.6796017231806, 111.9681062463718],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_11(self):
# This is Ward's example #2.
# It is a symmetric matrix.
A = np.array([
[29.87942128909879, 0.7815750847907159, -2.289519314033932],
[0.7815750847907159, 25.72656945571064, 8.680737820540137],
[-2.289519314033932, 8.680737820540137, 34.39400925519054],
], dtype=float)
assert_allclose(scipy.linalg.eigvalsh(A), (20, 30, 40))
desired = np.array([
[
5.496313853692378E+15,
-1.823188097200898E+16,
-3.047577080858001E+16],
[
-1.823188097200899E+16,
6.060522870222108E+16,
1.012918429302482E+17],
[
-3.047577080858001E+16,
1.012918429302482E+17,
1.692944112408493E+17],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_12(self):
# This is Ward's example #3.
# Ward's algorithm has difficulty estimating the accuracy
# of its results.
A = np.array([
[-131, 19, 18],
[-390, 56, 54],
[-387, 57, 52],
], dtype=float)
assert_allclose(sorted(scipy.linalg.eigvals(A)), (-20, -2, -1))
desired = np.array([
[-1.509644158793135, 0.3678794391096522, 0.1353352811751005],
[-5.632570799891469, 1.471517758499875, 0.4060058435250609],
[-4.934938326088363, 1.103638317328798, 0.5413411267617766],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_burkardt_13(self):
# This is Ward's example #4.
# This is a version of the Forsythe matrix.
# The eigenvector problem is badly conditioned.
# Ward's algorithm has difficulty estimating the accuracy
# of its results for this problem.
#
# Check the construction of one instance of this family of matrices.
A4_actual = _burkardt_13_power(4, 1)
A4_desired = [[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
[1e-4, 0, 0, 0]]
assert_allclose(A4_actual, A4_desired)
# Check the expm for a few instances.
for n in (2, 3, 4, 10):
# Approximate expm using Taylor series.
# This works well for this matrix family
# because each matrix in the summation,
# even before dividing by the factorial,
# is entrywise positive with max entry 10**(-floor(p/n)*n).
k = max(1, int(np.ceil(16/n)))
desired = np.zeros((n, n), dtype=float)
for p in range(n*k):
Ap = _burkardt_13_power(n, p)
assert_equal(np.min(Ap), 0)
assert_allclose(np.max(Ap), np.power(10, -np.floor(p/n)*n))
desired += Ap / factorial(p)
actual = expm(_burkardt_13_power(n, 1))
assert_allclose(actual, desired)
def test_burkardt_14(self):
# This is Moler's example.
# This badly scaled matrix caused problems for MATLAB's expm().
A = np.array([
[0, 1e-8, 0],
[-(2e10 + 4e8/6.), -3, 2e10],
[200./3., 0, -200./3.],
], dtype=float)
desired = np.array([
[0.446849468283175, 1.54044157383952e-09, 0.462811453558774],
[-5743067.77947947, -0.0152830038686819, -4526542.71278401],
[0.447722977849494, 1.54270484519591e-09, 0.463480648837651],
], dtype=float)
actual = expm(A)
assert_allclose(actual, desired)
def test_pascal(self):
# Test pascal triangle.
# Nilpotent exponential, used to trigger a failure (gh-8029)
for scale in [1.0, 1e-3, 1e-6]:
for n in range(0, 80, 3):
sc = scale ** np.arange(n, -1, -1)
if np.any(sc < 1e-300):
break
A = np.diag(np.arange(1, n + 1), -1) * scale
B = expm(A)
got = B
expected = binom(np.arange(n + 1)[:,None],
np.arange(n + 1)[None,:]) * sc[None,:] / sc[:,None]
atol = 1e-13 * abs(expected).max()
assert_allclose(got, expected, atol=atol)
def test_matrix_input(self):
# Large np.matrix inputs should work, gh-5546
A = np.zeros((200, 200))
A[-1,0] = 1
B0 = expm(A)
with suppress_warnings() as sup:
sup.filter(DeprecationWarning, "the matrix subclass.*")
sup.filter(PendingDeprecationWarning, "the matrix subclass.*")
B = expm(np.matrix(A))
assert_allclose(B, B0)
def test_exp_sinch_overflow(self):
# Check overflow in intermediate steps is fixed (gh-11839)
L = np.array([[1.0, -0.5, -0.5, 0.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, -0.5, -0.5, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0, -0.5, -0.5],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]])
E0 = expm(-L)
E1 = expm(-2**11 * L)
E2 = E0
for j in range(11):
E2 = E2 @ E2
assert_allclose(E1, E2)
class TestOperators:
def test_product_operator(self):
random.seed(1234)
n = 5
k = 2
nsamples = 10
for i in range(nsamples):
A = np.random.randn(n, n)
B = np.random.randn(n, n)
C = np.random.randn(n, n)
D = np.random.randn(n, k)
op = ProductOperator(A, B, C)
assert_allclose(op.matmat(D), A.dot(B).dot(C).dot(D))
assert_allclose(op.T.matmat(D), (A.dot(B).dot(C)).T.dot(D))
def test_matrix_power_operator(self):
random.seed(1234)
n = 5
k = 2
p = 3
nsamples = 10
for i in range(nsamples):
A = np.random.randn(n, n)
B = np.random.randn(n, k)
op = MatrixPowerOperator(A, p)
assert_allclose(op.matmat(B), np.linalg.matrix_power(A, p).dot(B))
assert_allclose(op.T.matmat(B), np.linalg.matrix_power(A, p).T.dot(B))

View File

@ -0,0 +1,141 @@
"""Test functions for the sparse.linalg.norm module
"""
import pytest
import numpy as np
from numpy.linalg import norm as npnorm
from numpy.testing import assert_allclose, assert_equal
from pytest import raises as assert_raises
import scipy.sparse
from scipy.sparse.linalg import norm as spnorm
# https://github.com/scipy/scipy/issues/16031
def test_sparray_norm():
row = np.array([0, 0, 1, 1])
col = np.array([0, 1, 2, 3])
data = np.array([4, 5, 7, 9])
test_arr = scipy.sparse.coo_array((data, (row, col)), shape=(2, 4))
test_mat = scipy.sparse.coo_matrix((data, (row, col)), shape=(2, 4))
assert_equal(spnorm(test_arr, ord=1, axis=0), np.array([4, 5, 7, 9]))
assert_equal(spnorm(test_mat, ord=1, axis=0), np.array([4, 5, 7, 9]))
assert_equal(spnorm(test_arr, ord=1, axis=1), np.array([9, 16]))
assert_equal(spnorm(test_mat, ord=1, axis=1), np.array([9, 16]))
class TestNorm:
def setup_method(self):
a = np.arange(9) - 4
b = a.reshape((3, 3))
self.b = scipy.sparse.csr_matrix(b)
def test_matrix_norm(self):
# Frobenius norm is the default
assert_allclose(spnorm(self.b), 7.745966692414834)
assert_allclose(spnorm(self.b, 'fro'), 7.745966692414834)
assert_allclose(spnorm(self.b, np.inf), 9)
assert_allclose(spnorm(self.b, -np.inf), 2)
assert_allclose(spnorm(self.b, 1), 7)
assert_allclose(spnorm(self.b, -1), 6)
# Only floating or complex floating dtype supported by svds.
with pytest.warns(UserWarning, match="The problem size"):
assert_allclose(spnorm(self.b.astype(np.float64), 2),
7.348469228349534)
# _multi_svd_norm is not implemented for sparse matrix
assert_raises(NotImplementedError, spnorm, self.b, -2)
def test_matrix_norm_axis(self):
for m, axis in ((self.b, None), (self.b, (0, 1)), (self.b.T, (1, 0))):
assert_allclose(spnorm(m, axis=axis), 7.745966692414834)
assert_allclose(spnorm(m, 'fro', axis=axis), 7.745966692414834)
assert_allclose(spnorm(m, np.inf, axis=axis), 9)
assert_allclose(spnorm(m, -np.inf, axis=axis), 2)
assert_allclose(spnorm(m, 1, axis=axis), 7)
assert_allclose(spnorm(m, -1, axis=axis), 6)
def test_vector_norm(self):
v = [4.5825756949558398, 4.2426406871192848, 4.5825756949558398]
for m, a in (self.b, 0), (self.b.T, 1):
for axis in a, (a, ), a-2, (a-2, ):
assert_allclose(spnorm(m, 1, axis=axis), [7, 6, 7])
assert_allclose(spnorm(m, np.inf, axis=axis), [4, 3, 4])
assert_allclose(spnorm(m, axis=axis), v)
assert_allclose(spnorm(m, ord=2, axis=axis), v)
assert_allclose(spnorm(m, ord=None, axis=axis), v)
def test_norm_exceptions(self):
m = self.b
assert_raises(TypeError, spnorm, m, None, 1.5)
assert_raises(TypeError, spnorm, m, None, [2])
assert_raises(ValueError, spnorm, m, None, ())
assert_raises(ValueError, spnorm, m, None, (0, 1, 2))
assert_raises(ValueError, spnorm, m, None, (0, 0))
assert_raises(ValueError, spnorm, m, None, (0, 2))
assert_raises(ValueError, spnorm, m, None, (-3, 0))
assert_raises(ValueError, spnorm, m, None, 2)
assert_raises(ValueError, spnorm, m, None, -3)
assert_raises(ValueError, spnorm, m, 'plate_of_shrimp', 0)
assert_raises(ValueError, spnorm, m, 'plate_of_shrimp', (0, 1))
class TestVsNumpyNorm:
_sparse_types = (
scipy.sparse.bsr_matrix,
scipy.sparse.coo_matrix,
scipy.sparse.csc_matrix,
scipy.sparse.csr_matrix,
scipy.sparse.dia_matrix,
scipy.sparse.dok_matrix,
scipy.sparse.lil_matrix,
)
_test_matrices = (
(np.arange(9) - 4).reshape((3, 3)),
[
[1, 2, 3],
[-1, 1, 4]],
[
[1, 0, 3],
[-1, 1, 4j]],
)
def test_sparse_matrix_norms(self):
for sparse_type in self._sparse_types:
for M in self._test_matrices:
S = sparse_type(M)
assert_allclose(spnorm(S), npnorm(M))
assert_allclose(spnorm(S, 'fro'), npnorm(M, 'fro'))
assert_allclose(spnorm(S, np.inf), npnorm(M, np.inf))
assert_allclose(spnorm(S, -np.inf), npnorm(M, -np.inf))
assert_allclose(spnorm(S, 1), npnorm(M, 1))
assert_allclose(spnorm(S, -1), npnorm(M, -1))
def test_sparse_matrix_norms_with_axis(self):
for sparse_type in self._sparse_types:
for M in self._test_matrices:
S = sparse_type(M)
for axis in None, (0, 1), (1, 0):
assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis))
for ord in 'fro', np.inf, -np.inf, 1, -1:
assert_allclose(spnorm(S, ord, axis=axis),
npnorm(M, ord, axis=axis))
# Some numpy matrix norms are allergic to negative axes.
for axis in (-2, -1), (-1, -2), (1, -2):
assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis))
assert_allclose(spnorm(S, 'f', axis=axis),
npnorm(M, 'f', axis=axis))
assert_allclose(spnorm(S, 'fro', axis=axis),
npnorm(M, 'fro', axis=axis))
def test_sparse_vector_norms(self):
for sparse_type in self._sparse_types:
for M in self._test_matrices:
S = sparse_type(M)
for axis in (0, 1, -1, -2, (0, ), (1, ), (-1, ), (-2, )):
assert_allclose(spnorm(S, axis=axis), npnorm(M, axis=axis))
for ord in None, 2, np.inf, -np.inf, 1, 0.5, 0.42:
assert_allclose(spnorm(S, ord, axis=axis),
npnorm(M, ord, axis=axis))

View File

@ -0,0 +1,252 @@
"""Test functions for the sparse.linalg._onenormest module
"""
import numpy as np
from numpy.testing import assert_allclose, assert_equal, assert_
import pytest
import scipy.linalg
import scipy.sparse.linalg
from scipy.sparse.linalg._onenormest import _onenormest_core, _algorithm_2_2
class MatrixProductOperator(scipy.sparse.linalg.LinearOperator):
"""
This is purely for onenormest testing.
"""
def __init__(self, A, B):
if A.ndim != 2 or B.ndim != 2:
raise ValueError('expected ndarrays representing matrices')
if A.shape[1] != B.shape[0]:
raise ValueError('incompatible shapes')
self.A = A
self.B = B
self.ndim = 2
self.shape = (A.shape[0], B.shape[1])
def _matvec(self, x):
return np.dot(self.A, np.dot(self.B, x))
def _rmatvec(self, x):
return np.dot(np.dot(x, self.A), self.B)
def _matmat(self, X):
return np.dot(self.A, np.dot(self.B, X))
@property
def T(self):
return MatrixProductOperator(self.B.T, self.A.T)
class TestOnenormest:
@pytest.mark.xslow
def test_onenormest_table_3_t_2(self):
# This will take multiple seconds if your computer is slow like mine.
# It is stochastic, so the tolerance could be too strict.
np.random.seed(1234)
t = 2
n = 100
itmax = 5
nsamples = 5000
observed = []
expected = []
nmult_list = []
nresample_list = []
for i in range(nsamples):
A = scipy.linalg.inv(np.random.randn(n, n))
est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax)
observed.append(est)
expected.append(scipy.linalg.norm(A, 1))
nmult_list.append(nmults)
nresample_list.append(nresamples)
observed = np.array(observed, dtype=float)
expected = np.array(expected, dtype=float)
relative_errors = np.abs(observed - expected) / expected
# check the mean underestimation ratio
underestimation_ratio = observed / expected
assert_(0.99 < np.mean(underestimation_ratio) < 1.0)
# check the max and mean required column resamples
assert_equal(np.max(nresample_list), 2)
assert_(0.05 < np.mean(nresample_list) < 0.2)
# check the proportion of norms computed exactly correctly
nexact = np.count_nonzero(relative_errors < 1e-14)
proportion_exact = nexact / float(nsamples)
assert_(0.9 < proportion_exact < 0.95)
# check the average number of matrix*vector multiplications
assert_(3.5 < np.mean(nmult_list) < 4.5)
@pytest.mark.xslow
def test_onenormest_table_4_t_7(self):
# This will take multiple seconds if your computer is slow like mine.
# It is stochastic, so the tolerance could be too strict.
np.random.seed(1234)
t = 7
n = 100
itmax = 5
nsamples = 5000
observed = []
expected = []
nmult_list = []
nresample_list = []
for i in range(nsamples):
A = np.random.randint(-1, 2, size=(n, n))
est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax)
observed.append(est)
expected.append(scipy.linalg.norm(A, 1))
nmult_list.append(nmults)
nresample_list.append(nresamples)
observed = np.array(observed, dtype=float)
expected = np.array(expected, dtype=float)
relative_errors = np.abs(observed - expected) / expected
# check the mean underestimation ratio
underestimation_ratio = observed / expected
assert_(0.90 < np.mean(underestimation_ratio) < 0.99)
# check the required column resamples
assert_equal(np.max(nresample_list), 0)
# check the proportion of norms computed exactly correctly
nexact = np.count_nonzero(relative_errors < 1e-14)
proportion_exact = nexact / float(nsamples)
assert_(0.15 < proportion_exact < 0.25)
# check the average number of matrix*vector multiplications
assert_(3.5 < np.mean(nmult_list) < 4.5)
def test_onenormest_table_5_t_1(self):
# "note that there is no randomness and hence only one estimate for t=1"
t = 1
n = 100
itmax = 5
alpha = 1 - 1e-6
A = -scipy.linalg.inv(np.identity(n) + alpha*np.eye(n, k=1))
first_col = np.array([1] + [0]*(n-1))
first_row = np.array([(-alpha)**i for i in range(n)])
B = -scipy.linalg.toeplitz(first_col, first_row)
assert_allclose(A, B)
est, v, w, nmults, nresamples = _onenormest_core(B, B.T, t, itmax)
exact_value = scipy.linalg.norm(B, 1)
underest_ratio = est / exact_value
assert_allclose(underest_ratio, 0.05, rtol=1e-4)
assert_equal(nmults, 11)
assert_equal(nresamples, 0)
# check the non-underscored version of onenormest
est_plain = scipy.sparse.linalg.onenormest(B, t=t, itmax=itmax)
assert_allclose(est, est_plain)
@pytest.mark.xslow
def test_onenormest_table_6_t_1(self):
#TODO this test seems to give estimates that match the table,
#TODO even though no attempt has been made to deal with
#TODO complex numbers in the one-norm estimation.
# This will take multiple seconds if your computer is slow like mine.
# It is stochastic, so the tolerance could be too strict.
np.random.seed(1234)
t = 1
n = 100
itmax = 5
nsamples = 5000
observed = []
expected = []
nmult_list = []
nresample_list = []
for i in range(nsamples):
A_inv = np.random.rand(n, n) + 1j * np.random.rand(n, n)
A = scipy.linalg.inv(A_inv)
est, v, w, nmults, nresamples = _onenormest_core(A, A.T, t, itmax)
observed.append(est)
expected.append(scipy.linalg.norm(A, 1))
nmult_list.append(nmults)
nresample_list.append(nresamples)
observed = np.array(observed, dtype=float)
expected = np.array(expected, dtype=float)
relative_errors = np.abs(observed - expected) / expected
# check the mean underestimation ratio
underestimation_ratio = observed / expected
underestimation_ratio_mean = np.mean(underestimation_ratio)
assert_(0.90 < underestimation_ratio_mean < 0.99)
# check the required column resamples
max_nresamples = np.max(nresample_list)
assert_equal(max_nresamples, 0)
# check the proportion of norms computed exactly correctly
nexact = np.count_nonzero(relative_errors < 1e-14)
proportion_exact = nexact / float(nsamples)
assert_(0.7 < proportion_exact < 0.8)
# check the average number of matrix*vector multiplications
mean_nmult = np.mean(nmult_list)
assert_(4 < mean_nmult < 5)
def _help_product_norm_slow(self, A, B):
# for profiling
C = np.dot(A, B)
return scipy.linalg.norm(C, 1)
def _help_product_norm_fast(self, A, B):
# for profiling
t = 2
itmax = 5
D = MatrixProductOperator(A, B)
est, v, w, nmults, nresamples = _onenormest_core(D, D.T, t, itmax)
return est
@pytest.mark.slow
def test_onenormest_linear_operator(self):
# Define a matrix through its product A B.
# Depending on the shapes of A and B,
# it could be easy to multiply this product by a small matrix,
# but it could be annoying to look at all of
# the entries of the product explicitly.
np.random.seed(1234)
n = 6000
k = 3
A = np.random.randn(n, k)
B = np.random.randn(k, n)
fast_estimate = self._help_product_norm_fast(A, B)
exact_value = self._help_product_norm_slow(A, B)
assert_(fast_estimate <= exact_value <= 3*fast_estimate,
f'fast: {fast_estimate:g}\nexact:{exact_value:g}')
def test_returns(self):
np.random.seed(1234)
A = scipy.sparse.rand(50, 50, 0.1)
s0 = scipy.linalg.norm(A.toarray(), 1)
s1, v = scipy.sparse.linalg.onenormest(A, compute_v=True)
s2, w = scipy.sparse.linalg.onenormest(A, compute_w=True)
s3, v2, w2 = scipy.sparse.linalg.onenormest(A, compute_w=True, compute_v=True)
assert_allclose(s1, s0, rtol=1e-9)
assert_allclose(np.linalg.norm(A.dot(v), 1), s0*np.linalg.norm(v, 1), rtol=1e-9)
assert_allclose(A.dot(v), w, rtol=1e-9)
class TestAlgorithm_2_2:
def test_randn_inv(self):
np.random.seed(1234)
n = 20
nsamples = 100
for i in range(nsamples):
# Choose integer t uniformly between 1 and 3 inclusive.
t = np.random.randint(1, 4)
# Choose n uniformly between 10 and 40 inclusive.
n = np.random.randint(10, 41)
# Sample the inverse of a matrix with random normal entries.
A = scipy.linalg.inv(np.random.randn(n, n))
# Compute the 1-norm bounds.
g, ind = _algorithm_2_2(A, A.T, t)

View File

@ -0,0 +1,166 @@
import os
import pytest
import numpy as np
from numpy.testing import assert_allclose
from pytest import raises as assert_raises
from scipy.sparse.linalg._svdp import _svdp
from scipy.sparse import csr_matrix, csc_matrix
# dtype_flavour to tolerance
TOLS = {
np.float32: 1e-4,
np.float64: 1e-8,
np.complex64: 1e-4,
np.complex128: 1e-8,
}
def is_complex_type(dtype):
return np.dtype(dtype).kind == "c"
_dtypes = []
for dtype_flavour in TOLS.keys():
marks = []
if is_complex_type(dtype_flavour):
marks = [pytest.mark.slow]
_dtypes.append(pytest.param(dtype_flavour, marks=marks,
id=dtype_flavour.__name__))
_dtypes = tuple(_dtypes) # type: ignore[assignment]
def generate_matrix(constructor, n, m, f,
dtype=float, rseed=0, **kwargs):
"""Generate a random sparse matrix"""
rng = np.random.RandomState(rseed)
if is_complex_type(dtype):
M = (- 5 + 10 * rng.rand(n, m)
- 5j + 10j * rng.rand(n, m)).astype(dtype)
else:
M = (-5 + 10 * rng.rand(n, m)).astype(dtype)
M[M.real > 10 * f - 5] = 0
return constructor(M, **kwargs)
def assert_orthogonal(u1, u2, rtol, atol):
"""Check that the first k rows of u1 and u2 are orthogonal"""
A = abs(np.dot(u1.conj().T, u2))
assert_allclose(A, np.eye(u1.shape[1], u2.shape[1]), rtol=rtol, atol=atol)
def check_svdp(n, m, constructor, dtype, k, irl_mode, which, f=0.8):
tol = TOLS[dtype]
M = generate_matrix(np.asarray, n, m, f, dtype)
Msp = constructor(M)
u1, sigma1, vt1 = np.linalg.svd(M, full_matrices=False)
u2, sigma2, vt2, _ = _svdp(Msp, k=k, which=which, irl_mode=irl_mode,
tol=tol)
# check the which
if which.upper() == 'SM':
u1 = np.roll(u1, k, 1)
vt1 = np.roll(vt1, k, 0)
sigma1 = np.roll(sigma1, k)
# check that singular values agree
assert_allclose(sigma1[:k], sigma2, rtol=tol, atol=tol)
# check that singular vectors are orthogonal
assert_orthogonal(u1, u2, rtol=tol, atol=tol)
assert_orthogonal(vt1.T, vt2.T, rtol=tol, atol=tol)
@pytest.mark.parametrize('ctor', (np.array, csr_matrix, csc_matrix))
@pytest.mark.parametrize('dtype', _dtypes)
@pytest.mark.parametrize('irl', (True, False))
@pytest.mark.parametrize('which', ('LM', 'SM'))
def test_svdp(ctor, dtype, irl, which):
np.random.seed(0)
n, m, k = 10, 20, 3
if which == 'SM' and not irl:
message = "`which`='SM' requires irl_mode=True"
with assert_raises(ValueError, match=message):
check_svdp(n, m, ctor, dtype, k, irl, which)
else:
check_svdp(n, m, ctor, dtype, k, irl, which)
@pytest.mark.xslow
@pytest.mark.parametrize('dtype', _dtypes)
@pytest.mark.parametrize('irl', (False, True))
@pytest.mark.timeout(120) # True, complex64 > 60 s: prerel deps cov 64bit blas
def test_examples(dtype, irl):
# Note: atol for complex64 bumped from 1e-4 to 1e-3 due to test failures
# with BLIS, Netlib, and MKL+AVX512 - see
# https://github.com/conda-forge/scipy-feedstock/pull/198#issuecomment-999180432
atol = {
np.float32: 1.3e-4,
np.float64: 1e-9,
np.complex64: 1e-3,
np.complex128: 1e-9,
}[dtype]
path_prefix = os.path.dirname(__file__)
# Test matrices from `illc1850.coord` and `mhd1280b.cua` distributed with
# PROPACK 2.1: http://sun.stanford.edu/~rmunk/PROPACK/
relative_path = "propack_test_data.npz"
filename = os.path.join(path_prefix, relative_path)
with np.load(filename, allow_pickle=True) as data:
if is_complex_type(dtype):
A = data['A_complex'].item().astype(dtype)
else:
A = data['A_real'].item().astype(dtype)
k = 200
u, s, vh, _ = _svdp(A, k, irl_mode=irl, random_state=0)
# complex example matrix has many repeated singular values, so check only
# beginning non-repeated singular vectors to avoid permutations
sv_check = 27 if is_complex_type(dtype) else k
u = u[:, :sv_check]
vh = vh[:sv_check, :]
s = s[:sv_check]
# Check orthogonality of singular vectors
assert_allclose(np.eye(u.shape[1]), u.conj().T @ u, atol=atol)
assert_allclose(np.eye(vh.shape[0]), vh @ vh.conj().T, atol=atol)
# Ensure the norm of the difference between the np.linalg.svd and
# PROPACK reconstructed matrices is small
u3, s3, vh3 = np.linalg.svd(A.todense())
u3 = u3[:, :sv_check]
s3 = s3[:sv_check]
vh3 = vh3[:sv_check, :]
A3 = u3 @ np.diag(s3) @ vh3
recon = u @ np.diag(s) @ vh
assert_allclose(np.linalg.norm(A3 - recon), 0, atol=atol)
@pytest.mark.parametrize('shifts', (None, -10, 0, 1, 10, 70))
@pytest.mark.parametrize('dtype', _dtypes[:2])
def test_shifts(shifts, dtype):
np.random.seed(0)
n, k = 70, 10
A = np.random.random((n, n))
if shifts is not None and ((shifts < 0) or (k > min(n-1-shifts, n))):
with pytest.raises(ValueError):
_svdp(A, k, shifts=shifts, kmax=5*k, irl_mode=True)
else:
_svdp(A, k, shifts=shifts, kmax=5*k, irl_mode=True)
@pytest.mark.slow
@pytest.mark.xfail()
def test_shifts_accuracy():
np.random.seed(0)
n, k = 70, 10
A = np.random.random((n, n)).astype(np.float64)
u1, s1, vt1, _ = _svdp(A, k, shifts=None, which='SM', irl_mode=True)
u2, s2, vt2, _ = _svdp(A, k, shifts=32, which='SM', irl_mode=True)
# shifts <= 32 doesn't agree with shifts > 32
# Does agree when which='LM' instead of 'SM'
assert_allclose(s1, s2)

View File

@ -0,0 +1,243 @@
import pytest
import numpy as np
import scipy.sparse as sp
import scipy.sparse.linalg as splin
from numpy.testing import assert_allclose, assert_equal
try:
import sparse
except Exception:
sparse = None
pytestmark = pytest.mark.skipif(sparse is None,
reason="pydata/sparse not installed")
msg = "pydata/sparse (0.15.1) does not implement necessary operations"
sparse_params = (pytest.param("COO"),
pytest.param("DOK", marks=[pytest.mark.xfail(reason=msg)]))
scipy_sparse_classes = [
sp.bsr_matrix,
sp.csr_matrix,
sp.coo_matrix,
sp.csc_matrix,
sp.dia_matrix,
sp.dok_matrix
]
@pytest.fixture(params=sparse_params)
def sparse_cls(request):
return getattr(sparse, request.param)
@pytest.fixture(params=scipy_sparse_classes)
def sp_sparse_cls(request):
return request.param
@pytest.fixture
def same_matrix(sparse_cls, sp_sparse_cls):
np.random.seed(1234)
A_dense = np.random.rand(9, 9)
return sp_sparse_cls(A_dense), sparse_cls(A_dense)
@pytest.fixture
def matrices(sparse_cls):
np.random.seed(1234)
A_dense = np.random.rand(9, 9)
A_dense = A_dense @ A_dense.T
A_sparse = sparse_cls(A_dense)
b = np.random.rand(9)
return A_dense, A_sparse, b
def test_isolve_gmres(matrices):
# Several of the iterative solvers use the same
# isolve.utils.make_system wrapper code, so test just one of them.
A_dense, A_sparse, b = matrices
x, info = splin.gmres(A_sparse, b, atol=1e-15)
assert info == 0
assert isinstance(x, np.ndarray)
assert_allclose(A_sparse @ x, b)
def test_lsmr(matrices):
A_dense, A_sparse, b = matrices
res0 = splin.lsmr(A_dense, b)
res = splin.lsmr(A_sparse, b)
assert_allclose(res[0], res0[0], atol=1e-3)
# test issue 17012
def test_lsmr_output_shape():
x = splin.lsmr(A=np.ones((10, 1)), b=np.zeros(10), x0=np.ones(1))[0]
assert_equal(x.shape, (1,))
def test_lsqr(matrices):
A_dense, A_sparse, b = matrices
res0 = splin.lsqr(A_dense, b)
res = splin.lsqr(A_sparse, b)
assert_allclose(res[0], res0[0], atol=1e-5)
def test_eigs(matrices):
A_dense, A_sparse, v0 = matrices
M_dense = np.diag(v0**2)
M_sparse = A_sparse.__class__(M_dense)
w_dense, v_dense = splin.eigs(A_dense, k=3, v0=v0)
w, v = splin.eigs(A_sparse, k=3, v0=v0)
assert_allclose(w, w_dense)
assert_allclose(v, v_dense)
for M in [M_sparse, M_dense]:
w_dense, v_dense = splin.eigs(A_dense, M=M_dense, k=3, v0=v0)
w, v = splin.eigs(A_sparse, M=M, k=3, v0=v0)
assert_allclose(w, w_dense)
assert_allclose(v, v_dense)
w_dense, v_dense = splin.eigsh(A_dense, M=M_dense, k=3, v0=v0)
w, v = splin.eigsh(A_sparse, M=M, k=3, v0=v0)
assert_allclose(w, w_dense)
assert_allclose(v, v_dense)
def test_svds(matrices):
A_dense, A_sparse, v0 = matrices
u0, s0, vt0 = splin.svds(A_dense, k=2, v0=v0)
u, s, vt = splin.svds(A_sparse, k=2, v0=v0)
assert_allclose(s, s0)
assert_allclose(np.abs(u), np.abs(u0))
assert_allclose(np.abs(vt), np.abs(vt0))
def test_lobpcg(matrices):
A_dense, A_sparse, x = matrices
X = x[:,None]
w_dense, v_dense = splin.lobpcg(A_dense, X)
w, v = splin.lobpcg(A_sparse, X)
assert_allclose(w, w_dense)
assert_allclose(v, v_dense)
def test_spsolve(matrices):
A_dense, A_sparse, b = matrices
b2 = np.random.rand(len(b), 3)
x0 = splin.spsolve(sp.csc_matrix(A_dense), b)
x = splin.spsolve(A_sparse, b)
assert isinstance(x, np.ndarray)
assert_allclose(x, x0)
x0 = splin.spsolve(sp.csc_matrix(A_dense), b)
x = splin.spsolve(A_sparse, b, use_umfpack=True)
assert isinstance(x, np.ndarray)
assert_allclose(x, x0)
x0 = splin.spsolve(sp.csc_matrix(A_dense), b2)
x = splin.spsolve(A_sparse, b2)
assert isinstance(x, np.ndarray)
assert_allclose(x, x0)
x0 = splin.spsolve(sp.csc_matrix(A_dense),
sp.csc_matrix(A_dense))
x = splin.spsolve(A_sparse, A_sparse)
assert isinstance(x, type(A_sparse))
assert_allclose(x.todense(), x0.todense())
def test_splu(matrices):
A_dense, A_sparse, b = matrices
n = len(b)
sparse_cls = type(A_sparse)
lu = splin.splu(A_sparse)
assert isinstance(lu.L, sparse_cls)
assert isinstance(lu.U, sparse_cls)
_Pr_scipy = sp.csc_matrix((np.ones(n), (lu.perm_r, np.arange(n))))
_Pc_scipy = sp.csc_matrix((np.ones(n), (np.arange(n), lu.perm_c)))
Pr = sparse_cls.from_scipy_sparse(_Pr_scipy)
Pc = sparse_cls.from_scipy_sparse(_Pc_scipy)
A2 = Pr.T @ lu.L @ lu.U @ Pc.T
assert_allclose(A2.todense(), A_sparse.todense())
z = lu.solve(A_sparse.todense())
assert_allclose(z, np.eye(n), atol=1e-10)
def test_spilu(matrices):
A_dense, A_sparse, b = matrices
sparse_cls = type(A_sparse)
lu = splin.spilu(A_sparse)
assert isinstance(lu.L, sparse_cls)
assert isinstance(lu.U, sparse_cls)
z = lu.solve(A_sparse.todense())
assert_allclose(z, np.eye(len(b)), atol=1e-3)
def test_spsolve_triangular(matrices):
A_dense, A_sparse, b = matrices
A_sparse = sparse.tril(A_sparse)
x = splin.spsolve_triangular(A_sparse, b)
assert_allclose(A_sparse @ x, b)
def test_onenormest(matrices):
A_dense, A_sparse, b = matrices
est0 = splin.onenormest(A_dense)
est = splin.onenormest(A_sparse)
assert_allclose(est, est0)
def test_inv(matrices):
A_dense, A_sparse, b = matrices
x0 = splin.inv(sp.csc_matrix(A_dense))
x = splin.inv(A_sparse)
assert_allclose(x.todense(), x0.todense())
def test_expm(matrices):
A_dense, A_sparse, b = matrices
x0 = splin.expm(sp.csc_matrix(A_dense))
x = splin.expm(A_sparse)
assert_allclose(x.todense(), x0.todense())
def test_expm_multiply(matrices):
A_dense, A_sparse, b = matrices
x0 = splin.expm_multiply(A_dense, b)
x = splin.expm_multiply(A_sparse, b)
assert_allclose(x, x0)
def test_eq(same_matrix):
sp_sparse, pd_sparse = same_matrix
assert (sp_sparse == pd_sparse).all()
def test_ne(same_matrix):
sp_sparse, pd_sparse = same_matrix
assert not (sp_sparse != pd_sparse).any()

View File

@ -0,0 +1,337 @@
import pytest
import numpy as np
from numpy.testing import assert_array_equal, assert_allclose
from scipy.sparse import diags, csgraph
from scipy.linalg import eigh
from scipy.sparse.linalg import LaplacianNd
from scipy.sparse.linalg._special_sparse_arrays import Sakurai
from scipy.sparse.linalg._special_sparse_arrays import MikotaPair
INT_DTYPES = [np.int8, np.int16, np.int32, np.int64]
REAL_DTYPES = [np.float32, np.float64]
COMPLEX_DTYPES = [np.complex64, np.complex128]
ALLDTYPES = INT_DTYPES + REAL_DTYPES + COMPLEX_DTYPES
class TestLaplacianNd:
"""
LaplacianNd tests
"""
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_1d_specific_shape(self, bc):
lap = LaplacianNd(grid_shape=(6, ), boundary_conditions=bc)
lapa = lap.toarray()
if bc == 'neumann':
a = np.array(
[
[-1, 1, 0, 0, 0, 0],
[1, -2, 1, 0, 0, 0],
[0, 1, -2, 1, 0, 0],
[0, 0, 1, -2, 1, 0],
[0, 0, 0, 1, -2, 1],
[0, 0, 0, 0, 1, -1],
]
)
elif bc == 'dirichlet':
a = np.array(
[
[-2, 1, 0, 0, 0, 0],
[1, -2, 1, 0, 0, 0],
[0, 1, -2, 1, 0, 0],
[0, 0, 1, -2, 1, 0],
[0, 0, 0, 1, -2, 1],
[0, 0, 0, 0, 1, -2],
]
)
else:
a = np.array(
[
[-2, 1, 0, 0, 0, 1],
[1, -2, 1, 0, 0, 0],
[0, 1, -2, 1, 0, 0],
[0, 0, 1, -2, 1, 0],
[0, 0, 0, 1, -2, 1],
[1, 0, 0, 0, 1, -2],
]
)
assert_array_equal(a, lapa)
def test_1d_with_graph_laplacian(self):
n = 6
G = diags(np.ones(n - 1), 1, format='dia')
Lf = csgraph.laplacian(G, symmetrized=True, form='function')
La = csgraph.laplacian(G, symmetrized=True, form='array')
grid_shape = (n,)
bc = 'neumann'
lap = LaplacianNd(grid_shape, boundary_conditions=bc)
assert_array_equal(lap(np.eye(n)), -Lf(np.eye(n)))
assert_array_equal(lap.toarray(), -La.toarray())
# https://github.com/numpy/numpy/issues/24351
assert_array_equal(lap.tosparse().toarray(), -La.toarray())
@pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)])
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_eigenvalues(self, grid_shape, bc):
lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=np.float64)
L = lap.toarray()
eigvals = eigh(L, eigvals_only=True)
n = np.prod(grid_shape)
eigenvalues = lap.eigenvalues()
dtype = eigenvalues.dtype
atol = n * n * np.finfo(dtype).eps
# test the default ``m = None``
assert_allclose(eigenvalues, eigvals, atol=atol)
# test every ``m > 0``
for m in np.arange(1, n + 1):
assert_array_equal(lap.eigenvalues(m), eigenvalues[-m:])
@pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)])
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_eigenvectors(self, grid_shape, bc):
lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=np.float64)
n = np.prod(grid_shape)
eigenvalues = lap.eigenvalues()
eigenvectors = lap.eigenvectors()
dtype = eigenvectors.dtype
atol = n * n * max(np.finfo(dtype).eps, np.finfo(np.double).eps)
# test the default ``m = None`` every individual eigenvector
for i in np.arange(n):
r = lap.toarray() @ eigenvectors[:, i] - eigenvectors[:, i] * eigenvalues[i]
assert_allclose(r, np.zeros_like(r), atol=atol)
# test every ``m > 0``
for m in np.arange(1, n + 1):
e = lap.eigenvalues(m)
ev = lap.eigenvectors(m)
r = lap.toarray() @ ev - ev @ np.diag(e)
assert_allclose(r, np.zeros_like(r), atol=atol)
@pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)])
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_toarray_tosparse_consistency(self, grid_shape, bc):
lap = LaplacianNd(grid_shape, boundary_conditions=bc)
n = np.prod(grid_shape)
assert_array_equal(lap.toarray(), lap(np.eye(n)))
assert_array_equal(lap.tosparse().toarray(), lap.toarray())
@pytest.mark.parametrize('dtype', ALLDTYPES)
@pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)])
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_linearoperator_shape_dtype(self, grid_shape, bc, dtype):
lap = LaplacianNd(grid_shape, boundary_conditions=bc, dtype=dtype)
n = np.prod(grid_shape)
assert lap.shape == (n, n)
assert lap.dtype == dtype
assert_array_equal(
LaplacianNd(
grid_shape, boundary_conditions=bc, dtype=dtype
).toarray(),
LaplacianNd(grid_shape, boundary_conditions=bc)
.toarray()
.astype(dtype),
)
assert_array_equal(
LaplacianNd(grid_shape, boundary_conditions=bc, dtype=dtype)
.tosparse()
.toarray(),
LaplacianNd(grid_shape, boundary_conditions=bc)
.tosparse()
.toarray()
.astype(dtype),
)
@pytest.mark.parametrize('dtype', ALLDTYPES)
@pytest.mark.parametrize('grid_shape', [(6, ), (2, 3), (2, 3, 4)])
@pytest.mark.parametrize('bc', ['neumann', 'dirichlet', 'periodic'])
def test_dot(self, grid_shape, bc, dtype):
""" Test the dot-product for type preservation and consistency.
"""
lap = LaplacianNd(grid_shape, boundary_conditions=bc)
n = np.prod(grid_shape)
x0 = np.arange(n)
x1 = x0.reshape((-1, 1))
x2 = np.arange(2 * n).reshape((n, 2))
input_set = [x0, x1, x2]
for x in input_set:
y = lap.dot(x.astype(dtype))
assert x.shape == y.shape
assert y.dtype == dtype
if x.ndim == 2:
yy = lap.toarray() @ x.astype(dtype)
assert yy.dtype == dtype
np.array_equal(y, yy)
def test_boundary_conditions_value_error(self):
with pytest.raises(ValueError, match="Unknown value 'robin'"):
LaplacianNd(grid_shape=(6, ), boundary_conditions='robin')
class TestSakurai:
"""
Sakurai tests
"""
def test_specific_shape(self):
sak = Sakurai(6)
assert_array_equal(sak.toarray(), sak(np.eye(6)))
a = np.array(
[
[ 5, -4, 1, 0, 0, 0],
[-4, 6, -4, 1, 0, 0],
[ 1, -4, 6, -4, 1, 0],
[ 0, 1, -4, 6, -4, 1],
[ 0, 0, 1, -4, 6, -4],
[ 0, 0, 0, 1, -4, 5]
]
)
np.array_equal(a, sak.toarray())
np.array_equal(sak.tosparse().toarray(), sak.toarray())
ab = np.array(
[
[ 1, 1, 1, 1, 1, 1],
[-4, -4, -4, -4, -4, -4],
[ 5, 6, 6, 6, 6, 5]
]
)
np.array_equal(ab, sak.tobanded())
e = np.array(
[0.03922866, 0.56703972, 2.41789479, 5.97822974,
10.54287655, 14.45473055]
)
np.array_equal(e, sak.eigenvalues())
np.array_equal(e[:2], sak.eigenvalues(2))
# `Sakurai` default `dtype` is `np.int8` as its entries are small integers
@pytest.mark.parametrize('dtype', ALLDTYPES)
def test_linearoperator_shape_dtype(self, dtype):
n = 7
sak = Sakurai(n, dtype=dtype)
assert sak.shape == (n, n)
assert sak.dtype == dtype
assert_array_equal(sak.toarray(), Sakurai(n).toarray().astype(dtype))
assert_array_equal(sak.tosparse().toarray(),
Sakurai(n).tosparse().toarray().astype(dtype))
@pytest.mark.parametrize('dtype', ALLDTYPES)
@pytest.mark.parametrize('argument_dtype', ALLDTYPES)
def test_dot(self, dtype, argument_dtype):
""" Test the dot-product for type preservation and consistency.
"""
result_dtype = np.promote_types(argument_dtype, dtype)
n = 5
sak = Sakurai(n)
x0 = np.arange(n)
x1 = x0.reshape((-1, 1))
x2 = np.arange(2 * n).reshape((n, 2))
input_set = [x0, x1, x2]
for x in input_set:
y = sak.dot(x.astype(argument_dtype))
assert x.shape == y.shape
assert np.can_cast(y.dtype, result_dtype)
if x.ndim == 2:
ya = sak.toarray() @ x.astype(argument_dtype)
np.array_equal(y, ya)
assert np.can_cast(ya.dtype, result_dtype)
ys = sak.tosparse() @ x.astype(argument_dtype)
np.array_equal(y, ys)
assert np.can_cast(ys.dtype, result_dtype)
class TestMikotaPair:
"""
MikotaPair tests
"""
# both MikotaPair `LinearOperator`s share the same dtype
# while `MikotaK` `dtype` can be as small as its default `np.int32`
# since its entries are integers, the `MikotaM` involves inverses
# so its smallest still accurate `dtype` is `np.float32`
tested_types = REAL_DTYPES + COMPLEX_DTYPES
def test_specific_shape(self):
n = 6
mik = MikotaPair(n)
mik_k = mik.k
mik_m = mik.m
assert_array_equal(mik_k.toarray(), mik_k(np.eye(n)))
assert_array_equal(mik_m.toarray(), mik_m(np.eye(n)))
k = np.array(
[
[11, -5, 0, 0, 0, 0],
[-5, 9, -4, 0, 0, 0],
[ 0, -4, 7, -3, 0, 0],
[ 0, 0, -3, 5, -2, 0],
[ 0, 0, 0, -2, 3, -1],
[ 0, 0, 0, 0, -1, 1]
]
)
np.array_equal(k, mik_k.toarray())
np.array_equal(mik_k.tosparse().toarray(), k)
kb = np.array(
[
[ 0, -5, -4, -3, -2, -1],
[11, 9, 7, 5, 3, 1]
]
)
np.array_equal(kb, mik_k.tobanded())
minv = np.arange(1, n + 1)
np.array_equal(np.diag(1. / minv), mik_m.toarray())
np.array_equal(mik_m.tosparse().toarray(), mik_m.toarray())
np.array_equal(1. / minv, mik_m.tobanded())
e = np.array([ 1, 4, 9, 16, 25, 36])
np.array_equal(e, mik.eigenvalues())
np.array_equal(e[:2], mik.eigenvalues(2))
@pytest.mark.parametrize('dtype', tested_types)
def test_linearoperator_shape_dtype(self, dtype):
n = 7
mik = MikotaPair(n, dtype=dtype)
mik_k = mik.k
mik_m = mik.m
assert mik_k.shape == (n, n)
assert mik_k.dtype == dtype
assert mik_m.shape == (n, n)
assert mik_m.dtype == dtype
mik_default_dtype = MikotaPair(n)
mikd_k = mik_default_dtype.k
mikd_m = mik_default_dtype.m
assert mikd_k.shape == (n, n)
assert mikd_k.dtype == np.float64
assert mikd_m.shape == (n, n)
assert mikd_m.dtype == np.float64
assert_array_equal(mik_k.toarray(),
mikd_k.toarray().astype(dtype))
assert_array_equal(mik_k.tosparse().toarray(),
mikd_k.tosparse().toarray().astype(dtype))
@pytest.mark.parametrize('dtype', tested_types)
@pytest.mark.parametrize('argument_dtype', ALLDTYPES)
def test_dot(self, dtype, argument_dtype):
""" Test the dot-product for type preservation and consistency.
"""
result_dtype = np.promote_types(argument_dtype, dtype)
n = 5
mik = MikotaPair(n, dtype=dtype)
mik_k = mik.k
mik_m = mik.m
x0 = np.arange(n)
x1 = x0.reshape((-1, 1))
x2 = np.arange(2 * n).reshape((n, 2))
lo_set = [mik_k, mik_m]
input_set = [x0, x1, x2]
for lo in lo_set:
for x in input_set:
y = lo.dot(x.astype(argument_dtype))
assert x.shape == y.shape
assert np.can_cast(y.dtype, result_dtype)
if x.ndim == 2:
ya = lo.toarray() @ x.astype(argument_dtype)
np.array_equal(y, ya)
assert np.can_cast(ya.dtype, result_dtype)
ys = lo.tosparse() @ x.astype(argument_dtype)
np.array_equal(y, ys)
assert np.can_cast(ys.dtype, result_dtype)