Forecast a Sud schedule through its confirm steps, not just up to the first one
A user_wait_for_continue step used to stop SudForecastEstimator.estimate() dead, so the GUI's forecast plot only ever showed the first segment on Load. Model the wait as a zero-delay auto-confirm instead, so the whole schedule's projected curve is visible right away; correct that assumption piecewise as real confirmations actually happen, anchored at the real elapsed time and temperature. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
This commit is contained in:
+20
-57
@@ -110,21 +110,19 @@ class RealtimePlot(FigureCanvasQTAgg):
|
||||
|
||||
class SudForecastPlot(FigureCanvasQTAgg):
|
||||
"""Compares a Sud schedule's actual control behavior (line, solid)
|
||||
against a fixed, simulation-based forecast (line_projected, dashed) -
|
||||
the same plant/controller model the real run uses
|
||||
against a simulation-based forecast (line_projected, dashed) - the
|
||||
same plant/controller model the real run uses
|
||||
(components/sud_forecast.py's SudForecastEstimator - see
|
||||
show_computing()/show_forecast()). The forecast is computed once per
|
||||
piecewise segment and never revisited once a run is using it - the
|
||||
whole point is an honest, unmoving baseline to compare reality
|
||||
against, not a prediction that keeps re-anchoring itself to match
|
||||
whatever's actually happening. The one exception is a step requiring
|
||||
user confirmation: a human's response time genuinely can't be
|
||||
forecast, so the segment just stops there, and the next one is
|
||||
computed for real (anchored at the real elapsed time and
|
||||
temperature) only once the user actually confirms - see
|
||||
tasks/sud.py's SudTask._continue_forecast_after_confirm().
|
||||
extend_forecast_while_waiting() keeps the dashed line visually flat
|
||||
through the wait in the meantime."""
|
||||
show_computing()/show_forecast()). The forecast covers the whole
|
||||
schedule from the very first Load, including through any step
|
||||
requiring user confirmation - a human's response time genuinely
|
||||
can't be forecast, so it's modeled as a zero-delay auto-confirm
|
||||
there instead of stopping. That assumption gets corrected once the
|
||||
real confirmation actually happens (anchored at the real elapsed
|
||||
time and temperature) - see tasks/sud.py's SudTask.
|
||||
_continue_forecast_after_confirm() - at which point show_forecast()
|
||||
is called again with the corrected curve, replacing the optimistic
|
||||
guess outright rather than the GUI patching it up itself."""
|
||||
|
||||
def __init__(self):
|
||||
figure = Figure(facecolor='white')
|
||||
@@ -143,13 +141,6 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
|
||||
figure.tight_layout()
|
||||
|
||||
# The forecast as last received from the server (see
|
||||
# show_forecast()) - kept around so extend_forecast_while_waiting()
|
||||
# can repeat its last value without the caller needing to pass it
|
||||
# again every tick.
|
||||
self._forecast_t_min = []
|
||||
self._forecast_theta = []
|
||||
|
||||
def show_computing(self, name):
|
||||
"""Shown right after a schedule loads, while the first
|
||||
simulation-based forecast segment is still being computed
|
||||
@@ -159,8 +150,6 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
(there's nothing meaningful to fit yet), so whatever was on
|
||||
screen before just stays frozen underneath the title change
|
||||
until show_forecast() replaces it."""
|
||||
self._forecast_t_min = []
|
||||
self._forecast_theta = []
|
||||
self.line.set_data([], [])
|
||||
self.line_projected.set_data([], [])
|
||||
self.ax.set_title("{} - computing forecast...".format(name), fontsize='small')
|
||||
@@ -168,18 +157,16 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
self.draw_idle()
|
||||
|
||||
def show_forecast(self, t_min, theta, name, finished):
|
||||
"""The forecast curve (dashed) - computed once, piecewise, by the
|
||||
server simulating the schedule with its real plant/controller
|
||||
"""The forecast curve (dashed) - computed by the server simulating
|
||||
the schedule with its real plant/controller
|
||||
(components/sud_forecast.py) - see Window.on_sud_forecast_
|
||||
received(). Called again, with a longer t_min/theta, each time a
|
||||
further piecewise segment gets appended after a real user
|
||||
confirmation; finished is False while the forecast doesn't yet
|
||||
cover the rest of the schedule (stopped at a step requiring
|
||||
confirmation - see extend_forecast_while_waiting()). Locks the
|
||||
axes to fit the forecast alone, regardless of whatever the
|
||||
received(). Called again, with a corrected t_min/theta, each time
|
||||
a real user confirmation replaces a zero-delay guess with one
|
||||
anchored at the real elapsed time and temperature; finished is
|
||||
False only in the pathological case of a step whose target can
|
||||
never be reached (components/sud_forecast.py's MAX_TICKS). Locks
|
||||
the axes to fit the forecast alone, regardless of whatever the
|
||||
actual trace (line) is currently doing."""
|
||||
self._forecast_t_min = t_min
|
||||
self._forecast_theta = theta
|
||||
self.line_projected.set_data(t_min, theta)
|
||||
self._set_fixed_limits(t_min, theta)
|
||||
total = t_min[-1] if t_min else 0.0
|
||||
@@ -188,21 +175,6 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
self.figure.tight_layout()
|
||||
self.draw_idle()
|
||||
|
||||
def extend_forecast_while_waiting(self, elapsed_min):
|
||||
"""Called every tick while the real Sud is waiting on user
|
||||
confirmation: a human's response time can't be forecast, so
|
||||
rather than guess, the dashed line just repeats its last value
|
||||
out to 'now' until the next segment is appended for real (see
|
||||
tasks/sud.py's SudTask._continue_forecast_after_confirm(), fired
|
||||
the moment the real confirmation happens) - at which point
|
||||
show_forecast() overwrites this temporary extension with the
|
||||
actual next segment."""
|
||||
if not self._forecast_t_min or elapsed_min <= self._forecast_t_min[-1]:
|
||||
return
|
||||
last_theta = self._forecast_theta[-1]
|
||||
self.line_projected.set_data(self._forecast_t_min + [elapsed_min], self._forecast_theta + [last_theta])
|
||||
self.draw_idle()
|
||||
|
||||
def _set_fixed_limits(self, t_list, theta_list):
|
||||
"""Explicitly sets (and, unlike relim()+autoscale_view(), locks)
|
||||
the axes' view to fit exactly the given data, with matplotlib's
|
||||
@@ -235,8 +207,6 @@ class SudForecastPlot(FigureCanvasQTAgg):
|
||||
self.draw_idle()
|
||||
|
||||
def show_no_schedule(self):
|
||||
self._forecast_t_min = []
|
||||
self._forecast_theta = []
|
||||
self.line.set_data([], [])
|
||||
self.line_projected.set_data([], [])
|
||||
self.clear_progress()
|
||||
@@ -472,13 +442,6 @@ class Window(QtWidgets.QMainWindow, Ui_MainWindow):
|
||||
if self.sud_state != SUD_PAUSED_STATE and self.plot_temp_ist:
|
||||
self.forecast_history.append((elapsed_min, self.plot_temp_ist))
|
||||
self.forecast_plot.show_dynamic(self.forecast_history)
|
||||
if self.sud_state == SUD_WAIT_USER_STATE:
|
||||
# A human's confirm time can't be forecast - the dashed
|
||||
# line just repeats its last value out to 'now' until
|
||||
# the real confirmation appends the next segment (see
|
||||
# tasks/sud.py's SudTask._continue_forecast_after_
|
||||
# confirm()).
|
||||
self.forecast_plot.extend_forecast_while_waiting(elapsed_min)
|
||||
else:
|
||||
self.forecast_plot.clear_progress()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user