//! Convergence configuration and reporting. use std::time::Duration; use smallvec::SmallVec; #[derive(Clone, Copy, Debug)] pub struct ConvergenceOptions { pub max_iter: usize, pub epsilon: f64, /// EP damping factor in natural-parameter space: each per-factor /// update inside a single game writes `α·new + (1−α)·old`. `1.0` is /// undamped (default); `< 1.0` stabilises oscillating fixed-point /// loops at the cost of more iterations. Must be in `(0.0, 1.0]`. /// /// Applies only to the within-game EP loop (`run_chain`). The outer /// `History::converge` cross-history sweep is undamped regardless of /// this value — cross-slice damping is a different concept and not /// in scope. pub alpha: f64, } impl Default for ConvergenceOptions { fn default() -> Self { Self { max_iter: crate::ITERATIONS, epsilon: crate::EPSILON, alpha: 1.0, } } } /// Post-hoc summary of a `History::converge` call. #[derive(Clone, Debug)] pub struct ConvergenceReport { pub iterations: usize, pub final_step: (f64, f64), pub log_evidence: f64, pub converged: bool, pub per_iteration_time: SmallVec<[Duration; 32]>, pub slices_skipped: usize, } #[cfg(test)] mod tests { use super::*; #[test] fn default_alpha_is_one_for_undamped_behavior() { let opts = ConvergenceOptions::default(); assert_eq!(opts.alpha, 1.0); } }