[tests] more tests
This commit is contained in:
parent
9ac24892d6
commit
0ff23542ac
4 changed files with 392 additions and 11 deletions
|
|
@ -18,35 +18,35 @@ from .types import (
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: None) -> None:
|
def vec(f: None) -> None:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: fdfield_t) -> vfdfield_t:
|
def vec(f: fdfield_t) -> vfdfield_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: cfdfield_t) -> vcfdfield_t:
|
def vec(f: cfdfield_t) -> vcfdfield_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: fdfield2_t) -> vfdfield2_t:
|
def vec(f: fdfield2_t) -> vfdfield2_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: cfdfield2_t) -> vcfdfield2_t:
|
def vec(f: cfdfield2_t) -> vcfdfield2_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: fdslice_t) -> vfdslice_t:
|
def vec(f: fdslice_t) -> vfdslice_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: cfdslice_t) -> vcfdslice_t:
|
def vec(f: cfdslice_t) -> vcfdslice_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def vec(f: ArrayLike) -> NDArray:
|
def vec(f: ArrayLike) -> NDArray:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
def vec(
|
def vec(
|
||||||
f: fdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | None,
|
f: fdfield_t | cfdfield_t | fdfield2_t | cfdfield2_t | fdslice_t | cfdslice_t | ArrayLike | None,
|
||||||
|
|
@ -70,15 +70,15 @@ def vec(
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: None, shape: Sequence[int], nvdim: int = 3) -> None:
|
def unvec(v: None, shape: Sequence[int], nvdim: int = 3) -> None:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: vfdfield_t, shape: Sequence[int], nvdim: int = 3) -> fdfield_t:
|
def unvec(v: vfdfield_t, shape: Sequence[int], nvdim: int = 3) -> fdfield_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3) -> cfdfield_t:
|
def unvec(v: vcfdfield_t, shape: Sequence[int], nvdim: int = 3) -> cfdfield_t:
|
||||||
pass
|
pass # pragma: no cover
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
def unvec(v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3) -> fdfield2_t:
|
def unvec(v: vfdfield2_t, shape: Sequence[int], nvdim: int = 3) -> fdfield2_t:
|
||||||
|
|
|
||||||
45
meanas/test/test_fdmath_vectorization.py
Normal file
45
meanas/test/test_fdmath_vectorization.py
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import numpy
|
||||||
|
from numpy.testing import assert_allclose, assert_array_equal
|
||||||
|
|
||||||
|
from ..fdmath import unvec, vec
|
||||||
|
|
||||||
|
|
||||||
|
SHAPE = (2, 3, 2)
|
||||||
|
FIELD = numpy.arange(3 * numpy.prod(SHAPE), dtype=float).reshape((3, *SHAPE), order='C')
|
||||||
|
COMPLEX_FIELD = (FIELD + 0.5j * FIELD).astype(complex)
|
||||||
|
|
||||||
|
|
||||||
|
def test_vec_and_unvec_return_none_for_none_input() -> None:
|
||||||
|
assert vec(None) is None
|
||||||
|
assert unvec(None, SHAPE) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_real_field_round_trip_preserves_shape_and_values() -> None:
|
||||||
|
vector = vec(FIELD)
|
||||||
|
assert vector is not None
|
||||||
|
restored = unvec(vector, SHAPE)
|
||||||
|
assert restored is not None
|
||||||
|
assert restored.shape == (3, *SHAPE)
|
||||||
|
assert_array_equal(restored, FIELD)
|
||||||
|
|
||||||
|
|
||||||
|
def test_complex_field_round_trip_preserves_shape_and_values() -> None:
|
||||||
|
vector = vec(COMPLEX_FIELD)
|
||||||
|
assert vector is not None
|
||||||
|
restored = unvec(vector, SHAPE)
|
||||||
|
assert restored is not None
|
||||||
|
assert restored.shape == (3, *SHAPE)
|
||||||
|
assert_allclose(restored, COMPLEX_FIELD)
|
||||||
|
|
||||||
|
|
||||||
|
def test_unvec_with_two_components_round_trips_vector() -> None:
|
||||||
|
vector = numpy.arange(2 * numpy.prod(SHAPE), dtype=float)
|
||||||
|
field = unvec(vector, SHAPE, nvdim=2)
|
||||||
|
assert field is not None
|
||||||
|
assert field.shape == (2, *SHAPE)
|
||||||
|
assert_array_equal(vec(field), vector)
|
||||||
|
|
||||||
|
|
||||||
|
def test_vec_accepts_arraylike_input() -> None:
|
||||||
|
arraylike = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
||||||
|
assert_array_equal(vec(arraylike), numpy.ravel(arraylike, order='C'))
|
||||||
98
meanas/test/test_fdtd_energy.py
Normal file
98
meanas/test/test_fdtd_energy.py
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
import numpy
|
||||||
|
from numpy.testing import assert_allclose
|
||||||
|
|
||||||
|
from .. import fdtd
|
||||||
|
from ..fdtd import energy as fdtd_energy
|
||||||
|
|
||||||
|
|
||||||
|
SHAPE = (2, 2, 2)
|
||||||
|
DT = 0.25
|
||||||
|
UNIT_DXES = tuple(tuple(numpy.ones(length) for length in SHAPE) for _ in range(2))
|
||||||
|
DXES = (
|
||||||
|
(
|
||||||
|
numpy.array([1.0, 1.5]),
|
||||||
|
numpy.array([0.75, 1.25]),
|
||||||
|
numpy.array([1.1, 0.9]),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
numpy.array([0.8, 1.2]),
|
||||||
|
numpy.array([1.4, 0.6]),
|
||||||
|
numpy.array([0.7, 1.3]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
E0 = numpy.arange(3 * numpy.prod(SHAPE), dtype=float).reshape((3, *SHAPE), order='C')
|
||||||
|
E1 = E0 + 0.5
|
||||||
|
E2 = E0 + 1.0
|
||||||
|
E3 = E0 + 1.5
|
||||||
|
H0 = (numpy.arange(3 * numpy.prod(SHAPE), dtype=float).reshape((3, *SHAPE), order='C') + 2.0) / 3.0
|
||||||
|
H1 = H0 + 0.25
|
||||||
|
H2 = H0 + 0.5
|
||||||
|
H3 = H0 + 0.75
|
||||||
|
J0 = (E0 + 2.0) / 5.0
|
||||||
|
EPSILON = 1.0 + E0 / 20.0
|
||||||
|
MU = 1.5 + H0 / 10.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_poynting_default_spacing_matches_explicit_unit_spacing() -> None:
|
||||||
|
default_spacing = fdtd.poynting(E1, H1)
|
||||||
|
explicit_spacing = fdtd.poynting(E1, H1, dxes=UNIT_DXES)
|
||||||
|
assert_allclose(default_spacing, explicit_spacing)
|
||||||
|
|
||||||
|
|
||||||
|
def test_poynting_divergence_matches_precomputed_poynting_vector() -> None:
|
||||||
|
s = fdtd.poynting(E2, H2, dxes=DXES)
|
||||||
|
from_fields = fdtd.poynting_divergence(e=E2, h=H2, dxes=DXES)
|
||||||
|
from_vector = fdtd.poynting_divergence(s=s)
|
||||||
|
assert_allclose(from_fields, from_vector)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_energy_h2e_matches_direct_dxmul_formula() -> None:
|
||||||
|
expected = fdtd_energy.dxmul(
|
||||||
|
E2 * (E2 - E0) / DT,
|
||||||
|
H1 * (H3 - H1) / DT,
|
||||||
|
EPSILON,
|
||||||
|
MU,
|
||||||
|
DXES,
|
||||||
|
)
|
||||||
|
actual = fdtd.delta_energy_h2e(
|
||||||
|
dt=DT,
|
||||||
|
e0=E0,
|
||||||
|
h1=H1,
|
||||||
|
e2=E2,
|
||||||
|
h3=H3,
|
||||||
|
epsilon=EPSILON,
|
||||||
|
mu=MU,
|
||||||
|
dxes=DXES,
|
||||||
|
)
|
||||||
|
assert_allclose(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_energy_e2h_matches_direct_dxmul_formula() -> None:
|
||||||
|
expected = fdtd_energy.dxmul(
|
||||||
|
E1 * (E3 - E1) / DT,
|
||||||
|
H2 * (H2 - H0) / DT,
|
||||||
|
EPSILON,
|
||||||
|
MU,
|
||||||
|
DXES,
|
||||||
|
)
|
||||||
|
actual = fdtd_energy.delta_energy_e2h(
|
||||||
|
dt=DT,
|
||||||
|
h0=H0,
|
||||||
|
e1=E1,
|
||||||
|
h2=H2,
|
||||||
|
e3=E3,
|
||||||
|
epsilon=EPSILON,
|
||||||
|
mu=MU,
|
||||||
|
dxes=DXES,
|
||||||
|
)
|
||||||
|
assert_allclose(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_energy_j_defaults_to_unit_cell_volume() -> None:
|
||||||
|
expected = (J0 * E1).sum(axis=0)
|
||||||
|
assert_allclose(fdtd.delta_energy_j(j0=J0, e1=E1), expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_dxmul_defaults_to_unit_materials_and_spacing() -> None:
|
||||||
|
expected = E1.sum(axis=0) + H1.sum(axis=0)
|
||||||
|
assert_allclose(fdtd_energy.dxmul(E1, H1), expected)
|
||||||
238
meanas/test/test_regressions.py
Normal file
238
meanas/test/test_regressions.py
Normal file
|
|
@ -0,0 +1,238 @@
|
||||||
|
import numpy
|
||||||
|
import pytest # type: ignore
|
||||||
|
from numpy.testing import assert_allclose
|
||||||
|
from scipy import sparse
|
||||||
|
|
||||||
|
from ..eigensolvers import power_iteration, rayleigh_quotient_iteration, signed_eigensolve
|
||||||
|
from ..fdfd import eme, farfield
|
||||||
|
from ..fdtd.boundaries import conducting_boundary
|
||||||
|
from ..fdtd.misc import gaussian_beam, gaussian_packet, ricker_pulse
|
||||||
|
from ..fdtd.pml import cpml_params, updates_with_cpml
|
||||||
|
|
||||||
|
|
||||||
|
def _axis_index(axis: int, index: int) -> tuple[slice | int, ...]:
|
||||||
|
coords: list[slice | int] = [slice(None), slice(None), slice(None)]
|
||||||
|
coords[axis] = index
|
||||||
|
return tuple(coords)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('one_sided', [False, True])
|
||||||
|
def test_gaussian_packet_accepts_array_input(one_sided: bool) -> None:
|
||||||
|
dt = 0.01
|
||||||
|
source, delay = gaussian_packet(1.55, 0.1, dt, one_sided=one_sided)
|
||||||
|
steps = numpy.array([0, int(numpy.ceil(delay / dt)) + 5])
|
||||||
|
envelope, cc, ss = source(steps)
|
||||||
|
|
||||||
|
assert envelope.shape == (2,)
|
||||||
|
assert numpy.isfinite(envelope).all()
|
||||||
|
assert numpy.isfinite(cc).all()
|
||||||
|
assert numpy.isfinite(ss).all()
|
||||||
|
if one_sided:
|
||||||
|
assert envelope[-1] == pytest.approx(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ricker_pulse_returns_finite_values() -> None:
|
||||||
|
source, delay = ricker_pulse(1.55, 0.01)
|
||||||
|
envelope, cc, ss = source(numpy.array([0, 1, 2]))
|
||||||
|
|
||||||
|
assert numpy.isfinite(delay)
|
||||||
|
assert numpy.isfinite(envelope).all()
|
||||||
|
assert numpy.isfinite(cc).all()
|
||||||
|
assert numpy.isfinite(ss).all()
|
||||||
|
|
||||||
|
|
||||||
|
def test_gaussian_beam_centered_grid_is_finite_and_normalized() -> None:
|
||||||
|
beam = gaussian_beam(
|
||||||
|
xyz=[numpy.linspace(-1, 1, 3), numpy.linspace(-1, 1, 3), numpy.linspace(-1, 1, 3)],
|
||||||
|
center=[0, 0, 0],
|
||||||
|
waist_radius=1.0,
|
||||||
|
wl=1.55,
|
||||||
|
)
|
||||||
|
|
||||||
|
row = beam[:, :, beam.shape[2] // 2]
|
||||||
|
assert numpy.isfinite(beam).all()
|
||||||
|
assert numpy.linalg.norm(row) == pytest.approx(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('direction', [0, 1, 2])
|
||||||
|
@pytest.mark.parametrize('polarity', [-1, 1])
|
||||||
|
def test_conducting_boundary_updates_expected_faces(direction: int, polarity: int) -> None:
|
||||||
|
e = numpy.arange(3 * 4 * 4 * 4, dtype=float).reshape(3, 4, 4, 4)
|
||||||
|
h = e.copy()
|
||||||
|
e0 = e.copy()
|
||||||
|
h0 = h.copy()
|
||||||
|
|
||||||
|
update_e, update_h = conducting_boundary(direction, polarity)
|
||||||
|
update_e(e)
|
||||||
|
update_h(h)
|
||||||
|
|
||||||
|
dirs = [0, 1, 2]
|
||||||
|
dirs.remove(direction)
|
||||||
|
u, v = dirs
|
||||||
|
|
||||||
|
if polarity < 0:
|
||||||
|
boundary = _axis_index(direction, 0)
|
||||||
|
shifted1 = _axis_index(direction, 1)
|
||||||
|
|
||||||
|
assert_allclose(e[direction][boundary], 0)
|
||||||
|
assert_allclose(e[u][boundary], e0[u][shifted1])
|
||||||
|
assert_allclose(e[v][boundary], e0[v][shifted1])
|
||||||
|
assert_allclose(h[direction][boundary], h0[direction][shifted1])
|
||||||
|
assert_allclose(h[u][boundary], 0)
|
||||||
|
assert_allclose(h[v][boundary], 0)
|
||||||
|
else:
|
||||||
|
boundary = _axis_index(direction, -1)
|
||||||
|
shifted1 = _axis_index(direction, -2)
|
||||||
|
shifted2 = _axis_index(direction, -3)
|
||||||
|
|
||||||
|
assert_allclose(e[direction][boundary], -e0[direction][shifted2])
|
||||||
|
assert_allclose(e[direction][shifted1], 0)
|
||||||
|
assert_allclose(e[u][boundary], e0[u][shifted1])
|
||||||
|
assert_allclose(e[v][boundary], e0[v][shifted1])
|
||||||
|
assert_allclose(h[direction][boundary], h0[direction][shifted1])
|
||||||
|
assert_allclose(h[u][boundary], -h0[u][shifted2])
|
||||||
|
assert_allclose(h[u][shifted1], 0)
|
||||||
|
assert_allclose(h[v][boundary], -h0[v][shifted2])
|
||||||
|
assert_allclose(h[v][shifted1], 0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('direction', 'polarity'),
|
||||||
|
[(-1, 1), (3, 1), (0, 0)],
|
||||||
|
)
|
||||||
|
def test_conducting_boundary_rejects_invalid_arguments(direction: int, polarity: int) -> None:
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
conducting_boundary(direction, polarity)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_abcd_returns_sparse_block_matrix(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
t = numpy.array([[1.0, 0.0], [0.0, 2.0]])
|
||||||
|
r = numpy.array([[0.0, 0.1], [0.2, 0.0]])
|
||||||
|
monkeypatch.setattr(eme, 'get_tr', lambda *args, **kwargs: (t, r))
|
||||||
|
|
||||||
|
abcd = eme.get_abcd(None, None, None, None)
|
||||||
|
|
||||||
|
assert sparse.issparse(abcd)
|
||||||
|
assert abcd.shape == (4, 4)
|
||||||
|
assert numpy.isfinite(abcd.toarray()).all()
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_s_force_reciprocal_symmetrizes_output(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
left = object()
|
||||||
|
right = object()
|
||||||
|
|
||||||
|
def fake_get_tr(_eh_l, wavenumbers_l, _eh_r, _wavenumbers_r, **kwargs):
|
||||||
|
if wavenumbers_l is left:
|
||||||
|
return (
|
||||||
|
numpy.array([[1.0, 2.0], [0.5, 1.0]]),
|
||||||
|
numpy.array([[0.0, 1.0], [2.0, 0.0]]),
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
numpy.array([[1.0, -1.0], [0.0, 1.0]]),
|
||||||
|
numpy.array([[0.0, 0.5], [1.5, 0.0]]),
|
||||||
|
)
|
||||||
|
|
||||||
|
monkeypatch.setattr(eme, 'get_tr', fake_get_tr)
|
||||||
|
ss = eme.get_s(None, left, None, right, force_reciprocal=True)
|
||||||
|
|
||||||
|
assert_allclose(ss, ss.T)
|
||||||
|
|
||||||
|
|
||||||
|
def test_farfield_roundtrip_supports_rectangular_arrays() -> None:
|
||||||
|
e_near = [numpy.zeros((4, 8), dtype=complex), numpy.zeros((4, 8), dtype=complex)]
|
||||||
|
h_near = [numpy.zeros((4, 8), dtype=complex), numpy.zeros((4, 8), dtype=complex)]
|
||||||
|
e_near[0][1, 3] = 1.0 + 0.25j
|
||||||
|
h_near[1][2, 5] = -0.5j
|
||||||
|
|
||||||
|
ff = farfield.near_to_farfield(e_near, h_near, dx=0.2, dy=0.3, padded_size=(4, 8))
|
||||||
|
restored = farfield.far_to_nearfield(
|
||||||
|
[field.copy() for field in ff['E']],
|
||||||
|
[field.copy() for field in ff['H']],
|
||||||
|
ff['dkx'],
|
||||||
|
ff['dky'],
|
||||||
|
padded_size=(4, 8),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert isinstance(ff['dkx'], float)
|
||||||
|
assert isinstance(ff['dky'], float)
|
||||||
|
assert ff['E'][0].shape == (4, 8)
|
||||||
|
assert restored['E'][0].shape == (4, 8)
|
||||||
|
assert restored['H'][0].shape == (4, 8)
|
||||||
|
assert restored['dx'] == pytest.approx(0.2)
|
||||||
|
assert restored['dy'] == pytest.approx(0.3)
|
||||||
|
assert numpy.isfinite(restored['E'][0]).all()
|
||||||
|
assert numpy.isfinite(restored['H'][0]).all()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
('axis', 'polarity', 'thickness', 'epsilon_eff'),
|
||||||
|
[(3, 1, 4, 1.0), (0, 0, 4, 1.0), (0, 1, 2, 1.0), (0, 1, 4, 0.0)],
|
||||||
|
)
|
||||||
|
def test_cpml_params_reject_invalid_arguments(axis: int, polarity: int, thickness: int, epsilon_eff: float) -> None:
|
||||||
|
with pytest.raises(Exception):
|
||||||
|
cpml_params(axis=axis, polarity=polarity, dt=0.1, thickness=thickness, epsilon_eff=epsilon_eff)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cpml_params_shapes_and_region() -> None:
|
||||||
|
params = cpml_params(axis=1, polarity=1, dt=0.1, thickness=3)
|
||||||
|
p0e, p1e, p2e = params['param_e']
|
||||||
|
p0h, p1h, p2h = params['param_h']
|
||||||
|
|
||||||
|
assert p0e.shape == (1, 3, 1)
|
||||||
|
assert p1e.shape == (1, 3, 1)
|
||||||
|
assert p2e.shape == (1, 3, 1)
|
||||||
|
assert p0h.shape == (1, 3, 1)
|
||||||
|
assert p1h.shape == (1, 3, 1)
|
||||||
|
assert p2h.shape == (1, 3, 1)
|
||||||
|
assert params['region'][1] == slice(-3, None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_updates_with_cpml_keeps_zero_fields_zero() -> None:
|
||||||
|
shape = (3, 4, 4, 4)
|
||||||
|
epsilon = numpy.ones(shape, dtype=float)
|
||||||
|
e = numpy.zeros(shape, dtype=float)
|
||||||
|
h = numpy.zeros(shape, dtype=float)
|
||||||
|
dxes = [[numpy.ones(4), numpy.ones(4), numpy.ones(4)] for _ in range(2)]
|
||||||
|
params = [[None, None] for _ in range(3)]
|
||||||
|
params[0][0] = cpml_params(axis=0, polarity=-1, dt=0.1, thickness=3)
|
||||||
|
|
||||||
|
update_e, update_h = updates_with_cpml(params, dt=0.1, dxes=dxes, epsilon=epsilon)
|
||||||
|
update_e(e, h, epsilon)
|
||||||
|
update_h(e, h)
|
||||||
|
|
||||||
|
assert not e.any()
|
||||||
|
assert not h.any()
|
||||||
|
|
||||||
|
|
||||||
|
def test_power_iteration_finds_dominant_mode() -> None:
|
||||||
|
operator = sparse.diags([5.0, 3.0, 1.0, -2.0]).tocsr()
|
||||||
|
eigval, eigvec = power_iteration(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_mode() -> None:
|
||||||
|
operator = sparse.diags([5.0, 3.0, 1.0, -2.0]).tocsr()
|
||||||
|
|
||||||
|
def solver(matrix: sparse.spmatrix, rhs: numpy.ndarray) -> numpy.ndarray:
|
||||||
|
return numpy.linalg.lstsq(matrix.toarray(), rhs, rcond=None)[0]
|
||||||
|
|
||||||
|
eigval, eigvec = rayleigh_quotient_iteration(
|
||||||
|
operator,
|
||||||
|
numpy.array([1.0, 0.1, 0.0, 0.0], dtype=complex),
|
||||||
|
iterations=8,
|
||||||
|
solver=solver,
|
||||||
|
)
|
||||||
|
|
||||||
|
residual = numpy.linalg.norm(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:
|
||||||
|
operator = sparse.diags([5.0, 3.0, 1.0, -2.0]).tocsr()
|
||||||
|
eigvals, eigvecs = signed_eigensolve(operator, how_many=2)
|
||||||
|
|
||||||
|
assert_allclose(eigvals, [3.0, 5.0], atol=1e-6)
|
||||||
|
assert eigvecs.shape == (4, 2)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue