up follow livre

This commit is contained in:
Tykayn 2025-08-30 18:14:14 +02:00 committed by tykayn
parent b4b4398bb0
commit 3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions

View file

@ -0,0 +1,23 @@
#!/usr/bin/env python3
#cython: language_level=3
#cython: boundscheck=False
#cython: wraparound=False
cimport scipy.linalg
from scipy.linalg.cython_blas cimport cdotu
from scipy.linalg.cython_lapack cimport dgtsv
cpdef tridiag(double[:] a, double[:] b, double[:] c, double[:] x):
""" Solve the system A y = x for y where A is the tridiagonal matrix with
subdiagonal 'a', diagonal 'b', and superdiagonal 'c'. """
cdef int n=b.shape[0], nrhs=1, info
# Solution is written over the values in x.
dgtsv(&n, &nrhs, &a[0], &b[0], &c[0], &x[0], &n, &info)
cpdef float complex complex_dot(float complex[:] cx, float complex[:] cy):
""" Take dot product of two complex vectors """
cdef:
int n = cx.shape[0]
int incx = cx.strides[0] // sizeof(cx[0])
int incy = cy.strides[0] // sizeof(cy[0])
return cdotu(&n, &cx[0], &incx, &cy[0], &incy)

View file

@ -0,0 +1,34 @@
project('random-build-examples', 'c', 'cpp', 'cython')
fs = import('fs')
py3 = import('python').find_installation(pure: false)
cy = meson.get_compiler('cython')
if not cy.version().version_compare('>=3.0.8')
error('tests requires Cython >= 3.0.8')
endif
cython_args = []
if cy.version().version_compare('>=3.1.0')
cython_args += ['-Xfreethreading_compatible=True']
endif
py3.extension_module(
'extending',
'extending.pyx',
install: false,
cython_args: cython_args,
c_args: ['-DCYTHON_CCOMPLEX=0'] # see gh-18975 for why we need this
)
extending_cpp = fs.copyfile('extending.pyx', 'extending_cpp.pyx')
py3.extension_module(
'extending_cpp',
extending_cpp,
install: false,
override_options : ['cython_language=cpp'],
cython_args: cython_args,
cpp_args: ['-DCYTHON_CCOMPLEX=0'] # see gh-18975 for why we need this
)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,588 @@
import inspect
import pytest
import numpy as np
from numpy.testing import assert_allclose
from scipy import linalg, sparse
real_floating = [np.float32, np.float64]
complex_floating = [np.complex64, np.complex128]
floating = real_floating + complex_floating
def get_random(shape, *, dtype, rng):
A = rng.random(shape)
if np.issubdtype(dtype, np.complexfloating):
A = A + rng.random(shape) * 1j
return A.astype(dtype)
def get_nearly_hermitian(shape, dtype, atol, rng):
# Generate a batch of nearly Hermitian matrices with specified
# `shape` and `dtype`. `atol` controls the level of noise in
# Hermitian-ness to by generated by `rng`.
A = rng.random(shape).astype(dtype)
At = np.conj(A.swapaxes(-1, -2))
noise = rng.standard_normal(size=A.shape).astype(dtype) * atol
return A + At + noise
class TestBatch:
# Test batch support for most linalg functions
def batch_test(self, fun, arrays, *, core_dim=2, n_out=1, kwargs=None, dtype=None,
broadcast=True, check_kwargs=True):
# Check that all outputs of batched call `fun(A, **kwargs)` are the same
# as if we loop over the separate vectors/matrices in `A`. Also check
# that `fun` accepts `A` by position or keyword and that results are
# identical. This is important because the name of the array argument
# is manually specified to the decorator, and it's easy to mess up.
# However, this makes it hard to test positional arguments passed
# after the array, so we test that separately for a few functions to
# make sure the decorator is working as it should.
kwargs = {} if kwargs is None else kwargs
parameters = list(inspect.signature(fun).parameters.keys())
arrays = (arrays,) if not isinstance(arrays, tuple) else arrays
# Identical results when passing argument by keyword or position
res2 = fun(*arrays, **kwargs)
if check_kwargs:
res1 = fun(**dict(zip(parameters, arrays)), **kwargs)
for out1, out2 in zip(res1, res2): # even a single array is iterable...
np.testing.assert_equal(out1, out2)
# Check results vs looping over
res = (res2,) if n_out == 1 else res2
# This is not the general behavior (only batch dimensions get
# broadcasted by the decorator) but it's easier for testing.
if broadcast:
arrays = np.broadcast_arrays(*arrays)
batch_shape = arrays[0].shape[:-core_dim]
for i in range(batch_shape[0]):
for j in range(batch_shape[1]):
arrays_ij = (array[i, j] for array in arrays)
ref = fun(*arrays_ij, **kwargs)
ref = ((np.asarray(ref),) if n_out == 1 else
tuple(np.asarray(refk) for refk in ref))
for k in range(n_out):
assert_allclose(res[k][i, j], ref[k])
assert np.shape(res[k][i, j]) == ref[k].shape
for k in range(len(ref)):
out_dtype = ref[k].dtype if dtype is None else dtype
assert res[k].dtype == out_dtype
return res2 # return original, non-tuplized result
@pytest.mark.parametrize('dtype', floating)
def test_expm_cond(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = rng.random((5, 3, 4, 4)).astype(dtype)
self.batch_test(linalg.expm_cond, A)
@pytest.mark.parametrize('dtype', floating)
def test_issymmetric(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_nearly_hermitian((5, 3, 4, 4), dtype, 3e-4, rng)
res = self.batch_test(linalg.issymmetric, A, kwargs=dict(atol=1e-3))
assert not np.all(res) # ensure test is not trivial: not all True or False;
assert np.any(res) # also confirms that `atol` is passed to issymmetric
@pytest.mark.parametrize('dtype', floating)
def test_ishermitian(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_nearly_hermitian((5, 3, 4, 4), dtype, 3e-4, rng)
res = self.batch_test(linalg.ishermitian, A, kwargs=dict(atol=1e-3))
assert not np.all(res) # ensure test is not trivial: not all True or False;
assert np.any(res) # also confirms that `atol` is passed to ishermitian
@pytest.mark.parametrize('dtype', floating)
def test_diagsvd(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = rng.random((5, 3, 4)).astype(dtype)
res1 = self.batch_test(linalg.diagsvd, A, kwargs=dict(M=6, N=4), core_dim=1)
# test that `M, N` can be passed by position
res2 = linalg.diagsvd(A, 6, 4)
np.testing.assert_equal(res1, res2)
@pytest.mark.parametrize('fun', [linalg.inv, linalg.sqrtm, linalg.signm,
linalg.sinm, linalg.cosm, linalg.tanhm,
linalg.sinhm, linalg.coshm, linalg.tanhm,
linalg.pinv, linalg.pinvh, linalg.orth])
@pytest.mark.parametrize('dtype', floating)
def test_matmat(self, fun, dtype): # matrix in, matrix out
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
# sqrtm can return complex output for real input resulting in i/o type
# mismatch. Nudge the eigenvalues to positive side to avoid this.
if fun == linalg.sqrtm:
A = A + 3*np.eye(4, dtype=dtype)
self.batch_test(fun, A)
@pytest.mark.parametrize('dtype', floating)
def test_null_space(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 6), dtype=dtype, rng=rng)
self.batch_test(linalg.null_space, A)
@pytest.mark.parametrize('dtype', floating)
def test_funm(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 4, 3, 3), dtype=dtype, rng=rng)
self.batch_test(linalg.funm, A, kwargs=dict(func=np.sin))
@pytest.mark.parametrize('dtype', floating)
def test_fractional_matrix_power(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 4, 3, 3), dtype=dtype, rng=rng)
res1 = self.batch_test(linalg.fractional_matrix_power, A, kwargs={'t':1.5})
# test that `t` can be passed by position
res2 = linalg.fractional_matrix_power(A, 1.5)
np.testing.assert_equal(res1, res2)
@pytest.mark.parametrize('dtype', floating)
def test_logm(self, dtype):
# One test failed absolute tolerance with default random seed
rng = np.random.default_rng(89940026998903887141749720079406074936)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
A = A + 3*np.eye(4) # avoid complex output for real input
res1 = self.batch_test(linalg.logm, A)
# test that `disp` can be passed by position
res2 = linalg.logm(A)
for res1i, res2i in zip(res1, res2):
np.testing.assert_equal(res1i, res2i)
@pytest.mark.parametrize('dtype', floating)
def test_pinv(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(linalg.pinv, A, n_out=2, kwargs=dict(return_rank=True))
@pytest.mark.parametrize('dtype', floating)
def test_matrix_balance(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(linalg.matrix_balance, A, n_out=2)
self.batch_test(linalg.matrix_balance, A, n_out=2, kwargs={'separate':True})
@pytest.mark.parametrize('dtype', floating)
def test_bandwidth(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((4, 4), dtype=dtype, rng=rng)
A = np.asarray([np.triu(A, k) for k in range(-3, 3)]).reshape((2, 3, 4, 4))
self.batch_test(linalg.bandwidth, A, n_out=2)
@pytest.mark.parametrize('fun_n_out', [(linalg.cholesky, 1), (linalg.ldl, 3),
(linalg.cho_factor, 2)])
@pytest.mark.parametrize('dtype', floating)
def test_ldl_cholesky(self, fun_n_out, dtype):
rng = np.random.default_rng(8342310302941288912051)
fun, n_out = fun_n_out
A = get_nearly_hermitian((5, 3, 4, 4), dtype, 0, rng) # exactly Hermitian
A = A + 4*np.eye(4, dtype=dtype) # ensure positive definite for Cholesky
self.batch_test(fun, A, n_out=n_out)
@pytest.mark.parametrize('compute_uv', [False, True])
@pytest.mark.parametrize('dtype', floating)
def test_svd(self, compute_uv, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 2, 4), dtype=dtype, rng=rng)
n_out = 3 if compute_uv else 1
self.batch_test(linalg.svd, A, n_out=n_out, kwargs=dict(compute_uv=compute_uv))
@pytest.mark.parametrize('fun', [linalg.polar, linalg.qr, linalg.rq])
@pytest.mark.parametrize('dtype', floating)
def test_polar_qr_rq(self, fun, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 2, 4), dtype=dtype, rng=rng)
self.batch_test(fun, A, n_out=2)
@pytest.mark.parametrize('cdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_qr_multiply(self, cdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
c = get_random(cdim, dtype=dtype, rng=rng)
res = linalg.qr_multiply(A, c, mode='left')
q, r = linalg.qr(A)
ref = q @ c
atol = 1e-6 if dtype in {np.float32, np.complex64} else 1e-12
assert_allclose(res[0], ref, atol=atol)
assert_allclose(res[1], r, atol=atol)
@pytest.mark.parametrize('uvdim', [[(5,), (3,)], [(4, 5, 2), (4, 3, 2)]])
@pytest.mark.parametrize('dtype', floating)
def test_qr_update(self, uvdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
udim, vdim = uvdim
A = get_random((4, 5, 3), dtype=dtype, rng=rng)
u = get_random(udim, dtype=dtype, rng=rng)
v = get_random(vdim, dtype=dtype, rng=rng)
q, r = linalg.qr(A)
res = linalg.qr_update(q, r, u, v)
for i in range(4):
qi, ri = q[i], r[i]
ui, vi = (u, v) if u.ndim == 1 else (u[i], v[i])
ref_i = linalg.qr_update(qi, ri, ui, vi)
assert_allclose(res[0][i], ref_i[0])
assert_allclose(res[1][i], ref_i[1])
@pytest.mark.parametrize('udim', [(5,), (4, 3, 5)])
@pytest.mark.parametrize('kdim', [(), (4,)])
@pytest.mark.parametrize('dtype', floating)
def test_qr_insert(self, udim, kdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((4, 5, 5), dtype=dtype, rng=rng)
u = get_random(udim, dtype=dtype, rng=rng)
k = rng.integers(0, 5, size=kdim)
q, r = linalg.qr(A)
res = linalg.qr_insert(q, r, u, k)
for i in range(4):
qi, ri = q[i], r[i]
ki = k if k.ndim == 0 else k[i]
ui = u if u.ndim == 1 else u[i]
ref_i = linalg.qr_insert(qi, ri, ui, ki)
assert_allclose(res[0][i], ref_i[0])
assert_allclose(res[1][i], ref_i[1])
@pytest.mark.parametrize('kdim', [(), (4,)])
@pytest.mark.parametrize('dtype', floating)
def test_qr_delete(self, kdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((4, 5, 5), dtype=dtype, rng=rng)
k = rng.integers(0, 4, size=kdim)
q, r = linalg.qr(A)
res = linalg.qr_delete(q, r, k)
for i in range(4):
qi, ri = q[i], r[i]
ki = k if k.ndim == 0 else k[i]
ref_i = linalg.qr_delete(qi, ri, ki)
assert_allclose(res[0][i], ref_i[0])
assert_allclose(res[1][i], ref_i[1])
@pytest.mark.parametrize('fun', [linalg.schur, linalg.lu_factor])
@pytest.mark.parametrize('dtype', floating)
def test_schur_lu(self, fun, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(fun, A, n_out=2)
@pytest.mark.parametrize('calc_q', [False, True])
@pytest.mark.parametrize('dtype', floating)
def test_hessenberg(self, calc_q, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
n_out = 2 if calc_q else 1
self.batch_test(linalg.hessenberg, A, n_out=n_out, kwargs=dict(calc_q=calc_q))
@pytest.mark.parametrize('eigvals_only', [False, True])
@pytest.mark.parametrize('dtype', floating)
def test_eig_banded(self, eigvals_only, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
n_out = 1 if eigvals_only else 2
self.batch_test(linalg.eig_banded, A, n_out=n_out,
kwargs=dict(eigvals_only=eigvals_only))
@pytest.mark.parametrize('dtype', floating)
def test_eigvals_banded(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(linalg.eigvals_banded, A)
@pytest.mark.parametrize('two_in', [False, True])
@pytest.mark.parametrize('fun_n_nout', [(linalg.eigh, 1), (linalg.eigh, 2),
(linalg.eigvalsh, 1), (linalg.eigvals, 1)])
@pytest.mark.parametrize('dtype', floating)
def test_eigh(self, two_in, fun_n_nout, dtype):
rng = np.random.default_rng(8342310302941288912051)
fun, n_out = fun_n_nout
A = get_nearly_hermitian((1, 3, 4, 4), dtype, 0, rng) # exactly Hermitian
B = get_nearly_hermitian((2, 1, 4, 4), dtype, 0, rng) # exactly Hermitian
B = B + 4*np.eye(4).astype(dtype) # needs to be positive definite
args = (A, B) if two_in else (A,)
kwargs = dict(eigvals_only=True) if (n_out == 1 and fun==linalg.eigh) else {}
self.batch_test(fun, args, n_out=n_out, kwargs=kwargs)
@pytest.mark.parametrize('compute_expm', [False, True])
@pytest.mark.parametrize('dtype', floating)
def test_expm_frechet(self, compute_expm, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((1, 3, 4, 4), dtype=dtype, rng=rng)
E = get_random((2, 1, 4, 4), dtype=dtype, rng=rng)
n_out = 2 if compute_expm else 1
self.batch_test(linalg.expm_frechet, (A, E), n_out=n_out,
kwargs=dict(compute_expm=compute_expm))
@pytest.mark.parametrize('dtype', floating)
def test_subspace_angles(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((1, 3, 4, 3), dtype=dtype, rng=rng)
B = get_random((2, 1, 4, 3), dtype=dtype, rng=rng)
self.batch_test(linalg.subspace_angles, (A, B))
# just to show that A and B don't need to be broadcastable
M, N, K = 4, 5, 3
A = get_random((1, 3, M, N), dtype=dtype, rng=rng)
B = get_random((2, 1, M, K), dtype=dtype, rng=rng)
assert linalg.subspace_angles(A, B).shape == (2, 3, min(N, K))
@pytest.mark.parametrize('fun', [linalg.svdvals])
@pytest.mark.parametrize('dtype', floating)
def test_svdvals(self, fun, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 4, 5), dtype=dtype, rng=rng)
self.batch_test(fun, A)
@pytest.mark.parametrize('fun_n_out', [(linalg.orthogonal_procrustes, 2),
(linalg.khatri_rao, 1),
(linalg.solve_continuous_lyapunov, 1),
(linalg.solve_discrete_lyapunov, 1),
(linalg.qz, 4),
(linalg.ordqz, 6)])
@pytest.mark.parametrize('dtype', floating)
def test_two_generic_matrix_inputs(self, fun_n_out, dtype):
rng = np.random.default_rng(8342310302941288912051)
fun, n_out = fun_n_out
A = get_random((2, 3, 4, 4), dtype=dtype, rng=rng)
B = get_random((2, 3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(fun, (A, B), n_out=n_out)
@pytest.mark.parametrize('dtype', floating)
def test_cossin(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
p, q = 3, 4
X = get_random((2, 3, 10, 10), dtype=dtype, rng=rng)
x11, x12, x21, x22 = (X[..., :p, :q], X[..., :p, q:],
X[..., p:, :q], X[..., p:, q:])
res = linalg.cossin(X, p, q)
ref = linalg.cossin((x11, x12, x21, x22))
for res_i, ref_i in zip(res, ref):
np.testing.assert_equal(res_i, ref_i)
for j in range(2):
for k in range(3):
ref_jk = linalg.cossin(X[j, k], p, q)
for res_i, ref_ijk in zip(res, ref_jk):
np.testing.assert_equal(res_i[j, k], ref_ijk)
@pytest.mark.parametrize('dtype', floating)
def test_sylvester(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
B = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
C = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
self.batch_test(linalg.solve_sylvester, (A, B, C))
@pytest.mark.parametrize('fun', [linalg.solve_continuous_are,
linalg.solve_discrete_are])
@pytest.mark.parametrize('dtype', floating)
def test_are(self, fun, dtype):
rng = np.random.default_rng(8342310302941288912051)
a = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
b = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
q = get_nearly_hermitian((2, 3, 5, 5), dtype=dtype, atol=0, rng=rng)
r = get_nearly_hermitian((2, 3, 5, 5), dtype=dtype, atol=0, rng=rng)
a = a + 5*np.eye(5) # making these positive definite seems to help
b = b + 5*np.eye(5)
q = q + 5*np.eye(5)
r = r + 5*np.eye(5)
# can't easily generate valid random e, s
self.batch_test(fun, (a, b, q, r))
@pytest.mark.parametrize('dtype', floating)
def test_rsf2cs(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 4, 4), dtype=dtype, rng=rng)
T, Z = linalg.schur(A)
self.batch_test(linalg.rsf2csf, (T, Z), n_out=2)
@pytest.mark.parametrize('dtype', floating)
def test_cholesky_banded(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
ab = get_random((5, 4, 3, 6), dtype=dtype, rng=rng)
ab[..., -1, :] = 10 # make diagonal dominant
self.batch_test(linalg.cholesky_banded, ab)
@pytest.mark.parametrize('dtype', floating)
def test_block_diag(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
a = get_random((1, 3, 1, 3), dtype=dtype, rng=rng)
b = get_random((2, 1, 3, 6), dtype=dtype, rng=rng)
c = get_random((1, 1, 3, 2), dtype=dtype, rng=rng)
# batch_test doesn't have the logic to broadcast just the batch shapes,
# so do it manually.
a2 = np.broadcast_to(a, (2, 3, 1, 3))
b2 = np.broadcast_to(b, (2, 3, 3, 6))
c2 = np.broadcast_to(c, (2, 3, 3, 2))
ref = self.batch_test(linalg.block_diag, (a2, b2, c2),
check_kwargs=False, broadcast=False)
# Check that `block_diag` broadcasts the batch shapes as expected.
res = linalg.block_diag(a, b, c)
assert_allclose(res, ref)
@pytest.mark.parametrize('fun_n_out', [(linalg.eigh_tridiagonal, 2),
(linalg.eigvalsh_tridiagonal, 1)])
@pytest.mark.parametrize('dtype', real_floating)
# "Only real arrays currently supported"
def test_eigh_tridiagonal(self, fun_n_out, dtype):
rng = np.random.default_rng(8342310302941288912051)
fun, n_out = fun_n_out
d = get_random((3, 4, 5), dtype=dtype, rng=rng)
e = get_random((3, 4, 4), dtype=dtype, rng=rng)
self.batch_test(fun, (d, e), core_dim=1, n_out=n_out, broadcast=False)
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_solve(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.solve(A, b)
if len(bdim) == 1:
x = x[..., np.newaxis]
b = b[..., np.newaxis]
assert_allclose(A @ x - b, 0, atol=1.5e-6)
assert_allclose(x, np.linalg.solve(A, b), atol=3e-6)
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_lu_solve(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
b = get_random(bdim, dtype=dtype, rng=rng)
lu_and_piv = linalg.lu_factor(A)
x = linalg.lu_solve(lu_and_piv, b)
if len(bdim) == 1:
x = x[..., np.newaxis]
b = b[..., np.newaxis]
assert_allclose(A @ x - b, 0, atol=1.5e-6)
assert_allclose(x, np.linalg.solve(A, b), atol=3e-6)
@pytest.mark.parametrize('l_and_u', [(1, 1), ([2, 1, 0], [0, 1 , 2])])
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_solve_banded(self, l_and_u, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
l, u = l_and_u
ab = get_random((2, 3, 3, 5), dtype=dtype, rng=rng)
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.solve_banded((l, u), ab, b)
for i in range(2):
for j in range(3):
bij = b if len(bdim) <= 2 else b[i, j]
lj = l if np.ndim(l) == 0 else l[j]
uj = u if np.ndim(u) == 0 else u[j]
xij = linalg.solve_banded((lj, uj), ab[i, j], bij)
assert_allclose(x[i, j], xij)
# Can uncomment when `solve_toeplitz` deprecation is done (SciPy 1.17)
# @pytest.mark.parametrize('separate_r', [False, True])
# @pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
# @pytest.mark.parametrize('dtype', floating)
# def test_solve_toeplitz(self, separate_r, bdim, dtype):
# rng = np.random.default_rng(8342310302941288912051)
# c = get_random((2, 3, 5), dtype=dtype, rng=rng)
# r = get_random((2, 3, 5), dtype=dtype, rng=rng)
# c_or_cr = (c, r) if separate_r else c
# b = get_random(bdim, dtype=dtype, rng=rng)
# x = linalg.solve_toeplitz(c_or_cr, b)
# for i in range(2):
# for j in range(3):
# bij = b if len(bdim) <= 2 else b[i, j]
# c_or_cr_ij = (c[i, j], r[i, j]) if separate_r else c[i, j]
# xij = linalg.solve_toeplitz(c_or_cr_ij, bij)
# assert_allclose(x[i, j], xij)
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_cho_solve(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_nearly_hermitian((2, 3, 5, 5), dtype=dtype, atol=0, rng=rng)
A = A + 5*np.eye(5)
c_and_lower = linalg.cho_factor(A)
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.cho_solve(c_and_lower, b)
if len(bdim) == 1:
x = x[..., np.newaxis]
b = b[..., np.newaxis]
assert_allclose(A @ x - b, 0, atol=1e-6)
assert_allclose(x, np.linalg.solve(A, b), atol=2e-6)
@pytest.mark.parametrize('lower', [False, True])
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_cho_solve_banded(self, lower, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 3, 5), dtype=dtype, rng=rng)
row_diag = 0 if lower else -1
A[:, :, row_diag] = 10
cb = linalg.cholesky_banded(A, lower=lower)
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.cho_solve_banded((cb, lower), b)
for i in range(2):
for j in range(3):
bij = b if len(bdim) <= 2 else b[i, j]
xij = linalg.cho_solve_banded((cb[i, j], lower), bij)
assert_allclose(x[i, j], xij)
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_solveh_banded(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 3, 5), dtype=dtype, rng=rng)
A[:, :, -1] = 10
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.solveh_banded(A, b)
for i in range(2):
for j in range(3):
bij = b if len(bdim) <= 2 else b[i, j]
xij = linalg.solveh_banded(A[i, j], bij)
assert_allclose(x[i, j], xij)
@pytest.mark.parametrize('bdim', [(5,), (5, 4), (2, 3, 5, 4)])
@pytest.mark.parametrize('dtype', floating)
def test_solve_triangular(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 5, 5), dtype=dtype, rng=rng)
A = np.tril(A)
b = get_random(bdim, dtype=dtype, rng=rng)
x = linalg.solve_triangular(A, b, lower=True)
if len(bdim) == 1:
x = x[..., np.newaxis]
b = b[..., np.newaxis]
atol = 1e-10 if dtype in (np.complex128, np.float64) else 2e-4
assert_allclose(A @ x - b, 0, atol=atol)
assert_allclose(x, np.linalg.solve(A, b), atol=5*atol)
@pytest.mark.parametrize('bdim', [(4,), (4, 3), (2, 3, 4, 3)])
@pytest.mark.parametrize('dtype', floating)
def test_lstsq(self, bdim, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((2, 3, 4, 5), dtype=dtype, rng=rng)
b = get_random(bdim, dtype=dtype, rng=rng)
res = linalg.lstsq(A, b)
x = res[0]
if len(bdim) == 1:
x = x[..., np.newaxis]
b = b[..., np.newaxis]
assert_allclose(A @ x - b, 0, atol=2e-6)
assert len(res) == 4
@pytest.mark.parametrize('dtype', floating)
def test_clarkson_woodruff_transform(self, dtype):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 6), dtype=dtype, rng=rng)
self.batch_test(linalg.clarkson_woodruff_transform, A,
kwargs=dict(sketch_size=3, rng=311224))
def test_clarkson_woodruff_transform_sparse(self):
rng = np.random.default_rng(8342310302941288912051)
A = get_random((5, 3, 4, 6), dtype=np.float64, rng=rng)
A = sparse.coo_array(A)
message = "Batch support for sparse arrays is not available."
with pytest.raises(NotImplementedError, match=message):
linalg.clarkson_woodruff_transform(A, sketch_size=3, rng=rng)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,118 @@
import numpy as np
from numpy.testing import (assert_allclose,
assert_equal)
import scipy.linalg.cython_blas as blas
class TestDGEMM:
def test_transposes(self):
a = np.arange(12, dtype='d').reshape((3, 4))[:2,:2]
b = np.arange(1, 13, dtype='d').reshape((4, 3))[:2,:2]
c = np.empty((2, 4))[:2,:2]
blas._test_dgemm(1., a, b, 0., c)
assert_allclose(c, a.dot(b))
blas._test_dgemm(1., a.T, b, 0., c)
assert_allclose(c, a.T.dot(b))
blas._test_dgemm(1., a, b.T, 0., c)
assert_allclose(c, a.dot(b.T))
blas._test_dgemm(1., a.T, b.T, 0., c)
assert_allclose(c, a.T.dot(b.T))
blas._test_dgemm(1., a, b, 0., c.T)
assert_allclose(c, a.dot(b).T)
blas._test_dgemm(1., a.T, b, 0., c.T)
assert_allclose(c, a.T.dot(b).T)
blas._test_dgemm(1., a, b.T, 0., c.T)
assert_allclose(c, a.dot(b.T).T)
blas._test_dgemm(1., a.T, b.T, 0., c.T)
assert_allclose(c, a.T.dot(b.T).T)
def test_shapes(self):
a = np.arange(6, dtype='d').reshape((3, 2))
b = np.arange(-6, 2, dtype='d').reshape((2, 4))
c = np.empty((3, 4))
blas._test_dgemm(1., a, b, 0., c)
assert_allclose(c, a.dot(b))
blas._test_dgemm(1., b.T, a.T, 0., c.T)
assert_allclose(c, b.T.dot(a.T).T)
class TestWfuncPointers:
""" Test the function pointers that are expected to fail on
Mac OS X without the additional entry statement in their definitions
in fblas_l1.pyf.src. """
def test_complex_args(self):
cx = np.array([.5 + 1.j, .25 - .375j, 12.5 - 4.j], np.complex64)
cy = np.array([.8 + 2.j, .875 - .625j, -1. + 2.j], np.complex64)
assert_allclose(blas._test_cdotc(cx, cy),
-17.6468753815+21.3718757629j)
assert_allclose(blas._test_cdotu(cx, cy),
-6.11562538147+30.3156242371j)
assert_equal(blas._test_icamax(cx), 3)
assert_allclose(blas._test_scasum(cx), 18.625)
assert_allclose(blas._test_scnrm2(cx), 13.1796483994)
assert_allclose(blas._test_cdotc(cx[::2], cy[::2]),
-18.1000003815+21.2000007629j)
assert_allclose(blas._test_cdotu(cx[::2], cy[::2]),
-6.10000038147+30.7999992371j)
assert_allclose(blas._test_scasum(cx[::2]), 18.)
assert_allclose(blas._test_scnrm2(cx[::2]), 13.1719398499)
def test_double_args(self):
x = np.array([5., -3, -.5], np.float64)
y = np.array([2, 1, .5], np.float64)
assert_allclose(blas._test_dasum(x), 8.5)
assert_allclose(blas._test_ddot(x, y), 6.75)
assert_allclose(blas._test_dnrm2(x), 5.85234975815)
assert_allclose(blas._test_dasum(x[::2]), 5.5)
assert_allclose(blas._test_ddot(x[::2], y[::2]), 9.75)
assert_allclose(blas._test_dnrm2(x[::2]), 5.0249376297)
assert_equal(blas._test_idamax(x), 1)
def test_float_args(self):
x = np.array([5., -3, -.5], np.float32)
y = np.array([2, 1, .5], np.float32)
assert_equal(blas._test_isamax(x), 1)
assert_allclose(blas._test_sasum(x), 8.5)
assert_allclose(blas._test_sdot(x, y), 6.75)
assert_allclose(blas._test_snrm2(x), 5.85234975815)
assert_allclose(blas._test_sasum(x[::2]), 5.5)
assert_allclose(blas._test_sdot(x[::2], y[::2]), 9.75)
assert_allclose(blas._test_snrm2(x[::2]), 5.0249376297)
def test_double_complex_args(self):
cx = np.array([.5 + 1.j, .25 - .375j, 13. - 4.j], np.complex128)
cy = np.array([.875 + 2.j, .875 - .625j, -1. + 2.j], np.complex128)
assert_equal(blas._test_izamax(cx), 3)
assert_allclose(blas._test_zdotc(cx, cy), -18.109375+22.296875j)
assert_allclose(blas._test_zdotu(cx, cy), -6.578125+31.390625j)
assert_allclose(blas._test_zdotc(cx[::2], cy[::2]), -18.5625+22.125j)
assert_allclose(blas._test_zdotu(cx[::2], cy[::2]), -6.5625+31.875j)

View file

@ -0,0 +1,22 @@
from numpy.testing import assert_allclose
from scipy.linalg import cython_lapack as cython_lapack
from scipy.linalg import lapack
class TestLamch:
def test_slamch(self):
for c in [b'e', b's', b'b', b'p', b'n', b'r', b'm', b'u', b'l', b'o']:
assert_allclose(cython_lapack._test_slamch(c),
lapack.slamch(c))
def test_dlamch(self):
for c in [b'e', b's', b'b', b'p', b'n', b'r', b'm', b'u', b'l', b'o']:
assert_allclose(cython_lapack._test_dlamch(c),
lapack.dlamch(c))
def test_complex_ladiv(self):
cx = .5 + 1.j
cy = .875 + 2.j
assert_allclose(cython_lapack._test_zladiv(cy, cx), 1.95+0.1j)
assert_allclose(cython_lapack._test_cladiv(cy, cx), 1.95+0.1j)

View file

@ -0,0 +1,130 @@
import numpy as np
from scipy.linalg import bandwidth, issymmetric, ishermitian
import pytest
from pytest import raises
def test_bandwidth_dtypes():
n = 5
for t in np.typecodes['All']:
A = np.zeros([n, n], dtype=t)
if t in 'eUVOMm':
raises(TypeError, bandwidth, A)
elif t == 'G': # No-op test. On win these pass on others fail.
pass
else:
_ = bandwidth(A)
def test_bandwidth_non2d_input():
A = np.array([1, 2, 3])
raises(ValueError, bandwidth, A)
@pytest.mark.parametrize('T', [x for x in np.typecodes['All']
if x not in 'eGUVOMm'])
def test_bandwidth_square_inputs(T):
n = 20
k = 4
R = np.zeros([n, n], dtype=T, order='F')
# form a banded matrix inplace
R[[x for x in range(n)], [x for x in range(n)]] = 1
R[[x for x in range(n-k)], [x for x in range(k, n)]] = 1
R[[x for x in range(1, n)], [x for x in range(n-1)]] = 1
R[[x for x in range(k, n)], [x for x in range(n-k)]] = 1
assert bandwidth(R) == (k, k)
A = np.array([
[1, 1, 0, 0, 0, 0, 0, 0],
[1, 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, 1, 1, 1],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
])
assert bandwidth(A) == (2, 2)
@pytest.mark.parametrize('T', [x for x in np.typecodes['All']
if x not in 'eGUVOMm'])
def test_bandwidth_rect_inputs(T):
n, m = 10, 20
k = 5
R = np.zeros([n, m], dtype=T, order='F')
# form a banded matrix inplace
R[[x for x in range(n)], [x for x in range(n)]] = 1
R[[x for x in range(n-k)], [x for x in range(k, n)]] = 1
R[[x for x in range(1, n)], [x for x in range(n-1)]] = 1
R[[x for x in range(k, n)], [x for x in range(n-k)]] = 1
assert bandwidth(R) == (k, k)
def test_issymetric_ishermitian_dtypes():
n = 5
for t in np.typecodes['All']:
A = np.zeros([n, n], dtype=t)
if t in 'eUVOMm':
raises(TypeError, issymmetric, A)
raises(TypeError, ishermitian, A)
elif t == 'G': # No-op test. On win these pass on others fail.
pass
else:
assert issymmetric(A)
assert ishermitian(A)
def test_issymmetric_ishermitian_invalid_input():
A = np.array([1, 2, 3])
raises(ValueError, issymmetric, A)
raises(ValueError, ishermitian, A)
A = np.array([[[1, 2, 3], [4, 5, 6]]])
raises(ValueError, issymmetric, A)
raises(ValueError, ishermitian, A)
A = np.array([[1, 2, 3], [4, 5, 6]])
raises(ValueError, issymmetric, A)
raises(ValueError, ishermitian, A)
def test_issymetric_complex_decimals():
A = np.arange(1, 10).astype(complex).reshape(3, 3)
A += np.arange(-4, 5).astype(complex).reshape(3, 3)*1j
# make entries decimal
A /= np.pi
A = A + A.T
assert issymmetric(A)
def test_ishermitian_complex_decimals():
A = np.arange(1, 10).astype(complex).reshape(3, 3)
A += np.arange(-4, 5).astype(complex).reshape(3, 3)*1j
# make entries decimal
A /= np.pi
A = A + A.T.conj()
assert ishermitian(A)
def test_issymmetric_approximate_results():
n = 20
rng = np.random.RandomState(123456789)
x = rng.uniform(high=5., size=[n, n])
y = x @ x.T # symmetric
p = rng.standard_normal([n, n])
z = p @ y @ p.T
assert issymmetric(z, atol=1e-10)
assert issymmetric(z, atol=1e-10, rtol=0.)
assert issymmetric(z, atol=0., rtol=1e-12)
assert issymmetric(z, atol=1e-13, rtol=1e-12)
def test_ishermitian_approximate_results():
n = 20
rng = np.random.RandomState(987654321)
x = rng.uniform(high=5., size=[n, n])
y = x @ x.T # symmetric
p = rng.standard_normal([n, n]) + rng.standard_normal([n, n])*1j
z = p @ y @ p.conj().T
assert ishermitian(z, atol=1e-10)
assert ishermitian(z, atol=1e-10, rtol=0.)
assert ishermitian(z, atol=0., rtol=1e-12)
assert ishermitian(z, atol=1e-13, rtol=1e-12)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,268 @@
import pytest
import numpy as np
from numpy.testing import assert_array_almost_equal
from pytest import raises as assert_raises
from numpy import array, transpose, dot, conjugate, zeros_like, empty
from numpy.random import random
from scipy.linalg import (cholesky, cholesky_banded, cho_solve_banded,
cho_factor, cho_solve)
from scipy.linalg._testutils import assert_no_overwrite
class TestCholesky:
def test_simple(self):
a = [[8, 2, 3], [2, 9, 3], [3, 3, 6]]
c = cholesky(a)
assert_array_almost_equal(dot(transpose(c), c), a)
c = transpose(c)
a = dot(c, transpose(c))
assert_array_almost_equal(cholesky(a, lower=1), c)
def test_check_finite(self):
a = [[8, 2, 3], [2, 9, 3], [3, 3, 6]]
c = cholesky(a, check_finite=False)
assert_array_almost_equal(dot(transpose(c), c), a)
c = transpose(c)
a = dot(c, transpose(c))
assert_array_almost_equal(cholesky(a, lower=1, check_finite=False), c)
def test_simple_complex(self):
m = array([[3+1j, 3+4j, 5], [0, 2+2j, 2+7j], [0, 0, 7+4j]])
a = dot(transpose(conjugate(m)), m)
c = cholesky(a)
a1 = dot(transpose(conjugate(c)), c)
assert_array_almost_equal(a, a1)
c = transpose(c)
a = dot(c, transpose(conjugate(c)))
assert_array_almost_equal(cholesky(a, lower=1), c)
def test_random(self):
n = 20
for k in range(2):
m = random([n, n])
for i in range(n):
m[i, i] = 20*(.1+m[i, i])
a = dot(transpose(m), m)
c = cholesky(a)
a1 = dot(transpose(c), c)
assert_array_almost_equal(a, a1)
c = transpose(c)
a = dot(c, transpose(c))
assert_array_almost_equal(cholesky(a, lower=1), c)
def test_random_complex(self):
n = 20
for k in range(2):
m = random([n, n])+1j*random([n, n])
for i in range(n):
m[i, i] = 20*(.1+abs(m[i, i]))
a = dot(transpose(conjugate(m)), m)
c = cholesky(a)
a1 = dot(transpose(conjugate(c)), c)
assert_array_almost_equal(a, a1)
c = transpose(c)
a = dot(c, transpose(conjugate(c)))
assert_array_almost_equal(cholesky(a, lower=1), c)
@pytest.mark.xslow
def test_int_overflow(self):
# regression test for
# https://github.com/scipy/scipy/issues/17436
# the problem was an int overflow in zeroing out
# the unused triangular part
n = 47_000
x = np.eye(n, dtype=np.float64, order='F')
x[:4, :4] = np.array([[4, -2, 3, -1],
[-2, 4, -3, 1],
[3, -3, 5, 0],
[-1, 1, 0, 5]])
cholesky(x, check_finite=False, overwrite_a=True) # should not segfault
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64])
@pytest.mark.parametrize('dt_b', [int, float, np.float32, complex, np.complex64])
def test_empty(self, dt, dt_b):
a = empty((0, 0), dtype=dt)
c = cholesky(a)
assert c.shape == (0, 0)
assert c.dtype == cholesky(np.eye(2, dtype=dt)).dtype
c_and_lower = (c, True)
b = np.asarray([], dtype=dt_b)
x = cho_solve(c_and_lower, b)
assert x.shape == (0,)
assert x.dtype == cho_solve((np.eye(2, dtype=dt), True),
np.ones(2, dtype=dt_b)).dtype
b = empty((0, 0), dtype=dt_b)
x = cho_solve(c_and_lower, b)
assert x.shape == (0, 0)
assert x.dtype == cho_solve((np.eye(2, dtype=dt), True),
np.ones(2, dtype=dt_b)).dtype
a1 = array([])
a2 = array([[]])
a3 = []
a4 = [[]]
for x in ([a1, a2, a3, a4]):
assert_raises(ValueError, cholesky, x)
class TestCholeskyBanded:
"""Tests for cholesky_banded() and cho_solve_banded."""
def test_check_finite(self):
# Symmetric positive definite banded matrix `a`
a = array([[4.0, 1.0, 0.0, 0.0],
[1.0, 4.0, 0.5, 0.0],
[0.0, 0.5, 4.0, 0.2],
[0.0, 0.0, 0.2, 4.0]])
# Banded storage form of `a`.
ab = array([[-1.0, 1.0, 0.5, 0.2],
[4.0, 4.0, 4.0, 4.0]])
c = cholesky_banded(ab, lower=False, check_finite=False)
ufac = zeros_like(a)
ufac[list(range(4)), list(range(4))] = c[-1]
ufac[(0, 1, 2), (1, 2, 3)] = c[0, 1:]
assert_array_almost_equal(a, dot(ufac.T, ufac))
b = array([0.0, 0.5, 4.2, 4.2])
x = cho_solve_banded((c, False), b, check_finite=False)
assert_array_almost_equal(x, [0.0, 0.0, 1.0, 1.0])
def test_upper_real(self):
# Symmetric positive definite banded matrix `a`
a = array([[4.0, 1.0, 0.0, 0.0],
[1.0, 4.0, 0.5, 0.0],
[0.0, 0.5, 4.0, 0.2],
[0.0, 0.0, 0.2, 4.0]])
# Banded storage form of `a`.
ab = array([[-1.0, 1.0, 0.5, 0.2],
[4.0, 4.0, 4.0, 4.0]])
c = cholesky_banded(ab, lower=False)
ufac = zeros_like(a)
ufac[list(range(4)), list(range(4))] = c[-1]
ufac[(0, 1, 2), (1, 2, 3)] = c[0, 1:]
assert_array_almost_equal(a, dot(ufac.T, ufac))
b = array([0.0, 0.5, 4.2, 4.2])
x = cho_solve_banded((c, False), b)
assert_array_almost_equal(x, [0.0, 0.0, 1.0, 1.0])
def test_upper_complex(self):
# Hermitian positive definite banded matrix `a`
a = array([[4.0, 1.0, 0.0, 0.0],
[1.0, 4.0, 0.5, 0.0],
[0.0, 0.5, 4.0, -0.2j],
[0.0, 0.0, 0.2j, 4.0]])
# Banded storage form of `a`.
ab = array([[-1.0, 1.0, 0.5, -0.2j],
[4.0, 4.0, 4.0, 4.0]])
c = cholesky_banded(ab, lower=False)
ufac = zeros_like(a)
ufac[list(range(4)), list(range(4))] = c[-1]
ufac[(0, 1, 2), (1, 2, 3)] = c[0, 1:]
assert_array_almost_equal(a, dot(ufac.conj().T, ufac))
b = array([0.0, 0.5, 4.0-0.2j, 0.2j + 4.0])
x = cho_solve_banded((c, False), b)
assert_array_almost_equal(x, [0.0, 0.0, 1.0, 1.0])
def test_lower_real(self):
# Symmetric positive definite banded matrix `a`
a = array([[4.0, 1.0, 0.0, 0.0],
[1.0, 4.0, 0.5, 0.0],
[0.0, 0.5, 4.0, 0.2],
[0.0, 0.0, 0.2, 4.0]])
# Banded storage form of `a`.
ab = array([[4.0, 4.0, 4.0, 4.0],
[1.0, 0.5, 0.2, -1.0]])
c = cholesky_banded(ab, lower=True)
lfac = zeros_like(a)
lfac[list(range(4)), list(range(4))] = c[0]
lfac[(1, 2, 3), (0, 1, 2)] = c[1, :3]
assert_array_almost_equal(a, dot(lfac, lfac.T))
b = array([0.0, 0.5, 4.2, 4.2])
x = cho_solve_banded((c, True), b)
assert_array_almost_equal(x, [0.0, 0.0, 1.0, 1.0])
def test_lower_complex(self):
# Hermitian positive definite banded matrix `a`
a = array([[4.0, 1.0, 0.0, 0.0],
[1.0, 4.0, 0.5, 0.0],
[0.0, 0.5, 4.0, -0.2j],
[0.0, 0.0, 0.2j, 4.0]])
# Banded storage form of `a`.
ab = array([[4.0, 4.0, 4.0, 4.0],
[1.0, 0.5, 0.2j, -1.0]])
c = cholesky_banded(ab, lower=True)
lfac = zeros_like(a)
lfac[list(range(4)), list(range(4))] = c[0]
lfac[(1, 2, 3), (0, 1, 2)] = c[1, :3]
assert_array_almost_equal(a, dot(lfac, lfac.conj().T))
b = array([0.0, 0.5j, 3.8j, 3.8])
x = cho_solve_banded((c, True), b)
assert_array_almost_equal(x, [0.0, 0.0, 1.0j, 1.0])
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64])
@pytest.mark.parametrize('dt_b', [int, float, np.float32, complex, np.complex64])
def test_empty(self, dt, dt_b):
ab = empty((0, 0), dtype=dt)
cb = cholesky_banded(ab)
assert cb.shape == (0, 0)
m = cholesky_banded(np.array([[0, 0], [1, 1]], dtype=dt))
assert cb.dtype == m.dtype
cb_and_lower = (cb, True)
b = np.asarray([], dtype=dt_b)
x = cho_solve_banded(cb_and_lower, b)
assert x.shape == (0,)
dtype_nonempty = cho_solve_banded((m, True), np.ones(2, dtype=dt_b)).dtype
assert x.dtype == dtype_nonempty
b = empty((0, 0), dtype=dt_b)
x = cho_solve_banded(cb_and_lower, b)
assert x.shape == (0, 0)
assert x.dtype == dtype_nonempty
class TestOverwrite:
def test_cholesky(self):
assert_no_overwrite(cholesky, [(3, 3)])
def test_cho_factor(self):
assert_no_overwrite(cho_factor, [(3, 3)])
def test_cho_solve(self):
x = array([[2, -1, 0], [-1, 2, -1], [0, -1, 2]])
xcho = cho_factor(x)
assert_no_overwrite(lambda b: cho_solve(xcho, b), [(3,)])
def test_cholesky_banded(self):
assert_no_overwrite(cholesky_banded, [(2, 3)])
def test_cho_solve_banded(self):
x = array([[0, -1, -1], [2, 2, 2]])
xcho = cholesky_banded(x)
assert_no_overwrite(lambda b: cho_solve_banded((xcho, False), b),
[(3,)])
class TestChoFactor:
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64])
def test_empty(self, dt):
a = np.empty((0, 0), dtype=dt)
x, lower = cho_factor(a)
assert x.shape == (0, 0)
xx, lower = cho_factor(np.eye(2, dtype=dt))
assert x.dtype == xx.dtype

View file

@ -0,0 +1,314 @@
import pytest
import numpy as np
from numpy.random import default_rng
from numpy.testing import assert_allclose
from scipy import linalg
from scipy.linalg.lapack import _compute_lwork
from scipy.stats import ortho_group, unitary_group
from scipy.linalg import cossin, get_lapack_funcs
REAL_DTYPES = (np.float32, np.float64)
COMPLEX_DTYPES = (np.complex64, np.complex128)
DTYPES = REAL_DTYPES + COMPLEX_DTYPES
@pytest.mark.parametrize('dtype_', DTYPES)
@pytest.mark.parametrize('m, p, q',
[
(2, 1, 1),
(3, 2, 1),
(3, 1, 2),
(4, 2, 2),
(4, 1, 2),
(40, 12, 20),
(40, 30, 1),
(40, 1, 30),
(100, 50, 1),
(100, 50, 50),
])
@pytest.mark.parametrize('swap_sign', [True, False])
def test_cossin(dtype_, m, p, q, swap_sign):
rng = default_rng(1708093570726217)
if dtype_ in COMPLEX_DTYPES:
x = np.array(unitary_group.rvs(m, random_state=rng), dtype=dtype_)
else:
x = np.array(ortho_group.rvs(m, random_state=rng), dtype=dtype_)
u, cs, vh = cossin(x, p, q,
swap_sign=swap_sign)
assert_allclose(x, u @ cs @ vh, rtol=0., atol=m*1e3*np.finfo(dtype_).eps)
assert u.dtype == dtype_
# Test for float32 or float 64
assert cs.dtype == np.real(u).dtype
assert vh.dtype == dtype_
u, cs, vh = cossin([x[:p, :q], x[:p, q:], x[p:, :q], x[p:, q:]],
swap_sign=swap_sign)
assert_allclose(x, u @ cs @ vh, rtol=0., atol=m*1e3*np.finfo(dtype_).eps)
assert u.dtype == dtype_
assert cs.dtype == np.real(u).dtype
assert vh.dtype == dtype_
_, cs2, vh2 = cossin(x, p, q,
compute_u=False,
swap_sign=swap_sign)
assert_allclose(cs, cs2, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(vh, vh2, rtol=0., atol=10*np.finfo(dtype_).eps)
u2, cs2, _ = cossin(x, p, q,
compute_vh=False,
swap_sign=swap_sign)
assert_allclose(u, u2, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(cs, cs2, rtol=0., atol=10*np.finfo(dtype_).eps)
_, cs2, _ = cossin(x, p, q,
compute_u=False,
compute_vh=False,
swap_sign=swap_sign)
assert_allclose(cs, cs2, rtol=0., atol=10*np.finfo(dtype_).eps)
def test_cossin_mixed_types():
rng = default_rng(1708093736390459)
x = np.array(ortho_group.rvs(4, random_state=rng), dtype=np.float64)
u, cs, vh = cossin([x[:2, :2],
np.array(x[:2, 2:], dtype=np.complex128),
x[2:, :2],
x[2:, 2:]])
assert u.dtype == np.complex128
assert cs.dtype == np.float64
assert vh.dtype == np.complex128
assert_allclose(x, u @ cs @ vh, rtol=0.,
atol=1e4 * np.finfo(np.complex128).eps)
def test_cossin_error_incorrect_subblocks():
with pytest.raises(ValueError, match="be due to missing p, q arguments."):
cossin(([1, 2], [3, 4, 5], [6, 7], [8, 9, 10]))
def test_cossin_error_empty_subblocks():
with pytest.raises(ValueError, match="x11.*empty"):
cossin(([], [], [], []))
with pytest.raises(ValueError, match="x12.*empty"):
cossin(([1, 2], [], [6, 7], [8, 9, 10]))
with pytest.raises(ValueError, match="x21.*empty"):
cossin(([1, 2], [3, 4, 5], [], [8, 9, 10]))
with pytest.raises(ValueError, match="x22.*empty"):
cossin(([1, 2], [3, 4, 5], [2], []))
def test_cossin_error_missing_partitioning():
with pytest.raises(ValueError, match=".*exactly four arrays.* got 2"):
cossin(unitary_group.rvs(2))
with pytest.raises(ValueError, match=".*might be due to missing p, q"):
cossin(unitary_group.rvs(4))
def test_cossin_error_non_iterable():
with pytest.raises(ValueError, match="containing the subblocks of X"):
cossin(12j)
def test_cossin_error_invalid_shape():
# Invalid x12 dimensions
p, q = 3, 4
invalid_x12 = np.ones((p, q + 2))
valid_ones = np.ones((p, q))
with pytest.raises(ValueError,
match=r"Invalid x12 dimensions: desired \(3, 4\), got \(3, 6\)"):
cossin((valid_ones, invalid_x12, valid_ones, valid_ones))
# Invalid x21 dimensions
invalid_x21 = np.ones(p + 2)
with pytest.raises(ValueError,
match=r"Invalid x21 dimensions: desired \(3, 4\), got \(1, 5\)"):
cossin((valid_ones, valid_ones, invalid_x21, valid_ones))
def test_cossin_error_non_square():
with pytest.raises(ValueError, match="only supports square"):
cossin(np.array([[1, 2]]), 1, 1)
def test_cossin_error_partitioning():
x = np.array(ortho_group.rvs(4), dtype=np.float64)
with pytest.raises(ValueError, match="invalid p=0.*0<p<4.*"):
cossin(x, 0, 1)
with pytest.raises(ValueError, match="invalid p=4.*0<p<4.*"):
cossin(x, 4, 1)
with pytest.raises(ValueError, match="invalid q=-2.*0<q<4.*"):
cossin(x, 1, -2)
with pytest.raises(ValueError, match="invalid q=5.*0<q<4.*"):
cossin(x, 1, 5)
@pytest.mark.parametrize("dtype_", DTYPES)
def test_cossin_separate(dtype_):
rng = default_rng(1708093590167096)
m, p, q = 98, 37, 61
pfx = 'or' if dtype_ in REAL_DTYPES else 'un'
X = (ortho_group.rvs(m, random_state=rng) if pfx == 'or'
else unitary_group.rvs(m, random_state=rng))
X = np.array(X, dtype=dtype_)
drv, dlw = get_lapack_funcs((pfx + 'csd', pfx + 'csd_lwork'), [X])
lwval = _compute_lwork(dlw, m, p, q)
lwvals = {'lwork': lwval} if pfx == 'or' else dict(zip(['lwork',
'lrwork'],
lwval))
*_, theta, u1, u2, v1t, v2t, _ = \
drv(X[:p, :q], X[:p, q:], X[p:, :q], X[p:, q:], **lwvals)
(u1_2, u2_2), theta2, (v1t_2, v2t_2) = cossin(X, p, q, separate=True)
assert_allclose(u1_2, u1, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(u2_2, u2, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(v1t_2, v1t, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(v2t_2, v2t, rtol=0., atol=10*np.finfo(dtype_).eps)
assert_allclose(theta2, theta, rtol=0., atol=10*np.finfo(dtype_).eps)
@pytest.mark.parametrize("m", [2, 5, 10, 15, 20])
@pytest.mark.parametrize("p", [1, 4, 9, 14, 19])
@pytest.mark.parametrize("q", [1, 4, 9, 14, 19])
@pytest.mark.parametrize("swap_sign", [True, False])
def test_properties(m, p, q, swap_sign):
# Test all the properties advertised in `linalg.cossin` documentation.
# There may be some overlap with tests above, but this is sensitive to
# the bug reported in gh-19365 and more.
if (p >= m) or (q >= m):
pytest.skip("`0 < p < m` and `0 < q < m` must hold")
# Generate unitary input
rng = np.random.default_rng(329548272348596421)
X = unitary_group.rvs(m, random_state=rng)
np.testing.assert_allclose(X @ X.conj().T, np.eye(m), atol=1e-15)
# Perform the decomposition
u0, cs0, vh0 = linalg.cossin(X, p=p, q=q, separate=True, swap_sign=swap_sign)
u1, u2 = u0
v1, v2 = vh0
v1, v2 = v1.conj().T, v2.conj().T
# "U1, U2, V1, V2 are square orthogonal/unitary matrices
# of dimensions (p,p), (m-p,m-p), (q,q), and (m-q,m-q) respectively"
np.testing.assert_allclose(u1 @ u1.conj().T, np.eye(p), atol=1e-13)
np.testing.assert_allclose(u2 @ u2.conj().T, np.eye(m-p), atol=1e-13)
np.testing.assert_allclose(v1 @ v1.conj().T, np.eye(q), atol=1e-13)
np.testing.assert_allclose(v2 @ v2.conj().T, np.eye(m-q), atol=1e-13)
# "and C and S are (r, r) nonnegative diagonal matrices..."
C = np.diag(np.cos(cs0))
S = np.diag(np.sin(cs0))
# "...satisfying C^2 + S^2 = I where r = min(p, m-p, q, m-q)."
r = min(p, m-p, q, m-q)
np.testing.assert_allclose(C**2 + S**2, np.eye(r))
# "Moreover, the rank of the identity matrices are
# min(p, q) - r, min(p, m - q) - r, min(m - p, q) - r,
# and min(m - p, m - q) - r respectively."
I11 = np.eye(min(p, q) - r)
I12 = np.eye(min(p, m - q) - r)
I21 = np.eye(min(m - p, q) - r)
I22 = np.eye(min(m - p, m - q) - r)
# From:
# ┌ ┐
# │ I 0 0 │ 0 0 0 │
# ┌ ┐ ┌ ┐│ 0 C 0 │ 0 -S 0 │┌ ┐*
# │ X11 │ X12 │ │ U1 │ ││ 0 0 0 │ 0 0 -I ││ V1 │ │
# │ ────┼──── │ = │────┼────││─────────┼─────────││────┼────│
# │ X21 │ X22 │ │ │ U2 ││ 0 0 0 │ I 0 0 ││ │ V2 │
# └ ┘ └ ┘│ 0 S 0 │ 0 C 0 │└ ┘
# │ 0 0 I │ 0 0 0 │
# └ ┘
# We can see that U and V are block diagonal matrices like so:
U = linalg.block_diag(u1, u2)
V = linalg.block_diag(v1, v2)
# And the center matrix, which we'll call Q here, must be:
Q11 = np.zeros((u1.shape[1], v1.shape[0]))
IC11 = linalg.block_diag(I11, C)
Q11[:IC11.shape[0], :IC11.shape[1]] = IC11
Q12 = np.zeros((u1.shape[1], v2.shape[0]))
SI12 = linalg.block_diag(S, I12) if swap_sign else linalg.block_diag(-S, -I12)
Q12[-SI12.shape[0]:, -SI12.shape[1]:] = SI12
Q21 = np.zeros((u2.shape[1], v1.shape[0]))
SI21 = linalg.block_diag(-S, -I21) if swap_sign else linalg.block_diag(S, I21)
Q21[-SI21.shape[0]:, -SI21.shape[1]:] = SI21
Q22 = np.zeros((u2.shape[1], v2.shape[0]))
IC22 = linalg.block_diag(I22, C)
Q22[:IC22.shape[0], :IC22.shape[1]] = IC22
Q = np.block([[Q11, Q12], [Q21, Q22]])
# Confirm that `cossin` decomposes `X` as shown
np.testing.assert_allclose(X, U @ Q @ V.conj().T)
# And check that `separate=False` agrees
U0, CS0, Vh0 = linalg.cossin(X, p=p, q=q, swap_sign=swap_sign)
np.testing.assert_allclose(U, U0)
np.testing.assert_allclose(Q, CS0)
np.testing.assert_allclose(V, Vh0.conj().T)
# Confirm that `compute_u`/`compute_vh` don't affect the results
kwargs = dict(p=p, q=q, swap_sign=swap_sign)
# `compute_u=False`
u, cs, vh = linalg.cossin(X, separate=True, compute_u=False, **kwargs)
assert u[0].shape == (0, 0) # probably not ideal, but this is what it does
assert u[1].shape == (0, 0)
assert_allclose(cs, cs0, rtol=1e-15)
assert_allclose(vh[0], vh0[0], rtol=1e-15)
assert_allclose(vh[1], vh0[1], rtol=1e-15)
U, CS, Vh = linalg.cossin(X, compute_u=False, **kwargs)
assert U.shape == (0, 0)
assert_allclose(CS, CS0, rtol=1e-15)
assert_allclose(Vh, Vh0, rtol=1e-15)
# `compute_vh=False`
u, cs, vh = linalg.cossin(X, separate=True, compute_vh=False, **kwargs)
assert_allclose(u[0], u[0], rtol=1e-15)
assert_allclose(u[1], u[1], rtol=1e-15)
assert_allclose(cs, cs0, rtol=1e-15)
assert vh[0].shape == (0, 0)
assert vh[1].shape == (0, 0)
U, CS, Vh = linalg.cossin(X, compute_vh=False, **kwargs)
assert_allclose(U, U0, rtol=1e-15)
assert_allclose(CS, CS0, rtol=1e-15)
assert Vh.shape == (0, 0)
# `compute_u=False, compute_vh=False`
u, cs, vh = linalg.cossin(X, separate=True, compute_u=False,
compute_vh=False, **kwargs)
assert u[0].shape == (0, 0)
assert u[1].shape == (0, 0)
assert_allclose(cs, cs0, rtol=1e-15)
assert vh[0].shape == (0, 0)
assert vh[1].shape == (0, 0)
U, CS, Vh = linalg.cossin(X, compute_u=False, compute_vh=False, **kwargs)
assert U.shape == (0, 0)
assert_allclose(CS, CS0, rtol=1e-15)
assert Vh.shape == (0, 0)
def test_indexing_bug_gh19365():
# Regression test for gh-19365, which reported a bug with `separate=False`
rng = np.random.default_rng(32954827234421)
m = rng.integers(50, high=100)
p = rng.integers(10, 40) # always p < m
q = rng.integers(m - p + 1, m - 1) # always m-p < q < m
X = unitary_group.rvs(m, random_state=rng) # random unitary matrix
U, D, Vt = linalg.cossin(X, p=p, q=q, separate=False)
assert np.allclose(U @ D @ Vt, X)

View file

@ -0,0 +1,137 @@
from numpy.testing import assert_array_almost_equal, assert_allclose, assert_
from numpy import (array, eye, zeros, empty_like, empty, tril_indices_from,
tril, triu_indices_from, spacing, float32, float64,
complex64, complex128)
from numpy.exceptions import ComplexWarning
from numpy.random import rand, randint, seed
from scipy.linalg import ldl
import pytest
from pytest import raises as assert_raises, warns
@pytest.mark.thread_unsafe
def test_args():
A = eye(3)
# Nonsquare array
assert_raises(ValueError, ldl, A[:, :2])
# Complex matrix with imaginary diagonal entries with "hermitian=True"
with warns(ComplexWarning):
ldl(A*1j)
def test_empty_array():
a = empty((0, 0), dtype=complex)
l, d, p = ldl(empty((0, 0)))
assert_array_almost_equal(l, empty_like(a))
assert_array_almost_equal(d, empty_like(a))
assert_array_almost_equal(p, array([], dtype=int))
def test_simple():
a = array([[-0.39-0.71j, 5.14-0.64j, -7.86-2.96j, 3.80+0.92j],
[5.14-0.64j, 8.86+1.81j, -3.52+0.58j, 5.32-1.59j],
[-7.86-2.96j, -3.52+0.58j, -2.83-0.03j, -1.54-2.86j],
[3.80+0.92j, 5.32-1.59j, -1.54-2.86j, -0.56+0.12j]])
b = array([[5., 10, 1, 18],
[10., 2, 11, 1],
[1., 11, 19, 9],
[18., 1, 9, 0]])
c = array([[52., 97, 112, 107, 50],
[97., 114, 89, 98, 13],
[112., 89, 64, 33, 6],
[107., 98, 33, 60, 73],
[50., 13, 6, 73, 77]])
d = array([[2., 2, -4, 0, 4],
[2., -2, -2, 10, -8],
[-4., -2, 6, -8, -4],
[0., 10, -8, 6, -6],
[4., -8, -4, -6, 10]])
e = array([[-1.36+0.00j, 0+0j, 0+0j, 0+0j],
[1.58-0.90j, -8.87+0j, 0+0j, 0+0j],
[2.21+0.21j, -1.84+0.03j, -4.63+0j, 0+0j],
[3.91-1.50j, -1.78-1.18j, 0.11-0.11j, -1.84+0.00j]])
for x in (b, c, d):
l, d, p = ldl(x)
assert_allclose(l.dot(d).dot(l.T), x, atol=spacing(1000.), rtol=0)
u, d, p = ldl(x, lower=False)
assert_allclose(u.dot(d).dot(u.T), x, atol=spacing(1000.), rtol=0)
l, d, p = ldl(a, hermitian=False)
assert_allclose(l.dot(d).dot(l.T), a, atol=spacing(1000.), rtol=0)
u, d, p = ldl(a, lower=False, hermitian=False)
assert_allclose(u.dot(d).dot(u.T), a, atol=spacing(1000.), rtol=0)
# Use upper part for the computation and use the lower part for comparison
l, d, p = ldl(e.conj().T, lower=0)
assert_allclose(tril(l.dot(d).dot(l.conj().T)-e), zeros((4, 4)),
atol=spacing(1000.), rtol=0)
def test_permutations():
seed(1234)
for _ in range(10):
n = randint(1, 100)
# Random real/complex array
x = rand(n, n) if randint(2) else rand(n, n) + rand(n, n)*1j
x = x + x.conj().T
x += eye(n)*randint(5, 1e6)
l_ind = tril_indices_from(x, k=-1)
u_ind = triu_indices_from(x, k=1)
# Test whether permutations lead to a triangular array
u, d, p = ldl(x, lower=0)
# lower part should be zero
assert_(not any(u[p, :][l_ind]), f'Spin {_} failed')
l, d, p = ldl(x, lower=1)
# upper part should be zero
assert_(not any(l[p, :][u_ind]), f'Spin {_} failed')
@pytest.mark.parametrize("dtype", [float32, float64])
@pytest.mark.parametrize("n", [30, 150])
def test_ldl_type_size_combinations_real(n, dtype):
seed(1234)
msg = (f"Failed for size: {n}, dtype: {dtype}")
x = rand(n, n).astype(dtype)
x = x + x.T
x += eye(n, dtype=dtype)*dtype(randint(5, 1e6))
l, d1, p = ldl(x)
u, d2, p = ldl(x, lower=0)
rtol = 1e-4 if dtype is float32 else 1e-10
assert_allclose(l.dot(d1).dot(l.T), x, rtol=rtol, err_msg=msg)
assert_allclose(u.dot(d2).dot(u.T), x, rtol=rtol, err_msg=msg)
@pytest.mark.parametrize("dtype", [complex64, complex128])
@pytest.mark.parametrize("n", [30, 150])
def test_ldl_type_size_combinations_complex(n, dtype):
seed(1234)
msg1 = (f"Her failed for size: {n}, dtype: {dtype}")
msg2 = (f"Sym failed for size: {n}, dtype: {dtype}")
# Complex hermitian upper/lower
x = (rand(n, n)+1j*rand(n, n)).astype(dtype)
x = x+x.conj().T
x += eye(n, dtype=dtype)*dtype(randint(5, 1e6))
l, d1, p = ldl(x)
u, d2, p = ldl(x, lower=0)
rtol = 2e-4 if dtype is complex64 else 1e-10
assert_allclose(l.dot(d1).dot(l.conj().T), x, rtol=rtol, err_msg=msg1)
assert_allclose(u.dot(d2).dot(u.conj().T), x, rtol=rtol, err_msg=msg1)
# Complex symmetric upper/lower
x = (rand(n, n)+1j*rand(n, n)).astype(dtype)
x = x+x.T
x += eye(n, dtype=dtype)*dtype(randint(5, 1e6))
l, d1, p = ldl(x, hermitian=0)
u, d2, p = ldl(x, lower=0, hermitian=0)
assert_allclose(l.dot(d1).dot(l.T), x, rtol=rtol, err_msg=msg2)
assert_allclose(u.dot(d2).dot(u.T), x, rtol=rtol, err_msg=msg2)

View file

@ -0,0 +1,308 @@
import pytest
from pytest import raises as assert_raises
import numpy as np
from scipy.linalg import lu, lu_factor, lu_solve, get_lapack_funcs, solve
from numpy.testing import assert_allclose, assert_array_equal, assert_equal
REAL_DTYPES = [np.float32, np.float64]
COMPLEX_DTYPES = [np.complex64, np.complex128]
DTYPES = REAL_DTYPES + COMPLEX_DTYPES
class TestLU:
def setup_method(self):
self.rng = np.random.default_rng(1682281250228846)
def test_old_lu_smoke_tests(self):
"Tests from old fortran based lu test suite"
a = np.array([[1, 2, 3], [1, 2, 3], [2, 5, 6]])
p, l, u = lu(a)
result_lu = np.array([[2., 5., 6.], [0.5, -0.5, 0.], [0.5, 1., 0.]])
assert_allclose(p, np.rot90(np.eye(3)))
assert_allclose(l, np.tril(result_lu, k=-1)+np.eye(3))
assert_allclose(u, np.triu(result_lu))
a = np.array([[1, 2, 3], [1, 2, 3], [2, 5j, 6]])
p, l, u = lu(a)
result_lu = np.array([[2., 5.j, 6.], [0.5, 2-2.5j, 0.], [0.5, 1., 0.]])
assert_allclose(p, np.rot90(np.eye(3)))
assert_allclose(l, np.tril(result_lu, k=-1)+np.eye(3))
assert_allclose(u, np.triu(result_lu))
b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
p, l, u = lu(b)
assert_allclose(p, np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]))
assert_allclose(l, np.array([[1, 0, 0], [1/7, 1, 0], [4/7, 0.5, 1]]))
assert_allclose(u, np.array([[7, 8, 9], [0, 6/7, 12/7], [0, 0, 0]]),
rtol=0., atol=1e-14)
cb = np.array([[1.j, 2.j, 3.j], [4j, 5j, 6j], [7j, 8j, 9j]])
p, l, u = lu(cb)
assert_allclose(p, np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]))
assert_allclose(l, np.array([[1, 0, 0], [1/7, 1, 0], [4/7, 0.5, 1]]))
assert_allclose(u, np.array([[7, 8, 9], [0, 6/7, 12/7], [0, 0, 0]])*1j,
rtol=0., atol=1e-14)
# Rectangular matrices
hrect = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 12, 12]])
p, l, u = lu(hrect)
assert_allclose(p, np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]))
assert_allclose(l, np.array([[1, 0, 0], [1/9, 1, 0], [5/9, 0.5, 1]]))
assert_allclose(u, np.array([[9, 10, 12, 12], [0, 8/9, 15/9, 24/9],
[0, 0, -0.5, 0]]), rtol=0., atol=1e-14)
chrect = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 12, 12]])*1.j
p, l, u = lu(chrect)
assert_allclose(p, np.array([[0, 1, 0], [0, 0, 1], [1, 0, 0]]))
assert_allclose(l, np.array([[1, 0, 0], [1/9, 1, 0], [5/9, 0.5, 1]]))
assert_allclose(u, np.array([[9, 10, 12, 12], [0, 8/9, 15/9, 24/9],
[0, 0, -0.5, 0]])*1j, rtol=0., atol=1e-14)
vrect = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 12, 12]])
p, l, u = lu(vrect)
assert_allclose(p, np.eye(4)[[1, 3, 2, 0], :])
assert_allclose(l, np.array([[1., 0, 0], [0.1, 1, 0], [0.7, -0.5, 1],
[0.4, 0.25, 0.5]]))
assert_allclose(u, np.array([[10, 12, 12],
[0, 0.8, 1.8],
[0, 0, 1.5]]))
cvrect = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 12, 12]])*1j
p, l, u = lu(cvrect)
assert_allclose(p, np.eye(4)[[1, 3, 2, 0], :])
assert_allclose(l, np.array([[1., 0, 0],
[0.1, 1, 0],
[0.7, -0.5, 1],
[0.4, 0.25, 0.5]]))
assert_allclose(u, np.array([[10, 12, 12],
[0, 0.8, 1.8],
[0, 0, 1.5]])*1j)
@pytest.mark.parametrize('shape', [[2, 2], [2, 4], [4, 2], [20, 20],
[20, 4], [4, 20], [3, 2, 9, 9],
[2, 2, 17, 5], [2, 2, 11, 7]])
def test_simple_lu_shapes_real_complex(self, shape):
a = self.rng.uniform(-10., 10., size=shape)
p, l, u = lu(a)
assert_allclose(a, p @ l @ u)
pl, u = lu(a, permute_l=True)
assert_allclose(a, pl @ u)
b = self.rng.uniform(-10., 10., size=shape)*1j
b += self.rng.uniform(-10, 10, size=shape)
pl, u = lu(b, permute_l=True)
assert_allclose(b, pl @ u)
@pytest.mark.parametrize('shape', [[2, 2], [2, 4], [4, 2], [20, 20],
[20, 4], [4, 20]])
def test_simple_lu_shapes_real_complex_2d_indices(self, shape):
a = self.rng.uniform(-10., 10., size=shape)
p, l, u = lu(a, p_indices=True)
assert_allclose(a, l[p, :] @ u)
def test_1by1_input_output(self):
a = self.rng.random([4, 5, 1, 1], dtype=np.float32)
p, l, u = lu(a, p_indices=True)
assert_allclose(p, np.zeros(shape=(4, 5, 1), dtype=int))
assert_allclose(l, np.ones(shape=(4, 5, 1, 1), dtype=np.float32))
assert_allclose(u, a)
a = self.rng.random([4, 5, 1, 1], dtype=np.float32)
p, l, u = lu(a)
assert_allclose(p, np.ones(shape=(4, 5, 1, 1), dtype=np.float32))
assert_allclose(l, np.ones(shape=(4, 5, 1, 1), dtype=np.float32))
assert_allclose(u, a)
pl, u = lu(a, permute_l=True)
assert_allclose(pl, np.ones(shape=(4, 5, 1, 1), dtype=np.float32))
assert_allclose(u, a)
a = self.rng.random([4, 5, 1, 1], dtype=np.float32)*np.complex64(1.j)
p, l, u = lu(a)
assert_allclose(p, np.ones(shape=(4, 5, 1, 1), dtype=np.complex64))
assert_allclose(l, np.ones(shape=(4, 5, 1, 1), dtype=np.complex64))
assert_allclose(u, a)
def test_empty_edge_cases(self):
a = np.empty([0, 0])
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(0, 0), dtype=np.float64))
assert_allclose(l, np.empty(shape=(0, 0), dtype=np.float64))
assert_allclose(u, np.empty(shape=(0, 0), dtype=np.float64))
a = np.empty([0, 3], dtype=np.float16)
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(0, 0), dtype=np.float32))
assert_allclose(l, np.empty(shape=(0, 0), dtype=np.float32))
assert_allclose(u, np.empty(shape=(0, 3), dtype=np.float32))
a = np.empty([3, 0], dtype=np.complex64)
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(0, 0), dtype=np.float32))
assert_allclose(l, np.empty(shape=(3, 0), dtype=np.complex64))
assert_allclose(u, np.empty(shape=(0, 0), dtype=np.complex64))
p, l, u = lu(a, p_indices=True)
assert_allclose(p, np.empty(shape=(0,), dtype=int))
assert_allclose(l, np.empty(shape=(3, 0), dtype=np.complex64))
assert_allclose(u, np.empty(shape=(0, 0), dtype=np.complex64))
pl, u = lu(a, permute_l=True)
assert_allclose(pl, np.empty(shape=(3, 0), dtype=np.complex64))
assert_allclose(u, np.empty(shape=(0, 0), dtype=np.complex64))
a = np.empty([3, 0, 0], dtype=np.complex64)
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(3, 0, 0), dtype=np.float32))
assert_allclose(l, np.empty(shape=(3, 0, 0), dtype=np.complex64))
assert_allclose(u, np.empty(shape=(3, 0, 0), dtype=np.complex64))
a = np.empty([0, 0, 3])
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(0, 0, 0)))
assert_allclose(l, np.empty(shape=(0, 0, 0)))
assert_allclose(u, np.empty(shape=(0, 0, 3)))
with assert_raises(ValueError, match='at least two-dimensional'):
lu(np.array([]))
a = np.array([[]])
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(0, 0)))
assert_allclose(l, np.empty(shape=(1, 0)))
assert_allclose(u, np.empty(shape=(0, 0)))
a = np.array([[[]]])
p, l, u = lu(a)
assert_allclose(p, np.empty(shape=(1, 0, 0)))
assert_allclose(l, np.empty(shape=(1, 1, 0)))
assert_allclose(u, np.empty(shape=(1, 0, 0)))
class TestLUFactor:
def setup_method(self):
self.rng = np.random.default_rng(1682281250228846)
self.a = np.array([[1, 2, 3], [1, 2, 3], [2, 5, 6]])
self.ca = np.array([[1, 2, 3], [1, 2, 3], [2, 5j, 6]])
# Those matrices are more robust to detect problems in permutation
# matrices than the ones above
self.b = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
self.cb = np.array([[1j, 2j, 3j], [4j, 5j, 6j], [7j, 8j, 9j]])
# Rectangular matrices
self.hrect = np.array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 12, 12]])
self.chrect = np.array([[1, 2, 3, 4], [5, 6, 7, 8],
[9, 10, 12, 12]]) * 1.j
self.vrect = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 12, 12]])
self.cvrect = 1.j * np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 12, 12]])
# Medium sizes matrices
self.med = self.rng.random((30, 40))
self.cmed = self.rng.random((30, 40)) + 1.j*self.rng.random((30, 40))
def _test_common_lu_factor(self, data):
l_and_u1, piv1 = lu_factor(data)
(getrf,) = get_lapack_funcs(("getrf",), (data,))
l_and_u2, piv2, _ = getrf(data, overwrite_a=False)
assert_allclose(l_and_u1, l_and_u2)
assert_allclose(piv1, piv2)
# Simple tests.
# For lu_factor gives a LinAlgWarning because these matrices are singular
def test_hrectangular(self):
self._test_common_lu_factor(self.hrect)
def test_vrectangular(self):
self._test_common_lu_factor(self.vrect)
def test_hrectangular_complex(self):
self._test_common_lu_factor(self.chrect)
def test_vrectangular_complex(self):
self._test_common_lu_factor(self.cvrect)
# Bigger matrices
def test_medium1(self):
"""Check lu decomposition on medium size, rectangular matrix."""
self._test_common_lu_factor(self.med)
def test_medium1_complex(self):
"""Check lu decomposition on medium size, rectangular matrix."""
self._test_common_lu_factor(self.cmed)
def test_check_finite(self):
p, l, u = lu(self.a, check_finite=False)
assert_allclose(p @ l @ u, self.a)
def test_simple_known(self):
# Ticket #1458
for order in ['C', 'F']:
A = np.array([[2, 1], [0, 1.]], order=order)
LU, P = lu_factor(A)
assert_allclose(LU, np.array([[2, 1], [0, 1]]))
assert_array_equal(P, np.array([0, 1]))
@pytest.mark.parametrize("m", [0, 1, 2])
@pytest.mark.parametrize("n", [0, 1, 2])
@pytest.mark.parametrize('dtype', DTYPES)
def test_shape_dtype(self, m, n, dtype):
k = min(m, n)
a = np.eye(m, n, dtype=dtype)
lu, p = lu_factor(a)
assert_equal(lu.shape, (m, n))
assert_equal(lu.dtype, dtype)
assert_equal(p.shape, (k,))
assert_equal(p.dtype, np.int32)
@pytest.mark.parametrize(("m", "n"), [(0, 0), (0, 2), (2, 0)])
def test_empty(self, m, n):
a = np.zeros((m, n))
lu, p = lu_factor(a)
assert_allclose(lu, np.empty((m, n)))
assert_allclose(p, np.arange(0))
class TestLUSolve:
def setup_method(self):
self.rng = np.random.default_rng(1682281250228846)
def test_lu(self):
a0 = self.rng.random((10, 10))
b = self.rng.random((10,))
for order in ['C', 'F']:
a = np.array(a0, order=order)
x1 = solve(a, b)
lu_a = lu_factor(a)
x2 = lu_solve(lu_a, b)
assert_allclose(x1, x2)
def test_check_finite(self):
a = self.rng.random((10, 10))
b = self.rng.random((10,))
x1 = solve(a, b)
lu_a = lu_factor(a, check_finite=False)
x2 = lu_solve(lu_a, b, check_finite=False)
assert_allclose(x1, x2)
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64])
@pytest.mark.parametrize('dt_b', [int, float, np.float32, complex, np.complex64])
def test_empty(self, dt, dt_b):
lu_and_piv = (np.empty((0, 0), dtype=dt), np.array([]))
b = np.asarray([], dtype=dt_b)
x = lu_solve(lu_and_piv, b)
assert x.shape == (0,)
m = lu_solve((np.eye(2, dtype=dt), [0, 1]), np.ones(2, dtype=dt_b))
assert x.dtype == m.dtype
b = np.empty((0, 0), dtype=dt_b)
x = lu_solve(lu_and_piv, b)
assert x.shape == (0, 0)
assert x.dtype == m.dtype

View file

@ -0,0 +1,110 @@
import pytest
import numpy as np
from numpy.linalg import norm
from numpy.testing import (assert_, assert_allclose, assert_equal)
from scipy.linalg import polar, eigh
diag2 = np.array([[2, 0], [0, 3]])
a13 = np.array([[1, 2, 2]])
precomputed_cases = [
[[[0]], 'right', [[1]], [[0]]],
[[[0]], 'left', [[1]], [[0]]],
[[[9]], 'right', [[1]], [[9]]],
[[[9]], 'left', [[1]], [[9]]],
[diag2, 'right', np.eye(2), diag2],
[diag2, 'left', np.eye(2), diag2],
[a13, 'right', a13/norm(a13[0]), a13.T.dot(a13)/norm(a13[0])],
]
verify_cases = [
[[1, 2], [3, 4]],
[[1, 2, 3]],
[[1], [2], [3]],
[[1, 2, 3], [3, 4, 0]],
[[1, 2], [3, 4], [5, 5]],
[[1, 2], [3, 4+5j]],
[[1, 2, 3j]],
[[1], [2], [3j]],
[[1, 2, 3+2j], [3, 4-1j, -4j]],
[[1, 2], [3-2j, 4+0.5j], [5, 5]],
[[10000, 10, 1], [-1, 2, 3j], [0, 1, 2]],
np.empty((0, 0)),
np.empty((0, 2)),
np.empty((2, 0)),
]
def check_precomputed_polar(a, side, expected_u, expected_p):
# Compare the result of the polar decomposition to a
# precomputed result.
u, p = polar(a, side=side)
assert_allclose(u, expected_u, atol=1e-15)
assert_allclose(p, expected_p, atol=1e-15)
def verify_polar(a):
# Compute the polar decomposition, and then verify that
# the result has all the expected properties.
product_atol = np.sqrt(np.finfo(float).eps)
aa = np.asarray(a)
m, n = aa.shape
u, p = polar(a, side='right')
assert_equal(u.shape, (m, n))
assert_equal(p.shape, (n, n))
# a = up
assert_allclose(u.dot(p), a, atol=product_atol)
if m >= n:
assert_allclose(u.conj().T.dot(u), np.eye(n), atol=1e-15)
else:
assert_allclose(u.dot(u.conj().T), np.eye(m), atol=1e-15)
# p is Hermitian positive semidefinite.
assert_allclose(p.conj().T, p)
evals = eigh(p, eigvals_only=True)
nonzero_evals = evals[abs(evals) > 1e-14]
assert_((nonzero_evals >= 0).all())
u, p = polar(a, side='left')
assert_equal(u.shape, (m, n))
assert_equal(p.shape, (m, m))
# a = pu
assert_allclose(p.dot(u), a, atol=product_atol)
if m >= n:
assert_allclose(u.conj().T.dot(u), np.eye(n), atol=1e-15)
else:
assert_allclose(u.dot(u.conj().T), np.eye(m), atol=1e-15)
# p is Hermitian positive semidefinite.
assert_allclose(p.conj().T, p)
evals = eigh(p, eigvals_only=True)
nonzero_evals = evals[abs(evals) > 1e-14]
assert_((nonzero_evals >= 0).all())
def test_precomputed_cases():
for a, side, expected_u, expected_p in precomputed_cases:
check_precomputed_polar(a, side, expected_u, expected_p)
def test_verify_cases():
for a in verify_cases:
verify_polar(a)
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64])
@pytest.mark.parametrize('shape', [(0, 0), (0, 2), (2, 0)])
@pytest.mark.parametrize('side', ['left', 'right'])
def test_empty(dt, shape, side):
a = np.empty(shape, dtype=dt)
m, n = shape
p_shape = (m, m) if side == 'left' else (n, n)
u, p = polar(a, side=side)
u_n, p_n = polar(np.eye(5, dtype=dt))
assert_equal(u.dtype, u_n.dtype)
assert_equal(p.dtype, p_n.dtype)
assert u.shape == shape
assert p.shape == p_shape
assert np.all(p == 0)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
import os
import platform
import sysconfig
import numpy as np
import pytest
from scipy._lib._testutils import IS_EDITABLE, _test_cython_extension, cython
from scipy.linalg.blas import cdotu # type: ignore[attr-defined]
from scipy.linalg.lapack import dgtsv # type: ignore[attr-defined]
@pytest.mark.fail_slow(120)
# essential per https://github.com/scipy/scipy/pull/20487#discussion_r1567057247
@pytest.mark.skipif(IS_EDITABLE,
reason='Editable install cannot find .pxd headers.')
@pytest.mark.skipif((platform.system() == 'Windows' and
sysconfig.get_config_var('Py_GIL_DISABLED')),
reason='gh-22039')
@pytest.mark.skipif(platform.machine() in ["wasm32", "wasm64"],
reason="Can't start subprocess")
@pytest.mark.skipif(cython is None, reason="requires cython")
def test_cython(tmp_path):
srcdir = os.path.dirname(os.path.dirname(__file__))
extensions, extensions_cpp = _test_cython_extension(tmp_path, srcdir)
# actually test the cython c-extensions
a = np.ones(8) * 3
b = np.ones(9)
c = np.ones(8) * 4
x = np.ones(9)
_, _, _, x, _ = dgtsv(a, b, c, x)
a = np.ones(8) * 3
b = np.ones(9)
c = np.ones(8) * 4
x_c = np.ones(9)
extensions.tridiag(a, b, c, x_c)
a = np.ones(8) * 3
b = np.ones(9)
c = np.ones(8) * 4
x_cpp = np.ones(9)
extensions_cpp.tridiag(a, b, c, x_cpp)
np.testing.assert_array_equal(x, x_cpp)
cx = np.array([1-1j, 2+2j, 3-3j], dtype=np.complex64)
cy = np.array([4+4j, 5-5j, 6+6j], dtype=np.complex64)
np.testing.assert_array_equal(cdotu(cx, cy), extensions.complex_dot(cx, cy))
np.testing.assert_array_equal(cdotu(cx, cy), extensions_cpp.complex_dot(cx, cy))

