solverforge_solver/phase/construction/
phase.rs1use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::Director;
8use tracing::info;
9
10use crate::heuristic::r#move::Move;
11use crate::phase::construction::{ConstructionForager, EntityPlacer};
12use crate::phase::Phase;
13use crate::scope::ProgressCallback;
14use crate::scope::{PhaseScope, SolverScope, StepScope};
15
16pub struct ConstructionHeuristicPhase<S, M, P, Fo>
27where
28 S: PlanningSolution,
29 M: Move<S>,
30 P: EntityPlacer<S, M>,
31 Fo: ConstructionForager<S, M>,
32{
33 placer: P,
34 forager: Fo,
35 _phantom: PhantomData<fn() -> (S, M)>,
36}
37
38impl<S, M, P, Fo> ConstructionHeuristicPhase<S, M, P, Fo>
39where
40 S: PlanningSolution,
41 M: Move<S>,
42 P: EntityPlacer<S, M>,
43 Fo: ConstructionForager<S, M>,
44{
45 pub fn new(placer: P, forager: Fo) -> Self {
46 Self {
47 placer,
48 forager,
49 _phantom: PhantomData,
50 }
51 }
52}
53
54impl<S, M, P, Fo> Debug for ConstructionHeuristicPhase<S, M, P, Fo>
55where
56 S: PlanningSolution,
57 M: Move<S>,
58 P: EntityPlacer<S, M> + Debug,
59 Fo: ConstructionForager<S, M> + Debug,
60{
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("ConstructionHeuristicPhase")
63 .field("placer", &self.placer)
64 .field("forager", &self.forager)
65 .finish()
66 }
67}
68
69impl<S, D, BestCb, M, P, Fo> Phase<S, D, BestCb> for ConstructionHeuristicPhase<S, M, P, Fo>
70where
71 S: PlanningSolution,
72 D: Director<S>,
73 BestCb: ProgressCallback<S>,
74 M: Move<S>,
75 P: EntityPlacer<S, M>,
76 Fo: ConstructionForager<S, M>,
77{
78 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
79 let mut phase_scope = PhaseScope::new(solver_scope, 0);
80 let phase_index = phase_scope.phase_index();
81
82 info!(
83 event = "phase_start",
84 phase = "Construction Heuristic",
85 phase_index = phase_index,
86 );
87
88 let placements = self.placer.get_placements(phase_scope.score_director());
90
91 for mut placement in placements {
92 if phase_scope
95 .solver_scope_mut()
96 .should_terminate_construction()
97 {
98 break;
99 }
100
101 let moves_in_placement = placement.moves.len() as u64;
104
105 let mut step_scope = StepScope::new(&mut phase_scope);
106
107 let selected_idx = self
109 .forager
110 .pick_move_index(&placement, step_scope.score_director_mut());
111
112 for i in 0..moves_in_placement {
114 let accepted = selected_idx == Some(i as usize);
115 step_scope
116 .phase_scope_mut()
117 .solver_scope_mut()
118 .stats_mut()
119 .record_move(accepted);
120 }
121
122 if let Some(idx) = selected_idx {
123 let m = placement.take_move(idx);
125
126 m.do_move(step_scope.score_director_mut());
128
129 let step_score = step_scope.calculate_score();
131 step_scope.set_step_score(step_score);
132 }
133
134 step_scope.complete();
135 }
136
137 phase_scope.update_best_solution();
139
140 let best_score = phase_scope
141 .solver_scope()
142 .best_score()
143 .map(|s| format!("{}", s))
144 .unwrap_or_else(|| "none".to_string());
145
146 let duration = phase_scope.elapsed();
147 let steps = phase_scope.step_count();
148 let speed = if duration.as_secs_f64() > 0.0 {
149 (steps as f64 / duration.as_secs_f64()) as u64
150 } else {
151 0
152 };
153 let stats = phase_scope.solver_scope().stats();
154
155 info!(
156 event = "phase_end",
157 phase = "Construction Heuristic",
158 phase_index = phase_index,
159 duration_ms = duration.as_millis() as u64,
160 steps = steps,
161 moves_evaluated = stats.moves_evaluated,
162 moves_accepted = stats.moves_accepted,
163 score_calculations = stats.score_calculations,
164 speed = speed,
165 score = best_score,
166 );
167 }
168
169 fn phase_type_name(&self) -> &'static str {
170 "ConstructionHeuristic"
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177 use crate::heuristic::selector::{FromSolutionEntitySelector, StaticValueSelector};
178 use crate::phase::construction::{BestFitForager, FirstFitForager, QueuedEntityPlacer};
179 use crate::test_utils::{
180 create_simple_nqueens_director, get_queen_row, set_queen_row, NQueensSolution,
181 };
182
183 fn create_placer(
184 values: Vec<i64>,
185 ) -> QueuedEntityPlacer<
186 NQueensSolution,
187 i64,
188 FromSolutionEntitySelector,
189 StaticValueSelector<NQueensSolution, i64>,
190 > {
191 let es = FromSolutionEntitySelector::new(0);
192 let vs = StaticValueSelector::new(values);
193 QueuedEntityPlacer::new(es, vs, get_queen_row, set_queen_row, 0, "row")
194 }
195
196 #[test]
197 fn test_construction_first_fit() {
198 let director = create_simple_nqueens_director(4);
199 let mut solver_scope = SolverScope::new(director);
200 solver_scope.start_solving();
201
202 let values: Vec<i64> = (0..4).collect();
203 let placer = create_placer(values);
204 let forager = FirstFitForager::new();
205 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
206
207 phase.solve(&mut solver_scope);
208
209 let solution = solver_scope.working_solution();
210 assert_eq!(solution.queens.len(), 4);
211 for queen in &solution.queens {
212 assert!(queen.row.is_some(), "Queen should have a row assigned");
213 }
214
215 assert!(solver_scope.best_solution().is_some());
216 assert!(solver_scope.stats().moves_evaluated > 0);
218 }
219
220 #[test]
221 fn test_construction_best_fit() {
222 let director = create_simple_nqueens_director(4);
223 let mut solver_scope = SolverScope::new(director);
224 solver_scope.start_solving();
225
226 let values: Vec<i64> = (0..4).collect();
227 let placer = create_placer(values);
228 let forager = BestFitForager::new();
229 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
230
231 phase.solve(&mut solver_scope);
232
233 let solution = solver_scope.working_solution();
234 for queen in &solution.queens {
235 assert!(queen.row.is_some(), "Queen should have a row assigned");
236 }
237
238 assert!(solver_scope.best_solution().is_some());
239 assert!(solver_scope.best_score().is_some());
240
241 assert_eq!(solver_scope.stats().moves_evaluated, 16);
243 }
244
245 #[test]
246 fn test_construction_empty_solution() {
247 let director = create_simple_nqueens_director(0);
248 let mut solver_scope = SolverScope::new(director);
249 solver_scope.start_solving();
250
251 let values: Vec<i64> = vec![];
252 let placer = create_placer(values);
253 let forager = FirstFitForager::new();
254 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
255
256 phase.solve(&mut solver_scope);
257
258 assert_eq!(solver_scope.stats().moves_evaluated, 0);
260 }
261}