From 86129d4c72cd3813cb516881c3d545d42071709e Mon Sep 17 00:00:00 2001 From: Charles Shaw Date: Mon, 29 Jun 2026 07:28:17 +0100 Subject: [PATCH] fix: define zero-column rank detection contract --- diff_diff/linalg.py | 4 +++- tests/test_linalg.py | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/diff_diff/linalg.py b/diff_diff/linalg.py index 7085e78d..38edf953 100644 --- a/diff_diff/linalg.py +++ b/diff_diff/linalg.py @@ -134,7 +134,9 @@ def _detect_rank_deficiency( pivot : ndarray of int Column permutation from QR decomposition. """ - n, k = X.shape + _, k = X.shape + if k == 0: + return 0, np.array([], dtype=int), np.array([], dtype=int) # R's qr() uses tol = 1e-07 by default (sqrt(eps) ≈ 1.49e-08); we use 1e-07. if rcond is None: diff --git a/tests/test_linalg.py b/tests/test_linalg.py index fed4e357..caa7e44f 100644 --- a/tests/test_linalg.py +++ b/tests/test_linalg.py @@ -1525,6 +1525,20 @@ def test_solve_ols_rank_zero_returns_nan_not_indexerror(self): np.testing.assert_allclose(resid, y) # fitted = 0 assert vcov.shape == (3, 3) and np.all(np.isnan(vcov)) + def test_rank_detection_zero_column_matrix_returns_empty_contract(self): + """An ``(n, 0)`` design has rank 0, no dropped columns, and no pivot.""" + from diff_diff.linalg import _detect_rank_deficiency + + X = np.empty((5, 0)) + + rank, dropped, pivot = _detect_rank_deficiency(X) + + assert rank == 0 + assert dropped.dtype == int + assert pivot.dtype == int + assert dropped.shape == (0,) + assert pivot.shape == (0,) + def test_rank_detection_scale_repair_preserves_raw_drop_selection(self): """The scale-invariance repair must NOT change which column is dropped in a genuinely collinear, well-scaled design: the dropped column equals the raw