Repository navigation
Drop sums and twelfths an own-period input makes stale - #593
Merged
Merged
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>
Keep ordinary overlap comparisons unchanged and use master's Gregorian end ordering only when a period stop overflows. Cover warmed stored and fast-cache-only defaults beyond datetime's last date. Recreate shadow disk caches after own-period cleanup so the provenance regressions and property retain their original witnesses. Clarify dependency invalidation versus same-variable overlap cleanup in the merged branch docs.
Contributor
Author
|
Merged by the US + core hub at
|
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.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.Merge with master
Merged master
7399c4ed8b9401febd546512cc55ba1c3e896ab0with merge commit10574bb63e7f6329f0a7072d6865fc3efe6b2515; validated/pushed headadc18425ee153799911678a379fc31c3fd65c9ceincludes the separate compatibility fix below. This section supersedes the historical stacked-base, pending-CI, and old dump-format notes above. Master already contains #581 and independent clone/input provenance handling.Conflict resolutions:
policyengine_core/holders/helpers.py: retained master's tier-aware supplied-input checks, normalized/early-year storage keys, unconditional successful helper cleanup, and dropped provenance/sequence metadata cleanup; restored Drop sums and twelfths an own-period input makes stale #593's own-period cleanup.policyengine_core/holders/holder.py: retained master's pre-store dependency invalidation and custom-handler calculation/failure cleanup; run Drop sums and twelfths an own-period input makes stale #593's cleanup after a successful direct non-ETERNITY input store.policyengine_core/tools/simulation_dumper.py: retained master unchanged. Its branch-visible derived-value format, format marker, and input-first restore with unknown-source sequence history supersede the older stackedinputs.txtimplementation.tests/core/test_set_input_helper_order.py: retained master's expanded input-tier and early-year regression coverage.tests/core/test_set_input_helper_order_property.py: retained master's disk/branch restrictions and removed the own-period aggregate exclusion exactly as Drop sums and twelfths an own-period input makes stale #593 originally did.tests/fixtures/set_input_helper_order.py: retained master's fixture and conservative disk-period domain documentation.set-input-helper-order.fixed.mdfragment, already consumed by master release commita934dc3465cd3284b6d21fb6fd7a0506b3e6581b; kept Drop sums and twelfths an own-period input makes stale #593's fragment.Combined behavior: master still invalidates dependencies before an input and after calculations inside a custom handler. #593 additionally drops this variable's overlapping calculated sums/twelfths and fast-cache entries on readable branches after an own-period input, preserving supplied inputs in either storage tier and leaving parent simulations intact. A same-value branch input still skips master's broad dependency invalidation, while #593 may clear same-variable overlapping calculated caches. Restored derived values follow the same cleanup; restored inputs remain inputs. ETERNITY skips #593's additional cleanup.
Compatibility fix
adc18425ee153799911678a379fc31c3fd65c9ce:The first merged checks exposed two interactions with newer master. A separate compatibility commit keeps ordinary overlap comparisons unchanged and reuses master's Gregorian end ordering only when
period.stopoverflows, preserving supported periods ending after year 9999. Two warmed-default cases cover stored values and fast-cache-only values. Provenance examples and the generated tier property now explicitly confirm old disk-cache eviction, then recreate a later shadow disk cache so their deletion/export/reform witnesses remain meaningful. Branch documentation distinguishes dependency invalidation from overlap cleanup. There is no change to #593's stale-sum/twelfth rule.Validation: 883 passed, 2 expected xfails across 38 files, with no unexpected failures. Each file ran separately in the foreground using
uv run pytest -q <file>. All runs usedOMP_NUM_THREADS=4 OPENBLAS_NUM_THREADS=4 MKL_NUM_THREADS=4 VECLIB_MAXIMUM_THREADS=4 NUMEXPR_NUM_THREADS=4 OMP_WAIT_POLICY=PASSIVE.tests/core/)test_branch_helper_invalidation.pytest_branch_input_fixed_point.pytest_branch_input_fixed_point_property.pytest_branch_input_invalidation.pytest_branch_input_invalidation_property.pytest_branch_scoped_delete_arrays.pytest_branch_shared_arrays.pytest_branch_shared_arrays_differential.pytest_carry_over_order.pytest_carry_over_order_property.pytest_data_storage_dir.pytest_data_storage_dir_property.pytest_dataset_default_role.pytest_disk_storage_differential.pytest_disk_storage_ownership.pytest_dump_restore.pytest_fast_cache_contained_periods.pytest_fast_cache_contained_periods_property.pytest_holder_branch_fallback.pytest_holder_write_fast_cache.pytest_holder_write_fast_cache_property.pytest_holders.pytest_input_tier_invalidation.pytest_on_disk_storage_clones.pytest_option_result_cache.pytest_own_period_input_overlapping_cache.pytest_registered_input_deletion.pytest_restore_input_provenance.pytest_restore_input_registry.pytest_restore_input_registry_property.pytest_set_input_helper_order.pytest_set_input_helper_order_property.pytest_subsample_restore_inputs.pytest_uprating_order.pytest_uprating_order_property.pytest_user_input_keys.pytest_user_input_keys_property.pytest_user_input_keys_soundness.pyRuff on all 9 changed/resolved Python files:
ruff check→All checks passed!;ruff format --check→9 files already formatted.git diff --checkpassed.The hub will rerun PE-US A/B and impact at merged head
adc18425ee153799911678a379fc31c3fd65c9ce, then queue shell review and Max's core-semantics ruling. No microsim was run in this workspace. This push starts the PR's first CI run; CI outcome remains pending.axiom: n/a: engine cache fix, no policy change
🤖 Generated with Claude Code