OPT-20: a refusal probe asks only PROBE_W, not the whole trade
checks / unit (pull_request) Failing after 40m47s
infra-pin / fresh (pull_request) Successful in 1s

The probe's trade was sized at the probing battery's rating. Its SAFETY-73
pin held 200 W of that, and the allocator put the rest on a battery with no
mode range (one in a maintenance phase): TEST-23 OPT-15 g7, -2300 W on A in
its maintenance charge for 34 s, grid 3100 W.

modetrade.decide() now asks PROBE_W for the probing battery plus what the
other chargers would have had without the probe; size() adds the probe on top
of their share, or files PROBE_W exactly, or nothing. _mode_trade() carries
the pin only on the ticks it is filed, so the window's first and last cycle
do not move another battery's share either. Modes off and modes on with no
probe are unchanged (bf9019e digests). Tests: test_opt20.py.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
glenn schrooyen
2026-10-15 13:08:59 +02:00
co-authored by Claude
parent bf9019e70d
commit 06339a2446
5 changed files with 482 additions and 4 deletions
+13
View File
@@ -2441,6 +2441,19 @@ class Controller:
tick = self.mode_tick
if tick is None:
return False
if tick.action == modetrade.CHARGE:
# OPT-20: the ask carries PROBE_W for exactly the probes whose pin
# is filed this tick. `decide()` ran before the pin was (or the
# window closed since), so the two can differ by one cycle; left
# alone, the allocator takes the pin's watts out of another
# battery's share, or hands them to one it can reach.
pins = _ctl.PROBE_W * sum(
1 for c in claims if c.source == "refused"
and c.kind == "set" and c.value < 0)
if pins != tick.probe_w:
tick = self.mode_tick = dataclasses.replace(
tick, ask_w=tick.ask_w + tick.probe_w - pins,
probe_w=pins)
size = modetrade.size(tick, self.loop_bound(claims)
if tick.action else None, self.arb_headroom_w)
runs = self.mode_runs