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::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::SimpleScoreDirector;
24/// use solverforge_core::domain::PlanningSolution;
25/// use solverforge_core::score::SimpleScore;
26/// use std::time::Duration;
27///
28/// #[derive(Clone)]
29/// struct MySolution;
30/// impl PlanningSolution for MySolution {
31/// type Score = SimpleScore;
32/// fn score(&self) -> Option<Self::Score> { None }
33/// fn set_score(&mut self, _: Option<Self::Score>) {}
34/// }
35///
36/// type MyDirector = SimpleScoreDirector<MySolution, fn(&MySolution) -> SimpleScore>;
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::SimpleScoreDirector;
68/// use solverforge_core::score::SimpleScore;
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 = SimpleScore;
76/// fn score(&self) -> Option<Self::Score> { None }
77/// fn set_score(&mut self, _: Option<Self::Score>) {}
78/// }
79///
80/// type MyDirector = SimpleScoreDirector<MySolution, fn(&MySolution) -> SimpleScore>;
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: ScoreDirector<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: ScoreDirector<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);