LoopKit / Loop

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

Integral retrospective correction #695

Closed dm61 closed 5 years ago

dm61 commented 6 years ago

Integral Retrospective Correction (IRC) (updated July 16, 2023):

Some important points and an algorithm description are as follows:

  1. Known risk factors compared to standard Loop: With IRC turned on, in response to persistent discrepancies between observed and predicted glucose motion, Loop will likely increase insulin corrections, which may increase the risks of hypoglycemia. IRC may also lead to increased oscillations ("roller-coaster") in glucose responses. Both of these risk factors are higher if the user's setting value for Insulin Sensitivity (ISF) is too low. Increasing ISF setting value tends to mitigate these risks but it is impossible to offer any guarantees for anything around T1D.

  2. Compared to standard RC, IRC is more likely to improve glucose control in the following scenarios:

  1. In some scenarios IRC does not differ from standard Loop RC

    • Regardless of the current glucose level, neither RC nor IRC is adding to the glucose forecast during the times when the absorption rate of announced carbs is greater than the minimum absorption rate.
    • Neither RC nor IRC effects depend on glucose level; both depend on discrepancies between predicted and actual glucose responses.
  2. Please do not expect immediate or very substantial improvements in blood glucose control. A one-time success after turning IRC on does not really mean that IRC "works" - this could just as well be a temporal coincidence. Some ways to decide if IRC could be safe and effective for you include:

    • Responses to unannounced meals - spikes should in general be somewhat lower than with standard Loop, but there should also be no follow-up lows
    • Nighttime responses over a few weeks - highs or lows should be less frequent compared to standard Loop; at the wake-up time blood glucose should in general be closer to the correction range.

**** IRC Algorithm Description **** Integral retrospective correction (integral RC) is an experimental modification of the Loop's retrospective correction (RC) algorithm. Just like the standard RC, the motivation behind IRC is to make Loop less dependent on how well carb entries, ISF, CR, or programmed basal rates represent reality, and to improve responses in the presence of any unmodeled factors.

Operation of the standard Loop retrospective correction is illustrated below, where time = 0 represents the present time.
rc_illustration A modeling error called discrepancy is calculated as the difference between the actual BG and the BG predicted based on insulin and carbs models over the past 30 minutes. In the example shown, discrepancy = 15 mg/dL. The discrepancy is then used to modify the BG forecast over a correction interval of 60 min. The net effect of standard RC is that the eventual predicted BG is adjusted up (or down) by a retrospective correction equal to the current discrepancy (subject to momentum effects, but that’s a different topic).

In the integral RC, the correction magnitude and the correction time interval depend not only on the current discrepancy but also on past discrepancy values, so that the correction becomes more aggressive if the discrepancy persists over consecutive Loop cycles. This results in more aggressive dosing in response to any persistent modeling errors due to over/under-estimated carbs, parameters (ISF, CR, or basal rates) being away from reality, exercise, … whatever.

Simulation examples

In the first example, the actual basal needs increase by 40% (to 0.7 U/h) over a period of about 10 hours. Standard Loop high temps, which keeps BG from ramping up, but BG hovers away from the target. Integral RC kicks in more aggressive high temp corrections, which brings BG closer to the target.

22g_12h

In the second simulation example, the actual basal needs fall by 40% (to 0.3 U/h) over a period of around 10 hours. Standard Loop responds by low temping, but BG still drops below suspend threshold, which triggers some on/off oscillations around the suspend threshold. Integral RC results in a smaller BG drop and keeps BG closer to the target throughout.

-22g_12h

The third example is a case of unannounced 25 g carbs with an absorption time of about 3 hours. Compared to the standard Loop, integral RC reduces the magnitude and the duration of the spike.

25g_3 5h

Integral RC math

Integral RC math is relatively simple. Overall retrospective correction is computed as a sum of two components, proportional and integral:

overallDiscrepancy = proportionalGain * discrepancy + integralDiscrepancy

where integralDiscrepancy is computed as:

integralDiscrepancy = integralForget * integralDiscrepancy + integralGain * discrepancy

proportionalGain, integralForget and integralGain are constant parameters of the integral RC filter. In the standard RC, proportionalGain = 1, and integralGain = 0. In simple control theory terms, standard RC can be viewed as a proportional (P) controller trying to reduce modeling error (discrepancy). Integral RC adds an integral (I) action to this control loop. In addition to the above basic math, safety provisions (integration resets and limits) are included to minimize the chances of over-correction. A differential term has been added to mitigate the sluggishness of the response when discrepancies change polarity.

