Skip to content

Commit 28507ec

Browse files
igerberclaude
andcommitted
Assert dense fallback SEs remain usable in TwoStage sparse->dense tests
AI review on PR #319 flagged that my new TwoStage warning tests only verify that the UserWarning fires but not that the dense fallback still produces finite, usable SEs. A future control-flow regression could keep the warning while breaking the degraded path. Mirror the assertion shape used in the pre-existing ImputationDiD test_sparse_solver_dense_fallback: after the warned .fit(), assert overall_se is finite and > 0 for both the GMM sandwich and bootstrap paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 8339dcc commit 28507ec

1 file changed

Lines changed: 19 additions & 4 deletions

File tree

tests/test_two_stage.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,11 @@ def test_sparse_factorized_dense_fallback_emits_warning(self):
494494
"""Silent-failure audit axis C: when sparse factorization of Stage 1's
495495
normal-equations matrix fails and the GMM sandwich falls back to dense
496496
lstsq, a UserWarning must surface so callers know SE came from the
497-
degraded path rather than the fast sparse path."""
497+
degraded path rather than the fast sparse path.
498+
499+
Also verifies the dense fallback still yields finite, usable SEs so
500+
that a future regression in the fallback control flow cannot keep the
501+
warning while breaking the degraded path."""
498502
import unittest.mock
499503

500504
data = generate_test_data()
@@ -504,17 +508,24 @@ def test_sparse_factorized_dense_fallback_emits_warning(self):
504508
side_effect=RuntimeError("test failure"),
505509
):
506510
with pytest.warns(UserWarning, match="sparse factorization.*falling back to dense lstsq"):
507-
TwoStageDiD().fit(
511+
results = TwoStageDiD().fit(
508512
data,
509513
outcome="outcome",
510514
unit="unit",
511515
time="time",
512516
first_treat="first_treat",
513517
)
514518

519+
# Dense fallback must still produce a usable SE.
520+
assert np.isfinite(results.overall_se)
521+
assert results.overall_se > 0
522+
515523
def test_sparse_factorized_bootstrap_dense_fallback_emits_warning(self):
516524
"""Silent-failure audit axis C: the TwoStage bootstrap path has the
517-
same sparse->dense fallback and must also emit a UserWarning."""
525+
same sparse->dense fallback and must also emit a UserWarning.
526+
527+
Also verifies the bootstrap dense fallback still yields finite,
528+
usable SEs."""
518529
import unittest.mock
519530

520531
data = generate_test_data()
@@ -524,14 +535,18 @@ def test_sparse_factorized_bootstrap_dense_fallback_emits_warning(self):
524535
side_effect=RuntimeError("test failure"),
525536
):
526537
with pytest.warns(UserWarning, match="sparse factorization.*falling back to dense lstsq"):
527-
TwoStageDiD(n_bootstrap=4, seed=42).fit(
538+
results = TwoStageDiD(n_bootstrap=4, seed=42).fit(
528539
data,
529540
outcome="outcome",
530541
unit="unit",
531542
time="time",
532543
first_treat="first_treat",
533544
)
534545

546+
# Bootstrap dense fallback must still produce a usable SE.
547+
assert np.isfinite(results.overall_se)
548+
assert results.overall_se > 0
549+
535550

536551
# =============================================================================
537552
# TestTwoStageDiDEdgeCases

0 commit comments

Comments
 (0)