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::BestSolutionCallback;
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 {
47 Self {
48 placer,
49 forager,
50 _phantom: PhantomData,
51 }
52 }
53}
54
55impl<S, M, P, Fo> Debug for ConstructionHeuristicPhase<S, M, P, Fo>
56where
57 S: PlanningSolution,
58 M: Move<S>,
59 P: EntityPlacer<S, M> + Debug,
60 Fo: ConstructionForager<S, M> + Debug,
61{
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 f.debug_struct("ConstructionHeuristicPhase")
64 .field("placer", &self.placer)
65 .field("forager", &self.forager)
66 .finish()
67 }
68}
69
70impl<S, D, BestCb, M, P, Fo> Phase<S, D, BestCb> for ConstructionHeuristicPhase<S, M, P, Fo>
71where
72 S: PlanningSolution,
73 D: ScoreDirector<S>,
74 BestCb: BestSolutionCallback<S>,
75 M: Move<S>,
76 P: EntityPlacer<S, M>,
77 Fo: ConstructionForager<S, M>,
78{
79 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
80 let mut phase_scope = PhaseScope::new(solver_scope, 0);
81 let phase_index = phase_scope.phase_index();
82
83 info!(
84 event = "phase_start",
85 phase = "Construction Heuristic",
86 phase_index = phase_index,
87 );
88
89 let placements = self.placer.get_placements(phase_scope.score_director());
91
92 for mut placement in placements {
93 if phase_scope.solver_scope().should_terminate_construction() {
96 break;
97 }
98
99 let moves_in_placement = placement.moves.len() as u64;
102
103 let mut step_scope = StepScope::new(&mut phase_scope);
104
105 let selected_idx = self
107 .forager
108 .pick_move_index(&placement, step_scope.score_director_mut());
109
110 for i in 0..moves_in_placement {
112 let accepted = selected_idx == Some(i as usize);
113 step_scope
114 .phase_scope_mut()
115 .solver_scope_mut()
116 .stats_mut()
117 .record_move(accepted);
118 }
119
120 if let Some(idx) = selected_idx {
121 let m = placement.take_move(idx);
123
124 m.do_move(step_scope.score_director_mut());
126
127 let step_score = step_scope.calculate_score();
129 step_scope.set_step_score(step_score);
130 }
131
132 step_scope.complete();
133 }
134
135 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 = phase_index,
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 "ConstructionHeuristic"
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::heuristic::selector::{FromSolutionEntitySelector, StaticTypedValueSelector};
172 use crate::phase::construction::{BestFitForager, FirstFitForager, QueuedEntityPlacer};
173 use crate::test_utils::{
174 create_simple_nqueens_director, get_queen_row, set_queen_row, NQueensSolution,
175 };
176
177 fn create_placer(
178 values: Vec<i64>,
179 ) -> QueuedEntityPlacer<
180 NQueensSolution,
181 i64,
182 FromSolutionEntitySelector,
183 StaticTypedValueSelector<NQueensSolution, i64>,
184 > {
185 let es = FromSolutionEntitySelector::new(0);
186 let vs = StaticTypedValueSelector::new(values);
187 QueuedEntityPlacer::new(es, vs, get_queen_row, set_queen_row, 0, "row")
188 }
189
190 #[test]
191 fn test_construction_first_fit() {
192 let director = create_simple_nqueens_director(4);
193 let mut solver_scope = SolverScope::new(director);
194 solver_scope.start_solving();
195
196 let values: Vec<i64> = (0..4).collect();
197 let placer = create_placer(values);
198 let forager = FirstFitForager::new();
199 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
200
201 phase.solve(&mut solver_scope);
202
203 let solution = solver_scope.working_solution();
204 assert_eq!(solution.queens.len(), 4);
205 for queen in &solution.queens {
206 assert!(queen.row.is_some(), "Queen should have a row assigned");
207 }
208
209 assert!(solver_scope.best_solution().is_some());
210 assert!(solver_scope.stats().moves_evaluated > 0);
212 }
213
214 #[test]
215 fn test_construction_best_fit() {
216 let director = create_simple_nqueens_director(4);
217 let mut solver_scope = SolverScope::new(director);
218 solver_scope.start_solving();
219
220 let values: Vec<i64> = (0..4).collect();
221 let placer = create_placer(values);
222 let forager = BestFitForager::new();
223 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
224
225 phase.solve(&mut solver_scope);
226
227 let solution = solver_scope.working_solution();
228 for queen in &solution.queens {
229 assert!(queen.row.is_some(), "Queen should have a row assigned");
230 }
231
232 assert!(solver_scope.best_solution().is_some());
233 assert!(solver_scope.best_score().is_some());
234
235 assert_eq!(solver_scope.stats().moves_evaluated, 16);
237 }
238
239 #[test]
240 fn test_construction_empty_solution() {
241 let director = create_simple_nqueens_director(0);
242 let mut solver_scope = SolverScope::new(director);
243 solver_scope.start_solving();
244
245 let values: Vec<i64> = vec![];
246 let placer = create_placer(values);
247 let forager = FirstFitForager::new();
248 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
249
250 phase.solve(&mut solver_scope);
251
252 assert_eq!(solver_scope.stats().moves_evaluated, 0);
254 }
255}