From 012aa23fdbf1eeba41aaf7da374b841baff0f9f0 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Tue, 14 Jan 2020 13:41:43 -0700 Subject: [PATCH 1/3] Include insulin model delay in computation of dose --- Loop/Managers/DoseMath.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index 5502084671..c0e82b1597 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -236,6 +236,7 @@ extension Collection where Element: GlucoseValue { let unit = correctionRange.unit let sensitivityValue = sensitivity.doubleValue(for: unit) let suspendThresholdValue = suspendThreshold.doubleValue(for: unit) + let delay = TimeInterval(minutes: 10) // For each prediction above target, determine the amount of insulin necessary to correct glucose based on the modeled effectiveness of the insulin at that time for prediction in self { @@ -267,7 +268,8 @@ extension Collection where Element: GlucoseValue { // Compute the dose required to bring this prediction to target: // dose = (Glucose Δ) / (% effect × sensitivity) - let percentEffected = 1 - model.percentEffectRemaining(at: time) + // For 0 <= time <= delay, assume a small amount effected. This will result in large unit recommendation rather than no recommendation at all. + let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time - delay)) let effectedSensitivity = percentEffected * sensitivityValue guard let correctionUnits = insulinCorrectionUnits( fromValue: predictedGlucoseValue, @@ -299,8 +301,8 @@ extension Collection where Element: GlucoseValue { eventual.quantity < eventualGlucoseTargets.lowerBound { let time = min.startDate.timeIntervalSince(date) - // For time = 0, assume a small amount effected. This will result in large (negative) unit recommendation rather than no recommendation at all. - let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time)) + // For 0 <= time <= delay, assume a small amount effected. This will result in large (negative) unit recommendation rather than no recommendation at all. + let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time - delay)) guard let units = insulinCorrectionUnits( fromValue: min.quantity.doubleValue(for: unit), From c45caaa22a5921c93e169cd88754d1bc80949efa Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Tue, 14 Jan 2020 14:14:59 -0700 Subject: [PATCH 2/3] Fix dose math tests --- DoseMathTests/DoseMathTests.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index f2c1b495cf..1cf10a89a3 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -347,7 +347,7 @@ class RecommendTempBasalTests: XCTestCase { lastTempBasal: nil ) - XCTAssertEqual(1.425, dose!.unitsPerHour, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.450, dose!.unitsPerHour, accuracy: 1.0 / 40.0) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } @@ -588,7 +588,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(1.575, dose.amount) + XCTAssertEqual(1.625, dose.amount) if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter), 60) @@ -657,7 +657,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(1.4, dose.amount) + XCTAssertEqual(1.575, dose.amount) XCTAssertEqual(BolusRecommendationNotice.predictedGlucoseBelowTarget(minGlucose: glucose[1]), dose.notice!) } @@ -676,7 +676,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(0.575, dose.amount) + XCTAssertEqual(0.625, dose.amount) } func testStartVeryLowEndHigh() { @@ -708,7 +708,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.575, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.625, dose.amount, accuracy: 1.0 / 40.0) } func testHighAndFalling() { @@ -787,7 +787,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.25, dose.amount) + XCTAssertEqual(1.30, dose.amount, accuracy: 1.0 / 40.0) // Use mmol sensitivity value let insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: HKUnit.millimolesPerLiter, dailyItems: [RepeatingScheduleValue(startTime: 0.0, value: 10.0 / 3)])! @@ -802,7 +802,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.25, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.30, dose.amount, accuracy: 1.0 / 40.0) } func testRiseAfterDIA() { From 40ae183e4b65c7a3e249181b759580493d2cfa99 Mon Sep 17 00:00:00 2001 From: "@dm61" Date: Fri, 17 Jan 2020 22:01:48 -0700 Subject: [PATCH 3/3] add delay to insulin model presets --- DoseMathTests/DoseMathTests.swift | 20 +++++++++---------- Loop/Managers/DoseMath.swift | 9 ++++----- .../ExponentialInsulinModelPreset.swift | 17 +++++++++++++++- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/DoseMathTests/DoseMathTests.swift b/DoseMathTests/DoseMathTests.swift index 1cf10a89a3..6637784f2b 100644 --- a/DoseMathTests/DoseMathTests.swift +++ b/DoseMathTests/DoseMathTests.swift @@ -347,7 +347,7 @@ class RecommendTempBasalTests: XCTestCase { lastTempBasal: nil ) - XCTAssertEqual(1.450, dose!.unitsPerHour, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.60, dose!.unitsPerHour, accuracy: 1.0 / 40.0) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } @@ -365,7 +365,7 @@ class RecommendTempBasalTests: XCTestCase { lastTempBasal: nil ) - XCTAssertEqual(1.475, dose!.unitsPerHour, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.60, dose!.unitsPerHour, accuracy: 1.0 / 40.0) XCTAssertEqual(TimeInterval(minutes: 30), dose!.duration) } @@ -588,7 +588,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(1.625, dose.amount) + XCTAssertEqual(1.7, dose.amount) if case BolusRecommendationNotice.currentGlucoseBelowTarget(let glucose) = dose.notice! { XCTAssertEqual(glucose.quantity.doubleValue(for: .milligramsPerDeciliter), 60) @@ -676,7 +676,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(0.625, dose.amount) + XCTAssertEqual(0.7, dose.amount) } func testStartVeryLowEndHigh() { @@ -708,7 +708,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.625, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.7, dose.amount, accuracy: 1.0 / 40.0) } func testHighAndFalling() { @@ -724,7 +724,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(0.325, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(0.4, dose.amount, accuracy: 1.0 / 40.0) } func testInRangeAndRising() { @@ -740,7 +740,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(0.325, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(0.4, dose.amount, accuracy: 1.0 / 40.0) // Less existing temp @@ -771,7 +771,7 @@ class RecommendBolusTests: XCTestCase { volumeRounder: fortyIncrementsPerUnitRounder ) - XCTAssertEqual(0.275, dose.amount) + XCTAssertEqual(0.375, dose.amount) } func testHighAndRising() { @@ -787,7 +787,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.30, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.35, dose.amount, accuracy: 1.0 / 40.0) // Use mmol sensitivity value let insulinSensitivitySchedule = InsulinSensitivitySchedule(unit: HKUnit.millimolesPerLiter, dailyItems: [RepeatingScheduleValue(startTime: 0.0, value: 10.0 / 3)])! @@ -802,7 +802,7 @@ class RecommendBolusTests: XCTestCase { maxBolus: maxBolus ) - XCTAssertEqual(1.30, dose.amount, accuracy: 1.0 / 40.0) + XCTAssertEqual(1.35, dose.amount, accuracy: 1.0 / 40.0) } func testRiseAfterDIA() { diff --git a/Loop/Managers/DoseMath.swift b/Loop/Managers/DoseMath.swift index c0e82b1597..b10e4450c0 100644 --- a/Loop/Managers/DoseMath.swift +++ b/Loop/Managers/DoseMath.swift @@ -236,7 +236,6 @@ extension Collection where Element: GlucoseValue { let unit = correctionRange.unit let sensitivityValue = sensitivity.doubleValue(for: unit) let suspendThresholdValue = suspendThreshold.doubleValue(for: unit) - let delay = TimeInterval(minutes: 10) // For each prediction above target, determine the amount of insulin necessary to correct glucose based on the modeled effectiveness of the insulin at that time for prediction in self { @@ -268,8 +267,8 @@ extension Collection where Element: GlucoseValue { // Compute the dose required to bring this prediction to target: // dose = (Glucose Δ) / (% effect × sensitivity) - // For 0 <= time <= delay, assume a small amount effected. This will result in large unit recommendation rather than no recommendation at all. - let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time - delay)) + // For 0 <= time <= effectDelay, assume a small amount effected. This will result in large unit recommendation rather than no recommendation at all. + let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time)) let effectedSensitivity = percentEffected * sensitivityValue guard let correctionUnits = insulinCorrectionUnits( fromValue: predictedGlucoseValue, @@ -301,8 +300,8 @@ extension Collection where Element: GlucoseValue { eventual.quantity < eventualGlucoseTargets.lowerBound { let time = min.startDate.timeIntervalSince(date) - // For 0 <= time <= delay, assume a small amount effected. This will result in large (negative) unit recommendation rather than no recommendation at all. - let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time - delay)) + // For 0 <= time <= effectDelay, assume a small amount effected. This will result in large (negative) unit recommendation rather than no recommendation at all. + let percentEffected = Swift.max(.ulpOfOne, 1 - model.percentEffectRemaining(at: time)) guard let units = insulinCorrectionUnits( fromValue: min.quantity.doubleValue(for: unit), diff --git a/LoopCore/Insulin/ExponentialInsulinModelPreset.swift b/LoopCore/Insulin/ExponentialInsulinModelPreset.swift index e2f0877ed8..e65cdd1fb7 100644 --- a/LoopCore/Insulin/ExponentialInsulinModelPreset.swift +++ b/LoopCore/Insulin/ExponentialInsulinModelPreset.swift @@ -38,9 +38,20 @@ extension ExponentialInsulinModelPreset { return .minutes(55) } } + + var effectDelay: TimeInterval { + switch self { + case .humalogNovologAdult: + return .minutes(10) + case .humalogNovologChild: + return .minutes(10) + case .fiasp: + return .minutes(10) + } + } var model: InsulinModel { - return ExponentialInsulinModel(actionDuration: actionDuration, peakActivityTime: peakActivity) + return ExponentialInsulinModel(actionDuration: actionDuration, peakActivityTime: peakActivity, delay: effectDelay) } } @@ -49,6 +60,10 @@ extension ExponentialInsulinModelPreset: InsulinModel { public var effectDuration: TimeInterval { return model.effectDuration } + + public var delay: TimeInterval { + return model.delay + } public func percentEffectRemaining(at time: TimeInterval) -> Double { return model.percentEffectRemaining(at: time)