Skip to content

Dissolution routing: revert each individual to its own single regime (synthetic mode) - #401

Closed
hmgaudecker wants to merge 1 commit into
collective-regimes-draftfrom
collective-sim-rowsplit
Closed

Dissolution routing: revert each individual to its own single regime (synthetic mode)#401
hmgaudecker wants to merge 1 commit into
collective-regimes-draftfrom
collective-sim-rowsplit

Conversation

@hmgaudecker

@hmgaudecker hmgaudecker commented Jul 12, 2026

Copy link
Copy Markdown
Member

Dissolution routing: revert each individual to its own single regime (synthetic mode)

Draft — stacked on the collective-regimes extension. Review the row-split commit; the base carries the extension.

Naming: this routes an individual to its own single regime when a collective/matched regime dissolves (the general D dissolution flag — empty feasible set). "Dissolution" is the engine term; in a marriage model (e.g. EKL) the instance is divorce.

Motivation

The collective-regimes extension's forward simulation is a fixed-size pass, so on dissolution a household row could not become two independently-tracked single rows — the continuing membership followed the first declared edge leg regardless of which partner the row represents. Wrong for any two-sided dissolution simulation.

The immediate driver is Eckstein–Keane–Lifshitz (2019) (ecksteinCareerFamilyDecisions2019). EKL's simulation (Appendix F) tracks individuals with synthetic partners: two independent single-gender cohorts, each drawing a synthetic spouse on marriage, no cross-population linkage. On dissolution each tracked individual must revert to its own single regime (single_f for women, single_m for men). The old "first leg" convention routed both cohorts to the same leg — silently wrong for one gender.

What this PR does (synthetic mode)

Adds own_stakeholder: str | None = None to the simulation router (route_gated_edges), threaded through simulate(). When set, a dissolved row routes to the leg whose source_stakeholder matches — its own single regime with its own projected state — instead of unconditionally legs[0]:

women = simulate(..., own_stakeholder="f")   # → single_f on dissolution
men   = simulate(..., own_stakeholder="m")   # → single_m on dissolution
  • own_stakeholder=None (default) reproduces the exact prior "first declared leg" routing — byte-identical for every existing caller.
  • New helper _select_own_leg(legs, own_stakeholder). No cross-row gather, no matcher, no new per-subject array — those belong to linked mode (below).

Synthetic vs the general two-sided (linked) split

A true two-way split — one row becoming two linked tracked singles, both ex-partners living on with transfers/asset-division between them — is a larger, separate feature (linked mode): a per-subject role state + partner_row link array, a model-declared matcher (a global cross-subject reduction the row-independent simulate deliberately avoids), and marriage-time pairing. Deferred and scoped in ROWSPLIT-STATUS.md (incl. a gather-only, no-scatter simplification). Synthetic mode is the minimum that makes EKL's Appendix-F simulation faithful. Linked mode is what these pool papers additionally require (both ex-partners tracked):

paper why linked mode
foersterUntyingKnotHow2024Untying the Knot alimony/child-support flow between the two ex-spouses
voenaYoursMineOurs2015Yours, Mine, and Ours: Do Divorce Laws Affect… endogenous divorce; separate post-divorce budgets/assets per spouse
reynosoImpactDivorceLaws2024Impact of Divorce Laws on the Marriage Market marriage-market equilibrium, both ex-spouses tracked
borellaAreMarriageRelatedTaxes2022 — Borella, De Nardi & Yang divorce shock splits wealth equally → tracked single man + single woman
denardiWageRiskGovernment2024Wage Risk and Government and Spousal Insurance wealth pooled on marriage, divided equally on divorce; separate single-m/single-f value fns

(Widowhood models — hongLifeInsuranceHousehold2012, denardiWhyCouplesSingles2025 — are couple→single with one surviving row, no two-way split; chiapporiMarriageMarketLabor2018 models no dissolution.)

Tests & verification

tests/regime_building/test_row_split_synthetic.py (3, green): own-role routing, own_stakeholder=None byte-identical regression pin, two independent cohorts differing only in landing regime. Broad tests/solution tests/simulation = 570/5/2 (default path byte-identical). ty + ruff clean.

🤖 Generated with Claude Code

…me (synthetic mode)

Row-split PLAN commit 1 (synthetic mode). `route_gated_edges` previously
routed a divorced row to the FIRST declared leg's fallback regardless of
which stakeholder the row actually represents. Adds `own_stakeholder`, a
single value for the whole `simulate()` call (not a per-subject array),
selecting the leg whose `source_stakeholder` matches the row's own role;
`None` (default) preserves the exact prior "first declared leg" behavior,
so every existing caller is byte-identical. This is EKL Appendix F's
design: two independent, single-gender cohorts (all-women, all-men), each
carrying a synthetic opposite-gender partner, each correctly reverting to
its own single regime on divorce.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@read-the-docs-community

Copy link
Copy Markdown

@hmgaudecker hmgaudecker changed the title Divorce routing: revert each individual to its own single regime (synthetic mode) Dissolution routing: revert each individual to its own single regime (synthetic mode) Jul 12, 2026
@github-actions

Copy link
Copy Markdown

Benchmark comparison (main → HEAD)

Comparing 28d7d93a (main) → 94b9fda8 (HEAD)

Benchmark Statistic before after Ratio Alert
aca-baseline execution time 13.527 s 13.817 s 1.02
peak GPU mem 588 MB 588 MB 1.00
compilation time 374.68 s 386.35 s 1.03
peak CPU mem 7.03 GB 6.66 GB 0.95
aca-baseline-debug execution time 58.332 s 60.564 s 1.04
peak GPU mem 587 MB 587 MB 1.00
compilation time 440.03 s 464.62 s 1.06
peak CPU mem 7.76 GB 7.77 GB 1.00
Mahler-Yum execution time 4.697 s 4.631 s 0.99
peak GPU mem 520 MB 520 MB 1.00
compilation time 11.33 s 11.57 s 1.02
peak CPU mem 1.58 GB 1.58 GB 1.00
Precautionary Savings - Solve execution time 24.2 ms 23.2 ms 0.96
peak GPU mem 8 MB 8 MB 1.00
compilation time 1.57 s 1.73 s 1.10
peak CPU mem 1.15 GB 1.15 GB 1.00
Precautionary Savings - Simulate execution time 61.9 ms 61.7 ms 1.00
peak GPU mem 157 MB 157 MB 1.00
compilation time 3.58 s 3.55 s 0.99
peak CPU mem 1.33 GB 1.33 GB 1.00
Precautionary Savings - Solve & Simulate execution time 94.9 ms 94.1 ms 0.99
peak GPU mem 566 MB 566 MB 1.00
compilation time 4.75 s 4.85 s 1.02
peak CPU mem 1.31 GB 1.32 GB 1.00
Precautionary Savings - Solve & Simulate (irreg) execution time 202.7 ms 197.2 ms 0.97
peak GPU mem 2.18 GB 2.18 GB 1.00
compilation time 5.11 s 5.11 s 1.00
peak CPU mem 1.37 GB 1.37 GB 1.00
IskhakovEtAl2017DCEGMSimulate execution time 232.8 ms
compilation time 4.40 s
peak CPU mem 1.70 GB
IskhakovEtAl2017DCEGMSolve execution time 1.999 s
compilation time 7.28 s
peak CPU mem 1.59 GB
IskhakovEtAl2017Simulate execution time 200.6 ms 202.0 ms 1.01
compilation time 4.18 s 4.40 s 1.05
peak CPU mem 1.29 GB 1.29 GB 1.00
IskhakovEtAl2017Solve execution time 44.5 ms 54.1 ms 1.21
compilation time 0.70 s 0.76 s 1.09
peak CPU mem 1.15 GB 1.16 GB 1.00
IskhakovEtAl2017DCEGMSimulateGpuPeakMem peak GPU mem 282 MB
IskhakovEtAl2017DCEGMSolveGpuPeakMem peak GPU mem 0 MB
IskhakovEtAl2017SimulateGpuPeakMem peak GPU mem 281 MB 281 MB 1.00
IskhakovEtAl2017SolveGpuPeakMem peak GPU mem 67 MB 67 MB 1.00

@hmgaudecker

Copy link
Copy Markdown
Member Author

Superseded by #409, which targets main and folds this branch's head into the audited collective-regimes tip. Closing.

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