///|
/// Summary for one inclusive analysis window.
pub struct WindowSummary {
start : Int64
end : Int64
changes : Int
signals : Int
rising_edges : Int
falling_edges : Int
unknown_changes : Int
} derive(Debug, Eq)
///|
/// A rolling point for plotting change density and activity.
pub struct RollingPoint {
time : Int64
changes : Int
active_signals : Int
} derive(Debug, Eq)
///|
/// Return windows of fixed width over the complete trace.
pub fn time_windows(trace : Trace, width : Int64) -> Array[(Int64, Int64)] {
let safe = Int64::max(1L, width)
let result : Array[(Int64, Int64)] = []
let mut start : Int64 = 0L
while start <= trace.end_time {
result.push((start, Int64::min(trace.end_time, start + safe)))
start += safe
}
result
}
///|
/// Summarize a single inclusive time window.
pub fn summarize_window(
trace : Trace,
start : Int64,
end : Int64,
) -> WindowSummary {
let changes = trace.changes.filter(fn(change) {
change.time >= start && change.time <= end
})
let ids : Array[String] = []
let mut rising = 0
let mut falling = 0
let mut unknown = 0
for change in changes {
if !ids.contains(change.id) {
ids.push(change.id)
}
if change.value.is_unknown() {
unknown += 1
}
}
for signal in ids {
for event in edges(trace, signal) {
if event.time >= start && event.time <= end {
match event.kind {
Rising => rising += 1
Falling => falling += 1
_ => ()
}
}
}
}
{
start,
end,
changes: changes.length(),
signals: ids.length(),
rising_edges: rising,
falling_edges: falling,
unknown_changes: unknown,
}
}
///|
/// Summarize every fixed-width window.
pub fn window_summaries(trace : Trace, width : Int64) -> Array[WindowSummary] {
time_windows(trace, width).map(fn(window) {
summarize_window(trace, window.0, window.1)
})
}
///|
/// Produce one rolling point at the beginning of every window.
pub fn rolling_points(trace : Trace, width : Int64) -> Array[RollingPoint] {
window_summaries(trace, width).map(fn(item) {
{ time: item.start, changes: item.changes, active_signals: item.signals }
})
}
///|
/// Return the fraction of a window covered by known scalar intervals.
pub fn window_coverage(
trace : Trace,
signal_id : String,
start : Int64,
end : Int64,
) -> Double {
let width = Int64::max(0L, end - start)
if width == 0L {
return 0.0
}
let covered = value_intervals(trace, signal_id).fold(init=0L, fn(
total,
interval,
) {
let left = Int64::max(start, interval.start)
let right = Int64::min(end, interval.end)
let known = match interval.value {
Scalar(value) => is_known_scalar(value)
_ => false
}
if known && right > left {
total + right - left
} else {
total
}
})
covered.to_double() / width.to_double()
}
///|
/// Return windows where a signal has at least one transition.
pub fn active_windows(
trace : Trace,
signal_id : String,
width : Int64,
) -> Array[(Int64, Int64)] {
time_windows(trace, width).filter(fn(window) {
signal_changes(trace, signal_id).any(fn(change) {
change.time >= window.0 && change.time <= window.1
})
})
}
///|
/// Return the peak transition count in any window.
pub fn peak_window_transitions(
trace : Trace,
signal_id : String,
width : Int64,
) -> Int {
time_windows(trace, width).fold(init=0, fn(current, window) {
let count = signal_changes(trace, signal_id).count_if(fn(change) {
change.time >= window.0 && change.time <= window.1
})
Int::max(current, count)
})
}
///|
/// Render rolling points as CSV.
pub fn rolling_points_csv(points : Array[RollingPoint]) -> String {
let out : Array[String] = ["time,changes,active_signals"]
for point in points {
out.push(
point.time.to_string() +
"," +
point.changes.to_string() +
"," +
point.active_signals.to_string(),
)
}
out.join("\n")
}