1use std::fmt;
14use std::marker::PhantomData;
15use std::sync::atomic::AtomicBool;
16use std::time::Duration;
17
18use solverforge_config::{ConstructionHeuristicType, PhaseConfig, SolverConfig};
19use solverforge_core::domain::PlanningSolution;
20use solverforge_core::score::{ParseableScore, Score};
21use solverforge_scoring::{ConstraintSet, ScoreDirector};
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::{
32 ListCheapestInsertionPhase, ListClarkeWrightPhase, ListKOptPhase, ListRegretInsertionPhase,
33};
34use crate::phase::localsearch::{AcceptedCountForager, LateAcceptanceAcceptor, LocalSearchPhase};
35use crate::problem_spec::ProblemSpec;
36use crate::run::AnyTermination;
37use crate::solver::{SolveResult, Solver};
38
39type ConfigListLocalSearch<S, V, DM, IDM> = LocalSearchPhase<
41 S,
42 ListMoveImpl<S, V>,
43 VecUnionSelector<S, ListMoveImpl<S, V>, ListLeafSelector<S, V, DM, IDM>>,
44 AnyAcceptor<S>,
45 AnyForager<S>,
46>;
47
48type DefaultListLocalSearch<S, V, DM, IDM> = LocalSearchPhase<
50 S,
51 ListMoveImpl<S, V>,
52 VecUnionSelector<S, ListMoveImpl<S, V>, ListLeafSelector<S, V, DM, IDM>>,
53 LateAcceptanceAcceptor<S>,
54 AcceptedCountForager<S>,
55>;
56
57#[allow(clippy::large_enum_variant)]
60enum ListLocalSearch<S, V, DM, IDM>
61where
62 S: PlanningSolution,
63 V: Clone + PartialEq + Send + Sync + fmt::Debug + 'static,
64 DM: CrossEntityDistanceMeter<S>,
65 IDM: CrossEntityDistanceMeter<S> + 'static,
66{
67 Default(DefaultListLocalSearch<S, V, DM, IDM>),
68 Config(ConfigListLocalSearch<S, V, DM, IDM>),
69}
70
71enum ListConstruction<S, V>
73where
74 S: PlanningSolution,
75 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + 'static,
76{
77 CheapestInsertion(ListCheapestInsertionPhase<S, V>),
78 RegretInsertion(ListRegretInsertionPhase<S, V>),
79 ClarkeWright(ListClarkeWrightPhase<S, V>),
80 KOpt(ListKOptPhase<S, V>),
81}
82
83pub struct ListSpec<S, V, DM, IDM> {
88 pub list_len: fn(&S, usize) -> usize,
90 pub list_remove: fn(&mut S, usize, usize) -> Option<V>,
91 pub list_insert: fn(&mut S, usize, usize, V),
92 pub list_get: fn(&S, usize, usize) -> Option<V>,
93 pub list_set: fn(&mut S, usize, usize, V),
94 pub list_reverse: fn(&mut S, usize, usize, usize),
95 pub sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
96 pub sublist_insert: fn(&mut S, usize, usize, Vec<V>),
97 pub ruin_remove: fn(&mut S, usize, usize) -> V,
98 pub ruin_insert: fn(&mut S, usize, usize, V),
99 pub element_count: fn(&S) -> usize,
101 pub get_assigned: fn(&S) -> Vec<V>,
102 pub entity_count: fn(&S) -> usize,
103 pub list_remove_for_construction: fn(&mut S, usize, usize) -> V,
104 pub index_to_element: fn(&S, usize) -> V,
105 pub cross_distance_meter: DM,
107 pub intra_distance_meter: IDM,
108 pub depot_fn: Option<fn(&S) -> usize>,
110 pub distance_fn: Option<fn(&S, usize, usize) -> i64>,
111 pub element_load_fn: Option<fn(&S, usize) -> i64>,
112 pub capacity_fn: Option<fn(&S) -> i64>,
113 pub assign_route_fn: Option<fn(&mut S, usize, Vec<V>)>,
114 pub merge_feasible_fn: Option<fn(&S, &[usize]) -> bool>,
115 pub k_opt_get_route: Option<fn(&S, usize) -> Vec<usize>>,
117 pub k_opt_set_route: Option<fn(&mut S, usize, Vec<usize>)>,
118 pub k_opt_depot_fn: Option<fn(&S, usize) -> usize>,
119 pub k_opt_distance_fn: Option<fn(&S, usize, usize) -> i64>,
120 pub k_opt_feasible_fn: Option<fn(&S, usize, &[usize]) -> bool>,
121 pub variable_name: &'static str,
123 pub descriptor_index: usize,
124 pub _phantom: PhantomData<fn() -> S>,
125}
126
127impl<S, V, C, DM, IDM> ProblemSpec<S, C> for ListSpec<S, V, DM, IDM>
128where
129 S: PlanningSolution,
130 S::Score: Score + ParseableScore,
131 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
132 C: ConstraintSet<S, S::Score>,
133 DM: CrossEntityDistanceMeter<S> + Clone,
134 IDM: CrossEntityDistanceMeter<S> + Clone + 'static,
135{
136 fn is_trivial(&self, solution: &S) -> bool {
137 (self.entity_count)(solution) == 0
138 }
139
140 fn default_time_limit_secs(&self) -> u64 {
141 60
142 }
143
144 fn log_scale(&self, solution: &S) {
145 info!(
146 event = "solve_start",
147 entity_count = (self.entity_count)(solution),
148 element_count = (self.element_count)(solution),
149 );
150 }
151
152 fn build_and_solve(
153 self,
154 director: ScoreDirector<S, C>,
155 config: &SolverConfig,
156 time_limit: Duration,
157 termination: AnyTermination<S, ScoreDirector<S, C>>,
158 terminate: Option<&AtomicBool>,
159 callback: impl Fn(&S) + Send + Sync,
160 ) -> SolveResult<S> {
161 let construction = build_list_construction::<S, V>(
162 config,
163 self.element_count,
164 self.get_assigned,
165 self.entity_count,
166 self.list_len,
167 self.list_insert,
168 self.list_remove_for_construction,
169 self.index_to_element,
170 self.descriptor_index,
171 self.depot_fn,
172 self.distance_fn,
173 self.element_load_fn,
174 self.capacity_fn,
175 self.assign_route_fn,
176 self.merge_feasible_fn,
177 self.k_opt_get_route,
178 self.k_opt_set_route,
179 self.k_opt_depot_fn,
180 self.k_opt_distance_fn,
181 self.k_opt_feasible_fn,
182 );
183
184 let ctx = ListContext::new(
185 self.list_len,
186 self.list_remove,
187 self.list_insert,
188 self.list_get,
189 self.list_set,
190 self.list_reverse,
191 self.sublist_remove,
192 self.sublist_insert,
193 self.ruin_remove,
194 self.ruin_insert,
195 self.entity_count,
196 self.cross_distance_meter,
197 self.intra_distance_meter,
198 self.variable_name,
199 self.descriptor_index,
200 );
201
202 let local_search = build_list_local_search::<S, V, DM, IDM>(config, &ctx);
203
204 match (construction, local_search) {
205 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Default(ls)) => {
206 let solver = Solver::new(((), c, ls))
207 .with_termination(termination)
208 .with_time_limit(time_limit)
209 .with_best_solution_callback(callback);
210 if let Some(flag) = terminate {
211 solver.with_terminate(flag).solve(director)
212 } else {
213 solver.solve(director)
214 }
215 }
216 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Config(ls)) => {
217 let solver = Solver::new(((), c, ls))
218 .with_termination(termination)
219 .with_time_limit(time_limit)
220 .with_best_solution_callback(callback);
221 if let Some(flag) = terminate {
222 solver.with_terminate(flag).solve(director)
223 } else {
224 solver.solve(director)
225 }
226 }
227 (ListConstruction::RegretInsertion(c), ListLocalSearch::Default(ls)) => {
228 let solver = Solver::new(((), c, ls))
229 .with_termination(termination)
230 .with_time_limit(time_limit)
231 .with_best_solution_callback(callback);
232 if let Some(flag) = terminate {
233 solver.with_terminate(flag).solve(director)
234 } else {
235 solver.solve(director)
236 }
237 }
238 (ListConstruction::RegretInsertion(c), ListLocalSearch::Config(ls)) => {
239 let solver = Solver::new(((), c, ls))
240 .with_termination(termination)
241 .with_time_limit(time_limit)
242 .with_best_solution_callback(callback);
243 if let Some(flag) = terminate {
244 solver.with_terminate(flag).solve(director)
245 } else {
246 solver.solve(director)
247 }
248 }
249 (ListConstruction::ClarkeWright(c), ListLocalSearch::Default(ls)) => {
250 let solver = Solver::new(((), c, ls))
251 .with_termination(termination)
252 .with_time_limit(time_limit)
253 .with_best_solution_callback(callback);
254 if let Some(flag) = terminate {
255 solver.with_terminate(flag).solve(director)
256 } else {
257 solver.solve(director)
258 }
259 }
260 (ListConstruction::ClarkeWright(c), ListLocalSearch::Config(ls)) => {
261 let solver = Solver::new(((), c, ls))
262 .with_termination(termination)
263 .with_time_limit(time_limit)
264 .with_best_solution_callback(callback);
265 if let Some(flag) = terminate {
266 solver.with_terminate(flag).solve(director)
267 } else {
268 solver.solve(director)
269 }
270 }
271 (ListConstruction::KOpt(c), ListLocalSearch::Default(ls)) => {
272 let solver = Solver::new(((), c, ls))
273 .with_termination(termination)
274 .with_time_limit(time_limit)
275 .with_best_solution_callback(callback);
276 if let Some(flag) = terminate {
277 solver.with_terminate(flag).solve(director)
278 } else {
279 solver.solve(director)
280 }
281 }
282 (ListConstruction::KOpt(c), ListLocalSearch::Config(ls)) => {
283 let solver = Solver::new(((), c, ls))
284 .with_termination(termination)
285 .with_time_limit(time_limit)
286 .with_best_solution_callback(callback);
287 if let Some(flag) = terminate {
288 solver.with_terminate(flag).solve(director)
289 } else {
290 solver.solve(director)
291 }
292 }
293 }
294 }
295}
296
297#[allow(clippy::too_many_arguments)]
299fn build_list_construction<S, V>(
300 config: &SolverConfig,
301 element_count: fn(&S) -> usize,
302 get_assigned: fn(&S) -> Vec<V>,
303 entity_count: fn(&S) -> usize,
304 list_len: fn(&S, usize) -> usize,
305 list_insert: fn(&mut S, usize, usize, V),
306 list_remove: fn(&mut S, usize, usize) -> V,
307 index_to_element: fn(&S, usize) -> V,
308 descriptor_index: usize,
309 depot_fn: Option<fn(&S) -> usize>,
310 distance_fn: Option<fn(&S, usize, usize) -> i64>,
311 element_load_fn: Option<fn(&S, usize) -> i64>,
312 capacity_fn: Option<fn(&S) -> i64>,
313 assign_route_fn: Option<fn(&mut S, usize, Vec<V>)>,
314 merge_feasible_fn: Option<fn(&S, &[usize]) -> bool>,
315 k_opt_get_route: Option<fn(&S, usize) -> Vec<usize>>,
316 k_opt_set_route: Option<fn(&mut S, usize, Vec<usize>)>,
317 k_opt_depot_fn: Option<fn(&S, usize) -> usize>,
318 k_opt_distance_fn: Option<fn(&S, usize, usize) -> i64>,
319 k_opt_feasible_fn: Option<fn(&S, usize, &[usize]) -> bool>,
320) -> ListConstruction<S, V>
321where
322 S: PlanningSolution,
323 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
324{
325 let (ch_type, k) = config
326 .phases
327 .iter()
328 .find_map(|p| {
329 if let PhaseConfig::ConstructionHeuristic(ch) = p {
330 Some((ch.construction_heuristic_type, ch.k))
331 } else {
332 None
333 }
334 })
335 .unwrap_or((ConstructionHeuristicType::ListCheapestInsertion, 2));
336
337 match ch_type {
338 ConstructionHeuristicType::ListRegretInsertion => {
339 ListConstruction::RegretInsertion(ListRegretInsertionPhase::new(
340 element_count,
341 get_assigned,
342 entity_count,
343 list_len,
344 list_insert,
345 list_remove,
346 index_to_element,
347 descriptor_index,
348 ))
349 }
350 ConstructionHeuristicType::ListClarkeWright => {
351 match (
352 depot_fn,
353 distance_fn,
354 element_load_fn,
355 capacity_fn,
356 assign_route_fn,
357 ) {
358 (Some(depot), Some(dist), Some(load), Some(cap), Some(assign)) => {
359 ListConstruction::ClarkeWright(ListClarkeWrightPhase::new(
360 element_count,
361 get_assigned,
362 entity_count,
363 assign,
364 index_to_element,
365 depot,
366 dist,
367 load,
368 cap,
369 merge_feasible_fn,
370 descriptor_index,
371 ))
372 }
373 _ => {
374 tracing::warn!(
375 "ListClarkeWright selected but one or more required fields \
376 (depot_fn, distance_fn, element_load_fn, capacity_fn, assign_route_fn) \
377 are None — falling back to ListCheapestInsertion"
378 );
379 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
380 element_count,
381 get_assigned,
382 entity_count,
383 list_len,
384 list_insert,
385 list_remove,
386 index_to_element,
387 descriptor_index,
388 ))
389 }
390 }
391 }
392 ConstructionHeuristicType::ListKOpt => {
393 match (
394 k_opt_get_route,
395 k_opt_set_route,
396 k_opt_depot_fn,
397 k_opt_distance_fn,
398 ) {
399 (Some(get_route), Some(set_route), Some(ko_depot), Some(ko_dist)) => {
400 ListConstruction::KOpt(ListKOptPhase::new(
401 k,
402 entity_count,
403 get_route,
404 set_route,
405 ko_depot,
406 ko_dist,
407 k_opt_feasible_fn,
408 descriptor_index,
409 ))
410 }
411 _ => {
412 tracing::warn!(
413 "ListKOpt selected but one or more required fields \
414 (k_opt_get_route, k_opt_set_route, k_opt_depot_fn, k_opt_distance_fn) \
415 are None — falling back to ListCheapestInsertion"
416 );
417 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
418 element_count,
419 get_assigned,
420 entity_count,
421 list_len,
422 list_insert,
423 list_remove,
424 index_to_element,
425 descriptor_index,
426 ))
427 }
428 }
429 }
430 _ => {
431 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
433 element_count,
434 get_assigned,
435 entity_count,
436 list_len,
437 list_insert,
438 list_remove,
439 index_to_element,
440 descriptor_index,
441 ))
442 }
443 }
444}
445
446fn build_list_local_search<S, V, DM, IDM>(
448 config: &SolverConfig,
449 ctx: &ListContext<S, V, DM, IDM>,
450) -> ListLocalSearch<S, V, DM, IDM>
451where
452 S: PlanningSolution,
453 S::Score: Score,
454 V: Clone + Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
455 DM: CrossEntityDistanceMeter<S> + Clone,
456 IDM: CrossEntityDistanceMeter<S> + Clone,
457{
458 let ls_config = config.phases.iter().find_map(|p| {
459 if let PhaseConfig::LocalSearch(ls) = p {
460 Some(ls)
461 } else {
462 None
463 }
464 });
465
466 let Some(ls) = ls_config else {
467 let acceptor = LateAcceptanceAcceptor::<S>::new(400);
469 let forager = AcceptedCountForager::new(4);
470 let move_selector = ListMoveSelectorBuilder::build(None, ctx);
471 return ListLocalSearch::Default(LocalSearchPhase::new(
472 move_selector,
473 acceptor,
474 forager,
475 None,
476 ));
477 };
478
479 let acceptor = ls
480 .acceptor
481 .as_ref()
482 .map(|ac| AcceptorBuilder::build::<S>(ac))
483 .unwrap_or_else(|| AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(400)));
484
485 let forager = ForagerBuilder::build::<S>(ls.forager.as_ref());
486 let move_selector = ListMoveSelectorBuilder::build(ls.move_selector.as_ref(), ctx);
487
488 ListLocalSearch::Config(LocalSearchPhase::new(
489 move_selector,
490 acceptor,
491 forager,
492 None,
493 ))
494}