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
Codex Wave A R1: fix sparse haversine cutoff > πR and DiD absorb-time leak
Address Wave A codex review round 1 findings.
P0 #1 — Sparse haversine wrong at cutoff > π·R_earth
- `_compute_spatial_bartlett_meat_sparse` computed
`chord_radius = 2·sin(cutoff/(2R))` directly. That function is
monotone only on [0, πR]; for cutoff > π·R (~20,015 km, half-Earth
circumference) it shrinks again, and the kd-tree silently drops
pairs that still have positive Bartlett weight. The dense path
saturates at πR via `_haversine_km`'s `np.clip(a, 0, 1)`; the
sparse path now mirrors that by clamping the arc to π radians
before the chord computation:
arc_radians = min(cutoff / R_earth, π)
chord_radius = 2 · sin(arc_radians / 2)
At cutoff >= πR, chord_radius reaches 2 (sphere diameter), so the
kd-tree captures all pairs.
P0 #2 — DiD + Conley + absorb leaks demeaned time into the helper
- `DifferenceInDifferences.fit()` built `_conley_time_arr`,
`_conley_unit_arr`, and `_conley_coords_arr` from `working_data`,
but `absorb` demeans columns in `working_data` (including `time`
when `time` is listed in `absorb`, or whenever `time` appears in
`vars_to_demean`). The Conley helper would then partition the
within-period spatial sandwich on residualized floats instead of
the true pre/post periods. Now reads from the original `data`
frame, mirroring `TwoWayFixedEffects.fit` which has the same
FWL-composability contract: meat is computed on demeaned scores
but the kernel grid uses raw coords + time/unit.
P2 — Regression coverage
- `test_sparse_haversine_cutoff_above_half_earth_circumference`:
forces sparse path with cutoff = 25,000 km (> π·R_earth ≈ 20,015 km);
asserts dense/sparse parity at atol=1e-10.
- `test_sparse_haversine_cutoff_at_exactly_half_earth_circumference`:
boundary test at cutoff = π·R_earth exactly.
- `test_did_conley_with_absorb_uses_raw_time_labels`: monkeypatches
`_compute_conley_vcov` to capture the `time` arg, asserts the
helper sees raw integer 0/1 labels (not absorb-demeaned floats).
P3 — Stale docstrings reconciled
- `diff_diff/estimators.py` DiD class docstring `vcov_type` entry:
"rejected at fit-time" → Wave A support description.
- `diff_diff/estimators.py` MultiPeriodDiD class docstring `vcov_type`
entry: dropped the "cluster= raises" restriction (Wave A #119 lifts
it via the combined kernel).
- `diff_diff/linalg.py` `compute_robust_vcov` docstring: `vcov_type`
entry updated for the combined spatial + cluster kernel.
- `diff_diff/linalg.py` `LinearRegression.__init__` docstring:
Conley contract updated for combined cluster kernel + DiD support.
Full Wave A regression: 514 passed (up from 511 with the 3 new tests).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0 commit comments