Repository navigation
Recalculate after each modifier step on the built UK simulation - #718
Conversation
The UK runner applies the policy modifier, the dynamic's late parameter values and the dynamic modifier to the built simulation. policyengine-uk keeps every value it calculates, so a value calculated before one of these steps kept its old value: late dynamic values missed values a policy modifier had calculated, and values calculated before a modifier's set_input did not include it. Each step now ends with drop_computed_arrays(), which keeps every input (including those a modifier stores with set_input) and drops the rest, in finally so a failed step drops too before the error ends the run. Fixes #717 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review at I ran everything below in a fresh clone and two fresh venvs: the pinned bundle (policyengine-uk 2.132.6, core 3.33.3) and the same with policyengine-uk at #2673's head Answers to what we raised
Findings
What I ran
🤖 Generated with Claude Code |
|
Thanks, Vahid. No code changes from this round; replies by number.
Thanks too for the checks beyond ours: the input-only variables, the dispatch handler and the yearly input on a MONTH variable cover the case core's docstring leaves open. 🤖 Generated with Claude Code |
Fixes #717
The UK runner applies three steps to the built
policyengine_uk.Microsimulation: the policy's simulation modifier, the dynamic's parameter values (written after a policy modifier, as #575 kept), and the dynamic's simulation modifier. policyengine-uk keeps every value it calculates, so a value calculated before one of these steps kept its old value. Each step now ends withmicrosim.drop_computed_arrays(). That keeps every input, including values a modifier stores withset_input, and deletes everything else, which is calculated again when next read. It runs infinally, so a failed step drops too before the error ends the run.The order (policy values, policy modifier, late dynamic values, dynamic modifier) and #575's date handling are unchanged. A run without a simulation modifier reaches none of the three steps, so it is unchanged.
drop_computed_arraysis in policyengine-core from 3.33.0; the bundle pins 3.33.3.What changes (one synthetic parent and child, 2027)
set_input, on the pinned policyengine-uk. A modifier that calculates household net income and then stores Child Benefit £1,000 higher left net income £1,000 short in the output. policyengine-uk#2673 fixes this inside policyengine-uk; this change also fixes it on 2.132.6.Split with policyengine-uk#2673
With #2673 installed and the wrapper unchanged, both late-dynamic-value cases and the failure case still fail; only the
set_inputcase passes. policyengine-uk does not watch the parameter tree (#2673 leaves that to the caller), so both changes are needed. Production use of the labour supply part also needs a policyengine-uk release with #2673 and a pin bump here; this PR does not change any pin.Checks
New
tests/test_uk_modifier_cache.py(the real model and one synthetic family, no mocks):set_inputstays through every step, while net income it calculated before is refreshed;All four fail on main and pass with this change, on 2.132.6 and on the #2673 head.
test_population_route_applies_policy_before_dynamic: the recording stub now records each drop, so its eight policy/dynamic combinations pin where the drops happen.No-op check: with modifiers that calculate a dozen outputs but change nothing, plus late dynamic values that change nothing, every output column for 25 people in 12 synthetic households is bitwise identical to a run without modifiers (75 columns on 2.132.6, 73 on the #2673 head).
Touched-surface suite (dated reforms, the new tests, budget reform contract, CGT responses, labour supply response, year files, extra variables, parametric and dict reforms, run records, execution receipts): 1,168 passed, 7 skipped (execution-receipt cases that need a token or another Python version), none failed.
Cost: none without a modifier. With one, what it calculated is calculated again. On a synthetic file of 50,000 people, a policy modifier that calculates net income before late dynamic values took about 2.4 s instead of 2.0 s; a modifier that reads Child Benefit and stores a new amount showed no difference.
ruff0.17.2 format and check are clean; mypy reports nothing new inmodel.py.Downstream
PolicyEngineUKLatest.runapplies modifiers to a built simulation. The US route merges everything into the construction reform, andmanaged_microsimulationandcalculate_householdrun no wrapper modifiers.Scenariodon't go through this code, including uk-autumn-budget-dashboard-2026, which runs them throughmanaged_microsimulation.PolicyorDynamic: uk-cgt-reform (pins 6.10.1), uk-cgt-paper (4.22.3) and macro (below 5). They get this change only when they upgrade. Their modifiers edit parameters, swap formulas, store inputs withset_input, or registersim.baselineas a branch. All of that survives the drops, and none of them calculates values before a later step.ensure()reuses an output saved under the same id, and this change does not mark outputs saved before it as stale. Only a run whose modifier calculated values before a later step changed them gives a different result now; such outputs should be run again.🤖 Generated with Claude Code