View file

@ -0,0 +1,607 @@
# Test interfaces to fortran blas.
#
# The tests are more of interface than they are of the underlying blas.
# Only very small matrices checked -- N=3 or so.
#
# !! Complex calculations really aren't checked that carefully.
# !! Only real valued complex numbers are used in tests.
from numpy import float32, float64, complex64, complex128, arange, array, \
zeros, shape, transpose, newaxis, common_type, conjugate
from scipy.linalg import _fblas as fblas
from numpy.testing import assert_array_equal, \
assert_allclose, assert_array_almost_equal, assert_
import pytest
# decimal accuracy to require between Python and LAPACK/BLAS calculations
accuracy = 5
# Since numpy.dot likely uses the same blas, use this routine
# to check.
def matrixmultiply(a, b):
if len(b.shape) == 1:
b_is_vector = True
b = b[:, newaxis]
else:
b_is_vector = False
assert_(a.shape[1] == b.shape[0])
c = zeros((a.shape[0], b.shape[1]), common_type(a, b))
for i in range(a.shape[0]):
for j in range(b.shape[1]):
s = 0
for k in range(a.shape[1]):
s += a[i, k] * b[k, j]
c[i, j] = s
if b_is_vector:
c = c.reshape((a.shape[0],))
return c
##################################################
# Test blas ?axpy
class BaseAxpy:
''' Mixin class for axpy tests '''
def test_default_a(self):
x = arange(3., dtype=self.dtype)
y = arange(3., dtype=x.dtype)
real_y = x*1.+y
y = self.blas_func(x, y)
assert_array_equal(real_y, y)
def test_simple(self):
x = arange(3., dtype=self.dtype)
y = arange(3., dtype=x.dtype)
real_y = x*3.+y
y = self.blas_func(x, y, a=3.)
assert_array_equal(real_y, y)
def test_x_stride(self):
x = arange(6., dtype=self.dtype)
y = zeros(3, x.dtype)
y = arange(3., dtype=x.dtype)
real_y = x[::2]*3.+y
y = self.blas_func(x, y, a=3., n=3, incx=2)
assert_array_equal(real_y, y)
def test_y_stride(self):
x = arange(3., dtype=self.dtype)
y = zeros(6, x.dtype)
real_y = x*3.+y[::2]
y = self.blas_func(x, y, a=3., n=3, incy=2)
assert_array_equal(real_y, y[::2])
def test_x_and_y_stride(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
real_y = x[::4]*3.+y[::2]
y = self.blas_func(x, y, a=3., n=3, incx=4, incy=2)
assert_array_equal(real_y, y[::2])
def test_x_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=4, incx=5)
def test_y_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=3, incy=5)
try:
class TestSaxpy(BaseAxpy):
blas_func = fblas.saxpy
dtype = float32
except AttributeError:
class TestSaxpy:
pass
class TestDaxpy(BaseAxpy):
blas_func = fblas.daxpy
dtype = float64
try:
class TestCaxpy(BaseAxpy):
blas_func = fblas.caxpy
dtype = complex64
except AttributeError:
class TestCaxpy:
pass
class TestZaxpy(BaseAxpy):
blas_func = fblas.zaxpy
dtype = complex128
##################################################
# Test blas ?scal
class BaseScal:
''' Mixin class for scal testing '''
def test_simple(self):
x = arange(3., dtype=self.dtype)
real_x = x*3.
x = self.blas_func(3., x)
assert_array_equal(real_x, x)
def test_x_stride(self):
x = arange(6., dtype=self.dtype)
real_x = x.copy()
real_x[::2] = x[::2]*array(3., self.dtype)
x = self.blas_func(3., x, n=3, incx=2)
assert_array_equal(real_x, x)
def test_x_bad_size(self):
x = arange(12., dtype=self.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(2., x, n=4, incx=5)
try:
class TestSscal(BaseScal):
blas_func = fblas.sscal
dtype = float32
except AttributeError:
class TestSscal:
pass
class TestDscal(BaseScal):
blas_func = fblas.dscal
dtype = float64
try:
class TestCscal(BaseScal):
blas_func = fblas.cscal
dtype = complex64
except AttributeError:
class TestCscal:
pass
class TestZscal(BaseScal):
blas_func = fblas.zscal
dtype = complex128
##################################################
# Test blas ?copy
class BaseCopy:
''' Mixin class for copy testing '''
def test_simple(self):
x = arange(3., dtype=self.dtype)
y = zeros(shape(x), x.dtype)
y = self.blas_func(x, y)
assert_array_equal(x, y)
def test_x_stride(self):
x = arange(6., dtype=self.dtype)
y = zeros(3, x.dtype)
y = self.blas_func(x, y, n=3, incx=2)
assert_array_equal(x[::2], y)
def test_y_stride(self):
x = arange(3., dtype=self.dtype)
y = zeros(6, x.dtype)
y = self.blas_func(x, y, n=3, incy=2)
assert_array_equal(x, y[::2])
def test_x_and_y_stride(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
y = self.blas_func(x, y, n=3, incx=4, incy=2)
assert_array_equal(x[::4], y[::2])
def test_x_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=4, incx=5)
def test_y_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=3, incy=5)
# def test_y_bad_type(self):
## Hmmm. Should this work? What should be the output.
# x = arange(3.,dtype=self.dtype)
# y = zeros(shape(x))
# self.blas_func(x,y)
# assert_array_equal(x,y)
try:
class TestScopy(BaseCopy):
blas_func = fblas.scopy
dtype = float32
except AttributeError:
class TestScopy:
pass
class TestDcopy(BaseCopy):
blas_func = fblas.dcopy
dtype = float64
try:
class TestCcopy(BaseCopy):
blas_func = fblas.ccopy
dtype = complex64
except AttributeError:
class TestCcopy:
pass
class TestZcopy(BaseCopy):
blas_func = fblas.zcopy
dtype = complex128
##################################################
# Test blas ?swap
class BaseSwap:
''' Mixin class for swap tests '''
def test_simple(self):
x = arange(3., dtype=self.dtype)
y = zeros(shape(x), x.dtype)
desired_x = y.copy()
desired_y = x.copy()
x, y = self.blas_func(x, y)
assert_array_equal(desired_x, x)
assert_array_equal(desired_y, y)
def test_x_stride(self):
x = arange(6., dtype=self.dtype)
y = zeros(3, x.dtype)
desired_x = y.copy()
desired_y = x.copy()[::2]
x, y = self.blas_func(x, y, n=3, incx=2)
assert_array_equal(desired_x, x[::2])
assert_array_equal(desired_y, y)
def test_y_stride(self):
x = arange(3., dtype=self.dtype)
y = zeros(6, x.dtype)
desired_x = y.copy()[::2]
desired_y = x.copy()
x, y = self.blas_func(x, y, n=3, incy=2)
assert_array_equal(desired_x, x)
assert_array_equal(desired_y, y[::2])
def test_x_and_y_stride(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
desired_x = y.copy()[::2]
desired_y = x.copy()[::4]
x, y = self.blas_func(x, y, n=3, incx=4, incy=2)
assert_array_equal(desired_x, x[::4])
assert_array_equal(desired_y, y[::2])
def test_x_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=4, incx=5)
def test_y_bad_size(self):
x = arange(12., dtype=self.dtype)
y = zeros(6, x.dtype)
with pytest.raises(Exception, match='failed for 1st keyword'):
self.blas_func(x, y, n=3, incy=5)
try:
class TestSswap(BaseSwap):
blas_func = fblas.sswap
dtype = float32
except AttributeError:
class TestSswap:
pass
class TestDswap(BaseSwap):
blas_func = fblas.dswap
dtype = float64
try:
class TestCswap(BaseSwap):
blas_func = fblas.cswap
dtype = complex64
except AttributeError:
class TestCswap:
pass
class TestZswap(BaseSwap):
blas_func = fblas.zswap
dtype = complex128
##################################################
# Test blas ?gemv
# This will be a mess to test all cases.
class BaseGemv:
''' Mixin class for gemv tests '''
def get_data(self, x_stride=1, y_stride=1):
mult = array(1, dtype=self.dtype)
if self.dtype in [complex64, complex128]:
mult = array(1+1j, dtype=self.dtype)
from numpy.random import normal, seed
seed(1234)
alpha = array(1., dtype=self.dtype) * mult
beta = array(1., dtype=self.dtype) * mult
a = normal(0., 1., (3, 3)).astype(self.dtype) * mult
x = arange(shape(a)[0]*x_stride, dtype=self.dtype) * mult
y = arange(shape(a)[1]*y_stride, dtype=self.dtype) * mult
return alpha, beta, a, x, y
def test_simple(self):
alpha, beta, a, x, y = self.get_data()
desired_y = alpha*matrixmultiply(a, x)+beta*y
y = self.blas_func(alpha, a, x, beta, y)
assert_array_almost_equal(desired_y, y)
def test_default_beta_y(self):
alpha, beta, a, x, y = self.get_data()
desired_y = matrixmultiply(a, x)
y = self.blas_func(1, a, x)
assert_array_almost_equal(desired_y, y)
def test_simple_transpose(self):
alpha, beta, a, x, y = self.get_data()
desired_y = alpha*matrixmultiply(transpose(a), x)+beta*y
y = self.blas_func(alpha, a, x, beta, y, trans=1)
assert_array_almost_equal(desired_y, y)
def test_simple_transpose_conj(self):
alpha, beta, a, x, y = self.get_data()
desired_y = alpha*matrixmultiply(transpose(conjugate(a)), x)+beta*y
y = self.blas_func(alpha, a, x, beta, y, trans=2)
assert_array_almost_equal(desired_y, y)
def test_x_stride(self):
alpha, beta, a, x, y = self.get_data(x_stride=2)
desired_y = alpha*matrixmultiply(a, x[::2])+beta*y
y = self.blas_func(alpha, a, x, beta, y, incx=2)
assert_array_almost_equal(desired_y, y)
def test_x_stride_transpose(self):
alpha, beta, a, x, y = self.get_data(x_stride=2)
desired_y = alpha*matrixmultiply(transpose(a), x[::2])+beta*y
y = self.blas_func(alpha, a, x, beta, y, trans=1, incx=2)
assert_array_almost_equal(desired_y, y)
def test_x_stride_assert(self):
# What is the use of this test?
alpha, beta, a, x, y = self.get_data(x_stride=2)
with pytest.raises(Exception, match='failed for 3rd argument'):
y = self.blas_func(1, a, x, 1, y, trans=0, incx=3)
with pytest.raises(Exception, match='failed for 3rd argument'):
y = self.blas_func(1, a, x, 1, y, trans=1, incx=3)
def test_y_stride(self):
alpha, beta, a, x, y = self.get_data(y_stride=2)
desired_y = y.copy()
desired_y[::2] = alpha*matrixmultiply(a, x)+beta*y[::2]
y = self.blas_func(alpha, a, x, beta, y, incy=2)
assert_array_almost_equal(desired_y, y)
def test_y_stride_transpose(self):
alpha, beta, a, x, y = self.get_data(y_stride=2)
desired_y = y.copy()
desired_y[::2] = alpha*matrixmultiply(transpose(a), x)+beta*y[::2]
y = self.blas_func(alpha, a, x, beta, y, trans=1, incy=2)
assert_array_almost_equal(desired_y, y)
def test_y_stride_assert(self):
# What is the use of this test?
alpha, beta, a, x, y = self.get_data(y_stride=2)
with pytest.raises(Exception, match='failed for 2nd keyword'):
y = self.blas_func(1, a, x, 1, y, trans=0, incy=3)
with pytest.raises(Exception, match='failed for 2nd keyword'):
y = self.blas_func(1, a, x, 1, y, trans=1, incy=3)
try:
class TestSgemv(BaseGemv):
blas_func = fblas.sgemv
dtype = float32
def test_sgemv_on_osx(self):
from itertools import product
import sys
import numpy as np
if sys.platform != 'darwin':
return
def aligned_array(shape, align, dtype, order='C'):
# Make array shape `shape` with aligned at `align` bytes
d = dtype()
# Make array of correct size with `align` extra bytes
N = np.prod(shape)
tmp = np.zeros(N * d.nbytes + align, dtype=np.uint8)
address = tmp.__array_interface__["data"][0]
# Find offset into array giving desired alignment
for offset in range(align):
if (address + offset) % align == 0:
break
tmp = tmp[offset:offset+N*d.nbytes].view(dtype=dtype)
return tmp.reshape(shape, order=order)
def as_aligned(arr, align, dtype, order='C'):
# Copy `arr` into an aligned array with same shape
aligned = aligned_array(arr.shape, align, dtype, order)
aligned[:] = arr[:]
return aligned
def assert_dot_close(A, X, desired):
assert_allclose(self.blas_func(1.0, A, X), desired,
rtol=1e-5, atol=1e-7)
testdata = product((15, 32), (10000,), (200, 89), ('C', 'F'))
for align, m, n, a_order in testdata:
A_d = np.random.rand(m, n)
X_d = np.random.rand(n)
desired = np.dot(A_d, X_d)
# Calculation with aligned single precision
A_f = as_aligned(A_d, align, np.float32, order=a_order)
X_f = as_aligned(X_d, align, np.float32, order=a_order)
assert_dot_close(A_f, X_f, desired)
except AttributeError:
class TestSgemv:
pass
class TestDgemv(BaseGemv):
blas_func = fblas.dgemv
dtype = float64
try:
class TestCgemv(BaseGemv):
blas_func = fblas.cgemv
dtype = complex64
except AttributeError:
class TestCgemv:
pass
class TestZgemv(BaseGemv):
blas_func = fblas.zgemv
dtype = complex128
"""
##################################################
### Test blas ?ger
### This will be a mess to test all cases.
class BaseGer:
def get_data(self,x_stride=1,y_stride=1):
from numpy.random import normal, seed
seed(1234)
alpha = array(1., dtype = self.dtype)
a = normal(0.,1.,(3,3)).astype(self.dtype)
x = arange(shape(a)[0]*x_stride,dtype=self.dtype)
y = arange(shape(a)[1]*y_stride,dtype=self.dtype)
return alpha,a,x,y
def test_simple(self):
alpha,a,x,y = self.get_data()
# transpose takes care of Fortran vs. C(and Python) memory layout
desired_a = alpha*transpose(x[:,newaxis]*y) + a
self.blas_func(x,y,a)
assert_array_almost_equal(desired_a,a)
def test_x_stride(self):
alpha,a,x,y = self.get_data(x_stride=2)
desired_a = alpha*transpose(x[::2,newaxis]*y) + a
self.blas_func(x,y,a,incx=2)
assert_array_almost_equal(desired_a,a)
def test_x_stride_assert(self):
alpha,a,x,y = self.get_data(x_stride=2)
with pytest.raises(ValueError, match='foo'):
self.blas_func(x,y,a,incx=3)
def test_y_stride(self):
alpha,a,x,y = self.get_data(y_stride=2)
desired_a = alpha*transpose(x[:,newaxis]*y[::2]) + a
self.blas_func(x,y,a,incy=2)
assert_array_almost_equal(desired_a,a)
def test_y_stride_assert(self):
alpha,a,x,y = self.get_data(y_stride=2)
with pytest.raises(ValueError, match='foo'):
self.blas_func(a,x,y,incy=3)
class TestSger(BaseGer):
blas_func = fblas.sger
dtype = float32
class TestDger(BaseGer):
blas_func = fblas.dger
dtype = float64
"""
##################################################
# Test blas ?gerc
# This will be a mess to test all cases.
"""
class BaseGerComplex(BaseGer):
def get_data(self,x_stride=1,y_stride=1):
from numpy.random import normal, seed
seed(1234)
alpha = array(1+1j, dtype = self.dtype)
a = normal(0.,1.,(3,3)).astype(self.dtype)
a = a + normal(0.,1.,(3,3)) * array(1j, dtype = self.dtype)
x = normal(0.,1.,shape(a)[0]*x_stride).astype(self.dtype)
x = x + x * array(1j, dtype = self.dtype)
y = normal(0.,1.,shape(a)[1]*y_stride).astype(self.dtype)
y = y + y * array(1j, dtype = self.dtype)
return alpha,a,x,y
def test_simple(self):
alpha,a,x,y = self.get_data()
# transpose takes care of Fortran vs. C(and Python) memory layout
a = a * array(0.,dtype = self.dtype)
#desired_a = alpha*transpose(x[:,newaxis]*self.transform(y)) + a
desired_a = alpha*transpose(x[:,newaxis]*y) + a
#self.blas_func(x,y,a,alpha = alpha)
fblas.cgeru(x,y,a,alpha = alpha)
assert_array_almost_equal(desired_a,a)
#def test_x_stride(self):
# alpha,a,x,y = self.get_data(x_stride=2)
# desired_a = alpha*transpose(x[::2,newaxis]*self.transform(y)) + a
# self.blas_func(x,y,a,incx=2)
# assert_array_almost_equal(desired_a,a)
#def test_y_stride(self):
# alpha,a,x,y = self.get_data(y_stride=2)
# desired_a = alpha*transpose(x[:,newaxis]*self.transform(y[::2])) + a
# self.blas_func(x,y,a,incy=2)
# assert_array_almost_equal(desired_a,a)
class TestCgeru(BaseGerComplex):
blas_func = fblas.cgeru
dtype = complex64
def transform(self,x):
return x
class TestZgeru(BaseGerComplex):
blas_func = fblas.zgeru
dtype = complex128
def transform(self,x):
return x
class TestCgerc(BaseGerComplex):
blas_func = fblas.cgerc
dtype = complex64
def transform(self,x):
return conjugate(x)
class TestZgerc(BaseGerComplex):
blas_func = fblas.zgerc
dtype = complex128
def transform(self,x):
return conjugate(x)
"""

