1use std::fmt;
7use std::sync::atomic::AtomicBool;
8use std::time::Duration;
9
10use solverforge_config::SolverConfig;
11use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
12use solverforge_core::score::{ParseableScore, Score};
13use solverforge_scoring::{ConstraintSet, Director, ScoreDirector};
14use tokio::sync::mpsc;
15use tracing::info;
16
17use crate::problem_spec::ProblemSpec;
18use crate::scope::{BestSolutionCallback, SolverScope};
19use crate::termination::{
20 BestScoreTermination, OrTermination, StepCountTermination, Termination, TimeTermination,
21 UnimprovedStepCountTermination, UnimprovedTimeTermination,
22};
23
24pub enum AnyTermination<S: PlanningSolution, D: Director<S>> {
29 Default(OrTermination<(TimeTermination,), S, D>),
30 WithBestScore(OrTermination<(TimeTermination, BestScoreTermination<S::Score>), S, D>),
31 WithStepCount(OrTermination<(TimeTermination, StepCountTermination), S, D>),
32 WithUnimprovedStep(OrTermination<(TimeTermination, UnimprovedStepCountTermination<S>), S, D>),
33 WithUnimprovedTime(OrTermination<(TimeTermination, UnimprovedTimeTermination<S>), S, D>),
34}
35
36impl<S: PlanningSolution, D: Director<S>> fmt::Debug for AnyTermination<S, D> {
37 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38 match self {
39 Self::Default(_) => write!(f, "AnyTermination::Default"),
40 Self::WithBestScore(_) => write!(f, "AnyTermination::WithBestScore"),
41 Self::WithStepCount(_) => write!(f, "AnyTermination::WithStepCount"),
42 Self::WithUnimprovedStep(_) => write!(f, "AnyTermination::WithUnimprovedStep"),
43 Self::WithUnimprovedTime(_) => write!(f, "AnyTermination::WithUnimprovedTime"),
44 }
45 }
46}
47
48impl<S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>> Termination<S, D, BestCb>
49 for AnyTermination<S, D>
50where
51 S::Score: Score,
52{
53 fn is_terminated(&self, solver_scope: &SolverScope<S, D, BestCb>) -> bool {
54 match self {
55 Self::Default(t) => t.is_terminated(solver_scope),
56 Self::WithBestScore(t) => t.is_terminated(solver_scope),
57 Self::WithStepCount(t) => t.is_terminated(solver_scope),
58 Self::WithUnimprovedStep(t) => t.is_terminated(solver_scope),
59 Self::WithUnimprovedTime(t) => t.is_terminated(solver_scope),
60 }
61 }
62
63 fn install_inphase_limits(&self, solver_scope: &mut SolverScope<S, D, BestCb>) {
64 match self {
65 Self::Default(t) => t.install_inphase_limits(solver_scope),
66 Self::WithBestScore(t) => t.install_inphase_limits(solver_scope),
67 Self::WithStepCount(t) => t.install_inphase_limits(solver_scope),
68 Self::WithUnimprovedStep(t) => t.install_inphase_limits(solver_scope),
69 Self::WithUnimprovedTime(t) => t.install_inphase_limits(solver_scope),
70 }
71 }
72}
73
74pub fn build_termination<S, C>(
76 config: &SolverConfig,
77 default_secs: u64,
78) -> (AnyTermination<S, ScoreDirector<S, C>>, Duration)
79where
80 S: PlanningSolution,
81 S::Score: Score + ParseableScore,
82 C: ConstraintSet<S, S::Score>,
83{
84 let term_config = config.termination.as_ref();
85 let time_limit = term_config
86 .and_then(|c| c.time_limit())
87 .unwrap_or(Duration::from_secs(default_secs));
88 let time = TimeTermination::new(time_limit);
89
90 let best_score_target: Option<S::Score> = term_config
91 .and_then(|c| c.best_score_limit.as_ref())
92 .and_then(|s| S::Score::parse(s).ok());
93
94 let termination = if let Some(target) = best_score_target {
95 AnyTermination::WithBestScore(OrTermination::new((
96 time,
97 BestScoreTermination::new(target),
98 )))
99 } else if let Some(step_limit) = term_config.and_then(|c| c.step_count_limit) {
100 AnyTermination::WithStepCount(OrTermination::new((
101 time,
102 StepCountTermination::new(step_limit),
103 )))
104 } else if let Some(unimproved_step_limit) =
105 term_config.and_then(|c| c.unimproved_step_count_limit)
106 {
107 AnyTermination::WithUnimprovedStep(OrTermination::new((
108 time,
109 UnimprovedStepCountTermination::<S>::new(unimproved_step_limit),
110 )))
111 } else if let Some(unimproved_time) = term_config.and_then(|c| c.unimproved_time_limit()) {
112 AnyTermination::WithUnimprovedTime(OrTermination::new((
113 time,
114 UnimprovedTimeTermination::<S>::new(unimproved_time),
115 )))
116 } else {
117 AnyTermination::Default(OrTermination::new((time,)))
118 };
119
120 (termination, time_limit)
121}
122
123#[allow(clippy::too_many_arguments)]
130pub fn run_solver<S, C, Spec>(
131 mut solution: S,
132 finalize_fn: fn(&mut S),
133 constraints_fn: fn() -> C,
134 descriptor: fn() -> SolutionDescriptor,
135 entity_count_by_descriptor: fn(&S, usize) -> usize,
136 terminate: Option<&AtomicBool>,
137 sender: mpsc::UnboundedSender<(S, S::Score)>,
138 spec: Spec,
139) -> S
140where
141 S: PlanningSolution,
142 S::Score: Score + ParseableScore,
143 C: ConstraintSet<S, S::Score>,
144 Spec: ProblemSpec<S, C>,
145{
146 finalize_fn(&mut solution);
147
148 let config = SolverConfig::load("solver.toml").unwrap_or_default();
149
150 spec.log_scale(&solution);
151 let trivial = spec.is_trivial(&solution);
152
153 let constraints = constraints_fn();
154 let director = ScoreDirector::with_descriptor(
155 solution,
156 constraints,
157 descriptor(),
158 entity_count_by_descriptor,
159 );
160
161 if trivial {
162 let mut solver_scope = SolverScope::new(director);
163 solver_scope.start_solving();
164 let score = solver_scope.calculate_score();
165 info!(event = "solve_end", score = %score);
166 let solution = solver_scope.take_best_or_working_solution();
167 let _ = sender.send((solution.clone(), score));
168 return solution;
169 }
170
171 let (termination, time_limit) =
172 build_termination::<S, C>(&config, spec.default_time_limit_secs());
173
174 let callback_sender = sender.clone();
175 let callback = move |solution: &S| {
176 let score = solution.score().unwrap_or_default();
177 let _ = callback_sender.send((solution.clone(), score));
178 };
179
180 let result = spec.build_and_solve(
181 director,
182 &config,
183 time_limit,
184 termination,
185 terminate,
186 callback,
187 );
188
189 let final_score = result.solution.score().unwrap_or_default();
190 let _ = sender.send((result.solution.clone(), final_score));
191
192 info!(
193 event = "solve_end",
194 score = %final_score,
195 steps = result.stats.step_count,
196 moves_evaluated = result.stats.moves_evaluated,
197 );
198 result.solution
199}