forge_core/termination.rs
1//! Stopping criteria shared by every optimizer.
2//!
3//! All forge algorithms count objective **evaluations** as their budget unit
4//! (not iterations or generations), so runs are comparable across algorithms
5//! with very different inner loops. An optional target value enables early exit
6//! once a good-enough solution is reached.
7
8use crate::solution::StopReason;
9
10/// When an optimizer should stop.
11#[derive(Debug, Clone, Copy)]
12pub struct Termination {
13 /// Maximum number of objective evaluations.
14 pub max_evaluations: usize,
15 /// Optional target: stop as soon as the minimized objective drops to or
16 /// below this value. `None` disables early exit.
17 pub target: Option<f64>,
18}
19
20impl Termination {
21 /// A pure evaluation budget with no target.
22 pub fn budget(max_evaluations: usize) -> Self {
23 Termination {
24 max_evaluations,
25 target: None,
26 }
27 }
28
29 /// Adds an early-exit target (minimized sense).
30 pub fn with_target(mut self, target: f64) -> Self {
31 self.target = Some(target);
32 self
33 }
34
35 /// True once `best` meets the target, if any.
36 #[inline]
37 pub fn target_met(&self, best: f64) -> bool {
38 matches!(self.target, Some(t) if best <= t)
39 }
40
41 /// The stop reason implied by the current state.
42 #[inline]
43 pub fn reason(&self, evaluations: usize, best: f64) -> Option<StopReason> {
44 if self.target_met(best) {
45 Some(StopReason::TargetReached)
46 } else if evaluations >= self.max_evaluations {
47 Some(StopReason::BudgetExhausted)
48 } else {
49 None
50 }
51 }
52}
53
54impl Default for Termination {
55 fn default() -> Self {
56 Termination::budget(1000)
57 }
58}