up follow livre
This commit is contained in:
parent
b4b4398bb0
commit
3a7a3849ae
12242 changed files with 2564461 additions and 6914 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2074
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_basic.py
Normal file
2074
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_basic.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
1127
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_blas.py
Normal file
1127
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_blas.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
3189
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_decomp.py
Normal file
3189
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_decomp.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
"""
|
||||
|
|
@ -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))
|
||||
3616
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_lapack.py
Normal file
3616
venv/lib/python3.13/site-packages/scipy/linalg/tests/test_lapack.py
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
@ -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))
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue