Skip to content

perf(utils): precompute r-independent wild-bootstrap inversion pieces (~7x, outputs unchanged)#647

Open
igerber wants to merge 1 commit into
mainfrom
perf/wild-bootstrap-tstar-chunking
Open

perf(utils): precompute r-independent wild-bootstrap inversion pieces (~7x, outputs unchanged)#647
igerber wants to merge 1 commit into
mainfrom
perf/wild-bootstrap-tstar-chunking

Conversation

@igerber

@igerber igerber commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • The WCR bootstrap DGP is linear in the candidate null r (y*(r) = A + r·B with r-independent A, B), so beta_j*(r) is affine and the per-cluster score variance is the PSD quadratic qa + r·qb + r²·qc — five precomputable (B,) vectors. The CI inversion's ~O(100) _t_star(r) evaluations, which each materialized fresh (B×n) bootstrap-outcome, (k×B) refit, and (n×B) residual arrays, now cost O(B) arithmetic each. This lands BOTH options the TODO row offered: the one-time precompute pass is chunked over draws (module byte-budget constant _WILD_PRECOMPUTE_CHUNK_BYTES, sized with a conservative ×8 live-temporary multiplier and monkeypatchable), so peak memory is bounded for large n/B, and no full (B, n) observation-level weight matrix is ever materialized.
  • Numbers: verified against a pristine origin/main worktree on a 5-seed few-cluster grid with the backend pinned — SE, p-value, and inverted CI endpoints all bit-identical; 6.8x end-to-end (0.177s → 0.026s for 5×999-draw inversions). The boottest parity suite (61 tests incl. pinned values) passes unchanged. The quadratic-form evaluation is a ~1-ULP reassociation of the per-call sum(scores²); the strict-inequality tie guard absorbs sub-1e-9 shifts by design. A new TestPrecomputeChunking forces hundreds of draw-chunks and pins chunk-count invariance.
  • Side finding recorded as a new TODO row (Medium): while validating, discovered the Rust-backend solve_ols(return_vcov=True) is run-to-run nondeterministic (~1e-14 rel; 3 distinct clustered-vcov values in 8 identical calls on Apple Silicon; pure-Python backend is bit-stable). Not introduced by this PR (it studentizes with the pre-existing analytical CR1) — tracked for investigation with a repro recipe.
  • Also parks the CS RC pscore (g,t)-dedup row with findings: the RC propensity is fit on the pooled two-period sample whose period-t blocks change with every t, so no two cells share a logit — caching would change the estimator away from DRDID::*_rc.

Methodology references (required if estimator / math changes)

  • Method name(s): Cameron-Gelbach-Miller (2008) WCR wild cluster bootstrap / Roodman et al. (2019) boottest conventions — algebraic reorganization only; restricted-residual linearity in r is the documented basis of the inversion
  • Paper / source link(s): REGISTRY.md § wild cluster bootstrap (WCR contract)
  • Any intentional deviations from the source (and why): None — outputs bit-identical on the verification grid; the reported SE remains the analytical CR1.

Validation

  • Tests added/updated: tests/test_wild_bootstrap.py::TestPrecomputeChunking (chunk-count invariance; tolerances documented against the ambient rust-vcov wobble). Full wild suite 61 passed; tests/test_utils.py, tests/test_methodology_did.py, tests/test_estimators.py pass.
  • Evidence: A/B script vs origin/main worktree (backend pinned): outputs identical, 6.8x.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Generated with Claude Code

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

Overall Assessment

✅ Looks good. I found no unmitigated P0/P1 issues.

Executive Summary

  • Affected method: WCR wild cluster bootstrap in diff_diff.utils.wild_bootstrap_se, used by DiD/TWFE.
  • The precompute is an algebraic reorganization of the registry-documented WCR inversion: restricted residuals linear in r, fixed bootstrap weights, CR1 studentization, strict tail counts.
  • NaN/zero-SE handling remains all-or-nothing for public inference and guarded for bootstrap draws.
  • Deferred Rust backend nondeterminism is tracked in TODO.md, so it is not a blocker for this PR.
  • I could not run tests in this sandbox because Python cannot import numpy.

Methodology

No findings.

Cross-check: docs/methodology/REGISTRY.md documents WCR restricted residuals linear in r, bootstrap refit under y*, CR1 studentization, strict exceedance p-values, and CI inversion. The changed code at diff_diff/utils.py:L878-L938 preserves those contracts by precomputing the affine coefficient pieces and PSD quadratic score variance. This is an implementation choice, not an undocumented methodology deviation.

Code Quality

No findings.

The chunk sizing and precompute loop are localized and keep the existing result surface unchanged: diff_diff/utils.py:L900-L926.

Performance

No findings.

The change removes repeated (B, n) materialization inside _t_star(r) and chunks the one-time pass by _WILD_PRECOMPUTE_CHUNK_BYTES: diff_diff/utils.py:L639-L642, diff_diff/utils.py:L911-L926.

