Skip to content

Instantly share code, notes, and snippets.

@scottleibrand
Last active September 2, 2015 17:36
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save scottleibrand/6fcb61e1ebb5c0f89488 to your computer and use it in GitHub Desktop.
Save scottleibrand/6fcb61e1ebb5c0f89488 to your computer and use it in GitHub Desktop.
Non-runnable pseudocode for more precisely describing the OpenAPS reference design algorithm
# 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