LoopKit / Loop

An automated insulin delivery app for iOS, built on LoopKit
https://loopdocs.org
Other
1.48k stars 1.3k forks source link

Changing temp basals always restart the interval until the first pulse #1015

Closed greglongo518 closed 5 years ago

greglongo518 commented 5 years ago

Describe the bug Loop is built upon potentially frequently changing basal rates to manage bg toward target. The issue is that every time a temp basal is changed the "time until the next pulse" essentially starts over and is always reset to the maximum interval for that basal rate. This can result in pulses being delayed sometimes drastically especially for users with low basal rates. Eventually loop should make up for this, but it can potentially take a long time.

Attach an Issue Report Tap the Loop settings icon on the bottom of the screen, then tap Issue Report and attach it to this ticket.

To Reproduce Steps to reproduce the behavior: I was able to reproduce this behavior in a test session using omnipod (not hooked up to a person). I ran in open loop, and loaded up fake carbs, but limited the basal rate suggestions via the delivery limits settings for max basal rate. I started low and increased the setting to control the basal rate loop would suggest, and listened for clicks(pulses). I would then modify the limit setting and accept the increased temp basal shortly before the next scheduled pulse and observed that each time the pulse would be delayed with the new temp basal even though the rate enacted should have been increasing. This was because the new temp basal always put the first pulse at the end of the interval.

Expected behavior I expect that an app built upon managing blood sugar primarily by changing basal rates could handle changing of basal rates cleaner and without the potential for missed pulses. I believe this is one of the reasons why so many children that use loop have to drastically increase their basal rates (often 2x-3x) as opposed to without loop to try and make up for some of these missed pulses in an imprecise way.

Screenshots If applicable, add screenshots to help explain your problem.

Pump

Additional context I understand the change needed to fix this is really an algorithm enhancement and goes fairly deep into the core of loops algorithm, and also that any change would have to be done in a separate branch because it would change existing behavior for people that tuned to the current system and are running with higher basals to try and offset this problem, but without it there is a lot of uncertainty in dosing especially when dealing with users with low rates that are changed frequently. It seems like openomni has the ability to pass a delayUntilNextPulse parameter when setting a temp basal but currently with loop this is always set to the same value as delaybetweenpulses (at least for omnipod), causing the pulse to always come at the end of the rate. With acceptable rates as low as 1 pulse an hour and possible rate changes every 5 minutes this can cause problems. My suggestion would be to maintain the current phase position when changing temp basals or reverting back to the default basal rate. For example if you are .75 of the way to the next pulse on the previous basal rate you are changing, then maintain that phase position and set the delay until next pulse parameter the remainder of the phase ( .25 of the new rate interval from when you apply the temp basal). This would be a much smoother transitions between rates, and eliminate a lot of the randomness that currently exists.

ps2 commented 5 years ago

Loop calculates the amount delivered from a dose using math that approximates the counters inside the pump. This, combined with tracking negative IOB, ends up basically doing what you're proposing. The negative IOB ends up acting like a "counter", triggering a higher rate eventually.

You're welcome to propose an update to the algorithm. I think showing actual delivery in example scenarios of current Loop dosing algo vs proposed Loop dosing algo would be necessary to evaluate this kind of change.

greglongo518 commented 5 years ago

My proposed update to the algorithm is buried in the additional context above, but it would be to carry the current phase completion percentage forward from the previous basal rate to the next. For example if you were two thirds of the way through the previous interval when the rate is changed, to start the new rate two thirds of the way into it as well, and place the first pulse one third of the new interval in the future on the change, instead of the entire interval which it is doing now. This would ensure you are not starting over each time, and would also ensure that increasing the rate could not actually cause a delay in the actual delivery, which is very common with the current algo with low rates. My son has a basal rate of .15 u/hr or one pulse every 20 minutes. Even though eventually loop should correct for it with negative insulin, it will take until his rate increases 4x to be guaranteed an actual delivery in the 5 minute minimum interval that the rate can be active.

ps2 commented 5 years ago

