1use std::fmt;
14use std::sync::atomic::AtomicBool;
15use std::time::Duration;
16
17use solverforge_config::{ConstructionHeuristicType, PhaseConfig, SolverConfig};
18use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
19use solverforge_core::score::{ParseableScore, Score};
20use solverforge_scoring::{ConstraintSet, Director, ScoreDirector};
21use tokio::sync::mpsc;
22use tracing::info;
23
24use crate::builder::list_selector::ListLeafSelector;
25use crate::builder::{
26 AcceptorBuilder, AnyAcceptor, AnyForager, ForagerBuilder, ListContext, ListMoveSelectorBuilder,
27};
28use crate::heuristic::r#move::ListMoveImpl;
29use crate::heuristic::selector::decorator::VecUnionSelector;
30use crate::heuristic::selector::nearby_list_change::CrossEntityDistanceMeter;
31use crate::manager::{ListCheapestInsertionPhase, ListRegretInsertionPhase};
32use crate::phase::localsearch::{AcceptedCountForager, LateAcceptanceAcceptor, LocalSearchPhase};
33use crate::scope::BestSolutionCallback;
34use crate::scope::SolverScope;
35use crate::solver::Solver;
36use crate::termination::{
37 BestScoreTermination, OrTermination, StepCountTermination, Termination, TimeTermination,
38 UnimprovedStepCountTermination, UnimprovedTimeTermination,
39};
40
41const DEFAULT_TIME_LIMIT_SECS: u64 = 60;
43
44pub(crate) enum AnyListTermination<S: PlanningSolution, D: Director<S>> {
46 Default(OrTermination<(TimeTermination,), S, D>),
47 WithBestScore(OrTermination<(TimeTermination, BestScoreTermination<S::Score>), S, D>),
48 WithStepCount(OrTermination<(TimeTermination, StepCountTermination), S, D>),
49 WithUnimprovedStep(OrTermination<(TimeTermination, UnimprovedStepCountTermination<S>), S, D>),
50 WithUnimprovedTime(OrTermination<(TimeTermination, UnimprovedTimeTermination<S>), S, D>),
51}
52
53impl<S: PlanningSolution, D: Director<S>> fmt::Debug for AnyListTermination<S, D> {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 match self {
56 Self::Default(_) => write!(f, "AnyListTermination::Default"),
57 Self::WithBestScore(_) => write!(f, "AnyListTermination::WithBestScore"),
58 Self::WithStepCount(_) => write!(f, "AnyListTermination::WithStepCount"),
59 Self::WithUnimprovedStep(_) => write!(f, "AnyListTermination::WithUnimprovedStep"),
60 Self::WithUnimprovedTime(_) => write!(f, "AnyListTermination::WithUnimprovedTime"),
61 }
62 }
63}
64
65impl<S: PlanningSolution, D: Director<S>, BestCb: BestSolutionCallback<S>> Termination<S, D, BestCb>
66 for AnyListTermination<S, D>
67where
68 S::Score: Score,
69{
70 fn is_terminated(&self, solver_scope: &SolverScope<S, D, BestCb>) -> bool {
71 match self {
72 Self::Default(t) => t.is_terminated(solver_scope),
73 Self::WithBestScore(t) => t.is_terminated(solver_scope),
74 Self::WithStepCount(t) => t.is_terminated(solver_scope),
75 Self::WithUnimprovedStep(t) => t.is_terminated(solver_scope),
76 Self::WithUnimprovedTime(t) => t.is_terminated(solver_scope),
77 }
78 }
79
80 fn install_inphase_limits(&self, solver_scope: &mut SolverScope<S, D, BestCb>) {
81 match self {
82 Self::Default(t) => t.install_inphase_limits(solver_scope),
83 Self::WithBestScore(t) => t.install_inphase_limits(solver_scope),
84 Self::WithStepCount(t) => t.install_inphase_limits(solver_scope),
85 Self::WithUnimprovedStep(t) => t.install_inphase_limits(solver_scope),
86 Self::WithUnimprovedTime(t) => t.install_inphase_limits(solver_scope),
87 }
88 }
89}
90
91type ConfigListLocalSearch<S, V, DM, IDM> = LocalSearchPhase<
93 S,
94 ListMoveImpl<S, V>,
95 VecUnionSelector<S, ListMoveImpl<S, V>, ListLeafSelector<S, V, DM, IDM>>,
96 AnyAcceptor<S>,
97 AnyForager<S>,
98>;
99
100type DefaultListLocalSearch<S, V, DM, IDM> = LocalSearchPhase<
102 S,
103 ListMoveImpl<S, V>,
104 VecUnionSelector<S, ListMoveImpl<S, V>, ListLeafSelector<S, V, DM, IDM>>,
105 LateAcceptanceAcceptor<S>,
106 AcceptedCountForager<S>,
107>;
108
109#[allow(clippy::large_enum_variant)]
112enum ListLocalSearch<S, V, DM, IDM>
113where
114 S: PlanningSolution,
115 V: Clone + PartialEq + Send + Sync + fmt::Debug + 'static,
116 DM: CrossEntityDistanceMeter<S>,
117 IDM: CrossEntityDistanceMeter<S>,
118{
119 Default(DefaultListLocalSearch<S, V, DM, IDM>),
120 Config(ConfigListLocalSearch<S, V, DM, IDM>),
121}
122
123enum ListConstruction<S, V>
125where
126 S: PlanningSolution,
127 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + 'static,
128{
129 CheapestInsertion(ListCheapestInsertionPhase<S, V>),
130 RegretInsertion(ListRegretInsertionPhase<S, V>),
131}
132
133#[allow(clippy::too_many_arguments)]
156pub fn run_list_solver<S, V, C, DM, IDM>(
157 mut solution: S,
158 finalize_fn: fn(&mut S),
159 constraints_fn: fn() -> C,
160 list_len: fn(&S, usize) -> usize,
162 list_remove: fn(&mut S, usize, usize) -> Option<V>,
163 list_insert: fn(&mut S, usize, usize, V),
164 list_get: fn(&S, usize, usize) -> Option<V>,
165 list_set: fn(&mut S, usize, usize, V),
166 list_reverse: fn(&mut S, usize, usize, usize),
167 sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
168 sublist_insert: fn(&mut S, usize, usize, Vec<V>),
169 ruin_remove: fn(&mut S, usize, usize) -> V,
170 ruin_insert: fn(&mut S, usize, usize, V),
171 element_count: fn(&S) -> usize,
173 get_assigned: fn(&S) -> Vec<V>,
174 entity_count: fn(&S) -> usize,
175 list_remove_for_construction: fn(&mut S, usize, usize) -> V,
176 index_to_element: fn(&S, usize) -> V,
177 cross_distance_meter: DM,
179 intra_distance_meter: IDM,
180 variable_name: &'static str,
182 descriptor_index: usize,
183 descriptor: fn() -> SolutionDescriptor,
185 entity_count_by_descriptor: fn(&S, usize) -> usize,
186 terminate: Option<&AtomicBool>,
187 sender: mpsc::UnboundedSender<(S, S::Score)>,
188) -> S
189where
190 S: PlanningSolution,
191 S::Score: Score + ParseableScore,
192 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
193 C: ConstraintSet<S, S::Score>,
194 DM: CrossEntityDistanceMeter<S> + Clone,
195 IDM: CrossEntityDistanceMeter<S> + Clone,
196{
197 finalize_fn(&mut solution);
198
199 let config = SolverConfig::load("solver.toml").unwrap_or_default();
200 let n_entities = entity_count(&solution);
201 let n_elements = element_count(&solution);
202
203 info!(
204 event = "solve_start",
205 entity_count = n_entities,
206 element_count = n_elements,
207 );
208
209 let constraints = constraints_fn();
210 let director = ScoreDirector::with_descriptor(
211 solution,
212 constraints,
213 descriptor(),
214 entity_count_by_descriptor,
215 );
216
217 if n_entities == 0 {
218 let mut solver_scope = SolverScope::new(director);
219 solver_scope.start_solving();
220 let score = solver_scope.calculate_score();
221 info!(event = "solve_end", score = %score);
222 let solution = solver_scope.take_best_or_working_solution();
223 let _ = sender.send((solution.clone(), score));
224 return solution;
225 }
226
227 let construction = build_list_construction::<S, V>(
229 &config,
230 element_count,
231 get_assigned,
232 entity_count,
233 list_len,
234 list_insert,
235 list_remove_for_construction,
236 index_to_element,
237 descriptor_index,
238 );
239
240 let ctx = ListContext::new(
241 list_len,
242 list_remove,
243 list_insert,
244 list_get,
245 list_set,
246 list_reverse,
247 sublist_remove,
248 sublist_insert,
249 ruin_remove,
250 ruin_insert,
251 entity_count,
252 cross_distance_meter,
253 intra_distance_meter,
254 variable_name,
255 descriptor_index,
256 );
257
258 let local_search = build_list_local_search::<S, V, DM, IDM>(&config, &ctx);
260
261 let term_config = config.termination.as_ref();
263 let time_limit = term_config
264 .and_then(|c| c.time_limit())
265 .unwrap_or(Duration::from_secs(DEFAULT_TIME_LIMIT_SECS));
266 let time = TimeTermination::new(time_limit);
267
268 let best_score_target: Option<S::Score> = term_config
269 .and_then(|c| c.best_score_limit.as_ref())
270 .and_then(|s| S::Score::parse(s).ok());
271
272 let termination: AnyListTermination<S, ScoreDirector<S, C>> =
273 if let Some(target) = best_score_target {
274 AnyListTermination::WithBestScore(OrTermination::new((
275 time,
276 BestScoreTermination::new(target),
277 )))
278 } else if let Some(step_limit) = term_config.and_then(|c| c.step_count_limit) {
279 AnyListTermination::WithStepCount(OrTermination::new((
280 time,
281 StepCountTermination::new(step_limit),
282 )))
283 } else if let Some(unimproved_step_limit) =
284 term_config.and_then(|c| c.unimproved_step_count_limit)
285 {
286 AnyListTermination::WithUnimprovedStep(OrTermination::new((
287 time,
288 UnimprovedStepCountTermination::<S>::new(unimproved_step_limit),
289 )))
290 } else if let Some(unimproved_time) = term_config.and_then(|c| c.unimproved_time_limit()) {
291 AnyListTermination::WithUnimprovedTime(OrTermination::new((
292 time,
293 UnimprovedTimeTermination::<S>::new(unimproved_time),
294 )))
295 } else {
296 AnyListTermination::Default(OrTermination::new((time,)))
297 };
298
299 let callback_sender = sender.clone();
300 let callback = move |solution: &S| {
301 let score = solution.score().unwrap_or_default();
302 let _ = callback_sender.send((solution.clone(), score));
303 };
304
305 let result = match (construction, local_search) {
307 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Default(ls)) => {
308 let solver = Solver::new(((), c, ls))
309 .with_termination(termination)
310 .with_time_limit(time_limit)
311 .with_best_solution_callback(callback);
312 if let Some(flag) = terminate {
313 solver.with_terminate(flag).solve(director)
314 } else {
315 solver.solve(director)
316 }
317 }
318 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Config(ls)) => {
319 let solver = Solver::new(((), c, ls))
320 .with_termination(termination)
321 .with_time_limit(time_limit)
322 .with_best_solution_callback(callback);
323 if let Some(flag) = terminate {
324 solver.with_terminate(flag).solve(director)
325 } else {
326 solver.solve(director)
327 }
328 }
329 (ListConstruction::RegretInsertion(c), ListLocalSearch::Default(ls)) => {
330 let solver = Solver::new(((), c, ls))
331 .with_termination(termination)
332 .with_time_limit(time_limit)
333 .with_best_solution_callback(callback);
334 if let Some(flag) = terminate {
335 solver.with_terminate(flag).solve(director)
336 } else {
337 solver.solve(director)
338 }
339 }
340 (ListConstruction::RegretInsertion(c), ListLocalSearch::Config(ls)) => {
341 let solver = Solver::new(((), c, ls))
342 .with_termination(termination)
343 .with_time_limit(time_limit)
344 .with_best_solution_callback(callback);
345 if let Some(flag) = terminate {
346 solver.with_terminate(flag).solve(director)
347 } else {
348 solver.solve(director)
349 }
350 }
351 };
352
353 let final_score = result.solution.score().unwrap_or_default();
354 let _ = sender.send((result.solution.clone(), final_score));
355
356 info!(
357 event = "solve_end",
358 score = %final_score,
359 steps = result.stats.step_count,
360 moves_evaluated = result.stats.moves_evaluated,
361 );
362 result.solution
363}
364
365#[allow(clippy::too_many_arguments)]
367fn build_list_construction<S, V>(
368 config: &SolverConfig,
369 element_count: fn(&S) -> usize,
370 get_assigned: fn(&S) -> Vec<V>,
371 entity_count: fn(&S) -> usize,
372 list_len: fn(&S, usize) -> usize,
373 list_insert: fn(&mut S, usize, usize, V),
374 list_remove: fn(&mut S, usize, usize) -> V,
375 index_to_element: fn(&S, usize) -> V,
376 descriptor_index: usize,
377) -> ListConstruction<S, V>
378where
379 S: PlanningSolution,
380 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
381{
382 let ch_type = config
383 .phases
384 .iter()
385 .find_map(|p| {
386 if let PhaseConfig::ConstructionHeuristic(ch) = p {
387 Some(ch.construction_heuristic_type)
388 } else {
389 None
390 }
391 })
392 .unwrap_or(ConstructionHeuristicType::ListCheapestInsertion);
393
394 match ch_type {
395 ConstructionHeuristicType::ListRegretInsertion => {
396 ListConstruction::RegretInsertion(ListRegretInsertionPhase::new(
397 element_count,
398 get_assigned,
399 entity_count,
400 list_len,
401 list_insert,
402 list_remove,
403 index_to_element,
404 descriptor_index,
405 ))
406 }
407 _ => {
408 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
410 element_count,
411 get_assigned,
412 entity_count,
413 list_len,
414 list_insert,
415 list_remove,
416 index_to_element,
417 descriptor_index,
418 ))
419 }
420 }
421}
422
423fn build_list_local_search<S, V, DM, IDM>(
425 config: &SolverConfig,
426 ctx: &ListContext<S, V, DM, IDM>,
427) -> ListLocalSearch<S, V, DM, IDM>
428where
429 S: PlanningSolution,
430 S::Score: Score,
431 V: Clone + Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
432 DM: CrossEntityDistanceMeter<S> + Clone,
433 IDM: CrossEntityDistanceMeter<S> + Clone,
434{
435 let ls_config = config.phases.iter().find_map(|p| {
436 if let PhaseConfig::LocalSearch(ls) = p {
437 Some(ls)
438 } else {
439 None
440 }
441 });
442
443 let Some(ls) = ls_config else {
444 let acceptor = LateAcceptanceAcceptor::<S>::new(400);
446 let forager = AcceptedCountForager::new(4);
447 let move_selector = ListMoveSelectorBuilder::build(None, ctx);
448 return ListLocalSearch::Default(LocalSearchPhase::new(
449 move_selector,
450 acceptor,
451 forager,
452 None,
453 ));
454 };
455
456 let acceptor = ls
457 .acceptor
458 .as_ref()
459 .map(|ac| AcceptorBuilder::build::<S>(ac))
460 .unwrap_or_else(|| AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(400)));
461
462 let forager = ForagerBuilder::build::<S>(ls.forager.as_ref());
463 let move_selector = ListMoveSelectorBuilder::build(ls.move_selector.as_ref(), ctx);
464
465 ListLocalSearch::Config(LocalSearchPhase::new(
466 move_selector,
467 acceptor,
468 forager,
469 None,
470 ))
471}