solverforge_solver/termination/
composite.rs

1//! Composite termination conditions (AND/OR).
2//!
3//! Uses macro-generated tuple implementations for zero-erasure architecture.
4
5use std::fmt::Debug;
6use std::marker::PhantomData;
7
8use solverforge_core::domain::PlanningSolution;
9use solverforge_scoring::ScoreDirector;
10
11use super::Termination;
12use crate::scope::SolverScope;
13
14/// Combines multiple terminations with OR logic.
15///
16/// Terminates when ANY of the child terminations triggers.
17///
18/// # Example
19///
20/// ```
21/// use solverforge_solver::termination::{OrTermination, TimeTermination, StepCountTermination};
22/// use solverforge_scoring::SimpleScoreDirector;
23/// use solverforge_core::domain::PlanningSolution;
24/// use solverforge_core::score::SimpleScore;
25/// use std::time::Duration;
26///
27/// #[derive(Clone)]
28/// struct MySolution;
29/// impl PlanningSolution for MySolution {
30///     type Score = SimpleScore;
31///     fn score(&self) -> Option<Self::Score> { None }
32///     fn set_score(&mut self, _: Option<Self::Score>) {}
33/// }
34///
35/// type MyDirector = SimpleScoreDirector<MySolution, fn(&MySolution) -> SimpleScore>;
36///
37/// // Terminate after 30 seconds OR 1000 steps
38/// let term: OrTermination<_, MySolution, MyDirector> = OrTermination::new((
39///     TimeTermination::seconds(30),
40///     StepCountTermination::new(1000),
41/// ));
42/// ```
43#[derive(Clone)]
44pub struct OrTermination<T, S, D>(pub T, PhantomData<fn(S, D)>);
45
46impl<T: Debug, S, D> Debug for OrTermination<T, S, D> {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        f.debug_tuple("OrTermination").field(&self.0).finish()
49    }
50}
51
52impl<T, S, D> OrTermination<T, S, D> {
53    pub fn new(terminations: T) -> Self {
54        Self(terminations, PhantomData)
55    }
56}
57
58/// Combines multiple terminations with AND logic.
59///
60/// All terminations must agree before solving terminates.
61///
62/// # Example
63///
64/// ```
65/// use solverforge_solver::termination::{AndTermination, TimeTermination, StepCountTermination};
66/// use solverforge_scoring::SimpleScoreDirector;
67/// use solverforge_core::score::SimpleScore;
68/// use solverforge_core::domain::PlanningSolution;
69/// use std::time::Duration;
70///
71/// #[derive(Clone)]
72/// struct MySolution;
73/// impl PlanningSolution for MySolution {
74///     type Score = SimpleScore;
75///     fn score(&self) -> Option<Self::Score> { None }
76///     fn set_score(&mut self, _: Option<Self::Score>) {}
77/// }
78///
79/// type MyDirector = SimpleScoreDirector<MySolution, fn(&MySolution) -> SimpleScore>;
80///
81/// // Terminate when both conditions are met
82/// let term: AndTermination<_, MySolution, MyDirector> = AndTermination::new((
83///     TimeTermination::seconds(10),
84///     StepCountTermination::new(100),
85/// ));
86/// ```
87#[derive(Clone)]
88pub struct AndTermination<T, S, D>(pub T, PhantomData<fn(S, D)>);
89
90impl<T: Debug, S, D> Debug for AndTermination<T, S, D> {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_tuple("AndTermination").field(&self.0).finish()
93    }
94}
95
96impl<T, S, D> AndTermination<T, S, D> {
97    pub fn new(terminations: T) -> Self {
98        Self(terminations, PhantomData)
99    }
100}
101
102macro_rules! impl_composite_termination {
103    ($($idx:tt: $T:ident),+) => {
104        impl<S, D, $($T),+> Termination<S, D> for OrTermination<($($T,)+), S, D>
105        where
106            S: PlanningSolution,
107            D: ScoreDirector<S>,
108            $($T: Termination<S, D>,)+
109        {
110            fn is_terminated(&self, solver_scope: &SolverScope<S, D>) -> bool {
111                $(
112                    if self.0.$idx.is_terminated(solver_scope) {
113                        return true;
114                    }
115                )+
116                false
117            }
118        }
119
120        impl<S, D, $($T),+> Termination<S, D> for AndTermination<($($T,)+), S, D>
121        where
122            S: PlanningSolution,
123            D: ScoreDirector<S>,
124            $($T: Termination<S, D>,)+
125        {
126            fn is_terminated(&self, solver_scope: &SolverScope<S, D>) -> bool {
127                $(
128                    if !self.0.$idx.is_terminated(solver_scope) {
129                        return false;
130                    }
131                )+
132                true
133            }
134        }
135    };
136}
137
138impl_composite_termination!(0: T0);
139impl_composite_termination!(0: T0, 1: T1);
140impl_composite_termination!(0: T0, 1: T1, 2: T2);
141impl_composite_termination!(0: T0, 1: T1, 2: T2, 3: T3);
142impl_composite_termination!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4);
143impl_composite_termination!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5);
144impl_composite_termination!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6);
145impl_composite_termination!(0: T0, 1: T1, 2: T2, 3: T3, 4: T4, 5: T5, 6: T6, 7: T7);