The IRC code can be viewed here

Kdisimone commented 6 years ago

I like this concept. Thank you for sharing. I believe I’ll test this one out. 👍🏻 And report back

Up and running as of about 30 min before...

screen shot 2018-03-31 at 8 55 23 pm

img_0539

elnjensen commented 6 years ago

This looks great, @dm61. I’ll try this and let you know how it goes.

dm61 commented 6 years ago

@Kdisimone one important point I did not mention in the original post is that integral RC will default to standard RC if retrospective carb effect is positive and greater than a threshold (which is in the code set to 30 mg/dL over past 30 minutes). This is a safety issue. Without this provision, there is a scenario when integral RC could lead to over-correction (and subsequent low), specifically when a carbs in an entry are overestimated yet carbs are initially having faster than expected absorption. In the Console screen you've posted you may see that overall correction equals discrepancy. This is because discrepancy is positive and retrospective carb action is above 30, so at the moment you are having just standard RC. Once carb action subsides, or once discrepancy becomes negative, a difference between overall correction and current discrepancy will show up.

Kdisimone commented 6 years ago

(writing in my own account now instead of the person I was troubleshooting for lol)

@dm61 funny you should mention that because I think that situation is exactly what we just had...and I'm glad the safety is there. She overestimated a fruit drink and entered it at 3 hours because it was part of a larger meal...so a bit of a faster absorption. She's going a wee bit low, but easy enough to stop and doesn't have a lot behind it. I was reading the code, so I appreciate the situational awareness explanation to why the safety setting is there. I'm reading up on the duration determination (x+10 vs 180) and assimilating that part too.

Kdisimone commented 6 years ago

@dm61 that saftey setting (30 over past 30) is there whether or not there are COB? Looks like it to me, but just checking.

dm61 commented 6 years ago

@Kdisimone that limit is entirely based on modeled carb action over past 30 minutes. If there is no COB, there is no modeled carb action, and that limit goes away. Integral action will always be active for unannounced carbs or such.

The duration extension logic follows standard RC approach where velocity of discrepancy over past 30 minutes decays linearly over next 60 minutes. Whenever integral action is updated, that means we are effectively looking another 5 min in the past, so velocity is set to linearly decay by 10 min longer time. In other words, we are predicting that the correction will extend further in time. The 180min is somewhat arbitrary, but is related to the filter time constant, which is set to 90min, so 2*90 = 180m is how far correction may ever extend in the future.

Kdisimone commented 6 years ago

thanks you. I think that helps me. So if I have that roughly right, the longer the discrepancy has persisted (and continues, assuming no inflection point), the longer the corresponding future correction?

dm61 commented 6 years ago

Correct. Note, however, that as far as dosing is concerned amplitude of the correction is more important than duration, since eventual predicted bg is simply moved up or down by the correction amplitude (as is also the case in standard RC). The duration affects the shape of the prediction curve, which may affect dosing in some cases, such as when forecast crosses through correction range limits or suspend threshold.

francesc0-cgm commented 6 years ago

Up and running here too. Thanks @dm61

francesc0-cgm commented 6 years ago

@dm61 a bad sensor or old One used with Spike so with some jumps could affect safety using this RC release?

dm61 commented 6 years ago

@francesc0-cgm in case of noisy BG readings, integral RC should operate no worse than standard RC, but probably no better either. But, if you see anything that looks suspicious in bg forecast, please report.

amazaheri commented 6 years ago

Nicely done! I just started using it today and observing will report back.

francesc0-cgm commented 6 years ago

@dm61 integral RC makes change in bolus reccomandation or acts only on basals?

jeremybarnum commented 6 years ago

This is exciting @dm61 - thanks for doing it. You said this is the only modification you have put into this branch - does that mean you reversed out #577?

@francesc0-cgm I believe it acts on the prediction and therefore all recommendations will be affected by it (bolus and basal).

francesc0-cgm commented 6 years ago

Last question @dm61. We have to enable or disable retrospective correction? Will It work the same with It enabled or could have some strange interaction?

dm61 commented 6 years ago

@francesc0-cgm integral RC this is just a modification of the existing retrospective correction. The same enable/disable switch applies. If retrospective correction is disabled by the user, there will be no retrospective correction at all.

francesc0-cgm commented 6 years ago

@dm61 thank you. I noticed It works quite well but seems a bit aggressive on my 5 yrs old...May i change some settings to mitigate It in RC math code?

francesc0-cgm commented 6 years ago

screenshot_20180402-195100 screenshot_20180402-195046 screenshot_20180402-195107 screenshot_20180402-195132 screenshot_20180402-202328

In this case where i inserted fewer carbs It seems It has overcorrected. Carb was going to 0 and It continued to High temping. ISF is almost ok, basal rates too. Since i use the RC integral in these situations i have these drops And his graph beacame a bit rollercoaster This happens with cob. During the night It works better than standard RC

dm61 commented 6 years ago

@francesc0-cgm thanks for the feedback. You can change settings to address the more aggressive high temping you are observing. In line 769 change 30.0 to 0.0. This will revert integral RC to standard RC high-temping whenever there are any active carbs present. Again, if you see anything you do not like or that looks suspicious, please go back to your standard Loop. I have to say I am a little concerned about early testing of a very experimental approach done with a little kid.

francesc0-cgm commented 6 years ago

Thanks to You @dm61. Putting a value like 10-15 could give a lighter effect? Setting It to 0 Will keep integral RC for other situations without cob like air bubble in the line or transient isf increasing-decreasing? During the night without any meal integral RC seems to give more stable graph. Don't worry, i tested It knowing possible issues like this. If we could mitigate the effect It worths to be applied on Kids too.

dm61 commented 6 years ago

@francesc0-cgm that's correct, setting carbEffectLimit to zero keeps integral RC fully operational as long as there are no active carbs. It also keeps more aggressive low-temping based on integral RC even when carbs are present, which may help a bit in situations when carbs entered are overestimated. It reverts back to more conservative high temping based on standard RC when any entered carbs are present. I suggest you keep carbEffectLimit at zero and still very carefully monitor Loop operation. Maybe the default value for that parameter should in fact be zero.

francesc0-cgm commented 6 years ago

Now i Am trying to use 15.0 could be useful and what have i to expect? Seems less aggressive atm.

Edit: using 15.0 instead 30.0 seems safer and less aggressive Also with COB. This afternoon my son had air bubbles in the line and integral RC continued to keep him stable before and then made him drop a bit closer to target. With standard RC in these situations Loop could not counteract the issue and he would have gone high. Sounds right for You @dm61?

Kdisimone commented 6 years ago

working wicked well for us...three days in.

screen shot 2018-04-03 at 9 01 38 am
dm61 commented 6 years ago

@francesc0-cgm integral RC could have helped some in the bubbles situation, but it is impossible to tell how much or how bg would have looked like without it. In any case, I'd not place too much importance to any single event. T1D is wicked - there are so many factors we can't identify or compensate for very well. Bubbles can be nasty, best to just prime them out manually on the pump.

francescaneri commented 6 years ago

7306bd6e-6985-4871-a76b-5c821dcb7386 @dm61 it works! Thank you!🤙👍💪👌

MitchDex commented 6 years ago

Wow! This has been a huge improvement to my sons looping. He has only been using irc for a little over 24hrs and we have not seen any issues. Thank you very much, we are very appreciative of all your hard work.

MitchDex commented 6 years ago

IMG_3968.jpg

mbieweng commented 6 years ago

Another thumbs up here. Big improvement, working very well for several days across multiple different situations.

francescaneri commented 6 years ago

ab453695-bc15-4236-8a44-f3b1006b03e7 58aec25b-caac-45b6-b898-d6b214e730d9 🤙

francesc0-cgm commented 6 years ago

screenshot_20180407-183601

Editing code and putting 15.0 ad retrospective carb action instead 30.0 i Am getting great results. @ps2 are You considering to add this mod to Dev? Seems safer than standard RC (getting great SD with less lows here)

ddaniels1 commented 6 years ago

15 makes it more aggressive I assume?

DD


From: francesc0-cgm notifications@github.com Sent: Saturday, April 7, 2018 9:39:43 AM To: LoopKit/Loop Cc: Subscribed Subject: Re: [LoopKit/Loop] Integral retrospective correction (#695)

[screenshot_20180407-183601]https://user-images.githubusercontent.com/10731469/38457578-a775df74-3a92-11e8-9199-1c6f41d0eb67.png

Editing code and putting 15.0 ad retrospective carb action instead 30.0 i Am getting great results. @ps2https://github.com/ps2 are You considering to add this mod to Dev? Seems safer than standard RC (getting great SD with less lows here)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHubhttps://github.com/LoopKit/Loop/issues/695#issuecomment-379482606, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ARiYg06-MtQ3ytbBsleOX7N3T-WWhIyQks5tmOvPgaJpZM4TClG5.

francesc0-cgm commented 6 years ago

@ddaniels1 no It makes less aggressive. 0.0 on line 769 convert It to standard RC when carbs are on board. 30 mg/dl is the safety limit chosen by @dm61 to avoid issues after a meal. Higher value means integral RC gets in more often to try correcting a raising BG after carbs are eaten. But if You choose a wrong absorption time It could overtreat

dm61 commented 6 years ago

@ddaniels1 what @francesc0-cgm said above regarding the value of carbEffectLimit is correct.

dabear commented 6 years ago

@dm61 could you update your branch to work with xcode 9.3? You just need to pull from dev or master; Like I did here: https://github.com/dabear/Loop-integral-updated

dm61 commented 6 years ago

@dabear thanks for the reminder, the branch is now up to date with dev

j4cbo commented 6 years ago

Am I interpreting it right that there are no algorithm differences between the last pre-9.3 commit (cbd8a320c2a5d6d5209317e40614266292a3b778) and the latest? I'm still avoiding upgrading to High Sierra :)

dm61 commented 6 years ago

@j4cbo no algo differences, the last commit was just to align the integral RC branch with the latest dev updates to Xcode 9.3, Swift 4.1

ddaniels1 commented 6 years ago

@dm61 Working pretty well for us, but still having sometimes where it is too aggressive at night. May be because Olivia is only 3, I set her suspend threshold very high so she would not get low, but she suspends for a while, then once just above suspend has enough negative iob that she high temps significantly and then gets low. I have tried to address this with raising the ISF, but if anything might be making it worse because with a high ISF and negative IOB it projects a higer eventual BG. I would think IRC could help here as well, but not sure how to tune it to be useful for us.

any thoughts?

Kdisimone commented 6 years ago

@ddaniels1 sounds like a basal reduction at night would help solve the accumulation of neg iob.

dm61 commented 6 years ago

@ddaniels1 completely agree with @Kdisimone, negative iob overnight likely means that basal rates are too high.

You may also move the correction range up, but I assume you've probably already done so. However, I'd not move suspend threshold too close to the bottom of the correction range. Suspend threshold imposes a pretty hard nonlinearity in the control loop, which may result in some bg oscillations even when ISF is otherwise well set (see "simulation example 2" in the original post). Integral RC won't be able to eliminate such oscillations, and increasing ISF won't help either in that particular scenario (which I think is what you may be observing). Loop (with or without RC) will generally work better if bg is not allowed to drop below suspend threshold, so leaving some room between the bottom of the correction range and suspend threshold may actually help.

ddaniels1 commented 6 years ago

@dm61 @Kdisimone thanks. Definitely aware of the negative iob being a sign that basals may be too high. Unfortunately what is high basals one night is too low the next. YDMV as everyone knows. I think that the suspend threshold being to close to the bottom of the correction range is likely causing the issue. I will start with increasing the correction range and go from there to see if that helps. Our primary issue, and reason loop is not as helpful is that her (and most sub 5 year olds with T1D) have a high proportion of total daily dose that is bolus, typically around 75%. This makes low temping less useful in preventing lows as there is less insulin to take away. One of the reasons I believe dual hormone Loop systems, assuming stable glucagon is proven safe and effective, will be most important for the youngest ones.

joeyovens commented 6 years ago

@dm61 Question - does this process evaluate insulin sensitivity? If so, would it be possible to create a log to use to evaluate that? I currently run openaps autotune (weekly when I remember) to get this, but it only gives one insulin sensitivity and carb ratio. Would be neat to look at that to evaluate if any efficiencies could be created (ideally its doing this automatically but it would have to "think" less if you could use it to tweak the base.

dm61 commented 6 years ago

@joeyovens no, integral RC does not explicitly evaluate sensitivity or any other parameter. It tries to counteract whatever unmodeled factors may be pushing bg up or down on relatively short time scales (up to a few hours). Parameter estimation and tuning may come on top of that based on data observed over longer periods of time.

trixing commented 6 years ago

Fwiw, IRC works great in my branch (with my automatic bolus changes). It would be great if it could use the logger function though to log a bit of data or include some data in the loop pill. The effect is right now a bit hard to see.

The other thing is that the implementation is adding a stateful (the first?) piece to Loop. So it matters now if Loop just got restarted or not. It might be nicer to either (a) store the current integral in UserDefaults, or (b) calculate it based on the saved retrospective correction values (and put those into UserDefaults). As it is debugging requires a multiple hour trial run afaict.

On Wed, Apr 18, 2018 at 5:10 PM, Dragan Maksimovic <notifications@github.com

wrote:

@joeyovens https://github.com/joeyovens no, integral RC does not explicitly evaluate sensitivity or any other parameter. It tries to counteract whatever unmodeled factors may be pushing bg up or down on relatively short time scales (up to a few hours). Parameter estimation and tuning may come on top of that based on data observed over longer periods of time.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/LoopKit/Loop/issues/695#issuecomment-382421319, or mute the thread https://github.com/notifications/unsubscribe-auth/AAE2arrZ5Qff_bDNaf2jMJRHgI7rZnflks5tp1dSgaJpZM4TClG5 .

-- Jan Dittmer jdi@l4x.org, http://l4x.org

elnjensen commented 6 years ago

@trixing Thanks for pointing that out. I've been restarting Loop fairly frequently to deal with a UI lag issue that worsens as Loop runs longer, so it's good to know that this affects IRC - hadn't realized that, so I'll be a little more restrained with my restarts now.

dm61 commented 6 years ago

@trixing yes, I agree on both points you made regarding implementation. When I get a chance, I'll work on preserving states in UserDefaults - need to do that for some other work in progress anyway. In the meanwhile, just to clarify for others who may be testing integral RC: in the current implementation, restarting Loop resets integral action, which then starts as standard RC and builds up from there. There are no safety risks associated with this implementation issue, except that restarting Loop often will diminish integral action (by "restarting" I mean swiping out Loop app, or reinstalling the app, or Loop app crashing for whatever reason. Normal opening and working with Loop is just fine).

peterlynton commented 6 years ago

@trixing I'd be interested in knowing more about your automatic bolus changes - could you link to your repository?

Two of us now using IRC here. I initially adjusted the carb effect limit for my son (13 years old) but found it too cautious so have returned to the full 30. @dm61 A query is can I make it more aggressive by increasing the value above 30 for myself?

lgruen commented 6 years ago

Thanks so much for making this available, @dm61! This is really exciting.

What I've found is that it's not taking slower-than-expected absorption into account as much as I'd hope for. I.e. I also ran into an overtreatment issue, but it's not after "initially having faster than expected absorption", but delayed / completely missing absorption. I'll play around with carbEffectLimit, although I'm not entirely sure whether the safety setting shouldn't already have kicked in for the example below and it's just standard Loop that was too aggressive in this case.

Here's the concrete example: I've had a late night snack, consisting of protein / fat, for which I entered 10g of carbs absorbed over 6h -- basically I just wanted to give Loop a hint to increase my basals a little bit.

What happened is that Loop didn't low-temp quickly enough after the blood sugar didn't increase as quickly as expected, causing a low about 90 minutes later (the graphs are showing mmol/L).

irc1 irc2
dm61 commented 6 years ago

@lgruen, in cases when carb absorption is none or slower than expected, integral RC should reduce temps more aggressively compared to standard Loop. Not exactly sure what happened in the example you posted. Looks like there was some uptick just before you entered 10g, which in part due to momentum effect may have led to a higher eventual bg prediction and higher temps just after the 10g entry. Since 10g entry was small, carb effects were most likely below (1 mg/dL)/min (corresponding to carbEffectLimit = 30), so that integral RC was most likely active and it added a little bit to those initial high temps on top of what standard Loop would have done. Between 0:13 and 1:36 looks like bg nicely followed predictions, with none or small discrepancies, which is why integral RC had no basis for reducing predictions down, and that steeper decline at 1:36 came in as a surprise. In the example you posted, I doubt things would have looked very different without integral RC. Let me know how things work with carbEffectLimit set to zero - thanks.

dm61 commented 6 years ago

@peterlynton carbEffectLimit is meant to reduce risks in situations where integral RC would result in overcorrections in the presence of active carbs. The limit of 30 corresponds to the expected carb impact of (1 mg/dL)/min. Increasing this value would make integral RC active and Loop high temping more aggressively in the presence of larger carb entries. Larger entries imply larger errors, which imply higher overcorrection risks - it's as simple as that. It's up to you of course, but I'd keep carbEffectLimit more conservative.