///|
/// A numeric summary of a timestamp sequence.
pub struct TimeStatistics {
count : Int
minimum : Int64?
maximum : Int64?
mean : Double?
median : Double?
spread : Double?
} derive(Debug, Eq)
///|
/// Summarize timestamp values using deterministic sorting.
pub fn time_statistics(values : Array[Int64]) -> TimeStatistics {
let sorted = values.copy()
sorted.sort()
let count = sorted.length()
let total : Int64 = sorted.fold(init=0L, fn(sum, value) { sum + value })
let mean = if count == 0 {
None
} else {
Some(total.to_double() / count.to_double())
}
let median = if count == 0 {
None
} else if count % 2 == 1 {
Some(sorted[count / 2].to_double())
} else {
Some(
(sorted[count / 2 - 1].to_double() + sorted[count / 2].to_double()) / 2.0,
)
}
let spread = match (mean, count) {
(Some(center), value) if value > 0 =>
Some(
sorted.fold(init=0.0, fn(sum, item) {
let distance = item.to_double() - center
sum + distance * distance
}) /
value.to_double(),
)
_ => None
}
{
count,
minimum: sorted.get(0),
maximum: sorted.last(),
mean,
median,
spread,
}
}
///|
/// Return a signal's change timestamp statistics.
pub fn signal_time_statistics(
trace : Trace,
signal_id : String,
) -> TimeStatistics {
time_statistics(change_times(trace, signal_id))
}
///|
/// Return a signal's transition timestamp statistics.
pub fn transition_time_statistics(
trace : Trace,
signal_id : String,
) -> TimeStatistics {
time_statistics(transition_times(trace, signal_id))
}
///|
/// Compute an empirical transition histogram by integer bucket.
pub fn transition_histogram(
trace : Trace,
signal_id : String,
bucket : Int64,
) -> Array[(Int64, Int)] {
let width = Int64::max(1L, bucket)
let result : Array[(Int64, Int)] = []
for time in transition_times(trace, signal_id) {
let key = time / width * width
match result.search_by(fn(item) { item.0 == key }) {
Some(index) => result[index] = (key, result[index].1 + 1)
None => result.push((key, 1))
}
}
result.sort_by(fn(left, right) {
if left.0 < right.0 {
-1
} else if left.0 > right.0 {
1
} else {
0
}
})
result
}
///|
/// Compute Shannon-style entropy over observed values.
pub fn value_entropy(trace : Trace, signal_id : String) -> Double {
let values = distinct_values(trace, signal_id)
let total = signal_changes(trace, signal_id).length()
if total == 0 || values.length() == 0 {
return 0.0
}
let mut entropy = 0.0
for value in values {
let count = signal_changes(trace, signal_id).count_if(fn(change) {
change.value == value
})
let probability = count.to_double() / total.to_double()
entropy -= probability * log2_approx(probability)
}
entropy
}
///|
/// Return the average distance between changes, if at least two exist.
pub fn mean_change_interval(trace : Trace, signal_id : String) -> Double? {
let times = change_times(trace, signal_id)
if times.length() < 2 {
None
} else {
let mut total : Int64 = 0L
for i in 1.. Double {
if value <= 0.0 {
return 0.0
}
let mut x = value
let mut exponent = 0
while x < 1.0 {
x *= 2.0
exponent -= 1
}
while x >= 2.0 {
x /= 2.0
exponent += 1
}
let y = (x - 1.0) / (x + 1.0)
let y2 = y * y
let mut term = y
let mut sum = 0.0
for n in 0..<12 {
sum += term / (2.0 * n.to_double() + 1.0)
term *= y2
}
2.0 * sum / 0.6931471805599453 + exponent.to_double()
}