Skip to content

Guard + Runway, second version; slow retrospective correction experiment (off by default) - #16

Merged
ps2 merged 3 commits into
LoopKit:mainfrom
loopkitdev:experiments/gr2-slow-rc
Nov 1, 2026
Merged

ps2 merged 3 commits into
LoopKit:mainfrom
loopkitdev:experiments/gr2-slow-rc

Conversation

@ps2

@ps2 ps2 commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

guardAndRunway (changed)

  • Volatility band for the low guard only. The band now lowers only the forecast the low guard checks. A banded point below the suspend threshold suspends, and the banded minimum is the minimum the guard sees. The correction is sized from the unbanded forecast, and that is also the forecast returned in predictedGlucose. The displayed forecast no longer carries the band's dip and taper. GuardAndRunwayOutput.guarded(_:) still gives the guard's view.
  • Runway fades with carbs. The runway reserve scales down linearly from no carbs on board to none at 20 g, instead of closing at any carbs.

In LoopEval's generator (166 people × 2 trainings) the change is neutral against the current version: TIR +0.04, time below 70 +0.02, time below 54 +0.01.

slowRetrospectiveCorrection (new)

A day-scale estimate of how much more or less insulin the person needs than their settings say.

  • Level: the exponentially weighted mean, with a 24 h time constant, of the glucose change the model did not explain, per 5 minutes. That change is insulin counteraction less carb effect, at the scheduled ISF. The mean covers the glucose history passed in, up to 72 h, starting from zero.
  • Multiplier: ISF for the whole run is multiplied by 1 − 0.05 × level, kept within 0.7–1.3.
  • Output: AlgorithmOutput.slowRetrospectiveCorrection reports the level and the multiplier.

In the generator, on top of this PR's guardAndRunway, it gives TIR +1.5, time below 70 +0.23 and time below 54 +0.08. That is +0.46 TIR [+0.13, +0.74] more than raising insulin needs reaches at the same time below 70. In a four-day replay of one person running Guard + Runway, it gave TIR +6 at the same insulin and no time below 70.

Implementation notes

  • History. The experiment keeps no state; it is recomputed from the inputs on every run, so it needs a long history:

    • glucose: 72 h;
    • carb entries: from 72 h plus the longest absorption time;
    • doses: from 72 h plus the insulin activity duration.

    With 12 h of history the level reaches only about 40 % of its working value.

  • Carbs for the whole history. A meal whose entry is dropped while its absorption is still inside the glucose history reads as unexplained rise and pushes the level up. In a replay that moved the multiplier by about 0.04 for a day.

  • Timeline coverage. The carb ratio and ISF timelines must start at or before the oldest carb entry. run now checks this and fails with the new AlgorithmError.carbRatioOrSensitivityStartsAfterCarbEntry; before, CarbStatus construction hit preconditionFailure. The check applies with or without experiments, and Loop should handle the new case.

  • Cost. Two forecasts per run when the multiplier is not 1, each over the full history: 23 ms median, 207 ms worst, on a Mac (release build).

  • Agreement with LoopEval. Each cycle of a four-day closed-loop replay was run through this package: multiplier difference median 0.0007, 98.5 % of doses identical, worst difference 0.05 U.

ps2 added 3 commits October 31, 2026 20:12
… slow retrospective correction experiment

guardAndRunway: the volatility band now lowers only the forecast the low guard checks (a banded point below the
suspend threshold suspends; the banded minimum is the guard's minimum). The correction is sized, and the forecast
shown, from the unbanded forecast. The runway reserve fades out linearly from no carbs on board to 20 g, instead of
closing at any carbs.

slowRetrospectiveCorrection (new, off by default): ISF x (1 - 0.05 x level), within 0.7-1.3, where level is the
24 h EWMA of the unexplained glucose change (insulin counteraction less carb effect, per 5 min, at the scheduled
ISF) over the glucose history passed in, up to 72 h. Stateless: it needs 72 h of glucose, carbs from 72 h plus the
longest absorption time, and doses from 72 h plus the insulin activity duration.
…ead of crashing

run() now checks that the carb ratio and ISF timelines start at or before the
oldest carb entry, before any forecast is built. Previously CarbMath hit
preconditionFailure. The slow retrospective correction experiment's 72 h carb
history makes this case easier to reach.
@ps2
ps2 merged commit 3965100 into LoopKit:main Nov 1, 2026
ps2 added a commit to LoopKit/Loop that referenced this pull request Nov 2, 2026
…uard (#2643)

Slow retrospective correction (LoopKit/LoopAlgorithm#16) becomes an algorithm
experiment. When it is on, Loop reads 72 hours of glucose, carbs from 72 hours
plus the longest absorption time, and doses from 72 hours plus the insulin
activity duration, with schedule history covering them, since the experiment
keeps no state. Its level and ISF multiplier are recorded on the dosing
decision, and the bolus screen and forecast details forecasts use the
multiplier so they match the algorithm's.

Guard + Runway is labeled v2, its description says what changed, and its
recorded parameters carry version 2. The forecast details screen draws the
forecast the low guard checks as a dotted line while Guard + Runway is selected,
and no longer lowers the comparison forecast by the band, since the second
version no longer lowers the displayed forecast.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant