-
-
Save scottleibrand/6fcb61e1ebb5c0f89488 to your computer and use it in GitHub Desktop.
Non-runnable pseudocode for more precisely describing the OpenAPS reference design algorithm
This file contains 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
# The following heavily commented pseudocode is not actually runnable: is an amalgam of syntax | |
# from multiple programming languages, references a number of functions that are not defined, | |
# and has not been debugged. | |
# It is only useful for educational purposes: in particular, to more precisely describe | |
# the closed loop algorithm proposed in the OpenAPS reference design. | |
# Collect profile info at startup: | |
function onStartup() { | |
profile = getProfile(); # collect profile info from pump | |
function getProfile() { | |
return { | |
dia: dia, # Duration of Insulin Action (hours) | |
isf: isf, # Insulin Sensitivity Factor (mg/dL/U) | |
bgTargetMin: bgTargetMin, # low end of BG Target range | |
bgTargetMax: bgTargetMax, # high end of BG Target range | |
bgSuspend: bgTargetMin - 30, # temp to 0 if dropping below this BG | |
maxBasal: maxBasal # pump's maximum basal setting | |
#ic: ic, # Insulin to Carb Ratio (g/U) | |
#csf: isf / ic, # Carb Sensitivity Factor (mg/dL/g) | |
basals: basals # Basal Schedule (array of [start time of day, rate (U/hr)]) | |
}; | |
}; # end function getProfile() | |
function basalLookup(timeOfDay) { | |
#Return basal rate (U/hr) at the provided timeOfDay | |
}; # end function basalLookup(timeOfDay) | |
function maxBasalLookup() { | |
#Return maximum daily basal rate (U/hr) from profile.basals | |
}; # end function maxBasalLookup() | |
}; | |
function everyFewMinutes() { | |
#Collect real-time info every few minutes: | |
bg = getBG(now); # Current BG from fingerstick or CGM | |
delta = getBGDelta(now); # Current change in BG / BG trend (+ / - / 0) | |
iob = getIOB(now); # Current IOB, reported by (or calculated from) pump | |
tempBasalHistory = getTempBasalHistory(); # time, duration (hours), and rate (U/hr) of any temp basals | |
# not included in IOB (limited to last dia hours) | |
bolusHistory = getBolusHistory(); # time and size (U) of boluses for last dia hours | |
netIOB = getNetIOB(iob, tempBasalHistory); # Calculate net IOB, taking into account temp basals | |
function getNetIOB(iob, tempBasalHistory) { | |
foreach tempBasal in tempBasalHistory { | |
normalBasal = basalLookup(tempbasal.time); | |
netInsulin = (tempBasal.rate - normalBasal) * tempBasal.duration; # How much insulin (+/-) | |
hoursAgo = now – basal.time; # hours since basal was started | |
if(hoursAgo > profile.dia) { iobContrib = 0; next; } # getBasalHistory shouldn’t pass any old basals, | |
# but ignore them if it does | |
iobContrib = netInsulin * (profile.dia - hoursAgo) / profile.dia # simple straight-line IOB decay. | |
# For manual/mental calculation, iobContrib can be approximated by using round-number math. | |
iob = iob + iobContrib; # (add/subtract each basal’s net impact to the pump’s IOB number) | |
} | |
return(iob); | |
}; # end function getNetIOB(iob, tempBasalHistory) | |
#Perform intermediate calculations based on latest BG and insulin history | |
# When the user does a manual bolus, we assume they know what they're doing, and try to avoid counteracting it. | |
# To do so, we snooze OpenAPS to avoid low-temps for a certain number of hours for each 1U bolused. | |
snoozeFactor = getSnoozeFactor(); # Hours to snooze after each 1U bolus | |
getSnoozeFactor(){ return 0.5; } # By default, 1U = 30m; 6U = 3h | |
function bolusSnooze(bolusHistory, snoozeFactor) { | |
snoozeUntil = now; | |
foreach bolus in bolusHistory { | |
# extend the snooze time if appropriate based on IOB after each bolus | |
snoozeHours = min(profile.dia, getIOB(bolus.time) * snoozeFactor)); # don't snooze more than dia hours | |
snoozeUntil = max(snoozeUntil, bolus.time + snoozeHours); # extend snooze if appropriate | |
} | |
return snoozeUntil; | |
}; # end function bolusSnooze(bolusHistory, snoozeFactor) | |
snoozeUntil = bolusSnooze(bolusHistory, snoozeFactor); | |
if (snoozeUntil > now) { | |
return 0; # no action | |
} | |
eventualBG = bg - (netIOB * profile.isf) ; # BG expected after dia hours, once all net IOB takes effect | |
# If bg < profile.bgSuspend and BG is not rising, temp to zero | |
currentTempBasal = calcCurrentTempBasal(tempBasalHistory); # current temp basal rate, or null | |
function setTempBasal(rate, duration) { | |
maxSafeBasal = min(profile.maxBasal, 2 * maxBasalLookup, 4 * basalLookup(now)); | |
if (rate < 0) { rate = 0; } # you can't give less than 0 U/hr | |
elseif (rate > maxSafeBasal) { rate = maxSafeBasal; } # don't give more than maxSafeBasal | |
# issue rate U/hr temp for duration minutes | |
system("temp-basal.py --rate rate --duration duration"); | |
}; # end function setTempBasal(rate) | |
if (bg < profile.bgSuspend) { | |
if (delta > 0) { # if BG is rising | |
if (currentTempBasal > basalLookup(now)) { # if a high-temp is running | |
setTempBasal(0, 0); # cancel temp | |
} | |
} | |
else { # (delta <= 0), BG is not yet rising | |
setTempBasal(0, 30); | |
} | |
} | |
# if BG is rising but eventual BG is below target, or BG is falling but eventual BG is above target, | |
# then cancel any temp basals. | |
if ((delta > 0 && eventualBG < bgTargetMin) || (delta < 0 && eventualBG > bgTargetMax)) { | |
if (currentTempBasal) { # if there is currently any temp basal running | |
setTempBasal(0, 0); # cancel temp | |
} | |
} elseif (eventualBG < bgTargetMin) { # if eventual BG is below target: | |
# calculate 30m low-temp required to get projected BG up to target | |
insulinReq = (bgTargetMin - eventualBG) / isf; # negative insulin required to get up to min | |
rate = currentTempBasal - 2 * insulinRequired; # rate required to deliver insulinReq less insulin over 30m | |
if (rate < currentTempBasal) { # if required temp < existing basal | |
setTempBasal(rate, 30); # issue the new temp basal | |
} # if >30m @ 0 required, zero temp will be extended to 30m instead | |
} elseif (eventualBG > bgTargetMax) { # if eventual BG is above target: | |
# calculate 30m high-temp required to get projected BG down to target | |
insulinReq = (bgTargetMax - eventualBG) / isf; # negative insulin required to get down to max | |
rate = currentTempBasal - 2 * insulinRequired; # rate required to deliver insulinReq more insulin over 30m | |
if (rate > currentTempBasal) { # if required temp > existing basal | |
setTempBasal(rate, 30); # issue the new temp basal | |
} # if new temp basal > max temp basal, max temp will be extended to 30m instead | |
} | |
}; # end funciton everyFewMinutes() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment