solverforge_solver/
solver.rs

1//! Solver implementation.
2
3use std::fmt::Debug;
4use std::marker::PhantomData;
5use std::sync::atomic::AtomicBool;
6use std::time::Duration;
7
8use solverforge_config::SolverConfig;
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::ScoreDirector;
11
12use crate::phase::Phase;
13use crate::scope::SolverScope;
14use crate::termination::Termination;
15
16/// The main solver that optimizes planning solutions.
17///
18/// Uses macro-generated tuple implementations for phases, preserving
19/// concrete types through the entire pipeline (zero-erasure architecture).
20///
21/// # Type Parameters
22/// * `'t` - Lifetime of the termination flag reference
23/// * `P` - Tuple of phases to execute
24/// * `T` - Termination condition (use `Option<ConcreteTermination>`)
25/// * `S` - Solution type
26/// * `D` - Score director type
27pub struct Solver<'t, P, T, S, D> {
28    phases: P,
29    termination: T,
30    terminate: Option<&'t AtomicBool>,
31    config: Option<SolverConfig>,
32    time_limit: Option<Duration>,
33    _phantom: PhantomData<fn(S, D)>,
34}
35
36impl<P: Debug, T: Debug, S, D> Debug for Solver<'_, P, T, S, D> {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        f.debug_struct("Solver")
39            .field("phases", &self.phases)
40            .field("termination", &self.termination)
41            .finish()
42    }
43}
44
45impl<P, S, D> Solver<'static, P, NoTermination, S, D>
46where
47    S: PlanningSolution,
48{
49    /// Creates a new solver with the given phases tuple and no termination.
50    pub fn new(phases: P) -> Self {
51        Solver {
52            phases,
53            termination: NoTermination,
54            terminate: None,
55            config: None,
56            time_limit: None,
57            _phantom: PhantomData,
58        }
59    }
60
61    /// Sets the termination condition.
62    pub fn with_termination<T>(self, termination: T) -> Solver<'static, P, Option<T>, S, D> {
63        Solver {
64            phases: self.phases,
65            termination: Some(termination),
66            terminate: self.terminate,
67            config: self.config,
68            time_limit: self.time_limit,
69            _phantom: PhantomData,
70        }
71    }
72}
73
74impl<'t, P, T, S, D> Solver<'t, P, T, S, D>
75where
76    S: PlanningSolution,
77{
78    /// Sets the external termination flag.
79    ///
80    /// The solver will check this flag periodically and terminate early if set.
81    pub fn with_terminate(self, terminate: &AtomicBool) -> Solver<'_, P, T, S, D> {
82        Solver {
83            phases: self.phases,
84            termination: self.termination,
85            terminate: Some(terminate),
86            config: self.config,
87            time_limit: self.time_limit,
88            _phantom: PhantomData,
89        }
90    }
91
92    /// Sets the time limit for solving.
93    pub fn with_time_limit(mut self, limit: Duration) -> Self {
94        self.time_limit = Some(limit);
95        self
96    }
97
98    /// Sets configuration.
99    pub fn with_config(mut self, config: SolverConfig) -> Self {
100        self.config = Some(config);
101        self
102    }
103
104    /// Returns the configuration if set.
105    pub fn config(&self) -> Option<&SolverConfig> {
106        self.config.as_ref()
107    }
108}
109
110/// Marker type indicating no termination.
111#[derive(Debug, Clone, Copy, Default)]
112pub struct NoTermination;
113
114/// Marker trait for termination types that can be used in Solver.
115pub trait MaybeTermination<S: PlanningSolution, D: ScoreDirector<S>>: Send {
116    /// Checks if the solver should terminate.
117    fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D>) -> bool;
118}
119
120impl<S: PlanningSolution, D: ScoreDirector<S>, T: Termination<S, D>> MaybeTermination<S, D>
121    for Option<T>
122{
123    fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D>) -> bool {
124        match self {
125            Some(t) => t.is_terminated(solver_scope),
126            None => false,
127        }
128    }
129}
130
131impl<S: PlanningSolution, D: ScoreDirector<S>> MaybeTermination<S, D> for NoTermination {
132    fn should_terminate(&self, _solver_scope: &SolverScope<'_, S, D>) -> bool {
133        false
134    }
135}
136
137impl<S: PlanningSolution, D: ScoreDirector<S>> Termination<S, D> for NoTermination {
138    fn is_terminated(&self, _solver_scope: &SolverScope<'_, S, D>) -> bool {
139        false
140    }
141}
142
143macro_rules! impl_solver {
144    ($($idx:tt: $P:ident),+) => {
145        impl<'t, S, D, T, $($P),+> Solver<'t, ($($P,)+), T, S, D>
146        where
147            S: PlanningSolution,
148            D: ScoreDirector<S>,
149            T: MaybeTermination<S, D>,
150            $($P: Phase<S, D>,)+
151        {
152            /// Solves using the provided score director.
153            pub fn solve(&mut self, score_director: D) -> S {
154                let mut solver_scope = SolverScope::with_terminate(score_director, self.terminate);
155                if let Some(limit) = self.time_limit {
156                    solver_scope.set_time_limit(limit);
157                }
158                solver_scope.start_solving();
159
160                // Execute phases with termination checking
161                $(
162                    if !self.check_termination(&solver_scope) {
163                        tracing::debug!(
164                            "Starting phase {} ({})",
165                            $idx,
166                            self.phases.$idx.phase_type_name()
167                        );
168                        self.phases.$idx.solve(&mut solver_scope);
169                        tracing::debug!(
170                            "Finished phase {} ({}) with score {:?}",
171                            $idx,
172                            self.phases.$idx.phase_type_name(),
173                            solver_scope.best_score()
174                        );
175                    }
176                )+
177
178                solver_scope.take_best_or_working_solution()
179            }
180
181            fn check_termination(&self, solver_scope: &SolverScope<'_, S, D>) -> bool {
182                // Check external termination flag first
183                if solver_scope.is_terminate_early() {
184                    return true;
185                }
186                // Then check configured termination conditions
187                self.termination.should_terminate(solver_scope)
188            }
189        }
190    };
191}
192
193macro_rules! impl_solver_with_director {
194    ($($idx:tt: $P:ident),+) => {
195        impl<'t, S, T, $($P),+> Solver<'t, ($($P,)+), T, S, ()>
196        where
197            S: PlanningSolution,
198            T: Send,
199        {
200            /// Solves using a provided score director.
201            pub fn solve_with_director<D>(self, director: D) -> S
202            where
203                D: ScoreDirector<S>,
204                T: MaybeTermination<S, D>,
205                $($P: Phase<S, D>,)+
206            {
207                let mut solver: Solver<'t, ($($P,)+), T, S, D> = Solver {
208                    phases: self.phases,
209                    termination: self.termination,
210                    terminate: self.terminate,
211                    config: self.config,
212                    time_limit: self.time_limit,
213                    _phantom: PhantomData,
214                };
215                solver.solve(director)
216            }
217        }
218    };
219}
220
221impl_solver_with_director!(0: P0);
222impl_solver_with_director!(0: P0, 1: P1);
223impl_solver_with_director!(0: P0, 1: P1, 2: P2);
224impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3);
225impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
226impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
227impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
228impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);
229
230impl_solver!(0: P0);
231impl_solver!(0: P0, 1: P1);
232impl_solver!(0: P0, 1: P1, 2: P2);
233impl_solver!(0: P0, 1: P1, 2: P2, 3: P3);
234impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
235impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
236impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
237impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);