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