Skip to content

fix(honest_did): nested optimal-FLCI optimizer — R center parity (SE-audit B2b)#635

Merged
igerber merged 1 commit into
mainfrom
fix/honest-flci-nested-parity
Jul 7, 2026
Merged

fix(honest_did): nested optimal-FLCI optimizer — R center parity (SE-audit B2b)#635
igerber merged 1 commit into
mainfrom
fix/honest-flci-nested-parity

Conversation

@igerber

@igerber igerber commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • Replaces the flat Nelder-Mead optimizer in the HonestDiD Δ^SD optimal Fixed-Length CI (_compute_optimal_flci) with a faithful port of R HonestDiD::findOptimalFLCI's nested convex program: an inner minimum-worst-case-bias problem at a fixed estimator SD h (a smooth convex QCQP over slope weights — U ≥ |cumsum(w)|, one second-order-cone var(w) ≤ h², linear-trend neutrality; scipy SLSQP, no cvxpy, gated on feasibility not res.success) and an outer 1-D grid-zoom over h minimizing cv(M·bias/h)·h.
  • Fixes SE-audit B2b: the prior optimizer matched R's CI width but its center drifted up to ~9% from R's optimal affine estimator at intermediate smoothness M. The nested structure removes the drift — center + half-length + optimalVec now match R to ~1e-3 (median ~1e-5) across a stress grid (num_pre × num_post × AR(1) ρ × M). M=0 (the min-variance estimator) is byte-unchanged.
  • The worst-case bias uses R's exact closed form M·(constant + Σ_i|cumsum(w)_i|), linear in the abs-linearized weights (equals the LP oracle _compute_worst_case_bias for nonnegative l_vec; for signed contrasts it follows R's conservative form — we match R either way, verified to ~1e-6).
  • diff-diff keeps its analytical folded-normal critical value (_cv_alpha), strictly more accurate than R's Monte-Carlo .qfoldednormal; the golden overrides R's MC quantile with an analytical one so both solve the same deterministic outer problem (~1e-3 parity; vs stock MC-R ~1.4e-2, the residual being R's simulation noise on the near-flat width surface).
  • _compute_optimal_flci is now a thin (lb, ub) wrapper over _flci_solve (which also returns the optimal estimator direction for parity tests). Zero-SD / degenerate covariance returns NaN inference (no crash).

Methodology references (required if estimator / math changes)

  • Method name(s): HonestDiD Δ^SD optimal Fixed-Length Confidence Interval (Rambachan & Roth 2023 §4.1); R HonestDiD::findOptimalFLCI nested algorithm (.findWorstCaseBiasGivenH inner SOCP + .findOptimalCIDerivativeBisection outer; .createObjectiveObjectForBias closed-form bias; .findLowestH / .findHForMinimumBias bracket).
  • Paper / source link(s): Rambachan & Roth (2023, ReStud); R HonestDiD 0.2.6. See docs/methodology/REGISTRY.md (Δ^SD FLCI optimizer note).
  • Any intentional deviations from the source (and why): (1) analytical folded-normal critical value instead of R's 10⁶-draw Monte-Carlo .qfoldednormal — strictly more accurate (noise-free); parity is asserted against override-R (deterministic) to ~1e-3, and against stock MC-R to ~1.4e-2 (R's own simulation noise). (2) For signed / contrast l_vec the bias objective follows R's conservative closed form (matches R; the LP-oracle-exact bias would be tighter but would diverge from R). Both documented in REGISTRY.

Validation

  • Tests added/updated: tests/test_methodology_honest_did.py — new TestHonestFLCIParityR (center + half-length + optimalVec vs R across the stress grid: override-R to 1e-3 + a stock-R MC-noise realism tier; a behavioral NaN test for inner-solve failure). Upgraded the M-grid test from finiteness-only to intermediate-M center parity; added zero-SD and signed-l_vec R-parity regression tests. New R golden benchmarks/data/honest_flci_golden.json (generator benchmarks/R/generate_honest_flci_golden.R, run from repo root), verified vs R HonestDiD 0.2.6. 107 honest tests pass; M=0 and all existing behaviour unchanged.
  • Backtest / simulation / notebook evidence (if applicable): N/A.

Security / privacy

  • Confirm no secrets/PII in this PR: Yes

🤖 Generated with Claude Code

https://claude.ai/code/session_0178r8ZK6VRiXbaBiknrWjQB

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

PR Review Report

Overall Assessment

⚠️ Needs changes — one unmitigated P1 methodology/assumption-check issue.

Executive Summary

  • Affected method: HonestDiD Δ^SD optimal FLCI.
  • The documented R/analytical critical-value deviation is properly recorded in REGISTRY.md; not a defect.
  • The signed-contrast conservative bias objective is also documented; not a defect.
  • The new optimizer can accept a negative M through public override paths and treat it like a positive smoothness value.
  • I could not run tests locally because the environment is missing numpy.

Methodology

Finding 1 — P1: negative M can silently produce a finite Δ^SD FLCI

Location: diff_diff/honest_did.py:L2479, diff_diff/honest_did.py:L1875, diff_diff/honest_did.py:L1971, diff_diff/honest_did.py:L1511; registry contract at docs/methodology/REGISTRY.md:L3620

Impact: The registry says negative M is invalid. Constructor validation catches HonestDiD(M=-1), but fit(results, M=...) and sensitivity_analysis(M_grid=...) bypass that validation. In the new nested FLCI path, M * bias_unit / h is passed to _cv_alpha(), which immediately applies abs(t), so M=-0.1 is effectively treated like M=0.1 for the FLCI half-length. That can report a finite CI for an infeasible/invalid smoothness restriction with no warning or error.

Concrete fix: Validate the resolved M in fit() before extracting event-study parameters, and validate all values in M_grid before looping. Add a defensive M < 0 guard in _flci_solve() / _compute_optimal_flci() as well, since tests call it directly.

Code Quality

No additional findings.

Performance

No findings. The grid-zoom/SLSQP approach is documented as an implementation choice and parity-tested against the R golden.

Maintainability

No findings.

Tech Debt

No findings. The prior FLCI-center TODO is marked landed and the methodology note documents the replacement algorithm.

Security

No findings. I did not see secrets or unsafe external inputs in the changed files.

Documentation/Tests

Finding 2 — P1: missing tests for negative M override/grid paths

Location: tests/test_honest_did.py:L356-L359, diff_diff/honest_did.py:L2827-L2860

Impact: Tests only cover constructor-time negative M; they do not cover fit(..., M=-...), sensitivity_analysis(..., M_grid=[..., -...]), or direct _compute_optimal_flci(..., M=-...).

Concrete fix: Add tests asserting ValueError("M must be non-negative") for:

  • HonestDiD(method="smoothness").fit(mock_multiperiod_results, M=-0.1)
  • HonestDiD(method="smoothness").sensitivity_analysis(mock_multiperiod_results, M_grid=[0, -0.1])
  • _compute_optimal_flci(..., M=-0.1) or _flci_solve(..., M=-0.1)

Path to Approval

  1. Add non-negative M validation for fit() overrides and sensitivity_analysis() grids.
  2. Add a defensive negative-M guard in the direct FLCI helper path.
  3. Add the three negative-M regression tests listed above.

@igerber igerber force-pushed the fix/honest-flci-nested-parity branch from f54041f to 4e6b78e Compare July 7, 2026 16:31
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 4e6b78e6d24e7025de03d30631cd75a2bbcd3f7e


PR Review Report

Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected method: HonestDiD Δ^SD optimal FLCI.
  • Prior P1 is resolved: negative M is now guarded in fit(), sensitivity_analysis(), and direct FLCI helper paths.
  • The nested optimizer, signed-contrast behavior, and analytical folded-normal critical value are documented in REGISTRY.md.
  • Documented R deviations are P3-informational under the review rules, not blockers.
  • I could not run tests locally because this environment is missing both pytest and numpy.

Methodology

Finding 1 — P3-informational: documented analytical critical-value deviation

Location: docs/methodology/REGISTRY.md:L3647-L3652, diff_diff/honest_did.py:L1486-L1544

Impact: diff-diff uses an analytical folded-normal / folded noncentral-t critical value instead of R’s Monte Carlo .qfoldednormal. This is explicitly documented as a deviation from R and is parity-tested against override-R.

Concrete fix: No action required.

Finding 2 — P3-informational: documented signed-contrast bias convention

Location: docs/methodology/REGISTRY.md:L3622-L3646, diff_diff/honest_did.py:L1712-L1726

Impact: For signed l_vec, the closed-form bias objective follows R’s conservative form rather than the tighter LP oracle. This is documented in the registry and tested against R, so it is not a methodology defect.

Concrete fix: No action required.

Prior Finding Check — resolved

Negative M now raises before computation in _flci_solve, fit(), and sensitivity_analysis(), with regression tests added.
Locations: diff_diff/honest_did.py:L1943-L1949, diff_diff/honest_did.py:L2484-L2488, diff_diff/honest_did.py:L2863-L2865, tests/test_honest_did.py:L392-L403, tests/test_methodology_honest_did.py:L543-L552

Code Quality

No findings.

Performance

No findings. The SLSQP inner solve plus grid-zoom outer solve is documented as an implementation choice and covered by R parity tests.

Maintainability

No findings.

Tech Debt

No findings. The prior FLCI-center item is marked landed in TODO.md, and the methodology registry documents the replacement algorithm.

Security

No findings. I did not see secrets or unsafe handling of external inputs in the changed files.

Documentation/Tests

Finding 3 — P3: _flci_solve return docstring omits the third return value

Location: diff_diff/honest_did.py:L1896-L1942

Impact: _flci_solve() now returns (ci_lb, ci_ub, optimal_vec), but the Returns block only documents the CI bounds. This is minor because the helper is internal and tests use it correctly.

Concrete fix: Add optimal_vec : np.ndarray or None to the Returns block.

Verification note — P3-informational

Impact: I could not execute the added tests locally: pytest and numpy are not installed in the review environment.

Concrete fix: Ensure CI runs the targeted HonestDiD tests, especially the new negative-M, zero-SD, signed-contrast, and R golden parity tests.

@igerber igerber force-pushed the fix/honest-flci-nested-parity branch from 4e6b78e to 8fb12e0 Compare July 7, 2026 16:37
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 8fb12e0b9380cdf46b050fd12029a65a41f939af


PR Review Report

Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected method: HonestDiD Δ^SD optimal Fixed-Length CI.
  • Prior negative-M concern remains resolved in direct helper, fit(), and sensitivity_analysis().
  • The nested FLCI optimizer and R-parity scope are documented in REGISTRY.md.
  • Documented R deviations are P3-informational under the review rules.
  • I could not run tests locally because this container is missing pytest and numpy.

Methodology

Finding 1 — P3-informational: documented analytical critical-value deviation

Location: docs/methodology/REGISTRY.md:L3657-L3662, diff_diff/honest_did.py:L1486-L1544

Impact: diff-diff uses analytical folded-normal / folded noncentral-t critical values instead of R’s Monte Carlo .qfoldednormal. This is explicitly documented as a deviation from R and parity-tested against override-R.

Concrete fix: No action required.

Finding 2 — P3-informational: documented signed-contrast bias convention

Location: docs/methodology/REGISTRY.md:L3639-L3648, diff_diff/honest_did.py:L1712-L1726

Impact: For signed l_vec, the implementation follows R’s conservative closed-form bias convention rather than the tighter LP-oracle bias. This is documented and tested against R, so it is not a methodology defect.

Concrete fix: No action required.

Prior Finding Check — resolved

Negative M is now rejected in _flci_solve, fit(), and sensitivity_analysis(), with regression tests.

Locations: diff_diff/honest_did.py:L1943-L1950, diff_diff/honest_did.py:L2484-L2488, diff_diff/honest_did.py:L2863-L2865, tests/test_honest_did.py:L392-L403, tests/test_methodology_honest_did.py:L543-L552

Code Quality

No findings.

Performance

No findings. The SLSQP inner solve plus grid-zoom outer solve is a documented implementation choice for R center parity, not a methodology issue.

Maintainability

No findings.

Tech Debt

No blocker. The prior FLCI center-parity item is marked landed in TODO.md:L37.

Security

No findings. I did not see secrets or unsafe external-input handling in the changed files.

Documentation/Tests

Finding 3 — P3: _flci_solve return docstring omits optimal_vec

Location: diff_diff/honest_did.py:L1936-L1942

Impact: _flci_solve() returns (ci_lb, ci_ub, optimal_vec), but the Returns block only documents the CI bounds. This is minor because the helper is internal and tests call it correctly.

Concrete fix: Add optimal_vec : np.ndarray or None to the Returns block.

Verification note — P3-informational

Impact: I could not execute the targeted tests locally because pytest and numpy are not installed.

Concrete fix: Ensure CI runs the HonestDiD targeted tests, especially the new negative-M, zero-SD, signed-contrast, and R golden parity tests.

…audit B2b)

Replace the flat Nelder-Mead over slope weights in the Delta^SD optimal FLCI with a
faithful port of R HonestDiD::findOptimalFLCI's nested convex program: an inner
minimum-worst-case-bias problem at a fixed estimator SD h (a smooth convex QCQP over
the slope weights -- U >= |cumsum(w)|, one second-order-cone var(w) <= h^2,
linear-trend neutrality; scipy SLSQP, no cvxpy, gated on feasibility not
res.success) and an outer 1-D grid-zoom over h minimizing cv(M*bias/h)*h.

