use argmin::core::observers::Observe; use argmin::core::{Error, State}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; /// Observer that enforces strict trial budget limits /// /// Argmin's PSO does not reliably respect max_iters when set to small values (e.g., 1). /// This observer tracks evaluations across all iterations and terminates when budget is hit. #[derive(Clone, Debug)] pub struct TrialBudgetObserver { max_trials: usize, trials_used: Arc, } impl TrialBudgetObserver { pub fn new(max_trials: usize) -> Self { Self { max_trials, trials_used: Arc::new(AtomicUsize::new(0)), } } pub fn increment_trial(&self) { self.trials_used.fetch_add(1, Ordering::Relaxed); } pub fn should_terminate(&self) -> bool { self.trials_used.load(Ordering::Relaxed) >= self.max_trials } pub fn get_trials_used(&self) -> usize { self.trials_used.load(Ordering::Relaxed) } } impl Observe for TrialBudgetObserver where I: State, { fn observe_iter(&mut self, _state: &I, _kv: &argmin::core::KV) -> Result<(), Error> { // Check if we've exceeded budget if self.should_terminate() { tracing::warn!( "Trial budget exhausted: {}/{} trials used. Terminating optimization.", self.get_trials_used(), self.max_trials ); // Return error to signal termination return Err(Error::msg(format!( "Trial budget exhausted: {}/{} trials", self.get_trials_used(), self.max_trials ))); } Ok(()) } }