diff --git a/brainspace/null_models/moran.py b/brainspace/null_models/moran.py index 54da5d9..9c722b9 100644 --- a/brainspace/null_models/moran.py +++ b/brainspace/null_models/moran.py @@ -99,8 +99,12 @@ def compute_mem(w, n_ring=1, spectrum='nonzero', tol=1e-10): mask_zero = ev_abs < tol n_zero = np.count_nonzero(mask_zero) - if n_zero == 0: - raise ValueError('Weight matrix has no zero eigenvalue.') + if spectrum == 'all' and n_zero == 0: + raise ValueError( + "Weight matrix has no zero eigenvalue; spectrum='all' " + "requires at least one. Use spectrum='nonzero' for matrices " + "without a kernel (e.g., surfaces with no medial wall)." + ) # Multiple zero eigenvalues if spectrum == 'all': diff --git a/brainspace/tests/test_null_models.py b/brainspace/tests/test_null_models.py index 0219d3a..078db38 100644 --- a/brainspace/tests/test_null_models.py +++ b/brainspace/tests/test_null_models.py @@ -74,6 +74,31 @@ def test_moran(): assert np.allclose(r2, msr.randomize(feats)) +def test_compute_mem_no_zero_eigvals_nonzero_branch(): + """spectrum='nonzero' must accept matrices with no zero eigenvalues (#112). + + Surfaces with no medial wall (e.g. hippocampal subfields) produce weight + matrices with no kernel; the previous unconditional ValueError blocked + that legitimate use case. + """ + rs = np.random.RandomState(0) + a = rs.randn(20, 20) + w = a @ a.T # positive definite -> no zero eigenvalues + mem, ev = compute_mem(w, spectrum='nonzero', tol=1e-10) + assert mem.shape[0] == 20 + assert ev.shape[0] == mem.shape[1] + assert np.all(np.abs(ev) > 1e-10) + + +def test_compute_mem_no_zero_eigvals_all_branch_still_raises(): + """spectrum='all' still requires a zero eigenvalue (#112).""" + rs = np.random.RandomState(1) + a = rs.randn(20, 20) + w = a @ a.T + with pytest.raises(ValueError, match="spectrum='all'"): + compute_mem(w, spectrum='all', tol=1e-10) + + def test_spin(): # Create dummy spheres or left and right hemispheres sphere_lh = wrap_vtk(vtk.vtkSphereSource, radius=20, thetaResolution=10,