View file

@ -0,0 +1,232 @@
# ******************************************************************************
# Copyright (C) 2013 Kenneth L. Ho
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer. Redistributions in binary
# form must reproduce the above copyright notice, this list of conditions and
# the following disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# None of the names of the copyright holders may be used to endorse or
# promote products derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ******************************************************************************
import scipy.linalg.interpolative as pymatrixid
import numpy as np
from scipy.linalg import hilbert, svdvals, norm
from scipy.sparse.linalg import aslinearoperator
from scipy.linalg.interpolative import interp_decomp
from numpy.testing import (assert_, assert_allclose, assert_equal,
assert_array_equal)
import pytest
from pytest import raises as assert_raises
@pytest.fixture()
def eps():
yield 1e-12
@pytest.fixture()
def rng():
rng = np.random.default_rng(1718313768084012)
yield rng
@pytest.fixture(params=[np.float64, np.complex128])
def A(request):
# construct Hilbert matrix
# set parameters
n = 300
yield hilbert(n).astype(request.param)
@pytest.fixture()
def L(A):
yield aslinearoperator(A)
@pytest.fixture()
def rank(A, eps):
S = np.linalg.svd(A, compute_uv=False)
try:
rank = np.nonzero(S < eps)[0][0]
except IndexError:
rank = A.shape[0]
return rank
class TestInterpolativeDecomposition:
@pytest.mark.parametrize(
"rand,lin_op",
[(False, False), (True, False), (True, True)])
def test_real_id_fixed_precision(self, A, L, eps, rand, lin_op, rng):
# Test ID routines on a Hilbert matrix.
A_or_L = A if not lin_op else L
k, idx, proj = pymatrixid.interp_decomp(A_or_L, eps, rand=rand, rng=rng)
B = pymatrixid.reconstruct_matrix_from_id(A[:, idx[:k]], idx, proj)
assert_allclose(A, B, rtol=eps, atol=1e-08)
@pytest.mark.parametrize(
"rand,lin_op",
[(False, False), (True, False), (True, True)])
def test_real_id_fixed_rank(self, A, L, eps, rank, rand, lin_op, rng):
k = rank
A_or_L = A if not lin_op else L
idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand, rng=rng)
B = pymatrixid.reconstruct_matrix_from_id(A[:, idx[:k]], idx, proj)
assert_allclose(A, B, rtol=eps, atol=1e-08)
@pytest.mark.parametrize("rand,lin_op", [(False, False)])
def test_real_id_skel_and_interp_matrices(
self, A, L, eps, rank, rand, lin_op, rng):
k = rank
A_or_L = A if not lin_op else L
idx, proj = pymatrixid.interp_decomp(A_or_L, k, rand=rand, rng=rng)
P = pymatrixid.reconstruct_interp_matrix(idx, proj)
B = pymatrixid.reconstruct_skel_matrix(A, k, idx)
assert_allclose(B, A[:, idx[:k]], rtol=eps, atol=1e-08)
assert_allclose(B @ P, A, rtol=eps, atol=1e-08)
@pytest.mark.parametrize(
"rand,lin_op",
[(False, False), (True, False), (True, True)])
def test_svd_fixed_precision(self, A, L, eps, rand, lin_op, rng):
A_or_L = A if not lin_op else L
U, S, V = pymatrixid.svd(A_or_L, eps, rand=rand, rng=rng)
B = U * S @ V.T.conj()
assert_allclose(A, B, rtol=eps, atol=1e-08)
@pytest.mark.parametrize(
"rand,lin_op",
[(False, False), (True, False), (True, True)])
def test_svd_fixed_rank(self, A, L, eps, rank, rand, lin_op, rng):
k = rank
A_or_L = A if not lin_op else L
U, S, V = pymatrixid.svd(A_or_L, k, rand=rand, rng=rng)
B = U * S @ V.T.conj()
assert_allclose(A, B, rtol=eps, atol=1e-08)
def test_id_to_svd(self, A, eps, rank):
k = rank
idx, proj = pymatrixid.interp_decomp(A, k, rand=False)
U, S, V = pymatrixid.id_to_svd(A[:, idx[:k]], idx, proj)
B = U * S @ V.T.conj()
assert_allclose(A, B, rtol=eps, atol=1e-08)
def test_estimate_spectral_norm(self, A, rng):
s = svdvals(A)
norm_2_est = pymatrixid.estimate_spectral_norm(A, rng=rng)
assert_allclose(norm_2_est, s[0], rtol=1e-6, atol=1e-8)
def test_estimate_spectral_norm_diff(self, A, rng):
B = A.copy()
B[:, 0] *= 1.2
s = svdvals(A - B)
norm_2_est = pymatrixid.estimate_spectral_norm_diff(A, B, rng=rng)
assert_allclose(norm_2_est, s[0], rtol=1e-6, atol=1e-8)
def test_rank_estimates_array(self, A, rng):
B = np.array([[1, 1, 0], [0, 0, 1], [0, 0, 1]], dtype=A.dtype)
for M in [A, B]:
rank_tol = 1e-9
rank_np = np.linalg.matrix_rank(M, norm(M, 2) * rank_tol)
rank_est = pymatrixid.estimate_rank(M, rank_tol, rng=rng)
assert_(rank_est >= rank_np)
assert_(rank_est <= rank_np + 10)
def test_rank_estimates_lin_op(self, A, rng):
B = np.array([[1, 1, 0], [0, 0, 1], [0, 0, 1]], dtype=A.dtype)
for M in [A, B]:
ML = aslinearoperator(M)
rank_tol = 1e-9
rank_np = np.linalg.matrix_rank(M, norm(M, 2) * rank_tol)
rank_est = pymatrixid.estimate_rank(ML, rank_tol, rng=rng)
assert_(rank_est >= rank_np - 4)
assert_(rank_est <= rank_np + 4)
def test_badcall(self):
A = hilbert(5).astype(np.float32)
with assert_raises(ValueError):
pymatrixid.interp_decomp(A, 1e-6, rand=False)
def test_rank_too_large(self):
# svd(array, k) should not segfault
a = np.ones((4, 3))
with assert_raises(ValueError):
pymatrixid.svd(a, 4)
def test_full_rank(self):
eps = 1.0e-12
# fixed precision
A = np.random.rand(16, 8)
k, idx, proj = pymatrixid.interp_decomp(A, eps)
assert_equal(k, A.shape[1])
P = pymatrixid.reconstruct_interp_matrix(idx, proj)
B = pymatrixid.reconstruct_skel_matrix(A, k, idx)
assert_allclose(A, B @ P)
# fixed rank
idx, proj = pymatrixid.interp_decomp(A, k)
P = pymatrixid.reconstruct_interp_matrix(idx, proj)
B = pymatrixid.reconstruct_skel_matrix(A, k, idx)
assert_allclose(A, B @ P)
@pytest.mark.parametrize("dtype", [np.float64, np.complex128])
@pytest.mark.parametrize("rand", [True, False])
@pytest.mark.parametrize("eps", [1, 0.1])
def test_bug_9793(self, dtype, rand, eps):
A = np.array([[-1, -1, -1, 0, 0, 0],
[0, 0, 0, 1, 1, 1],
[1, 0, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0],
[0, 0, 1, 0, 0, 1]],
dtype=dtype, order="C")
B = A.copy()
interp_decomp(A.T, eps, rand=rand)
assert_array_equal(A, B)
def test_svd_aslinearoperator_shape_check(self):
# See gh-issue #22451
rng = np.random.default_rng(1744580941832515)
x = rng.uniform(size=[7, 5])
xl = aslinearoperator(x)
u, s, v = pymatrixid.svd(xl, 3)
assert_equal(u.shape, (7, 3))
assert_equal(s.shape, (3,))
assert_equal(v.shape, (5, 3))
x = rng.uniform(size=[4, 9])
xl = aslinearoperator(x)
u, s, v = pymatrixid.svd(xl, 2)
assert_equal(u.shape, (4, 2))
assert_equal(s.shape, (2,))
assert_equal(v.shape, (9, 2))

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,136 @@
"""Test functions for linalg.matmul_toeplitz function
"""
import numpy as np
from scipy.linalg import toeplitz, matmul_toeplitz
from pytest import raises as assert_raises
from numpy.testing import assert_allclose
class TestMatmulToeplitz:
def setup_method(self):
self.rng = np.random.RandomState(42)
self.tolerance = 1.5e-13
def test_real(self):
cases = []
n = 1
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=(n, 1))
cases.append((x, c, r, False))
n = 2
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=(n, 1))
cases.append((x, c, r, False))
n = 101
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=(n, 1))
cases.append((x, c, r, True))
n = 1000
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=(n, 1))
cases.append((x, c, r, False))
n = 100
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=(n, self.rng.randint(1, 10)))
cases.append((x, c, r, False))
n = 100
c = self.rng.normal(size=(n, 1))
r = self.rng.normal(size=(n, 1))
x = self.rng.normal(size=(n, self.rng.randint(1, 10)))
cases.append((x, c, r, True))
n = 100
c = self.rng.normal(size=(n, 1))
r = None
x = self.rng.normal(size=(n, self.rng.randint(1, 10)))
cases.append((x, c, r, True, -1))
n = 100
c = self.rng.normal(size=(n, 1))
r = None
x = self.rng.normal(size=n)
cases.append((x, c, r, False))
n = 101
c = self.rng.normal(size=n)
r = self.rng.normal(size=n-27)
x = self.rng.normal(size=(n-27, 1))
cases.append((x, c, r, True))
n = 100
c = self.rng.normal(size=n)
r = self.rng.normal(size=n//4)
x = self.rng.normal(size=(n//4, self.rng.randint(1, 10)))
cases.append((x, c, r, True))
[self.do(*i) for i in cases]
def test_complex(self):
n = 127
c = self.rng.normal(size=(n, 1)) + self.rng.normal(size=(n, 1))*1j
r = self.rng.normal(size=(n, 1)) + self.rng.normal(size=(n, 1))*1j
x = self.rng.normal(size=(n, 3)) + self.rng.normal(size=(n, 3))*1j
self.do(x, c, r, False)
n = 100
c = self.rng.normal(size=(n, 1)) + self.rng.normal(size=(n, 1))*1j
r = self.rng.normal(size=(n//2, 1)) +\
self.rng.normal(size=(n//2, 1))*1j
x = self.rng.normal(size=(n//2, 3)) +\
self.rng.normal(size=(n//2, 3))*1j
self.do(x, c, r, False)
def test_empty(self):
c = []
r = []
x = []
self.do(x, c, r, False)
x = np.empty((0, 0))
self.do(x, c, r, False)
def test_exceptions(self):
n = 100
c = self.rng.normal(size=n)
r = self.rng.normal(size=2*n)
x = self.rng.normal(size=n)
assert_raises(ValueError, matmul_toeplitz, (c, r), x, True)
n = 100
c = self.rng.normal(size=n)
r = self.rng.normal(size=n)
x = self.rng.normal(size=n-1)
assert_raises(ValueError, matmul_toeplitz, (c, r), x, True)
n = 100
c = self.rng.normal(size=n)
r = self.rng.normal(size=n//2)
x = self.rng.normal(size=n//2-1)
assert_raises(ValueError, matmul_toeplitz, (c, r), x, True)
# For toeplitz matrices, matmul_toeplitz() should be equivalent to @.
def do(self, x, c, r=None, check_finite=False, workers=None):
c = np.ravel(c)
if r is None:
actual = matmul_toeplitz(c, x, check_finite, workers)
else:
r = np.ravel(r)
actual = matmul_toeplitz((c, r), x, check_finite)
desired = toeplitz(c, r) @ x
assert_allclose(actual, desired,
rtol=self.tolerance, atol=self.tolerance)

View file

@ -0,0 +1,214 @@
from itertools import product, permutations
import numpy as np
import pytest
from numpy.testing import assert_array_less, assert_allclose
from pytest import raises as assert_raises
from scipy.linalg import inv, eigh, norm, svd
from scipy.linalg import orthogonal_procrustes
from scipy.sparse._sputils import matrix
def test_orthogonal_procrustes_ndim_too_small():
rng = np.random.RandomState(1234)
A = rng.randn(3)
B = rng.randn(3)
assert_raises(ValueError, orthogonal_procrustes, A, B)
def test_orthogonal_procrustes_shape_mismatch():
rng = np.random.RandomState(1234)
shapes = ((3, 3), (3, 4), (4, 3), (4, 4))
for a, b in permutations(shapes, 2):
A = rng.randn(*a)
B = rng.randn(*b)
assert_raises(ValueError, orthogonal_procrustes, A, B)
def test_orthogonal_procrustes_checkfinite_exception():
rng = np.random.RandomState(1234)
m, n = 2, 3
A_good = rng.randn(m, n)
B_good = rng.randn(m, n)
for bad_value in np.inf, -np.inf, np.nan:
A_bad = A_good.copy()
A_bad[1, 2] = bad_value
B_bad = B_good.copy()
B_bad[1, 2] = bad_value
for A, B in ((A_good, B_bad), (A_bad, B_good), (A_bad, B_bad)):
assert_raises(ValueError, orthogonal_procrustes, A, B)
def test_orthogonal_procrustes_scale_invariance():
rng = np.random.RandomState(1234)
m, n = 4, 3
for i in range(3):
A_orig = rng.randn(m, n)
B_orig = rng.randn(m, n)
R_orig, s = orthogonal_procrustes(A_orig, B_orig)
for A_scale in np.square(rng.randn(3)):
for B_scale in np.square(rng.randn(3)):
R, s = orthogonal_procrustes(A_orig * A_scale, B_orig * B_scale)
assert_allclose(R, R_orig)
def test_orthogonal_procrustes_array_conversion():
rng = np.random.RandomState(1234)
for m, n in ((6, 4), (4, 4), (4, 6)):
A_arr = rng.randn(m, n)
B_arr = rng.randn(m, n)
As = (A_arr, A_arr.tolist(), matrix(A_arr))
Bs = (B_arr, B_arr.tolist(), matrix(B_arr))
R_arr, s = orthogonal_procrustes(A_arr, B_arr)
AR_arr = A_arr.dot(R_arr)
for A, B in product(As, Bs):
R, s = orthogonal_procrustes(A, B)
AR = A_arr.dot(R)
assert_allclose(AR, AR_arr)
def test_orthogonal_procrustes():
rng = np.random.RandomState(1234)
for m, n in ((6, 4), (4, 4), (4, 6)):
# Sample a random target matrix.
B = rng.randn(m, n)
# Sample a random orthogonal matrix
# by computing eigh of a sampled symmetric matrix.
X = rng.randn(n, n)
w, V = eigh(X.T + X)
assert_allclose(inv(V), V.T)
# Compute a matrix with a known orthogonal transformation that gives B.
A = np.dot(B, V.T)
# Check that an orthogonal transformation from A to B can be recovered.
R, s = orthogonal_procrustes(A, B)
assert_allclose(inv(R), R.T)
assert_allclose(A.dot(R), B)
# Create a perturbed input matrix.
A_perturbed = A + 1e-2 * rng.randn(m, n)
# Check that the orthogonal procrustes function can find an orthogonal
# transformation that is better than the orthogonal transformation
# computed from the original input matrix.
R_prime, s = orthogonal_procrustes(A_perturbed, B)
assert_allclose(inv(R_prime), R_prime.T)
# Compute the naive and optimal transformations of the perturbed input.
naive_approx = A_perturbed.dot(R)
optim_approx = A_perturbed.dot(R_prime)
# Compute the Frobenius norm errors of the matrix approximations.
naive_approx_error = norm(naive_approx - B, ord='fro')
optim_approx_error = norm(optim_approx - B, ord='fro')
# Check that the orthogonal Procrustes approximation is better.
assert_array_less(optim_approx_error, naive_approx_error)
def _centered(A):
mu = A.mean(axis=0)
return A - mu, mu
def test_orthogonal_procrustes_exact_example():
# Check a small application.
# It uses translation, scaling, reflection, and rotation.
#
# |
# a b |
# |
# d c | w
# |
# --------+--- x ----- z ---
# |
# | y
# |
#
A_orig = np.array([[-3, 3], [-2, 3], [-2, 2], [-3, 2]], dtype=float)
B_orig = np.array([[3, 2], [1, 0], [3, -2], [5, 0]], dtype=float)
A, A_mu = _centered(A_orig)
B, B_mu = _centered(B_orig)
R, s = orthogonal_procrustes(A, B)
scale = s / np.square(norm(A))
B_approx = scale * np.dot(A, R) + B_mu
assert_allclose(B_approx, B_orig, atol=1e-8)
def test_orthogonal_procrustes_stretched_example():
# Try again with a target with a stretched y axis.
A_orig = np.array([[-3, 3], [-2, 3], [-2, 2], [-3, 2]], dtype=float)
B_orig = np.array([[3, 40], [1, 0], [3, -40], [5, 0]], dtype=float)
A, A_mu = _centered(A_orig)
B, B_mu = _centered(B_orig)
R, s = orthogonal_procrustes(A, B)
scale = s / np.square(norm(A))
B_approx = scale * np.dot(A, R) + B_mu
expected = np.array([[3, 21], [-18, 0], [3, -21], [24, 0]], dtype=float)
assert_allclose(B_approx, expected, atol=1e-8)
# Check disparity symmetry.
expected_disparity = 0.4501246882793018
AB_disparity = np.square(norm(B_approx - B_orig) / norm(B))
assert_allclose(AB_disparity, expected_disparity)
R, s = orthogonal_procrustes(B, A)
scale = s / np.square(norm(B))
A_approx = scale * np.dot(B, R) + A_mu
BA_disparity = np.square(norm(A_approx - A_orig) / norm(A))
assert_allclose(BA_disparity, expected_disparity)
def test_orthogonal_procrustes_skbio_example():
# This transformation is also exact.
# It uses translation, scaling, and reflection.
#
# |
# | a
# | b
# | c d
# --+---------
# |
# | w
# |
# | x
# |
# | z y
# |
#
A_orig = np.array([[4, -2], [4, -4], [4, -6], [2, -6]], dtype=float)
B_orig = np.array([[1, 3], [1, 2], [1, 1], [2, 1]], dtype=float)
B_standardized = np.array([
[-0.13363062, 0.6681531],
[-0.13363062, 0.13363062],
[-0.13363062, -0.40089186],
[0.40089186, -0.40089186]])
A, A_mu = _centered(A_orig)
B, B_mu = _centered(B_orig)
R, s = orthogonal_procrustes(A, B)
scale = s / np.square(norm(A))
B_approx = scale * np.dot(A, R) + B_mu
assert_allclose(B_approx, B_orig)
assert_allclose(B / norm(B), B_standardized)
def test_empty():
a = np.empty((0, 0))
r, s = orthogonal_procrustes(a, a)
assert_allclose(r, np.empty((0, 0)))
a = np.empty((0, 3))
r, s = orthogonal_procrustes(a, a)
assert_allclose(r, np.identity(3))
@pytest.mark.parametrize('shape', [(4, 5), (5, 5), (5, 4)])
def test_unitary(shape):
# gh-12071 added support for unitary matrices; check that it
# works as intended.
m, n = shape
rng = np.random.default_rng(589234981235)
A = rng.random(shape) + rng.random(shape) * 1j
Q = rng.random((n, n)) + rng.random((n, n)) * 1j
Q, _ = np.linalg.qr(Q)
B = A @ Q
R, scale = orthogonal_procrustes(A, B)
assert_allclose(R @ R.conj().T, np.eye(n), atol=1e-14)
assert_allclose(A @ Q, B)
if shape != (4, 5): # solution is unique
assert_allclose(R, Q)
_, s, _ = svd(A.conj().T @ B)
assert_allclose(scale, np.sum(s))

View file

@ -0,0 +1,118 @@
"""Tests for _sketches.py."""
import numpy as np
from numpy.testing import assert_, assert_equal
from scipy.linalg import clarkson_woodruff_transform
from scipy.linalg._sketches import cwt_matrix
from scipy.sparse import issparse, rand
from scipy.sparse.linalg import norm
class TestClarksonWoodruffTransform:
"""
Testing the Clarkson Woodruff Transform
"""
# set seed for generating test matrices
rng = np.random.default_rng(1179103485)
# Test matrix parameters
n_rows = 2000
n_cols = 100
density = 0.1
# Sketch matrix dimensions
n_sketch_rows = 200
# Seeds to test with
seeds = [1755490010, 934377150, 1391612830, 1752708722, 2008891431,
1302443994, 1521083269, 1501189312, 1126232505, 1533465685]
A_dense = rng.random((n_rows, n_cols))
A_csc = rand(
n_rows, n_cols, density=density, format='csc', random_state=rng,
)
A_csr = rand(
n_rows, n_cols, density=density, format='csr', random_state=rng,
)
A_coo = rand(
n_rows, n_cols, density=density, format='coo', random_state=rng,
)
# Collect the test matrices
test_matrices = [
A_dense, A_csc, A_csr, A_coo,
]
# Test vector with norm ~1
x = rng.random((n_rows, 1)) / np.sqrt(n_rows)
def test_sketch_dimensions(self):
for A in self.test_matrices:
for seed in self.seeds:
# seed to ensure backwards compatibility post SPEC7
sketch = clarkson_woodruff_transform(
A, self.n_sketch_rows, seed=seed
)
assert_(sketch.shape == (self.n_sketch_rows, self.n_cols))
def test_seed_returns_identical_transform_matrix(self):
for seed in self.seeds:
S1 = cwt_matrix(
self.n_sketch_rows, self.n_rows, rng=seed
).toarray()
S2 = cwt_matrix(
self.n_sketch_rows, self.n_rows, rng=seed
).toarray()
assert_equal(S1, S2)
def test_seed_returns_identically(self):
for A in self.test_matrices:
for seed in self.seeds:
sketch1 = clarkson_woodruff_transform(
A, self.n_sketch_rows, rng=seed
)
sketch2 = clarkson_woodruff_transform(
A, self.n_sketch_rows, rng=seed
)
if issparse(sketch1):
sketch1 = sketch1.toarray()
if issparse(sketch2):
sketch2 = sketch2.toarray()
assert_equal(sketch1, sketch2)
def test_sketch_preserves_frobenius_norm(self):
# Given the probabilistic nature of the sketches
# we run the test multiple times and check that
# we pass all/almost all the tries.
n_errors = 0
for A in self.test_matrices:
if issparse(A):
true_norm = norm(A)
else:
true_norm = np.linalg.norm(A)
for seed in self.seeds:
sketch = clarkson_woodruff_transform(
A, self.n_sketch_rows, rng=seed,
)
if issparse(sketch):
sketch_norm = norm(sketch)
else:
sketch_norm = np.linalg.norm(sketch)
if np.abs(true_norm - sketch_norm) > 0.1 * true_norm:
n_errors += 1
assert_(n_errors == 0)
def test_sketch_preserves_vector_norm(self):
n_errors = 0
n_sketch_rows = int(np.ceil(2. / (0.01 * 0.5**2)))
true_norm = np.linalg.norm(self.x)
for seed in self.seeds:
sketch = clarkson_woodruff_transform(
self.x, n_sketch_rows, rng=seed,
)
sketch_norm = np.linalg.norm(sketch)
if np.abs(true_norm - sketch_norm) > 0.5 * true_norm:
n_errors += 1
assert_(n_errors == 0)

View file

@ -0,0 +1,150 @@
"""Test functions for linalg._solve_toeplitz module
"""
import numpy as np
from scipy.linalg._solve_toeplitz import levinson
from scipy.linalg import solve, toeplitz, solve_toeplitz, matmul_toeplitz
from numpy.testing import assert_equal, assert_allclose
import pytest
from pytest import raises as assert_raises
def test_solve_equivalence():
# For toeplitz matrices, solve_toeplitz() should be equivalent to solve().
random = np.random.RandomState(1234)
for n in (1, 2, 3, 10):
c = random.randn(n)
if random.rand() < 0.5:
c = c + 1j * random.randn(n)
r = random.randn(n)
if random.rand() < 0.5:
r = r + 1j * random.randn(n)
y = random.randn(n)
if random.rand() < 0.5:
y = y + 1j * random.randn(n)
# Check equivalence when both the column and row are provided.
actual = solve_toeplitz((c,r), y)
desired = solve(toeplitz(c, r=r), y)
assert_allclose(actual, desired)
# Check equivalence when the column is provided but not the row.
actual = solve_toeplitz(c, b=y)
desired = solve(toeplitz(c), y)
assert_allclose(actual, desired)
def test_multiple_rhs():
random = np.random.RandomState(1234)
c = random.randn(4)
r = random.randn(4)
for offset in [0, 1j]:
for yshape in ((4,), (4, 3)):
y = random.randn(*yshape) + offset
actual = solve_toeplitz((c,r), b=y)
desired = solve(toeplitz(c, r=r), y)
assert_equal(actual.shape, yshape)
assert_equal(desired.shape, yshape)
assert_allclose(actual, desired)
def test_native_list_arguments():
c = [1,2,4,7]
r = [1,3,9,12]
y = [5,1,4,2]
actual = solve_toeplitz((c,r), y)
desired = solve(toeplitz(c, r=r), y)
assert_allclose(actual, desired)
def test_zero_diag_error():
# The Levinson-Durbin implementation fails when the diagonal is zero.
random = np.random.RandomState(1234)
n = 4
c = random.randn(n)
r = random.randn(n)
y = random.randn(n)
c[0] = 0
assert_raises(np.linalg.LinAlgError,
solve_toeplitz, (c, r), b=y)
def test_wikipedia_counterexample():
# The Levinson-Durbin implementation also fails in other cases.
# This example is from the talk page of the wikipedia article.
random = np.random.RandomState(1234)
c = [2, 2, 1]
y = random.randn(3)
assert_raises(np.linalg.LinAlgError, solve_toeplitz, c, b=y)
def test_reflection_coeffs():
# check that the partial solutions are given by the reflection
# coefficients
random = np.random.RandomState(1234)
y_d = random.randn(10)
y_z = random.randn(10) + 1j
reflection_coeffs_d = [1]
reflection_coeffs_z = [1]
for i in range(2, 10):
reflection_coeffs_d.append(solve_toeplitz(y_d[:(i-1)], b=y_d[1:i])[-1])
reflection_coeffs_z.append(solve_toeplitz(y_z[:(i-1)], b=y_z[1:i])[-1])
y_d_concat = np.concatenate((y_d[-2:0:-1], y_d[:-1]))
y_z_concat = np.concatenate((y_z[-2:0:-1].conj(), y_z[:-1]))
_, ref_d = levinson(y_d_concat, b=y_d[1:])
_, ref_z = levinson(y_z_concat, b=y_z[1:])
assert_allclose(reflection_coeffs_d, ref_d[:-1])
assert_allclose(reflection_coeffs_z, ref_z[:-1])
@pytest.mark.xfail(reason='Instability of Levinson iteration')
def test_unstable():
# this is a "Gaussian Toeplitz matrix", as mentioned in Example 2 of
# I. Gohbert, T. Kailath and V. Olshevsky "Fast Gaussian Elimination with
# Partial Pivoting for Matrices with Displacement Structure"
# Mathematics of Computation, 64, 212 (1995), pp 1557-1576
# which can be unstable for levinson recursion.
# other fast toeplitz solvers such as GKO or Burg should be better.
random = np.random.RandomState(1234)
n = 100
c = 0.9 ** (np.arange(n)**2)
y = random.randn(n)
solution1 = solve_toeplitz(c, b=y)
solution2 = solve(toeplitz(c), y)
assert_allclose(solution1, solution2)
@pytest.mark.parametrize('dt_c', [int, float, np.float32, complex, np.complex64])
@pytest.mark.parametrize('dt_b', [int, float, np.float32, complex, np.complex64])
def test_empty(dt_c, dt_b):
c = np.array([], dtype=dt_c)
b = np.array([], dtype=dt_b)
x = solve_toeplitz(c, b)
assert x.shape == (0,)
assert x.dtype == solve_toeplitz(np.array([2, 1], dtype=dt_c),
np.ones(2, dtype=dt_b)).dtype
b = np.empty((0, 0), dtype=dt_b)
x1 = solve_toeplitz(c, b)
assert x1.shape == (0, 0)
assert x1.dtype == x.dtype
@pytest.mark.parametrize('fun', [solve_toeplitz, matmul_toeplitz])
def test_nd_FutureWarning(fun):
# Test future warnings with n-D `c`/`r`
rng = np.random.default_rng(283592436523456)
c = rng.random((2, 3, 4))
r = rng.random((2, 3, 4))
b_or_x = rng.random(24)
message = "Beginning in SciPy 1.17, multidimensional input will be..."
with pytest.warns(FutureWarning, match=message):
fun(c, b_or_x)
with pytest.warns(FutureWarning, match=message):
fun((c, r), b_or_x)

View file

@ -0,0 +1,844 @@
import os
import numpy as np
from numpy.testing import assert_array_almost_equal, assert_allclose
import pytest
from pytest import raises as assert_raises
from scipy.linalg import solve_sylvester
from scipy.linalg import solve_continuous_lyapunov, solve_discrete_lyapunov
from scipy.linalg import solve_continuous_are, solve_discrete_are
from scipy.linalg import block_diag, solve, LinAlgError
from scipy.sparse._sputils import matrix
# dtypes for testing size-0 case following precedent set in gh-20295
dtypes = [int, float, np.float32, complex, np.complex64]
def _load_data(name):
"""
Load npz data file under data/
Returns a copy of the data, rather than keeping the npz file open.
"""
filename = os.path.join(os.path.abspath(os.path.dirname(__file__)),
'data', name)
with np.load(filename) as f:
return dict(f.items())
class TestSolveLyapunov:
cases = [
# empty case
(np.empty((0, 0)),
np.empty((0, 0))),
(np.array([[1, 2], [3, 4]]),
np.array([[9, 10], [11, 12]])),
# a, q all complex.
(np.array([[1.0+1j, 2.0], [3.0-4.0j, 5.0]]),
np.array([[2.0-2j, 2.0+2j], [-1.0-1j, 2.0]])),
# a real; q complex.
(np.array([[1.0, 2.0], [3.0, 5.0]]),
np.array([[2.0-2j, 2.0+2j], [-1.0-1j, 2.0]])),
# a complex; q real.
(np.array([[1.0+1j, 2.0], [3.0-4.0j, 5.0]]),
np.array([[2.0, 2.0], [-1.0, 2.0]])),
# An example from Kitagawa, 1977
(np.array([[3, 9, 5, 1, 4], [1, 2, 3, 8, 4], [4, 6, 6, 6, 3],
[1, 5, 2, 0, 7], [5, 3, 3, 1, 5]]),
np.array([[2, 4, 1, 0, 1], [4, 1, 0, 2, 0], [1, 0, 3, 0, 3],
[0, 2, 0, 1, 0], [1, 0, 3, 0, 4]])),
# Companion matrix example. a complex; q real; a.shape[0] = 11
(np.array([[0.100+0.j, 0.091+0.j, 0.082+0.j, 0.073+0.j, 0.064+0.j,
0.055+0.j, 0.046+0.j, 0.037+0.j, 0.028+0.j, 0.019+0.j,
0.010+0.j],
[1.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 1.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 1.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 1.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 1.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
1.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 1.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 1.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 1.000+0.j, 0.000+0.j,
0.000+0.j],
[0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j,
0.000+0.j, 0.000+0.j, 0.000+0.j, 0.000+0.j, 1.000+0.j,
0.000+0.j]]),
np.eye(11)),
# https://github.com/scipy/scipy/issues/4176
(matrix([[0, 1], [-1/2, -1]]),
(matrix([0, 3]).T @ matrix([0, 3]).T.T)),
# https://github.com/scipy/scipy/issues/4176
(matrix([[0, 1], [-1/2, -1]]),
(np.array(matrix([0, 3]).T @ matrix([0, 3]).T.T))),
]
def test_continuous_squareness_and_shape(self):
nsq = np.ones((3, 2))
sq = np.eye(3)
assert_raises(ValueError, solve_continuous_lyapunov, nsq, sq)
assert_raises(ValueError, solve_continuous_lyapunov, sq, nsq)
assert_raises(ValueError, solve_continuous_lyapunov, sq, np.eye(2))
def check_continuous_case(self, a, q):
x = solve_continuous_lyapunov(a, q)
assert_array_almost_equal(
np.dot(a, x) + np.dot(x, a.conj().transpose()), q)
def check_discrete_case(self, a, q, method=None):
x = solve_discrete_lyapunov(a, q, method=method)
assert_array_almost_equal(
np.dot(np.dot(a, x), a.conj().transpose()) - x, -1.0*q)
def test_cases(self):
for case in self.cases:
self.check_continuous_case(case[0], case[1])
self.check_discrete_case(case[0], case[1])
self.check_discrete_case(case[0], case[1], method='direct')
self.check_discrete_case(case[0], case[1], method='bilinear')
@pytest.mark.parametrize("dtype_a", dtypes)
@pytest.mark.parametrize("dtype_q", dtypes)
def test_size_0(self, dtype_a, dtype_q):
rng = np.random.default_rng(234598235)
a = np.zeros((0, 0), dtype=dtype_a)
q = np.zeros((0, 0), dtype=dtype_q)
res = solve_continuous_lyapunov(a, q)
a = (rng.random((5, 5))*100).astype(dtype_a)
q = (rng.random((5, 5))*100).astype(dtype_q)
ref = solve_continuous_lyapunov(a, q)
assert res.shape == (0, 0)
assert res.dtype == ref.dtype
class TestSolveContinuousAre:
mat6 = _load_data('carex_6_data.npz')
mat15 = _load_data('carex_15_data.npz')
mat18 = _load_data('carex_18_data.npz')
mat19 = _load_data('carex_19_data.npz')
mat20 = _load_data('carex_20_data.npz')
cases = [
# Carex examples taken from (with default parameters):
# [1] P.BENNER, A.J. LAUB, V. MEHRMANN: 'A Collection of Benchmark
# Examples for the Numerical Solution of Algebraic Riccati
# Equations II: Continuous-Time Case', Tech. Report SPC 95_23,
# Fak. f. Mathematik, TU Chemnitz-Zwickau (Germany), 1995.
#
# The format of the data is (a, b, q, r, knownfailure), where
# knownfailure is None if the test passes or a string
# indicating the reason for failure.
#
# Test Case 0: carex #1
(np.diag([1.], 1),
np.array([[0], [1]]),
block_diag(1., 2.),
1,
None),
# Test Case 1: carex #2
(np.array([[4, 3], [-4.5, -3.5]]),
np.array([[1], [-1]]),
np.array([[9, 6], [6, 4.]]),
1,
None),
# Test Case 2: carex #3
(np.array([[0, 1, 0, 0],
[0, -1.89, 0.39, -5.53],
[0, -0.034, -2.98, 2.43],
[0.034, -0.0011, -0.99, -0.21]]),
np.array([[0, 0], [0.36, -1.6], [-0.95, -0.032], [0.03, 0]]),
np.array([[2.313, 2.727, 0.688, 0.023],
[2.727, 4.271, 1.148, 0.323],
[0.688, 1.148, 0.313, 0.102],
[0.023, 0.323, 0.102, 0.083]]),
np.eye(2),
None),
# Test Case 3: carex #4
(np.array([[-0.991, 0.529, 0, 0, 0, 0, 0, 0],
[0.522, -1.051, 0.596, 0, 0, 0, 0, 0],
[0, 0.522, -1.118, 0.596, 0, 0, 0, 0],
[0, 0, 0.522, -1.548, 0.718, 0, 0, 0],
[0, 0, 0, 0.922, -1.64, 0.799, 0, 0],
[0, 0, 0, 0, 0.922, -1.721, 0.901, 0],
[0, 0, 0, 0, 0, 0.922, -1.823, 1.021],
[0, 0, 0, 0, 0, 0, 0.922, -1.943]]),
np.array([[3.84, 4.00, 37.60, 3.08, 2.36, 2.88, 3.08, 3.00],
[-2.88, -3.04, -2.80, -2.32, -3.32, -3.82, -4.12, -3.96]]
).T * 0.001,
np.array([[1.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.1],
[0.0, 1.0, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 0.0, 0.0],
[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0],
[0.5, 0.1, 0.0, 0.0, 0.1, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.5, 0.0, 0.0, 0.1, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.0],
[0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1]]),
np.eye(2),
None),
# Test Case 4: carex #5
(np.array(
[[-4.019, 5.120, 0., 0., -2.082, 0., 0., 0., 0.870],
[-0.346, 0.986, 0., 0., -2.340, 0., 0., 0., 0.970],
[-7.909, 15.407, -4.069, 0., -6.450, 0., 0., 0., 2.680],
[-21.816, 35.606, -0.339, -3.870, -17.800, 0., 0., 0., 7.390],
[-60.196, 98.188, -7.907, 0.340, -53.008, 0., 0., 0., 20.400],
[0, 0, 0, 0, 94.000, -147.200, 0., 53.200, 0.],
[0, 0, 0, 0, 0, 94.000, -147.200, 0, 0],
[0, 0, 0, 0, 0, 12.800, 0.000, -31.600, 0],
[0, 0, 0, 0, 12.800, 0.000, 0.000, 18.800, -31.600]]),
np.array([[0.010, -0.011, -0.151],
[0.003, -0.021, 0.000],
[0.009, -0.059, 0.000],
[0.024, -0.162, 0.000],
[0.068, -0.445, 0.000],
[0.000, 0.000, 0.000],
[0.000, 0.000, 0.000],
[0.000, 0.000, 0.000],
[0.000, 0.000, 0.000]]),
np.eye(9),
np.eye(3),
None),
# Test Case 5: carex #6
(mat6['A'], mat6['B'], mat6['Q'], mat6['R'], None),
# Test Case 6: carex #7
(np.array([[1, 0], [0, -2.]]),
np.array([[1e-6], [0]]),
np.ones((2, 2)),
1.,
'Bad residual accuracy'),
# Test Case 7: carex #8
(block_diag(-0.1, -0.02),
np.array([[0.100, 0.000], [0.001, 0.010]]),
np.array([[100, 1000], [1000, 10000]]),
np.ones((2, 2)) + block_diag(1e-6, 0),
None),
# Test Case 8: carex #9
(np.array([[0, 1e6], [0, 0]]),
np.array([[0], [1.]]),
np.eye(2),
1.,
None),
# Test Case 9: carex #10
(np.array([[1.0000001, 1], [1., 1.0000001]]),
np.eye(2),
np.eye(2),
np.eye(2),
None),
# Test Case 10: carex #11
(np.array([[3, 1.], [4, 2]]),
np.array([[1], [1]]),
np.array([[-11, -5], [-5, -2.]]),
1.,
None),
# Test Case 11: carex #12
(np.array([[7000000., 2000000., -0.],
[2000000., 6000000., -2000000.],
[0., -2000000., 5000000.]]) / 3,
np.eye(3),
np.array([[1., -2., -2.], [-2., 1., -2.], [-2., -2., 1.]]).dot(
np.diag([1e-6, 1, 1e6])).dot(
np.array([[1., -2., -2.], [-2., 1., -2.], [-2., -2., 1.]])) / 9,
np.eye(3) * 1e6,
'Bad Residual Accuracy'),
# Test Case 12: carex #13
(np.array([[0, 0.4, 0, 0],
[0, 0, 0.345, 0],
[0, -0.524e6, -0.465e6, 0.262e6],
[0, 0, 0, -1e6]]),
np.array([[0, 0, 0, 1e6]]).T,
np.diag([1, 0, 1, 0]),
1.,
None),
# Test Case 13: carex #14
(np.array([[-1e-6, 1, 0, 0],
[-1, -1e-6, 0, 0],
[0, 0, 1e-6, 1],
[0, 0, -1, 1e-6]]),
np.ones((4, 1)),
np.ones((4, 4)),
1.,
None),
# Test Case 14: carex #15
(mat15['A'], mat15['B'], mat15['Q'], mat15['R'], None),
# Test Case 15: carex #16
(np.eye(64, 64, k=-1) + np.eye(64, 64)*(-2.) + np.rot90(
block_diag(1, np.zeros((62, 62)), 1)) + np.eye(64, 64, k=1),
np.eye(64),
np.eye(64),
np.eye(64),
None),
# Test Case 16: carex #17
(np.diag(np.ones((20, )), 1),
np.flipud(np.eye(21, 1)),
np.eye(21, 1) * np.eye(21, 1).T,
1,
'Bad Residual Accuracy'),
# Test Case 17: carex #18
(mat18['A'], mat18['B'], mat18['Q'], mat18['R'], None),
# Test Case 18: carex #19
(mat19['A'], mat19['B'], mat19['Q'], mat19['R'],
'Bad Residual Accuracy'),
# Test Case 19: carex #20
(mat20['A'], mat20['B'], mat20['Q'], mat20['R'],
'Bad Residual Accuracy')
]
# Makes the minimum precision requirements customized to the test.
# Here numbers represent the number of decimals that agrees with zero
# matrix when the solution x is plugged in to the equation.
#
# res = array([[8e-3,1e-16],[1e-16,1e-20]]) --> min_decimal[k] = 2
#
# If the test is failing use "None" for that entry.
#
min_decimal = (14, 12, 13, 14, 11, 6, None, 5, 7, 14, 14,
None, 9, 14, 13, 14, None, 12, None, None)
@pytest.mark.parametrize("j, case", enumerate(cases))
def test_solve_continuous_are(self, j, case):
"""Checks if 0 = XA + A'X - XB(R)^{-1} B'X + Q is true"""
a, b, q, r, knownfailure = case
if knownfailure:
pytest.xfail(reason=knownfailure)
dec = self.min_decimal[j]
x = solve_continuous_are(a, b, q, r)
res = x @ a + a.conj().T @ x + q
out_fact = x @ b
res -= out_fact @ solve(np.atleast_2d(r), out_fact.conj().T)
assert_array_almost_equal(res, np.zeros_like(res), decimal=dec)
class TestSolveDiscreteAre:
cases = [
# Darex examples taken from (with default parameters):
# [1] P.BENNER, A.J. LAUB, V. MEHRMANN: 'A Collection of Benchmark
# Examples for the Numerical Solution of Algebraic Riccati
# Equations II: Discrete-Time Case', Tech. Report SPC 95_23,
# Fak. f. Mathematik, TU Chemnitz-Zwickau (Germany), 1995.
# [2] T. GUDMUNDSSON, C. KENNEY, A.J. LAUB: 'Scaling of the
# Discrete-Time Algebraic Riccati Equation to Enhance Stability
# of the Schur Solution Method', IEEE Trans.Aut.Cont., vol.37(4)
#
# The format of the data is (a, b, q, r, knownfailure), where
# knownfailure is None if the test passes or a string
# indicating the reason for failure.
#
# TEST CASE 0 : Complex a; real b, q, r
(np.array([[2, 1-2j], [0, -3j]]),
np.array([[0], [1]]),
np.array([[1, 0], [0, 2]]),
np.array([[1]]),
None),
# TEST CASE 1 :Real a, q, r; complex b
(np.array([[2, 1], [0, -1]]),
np.array([[-2j], [1j]]),
np.array([[1, 0], [0, 2]]),
np.array([[1]]),
None),
# TEST CASE 2 : Real a, b; complex q, r
(np.array([[3, 1], [0, -1]]),
np.array([[1, 2], [1, 3]]),
np.array([[1, 1+1j], [1-1j, 2]]),
np.array([[2, -2j], [2j, 3]]),
None),
# TEST CASE 3 : User-reported gh-2251 (Trac #1732)
(np.array([[0.63399379, 0.54906824, 0.76253406],
[0.5404729, 0.53745766, 0.08731853],
[0.27524045, 0.84922129, 0.4681622]]),
np.array([[0.96861695], [0.05532739], [0.78934047]]),
np.eye(3),
np.eye(1),
None),
# TEST CASE 4 : darex #1
(np.array([[4, 3], [-4.5, -3.5]]),
np.array([[1], [-1]]),
np.array([[9, 6], [6, 4]]),
np.array([[1]]),
None),
# TEST CASE 5 : darex #2
(np.array([[0.9512, 0], [0, 0.9048]]),
np.array([[4.877, 4.877], [-1.1895, 3.569]]),
np.array([[0.005, 0], [0, 0.02]]),
np.array([[1/3, 0], [0, 3]]),
None),
# TEST CASE 6 : darex #3
(np.array([[2, -1], [1, 0]]),
np.array([[1], [0]]),
np.array([[0, 0], [0, 1]]),
np.array([[0]]),
None),
# TEST CASE 7 : darex #4 (skipped the gen. Ric. term S)
(np.array([[0, 1], [0, -1]]),
np.array([[1, 0], [2, 1]]),
np.array([[-4, -4], [-4, 7]]) * (1/11),
np.array([[9, 3], [3, 1]]),
None),
# TEST CASE 8 : darex #5
(np.array([[0, 1], [0, 0]]),
np.array([[0], [1]]),
np.array([[1, 2], [2, 4]]),
np.array([[1]]),
None),
# TEST CASE 9 : darex #6
(np.array([[0.998, 0.067, 0, 0],
[-.067, 0.998, 0, 0],
[0, 0, 0.998, 0.153],
[0, 0, -.153, 0.998]]),
np.array([[0.0033, 0.0200],
[0.1000, -.0007],
[0.0400, 0.0073],
[-.0028, 0.1000]]),
np.array([[1.87, 0, 0, -0.244],
[0, 0.744, 0.205, 0],
[0, 0.205, 0.589, 0],
[-0.244, 0, 0, 1.048]]),
np.eye(2),
None),
# TEST CASE 10 : darex #7
(np.array([[0.984750, -.079903, 0.0009054, -.0010765],
[0.041588, 0.998990, -.0358550, 0.0126840],
[-.546620, 0.044916, -.3299100, 0.1931800],
[2.662400, -.100450, -.9245500, -.2632500]]),
np.array([[0.0037112, 0.0007361],
[-.0870510, 9.3411e-6],
[-1.198440, -4.1378e-4],
[-3.192700, 9.2535e-4]]),
np.eye(4)*1e-2,
np.eye(2),
None),
# TEST CASE 11 : darex #8
(np.array([[-0.6000000, -2.2000000, -3.6000000, -5.4000180],
[1.0000000, 0.6000000, 0.8000000, 3.3999820],
[0.0000000, 1.0000000, 1.8000000, 3.7999820],
[0.0000000, 0.0000000, 0.0000000, -0.9999820]]),
np.array([[1.0, -1.0, -1.0, -1.0],
[0.0, 1.0, -1.0, -1.0],
[0.0, 0.0, 1.0, -1.0],
[0.0, 0.0, 0.0, 1.0]]),
np.array([[2, 1, 3, 6],
[1, 2, 2, 5],
[3, 2, 6, 11],
[6, 5, 11, 22]]),
np.eye(4),
None),
# TEST CASE 12 : darex #9
(np.array([[95.4070, 1.9643, 0.3597, 0.0673, 0.0190],
[40.8490, 41.3170, 16.0840, 4.4679, 1.1971],
[12.2170, 26.3260, 36.1490, 15.9300, 12.3830],
[4.1118, 12.8580, 27.2090, 21.4420, 40.9760],
[0.1305, 0.5808, 1.8750, 3.6162, 94.2800]]) * 0.01,
np.array([[0.0434, -0.0122],
[2.6606, -1.0453],
[3.7530, -5.5100],
[3.6076, -6.6000],
[0.4617, -0.9148]]) * 0.01,
np.eye(5),
np.eye(2),
None),
# TEST CASE 13 : darex #10
(np.kron(np.eye(2), np.diag([1, 1], k=1)),
np.kron(np.eye(2), np.array([[0], [0], [1]])),
np.array([[1, 1, 0, 0, 0, 0],
[1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, -1, 0],
[0, 0, 0, -1, 1, 0],
[0, 0, 0, 0, 0, 0]]),
np.array([[3, 0], [0, 1]]),
None),
# TEST CASE 14 : darex #11
(0.001 * np.array(
[[870.1, 135.0, 11.59, .5014, -37.22, .3484, 0, 4.242, 7.249],
[76.55, 897.4, 12.72, 0.5504, -40.16, .3743, 0, 4.53, 7.499],
[-127.2, 357.5, 817, 1.455, -102.8, .987, 0, 11.85, 18.72],
[-363.5, 633.9, 74.91, 796.6, -273.5, 2.653, 0, 31.72, 48.82],
[-960, 1645.9, -128.9, -5.597, 71.42, 7.108, 0, 84.52, 125.9],
[-664.4, 112.96, -88.89, -3.854, 84.47, 13.6, 0, 144.3, 101.6],
[-410.2, 693, -54.71, -2.371, 66.49, 12.49, .1063, 99.97, 69.67],
[-179.9, 301.7, -23.93, -1.035, 60.59, 22.16, 0, 213.9, 35.54],
[-345.1, 580.4, -45.96, -1.989, 105.6, 19.86, 0, 219.1, 215.2]]),
np.array([[4.7600, -0.5701, -83.6800],
[0.8790, -4.7730, -2.7300],
[1.4820, -13.1200, 8.8760],
[3.8920, -35.1300, 24.8000],
[10.3400, -92.7500, 66.8000],
[7.2030, -61.5900, 38.3400],
[4.4540, -36.8300, 20.2900],
[1.9710, -15.5400, 6.9370],
[3.7730, -30.2800, 14.6900]]) * 0.001,
np.diag([50, 0, 0, 0, 50, 0, 0, 0, 0]),
np.eye(3),
None),
# TEST CASE 15 : darex #12 - numerically least accurate example
(np.array([[0, 1e6], [0, 0]]),
np.array([[0], [1]]),
np.eye(2),
np.array([[1]]),
None),
# TEST CASE 16 : darex #13
(np.array([[16, 10, -2],
[10, 13, -8],
[-2, -8, 7]]) * (1/9),
np.eye(3),
1e6 * np.eye(3),
1e6 * np.eye(3),
None),
# TEST CASE 17 : darex #14
(np.array([[1 - 1/1e8, 0, 0, 0],
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0]]),
np.array([[1e-08], [0], [0], [0]]),
np.diag([0, 0, 0, 1]),
np.array([[0.25]]),
None),
# TEST CASE 18 : darex #15
(np.eye(100, k=1),
np.flipud(np.eye(100, 1)),
np.eye(100),
np.array([[1]]),
None)
]
# Makes the minimum precision requirements customized to the test.
# Here numbers represent the number of decimals that agrees with zero
# matrix when the solution x is plugged in to the equation.
#
# res = array([[8e-3,1e-16],[1e-16,1e-20]]) --> min_decimal[k] = 2
#
# If the test is failing use "None" for that entry.
#
min_decimal = (12, 14, 13, 14, 13, 16, 18, 14, 14, 13,
14, 13, 13, 14, 12, 2, 4, 6, 10)
max_tol = [1.5 * 10**-ind for ind in min_decimal]
# relaxed tolerance in gh-18012 after bump to OpenBLAS
max_tol[11] = 2.5e-13
# relaxed tolerance in gh-20335 for linux-aarch64 build on Cirrus
# with OpenBLAS from ubuntu jammy
max_tol[15] = 2.0e-2
# relaxed tolerance in gh-20335 for OpenBLAS 3.20 on ubuntu jammy
# bump not needed for OpenBLAS 3.26
max_tol[16] = 2.0e-4
@pytest.mark.parametrize("j, case", enumerate(cases))
def test_solve_discrete_are(self, j, case):
"""Checks if X = A'XA-(A'XB)(R+B'XB)^-1(B'XA)+Q) is true"""
a, b, q, r, knownfailure = case
if knownfailure:
pytest.xfail(reason=knownfailure)
atol = self.max_tol[j]
x = solve_discrete_are(a, b, q, r)
bH = b.conj().T
xa, xb = x @ a, x @ b
res = a.conj().T @ xa - x + q
res -= a.conj().T @ xb @ (solve(r + bH @ xb, bH) @ xa)
# changed from
# assert_array_almost_equal(res, np.zeros_like(res), decimal=dec)
# in gh-18012 as it's easier to relax a tolerance and allclose is
# preferred
assert_allclose(res, np.zeros_like(res), atol=atol)
def test_infeasible(self):
# An infeasible example taken from https://arxiv.org/abs/1505.04861v1
A = np.triu(np.ones((3, 3)))
A[0, 1] = -1
B = np.array([[1, 1, 0], [0, 0, 1]]).T
Q = np.full_like(A, -2) + np.diag([8, -1, -1.9])
R = np.diag([-10, 0.1])
assert_raises(LinAlgError, solve_continuous_are, A, B, Q, R)
def test_solve_generalized_continuous_are():
cases = [
# Two random examples differ by s term
# in the absence of any literature for demanding examples.
(np.array([[2.769230e-01, 8.234578e-01, 9.502220e-01],
[4.617139e-02, 6.948286e-01, 3.444608e-02],
[9.713178e-02, 3.170995e-01, 4.387444e-01]]),
np.array([[3.815585e-01, 1.868726e-01],
[7.655168e-01, 4.897644e-01],
[7.951999e-01, 4.455862e-01]]),
np.eye(3),
np.eye(2),
np.array([[6.463130e-01, 2.760251e-01, 1.626117e-01],
[7.093648e-01, 6.797027e-01, 1.189977e-01],
[7.546867e-01, 6.550980e-01, 4.983641e-01]]),
np.zeros((3, 2)),
None),
(np.array([[2.769230e-01, 8.234578e-01, 9.502220e-01],
[4.617139e-02, 6.948286e-01, 3.444608e-02],
[9.713178e-02, 3.170995e-01, 4.387444e-01]]),
np.array([[3.815585e-01, 1.868726e-01],
[7.655168e-01, 4.897644e-01],
[7.951999e-01, 4.455862e-01]]),
np.eye(3),
np.eye(2),
np.array([[6.463130e-01, 2.760251e-01, 1.626117e-01],
[7.093648e-01, 6.797027e-01, 1.189977e-01],
[7.546867e-01, 6.550980e-01, 4.983641e-01]]),
np.ones((3, 2)),
None)
]
min_decimal = (10, 10)
def _test_factory(case, dec):
"""Checks if X = A'XA-(A'XB)(R+B'XB)^-1(B'XA)+Q) is true"""
a, b, q, r, e, s, knownfailure = case
if knownfailure:
pytest.xfail(reason=knownfailure)
x = solve_continuous_are(a, b, q, r, e, s)
res = a.conj().T.dot(x.dot(e)) + e.conj().T.dot(x.dot(a)) + q
out_fact = e.conj().T.dot(x).dot(b) + s
res -= out_fact.dot(solve(np.atleast_2d(r), out_fact.conj().T))
assert_array_almost_equal(res, np.zeros_like(res), decimal=dec)
for ind, case in enumerate(cases):
_test_factory(case, min_decimal[ind])
def test_solve_generalized_discrete_are():
mat20170120 = _load_data('gendare_20170120_data.npz')
cases = [
# Two random examples differ by s term
# in the absence of any literature for demanding examples.
(np.array([[2.769230e-01, 8.234578e-01, 9.502220e-01],
[4.617139e-02, 6.948286e-01, 3.444608e-02],
[9.713178e-02, 3.170995e-01, 4.387444e-01]]),
np.array([[3.815585e-01, 1.868726e-01],
[7.655168e-01, 4.897644e-01],
[7.951999e-01, 4.455862e-01]]),
np.eye(3),
np.eye(2),
np.array([[6.463130e-01, 2.760251e-01, 1.626117e-01],
[7.093648e-01, 6.797027e-01, 1.189977e-01],
[7.546867e-01, 6.550980e-01, 4.983641e-01]]),
np.zeros((3, 2)),
None),
(np.array([[2.769230e-01, 8.234578e-01, 9.502220e-01],
[4.617139e-02, 6.948286e-01, 3.444608e-02],
[9.713178e-02, 3.170995e-01, 4.387444e-01]]),
np.array([[3.815585e-01, 1.868726e-01],
[7.655168e-01, 4.897644e-01],
[7.951999e-01, 4.455862e-01]]),
np.eye(3),
np.eye(2),
np.array([[6.463130e-01, 2.760251e-01, 1.626117e-01],
[7.093648e-01, 6.797027e-01, 1.189977e-01],
[7.546867e-01, 6.550980e-01, 4.983641e-01]]),
np.ones((3, 2)),
None),
# user-reported (under PR-6616) 20-Jan-2017
# tests against the case where E is None but S is provided
(mat20170120['A'],
mat20170120['B'],
mat20170120['Q'],
mat20170120['R'],
None,
mat20170120['S'],
None),
]
max_atol = (1.5e-11, 1.5e-11, 3.5e-16)
def _test_factory(case, atol):
"""Checks if X = A'XA-(A'XB)(R+B'XB)^-1(B'XA)+Q) is true"""
a, b, q, r, e, s, knownfailure = case
if knownfailure:
pytest.xfail(reason=knownfailure)
x = solve_discrete_are(a, b, q, r, e, s)
if e is None:
e = np.eye(a.shape[0])
if s is None:
s = np.zeros_like(b)
res = a.conj().T.dot(x.dot(a)) - e.conj().T.dot(x.dot(e)) + q
res -= (a.conj().T.dot(x.dot(b)) + s).dot(
solve(r+b.conj().T.dot(x.dot(b)),
(b.conj().T.dot(x.dot(a)) + s.conj().T)
)
)
# changed from:
# assert_array_almost_equal(res, np.zeros_like(res), decimal=dec)
# in gh-17950 because of a Linux 32 bit fail.
assert_allclose(res, np.zeros_like(res), atol=atol)
for ind, case in enumerate(cases):
_test_factory(case, max_atol[ind])
def test_are_validate_args():
def test_square_shape():
nsq = np.ones((3, 2))
sq = np.eye(3)
for x in (solve_continuous_are, solve_discrete_are):
assert_raises(ValueError, x, nsq, 1, 1, 1)
assert_raises(ValueError, x, sq, sq, nsq, 1)
assert_raises(ValueError, x, sq, sq, sq, nsq)
assert_raises(ValueError, x, sq, sq, sq, sq, nsq)
def test_compatible_sizes():
nsq = np.ones((3, 2))
sq = np.eye(4)
for x in (solve_continuous_are, solve_discrete_are):
assert_raises(ValueError, x, sq, nsq, 1, 1)
assert_raises(ValueError, x, sq, sq, sq, sq, sq, nsq)
assert_raises(ValueError, x, sq, sq, np.eye(3), sq)
assert_raises(ValueError, x, sq, sq, sq, np.eye(3))
assert_raises(ValueError, x, sq, sq, sq, sq, np.eye(3))
def test_symmetry():
nsym = np.arange(9).reshape(3, 3)
sym = np.eye(3)
for x in (solve_continuous_are, solve_discrete_are):
assert_raises(ValueError, x, sym, sym, nsym, sym)
assert_raises(ValueError, x, sym, sym, sym, nsym)
def test_singularity():
sing = np.full((3, 3), 1e12)
sing[2, 2] -= 1
sq = np.eye(3)
for x in (solve_continuous_are, solve_discrete_are):
assert_raises(ValueError, x, sq, sq, sq, sq, sing)
assert_raises(ValueError, solve_continuous_are, sq, sq, sq, sing)
def test_finiteness():
nm = np.full((2, 2), np.nan)
sq = np.eye(2)
for x in (solve_continuous_are, solve_discrete_are):
assert_raises(ValueError, x, nm, sq, sq, sq)
assert_raises(ValueError, x, sq, nm, sq, sq)
assert_raises(ValueError, x, sq, sq, nm, sq)
assert_raises(ValueError, x, sq, sq, sq, nm)
assert_raises(ValueError, x, sq, sq, sq, sq, nm)
assert_raises(ValueError, x, sq, sq, sq, sq, sq, nm)
class TestSolveSylvester:
cases = [
# empty cases
(np.empty((0, 0)),
np.empty((0, 0)),
np.empty((0, 0))),
(np.empty((0, 0)),
np.empty((2, 2)),
np.empty((0, 2))),
(np.empty((2, 2)),
np.empty((0, 0)),
np.empty((2, 0))),
# a, b, c all real.
(np.array([[1, 2], [0, 4]]),
np.array([[5, 6], [0, 8]]),
np.array([[9, 10], [11, 12]])),
# a, b, c all real, 4x4. a and b have non-trivial 2x2 blocks in their
# quasi-triangular form.
(np.array([[1.0, 0, 0, 0],
[0, 1.0, 2.0, 0.0],
[0, 0, 3.0, -4],
[0, 0, 2, 5]]),
np.array([[2.0, 0, 0, 1.0],
[0, 1.0, 0.0, 0.0],
[0, 0, 1.0, -1],
[0, 0, 1, 1]]),
np.array([[1.0, 0, 0, 0],
[0, 1.0, 0, 0],
[0, 0, 1.0, 0],
[0, 0, 0, 1.0]])),
# a, b, c all complex.
(np.array([[1.0+1j, 2.0], [3.0-4.0j, 5.0]]),
np.array([[-1.0, 2j], [3.0, 4.0]]),
np.array([[2.0-2j, 2.0+2j], [-1.0-1j, 2.0]])),
# a and b real; c complex.
(np.array([[1.0, 2.0], [3.0, 5.0]]),
np.array([[-1.0, 0], [3.0, 4.0]]),
np.array([[2.0-2j, 2.0+2j], [-1.0-1j, 2.0]])),
# a and c complex; b real.
(np.array([[1.0+1j, 2.0], [3.0-4.0j, 5.0]]),
np.array([[-1.0, 0], [3.0, 4.0]]),
np.array([[2.0-2j, 2.0+2j], [-1.0-1j, 2.0]])),
# a complex; b and c real.
(np.array([[1.0+1j, 2.0], [3.0-4.0j, 5.0]]),
np.array([[-1.0, 0], [3.0, 4.0]]),
np.array([[2.0, 2.0], [-1.0, 2.0]])),
# not square matrices, real
(np.array([[8, 1, 6], [3, 5, 7], [4, 9, 2]]),
np.array([[2, 3], [4, 5]]),
np.array([[1, 2], [3, 4], [5, 6]])),
# not square matrices, complex
(np.array([[8, 1j, 6+2j], [3, 5, 7], [4, 9, 2]]),
np.array([[2, 3], [4, 5-1j]]),
np.array([[1, 2j], [3, 4j], [5j, 6+7j]])),
]
def check_case(self, a, b, c):
x = solve_sylvester(a, b, c)
assert_array_almost_equal(np.dot(a, x) + np.dot(x, b), c)
def test_cases(self):
for case in self.cases:
self.check_case(case[0], case[1], case[2])
def test_trivial(self):
a = np.array([[1.0, 0.0], [0.0, 1.0]])
b = np.array([[1.0]])
c = np.array([2.0, 2.0]).reshape(-1, 1)
x = solve_sylvester(a, b, c)
assert_array_almost_equal(x, np.array([1.0, 1.0]).reshape(-1, 1))
# Feel free to adjust this to test fewer dtypes or random selections rather than
# the Cartesian product. It doesn't take very long to test all combinations,
# though, so we'll start there and trim it down as we see fit.
@pytest.mark.parametrize("dtype_a", dtypes)
@pytest.mark.parametrize("dtype_b", dtypes)
@pytest.mark.parametrize("dtype_q", dtypes)
@pytest.mark.parametrize("m", [0, 3])
@pytest.mark.parametrize("n", [0, 3])
def test_size_0(self, m, n, dtype_a, dtype_b, dtype_q):
if m == n != 0:
pytest.skip('m = n != 0 is not a case that needs to be tested here.')
rng = np.random.default_rng(598435298262546)
a = np.zeros((m, m), dtype=dtype_a)
b = np.zeros((n, n), dtype=dtype_b)
q = np.zeros((m, n), dtype=dtype_q)
res = solve_sylvester(a, b, q)
a = (rng.random((5, 5))*100).astype(dtype_a)
b = (rng.random((6, 6))*100).astype(dtype_b)
q = (rng.random((5, 6))*100).astype(dtype_q)
ref = solve_sylvester(a, b, q)
assert res.shape == (m, n)
assert res.dtype == ref.dtype

View file

@ -0,0 +1,636 @@
import pytest
import numpy as np
from numpy import arange, array, eye, copy, sqrt
from numpy.testing import (assert_equal, assert_array_equal,
assert_array_almost_equal, assert_allclose)
from pytest import raises as assert_raises
from scipy.fft import fft
from scipy.special import comb
from scipy.linalg import (toeplitz, hankel, circulant, hadamard, leslie, dft,
companion, kron, block_diag,
helmert, hilbert, invhilbert, pascal, invpascal,
fiedler, fiedler_companion, eigvals,
convolution_matrix)
from numpy.linalg import cond
class TestToeplitz:
def test_basic(self):
y = toeplitz([1, 2, 3])
assert_array_equal(y, [[1, 2, 3], [2, 1, 2], [3, 2, 1]])
y = toeplitz([1, 2, 3], [1, 4, 5])
assert_array_equal(y, [[1, 4, 5], [2, 1, 4], [3, 2, 1]])
def test_complex_01(self):
data = (1.0 + arange(3.0)) * (1.0 + 1.0j)
x = copy(data)
t = toeplitz(x)
# Calling toeplitz should not change x.
assert_array_equal(x, data)
# According to the docstring, x should be the first column of t.
col0 = t[:, 0]
assert_array_equal(col0, data)
assert_array_equal(t[0, 1:], data[1:].conj())
def test_scalar_00(self):
"""Scalar arguments still produce a 2D array."""
t = toeplitz(10)
assert_array_equal(t, [[10]])
t = toeplitz(10, 20)
assert_array_equal(t, [[10]])
def test_scalar_01(self):
c = array([1, 2, 3])
t = toeplitz(c, 1)
assert_array_equal(t, [[1], [2], [3]])
def test_scalar_02(self):
c = array([1, 2, 3])
t = toeplitz(c, array(1))
assert_array_equal(t, [[1], [2], [3]])
def test_scalar_03(self):
c = array([1, 2, 3])
t = toeplitz(c, array([1]))
assert_array_equal(t, [[1], [2], [3]])
def test_scalar_04(self):
r = array([10, 2, 3])
t = toeplitz(1, r)
assert_array_equal(t, [[1, 2, 3]])
class TestHankel:
def test_basic(self):
y = hankel([1, 2, 3])
assert_array_equal(y, [[1, 2, 3], [2, 3, 0], [3, 0, 0]])
y = hankel([1, 2, 3], [3, 4, 5])
assert_array_equal(y, [[1, 2, 3], [2, 3, 4], [3, 4, 5]])
class TestCirculant:
def test_basic(self):
y = circulant([1, 2, 3])
assert_array_equal(y, [[1, 3, 2], [2, 1, 3], [3, 2, 1]])
class TestHadamard:
def test_basic(self):
y = hadamard(1)
assert_array_equal(y, [[1]])
y = hadamard(2, dtype=float)
assert_array_equal(y, [[1.0, 1.0], [1.0, -1.0]])
y = hadamard(4)
assert_array_equal(y, [[1, 1, 1, 1],
[1, -1, 1, -1],
[1, 1, -1, -1],
[1, -1, -1, 1]])
assert_raises(ValueError, hadamard, 0)
assert_raises(ValueError, hadamard, 5)
class TestLeslie:
def test_bad_shapes(self):
assert_raises(ValueError, leslie, [[1, 1], [2, 2]], [3, 4, 5])
assert_raises(ValueError, leslie, [1, 2], [1, 2])
assert_raises(ValueError, leslie, [1], [])
def test_basic(self):
a = leslie([1, 2, 3], [0.25, 0.5])
expected = array([[1.0, 2.0, 3.0],
[0.25, 0.0, 0.0],
[0.0, 0.5, 0.0]])
assert_array_equal(a, expected)
class TestCompanion:
def test_bad_shapes(self):
assert_raises(ValueError, companion, [0, 4, 5])
assert_raises(ValueError, companion, [1])
assert_raises(ValueError, companion, [])
def test_basic(self):
c = companion([1, 2, 3])
expected = array([
[-2.0, -3.0],
[1.0, 0.0]])
assert_array_equal(c, expected)
c = companion([2.0, 5.0, -10.0])
expected = array([
[-2.5, 5.0],
[1.0, 0.0]])
assert_array_equal(c, expected)
c = companion([(1.0, 2.0, 3.0),
(4.0, 5.0, 6.0)])
expected = array([
([-2.00, -3.00],
[+1.00, +0.00]),
([-1.25, -1.50],
[+1.00, +0.00])
])
assert_array_equal(c, expected)
class TestBlockDiag:
def test_basic(self):
x = block_diag(eye(2), [[1, 2], [3, 4], [5, 6]], [[1, 2, 3]])
assert_array_equal(x, [[1, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 0, 0, 0],
[0, 0, 3, 4, 0, 0, 0],
[0, 0, 5, 6, 0, 0, 0],
[0, 0, 0, 0, 1, 2, 3]])
def test_dtype(self):
x = block_diag([[1.5]])
assert_equal(x.dtype, float)
x = block_diag([[True]])
assert_equal(x.dtype, bool)
def test_mixed_dtypes(self):
actual = block_diag([[1]], [[1j]])
desired = np.array([[1, 0], [0, 1j]])
assert_array_equal(actual, desired)
def test_scalar_and_1d_args(self):
a = block_diag(1)
assert_equal(a.shape, (1, 1))
assert_array_equal(a, [[1]])
a = block_diag([2, 3], 4)
assert_array_equal(a, [[2, 3, 0], [0, 0, 4]])
def test_no_args(self):
a = block_diag()
assert_equal(a.ndim, 2)
assert_equal(a.nbytes, 0)
def test_empty_matrix_arg(self):
# regression test for gh-4596: check the shape of the result
# for empty matrix inputs. Empty matrices are no longer ignored
# (gh-4908) it is viewed as a shape (1, 0) matrix.
a = block_diag([[1, 0], [0, 1]],
[],
[[2, 3], [4, 5], [6, 7]])
assert_array_equal(a, [[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 2, 3],
[0, 0, 4, 5],
[0, 0, 6, 7]])
def test_zerosized_matrix_arg(self):
# test for gh-4908: check the shape of the result for
# zero-sized matrix inputs, i.e. matrices with shape (0,n) or (n,0).
# note that [[]] takes shape (1,0)
a = block_diag([[1, 0], [0, 1]],
[[]],
[[2, 3], [4, 5], [6, 7]],
np.zeros([0, 2], dtype='int32'))
assert_array_equal(a, [[1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 2, 3, 0, 0],
[0, 0, 4, 5, 0, 0],
[0, 0, 6, 7, 0, 0]])
class TestKron:
@pytest.mark.thread_unsafe
def test_dep(self):
with pytest.deprecated_call(match="`kron`"):
kron(np.array([[1, 2],[3, 4]]),np.array([[1, 1, 1]]))
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_basic(self):
a = kron(array([[1, 2], [3, 4]]), array([[1, 1, 1]]))
assert_array_equal(a, array([[1, 1, 1, 2, 2, 2],
[3, 3, 3, 4, 4, 4]]))
m1 = array([[1, 2], [3, 4]])
m2 = array([[10], [11]])
a = kron(m1, m2)
expected = array([[10, 20],
[11, 22],
[30, 40],
[33, 44]])
assert_array_equal(a, expected)
@pytest.mark.filterwarnings('ignore::DeprecationWarning')
def test_empty(self):
m1 = np.empty((0, 2))
m2 = np.empty((1, 3))
a = kron(m1, m2)
assert_allclose(a, np.empty((0, 6)))
class TestHelmert:
def test_orthogonality(self):
for n in range(1, 7):
H = helmert(n, full=True)
Id = np.eye(n)
assert_allclose(H.dot(H.T), Id, atol=1e-12)
assert_allclose(H.T.dot(H), Id, atol=1e-12)
def test_subspace(self):
for n in range(2, 7):
H_full = helmert(n, full=True)
H_partial = helmert(n)
for U in H_full[1:, :].T, H_partial.T:
C = np.eye(n) - np.full((n, n), 1 / n)
assert_allclose(U.dot(U.T), C)
assert_allclose(U.T.dot(U), np.eye(n-1), atol=1e-12)
class TestHilbert:
def test_basic(self):
h3 = array([[1.0, 1/2., 1/3.],
[1/2., 1/3., 1/4.],
[1/3., 1/4., 1/5.]])
assert_array_almost_equal(hilbert(3), h3)
assert_array_equal(hilbert(1), [[1.0]])
h0 = hilbert(0)
assert_equal(h0.shape, (0, 0))
class TestInvHilbert:
def test_basic(self):
invh1 = array([[1]])
assert_array_equal(invhilbert(1, exact=True), invh1)
assert_array_equal(invhilbert(1), invh1)
invh2 = array([[4, -6],
[-6, 12]])
assert_array_equal(invhilbert(2, exact=True), invh2)
assert_array_almost_equal(invhilbert(2), invh2)
invh3 = array([[9, -36, 30],
[-36, 192, -180],
[30, -180, 180]])
assert_array_equal(invhilbert(3, exact=True), invh3)
assert_array_almost_equal(invhilbert(3), invh3)
invh4 = array([[16, -120, 240, -140],
[-120, 1200, -2700, 1680],
[240, -2700, 6480, -4200],
[-140, 1680, -4200, 2800]])
assert_array_equal(invhilbert(4, exact=True), invh4)
assert_array_almost_equal(invhilbert(4), invh4)
invh5 = array([[25, -300, 1050, -1400, 630],
[-300, 4800, -18900, 26880, -12600],
[1050, -18900, 79380, -117600, 56700],
[-1400, 26880, -117600, 179200, -88200],
[630, -12600, 56700, -88200, 44100]])
assert_array_equal(invhilbert(5, exact=True), invh5)
assert_array_almost_equal(invhilbert(5), invh5)
invh17 = array([
[289, -41616, 1976760, -46124400, 629598060, -5540462928,
33374693352, -143034400080, 446982500250, -1033026222800,
1774926873720, -2258997839280, 2099709530100, -1384423866000,
613101997800, -163493866080, 19835652870],
[-41616, 7990272, -426980160, 10627061760, -151103534400,
1367702848512, -8410422724704, 36616806420480, -115857864064800,
270465047424000, -468580694662080, 600545887119360,
-561522320049600, 372133135180800, -165537539406000,
44316454993920, -5395297580640],
[1976760, -426980160, 24337869120, -630981792000, 9228108708000,
-85267724461920, 532660105897920, -2348052711713280,
7504429831470000, -17664748409880000, 30818191841236800,
-39732544853164800, 37341234283298400, -24857330514030000,
11100752642520000, -2982128117299200, 364182586693200],
[-46124400, 10627061760, -630981792000, 16826181120000,
-251209625940000, 2358021022156800, -14914482965141760,
66409571644416000, -214015221119700000, 507295338950400000,
-890303319857952000, 1153715376477081600, -1089119333262870000,
727848632044800000, -326170262829600000, 87894302404608000,
-10763618673376800],
[629598060, -151103534400, 9228108708000,
-251209625940000, 3810012660090000, -36210360321495360,
231343968720664800, -1038687206500944000, 3370739732635275000,
-8037460526495400000, 14178080368737885600, -18454939322943942000,
17489975175339030000, -11728977435138600000, 5272370630081100000,
-1424711708039692800, 174908803442373000],
[-5540462928, 1367702848512, -85267724461920, 2358021022156800,
-36210360321495360, 347619459086355456, -2239409617216035264,
10124803292907663360, -33052510749726468000,
79217210949138662400, -140362995650505067440,
183420385176741672960, -174433352415381259200,
117339159519533952000, -52892422160973595200,
14328529177999196160, -1763080738699119840],
[33374693352, -8410422724704, 532660105897920,
-14914482965141760, 231343968720664800, -2239409617216035264,
14527452132196331328, -66072377044391477760,
216799987176909536400, -521925895055522958000,
928414062734059661760, -1217424500995626443520,
1161358898976091015200, -783401860847777371200,
354015418167362952000, -96120549902411274240,
11851820521255194480],
[-143034400080, 36616806420480, -2348052711713280,
66409571644416000, -1038687206500944000, 10124803292907663360,
-66072377044391477760, 302045152202932469760,
-995510145200094810000, 2405996923185123840000,
-4294704507885446054400, 5649058909023744614400,
-5403874060541811254400, 3654352703663101440000,
-1655137020003255360000, 450325202737117593600,
-55630994283442749600],
[446982500250, -115857864064800, 7504429831470000,
-214015221119700000, 3370739732635275000, -33052510749726468000,
216799987176909536400, -995510145200094810000,
3293967392206196062500, -7988661659013106500000,
14303908928401362270000, -18866974090684772052000,
18093328327706957325000, -12263364009096700500000,
5565847995255512250000, -1517208935002984080000,
187754605706619279900],
[-1033026222800, 270465047424000, -17664748409880000,
507295338950400000, -8037460526495400000, 79217210949138662400,
-521925895055522958000, 2405996923185123840000,
-7988661659013106500000, 19434404971634224000000,
-34894474126569249192000, 46141453390504792320000,
-44349976506971935800000, 30121928988527376000000,
-13697025107665828500000, 3740200989399948902400,
-463591619028689580000],
[1774926873720, -468580694662080,
30818191841236800, -890303319857952000, 14178080368737885600,
-140362995650505067440, 928414062734059661760,
-4294704507885446054400, 14303908928401362270000,
-34894474126569249192000, 62810053427824648545600,
-83243376594051600326400, 80177044485212743068000,
-54558343880470209780000, 24851882355348879230400,
-6797096028813368678400, 843736746632215035600],
[-2258997839280, 600545887119360, -39732544853164800,
1153715376477081600, -18454939322943942000, 183420385176741672960,
-1217424500995626443520, 5649058909023744614400,
-18866974090684772052000, 46141453390504792320000,
-83243376594051600326400, 110552468520163390156800,
-106681852579497947388000, 72720410752415168870400,
-33177973900974346080000, 9087761081682520473600,
-1129631016152221783200],
[2099709530100, -561522320049600, 37341234283298400,
-1089119333262870000, 17489975175339030000,
-174433352415381259200, 1161358898976091015200,
-5403874060541811254400, 18093328327706957325000,
-44349976506971935800000, 80177044485212743068000,
-106681852579497947388000, 103125790826848015808400,
-70409051543137015800000, 32171029219823375700000,
-8824053728865840192000, 1098252376814660067000],
[-1384423866000, 372133135180800,
-24857330514030000, 727848632044800000, -11728977435138600000,
117339159519533952000, -783401860847777371200,
3654352703663101440000, -12263364009096700500000,
30121928988527376000000, -54558343880470209780000,
72720410752415168870400, -70409051543137015800000,
48142941226076592000000, -22027500987368499000000,
6049545098753157120000, -753830033789944188000],
[613101997800, -165537539406000,
11100752642520000, -326170262829600000, 5272370630081100000,
-52892422160973595200, 354015418167362952000,
-1655137020003255360000, 5565847995255512250000,
-13697025107665828500000, 24851882355348879230400,
-33177973900974346080000, 32171029219823375700000,
-22027500987368499000000, 10091416708498869000000,
-2774765838662800128000, 346146444087219270000],
[-163493866080, 44316454993920, -2982128117299200,
87894302404608000, -1424711708039692800,
14328529177999196160, -96120549902411274240,
450325202737117593600, -1517208935002984080000,
3740200989399948902400, -6797096028813368678400,
9087761081682520473600, -8824053728865840192000,
6049545098753157120000, -2774765838662800128000,
763806510427609497600, -95382575704033754400],
[19835652870, -5395297580640, 364182586693200, -10763618673376800,
174908803442373000, -1763080738699119840, 11851820521255194480,
-55630994283442749600, 187754605706619279900,
-463591619028689580000, 843736746632215035600,
-1129631016152221783200, 1098252376814660067000,
-753830033789944188000, 346146444087219270000,
-95382575704033754400, 11922821963004219300]
])
assert_array_equal(invhilbert(17, exact=True), invh17)
assert_allclose(invhilbert(17), invh17.astype(float), rtol=1e-12)
def test_inverse(self):
for n in range(1, 10):
a = hilbert(n)
b = invhilbert(n)
# The Hilbert matrix is increasingly badly conditioned,
# so take that into account in the test
c = cond(a)
assert_allclose(a.dot(b), eye(n), atol=1e-15*c, rtol=1e-15*c)
class TestPascal:
cases = [
(1, array([[1]]), array([[1]])),
(2, array([[1, 1],
[1, 2]]),
array([[1, 0],
[1, 1]])),
(3, array([[1, 1, 1],
[1, 2, 3],
[1, 3, 6]]),
array([[1, 0, 0],
[1, 1, 0],
[1, 2, 1]])),
(4, array([[1, 1, 1, 1],
[1, 2, 3, 4],
[1, 3, 6, 10],
[1, 4, 10, 20]]),
array([[1, 0, 0, 0],
[1, 1, 0, 0],
[1, 2, 1, 0],
[1, 3, 3, 1]])),
]
def check_case(self, n, sym, low):
assert_array_equal(pascal(n), sym)
assert_array_equal(pascal(n, kind='lower'), low)
assert_array_equal(pascal(n, kind='upper'), low.T)
assert_array_almost_equal(pascal(n, exact=False), sym)
assert_array_almost_equal(pascal(n, exact=False, kind='lower'), low)
assert_array_almost_equal(pascal(n, exact=False, kind='upper'), low.T)
def test_cases(self):
for n, sym, low in self.cases:
self.check_case(n, sym, low)
def test_big(self):
p = pascal(50)
assert p[-1, -1] == comb(98, 49, exact=True)
def test_threshold(self):
# Regression test. An early version of `pascal` returned an
# array of type np.uint64 for n=35, but that data type is too small
# to hold p[-1, -1]. The second assert_equal below would fail
# because p[-1, -1] overflowed.
p = pascal(34)
assert_equal(2*p.item(-1, -2), p.item(-1, -1), err_msg="n = 34")
p = pascal(35)
assert_equal(2.*p.item(-1, -2), 1.*p.item(-1, -1), err_msg="n = 35")
def test_invpascal():
def check_invpascal(n, kind, exact):
ip = invpascal(n, kind=kind, exact=exact)
p = pascal(n, kind=kind, exact=exact)
# Matrix-multiply ip and p, and check that we get the identity matrix.
# We can't use the simple expression e = ip.dot(p), because when
# n < 35 and exact is True, p.dtype is np.uint64 and ip.dtype is
# np.int64. The product of those dtypes is np.float64, which loses
# precision when n is greater than 18. Instead we'll cast both to
# object arrays, and then multiply.
e = ip.astype(object).dot(p.astype(object))
assert_array_equal(e, eye(n), err_msg=f"n={n} kind={kind!r} exact={exact!r}")
kinds = ['symmetric', 'lower', 'upper']
ns = [1, 2, 5, 18]
for n in ns:
for kind in kinds:
for exact in [True, False]:
check_invpascal(n, kind, exact)
ns = [19, 34, 35, 50]
for n in ns:
for kind in kinds:
check_invpascal(n, kind, True)
def test_dft():
m = dft(2)
expected = array([[1.0, 1.0], [1.0, -1.0]])
assert_array_almost_equal(m, expected)
m = dft(2, scale='n')
assert_array_almost_equal(m, expected/2.0)
m = dft(2, scale='sqrtn')
assert_array_almost_equal(m, expected/sqrt(2.0))
x = array([0, 1, 2, 3, 4, 5, 0, 1])
m = dft(8)
mx = m.dot(x)
fx = fft(x)
assert_array_almost_equal(mx, fx)
def test_fiedler():
f = fiedler([])
assert_equal(f.size, 0)
f = fiedler([123.])
assert_array_equal(f, np.array([[0.]]))
f = fiedler(np.arange(1, 7))
des = np.array([[0, 1, 2, 3, 4, 5],
[1, 0, 1, 2, 3, 4],
[2, 1, 0, 1, 2, 3],
[3, 2, 1, 0, 1, 2],
[4, 3, 2, 1, 0, 1],
[5, 4, 3, 2, 1, 0]])
assert_array_equal(f, des)
def test_fiedler_companion():
fc = fiedler_companion([])
assert_equal(fc.size, 0)
fc = fiedler_companion([1.])
assert_equal(fc.size, 0)
fc = fiedler_companion([1., 2.])
assert_array_equal(fc, np.array([[-2.]]))
fc = fiedler_companion([1e-12, 2., 3.])
assert_array_almost_equal(fc, companion([1e-12, 2., 3.]))
with assert_raises(ValueError):
fiedler_companion([0, 1, 2])
fc = fiedler_companion([1., -16., 86., -176., 105.])
assert_array_almost_equal(eigvals(fc),
np.array([7., 5., 3., 1.]))
class TestConvolutionMatrix:
"""
Test convolution_matrix vs. numpy.convolve for various parameters.
"""
def create_vector(self, n, cpx):
"""Make a complex or real test vector of length n."""
x = np.linspace(-2.5, 2.2, n)
if cpx:
x = x + 1j*np.linspace(-1.5, 3.1, n)
return x
def test_bad_n(self):
# n must be a positive integer
with pytest.raises(ValueError, match='n must be a positive integer'):
convolution_matrix([1, 2, 3], 0)
def test_empty_first_arg(self):
# first arg must have at least one value
with pytest.raises(ValueError, match=r'len\(a\)'):
convolution_matrix([], 4)
def test_bad_mode(self):
# mode must be in ('full', 'valid', 'same')
with pytest.raises(ValueError, match='mode.*must be one of'):
convolution_matrix((1, 1), 4, mode='invalid argument')
@pytest.mark.parametrize('cpx', [False, True])
@pytest.mark.parametrize('na', [1, 2, 9])
@pytest.mark.parametrize('nv', [1, 2, 9])
@pytest.mark.parametrize('mode', [None, 'full', 'valid', 'same'])
def test_against_numpy_convolve(self, cpx, na, nv, mode):
a = self.create_vector(na, cpx)
v = self.create_vector(nv, cpx)
if mode is None:
y1 = np.convolve(v, a)
A = convolution_matrix(a, nv)
else:
y1 = np.convolve(v, a, mode)
A = convolution_matrix(a, nv, mode)
y2 = A @ v
assert_array_almost_equal(y1, y2)
@pytest.mark.thread_unsafe
@pytest.mark.fail_slow(5) # `leslie` has an import in the function
@pytest.mark.parametrize('f, args', [(circulant, ()),
(companion, ()),
(convolution_matrix, (5, 'same')),
(fiedler, ()),
(fiedler_companion, ()),
(leslie, (np.arange(9),)),
(toeplitz, (np.arange(9),)),
])
def test_batch(f, args):
rng = np.random.default_rng(283592436523456)
batch_shape = (2, 3)
m = 10
A = rng.random(batch_shape + (m,))
if f in {toeplitz}:
message = "Beginning in SciPy 1.17, multidimensional input will be..."
with pytest.warns(FutureWarning, match=message):
f(A, *args)
return
res = f(A, *args)
ref = np.asarray([f(a, *args) for a in A.reshape(-1, m)])
ref = ref.reshape(A.shape[:-1] + ref.shape[-2:])
assert_allclose(res, ref)