Skip to main content

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