Skip to content

Rowwise local solve surface under the doctrine (#495 increment 6b) - #541

Merged
MaxGhenis merged 2 commits into
mainfrom
uk-rowwise-solve
Jul 25, 2026
Merged

Rowwise local solve surface under the doctrine (#495 increment 6b)#541
MaxGhenis merged 2 commits into
mainfrom
uk-rowwise-solve

Conversation

@MaxGhenis

Copy link
Copy Markdown
Contributor

What

Increment 6b of #495: the solve shape the rowwise candidate uses — one weight vector over the cloned households, where each household supports only its assigned constituency's target rows (the US Build-N structure).

  • build_uk_rowwise_local_matrix: one column per household; an area's rows draw support only from households assigned there; fails closed when the target surface misses an assigned area (the 650/650 requirement — misses are support or target work, never silent exclusion); reuses the stacked path's align_area_targets contract.
  • solve_uk_rowwise_weights_under_doctrine: structurally knob-free exactly like the UK local solve doctrine: uniform operator, declared bounds, past-cap census (#495 increment 4) #503 stacked doctrine solve (no per-target parameters, no doctrine injection point, duplicate-surface refusal), with the one rowwise-correct difference: initial weights are the household base weights floored at min_initial_weight — never split across areas, because a rowwise household exists in exactly one area.
  • Shared core extracted as solve_prepared_local_weights: the stacked path routes through it unchanged (pinned by a regression test asserting the split-initialization behavior), and both shapes carry the Loss shape: flat cap at 1.0 makes past-cap rows free dumping grounds (17 rows pushed out in Build N); bounded-tail experiment + past-cap census diagnostic #492 past-cap census.
  • rowwise_area_support_summary: per-area assigned/nonzero households, distinct source households, weight mass, ESS — over every target area, zeros included.

Testing

8 new tests (matrix placement asserted cell-by-cell against hand values, uncovered-area/alignment/finiteness refusals, base-weight initialization pinned, knob-freedom signature + TypeError, duplicate-surface refusal, support summary hand-checked, stacked-behavior regression) plus the existing doctrine/solver/runner suites green (36 total in the solve family). Ruff clean.

Part of #495.

🤖 Generated with Claude Code

…e doctrine

Increment 6b of the UK dense/local epic (#495) — the US Build-N shape for
the UK. build_uk_rowwise_local_matrix places each cloned household's
metrics only in its assigned constituency's target rows (one column per
household, never area x household stacking) and fails closed when the
target surface does not cover an assigned area: local misses are support
or target work, never silent exclusion.

solve_uk_rowwise_weights_under_doctrine mirrors the stacked doctrine
solve's structural knob-freedom (no per-target parameters, no doctrine
injection point, uniform-surface refusal) with rowwise initial weights:
the household base weights floored at min_initial_weight, never split
across areas, since a rowwise household exists in exactly one area. The
shared torch core is extracted as solve_prepared_local_weights — the
stacked path routes through it unchanged (regression-tested) and both
shapes carry the #492 past-cap census.

rowwise_area_support_summary reports per-area assigned/nonzero households,
distinct sources, weight mass, and effective sample size over every target
area.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…w fence, calibration mass record, hardened core

Adjudicated findings from the sol review of #541 (all three blockers
confirmed):

- The matrix builder fails closed on unreachable rows: a nonzero target
  whose area has zero household support would sit invisibly inside the loss
  cap; it now refuses with named (area, metric) examples, while zero
  targets with zero support remain consistent.
- The doctrine solve refuses zero base weights outright — the positivity
  floor must not resurrect dead rows; reviving them is upstream work with a
  recorded mass change. min_initial_weight=0 is now allowed (parity with
  the stacked path) since positivity is guaranteed by the refusal.
- rowwise_calibration_mass_record constructs the mass-change record a
  calibration must append before write; the #501 writer's chain-currency
  fence already refuses an unrecorded mass change, and the solve docstring
  now points at that contract explicitly.
- solve_prepared_local_weights validates dimensionality, finiteness,
  matrix/target/frame alignment, and non-empty surfaces (NaN weights and
  (n,1) broadcasting no longer slip through); the stacked path's
  established base_weights error message is restored.
- The builder refuses duplicate metric labels by name and metric columns
  colliding with target-frame metadata names (silent metadata calibration).
- Support summary normalizes inputs: 1-D weight enforcement and composite
  (tuple) source ids handled without object-array coercion.
- Nine review tests added covering each refusal, the mass-record helper,
  and the zero-target-with-zero-support allowance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Cross-family review completed (sol adversarial pass, fable adjudication) — all three blockers confirmed and fixed in the second commit:

  1. Unreachable target rows (nonzero target, zero support — sol reproduced error 1.0 with zero past-cap counts): the builder now fails closed with named (area, metric) examples; zero targets with zero support stay legal.
  2. Solver→writer mass fence: rowwise_calibration_mass_record constructs the record a calibration must append, and the solve docstring names the contract — with the precision that the Rowwise clone: carry weight-kind/mass-log fences, exact mass conservation, pool lineage, dry-run plan (#495 increment 2) #501 writer's chain-currency fence already refuses an unrecorded mass change at write time (sol's mass-drift repro would fail the write), so the gap was the missing record constructor and documentation, not an unfenced write path.
  3. Dead-row resurrection: zero base weights are refused outright — the floor must not revive IMPORTANCE-zero rows; reviving is upstream work with a recorded mass change. min_initial_weight=0 now allowed (stacked parity, finding 10).

Should-fixes all fixed: prepared-core validation (NaN/shape/alignment/empty-surface, finding 4), metadata-name collision refusal (finding 5), named duplicate-label refusal (finding 6), support-summary input normalization incl. composite ids (finding 7), restored stacked error fidelity (finding 8), and the nine tests (finding 9). Sol's bitwise-equivalence verification of the refactor across ordinary/conserving/L0/budget solves (finding 11) is the strongest evidence the shared core preserved behavior.

41 solve-family tests green; ruff clean.

@MaxGhenis
MaxGhenis merged commit eccedfb into main Jul 25, 2026
4 checks passed
@MaxGhenis
MaxGhenis deleted the uk-rowwise-solve branch July 25, 2026 12:19
MaxGhenis added a commit that referenced this pull request Jul 25, 2026
Increment 6d of the UK dense/local epic (#495): one command from the
national staging H5 to a calibrated rowwise candidate, composing the three
merged increments — the ladder clone route (#540), the rowwise solve
surface (#541), and the census household-count family (#542).

Pipeline: load the ladder, clone through the ladder route (release gate
runs before a result exists), bind the constituency household-count targets
from the SAME loaded ladder (pairing provenance recorded), solve one weight
per cloned household under the reviewed doctrine, append the canonical
rowwise_calibration_mass_record, re-gate on the calibrated weights,
transition the weight kind to CALIBRATED through assert_kind_transition,
and write the candidate H5 with evidence sidecars.

The candidate is an adjudicated partial by construction: exactly one target
family is bound and named in the manifest as bound_target_families;
unbound census families stay unbound rather than approximated. Publication
is deliberately NOT automated — the tool writes a local candidate and its
evidence, nothing more.

Implementation delegated to sol (gpt-5.6-sol) cross-family and verified
here: tests and lint re-run independently, the calibration path read line
by line, and assert_kind_transition confirmed as a real populace.frame
contract rather than an invented helper.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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