Skip to content

Commit

Permalink
Merge pull request #15 from drpeterrohde/window-optimisaton
Browse files Browse the repository at this point in the history
Window optimisaton
  • Loading branch information
drpeterrohde committed Dec 29, 2022
2 parents 660eeed + ac3f490 commit 6f80cee
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 54 deletions.
78 changes: 78 additions & 0 deletions MoodSnap/Base/Filtering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ import SwiftUI
return firstDate
}

/**
Returns the earliest `Date` amongst `moodSnaps`.
*/
@inline(__always) func getFirstDate(moodSnaps: [MoodSnapStruct]) -> Date {
var firstDate = Date().startOfDay()
for moodSnap in moodSnaps {
if moodSnap.timestamp < firstDate {
if moodSnap.snapType == .mood || moodSnap.snapType == .note || moodSnap.snapType == .event || moodSnap.snapType == .media {
firstDate = moodSnap.timestamp
}
}
}
return firstDate
}

/**
Returns the most recent `Date` amongst `moodSnaps`.
*/
Expand All @@ -51,6 +66,19 @@ import SwiftUI
return lastDate
}

/**
Returns the most recent `Date` amongst `moodSnaps`.
*/
@inline(__always) func getLastDate(moodSnaps: [MoodSnapStruct]) -> Date {
var lastDate = Date().endOfDay()
for moodSnap in moodSnaps {
if moodSnap.timestamp > lastDate {
lastDate = moodSnap.timestamp
}
}
return lastDate
}

/**
Returns an array of elements from `moodSnaps` that coincide with the same day of `date`. The optional `flatten` parameter merges them into their single day equivalent.
*/
Expand All @@ -72,6 +100,27 @@ import SwiftUI
return filtered
}

/**
Returns an array of elements from `moodSnaps` that coincide with the same day of `date`. The optional `flatten` parameter merges them into their single day equivalent.
*/
@inline(__always) func getMoodSnapsByDate(moodSnaps: [MoodSnapStruct], date: Date, flatten: Bool = false) -> [MoodSnapStruct] {
var filtered: [MoodSnapStruct] = []
let dateComponents = date.getComponents()
for moodSnap in moodSnaps {
if moodSnap.timestamp.getComponents() == dateComponents {
if moodSnap.snapType == .mood {
filtered.append(moodSnap)
}
}
}
if flatten {
if filtered.count > 0 {
filtered = [mergeMoodSnaps(moodSnaps: filtered)!]
}
}
return filtered
}

/**
Returns an array of elements from `healthSnaps` that coincide with the same day of `date`. The optional `flatten` parameter merges them into their single day equivalent.
*/
Expand Down Expand Up @@ -120,6 +169,35 @@ import SwiftUI
return filtered
}

/**
Returns an array of elements from `moodSnaps` that sit within a window of `windowStart` and `windowEnd` days after `date`. The optional `flatten` parameter merges them into their single day equivalents on a per-day basis.
*/
@inline(__always) func getMoodSnapsByDateWindow(moodSnaps: [MoodSnapStruct], date: Date, windowStart: Int, windowEnd: Int, flatten: Bool = false) -> [MoodSnapStruct] {
var filtered: [MoodSnapStruct] = []
var theseMoodSnaps: [MoodSnapStruct?]

if flatten {
theseMoodSnaps = flattenSequence(sequence: sequenceMoodSnaps(moodSnaps: moodSnaps))
} else {
theseMoodSnaps = moodSnaps
}

let startDate = date.addDays(days: windowStart).startOfDay()
let endDate = date.addDays(days: windowEnd).endOfDay()

for moodSnap in theseMoodSnaps {
if moodSnap != nil {
if moodSnap!.timestamp >= startDate && moodSnap!.timestamp <= endDate {
if moodSnap!.snapType == .mood {
filtered.append(moodSnap!)
}
}
}
}

return filtered
}

