Repository navigation
Conversation
marginal_tax_rate and its relatives measure a rate by raising one adult's earnings in a branch that keeps only the simulation's listed inputs. Two defects made the branch disagree with a fresh simulation of the same household: - Simulation moves employment_income, self-employment income, weekly hours and long-term gains onto pre-response variables, and backfills state_code from state_code_str, after policyengine-core records input_variables. Only Microsimulation patched the list, so household branches erased the moved inputs. An Ohio parent earning $28,000 then drew $7,475 of Ohio Works First in the branch and reported a rate of -4.70; fresh simulations give 0.527. - Branches raised the employment_income aggregate, which programs that read pre-response income (TANF's earned income) never see, and left the base's cached monthly values in place. Microsimulation rates therefore missed TANF phase-outs. The moves and the input list now live in shared helpers used by both wrappers, and one branch builder raises the variable that holds each input for every branch-measured rate. marginal_tax_rate_on_capital_gains raised the capital_gains aggregate, which taxed the increase at ordinary rates; it now raises long-term gains. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9698 +/- ##
===========================================
- Coverage 100.00% 97.24% -2.76%
===========================================
Files 4 2 -2
Lines 76 145 +69
Branches 2 7 +5
===========================================
+ Hits 76 141 +65
- Misses 0 4 +4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Conflict: tests/policy/baseline/household/marginal_tax_rate.yaml. Both sides appended cases at the end of the file. Kept main's comment fix and its three-roommate case from #9653, then this PR's six Ohio base/alt/rate cases. The rate variables' docstrings from #9653 merged cleanly next to this PR's formula changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… moving inputs Rates measured in a reformed simulation with labor supply or capital gains elasticities now hold the responses at their simulated values. Since the branches raise pre-response inputs (employment_income_before_lsr, long_term_capital_gains_before_response), recomputing the responses there scaled them with the raise: a Texas worker's $1,000 raise became $971 of wages (rate 0.3166, fresh 0.2965), and a 20% capital gains rate read 0.3307. create_perturbed_branch, which every earnings and capital gains rate uses, now sets the three earnings responses, the capital gains response and the weekly hours response to their values in the simulation; neutralized ones, as in the behavioral response measurement branches, stay zero. Core branches a reformed simulation's baseline while constructing it, before the wrappers move inputs. That branch kept employment_income and weekly_hours_worked as inputs with empty pre-response variables and the stale input_variables, so its TANF counted no earnings and the baseline rate that labor supply responses compare against erased pre-response wages: an Ohio Works First parent got a -$416 response to a top-rate reform that does not touch them. Both wrappers now branch the baseline again after the moves, as core's subsample does. Adds tests for both (invariants 5 and 6) and corrects the #9653 comment on the component helper's adult loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 8, 2026
…9994) * Bind a subsampled reform's rebuilt baseline arm to baseline policy Core's subsample rebuilds the baseline arm as a branch of the reform and then hands it the replaced arm's policy. Before core 3.32.21 it does not rebind the branch's holders, so the baseline read the reform's neutralized income_tax ($0 instead of $4,016), and the arm kept the replaced arm as its own baseline. No core version points the policy back at the new arm or primes its private parameter-tracing root, so a traced baseline arm wrote its tracer onto the shared parameter tree (core 3.32.8). Clear the arm's baseline, rebind its holders and isolate its tracing, as construction does. Adds a regression test for both wrappers and their clones and a Hypothesis property that subsampling never changes which policy a holder belongs to. Found in review of #9698 (P3-1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop holders a baseline arm cannot use, at construction and after subsampling A reform's baseline arm is a branch of the reform, so its holders were built for the reform's variables. Rebinding one to the baseline's variable kept the holder's population and storage, so a reform that moved a variable to another entity, or into or out of ETERNITY, left the baseline arm with storage its own variable cannot use (core 3.32.8; core 3.32.21 drops these holders itself). Drop them with their recorded inputs, as core 3.32.21's binder does, on both baseline-arm paths. apply_reform keeps a simulation's own inputs, as core's does. No shipped reform changes a variable's entity or ETERNITY-ness. Tests now also check that each holder suits its variable, and the property test's structural reform redefines is_household_head as yearly. The regression test reforms the supplied system instead of rebuilding policy from source. Addresses review r1 of #9994. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
MaxGhenis
added a commit
that referenced
this pull request
Oct 11, 2026
…'s branch Review r1 of #10058 found two ways the branch lost an input: - A capital_gains total set through set_input after construction is not among the construction inputs the branch keeps, so the branch dropped it and rebuilt the total from long-term gains: 73.4 and 231.9 instead of 0.188. The branch now raises the parent's total whenever set_input stored one for the period, at construction or later (tools.period_branch.has_input). - A variable given for one period kept every period's array, so a 2026 value calculated before branching survived when the input was for 2025 (adjusted gross income for 2025 measured 0.000). The branch now drops what was calculated for periods the input was not given for (drop_inherited_values limited to those variables). Also neutralize the hours response in the behavioral response measurement branches, as #9698 does: a capital-gains-first request with labor supply elasticities otherwise re-entered the measurements through hours and raised a CycleError. In labor-first order those branches already returned zero for it through the labor supply response's re-entry guard. Tests: a late-set total (Python), an input for another year and New York's $25 million threshold, which the scaled rise spreads over $24,985 (YAML). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
added a commit
that referenced
this pull request
Oct 24, 2026
* Measure the capital gains marginal tax rate on long-term gains marginal_tax_rate_on_capital_gains raised capital_gains, the sum of short- and long-term gains. That reaches adjusted gross income but not net capital gain, so the rise was taxed as ordinary income: a Texas filer with $1 million of long-term gains measured 0.298 instead of 0.238, and raising the top capital gains rate from 20% to 28% left a top-bracket filer's rate, and so the realization response, unchanged. Raise long_term_capital_gains instead (the change PR #9698 makes inside its wider rework of marginal-rate branches), and scale the rise to 0.1% of the household's net income or the person's gains above $1 million, because a $1,000 rise resolves a $100 million household's float32 net income only to 0.008 of rate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Correct the top rate at which the ordinary-rate tax takes over for $100 million of gains Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Raise a capital_gains total given as an input along with long-term gains capital_gains adds short- and long-term gains, and adjusted gross income reads it. A situation that gives the total keeps it as given, so raising only long_term_capital_gains never reached such a household's income and its rate measured 0.84 to 1.0 instead of 0.188. Raise the given total by the same amount, and cover it in the branch-versus-new-simulation property. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Move fixed capital gains rate cases to YAML and run the property tests in heavy-a Fixed household examples (each 2026 Texas bracket, the alternative minimum tax band, $20M and $100M of gains, wages under gains, joint filers, and capital_gains totals given as inputs) are YAML cases now, per the repo's test design rules. The Python file keeps what YAML cannot do: the Hypothesis properties, reforms read through the measurement branches, and the differential against new simulations, whose reference now raises a stored capital_gains total for every adult when any adult gives one (it found a household where one spouse gave a zero total). The direct reform property runs three examples instead of four. The file took 246 s of the core Rest group in CI run 38056548883, so it moves to heavy-a, which had the lowest time and peak memory of the groups there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keep late capital_gains totals and period-specific inputs in the rate's branch Review r1 of #10058 found two ways the branch lost an input: - A capital_gains total set through set_input after construction is not among the construction inputs the branch keeps, so the branch dropped it and rebuilt the total from long-term gains: 73.4 and 231.9 instead of 0.188. The branch now raises the parent's total whenever set_input stored one for the period, at construction or later (tools.period_branch.has_input). - A variable given for one period kept every period's array, so a 2026 value calculated before branching survived when the input was for 2025 (adjusted gross income for 2025 measured 0.000). The branch now drops what was calculated for periods the input was not given for (drop_inherited_values limited to those variables). Also neutralize the hours response in the behavioral response measurement branches, as #9698 does: a capital-gains-first request with labor supply elasticities otherwise re-entered the measurements through hours and raised a CycleError. In labor-first order those branches already returned zero for it through the labor supply response's re-entry guard. Tests: a late-set total (Python), an input for another year and New York's $25 million threshold, which the scaled rise spreads over $24,985 (YAML). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Test that response order does not matter and that the branch helpers keep inputs by period - test_responses_do_not_depend_on_which_is_requested_first: with labor supply and capital gains elasticities set, requesting either response first gives both the same nonzero values. At the previous head the capital-gains-first request raised CycleError. - test_drop_inherited_values_limited_to_variables: limited to some variables, drop_inherited_values leaves every other array alone, and has_input reports the periods set_input stored, from the situation or later. - Correct comments: SNAP reads hours before the labor supply response, so Texas child care is the program named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Document how the capital gains rate is measured and test the revenue peak #10054 merged first, so this PR carries what was agreed for whichever merged second: - docs-quarto/methodology/capital-gains-responses.qmd: replace "Known limitation: how the rate is measured" with "How the rate is measured" (the rise and its scaling, the cliff trade-off, a Texas rate table with the AMT band, the 1(h)(1) cap); add a filer's own revenue peak to the semi-elasticity section; call the single-rate analytical peak the unique maximum when beta(1 - s) - ln beta > 1, with a 100% federal rate tying at equality (#10054 round-3 review). - test_federal_income_tax_peaks_at_the_revenue_maximizing_top_rate: with semi_elasticity 1/0.30, a Texas filer with $20M of gains pays the most federal income tax at the top rate solving r + 3.8% = (1 - 545,500/A)/beta, 25.23%; realizations and tax match their closed forms. - Fold the 28% measurement-branch case into the response-order test, which now also checks the measured rate changes for four filers, saving one reformed simulation with full measurements in CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Run heavy-a in Rest shard 1 and simulate the revenue peak once Main's core group grew to 43:46 (run 40031737740), putting shard 2's groups at 69.0 minutes before this PR's file, which made heavy-a 16:18 in run 39507589106. heavy-a now runs in shard 1 (48.8 minutes on main). The revenue-peak test simulates only the peak and checks the closed form at the neighbouring rates, cutting about 150 s. The docs say how a higher top rate moves each row of the Texas table and note the alternative minimum tax sliver just above 28%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Qualify the revenue-peak closed form and refresh heavy-a's CI figures The closed form holds at the top rates near the peak, not in the alternative minimum tax sliver or past the 1(h)(1) cap. heavy-a's figures are from run 40045994536, in GiB. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #9697.
Summary
Marginal-rate branches now perturb the inputs that programs actually read, preserve moved wages, hours and capital gains, and clear cached monthly values before applying the increase. The earnings, health-benefit and component rates share the same adult loop; the capital-gains rate raises long-term gains so preferential rates apply. For the reported Ohio parent earning $28,000, the corrected earnings rate is 0.5271.
Without behavioral responses, branch rates match finite differences between fresh simulations. Under dynamic reforms, they match fresh simulations with the parent's earnings, capital-gains and hours responses held at their simulated values. Setting responses to zero only in the raised branch would change behavior between the two sides of the comparison; freezing the same responses preserves the marginal-rate interpretation.
Both simulation wrappers synchronize
input_variablesafter moving inputs and backfilling geography, then rebuild the reform's baseline with those inputs. This also changes exported input names: datasets exported throughinput_variablesretain pre-response variables instead of stale aggregate columns, includinglong_term_capital_gains.Round 3 fixes
UserReformregression must calculate $4,016 of baseline income tax while the reform calculates $0; reform-only inputs must not leak into baseline cache invalidation.Validation
Python tests run one file at a time on macOS using Python 3.13.9 and compatible Core 3.32.15. Local wall times include setup and reflect heavy host contention; they are not Linux CI benchmarks. Python API coverage is required for branch ownership, tracing, cold-cache query order and shared measurements. The new tracing case stays in the existing SPM group; the new combined-response file routes into the existing Rest/core group. No CI runners were added. The failed-probe timings stop at different assertions and are not cost deltas for the new tests.
New head:
37bb63197728f0d481b0782609321872f7bd0a29(fast-forward from792e7d4dbb; no merge in this round).make format: passed before each commit; formatting and lint pass.gh pr checkson the new head: confirmed on new head 37bb631; CI started and the initial three guard checks are queued (run 37630379186). Full-suite CI is pending, not yet confirmed green.Historical differential sweep, reported by earlier rounds and not rerun in round 3:
Impact
pending (hub), after round 3.
Earlier impact batches never ran: the private zsh runner aborted on its read-only
statusvariable. No microsimulations were run in round 3. The baseline correction affects reform comparisons, so the hub must run all eight paired batches on the default dataset, comparing merge base47ab53e0fb13eb2e7dfe91025f94f6f2c4310c89against37bb63197728f0d481b0782609321872f7bd0a29:Use the existing top-rate reform (47%), CBO labor-supply elasticities (income −0.05, substitution 0.25), and capital-gains elasticity −0.62. Report person cliff counts/shares, mean cliff gap, mean MTR overall and among TANF recipients, static budget/poverty/distribution comparisons, and dynamic budget effects relative to the static reform within each version. Unreformed static budget, poverty and distribution outputs do not read marginal rates and are expected to be unchanged; population confirmation remains pending. Reform-versus-baseline comparisons, cliff measures and dynamic results require the hub runs after the baseline correction.
Remaining review points
Round 4 fixes
Head:
a729eecd02f9121f0a0a2712268d44e2af7fe952. The redo verified the two existing fast-forward commits,bc17da2fdfae1824170225571e7a925449594f66(P1) anda729eecd02f9121f0a0a2712268d44e2af7fe952(P2), and recovered their completed CI results. No merge, history rewrite, or additional source changes were needed in the redo.set_inputcalls through the shared US simulation mixin. Each branch receives a separate input list; non-input calculated caches still clear, and neutralized or expired writes do not become inputs. Raised branches retain their measuring branch's explicit identification and other inputs, except the perturbed earnings/gains. The fix also reaches health-inclusive rates and both baseline/reform behavioral measurement arms.Recovered failing evidence at reviewed head
275b87b040a39dfc3c0c246c6947622447d0256b, on macOS with Python 3.14.7 and Core 3.32.15:has_itin=[False, True, True]: federal MTR was -4.28 instead of 0.12, restoring $4,400 CTC in the raised branch. Health-inclusive MTR was -12.481421 instead of 0.1965. The generated input-preservation invariant also failed because raisedhas_itinwas absent: 3 failed, 12 deselected (372.56s).All four requested files passed individually and sequentially in CI run 37687411397, selective job 113055230778, associated with PR head
a729eecd. GitHub tested merge commit2dffe27e73ac39bc8b9e7238dcef529187b9a114, which includes six newer main changes. All round-4 changed files and the four requested test files are identical between that tree and the literal PR head; those incoming policy changes do not affect the new branch-input regressions. Linux used Python 3.14.7, pytest 8.4.2 and Core 3.32.21. These are completed CI runs, not fresh local full-file runs in the redo.test_marginal_rate_branches.pytest_combined_behavioral_responses.pytest_spm_system.pymarginal_tax_rate.yamlTotal: 105 passed across the four files. The Rest Python job explicitly names the federal, health, property and combined measurement regressions as passing. The generated invariant covers boolean, enum, string and money inputs; monthly, annual and eternal periods; nested branches; cache recomputation; and caller/sibling isolation. The combined regression observes real raised branches in all four baseline/reform earnings/capital-gains paths. Each selective file reports one plugin assertion-rewrite warning. All 37 checks, including lint, partner contracts and the full SPM/microsimulation CI job, are successful for this PR head.
New-case selective call times were 1.08s (federal), 2.16s (health), 2.99s (property) and 35.93s (combined measurement preservation), with 5.67s shared fixture setup for the identification cases. No per-file peak RSS is available. Existing CI resource artifacts report 9,351,764 kB for the 724-test Rest/core group and 5,361,316 kB for the 113-test SPM group; these are whole-group measurements. Local before timings reflect host contention and a different Core version, so they are not Linux before/after cost benchmarks. New API/branch tests remain in Rest/core; the unchanged SPM file stays in its existing CI group. No CI jobs or runner budgets were added. Repository dependency and lock files were not changed. Prior
make formatruns and CI lint both passed.Additional local check at literal head
a729eecd, using the same Python 3.14.7 / Core 3.32.15 environment as the recovered failures: the federal, health and generated input-preservation regressions all passed (3 passed, 12 deselected, 4 existing numerical divide warnings). Pytest reported 3,474.20s including collection/setup; end-to-end foreground invocation was 3,955.40s, with 45.46s user CPU, 12.57s system CPU and 1.14 GiB peak RSS. Shared fixture setup was 95.99s; case calls were 5.73s, 10.81s and 11.51s. These macOS measurements reflect severe host contention and are not Linux CI cost comparisons. The process finished normally; no interruption or additional local test process was needed. The four full-file results and behavioral regression passing evidence above come from the completed CI runs.Impact: P1 is test-only. The P2 source audit finds no expected numeric change in the hub's recorded eight default-dataset batches (2025/2026 base, static top-rate, CBO labor supply and capital-gains response). Construction-time inputs remain equivalent; internal measurement setters copy already preserved primitives or unchanged zero defaults, and sibling input lists stay isolated. Market-income/tax-first queries start shared measurements at the top-level simulation before policy-choice branches. The running impact at
37bb631977therefore remains applicable to P2. This conclusion is scoped to those recorded paths; direct cold dynamic queries inside policy override branches can now preserve their explicitly set overrides and change accordingly. Main's school-meal parameter update is already in the merged head and is separate from the test-only P1 refresh. No population microsimulations were run in this redo. Hub impact remains pending; CI for this PR head is green.Merge with main
Merged main
f9b55fc4dd05f1bed921a4dd06cb8c81f6ceb2c7intoa729eecd02f9121f0a0a2712268d44e2af7fe952using merge commit2c1029deeb176d69a5a081f6233d7cddcb6d2d22, followed by period-specific input fix3f807d71ca409e421f86d6c56a2beee280171933. No rebase or history rewrite. This section records validation at the merged head; earlier head and CI references remain historical.Conflict resolutions:
policyengine_us/system.py: keep the shared pre-response input migration, synchronized input registry, and post-migration baseline rebuild; retain main'sdelete_input_valuescleanup of obsolete replay keys in both simulation wrappers.policyengine_us/variables/household/marginal_tax_rate_helpers.py: keep the shared earnings/component perturbation and held measured responses; retain Fix IRA deduction phase-outs and compensation limits #10032'suser_set_variablesand use it inside the shared helper to preserve supplied IRA amounts while clearing formula-internal SSA holder writes.policyengine_us/variables/household/marginal_tax_rate.py: retainmarginal_earnings_change, with Fix IRA deduction phase-outs and compensation limits #10032 input preservation applied by the shared helper.policyengine_us/variables/household/marginal_tax_rate_including_health_benefits.py: retain the shared health-inclusive earnings path with the same input preservation.policyengine_us/variables/gov/simulation/capital_gains_responses.py: retain main's response forms/validation and the PR's existing perturbation call. The capital-gains MTR class and loop are unchanged froma729eecd02.The auto-merged behavioral measurement code keeps main's private baseline-policy measurement branches and cache isolation alongside this PR's hours-response neutralization. The migrated-input regression now expects the deliberately rebuilt baseline to hold pre-response keys; it still checks source/baseline privacy and explicit override retention. d1021's reuse rule is preserved: MTR and perturbation branches hold behavioral responses at the household's measured values.
A merge regression exposed a cross-period interaction with #10032: setting a Roth IRA contribution for 2027 could preserve a calculated 2026 contribution in the raised branch. On locked Core 3.32.27, the new case failed with $500 in the raised branch versus $1,000 in a fresh reference. The shared perturbation helper now applies existing
drop_inherited_valuesafter its variable-name filter, preserving exact supplied branch/period keys while clearing other-period calculated caches. The regression checks both IRA allocations, the explicit next-year $2,000 contribution, and parent isolation. It reuses the existing read-only fixture; no full-model setup or CI runner was added. The capital-gains loop waits for #10058.Validation at
3f807d71ca409e421f86d6c56a2beee280171933: 369 passed across 37 files, each run individually, sequentially, in the foreground withOMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4 VECLIB_MAXIMUM_THREADS=4 NUMEXPR_NUM_THREADS=4 OMP_WAIT_POLICY=PASSIVE. This covers every PR-added/changed test file, all marginal-rate files, branch/perturbation and behavioral measurement coverage, and every test file changed by #10032 (plus the two later IRA variable files). Python 3.13.9 used the prescribed shared executable and a workspace-local copy of lockfile-pinned Core 3.32.27; dependency and lock files match merged main.make formatpasses. No population microsimulations were run.policyengine_us/tests/)core/test_behavioral_response_measurement_copies.pycore/test_combined_behavioral_responses.pycore/test_spm_system.pycore/test_marginal_rate_branches.pycore/test_marginal_tax_rate_coverage.pycore/test_behavioral_response_measurements.pycore/test_override_branches.pycore/test_has_tin_branch_inputs.pypolicy/baseline/household/federal_marginal_tax_rate.yamlpolicy/baseline/household/fica_marginal_tax_rate.yamlpolicy/baseline/household/marginal_tax_rate.yamlpolicy/baseline/household/marginal_tax_rate_computed.yamlpolicy/baseline/household/marginal_tax_rate_including_health_benefits.yamlpolicy/baseline/household/state_marginal_tax_rate.yamlcore/test_dependent_above_the_line_deductions_invariants.pycore/test_tax_unit_non_dep_add_projector.pypolicy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yamlpolicy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yamlpolicy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.yamlpolicy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yamlpolicy/baseline/gov/irs/income/taxable_income/deductions/qbid/estate_income_would_be_qualified.yamlpolicy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yamlpolicy/baseline/gov/irs/ira/ira_active_participant.yamlpolicy/baseline/gov/irs/ira/ira_phase_out.yamlpolicy/baseline/gov/irs/ira/policybench_ira.yamlpolicy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yamlpolicy/baseline/gov/states/mi/tax/income/household_resources_adjustments.yamlpolicy/baseline/gov/states/mn/tax/property/homestead_credit_refund/mn_homestead_credit_refund_household_income.yamlpolicy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/retirement/pr_retirement_deduction.yamlpolicy/baseline/household/expense/retirement/ira_compensation.yamlpolicy/baseline/household/expense/retirement/ira_contribution_limit.yamlpolicy/baseline/household/expense/retirement/self_employed_pension_contribution_limit.yamltest_ira_formula_differential.pytest_mi_household_resources_adjustments.pypolicy/baseline/gov/irs/ira/ira_219g_magi.yamlpolicy/baseline/gov/irs/ira/ira_219g_taxable_social_security.yamlunit/test_ira_invariants.pyThe new regression's isolated before probe failed in 1.09s pytest time / 24.04s including startup; the fixed probe passed in 0.58s / 12.63s, with 0.03s setup and 0.46s case time. These probes stop at different assertions and are not a before/after performance benchmark. Per-case peak RSS is unavailable (
/usr/bin/time -lcannot read the sandbox's kernel clock data). The existing Rest/core CI group and runner budget are unchanged. An additional pre-fix merge check on shared Core 3.33.2 passed all 16 behavioral-copy tests (877.20s pytest / 920.17s wall); the table above is the complete final-head validation on locked Core.The hub reruns base and static impacts at the merged head; dynamic (LSR+CG) runs need a memory window that the hub schedules separately.
Review r5: responses
The cross-year aggregate-input P2 is fixed.
user_set_variables(sim, period)now checks visible(variable, branch, period)input keys, with eternal inputs valid in every period. The shared perturbation helper uses that measured-period registry to choose aggregate versus pre-response targets. The IRA fallback passes through the same period filter. Cache cleanup intentionally starts with all supplied variable names, thendrop_inherited_valuesretains only exact supplied keys, preserving the 2027 input while clearing calculated 2026 caches. The no-period API remains compatible with the IRMAA reference helper. d1021 still applies: measured behavioral responses remain fixed.Fetched PR head
3f807d71ca409e421f86d6c56a2beee280171933and mainf9b55fc4dd05f1bed921a4dd06cb8c81f6ceb2c7;git merge gh/mainreported already up to date. No conflict resolutions or history rewrites were needed.The regression uses
OHIO_WORKS_FIRST_PARENTwith a branch-local aggregate input, checks both future-year and measured-year inputs, compares against independent fresh references, and verifies source/branch isolation and the supplied 2027 value.The unchanged new regression produced 1 failed, 1 passed before and 2 passed after on Python 3.13.9 / locked Core 3.32.27. It needs Python coverage because YAML cannot exercise post-construction branch inputs. The fixture builds three independent household models once and returns one read-only source plus scalar references. Local before/after elapsed times, including startup, were 38.93s / 30.56s (pytest 9.84s / 6.33s). The passing fixture setup took 2.29s and calls 0.61s / 0.63s. Since the failing run stops at an earlier assertion, these figures are execution evidence rather than a performance delta. Peak RSS is unavailable. The tests remain in the existing Rest/core group; no CI jobs or runner budgets changed.
Validation at
23b7f445dbe8164377b7996c886654b382798780: 371 passed across 39 files. Each file ran in a separate foreground process, sequentially, using the prescribed shared Python 3.13.9 interpreter and workspace-local cached Core 3.32.27 source. Every process setOMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4 VECLIB_MAXIMUM_THREADS=4 NUMEXPR_NUM_THREADS=4 OMP_WAIT_POLICY=PASSIVE. All MTR files, branch/perturbation files, every test file changed by #10032, and the existing cross-period IRA regression passed. The additional labor-supply guard and IRMAA helper compatibility files passed too. 26 Microsimulation cases were deselected (2 in the MTR branch file, 24 in SPM); no Microsimulation cases ran.make formatandgit diff --checkpass. No dependency, lockfile, Makefile or workflow changes. New-head CI and population impacts are left to the hub.Paths below are relative to
policyengine_us/tests/; wall times include interpreter startup and cleanup, and are local macOS measurements.core/test_marginal_rate_branches.pycore/test_marginal_tax_rate_coverage.pycore/test_override_branches.pycore/test_has_tin_branch_inputs.pycore/test_behavioral_response_measurement_copies.pycore/test_combined_behavioral_responses.pycore/test_behavioral_response_measurements.pycore/test_spm_system.pypolicy/baseline/household/federal_marginal_tax_rate.yamlpolicy/baseline/household/fica_marginal_tax_rate.yamlpolicy/baseline/household/marginal_tax_rate.yamlpolicy/baseline/household/marginal_tax_rate_computed.yamlpolicy/baseline/household/marginal_tax_rate_including_health_benefits.yamlpolicy/baseline/household/state_marginal_tax_rate.yamlcore/test_dependent_above_the_line_deductions_invariants.pycore/test_tax_unit_non_dep_add_projector.pypolicy/baseline/gov/irs/credits/retirement_saving/savers_credit_person.yamlpolicy/baseline/gov/irs/credits/retirement_saving/savers_credit_qualified_contributions.yamlpolicy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_social_security.yamlpolicy/baseline/gov/irs/income/taxable_income/adjusted_gross_income/irs_gross_income/social_security/dependent_taxable_ss_magi.yamlpolicy/baseline/gov/irs/income/taxable_income/deductions/qbid/estate_income_would_be_qualified.yamlpolicy/baseline/gov/irs/ira/dependent_traditional_ira_deduction.yamlpolicy/baseline/gov/irs/ira/ira_active_participant.yamlpolicy/baseline/gov/irs/ira/ira_phase_out.yamlpolicy/baseline/gov/irs/ira/policybench_ira.yamlpolicy/baseline/gov/states/ca/tax/income/deductions/itemized/ca_itemized_deductions_pre_limitation.yamlpolicy/baseline/gov/states/mi/tax/income/household_resources_adjustments.yamlpolicy/baseline/gov/states/mn/tax/property/homestead_credit_refund/mn_homestead_credit_refund_household_income.yamlpolicy/baseline/gov/territories/pr/tax/income/taxable_income/deductions/retirement/pr_retirement_deduction.yamlpolicy/baseline/household/expense/retirement/ira_compensation.yamlpolicy/baseline/household/expense/retirement/ira_contribution_limit.yamlpolicy/baseline/household/expense/retirement/self_employed_pension_contribution_limit.yamltest_ira_formula_differential.pytest_mi_household_resources_adjustments.pyunit/test_ira_invariants.pypolicy/baseline/gov/irs/ira/ira_219g_magi.yamlpolicy/baseline/gov/irs/ira/ira_219g_taxable_social_security.yamlcore/test_labor_supply_behavioral_response_guard.pypolicy/baseline/gov/hhs/medicare/test_irmaa_lookback_and_switch.pyaxiom: n/a: simulation infrastructure; no statutory provision changes.