ALLOC-01: clamp each envelope end on its own, and widen the replay that missed it
checks / unit (pull_request) Successful in 4m9s
checks / unit (pull_request) Successful in 4m9s
Two blocking findings from the review of 186e13c. The second is why the first
survived, and it is the one this commit is really about.
THE CLAMP. `_clamp_int()` folded both ends of the interval into one
`min(max(...))` and substituted `v` itself for a bound that was infinite. That
reads as "an absent bound cannot bind", and it is not what it does: with
`hi == inf` the expression collapses to `min(max(v, lo), v) == v`, so A MISSING
CEILING SILENTLY DISCARDED THE FLOOR. `_clamp_int(0, 2142, inf)` returned 0. It
reached the shares through all three call sites - the base, the pin clamp and
the post-floor clamp.
The worked failure: bounds `{a: [2000, +inf], b: [-5000, 5000]}` against a
+1000 W site target returned `a=+500, b=+500` - inverter-a commanded at a
quarter of its own 2000 W floor - while THE SAME CASE WITH A FINITE CEILING
correctly REFUSES on sign coherence. So one defect broke the ticket's first
named deliverable ("each share clipped to that unit's UnitBounds interval") and
suppressed a circulating-power refusal that should have fired. Both now behave
identically, and the refusal is the behaviour.
Not reachable on the live install - N=1 is a pass-through, and via main.py the
supervised claim unconditionally files a finite +/-max_w LIMIT at P_SAFETY that
pre_resolve's gate can never filter because nothing outranks P_SAFETY. But
`plan()` and `arbiter.allocate()` are the public envelope contract SAFETY-28 and
DEV-02 are being asked to build on, and `UnitBounds()` DEFAULTS TO
`(-inf, +inf)`, so a one-sided envelope is the ordinary case at N>=2 rather than
an edge, and any caller with one inherited the defect.
Fixed by clamping the two ends independently: cast to int, apply `lo` only if it
is not -inf, apply `hi` only if it is not +inf. `_int_envelope()` already passes
infinities through untouched and needed no change; it is the only other place in
the module that special-cases infinity, and the rest of the tree's `isfinite`
calls are input validation in unrelated modules.
THE RECORD. `UnitPlan.explain()` printed
`inverter-a -> +500 W in [2000, +inf] (weighted share)` - a line asserting a
share is inside an interval that excludes it, in the record SAFETY-28 gates on.
It now ASKS: `within_envelope` checks the share against the interval, and a
violated plan prints `OUTSIDE ... - ENVELOPE VIOLATED, DO NOT WRITE` instead. A
dropped unit is exempt and prints "not commanded" - it was told nothing, its
interval may be a contradiction no number is inside, and a banner there would
cry wolf on the one row that is working.
THE REPLAY THAT COULD NOT SEE IT, WHICH IS THE FINDING THAT MATTERS. The
250 000-tick replay drew every envelope as `B(-uniform(0, RATING),
uniform(0, RATING))` - finite on both ends and containing zero, every time -
while the check it fed claimed "no share ever leaves the envelope it was clamped
into", which reads as a statement about `UnitBounds`. The data could only
exercise a symmetric closed interval around zero. That is why it reported 0
escapes against a live defect while the reviewer's own 120 000 ticks reported
6 254.
The generator now draws from every shape `UnitBounds` can express: finite
straddling zero, half-open above, half-open below, finite excluding zero in each
direction, half-open AND excluding zero, and bare `UnitBounds()`. Over the same
seed and the same 250 000 ticks it draws 166 657 half-open intervals, 55 233
bare ones and 222 082 that exclude zero, and those counts are PRINTED and
asserted so "the replay covers half-open intervals" is a number a reader can see
rather than a sentence the file asserts about itself. The escape check also
dropped its 1 W tolerance: `_int_envelope()` narrows inward, so there is nothing
for slack to absorb except a defect.
GRADED, NOT MERELY APPLIED - the acceptance bar for this round. A/B on one seed
and one tick count, against the defective clamp: THE OLD GENERATOR SEES 0
ESCAPES, THE NEW ONE SEES 39 991. Reverting the fix under the widened generator
takes test_alloc.py RED on four checks (the escape count, the record check, and
both halves of the new half-open block); re-applying it takes it green. The
collapse is now a row in part 12's mutation table, recorded as the mutation that
actually shipped rather than a hypothetical one.
TWO CHECKS RE-AIMED RATHER THAN LOOSENED.
* Part 6's "the sign gate never fires on an ordinary tick" counted refusals over
the whole replay. Envelopes that EXCLUDE zero are precisely what that gate is
for, so the widened generator makes refusals correct there. It now counts an
ORDINARY tick explicitly - no pin, every envelope contains zero - and asserts
0 refusals over 77 533 of them, half-open and bare envelopes among them. That
is stronger than what it replaced, not weaker.
* Part 1's third check summed `sum_w - sum(shares)` over ticks where
`unmet_w == 0`, and `unmet_w` IS that difference - so it was true by
construction and would have gone quiet rather than red. It now measures the
one rounding that is actually left: the site-level `quantisation_w`, totalling
-39.8 W over 137 964 allocations, i.e. -0.0003 W per tick. Largest remainder
against the per-unit alternative is still graded in part 2, where the
counterfactual drifts -125 W over 50 000 ticks at three inverters.
Verified: all 18 suites green in the CI image on a tree with no bytecode cache;
test_alloc.py also re-run through tools/mutation_run.sh, which purges. AC 2 and
AC 3 are untouched by this change - the rig and part 8 use symmetric finite
envelopes, which the old and new clamp treat identically - and part 8 is green.
No hardware touched; admin/goodwe not fetched, not modified, not pushed to.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
186e13ceca
commit
3fe3c7acb0
@@ -37,8 +37,25 @@ measurement.
|
||||
chases and never reaches. So the target is rounded **once**, at the top, and
|
||||
the fractional parts are settled by **largest remainder** with a deterministic
|
||||
tie-break; every sum below that line is integer arithmetic and cannot drift.
|
||||
Verified over 250 000 randomised ticks: **0 sum failures, 0 envelope escapes,
|
||||
0 W accumulated bias.**
|
||||
Verified over 250 000 randomised ticks: **0 sum failures, 0 envelope escapes**,
|
||||
and the one rounding that is left - the single site-level `quantisation_w`,
|
||||
never distributed into a per-unit bias - totals **-39.8 W over 137 964
|
||||
allocations, i.e. -0.0003 W per tick.**
|
||||
* **Each share is clipped to that unit's interval, and THE TWO ENDS OF THE
|
||||
INTERVAL ARE APPLIED INDEPENDENTLY.** `UnitBounds()` defaults to
|
||||
`(-inf, +inf)`, so a one-sided envelope is the ordinary case at N>=2 rather
|
||||
than an edge. The first cut of this work folded both ends into one
|
||||
`min(max(v, lo), hi)` and substituted `v` itself for a missing bound, which
|
||||
reads as "an absent bound cannot bind" but is not what it does: with
|
||||
`hi == inf` the expression collapses to `min(max(v, lo), v) == v`, so **a
|
||||
missing ceiling silently discarded the floor.** `_clamp_int(0, 2142, inf)`
|
||||
returned 0. The worked failure it caused: a unit with a 2000 W floor and no
|
||||
ceiling, beside a symmetric unit, against a +1000 W site target, was allocated
|
||||
**+500 W - a quarter of its own floor** - and the sign-coherence refusal that
|
||||
the same case with a finite ceiling correctly raises was **suppressed** with
|
||||
it. Fixed by clamping each end on its own, and `UnitPlan.explain()` can no
|
||||
longer print a share as being inside an interval that excludes it: that line
|
||||
now reads `OUTSIDE ... - ENVELOPE VIOLATED, DO NOT WRITE`.
|
||||
* **The envelope is read per tick, never cached.** `plan()` is a pure function
|
||||
of that tick's intervals and holds no state, because a unit's range moves for
|
||||
reasons unrelated to the allocator - a stale link, a pack at its floor, a
|
||||
@@ -69,6 +86,19 @@ measurement.
|
||||
**re-aimed, not deleted** - `allocated=False` is now reachable on a mis-routed
|
||||
claim, an empty envelope set, or sign incoherence, and all three are graded on
|
||||
real cycles.
|
||||
* **And the replay that missed it was widened, so the fix is graded rather than
|
||||
merely applied.** The 250 000-tick replay drew every envelope as
|
||||
`B(-uniform(0, rating), uniform(0, rating))` - **finite on both ends and
|
||||
containing zero, every time** - while the check it fed claimed "no share ever
|
||||
leaves the envelope it was clamped into". The data could only exercise a
|
||||
symmetric closed interval around zero, so it reported 0 escapes against a live
|
||||
defect. The generator now draws from **every shape `UnitBounds` can express**:
|
||||
half-open above, half-open below, intervals that exclude zero, and bare
|
||||
`UnitBounds()`. Measured A/B against the defective clamp on the same seed and
|
||||
the same 250 000 ticks: **the old generator sees 0 escapes, the new one sees
|
||||
39 991.** Reverting the clamp fix under the widened generator takes
|
||||
`test_alloc.py` red on four checks; re-applying it takes it green. The
|
||||
collapse is now a row in part 12's mutation table.
|
||||
* **Known and not closed here:** at a roster of two the loop's `actual_w` is one
|
||||
inverter's power used as a site measurement, because the add-on binds a single
|
||||
`inverter_power` sensor. That is the *sensing* half of the same N=1 identity
|
||||
|
||||
Reference in New Issue
Block a user