Repository navigation
Conversation
…ger period set_input_divide_by_period and set_input_dispatch_by_period treated any value stored for a sub-period as already set, including values the simulation had calculated (a cached default or formula result). The same annual input then gave different months depending on what was calculated first: a divided input skipped calculated months and shared the rest among the others, or failed as inconsistent after the year had been read; a dispatched input reused a calculated month for every later month. The helpers now read the simulation's record of inputs (_user_input_keys): a recorded sub-period keeps its input (and, for dispatch, passes it on to the later sub-periods, as before), and a calculated one is replaced. After storing, they drop the variable's calculated values over overlapping periods (the sums calculate_add caches, the twelfths calculate_divide caches) under the input's branch and the branches it reads through, record what they store as inputs, and evict calculate's fast cache for those periods. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
calculate does not put sums or twelfths in its fast cache today, so this is defensive: a value held there for a period whose stored value the helper drops goes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dump does not record which values were inputs, and restore_simulation stored every value with put_in_cache, so a restored simulation had an empty input record and the helpers replaced restored inputs (review finding 1). Restore now stores each value as a recorded input, the rule #576 applies to dumps without an inputs.txt. Tests: a restored month keeps its value under a yearly input (divide and dispatch); an input on a nested branch drops the sum its parent branch calculated; an input stored for twelve months from March is kept. The order-independence property now creates up to two nested branches, with calculations at each level. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On disk, a value calculated for a period like year:2013:2 is stored in a file named after the period, and Windows rejects ":" in file names (policyengine-core#526). The Windows CI jobs failed on such a read in the on-disk examples. In disk mode the property now skips those periods. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… replaces Review round 2: 1. Restoring every dumped value as an input froze calculated values through a later reform. The dumper now writes, next to each variable's arrays, the periods whose value was an input (inputs.txt), and restore records exactly those; a dump without the file restores every value as an input. This is policyengine-core#576's dumper change, taken byte for byte so the two PRs merge without conflict. 2. A value stored by put_in_cache over an input (say a calculate_add sum over an input set for twelve months) left the input's record entry in place, so the helpers and apply_reform kept treating the calculated value as an input. Holder._set now drops the entry, in both of its forms for twelve months from the first of a month, when it stores a value outside set_input. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…culate_add With policyengine-core#571, calculate_add no longer caches a sum over an input a plain read finds, so the input is kept and the test's premise (master's calculate_add overwriting it) did not hold. Storing the calculated value with put_in_cache replaces the input with or without #571. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ly for read branches Review round 3: - Holder._set no longer drops a record entry when a calculated value is stored over an input. A calculate_add at a variable's own period stores the unchanged input and lost its record; a clone, which shares its source's record until #561, deleted the source's entries; and ETERNITY variables keep entries under several periods. The case the cleanup was for, calculate_add caching a sum over an input, is what #571 stops. - Master now gives a storage that shares nothing a frozenset for _shared (#578). The drop step deleted from it directly; it now leaves that to InMemoryStorage._stop_sharing_dropped_keys. - The helpers evicted calculate's fast cache for a sub-period stored under any branch name, including one the simulation does not read. They now evict only for branches it reads, as #576's holder-write property requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
set_input for one of a variable's own periods (a month of a monthly variable, a year of a yearly one) replaced the stored value but left what calculate had derived from it at periods of another size: the sum of a monthly flow over a year, a yearly flow's twelfth at a month. A later calculate returned the old result, so it depended on whether that period was read before the input was set (#579). Warm annual flow 0, January set to 1: the annual read stayed 0. Holder.set_input now applies the rule the set_input helpers follow (_drop_calculated_overlapping): after storing an input for one of the variable's own periods, it drops the values the simulation calculated for the variable at overlapping periods, on the input's branch and the branches it reads, and evicts calculate's fast-cache entries for overlapping periods. Inputs are kept. ETERNITY variables are left alone. Stacked on #581, whose helper it reuses. Removes #581's property exclusion for #579, so test_inputs_do_not_depend_on_what_was_calculated_before now compares reads of other-size periods for every variable. On #581's head that property fails, shrunk to an annual flow_m read on a branch of depth 2; with this change it passes. Adds seven regression examples, all of which fail on #581's head. 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 #579. Stacked on #581 (base
fix-set-input-helper-order), whose helper it reuses. The diff below is this PR's change only.What this fixes
set_inputfor one of a variable's own periods (a month of a monthly variable, a year of a yearly one) replaced the stored value. It left whatcalculatehad derived from the old value at periods of another size: the sumcalculate_addstores for a monthly flow over a year, and the twelfthcalculate_dividestores for a yearly flow at a month. A latercalculatereturned the old result, so it depended on whether that period was read before the input was set. The audit's witness: annual flow read as 0, then January set to 1, and the annual read stays 0.#581 already drops such calculated values when a
set_inputhelper spreads a longer period (_drop_calculated_overlapping). #579 is the remaining path, an input that needs no helper. This PR applies the same rule there:Holder.set_input, after storing an own-period input of a non-ETERNITY variable, calls_drop_calculated_over_own_period. That drops what the simulation calculated for the variable at overlapping periods, on the input's branch and the branches it reads. Inputs are kept.calculate's fast-cache entries for the variable at overlapping periods, the input's own period included. The fast cache never holds the sums and twelfths themselves (I checked;calculate_addandcalculate_dividestore only in the holder). The eviction matters for inputs set on the holder directly, whereSimulation.set_input's exact-key pop never runs.Simulation.set_input, whose fast-cache block draft Drop every contained period from the fast cache when deleting or setting a period #566 rewrites.#581's property
test_inputs_do_not_depend_on_what_was_calculated_beforeexcluded other-size reads for variables given an own-period input, because of #579. This PR removes that exclusion.Invariants
calculatecalls in between on the simulation and up to two nested branches, and memory or disk storage: every plain read, at the variable's own periods and at periods of another size, equals the same read on a simulation that calculated nothing first. This is Count only inputs as already set when a set_input helper splits a longer period #581's property with the set_input for one period leaves the variable's cached sum or twelfth over an overlapping period in place #579 exclusion removed.Tests
test_own_period_input_overlapping_cache.py, 7 examples:test_set_input_helper_order_property.py: the set_input for one period leaves the variable's cached sum or twelfth over an overlapping period in place #579 exclusion removed.Before, on #581's head
917f0c23:After:
Ruff format and ruff check pass. I ran these files one at a time, because the shared host is saturated. This repository's PR workflow runs only for PRs into
master, so CI runs once this PR is retargeted, after #581 lands or after this PR is merged into #581's branch.Impact
Outputs can change wherever an own-period input is set after the simulation has calculated a sum or twelfth of that variable. policyengine-us
marginal_tax_rate, for example, sets inputs on branches after calculating. Dataset loading sets inputs before any calculation, so there is nothing to drop there.PE-US A/B (core
917f0c23vs this head, same policyengine-us checkout and data, 2026; household_net_income, income_tax, state_income_tax, snap, marginal_tax_rate): pending. It is queued behind the shared microsim lock, and I will add results here when it finishes.Known limits (from the independent review, which approved at
1fa8b034)Inherited, not introduced here: the rule trusts the simulation's record of inputs,
_user_input_keys, andSimulation.clone(soget_branch) still shares that set with the parent. An input the parent sets after branching can therefore make the branch keep a stale calculated value. Executed witness:flow_mfor 2013 (0);month:2013-01:12;The branch's annual then reads
[0, 0]instead of[50, 0]. Giving each clone its own copy of the record fixes it. That belongs with the record's own fixes (Keep the record of set_input values in step with storage #561), not this PR.Cost: each own-period input scans the variable's stored keys, so N successive inputs to one variable check N(N+1)/2 keys. The reviewer measured 0.012 s for 100 inputs, 0.040 s for 200 and 0.152 s for 400. Dataset loading sets few periods per variable and starts with an empty fast cache, so that is negligible; long panels would merit a cheaper index.
Landing
Either merge this into #581's branch, so #581 (decision d899) lands both, or land it after #581. GitHub retargets it to
masterwhen #581's branch is deleted. #581 currently conflicts withmasterinholders/helpers.pyandtools/simulation_dumper.py. This PR adds no conflict of its own:git merge-treeagainstmasterreports the same two files.axiom: n/a: engine cache fix, no policy change
Other audit findings, handled elsewhere
The 2026-10-06 invariants audit of master
757147c7also confirmed defects that this PR leaves alone, because other work owns them:Branch staleness after
set_inputand raw-file restore are documented behaviour, not defects.🤖 Generated with Claude Code