Explorar el Código

Merge pull request #624 from nightscout/oref-swift-iob-optimization

IoB algorithm performance increase
Sam King hace 2 meses
padre
commit
a868ccd766

+ 28 - 16
Trio/Sources/APS/OpenAPSSwift/Iob/IobCalculation.swift

@@ -12,8 +12,8 @@ struct IobTotal: Codable {
 
 enum IobCalculation {
     struct IobCalculationResult {
-        let activityContrib: Decimal
-        let iobContrib: Decimal
+        let activityContrib: Double
+        let iobContrib: Double
     }
 
     /// logic to look up insulinPeakTime, taking into account `useCustomPeakTime`
@@ -65,7 +65,19 @@ enum IobCalculation {
         let iobContrib = insulin *
             (1 - S * (1 - a) * ((pow(minsAgo, 2) / (tau * end * (1 - a)) - minsAgo / tau - 1) * exp(-minsAgo / tau) + 1))
 
-        return IobCalculationResult(activityContrib: Decimal(activityContrib), iobContrib: Decimal(iobContrib))
+        guard activityContrib.isFinite, iobContrib.isFinite else {
+            return IobCalculationResult(activityContrib: 0, iobContrib: 0)
+        }
+
+        return IobCalculationResult(activityContrib: activityContrib, iobContrib: iobContrib)
+    }
+
+    /// Round a Double using the same logic as Decimal.jsRounded(scale:):
+    /// floor(value * 10^scale + 0.5) / 10^scale
+    private static func jsRound(_ value: Double, scale: Int) -> Decimal {
+        guard value.isFinite else { return 0 }
+        let multiplier = pow(10.0, Double(scale))
+        return Decimal((value * multiplier + 0.5).rounded(.down) / multiplier)
     }
 
     static func iobTotal(treatments: [ComputedPumpHistoryEvent], profile: Profile, time now: Date) throws -> IobTotal {
@@ -73,12 +85,12 @@ enum IobCalculation {
             throw IobError.diaNotSet
         }
 
-        var iob = Decimal(0)
-        var basaliob = Decimal(0)
-        var bolusiob = Decimal(0)
-        var netbasalinsulin = Decimal(0)
-        var bolusinsulin = Decimal(0)
-        var activity = Decimal(0)
+        var iob = 0.0
+        var basaliob = 0.0
+        var bolusiob = 0.0
+        var netbasalinsulin = 0.0
+        var bolusinsulin = 0.0
+        var activity = 0.0
 
         if dia < 5 {
             dia = 5
@@ -88,7 +100,7 @@ enum IobCalculation {
         let treatments = treatments.filter({ $0.timestamp <= now && $0.timestamp > diaAgo })
         for treatment in treatments {
             guard let tIOB = try iobCalc(treatment: treatment, time: now, dia: dia, profile: profile),
-                  let insulin = treatment.insulin
+                  let insulin = treatment.insulin.map({ Double($0) })
             else {
                 continue
             }
@@ -107,12 +119,12 @@ enum IobCalculation {
         }
 
         return IobTotal(
-            iob: iob.jsRounded(scale: 3),
-            activity: activity.jsRounded(scale: 4),
-            basaliob: basaliob.jsRounded(scale: 3),
-            bolusiob: bolusiob.jsRounded(scale: 3),
-            netbasalinsulin: netbasalinsulin.jsRounded(scale: 3),
-            bolusinsulin: bolusinsulin.jsRounded(scale: 3),
+            iob: jsRound(iob, scale: 3),
+            activity: jsRound(activity, scale: 4),
+            basaliob: jsRound(basaliob, scale: 3),
+            bolusiob: jsRound(bolusiob, scale: 3),
+            netbasalinsulin: jsRound(netbasalinsulin, scale: 3),
+            bolusinsulin: jsRound(bolusinsulin, scale: 3),
             time: now
         )
     }

+ 1 - 0
Trio/Sources/APS/OpenAPSSwift/Logging/OrefFunction.swift

@@ -67,6 +67,7 @@ enum OrefFunction: String, Codable {
                 "TDD",
                 "minDelta",
                 "received",
+                "deliverAt",
                 // intentionally removed from Swift, but in JS
                 "insulinForManualBolus",
                 "manualBolusErrorString",

+ 6 - 6
TrioTests/OpenAPSSwiftTests/IobCalculateTests.swift

@@ -55,8 +55,8 @@ import Testing
         )
 
         #expect(result != nil)
-        #expect(result!.activityContrib.isWithin(0.0001, of: 0.0115))
-        #expect(result!.iobContrib.isWithin(0.0001, of: 1.8085))
+        #expect(result!.activityContrib.isApproximatelyEqual(to: 0.0115, epsilon: 0.0001))
+        #expect(result!.iobContrib.isApproximatelyEqual(to: 1.8085, epsilon: 0.0001))
     }
 
     @Test("should calculate IOB with custom peak time for rapid-acting insulin") func calculateCustomPeakRapidActing() async throws {
@@ -78,8 +78,8 @@ import Testing
         )
 
         #expect(result != nil)
-        #expect(result!.activityContrib.isWithin(0.0001, of: 0.0079))
-        #expect(result!.iobContrib.isWithin(0.0001, of: 1.8763))
+        #expect(result!.activityContrib.isApproximatelyEqual(to: 0.0079, epsilon: 0.0001))
+        #expect(result!.iobContrib.isApproximatelyEqual(to: 1.8763, epsilon: 0.0001))
     }
 
     @Test("should handle peak time limits for rapid-acting insulin") func handlePeakTimeLimitsRapidActing() async throws {
@@ -131,8 +131,8 @@ import Testing
         )
 
         #expect(result != nil)
-        #expect(result!.activityContrib.isWithin(0.0001, of: 0.01569))
-        #expect(result!.iobContrib.isWithin(0.0001, of: 1.7202))
+        #expect(result!.activityContrib.isApproximatelyEqual(to: 0.01569, epsilon: 0.0001))
+        #expect(result!.iobContrib.isApproximatelyEqual(to: 1.7202, epsilon: 0.0001))
     }
 
     @Test("should handle peak time limits for ultra-rapid insulin") func handlePeakTimeLimitsUltraRapid() async throws {