The prior optimizer matched R's CI width but its center drifted up to ~9% from R's
optimal affine estimator at intermediate smoothness M (the two land on different
equi-width estimators). The nested structure removes that drift: center +
half-length + optimalVec now match R to ~1e-3 (median ~1e-5) across a stress grid
(num_pre x num_post x AR(1) rho x M). M=0 (min-variance estimator) is unchanged.

The worst-case bias has the closed form M*(constant + sum_i|cumsum(w)_i|), verified
equal to the LP oracle _compute_worst_case_bias to machine precision, so the inner
objective is linear in the abs-linearized weights. diff-diff keeps its ANALYTICAL
folded-normal critical value (_cv_alpha), strictly more accurate than R's Monte-Carlo
.qfoldednormal; the golden overrides R's MC quantile with an analytical one so both
solve the same deterministic outer problem (parity ~1e-3; vs stock MC-R ~1.4e-2, the
residual being R's simulation noise on the near-flat width surface). A
derivative/envelope-theorem outer solve is not viable -- the min-bias inner solutions
are degenerate (most cumsum(w) at the abs-value kink), so the SD multiplier is not
recoverable from sign(Lw).

_compute_optimal_flci is now a thin (lb, ub) wrapper over _flci_solve (which also
returns the optimal estimator direction for parity tests). Verified vs R HonestDiD
0.2.6 (benchmarks/data/honest_flci_golden.json + TestHonestFLCIParityR); 107 honest
tests pass. Closes SE-audit B2b.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0178r8ZK6VRiXbaBiknrWjQB
@igerber igerber force-pushed the fix/honest-flci-nested-parity branch from 8fb12e0 to 365e485 Compare July 7, 2026 18:06
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 365e4854243865b6f74d3bf909ce6592ccafb545


PR Review Report

Overall Assessment

Looks good — no unmitigated P0/P1 findings.

Executive Summary

  • Affected method: HonestDiD Δ^SD optimal Fixed-Length CI.
  • The prior negative-M concern remains resolved across direct helper, fit(), and sensitivity_analysis().
  • The nested FLCI optimizer, R-parity scope, and intentional R deviations are documented in REGISTRY.md.
  • Zero-SD / degenerate covariance now NaN-fails the CI endpoints together.
  • I could not run tests locally because this container is missing numpy.

Methodology

Finding 1 — P3-informational: documented analytical critical-value deviation

Location: docs/methodology/REGISTRY.md:L3657-L3662, diff_diff/honest_did.py:L1486-L1544

Impact: diff-diff uses analytical folded-normal / folded noncentral-t critical values instead of R’s Monte Carlo .qfoldednormal. This is explicitly documented as a deviation from R and parity-tested against override-R.

Concrete fix: No action required.

Finding 2 — P3-informational: documented signed-contrast bias convention

Location: docs/methodology/REGISTRY.md:L3639-L3648, diff_diff/honest_did.py:L1712-L1726, tests/test_methodology_honest_did.py:L525-L541

Impact: For signed l_vec, the implementation follows R’s conservative closed-form bias convention rather than the tighter LP-oracle bias. This is documented and covered by an R-parity regression, so it is not a methodology defect.

Concrete fix: No action required.

Prior Finding Check — resolved

Location: diff_diff/honest_did.py:L1949-L1953, diff_diff/honest_did.py:L2487-L2491, diff_diff/honest_did.py:L2866-L2868, tests/test_honest_did.py:L392-L403, tests/test_methodology_honest_did.py:L543-L552

Negative M is rejected in the direct FLCI helper, fit() override path, and sensitivity_analysis() grid path.

Code Quality

No findings. The public _compute_optimal_flci() two-value contract is preserved via the _flci_solve() wrapper, and the prior _flci_solve return-docstring gap is addressed.

Performance

No findings. The SLSQP inner solve plus grid-zoom outer solve is a documented implementation choice for R center parity, not a methodology issue.

Maintainability

No findings.

Tech Debt

No blocker. The prior HonestDiD FLCI center-parity item is marked landed in TODO.md:L37.

Security

No findings. I did not see secrets or unsafe external-input handling in the changed files.

Documentation/Tests

Finding 3 — P3-informational: local verification not executed

Location: test suite generally; new coverage at tests/test_methodology_honest_did.py:L512-L649

Impact: I could not run the targeted tests locally because importing the package fails with ModuleNotFoundError: No module named 'numpy'.

Concrete fix: Ensure CI runs the HonestDiD targeted tests, especially the R golden parity, zero-SD, signed-contrast, and negative-M regressions.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Jul 7, 2026
@igerber igerber merged commit f37b242 into main Jul 7, 2026
35 of 36 checks passed
@igerber igerber deleted the fix/honest-flci-nested-parity branch July 7, 2026 20:03
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