You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
perf(linalg): compute hc2_bm CR2 sandwich once, not twice
LinearRegression(vcov_type="hc2_bm").fit computed the CR2/HC2-BM sandwich
twice: once inside solve_ols for the vcov, then again via
compute_robust_vcov(return_dof=True) just to extract the per-coefficient
Bell-McCaffrey DOF. The CR2 helper returns (vcov, dof) together, so the
second sandwich (per-cluster A_g + the O(n^2 k) Satterthwaite loop) was
pure waste (#475).
Skip solve_ols's vcov on the hc2_bm-not-survey path (return_vcov=False via
a new _is_bm_path flag) and source BOTH vcov and dof from one
compute_robust_vcov(return_dof=True) call, reusing the existing
_expand_vcov_with_nan rank-deficient expansion. solve_ols (and its ~50
callers) is left untouched. Affects every hc2_bm fit: weighted one-way
WLS-CR2, weighted/unweighted clustered CR2-BM (DiD/TWFE/MPD with
vcov_type="hc2_bm").
Bit-identical SEs + per-coef DOF (proven at atol=0 across weighted/
unweighted x one-way/clustered + rank-deficient; both vcov computations
route through the same _compute_robust_vcov_numpy). No methodology,
numerical, or public-API change. Minor: the HC2->HC1 high-leverage
fallback UserWarning now fires once per fit instead of twice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|`ImputationDiD` dense `(A0'A0).toarray()` scales `O((U+T+K)^2)` — OOM risk on large panels (only triggers when the sparse solver fails). Needs an alternative dense fallback or richer sparse strategy. |`imputation.py`|#141| Heavy | Medium |
59
-
|`LinearRegression.fit()` pays the CR2 cost twice on the weighted `hc2_bm` path: once in `solve_ols(..., return_vcov=True)` and again via `compute_robust_vcov(..., return_dof=True)` for `_bm_dof`. Fix: thread `return_dof` through `solve_ols`, or cache the per-cluster `A_g` / `MUWTWUM` precomputes. (CI codex P3 on #475.) |`linalg.py`|#475| Mid | Low |
60
59
| MPD `cluster+hc2_bm` computes CR2 precomputes twice — `solve_ols → _compute_cr2_bm` for vcov+DOF, then `_compute_cr2_bm_contrast_dof` for the post-period-average contrast DOF. Both rebuild `H`, `M`, per-cluster `A_g`. Plumb the contrast DOF through the vcov path or share via a cached helper. |`linalg.py`, `estimators.py::MultiPeriodDiD.fit`| follow-up | Mid | Low |
61
60
| CR2 Bell-McCaffrey DOF uses a naive `O(n²k)` per-coefficient loop over cluster pairs; Pustejovsky-Tipton (2018) Appendix B has a scores-based formulation avoiding the full `n×n``M`. Switch when a user hits a large-`n` cluster-robust design. |`linalg.py::_compute_cr2_bm`| Phase 1a | Heavy | Low |
62
61
| Rust-backend HC2: the Rust path only supports HC1; HC2 and CR2 Bell-McCaffrey fall through to NumPy. Noticeable for large-`n` fits. |`rust/src/linalg.rs`| Phase 1a | Mid | Low |
0 commit comments