Skip to content

perf(linalg): scores-based CR2 Bell-McCaffrey DOF (algebraic identity; PT2018 §3.1 Satterthwaite)#656

Merged
igerber merged 10 commits into
mainfrom
perf/cr2-bm-scores-dof
Jul 9, 2026
Merged

perf(linalg): scores-based CR2 Bell-McCaffrey DOF (algebraic identity; PT2018 §3.1 Satterthwaite)#656
igerber merged 10 commits into
mainfrom
perf/cr2-bm-scores-dof

Conversation

@igerber

@igerber igerber commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Closes the CR2-BM Heavy TODO row: the unweighted per-contrast Satterthwaite DOF materialized the dense n×n residual-maker M = I − X(X'X)⁻¹X' and contracted it over all cluster pairs — O(n²) time per contrast and O(n²) memory (3.2 GB at n=20k). Per Pustejovsky-Tipton (2018) Appendix B, with Ω the (n, G) matrix stacking ω_g on disjoint cluster supports, the pairwise matrix collapses to B = Ω'MΩ = diag(‖ω_g‖²) − P'M_U P with P = X'ΩO(nk + G²k) per contrast, no n×n allocation.
  • Measured: ~32x at n=5k/G=50 (0.57s → 0.018s); n=20k/G=100 completes in 0.12s where the old path would allocate 3.2 GB. Algebraically identical: a frozen pair-loop oracle (the previous implementation reimplemented verbatim in-test) agrees at rtol 1e-10 across balanced, unbalanced (cluster sizes 3–60), and compound-contrast designs. Both NaN-reliability guards (noise floor via max|B|, cluster-count bound DOF ≤ G) operate unchanged on the same B values.
  • The weighted clubSandwich P-array path is untouched (it never built the n×n M). All 432 consumer tests (linalg, vcov-type incl. the CI-inversion dof_hc2_bm pins, stacked-DiD methodology, estimators) pass unmodified. REGISTRY: per-fit setup wording updated + an Appendix-B evaluation sentence on the existing unweighted-DOF-guard Note.

Methodology references (required if estimator / math changes)

  • Method name(s): CR2 Bell-McCaffrey Satterthwaite DOF (Pustejovsky & Tipton 2018, Section 4 / Appendix A formula; Appendix B scores-based evaluation) — evaluation change only, same estimand
  • Paper / source link(s): REGISTRY.md DiD/MPD hc2_bm Notes (updated); PT2018 J. Bus. Econ. Statist.
  • Any intentional deviations from the source (and why): None — algebraically identical evaluation, oracle-locked.

Validation

  • Tests added/updated: tests/test_linalg.py::TestCR2BMScoresBasedDOF (3 frozen-oracle parity tests).
  • Backtest / simulation / notebook evidence (if applicable): timing table above (in-session measurements).

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

Generated with Claude Code

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Method affected: unweighted CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py.
  • The scores-based B = Omega' M Omega expansion is algebraically consistent with the previous residual-maker contraction and with the project’s REGISTRY note.
  • Existing NaN/noise-floor and cluster-count guards remain in place.
  • P2 performance issue: the implementation materializes P_all for all contrasts, so batched per-coefficient DOF can allocate O(G*k*m) memory, contradicting the stated O(G*k) memory bound.
  • I could not run tests: numpy, scipy, and pytest are not installed in this environment.

Methodology

No blocking findings.

Severity: P3 informational
Impact: This is an evaluation rewrite, not a methodology change. The affected method is CR2 Bell-McCaffrey Satterthwaite DOF. PT2018 covers BRL/CRVE with Satterthwaite degrees of freedom, and clubSandwich’s Satterthwaite_df reduces to (trace P)^2 / sum(P^2), matching the registry/code contract. (arxiv.org)
Concrete fix: No required change.

Code Quality

No findings.

Performance

Severity: P2
Impact: diff_diff/linalg.py:L2151-L2168 allocates P_all = np.zeros((G, k, m)). For _compute_cr2_bm(..., contrasts=np.eye(k)), m == k, so this becomes O(G*k^2) memory. That can be worse than intended on full-dummy/absorbed-FE-style designs with many clusters and coefficients, and it contradicts the O(G*k) memory claim in diff_diff/linalg.py:L2124-L2126, CHANGELOG.md:L453-L456, and docs/methodology/REGISTRY.md:L246.
Concrete fix: Stream or chunk contrasts. Inside the for j in range(m) loop, build P_j from each cluster’s omega_all[g][:, j] instead of storing all P_all; alternatively chunk m so peak memory is bounded. Update the documented memory bound to include the G x G B_j matrix if it remains materialized.

Maintainability

No blocking findings.

Severity: P3
Impact: diff_diff/linalg.py:L1856-L1858 still says the shared core precomputes the unweighted residual-maker M, but this PR removes that allocation.
Concrete fix: Remove the stale M reference from the docstring.

Tech Debt

No new untracked correctness debt found. The old TODO row for CR2-BM dense residual-maker allocation is reasonably removed, subject to the P2 memory follow-up above.

Security

No findings.

Documentation/Tests

Severity: P3
Impact: The added oracle tests cover balanced, unbalanced, and compound-contrast parity, but I could not execute them locally because test dependencies are unavailable.
Concrete fix: Run tests/test_linalg.py::TestCR2BMScoresBasedDOF and the existing CR2-BM NaN-guard tests in CI.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jul 8, 2026
@igerber igerber force-pushed the perf/cr2-bm-scores-dof branch 2 times, most recently from 7bc8d10 to 47d22dd Compare July 8, 2026 11:20
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 47d22ddf5dd39948e8843f0e460e4ef6286ff9b6


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py.
  • The prior P2 P_all = O(G*k*m) memory issue is addressed by contrast chunking at diff_diff/linalg.py:L2236-L2245.
  • The prior stale docstring reference to the dense residual-maker is fixed at diff_diff/linalg.py:L1919-L1921.
  • No methodology or inference correctness blockers found.
  • Tests could not be run here: numpy and pytest are not installed.

Methodology

Severity: P3
Impact: The implementation remains an algebraically equivalent evaluation of the existing unweighted CR2-BM Satterthwaite DOF. The paper’s Satterthwaite t-test DOF is the model-assisted moment ratio in §3.1, and the code preserves that contract via the same (tr B)^2 / tr(B^2) structure while replacing the explicit residual-maker contraction with Omega' M Omega. (arxiv.org)
Concrete fix: No required change.

Severity: P2
Impact: The new code/docs repeatedly cite “PT2018 Appendix B” for the scores-based evaluation, but the accessible arXiv text labels Appendix B as simulation-study details; the Satterthwaite DOF formula is in §3.1 and the relevant residual-projection efficiency discussion is in §2.2. This is a source-attribution issue, not a numerical defect. docs/methodology/REGISTRY.md:L246, diff_diff/linalg.py:L2187-L2203. (arxiv.org)
Concrete fix: Reword to cite PT2018 §3.1 / Eq. 13 plus the algebraic identity Omega' M Omega = diag(||omega_g||^2) - P' M_U P, rather than Appendix B.

Code Quality

No findings.

Performance

Severity: P3
Impact: The prior review’s main performance concern is resolved: P_all is now chunked under _CR2_BM_CONTRAST_CHUNK_BYTES, with a regression test forcing one-contrast chunks at tests/test_linalg.py:L2810-L2828.
Concrete fix: No required change.

Maintainability

No findings. The stale shared-core docstring was corrected.

Tech Debt

No new untracked correctness debt found. Removing the old CR2-BM dense residual-maker TODO is reasonable given the current implementation.

Security

No findings.

Documentation/Tests

Severity: P3
Impact: Added parity coverage is well targeted: balanced, unbalanced, compound contrast, and chunking bit-identity cases are covered in tests/test_linalg.py:L2719-L2828. I could not execute them locally because numpy and pytest are missing.
Concrete fix: Run tests/test_linalg.py::TestCR2BMScoresBasedDOF and existing CR2-BM CI/NaN guard tests in CI.

@igerber igerber removed the ready-for-ci Triggers CI test workflows label Jul 9, 2026
@igerber igerber changed the title perf(linalg): scores-based CR2 Bell-McCaffrey DOF (PT2018 Appendix B) perf(linalg): scores-based CR2 Bell-McCaffrey DOF (algebraic identity; PT2018 §3.1 Satterthwaite) Jul 9, 2026
@igerber igerber force-pushed the perf/cr2-bm-scores-dof branch from 47d22dd to 576d070 Compare July 9, 2026 10:26
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 576d0707b6549e0b7f6cbfa67128b5bd648243d9


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py.
  • The dense n x n residual-maker path is replaced by an algebraically equivalent Omega' M Omega evaluation; I found no methodology or inference correctness mismatch.
  • The prior memory concern is addressed by contrast chunking at diff_diff/linalg.py:L2233-L2247, with a forced multi-chunk regression at tests/test_linalg.py:L2811-L2829.
  • The prior stale shared-core docstring reference to dense M is fixed at diff_diff/linalg.py:L1919-L1921.
  • I could not run tests locally because pytest is not installed.

Methodology

Finding: P3
Impact: The implementation still follows the documented scalar contrast Satterthwaite DOF ratio and keeps the existing REGISTRY-documented NaN/cluster-bound guards at diff_diff/linalg.py:L2258-L2332. No P0/P1 methodology issue found. The remaining wording “PT2018 §3.1 / Eq. 13 Satterthwaite DOF” in CHANGELOG.md:L559, diff_diff/linalg.py:L2187-L2189, and docs/methodology/REGISTRY.md:L246 may be slightly brittle because the source distinguishes Satterthwaite t-test DOF from a generalized multiple-parameter test. (arxiv.org)
Concrete fix: Optional: cite “PT2018 §3.1 Satterthwaite t-test / scalar contrast DOF” without an equation number, or add the version-specific equation reference.

Code Quality

No findings.

Performance

No findings. The prior P_all = O(G*k*m) concern is mitigated by _CR2_BM_CONTRAST_CHUNK_BYTES and chunk-local allocation at diff_diff/linalg.py:L2152-L2159 and diff_diff/linalg.py:L2233-L2247.

Maintainability

No findings.

Tech Debt

No findings. Removing the old CR2-BM dense residual-maker TODO row is appropriate given the new implementation and tests.

Security

No findings.

Documentation/Tests

Finding: P3
Impact: Added parity tests cover balanced, unbalanced, compound contrast, and chunk-count invariance cases at tests/test_linalg.py:L2719-L2829. I could not execute them in this environment because pytest is missing.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM guard/parity suites.

@igerber igerber added ready-for-ci Triggers CI test workflows and removed ready-for-ci Triggers CI test workflows labels Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: d5d0ac023d3f4c661c8d2d03ddae3eb50eeeb053


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner, reached through _compute_cr2_bm and _compute_cr2_bm_contrast_dof.
  • The new scores-based B = diag(||omega_g||^2) - P M_U P' construction at diff_diff/linalg.py:L2248-L2263 is algebraically equivalent to the old dense Omega' M Omega pair loop for disjoint cluster supports.
  • Existing NaN/noise-floor and DOF <= G guards remain applied after the new B_j construction at diff_diff/linalg.py:L2265-L2326; no inline inference anti-pattern was added.
  • The prior memory concern is addressed by contrast chunking at diff_diff/linalg.py:L2239-L2245, with regression coverage in tests/test_linalg.py:L2811-L2833.
  • I could not execute tests locally: numpy and pytest are not installed in this sandbox.

Methodology

Finding: P3 informational
Impact: The methodology deviation/guard surface is documented in docs/methodology/REGISTRY.md:L246 under a **Note (...)** label, including the new algebraic evaluation and chunking caveat. Under the review rules, this is not a defect. The implementation preserves the same CR2-BM DOF formula and only changes evaluation order.
Concrete fix: No required change. Optional: make the citation wording “scalar Satterthwaite t-test / one-row HTZ” where the PR currently says “PT2018 §3.1 / Eq. 13” in CHANGELOG.md:L559 and diff_diff/linalg.py:L2191-L2192.

Code Quality

No findings.

Performance

No findings. The dense n x n residual-maker allocation was removed from the affected unweighted CR2-BM path, and the new (G, k, chunk) buffer is capped by _CR2_BM_CONTRAST_CHUNK_BYTES.

Maintainability

No findings.

Tech Debt

No findings. Removing the CR2-BM dense residual-maker TODO row is appropriate for the affected _compute_cr2_bm path.

Security

No findings. I did not see secret-like material introduced in the changed files.

Documentation/Tests

Finding: P3 informational
Impact: Some surrounding comments still say the unweighted CR2-BM path is “bit-equal to prior” even though the new docs/tests correctly describe allclose-level parity and possible ~1 ULP BLAS-width drift. See diff_diff/linalg.py:L2066-L2069 and diff_diff/linalg.py:L2553-L2556.
Concrete fix: Optional: replace “bit-equal” with “algebraically identical / parity within floating-point tolerance” in those comments/docstrings.

Finding: P3 informational
Impact: Targeted local verification could not be run here because pytest is missing and importing numpy fails.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM parity/guard suites.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 06e7935fe6aec18a0e8b95edfbfbdd70e253c0c9


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner, reached through _compute_cr2_bm and _compute_cr2_bm_contrast_dof.
  • Methodology check: the new B = Ω'MΩ = diag(||ω_g||²) - P' M_U P path is an algebraic evaluation change, not a changed estimand; this matches the registry’s new documented **Note (...)** entry at docs/methodology/REGISTRY.md:L246.
  • Prior review’s documentation comments were addressed at diff_diff/linalg.py:L2066-L2070 and diff_diff/linalg.py:L2555-L2560.
  • NaN/noise-floor and DOF <= G guards remain after the new B_j construction at diff_diff/linalg.py:L2263-L2328.
  • I found one P2 performance/documentation issue: the code still materializes dense G x G B_j matrices, so the documented memory bound is too optimistic for many-cluster designs.
  • I could not run tests locally: numpy is missing and pytest is not installed.

Methodology

No P0/P1 findings.

Severity: P3 informational
Impact: The changed path is documented as an evaluation change for the existing CR2-BM Satterthwaite DOF, and the registry labels the deviation/guard surface with **Note (...)**, so it is not a methodology defect under the review rules. The cited PT paper describes BRL/CRVE small-sample inference using Satterthwaite t-tests and a multi-parameter extension, consistent with the method family being reviewed. (arxiv.org)
Concrete fix: No required change.

Code Quality

No findings.

Performance

Severity: P2
Impact: The dense residual-maker M is removed, but each contrast still builds B_j as a dense (G, G) matrix at diff_diff/linalg.py:L2260-L2264. That means the implementation has O(G²) memory per contrast, while the docstring/CHANGELOG/registry imply O(G k) memory or “no n x n allocation” without calling out the G x G matrix (diff_diff/linalg.py:L2204-L2208, CHANGELOG.md:L563-L567, docs/methodology/REGISTRY.md:L246). This is harmless for the stated G=50/100 benchmark but can still OOM for many-cluster or singleton-cluster CR2 calls.
Concrete fix: Either compute trace_B2 and max_abs_B without materializing all of B_j (row-chunk B_j, or use Gram identities plus a bounded max pass), or update the documentation to state the actual O(G²) memory bound.

Maintainability

No findings.

Tech Debt

No findings. Removing the completed CR2-BM dense residual-maker TODO row is appropriate for the scoped clustered path.

Security

No findings. I saw no secret-like material introduced in the changed files.

Documentation/Tests

Severity: P3 informational
Impact: One older registry sentence still says “Unweighted CR2-BM behavior is bit-equal to prior” at docs/methodology/REGISTRY.md:L3401, while the new implementation and tests correctly describe floating-point-tolerance parity.
Concrete fix: Optional: change that sentence to “algebraically identical / parity within floating-point tolerance.”

Severity: P3 informational
Impact: Local verification was not possible in this sandbox: importing numpy fails and pytest is unavailable.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus existing CR2-BM parity/guard suites.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 1bfc28317e88fa9e5ba65a7dc03f30ced0dee0a1


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner, reached via _compute_cr2_bm and _compute_cr2_bm_contrast_dof.
  • Methodology: the PR changes evaluation of the same (tr B)^2 / tr(B^2) DOF, not the estimand. The registry documents this as an evaluation note, and the weighted clubSandwich P-array path is untouched.
  • Prior P2 finding on dense (G, G) B_j materialization is addressed by row-chunking at diff_diff/linalg.py:L2264-L2279.
  • Prior P3 registry wording about “bit-equal” was addressed at docs/methodology/REGISTRY.md:L3401.
  • Remaining findings are P2/P3 only: the memory-cap prose still overstates what is actually bounded, and there are a couple of stale documentation/test-verification notes.
  • Local tests could not run: pytest is not installed, and importing numpy fails.

Methodology

No P0/P1 findings.

Severity: P3 informational
Impact: The changed unweighted clustered CR2-BM path preserves the documented scalar Satterthwaite DOF formula and only changes how B = Ω'MΩ is evaluated. This is consistent with the registry note at docs/methodology/REGISTRY.md:L246 and the cited PT2018 small-sample CRVE/Satterthwaite method family. citeturn3search0
Concrete fix: No required change.

Code Quality

No findings.

Performance

Severity: P2
Impact: The (G, G) pairwise matrix is now correctly row-chunked, but the “memory bounded by a 64 MB cap” wording is still too broad. Q = X_bi @ contrasts, A_g_Xbi, and omega_all are still full-width arrays at diff_diff/linalg.py:L2229-L2235, scaling as O(nk) / O(nm) outside _CR2_BM_CONTRAST_CHUNK_BYTES. The cap currently bounds P_all and B_rows, not total peak memory.
Concrete fix: Either compute Q and omega_all inside the contrast chunk, or narrow the docs in diff_diff/linalg.py:L2204-L2209, CHANGELOG.md:L563-L568, and docs/methodology/REGISTRY.md:L246 to state the actual bounded and unbounded arrays.

Maintainability

No findings.

Tech Debt

Severity: P3 informational
Impact: _compute_bm_dof_from_contrasts still says large unweighted one-way BM DOF should switch to a scores-based formulation “tracked in TODO.md” at diff_diff/linalg.py:L2620-L2623, but this PR removes the CR2-BM TODO row.
Concrete fix: Add a specific TODO row for the remaining one-way dense M path, or remove/update the stale “tracked in TODO.md” sentence.

Security

No findings. Secret-pattern scan of the changed files found no credential-like additions.

Documentation/Tests

Severity: P3 informational
Impact: _compute_cr2_bm still says the unweighted special case is “Bit-equal to the prior unweighted behavior” at diff_diff/linalg.py:L2111-L2114, while the changed path and new tests correctly describe floating-point-tolerance parity.
Concrete fix: Change that sentence to “algebraically identical / parity within floating-point tolerance.”

Severity: P3 informational
Impact: I could not run the focused test class in this sandbox: pytest is unavailable and numpy is not installed.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology/parity suites.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 112c9b5790e46ba4bf3e1a29ba0de0226ee3448d


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner.
  • Methodology: the PR changes evaluation of the same DOF algebra, not the estimand; the deviation/evaluation note is documented in docs/methodology/REGISTRY.md.
  • Prior P2 row-chunking concern is substantively addressed at diff_diff/linalg.py:L2266-L2286.
  • Prior stale “bit-equal” and TODO wording issues are addressed in diff_diff/linalg.py:L2111-L2116, docs/methodology/REGISTRY.md:L3401, and TODO.md:L50.
  • Remaining issues are P2/P3 only: the “64 MB cap” language is still a bit stronger than the implementation, and I could not run tests in this sandbox.

Methodology

No P0/P1 findings.

Severity: P3 informational
Impact: The changed unweighted CR2-BM path preserves the documented Bell-McCaffrey/Pustejovsky-Tipton Satterthwaite DOF calculation and replaces only the dense residual-maker contraction with the algebraic B = Ω'MΩ = diag(||ω_g||²) - P'M_U P evaluation. This is consistent with the registry’s documented evaluation note at docs/methodology/REGISTRY.md:L246; the cited paper frames BRL/CRVE inference around Satterthwaite df for t-tests and a multi-parameter generalization. (arxiv.org)
Concrete fix: No required change.

Code Quality

No findings.

Severity: P3 informational
Impact: Call propagation for the changed private signature is consistent: _compute_cr2_bm_vcov_and_dof now calls _cr2_bm_dof_inner without the removed dense M argument at diff_diff/linalg.py:L2071-L2072, and the public wrappers still route through the shared core at diff_diff/linalg.py:L2143-L2150 and diff_diff/linalg.py:L2598-L2605.
Concrete fix: No required change.

Performance

Severity: P2
Impact: The large full-width arrays from the prior review are gone, and (G, G) B is row-chunked. However, the hard “working buffers bounded by a 64 MB cap” wording is still not strictly true. chunk is computed only from G * k at diff_diff/linalg.py:L2254, so Q_chunk can exceed the cap when n dominates (diff_diff/linalg.py:L2259), and P_all/PB are at least one contrast wide even when G * k * 8 already exceeds the cap (diff_diff/linalg.py:L2261-L2265, diff_diff/linalg.py:L2276-L2283). The implementation is still a major improvement, but the docs overpromise the memory bound at diff_diff/linalg.py:L2206-L2213, CHANGELOG.md:L564-L568, and docs/methodology/REGISTRY.md:L246.
Concrete fix: Either include n and max cluster size in the contrast-chunk denominator, plus a fallback for G*k*8 > cap, or reword the docs to say the cap bounds chunked product/row blocks subject to a one-contrast lower bound and that additional O(n*width) / O(G*k) per-contrast buffers remain.

Maintainability

No findings.

Severity: P3 informational
Impact: The remaining dense one-way unweighted Bell-McCaffrey path is now explicitly tracked in TODO.md:L50, so the prior stale deferred-work note is resolved.
Concrete fix: No required change for this PR.

Tech Debt

No untracked blocking tech debt found.

Severity: P3 informational
Impact: The one-way dense M path remains deferred, but it is now tracked in TODO.md:L50; per review rules, this is not a blocker.
Concrete fix: No required change.

Security

No findings.

Severity: P3 informational
Impact: Secret-pattern scan of the changed files did not find credential-like additions.
Concrete fix: No required change.

Documentation/Tests

Severity: P3 informational
Impact: Added tests cover balanced, unbalanced, compound-contrast, and forced chunking paths in tests/test_linalg.py:L2719-L2836. I could not run them locally because pytest is unavailable and importing numpy fails in this sandbox.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology/parity suites.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: b9e92afd8c19ab90271cfc017c079ce361bdf10d


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner.
  • The dense n×n residual-maker contraction was replaced with an algebraically equivalent scores-based B = diag(||omega_g||²) - P' M_U P evaluation.
  • Prior P2 memory-cap concern appears addressed: contrast chunking now counts n, max cluster size, G*k, and G, and the (G,G) matrix is row-chunked.
  • Weighted CR2-BM P-array logic is unchanged.
  • Only finding is P3 documentation: one registry sentence cites the scalar Satterthwaite DOF as Eq. 13 instead of Eq. 11.
  • I could not run tests locally because pytest and numpy are unavailable in this sandbox.

Methodology

Severity: P3
Impact: The new registry note says the Satterthwaite DOF is “PT2018 §3.1 / Eq. 13” at docs/methodology/REGISTRY.md:L246. PT2018’s scalar t-test Satterthwaite degrees of freedom are Eq. 11 in §3.1; Eq. 13 is the multi-parameter AHT degrees-of-freedom expression, which the paper states reduces to Eq. 11 when q = 1. This is a reference/wording issue, not an estimator defect: the code’s trace_B**2 / trace_B2 implementation at diff_diff/linalg.py:L2284-L2298 matches the scalar formula. (arxiv.org)
Concrete fix: Reword docs/methodology/REGISTRY.md:L246 to “PT2018 §3.1 Eq. 11; equivalently the q=1 reduction of the AHT Eq. 13.”

Code Quality

No findings.

Severity: P3 informational
Impact: Signature propagation is consistent: _compute_cr2_bm_vcov_and_dof calls _cr2_bm_dof_inner with the new argument list at diff_diff/linalg.py:L2066-L2073, and wrapper paths continue to route through the shared core at diff_diff/linalg.py:L2143-L2150 and diff_diff/linalg.py:L2608-L2616.
Concrete fix: No action required.

Performance

No findings.

Severity: P3 informational
Impact: The prior memory-bound wording concern is substantively addressed. The new implementation chunks contrast-width buffers using n + n_g_max + G*k + G at diff_diff/linalg.py:L2252-L2264, avoids full-width Q/omega_all, and row-chunks the (G,G) pairwise matrix at diff_diff/linalg.py:L2276-L2296.
Concrete fix: No action required.

Maintainability

No findings.

Severity: P3 informational
Impact: The remaining dense one-way unweighted Bell-McCaffrey path is explicitly tracked in TODO.md:L50, so the deferred-work accounting is clear.
Concrete fix: No action required.

Tech Debt

No untracked blocking tech debt found.

Severity: P3 informational
Impact: The PR removes the clustered CR2-BM heavy TODO row and replaces the remaining one-way dense path with a scoped TODO entry at TODO.md:L50. Per review rules, this is acceptable deferred work.
Concrete fix: No action required.

Security

No findings.

Severity: P3 informational
Impact: Secret-pattern scan of the changed files found no credential-like additions.
Concrete fix: No action required.

Documentation/Tests

Severity: P3 informational
Impact: Added tests cover balanced, unbalanced, compound-contrast, and forced chunk/row-chunk paths at tests/test_linalg.py:L2719-L2836. I could not execute them because pytest is not installed and importing numpy fails in this sandbox.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM parity/methodology suites.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 438934891f0f89d55578d693e19bf150b7bc0fca


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py::_cr2_bm_dof_inner.
  • The rewrite preserves the documented scalar (tr B)^2 / tr(B^2) DOF formula while replacing dense residual-maker contraction with the algebraic B = diag(||omega_g||^2) - P' M_U P evaluation.
  • Prior re-review finding is resolved: the registry now cites PT2018 §3.1 Eq. 11 / q=1 AHT Eq. 13 wording at docs/methodology/REGISTRY.md:L246.
  • Weighted CR2-BM logic remains routed through _cr2_bm_dof_inner_weighted; no weighted methodology surface changed.
  • I could not run tests locally because pytest and numpy are unavailable in this sandbox.

Methodology

No findings.

Severity: P3 informational
Impact: The changed unweighted DOF path matches the documented Registry note and in-code algebra: diff_diff/linalg.py:L2186-L2223 states the original pair-loop formula and the scores-based identity, and diff_diff/linalg.py:L2284-L2298 computes the same trace numerator and Frobenius denominator.
Concrete fix: No action required.

Code Quality

No findings.

Severity: P3 informational
Impact: Signature propagation is complete for the private helper: the only call site is updated at diff_diff/linalg.py:L2071-L2072, and wrapper methods still delegate through _compute_cr2_bm_vcov_and_dof at diff_diff/linalg.py:L2143-L2150 and diff_diff/linalg.py:L2608-L2615.
Concrete fix: No action required.

Performance

No findings.

Severity: P3 informational
Impact: The prior memory concern is addressed: contrast chunking counts n, max cluster size, G*k, and G buffers at diff_diff/linalg.py:L2252-L2264, and the (G, G) pairwise matrix is row-chunked at diff_diff/linalg.py:L2276-L2296.
Concrete fix: No action required.

Maintainability

No findings.

Severity: P3 informational
Impact: The remaining dense one-way unweighted BM DOF path is explicitly tracked in TODO.md:L50, so this deferrable performance limitation is documented.
Concrete fix: No action required.

Tech Debt

No untracked blocking tech debt found.

Severity: P3 informational
Impact: The PR removes the old clustered CR2-BM heavy TODO row and replaces it with the narrower one-way dense-path TODO at TODO.md:L50. Per review rules, that is acceptable deferred work.
Concrete fix: No action required.

Security

No findings.

Severity: P3 informational
Impact: Secret-pattern scan of the changed files found no credential-like additions.
Concrete fix: No action required.

Documentation/Tests

No blocking findings.

Severity: P3 informational
Impact: Added tests cover pair-loop oracle parity, compound contrasts, unbalanced clusters, and forced contrast/row chunking at tests/test_linalg.py:L2719-L2836. Local execution was not possible because pytest is not installed and importing numpy fails.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology/parity suites.

@igerber igerber force-pushed the perf/cr2-bm-scores-dof branch from 4389348 to be4e5d2 Compare July 9, 2026 15:03
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: be4e5d28c92047d09fc1ed8cbbb017c60694acea


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py:L2066-L2073 and diff_diff/linalg.py:L2169-L2374.
  • The PR changes evaluation strategy only: dense residual-maker pair loops are replaced by the documented scores-based B = diag(||omega_g||^2) - P' M_U P identity.
  • Registry coverage is present at docs/methodology/REGISTRY.md:L246, including the PT2018 Satterthwaite reference and the documented floating-point parity caveat.
  • Weighted CR2-BM remains routed through _cr2_bm_dof_inner_weighted at diff_diff/linalg.py:L2073-L2083; I did not see an unintended weighted methodology change.
  • I could not run tests locally: pytest is not installed, and direct Python import also fails because numpy is unavailable.

Methodology

Severity: P3 informational
Impact: No methodology defect found. The changed path preserves the existing scalar Satterthwaite target and only changes how the pairwise B matrix is evaluated. The cited PT method source covers BRL/CRVE with Satterthwaite DOF and small-sample tests; the registry now documents this PR’s evaluation identity and parity tolerance. diff_diff/linalg.py:L2186-L2223, docs/methodology/REGISTRY.md:L246. (arxiv.org)
Concrete fix: No action required.

Code Quality

Severity: P3 informational
Impact: Private helper propagation is consistent: _compute_cr2_bm_vcov_and_dof calls the new _cr2_bm_dof_inner signature on the unweighted path, while public wrappers still delegate through the shared core. diff_diff/linalg.py:L2071-L2073, diff_diff/linalg.py:L2143-L2150, diff_diff/linalg.py:L2608-L2616.
Concrete fix: No action required.

Performance

Severity: P3 informational
Impact: The prior dense n x n clustered path is removed from the changed unweighted CR2-BM helper. Contrast and row chunking are bounded by _CR2_BM_CONTRAST_CHUNK_BYTES, avoiding full O(n*m), O(G*k*m), and O(G^2) intermediates. diff_diff/linalg.py:L2155-L2166, diff_diff/linalg.py:L2252-L2298.
Concrete fix: No action required.

Maintainability

Severity: P3 informational
Impact: The remaining dense one-way non-clustered BM DOF path is explicitly tracked as deferred work, so it is not a blocker under the review rules. TODO.md:L50, diff_diff/linalg.py:L2637-L2642.
Concrete fix: No action required.

Tech Debt

Severity: P3 informational
Impact: The old broad CR2-BM performance TODO is replaced with a narrower one-way dense-path TODO. This is properly tracked deferrable work, not silent statistical debt. TODO.md:L49-L50.
Concrete fix: No action required.

Security

Severity: P3 informational
Impact: Secret-pattern scan of the changed files found no credential-like additions; matches were only historical/documentation text.
Concrete fix: No action required.

Documentation/Tests

Severity: P3 informational
Impact: Added tests cover pair-loop oracle parity, compound contrasts, unbalanced clusters, and forced contrast/row chunking. tests/test_linalg.py:L2719-L2836. Local verification was blocked by missing pytest and numpy.
Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology/parity suites.

@igerber igerber added ready-for-ci Triggers CI test workflows and removed ready-for-ci Triggers CI test workflows labels Jul 9, 2026
igerber and others added 9 commits July 9, 2026 14:03
The unweighted per-contrast Satterthwaite DOF materialized the dense
n x n residual-maker M = I - X(X'X)^-1 X' and contracted it over all
cluster pairs (O(n^2) time per contrast, O(n^2) memory — 3.2 GB at
n=20k). With Omega the (n, G) matrix stacking omega_g on disjoint
cluster supports, B = Omega' M Omega collapses to
diag(||omega_g||^2) - P' M_U P with P = X' Omega: O(nk + G^2 k) per
contrast, no n x n allocation. ~32x at n=5k/G=50 (0.57s -> 0.018s);
n=20k/G=100 in 0.12s. Algebraically identical — frozen pair-loop
oracle parity at rtol 1e-10 (balanced, unbalanced, compound
contrasts); both NaN-reliability guards unchanged; 432 consumer tests
pass unmodified. REGISTRY notes updated (per-fit setup wording +
Appendix-B evaluation sentence).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…P2) + doc syncs (P3s)

P2: P_all was (G, k, m) — O(G*k^2) on the batched per-coefficient sweep
(contrasts=eye(k)), contradicting the stated O(Gk) bound. The
per-cluster product buffers are now contrast-chunked under a 64 MB
module-constant cap (monkeypatchable); each contrast's B is computed
independently so chunking is bit-identical (locked by a forced
one-contrast-chunk test with assert_array_equal). P3s: stale
residual-maker-M mention removed from the shared-core docstring;
CHANGELOG + REGISTRY memory claims updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The accessible PT2018 arXiv text labels Appendix B as simulation-study
details; the Satterthwaite DOF is Section 3.1 / Eq. 13. The scores
identity B = Omega'M Omega = diag(||omega_g||^2) - P'M_U P is presented
as an algebraic identity rather than paper-attributed, across the
linalg docstring, oracle-test docstring, CHANGELOG entry, REGISTRY
note, and the PR title.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ure fix)

