Skip to content

Delete compute_synthetic_weights shim; inline Frank-Wolfe in rank_control_units#344

Merged
igerber merged 3 commits intomainfrom
fix/delete-synthetic-weights-helper
Apr 20, 2026
Merged

Delete compute_synthetic_weights shim; inline Frank-Wolfe in rank_control_units#344
igerber merged 3 commits intomainfrom
fix/delete-synthetic-weights-helper

Conversation

@igerber
Copy link
Copy Markdown
Owner

@igerber igerber commented Apr 20, 2026

Summary

Post-audit cleanup closing finding #22 from the Phase 2 silent-failures audit. Replaces the "fix both backends" approach with "delete the helper and inline correctly in its single caller."

Scope rationale. The audit found that Rust and Python backends of `compute_synthetic_weights` solved different QPs with different step sizes. Closer inspection revealed the function was private, had one non-test caller (`rank_control_units` in prep.py), contributed to an informational-only column (`synthetic_weight` doesn't factor into `quality_score`; the ranking is unaffected by the bug), and was totally disconnected from `SyntheticDiD.fit()` (which uses Frank-Wolfe directly via a separate code path already matching R `synthdid`). Maintaining two broken PGD implementations of a one-caller helper was not worth the cost.

What changed

  • Deleted `compute_synthetic_weights` + `_compute_synthetic_weights_numpy` in `utils.py`.
  • Removed `_rust_synthetic_weights` from `_backend.py` import/fallback and `init.py` re-export.
  • Removed the `prep.py` import.
  • Inlined a Frank-Wolfe computation in `rank_control_units` (prep.py:990) via the shared `_sc_weight_fw` dispatcher — same solver SDID uses, matching R canonical. `zeta=sqrt(lambda_reg/N)` absorbs FW's (1/N) objective scaling; noise-level-scaled `min_decrease`; `n_control ∈ {0, 1}` short-circuits preserved; non-convergence warnings suppressed (heuristic score, not an estimate).
  • Deleted 5 obsolete test classes / methods that exercised the wrapper directly. Added one regression guard for extreme-Y scale in `tests/test_prep.py::TestRankControlUnits`.

SDID invariance verified.
Before/after `SyntheticDiD.fit()` on fixed-seed data:

before: att=1.1895009159752075  se=0.2576531609311485
         weight_sum=1.000000000000002e+00  weight_max=6.820411397386437e-02
after:  att=1.1895009159752075  se=0.2576531609311485
         weight_sum=1.000000000000002e+00  weight_max=6.820411397386437e-02

Bit-identical. Full SDID test suite: 40/40 pass, same IDs.

User-visible impact. `rank_control_units` public signature + return schema + `quality_score` ranking all unchanged. `synthetic_weight` column values unchanged to ~1e-7 on default-parameter typical data; differ only in edge cases where the old code was mathematically wrong. Private imports break (not part of the stable API).

Rust dead code. The `compute_synthetic_weights` PyO3 binding in `rust/src/weights.rs` is now unused by Python. Logged as a low-priority cleanup follow-up in TODO.md; requires `maturin develop` rebuild to remove.

Methodology references

  • Method: synthetic control weight optimization via Frank-Wolfe (Arkhangelsky et al. 2021 / R `synthdid::sc.weight.fw`).
  • No methodology change — SDID fit path is untouched. This PR makes the `rank_control_units` helper use the same canonical solver as SDID fit.

Validation

  • `pytest tests/test_prep.py::TestRankControlUnits tests/test_utils.py tests/test_estimators.py tests/test_rust_backend.py -m 'not slow'` → 305 passed, 14 deselected.
  • `pytest tests/test_methodology_sdid.py tests/test_estimators.py -k SyntheticDiD` → 40 passed (same as baseline).
  • Fixed-seed SDID numerical baseline: bit-identical pre/post.

Security / privacy

No secrets/PII in this PR: Yes.


Generated with Claude Code

@github-actions
Copy link
Copy Markdown

Overall assessment

⚠️ Needs changes

Executive summary

  • rank_control_units now comments that it is using the same canonical Frank-Wolfe path as SDID/R, but the implementation is a different optimization contract: single-pass, no centering, no SDID sparsify pass.
  • The PR also reverts the already-landed aggregate_survey scaffolding fast path and deletes the equivalence tests that protected it, reintroducing the BRFSS-scale slowdown.
  • Multiple release/version surfaces are rolled back from 3.2.0 to 3.1.3, which is unrelated to the stated cleanup and would ship misleading package metadata.
  • The remaining dead Rust binding is properly tracked in TODO.md, so that part is informational only.

Methodology

  • Severity P1. Impact: the new synthetic_weight path in [diff_diff/prep.py#L993](/home/runner/work/diff-diff/diff-diff/diff_diff/prep.py#L993) does not match the canonical SDID/R unit-weight procedure documented in [docs/methodology/REGISTRY.md#L1434](/home/runner/work/diff-diff/diff-diff/docs/methodology/REGISTRY.md#L1434) and implemented by [diff_diff/utils.py#L1510](/home/runner/work/diff-diff/diff-diff/diff_diff/utils.py#L1510). The registry contract is centered Frank-Wolfe plus the 100-iter -> sparsify -> 10000-iter two-pass flow; this PR uses _sc_weight_fw(..., intercept=False, max_iter=1000) once and then applies a different absolute-threshold postprocessing rule. quality_score is still driven by RMSE/covariates rather than synthetic_weight at [diff_diff/prep.py#L1101](/home/runner/work/diff-diff/diff-diff/diff_diff/prep.py#L1101), so the ranking itself is not the issue; the public synthetic_weight column is now a different method than advertised. Concrete fix: either call the full SDID unit-weight routine (or reproduce its centered two-pass behavior exactly), or explicitly document/rename this as a different heuristic instead of claiming canonical SDID/R parity.

Code Quality

  • Severity P1. Impact: this PR rolls core release metadata back to 3.1.3 in [pyproject.toml#L7](/home/runner/work/diff-diff/diff-diff/pyproject.toml#L7), [diff_diff/__init__.py#L254](/home/runner/work/diff-diff/diff-diff/diff_diff/__init__.py#L254), [rust/Cargo.toml#L3](/home/runner/work/diff-diff/diff-diff/rust/Cargo.toml#L3), [CITATION.cff#L10](/home/runner/work/diff-diff/diff-diff/CITATION.cff#L10), [diff_diff/guides/llms-full.txt#L5](/home/runner/work/diff-diff/diff-diff/diff_diff/guides/llms-full.txt#L5), and it drops the current 3.2.0 changelog section starting at [CHANGELOG.md#L10](/home/runner/work/diff-diff/diff-diff/CHANGELOG.md#L10). On current main, that would mislabel the shipped code, confuse debugging/support, and break release hygiene across Python, Rust, docs, and citation surfaces. Concrete fix: remove these unrelated rollbacks from the PR and restore the current version/changelog state.

Performance

  • Severity P2. Impact: the diff removes the _PsuScaffolding / _compute_if_variance_fast acceleration from [diff_diff/prep.py#L1366](/home/runner/work/diff-diff/diff-diff/diff_diff/prep.py#L1366) and [diff_diff/survey.py#L1307](/home/runner/work/diff-diff/diff-diff/diff_diff/survey.py#L1307), and deletes the dedicated equivalence suite at [tests/test_prep.py#L3482](/home/runner/work/diff-diff/diff-diff/tests/test_prep.py#L3482). The accompanying BRFSS baseline refreshes in [benchmarks/speed_review/baselines/brfss_panel_large_python.json](/home/runner/work/diff-diff/diff-diff/benchmarks/speed_review/baselines/brfss_panel_large_python.json) and [benchmarks/speed_review/baselines/brfss_panel_large_rust.json](/home/runner/work/diff-diff/diff-diff/benchmarks/speed_review/baselines/brfss_panel_large_rust.json) re-baseline the pre-fix ~24-25s behavior, undoing the “sub-2s” win documented at [docs/performance-plan.md#L64](/home/runner/work/diff-diff/diff-diff/docs/performance-plan.md#L64) and [docs/performance-plan.md#L163](/home/runner/work/diff-diff/diff-diff/docs/performance-plan.md#L163). Concrete fix: keep the fast path and its equivalence tests in this PR; if you truly want to defer it, track that explicitly in TODO.md rather than silently normalizing the regression.

Maintainability

  • Severity P3. Impact: the cleanup is incomplete in [diff_diff/_backend.py#L63](/home/runner/work/diff-diff/diff-diff/diff_diff/_backend.py#L63) and [diff_diff/_backend.py#L112](/home/runner/work/diff-diff/diff-diff/diff_diff/_backend.py#L112), where _rust_synthetic_weights still exists in the force-Python branch and __all__ even though the Python-side import/fallback is being deleted. That leaves a stale private surface behind. Concrete fix: either remove the remaining _rust_synthetic_weights references from _backend.py, or keep a deliberate deprecation shim until the Rust symbol is actually removed.

Tech Debt

  • Severity P3 informational. Impact: the dead Rust compute_synthetic_weights binding is explicitly tracked in [TODO.md#L86](/home/runner/work/diff-diff/diff-diff/TODO.md#L86), so leaving the Rust-side cleanup for a follow-up is acceptable by the project’s deferred-work policy. Concrete fix: none required for approval, provided the Python-side surface is cleaned up coherently.

Security

  • No findings.

Documentation/Tests

  • Severity P3. Impact: the new regression coverage at [tests/test_prep.py#L769](/home/runner/work/diff-diff/diff-diff/tests/test_prep.py#L769) only checks that synthetic_weight is a non-degenerate simplex at one extreme-scale fixture. It does not assert the intended SDID/R contract that the code comment now claims, and the aggregate_survey fast-path equivalence suite is removed entirely at [tests/test_prep.py#L3482](/home/runner/work/diff-diff/diff-diff/tests/test_prep.py#L3482). Concrete fix: add a deterministic parity test for whichever rank_control_units method you actually intend to guarantee, and retain the aggregate_survey equivalence coverage if the fast path stays.

Path to Approval

  1. Make rank_control_units consistent with the intended methodology: either use the canonical centered two-pass SDID unit-weight path, or document/rename it as a different heuristic instead of claiming SDID/R parity.
  2. Revert the unrelated version-surface rollback and restore current metadata in Python, Rust, docs, changelog, and CITATION.cff.
  3. Restore the aggregate_survey scaffolding fast path and its equivalence tests, or explicitly track that performance regression in TODO.md if you intend to defer it.

…trol_units

Post-audit cleanup closing finding #22 from the Phase 2 silent-failures
audit. Replaces the previous "fix both backends" approach with "delete the
helper and inline correctly in its single caller."

Why the scope change. The audit found that Rust and Python backends of
`compute_synthetic_weights` solved different QPs (Python shrink-to-uniform,
Rust shrink-to-zero) with different step sizes (Python adaptive, Rust broken
constant 0.1 that diverges at large Y). Closer inspection revealed:
- `compute_synthetic_weights` was private (not in __all__).
- Its only non-test caller was `rank_control_units` in prep.py — a
  user-facing diagnostic that ranks control units by trend similarity.
- The `synthetic_weight` column it feeds into the `rank_control_units`
  DataFrame is informational only — `quality_score` is computed from
  RMSE + covariate distance, NOT from `synthetic_weight`. So the bug
  did not affect donor-selection ranking at all.
- `SyntheticDiD.fit()` does NOT use this helper. It uses the Frank-Wolfe
  solver (`compute_sdid_unit_weights` / `_sc_weight_fw_numpy`) directly
  on a completely independent code path that already matches R synthdid.

Maintaining two broken PGD implementations of a one-caller helper was not
worth the cost. Delete the shim and inline correctly.

Changes:
- Delete `compute_synthetic_weights` (utils.py:1134) and
  `_compute_synthetic_weights_numpy` (utils.py:1202).
- Remove `_rust_synthetic_weights` from the `_backend.py` import and
  None-fallback, and from the `__init__.py` re-export.
- Remove the import from `prep.py`.
- Inline a Frank-Wolfe computation in `rank_control_units` (prep.py:990)
  using the shared `_sc_weight_fw` dispatcher — same solver SDID uses,
  matching R `synthdid::sc.weight.fw`. Threads `zeta=sqrt(lambda_reg/N)`
  to absorb FW's (1/N) objective scaling, noise-level-scaled
  `min_decrease` per R convention, and `max_iter=1000` to preserve the
  existing cost envelope.
- Short-circuit `n_control in {0, 1}` to avoid FW loop overhead on
  trivially-solvable cases.
- Suppress non-convergence warnings inside the inline block — the caller
  uses the output as a heuristic score, not a statistical estimate.

Tests:
- Delete `TestSyntheticWeightsBackendParity` (3 tests, 2 xfailed) and the
  3 direct-Rust-import tests in `test_rust_backend.py`.
- Delete `TestComputeSyntheticWeightsEdgeCases` (4 tests) in test_utils.py.
- Delete `test_compute_synthetic_weights` in test_estimators.py.
- Add `test_extreme_Y_scale_synthetic_weight_column` in test_prep.py —
  regression guard asserting the `synthetic_weight` column produces a
  valid non-degenerate simplex vector at `Y ~ 1e9` (the exact input that
  the old Rust PGD mishandled by collapsing onto a single vertex).

SDID invariance. Verified via bit-identical pre/post numerical baseline on
a fixed-seed `SyntheticDiD.fit()` (n_units=30, n_pre=8, n_post=2,
n_treated=3, n_bootstrap=20, seed=42):

    before: att=1.1895009159752075  se=0.2576531609311485
             weight_sum=1.000000000000002e+00  weight_max=6.820411397386437e-02
    after:  att=1.1895009159752075  se=0.2576531609311485
             weight_sum=1.000000000000002e+00  weight_max=6.820411397386437e-02

Full SDID test suite: 40/40 pass before and after (test outcome diff
empty). All 305 tests in the touched files pass.

Rust-side `compute_synthetic_weights` PyO3 binding in `rust/src/weights.rs`
is now dead code (no Python code calls it). Tracked as a low-priority
cleanup follow-up in TODO.md; removal requires `maturin develop` rebuild.

User-visible impact:
- `rank_control_units` public signature and return schema unchanged.
- `quality_score` values and ranking: unchanged.
- `synthetic_weight` column: values agree with old code at ~1e-7 on
  default-parameter typical data; differ only in edge cases where the old
  code was mathematically wrong (extreme Y scale, lambda_reg > 0).
- Private imports break: `from diff_diff.utils import compute_synthetic_weights`,
  `from diff_diff._rust_backend import compute_synthetic_weights`. These
  were not part of the documented stable API.

Net diff: +110 / -367 lines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@igerber igerber force-pushed the fix/delete-synthetic-weights-helper branch from 69d3df3 to de21ef9 Compare April 20, 2026 11:45
@igerber
Copy link
Copy Markdown
Owner Author

igerber commented Apr 20, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: de21ef9c6c80baca826adf007eed53f8c294d0b3


Overall assessment

✅ Looks good. No unmitigated P0/P1 findings in the current diff. The prior methodology concern is addressed: the changed rank_control_units path is now explicitly documented as a heuristic, while canonical SDID unit weights still follow the registry-backed two-pass Frank-Wolfe path.

Executive summary

  • Previous P1s from the last review appear resolved: the current diff no longer contains the version rollback issue, diff_diff/_backend.py no longer re-exports _rust_synthetic_weights, and diff_diff/prep.py:L993-L1017 now correctly distinguishes the ranking heuristic from canonical SDID.
  • Cross-checking docs/methodology/REGISTRY.md:L1434-L1449 against diff_diff/utils.py:L1510-L1589 shows the actual SDID estimator path is unchanged and still matches the documented centered two-pass Frank-Wolfe procedure.
  • I found no P0/P1 issues in the changed math, weighting, variance, inference, or control-group logic. quality_score still ignores synthetic_weight and is computed only from outcome/covariate scores in diff_diff/prep.py:L1119-L1141.
  • One low-severity methodology note remains: a stale removal comment in diff_diff/utils.py:L1133-L1136 still overclaims R synthdid parity for the rank_control_units caller path.
  • Test coverage now targets the caller path, but the diff removes helper-level regularization/parity coverage and adds only one extreme-scale regression in tests/test_prep.py:L769-L804.
  • Static review only; I did not run the test suite in this environment.

Methodology

No P0/P1 findings. The changed code now correctly says rank_control_units is intentionally not the canonical SDID/R unit-weight procedure in diff_diff/prep.py:L1011-L1017, while canonical SDID remains the registry-matched path in docs/methodology/REGISTRY.md:L1434-L1449 and diff_diff/utils.py:L1510-L1589.

  • Severity P3. Impact: The cleanup note in diff_diff/utils.py:L1133-L1136 still says the sole caller now uses _sc_weight_fw “matching R synthdid::sc.weight.fw,” which contradicts the new explicit heuristic note in diff_diff/prep.py:L1011-L1017. The code is correct; the comment is not. Concrete fix: rewrite that utils.py note to say rank_control_units uses the shared Frank-Wolfe dispatcher for a single-pass uncentered heuristic, and point canonical SDID users to compute_sdid_unit_weights.

Code Quality

  • No findings.

Performance

  • No findings.

Maintainability

  • No findings.

Tech Debt

  • Severity P3 informational. Impact: The remaining Rust compute_synthetic_weights binding is still dead-but-callable, but this is explicitly tracked in TODO.md:L86-L86, so it is mitigated by the project’s deferred-work policy. Concrete fix: none required for approval; remove the dead Rust symbol in the planned Rust-cleanup PR.

Security

  • No findings.

Documentation/Tests

…+ backend-parity regressions

- diff_diff/utils.py: removed stale "matching R synthdid" language from the
  deletion-marker comment; now says the caller uses the shared FW dispatcher
  for a single-pass uncentered heuristic and points canonical SDID users to
  compute_sdid_unit_weights.
- tests/test_prep.py: two new TestRankControlUnits regressions covering the
  surface the reviewer flagged — (a) lambda_reg > 0 produces a valid simplex
  and does not collapse support, (b) full-pipeline Rust/Python backend parity
  via patch.object(utils_mod, "HAS_RUST_BACKEND", False).

512 passed, 14 deselected across tests/test_prep.py, tests/test_utils.py,
tests/test_estimators.py, tests/test_rust_backend.py; SDID fixed-seed
baseline unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@igerber
Copy link
Copy Markdown
Owner Author

igerber commented Apr 20, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 3e63058753bca7aa9afdb2da37c581a8751fd874


Overall Assessment

✅ Looks good — no unmitigated P0/P1 findings in the current diff.

Executive Summary

  • Previous re-review concerns are addressed in the changed code: the utils.py removal note now correctly distinguishes the rank_control_units caller path from canonical SDID, and the diff adds caller-level regressions for lambda_reg > 0 and backend parity in tests/test_prep.py:L806-L887 and diff_diff/utils.py:L1133-L1140.
  • The only affected method is the informational synthetic_weight path inside diff_diff/prep.py:L993-L1060. Cross-checking against the SDID registry entry docs/methodology/REGISTRY.md:L1434-L1534 and the canonical estimator implementation diff_diff/utils.py:L1514-L1595 shows SyntheticDiD.fit() methodology is unchanged.
  • I found no P0/P1 issues in estimator math, weighting, variance/SE, or control-group logic. quality_score still depends only on RMSE and covariate scores, not on synthetic_weight, in diff_diff/prep.py:L1118-L1157.
  • The remaining raw Rust compute_synthetic_weights binding is dead code but explicitly tracked in TODO.md:L86-L86, so it is mitigated per the project’s deferred-work policy.
  • Static review only: I could not execute the test suite in this environment because runtime dependencies are unavailable (numpy import fails here).

Methodology

No findings. The changed weighting logic is confined to the rank_control_units heuristic in diff_diff/prep.py:L993-L1060. The registry-backed SDID unit-weight path remains the documented two-pass centered Frank-Wolfe implementation in docs/methodology/REGISTRY.md:L1434-L1450 and diff_diff/utils.py:L1514-L1595.

Code Quality

No findings.

Performance

No findings.

Maintainability

No findings.

Tech Debt

  • Severity P3 informational. Impact: The raw PyO3 compute_synthetic_weights symbol remains dead-but-callable via diff_diff._rust_backend, but this is explicitly tracked in TODO.md:L86-L86 and no Python code still routes through it. Concrete fix: none required for approval; remove the Rust binding in the planned cleanup PR.

Security

No findings.

Documentation/Tests

  • Severity P3. Impact: The implementation now explicitly treats synthetic_weight as an informational heuristic that does not affect quality_score, but the public rank_control_units docstring still describes it only as “Weight from synthetic control optimization” in diff_diff/prep.py:L833-L842. In a methodology-sensitive library, that leaves the public contract less precise than the implementation notes. Concrete fix: update the docstring/API docs to say synthetic_weight is heuristic/informational and is not used in ranking or SDID estimation.

…_weight docstring

Docstring at prep.py:841 now states synthetic_weight is an informational
heuristic from a single-pass uncentered Frank-Wolfe solve, does NOT factor
into quality_score ranking, and is NOT the canonical SDID unit weight.
Directs users who want canonical SDID weights to SyntheticDiD.fit().

Aligns the public contract with the implementation notes already added in
the prior round.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@igerber
Copy link
Copy Markdown
Owner Author

igerber commented Apr 20, 2026

/ai-review

@github-actions
Copy link
Copy Markdown

🔁 AI review rerun (requested by @igerber)

Head SHA: 5dc6ba090e5f9fb7ff3ffbf70cc735dbf08f60d9


Overall Assessment

✅ Looks good — no unmitigated P0/P1 findings in the current diff.

Executive Summary

  • Re-review scope: the prior documentation concern is addressed. rank_control_units now explicitly documents synthetic_weight as heuristic-only, not part of ranking, and not the canonical SDID weight path in diff_diff/prep.py:L841-L844.
  • Affected method(s): only the informational synthetic_weight computation inside rank_control_units changed, via the inlined single-pass uncentered Frank-Wolfe solve in diff_diff/prep.py:L996-L1063.
  • Cross-check against the Methodology Registry shows canonical SDID unit weights are still the documented two-pass centered Frank-Wolfe procedure in docs/methodology/REGISTRY.md:L1434-L1450 and diff_diff/utils.py:L1514-L1595.
  • I found no P0/P1 issues in estimator math, weighting, variance/SE, control-group logic, or default estimator behavior. quality_score still depends only on outcome/covariate scores, not on synthetic_weight, in diff_diff/prep.py:L1121-L1158.
  • Python-side cleanup is consistent: the deleted helper/shim was removed from package imports and backend exports with no remaining Python callers in diff_diff/utils.py:L1133-L1140, diff_diff/_backend.py:L18-L39, and diff_diff/init.py:L17-L24.
  • Static review only: I could not run runtime tests in this sandbox because numpy is unavailable at import time, but all touched Python files parsed successfully, and the diff adds targeted caller-level regressions in tests/test_prep.py:L769-L887.

Methodology

  • No findings. The changed path is the rank_control_units heuristic only, not SyntheticDiD.fit(). The inlined objective mapping zeta = sqrt(lambda_reg / N_pre) is consistent with the shared Frank-Wolfe solver’s (1/N) ||Aw-b||^2 + zeta^2 ||w||^2 scaling in diff_diff/prep.py:L1003-L1012 and diff_diff/utils.py:L1300-L1395.

Code Quality

  • No findings.

Performance

  • No findings.

Maintainability

  • No findings.

Tech Debt

  • Severity P3 informational (tracked). Impact: the raw Rust binding compute_synthetic_weights remains callable dead code in rust/src/weights.rs:L43-L117 after the Python wrapper/tests were removed. This is explicitly tracked in TODO.md:L86, so it is mitigated under the project policy. Concrete fix: none required for approval; remove the Rust binding in the planned cleanup PR and rebuild the extension.

Security

  • No findings.

Documentation/Tests

  • No findings. The prior re-review documentation issue is fixed by the updated public docstring in diff_diff/prep.py:L841-L844.
  • No findings. Test coverage added for extreme outcome scale, lambda_reg > 0, and backend parity at the actual caller path in tests/test_prep.py:L769-L887. I could not execute them in this environment.

@igerber igerber added the ready-for-ci Triggers CI test workflows label Apr 20, 2026
@igerber igerber merged commit 9146f1e into main Apr 20, 2026
23 of 24 checks passed
@igerber igerber deleted the fix/delete-synthetic-weights-helper branch April 20, 2026 18:01
igerber added a commit that referenced this pull request Apr 20, 2026
Remove dead Rust compute_synthetic_weights (follow-up to PR #344)
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