Signal non-convergence in Frank-Wolfe SC weight solver (numpy path)#315
Signal non-convergence in Frank-Wolfe SC weight solver (numpy path)#315
Conversation
_sc_weight_fw_numpy ran its iterative Frank-Wolfe loop up to max_iter (R's default: 10000) and silently returned the final iterate when the convergence check vals[t-1] - vals[t] < min_decrease^2 never triggered early exit. This matches the silent-failure pattern audited under axis B of the silent-failures initiative (finding #1); REGISTRY:1499 previously documented this as "No warning emitted". Adds a converged flag to the numpy path and calls the shared warn_if_not_converged helper on exhaustion. Updates the REGISTRY entry to describe the new signal. Rust-backend path is unchanged; the Rust FFI function signature currently returns weights only and would need to thread a convergence status — left as an axis-G backend-parity follow-up (tracked in the Phase 2 findings). Warning-only: no new public parameter, no change to returned weights on inputs that already converge. Axis-B regression-lint baseline: 6 -> 5 silent range(max_iter) loops remaining (TROP global outer + inner + local). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Overall assessment✅ Looks good Highest unmitigated severity: P3. This PR does not change the Arkhangelsky et al. (2021) / Executive summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
…warning AI review on PR #315 asked for test coverage of the public _sc_weight_fw wrapper (rather than only the private _sc_weight_fw_numpy helper), to pin the warning contract against refactors that would bypass the numpy path. - test_fw_wrapper_warns_on_nonconvergence_without_rust: patches HAS_RUST_BACKEND=False and asserts the wrapper routes through the warning on non-convergence. - test_fw_wrapper_no_warning_on_convergence_without_rust: wrapper-level negative control. - test_fw_max_iter_zero_warns: pins the convention that max_iter=0 (which returns the uniform init without iterating) emits the warning, since by construction no convergence was reached. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/ai-review |
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment ✅ Looks good No unmitigated P0/P1 findings. The affected method is SDID’s Frank-Wolfe solver for unit and time weights; this diff leaves the solver math intact and only adds non-convergence signaling on the NumPy fallback. The official SDID paper establishes the estimator, and the official Executive Summary
Methodology
Code Quality
Performance
Maintainability
Tech Debt
Security
Documentation/Tests
|
… remove deprecated SyntheticDiD params Package four merged PRs (#312 SDID catastrophic cancellation at extreme Y scale, #313 roadmap refresh, #314 FE imputation non-convergence signaling, #315 Frank-Wolfe SC weight solver non-convergence signaling) as 3.1.2. Also remove the SyntheticDiD(lambda_reg=...) and SyntheticDiD(zeta=...) kwargs, which have been deprecated with DeprecationWarning since v2.3.1 (2026-02-10) in favor of zeta_omega / zeta_lambda; their warning messages announced removal in v3.1. Passing the old kwargs now raises TypeError at __init__ and ValueError: Unknown parameter at set_params. Internal ridge-regression helpers that accept a lambda_reg parameter (compute_synthetic_weights, rank_control_units, Rust FFI bindings) are unaffected. Version strings bumped in diff_diff/__init__.py, pyproject.toml, rust/Cargo.toml, and diff_diff/guides/llms-full.txt. CHANGELOG populated with Fixed / Changed / Removed sections and comparison-link footer. TODO.md's "Deprecated Code" entry removed now that the task is done. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per CI review feedback (#316): removing public kwargs under a patch version violates Semantic Versioning, which CHANGELOG.md explicitly claims to adhere to. Restore lambda_reg and zeta handling in SyntheticDiD.__init__ and set_params as warning-only, and bump the removal target in the DeprecationWarning text from "v3.1" to "v4.0.0". The 3.1.2 release now carries only the four fix/doc PRs (#312 SDID scale, #313 roadmap, #314 FE imputation convergence, #315 Frank-Wolfe convergence) with no breaking changes. - diff_diff/synthetic_did.py: restore deprecated kwargs + warnings (v4.0.0 text) - tests/test_methodology_sdid.py: restore TestDeprecatedParams class + set_params deprecation test - tests/test_estimators.py: restore test_deprecated_params - CHANGELOG.md: drop Removed section; add Changed entry documenting the v3.1 -> v4.0.0 bump in the removal target - TODO.md: restore Deprecated Code section with v4.0.0 removal target and SemVer rationale Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI review on #322 flagged that the 3.1.3 entries for PR #319 (sparse->dense lstsq fallback) and PR #317 (TROP non-convergence) claimed ConvergenceWarning, but the actual implementations emit UserWarning (imputation.py, two_stage.py, utils.py, trop.py, and the REGISTRY.md contract all use UserWarning). Users filtering warnings by category would be misled. Same factual error was in the 3.1.2 entries I wrote in PR #316 for PR #314 and PR #315. Fixing both entries in this PR — CHANGELOG is a living doc and the warning-category drift is actionable-ly misleading. No code or test changes; CHANGELOG-only edit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
_sc_weight_fw_numpyatutils.py:1434now threads aconvergedflag through its Frank-Wolfe loop and calls the sharedwarn_if_not_convergedhelper (introduced in Signal non-convergence in FE imputation alternating-projection solvers #314) whenmax_iterexhausts without hitting themin_decrease^2gate._sc_weight_fwdispatching intorust/src/weights.rs) currently returns weights without convergence status, so threading the signal there requires a cross-language FFI signature change. Left as an axis-G backend-parity follow-up; the Rust path is silent, but it is numerically equivalent to the numpy path and only reached whenHAS_RUST_BACKEND=True.Methodology references
docs/methodology/REGISTRY.md:1499explicitly documented "No warning emitted" on non-convergence. This PR updates that entry to reflect the new numpy-path signal and notes the remaining Rust-path gap.Validation
tests/test_methodology_sdid.py::TestFrankWolfe: one positive test forcing non-convergence (max_iter=1, min_decrease=1e-12) and one convergent negative control (max_iter=10000, min_decrease=1e-3) asserting no warning fires.tests/test_methodology_sdid.pyregression: 127 passed, 0 failed.Security / privacy