1use 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::Director;
11
12use crate::phase::Phase;
13use crate::scope::BestSolutionCallback;
14use crate::scope::SolverScope;
15use crate::stats::SolverStats;
16use crate::termination::Termination;
17
18#[derive(Debug)]
25pub struct SolveResult<S: PlanningSolution> {
26 pub solution: S,
28 pub stats: SolverStats,
30}
31
32impl<S: PlanningSolution> SolveResult<S> {
33 pub fn solution(&self) -> &S {
34 &self.solution
35 }
36
37 pub fn into_solution(self) -> S {
38 self.solution
39 }
40
41 pub fn stats(&self) -> &SolverStats {
42 &self.stats
43 }
44
45 pub fn step_count(&self) -> u64 {
46 self.stats.step_count
47 }
48
49 pub fn moves_evaluated(&self) -> u64 {
50 self.stats.moves_evaluated
51 }
52
53 pub fn moves_accepted(&self) -> u64 {
54 self.stats.moves_accepted
55 }
56}
57
58pub struct Solver<'t, P, T, S, D, BestCb = ()> {
71 phases: P,
72 termination: T,
73 terminate: Option<&'t AtomicBool>,
74 config: Option<SolverConfig>,
75 time_limit: Option<Duration>,
76 best_solution_callback: BestCb,
78 _phantom: PhantomData<fn(S, D)>,
79}
80
81impl<P: Debug, T: Debug, S, D, BestCb> Debug for Solver<'_, P, T, S, D, BestCb> {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 f.debug_struct("Solver")
84 .field("phases", &self.phases)
85 .field("termination", &self.termination)
86 .finish()
87 }
88}
89
90impl<P, S, D> Solver<'static, P, NoTermination, S, D, ()>
91where
92 S: PlanningSolution,
93{
94 pub fn new(phases: P) -> Self {
95 Solver {
96 phases,
97 termination: NoTermination,
98 terminate: None,
99 config: None,
100 time_limit: None,
101 best_solution_callback: (),
102 _phantom: PhantomData,
103 }
104 }
105
106 pub fn with_termination<T>(self, termination: T) -> Solver<'static, P, Option<T>, S, D, ()> {
107 Solver {
108 phases: self.phases,
109 termination: Some(termination),
110 terminate: self.terminate,
111 config: self.config,
112 time_limit: self.time_limit,
113 best_solution_callback: self.best_solution_callback,
114 _phantom: PhantomData,
115 }
116 }
117}
118
119impl<'t, P, T, S, D, BestCb> Solver<'t, P, T, S, D, BestCb>
120where
121 S: PlanningSolution,
122{
123 pub fn with_terminate(self, terminate: &'t AtomicBool) -> Solver<'t, P, T, S, D, BestCb> {
127 Solver {
128 phases: self.phases,
129 termination: self.termination,
130 terminate: Some(terminate),
131 config: self.config,
132 time_limit: self.time_limit,
133 best_solution_callback: self.best_solution_callback,
134 _phantom: PhantomData,
135 }
136 }
137
138 pub fn with_time_limit(mut self, limit: Duration) -> Self {
139 self.time_limit = Some(limit);
140 self
141 }
142
143 pub fn with_config(mut self, config: SolverConfig) -> Self {
144 self.config = Some(config);
145 self
146 }
147
148 pub fn with_best_solution_callback<F: Fn(&S) + Send + Sync>(
152 self,
153 callback: F,
154 ) -> Solver<'t, P, T, S, D, F> {
155 Solver {
156 phases: self.phases,
157 termination: self.termination,
158 terminate: self.terminate,
159 config: self.config,
160 time_limit: self.time_limit,
161 best_solution_callback: callback,
162 _phantom: PhantomData,
163 }
164 }
165
166 pub fn config(&self) -> Option<&SolverConfig> {
167 self.config.as_ref()
168 }
169}
170
171#[derive(Debug, Clone, Copy, Default)]
173pub struct NoTermination;
174
175pub trait MaybeTermination<
177 S: PlanningSolution,
178 D: Director<S>,
179 BestCb: BestSolutionCallback<S> = (),
180>: Send
181{
182 fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D, BestCb>) -> bool;
184
185 fn install_inphase_limits(&self, _solver_scope: &mut SolverScope<'_, S, D, BestCb>) {}
194}
195
196impl<S, D, BestCb, T> MaybeTermination<S, D, BestCb> for Option<T>
197where
198 S: PlanningSolution,
199 D: Director<S>,
200 BestCb: BestSolutionCallback<S>,
201 T: Termination<S, D, BestCb>,
202{
203 fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D, BestCb>) -> bool {
204 match self {
205 Some(t) => t.is_terminated(solver_scope),
206 None => false,
207 }
208 }
209
210 fn install_inphase_limits(&self, solver_scope: &mut SolverScope<'_, S, D, BestCb>) {
211 if let Some(t) = self {
212 t.install_inphase_limits(solver_scope);
213 }
214 }
215}
216
217impl<S, D, BestCb> MaybeTermination<S, D, BestCb> for NoTermination
218where
219 S: PlanningSolution,
220 D: Director<S>,
221 BestCb: BestSolutionCallback<S>,
222{
223 fn should_terminate(&self, _solver_scope: &SolverScope<'_, S, D, BestCb>) -> bool {
224 false
225 }
226
227 }
229
230impl<S, D, BestCb> Termination<S, D, BestCb> for NoTermination
231where
232 S: PlanningSolution,
233 D: Director<S>,
234 BestCb: BestSolutionCallback<S>,
235{
236 fn is_terminated(&self, _solver_scope: &SolverScope<'_, S, D, BestCb>) -> bool {
237 false
238 }
239}
240
241macro_rules! impl_solver {
242 ($($idx:tt: $P:ident),+) => {
243 impl<'t, S, D, T, BestCb, $($P),+> Solver<'t, ($($P,)+), T, S, D, BestCb>
244 where
245 S: PlanningSolution,
246 D: Director<S>,
247 T: MaybeTermination<S, D, BestCb>,
248 BestCb: BestSolutionCallback<S>,
249 $($P: Phase<S, D, BestCb>,)+
250 {
251 pub fn solve(self, score_director: D) -> SolveResult<S> {
256 let Solver {
257 mut phases,
258 termination,
259 terminate,
260 time_limit,
261 best_solution_callback,
262 ..
263 } = self;
264
265 let mut solver_scope = SolverScope::new_with_callback(
266 score_director,
267 best_solution_callback,
268 terminate,
269 );
270 if let Some(limit) = time_limit {
271 solver_scope.set_time_limit(limit);
272 }
273 solver_scope.start_solving();
274
275 termination.install_inphase_limits(&mut solver_scope);
278
279 $(
281 if !check_termination(&termination, &solver_scope) {
282 tracing::debug!(
283 "Starting phase {} ({})",
284 $idx,
285 phases.$idx.phase_type_name()
286 );
287 phases.$idx.solve(&mut solver_scope);
288 tracing::debug!(
289 "Finished phase {} ({}) with score {:?}",
290 $idx,
291 phases.$idx.phase_type_name(),
292 solver_scope.best_score()
293 );
294 }
295 )+
296
297 let (solution, stats) = solver_scope.take_solution_and_stats();
299 SolveResult { solution, stats }
300 }
301 }
302 };
303}
304
305fn check_termination<S, D, BestCb, T>(
306 termination: &T,
307 solver_scope: &SolverScope<'_, S, D, BestCb>,
308) -> bool
309where
310 S: PlanningSolution,
311 D: Director<S>,
312 BestCb: BestSolutionCallback<S>,
313 T: MaybeTermination<S, D, BestCb>,
314{
315 if solver_scope.is_terminate_early() {
316 return true;
317 }
318 termination.should_terminate(solver_scope)
319}
320
321macro_rules! impl_solver_with_director {
322 ($($idx:tt: $P:ident),+) => {
323 impl<'t, S, T, BestCb, $($P),+> Solver<'t, ($($P,)+), T, S, (), BestCb>
324 where
325 S: PlanningSolution,
326 T: Send,
327 BestCb: Send + Sync,
328 {
329 pub fn solve_with_director<D>(self, director: D) -> SolveResult<S>
331 where
332 D: Director<S>,
333 BestCb: BestSolutionCallback<S>,
334 T: MaybeTermination<S, D, BestCb>,
335 $($P: Phase<S, D, BestCb>,)+
336 {
337 let solver: Solver<'t, ($($P,)+), T, S, D, BestCb> = Solver {
338 phases: self.phases,
339 termination: self.termination,
340 terminate: self.terminate,
341 config: self.config,
342 time_limit: self.time_limit,
343 best_solution_callback: self.best_solution_callback,
344 _phantom: PhantomData,
345 };
346 solver.solve(director)
347 }
348 }
349 };
350}
351
352impl_solver_with_director!(0: P0);
353impl_solver_with_director!(0: P0, 1: P1);
354impl_solver_with_director!(0: P0, 1: P1, 2: P2);
355impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3);
356impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
357impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
358impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
359impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);
360
361impl_solver!(0: P0);
362impl_solver!(0: P0, 1: P1);
363impl_solver!(0: P0, 1: P1, 2: P2);
364impl_solver!(0: P0, 1: P1, 2: P2, 3: P3);
365impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
366impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
367impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
368impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);