CI (linux-arm + Windows) failed test_contrast_chunking_bit_identical
at 1-ULP differences: each contrast's B is computed independently, but
the per-cluster GEMM X_g' omega_g[:, c0:c1] runs over a width-c slice
and BLAS kernels (GEMV vs GEMM, platform-dependent) may accumulate a
column differently at width 1 vs width m — exact on Accelerate,
1-ULP drift elsewhere. The documented chunking-reassociation caveat
applies: test renamed to test_contrast_chunking_invariant with
assert_allclose(rtol=1e-13), and the bit-for-bit claims corrected in
the module-constant comment, docstring, CHANGELOG, and REGISTRY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… P3s)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te cap

CI-review P2: B_j was materialized dense (G,G) per contrast, an O(G^2)
memory bound the docs understated as O(Gk). trace_B2 (Frobenius sum) and
max|B| are row-separable, so B_j now streams in row blocks bounded by
_CR2_BM_CONTRAST_CHUNK_BYTES; neither the n x n residual-maker, the
O(G*k*m) product buffers, nor O(G^2) pairwise entries are ever held at
once. CHANGELOG/REGISTRY memory claims updated to the honest bound; the
forced-tiny-cap invariance test now exercises both chunk axes (row_chunk
= 6 < G = 10). Also reworded the stale "bit-equal to prior" REGISTRY
sentence (review P3) to floating-point-tolerance parity.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
…g docstring claims

