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::manager::{SolverRuntime, SolverTerminalReason};
13use crate::phase::Phase;
14use crate::scope::ProgressCallback;
15use crate::scope::SolverScope;
16use crate::stats::{
17 CandidateTraceExecutionPolicy, CandidateTraceHeader, CandidateTracePhasePlan,
18 QualifiedCandidateTraceRunProvenance, SolverStats,
19};
20use crate::termination::Termination;
21
22#[derive(Debug)]
29pub struct SolveResult<S: PlanningSolution> {
30 pub solution: S,
32 pub current_score: Option<S::Score>,
34 pub best_score: S::Score,
36 pub terminal_reason: SolverTerminalReason,
38 pub stats: SolverStats,
40}
41
42impl<S: PlanningSolution> SolveResult<S> {
43 pub fn solution(&self) -> &S {
44 &self.solution
45 }
46
47 pub fn into_solution(self) -> S {
48 self.solution
49 }
50
51 pub fn current_score(&self) -> Option<&S::Score> {
52 self.current_score.as_ref()
53 }
54
55 pub fn best_score(&self) -> &S::Score {
56 &self.best_score
57 }
58
59 pub fn terminal_reason(&self) -> SolverTerminalReason {
60 self.terminal_reason
61 }
62
63 pub fn stats(&self) -> &SolverStats {
64 &self.stats
65 }
66
67 pub fn step_count(&self) -> u64 {
68 self.stats.step_count
69 }
70
71 pub fn moves_evaluated(&self) -> u64 {
72 self.stats.moves_evaluated
73 }
74
75 pub fn moves_accepted(&self) -> u64 {
76 self.stats.moves_accepted
77 }
78}
79
80pub struct Solver<'t, P, T, S: PlanningSolution, D, ProgressCb = ()> {
93 phases: P,
94 termination: T,
95 terminate: Option<&'t AtomicBool>,
96 runtime: Option<SolverRuntime<S>>,
97 config: Option<SolverConfig>,
98 candidate_trace_execution_policy: Option<CandidateTraceExecutionPolicy>,
103 candidate_trace_qualified_run_provenance: Option<QualifiedCandidateTraceRunProvenance>,
105 time_limit: Option<Duration>,
106 progress_callback: ProgressCb,
108 _phantom: PhantomData<fn(S, D)>,
109}
110
111impl<P: Debug, T: Debug, S: PlanningSolution, D, ProgressCb> Debug
112 for Solver<'_, P, T, S, D, ProgressCb>
113{
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("Solver")
116 .field("phases", &self.phases)
117 .field("termination", &self.termination)
118 .finish()
119 }
120}
121
122impl<P, S, D> Solver<'static, P, NoTermination, S, D, ()>
123where
124 S: PlanningSolution,
125{
126 pub fn new(phases: P) -> Self {
127 Solver {
128 phases,
129 termination: NoTermination,
130 terminate: None,
131 runtime: None,
132 config: None,
133 candidate_trace_execution_policy: None,
134 candidate_trace_qualified_run_provenance: None,
135 time_limit: None,
136 progress_callback: (),
137 _phantom: PhantomData,
138 }
139 }
140
141 pub fn with_termination<T>(self, termination: T) -> Solver<'static, P, Option<T>, S, D, ()> {
142 Solver {
143 phases: self.phases,
144 termination: Some(termination),
145 terminate: self.terminate,
146 runtime: self.runtime,
147 config: self.config,
148 candidate_trace_execution_policy: self.candidate_trace_execution_policy,
149 candidate_trace_qualified_run_provenance: self.candidate_trace_qualified_run_provenance,
150 time_limit: self.time_limit,
151 progress_callback: self.progress_callback,
152 _phantom: PhantomData,
153 }
154 }
155}
156
157impl<'t, P, T, S, D, ProgressCb> Solver<'t, P, T, S, D, ProgressCb>
158where
159 S: PlanningSolution,
160{
161 pub fn with_terminate(self, terminate: &'t AtomicBool) -> Solver<'t, P, T, S, D, ProgressCb> {
165 Solver {
166 phases: self.phases,
167 termination: self.termination,
168 terminate: Some(terminate),
169 runtime: self.runtime,
170 config: self.config,
171 candidate_trace_execution_policy: self.candidate_trace_execution_policy,
172 candidate_trace_qualified_run_provenance: self.candidate_trace_qualified_run_provenance,
173 time_limit: self.time_limit,
174 progress_callback: self.progress_callback,
175 _phantom: PhantomData,
176 }
177 }
178
179 pub fn with_time_limit(mut self, limit: Duration) -> Self {
180 self.time_limit = Some(limit);
181 self
182 }
183
184 pub fn with_config(mut self, config: SolverConfig) -> Self {
185 self.config = Some(config);
186 self
187 }
188
189 pub(crate) fn with_candidate_trace_execution_policy(
194 mut self,
195 execution_policy: CandidateTraceExecutionPolicy,
196 ) -> Self {
197 self.candidate_trace_execution_policy = Some(execution_policy);
198 self
199 }
200
201 pub fn with_qualified_candidate_trace_run_provenance(
206 mut self,
207 provenance: QualifiedCandidateTraceRunProvenance,
208 ) -> Self {
209 self.candidate_trace_qualified_run_provenance = Some(provenance);
210 self
211 }
212
213 pub fn with_progress_callback<F>(self, callback: F) -> Solver<'t, P, T, S, D, F> {
217 Solver {
218 phases: self.phases,
219 termination: self.termination,
220 terminate: self.terminate,
221 runtime: self.runtime,
222 config: self.config,
223 candidate_trace_execution_policy: self.candidate_trace_execution_policy,
224 candidate_trace_qualified_run_provenance: self.candidate_trace_qualified_run_provenance,
225 time_limit: self.time_limit,
226 progress_callback: callback,
227 _phantom: PhantomData,
228 }
229 }
230
231 pub fn config(&self) -> Option<&SolverConfig> {
232 self.config.as_ref()
233 }
234
235 pub(crate) fn with_runtime(mut self, runtime: SolverRuntime<S>) -> Self {
236 self.runtime = Some(runtime);
237 self
238 }
239}
240
241#[derive(Debug, Clone, Copy, Default)]
243pub struct NoTermination;
244
245pub trait MaybeTermination<
247 S: PlanningSolution,
248 D: Director<S>,
249 ProgressCb: ProgressCallback<S> = (),
250>: Send
251{
252 fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D, ProgressCb>) -> bool;
254
255 fn install_inphase_limits(&self, _solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {}
264}
265
266impl<S, D, ProgressCb, T> MaybeTermination<S, D, ProgressCb> for Option<T>
267where
268 S: PlanningSolution,
269 D: Director<S>,
270 ProgressCb: ProgressCallback<S>,
271 T: Termination<S, D, ProgressCb>,
272{
273 fn should_terminate(&self, solver_scope: &SolverScope<'_, S, D, ProgressCb>) -> bool {
274 match self {
275 Some(t) => t.is_terminated(solver_scope),
276 None => false,
277 }
278 }
279
280 fn install_inphase_limits(&self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
281 if let Some(t) = self {
282 t.install_inphase_limits(solver_scope);
283 }
284 }
285}
286
287impl<S, D, ProgressCb> MaybeTermination<S, D, ProgressCb> for NoTermination
288where
289 S: PlanningSolution,
290 D: Director<S>,
291 ProgressCb: ProgressCallback<S>,
292{
293 fn should_terminate(&self, _solver_scope: &SolverScope<'_, S, D, ProgressCb>) -> bool {
294 false
295 }
296
297 }
299
300impl<S, D, ProgressCb> Termination<S, D, ProgressCb> for NoTermination
301where
302 S: PlanningSolution,
303 D: Director<S>,
304 ProgressCb: ProgressCallback<S>,
305{
306 fn is_terminated(&self, _solver_scope: &SolverScope<'_, S, D, ProgressCb>) -> bool {
307 false
308 }
309}
310
311macro_rules! impl_solver {
312 ($($idx:tt: $P:ident),+) => {
313 impl<'t, S, D, T, ProgressCb, $($P),+> Solver<'t, ($($P,)+), T, S, D, ProgressCb>
314 where
315 S: PlanningSolution,
316 D: Director<S>,
317 T: MaybeTermination<S, D, ProgressCb>,
318 ProgressCb: ProgressCallback<S>,
319 $($P: Phase<S, D, ProgressCb>,)+
320 {
321 pub fn solve(self, score_director: D) -> SolveResult<S> {
326 let Solver {
327 mut phases,
328 termination,
329 terminate,
330 runtime,
331 config,
332 candidate_trace_execution_policy,
333 candidate_trace_qualified_run_provenance,
334 time_limit,
335 progress_callback,
336 ..
337 } = self;
338
339 let mut solver_scope = SolverScope::new_with_callback(
340 score_director,
341 progress_callback,
342 terminate,
343 runtime,
344 );
345 if let Some(trace_config) = config.as_ref().and_then(|config| config.candidate_trace) {
346 let phase_children = vec![$(phases.$idx.candidate_trace_plan(),)+];
347 let resolved_phase_plan = CandidateTracePhasePlan::known(
348 "solverforge.solver",
349 [("top_level_phase_count", phase_children.len().to_string())],
350 phase_children,
351 );
352 let configured_input = config
353 .as_ref()
354 .expect("candidate trace configuration requires SolverConfig")
355 .canonical_toml();
356 let execution_policy = candidate_trace_execution_policy.unwrap_or_else(|| {
357 CandidateTraceExecutionPolicy::opaque_with_attributes(
358 "solverforge.execution_policy.direct_generic_termination",
359 [(
360 "solver_with_time_limit_ns",
361 time_limit.map_or_else(
362 || "none".to_string(),
363 |limit| limit.as_nanos().to_string(),
364 ),
365 )],
366 )
367 });
368 let header = match candidate_trace_qualified_run_provenance {
369 Some(provenance) => CandidateTraceHeader::new_qualified(
370 configured_input,
371 execution_policy,
372 resolved_phase_plan,
373 provenance,
374 ),
375 None => CandidateTraceHeader::new(
376 configured_input,
377 execution_policy,
378 resolved_phase_plan,
379 None,
380 ),
381 };
382 solver_scope.enable_candidate_trace(header, trace_config.max_entries.get());
383 }
384 if let Some(environment_mode) = config.as_ref().map(|cfg| cfg.environment_mode) {
385 solver_scope = solver_scope.with_environment_mode(environment_mode);
386 }
387 if let Some(seed) = config.as_ref().and_then(|cfg| cfg.random_seed) {
388 solver_scope = solver_scope.with_seed(seed);
389 }
390 if let Some(limit) = time_limit {
391 solver_scope.set_time_limit(limit);
392 }
393 solver_scope.initialize_working_solution_as_best();
394 solver_scope.report_best_solution();
395 solver_scope.pause_if_requested();
396
397 termination.install_inphase_limits(&mut solver_scope);
400
401 $(
403 solver_scope.pause_if_requested();
404 if !check_termination(&termination, &mut solver_scope) {
405 tracing::debug!(
406 "Starting phase {} ({})",
407 $idx,
408 phases.$idx.phase_type_name()
409 );
410 phases.$idx.solve(&mut solver_scope);
411 solver_scope.pause_if_requested();
412 tracing::debug!(
413 "Finished phase {} ({}) with score {:?}",
414 $idx,
415 phases.$idx.phase_type_name(),
416 solver_scope.best_score()
417 );
418 }
419 )+
420
421 $(
427 phases.$idx.on_solver_terminal(&mut solver_scope);
428 solver_scope.pause_if_requested();
429 let _ = check_termination(&termination, &mut solver_scope);
430 )+
431
432 let (solution, current_score, best_score, stats, terminal_reason) =
434 solver_scope.take_solution_and_stats();
435 SolveResult {
436 solution,
437 current_score,
438 best_score,
439 terminal_reason,
440 stats,
441 }
442 }
443 }
444 };
445}
446
447fn check_termination<S, D, ProgressCb, T>(
448 termination: &T,
449 solver_scope: &mut SolverScope<'_, S, D, ProgressCb>,
450) -> bool
451where
452 S: PlanningSolution,
453 D: Director<S>,
454 ProgressCb: ProgressCallback<S>,
455 T: MaybeTermination<S, D, ProgressCb>,
456{
457 if solver_scope.is_terminate_early() {
458 solver_scope.mark_cancelled();
459 return true;
460 }
461 if termination.should_terminate(solver_scope) {
462 solver_scope.mark_terminated_by_config();
463 true
464 } else {
465 false
466 }
467}
468
469macro_rules! impl_solver_with_director {
470 ($($idx:tt: $P:ident),+) => {
471 impl<'t, S, T, ProgressCb, $($P),+> Solver<'t, ($($P,)+), T, S, (), ProgressCb>
472 where
473 S: PlanningSolution,
474 T: Send,
475 ProgressCb: Send + Sync,
476 {
477 pub fn solve_with_director<D>(self, director: D) -> SolveResult<S>
479 where
480 D: Director<S>,
481 ProgressCb: ProgressCallback<S>,
482 T: MaybeTermination<S, D, ProgressCb>,
483 $($P: Phase<S, D, ProgressCb>,)+
484 {
485 let solver: Solver<'t, ($($P,)+), T, S, D, ProgressCb> = Solver {
486 phases: self.phases,
487 termination: self.termination,
488 terminate: self.terminate,
489 runtime: self.runtime,
490 config: self.config,
491 candidate_trace_execution_policy: self.candidate_trace_execution_policy,
492 candidate_trace_qualified_run_provenance: self.candidate_trace_qualified_run_provenance,
493 time_limit: self.time_limit,
494 progress_callback: self.progress_callback,
495 _phantom: PhantomData,
496 };
497 solver.solve(director)
498 }
499 }
500 };
501}
502
503impl_solver_with_director!(0: P0);
504impl_solver_with_director!(0: P0, 1: P1);
505impl_solver_with_director!(0: P0, 1: P1, 2: P2);
506impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3);
507impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
508impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
509impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
510impl_solver_with_director!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);
511
512impl_solver!(0: P0);
513impl_solver!(0: P0, 1: P1);
514impl_solver!(0: P0, 1: P1, 2: P2);
515impl_solver!(0: P0, 1: P1, 2: P2, 3: P3);
516impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4);
517impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5);
518impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6);
519impl_solver!(0: P0, 1: P1, 2: P2, 3: P3, 4: P4, 5: P5, 6: P6, 7: P7);
520
521#[cfg(test)]
522#[path = "solver_tests.rs"]
523mod tests;