1use std::fmt::{self, Debug};
8use std::marker::PhantomData;
9
10use rand::Rng;
11use solverforge_core::domain::PlanningSolution;
12use solverforge_core::score::Score;
13use solverforge_scoring::ScoreDirector;
14use tokio::sync::mpsc;
15use tracing::{debug, info, trace};
16
17use super::Phase;
18use crate::scope::{PhaseScope, SolverScope};
19
20const LATE_ACCEPTANCE_SIZE: usize = 400;
22
23pub struct BasicConstructionPhase<S, G, T, E, V>
34where
35 S: PlanningSolution,
36 G: Fn(&S, usize) -> Option<usize> + Send,
37 T: Fn(&mut S, usize, Option<usize>) + Send,
38 E: Fn(&S) -> usize + Send,
39 V: Fn(&S) -> usize + Send,
40{
41 get_variable: G,
42 set_variable: T,
43 entity_count: E,
44 value_count: V,
45 _phantom: PhantomData<fn() -> S>,
46}
47
48impl<S, G, T, E, V> Debug for BasicConstructionPhase<S, G, T, E, V>
49where
50 S: PlanningSolution,
51 G: Fn(&S, usize) -> Option<usize> + Send,
52 T: Fn(&mut S, usize, Option<usize>) + Send,
53 E: Fn(&S) -> usize + Send,
54 V: Fn(&S) -> usize + Send,
55{
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("BasicConstructionPhase").finish()
58 }
59}
60
61impl<S, G, T, E, V> BasicConstructionPhase<S, G, T, E, V>
62where
63 S: PlanningSolution,
64 G: Fn(&S, usize) -> Option<usize> + Send,
65 T: Fn(&mut S, usize, Option<usize>) + Send,
66 E: Fn(&S) -> usize + Send,
67 V: Fn(&S) -> usize + Send,
68{
69 pub fn new(get_variable: G, set_variable: T, entity_count: E, value_count: V) -> Self {
71 Self {
72 get_variable,
73 set_variable,
74 entity_count,
75 value_count,
76 _phantom: PhantomData,
77 }
78 }
79}
80
81impl<S, D, G, T, E, V> Phase<S, D> for BasicConstructionPhase<S, G, T, E, V>
82where
83 S: PlanningSolution,
84 S::Score: Score,
85 D: ScoreDirector<S>,
86 G: Fn(&S, usize) -> Option<usize> + Send,
87 T: Fn(&mut S, usize, Option<usize>) + Send,
88 E: Fn(&S) -> usize + Send,
89 V: Fn(&S) -> usize + Send,
90{
91 fn solve(&mut self, solver_scope: &mut SolverScope<S, D>) {
92 let mut phase_scope = PhaseScope::new(solver_scope, 0);
93 let mut rng = rand::rng();
94
95 let n_entities = (self.entity_count)(phase_scope.solver_scope().working_solution());
96 let n_values = (self.value_count)(phase_scope.solver_scope().working_solution());
97
98 info!(
99 event = "phase_start",
100 phase = "Construction Heuristic",
101 phase_index = 0,
102 );
103
104 if n_entities == 0 || n_values == 0 {
105 phase_scope.update_best_solution();
106 info!(
107 event = "phase_end",
108 phase = "Construction Heuristic",
109 phase_index = 0,
110 duration_ms = phase_scope.elapsed().as_millis() as u64,
111 steps = 0u64,
112 speed = 0u64,
113 score = "N/A",
114 );
115 return;
116 }
117
118 for entity_idx in 0..n_entities {
119 if phase_scope.solver_scope().should_terminate() {
120 break;
121 }
122
123 if (self.get_variable)(phase_scope.solver_scope().working_solution(), entity_idx)
124 .is_none()
125 {
126 let value = rng.random_range(0..n_values);
127 (self.set_variable)(
128 phase_scope.solver_scope_mut().working_solution_mut(),
129 entity_idx,
130 Some(value),
131 );
132 }
133 phase_scope.increment_step_count();
134 }
135
136 phase_scope.update_best_solution();
137
138 let best_score = phase_scope
139 .solver_scope()
140 .best_score()
141 .map(|s| format!("{s}"))
142 .unwrap_or_else(|| "none".to_string());
143
144 let duration = phase_scope.elapsed();
145 let steps = phase_scope.step_count();
146 let speed = if duration.as_secs_f64() > 0.0 {
147 (steps as f64 / duration.as_secs_f64()) as u64
148 } else {
149 0
150 };
151
152 info!(
153 event = "phase_end",
154 phase = "Construction Heuristic",
155 phase_index = 0,
156 duration_ms = duration.as_millis() as u64,
157 steps = steps,
158 speed = speed,
159 score = best_score,
160 );
161 }
162
163 fn phase_type_name(&self) -> &'static str {
164 "BasicConstruction"
165 }
166}
167
168pub struct BasicLocalSearchPhase<S, G, T, E, V>
177where
178 S: PlanningSolution,
179 G: Fn(&S, usize) -> Option<usize> + Send,
180 T: Fn(&mut S, usize, Option<usize>) + Send,
181 E: Fn(&S) -> usize + Send,
182 V: Fn(&S) -> usize + Send,
183{
184 get_variable: G,
185 set_variable: T,
186 entity_count: E,
187 value_count: V,
188 sender: mpsc::UnboundedSender<(S, S::Score)>,
189 _phantom: PhantomData<fn() -> S>,
190}
191
192impl<S, G, T, E, V> Debug for BasicLocalSearchPhase<S, G, T, E, V>
193where
194 S: PlanningSolution,
195 G: Fn(&S, usize) -> Option<usize> + Send,
196 T: Fn(&mut S, usize, Option<usize>) + Send,
197 E: Fn(&S) -> usize + Send,
198 V: Fn(&S) -> usize + Send,
199{
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.debug_struct("BasicLocalSearchPhase").finish()
202 }
203}
204
205impl<S, G, T, E, V> BasicLocalSearchPhase<S, G, T, E, V>
206where
207 S: PlanningSolution,
208 G: Fn(&S, usize) -> Option<usize> + Send,
209 T: Fn(&mut S, usize, Option<usize>) + Send,
210 E: Fn(&S) -> usize + Send,
211 V: Fn(&S) -> usize + Send,
212{
213 pub fn new(
215 get_variable: G,
216 set_variable: T,
217 entity_count: E,
218 value_count: V,
219 sender: mpsc::UnboundedSender<(S, S::Score)>,
220 ) -> Self {
221 Self {
222 get_variable,
223 set_variable,
224 entity_count,
225 value_count,
226 sender,
227 _phantom: PhantomData,
228 }
229 }
230}
231
232impl<S, D, G, T, E, V> Phase<S, D> for BasicLocalSearchPhase<S, G, T, E, V>
233where
234 S: PlanningSolution,
235 S::Score: Score,
236 D: ScoreDirector<S>,
237 G: Fn(&S, usize) -> Option<usize> + Send,
238 T: Fn(&mut S, usize, Option<usize>) + Send,
239 E: Fn(&S) -> usize + Send,
240 V: Fn(&S) -> usize + Send,
241{
242 fn solve(&mut self, solver_scope: &mut SolverScope<S, D>) {
243 let mut phase_scope = PhaseScope::new(solver_scope, 1);
244 let mut rng = rand::rng();
245
246 let n_entities = (self.entity_count)(phase_scope.solver_scope().working_solution());
247 let n_values = (self.value_count)(phase_scope.solver_scope().working_solution());
248
249 info!(
250 event = "phase_start",
251 phase = "Late Acceptance",
252 phase_index = 1,
253 );
254
255 if n_entities == 0 || n_values == 0 {
256 info!(
257 event = "phase_end",
258 phase = "Late Acceptance",
259 phase_index = 1,
260 duration_ms = phase_scope.elapsed().as_millis() as u64,
261 steps = 0u64,
262 speed = 0u64,
263 score = "N/A",
264 );
265 return;
266 }
267
268 let initial_score = phase_scope.calculate_score();
270 let mut current_score = initial_score;
271 let mut best_score = initial_score;
272
273 let mut late_scores = [initial_score; LATE_ACCEPTANCE_SIZE];
275 let mut moves_evaluated: u64 = 0;
276 let mut last_progress_time = std::time::Instant::now();
277 let mut last_progress_moves: u64 = 0;
278
279 {
281 let solution = phase_scope.solver_scope().working_solution().clone();
282 let _ = self.sender.send((solution, best_score));
283 }
284
285 loop {
286 if phase_scope.solver_scope().should_terminate() {
287 break;
288 }
289
290 let entity_idx = rng.random_range(0..n_entities);
291 let old_value =
292 (self.get_variable)(phase_scope.solver_scope().working_solution(), entity_idx);
293 let new_value = Some(rng.random_range(0..n_values));
294
295 if old_value == new_value {
296 continue;
297 }
298
299 moves_evaluated += 1;
300
301 let now = std::time::Instant::now();
303 if now.duration_since(last_progress_time).as_secs() >= 1 {
304 let moves_delta = moves_evaluated - last_progress_moves;
305 let elapsed_secs = now.duration_since(last_progress_time).as_secs_f64();
306 let current_speed = (moves_delta as f64 / elapsed_secs) as u64;
307 debug!(
308 event = "progress",
309 steps = phase_scope.step_count(),
310 speed = current_speed,
311 score = %best_score,
312 );
313 last_progress_time = now;
314 last_progress_moves = moves_evaluated;
315 }
316
317 let director = phase_scope.score_director_mut();
319 director.before_entity_changed(entity_idx);
320 (self.set_variable)(director.working_solution_mut(), entity_idx, new_value);
321 director.after_entity_changed(entity_idx);
322 let new_score = director.calculate_score();
323
324 let step = phase_scope.step_count();
325 let late_idx = (step as usize) % LATE_ACCEPTANCE_SIZE;
326 let late_score = late_scores[late_idx];
327
328 if new_score >= current_score || new_score >= late_score {
329 late_scores[late_idx] = new_score;
330 current_score = new_score;
331 let new_step = phase_scope.increment_step_count();
332
333 trace!(
334 event = "step",
335 step = new_step,
336 entity = entity_idx,
337 score = %new_score,
338 accepted = true,
339 );
340
341 if new_score > best_score {
342 best_score = new_score;
343 phase_scope.update_best_solution();
344
345 let solution = phase_scope.solver_scope().working_solution().clone();
346 let _ = self.sender.send((solution, best_score));
347 }
348 } else {
349 trace!(
350 event = "step",
351 step = moves_evaluated,
352 entity = entity_idx,
353 score = %new_score,
354 accepted = false,
355 );
356 let director = phase_scope.score_director_mut();
358 director.before_entity_changed(entity_idx);
359 (self.set_variable)(director.working_solution_mut(), entity_idx, old_value);
360 director.after_entity_changed(entity_idx);
361 director.calculate_score();
362 }
363 }
364
365 let duration = phase_scope.elapsed();
366 let speed = if duration.as_secs_f64() > 0.0 {
367 (moves_evaluated as f64 / duration.as_secs_f64()) as u64
368 } else {
369 0
370 };
371
372 let best_score_str = format!("{best_score}");
373 info!(
374 event = "phase_end",
375 phase = "Late Acceptance",
376 phase_index = 1,
377 duration_ms = duration.as_millis() as u64,
378 steps = phase_scope.step_count(),
379 speed = speed,
380 score = best_score_str,
381 );
382 }
383
384 fn phase_type_name(&self) -> &'static str {
385 "BasicLocalSearch"
386 }
387}