Review follow-ups on the row-chunk commit:
- P2: "memory bounded by the cap" was still too broad — Q (n,m) and the
  per-cluster omega_all dict (n,m total) were full-width precomputes. Both
  now compute inside the contrast chunk (same total FLOPs), leaving X_bi
  and A_g_Xbi (both O(nk), input-scale) as the only full-size arrays;
  docstring/CHANGELOG/REGISTRY state exactly which arrays are bounded.
- P3: one-way BM DOF's "scores-based formulation (tracked in TODO.md)"
  pointed at the CR2-BM row this PR resolves — added a specific TODO row
  for the one-way dense-M path and repointed the note.
- P3: scoped _compute_cr2_bm's "bit-equal to prior unweighted behavior"
  claim to the vcov; the DOF evaluation is scores-based (floating-point-
  tolerance parity).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
…ominator

Review round 3 P2: the contrast-chunk denominator only counted the P_all
slab (G*k), so Q_chunk (n per width) escaped the cap when n dominates,
and the docs' "bounded by the cap" claim ignored the intrinsic
one-contrast floor. The denominator now counts all width-scaled buffers
(n + n_g_max + G*k + G, in bytes); docstring/CHANGELOG/REGISTRY state
the cap is subject to a one-contrast lower bound (a single contrast
needs the O(n) q vector and O(G*k) P_j/PB buffers by construction).
Default 64 MB cap still yields a single chunk at benchmark sizes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
…n of AHT Eq. 13)