Maintainability

No findings.

The explanatory comments are long but useful here because this is math-sensitive code.

Tech Debt

  • Severity: P3 informational, tracked in TODO.md
    Impact: Rust backend clustered VCV nondeterminism is documented as pre-existing follow-up work, not introduced by this PR.
    Concrete fix: No PR-blocking fix required; follow the tracked TODO investigation.
    Location: TODO.md:L31-L33

Security

No findings.

Documentation/Tests

  • Severity: P3
    Impact: CHANGELOG.md says the wild bootstrap suite has 60 tests, while this PR adds a new test and the PR body claims 61. This is only a stale validation count.
    Concrete fix: Update the count to 61 or remove the exact count.
    Location: CHANGELOG.md:L447-L448

Test coverage added for chunk-count invariance at tests/test_wild_bootstrap.py:L1485-L1517; existing brute-force/R parity tests remain in scope for WCR correctness.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jul 7, 2026
@igerber igerber force-pushed the perf/wild-bootstrap-tstar-chunking branch 2 times, most recently from 0e04a8f to d382c4e Compare July 8, 2026 00:15
…st inversion (~7x, bit-identical outputs)

The WCR bootstrap DGP is linear in the candidate null r
(y*(r) = A + r*B), so beta_j*(r) is affine and the per-cluster score
variance is the PSD quadratic qa + r*qb + r^2*qc — five precomputable
(B,) vectors. The CI inversion's ~O(100) _t_star(r) evaluations (each
previously materializing fresh (B,n)/(k,B)/(n,B) arrays) become O(B)
arithmetic; the one-time precompute pass is chunked over draws (~256MB
cap) so peak memory is bounded for large n/B — resolving both options
the TODO row offered at once.

Verified vs origin/main on a 5-seed few-cluster grid (backend pinned):
SE, p-value, and inverted CI endpoints all bit-identical; 6.8x
end-to-end. boottest parity suite (60 tests, pinned values) unchanged.

Also parks the CS RC pscore (g,t)-dedup TODO row with findings: the RC
propensity is fit on the pooled two-period sample whose period-t blocks
change with every t, so no two cells share a logit — caching would
change the estimator away from DRDID::*_rc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 8027a33dac022236e0616a0280808e0559530c3b


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: WCR wild cluster bootstrap in diff_diff.utils.wild_bootstrap_se, used by DifferenceInDifferences and inherited by TwoWayFixedEffects.
  • The precompute preserves the registry contract: restricted residuals linear in r, fixed cluster weights during inversion, full-design WCR statistic, CR1 studentization, strict tail counts, and CI inversion.
  • The new chunking is an algebraic/numerical implementation choice, not an undocumented methodology deviation.
  • Prior P3 changelog count issue is still present.
  • I could not run tests in this sandbox: pytest and numpy are unavailable.

Methodology

No findings.

Cross-check: docs/methodology/REGISTRY.md:L94-L146 documents WCR as imposing the null via restricted residuals M_-j y - r M_-j x_j, refitting on y*, using bootstrap CR1 se*, strict exceedance p-values, and CI inversion. The new precompute at diff_diff/utils.py:L878-L938 preserves those objects by caching the affine coefficient and cluster-score pieces and evaluating the same PSD quadratic score variance. This is a valid algebraic reorganization, not a methodology mismatch.

Code Quality

No findings.

The chunk-budget constant and chunk loop are localized at diff_diff/utils.py:L639-L642 and diff_diff/utils.py:L900-L926.

Performance

No findings.

The PR removes repeated observation-level (B, n) materialization inside _t_star(r) and builds only per-chunk (Bc, n) arrays during precompute: diff_diff/utils.py:L911-L926.

Maintainability

No findings.

The explanatory comments are long, but appropriate for math-sensitive inference code.

Tech Debt

  • Severity: P3 informational, tracked in TODO.md
    Impact: Rust backend clustered VCV nondeterminism is documented as pre-existing follow-up work, not introduced by this PR.
    Concrete fix: No PR-blocking fix required; follow the tracked investigation.
    Location: TODO.md:L31-L33

Security

No findings.

Documentation/Tests

  • Severity: P3
    Impact: CHANGELOG.md still says the wild bootstrap parity suite has 60 tests, while this PR adds TestPrecomputeChunking. This is stale validation prose only.
    Concrete fix: Update the count or remove the exact count.
    Location: CHANGELOG.md:L446-L448

  • Severity: P3
    Impact: The new test docstring/name say “bit-for-bit,” but the assertions use tolerances for se and CI endpoints. The intended reason is explained in comments, so this is only wording drift.
    Concrete fix: Rename the test/docstring to “chunk-count invariant” or make the exactness claim only for p_value.
    Location: tests/test_wild_bootstrap.py:L1485-L1517

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-ci Triggers CI test workflows

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant