OPT-11 P1 review: a mode never beats the peak floor; SAFETY-03 kept at the rail
- arbiter: a mode now gives way only as far as a site limit above P_MODE
needs (_unit_modes at N>1, _site_mode at N=1). Before, the modes were
released only when the peak floor bound S, so a trade won above the floor
kept them and placed less than the floor (an Off battery at N=1 went to 0 W
under a 2000 W floor), and a capped grid charge released an Off battery.
While released, held batteries weigh 0 and carry only the shortfall.
- control: a side of the loop bound that reaches the rail is left to the
rail, so SAFETY-03's i_clamped still fires with every battery on.
- opt11_identity: add maintenance scenarios and a 6000-case arbiter digest
(peak floor, trades, maintenance, refusals); a5dddad digests pinned.
- tests: test_arbiter part 11, test_control (g), test_modes (0/1 refused,
loop bound counts live units only). DOCS and DECISIONS say the new rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UBtqPrG1mDLXzTEBnhEkkV
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d69fd8fbfd
commit
ffc9a4d721
@@ -694,6 +694,102 @@ check("...and with no mode claim at all, stage 2's answer is untouched "
|
||||
resolve([Claim.set("loop", P_LOOP, 1200.0)], units=ONE).target_w
|
||||
== 1200.0)
|
||||
|
||||
print()
|
||||
print("OPT-11 11. a mode gives way only as far as the peak limit needs")
|
||||
# Review of PR #149. The first cut released the modes only when a limit above
|
||||
# P_MODE bound S. A trade (30) that won above the peak floor then kept the
|
||||
# modes, and the units placed less than the floor: the mode beat the peak
|
||||
# guard (a billed peak). And a peak limit that merely capped a charge released
|
||||
# the modes, so an Off battery was charged from the grid. The rule now: clamp
|
||||
# into what the modes can place, then back into the limits above the mode.
|
||||
_offb = {"inverter-b": (0.0, 0.0)}
|
||||
|
||||
|
||||
def _placed(r):
|
||||
return sum(r.shares.values())
|
||||
|
||||
|
||||
_ra = resolve(_two(0.0, arb=3000.0, peak_lo=2000.0, rating=1500.0,
|
||||
modes=_offb)[:-1] + [
|
||||
Claim.set("arbitrage", P_ARBITRAGE, 3000.0),
|
||||
Claim.limit("peak", P_PEAK, lo=2000.0)], units=TWO)
|
||||
print(f" trade 3000 W over a 2000 W floor, A 1500 W, B off: {_ra.shares}")
|
||||
check("⭐ a trade above the peak floor with B Off: the units still place the "
|
||||
"floor (2000 W), A to its rating and B only the 500 W A cannot give",
|
||||
_placed(_ra) == 2000 and _ra.shares == {"inverter-a": 1500,
|
||||
"inverter-b": 500})
|
||||
_r1t = resolve(_one + [Claim.set("arbitrage", P_ARBITRAGE, 3000.0),
|
||||
Claim.limit("peak", P_PEAK, lo=2000.0)], units=ONE)
|
||||
check("⭐ ...and at N=1 an Off battery under a trade gives the peak floor "
|
||||
"(2000 W), not 0 W", _r1t.target_w == 2000.0
|
||||
and _r1t.bound_by == "peak")
|
||||
_rc = resolve(_two(0.0, arb=-3000.0, peak_lo=-1000.0, modes=_offb),
|
||||
units=TWO)
|
||||
check("⭐ a grid charge the peak limit caps at 1000 W does not release B's "
|
||||
"Off: A takes it all and B stays at 0 W",
|
||||
_rc.shares == {"inverter-a": -1000, "inverter-b": 0})
|
||||
_r1c = resolve(_one + [Claim.set("arbitrage", P_ARBITRAGE, -3000.0),
|
||||
Claim.limit("peak", P_PEAK, lo=-1000.0)], units=ONE)
|
||||
check("...and at N=1 an Off battery is not charged from the grid under a "
|
||||
"capped trade", _r1c.target_w == 0.0)
|
||||
_rp = resolve(_two(500.0, peak_lo=1500.0, modes=_offb), units=TWO)
|
||||
check("a peak floor A can carry alone leaves B Off at 0 W",
|
||||
_rp.shares == {"inverter-a": 1500, "inverter-b": 0}
|
||||
and _rp.target_w == 1500.0)
|
||||
_rall = resolve(_two(0.0, peak_lo=3000.0,
|
||||
modes={"inverter-a": (0.0, 0.0),
|
||||
"inverter-b": (0.0, 0.0)}), units=TWO)
|
||||
check("every battery Off and a 3000 W peak floor: the floor is placed, "
|
||||
"split between them, and nothing contradicts",
|
||||
_placed(_rall) == 3000 and not _rall.contradiction
|
||||
and _rall.allocation.refused is None)
|
||||
|
||||
import random as _rnd # noqa: E402
|
||||
_g = _rnd.Random(149)
|
||||
_worse, _needless, _sign, _nn = [], [], [], 0
|
||||
for _ in range(20000):
|
||||
_units = ("inverter-a", "inverter-b", "inverter-c")[:_g.choice((2, 3))]
|
||||
_cl = []
|
||||
for _u in _units:
|
||||
_rt = _g.choice((1000.0, 2500.0))
|
||||
_cl.append(Claim.limit("supervised", P_SAFETY, -_rt, _rt,
|
||||
actuator=_u))
|
||||
if _g.random() < 0.2:
|
||||
_cl.append(Claim.limit("pack", P_SAFETY,
|
||||
*_g.choice(((0.0, inf), (-inf, 0.0))),
|
||||
actuator=_u))
|
||||
_cl.append(Claim.limit(MODE_SOURCE, P_MODE,
|
||||
_g.choice((0.0, -_rt)), _g.choice((0.0, _rt)),
|
||||
actuator=_u))
|
||||
_src = _g.choice(("loop", "arbitrage"))
|
||||
_cl.append(Claim.set(_src, P_LOOP if _src == "loop" else P_ARBITRAGE,
|
||||
_g.uniform(-7000.0, 7000.0)))
|
||||
_pk = _g.uniform(-4000.0, 6000.0)
|
||||
_cl.append(Claim.limit("peak", P_PEAK, lo=_pk))
|
||||
_r = resolve(_cl, units=_units)
|
||||
_r0 = resolve([c for c in _cl if c.source != MODE_SOURCE], units=_units)
|
||||
_nn += 1
|
||||
if _r.allocation.refused is not None:
|
||||
_sign.append(_cl)
|
||||
continue
|
||||
if _placed(_r) < _pk - 1 and _placed(_r0) >= _pk - 1:
|
||||
_worse.append(_cl)
|
||||
# An Off unit gives power only when the peak floor is short without it.
|
||||
_off = [c.actuator for c in _cl if c.source == MODE_SOURCE
|
||||
and c.lo == 0.0 and c.hi == 0.0]
|
||||
# What the units whose mode lets them give power can give, within it.
|
||||
_on_cap = sum(max(0.0, min(_r.unit_bounds[u].hi, _r.unit_bounds[u].mode_hi))
|
||||
for u in _units if _r.unit_bounds[u].mode_hi > 0.0
|
||||
and _r.unit_bounds[u].lo <= 0.0)
|
||||
if any(_r.shares.get(u, 0) > 0 for u in _off) and _pk <= _on_cap - 1:
|
||||
_needless.append(_cl)
|
||||
check(f"over {_nn} random N=2/3 cycles (modes, packs, a trade or the loop, a "
|
||||
f"peak floor): no split is refused, the placed power never falls under "
|
||||
f"the floor where the same cycle without modes met it, and an Off "
|
||||
f"battery gives power only when the others cannot carry the floor "
|
||||
f"({len(_sign)}, {len(_worse)}, {len(_needless)})",
|
||||
not _sign and not _worse and not _needless)
|
||||
|
||||
print()
|
||||
if fails:
|
||||
print(f"{len(fails)} FAILED: {', '.join(fails)}")
|
||||
|
||||
Reference in New Issue
Block a user