Review round 4 P3: the scalar t-test Satterthwaite degrees of freedom
are Eq. 11 in PT2018 §3.1; Eq. 13 is the multi-parameter AHT expression
that reduces to Eq. 11 at q=1. linalg.py's own citation names only §3.1
(no equation number) and needs no change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
@igerber igerber force-pushed the perf/cr2-bm-scores-dof branch from be4e5d2 to 6cf18fc Compare July 9, 2026 18:04
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 6cf18fc27c7ad8f5a301a5908b52900bfeeb8c81


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py:L2066-L2074 and diff_diff/linalg.py:L2169-L2298.
  • The weighted CR2-BM P-array path remains routed through _cr2_bm_dof_inner_weighted, so I do not see an unintended weighted-methodology change.
  • The registry documents the scores-based B = Omega' M Omega evaluation and its floating-point parity caveat at docs/methodology/REGISTRY.md:L246.
  • Added tests cover balanced, unbalanced, compound-contrast, and forced chunking paths at tests/test_linalg.py:L2719-L2836.
  • Could not run tests locally: pytest is not installed, and direct import fails because numpy is unavailable.

Methodology

  • Severity: P3 informational
    Impact: No methodology defect found. The changed unweighted clustered evaluator preserves the existing scalar Satterthwaite DOF target and changes only the evaluation of the cluster-pair matrix from an explicit dense residual-maker to the documented diag(||omega_g||^2) - P' bread_inv P identity. This is consistent with the local registry note and the cited PT2018 CRVE/Satterthwaite small-sample-testing context. diff_diff/linalg.py:L2186-L2223, docs/methodology/REGISTRY.md:L246. citeturn3search0
    Concrete fix: No action required.

Code Quality

  • Severity: P3 informational
    Impact: Signature propagation is consistent. _compute_cr2_bm_vcov_and_dof now calls the new _cr2_bm_dof_inner signature only on the unweighted path, and _compute_cr2_bm_contrast_dof still delegates through the shared core. diff_diff/linalg.py:L2071-L2083, diff_diff/linalg.py:L2605-L2616.
    Concrete fix: No action required.

Performance

  • Severity: P3 informational
    Impact: The new clustered path avoids materializing M = I - H and row-chunks the (G, G) pairwise matrix while contrast-chunking width-scaled buffers. This addresses the removed CR2-BM performance TODO for the clustered path. diff_diff/linalg.py:L2252-L2298.
    Concrete fix: No action required.

Maintainability

  • Severity: P3 informational
    Impact: The chunk cap is a module-level constant and has a monkeypatch-driven test, which makes the multi-chunk path reviewable and reproducible. diff_diff/linalg.py:L2155-L2166, tests/test_linalg.py:L2811-L2836.
    Concrete fix: No action required.

Tech Debt

  • Severity: P3 informational
    Impact: The remaining one-way non-clustered dense Bell-McCaffrey DOF path is explicitly tracked in TODO.md, so it is deferrable under the review rules. TODO.md:L50, diff_diff/linalg.py:L2637-L2642.
    Concrete fix: No action required.

