///|
/// Counts of observable value classes for one signal.
pub struct ValueDistribution {
signal_id : String
total : Int
scalar : Int
vector : Int
real : Int
zero : Int
one : Int
unknown : Int
high_impedance : Int
} derive(Debug, Eq)
///|
/// Count value classes without losing the distinction between x and z.
pub fn value_distribution(
trace : Trace,
signal_id : String,
) -> ValueDistribution {
let mut total = 0
let mut scalar = 0
let mut vector = 0
let mut real = 0
let mut zero = 0
let mut one = 0
let mut unknown = 0
let mut high_impedance = 0
for change in trace.changes {
if change.id != signal_id {
continue
}
total += 1
match change.value {
Scalar(value) => {
scalar += 1
match logic_bit(value) {
Zero => zero += 1
One => one += 1
Unknown => unknown += 1
HighImpedance => high_impedance += 1
}
}
Vector(bits) => {
vector += 1
if bits.contains("x") || bits.contains("X") {
unknown += 1
}
if bits.contains("z") || bits.contains("Z") {
high_impedance += 1
}
}
Real(_) => real += 1
}
}
{ signal_id, total, scalar, vector, real, zero, one, unknown, high_impedance }
}
///|
/// Return every observed scalar transition and its multiplicity.
pub fn transition_matrix(
trace : Trace,
signal_id : String,
) -> Array[(Char, Char, Int)] {
let result : Array[(Char, Char, Int)] = []
let mut previous : Char? = None
for change in trace.changes {
if change.id != signal_id {
continue
}
match change.value {
Scalar(current) => {
match previous {
Some(before) => {
let found = result.search_by(fn(item) {
item.0 == before && item.1 == current
})
match found {
Some(index) =>
result[index] = (before, current, result[index].2 + 1)
None => result.push((before, current, 1))
}
}
None => ()
}
previous = Some(current)
}
_ => previous = None
}
}
result
}
///|
/// Render a transition matrix as stable CSV.
pub fn transition_matrix_csv(trace : Trace, signal_id : String) -> String {
let out : Array[String] = ["from,to,count"]
for item in transition_matrix(trace, signal_id) {
out.push(
item.0.to_string() + "," + item.1.to_string() + "," + item.2.to_string(),
)
}
out.join("\n")
}
///|
/// Fraction of changes whose value contains no x or z state.
pub fn known_value_ratio(trace : Trace, signal_id : String) -> Double {
let distribution = value_distribution(trace, signal_id)
if distribution.total == 0 {
0.0
} else {
(distribution.total - distribution.unknown - distribution.high_impedance).to_double() /
distribution.total.to_double()
}
}
///|
/// Fraction of changes carrying an unknown or high-impedance state.
pub fn unknown_value_ratio(trace : Trace, signal_id : String) -> Double {
1.0 - known_value_ratio(trace, signal_id)
}
///|
/// Count signal changes per unit of observed trace time.
pub fn change_rate(trace : Trace, signal_id : String) -> Double {
if trace.end_time <= 0L {
0.0
} else {
signal_changes(trace, signal_id).length().to_double() /
trace.end_time.to_double()
}
}
///|
/// Count how many declared signals changed at each timestamp.
pub fn active_signal_counts(trace : Trace) -> Array[(Int64, Int)] {
let result : Array[(Int64, Int)] = []
let mut index = 0
while index < trace.changes.length() {
let timestamp = trace.changes[index].time
let ids : Array[String] = []
while index < trace.changes.length() &&
trace.changes[index].time == timestamp {
if !ids.contains(trace.changes[index].id) {
ids.push(trace.changes[index].id)
}
index += 1
}
result.push((timestamp, ids.length()))
}
result
}
///|
/// Return the timestamp at which the most signals changed together.
pub fn maximum_parallel_activity(trace : Trace) -> (Int64, Int) {
let result = active_signal_counts(trace)
let mut best = (0L, 0)
for item in result {
if item.1 > best.1 {
best = item
}
}
best
}
///|
/// Summarize all declared signals as a compact table.
pub fn statistics_catalog(
trace : Trace,
) -> Array[(String, Int, Double, Double)] {
trace.signals.map(fn(signal) {
(
signal_path(signal),
signal_changes(trace, signal.id).length(),
known_value_ratio(trace, signal.id),
change_rate(trace, signal.id),
)
})
}
///|
/// Render a stable CSV catalog for plotting and spreadsheet review.
pub fn statistics_catalog_csv(trace : Trace) -> String {
let out : Array[String] = ["path,changes,known_ratio,change_rate"]
for item in statistics_catalog(trace) {
out.push(
escape_text(item.0) +
"," +
item.1.to_string() +
"," +
item.2.to_string() +
"," +
item.3.to_string(),
)
}
out.join("\n")
}
///|
/// Encode one value distribution for dashboards.
pub fn value_distribution_json(item : ValueDistribution) -> String {
"{\"signal_id\":\"" +
escape_text(item.signal_id) +
"\",\"total\":" +
item.total.to_string() +
",\"scalar\":" +
item.scalar.to_string() +
",\"vector\":" +
item.vector.to_string() +
",\"real\":" +
item.real.to_string() +
",\"zero\":" +
item.zero.to_string() +
",\"one\":" +
item.one.to_string() +
",\"unknown\":" +
item.unknown.to_string() +
",\"high_impedance\":" +
item.high_impedance.to_string() +
"}"
}
///|
/// Produce a short human-readable signal quality line.
pub fn quality_line(trace : Trace, signal_id : String) -> String {
let item = value_distribution(trace, signal_id)
signal_id +
" total=" +
item.total.to_string() +
" known=" +
known_value_ratio(trace, item.signal_id).to_string() +
" unknown=" +
item.unknown.to_string() +
" z=" +
item.high_impedance.to_string()
}