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, 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>,
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}
78
79pub struct ListSpec<S, V, DM, IDM> {
84 pub list_len: fn(&S, usize) -> usize,
86 pub list_remove: fn(&mut S, usize, usize) -> Option<V>,
87 pub list_insert: fn(&mut S, usize, usize, V),
88 pub list_get: fn(&S, usize, usize) -> Option<V>,
89 pub list_set: fn(&mut S, usize, usize, V),
90 pub list_reverse: fn(&mut S, usize, usize, usize),
91 pub sublist_remove: fn(&mut S, usize, usize, usize) -> Vec<V>,
92 pub sublist_insert: fn(&mut S, usize, usize, Vec<V>),
93 pub ruin_remove: fn(&mut S, usize, usize) -> V,
94 pub ruin_insert: fn(&mut S, usize, usize, V),
95 pub element_count: fn(&S) -> usize,
97 pub get_assigned: fn(&S) -> Vec<V>,
98 pub entity_count: fn(&S) -> usize,
99 pub list_remove_for_construction: fn(&mut S, usize, usize) -> V,
100 pub index_to_element: fn(&S, usize) -> V,
101 pub cross_distance_meter: DM,
103 pub intra_distance_meter: IDM,
104 pub variable_name: &'static str,
106 pub descriptor_index: usize,
107 pub _phantom: PhantomData<fn() -> S>,
108}
109
110impl<S, V, C, DM, IDM> ProblemSpec<S, C> for ListSpec<S, V, DM, IDM>
111where
112 S: PlanningSolution,
113 S::Score: Score + ParseableScore,
114 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
115 C: ConstraintSet<S, S::Score>,
116 DM: CrossEntityDistanceMeter<S> + Clone,
117 IDM: CrossEntityDistanceMeter<S> + Clone,
118{
119 fn is_trivial(&self, solution: &S) -> bool {
120 (self.entity_count)(solution) == 0
121 }
122
123 fn default_time_limit_secs(&self) -> u64 {
124 60
125 }
126
127 fn log_scale(&self, solution: &S) {
128 info!(
129 event = "solve_start",
130 entity_count = (self.entity_count)(solution),
131 element_count = (self.element_count)(solution),
132 );
133 }
134
135 fn build_and_solve(
136 self,
137 director: ScoreDirector<S, C>,
138 config: &SolverConfig,
139 time_limit: Duration,
140 termination: AnyTermination<S, ScoreDirector<S, C>>,
141 terminate: Option<&AtomicBool>,
142 callback: impl Fn(&S) + Send + Sync,
143 ) -> SolveResult<S> {
144 let construction = build_list_construction::<S, V>(
145 config,
146 self.element_count,
147 self.get_assigned,
148 self.entity_count,
149 self.list_len,
150 self.list_insert,
151 self.list_remove_for_construction,
152 self.index_to_element,
153 self.descriptor_index,
154 );
155
156 let ctx = ListContext::new(
157 self.list_len,
158 self.list_remove,
159 self.list_insert,
160 self.list_get,
161 self.list_set,
162 self.list_reverse,
163 self.sublist_remove,
164 self.sublist_insert,
165 self.ruin_remove,
166 self.ruin_insert,
167 self.entity_count,
168 self.cross_distance_meter,
169 self.intra_distance_meter,
170 self.variable_name,
171 self.descriptor_index,
172 );
173
174 let local_search = build_list_local_search::<S, V, DM, IDM>(config, &ctx);
175
176 match (construction, local_search) {
177 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Default(ls)) => {
178 let solver = Solver::new(((), c, ls))
179 .with_termination(termination)
180 .with_time_limit(time_limit)
181 .with_best_solution_callback(callback);
182 if let Some(flag) = terminate {
183 solver.with_terminate(flag).solve(director)
184 } else {
185 solver.solve(director)
186 }
187 }
188 (ListConstruction::CheapestInsertion(c), ListLocalSearch::Config(ls)) => {
189 let solver = Solver::new(((), c, ls))
190 .with_termination(termination)
191 .with_time_limit(time_limit)
192 .with_best_solution_callback(callback);
193 if let Some(flag) = terminate {
194 solver.with_terminate(flag).solve(director)
195 } else {
196 solver.solve(director)
197 }
198 }
199 (ListConstruction::RegretInsertion(c), ListLocalSearch::Default(ls)) => {
200 let solver = Solver::new(((), c, ls))
201 .with_termination(termination)
202 .with_time_limit(time_limit)
203 .with_best_solution_callback(callback);
204 if let Some(flag) = terminate {
205 solver.with_terminate(flag).solve(director)
206 } else {
207 solver.solve(director)
208 }
209 }
210 (ListConstruction::RegretInsertion(c), ListLocalSearch::Config(ls)) => {
211 let solver = Solver::new(((), c, ls))
212 .with_termination(termination)
213 .with_time_limit(time_limit)
214 .with_best_solution_callback(callback);
215 if let Some(flag) = terminate {
216 solver.with_terminate(flag).solve(director)
217 } else {
218 solver.solve(director)
219 }
220 }
221 }
222 }
223}
224
225#[allow(clippy::too_many_arguments)]
227fn build_list_construction<S, V>(
228 config: &SolverConfig,
229 element_count: fn(&S) -> usize,
230 get_assigned: fn(&S) -> Vec<V>,
231 entity_count: fn(&S) -> usize,
232 list_len: fn(&S, usize) -> usize,
233 list_insert: fn(&mut S, usize, usize, V),
234 list_remove: fn(&mut S, usize, usize) -> V,
235 index_to_element: fn(&S, usize) -> V,
236 descriptor_index: usize,
237) -> ListConstruction<S, V>
238where
239 S: PlanningSolution,
240 V: Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
241{
242 let ch_type = config
243 .phases
244 .iter()
245 .find_map(|p| {
246 if let PhaseConfig::ConstructionHeuristic(ch) = p {
247 Some(ch.construction_heuristic_type)
248 } else {
249 None
250 }
251 })
252 .unwrap_or(ConstructionHeuristicType::ListCheapestInsertion);
253
254 match ch_type {
255 ConstructionHeuristicType::ListRegretInsertion => {
256 ListConstruction::RegretInsertion(ListRegretInsertionPhase::new(
257 element_count,
258 get_assigned,
259 entity_count,
260 list_len,
261 list_insert,
262 list_remove,
263 index_to_element,
264 descriptor_index,
265 ))
266 }
267 _ => {
268 ListConstruction::CheapestInsertion(ListCheapestInsertionPhase::new(
270 element_count,
271 get_assigned,
272 entity_count,
273 list_len,
274 list_insert,
275 list_remove,
276 index_to_element,
277 descriptor_index,
278 ))
279 }
280 }
281}
282
283fn build_list_local_search<S, V, DM, IDM>(
285 config: &SolverConfig,
286 ctx: &ListContext<S, V, DM, IDM>,
287) -> ListLocalSearch<S, V, DM, IDM>
288where
289 S: PlanningSolution,
290 S::Score: Score,
291 V: Clone + Copy + PartialEq + Eq + std::hash::Hash + Send + Sync + fmt::Debug + 'static,
292 DM: CrossEntityDistanceMeter<S> + Clone,
293 IDM: CrossEntityDistanceMeter<S> + Clone,
294{
295 let ls_config = config.phases.iter().find_map(|p| {
296 if let PhaseConfig::LocalSearch(ls) = p {
297 Some(ls)
298 } else {
299 None
300 }
301 });
302
303 let Some(ls) = ls_config else {
304 let acceptor = LateAcceptanceAcceptor::<S>::new(400);
306 let forager = AcceptedCountForager::new(4);
307 let move_selector = ListMoveSelectorBuilder::build(None, ctx);
308 return ListLocalSearch::Default(LocalSearchPhase::new(
309 move_selector,
310 acceptor,
311 forager,
312 None,
313 ));
314 };
315
316 let acceptor = ls
317 .acceptor
318 .as_ref()
319 .map(|ac| AcceptorBuilder::build::<S>(ac))
320 .unwrap_or_else(|| AnyAcceptor::LateAcceptance(LateAcceptanceAcceptor::<S>::new(400)));
321
322 let forager = ForagerBuilder::build::<S>(ls.forager.as_ref());
323 let move_selector = ListMoveSelectorBuilder::build(ls.move_selector.as_ref(), ctx);
324
325 ListLocalSearch::Config(LocalSearchPhase::new(
326 move_selector,
327 acceptor,
328 forager,
329 None,
330 ))
331}