/**
Does `string` contain `hashtag`.
*/
Expand Down
10 changes: 5 additions & 5 deletions MoodSnap/HealthKit/HealthManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ final class HealthManager: ObservableObject {

let group = DispatchGroup()
let queue = DispatchQueue(label: "thread-safe-array")

while date >= earliest {
await makeHealthSnapForDate(date: date, group: group, queue: queue)
date = date.addDays(days: -1)
Expand All @@ -91,7 +91,7 @@ final class HealthManager: ObservableObject {
let quantityTypeActiveEnergy = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.activeEnergyBurned)!
let quantityTypeMenstrual = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.menstrualFlow)!
let quantityTypeSleep = HKObjectType.categoryType(forIdentifier: HKCategoryTypeIdentifier.sleepAnalysis)!

let predicate = HKQuery.predicateForSamples(withStart: startDate,
end: endDate,
options: .strictStartDate)
Expand Down Expand Up @@ -423,9 +423,9 @@ final class HealthManager: ObservableObject {
func processMenstrual(data: DataStoreClass) async -> Bool {
let menstrualDataUI: [CGFloat?] = getMenstrualData(data: data, health: self)
let menstrualDatesUI: [Date] = getMenstrualDates(healthSnaps: self.healthSnaps)
let menstrualButterflyUI: ButterflyEntryStruct = averageMenstrualTransientForDates(dates: menstrualDatesUI,
data: data,
maxWindow: menstrualTransientWindow)
let menstrualButterflyUI: ButterflyEntryStruct = averageTransientForDates(dates: menstrualDatesUI,
data: data,
maxWindow: menstrualTransientWindow)

DispatchQueue.main.async {
self.menstrualData = menstrualDataUI
Expand Down
33 changes: 33 additions & 0 deletions MoodSnap/Statistics/GenerateHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@ import SwiftUI
return sequence
}

/**
Sequence `moodSnaps` into chronological array.
*/
@inline(__always) func sequenceMoodSnaps(moodSnaps: [MoodSnapStruct]) -> [[MoodSnapStruct]] {
let earliest: Date = getFirstDate(moodSnaps: moodSnaps)
let length: Int = Calendar.current.numberOfDaysBetween(from: earliest, to: Date()) + 1
var sequence: [[MoodSnapStruct]] = Array(repeating: [], count: length)

for moodSnap in moodSnaps {
if moodSnap.snapType == .mood {
let offset = length - 1 - Calendar.current.numberOfDaysBetween(from: moodSnap.timestamp, to: Date())
sequence[offset].append(moodSnap)
}
}

return sequence
}

/**
Flatten sequence of MoodSnaps on a per-day basis.
*/
Expand All @@ -33,6 +52,20 @@ import SwiftUI
return flattenedSequence
}

/**
Flatten sequence of MoodSnaps on a per-day basis.
*/
@inline(__always) func flattenSequence(sequence: [[MoodSnapStruct]]) -> [MoodSnapStruct?] {
var flattenedSequence: [MoodSnapStruct?] = []

for snaps in sequence {
let flattened: MoodSnapStruct? = mergeMoodSnaps(moodSnaps: snaps)
flattenedSequence.append(flattened)
}

return flattenedSequence
}

/**
Generate the complete history of mood levels, moving averages and moving volatilities from `data`.
*/
Expand Down
77 changes: 28 additions & 49 deletions MoodSnap/Statistics/Transients.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,44 +33,11 @@ import SwiftUI
return thisButterfly
}

/**
Average `ButterflyEntryStruct` from data centered around an array of `dates`.
*/
@inline(__always) func averageMenstrualTransientForDates(dates: [Date], data: DataStoreClass, maxWindow: Int) -> ButterflyEntryStruct {
let butterflyMood = averageDifferentialWindowForDates(
data: data,
dates: dates,
maxWindow: maxWindow)
let butterflyVolatility = volatilityDifferentialWindowForDates(
data: data,
dates: dates,
maxWindow: maxWindow)

var thisButterfly = ButterflyEntryStruct()

thisButterfly.elevation = butterflyMood[0]
thisButterfly.depression = butterflyMood[1]
thisButterfly.anxiety = butterflyMood[2]
thisButterfly.irritability = butterflyMood[3]

thisButterfly.elevationVolatility = butterflyVolatility[0]
thisButterfly.depressionVolatility = butterflyVolatility[1]
thisButterfly.anxietyVolatility = butterflyVolatility[2]
thisButterfly.irritabilityVolatility = butterflyVolatility[3]

thisButterfly.occurrences = dates.count

let timeline = generateTimelineForDates(data: data, dates: dates)
thisButterfly.timeline = timeline

return thisButterfly
}

/**
Differential (average) foccused on `date`.
*/
@inline(__always) func averageDifferential(data: DataStoreClass, date: Date, window: Int) -> [CGFloat?] {
var today: [MoodSnapStruct] = getMoodSnapsByDate(data: data, date: date, flatten: true)
@inline(__always) func averageDifferential(moodSnaps: [MoodSnapStruct], date: Date, window: Int) -> [CGFloat?] {
var today: [MoodSnapStruct] = getMoodSnapsByDate(moodSnaps: moodSnaps, date: date, flatten: true)

let todayCount = today.count

Expand All @@ -81,16 +48,17 @@ import SwiftUI
}

var samples: [MoodSnapStruct] = []

if window >= 0 {
samples = getMoodSnapsByDateWindow(
data: data,
moodSnaps: moodSnaps,
date: date,
windowStart: 0,
windowEnd: window,
flatten: true)
} else {
samples = getMoodSnapsByDateWindow(
data: data,
moodSnaps: moodSnaps,
date: date,
windowStart: window,
windowEnd: 0,
Expand Down Expand Up @@ -128,19 +96,19 @@ import SwiftUI
/**
Differential (volatility) foccused on `date`.
*/
@inline(__always) func volatilityDifferential(data: DataStoreClass, date: Date, window: Int) -> [CGFloat?] {
@inline(__always) func volatilityDifferential(moodSnaps: [MoodSnapStruct], date: Date, window: Int) -> [CGFloat?] {
var samples: [MoodSnapStruct] = []

if window >= 0 {
samples = getMoodSnapsByDateWindow(
data: data,
moodSnaps: moodSnaps,
date: date,
windowStart: 0,
windowEnd: window,
flatten: false)
} else {
samples = getMoodSnapsByDateWindow(
data: data,
moodSnaps: moodSnaps,
date: date,
windowStart: window,
windowEnd: 0,
Expand Down Expand Up @@ -215,10 +183,12 @@ import SwiftUI
var seriesD: [CGFloat?] = []
var seriesA: [CGFloat?] = []
var seriesI: [CGFloat?] = []

let windowSnaps = getMoodSnapsByDateWindow(data: data, date: date, windowStart: -maxWindow, windowEnd: maxWindow)

for window in -maxWindow ... maxWindow {
let thisDiff: [CGFloat?] = averageDifferential(
data: data,
moodSnaps: windowSnaps,
date: date,
window: window)
seriesE.append(thisDiff[0])
Expand All @@ -239,15 +209,24 @@ import SwiftUI
var seriesA: [CGFloat?] = []
var seriesI: [CGFloat?] = []

let windowSnaps = getMoodSnapsByDateWindow(data: data, date: date, windowStart: -maxWindow, windowEnd: maxWindow)

for window in -maxWindow ... maxWindow {
let thisDiff: [CGFloat?] = volatilityDifferential(
data: data,
date: date,
window: window)
seriesE.append(thisDiff[0])
seriesD.append(thisDiff[1])
seriesA.append(thisDiff[2])
seriesI.append(thisDiff[3])
if window == -maxWindow || window == maxWindow {
let thisDiff: [CGFloat?] = volatilityDifferential(
moodSnaps: windowSnaps,
date: date,
window: window)
seriesE.append(thisDiff[0])
seriesD.append(thisDiff[1])
seriesA.append(thisDiff[2])
seriesI.append(thisDiff[3])
} else {
seriesE.append(nil)
seriesD.append(nil)
seriesA.append(nil)
seriesI.append(nil)
}
}

return [seriesE, seriesD, seriesA, seriesI]
Expand Down

0 comments on commit 6f80cee

Please sign in to comment.