diff --git a/src/lib.rs b/src/lib.rs index c007764..d4efa2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub mod gaussian; mod history; mod key_table; mod matrix; +mod observer; mod outcome; mod rating; pub(crate) mod schedule; @@ -33,6 +34,7 @@ pub use gaussian::Gaussian; pub use history::History; pub use key_table::KeyTable; use matrix::Matrix; +pub use observer::{NullObserver, Observer}; pub use outcome::Outcome; pub use rating::Rating; pub use schedule::ScheduleReport; diff --git a/src/observer.rs b/src/observer.rs new file mode 100644 index 0000000..223948b --- /dev/null +++ b/src/observer.rs @@ -0,0 +1,48 @@ +//! Observer trait for progress reporting during convergence. +//! +//! Replaces the old `verbose: bool` + `println!` path. Callers wire in any +//! observer that implements the trait; default methods are no-ops so users +//! override only what they need. + +use crate::time::Time; + +/// Receives progress callbacks during `History::converge`. +/// +/// All methods have default no-op implementations; implement only what's +/// interesting. Send/Sync is NOT required in T2 (added in T3 along with +/// Rayon support). +pub trait Observer { + /// Called after each convergence iteration across the whole history. + fn on_iteration_end(&self, _iter: usize, _max_step: (f64, f64)) {} + + /// Called after each time slice is processed within an iteration. + fn on_batch_processed(&self, _time: &T, _slice_idx: usize, _n_events: usize) {} + + /// Called once when convergence completes (or max iters is reached). + fn on_converged(&self, _iters: usize, _final_step: (f64, f64), _converged: bool) {} +} + +/// ZST no-op observer; the default when none is configured. +#[derive(Copy, Clone, Debug, Default)] +pub struct NullObserver; + +impl Observer for NullObserver {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn null_observer_compiles_for_i64() { + let o = NullObserver; + >::on_iteration_end(&o, 1, (0.0, 0.0)); + >::on_converged(&o, 5, (1e-6, 1e-6), true); + } + + #[test] + fn null_observer_compiles_for_untimed() { + use crate::Untimed; + let o = NullObserver; + >::on_iteration_end(&o, 1, (0.0, 0.0)); + } +}