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