Security

  • Severity: P3 informational
    Impact: Secret-pattern scan of changed files found no actionable credential additions.
    Concrete fix: No action required.

Documentation/Tests

  • Severity: P3 informational
    Impact: Minor stale comment: the one-way unweighted helper still builds dense H/M, but its inline comment says “scores-based evaluation.” The surrounding docstring and TODO.md are correct, so this is documentation-only. diff_diff/linalg.py:L2688-L2704.
    Concrete fix: Change the parenthetical to “dense prior evaluation; floating-point-tolerance parity” or remove “scores-based evaluation.”

  • Severity: P3 informational
    Impact: Verification was environment-blocked. git diff --check passed, but pytest is missing and direct import fails due missing numpy.
    Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology suites.

…res-based

Review P3: the one-way unweighted helper still builds the dense H/M
(tracked in TODO.md); its inline comment wrongly said "scores-based
evaluation."

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: a7d78a1529de398cb4d4eeff86142823e964bb8b


Overall Assessment

✅ Looks good. No unmitigated P0/P1 findings.

Executive Summary

  • Affected method: unweighted clustered CR2 Bell-McCaffrey Satterthwaite DOF in diff_diff/linalg.py:L2066-L2072 and diff_diff/linalg.py:L2169-L2298.
  • Methodology check passes: the PR changes evaluation of the same B matrix, not the estimand or inference target; the registry documents this as a Note at docs/methodology/REGISTRY.md:L237-L246.
  • Weighted CR2-BM still routes through _cr2_bm_dof_inner_weighted, so I do not see an unintended weighted-path change at diff_diff/linalg.py:L2071-L2083.
  • Prior P3 stale-comment concern appears addressed in the one-way helper comment at diff_diff/linalg.py:L2688-L2690.
  • Verification was environment-limited: git diff --check passed, but local pytest execution is blocked because pytest and numpy are unavailable.

