apsis-rs/crates/sim/apsis-output/src/trace.rs

306 lines
9.2 KiB
Rust

//! The trace and its summaries.
use crate::record::{Record, RecordKind};
use std::collections::BTreeMap;
/// A list of records in the order they were made.
///
/// The trace preserves insertion order rather than sorting by time, because
/// the order a model recorded in is part of what it decided. A caller that
/// needs time order asks for [`Trace::in_time_order`], which leaves the
/// original alone.
///
/// 按作出次序排列的记录列表。
///
/// 轨迹保留插入次序而不按时间排序,因为模型记录的次序是其决策的一部分。
/// 需要时间次序的调用方索取 [`Trace::in_time_order`],它不改变原轨迹。
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Trace {
records: Vec<Record>,
}
impl Trace {
/// Creates an empty trace.
///
/// 创建一个空轨迹。
pub const fn new() -> Self {
Self {
records: Vec::new(),
}
}
/// Creates a trace from a list of records.
///
/// 由记录列表创建轨迹。
pub fn from_records(records: Vec<Record>) -> Self {
Self { records }
}
/// Returns the number of records.
///
/// 返回记录的数量。
pub fn len(&self) -> usize {
self.records.len()
}
/// Returns `true` when there are no records.
///
/// 当没有记录时返回 `true`。
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
/// Returns the records in the order they were made.
///
/// 按作出次序返回各记录。
pub fn records(&self) -> &[Record] {
&self.records
}
/// Adds a record, returning `false` when it is not usable.
///
/// A record whose time or value is not finite is refused rather than
/// stored, so a trace never holds a value that cannot be summarized.
///
/// 添加一条记录,不可用时返回 `false`。
///
/// 时间或值非有限的记录被拒绝而非存储,故轨迹绝不保存无法汇总的
/// 值。
pub fn push(&mut self, record: Record) -> bool {
if !record.is_valid() {
return false;
}
self.records.push(record);
true
}
/// Returns `true` when no record has a time or value that is not finite.
///
/// 当没有任何记录的时间或值非有限时返回 `true`。
pub fn is_stable(&self) -> bool {
self.records.iter().all(Record::is_valid)
}
/// Returns the records in time order, earliest first.
///
/// Records at the same instant keep their insertion order, so the sort is
/// stable and the result does not depend on the sort algorithm.
///
/// 按时间次序返回各记录,最早在前。
///
/// 同一瞬时的记录保持其插入次序,故该排序是稳定的,结果不依赖于
/// 排序算法。
pub fn in_time_order(&self) -> Vec<Record> {
let mut ordered = self.records.clone();
ordered.sort_by(|left, right| {
left.time
.partial_cmp(&right.time)
.unwrap_or(core::cmp::Ordering::Equal)
});
ordered
}
/// Returns the records of one kind, in order.
///
/// 按次序返回某一类别的记录。
pub fn of_kind(&self, kind: RecordKind) -> Vec<&Record> {
self.records
.iter()
.filter(|record| record.kind == kind)
.collect()
}
/// Returns the records with a label, in order.
///
/// 按次序返回具有某标签的记录。
pub fn with_label(&self, label: &str) -> Vec<&Record> {
self.records
.iter()
.filter(|record| record.label == label)
.collect()
}
/// Returns the value of the last record with a label.
///
/// This is what a caller asks for after a run: the final value a series
/// reached. Returns `None` when no record has the label.
///
/// 返回具有某标签的最后一条记录的值。
///
/// 这是调用方在运行结束后所索取的:某序列达到的最终值。没有记录
/// 具有该标签时返回 `None`。
pub fn value_of(&self, label: &str) -> Option<f64> {
self.records
.iter()
.rev()
.find(|record| record.label == label)
.map(|record| record.value)
}
/// Returns the time of the last record with a label.
///
/// 返回具有某标签的最后一条记录的时间。
pub fn last_time_of(&self, label: &str) -> Option<f64> {
self.records
.iter()
.rev()
.find(|record| record.label == label)
.map(|record| record.time)
}
/// Returns the labels present, in sorted order.
///
/// The order is sorted rather than first seen, so that two traces with
/// the same labels list them the same way.
///
/// 按排序次序返回出现的标签。
///
/// 次序是按排序而非按首次出现,使具有相同标签的两个轨迹以相同
/// 方式列出它们。
pub fn labels(&self) -> Vec<&str> {
let mut labels: Vec<&str> = self
.records
.iter()
.map(|record| record.label.as_str())
.collect();
labels.sort_unstable();
labels.dedup();
labels
}
/// Returns a summary of the series recorded under a label.
///
/// Returns `None` when no record has the label.
///
/// 返回在某标签下所记录序列的汇总。
///
/// 没有记录具有该标签时返回 `None`。
pub fn summarize(&self, label: &str) -> Option<SeriesSummary> {
let values: Vec<f64> = self
.records
.iter()
.filter(|record| record.label == label)
.map(|record| record.value)
.collect();
if values.is_empty() {
return None;
}
let count = values.len();
let sum: f64 = values.iter().sum();
let min = values.iter().copied().fold(f64::INFINITY, f64::min);
let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
let first = values[0];
let last = values[count - 1];
Some(SeriesSummary {
label: label.to_string(),
count,
min,
max,
mean: sum / count as f64,
first,
last,
change: last - first,
})
}
/// Returns the number of warnings in the trace.
///
/// 返回轨迹中警告的数量。
pub fn warning_count(&self) -> usize {
self.records
.iter()
.filter(|record| record.kind.is_warning())
.count()
}
/// Returns a summary of every label, in sorted label order.
///
/// 按标签排序次序返回每个标签的汇总。
pub fn summaries(&self) -> Vec<SeriesSummary> {
self.labels()
.into_iter()
.filter_map(|label| self.summarize(label))
.collect()
}
/// Returns the greatest number of records that share a label.
///
/// This is the length of the longest series, which a caller uses to size
/// a table of the trace.
///
/// 返回共享同一标签的记录数的最大值。
///
/// 它是最长序列的长度,调用方据此确定轨迹表格的大小。
pub fn longest_series(&self) -> usize {
let mut counts: BTreeMap<&str, usize> = BTreeMap::new();
for record in &self.records {
*counts.entry(record.label.as_str()).or_insert(0) += 1;
}
counts.values().copied().max().unwrap_or(0)
}
/// Removes every record and returns how many were discarded.
///
/// 移除所有记录并返回被丢弃的数量。
pub fn clear(&mut self) -> usize {
let count = self.records.len();
self.records.clear();
count
}
}
/// A summary of one series of records.
///
/// 某一条记录序列的汇总。
#[derive(Debug, Clone, PartialEq)]
pub struct SeriesSummary {
/// The label the series was recorded under.
///
/// 该序列所记录用的标签。
pub label: String,
/// The number of records in the series.
///
/// 该序列中记录的数量。
pub count: usize,
/// The smallest value recorded.
///
/// 所记录的最小值。
pub min: f64,
/// The greatest value recorded.
///
/// 所记录的最大值。
pub max: f64,
/// The mean of the values.
///
/// 各值的均值。
pub mean: f64,
/// The first value recorded.
///
/// 所记录的第一个值。
pub first: f64,
/// The last value recorded.
///
/// 所记录的最后一个值。
pub last: f64,
/// The difference between the last and first values.
///
/// 最后一个值与第一个值之差。
pub change: f64,
}
impl SeriesSummary {
/// Returns the spread between the greatest and smallest values.
///
/// 返回最大值与最小值之间的跨度。
pub fn range(&self) -> f64 {
self.max - self.min
}
/// Returns `true` when the series never changed.
///
/// 当该序列从未变化时返回 `true`。
pub fn is_constant(&self) -> bool {
self.max == self.min
}
}