Просмотр исходного кода

Merge pull request #534 from nightscout/oref-swift-iob-performance-improve

Swift IoB performance improvements
Deniz Cengiz 11 месяцев назад
Родитель
Сommit
4faef1a11e

+ 12 - 0
Trio/Sources/APS/OpenAPSSwift/Extensions/DoubleApproximateMatching.swift

@@ -1,4 +1,5 @@
 extension Double {
+    /// Approximate matching to check if it is within +/- epsilon
     func isApproximatelyEqual(to other: Double, epsilon: Double?) -> Bool {
         // If no epsilon provided, require exact match
         guard let epsilon = epsilon else {
@@ -18,4 +19,15 @@ extension Double {
         // For IOB values, use simple absolute difference
         return abs(self - other) <= epsilon
     }
+
+    /// Applies a simple clamp to Doubles
+    func clamp(lowerBound: Double, upperBound: Double) -> Double {
+        if self < lowerBound {
+            return lowerBound
+        } else if self > upperBound {
+            return upperBound
+        } else {
+            return self
+        }
+    }
 }

+ 13 - 13
Trio/Sources/APS/OpenAPSSwift/Iob/IobCalculation.swift

@@ -16,14 +16,17 @@ enum IobCalculation {
         let iobContrib: Decimal
     }
 
-    private static func lookupPeak(from profile: Profile) throws -> Decimal {
+    /// logic to look up insulinPeakTime, taking into account `useCustomPeakTime`
+    private static func lookupPeak(from profile: Profile) throws -> Double {
         switch (profile.curve, profile.useCustomPeakTime, profile.insulinPeakTime) {
         case (.rapidActing, true, let insulinPeakTime):
-            return insulinPeakTime.clamp(lowerBound: 50, upperBound: 120)
+            let peakTime = Double(insulinPeakTime)
+            return peakTime.clamp(lowerBound: 50, upperBound: 120)
         case (.rapidActing, false, _):
             return 75
         case (.ultraRapid, true, let insulinPeakTime):
-            return insulinPeakTime.clamp(lowerBound: 35, upperBound: 100)
+            let peakTime = Double(insulinPeakTime)
+            return peakTime.clamp(lowerBound: 35, upperBound: 100)
         case (.ultraRapid, false, _):
             return 55
         case (.bilinear, _, _):
@@ -31,26 +34,23 @@ enum IobCalculation {
         }
     }
 
-    private static func exp(_ x: Decimal) -> Decimal {
-        // Convert Decimal to Double, calculate exp, convert back to Decimal
-        let doubleX = NSDecimalNumber(decimal: x).doubleValue
-        return Decimal(Darwin.exp(doubleX))
-    }
-
+    /// Runs through the IoB calculation for a treatment.
+    ///
+    /// **IMPORTANT** this calculation uses Doubles internally for performance
     static func iobCalc(
         treatment: ComputedPumpHistoryEvent,
         time: Date,
         dia: Decimal,
         profile: Profile
     ) throws -> IobCalculationResult? {
-        guard let insulin = treatment.insulin else {
+        guard let insulin = treatment.insulin.map({ Double($0) }) else {
             return nil
         }
 
         let bolusTime = treatment.timestamp
-        let minsAgo = time.timeIntervalSince(bolusTime).secondsToMinutes.rounded()
+        let minsAgo = (time.timeIntervalSince(bolusTime) / 60.0).rounded()
         let peak = try lookupPeak(from: profile)
-        let end = dia * Decimal(60)
+        let end = Double(dia) * 60
 
         guard minsAgo < end else {
             return IobCalculationResult(activityContrib: 0, iobContrib: 0)
@@ -65,7 +65,7 @@ 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: activityContrib, iobContrib: iobContrib)
+        return IobCalculationResult(activityContrib: Decimal(activityContrib), iobContrib: Decimal(iobContrib))
     }
 
     static func iobTotal(treatments: [ComputedPumpHistoryEvent], profile: Profile, time now: Date) throws -> IobTotal {

+ 8 - 4
Trio/Sources/APS/OpenAPSSwift/Iob/IobGenerator.swift

@@ -5,10 +5,14 @@ struct IobGenerator {
         history: [PumpHistoryEvent],
         profile: Profile,
         clock: Date,
-        autosens: Autosens?,
-        fullOutput: Bool = false
+        autosens: Autosens?
     ) throws -> [IobResult] {
-        let pumpHistory = history.map { $0.computedEvent() }
+        // As a performance optimization, filter out any pump events
+        // that occurred before the DIA would use it
+        let diaAgo = Double(profile.dia ?? 10) * 60 * 60
+        // add an extra two hours to the DIA to ensure we get all temp basals
+        let lastDia = clock - diaAgo - 2.hoursToSeconds
+        let pumpHistory = history.filter({ $0.timestamp >= lastDia }).map({ $0.computedEvent() })
 
         let treatments = try IobHistory.calcTempTreatments(
             history: pumpHistory,
@@ -33,7 +37,7 @@ struct IobGenerator {
         let lastTemp = treatments.filter({ $0.rate != nil && ($0.duration ?? 0) > 0 }).sorted(by: { $0.timestamp < $1.timestamp })
             .last
 
-        let iStop = fullOutput ? 4 * 60 : 5 // look 4h into the future
+        let iStop = 4 * 60 // look 4h into the future
         var iobArray = try stride(from: 0, to: iStop, by: 5).map { minutes in
             let time = clock + minutes.minutesToSeconds
             let iob = try IobCalculation.iobTotal(treatments: treatments, profile: profile, time: time)

+ 6 - 1
Trio/Sources/APS/OpenAPSSwift/OpenAPSSwift.swift

@@ -52,7 +52,12 @@ struct OpenAPSSwift {
 
             iobInputs = IobInputs(history: pumpHistory, profile: profile, clock: clock, autosens: autosens)
 
-            let iobResult = try IobGenerator.generate(history: pumpHistory, profile: profile, clock: clock, autosens: autosens)
+            let iobResult = try IobGenerator.generate(
+                history: pumpHistory,
+                profile: profile,
+                clock: clock,
+                autosens: autosens
+            )
 
             return try (.success(JSONBridge.to(iobResult)), iobInputs)
         } catch {