Methodology

  • Severity: P3 informational
    Impact: No methodology defect found. The new evaluator preserves the CR2 Bell-McCaffrey scalar Satterthwaite DOF target and replaces explicit dense M contraction with the algebraic identity B = Omega' M Omega = diag(||omega_g||^2) - P' M_U P; this is consistent with the registry’s documented Note and PT2018’s CR2/Satterthwaite small-sample testing framework. diff_diff/linalg.py:L2186-L2223, docs/methodology/REGISTRY.md:L246. citeturn6view0
    Concrete fix: No action required.

Code Quality

  • Severity: P3 informational
    Impact: Signature/caller propagation is consistent. The unweighted branch now calls _cr2_bm_dof_inner without M, while weighted callers remain on _cr2_bm_dof_inner_weighted; wrappers still delegate through _compute_cr2_bm_vcov_and_dof. diff_diff/linalg.py:L2071-L2083, diff_diff/linalg.py:L2143-L2150, diff_diff/linalg.py:L2608-L2615.
    Concrete fix: No action required.

Performance

  • Severity: P3 informational
    Impact: The new path removes the dense n x n residual-maker from clustered unweighted CR2-BM DOF and chunks both contrast-width buffers and G x G row blocks. This addresses the tracked performance issue without changing the weighted path. diff_diff/linalg.py:L2155-L2166, diff_diff/linalg.py:L2252-L2298.
    Concrete fix: No action required.

