|
|
|
|
|
|
|
""" Test functions for linalg.matfuncs module |
|
|
|
""" |
|
import functools |
|
|
|
import numpy as np |
|
from numpy import array, identity, dot, sqrt |
|
from numpy.testing import (assert_array_almost_equal, assert_allclose, assert_, |
|
assert_array_less, assert_array_equal, assert_warns) |
|
import pytest |
|
|
|
import scipy.linalg |
|
from scipy.linalg import (funm, signm, logm, sqrtm, fractional_matrix_power, |
|
expm, expm_frechet, expm_cond, norm, khatri_rao, |
|
cosm, sinm, tanm, coshm, sinhm, tanhm) |
|
from scipy.linalg import _matfuncs_inv_ssq |
|
from scipy.linalg._matfuncs import pick_pade_structure |
|
from scipy.linalg._matfuncs_inv_ssq import LogmExactlySingularWarning |
|
import scipy.linalg._expm_frechet |
|
|
|
from scipy.optimize import minimize |
|
|
|
|
|
def _get_al_mohy_higham_2012_experiment_1(): |
|
""" |
|
Return the test matrix from Experiment (1) of [1]_. |
|
|
|
References |
|
---------- |
|
.. [1] Awad H. Al-Mohy and Nicholas J. Higham (2012) |
|
"Improved Inverse Scaling and Squaring Algorithms |
|
for the Matrix Logarithm." |
|
SIAM Journal on Scientific Computing, 34 (4). C152-C169. |
|
ISSN 1095-7197 |
|
|
|
""" |
|
A = np.array([ |
|
[3.2346e-1, 3e4, 3e4, 3e4], |
|
[0, 3.0089e-1, 3e4, 3e4], |
|
[0, 0, 3.2210e-1, 3e4], |
|
[0, 0, 0, 3.0744e-1]], dtype=float) |
|
return A |
|
|
|
|
|
class TestSignM: |
|
|
|
def test_nils(self): |
|
a = array([[29.2, -24.2, 69.5, 49.8, 7.], |
|
[-9.2, 5.2, -18., -16.8, -2.], |
|
[-10., 6., -20., -18., -2.], |
|
[-9.6, 9.6, -25.5, -15.4, -2.], |
|
[9.8, -4.8, 18., 18.2, 2.]]) |
|
cr = array([[11.94933333,-2.24533333,15.31733333,21.65333333,-2.24533333], |
|
[-3.84266667,0.49866667,-4.59066667,-7.18666667,0.49866667], |
|
[-4.08,0.56,-4.92,-7.6,0.56], |
|
[-4.03466667,1.04266667,-5.59866667,-7.02666667,1.04266667], |
|
[4.15733333,-0.50133333,4.90933333,7.81333333,-0.50133333]]) |
|
r = signm(a) |
|
assert_array_almost_equal(r,cr) |
|
|
|
def test_defective1(self): |
|
a = array([[0.0,1,0,0],[1,0,1,0],[0,0,0,1],[0,0,1,0]]) |
|
signm(a, disp=False) |
|
|
|
|
|
def test_defective2(self): |
|
a = array(( |
|
[29.2,-24.2,69.5,49.8,7.0], |
|
[-9.2,5.2,-18.0,-16.8,-2.0], |
|
[-10.0,6.0,-20.0,-18.0,-2.0], |
|
[-9.6,9.6,-25.5,-15.4,-2.0], |
|
[9.8,-4.8,18.0,18.2,2.0])) |
|
signm(a, disp=False) |
|
|
|
|
|
def test_defective3(self): |
|
a = array([[-2., 25., 0., 0., 0., 0., 0.], |
|
[0., -3., 10., 3., 3., 3., 0.], |
|
[0., 0., 2., 15., 3., 3., 0.], |
|
[0., 0., 0., 0., 15., 3., 0.], |
|
[0., 0., 0., 0., 3., 10., 0.], |
|
[0., 0., 0., 0., 0., -2., 25.], |
|
[0., 0., 0., 0., 0., 0., -3.]]) |
|
signm(a, disp=False) |
|
|
|
|
|
|
|
class TestLogM: |
|
|
|
def test_nils(self): |
|
a = array([[-2., 25., 0., 0., 0., 0., 0.], |
|
[0., -3., 10., 3., 3., 3., 0.], |
|
[0., 0., 2., 15., 3., 3., 0.], |
|
[0., 0., 0., 0., 15., 3., 0.], |
|
[0., 0., 0., 0., 3., 10., 0.], |
|
[0., 0., 0., 0., 0., -2., 25.], |
|
[0., 0., 0., 0., 0., 0., -3.]]) |
|
m = (identity(7)*3.1+0j)-a |
|
logm(m, disp=False) |
|
|
|
|
|
def test_al_mohy_higham_2012_experiment_1_logm(self): |
|
|
|
|
|
A = _get_al_mohy_higham_2012_experiment_1() |
|
A_logm, info = logm(A, disp=False) |
|
A_round_trip = expm(A_logm) |
|
assert_allclose(A_round_trip, A, rtol=5e-5, atol=1e-14) |
|
|
|
def test_al_mohy_higham_2012_experiment_1_funm_log(self): |
|
|
|
|
|
A = _get_al_mohy_higham_2012_experiment_1() |
|
A_funm_log, info = funm(A, np.log, disp=False) |
|
A_round_trip = expm(A_funm_log) |
|
assert_(not np.allclose(A_round_trip, A, rtol=1e-5, atol=1e-14)) |
|
|
|
def test_round_trip_random_float(self): |
|
np.random.seed(1234) |
|
for n in range(1, 6): |
|
M_unscaled = np.random.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
|
|
|
|
W = np.linalg.eigvals(M) |
|
err_msg = f'M:{M} eivals:{W}' |
|
|
|
|
|
M_sqrtm, info = sqrtm(M, disp=False) |
|
M_sqrtm_round_trip = M_sqrtm.dot(M_sqrtm) |
|
assert_allclose(M_sqrtm_round_trip, M) |
|
|
|
|
|
M_logm, info = logm(M, disp=False) |
|
M_logm_round_trip = expm(M_logm) |
|
assert_allclose(M_logm_round_trip, M, err_msg=err_msg) |
|
|
|
def test_round_trip_random_complex(self): |
|
np.random.seed(1234) |
|
for n in range(1, 6): |
|
M_unscaled = np.random.randn(n, n) + 1j * np.random.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
M_logm, info = logm(M, disp=False) |
|
M_round_trip = expm(M_logm) |
|
assert_allclose(M_round_trip, M) |
|
|
|
def test_logm_type_preservation_and_conversion(self): |
|
|
|
|
|
|
|
complex_dtype_chars = ('F', 'D', 'G') |
|
for matrix_as_list in ( |
|
[[1, 0], [0, 1]], |
|
[[1, 0], [1, 1]], |
|
[[2, 1], [1, 1]], |
|
[[2, 3], [1, 2]]): |
|
|
|
|
|
W = scipy.linalg.eigvals(matrix_as_list) |
|
assert_(not any(w.imag or w.real < 0 for w in W)) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=float) |
|
A_logm, info = logm(A, disp=False) |
|
assert_(A_logm.dtype.char not in complex_dtype_chars) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=complex) |
|
A_logm, info = logm(A, disp=False) |
|
assert_(A_logm.dtype.char in complex_dtype_chars) |
|
|
|
|
|
A = -np.array(matrix_as_list, dtype=float) |
|
A_logm, info = logm(A, disp=False) |
|
assert_(A_logm.dtype.char in complex_dtype_chars) |
|
|
|
def test_complex_spectrum_real_logm(self): |
|
|
|
|
|
M = [[1, 1, 2], [2, 1, 1], [1, 2, 1]] |
|
for dt in float, complex: |
|
X = np.array(M, dtype=dt) |
|
w = scipy.linalg.eigvals(X) |
|
assert_(1e-2 < np.absolute(w.imag).sum()) |
|
Y, info = logm(X, disp=False) |
|
assert_(np.issubdtype(Y.dtype, np.inexact)) |
|
assert_allclose(expm(Y), X) |
|
|
|
def test_real_mixed_sign_spectrum(self): |
|
|
|
|
|
for M in ( |
|
[[1, 0], [0, -1]], |
|
[[0, 1], [1, 0]]): |
|
for dt in float, complex: |
|
A = np.array(M, dtype=dt) |
|
A_logm, info = logm(A, disp=False) |
|
assert_(np.issubdtype(A_logm.dtype, np.complexfloating)) |
|
|
|
@pytest.mark.thread_unsafe |
|
def test_exactly_singular(self): |
|
A = np.array([[0, 0], [1j, 1j]]) |
|
B = np.asarray([[1, 1], [0, 0]]) |
|
for M in A, A.T, B, B.T: |
|
expected_warning = _matfuncs_inv_ssq.LogmExactlySingularWarning |
|
L, info = assert_warns(expected_warning, logm, M, disp=False) |
|
E = expm(L) |
|
assert_allclose(E, M, atol=1e-14) |
|
|
|
@pytest.mark.thread_unsafe |
|
def test_nearly_singular(self): |
|
M = np.array([[1e-100]]) |
|
expected_warning = _matfuncs_inv_ssq.LogmNearlySingularWarning |
|
L, info = assert_warns(expected_warning, logm, M, disp=False) |
|
E = expm(L) |
|
assert_allclose(E, M, atol=1e-14) |
|
|
|
def test_opposite_sign_complex_eigenvalues(self): |
|
|
|
E = [[0, 1], [-1, 0]] |
|
L = [[0, np.pi*0.5], [-np.pi*0.5, 0]] |
|
assert_allclose(expm(L), E, atol=1e-14) |
|
assert_allclose(logm(E), L, atol=1e-14) |
|
E = [[1j, 4], [0, -1j]] |
|
L = [[1j*np.pi*0.5, 2*np.pi], [0, -1j*np.pi*0.5]] |
|
assert_allclose(expm(L), E, atol=1e-14) |
|
assert_allclose(logm(E), L, atol=1e-14) |
|
E = [[1j, 0], [0, -1j]] |
|
L = [[1j*np.pi*0.5, 0], [0, -1j*np.pi*0.5]] |
|
assert_allclose(expm(L), E, atol=1e-14) |
|
assert_allclose(logm(E), L, atol=1e-14) |
|
|
|
def test_readonly(self): |
|
n = 5 |
|
a = np.ones((n, n)) + np.identity(n) |
|
a.flags.writeable = False |
|
logm(a) |
|
|
|
@pytest.mark.xfail(reason="ValueError: attempt to get argmax of an empty sequence") |
|
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64]) |
|
def test_empty(self, dt): |
|
a = np.empty((0, 0), dtype=dt) |
|
log_a = logm(a) |
|
a0 = np.eye(2, dtype=dt) |
|
log_a0 = logm(a0) |
|
|
|
assert log_a.shape == (0, 0) |
|
assert log_a.dtype == log_a0.dtype |
|
|
|
@pytest.mark.thread_unsafe |
|
@pytest.mark.parametrize('dtype', [int, float, np.float32, complex, np.complex64]) |
|
def test_no_ZeroDivisionError(self, dtype): |
|
|
|
|
|
|
|
with (pytest.warns(RuntimeWarning, match="logm result may be inaccurate"), |
|
pytest.warns(LogmExactlySingularWarning)): |
|
logm(np.zeros((2, 2), dtype=dtype)) |
|
|
|
|
|
class TestSqrtM: |
|
def test_round_trip_random_float(self): |
|
rng = np.random.RandomState(1234) |
|
for n in range(1, 6): |
|
M_unscaled = rng.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
M_sqrtm, info = sqrtm(M, disp=False) |
|
M_sqrtm_round_trip = M_sqrtm.dot(M_sqrtm) |
|
assert_allclose(M_sqrtm_round_trip, M) |
|
|
|
def test_round_trip_random_complex(self): |
|
rng = np.random.RandomState(1234) |
|
for n in range(1, 6): |
|
M_unscaled = rng.randn(n, n) + 1j * rng.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
M_sqrtm, info = sqrtm(M, disp=False) |
|
M_sqrtm_round_trip = M_sqrtm.dot(M_sqrtm) |
|
assert_allclose(M_sqrtm_round_trip, M) |
|
|
|
def test_bad(self): |
|
|
|
e = 2**-5 |
|
se = sqrt(e) |
|
a = array([[1.0,0,0,1], |
|
[0,e,0,0], |
|
[0,0,e,0], |
|
[0,0,0,1]]) |
|
sa = array([[1,0,0,0.5], |
|
[0,se,0,0], |
|
[0,0,se,0], |
|
[0,0,0,1]]) |
|
n = a.shape[0] |
|
assert_array_almost_equal(dot(sa,sa),a) |
|
|
|
esa = sqrtm(a, disp=False, blocksize=n)[0] |
|
assert_array_almost_equal(dot(esa,esa),a) |
|
|
|
esa = sqrtm(a, disp=False, blocksize=2)[0] |
|
assert_array_almost_equal(dot(esa,esa),a) |
|
|
|
def test_sqrtm_type_preservation_and_conversion(self): |
|
|
|
|
|
|
|
complex_dtype_chars = ('F', 'D', 'G') |
|
for matrix_as_list in ( |
|
[[1, 0], [0, 1]], |
|
[[1, 0], [1, 1]], |
|
[[2, 1], [1, 1]], |
|
[[2, 3], [1, 2]], |
|
[[1, 1], [1, 1]]): |
|
|
|
|
|
W = scipy.linalg.eigvals(matrix_as_list) |
|
assert_(not any(w.imag or w.real < 0 for w in W)) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=float) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(A_sqrtm.dtype.char not in complex_dtype_chars) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=complex) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(A_sqrtm.dtype.char in complex_dtype_chars) |
|
|
|
|
|
A = -np.array(matrix_as_list, dtype=float) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(A_sqrtm.dtype.char in complex_dtype_chars) |
|
|
|
def test_sqrtm_type_conversion_mixed_sign_or_complex_spectrum(self): |
|
complex_dtype_chars = ('F', 'D', 'G') |
|
for matrix_as_list in ( |
|
[[1, 0], [0, -1]], |
|
[[0, 1], [1, 0]], |
|
[[0, 1, 0], [0, 0, 1], [1, 0, 0]]): |
|
|
|
|
|
W = scipy.linalg.eigvals(matrix_as_list) |
|
assert_(any(w.imag or w.real < 0 for w in W)) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=complex) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(A_sqrtm.dtype.char in complex_dtype_chars) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=float) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(A_sqrtm.dtype.char in complex_dtype_chars) |
|
|
|
def test_blocksizes(self): |
|
|
|
np.random.seed(1234) |
|
for n in range(1, 8): |
|
A = np.random.rand(n, n) + 1j*np.random.randn(n, n) |
|
A_sqrtm_default, info = sqrtm(A, disp=False, blocksize=n) |
|
assert_allclose(A, np.linalg.matrix_power(A_sqrtm_default, 2)) |
|
for blocksize in range(1, 10): |
|
A_sqrtm_new, info = sqrtm(A, disp=False, blocksize=blocksize) |
|
assert_allclose(A_sqrtm_default, A_sqrtm_new) |
|
|
|
def test_al_mohy_higham_2012_experiment_1(self): |
|
|
|
A = _get_al_mohy_higham_2012_experiment_1() |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
A_round_trip = A_sqrtm.dot(A_sqrtm) |
|
assert_allclose(A_round_trip, A, rtol=1e-5) |
|
assert_allclose(np.tril(A_round_trip), np.tril(A)) |
|
|
|
def test_strict_upper_triangular(self): |
|
|
|
for dt in int, float: |
|
A = np.array([ |
|
[0, 3, 0, 0], |
|
[0, 0, 3, 0], |
|
[0, 0, 0, 3], |
|
[0, 0, 0, 0]], dtype=dt) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
assert_(np.isnan(A_sqrtm).all()) |
|
|
|
def test_weird_matrix(self): |
|
|
|
for dt in int, float: |
|
A = np.array([ |
|
[0, 0, 1], |
|
[0, 0, 0], |
|
[0, 1, 0]], dtype=dt) |
|
B = np.array([ |
|
[0, 1, 0], |
|
[0, 0, 0], |
|
[0, 0, 0]], dtype=dt) |
|
assert_array_equal(B, A.dot(A)) |
|
|
|
|
|
B_sqrtm, info = sqrtm(B, disp=False) |
|
assert_(np.isnan(B_sqrtm).all()) |
|
|
|
def test_disp(self): |
|
np.random.seed(1234) |
|
|
|
A = np.random.rand(3, 3) |
|
B = sqrtm(A, disp=True) |
|
assert_allclose(B.dot(B), A) |
|
|
|
def test_opposite_sign_complex_eigenvalues(self): |
|
M = [[2j, 4], [0, -2j]] |
|
R = [[1+1j, 2], [0, 1-1j]] |
|
assert_allclose(np.dot(R, R), M, atol=1e-14) |
|
assert_allclose(sqrtm(M), R, atol=1e-14) |
|
|
|
def test_gh4866(self): |
|
M = np.array([[1, 0, 0, 1], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[1, 0, 0, 1]]) |
|
R = np.array([[sqrt(0.5), 0, 0, sqrt(0.5)], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[sqrt(0.5), 0, 0, sqrt(0.5)]]) |
|
assert_allclose(np.dot(R, R), M, atol=1e-14) |
|
assert_allclose(sqrtm(M), R, atol=1e-14) |
|
|
|
def test_gh5336(self): |
|
M = np.diag([2, 1, 0]) |
|
R = np.diag([sqrt(2), 1, 0]) |
|
assert_allclose(np.dot(R, R), M, atol=1e-14) |
|
assert_allclose(sqrtm(M), R, atol=1e-14) |
|
|
|
def test_gh7839(self): |
|
M = np.zeros((2, 2)) |
|
R = np.zeros((2, 2)) |
|
assert_allclose(np.dot(R, R), M, atol=1e-14) |
|
assert_allclose(sqrtm(M), R, atol=1e-14) |
|
|
|
@pytest.mark.xfail(reason="failing on macOS after gh-20212") |
|
def test_gh17918(self): |
|
M = np.empty((19, 19)) |
|
M.fill(0.94) |
|
np.fill_diagonal(M, 1) |
|
assert np.isrealobj(sqrtm(M)) |
|
|
|
def test_data_size_preservation_uint_in_float_out(self): |
|
M = np.zeros((10, 10), dtype=np.uint8) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.uint16) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.uint32) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.uint64) |
|
assert sqrtm(M).dtype == np.float64 |
|
|
|
def test_data_size_preservation_int_in_float_out(self): |
|
M = np.zeros((10, 10), dtype=np.int8) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.int16) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.int32) |
|
assert sqrtm(M).dtype == np.float64 |
|
M = np.zeros((10, 10), dtype=np.int64) |
|
assert sqrtm(M).dtype == np.float64 |
|
|
|
def test_data_size_preservation_int_in_comp_out(self): |
|
M = np.array([[2, 4], [0, -2]], dtype=np.int8) |
|
assert sqrtm(M).dtype == np.complex128 |
|
M = np.array([[2, 4], [0, -2]], dtype=np.int16) |
|
assert sqrtm(M).dtype == np.complex128 |
|
M = np.array([[2, 4], [0, -2]], dtype=np.int32) |
|
assert sqrtm(M).dtype == np.complex128 |
|
M = np.array([[2, 4], [0, -2]], dtype=np.int64) |
|
assert sqrtm(M).dtype == np.complex128 |
|
|
|
def test_data_size_preservation_float_in_float_out(self): |
|
M = np.zeros((10, 10), dtype=np.float16) |
|
assert sqrtm(M).dtype == np.float32 |
|
M = np.zeros((10, 10), dtype=np.float32) |
|
assert sqrtm(M).dtype == np.float32 |
|
M = np.zeros((10, 10), dtype=np.float64) |
|
assert sqrtm(M).dtype == np.float64 |
|
if hasattr(np, 'float128'): |
|
M = np.zeros((10, 10), dtype=np.float128) |
|
assert sqrtm(M).dtype == np.float64 |
|
|
|
def test_data_size_preservation_float_in_comp_out(self): |
|
M = np.array([[2, 4], [0, -2]], dtype=np.float16) |
|
assert sqrtm(M).dtype == np.complex64 |
|
M = np.array([[2, 4], [0, -2]], dtype=np.float32) |
|
assert sqrtm(M).dtype == np.complex64 |
|
M = np.array([[2, 4], [0, -2]], dtype=np.float64) |
|
assert sqrtm(M).dtype == np.complex128 |
|
if hasattr(np, 'float128') and hasattr(np, 'complex256'): |
|
M = np.array([[2, 4], [0, -2]], dtype=np.float128) |
|
assert sqrtm(M).dtype == np.complex128 |
|
|
|
def test_data_size_preservation_comp_in_comp_out(self): |
|
M = np.array([[2j, 4], [0, -2j]], dtype=np.complex64) |
|
assert sqrtm(M).dtype == np.complex64 |
|
M = np.array([[2j, 4], [0, -2j]], dtype=np.complex128) |
|
assert sqrtm(M).dtype == np.complex128 |
|
if hasattr(np, 'complex256'): |
|
M = np.array([[2j, 4], [0, -2j]], dtype=np.complex256) |
|
assert sqrtm(M).dtype == np.complex128 |
|
|
|
@pytest.mark.parametrize('dt', [int, float, np.float32, complex, np.complex64]) |
|
def test_empty(self, dt): |
|
a = np.empty((0, 0), dtype=dt) |
|
s = sqrtm(a) |
|
a0 = np.eye(2, dtype=dt) |
|
s0 = sqrtm(a0) |
|
|
|
assert s.shape == (0, 0) |
|
assert s.dtype == s0.dtype |
|
|
|
|
|
class TestFractionalMatrixPower: |
|
def test_round_trip_random_complex(self): |
|
np.random.seed(1234) |
|
for p in range(1, 5): |
|
for n in range(1, 5): |
|
M_unscaled = np.random.randn(n, n) + 1j * np.random.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
M_root = fractional_matrix_power(M, 1/p) |
|
M_round_trip = np.linalg.matrix_power(M_root, p) |
|
assert_allclose(M_round_trip, M) |
|
|
|
def test_round_trip_random_float(self): |
|
|
|
|
|
|
|
|
|
np.random.seed(1234) |
|
for p in range(1, 5): |
|
for n in range(1, 5): |
|
M_unscaled = np.random.randn(n, n) |
|
for scale in np.logspace(-4, 4, 9): |
|
M = M_unscaled * scale |
|
M_root = fractional_matrix_power(M, 1/p) |
|
M_round_trip = np.linalg.matrix_power(M_root, p) |
|
assert_allclose(M_round_trip, M) |
|
|
|
def test_larger_abs_fractional_matrix_powers(self): |
|
np.random.seed(1234) |
|
for n in (2, 3, 5): |
|
for i in range(10): |
|
M = np.random.randn(n, n) + 1j * np.random.randn(n, n) |
|
M_one_fifth = fractional_matrix_power(M, 0.2) |
|
|
|
M_round_trip = np.linalg.matrix_power(M_one_fifth, 5) |
|
assert_allclose(M, M_round_trip) |
|
|
|
X = fractional_matrix_power(M, -5.4) |
|
Y = np.linalg.matrix_power(M_one_fifth, -27) |
|
assert_allclose(X, Y) |
|
|
|
X = fractional_matrix_power(M, 3.8) |
|
Y = np.linalg.matrix_power(M_one_fifth, 19) |
|
assert_allclose(X, Y) |
|
|
|
def test_random_matrices_and_powers(self): |
|
|
|
|
|
rng = np.random.default_rng(1726500458620605) |
|
nsamples = 20 |
|
for i in range(nsamples): |
|
|
|
n = rng.integers(1, 5) |
|
p = rng.random() |
|
|
|
|
|
matrix_scale = np.exp(rng.integers(-4, 5)) |
|
A = rng.random(size=[n, n]) |
|
if [True, False][rng.choice(2)]: |
|
A = A + 1j * rng.random(size=[n, n]) |
|
A = A * matrix_scale |
|
|
|
|
|
|
|
|
|
A_power = fractional_matrix_power(A, p) |
|
A_logm, info = logm(A, disp=False) |
|
A_power_expm_logm = expm(A_logm * p) |
|
assert_allclose(A_power, A_power_expm_logm) |
|
|
|
def test_al_mohy_higham_2012_experiment_1(self): |
|
|
|
A = _get_al_mohy_higham_2012_experiment_1() |
|
|
|
|
|
A_funm_sqrt, info = funm(A, np.sqrt, disp=False) |
|
A_sqrtm, info = sqrtm(A, disp=False) |
|
A_rem_power = _matfuncs_inv_ssq._remainder_matrix_power(A, 0.5) |
|
A_power = fractional_matrix_power(A, 0.5) |
|
assert_allclose(A_rem_power, A_power, rtol=1e-11) |
|
assert_allclose(A_sqrtm, A_power) |
|
assert_allclose(A_sqrtm, A_funm_sqrt) |
|
|
|
|
|
for p in (1/2, 5/3): |
|
A_power = fractional_matrix_power(A, p) |
|
A_round_trip = fractional_matrix_power(A_power, 1/p) |
|
assert_allclose(A_round_trip, A, rtol=1e-2) |
|
assert_allclose(np.tril(A_round_trip, 1), np.tril(A, 1)) |
|
|
|
def test_briggs_helper_function(self): |
|
np.random.seed(1234) |
|
for a in np.random.randn(10) + 1j * np.random.randn(10): |
|
for k in range(5): |
|
x_observed = _matfuncs_inv_ssq._briggs_helper_function(a, k) |
|
x_expected = a ** np.exp2(-k) - 1 |
|
assert_allclose(x_observed, x_expected) |
|
|
|
def test_type_preservation_and_conversion(self): |
|
|
|
|
|
|
|
|
|
complex_dtype_chars = ('F', 'D', 'G') |
|
for matrix_as_list in ( |
|
[[1, 0], [0, 1]], |
|
[[1, 0], [1, 1]], |
|
[[2, 1], [1, 1]], |
|
[[2, 3], [1, 2]]): |
|
|
|
|
|
W = scipy.linalg.eigvals(matrix_as_list) |
|
assert_(not any(w.imag or w.real < 0 for w in W)) |
|
|
|
|
|
|
|
for p in (-2.4, -0.9, 0.2, 3.3): |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=float) |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(A_power.dtype.char not in complex_dtype_chars) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=complex) |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(A_power.dtype.char in complex_dtype_chars) |
|
|
|
|
|
A = -np.array(matrix_as_list, dtype=float) |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(A_power.dtype.char in complex_dtype_chars) |
|
|
|
def test_type_conversion_mixed_sign_or_complex_spectrum(self): |
|
complex_dtype_chars = ('F', 'D', 'G') |
|
for matrix_as_list in ( |
|
[[1, 0], [0, -1]], |
|
[[0, 1], [1, 0]], |
|
[[0, 1, 0], [0, 0, 1], [1, 0, 0]]): |
|
|
|
|
|
W = scipy.linalg.eigvals(matrix_as_list) |
|
assert_(any(w.imag or w.real < 0 for w in W)) |
|
|
|
|
|
|
|
for p in (-2.4, -0.9, 0.2, 3.3): |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=complex) |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(A_power.dtype.char in complex_dtype_chars) |
|
|
|
|
|
A = np.array(matrix_as_list, dtype=float) |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(A_power.dtype.char in complex_dtype_chars) |
|
|
|
@pytest.mark.xfail(reason='Too unstable across LAPACKs.') |
|
def test_singular(self): |
|
|
|
for matrix_as_list in ( |
|
[[0, 0], [0, 0]], |
|
[[1, 1], [1, 1]], |
|
[[1, 2], [3, 6]], |
|
[[0, 0, 0], [0, 1, 1], [0, -1, 1]]): |
|
|
|
|
|
for newtype in (float, complex): |
|
A = np.array(matrix_as_list, dtype=newtype) |
|
for p in (-0.7, -0.9, -2.4, -1.3): |
|
A_power = fractional_matrix_power(A, p) |
|
assert_(np.isnan(A_power).all()) |
|
for p in (0.2, 1.43): |
|
A_power = fractional_matrix_power(A, p) |
|
A_round_trip = fractional_matrix_power(A_power, 1/p) |
|
assert_allclose(A_round_trip, A) |
|
|
|
def test_opposite_sign_complex_eigenvalues(self): |
|
M = [[2j, 4], [0, -2j]] |
|
R = [[1+1j, 2], [0, 1-1j]] |
|
assert_allclose(np.dot(R, R), M, atol=1e-14) |
|
assert_allclose(fractional_matrix_power(M, 0.5), R, atol=1e-14) |
|
|
|
|
|
class TestExpM: |
|
def test_zero(self): |
|
a = array([[0.,0],[0,0]]) |
|
assert_array_almost_equal(expm(a),[[1,0],[0,1]]) |
|
|
|
def test_single_elt(self): |
|
elt = expm(1) |
|
assert_allclose(elt, np.array([[np.e]])) |
|
|
|
@pytest.mark.parametrize('func', [expm, cosm, sinm, tanm, coshm, sinhm, tanhm]) |
|
@pytest.mark.parametrize('dt',[int, float, np.float32, complex, np.complex64]) |
|
@pytest.mark.parametrize('shape', [(0, 0), (1, 1)]) |
|
def test_small_empty_matrix_input(self, func, dt, shape): |
|
|
|
|
|
A = np.zeros(shape, dtype=dt) |
|
A0 = np.zeros((10, 10), dtype=dt) |
|
result = func(A) |
|
result0 = func(A0) |
|
assert result.shape == shape |
|
assert result.dtype == result0.dtype |
|
|
|
def test_2x2_input(self): |
|
E = np.e |
|
a = array([[1, 4], [1, 1]]) |
|
aa = (E**4 + 1)/(2*E) |
|
bb = (E**4 - 1)/E |
|
assert_allclose(expm(a), array([[aa, bb], [bb/4, aa]])) |
|
assert expm(a.astype(np.complex64)).dtype.char == 'F' |
|
assert expm(a.astype(np.float32)).dtype.char == 'f' |
|
|
|
def test_nx2x2_input(self): |
|
E = np.e |
|
|
|
a = np.array([[[1, 4], [1, 1]], |
|
[[1, 3], [1, -1]], |
|
[[1, 3], [4, 5]], |
|
[[1, 3], [5, 3]], |
|
[[4, 5], [-3, -4]]], order='F') |
|
|
|
a_res = np.array([ |
|
[[(E**4+1)/(2*E), (E**4-1)/E], |
|
[(E**4-1)/4/E, (E**4+1)/(2*E)]], |
|
[[1/(4*E**2)+(3*E**2)/4, (3*E**2)/4-3/(4*E**2)], |
|
[E**2/4-1/(4*E**2), 3/(4*E**2)+E**2/4]], |
|
[[3/(4*E)+E**7/4, -3/(8*E)+(3*E**7)/8], |
|
[-1/(2*E)+E**7/2, 1/(4*E)+(3*E**7)/4]], |
|
[[5/(8*E**2)+(3*E**6)/8, -3/(8*E**2)+(3*E**6)/8], |
|
[-5/(8*E**2)+(5*E**6)/8, 3/(8*E**2)+(5*E**6)/8]], |
|
[[-3/(2*E)+(5*E)/2, -5/(2*E)+(5*E)/2], |
|
[3/(2*E)-(3*E)/2, 5/(2*E)-(3*E)/2]] |
|
]) |
|
assert_allclose(expm(a), a_res) |
|
|
|
def test_readonly(self): |
|
n = 7 |
|
a = np.ones((n, n)) |
|
a.flags.writeable = False |
|
expm(a) |
|
|
|
@pytest.mark.thread_unsafe |
|
@pytest.mark.fail_slow(5) |
|
def test_gh18086(self): |
|
A = np.zeros((400, 400), dtype=float) |
|
rng = np.random.default_rng(100) |
|
i = rng.integers(0, 399, 500) |
|
j = rng.integers(0, 399, 500) |
|
A[i, j] = rng.random(500) |
|
|
|
Am = np.empty((5, 400, 400), dtype=float) |
|
Am[0] = A.copy() |
|
m, s = pick_pade_structure(Am) |
|
assert m == 9 |
|
|
|
first_res = expm(A) |
|
np.testing.assert_array_almost_equal(logm(first_res), A) |
|
|
|
for i in range(5): |
|
next_res = expm(A) |
|
np.testing.assert_array_almost_equal(first_res, next_res) |
|
|
|
|
|
class TestExpmFrechet: |
|
|
|
def test_expm_frechet(self): |
|
|
|
M = np.array([ |
|
[1, 2, 3, 4], |
|
[5, 6, 7, 8], |
|
[0, 0, 1, 2], |
|
[0, 0, 5, 6], |
|
], dtype=float) |
|
A = np.array([ |
|
[1, 2], |
|
[5, 6], |
|
], dtype=float) |
|
E = np.array([ |
|
[3, 4], |
|
[7, 8], |
|
], dtype=float) |
|
expected_expm = scipy.linalg.expm(A) |
|
expected_frechet = scipy.linalg.expm(M)[:2, 2:] |
|
for kwargs in ({}, {'method':'SPS'}, {'method':'blockEnlarge'}): |
|
observed_expm, observed_frechet = expm_frechet(A, E, **kwargs) |
|
assert_allclose(expected_expm, observed_expm) |
|
assert_allclose(expected_frechet, observed_frechet) |
|
|
|
def test_small_norm_expm_frechet(self): |
|
|
|
M_original = np.array([ |
|
[1, 2, 3, 4], |
|
[5, 6, 7, 8], |
|
[0, 0, 1, 2], |
|
[0, 0, 5, 6], |
|
], dtype=float) |
|
A_original = np.array([ |
|
[1, 2], |
|
[5, 6], |
|
], dtype=float) |
|
E_original = np.array([ |
|
[3, 4], |
|
[7, 8], |
|
], dtype=float) |
|
A_original_norm_1 = scipy.linalg.norm(A_original, 1) |
|
selected_m_list = [1, 3, 5, 7, 9, 11, 13, 15] |
|
m_neighbor_pairs = zip(selected_m_list[:-1], selected_m_list[1:]) |
|
for ma, mb in m_neighbor_pairs: |
|
ell_a = scipy.linalg._expm_frechet.ell_table_61[ma] |
|
ell_b = scipy.linalg._expm_frechet.ell_table_61[mb] |
|
target_norm_1 = 0.5 * (ell_a + ell_b) |
|
scale = target_norm_1 / A_original_norm_1 |
|
M = scale * M_original |
|
A = scale * A_original |
|
E = scale * E_original |
|
expected_expm = scipy.linalg.expm(A) |
|
expected_frechet = scipy.linalg.expm(M)[:2, 2:] |
|
observed_expm, observed_frechet = expm_frechet(A, E) |
|
assert_allclose(expected_expm, observed_expm) |
|
assert_allclose(expected_frechet, observed_frechet) |
|
|
|
def test_fuzz(self): |
|
rng = np.random.default_rng(1726500908359153) |
|
|
|
rfuncs = ( |
|
np.random.uniform, |
|
np.random.normal, |
|
np.random.standard_cauchy, |
|
np.random.exponential) |
|
ntests = 100 |
|
for i in range(ntests): |
|
rfunc = rfuncs[rng.choice(4)] |
|
target_norm_1 = rng.exponential() |
|
n = rng.integers(2, 16) |
|
A_original = rfunc(size=(n,n)) |
|
E_original = rfunc(size=(n,n)) |
|
A_original_norm_1 = scipy.linalg.norm(A_original, 1) |
|
scale = target_norm_1 / A_original_norm_1 |
|
A = scale * A_original |
|
E = scale * E_original |
|
M = np.vstack([ |
|
np.hstack([A, E]), |
|
np.hstack([np.zeros_like(A), A])]) |
|
expected_expm = scipy.linalg.expm(A) |
|
expected_frechet = scipy.linalg.expm(M)[:n, n:] |
|
observed_expm, observed_frechet = expm_frechet(A, E) |
|
assert_allclose(expected_expm, observed_expm, atol=5e-8) |
|
assert_allclose(expected_frechet, observed_frechet, atol=1e-7) |
|
|
|
def test_problematic_matrix(self): |
|
|
|
A = np.array([ |
|
[1.50591997, 1.93537998], |
|
[0.41203263, 0.23443516], |
|
], dtype=float) |
|
E = np.array([ |
|
[1.87864034, 2.07055038], |
|
[1.34102727, 0.67341123], |
|
], dtype=float) |
|
scipy.linalg.norm(A, 1) |
|
sps_expm, sps_frechet = expm_frechet( |
|
A, E, method='SPS') |
|
blockEnlarge_expm, blockEnlarge_frechet = expm_frechet( |
|
A, E, method='blockEnlarge') |
|
assert_allclose(sps_expm, blockEnlarge_expm) |
|
assert_allclose(sps_frechet, blockEnlarge_frechet) |
|
|
|
@pytest.mark.slow |
|
@pytest.mark.skip(reason='this test is deliberately slow') |
|
def test_medium_matrix(self): |
|
|
|
n = 1000 |
|
A = np.random.exponential(size=(n, n)) |
|
E = np.random.exponential(size=(n, n)) |
|
sps_expm, sps_frechet = expm_frechet( |
|
A, E, method='SPS') |
|
blockEnlarge_expm, blockEnlarge_frechet = expm_frechet( |
|
A, E, method='blockEnlarge') |
|
assert_allclose(sps_expm, blockEnlarge_expm) |
|
assert_allclose(sps_frechet, blockEnlarge_frechet) |
|
|
|
|
|
def _help_expm_cond_search(A, A_norm, X, X_norm, eps, p): |
|
p = np.reshape(p, A.shape) |
|
p_norm = norm(p) |
|
perturbation = eps * p * (A_norm / p_norm) |
|
X_prime = expm(A + perturbation) |
|
scaled_relative_error = norm(X_prime - X) / (X_norm * eps) |
|
return -scaled_relative_error |
|
|
|
|
|
def _normalized_like(A, B): |
|
return A * (scipy.linalg.norm(B) / scipy.linalg.norm(A)) |
|
|
|
|
|
def _relative_error(f, A, perturbation): |
|
X = f(A) |
|
X_prime = f(A + perturbation) |
|
return norm(X_prime - X) / norm(X) |
|
|
|
|
|
class TestExpmConditionNumber: |
|
def test_expm_cond_smoke(self): |
|
np.random.seed(1234) |
|
for n in range(1, 4): |
|
A = np.random.randn(n, n) |
|
kappa = expm_cond(A) |
|
assert_array_less(0, kappa) |
|
|
|
def test_expm_bad_condition_number(self): |
|
A = np.array([ |
|
[-1.128679820, 9.614183771e4, -4.524855739e9, 2.924969411e14], |
|
[0, -1.201010529, 9.634696872e4, -4.681048289e9], |
|
[0, 0, -1.132893222, 9.532491830e4], |
|
[0, 0, 0, -1.179475332], |
|
]) |
|
kappa = expm_cond(A) |
|
assert_array_less(1e36, kappa) |
|
|
|
def test_univariate(self): |
|
np.random.seed(12345) |
|
for x in np.linspace(-5, 5, num=11): |
|
A = np.array([[x]]) |
|
assert_allclose(expm_cond(A), abs(x)) |
|
for x in np.logspace(-2, 2, num=11): |
|
A = np.array([[x]]) |
|
assert_allclose(expm_cond(A), abs(x)) |
|
for i in range(10): |
|
A = np.random.randn(1, 1) |
|
assert_allclose(expm_cond(A), np.absolute(A)[0, 0]) |
|
|
|
@pytest.mark.slow |
|
def test_expm_cond_fuzz(self): |
|
rng = np.random.RandomState(12345) |
|
eps = 1e-5 |
|
nsamples = 10 |
|
for i in range(nsamples): |
|
n = rng.randint(2, 5) |
|
A = rng.randn(n, n) |
|
A_norm = scipy.linalg.norm(A) |
|
X = expm(A) |
|
X_norm = scipy.linalg.norm(X) |
|
kappa = expm_cond(A) |
|
|
|
|
|
|
|
f = functools.partial(_help_expm_cond_search, |
|
A, A_norm, X, X_norm, eps) |
|
guess = np.ones(n*n) |
|
out = minimize(f, guess, method='L-BFGS-B') |
|
xopt = out.x |
|
yopt = f(xopt) |
|
p_best = eps * _normalized_like(np.reshape(xopt, A.shape), A) |
|
p_best_relerr = _relative_error(expm, A, p_best) |
|
assert_allclose(p_best_relerr, -yopt * eps) |
|
|
|
|
|
|
|
for j in range(5): |
|
p_rand = eps * _normalized_like(rng.randn(*A.shape), A) |
|
assert_allclose(norm(p_best), norm(p_rand)) |
|
p_rand_relerr = _relative_error(expm, A, p_rand) |
|
assert_array_less(p_rand_relerr, p_best_relerr) |
|
|
|
|
|
|
|
|
|
assert_array_less(p_best_relerr, (1 + 2*eps) * eps * kappa) |
|
|
|
|
|
class TestKhatriRao: |
|
|
|
def test_basic(self): |
|
a = khatri_rao(array([[1, 2], [3, 4]]), |
|
array([[5, 6], [7, 8]])) |
|
|
|
assert_array_equal(a, array([[5, 12], |
|
[7, 16], |
|
[15, 24], |
|
[21, 32]])) |
|
|
|
b = khatri_rao(np.empty([2, 2]), np.empty([2, 2])) |
|
assert_array_equal(b.shape, (4, 2)) |
|
|
|
def test_number_of_columns_equality(self): |
|
with pytest.raises(ValueError): |
|
a = array([[1, 2, 3], |
|
[4, 5, 6]]) |
|
b = array([[1, 2], |
|
[3, 4]]) |
|
khatri_rao(a, b) |
|
|
|
def test_to_assure_2d_array(self): |
|
with pytest.raises(ValueError): |
|
|
|
a = array([1, 2, 3]) |
|
b = array([4, 5, 6]) |
|
khatri_rao(a, b) |
|
|
|
with pytest.raises(ValueError): |
|
|
|
a = array([1, 2, 3]) |
|
b = array([ |
|
[1, 2, 3], |
|
[4, 5, 6] |
|
]) |
|
khatri_rao(a, b) |
|
|
|
with pytest.raises(ValueError): |
|
|
|
a = array([ |
|
[1, 2, 3], |
|
[7, 8, 9] |
|
]) |
|
b = array([4, 5, 6]) |
|
khatri_rao(a, b) |
|
|
|
def test_equality_of_two_equations(self): |
|
a = array([[1, 2], [3, 4]]) |
|
b = array([[5, 6], [7, 8]]) |
|
|
|
res1 = khatri_rao(a, b) |
|
res2 = np.vstack([np.kron(a[:, k], b[:, k]) |
|
for k in range(b.shape[1])]).T |
|
|
|
assert_array_equal(res1, res2) |
|
|
|
def test_empty(self): |
|
a = np.empty((0, 2)) |
|
b = np.empty((3, 2)) |
|
res = khatri_rao(a, b) |
|
assert_allclose(res, np.empty((0, 2))) |
|
|
|
a = np.empty((3, 0)) |
|
b = np.empty((5, 0)) |
|
res = khatri_rao(a, b) |
|
assert_allclose(res, np.empty((15, 0))) |
|
|