Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pygpcca/_sorted_schur.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def _check_conj_split(eigenvalues: ArrayLike) -> bool:
"""
last_eigenvalue, second_last_eigenvalue = eigenvalues[-1], eigenvalues[-2]
splits_block = False
if last_eigenvalue.imag > EPS:
if abs(last_eigenvalue.imag) > EPS:
splits_block = not np.isclose(last_eigenvalue, np.conj(second_last_eigenvalue))

return splits_block
Expand Down Expand Up @@ -221,7 +221,7 @@ def sorted_krylov_schur(
eigenvalues_error
Array of shape `(k,)` containing the error, based on the residual
norm, of the `i`th eigenpair at index `i`.
""" # noqa: D205, D400
"""
# We like to thank A. Sikorski and M. Weber for pointing us to SLEPc for partial Schur decompositions of
# sparse matrices.
# Further parts of sorted_krylov_schur were developed based on the function krylov_schur
Expand Down
19 changes: 19 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ def assert_allclose(actual, desired, rtol=1.0e-5, atol=1.0e-8, err_msg="", verbo
return assert_allclose_np(actual, desired, rtol=rtol, atol=atol, err_msg=err_msg, verbose=True)


def normalize_conj_pairs(eigenvalues: np.ndarray) -> np.ndarray:
"""Normalize conjugate pair ordering so positive imaginary part always comes first.

The ordering of eigenvalues within complex conjugate pairs from a Schur
decomposition is not guaranteed by LAPACK and may vary across scipy/numpy
versions. This function ensures a canonical ordering for testing purposes.
"""
result = eigenvalues.copy()
i = 0
while i < len(result) - 1:
if abs(result[i].imag) > 1e-10 and np.isclose(result[i], np.conj(result[i + 1])):
if result[i].imag < 0:
result[i], result[i + 1] = result[i + 1], result[i]
i += 2
else:
i += 1
return result


def get_known_input(Tc: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
assert not np.allclose(Tc, 0.0), "Tc doesn't seem to be a count matrix."
assert Tc.dtype == np.float64, "Expected double precision"
Expand Down
28 changes: 23 additions & 5 deletions tests/test_gpcca.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,13 @@
gpcca_coarsegrain,
_initialize_rot_matrix,
)
from tests.conftest import mu, assert_allclose, get_known_input, skip_if_no_petsc_slepc
from tests.conftest import (
mu,
assert_allclose,
get_known_input,
normalize_conj_pairs,
skip_if_no_petsc_slepc,
)
from pygpcca._sort_real_schur import sort_real_schur

eps = np.finfo(np.float64).eps * 1e10
Expand Down Expand Up @@ -845,8 +851,14 @@ def test_P_2_LM(
assert_allclose(g.crispness_values, crispness_values_P_2_LM)
assert_allclose(g.optimal_crispness, optimal_crispness_P_2_LM)
assert_allclose(n_m, n_m_P_2_LM)
assert_allclose(g.top_eigenvalues, top_eigenvalues_P_2_LM)
assert_allclose(g.dominant_eigenvalues, top_eigenvalues_P_2_LM[:n_m])
assert_allclose(
normalize_conj_pairs(g.top_eigenvalues),
normalize_conj_pairs(top_eigenvalues_P_2_LM),
)
assert_allclose(
normalize_conj_pairs(g.dominant_eigenvalues),
normalize_conj_pairs(top_eigenvalues_P_2_LM[:n_m]),
)

def test_split_warning_LM(self, P_2: np.ndarray):
g = GPCCA(P_2, eta=None, z="LM")
Expand Down Expand Up @@ -930,8 +942,14 @@ def test_P_2_LR(
assert_allclose(g.crispness_values, crispness_values_P_2_LR)
assert_allclose(g.optimal_crispness, optimal_crispness_P_2_LR)
assert_allclose(n_m, n_m_P_2_LR)
assert_allclose(g.top_eigenvalues, top_eigenvalues_P_2_LR)
assert_allclose(g.dominant_eigenvalues, top_eigenvalues_P_2_LR[:n_m])
assert_allclose(
normalize_conj_pairs(g.top_eigenvalues),
normalize_conj_pairs(top_eigenvalues_P_2_LR),
)
assert_allclose(
normalize_conj_pairs(g.dominant_eigenvalues),
normalize_conj_pairs(top_eigenvalues_P_2_LR[:n_m]),
)

def test_split_warning_LR(self, P_2: np.ndarray):
g = GPCCA(P_2, eta=None, z="LR")
Expand Down