Repository navigation
Conversation
subsample rebuilds the simulation from a person-level frame of recorded inputs plus the structural leaves retain_structure adds. retain_structure skipped any leaf the tax-benefit system had no variable for, so a system without person_<entity>_role rebuilt every member with the default role (silently on master; as a capacity error once #595 lands), and a system without person_id or person_<entity>_id variables failed to subsample. retain_structure now carries person IDs, memberships and role keys whether or not a variable backs them. An integer role variable cannot hold "no role", so subsample raises before changing anything instead of writing role index 0. The loader records the columns it reads for structure and no longer reports them as ignored. Found in review P3-1 on #595. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For any households of bounded, unbounded and unmatched role keys, person order, weights, default role, role source and sample, every kept household is a whole source household whose role counts (by key and nb_persons) equal the source's, with or without a role variable, through a second subsample too, and no structural column is reported as ignored. An integer role variable keeps the roles or raises before changing anything. Move the household-table oracle into the shared fixture module. 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.
Draft until the independent review comes back. The fix, the example tests and the property tests are in.
Problem
Simulation.subsample()rebuilds the simulation throughbuild_from_datasetfrom a person-level frame. That frame holds the recorded inputs plus the structural leaves thatretain_structureadds.retain_structureskipped any leaf the tax-benefit system has no variable for. The loader reads those columns by name, not through a variable, so whatever they held was lost:person_<entity>_rolevariable, the rebuild fell back todefault_role, so every kept member got the default role.ValueErrorpart-way through the rebuild. That leaves the simulation unusable (members_entity_idisNone).default_role=None, master raisedMissing person_household_role column.person_idvariable, subsample raisedKeyErrorpart-way through the rebuild. With noperson_<entity>_idvariable, it raisedIndexError.Found in review P3-1 on #595.
No shipped country package reaches the silent case today:
member, which is also its default."parent", which matches no role, and have no role variables.The subsample path still threw away roles the caller supplied.
Before and after
The reviewer's case: the country template with
person_household_roleremoved anddefault_role = "first_parent". The flat file hasperson_household_id__2024 = [0, 0, 0, 1]andperson_household_role__2024 = [first_parent, child, child, first_parent]. Thensubsample(n=2, seed=0):[1, 1][2, 0]subsample, master1cccc278e[3, 1](everyone first_parent)[0, 0]subsample, master + #595ef0f9d5e2cValueError: Cannot assign default role 'first_parent'…; simulation left unusablesubsample, this PR[1, 1][2, 0]Changes
All in
policyengine_core/simulations/simulation.py.retain_structure(insubsample) now writes a structural leaf whether or not a variable backs it.<name>__ETERNITY.members_role. A member the loader matched to no role holds0; it is written as""and reloads as no role.subsampleraisesValueErrorbefore changing anything. Master wrote index 0 here, which silently gave those members the first flattened role.build_from_datasetrecords each column name it reads for structure (throughget_eternity_array). It no longer reports those columns as "ignored" in the unknown-column warning, because they are used. Without this, a US subsample would warn about the fivememberrole columns it now carries. The original load already warned, wrongly, that an explicit role column with no variable was ignored. Genuinely unknown columns still warn.Invariants
These hold for every simulation loaded from a dataset and every
subsample(n, seed, quantize_weights):nb_persons(role)agrees with those counts for every flattened role.person_household_role, whateverdefault_roleis (bounded, unbounded, unmatched orNone), and whether roles came from the entity column or the genericrolecolumn.subsampleeither keeps roles (1–3) or raisesValueError. It raises exactly when some member has no flattened role, and then the simulation is unchanged: same dataset object, persons, roles and weights.Tests
The oracle is a hand-written household table in
tests/fixtures/subsample_roles.py; expected roles are never taken from the loader.tests/core/test_subsample_explicit_roles.py: 31 example tests.1cccc278e, 21 fail for the reasons above.tests/core/test_subsample_explicit_roles_property.py: two Hypothesis properties. Together they cover invariants 1–7.default_role, the role source,n, the seed andquantize_weightsall vary.lodgermember, where master writes role index 0 instead of raising.Master merged with Validate implicit dataset role capacities and role array shapes #595 (
ef0f9d5e2c; local merge, clean): the reviewer's case keeps first_parent[1, 1]and child[2, 0], with no capacity error. These files pass one at a time:test_subsample_explicit_roles.pytest_subsample_explicit_roles_property.pytest_dataset_default_role.pytest_dataset_default_role_property.pytest_subsample_structural_ids.pytest_dataset_unknown_column_warning.pytest_simulation_builder.pyExisting files, run one at a time on this branch: all pass.
tests/core/test_subsample_*.pyfiles, including the three existing property modules.test_dataset_unknown_column_warning.py,test_dataset_getattr.py,test_simulation_builder.py,test_dump_restore.py,test_bincount_minlength.py,test_entities.py,test_projectors.py,test_microsimulation_weights.pyandtest_microsimulation_person_accessor.py.Lint:
ruff format --check .andruff checkare clean.Cost: reading role keys takes 48 ms for 5 group entities × 400k persons, about the size of a full US subsample.
Not run here: the PE-US core gate and full-sample controls. The US + core hub runs those.
axiom: n/a: core engine infrastructure, no policy change.
🤖 Generated with Claude Code