Skip to main content

trellis_runner/engine/policy/
max_iter.rs

1//! Iteration budget termination policy.
2//!
3//! This policy enforces a hard upper bound on the number of solver
4//! iterations.
5//!
6//! # Behaviour
7//!
8//! - If `context.iter > max_iters`, returns
9//!   [`EngineAction::Stop(Termination::ExceededMaxIterations)`].
10//! - Otherwise returns [`EngineAction::Continue`].
11//!
12//! # Design notes
13//!
14//! This is a safety policy intended to guarantee termination even when
15//! convergence criteria are not met.
16//!
17//! It should generally be included in all production policy stacks.
18//!
19//! The check is strict (`>`), meaning the solver is allowed to reach exactly
20//! `max_iters` before stopping is triggered on the next decision cycle.
21use super::EnginePolicy;
22
23use crate::engine::{EngineAction, EngineContext, EventBatch};
24
25pub struct MaxIterationPolicy {
26    max_iters: usize,
27}
28
29impl MaxIterationPolicy {
30    pub fn new(max_iters: usize) -> Self {
31        Self { max_iters }
32    }
33}
34
35impl<F> EnginePolicy<F> for MaxIterationPolicy {
36    fn decide(&mut self, _batch: &EventBatch<F>, context: &EngineContext) -> EngineAction {
37        dbg!(&context.iter);
38        if context.iter >= self.max_iters {
39            return EngineAction::Stop(crate::Termination::ExceededMaxIterations);
40        }
41
42        EngineAction::Continue
43    }
44}
45
46#[cfg(test)]
47mod test {
48    use super::*;
49
50    use crate::engine::policy::PolicyStack;
51    use crate::progress::Progress;
52
53    #[test]
54    fn max_iteration_policy_terminates_when_iter_exceeds_limit() {
55        let mut stack = PolicyStack::<f64>::new().add(MaxIterationPolicy::new(100));
56
57        let batch: EventBatch<f64> = EventBatch::new().add(Progress::Complete);
58        let ctx = EngineContext {
59            iter: 101,
60            ..Default::default()
61        };
62
63        assert!(matches!(
64            stack.decide(&batch, &ctx),
65            EngineAction::Stop(crate::Termination::ExceededMaxIterations)
66        ))
67    }
68
69    #[test]
70    fn max_iteration_policy_does_not_terminate_when_iter_is_less_than_limit() {
71        let mut stack = PolicyStack::<f64>::new().add(MaxIterationPolicy::new(100));
72
73        let batch: EventBatch<f64> = EventBatch::new().add(Progress::Complete);
74        let ctx = EngineContext {
75            iter: 99,
76            ..Default::default()
77        };
78
79        assert!(matches!(stack.decide(&batch, &ctx), EngineAction::Continue))
80    }
81}