///|
/// Window aggregation helpers for dashboards and batch analysis.
///|
/// One scalar window observation.
pub(all) struct ScalarWindow {
start_index : Int
end_index : Int
mean : Double
median : Double
standard_deviation : Double
minimum : Double
maximum : Double
valid : Bool
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Window schedule.
pub(all) struct WindowSchedule {
size : Int
hop : Int
include_partial : Bool
} derive(FromJson, ToJson, Debug, Eq)
///|
/// Default overlapping schedule for display metrics.
pub fn WindowSchedule::default() -> WindowSchedule {
{ size: 60, hop: 30, include_partial: false }
}
///|
/// Return valid windows according to a schedule.
pub fn scalar_windows(
values : Array[Double],
schedule : WindowSchedule,
) -> Array[ScalarWindow] {
if schedule.size <= 0 || schedule.hop <= 0 {
return []
}
let result = []
let mut start = 0
while start < values.length() {
let end = if start + schedule.size > values.length() {
values.length()
} else {
start + schedule.size
}
if end - start < schedule.size && !schedule.include_partial {
break
}
let window = []
for i in start.. 0,
})
start += schedule.hop
}
result
}
///|
/// Aggregate RMSSD from complete RR windows.
pub fn rmssd_windows(
intervals : Array[Double],
schedule : WindowSchedule,
) -> Array[ScalarWindow] {
if schedule.size <= 1 || schedule.hop <= 0 {
return []
}
let result = []
let mut start = 0
while start < intervals.length() {
let end = if start + schedule.size > intervals.length() {
intervals.length()
} else {
start + schedule.size
}
if end - start < schedule.size && !schedule.include_partial {
break
}
let window = []
for i in start.. 1,
})
start += schedule.hop
}
result
}
///|
/// Return the mean of a window field.
pub fn window_mean(windows : Array[ScalarWindow]) -> Double {
let values = []
for window in windows {
if window.valid {
values.push(window.mean)
}
}
mean_value(values)
}
///|
/// Return the slope of window means.
pub fn window_mean_trend(windows : Array[ScalarWindow]) -> Double {
let values = []
for window in windows {
if window.valid {
values.push(window.mean)
}
}
fit_linear_trend(values).slope
}
///|
/// Return the best window by a scalar field.
pub fn best_scalar_window(windows : Array[ScalarWindow]) -> ScalarWindow? {
let mut result : ScalarWindow? = None
for window in windows {
if window.valid {
match result {
None => result = Some(window)
Some(current) => if window.mean > current.mean { result = Some(window) }
}
}
}
result
}
///|
/// Return the worst window by a scalar field.
pub fn worst_scalar_window(windows : Array[ScalarWindow]) -> ScalarWindow? {
let mut result : ScalarWindow? = None
for window in windows {
if window.valid {
match result {
None => result = Some(window)
Some(current) => if window.mean < current.mean { result = Some(window) }
}
}
}
result
}
///|
/// Return windows whose values changed by at least a threshold.
pub fn changed_windows(
windows : Array[ScalarWindow],
threshold : Double,
) -> Array[ScalarWindow] {
let result = []
if windows.length() == 0 {
return result
}
let bound = if threshold < 0.0 { 0.0 } else { threshold }
let mut previous = windows[0].mean
for i in 1..= bound {
result.push(windows[i])
}
previous = windows[i].mean
}
result
}
///|
/// Downsample window means into a fixed number of buckets.
pub fn bucket_window_means(
windows : Array[ScalarWindow],
bucket_count : Int,
) -> Array[Double] {
if bucket_count <= 0 || windows.length() == 0 {
return []
}
let result = Array::make(bucket_count, 0.0)
let counts = Array::make(bucket_count, 0)
for i in 0.. 0 {
result[i] /= counts[i].to_double()
}
}
result
}
///|
/// Convert scalar windows to a compact feature vector.
pub fn window_feature_vector(windows : Array[ScalarWindow]) -> Array[Double] {
let best = best_scalar_window(windows)
let worst = worst_scalar_window(windows)
[
windows.length().to_double(),
window_mean(windows),
window_mean_trend(windows),
match best {
Some(value) => value.mean
None => 0.0
},
match worst {
Some(value) => value.mean
None => 0.0
},
standard_deviation(windows.map(fn(window) { window.mean })),
]
}
///|
/// Return whether windows have monotonic, non-overlapping indices.
pub fn windows_are_ordered(windows : Array[ScalarWindow]) -> Bool {
for i in 1..