Skip to content

Bound each PUF QRF draw by the population slice its recipient stands for - #1259

Draft
MaxGhenis wants to merge 9 commits into
mainfrom
us-puf-tail-draw-dominance
Draft

MaxGhenis wants to merge 9 commits into
mainfrom
us-puf-tail-draw-dominance

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #1239 / #1251. One PUF-channel QRF draw can carry a large share of an amount column: in the #1251 rebuild a single $218.9M line-32 draw on a weight-644.7 tax unit put $141B into the frame. This PR bounds every raw amount draw by the mass of the population slice its recipient stands for.

It is an interim censor at the finalize seam, like the existing non_sch_d_capital_gains quantile cap. It limits each record to its slice; it does not remove concentration the slice itself has. The root fix is a tail stratum that carries PUF donors at their own weights (#958, #964); weight-aware draws (#481) complement it.

Not for merge by the author. It changes what the next US build carries, so the merge is queued for Max as d1504 (see "Decision").

Why one draw can dominate

Read in code at ed199a624, and reproduced bit for bit on Modal (below).

  1. The rank predictor sends heavy recipients to the top of the PUF, by design. The chain conditions on puf_predictor_income_rank_share (US PUF imputation predicts each income item from the recipient's own survey value, so the PUF clone half reproduces ASEC income #982), each unit's weighted mid-rank within its own population. A top-ranked ASEC tax unit (initial weight about 600 of 89.8M PUF-arm units) lands among the PUF's top ~1,000 returns.
  2. Those returns stand for one return or less. The top regular PUF records weigh about 1.08. The 3,985 synthetic records that replace the four disclosure aggregate rows weigh less: RECID 999999's 3,900 records share weight 497, about 0.13 each.
  3. A draw hands the recipient one donor's value. Amount forests are fit after one n-of-n weighted bootstrap and keep every leaf sample, so a top recipient's leaf holds a couple of top-slice donors and its quantile draw reads one of them.
  4. The recipient's weight multiplies it. A 0.13-weight donor drawn onto a 644.7-weight unit is a 4,960x amplification.

Weight-aware leaf draws (#481) do not fix this. They make a donor's draw probability track its weight, which fixes the inclusion lottery but not the granularity: the slice such a donor belongs to is smaller than one recipient, so whichever donor is drawn, one record carries it. DESIGN.md already states the rule this breaks: "tail support below pool resolution is the pool's job (strata), never the fit's." The proper home for that mass is a tail stratum carrying PUF donors at their own weights (#958 increment 2, draft #964).

Nor does the release's tail gate catch it. It tests the top 100 records' share (≤ 0.75); #1251's line 32 passes at 0.399.

How often, and how much (Modal seed sweep)

The primary chain was rerun in full for seeds 0–11 on main (ed199a624) and on #1251 (45a5b9891), on the Route A base inputs. Seed 0 reproduces both production bases exactly: line-32 net $306.8912B (certified control) and $455.3234B (#1251 treatment), with the same weight-644.7 unit drawing $218.9M from a synthetic donor of weight 0.13. Figures are raw tax-unit draws at initial weights, before finalization's other steps.

  • It hits many amount columns, not only line 32. 26 of 49 columns on main have a single draw above 5% of the column's weighted absolute mass in at least one seed.
  • The top draws come from both kinds of donor. Across the 12 Carry Schedule E line 32 and its passive subset on the US PUF donor #1251 seeds, the largest line-32 draw is a regular record of weight 1.08 to 1.10 in 8 seeds and a synthetic record (weight 0.13, 10 or 11) in 3. In the remaining seed it falls between donor values.
Column Largest single draw, worst seed Its share of the column Seed-to-seed sd of the net
Line 32, #1251 $141.1B → $33.3B 21.7% → 6.1% $34.5B → $37.6B
partnership_income, main $55.0B → $35.4B 9.2% → 6.4% $40.8B → $36.3B
Qualified dividends $124.9B → $26.0B 29.4% → 10.7% $66.5B → $25.5B
Non-cash charity $75.9B → $19.8B 60.1% → 28.2% $24.4B → $13.0B
Tax-exempt interest $68.9B → $2.8B 59.3% → 5.3% $18.1B → $3.2B
Short-term gains $36.8B → $8.0B 27.9% → 7.4% $13.8B → $8.2B
UBIA, main $437.7B → $110.5B 17.8% → 5.6% $262.8B → $154.4B
Form 4952 election $14.0B → $1.4B 38.5% → 22.1% $8.5B → $1.3B

The arrow is this PR's bound applied to the same draws. Across all ~50 columns it moves about 22 to 23 tax-unit cells per seed (22.7 on main, 22.2 on #1251, of ~11M), at most 3 in any one column. Column means move by the mass the clipped draws carried, for example line 32 on #1251 from $427.6B to $417.2B on average (donor-implied $376.8B), and qualified dividends from $220.8B to $205.9B (donor-implied $165.5B). Every column is in out/aggregate_main.md and out/aggregate_l32.md in the evidence folder.

What it does not fix.

The bound

At the existing tail-bound seam in finalize_us_puf_tax_detail_predictions, after the non_sch_d_capital_gains q0.999 cap, every amount output's raw tax-unit draw is clipped to [floor_r, cap_r]. For a recipient whose household design weight is the share s_r of all PUF recipients, take the donor slice holding the same share of donor weight at each end of the column (fractional at the edge):

  • cap_r is the top slice's positive mass per unit of slice weight. Within the positive donors this is the slice's weighted mean, and it falls as the share grows.
  • floor_r is the same for losses, from the bottom slice.
  • A side is infinite only when the donor has no value of that sign.

So w_r × value ≤ (W_r / W_d) × positive mass of the top s_r slice: no record carries more mass than the most extreme slice of the population it could stand for. The rule has no tuning constant. Snapped outputs have one exception, described below: their bound is max(cap_r, the smallest positive donor value).

  • Booleans (the QBI flags) and the origination years are not amounts and are left alone.
  • Snapped (sparse) outputs are bounded on their snap-grid donor values, and the cap is lowered onto the positive grid.
    • Computing on unrounded values clipped every draw at an aged statutory maximum to the grid value below: 1,163 qualified-tuition cells per seed in a first pass.
    • The cap is never lowered to zero. A carrier whose slice mass sits below the smallest positive donor value keeps that value, so the bound erases no carrier. How many records carry a sparse output stays the donor-rate sparsification's job. An existing snapping test caught the first version zeroing a carrier.
  • Chained targets still condition on the raw draws: the bound acts only at finalization.

Alternatives, evaluated on the same draws (all columns; main; out/rule_comparison.md):

Rule Dominated columns that still have a >5% draw in some seed (of 26) Median worst top-1 share Mean mass removed, all columns Cells moved per seed
None 26 21.7% — 0
Slice mass (this PR) 24 12.0% $116B 22.7
Slice mass at 4× the share 17 5.7% $255B 210.8
Slice boundary (min of the slice) 15 5.5% $272B 85.4
Fixed weighted q0.999 15 7.0% $966B 1,233.6
  • Slice mass is the largest bound under which no record carries more mass than the population slice it stands for. It moves the fewest cells and the least mass.
  • The slice boundary is the strict reading of "below pool resolution". It caps a record at the edge value of its slice and drops the slice's mass above that. It removes about 2.3 times as much and moves about 4 times as many cells. Its biggest cuts are in qualified dividends, long-term gains and UBIA.
  • A fixed quantile caps a weight-50 unit as hard as a weight-900 one. At q0.999 it removes about $1T across columns. At q0.9999 it still leaves a 5.2% line-32 draw.
  • Dropping the synthetic donors misses the 8 seeds whose top line-32 draw is a regular record.

Choosing slice mass over the boundary is a methodology call and is part of the decision queued for Max.

Invariants

Property tests use Hypothesis (300 examples for the helper, 60 through finalization) in tests/engine_free/us/test_us_puf_recipient_share_bound.py.

  1. Bound. floor_r ≤ v'_r ≤ cap_r for every bounded output and recipient. For a snapped output the upper bound is max(cap_r, the smallest positive grid value), so a carrier is never erased.
  2. Single-record mass bound. The inequality above, on both legs, checked against a donor-by-donor reference. Snapped outputs carry the same exception.
  3. Untouched inside the bound. Bit for bit. A moved draw lands exactly on its own bound.
  4. Sign and zero preserving, shrink only. sign(v') = sign(v) and |v'| ≤ |v|.
  5. Monotone in weight. cap is non-increasing and floor non-decreasing in the share. Each side applies to every recipient or to none.
  6. Scale free. Scaling all donor weights or all recipient weights leaves every bound unchanged.
  7. Order and split invariant. Donor order does not matter; recipient order permutes the bounds; splitting a donor record into two leaves the bounds unchanged.
  8. Within support. The slice edge ≤ cap ≤ the donor maximum, exactly. A slice made only of tied donors caps at their value exactly.
  9. Optimality. cap × slice weight equals the most positive mass any fractional donor subpopulation of that weight can hold. This is checked against a linear program (scipy.optimize.linprog), independently of the slice construction, and the same for the floor.
  10. Diagnostics account for the moved mass exactly. The two record kinds in the sink carry a rule field.
  11. Differential tests. The vectorized searchsorted implementation equals a donor-by-donor reference on random cases, and at production geometry (10^5 donors, 3,900 weight-0.13 Pareto records, recipients of weight about 600 among about 9×10^7).
  12. Fail closed. Non-finite bounded draws are refused before anything is written, including the quantile cap's write. Zero-weight donors never set a bound, and the helper's property strategies include zero weights on both sides.

Three documented exceptions. These later steps move bounded values without adding mass:

  • The donor-rate sparsification of sparse outputs restores the column's mass after zeroing carriers.
  • The signed-mass calibration scales each leg.
  • The Social Security reconciliation re-splits the four components' bounded total by CPS shares. Three of the four components are zero on every donor, so they have no finite cap anyway.

The first two lift every kept value of the column or leg by one common factor; the third keeps each unit's total. All three are tested.

Known limits.

  • Links across columns loosen. Each column is bounded on its own, so a clipped record's links to other columns (mortgage balance and interest, gains and their subsets, partnership income and its SE earnings) loosen further. It touches about 22 to 23 cells per seed across all columns.
  • The bound depends on the rung. Shares are of the recipient population, so a smaller sample rung, whose records each stand for more of it, is bounded more tightly. Each diagnostics record carries the output's clipped mass, the recipient count, and the recipient and donor weight totals. The totals are the same at every rung, because stacked allocation scales each arm to the full population; the recipient count is what differs.

Binding and pins

  • Execution config. The rule's identity is bound into the stacked-spine primary PUF execution config, which moves from schema 5 to 6. The builder, the validator and imputation.schema.json all change.
    • A changed or missing bound is refused.
    • Stacked publication and the pool loader read the same schema constant as the builder and validator.
    • The attested schema-4 legacy scoring pool predates the bound and is admitted without the key, so it stays scoreable. Its test fixture now drops the key as a real schema-4 pool would.
    • Stacked pools built at schema 5 are retired: they were built without the bound, and the loader refuses them, as it does any pool whose recorded tail-bound configuration differs from the code's. If a schema-5 pool must still be scored, it needs an attested exception like schema 4's.
  • Diagnostics sink. Every bounded output writes a record to the existing tail-bound diagnostics sink, including outputs it leaves unchanged. The sink is now required whenever an amount output is finalized; every production caller (base tool, multispine pool tool, stacked spine in production mode) already passes one.
    • A non-production stacked pass that asks for no diagnostics hands finalization a discarded sink, and its execution config still records the sink as disabled.
    • Test calls that finalize amount outputs directly pass a sink (37 added tail_bound_diagnostics=[] arguments).
    • Five tests isolate another mechanism on frames of two to twenty units, where every unit stands for a large share of the population and the bound would cap their fixture draws. They narrow the bound with recipient_share_bound_outputs and say why: the quantile-map control, the snapping test's passthrough control column, educator sparsification, the farm signed-mass calibration, and Social Security preservation. impute_us_puf_tax_detail_support gains that test-only passthrough.
  • Pins this moves.
    • imputation.yaml is regenerated (generate_us_bundle_from_constants.py); the US spec_sha256 is now fb7990d6….
    • Field-usage counts: 42,194 configuration fields, 32,414 authored, producer-graph claim 24,498.
    • Inventory digests: seed protocol and map (they attest puf_support.py), producer registry, late resource semantics, full checkpoint identity.
    • The pointer-inventory digest and the coverage evidence. tools/spec_engine_coverage.py --check reports 42,194/42,194 fields and 41/41 checks.
    • The multispine-pool tool spec sha and the minimal-loader golden vector.

Interaction with #1251

The passive leaf is an amount output, so it is bounded automatically once #1251 lands; nothing names it. In the #1251 sweep its largest single draw falls from 16.0% to 7.9% of the column. The same holds for s_corp_income once the entity split (#1261, stacked on #1251) gives it values. All three PRs move the spec and inventory pins; whichever merges later re-derives them.

Frame and engine results

PolicyEngine-US 2.2.1 ran on three bases on Modal, loaded the way microcosm's reform validation loads a release. These are fixed-weight comparisons; no release was re-calibrated.

The three bases share every tax-unit and household id, so the certified Route A final weights apply to all three. The harness reproduces #1251's published deltas exactly.

In the build, the bound moved 26 tax-unit cells in 13 columns (out/evidence_base_tail_bounds.json). The line-32 draw fell from $141.1B to $33.3B, as the sweep predicted for seed 0, and the unit that drew $218.9M now carries $51.6M. The others include W-2 wages from qualified businesses ($48.0B → $20.1B), estate income ($16.7B → $7.1B) and the passive leaf ($10.5B → $7.4B).

At Route A final weights ($B; deltas in $B):

Control #1251 #1251 + bound #1251 − control Bound − control Bound − #1251
AGI $16,628.3B $16,705.0B $16,649.4B +76.7 +21.1 -55.6
Federal income tax $2,282.6B $2,311.8B $2,290.9B +29.2 +8.3 -20.9
Taxable income $12,896.6B $12,958.7B $12,902.5B +62.1 +6.0 -56.2
Line 32 (tax-unit) $1,020.5B $1,055.7B $1,009.5B +35.2 -11.0 -46.2
Passive subset $0.0B $140.4B $144.5B +140.4 +144.5 +4.1
Net investment income $2,267.2B $2,407.6B $2,410.2B +140.4 +142.9 +2.5
NIIT $60.5B $64.3B $64.3B +3.8 +3.8 +0.0
QBI deduction $229.2B $228.4B $228.5B -0.8 -0.8 +0.0

Tests

GitHub's runners for this repository were stalled for about four hours on 2026-10-13 (every run queued from 13:22Z). The workflow's suites were therefore also run on Modal against the pushed branch (scripts/modal_ci.py in the evidence folder), with the same commands on 16 CPUs.

  • Engine-free, head 0260d6404: 17,029 passed, 157 skipped, 3 xfailed, 4 failed.
  • Engine-free, origin/main (74f5e6123), same container: 16,990 passed, 157 skipped, 3 xfailed, and the same 4 failed. All four are environment-bound: one needs an unreadable directory (the container runs as root), one needs Python ≥ 3.14.7 (the image has 3.14.2), one needs ps, and one needs the telemetry emitter. The 39 extra passes are this PR's new tests.
  • Lint (ci_test_plan.py verify, the graph burndown check, ruff check .): clean.
  • Engine-us: in progress on Modal for 0260d6404. On the previous head its scenario shard passed (14 of 14) and its two contract shards passed 65 of 66 with 2 skipped; the one failure was a literal field count, fixed in 0260d6404.
  • Final head 683b64428 differs from 0260d6404 by one comment and the pins it moves. Locally, tools/spec_engine_coverage.py --check reports 42,194/42,194 fields and 41/41 checks, generate_us_bundle_from_constants.py --check is clean, and the loader golden test and the bound tests pass. A Modal pass and GitHub's own run on this head are in progress.

What the Modal run caught that a local run of the new tests had not:

  • the bound could zero a carrier of a snapped output (fixed, with a regression test);
  • two more literal pins of the field counts;
  • stacked test passes that give no sink;
  • four fixture tests whose two-to-twenty-unit frames the bound legitimately caps.

Review

Four rounds of independent review. Rounds 1 to 3 ran on Opus lanes while the Codex lanes were closed; round 4 ran on GPT-6.1 Sol.

  • Round 1: REQUEST CHANGES (d9f180852).
    • Blocker: stacked publication and the pool loader still required schema 5.
    • Also: narrow the claim and label the bound interim; the Social Security re-split; zero-weight edge cases; weak tests; mixed diagnostics shapes.
  • Round 2: REQUEST CHANGES (8e54ef72b).
    • Blocker: one more schema-5 literal in test_us_spec_bundle.py.
    • Also: two overreaching sentences; a non-finite test that could not fail; an LP tolerance that could flake.
  • Round 3: APPROVE WITH NITS (635ae0a11). The blocker was resolved and every figure matched the evidence files. The nits were one leftover sentence, the rung-comparison claim (now backed by recipient_count) and LP conditioning.
  • Round 4: APPROVE WITH NITS (0260d6404, GPT-6.1 Sol), with no code finding.
    • Confirmed: the never-zero fix and its tests, the field counts, that production still requires the sink, all five test isolations, and all 48 entries of the engine table.
    • Nits, all wording: state the snapped-output exception beside the guarantee (done here and in the module comment, 683b64428); "NII and NIIT are unchanged" overstated +$2.5B and +$0.03B; a stale spec hash.

Reports are review-r{1,2,3,4}-out.md in the evidence folder.

Decision

Queued for Max as d1504. It changes what the next US build carries and retires schema-5 stacked pools, and the choice of the slice-mass rule over a fixed quantile or the slice boundary is a methodology call. It is best merged before or with #1251.

Follow-ups

Evidence (outside the repository): ~/PolicyEngine/_reviews/puf-tail-draw-dominance/, which holds design.md, the sweep scripts, out/aggregate_*.md and out/*.json. The PUF microdata stayed on Modal.

axiom: n/a: microdata imputation bound; no policy rule changes.

🤖 Generated with Claude Code

MaxGhenis and others added 9 commits October 13, 2026 07:31
The #982 income-rank predictor sends the top-ranked survey tax units, each
standing for several hundred units, onto the PUF's top-ranked returns,
which stand for one return or less (the synthetic Forbes-tail records weigh
about 0.13). One draw put $141B of line-32 mass on a weight-644 unit in the
#1251 rebuild. Weight-aware leaf draws (#481) cannot fix this: the slice
such a donor belongs to is smaller than one recipient.

QRF finalization now caps each raw amount draw at the positive mass per
unit of slice weight of the donor's top slice of the recipient's own weight
share, and floors losses from the bottom slice, on every amount output.
The rule is bound into the primary PUF execution config (schema 6), and
each bounded output writes a tail-bound diagnostics record.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Snapped (sparse) outputs are bounded on their snap-grid donor values. A
  slice inside one grid value now caps at that value exactly; computing on
  unrounded values clipped every draw at an aged statutory maximum to the
  grid value below (1,163 qualified-tuition rows per seed in the sweep).
- Every test that finalizes amount outputs passes a tail-bound diagnostics
  sink, now required because the recipient-share bound is always active.
- Regenerated imputation.yaml (primary execution config schema 6), field
  usage counts and claim digest, inventory digests (seed protocol and map,
  producer registry, resource semantics, full checkpoint identity), the
  pointer inventory digest, coverage evidence, the pool-tool spec sha and
  the loader golden vector.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ests

- Stacked publication and the pool loader required execution-config schema
  5 while the builder wrote 6, so no current stacked pool could publish or
  load. All four sites now read PRIMARY_PUF_EXECUTION_CONFIG_SCHEMA_VERSION.
- The comment and docstrings now call the bound an interim censor that
  limits a record to its slice (not a fix for concentration the slice has),
  name the root fixes (#958/#964, #481), and record the loosened links
  across columns, the rung dependence and the Social Security re-split.
- Slices use positive-weight donors only; non-finite bounded draws are
  refused before any write; quantile records carry a rule discriminator.
- Tests: zero weights on both sides; an LP optimality check replaces the
  tautological mass test; production-geometry differential test; tax-unit
  outputs, Social Security total, and per-leg calibration factors through
  finalization; the identity is pinned to a literal.
- Re-pinned the seed protocol and map, coverage evidence, pool-tool spec
  sha and loader golden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- test_us_spec_bundle asserted execution-config schema 5; it now reads the
  shared constant. The operator-ordering doc and a pass-through fixture
  said 5 too.
- Share-bound diagnostics records carry the recipient and donor weight
  totals, so rungs can be compared.
- Tests: the non-finite check is shown to stop before the quantile cap's
  write; the LP oracle is scaled to a unit objective so solver tolerance
  cannot flake; a restated assertion is removed; the base-builder fake
  record carries the rule field.
- Changelog: interim label; schema-5 stacked pools are no longer loadable.
- Re-pinned the seed protocol and map, coverage evidence, pool-tool spec
  sha and loader golden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub's runner queue for the repository was stalled, so the engine-free
and engine-us suites were run on Modal against this branch. They found:

- For a snapped output the cap was lowered to the largest grid value within
  it, which could be zero: a carrier whose slice mass sits below the
  smallest positive donor value was erased. The cap is now lowered onto the
  positive grid only, so the bound moves no draw to zero; how many records
  carry a sparse output stays the donor-rate sparsification's job.
- Two tests pinned the old field counts as literals.
- Non-production stacked passes that ask for no diagnostics now hand
  finalization a discarded sink; production still requires one.
- Four tests that isolate another mechanism on two- to twenty-unit frames
  (snapping control column, educator sparsification, farm signed-mass
  calibration, Social Security preservation) narrow the bound explicitly,
  through a new test-only passthrough on impute_us_puf_tax_detail_support.
- Share-bound diagnostics records carry the recipient count, which is what
  differs between sample rungs.
- Re-pinned the regenerated bundle and every digest derived from it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew r4)

A snapped output is bounded by the larger of its slice cap and its smallest
positive donor value, so the bound never erases a carrier. The module
comment promised the slice-mass bound without that exception. Comment only;
re-pinned the seed protocol and map, coverage evidence, pool-tool spec sha
and loader golden, which attest puf_support.py's bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant