use crate::grid::Condition;
use crate::stats::{ErrorRate, Estimate, Mean};
use comnum_core::Result;
use comnum_core::rng::Rng;
use std::collections::BTreeMap;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Record {
metrics: BTreeMap<String, Estimate>,
dropped: BTreeMap<String, u64>,
trial: u64,
}
impl Record {
pub fn new() -> Record {
Record::default()
}
pub fn for_trial(trial: u64) -> Record {
Record { trial, ..Record::default() }
}
pub fn trial(&self) -> u64 {
self.trial
}
pub fn errors(&mut self, name: &str, errors: u64, total: u64) {
let e = self.metrics.entry(name.to_string()).or_insert(Estimate::Rate(ErrorRate::default()));
if let Estimate::Rate(r) = e {
r.add(errors, total);
} else {
self.drop_values(name, 1);
}
}
pub fn value(&mut self, name: &str, v: f64) {
if !v.is_finite() {
self.drop_values(name, 1);
return;
}
let e = self.metrics.entry(name.to_string()).or_insert(Estimate::Mean(Mean::default()));
if let Estimate::Mean(m) = e {
m.add(v);
} else {
self.drop_values(name, 1);
}
}
pub fn values(&mut self, name: &str, vs: impl IntoIterator<Item = f64>) {
for v in vs {
self.value(name, v);
}
}
pub fn merge(&mut self, o: &Record) {
for (k, v) in &o.metrics {
match self.metrics.get_mut(k) {
Some(e) => {
if !e.merge(v) {
let n = match v {
Estimate::Mean(m) => m.count().max(1),
Estimate::Rate(_) => 1,
};
self.drop_values(k, n);
}
}
None => {
self.metrics.insert(k.clone(), *v);
}
}
}
for (k, n) in &o.dropped {
self.drop_values(k, *n);
}
}
fn drop_values(&mut self, name: &str, n: u64) {
*self.dropped.entry(name.to_string()).or_insert(0) += n;
}
pub fn dropped(&self, name: &str) -> u64 {
self.dropped.get(name).copied().unwrap_or(0)
}
pub fn dropped_iter(&self) -> impl Iterator<Item = (&String, u64)> {
self.dropped.iter().map(|(k, n)| (k, *n))
}
pub fn knows(&self, name: &str) -> bool {
self.metrics.contains_key(name) || self.dropped.contains_key(name)
}
pub fn get(&self, name: &str) -> Option<&Estimate> {
self.metrics.get(name)
}
pub fn iter(&self) -> impl Iterator<Item = (&String, &Estimate)> {
self.metrics.iter()
}
pub fn is_empty(&self) -> bool {
self.metrics.is_empty()
}
}
pub trait Scenario: Sync {
type State: Sync;
fn name(&self) -> &str;
fn setup(&self, condition: &Condition) -> Result<Self::State>;
fn trial(&self, state: &Self::State, condition: &Condition, rng: &mut Rng, record: &mut Record) -> Result<()>;
}
pub struct FnScenario<F> {
name: String,
f: F,
}
pub fn from_fn<F>(name: &str, f: F) -> FnScenario<F>
where
F: Fn(&Condition, &mut Rng, &mut Record) -> Result<()> + Sync,
{
FnScenario { name: name.to_string(), f }
}
impl<F> Scenario for FnScenario<F>
where
F: Fn(&Condition, &mut Rng, &mut Record) -> Result<()> + Sync,
{
type State = ();
fn name(&self) -> &str {
&self.name
}
fn setup(&self, _: &Condition) -> Result<()> {
Ok(())
}
fn trial(&self, (): &(), condition: &Condition, rng: &mut Rng, record: &mut Record) -> Result<()> {
(self.f)(condition, rng, record)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn non_finite_values_and_wrong_kinds_are_dropped_and_counted() {
let mut rec = Record::new();
rec.value("snr_db", 10.0);
rec.values("snr_db", [f64::INFINITY, f64::NAN, 12.0]);
rec.errors("snr_db", 3, 100);
rec.errors("ber", 1, 10);
rec.value("ber", 0.5);
rec.value("only_inf", f64::INFINITY);
assert_eq!(rec.get("snr_db").unwrap().count(), 2);
assert!((rec.get("snr_db").unwrap().value() - 11.0).abs() < 1e-12);
assert_eq!(rec.get("ber").unwrap().total(), 10);
assert_eq!((rec.dropped("snr_db"), rec.dropped("ber"), rec.dropped("only_inf")), (3, 1, 1));
assert!(rec.get("only_inf").is_none() && rec.knows("only_inf") && !rec.knows("evm"));
let mut other = Record::new();
other.errors("snr_db", 0, 4);
other.values("ber", [0.1, 0.2]);
other.value("only_inf", f64::NEG_INFINITY);
rec.merge(&other);
assert_eq!((rec.dropped("snr_db"), rec.dropped("ber"), rec.dropped("only_inf")), (4, 3, 2));
assert_eq!(rec.dropped("evm"), 0);
let all: Vec<(&String, u64)> = rec.dropped_iter().collect();
assert_eq!(all.len(), 3);
}
}