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