use crate::grid::{Condition, Value};
use crate::runner::{ConditionResult, RunInfo, secs};
use crate::stopping::Progress;
use std::io::Write;
use std::time::Instant;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Action {
Continue,
Skip,
StopCondition,
StopRun,
}
pub trait Callback: Send {
fn on_run_start(&mut self, _info: &RunInfo) {}
fn on_condition_start(&mut self, _index: usize, _condition: &Condition) -> Action {
Action::Continue
}
fn on_batch_end(&mut self, _progress: &Progress<'_>) -> Action {
Action::Continue
}
fn on_condition_end(&mut self, _result: &ConditionResult) -> Action {
Action::Continue
}
fn on_run_end(&mut self, _results: &[ConditionResult]) {}
}
#[derive(Clone, Debug)]
pub struct ProgressBar {
pub every_s: f64,
pub confidence: f64,
n: usize,
last: Option<Instant>,
}
impl Default for ProgressBar {
fn default() -> Self {
ProgressBar { every_s: 5.0, confidence: 0.95, n: 0, last: None }
}
}
impl ProgressBar {
pub fn new(every_s: f64) -> ProgressBar {
ProgressBar { every_s, ..ProgressBar::default() }
}
}
impl Callback for ProgressBar {
fn on_run_start(&mut self, info: &RunInfo) {
self.n = info.n_conditions;
eprintln!(
"[{}] {} conditions, seed {}, batches of {}, {} threads, stop: {}",
info.scenario, info.n_conditions, info.seed, info.batch, info.threads, info.stop
);
}
fn on_condition_start(&mut self, _: usize, _: &Condition) -> crate::callbacks::Action {
self.last = Some(Instant::now());
Action::Continue
}
fn on_batch_end(&mut self, p: &Progress<'_>) -> Action {
let now = Instant::now();
if self.last.is_some_and(|t| now.duration_since(t) >= secs(self.every_s)) {
self.last = Some(now);
eprint!(
"\r condition {}/{}: {} trials, {:.0} s",
p.condition + 1,
self.n,
p.trials,
p.elapsed.as_secs_f64()
);
let _ = std::io::stderr().flush();
}
Action::Continue
}
fn on_condition_end(&mut self, r: &ConditionResult) -> Action {
let metrics: Vec<String> = r
.record
.iter()
.map(|(k, e)| {
let (lo, hi) = e.interval(self.confidence);
format!("{k} = {:.4e} [{:.2e}, {:.2e}]", e.value(), lo, hi)
})
.collect();
eprintln!(
"\r[{}/{}] ({}) {}: {} trials, {:.2} s{}{}",
r.index + 1,
self.n,
r.condition.label(),
r.status.as_str(),
r.trials,
r.wall_s,
if metrics.is_empty() { "" } else { " " },
metrics.join(", ")
);
Action::Continue
}
}
#[derive(Clone, Debug)]
pub struct SkipAfter {
pub metric: String,
pub threshold: f64,
pub along: String,
done: Vec<Vec<(String, Value)>>,
}
impl SkipAfter {
pub fn below(metric: &str, threshold: f64, along: &str) -> SkipAfter {
SkipAfter { metric: metric.to_string(), threshold, along: along.to_string(), done: Vec::new() }
}
fn others(&self, c: &Condition) -> Vec<(String, Value)> {
c.entries().iter().filter(|(k, _)| *k != self.along).cloned().collect()
}
}
impl Callback for SkipAfter {
fn on_condition_start(&mut self, _: usize, c: &Condition) -> Action {
if self.done.contains(&self.others(c)) { Action::Skip } else { Action::Continue }
}
fn on_condition_end(&mut self, r: &ConditionResult) -> Action {
if r.trials > 0 && r.metric(&self.metric).is_some_and(|e| e.value() < self.threshold) {
let key = self.others(&r.condition);
if !self.done.contains(&key) {
self.done.push(key);
}
}
Action::Continue
}
}