Maintainability

  • Severity: P3 informational
    Impact: The chunk byte budget is a module-level constant and the forced-chunking regression test makes the multi-chunk behavior reviewable. diff_diff/linalg.py:L2155-L2166, tests/test_linalg.py:L2811-L2836.
    Concrete fix: No action required.

Tech Debt

  • Severity: P3 informational
    Impact: The remaining one-way non-clustered dense BM DOF path is explicitly tracked in TODO.md, so it is deferrable under the review rules. TODO.md:L50, diff_diff/linalg.py:L2637-L2642.
    Concrete fix: No action required.

Security

  • Severity: P3 informational
    Impact: Secret-pattern scan of the changed diff found no credential-like additions.
    Concrete fix: No action required.

Documentation/Tests

  • Severity: P3 informational
    Impact: Tests cover balanced, unbalanced, compound-contrast, and forced contrast/row chunking parity against a frozen dense pair-loop oracle. tests/test_linalg.py:L2719-L2836. I could not run them locally: python -m pytest reports No module named pytest, and a direct dependency probe also shows numpy missing. git diff --check passed.
    Concrete fix: Ensure CI runs tests/test_linalg.py::TestCR2BMScoresBasedDOF plus the existing CR2-BM methodology suites.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jul 9, 2026
@igerber igerber merged commit 19f8cec into main Jul 9, 2026
35 of 36 checks passed
@igerber igerber deleted the perf/cr2-bm-scores-dof branch July 9, 2026 20:30
igerber added a commit that referenced this pull request Jul 10, 2026
The non-clustered unweighted BM DOF computed a'(MoM)a against an
explicit dense M = I - H (O(n^2 k) hat build; ~20 GB at n=50k,
documented "practical for n < 10k"). The Schur-product expansion

  den = sum_i a_i^2 (1 - 2 h_ii) + tr((B S_a)^2),  S_a = X' diag(a) X

is exact algebra ((MoM)_{il} = delta_{il}(1-2h_ii) + (x_i'B x_l)^2):
O(n k^2 + k^3) per contrast, n=50k/k=20 in ~16 ms. Frozen dense-oracle
parity at rtol 1e-10 (basic/high-leverage/k40 + compound contrast);
R-anchored hc2_bm goldens pass unchanged.

New noise-floor cancellation guard: the dense den >= 0 exactly (MoM is
PSD by the Schur product theorem) but the expanded difference can
collapse below float precision on extreme-leverage contrasts; such
denominators now NaN instead of inflating the DOF arbitrarily (the
same failure mode the clustered scores path guards; the prior dense
den > 0 kept them).

Deletes the one-way TODO row (#656's follow-up). Completes the
scores-evaluation family: clustered CR2-BM (#656), low-rank A_g (#664),
one-way (this PR).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Lbd6nqWmg4snvvBmegwqiw
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