#![allow(dead_code)]
use std::time::{Duration, Instant};
pub struct ProgressReporter {
start_time: Instant,
name: String,
total_iterations: usize,
report_interval: Duration,
last_report: Instant,
current_iteration: usize,
best_loss: f64,
verbose: bool,
}
impl ProgressReporter {
pub fn new(name: impl Into<String>, total_iterations: usize) -> Self {
Self {
start_time: Instant::now(),
name: name.into(),
total_iterations,
report_interval: Duration::from_secs(5),
last_report: Instant::now(),
current_iteration: 0,
best_loss: f64::INFINITY,
verbose: true,
}
}
pub fn with_interval(mut self, interval: Duration) -> Self {
self.report_interval = interval;
self
}
pub fn with_verbose(mut self, verbose: bool) -> Self {
self.verbose = verbose;
self
}
pub fn report(&mut self, current_iter: usize, current_loss: f64) {
self.current_iteration = current_iter;
if current_loss < self.best_loss {
self.best_loss = current_loss;
}
if !self.verbose {
return;
}
let now = Instant::now();
if now.duration_since(self.last_report) >= self.report_interval {
let elapsed = now.duration_since(self.start_time);
let progress = if self.total_iterations > 0 {
current_iter as f64 / self.total_iterations as f64
} else {
0.0
};
let eta = if progress > 0.01 && current_iter > 0 {
let per_iter = elapsed.as_secs_f64() / current_iter as f64;
let remaining = self.total_iterations.saturating_sub(current_iter);
Duration::from_secs_f64(per_iter * remaining as f64)
} else {
Duration::from_secs(0)
};
eprintln!(
"[{}] {:3.1}% | iter {}/{} | loss: {:.6} (best: {:.6}) | elapsed: {:.1}s | ETA: {:.1}s",
self.name,
progress * 100.0,
current_iter,
self.total_iterations,
current_loss,
self.best_loss,
elapsed.as_secs_f64(),
eta.as_secs_f64()
);
self.last_report = now;
}
}
pub fn force_report(&mut self, current_iter: usize, current_loss: f64) {
self.current_iteration = current_iter;
if current_loss < self.best_loss {
self.best_loss = current_loss;
}
if !self.verbose {
return;
}
let elapsed = self.start_time.elapsed();
let progress = if self.total_iterations > 0 {
current_iter as f64 / self.total_iterations as f64
} else {
0.0
};
let eta = if progress > 0.01 && current_iter > 0 {
let per_iter = elapsed.as_secs_f64() / current_iter as f64;
let remaining = self.total_iterations.saturating_sub(current_iter);
Duration::from_secs_f64(per_iter * remaining as f64)
} else {
Duration::from_secs(0)
};
eprintln!(
"[{}] {:3.1}% | iter {}/{} | loss: {:.6} (best: {:.6}) | elapsed: {:.1}s | ETA: {:.1}s",
self.name,
progress * 100.0,
current_iter,
self.total_iterations,
current_loss,
self.best_loss,
elapsed.as_secs_f64(),
eta.as_secs_f64()
);
self.last_report = Instant::now();
}
pub fn finish(&self, final_loss: f64) {
if !self.verbose {
return;
}
let elapsed = self.start_time.elapsed();
let improvement = if self.best_loss < f64::INFINITY && self.best_loss > 0.0 {
let imp = (1.0 - final_loss / self.best_loss) * 100.0;
format!(" ({:+.1}% from initial)", imp)
} else {
String::new()
};
eprintln!(
"[{}] Complete | final loss: {:.6}{} | total time: {:.1}s | iters: {}",
self.name,
final_loss,
improvement,
elapsed.as_secs_f64(),
self.current_iteration
);
}
pub fn elapsed(&self) -> Duration {
self.start_time.elapsed()
}
pub fn best_loss(&self) -> f64 {
self.best_loss
}
pub fn current_iteration(&self) -> usize {
self.current_iteration
}
}
pub struct MultiStageProgress {
name: String,
stages: Vec<(String, f64)>,
current_stage: usize,
start_time: Instant,
verbose: bool,
}
impl MultiStageProgress {
pub fn new(name: impl Into<String>, stages: Vec<(String, f64)>) -> Self {
Self {
name: name.into(),
stages,
current_stage: 0,
start_time: Instant::now(),
verbose: true,
}
}
pub fn with_verbose(mut self, verbose: bool) -> Self {
self.verbose = verbose;
self
}
pub fn start_stage(&mut self, stage_name: &str) {
for (i, (name, _)) in self.stages.iter().enumerate() {
if name == stage_name {
self.current_stage = i;
break;
}
}
if !self.verbose {
return;
}
eprintln!(
"[{}] Stage {}/{}: {}",
self.name,
self.current_stage + 1,
self.stages.len(),
stage_name
);
}
pub fn complete_stage(&mut self) {
if self.current_stage >= self.stages.len() {
return;
}
let stage_name = self.stages[self.current_stage].0.clone();
self.current_stage += 1;
if !self.verbose {
return;
}
let elapsed = self.start_time.elapsed();
eprintln!(
"[{}] Stage '{}' complete | elapsed: {:.1}s",
self.name,
stage_name,
elapsed.as_secs_f64()
);
}
pub fn finish(&self) {
if !self.verbose {
return;
}
let elapsed = self.start_time.elapsed();
eprintln!(
"[{}] All stages complete | total time: {:.1}s",
self.name,
elapsed.as_secs_f64()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_reporter_new() {
let reporter = ProgressReporter::new("Test", 1000);
assert_eq!(reporter.total_iterations, 1000);
assert_eq!(reporter.best_loss, f64::INFINITY);
}
#[test]
fn test_progress_reporter_tracks_best_loss() {
let mut reporter = ProgressReporter::new("Test", 100).with_verbose(false);
reporter.report(10, 5.0);
assert_eq!(reporter.best_loss(), 5.0);
reporter.report(20, 3.0);
assert_eq!(reporter.best_loss(), 3.0);
reporter.report(30, 4.0);
assert_eq!(reporter.best_loss(), 3.0);
}
#[test]
fn test_progress_reporter_elapsed() {
let reporter = ProgressReporter::new("Test", 100);
std::thread::sleep(std::time::Duration::from_millis(10));
assert!(reporter.elapsed().as_millis() >= 10);
}
#[test]
fn test_multi_stage_progress() {
let stages = vec![
("Load".to_string(), 0.1),
("Optimize".to_string(), 0.8),
("Save".to_string(), 0.1),
];
let mut progress = MultiStageProgress::new("Test", stages).with_verbose(false);
progress.start_stage("Load");
assert_eq!(progress.current_stage, 0);
progress.complete_stage();
progress.start_stage("Optimize");
assert_eq!(progress.current_stage, 1);
}
}