@@ -253,6 +253,90 @@ def test_cluster_robust_vcov(self):
253253 assert vcov .shape == (k , k )
254254 assert np .all (np .diag (vcov ) > 0 )
255255
256+ # =========================================================================
257+ # LU Fallback Tests (for near-singular matrices)
258+ # =========================================================================
259+
260+ def test_near_singular_matrix_lu_fallback (self ):
261+ """Test that near-singular matrices trigger LU fallback and produce valid results.
262+
263+ When X'X is near-singular (not positive definite), Cholesky factorization
264+ fails and the Rust backend should fall back to LU decomposition.
265+ This test verifies:
266+ 1. No crash or exception is raised
267+ 2. Coefficients are finite
268+ 3. Results match NumPy implementation
269+ """
270+ from diff_diff ._rust_backend import solve_ols
271+ from scipy .linalg import lstsq
272+
273+ np .random .seed (42 )
274+ n = 100
275+
276+ # Create near-collinear design matrix (high condition number)
277+ # Column 3 is almost a linear combination of columns 1 and 2
278+ X = np .random .randn (n , 3 )
279+ X [:, 2 ] = X [:, 0 ] + X [:, 1 ] + np .random .randn (n ) * 1e-8
280+
281+ y = X [:, 0 ] + np .random .randn (n ) * 0.1
282+
283+ # Rust backend should handle this gracefully via LU fallback
284+ coeffs , residuals , vcov = solve_ols (X , y , None , True )
285+
286+ # Verify results are finite
287+ assert np .all (np .isfinite (coeffs )), "Coefficients should be finite"
288+ assert np .all (np .isfinite (residuals )), "Residuals should be finite"
289+
290+ # Verify residuals are correct given coefficients
291+ expected_residuals = y - X @ coeffs
292+ np .testing .assert_array_almost_equal (
293+ residuals , expected_residuals , decimal = 8 ,
294+ err_msg = "Residuals should match y - X @ coeffs"
295+ )
296+
297+ def test_high_condition_number_matrix (self ):
298+ """Test OLS with high condition number matrix uses LU fallback correctly."""
299+ from diff_diff ._rust_backend import solve_ols
300+
301+ np .random .seed (123 )
302+ n = 100
303+
304+ # Create matrix with high condition number via scaling
305+ X = np .random .randn (n , 4 )
306+ X [:, 0 ] *= 1e6 # Scale first column to create high condition number
307+ X [:, 3 ] *= 1e-6 # Scale last column very small
308+
309+ y = np .random .randn (n )
310+
311+ # Should not raise and should produce finite results
312+ coeffs , residuals , vcov = solve_ols (X , y , None , True )
313+
314+ assert np .all (np .isfinite (coeffs )), "Coefficients should be finite"
315+ assert np .all (np .isfinite (residuals )), "Residuals should be finite"
316+ assert vcov is not None , "VCoV should be returned"
317+
318+ def test_near_singular_with_clusters (self ):
319+ """Test near-singular matrix with cluster-robust SEs uses LU fallback."""
320+ from diff_diff ._rust_backend import solve_ols
321+
322+ np .random .seed (42 )
323+ n = 100
324+ n_clusters = 10
325+
326+ # Near-collinear design
327+ X = np .random .randn (n , 3 )
328+ X [:, 2 ] = X [:, 0 ] + X [:, 1 ] + np .random .randn (n ) * 1e-8
329+
330+ y = X [:, 0 ] + np .random .randn (n ) * 0.1
331+ cluster_ids = np .repeat (np .arange (n_clusters ), n // n_clusters ).astype (np .int64 )
332+
333+ # Should handle gracefully with cluster SEs
334+ coeffs , residuals , vcov = solve_ols (X , y , cluster_ids , True )
335+
336+ assert np .all (np .isfinite (coeffs )), "Coefficients should be finite"
337+ assert np .all (np .isfinite (residuals )), "Residuals should be finite"
338+ assert vcov .shape == (3 , 3 ), "VCoV should have correct shape"
339+
256340
257341@pytest .mark .skipif (not HAS_RUST_BACKEND , reason = "Rust backend not available" )
258342class TestRustVsNumpy :
0 commit comments