Repository navigation
Optional algorithm experiments (off by default): guard + runway, integral RC, glucose-based application factor - #10
Merged
Conversation
Adds AlgorithmExperiment, an optional input on AlgorithmInput (protocol-extension default nil, so existing conformers are unchanged). With nil every experiment code path is skipped and the forecast and dose are standard LoopAlgorithm. There is one experiment, with no tunable parameters, so that people who choose to try it pool into one group. It is LoopEval's current best candidate for time in range with fewer meal entries, paired with its lows guard: - Post-low rise-cut (C20): for 12 h after a reading below 70 mg/dL, and while glucose is below 180, the rising side of retrospective correction is weighted 0.3x. - Volatility band (C29): with no carbs on board, the first two hours of the forecast are lowered by recent glucose volatility (sigma5), so the low guard sees it. Depth is keyed to the person's median sigma5 (volatilityBaseline, or computed from a day or more of glucose history; without it the band is off). - Runway reserve (C40): at 150 mg/dL or above with no carbs on board, find the most extra insulin that suspending scheduled basal could still take back if glucose fell along the forecast less one sigma5 band, without reaching the suspend threshold within 3 h. 70% of it (at most 60 mg/dL's worth) is added to automatic dosing, as an internal lowering of the correction aim. The user's target, the manual bolus recommendation and the suspend threshold are unchanged. Gaps in a sparse forecast are filled every 5 min before the reserve is scanned. GlucoseVolatility.sigma5 is the causal volatility estimate (glucose thinned to 5-minute spacing so 1-minute CGM works); GlucoseVolatility.baseline the person's median. Tests: existing fixtures decode with no experiment; closed gates reproduce the standard output; unit tests per mechanism, including runway gates, its volatility response and cap, sparse forecasts, and that the manual recommendation is unaffected. Implementation cross-check against LoopEval's engine: on one donor-week (1792 decisions, each recomputed from its recorded inputs with and without the experiment) the per-decision dose differences agree, summing to +32.8 U vs +33.0 U with correlation 0.95. Decisions are replayed independently, so this checks the implementations match; it is not an estimate of extra insulin delivered.
loopkitdev
force-pushed
the
experiments/guard-runway
branch
from
October 18, 2026 06:10
7bb9ac3 to
e5f73da
Compare
ps2
marked this pull request as ready for review
October 23, 2026 16:40
…se-based application factor AlgorithmInput.experiment (one optional value) becomes experiments, a Set<AlgorithmExperiment> that defaults to empty, so experiments combine: each acts on its own part of the algorithm, and each combination is its own group when results are pooled. Fixtures encode it as a sorted array of names. Two experiments join guardAndRunway, both as on/off switches with no parameters: - integralRetrospectiveCorrection: the same as useIntegralRetrospectiveCorrection; either one enables integral RC. With guardAndRunway, the post-low rise-cut applies to integral RC's rise side as before. - glucoseBasedApplicationFactor: Loop's glucose-based partial application for automatic-bolus dosing. The factor rises with current glucose from 0.2 at the lower correction target + 10 mg/dL to 0.8 at 200 mg/dL (Loop's GlucoseBasedApplicationFactorStrategy, keyed on the target in effect now), and replaces automaticBolusApplicationFactor and, through it, the automatic-bolus cap. Temp-basal and manual recommendations are unchanged. Tests: the IRC experiment reproduces the input flag on two fixtures; the factor's ramp; the factor replaces the constant for automatic bolus only; a combination round-trips through a fixture.
A new user has under a day of glucose, so there is no baseline and the band was off: the experiment added time in range without its lows reduction. The band now runs at full depth (gain 1) until a baseline exists; the baseline only ever lowers it, for people whose glucose is volatile. Evaluated in LoopEval as full depth for everyone over four weeks (the worst case): lift +0.82 vs +0.86 on the simulated cohort and +0.98 vs +0.99 on donor replay, with lows no higher, against +0.55 / +0.99 with the band off.
This was referenced Oct 24, 2026
AndEliRich
pushed a commit
to AndEliRich/LoopWorkspace
that referenced
this pull request
Oct 24, 2026
…t, welcome screen width - LoopAlgorithm: optional algorithm experiments (LoopKit/LoopAlgorithm#10), type renamed to ExperimentOption (LoopKit/LoopAlgorithm#11) - Loop: use LoopAlgorithm's experiments, and add Guard + Runway (LoopKit/Loop#2608) - LoopOnboarding: limit the welcome screen to a readable width (LoopKit/LoopOnboarding#39)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds optional, off-by-default algorithm experiments,
AlgorithmExperiment, carried onAlgorithmInput.experiments(aSet, with a protocol-extension default of[], so existing conformers are unchanged). With the empty set, every experiment code path is skipped and the forecast and dose are standard LoopAlgorithm.Each experiment is an on/off switch with no tunable parameters, so that people who choose the same switches pool into one group whose results can be compared. Experiments combine; each combination is its own group.
guardAndRunwayintegralRetrospectiveCorrectionuseIntegralRetrospectiveCorrection; either one enables it.glucoseBasedApplicationFactorautomaticBolusApplicationFactor(and, through it, the automatic-bolus cap). Temp-basal and manual recommendations are unchanged.With
integralRetrospectiveCorrectionandguardAndRunwaytogether, the post-low rise-cut applies to integral RC's rise side.guardAndRunway
What it does
It aims to improve time in range for people who enter fewer meals, paired with a lows guard. It was evaluated in LoopEval: closed-loop counterfactual replay of real CGM and insulin data, plus a simulated cohort, scored against the standard algorithm swept over insulin needs.
volatilityBaseline(GlucoseVolatility.baseline(_:); four weeks recommended). Otherwise it is computed from a day or more of glucose history.Evaluation
Two LoopEval tools, both scored against standard LoopAlgorithm. Lift is the TIR gained beyond what insulin-needs tuning reaches at the same time below 54. That tuning is the standard algorithm with basal, ISF and carb ratio scaled together, ×0.90 to ×1.10. The 95% CI is bootstrapped over people.
Combinations (lift; simulated / replay):
guardAndRunwayguardAndRunway+glucoseBasedApplicationFactorguardAndRunway+integralRetrospectiveCorrectionglucoseBasedApplicationFactoraloneintegralRetrospectiveCorrectionaloneIn replay, integral RC without the clamp adds time below 54 (+0.39 with
guardAndRunway) and cancels the guard's reduction.Limits:
Implementation notes
GlucoseVolatility.sigma5is the causal volatility estimate: an EWMA of 5-minute glucose increments, restarted after gaps. Glucose is thinned to 5-minute spacing so 1-minute CGM works.The runway reads the forecast before the band lowers it.
Gaps in a sparse forecast (Loop drops points once nothing changes) are filled every 5 min before the reserve is scanned. A forecast shorter than 3 h gives no runway.
Retrospective correction (standard and integral) gains an optional rise-side weight, used only by the post-low rise-cut.
The app must pass
volatilityBaseline. Loop hands the algorithm 12 h of glucose, too little for the in-package fallback (at least a day), so without it every Loop user would get the band at full depth, never lowered for volatile glucose. Compute it withGlucoseVolatility.baseline(_:)from up to four weeks of stored glucose. It moves slowly, so once a day is often enough.New users. Until there is a day of glucose there is no baseline, and the band runs at full depth. The baseline only ever lowers it, for people whose glucose is volatile (depth min(1, 6.7 / baseline)). Full depth errs toward fewer lows at a little time in range. The app should pass a baseline from whatever history exists once there is a day of it, and the full four weeks after that.
Testing