SAFETY-119: re-pin test_safety84/87 digests (log only); prove it
The re-pinned digests differ only in OUTPUT_CLAMP log rows; every command and claim is the same as without the fix (test_safety119.py part 5). DECISIONS: the delay at a 5 s meter and the open swap case. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude
parent
c4e15ec4cd
commit
b0c2e5977d
@@ -5,23 +5,33 @@
|
|||||||
**Cause.** When another owner (the peak guard, a maintenance charge) has set
|
**Cause.** When another owner (the peak guard, a maintenance charge) has set
|
||||||
the wire and the loop wins again, `cycle()` re-seeds the integrator from that
|
the wire and the loop wins again, `cycle()` re-seeds the integrator from that
|
||||||
owner's command, and `compute()` adds `gain * error` from a meter that does
|
owner's command, and `compute()` adds `gain * error` from a meter that does
|
||||||
not show that command yet. On the TEST-23 rig (SAFETY-118 I-2) the guard's
|
not show that command yet. On the TEST-23 rig (SAFETY-118 I-2) the guard
|
||||||
last-seconds step went from -2000 W to +429 W. The next cycle's ask crossed
|
stepped from -2000 W to +429 W with 5 s left. A cycle later the loop's ask
|
||||||
the +2500 W rail and beat the guard's floor, so SAFETY-85 rule 1 counted it
|
crossed the +2500 W rail and beat the guard's floor, so SAFETY-85 rule 1
|
||||||
as the loop's own bound and raised `goodwe_integrator_bound`. The battery gave
|
counted it as the loop's own bound and raised `goodwe_integrator_bound`. The
|
||||||
what it was told; the cap held.
|
battery gave what it was told, and the cap held.
|
||||||
|
|
||||||
**Decision.** On the cycles after a hand-back, the loop's integrator bound is
|
**Decision.** After a hand-back, the loop's integrator bound is not counted
|
||||||
not counted until the plant's settle has passed (`SHORT_CYCLES` cycles and
|
until the plant's settle has passed (`SHORT_CYCLES` cycles and
|
||||||
`SHORT_SPAN_S`, the same window SAFETY-85 grades a battery on). After that it
|
`SHORT_SPAN_S`, the window SAFETY-85 grades a battery on). After that it
|
||||||
counts as before. An over-rating (`clamped`, a bug) always counts. No command
|
counts as before. An over-rating (`clamped`, a bug) always counts. No command
|
||||||
changes, and no text was added or changed.
|
changes, and no text was added or changed.
|
||||||
|
|
||||||
**Kept.** The loop in charge at its rail warns on the same cycle as before. A
|
**Kept.** The loop in charge at its rail warns on the same cycle as before.
|
||||||
loop still at its rail after the window warns then (about 22 s later): delay,
|
A loop still at its rail after a hand-back warns once the window has passed:
|
||||||
not loss. The start and a restart after a stop are not hand-backs.
|
22 s at a 2 s meter, 55 s at 5 s. Delay, not loss. The start and a restart
|
||||||
|
after a stop are not hand-backs.
|
||||||
|
|
||||||
|
**Re-pinned.** test_safety84 (all six) and test_safety87 (`n1_crossing`,
|
||||||
|
`n1_above`, `n1_charge_low`): only OUTPUT_CLAMP log rows differ; commands and
|
||||||
|
claims are the same (`test_safety119.py` part 5).
|
||||||
|
|
||||||
|
**Open.** If another owner and the loop swapped the wire every few cycles,
|
||||||
|
each swap restarts the window and the loop's bound would not be counted.
|
||||||
|
A loop at its rail beats a floor or ceiling on its own side, so it keeps the
|
||||||
|
wire; a swap needs a claim that keeps outranking it.
|
||||||
|
|
||||||
**Not changed.** The kick itself is control behaviour: for a few seconds at
|
**Not changed.** The kick itself is control behaviour: for a few seconds at
|
||||||
a quarter's edge the loop asks up to its rail. In `test_safety119.py` part 1
|
a quarter's edge the loop asks up to its rail. In `test_safety119.py` part 1
|
||||||
that is more discharge than the guard asked at a quarter's end, and about
|
that is more discharge than the guard asked at a quarter's end, and a few
|
||||||
1700 Ws more charge (2 W on that quarter's average) at the next one's start.
|
seconds of up to 500 W more charge at the next quarter's start.
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ DECISIONS/SAFETY-119-bound-after-hand-back.md.
|
|||||||
3. With a usable forecast: the same commands and the same alarms as without
|
3. With a usable forecast: the same commands and the same alarms as without
|
||||||
the fix.
|
the fix.
|
||||||
4. The rule at its boundaries (`Controller._settling`).
|
4. The rule at its boundaries (`Controller._settling`).
|
||||||
|
5. test_safety84's and test_safety87's re-pinned digests: the same commands
|
||||||
|
and claims; only OUTPUT_CLAMP log rows differ.
|
||||||
|
|
||||||
Mutations (MUTATION.md, purged copies) are listed in the pull request.
|
Mutations (MUTATION.md, purged copies) are listed in the pull request.
|
||||||
"""
|
"""
|
||||||
@@ -81,7 +83,8 @@ def _power(self, u, lag=1.0):
|
|||||||
return float(round(a))
|
return float(round(a))
|
||||||
|
|
||||||
|
|
||||||
_real_number = rig._Hass.number
|
_real_number, _real_power = rig._Hass.number, rig._Hass.power
|
||||||
|
_real_feed = s79._feed
|
||||||
|
|
||||||
|
|
||||||
async def _number(self, e, invert=False):
|
async def _number(self, e, invert=False):
|
||||||
@@ -338,6 +341,43 @@ _ctl._loop_alarm_input(_Res())
|
|||||||
check("a stop clears it: the next start is not a hand-back",
|
check("a stop clears it: the next start is not a hand-back",
|
||||||
(_ctl._wire_lost, _ctl._handback) == (False, None))
|
(_ctl._wire_lost, _ctl._handback) == (False, None))
|
||||||
|
|
||||||
|
# ============================================================================ #
|
||||||
|
print("\n5. the pinned digests of test_safety84 and test_safety87: only the "
|
||||||
|
"log changes")
|
||||||
|
# Those harnesses run at the 5 s tick with a battery that follows at once.
|
||||||
|
rig._Hass.power, rig.TICK, s79._feed = _real_power, 5.0, _real_feed
|
||||||
|
rig._Hass.number = _real_number
|
||||||
|
import safety87_identity as s87 # noqa: E402
|
||||||
|
|
||||||
|
RUNS = dict({f"s84 {k}": (lambda v=v: s84.run(**v))
|
||||||
|
for k, v in s84.SCENARIOS.items()},
|
||||||
|
**{f"s87 {k}": (lambda v=v: s87_run(v))
|
||||||
|
for k, v in s87.SCENARIOS.items()})
|
||||||
|
|
||||||
|
|
||||||
|
def s87_run(v):
|
||||||
|
v = dict(v)
|
||||||
|
return s84.run(1, dur=s87.DUR, store=s87.store(v.pop("store", None)), **v)
|
||||||
|
|
||||||
|
|
||||||
|
_moved, _gone = [], 0
|
||||||
|
for k, fn in RUNS.items():
|
||||||
|
new, old = fn()[0], no_fix(fn)[0]
|
||||||
|
same = all(a[:-1] == b[:-1] and a[-1][:4] == b[-1][:4]
|
||||||
|
for a, b in zip(new, old)) and len(new) == len(old)
|
||||||
|
logs = {e for r in old for e in [r[-1][5]] if e} - {
|
||||||
|
e for r in new for e in [r[-1][5]] if e}
|
||||||
|
_gone += len(logs)
|
||||||
|
if not same or any("OUTPUT_CLAMP episode" not in e for e in logs):
|
||||||
|
_moved.append(k)
|
||||||
|
print(f" {k}: commands and claims "
|
||||||
|
f"{'the same' if same else 'DIFFER'}, log rows differ {len(logs)}, "
|
||||||
|
f"digest {s84.digest(new)[:16]}")
|
||||||
|
check("⭐ in all twelve, every command, loop field, claim, guard decision "
|
||||||
|
"and phase is the same as without the fix; the only log rows that "
|
||||||
|
"differ are OUTPUT_CLAMP rows (gone, or a lower count)",
|
||||||
|
not _moved and _gone > 0)
|
||||||
|
|
||||||
print()
|
print()
|
||||||
if fails:
|
if fails:
|
||||||
print(f"FAILED: {len(fails)} check(s)")
|
print(f"FAILED: {len(fails)} check(s)")
|
||||||
|
|||||||
@@ -472,19 +472,22 @@ print("\n8. the N=1 and N=2 digests")
|
|||||||
# charge pin stands. n2_charge and _banked change only the loop's reason and
|
# charge pin stands. n2_charge and _banked change only the loop's reason and
|
||||||
# claim on 2 cycles; n2_charge_dear changes commands on 98 cycles, where A's
|
# claim on 2 cycles; n2_charge_dear changes commands on 98 cycles, where A's
|
||||||
# pin lifts (t=1800) and B no longer jumps 1390 W, and after the surplus step.
|
# pin lifts (t=1800) and B no longer jumps 1390 W, and after the surplus step.
|
||||||
|
# SAFETY-119 re-pinned all six: only the log. OUTPUT_CLAMP rows from the
|
||||||
|
# loop's bound just after the guard hands back are gone; every command and
|
||||||
|
# claim is the same (test_safety119.py part 5).
|
||||||
PINNED = {
|
PINNED = {
|
||||||
"n1_charge":
|
"n1_charge":
|
||||||
"0ed25c856456b97acb07a39299a8cb7b1a762c4d0005b88a0823c03057a80be5",
|
"19781f90b9751b10994dedeaafb38e42d48ad2551640792e601336b7f9b5bb4f",
|
||||||
"n1_charge_dear":
|
"n1_charge_dear":
|
||||||
"c25340b56060d4581f7204e200b07e7c8618711be2baca3b1667b7ebeb600dc3",
|
"eeeb0fa19a6fdc04d44a0838bb2bb911f506a936b0f93d8a6f4d87159053ec74",
|
||||||
"n1_charge_banked":
|
"n1_charge_banked":
|
||||||
"2619acdd06b04e56c000b168d64e1d044346c6ba3f929c94258d6ad8189ec609",
|
"a51f125a8c1c98fdbb009fce72b6781d20d384bdf0f5ce7bbfe367d86e5477db",
|
||||||
"n2_charge":
|
"n2_charge":
|
||||||
"b16d692128f14bcce3d87b142d290e0d6b13724289680e44a05215c9eaa24a7c",
|
"e37e98390baa5dad9128991fabf8b170894395fe026d8a33c8a51de7ed0789cc",
|
||||||
"n2_charge_dear":
|
"n2_charge_dear":
|
||||||
"00a197d89a5dd1e8b3ae6a5d1af66329a4dc4850476569c5931c125fa4d9eaee",
|
"7727372de423f62d77b4426a7226eae31a30f3bb89a3cf70272cc7c1823d7263",
|
||||||
"n2_charge_banked":
|
"n2_charge_banked":
|
||||||
"a6973faea916e4c88ca1770047f94478346eeb1efa4889b154fad0747f91267f",
|
"c77e26006c026b953e9c3dcc9a02f3993589f885cb52c7cbceac6bd59b600551",
|
||||||
}
|
}
|
||||||
_now = s84.digests()
|
_now = s84.digests()
|
||||||
for k in PINNED:
|
for k in PINNED:
|
||||||
@@ -492,7 +495,8 @@ for k in PINNED:
|
|||||||
f"{_now[k][:16]}")
|
f"{_now[k][:16]}")
|
||||||
check("⭐ at N=1 a charge in and out of a cheap window and under a banked "
|
check("⭐ at N=1 a charge in and out of a cheap window and under a banked "
|
||||||
"month peak is byte-identical to 9d5b7d1 (commands, reasons and every "
|
"month peak is byte-identical to 9d5b7d1 (commands, reasons and every "
|
||||||
"claim), and N=2 outside a cheap window matches its SAFETY-85 pin",
|
"claim; the log as re-pinned by SAFETY-119), and N=2 outside a cheap "
|
||||||
|
"window matches its SAFETY-85 pin",
|
||||||
all(_now[k] == PINNED[k] for k in ("n1_charge", "n1_charge_dear",
|
all(_now[k] == PINNED[k] for k in ("n1_charge", "n1_charge_dear",
|
||||||
"n1_charge_banked",
|
"n1_charge_banked",
|
||||||
"n2_charge_dear")))
|
"n2_charge_dear")))
|
||||||
|
|||||||
@@ -518,17 +518,21 @@ check("...a stopped controller still says so: the peak alarm does not take "
|
|||||||
|
|
||||||
# ============================================================================ #
|
# ============================================================================ #
|
||||||
print("\n9. N=1 is byte-identical to release/1.0 b8ddfd7")
|
print("\n9. N=1 is byte-identical to release/1.0 b8ddfd7")
|
||||||
|
# SAFETY-119 re-pinned n1_crossing, n1_above and n1_charge_low: only the
|
||||||
|
# log. The loop's bound just after a hand-back is not counted, so OUTPUT_CLAMP
|
||||||
|
# rows are gone or count fewer; every command and claim is the same
|
||||||
|
# (test_safety119.py part 5).
|
||||||
PINNED = { # tools/safety87_identity.py on b8ddfd7
|
PINNED = { # tools/safety87_identity.py on b8ddfd7
|
||||||
"n1_under":
|
"n1_under":
|
||||||
"d2395ef8d74fb23607ad864d5a4bf62c03044bbd9367e873675d7ad10e8c169f",
|
"d2395ef8d74fb23607ad864d5a4bf62c03044bbd9367e873675d7ad10e8c169f",
|
||||||
"n1_crossing":
|
"n1_crossing":
|
||||||
"97f7321017d9d34ee17a40dfd4abf73d915f25b9faef086a483d2b5cd124b198",
|
"20f8e542c1ee3eb3a58c8fd6ba3b3e5b6b1486800cd63b675abc68bc222a172b",
|
||||||
"n1_above":
|
"n1_above":
|
||||||
"45726a285aa84f40542d56482652397c3c1e212ba4fe2009f6cc52f537ccad52",
|
"3a79f1394b2b49fa88f3c054da1d9da450153ad5f0d2a2c2f960d02a7abe2208",
|
||||||
"n1_under_banked":
|
"n1_under_banked":
|
||||||
"a2876c7cc873bc4c974d2b4bd98e23bba2d007b84545587d2fe0b713b0a6249b",
|
"a2876c7cc873bc4c974d2b4bd98e23bba2d007b84545587d2fe0b713b0a6249b",
|
||||||
"n1_charge_low":
|
"n1_charge_low":
|
||||||
"91336e3c0314985a3b00f314c79b5ffb5a740d218de3ad44f88a4db6e4235b32",
|
"2e1ee6db0d4f9f18836c4cbbe406fddd496656b54b97f329da513a1591f0ce6c",
|
||||||
"n1_hold":
|
"n1_hold":
|
||||||
"0a1df347754c69166b20d04c15ee878bbcefb02abb824b9b452334f68f03e46d",
|
"0a1df347754c69166b20d04c15ee878bbcefb02abb824b9b452334f68f03e46d",
|
||||||
}
|
}
|
||||||
@@ -536,7 +540,7 @@ _now = s87.digests()
|
|||||||
for k in PINNED:
|
for k in PINNED:
|
||||||
print(f" {k} {'same' if _now[k] == PINNED[k] else 'DIFFERS'}")
|
print(f" {k} {'same' if _now[k] == PINNED[k] else 'DIFFERS'}")
|
||||||
check("⭐ every N=1 digest (claims, commands, the guard's decision and the "
|
check("⭐ every N=1 digest (claims, commands, the guard's decision and the "
|
||||||
"log) equals b8ddfd7's, under, crossing and above the reserve, in a "
|
"log) equals b8ddfd7's (the log as re-pinned by SAFETY-119), under, crossing and above the reserve, in a "
|
||||||
"charge and a HOLD, and under a banked peak",
|
"charge and a HOLD, and under a banked peak",
|
||||||
_now == PINNED)
|
_now == PINNED)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user