meanas/meanas/test/test_eigensolvers_numerics.py

101 lines
3.1 KiB
Python

import numpy
from numpy.linalg import norm
import pytest
from ._solver_cases import diagonal_eigen_case
from .utils import assert_close
from ..eigensolvers import power_iteration, rayleigh_quotient_iteration, signed_eigensolve
def test_rayleigh_quotient_iteration_with_linear_operator() -> None:
case = diagonal_eigen_case()
def dense_solver(
shifted_operator,
rhs: numpy.ndarray,
) -> numpy.ndarray:
basis = numpy.eye(case.operator.shape[0], dtype=complex)
columns = [shifted_operator.matvec(basis[:, ii]) for ii in range(case.operator.shape[0])]
dense_matrix = numpy.column_stack(columns)
return numpy.linalg.lstsq(dense_matrix, rhs, rcond=None)[0]
eigval, eigvec = rayleigh_quotient_iteration(
case.linear_operator,
case.guess_default,
iterations=8,
solver=dense_solver,
)
residual = norm(case.operator @ eigvec - eigval * eigvec)
assert abs(eigval - 3.0) < 1e-12
assert residual < 1e-12
def test_signed_eigensolve_negative_returns_largest_negative_mode() -> None:
case = diagonal_eigen_case()
eigvals, eigvecs = signed_eigensolve(case.operator, how_many=1, negative=True)
assert eigvals.shape == (1,)
assert eigvecs.shape == (4, 1)
assert abs(eigvals[0] + 2.0) < 1e-12
assert abs(eigvecs[3, 0]) > 0.99
def test_rayleigh_quotient_iteration_uses_default_linear_operator_solver() -> None:
case = diagonal_eigen_case()
eigval, eigvec = rayleigh_quotient_iteration(
case.linear_operator,
case.guess_default,
iterations=8,
)
residual = norm(case.operator @ eigvec - eigval * eigvec)
assert abs(eigval - 3.0) < 1e-12
assert residual < 1e-12
def test_signed_eigensolve_linear_operator_fallback_returns_dominant_positive_mode() -> None:
case = diagonal_eigen_case()
eigvals, eigvecs = signed_eigensolve(case.linear_operator, how_many=1)
assert eigvals.shape == (1,)
assert eigvecs.shape == (4, 1)
assert_close(eigvals[0], 5.0, atol=1e-12, rtol=1e-12)
assert abs(eigvecs[0, 0]) > 0.99
def test_power_iteration_finds_dominant_mode() -> None:
case = diagonal_eigen_case()
eigval, eigvec = power_iteration(case.operator, guess_vector=numpy.ones(4, dtype=complex), iterations=20)
assert eigval == pytest.approx(5.0, rel=1e-6)
assert abs(eigvec[0]) > abs(eigvec[1])
def test_rayleigh_quotient_iteration_refines_known_sparse_mode() -> None:
case = diagonal_eigen_case()
def solver(matrix, rhs: numpy.ndarray) -> numpy.ndarray:
return numpy.linalg.lstsq(matrix.toarray(), rhs, rcond=None)[0]
eigval, eigvec = rayleigh_quotient_iteration(
case.operator,
case.guess_sparse,
iterations=8,
solver=solver,
)
residual = numpy.linalg.norm(case.operator @ eigvec - eigval * eigvec)
assert eigval == pytest.approx(3.0, rel=1e-6)
assert residual < 1e-8
def test_signed_eigensolve_returns_largest_positive_modes() -> None:
case = diagonal_eigen_case()
eigvals, eigvecs = signed_eigensolve(case.operator, how_many=2)
assert_close(eigvals, [3.0, 5.0], atol=1e-6)
assert eigvecs.shape == (4, 2)