SAFETY-119 round 3c: the hidden-bound budget follows the cycle length
No single rolling budget met both cases in the sweep (8 false-alarmed on 2 s seed 25 and warned in 1 to 3 of 7 quarters at 15 s). The budget is now 10 cycles up to a 3.5 s cycle, 8 up to 11 s, 6 up to 16 s, 4 beyond, per rolling 15 minutes; 8 while the cycle length is unknown. A stop or a gap over two minutes is not a cycle length. The unsourced "owner accepted N=8" paragraph is removed from DECISIONS. test_safety119 part 4 now runs 2 s seed 25 and shows a fixed budget of 8 warns there; the never case runs at 2 s and checks every other quarter at 15 s. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
e27e62ff72
commit
6fd0fa05f1
@@ -10,7 +10,8 @@ limit, so the cycle counted as the loop's own and raised
|
||||
`goodwe_integrator_bound`. The battery gave what it was told. Now the loop's
|
||||
bound does not count until the plant's settle (SHORT_CYCLES cycles and
|
||||
SHORT_SPAN_S) has passed since the hand-back. Round 3: across all
|
||||
hand-backs the bound is hidden on at most ROLLING_BOUND_CYCLES cycles in any
|
||||
hand-backs the bound is hidden on at most ROLLING_BUDGET[cycle length] cycles
|
||||
(10 up to a 3.5 s cycle, 8 up to 11 s, 6 up to 16 s, 4 beyond) in any
|
||||
ROLLING_S seconds, so a bound that stays warns when the guard and the charge
|
||||
keep taking turns. DECISIONS/SAFETY-119-bound-after-hand-back.md.
|
||||
|
||||
@@ -23,10 +24,11 @@ keep taking turns. DECISIONS/SAFETY-119-bound-after-hand-back.md.
|
||||
hand-back (after the settle).
|
||||
3. With a usable forecast: the same commands and the same alarms as without
|
||||
the fix.
|
||||
4. Repeated hand-backs (N=2, the guard and the charge take turns), 5, 10 and
|
||||
15 s meters: it warns (every quarter at 5 s and 10 s); the quarter-edge
|
||||
case stays silent at 10 s and 15 s too; a start or a blind cycle is not a
|
||||
hand-back; a slow meter delays by cycles, not seconds.
|
||||
4. Repeated hand-backs (N=2, the guard and the charge take turns), 2, 5, 10
|
||||
and 15 s meters: it warns (every quarter at 2, 5 and 10 s, every other
|
||||
quarter at least at 15 s); the quarter-edge case stays silent at 2, 5, 10
|
||||
and 15 s, including the 2 s seed where a fixed budget of 8 warns; a start
|
||||
or a blind cycle is not a hand-back; a slow meter delays by cycles.
|
||||
5. The rule at its boundaries (`Controller._settling`): the settle, and the
|
||||
rolling budget.
|
||||
6. test_safety84's and test_safety87's re-pinned digests: the same commands
|
||||
@@ -250,7 +252,7 @@ _first = notes(_hb[1])[:1]
|
||||
print(f" hand-back to a loop at its rail at 600 s: first notification "
|
||||
f"{_first}, without the fix {notes(_hb_old[1])[:1]}; command at the "
|
||||
f"end {_hb[0][-1][1]}")
|
||||
MB = 8
|
||||
MB = 10 # the budget at a 1 s cycle
|
||||
check(f"⭐ the loop at its rail after a hand-back still warns, once "
|
||||
f"{MB} of its bound cycles are hidden (1 s meter: {MB} s)",
|
||||
_first and 600.0 + MB <= _first[0] <= 600.0 + MB + 4.0
|
||||
@@ -329,8 +331,8 @@ def per_quarter(c):
|
||||
for q in range(8)]
|
||||
|
||||
|
||||
_nv = {t: never(t) for t in (5.0, 10.0, 15.0)}
|
||||
_nv_old = {t: never(t, fix=False) for t in (5.0, 10.0, 15.0)}
|
||||
_nv = {t: never(t) for t in (2.0, 5.0, 10.0, 15.0)}
|
||||
_nv_old = {t: never(t, fix=False) for t in (2.0, 5.0, 10.0, 15.0)}
|
||||
_nv_r1 = patched(Controller, "_settling", round1)(lambda: never(5.0))
|
||||
for _t in _nv:
|
||||
print(f" N=2, {_t:.0f} s meter, house 5050 W: notifications "
|
||||
@@ -339,20 +341,23 @@ for _t in _nv:
|
||||
f"{len(notes(_nv_old[_t][1]))}, highest quarter "
|
||||
f"{max(quarters(_nv[_t][1], _t)):.0f} W")
|
||||
print(f" round 1's rule at 5 s: {len(notes(_nv_r1[1]))}")
|
||||
check("⭐ 5 s meter: it warns in every quarter after the first",
|
||||
all(per_quarter(_nv[5.0][1])[q] >= 1 for q in range(1, 8)))
|
||||
check("⭐ 2 s and 5 s meters: it warns in every quarter after the first",
|
||||
all(per_quarter(_nv[t][1])[q] >= 1 for q in range(1, 8)
|
||||
for t in (2.0, 5.0)))
|
||||
check("⭐ 10 s meter: it warns in every quarter after the first (round 2 "
|
||||
"gave none)",
|
||||
all(per_quarter(_nv[10.0][1])[q] >= 1 for q in range(1, 8)))
|
||||
check("⭐ 15 s meter: it warns (round 2 gave none), in at least one quarter "
|
||||
"of the seven after the first",
|
||||
sum(1 for n in per_quarter(_nv[15.0][1])[1:] if n) >= 1)
|
||||
check("⭐ 15 s meter: it warns at least every other quarter after the first "
|
||||
"(round 2 gave none)",
|
||||
not any(a == 0 and b == 0 for a, b in zip(
|
||||
per_quarter(_nv[15.0][1])[1:], per_quarter(_nv[15.0][1])[2:])))
|
||||
check("non-vacuous: round 1's rule (a window per hand-back) never warned "
|
||||
"here, and the fix still hides some of what was raised without it",
|
||||
notes(_nv_r1[1]) == [] and all(
|
||||
_nv[t][1].clamp_wd.bound < _nv_old[t][1].clamp_wd.bound
|
||||
for t in _nv))
|
||||
check("...every command is the same as without the fix, at 5, 10 and 15 s",
|
||||
check("...every command is the same as without the fix, at 2, 5, 10 and "
|
||||
"15 s",
|
||||
all(commands(_nv[t][0]) == commands(_nv_old[t][0]) for t in _nv))
|
||||
check("...and at 5 s the cap held",
|
||||
len(quarters(_nv[5.0][1], 5.0)) == 8
|
||||
@@ -361,9 +366,12 @@ check("...and at 5 s the cap held",
|
||||
# The quarter-edge case (part 1) at the slower meters: still no warning.
|
||||
# The meter's own lag limits the cap there, the same with and without the fix.
|
||||
_qe = {}
|
||||
# Seeds 1 to 3 are the brief's fixed set; 2 s seed 25 is the one where the
|
||||
# sweep's fixed budget of 8 warned (DECISIONS), kept here so that stays seen.
|
||||
for _t in (2.0, 5.0, 10.0, 15.0): # with part 1: 1, 2, 5, 10, 15 s
|
||||
rig.TICK = _t
|
||||
for _seed, _dl in ((1, 1), (2, 1), (3, 2)): # the brief's fixed set
|
||||
for _seed, _dl in ((1, 1), (2, 1), (3, 2)) + (
|
||||
((25, 2),) if _t == 2.0 else ()):
|
||||
_qe[_t, _seed] = (run(house(_seed), delay=_dl),
|
||||
no_fix(lambda: run(house(_seed), delay=_dl)))
|
||||
rig.TICK = 1.0
|
||||
@@ -371,8 +379,8 @@ for _t in (2.0, 5.0, 10.0, 15.0): # with part 1: 1, 2, 5, 10, 15 s
|
||||
f"{[len(notes(r[0][1])) for k, r in _qe.items() if k[0] == _t]} "
|
||||
f"(without the fix "
|
||||
f"{[len(notes(r[1][1])) for k, r in _qe.items() if k[0] == _t]})")
|
||||
check("⭐ the quarter-edge case at 2, 5, 10 and 15 s meters, seeds 1 to 3: "
|
||||
"no notification",
|
||||
check("⭐ the quarter-edge case at 2, 5, 10 and 15 s meters, seeds 1 to 3 "
|
||||
"and (2 s) 25: no notification",
|
||||
all(notes(r[0][1]) == [] and r[0][1].clamp_wd.bound == 0
|
||||
for r in _qe.values()))
|
||||
check("...every command the same as without the fix; without it the false "
|
||||
@@ -380,9 +388,20 @@ check("...every command the same as without the fix; without it the false "
|
||||
all(commands(r[0][0]) == commands(r[1][0]) for r in _qe.values())
|
||||
and all(any(len(notes(_qe[_t, k][1][1])) >= 1 for k in (1, 2, 3))
|
||||
for _t in (2.0, 5.0, 10.0, 15.0)))
|
||||
check("the budget is the one the sweep chose (8). Changing it means "
|
||||
"re-running the sweep in DECISIONS/SAFETY-119-bound-after-hand-back.md",
|
||||
app_main.ROLLING_BOUND_CYCLES == 8 and app_main.ROLLING_S == 900.0)
|
||||
rig.TICK = 2.0
|
||||
_q25_8 = patched(app_main, "ROLLING_BUDGET", ((math.inf, 8),))(
|
||||
lambda: run(house(25), delay=2))
|
||||
rig.TICK = 1.0
|
||||
print(f" 2 s meter, seed 25, a fixed budget of 8: notifications "
|
||||
f"{notes(_q25_8[1])}")
|
||||
check("...2 s seed 25 is the sweep's known case: a fixed budget of 8 warns "
|
||||
"there (why the budget follows the cycle length)",
|
||||
len(notes(_q25_8[1])) >= 1)
|
||||
check("the budget is the one the sweep chose. Changing it means re-running "
|
||||
"the sweep in DECISIONS/SAFETY-119-bound-after-hand-back.md",
|
||||
app_main.ROLLING_BUDGET == ((3.5, 10), (11.0, 8), (16.0, 6),
|
||||
(math.inf, 4))
|
||||
and app_main.ROLLING_BOUND_CYCLES == 8 and app_main.ROLLING_S == 900.0)
|
||||
|
||||
# A start, or a blind cycle, is not a hand-back: the loop at its rail after
|
||||
# 20 s of not running warns at once (R2: a restart counted as a hand-back).
|
||||
@@ -398,6 +417,7 @@ check("⭐ a start after a blind spell is not a hand-back: it warns at the "
|
||||
|
||||
# The budget is cycles, not seconds (R4): at a 5 s meter the loop at its
|
||||
# rail after a hand-back warns 8 cycles (40 s) later, not 8 s.
|
||||
MB = 8 # the budget at a 5 s cycle
|
||||
rig.TICK = 5.0
|
||||
_sl = run(house(1, step=(600.0, -6000.0)), dur=900.0,
|
||||
extra={"peak_cap_w": 20000})
|
||||
@@ -418,6 +438,7 @@ class _C:
|
||||
|
||||
def __init__(self):
|
||||
self._wire_lost, self._handback, self._hidden = False, None, ()
|
||||
self._settle_t, self._settle_dt = None, None
|
||||
self.loop_clamped = (False, "", False)
|
||||
|
||||
|
||||
@@ -537,6 +558,25 @@ _c.loop_clamped = (True, "", False)
|
||||
check("an over-rating does not use up the budget",
|
||||
_c._settling(True, _t + 5.0) is True and len(_c._hidden) == 1)
|
||||
|
||||
# The budget follows the cycle length: bound cycles hidden before the first
|
||||
# one counts, one hand-back per turn.
|
||||
_bud = {}
|
||||
for _tick in (1.0, 2.0, 3.0, 5.0, 10.0, 15.0, 20.0, 30.0, 60.0):
|
||||
_c = _C()
|
||||
_o, _ = turns(_c, _tick, 1, min(14, int(880.0 // (2 * _tick))))
|
||||
_bud[_tick] = sum(1 for _t2, h in _o if h)
|
||||
print(f" hidden before the first counts, by cycle length: {_bud}")
|
||||
check("the budget by cycle length: 10 at 1-3 s, 8 at 5-10 s, 6 at 15 s, 4 "
|
||||
"at 20-60 s",
|
||||
_bud == {1.0: 10, 2.0: 10, 3.0: 10, 5.0: 8, 10.0: 8, 15.0: 6,
|
||||
20.0: 4, 30.0: 4, 60.0: 4})
|
||||
_c = _C()
|
||||
_o, _t = turns(_c, 2.0, 3, 8)
|
||||
_o2, _ = turns(_c, 121.0, 1, 3, first=_t + 119.0)
|
||||
check("a gap over two minutes is not a cycle length: the 2 s budget (10) "
|
||||
"stays",
|
||||
[h for _t2, h in _o + _o2] == [True] * 10 + [False])
|
||||
|
||||
# No monotonic clock: the budget cannot refill, so a bound that stays is
|
||||
# counted after the budget however often the wire changes hands.
|
||||
_c = _C()
|
||||
@@ -576,8 +616,10 @@ check("...while the integrator bound on the next cycle is not counted",
|
||||
_ctl._wire_lost, _ctl._handback = True, (1.0, 3)
|
||||
_ctl._loop_decision = None # stopped or blind
|
||||
_ctl._loop_alarm_input(_Res())
|
||||
check("a stop clears it: the next start is not a hand-back",
|
||||
(_ctl._wire_lost, _ctl._handback) == (False, None))
|
||||
check("a stop clears it: the next start is not a hand-back, and the stop "
|
||||
"is not a cycle length",
|
||||
(_ctl._wire_lost, _ctl._handback, _ctl._settle_t)
|
||||
== (False, None, None))
|
||||
|
||||
# ============================================================================ #
|
||||
print("\n6. the pinned digests of test_safety84 and test_safety87: only the "
|
||||
|
||||
Reference in New Issue
Block a user