Loop calculates a temp basal rate that can be safe if it goes offline and and the full half hour is delivered. By changing the phase (if such a thing were possible; it's not on mdt, and only theoretical on omnipod), you're changing how much would deliver over that half hour, often by an additional pulse; typically not that big of a deal, but for sensitive kids, maybe. Also, if you did something like that, you'd have to somehow back out the negative IOB accumulation, since you're accounting for it in a different way. You've hand waved a thing that you think could lead to improvement. I'm saying if you want this to proceed, you'd need to move it from the hand waving part to something that could be evaluated. If not in code, at least something that mocks out a scenario in detail.

greglongo518 commented 5 years ago

To see how a real world scenario will play out I will use my sons starting basal rate of .15u/hr and imagine that his bg is rising linearly, so that loop will propose a steady increase of his basal rate with an acceleration of .05u/hr/10min (every 10 minutes loop will increase his rate by .05 u/hr) and see how much insulin is delivered over the next hour.

Current algo

t0 (rate .15 [interval 20 minutes])

t10 (rate .20 [interval 15 minutes])

t20 (rate .25 [interval 12 minutes])

t30 (rate .30 [interval 10 minutes])

X (first delivery .05 delivery at 40 minutes) t40 (rate .35 [interval ~8.5 minutes])

X (second delivery .05 delivered at 48.5 minutes)

t50 (rate .40 [interval ~7.5 minutes])

X (third delivery .05 delivered at 57.5 minutes)

t60 (rate .45)

As you can see above despite the accelerating rate the first delivery didn't occur until 40 minutes in (half of his original rate), and the amount of insulin delivered is the same as would have been delivered with his original rate even though his rate tripled over the course of the hour, and with much of it delayed he would have had less working insulin in the period than just using his normal basal rate.. During this period he had an average rate of .275 u/hr but he only had .15 units delivered

Proposed algo

t0 (rate .15 [interval 20 minutes])

t10 (rate .20 [interval 15 minutes] 50% phase completion)

X (first delivery at 17:30) t20 (rate .25 [interval 12 minutes] 16.67% phase completion)

X (second delivery at 30 minutes) t30 (rate .30 [interval 10 minutes] 0%phase completion)

X (third delivery at 40 minutes) t40 (rate .35 [interval ~8.5 minutes] 0% phase completion)

X (fourth delivery .05 delivered at 48.5 minutes)

t50 (rate .40 [interval ~7.5 minutes] 17.65% phase completion)

X (fifth delivery .05 delivered at 56.12 minutes)

t60 (rate .45) (~50% phase completion)

With the new algo there is no delay in receiving the insulin. The increasing rate proportinally increases the actual delivery. The average rate during the period was .275 u/hr and he wad delivered .25 units and is halfway to the next pulse so this matches exactly.

Admittedly this did not take into account negative insulin which may cause his rate to accelrate more in the current algo, but it is imprecise. Also, it is not even a worst case scenario. If the rates were changing every 5 minutes the delays with the current algo are even worse.

ps2 commented 5 years ago

This is still hand-wavey level, and doesn't cover a lot of important details, such as IOB, actual delivery. You're glossing over the main reason I said this would need to be evaluated (negative iob and how it changes delivery).

ps2 commented 5 years ago

I believe if you actually worked out your theory, you'd find it's not drastically different in terms of delivery. And, also, you're relying on (currently) imaginary features to implement it.

greglongo518 commented 5 years ago

Ps2, also thank you for the responses. I do realize that more work needs to be done on this proposal, that it's not a simple change, and that it would change existing behavior for current users so it would most likely have to be done in a separate branch or be configurable. . I also deeply believe that it would be a big improvement and eliminate a lot of randomness that currently exists depending on if the rate change happens just before vs just after a pulse, especially for users with low rates like my son. Many users with low rates have to increase their basal 2-3x when going on loop, and I believe it is at least partially to offset this problem. The rest of the app and algo seems so precise, that the approach that eventually the negative iob will build up so much that it should catch up at some time doesn't sit well with me. I am a developer, and I'm just starting to go through and get a feel of the code. I wouldn't mind working up a proposed code change, but it will take me a little while to get comfortable enough with the code to the point I am ready. I would love a resource familar with loop to be able to reach out to, to be able to talk about the technical details of this change, and it in more depth once I feel I am up to speed.

greglongo518 commented 5 years ago

I dont think I'm relying on imaginary details to implement it. Openomni has a parameter when setting a temp basal that tells how long until the first pulse, so the pod has the capability to do this. My understanding is that loop knows when each pulse is actually given. So when the rate is changed it knows the time since the last pulse. The (time since last pulse)/(previous rate interval) is the phase completion percentage. When setting the new rate, you would just set the time to first pulse (1-phaseCompletionPercentage) * delayBetweenPulses, instead of currently always setting it to just delayBetweenPulses

ps2 commented 5 years ago

I might be a bit familiar with omnipod protocol. ;). The delayUntilNextPulse parameter is my code. The pod is very sensitive to differences in the values passed in the temp basal command messages. We had floating point rounding errors that resulted in values off by tiny amounts, and it would end up in the pod screaming. The pod has internal counters that need to reconcile at the end of a delivery, or it will scream. I believe if you set that delayUntilNextPulse in a nonstandard way, it will mess with the reconciliation.

scottleibrand commented 5 years ago

(Sorry if this reply is still too handwavy.)

Do I understand you correctly that we basically have a constraint that we can't modify the delayUntilNextPulse (without needing to do excessive testing to avoid screamers)? Would that mean our effective choice is between allowing a partially completed temp basal to run to completion, and setting a new temp basal (and resetting the delayUntilNextPulse clock)?

If so, Loop should know, at the time it's deciding to set a new temp basal, what the current delayUntilNextPulse is, and therefore should be able to calculate the effective rate of the remaining temp basal, as distinct from the originally configured rate. For example, if it's been 15 minutes since you've set a temp basal of 0.15 U/h (0.05U every 20 minutes), then the pod is currently programmed to deliver 0.05U over the next 15 minutes, for an effective remaining temp basal rate of 0.2 U/h. If Loop is recommending a rate of 0.2 U/h, and the effective remaining temp basal rate of 0.2 U/h, then the best action might* be to leave the current temp basal running, wait until the next loop, and recalculate based on the new effective remaining temp basal rate at that time (and the new BG, IOB, etc.).

FWIW, as I understand it, this is not a problem with Medtronic pumps, because they calculate their pulse schedule from the top of the hour, not from the beginning of the currently-running temp basal, so there's no scenario in which continuously setting non-zero temp basals can substantially delay insulin delivery relative to the requested rate.

ps2 commented 5 years ago

Medtronic temp basal phase is also reset with each set of a temp basal. It'd be really hard to know when a pulse was delivered otherwise.

ps2 commented 5 years ago

I'm going to close this discussion, as it's a topic very heavily dependent on some pretty obscure details both from a hardware and from an algorithm side. For anyone who is willing to take the time to learn those details, and still thinks they have an improvement to make, they can try coding it up themselves, testing, and submitting the results of their testing.