Skip to content

Extend cd to mlsynth's feature set (PR B of 4) - #3

Merged
cmg777 merged 3 commits into
mainfrom
feat/cd-features
Oct 16, 2026
Merged

cmg777 merged 3 commits into
mainfrom
feat/cd-features

Conversation

@cmg777

@cmg777 cmg777 commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Stacked on #2. Once that merges, this PR retargets to main. It adds the Cao & Dowd features that mlsynth's SPILLSYNTH(method="cd") has and the merged cd model lacked. Each feature follows the paper's v3 definitions and is cross-checked against mlsynth.

Feature v3 reference API
Homogeneous spillovers Example 2 (Sec. 3.4) spillover_structure="homogeneous" with affected_units
Exponential distance decay Example 3 (Sec. 7.1) spillover_structure="distance_decay" with unit_distances
Several treated units, common start Sec. S.1.2 detected from the treat column; att_by_unit
Efficient (Ω⁻¹-weighted) estimator Sec. S.1.1 weighting="efficient", which fills result.cd.efficient
Pure-donor sensitivity Sec. 5.2.2 result.cd.sensitivity; plot kind "sensitivity"
Structure selection by κ_A Secs. 5.1.2 and S.1.3 result.select_structure_by_kappa(candidates)

Two choices worth a look:

  • Selection statistic. By default, structure selection uses the paper's multi-period κ_A (S.1.3): the norm of the summed residual vectors, divided by √T1. mlsynth instead averages the per-period norms. That variant is available as statistic="mean_norm", and the parity test uses it.
  • Efficient estimator. It is reported alongside the headline identity-weighted fit, point estimates only, as in mlsynth. It warns when T0 ≤ number of units, because Ω̂ is then singular without its ridge. That is the case for Proposition 99 (T0 = 19).

Validation

  • mlsynth fixtures. tools/freeze_mlsynth_cd_reference.py runs mlsynth 1.0.0 (cvxpy/CLARABEL) in a separate environment and writes benchmarks/reference/mlsynth_cd/reference.json. The README there records provenance and SHA-256s.
  • Parity test. test_cd_mlsynth_parity.py checks six cases plus structure selection. Coverage:
    • SCM weights within 1e-4, the difference between the two solvers;
    • effects, CIs, joint spillover test, κ_A, sensitivity weights and the efficient fit;
    • P values exactly (within one tie).
  • Live comparison. It also passes against a live mlsynth install, in a scratch environment.
  • Invariants. test_cd_features.py checks:
    • equal distances give the homogeneous structure;
    • per-unit and homogeneous agree with one affected unit;
    • a custom A equal to a built A gives the same results, and column rescaling only rescales γ;
    • W = I recovers the unweighted estimator;
    • the choice of headline unit doesn't change the effects;
    • staggered adoption is rejected, and sar still rejects two treated units.
  • Authors' code. The homogeneous κ_A matches the authors' code.
  • Full suite. make lint typecheck test: 306 passed, 3 skipped. Slow tests pass.

🤖 Generated with Claude Code

cmg777 and others added 3 commits October 16, 2026 10:50
tools/freeze_mlsynth_cd_reference.py runs mlsynth's SPILLSYNTH(method='cd')
building blocks in a separate environment (mlsynth, cvxpy, CLARABEL) on:
- the Cao-Dowd Proposition 99 panel: per-unit, homogeneous and
  distance-decay structures;
- synthetic panels: two treated units, efficient weighting, structure
  selection.
It writes benchmarks/reference/mlsynth_cd/reference.json, keyed by unit
label, with a provenance block (mlsynth 1.0.0, solver versions, source
SHA-256s). test_cd_mlsynth_parity.py compares scspill against it; a live
comparison also runs when mlsynth is importable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Exposure structures: spillover_structure 'homogeneous' (Example 2,
  one shared spillover) and 'distance_decay' (Example 3, b * exp(-d_i)
  from unit_distances), next to 'per_unit' and 'custom'.
- Several treated units with a common start date (Section S.1.2), each
  with its own direct effect; the headline is the first treated unit.
- weighting='efficient': the Omega^-1-weighted estimator of Section
  S.1.1, reported alongside the headline fit, with a warning when T0 is
  too short to estimate Omega.
- Pure-donor sensitivity (Section 5.2.2): bias weights of the
  spillover-aware and pure-donor estimators under missed spillovers, a
  'sensitivity' plot, and the pure-donor weights as the cd comparator in
  the 'weights' plot.
- Structure selection by kappa_A (Sections 5.1.2 and S.1.3), reusing the
  fit's SCM equations. The default statistic is the paper's multi-period
  kappa_A; statistic='mean_norm' is mlsynth's.

Invariant tests check that equal distances reproduce the homogeneous
structure, that one affected unit is the same per-unit or homogeneous,
that W = I recovers the unweighted estimator, and that the headline
choice does not move the effects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cmg777
cmg777 deleted the branch refactor/cd-architecture October 16, 2026 02:27
@cmg777 cmg777 closed this Oct 16, 2026
@cmg777 cmg777 reopened this Oct 16, 2026
@cmg777
cmg777 changed the base branch from refactor/cd-architecture to main October 16, 2026 02:28
@cmg777
cmg777 merged commit 6de2fb5 into main Oct 16, 2026
12 checks passed
@cmg777
cmg777 deleted the feat/cd-features branch October 16, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant