1use std::fmt;
13use std::sync::atomic::AtomicBool;
14use std::time::Duration;
15
16use solverforge_config::{PhaseConfig, SolverConfig};
17use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
18use solverforge_core::score::{ParseableScore, Score};
19use solverforge_scoring::{ConstraintSet, Director, ScoreDirector};
20use tokio::sync::mpsc;
21use tracing::info;
22
23use crate::builder::basic_selector::BasicLeafSelector;
24use crate::builder::{
25 AcceptorBuilder, AnyAcceptor, BasicContext, BasicMoveSelectorBuilder, ForagerBuilder,
26};
27use crate::heuristic::r#move::EitherMove;
28use crate::heuristic::selector::decorator::UnionMoveSelector;
29use crate::heuristic::selector::decorator::VecUnionSelector;
30use crate::heuristic::selector::{
31 EitherChangeMoveSelector, EitherSwapMoveSelector, FromSolutionEntitySelector,
32 StaticTypedValueSelector,
33};
34use crate::phase::construction::{BestFitForager, ConstructionHeuristicPhase, QueuedEntityPlacer};
35use crate::phase::localsearch::{LocalSearchPhase, SimulatedAnnealingAcceptor};
36use crate::scope::BestSolutionCallback;
37use crate::scope::SolverScope;
38use crate::solver::Solver;
39use crate::termination::{
40 BestScoreTermination, OrTermination, StepCountTermination, Termination, TimeTermination,
41 UnimprovedStepCountTermination, UnimprovedTimeTermination,
42};
43
44const DEFAULT_TIME_LIMIT_SECS: u64 = 30;
46
47pub(crate) enum AnyBasicTermination<S: PlanningSolution, D: Director<S>> {
52 Default(OrTermination<(TimeTermination,), S, D>),
53 WithBestScore(OrTermination<(TimeTermination, BestScoreTermination<S::Score>), S, D>),
54 WithStepCount(OrTermination<(TimeTermination, StepCountTermination), S, D>),
55 WithUnimprovedStep(OrTermination<(TimeTermination, UnimprovedStepCountTermination<S>), S, D>),
56 WithUnimprovedTime(OrTermination<(TimeTermination, UnimprovedTimeTermination<S>), S, D>),
57}
58
59impl<S: PlanningSolution, D: Director<S>> fmt::Debug for AnyBasicTermination<S, D> {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::Default(_) => write!(f, "AnyBasicTermination::Default"),
63 Self::WithBestScore(_) => write!(f, "AnyBasicTermination::WithBestScore"),
64 Self::WithStepCount(_) => write!(f, "AnyBasicTermination::WithStepCount"),
65 Self::WithUnimprovedStep(_) => write!(f, "AnyBasicTermination::WithUnimprovedStep"),
66 Self::WithUnimprovedTime(_) => write!(f, "AnyBasicTermination::WithUnimprovedTime"),
67 }
68 }
69}
70
71impl<S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>> Termination<S, D, BestCb>
72 for AnyBasicTermination<S, D>
73where
74 S::Score: Score,
75{
76 fn is_terminated(&self, solver_scope: &SolverScope<S, D, BestCb>) -> bool {
77 match self {
78 Self::Default(t) => t.is_terminated(solver_scope),
79 Self::WithBestScore(t) => t.is_terminated(solver_scope),
80 Self::WithStepCount(t) => t.is_terminated(solver_scope),
81 Self::WithUnimprovedStep(t) => t.is_terminated(solver_scope),
82 Self::WithUnimprovedTime(t) => t.is_terminated(solver_scope),
83 }
84 }
85
86 fn install_inphase_limits(&self, solver_scope: &mut SolverScope<S, D, BestCb>) {
87 match self {
88 Self::Default(t) => t.install_inphase_limits(solver_scope),
89 Self::WithBestScore(t) => t.install_inphase_limits(solver_scope),
90 Self::WithStepCount(t) => t.install_inphase_limits(solver_scope),
91 Self::WithUnimprovedStep(t) => t.install_inphase_limits(solver_scope),
92 Self::WithUnimprovedTime(t) => t.install_inphase_limits(solver_scope),
93 }
94 }
95}
96
97type ConfigLocalSearch<S> = LocalSearchPhase<
99 S,
100 EitherMove<S, usize>,
101 VecUnionSelector<S, EitherMove<S, usize>, BasicLeafSelector<S>>,
102 AnyAcceptor<S>,
103 crate::builder::AnyForager<S>,
104>;
105
106type DefaultLocalSearch<S> = LocalSearchPhase<
108 S,
109 EitherMove<S, usize>,
110 UnionMoveSelector<
111 S,
112 EitherMove<S, usize>,
113 EitherChangeMoveSelector<
114 S,
115 usize,
116 FromSolutionEntitySelector,
117 StaticTypedValueSelector<S, usize>,
118 >,
119 EitherSwapMoveSelector<S, usize, FromSolutionEntitySelector, FromSolutionEntitySelector>,
120 >,
121 SimulatedAnnealingAcceptor,
122 crate::phase::localsearch::AcceptedCountForager<S>,
123>;
124
125enum BasicLocalSearch<S: PlanningSolution>
127where
128 S::Score: Score,
129{
130 Default(DefaultLocalSearch<S>),
131 Config(ConfigLocalSearch<S>),
132}
133
134#[allow(clippy::too_many_arguments)]
162pub fn run_solver<S, C>(
163 mut solution: S,
164 finalize_fn: fn(&mut S),
165 constraints_fn: fn() -> C,
166 get_variable: fn(&S, usize) -> Option<usize>,
167 set_variable: fn(&mut S, usize, Option<usize>),
168 value_count: fn(&S) -> usize,
169 entity_count_fn: fn(&S) -> usize,
170 descriptor: fn() -> SolutionDescriptor,
171 entity_count_by_descriptor: fn(&S, usize) -> usize,
172 terminate: Option<&AtomicBool>,
173 sender: mpsc::UnboundedSender<(S, S::Score)>,
174 variable_field: &'static str,
175 descriptor_index: usize,
176) -> S
177where
178 S: PlanningSolution,
179 S::Score: Score + ParseableScore,
180 C: ConstraintSet<S, S::Score>,
181{
182 finalize_fn(&mut solution);
183
184 let config = SolverConfig::load("solver.toml").unwrap_or_default();
185 let n_entities = entity_count_fn(&solution);
186 let n_values = value_count(&solution);
187
188 info!(
189 event = "solve_start",
190 entity_count = n_entities,
191 value_count = n_values,
192 );
193
194 let constraints = constraints_fn();
196 let director = ScoreDirector::with_descriptor(
197 solution,
198 constraints,
199 descriptor(),
200 entity_count_by_descriptor,
201 );
202
203 if n_entities == 0 || n_values == 0 {
205 let mut solver_scope = SolverScope::new(director);
206 solver_scope.start_solving();
207 let score = solver_scope.calculate_score();
208 info!(event = "solve_end", score = %score);
209 let solution = solver_scope.take_best_or_working_solution();
210 let _ = sender.send((solution.clone(), score));
211 return solution;
212 }
213
214 let values: Vec<usize> = (0..n_values).collect();
216 let entity_selector = FromSolutionEntitySelector::new(0);
217 let value_selector = StaticTypedValueSelector::new(values.clone());
218 let placer = QueuedEntityPlacer::new(
219 entity_selector,
220 value_selector,
221 get_variable,
222 set_variable,
223 0,
224 variable_field,
225 );
226 let construction = ConstructionHeuristicPhase::new(placer, BestFitForager::new());
227
228 let local_search = build_local_search::<S>(
230 &config,
231 get_variable,
232 set_variable,
233 values,
234 variable_field,
235 descriptor_index,
236 );
237
238 let term_config = config.termination.as_ref();
240 let time_limit = term_config
241 .and_then(|c| c.time_limit())
242 .unwrap_or(Duration::from_secs(DEFAULT_TIME_LIMIT_SECS));
243 let time = TimeTermination::new(time_limit);
244
245 let best_score_target: Option<S::Score> = term_config
246 .and_then(|c| c.best_score_limit.as_ref())
247 .and_then(|s| S::Score::parse(s).ok());
248
249 let termination: AnyBasicTermination<S, ScoreDirector<S, C>> =
250 if let Some(target) = best_score_target {
251 AnyBasicTermination::WithBestScore(OrTermination::new((
252 time,
253 BestScoreTermination::new(target),
254 )))
255 } else if let Some(step_limit) = term_config.and_then(|c| c.step_count_limit) {
256 AnyBasicTermination::WithStepCount(OrTermination::new((
257 time,
258 StepCountTermination::new(step_limit),
259 )))
260 } else if let Some(unimproved_step_limit) =
261 term_config.and_then(|c| c.unimproved_step_count_limit)
262 {
263 AnyBasicTermination::WithUnimprovedStep(OrTermination::new((
264 time,
265 UnimprovedStepCountTermination::<S>::new(unimproved_step_limit),
266 )))
267 } else if let Some(unimproved_time) = term_config.and_then(|c| c.unimproved_time_limit()) {
268 AnyBasicTermination::WithUnimprovedTime(OrTermination::new((
269 time,
270 UnimprovedTimeTermination::<S>::new(unimproved_time),
271 )))
272 } else {
273 AnyBasicTermination::Default(OrTermination::new((time,)))
274 };
275
276 let callback_sender = sender.clone();
277 let callback = move |solution: &S| {
278 let score = solution.score().unwrap_or_default();
279 let _ = callback_sender.send((solution.clone(), score));
280 };
281
282 let result = match local_search {
284 BasicLocalSearch::Default(ls) => {
285 let solver = Solver::new(((), construction, ls))
286 .with_termination(termination)
287 .with_time_limit(time_limit)
288 .with_best_solution_callback(callback);
289 if let Some(flag) = terminate {
290 solver.with_terminate(flag).solve(director)
291 } else {
292 solver.solve(director)
293 }
294 }
295 BasicLocalSearch::Config(ls) => {
296 let solver = Solver::new(((), construction, ls))
297 .with_termination(termination)
298 .with_time_limit(time_limit)
299 .with_best_solution_callback(callback);
300 if let Some(flag) = terminate {
301 solver.with_terminate(flag).solve(director)
302 } else {
303 solver.solve(director)
304 }
305 }
306 };
307
308 let final_score = result.solution.score().unwrap_or_default();
310 let _ = sender.send((result.solution.clone(), final_score));
311
312 info!(
313 event = "solve_end",
314 score = %final_score,
315 steps = result.stats.step_count,
316 moves_evaluated = result.stats.moves_evaluated,
317 );
318 result.solution
319}
320
321fn build_local_search<S>(
323 config: &SolverConfig,
324 get_variable: fn(&S, usize) -> Option<usize>,
325 set_variable: fn(&mut S, usize, Option<usize>),
326 values: Vec<usize>,
327 variable_field: &'static str,
328 descriptor_index: usize,
329) -> BasicLocalSearch<S>
330where
331 S: PlanningSolution,
332 S::Score: Score,
333{
334 let ls_config = config.phases.iter().find_map(|p| {
336 if let PhaseConfig::LocalSearch(ls) = p {
337 Some(ls)
338 } else {
339 None
340 }
341 });
342
343 let Some(ls) = ls_config else {
344 let change_selector = EitherChangeMoveSelector::simple(
346 get_variable,
347 set_variable,
348 descriptor_index,
349 variable_field,
350 values,
351 );
352 let swap_selector = EitherSwapMoveSelector::simple(
353 get_variable,
354 set_variable,
355 descriptor_index,
356 variable_field,
357 );
358 let move_selector = UnionMoveSelector::new(change_selector, swap_selector);
359 let acceptor = SimulatedAnnealingAcceptor::default();
360 let forager = crate::phase::localsearch::AcceptedCountForager::new(1);
361 return BasicLocalSearch::Default(LocalSearchPhase::new(
362 move_selector,
363 acceptor,
364 forager,
365 None,
366 ));
367 };
368
369 let acceptor = ls
371 .acceptor
372 .as_ref()
373 .map(|ac| AcceptorBuilder::build::<S>(ac))
374 .unwrap_or_else(|| AnyAcceptor::SimulatedAnnealing(SimulatedAnnealingAcceptor::default()));
375
376 let forager = ForagerBuilder::build::<S>(ls.forager.as_ref());
377
378 let ctx = BasicContext {
379 get_variable,
380 set_variable,
381 values,
382 descriptor_index,
383 variable_field,
384 };
385
386 let move_selector = BasicMoveSelectorBuilder::build(ls.move_selector.as_ref(), &ctx);
387
388 BasicLocalSearch::Config(LocalSearchPhase::new(
389 move_selector,
390 acceptor,
391 forager,
392 None,
393 ))
394}