fix: allow gentle negative HOLD floor to fix steady-state overshoot
Testing the windup-fix rework live against sude/sud_0030.json surfaced a
distinct bug: a HOLD overshoot from grain-fill-in cooling never decayed -
process_pid()'s pid_outer_y floor clamped to exactly 0.0, so pid_inner
fought the pot's own ambient loss to hold the overshot temperature flat
instead of declining back to setpoint (see docs/overshoot2.png).
Replace the hardcoded 0.0 floor with a configurable Outer.y_hold_min
(default 0.0, backward compatible), set to -0.1 in config.json, both
.tpl templates, and the demo scripts - small enough to avoid
reintroducing the bb5af3c limit cycle while letting HOLD request a
gentle decline matching passive ambient cooling.
Adds TestHoldOvershootRecoversToSetpoint and documents the finding in
docs/overshoot_hold_windup.md's Follow-up section and
components/pid/TODO.md. Confirmed against a live sud_0030 re-run, not
just the unit test.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M2ierBoxW3v7nUbDw3M2pE
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -247,6 +247,60 @@ small (~0.07 in testing, well below the pre-fix disturbance's ~0.74 peak)
|
||||
step in `y` rather than the fully bumpless transfer described above. Not
|
||||
addressed here — flagged for awareness, not a blocker.
|
||||
|
||||
## Follow-up: steady-state overshoot in HOLD (`Outer.y_hold_min`)
|
||||
|
||||
Found while testing the rework against a real Sud run (`sude/sud_0030.json`,
|
||||
`logs/log_20260706T074658_Sud-0030.json`). Visible in `docs/overshoot2.png`
|
||||
(`theta_ist` vs `theta_soll` across the run): at both the 55°C and 63°C
|
||||
rests, `theta_ist` overshoots the step and then plateaus above `theta_soll`
|
||||
for the rest of the hold instead of converging back down — most clearly at
|
||||
the first rest, where it settles at ~55.5-55.6°C against a 55.0°C target.
|
||||
|
||||
A grain-fill-in disturbance during "1. Rast" (mash-in rest, `HOLD` at 55°C)
|
||||
pushed `temp_ist` to a ~0.4°C overshoot — well under `HoldCool`'s 1.0
|
||||
threshold, so the FSM stayed in `HOLD` throughout, same as the transient
|
||||
windup case above. But this overshoot never decayed: `temp_ist` sat in a
|
||||
55.30-55.44 band for the rest of the 20-minute hold instead of converging
|
||||
back to 55.0. Distinct failure mode from the transient windup fixed above
|
||||
(that one unwound over ~35s; this one was flat/permanent for as long as the
|
||||
hold lasted).
|
||||
|
||||
Root cause: `process_pid()`'s HOLD-state floor on `pid_outer_y` (added in
|
||||
`bb5af3c` to break a limit cycle - see the History section) clamped to
|
||||
exactly `0.0`. Once `temp_ist > temp_soll`, that floor forces
|
||||
`heatrate_soll = 0`, i.e. "hold flat" - and `pid_inner` then actively fights
|
||||
the pot's own ambient heat loss to keep the *overshot* temperature flat,
|
||||
rather than being allowed to request a genuine decline back toward
|
||||
setpoint. With `Outer.ki = 0`, there's no integral action to null the
|
||||
resulting steady-state error any other way, so the offset persists for the
|
||||
rest of the hold.
|
||||
|
||||
Fix: replaced the hardcoded `0.0` floor with a configurable
|
||||
`Outer.y_hold_min` (default `0.0`, so configs that don't set it keep the old
|
||||
behavior), set to `-0.1` in `config.json`, both `.tpl` templates, and the
|
||||
three demo scripts. A small negative floor lets HOLD ask for a gentle
|
||||
decline that roughly matches
|
||||
passive ambient cooling, rather than the fully unclamped `[-1, 1]` range
|
||||
that caused the original limit cycle (a large negative `heatrate_soll` asks
|
||||
for a decline steeper than passive loss can deliver, pinning power at 0 for
|
||||
an extended stretch and producing a hard undershoot/rebound). Test coverage
|
||||
added: `TestHoldOvershootRecoversToSetpoint` in
|
||||
`tests/components/pid/test_temp_controller_closed_loop.py` reproduces the
|
||||
sud_0030 disturbance, asserts the old flat-clamp behavior still fails to
|
||||
recover (regression guard) and the new floor converges close to setpoint,
|
||||
plus a guard that the recovery doesn't undershoot by more than the injected
|
||||
disturbance itself (i.e. doesn't reintroduce the `bb5af3c` limit cycle).
|
||||
|
||||
`-0.1` was chosen from the real Sud's plant params (`Pot.mass=5.96` +
|
||||
`water_mass=22` ≈ the test harness's `M=27.96`, `L=0.2`): passive ambient
|
||||
loss at a ~35°C delta works out to roughly 0.1-0.15 K/min, so
|
||||
`heatrate_soll_set * -0.1` lands in that same ballpark for a typical
|
||||
`heatrate_soll_set` of ~1.0-1.5 K/min. Not derived from first-principles
|
||||
tuning - may need adjustment per installation, same as the other PID gains.
|
||||
|
||||
Confirmed against a live re-run of `sud_0030` with the fix applied, not just
|
||||
the unit test above.
|
||||
|
||||
## Architecture diagram
|
||||
|
||||
A full signal-flow diagram of the cascade (`Outer`/`Inner.*` PIDs, FSM
|
||||
|
||||
Reference in New Issue
Block a user