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