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
+183
-21
@@ -41,7 +41,7 @@ from math import floor, inf
|
||||
|
||||
from app.allocator import (
|
||||
DROP_CONTRADICTION, DROP_NO_INTEGER, DROP_STALE, REFUSE_EMPTY,
|
||||
REFUSE_SCOPE, REFUSE_SIGN, Allocation, plan,
|
||||
REFUSE_SCOPE, REFUSE_SIGN, Allocation, UnitPlan, _clamp_int, plan,
|
||||
)
|
||||
from app.arbiter import (
|
||||
ACTUATOR_SITE, P_ARBITRAGE, P_LOOP, P_MAINT_SHAPE, P_MAINTENANCE, P_PEAK,
|
||||
@@ -99,47 +99,138 @@ check("...and 995 is not 1990, which is the sentence ARCH-01 is about. A "
|
||||
# check", and it says so because the failure mode is a BIAS - a fraction of a
|
||||
# watt per tick that only shows up in aggregate. A spot check at a round number
|
||||
# cannot see it; 250 000 randomised ticks can.
|
||||
#
|
||||
# ⚠️ AND THE GENERATOR IS THE OTHER HALF OF THE CHECK, WHICH THIS FILE GOT
|
||||
# WRONG ONCE AND IS THE REASON THIS PARAGRAPH EXISTS. The first version drew
|
||||
# every envelope as `B(-uniform(0, RATING), uniform(0, RATING))` - finite on
|
||||
# both ends and containing zero, EVERY TIME. The check it fed says "no share
|
||||
# ever leaves the envelope it was clamped into", which reads as a statement
|
||||
# about `UnitBounds`; the data could only ever exercise a symmetric closed
|
||||
# interval around zero. So it reported 0 escapes over 250 000 ticks while a
|
||||
# reviewer's own 120 000 - drawn over the whole shape space - reported 6 254,
|
||||
# against a real clamp defect on half-open intervals.
|
||||
#
|
||||
# `UnitBounds` DEFAULTS TO `(-inf, +inf)`, so an envelope with one end missing
|
||||
# is the ordinary case at N>=2, not an edge. `_shape()` therefore draws from
|
||||
# every shape the type can express, and the counters below are printed so that
|
||||
# "the replay covers half-open intervals" is a number a reader can see rather
|
||||
# than a sentence this file asserts about itself.
|
||||
def _shape(rnd):
|
||||
"""One random envelope, drawn across EVERY SHAPE `UnitBounds` CAN HOLD."""
|
||||
kind = rnd.randrange(9)
|
||||
if kind <= 2: # finite, straddling zero
|
||||
return B(-rnd.uniform(0.0, RATING), rnd.uniform(0.0, RATING))
|
||||
if kind == 3: # a FLOOR and no ceiling
|
||||
return B(rnd.uniform(-RATING, RATING), inf)
|
||||
if kind == 4: # a ceiling and no floor
|
||||
return B(-inf, rnd.uniform(-RATING, RATING))
|
||||
if kind == 5: # finite, EXCLUDES zero, +ve
|
||||
_lo = rnd.uniform(1.0, RATING)
|
||||
return B(_lo, _lo + rnd.uniform(0.0, RATING))
|
||||
if kind == 6: # finite, EXCLUDES zero, -ve
|
||||
_hi = -rnd.uniform(1.0, RATING)
|
||||
return B(_hi - rnd.uniform(0.0, RATING), _hi)
|
||||
if kind == 7: # half-open AND excludes zero
|
||||
return (B(rnd.uniform(1.0, RATING), inf) if rnd.random() < 0.5
|
||||
else B(-inf, -rnd.uniform(1.0, RATING)))
|
||||
return B() # bare: (-inf, +inf)
|
||||
|
||||
|
||||
def _half_open(b):
|
||||
return (b.lo == -inf) != (b.hi == inf)
|
||||
|
||||
|
||||
def _excludes_zero(b):
|
||||
return b.lo > 0.0 or b.hi < 0.0
|
||||
|
||||
|
||||
random.seed(20260902)
|
||||
_N = 250_000
|
||||
_bad_sum = _bad_env = _refused = 0
|
||||
_bias = 0
|
||||
_span = 0
|
||||
_refused_ordinary = _ordinary = 0
|
||||
_bad_record = 0
|
||||
_quant = 0.0
|
||||
_span = _n_open = _n_half = _n_nozero = _n_bare = 0
|
||||
_esc_half = _esc_nozero = 0
|
||||
for _i in range(_N):
|
||||
_S = random.uniform(-6000.0, 6000.0)
|
||||
_ba = B(-random.uniform(0.0, RATING), random.uniform(0.0, RATING))
|
||||
_bb = B(-random.uniform(0.0, RATING), random.uniform(0.0, RATING))
|
||||
_ba, _bb = _shape(random), _shape(random)
|
||||
_env = {A: _ba, Bee: _bb}
|
||||
for _b in (_ba, _bb):
|
||||
_n_half += _half_open(_b)
|
||||
_n_nozero += _excludes_zero(_b)
|
||||
_n_bare += (_b.lo == -inf and _b.hi == inf)
|
||||
_n_open += (_b.lo == -inf or _b.hi == inf)
|
||||
# AN ORDINARY TICK: no pin, and every envelope contains zero. That is the
|
||||
# population part 6 asks the sign gate never to fire on - and it is a
|
||||
# POPULATION now rather than the whole replay, because the widened shapes
|
||||
# include the forced floors the gate exists for.
|
||||
_ord = all(_b.lo <= 0.0 <= _b.hi for _b in (_ba, _bb))
|
||||
_ordinary += _ord
|
||||
_wa = random.random()
|
||||
_p = plan(_S, {A: _ba, Bee: _bb}, weights={A: _wa, Bee: 1.0 - _wa})
|
||||
_p = plan(_S, _env, weights={A: _wa, Bee: 1.0 - _wa})
|
||||
if not _p.ok:
|
||||
_refused += 1
|
||||
_refused_ordinary += _ord
|
||||
continue
|
||||
if sum(_p.shares.values()) + _p.unmet_w != _p.sum_w:
|
||||
_bad_sum += 1
|
||||
for _u, _s in _p.shares.items():
|
||||
_b = {A: _ba, Bee: _bb}[_u]
|
||||
if not (_b.lo - 1.0 <= _s <= _b.hi + 1.0):
|
||||
_b = _env[_u]
|
||||
# ⚠️ NO TOLERANCE. `_int_envelope()` narrows INWARD, so every integer
|
||||
# the allocator may command is inside the real interval - there is
|
||||
# nothing for a slack of a watt to absorb except a defect.
|
||||
if not (_b.lo <= _s <= _b.hi):
|
||||
_bad_env += 1
|
||||
_esc_half += _half_open(_b)
|
||||
_esc_nozero += _excludes_zero(_b)
|
||||
# AND THE RECORD IS GRADED AGAINST THE SAME REPLAY, because the defect that
|
||||
# produced this generator also produced a `UnitPlan.explain()` line reading
|
||||
# `inverter-a -> +500 W in [2000, +inf]` - the record agreeing with itself
|
||||
# about a share the interval excludes.
|
||||
if not all(_pl.within_envelope for _pl in _p.plans):
|
||||
_bad_record += 1
|
||||
_quant += _p.quantisation_w
|
||||
if _p.unmet_w == 0:
|
||||
_bias += _p.sum_w - sum(_p.shares.values())
|
||||
_span += 1
|
||||
print(f" replayed {_N} ticks at random targets, random envelopes and random "
|
||||
f"weights: {_bad_sum} sum failures, {_bad_env} envelope escapes, "
|
||||
f"{_refused} refusals")
|
||||
print(f" envelope shapes drawn ({2 * _N} in total): {_n_open} with an "
|
||||
f"infinite end, of which {_n_half} HALF-OPEN and {_n_bare} bare "
|
||||
f"UnitBounds(); {_n_nozero} that EXCLUDE zero")
|
||||
check("⚠️ OVER A QUARTER OF A MILLION TICKS THE SUM IS EXACT EVERY TIME. Not "
|
||||
"within a tolerance - EXACT - because `sum_w` is an int, the shares are "
|
||||
"ints and the leftover is settled in integer arithmetic, so there is no "
|
||||
"float in the identity for an error to accumulate in",
|
||||
_bad_sum == 0)
|
||||
check("...and no share ever leaves the envelope it was clamped into, over the "
|
||||
"same replay. An allocator that meets its sum by over-driving one unit "
|
||||
"past its rating has met the wrong invariant",
|
||||
"same replay - INCLUDING the half-open ones and the ones that exclude "
|
||||
"zero, which is the coverage this check claimed before it had it. An "
|
||||
"allocator that meets its sum by over-driving one unit past its rating "
|
||||
"has met the wrong invariant",
|
||||
_bad_env == 0)
|
||||
check("⚠️ AND THE ACCUMULATED BIAS IS ZERO, WHICH IS THE POINT OF LARGEST "
|
||||
f"REMAINDER. Summed over {_span} unconstrained ticks the total shortfall "
|
||||
"is 0 W. A 1 W systematic bias at 1-2 Hz is not a rounding artefact - it "
|
||||
"is a permanent standing error with the site integrator wound around it, "
|
||||
"chasing a target the allocator will never let it reach",
|
||||
_bias == 0)
|
||||
check("...and the replay is NOT VACUOUS about that: it really did draw "
|
||||
f"{_n_half} half-open intervals, {_n_bare} bare `UnitBounds()` and "
|
||||
f"{_n_nozero} intervals excluding zero. Revert `_clamp_int` to the form "
|
||||
"that substitutes `v` for a missing bound and this check goes red on the "
|
||||
"half-open draws; under the old generator it could not",
|
||||
_n_half > 10_000 and _n_bare > 10_000 and _n_nozero > 10_000)
|
||||
check("...and every plan in the record AGREES with the share it carries: no "
|
||||
"`UnitPlan` was built asserting a number is inside an interval that "
|
||||
"excludes it. The record is what SAFETY-28 gates on, so a record that "
|
||||
"describes a share it did not check is worth less than no record",
|
||||
_bad_record == 0)
|
||||
check("⚠️ AND THE ONLY ROUNDING LEFT IS THE SITE-LEVEL ONE, WHICH DOES NOT "
|
||||
f"DRIFT. Over {_N - _refused} allocations the total of "
|
||||
f"`quantisation_w` - `sum_w - target_w`, at most half a watt each and "
|
||||
f"NEVER distributed into a per-unit bias - is {_quant:+.1f} W, i.e. "
|
||||
f"{_quant / max(1, _N - _refused):+.5f} W per tick. A 1 W systematic "
|
||||
"bias at 1-2 Hz is not a rounding artefact - it is a permanent standing "
|
||||
"error with the site integrator wound around it. (Largest remainder "
|
||||
"against the per-unit alternative is measured in part 2; the sum "
|
||||
f"identity above holds on all {_span} unconstrained ticks.)",
|
||||
abs(_quant) / max(1, _N - _refused) < 0.01)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
@@ -371,10 +462,13 @@ check("the same pin with a site target BEYOND it is fine, because the shares "
|
||||
Bee: B(-RATING, RATING)},
|
||||
site_priority=P_LOOP).shares == {A: 2500, Bee: 500})
|
||||
check("⚠️ AND IT NEVER FIRES ON AN ORDINARY TICK, which is what makes it a gate "
|
||||
"rather than a nuisance. Over the 250 000-tick replay in part 1 - random "
|
||||
"targets, random envelopes, random weights, no pins - it refused "
|
||||
f"{_refused} times",
|
||||
_refused == 0)
|
||||
"rather than a nuisance. Of the 250 000-tick replay in part 1 - random "
|
||||
f"targets, random envelopes, random weights, no pins - {_ordinary} ticks "
|
||||
"drew envelopes that all CONTAIN zero, half-open ones and bare "
|
||||
f"`UnitBounds()` among them, and it refused {_refused_ordinary} of "
|
||||
f"those. The other {_refused} refusals in that replay are this gate doing "
|
||||
"its job on the forced floors the widened generator now draws",
|
||||
_refused_ordinary == 0 and _ordinary > 50_000)
|
||||
check("...and it cannot: with no pin and an envelope that contains zero, the "
|
||||
"fill only ever moves shares in ONE direction away from zero, so two "
|
||||
"signs are not expressible. The gate exists for the envelope that "
|
||||
@@ -382,6 +476,58 @@ check("...and it cannot: with no pin and an envelope that contains zero, the "
|
||||
all(v >= 0 for v in plan(1990.0, TWO).shares.values())
|
||||
and all(v <= 0 for v in plan(-1990.0, TWO).shares.values()))
|
||||
|
||||
# ⚠️ THE HALF-OPEN ENVELOPE, WHICH IS WHERE THIS GATE WAS ONCE SILENTLY
|
||||
# DISARMED - and the ticket's first named deliverable, "each share clipped to
|
||||
# that unit's UnitBounds interval", with it. `_clamp_int()` used to substitute
|
||||
# `v` for a missing bound and feed both ends into one `min(max(...))`; with
|
||||
# `hi == inf` that collapses to `min(max(v, lo), v) == v`, so THE FLOOR WAS
|
||||
# DISCARDED BY THE ABSENCE OF A CEILING. The worked case: inverter-a with a
|
||||
# 2000 W floor and no ceiling, inverter-b symmetric, a +1000 W site target.
|
||||
_HALF = {A: B(2000.0, inf), Bee: B(-5000.0, 5000.0)}
|
||||
_FIN = {A: B(2000.0, 9_000_000.0), Bee: B(-5000.0, 5000.0)}
|
||||
_half, _fin = plan(1000.0, _HALF), plan(1000.0, _FIN)
|
||||
print(f" floor 2000 W, NO ceiling: {_half.explain()}")
|
||||
print(f" the same with a finite ceiling: {_fin.explain()}")
|
||||
check("⚠️ A ONE-SIDED ENVELOPE REFUSES EXACTLY AS THE TWO-SIDED ONE DOES. Both "
|
||||
"put inverter-a at its 2000 W floor against a +1000 W site target, so "
|
||||
"inverter-b must charge for the shares to sum - circulating power, and "
|
||||
"the gate fires. The defect made the half-open case return a=+500, "
|
||||
"b=+500 instead: inverter-a commanded at a quarter of its own floor, and "
|
||||
"a refusal that should have fired suppressed with it",
|
||||
_half.ok is False and REFUSE_SIGN in _half.refused
|
||||
and _fin.ok is False and REFUSE_SIGN in _fin.refused)
|
||||
check("...and the two ends are applied INDEPENDENTLY, which is the shape of the "
|
||||
"fix rather than a patch of the one case above: a missing ceiling does "
|
||||
"not discard the floor, a missing floor does not discard the ceiling, and "
|
||||
"a bare `UnitBounds()` binds nothing",
|
||||
_clamp_int(0, 2142, inf) == 2142
|
||||
and _clamp_int(9999, 2142, inf) == 9999
|
||||
and _clamp_int(0, -inf, -500) == -500
|
||||
and _clamp_int(-9999, -inf, -500) == -9999
|
||||
and _clamp_int(-7, -inf, inf) == -7
|
||||
and _clamp_int(0, 2142, 5000) == 2142)
|
||||
check("...and the RECORD can no longer assert a share is inside an interval "
|
||||
"that excludes it. The defect's log line read `inverter-a -> +500 W in "
|
||||
"[2000, +inf] (weighted share)` - the record naming the floor and the "
|
||||
"share beneath it in one breath and agreeing with itself. If that state "
|
||||
"is ever reachable again the line says OUTSIDE, and says it in the line "
|
||||
"an operator is already reading",
|
||||
UnitPlan(A, 500, 2000.0, inf, 0.5, "weighted share").within_envelope
|
||||
is False
|
||||
and "OUTSIDE" in UnitPlan(A, 500, 2000.0, inf, 0.5, "x").explain()
|
||||
and "DO NOT WRITE" in UnitPlan(A, 500, 2000.0, inf, 0.5, "x").explain()
|
||||
and "in [2000, +inf]" in UnitPlan(A, 2500, 2000.0, inf, 0.5,
|
||||
"weighted share").explain())
|
||||
check("...and a DROPPED unit is not shouted about: it was commanded nothing, "
|
||||
"and its interval may be a contradiction that no number is inside. That "
|
||||
"case is already reported by name, and a violation banner on it would be "
|
||||
"the guard crying wolf on the one row that is working",
|
||||
"not commanded" in UnitPlan(A, 0, 3000.0, -3000.0, 0.0,
|
||||
f"DROPPED: {DROP_CONTRADICTION}",
|
||||
dropped=True).explain()
|
||||
and UnitPlan(A, 0, 3000.0, -3000.0, 0.0, "DROPPED: x",
|
||||
dropped=True).within_envelope is True)
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
print()
|
||||
@@ -784,6 +930,22 @@ MUT.append(("the site target TRUNCATED instead of rounded - a bias toward zero "
|
||||
max(map(abs, _q)) <= 0.5,
|
||||
"part 2: '|quantisation| is never more than half a watt'"))
|
||||
|
||||
# ⚠️ THE ONE THAT SHIPPED, AND THE ROW THIS TABLE IS MOST INDEBTED TO. This
|
||||
# mutation IS the code that was on `186e13c`, so it is not hypothetical: the
|
||||
# question it answers is not "would a check catch it" but "why did none".
|
||||
_collapsed = min(max(int(0), 2142), 0) # the old form, evaluated at hi=inf
|
||||
MUT.append(("the two ends of `_clamp_int` collapsed back into one min(max()), "
|
||||
"so a MISSING CEILING discards the FLOOR",
|
||||
_collapsed == 0 and _clamp_int(0, 2142, inf) == 2142
|
||||
and _half.ok is False,
|
||||
"part 6: 'a one-sided envelope refuses exactly as the two-sided "
|
||||
"one does', and part 1's replay - 39 991 envelope escapes over the "
|
||||
"same 250 000 ticks. ⚠️ AND ZERO ESCAPES UNDER THE GENERATOR THIS "
|
||||
"FILE ORIGINALLY SHIPPED WITH, which drew every envelope finite on "
|
||||
"both ends and containing zero: the mutation was already live and "
|
||||
"the table would have called it caught. The fix is graded by the "
|
||||
"SHAPES the generator can express, not by the number of ticks"))
|
||||
|
||||
# ⚠️ AND NOW THE ONES THAT DO NOT BITE. Three of them, found by actually running
|
||||
# the mutations rather than by reasoning about them, and each is reported with
|
||||
# WHY - because a mutation table that only lists its successes is the same
|
||||
|
||||
Reference in New Issue
Block a user