docs: refresh Forecast vs. actual duration for the real-temperature anchoring fixes
Documents the recent SudTask.send_forecast()/recv() changes (anchored at get_theta_ist_set(), re-anchored on every fresh Start) and the two spurious-divergence fixes: client/brewpi_gui.py's forecast_history t=0 seeding, and the hardcoded last_theta_ist=20 bug in both TempController variants. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
This commit is contained in:
@@ -308,11 +308,20 @@ server uses - plant params (`M`/`C`/`L`/`Td`) aren't a constructor
|
|||||||
argument at all, since they come from the doc being forecast itself
|
argument at all, since they come from the doc being forecast itself
|
||||||
(`Sud.derive_plant_params()`, same as the real run), not from any generic
|
(`Sud.derive_plant_params()`, same as the real run), not from any generic
|
||||||
default. A multi-hour brew simulates in well under a second since it's
|
default. A multi-hour brew simulates in well under a second since it's
|
||||||
pure CPU-bound iteration, no real time/IO involved. This
|
pure CPU-bound iteration, no real time/IO involved.
|
||||||
is deliberately *not* recomputed as a run progresses: a forecast that keeps
|
|
||||||
re-anchoring itself to match whatever's actually happening would no longer
|
It's anchored to the real current temperature
|
||||||
be a meaningful baseline to compare reality against. It's computed once,
|
(`TempControllerBase.get_theta_ist_set()` - the raw sensor reading, valid
|
||||||
up front, and left alone.
|
even before this controller's first `process()` tick, unlike
|
||||||
|
`get_theta_ist()`), not a cold start at ambient - `SudTask.send_forecast()`
|
||||||
|
computes it once on Load (a preview of what's coming), and again on every
|
||||||
|
fresh `Start` (not a Pause→resume, which keeps whatever forecast a run
|
||||||
|
already established rather than discarding it mid-run), since the real
|
||||||
|
temperature can have drifted in the meantime
|
||||||
|
(time passing, manual heating via the Manual tab in between). Once a run
|
||||||
|
is actually progressing, though, it's deliberately *not* continuously
|
||||||
|
re-anchored to match whatever's happening: a forecast that keeps chasing
|
||||||
|
reality would no longer be a meaningful baseline to compare it against.
|
||||||
|
|
||||||
`SudTask._send_forecast()` thins the curve to at most `MAX_FORECAST_POINTS`
|
`SudTask._send_forecast()` thins the curve to at most `MAX_FORECAST_POINTS`
|
||||||
(1000) before broadcasting it - a fine enough `dt` over a multi-hour brew
|
(1000) before broadcasting it - a fine enough `dt` over a multi-hour brew
|
||||||
@@ -366,6 +375,33 @@ speedup runs measurably below it (confirmed: ~80x instead of a configured
|
|||||||
100x), which on its own used to produce a divergence large enough to look
|
100x), which on its own used to produce a divergence large enough to look
|
||||||
like a control problem.
|
like a control problem.
|
||||||
|
|
||||||
|
Two other sources of spurious (non-control-related) divergence, both
|
||||||
|
fixed:
|
||||||
|
|
||||||
|
- `client/brewpi_gui.py`'s actual-trace history used to only get its first
|
||||||
|
sample at `on_plot_timer()`'s next 1-second (real time) tick - under a
|
||||||
|
real `sim_warp_factor`, simulated time has already moved measurably past
|
||||||
|
`t=0` by then, leaving a small visible gap between the forecast's exact
|
||||||
|
`t=0` anchor and where the actual line first appears. `on_sud_changed()`
|
||||||
|
now seeds `forecast_history` with the real `(elapsed, plot_temp_ist)`
|
||||||
|
point the moment the first `Elapsed` push of a *fresh* run arrives
|
||||||
|
(gated on elapsed still being small, `FRESH_RUN_ELAPSED_THRESHOLD_S`, so
|
||||||
|
reconnecting mid-brew doesn't get a bogus point plotted at `x=0`).
|
||||||
|
- Both `TempController` variants used to hardcode `last_theta_ist = 20` at
|
||||||
|
construction, used to compute the very first `process()` tick's
|
||||||
|
`heatrate_ist` - a wildly bogus rate spike whenever the real starting
|
||||||
|
temperature wasn't actually 20 (invisible until a forecast actually got
|
||||||
|
anchored to something other than 20, per the fix above), which then took
|
||||||
|
several exponentially-smoothed ticks to decay out of the rate-PID loop,
|
||||||
|
visibly distorting the trajectory during exactly that window. It now
|
||||||
|
starts `None` and treats the first tick's rate as `0` (no real history
|
||||||
|
yet) instead of computing it against a hardcoded, usually-wrong
|
||||||
|
baseline - `SudForecastEstimator` is the case that actually surfaced
|
||||||
|
this: it builds a fresh `TempController` on every call, so its first
|
||||||
|
tick always hit this fresh, unlike the real controller, which only ever
|
||||||
|
goes through it once near server startup and has long since
|
||||||
|
self-corrected by the time any forecast comparison matters.
|
||||||
|
|
||||||
## Logging & analysis
|
## Logging & analysis
|
||||||
|
|
||||||
`tasks/sud_log.py`'s `SudLogTask` records every Sud run's measured data -
|
`tasks/sud_log.py`'s `SudLogTask` records every Sud run's measured data -
|
||||||
|
|||||||
Reference in New Issue
Block a user