Fill the post-confirm forecast gap with the real setpoint, not a mid-ramp snapshot
_continue_forecast_after_confirm()'s bridge was repeating forecast_theta[-1] to cover the real-world wait, but that's whatever value the simulation happened to log the instant WAIT_USER tripped - often still mid-ramp, not the step's actual hold target. The real controller stays enabled and actively holding throughout WAIT_USER, so capture its setpoint (tc.get_theta_soll_set()) in recv() before calling Sud.confirm() - confirming synchronously pushes the next step's own target onto it - and use that to fill the gap instead. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DkkuG48uHFCGKe6dPSERFk
This commit is contained in:
@@ -309,8 +309,16 @@ looks up the relevant `confirm_points` entry, truncates the forecast right
|
||||
back to that point, and splices in a freshly anchored simulation of the
|
||||
remaining steps - anchored at the real elapsed time and real current
|
||||
temperature, so an actual delay shows up honestly as a gap in the timeline
|
||||
rather than as the assumed zero. The corrected forecast is sent in full
|
||||
each time, so the GUI's `SudForecastPlot.show_forecast()` simply redraws
|
||||
rather than as the assumed zero. That gap is filled at the real
|
||||
controller's setpoint at the moment of confirmation (`recv()` captures
|
||||
`tc.get_theta_soll_set()` *before* calling `Sud.confirm()`, since confirming
|
||||
synchronously pushes the next step's own target onto it) rather than
|
||||
whatever value the forecast happened to log the instant `WAIT_USER`
|
||||
tripped - the controller stays enabled and actively holding throughout
|
||||
`WAIT_USER`, so the setpoint is what it was actually converging toward
|
||||
during the wait, not a possibly-still-mid-ramp snapshot. The corrected
|
||||
forecast is sent in full each time, so the GUI's `SudForecastPlot.
|
||||
show_forecast()` simply redraws
|
||||
the dashed line outright rather than patching it up itself.
|
||||
|
||||
Comparing the two lines: real-world divergence from the forecast - more
|
||||
|
||||
+16
-6
@@ -164,7 +164,7 @@ class SudTask(ATask):
|
||||
'Finished': self.forecast_finished,
|
||||
}})
|
||||
|
||||
async def _continue_forecast_after_confirm(self, confirmed_index):
|
||||
async def _continue_forecast_after_confirm(self, confirmed_index, confirmed_target):
|
||||
"""Corrects the optimistic, zero-delay guess send_forecast() (or a
|
||||
previous call to this method) made at confirmed_index's
|
||||
user-confirmation step, now that the confirmation has actually
|
||||
@@ -177,6 +177,12 @@ class SudTask(ATask):
|
||||
the real current temperature (more trustworthy than whatever the
|
||||
now-superseded guess predicted it would be by now).
|
||||
|
||||
confirmed_target is the real controller's theta_soll_set at the
|
||||
moment of confirmation (captured by recv() *before* calling
|
||||
Sud.confirm(), since that synchronously pushes the next step's
|
||||
own target onto it) - used to fill the real-world wait itself
|
||||
(see below).
|
||||
|
||||
No-op if confirmed_index was never part of a computed forecast in
|
||||
the first place (e.g. the estimator isn't configured)."""
|
||||
if self.forecast_estimator is None:
|
||||
@@ -210,12 +216,15 @@ class SudTask(ATask):
|
||||
loop = asyncio.get_event_loop()
|
||||
t, theta, final_state, confirm_points = await loop.run_in_executor(None, self.forecast_estimator.estimate, doc, start_theta)
|
||||
# Bridge the gap between the confirmation point and the real
|
||||
# elapsed time it actually happened at, holding flat at its last
|
||||
# temperature - then append the new segment, offset to start
|
||||
# exactly there.
|
||||
# elapsed time it actually happened at - the real controller
|
||||
# stays enabled and actively holding through WAIT_USER, so
|
||||
# confirmed_target (its setpoint at the time) is what it was
|
||||
# actually converging toward during the wait, not whatever
|
||||
# (possibly still mid-ramp) value the forecast happened to log
|
||||
# the instant WAIT_USER tripped.
|
||||
if self.forecast_t and self.forecast_t[-1] < real_elapsed:
|
||||
self.forecast_t.append(real_elapsed)
|
||||
self.forecast_theta.append(self.forecast_theta[-1])
|
||||
self.forecast_theta.append(confirmed_target)
|
||||
self.forecast_t.extend(real_elapsed + seconds for seconds in t)
|
||||
self.forecast_theta.extend(theta)
|
||||
# confirm_points' indices/times are relative to this sub-schedule
|
||||
@@ -232,8 +241,9 @@ class SudTask(ATask):
|
||||
self.sud.start()
|
||||
elif 'Confirm' in pair[0]:
|
||||
confirmed_index = self.sud.index
|
||||
confirmed_target = self.tc.get_theta_soll_set()
|
||||
self.sud.confirm()
|
||||
asyncio.create_task(self._continue_forecast_after_confirm(confirmed_index))
|
||||
asyncio.create_task(self._continue_forecast_after_confirm(confirmed_index, confirmed_target))
|
||||
elif 'Pause' in pair[0]:
|
||||
self.sud.pause()
|
||||
elif 'Stop' in pair[0]:
|
||||
|
||||
Reference in New Issue
Block a user