From 132bfa7cf27e527eb8398db8941a2e633363d618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlson=20B=C3=BCth?= Date: Mon, 24 Aug 2026 18:26:42 +0200 Subject: [PATCH] fix: return real-valued high BC anisotropy on NumPy >= 2 NumPy >= 2 returns complex128 eigenvalues from np.linalg.eigvals even for real symmetric covariance matrices. This leaked complex scalars into the metrics, crashing ruamel.yaml key figure dumps with a RepresenterError and yielding inf+nanj instead of inf for degenerate covariances. Use eigvalsh (always real for symmetric input) and clip numerical-noise negative eigenvalues; additionally make _make_yaml_compatible collapse zero-imaginary complex scalars to float as a fallback. --- superblockify/metrics/measures.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/superblockify/metrics/measures.py b/superblockify/metrics/measures.py index 5c32d37..5e5ebd3 100644 --- a/superblockify/metrics/measures.py +++ b/superblockify/metrics/measures.py @@ -755,12 +755,14 @@ def __calculate_high_bc_anisotropy(coord_high_bc): ) # Covariance matrix cov = np.cov(coord_high_bc.T) - # Eigenvalues - eigvals = np.linalg.eigvals(cov) - # Sort eigenvalues - eigvals = np.sort(eigvals)[::-1] - # Anisotropy - return eigvals[0] / eigvals[1] + # Eigenvalues - the covariance matrix is symmetric, so use eigvalsh, which + # always returns real eigenvalues in ascending order (eigvals returns them as + # complex dtype on NumPy >= 2) + eigvals = np.linalg.eigvalsh(cov) + # Anisotropy - ratio of largest to smallest eigenvalue, infinite if degenerate; + # clip numerical-noise negative eigenvalues to zero + with np.errstate(divide="ignore", invalid="ignore"): + return float(eigvals[-1] / max(eigvals[0], 0.0)) def add_ltn_means(components, edge_attr):