1use std::fmt::{self, Debug};
2use std::hash::Hash;
3use std::marker::PhantomData;
4
5use solverforge_config::{
6 ConstructionHeuristicConfig, ConstructionHeuristicType, PhaseConfig, SolverConfig,
7};
8use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
9use solverforge_core::score::{ParseableScore, Score};
10
11use crate::builder::{build_local_search, build_vnd, LocalSearch, ModelContext, Vnd};
12use crate::descriptor_standard::{
13 build_descriptor_construction, standard_target_matches, standard_work_remaining,
14};
15use crate::heuristic::selector::nearby_list_change::CrossEntityDistanceMeter;
16use crate::manager::{
17 ListCheapestInsertionPhase, ListClarkeWrightPhase, ListKOptPhase, ListRegretInsertionPhase,
18};
19use crate::phase::{sequence::PhaseSequence, Phase};
20use crate::scope::{PhaseScope, ProgressCallback, SolverScope, StepScope};
21
22#[cfg(test)]
23#[path = "runtime_tests.rs"]
24mod tests;
25
26#[cfg(test)]
27#[path = "list_solver_tests.rs"]
28mod list_tests;
29
30pub struct ListVariableMetadata<S, DM, IDM> {
31 pub cross_distance_meter: DM,
32 pub intra_distance_meter: IDM,
33 pub merge_feasible_fn: Option<fn(&S, &[usize]) -> bool>,
34 pub cw_depot_fn: Option<fn(&S) -> usize>,
35 pub cw_distance_fn: Option<fn(&S, usize, usize) -> i64>,
36 pub cw_element_load_fn: Option<fn(&S, usize) -> i64>,
37 pub cw_capacity_fn: Option<fn(&S) -> i64>,
38 pub cw_assign_route_fn: Option<fn(&mut S, usize, Vec<usize>)>,
39 pub k_opt_get_route: Option<fn(&S, usize) -> Vec<usize>>,
40 pub k_opt_set_route: Option<fn(&mut S, usize, Vec<usize>)>,
41 pub k_opt_depot_fn: Option<fn(&S, usize) -> usize>,
42 pub k_opt_distance_fn: Option<fn(&S, usize, usize) -> i64>,
43 pub k_opt_feasible_fn: Option<fn(&S, usize, &[usize]) -> bool>,
44 _phantom: PhantomData<fn() -> S>,
45}
46
47pub trait ListVariableEntity<S> {
48 type CrossDistanceMeter: CrossEntityDistanceMeter<S> + Clone + fmt::Debug;
49 type IntraDistanceMeter: CrossEntityDistanceMeter<S> + Clone + fmt::Debug + 'static;
50
51 const HAS_LIST_VARIABLE: bool;
52 const LIST_VARIABLE_NAME: &'static str;
53 const LIST_ELEMENT_SOURCE: Option<&'static str>;
54
55 fn list_field(entity: &Self) -> &[usize];
56 fn list_field_mut(entity: &mut Self) -> &mut Vec<usize>;
57 fn list_metadata() -> ListVariableMetadata<S, Self::CrossDistanceMeter, Self::IntraDistanceMeter>;
58}
59
60impl<S, DM, IDM> ListVariableMetadata<S, DM, IDM> {
61 #[allow(clippy::too_many_arguments)]
62 pub fn new(
63 cross_distance_meter: DM,
64 intra_distance_meter: IDM,
65 merge_feasible_fn: Option<fn(&S, &[usize]) -> bool>,
66 cw_depot_fn: Option<fn(&S) -> usize>,
67 cw_distance_fn: Option<fn(&S, usize, usize) -> i64>,
68 cw_element_load_fn: Option<fn(&S, usize) -> i64>,
69 cw_capacity_fn: Option<fn(&S) -> i64>,
70 cw_assign_route_fn: Option<fn(&mut S, usize, Vec<usize>)>,
71 k_opt_get_route: Option<fn(&S, usize) -> Vec<usize>>,
72 k_opt_set_route: Option<fn(&mut S, usize, Vec<usize>)>,
73 k_opt_depot_fn: Option<fn(&S, usize) -> usize>,
74 k_opt_distance_fn: Option<fn(&S, usize, usize) -> i64>,
75 k_opt_feasible_fn: Option<fn(&S, usize, &[usize]) -> bool>,
76 ) -> Self {
77 Self {
78 cross_distance_meter,
79 intra_distance_meter,
80 merge_feasible_fn,
81 cw_depot_fn,
82 cw_distance_fn,
83 cw_element_load_fn,
84 cw_capacity_fn,
85 cw_assign_route_fn,
86 k_opt_get_route,
87 k_opt_set_route,
88 k_opt_depot_fn,
89 k_opt_distance_fn,
90 k_opt_feasible_fn,
91 _phantom: PhantomData,
92 }
93 }
94}
95
96enum ListConstruction<S, V>
97where
98 S: PlanningSolution,
99 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + 'static,
100{
101 RoundRobin(ListRoundRobinPhase<S, V>),
102 CheapestInsertion(ListCheapestInsertionPhase<S, V>),
103 RegretInsertion(ListRegretInsertionPhase<S, V>),
104 ClarkeWright(ListClarkeWrightPhase<S, V>),
105 KOpt(ListKOptPhase<S, V>),
106}
107
108impl<S, V> Debug for ListConstruction<S, V>
109where
110 S: PlanningSolution,
111 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
112{
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::RoundRobin(phase) => write!(f, "ListConstruction::RoundRobin({phase:?})"),
116 Self::CheapestInsertion(phase) => {
117 write!(f, "ListConstruction::CheapestInsertion({phase:?})")
118 }
119 Self::RegretInsertion(phase) => {
120 write!(f, "ListConstruction::RegretInsertion({phase:?})")
121 }
122 Self::ClarkeWright(phase) => {
123 write!(f, "ListConstruction::ClarkeWright({phase:?})")
124 }
125 Self::KOpt(phase) => write!(f, "ListConstruction::KOpt({phase:?})"),
126 }
127 }
128}
129
130impl<S, V, D, BestCb> Phase<S, D, BestCb> for ListConstruction<S, V>
131where
132 S: PlanningSolution,
133 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
134 D: solverforge_scoring::Director<S>,
135 BestCb: crate::scope::ProgressCallback<S>,
136{
137 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, BestCb>) {
138 match self {
139 Self::RoundRobin(phase) => phase.solve(solver_scope),
140 Self::CheapestInsertion(phase) => phase.solve(solver_scope),
141 Self::RegretInsertion(phase) => phase.solve(solver_scope),
142 Self::ClarkeWright(phase) => phase.solve(solver_scope),
143 Self::KOpt(phase) => phase.solve(solver_scope),
144 }
145 }
146
147 fn phase_type_name(&self) -> &'static str {
148 "ListConstruction"
149 }
150}
151
152struct ListRoundRobinPhase<S, V>
153where
154 S: PlanningSolution,
155 V: Copy + PartialEq + Eq + Hash + Send + Sync + 'static,
156{
157 element_count: fn(&S) -> usize,
158 get_assigned: fn(&S) -> Vec<V>,
159 entity_count: fn(&S) -> usize,
160 list_len: fn(&S, usize) -> usize,
161 list_insert: fn(&mut S, usize, usize, V),
162 index_to_element: fn(&S, usize) -> V,
163 descriptor_index: usize,
164 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
165}
166
167impl<S, V> Debug for ListRoundRobinPhase<S, V>
168where
169 S: PlanningSolution,
170 V: Copy + PartialEq + Eq + Hash + Send + Sync + 'static,
171{
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 f.debug_struct("ListRoundRobinPhase").finish()
174 }
175}
176
177impl<S, V, D, ProgressCb> Phase<S, D, ProgressCb> for ListRoundRobinPhase<S, V>
178where
179 S: PlanningSolution,
180 V: Copy + PartialEq + Eq + Hash + Send + Sync + Debug + 'static,
181 D: solverforge_scoring::Director<S>,
182 ProgressCb: ProgressCallback<S>,
183{
184 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
185 let mut phase_scope = PhaseScope::new(solver_scope, 0);
186
187 let solution = phase_scope.score_director().working_solution();
188 let n_elements = (self.element_count)(solution);
189 let n_entities = (self.entity_count)(solution);
190
191 if n_entities == 0 || n_elements == 0 {
192 let _score = phase_scope.score_director_mut().calculate_score();
193 phase_scope.update_best_solution();
194 return;
195 }
196
197 let assigned: Vec<V> = (self.get_assigned)(phase_scope.score_director().working_solution());
198 if assigned.len() >= n_elements {
199 let _score = phase_scope.score_director_mut().calculate_score();
200 phase_scope.update_best_solution();
201 return;
202 }
203
204 let assigned_set: std::collections::HashSet<V> = assigned.into_iter().collect();
205 let mut entity_idx = 0;
206
207 for elem_idx in 0..n_elements {
208 if phase_scope
209 .solver_scope_mut()
210 .should_terminate_construction()
211 {
212 break;
213 }
214
215 let element =
216 (self.index_to_element)(phase_scope.score_director().working_solution(), elem_idx);
217 if assigned_set.contains(&element) {
218 continue;
219 }
220
221 let mut step_scope = StepScope::new(&mut phase_scope);
222
223 {
224 let sd = step_scope.score_director_mut();
225 let insert_pos = (self.list_len)(sd.working_solution(), entity_idx);
226 sd.before_variable_changed(self.descriptor_index, entity_idx);
227 (self.list_insert)(sd.working_solution_mut(), entity_idx, insert_pos, element);
228 sd.after_variable_changed(self.descriptor_index, entity_idx);
229 }
230
231 let step_score = step_scope.calculate_score();
232 step_scope.set_step_score(step_score);
233 step_scope.complete();
234
235 entity_idx = (entity_idx + 1) % n_entities;
236 }
237
238 phase_scope.update_best_solution();
239 }
240
241 fn phase_type_name(&self) -> &'static str {
242 "ListRoundRobin"
243 }
244}
245
246pub struct ConstructionArgs<S, V> {
247 pub element_count: fn(&S) -> usize,
248 pub assigned_elements: fn(&S) -> Vec<V>,
249 pub entity_count: fn(&S) -> usize,
250 pub list_len: fn(&S, usize) -> usize,
251 pub list_insert: fn(&mut S, usize, usize, V),
252 pub list_remove: fn(&mut S, usize, usize) -> V,
253 pub index_to_element: fn(&S, usize) -> V,
254 pub descriptor_index: usize,
255 pub entity_type_name: &'static str,
256 pub variable_name: &'static str,
257 pub depot_fn: Option<fn(&S) -> usize>,
258 pub distance_fn: Option<fn(&S, usize, usize) -> i64>,
259 pub element_load_fn: Option<fn(&S, usize) -> i64>,
260 pub capacity_fn: Option<fn(&S) -> i64>,
261 pub assign_route_fn: Option<fn(&mut S, usize, Vec<V>)>,
262 pub merge_feasible_fn: Option<fn(&S, &[usize]) -> bool>,
263 pub k_opt_get_route: Option<fn(&S, usize) -> Vec<usize>>,
264 pub k_opt_set_route: Option<fn(&mut S, usize, Vec<usize>)>,
265 pub k_opt_depot_fn: Option<fn(&S, usize) -> usize>,
266 pub k_opt_distance_fn: Option<fn(&S, usize, usize) -> i64>,
267 pub k_opt_feasible_fn: Option<fn(&S, usize, &[usize]) -> bool>,
268}
269
270impl<S, V> Clone for ConstructionArgs<S, V> {
271 fn clone(&self) -> Self {
272 *self
273 }
274}
275
276impl<S, V> Copy for ConstructionArgs<S, V> {}
277
278fn list_work_remaining<S, V>(args: &ConstructionArgs<S, V>, solution: &S) -> bool
279where
280 S: PlanningSolution,
281 V: Copy + PartialEq + Eq + Hash + Send + Sync + 'static,
282{
283 (args.assigned_elements)(solution).len() < (args.element_count)(solution)
284}
285
286fn has_explicit_target(config: &ConstructionHeuristicConfig) -> bool {
287 config.target.variable_name.is_some() || config.target.entity_class.is_some()
288}
289
290fn is_list_only_heuristic(heuristic: ConstructionHeuristicType) -> bool {
291 matches!(
292 heuristic,
293 ConstructionHeuristicType::ListRoundRobin
294 | ConstructionHeuristicType::ListCheapestInsertion
295 | ConstructionHeuristicType::ListRegretInsertion
296 | ConstructionHeuristicType::ListClarkeWright
297 | ConstructionHeuristicType::ListKOpt
298 )
299}
300
301fn is_standard_only_heuristic(heuristic: ConstructionHeuristicType) -> bool {
302 matches!(
303 heuristic,
304 ConstructionHeuristicType::FirstFitDecreasing
305 | ConstructionHeuristicType::WeakestFit
306 | ConstructionHeuristicType::WeakestFitDecreasing
307 | ConstructionHeuristicType::StrongestFit
308 | ConstructionHeuristicType::StrongestFitDecreasing
309 | ConstructionHeuristicType::AllocateEntityFromQueue
310 | ConstructionHeuristicType::AllocateToValueFromQueue
311 )
312}
313
314fn list_target_matches<S, V>(
315 config: &ConstructionHeuristicConfig,
316 list_construction: Option<&ConstructionArgs<S, V>>,
317) -> bool
318where
319 S: PlanningSolution,
320 V: Copy + PartialEq + Eq + Hash + Send + Sync + 'static,
321{
322 if !has_explicit_target(config) {
323 return false;
324 }
325
326 let Some(list_construction) = list_construction else {
327 return false;
328 };
329
330 config
331 .target
332 .variable_name
333 .as_deref()
334 .is_none_or(|name| name == list_construction.variable_name)
335 && config
336 .target
337 .entity_class
338 .as_deref()
339 .is_none_or(|name| name == list_construction.entity_type_name)
340}
341
342fn normalize_list_construction_config(
343 config: Option<&ConstructionHeuristicConfig>,
344) -> Option<ConstructionHeuristicConfig> {
345 let mut config = config.cloned()?;
346 config.construction_heuristic_type = match config.construction_heuristic_type {
347 ConstructionHeuristicType::FirstFit | ConstructionHeuristicType::CheapestInsertion => {
348 ConstructionHeuristicType::ListCheapestInsertion
349 }
350 other => other,
351 };
352 Some(config)
353}
354
355fn build_list_construction<S, V>(
356 config: Option<&ConstructionHeuristicConfig>,
357 args: &ConstructionArgs<S, V>,
358) -> ListConstruction<S, V>
359where
360 S: PlanningSolution,
361 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
362{
363 let Some((ch_type, k)) = config.map(|cfg| (cfg.construction_heuristic_type, cfg.k)) else {
364 return ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
365 args.element_count,
366 args.assigned_elements,
367 args.entity_count,
368 args.list_len,
369 args.list_insert,
370 args.list_remove,
371 args.index_to_element,
372 args.descriptor_index,
373 ));
374 };
375
376 match ch_type {
377 ConstructionHeuristicType::ListRoundRobin => {
378 ListConstruction::RoundRobin(ListRoundRobinPhase {
379 element_count: args.element_count,
380 get_assigned: args.assigned_elements,
381 entity_count: args.entity_count,
382 list_len: args.list_len,
383 list_insert: args.list_insert,
384 index_to_element: args.index_to_element,
385 descriptor_index: args.descriptor_index,
386 _phantom: PhantomData,
387 })
388 }
389 ConstructionHeuristicType::ListRegretInsertion => {
390 ListConstruction::RegretInsertion(ListRegretInsertionPhase::new(
391 args.element_count,
392 args.assigned_elements,
393 args.entity_count,
394 args.list_len,
395 args.list_insert,
396 args.list_remove,
397 args.index_to_element,
398 args.descriptor_index,
399 ))
400 }
401 ConstructionHeuristicType::ListClarkeWright => {
402 match (
403 args.depot_fn,
404 args.distance_fn,
405 args.element_load_fn,
406 args.capacity_fn,
407 args.assign_route_fn,
408 ) {
409 (Some(depot), Some(dist), Some(load), Some(cap), Some(assign)) => {
410 ListConstruction::ClarkeWright(ListClarkeWrightPhase::new(
411 args.element_count,
412 args.assigned_elements,
413 args.entity_count,
414 args.list_len,
415 assign,
416 args.index_to_element,
417 depot,
418 dist,
419 load,
420 cap,
421 args.merge_feasible_fn,
422 args.descriptor_index,
423 ))
424 }
425 _ => {
426 panic!(
427 "list_clarke_wright requires depot_fn, distance_fn, element_load_fn, capacity_fn, and assign_route_fn"
428 );
429 }
430 }
431 }
432 ConstructionHeuristicType::ListKOpt => match (
433 args.k_opt_get_route,
434 args.k_opt_set_route,
435 args.k_opt_depot_fn,
436 args.k_opt_distance_fn,
437 ) {
438 (Some(get_route), Some(set_route), Some(ko_depot), Some(ko_dist)) => {
439 ListConstruction::KOpt(ListKOptPhase::new(
440 k,
441 args.entity_count,
442 get_route,
443 set_route,
444 ko_depot,
445 ko_dist,
446 args.k_opt_feasible_fn,
447 args.descriptor_index,
448 ))
449 }
450 _ => {
451 panic!(
452 "list_k_opt requires k_opt_get_route, k_opt_set_route, k_opt_depot_fn, and k_opt_distance_fn"
453 );
454 }
455 },
456 ConstructionHeuristicType::ListCheapestInsertion => {
457 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
458 args.element_count,
459 args.assigned_elements,
460 args.entity_count,
461 args.list_len,
462 args.list_insert,
463 args.list_remove,
464 args.index_to_element,
465 args.descriptor_index,
466 ))
467 }
468 ConstructionHeuristicType::FirstFit | ConstructionHeuristicType::CheapestInsertion => {
469 panic!(
470 "generic construction heuristic {:?} must be normalized before list construction",
471 ch_type
472 );
473 }
474 ConstructionHeuristicType::FirstFitDecreasing
475 | ConstructionHeuristicType::WeakestFit
476 | ConstructionHeuristicType::WeakestFitDecreasing
477 | ConstructionHeuristicType::StrongestFit
478 | ConstructionHeuristicType::StrongestFitDecreasing
479 | ConstructionHeuristicType::AllocateEntityFromQueue
480 | ConstructionHeuristicType::AllocateToValueFromQueue => {
481 panic!(
482 "standard construction heuristic {:?} configured against a list variable",
483 ch_type
484 );
485 }
486 }
487}
488
489pub struct Construction<S, V>
490where
491 S: PlanningSolution,
492 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + 'static,
493{
494 config: Option<ConstructionHeuristicConfig>,
495 descriptor: SolutionDescriptor,
496 list_construction: Option<ConstructionArgs<S, V>>,
497}
498
499impl<S, V> Construction<S, V>
500where
501 S: PlanningSolution,
502 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + 'static,
503{
504 fn new(
505 config: Option<ConstructionHeuristicConfig>,
506 descriptor: SolutionDescriptor,
507 list_construction: Option<ConstructionArgs<S, V>>,
508 ) -> Self {
509 Self {
510 config,
511 descriptor,
512 list_construction,
513 }
514 }
515}
516
517impl<S, V> Debug for Construction<S, V>
518where
519 S: PlanningSolution,
520 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
521{
522 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
523 f.debug_struct("Construction")
524 .field("config", &self.config)
525 .field("has_list_construction", &self.list_construction.is_some())
526 .finish()
527 }
528}
529
530impl<S, V, D, ProgressCb> Phase<S, D, ProgressCb> for Construction<S, V>
531where
532 S: PlanningSolution + 'static,
533 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
534 D: solverforge_scoring::Director<S>,
535 ProgressCb: ProgressCallback<S>,
536{
537 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
538 let config = self.config.as_ref();
539 let explicit_target = config.is_some_and(has_explicit_target);
540 let entity_class = config.and_then(|cfg| cfg.target.entity_class.as_deref());
541 let variable_name = config.and_then(|cfg| cfg.target.variable_name.as_deref());
542 let standard_matches = config.is_some_and(|_| {
543 standard_target_matches(&self.descriptor, entity_class, variable_name)
544 });
545 let list_matches =
546 config.is_some_and(|cfg| list_target_matches(cfg, self.list_construction.as_ref()));
547
548 if let Some(cfg) = config {
549 if explicit_target && !standard_matches && !list_matches {
550 panic!(
551 "construction heuristic matched no planning variables for entity_class={:?} variable_name={:?}",
552 cfg.target.entity_class,
553 cfg.target.variable_name
554 );
555 }
556
557 let heuristic = cfg.construction_heuristic_type;
558 if is_list_only_heuristic(heuristic) {
559 assert!(
560 self.list_construction.is_some(),
561 "list construction heuristic {:?} configured against a solution with no planning list variable",
562 heuristic
563 );
564 assert!(
565 !explicit_target || list_matches,
566 "list construction heuristic {:?} does not match the targeted planning list variable for entity_class={:?} variable_name={:?}",
567 heuristic,
568 cfg.target.entity_class,
569 cfg.target.variable_name
570 );
571 self.solve_list(solver_scope);
572 return;
573 }
574
575 if is_standard_only_heuristic(heuristic) {
576 assert!(
577 !explicit_target || standard_matches,
578 "standard construction heuristic {:?} does not match targeted standard planning variables for entity_class={:?} variable_name={:?}",
579 heuristic,
580 cfg.target.entity_class,
581 cfg.target.variable_name
582 );
583 build_descriptor_construction(Some(cfg), &self.descriptor).solve(solver_scope);
584 return;
585 }
586 }
587
588 if self.list_construction.is_none() {
589 build_descriptor_construction(config, &self.descriptor).solve(solver_scope);
590 return;
591 }
592
593 let standard_remaining = standard_work_remaining(
594 &self.descriptor,
595 if explicit_target { entity_class } else { None },
596 if explicit_target { variable_name } else { None },
597 solver_scope.working_solution(),
598 );
599 let list_remaining = self
600 .list_construction
601 .as_ref()
602 .map(|args| {
603 (!explicit_target || list_matches)
604 && list_work_remaining(args, solver_scope.working_solution())
605 })
606 .unwrap_or(false);
607
608 if standard_remaining {
609 build_descriptor_construction(config, &self.descriptor).solve(solver_scope);
610 }
611 if list_remaining {
612 self.solve_list(solver_scope);
613 }
614 }
615
616 fn phase_type_name(&self) -> &'static str {
617 "Construction"
618 }
619}
620
621impl<S, V> Construction<S, V>
622where
623 S: PlanningSolution + 'static,
624 V: Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
625{
626 fn solve_list<D, ProgressCb>(&self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>)
627 where
628 D: solverforge_scoring::Director<S>,
629 ProgressCb: ProgressCallback<S>,
630 {
631 let Some(args) = self.list_construction.as_ref() else {
632 panic!("list construction configured against a scalar-only context");
633 };
634 let normalized = normalize_list_construction_config(self.config.as_ref());
635 build_list_construction(normalized.as_ref(), args).solve(solver_scope);
636 }
637}
638
639pub enum RuntimePhase<C, LS, VND> {
640 Construction(C),
641 LocalSearch(LS),
642 Vnd(VND),
643}
644
645impl<C, LS, VND> Debug for RuntimePhase<C, LS, VND>
646where
647 C: Debug,
648 LS: Debug,
649 VND: Debug,
650{
651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652 match self {
653 Self::Construction(phase) => write!(f, "RuntimePhase::Construction({phase:?})"),
654 Self::LocalSearch(phase) => write!(f, "RuntimePhase::LocalSearch({phase:?})"),
655 Self::Vnd(phase) => write!(f, "RuntimePhase::Vnd({phase:?})"),
656 }
657 }
658}
659
660impl<S, D, ProgressCb, C, LS, VND> Phase<S, D, ProgressCb> for RuntimePhase<C, LS, VND>
661where
662 S: PlanningSolution,
663 D: solverforge_scoring::Director<S>,
664 ProgressCb: ProgressCallback<S>,
665 C: Phase<S, D, ProgressCb> + Debug,
666 LS: Phase<S, D, ProgressCb> + Debug,
667 VND: Phase<S, D, ProgressCb> + Debug,
668{
669 fn solve(&mut self, solver_scope: &mut SolverScope<'_, S, D, ProgressCb>) {
670 match self {
671 Self::Construction(phase) => phase.solve(solver_scope),
672 Self::LocalSearch(phase) => phase.solve(solver_scope),
673 Self::Vnd(phase) => phase.solve(solver_scope),
674 }
675 }
676
677 fn phase_type_name(&self) -> &'static str {
678 "RuntimePhase"
679 }
680}
681
682pub fn build_phases<S, V, DM, IDM>(
683 config: &SolverConfig,
684 descriptor: &SolutionDescriptor,
685 model: &ModelContext<S, V, DM, IDM>,
686 list_construction: Option<ConstructionArgs<S, V>>,
687) -> PhaseSequence<RuntimePhase<Construction<S, V>, LocalSearch<S, V, DM, IDM>, Vnd<S, V, DM, IDM>>>
688where
689 S: PlanningSolution + 'static,
690 S::Score: Score + ParseableScore,
691 V: Clone + Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
692 DM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
693 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + 'static,
694{
695 let mut phases = Vec::new();
696
697 if config.phases.is_empty() {
698 phases.push(RuntimePhase::Construction(Construction::new(
699 None,
700 descriptor.clone(),
701 list_construction,
702 )));
703 phases.push(RuntimePhase::LocalSearch(build_local_search(
704 None,
705 model,
706 config.random_seed,
707 )));
708 return PhaseSequence::new(phases);
709 }
710
711 for phase in &config.phases {
712 match phase {
713 PhaseConfig::ConstructionHeuristic(ch) => {
714 phases.push(RuntimePhase::Construction(Construction::new(
715 Some(ch.clone()),
716 descriptor.clone(),
717 list_construction,
718 )));
719 }
720 PhaseConfig::LocalSearch(ls) => {
721 phases.push(RuntimePhase::LocalSearch(build_local_search(
722 Some(ls),
723 model,
724 config.random_seed,
725 )));
726 }
727 PhaseConfig::Vnd(vnd) => {
728 phases.push(RuntimePhase::Vnd(build_vnd(vnd, model, config.random_seed)));
729 }
730 _ => {
731 panic!("unsupported phase in the runtime");
732 }
733 }
734 }
735
736 PhaseSequence::new(phases)
737}