solverforge_solver/phase/construction/
phase.rs1use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::ScoreDirector;
8
9use crate::heuristic::r#move::Move;
10use crate::phase::construction::{ConstructionForager, EntityPlacer};
11use crate::phase::Phase;
12use crate::scope::{PhaseScope, SolverScope, StepScope};
13
14pub struct ConstructionHeuristicPhase<S, M, P, Fo>
25where
26 S: PlanningSolution,
27 M: Move<S>,
28 P: EntityPlacer<S, M>,
29 Fo: ConstructionForager<S, M>,
30{
31 placer: P,
32 forager: Fo,
33 _phantom: PhantomData<fn() -> (S, M)>,
34}
35
36impl<S, M, P, Fo> ConstructionHeuristicPhase<S, M, P, Fo>
37where
38 S: PlanningSolution,
39 M: Move<S>,
40 P: EntityPlacer<S, M>,
41 Fo: ConstructionForager<S, M>,
42{
43 pub fn new(placer: P, forager: Fo) -> Self {
45 Self {
46 placer,
47 forager,
48 _phantom: PhantomData,
49 }
50 }
51}
52
53impl<S, M, P, Fo> Debug for ConstructionHeuristicPhase<S, M, P, Fo>
54where
55 S: PlanningSolution,
56 M: Move<S>,
57 P: EntityPlacer<S, M> + Debug,
58 Fo: ConstructionForager<S, M> + Debug,
59{
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("ConstructionHeuristicPhase")
62 .field("placer", &self.placer)
63 .field("forager", &self.forager)
64 .finish()
65 }
66}
67
68impl<S, D, M, P, Fo> Phase<S, D> for ConstructionHeuristicPhase<S, M, P, Fo>
69where
70 S: PlanningSolution,
71 D: ScoreDirector<S>,
72 M: Move<S>,
73 P: EntityPlacer<S, M>,
74 Fo: ConstructionForager<S, M>,
75{
76 fn solve(&mut self, solver_scope: &mut SolverScope<S, D>) {
77 let mut phase_scope = PhaseScope::new(solver_scope, 0);
78
79 let placements = self.placer.get_placements(phase_scope.score_director());
81
82 for mut placement in placements {
83 if phase_scope.solver_scope().is_terminate_early() {
85 break;
86 }
87
88 let mut step_scope = StepScope::new(&mut phase_scope);
89
90 let selected_idx = self
92 .forager
93 .pick_move_index(&placement, step_scope.score_director_mut());
94
95 if let Some(idx) = selected_idx {
96 let m = placement.take_move(idx);
98
99 m.do_move(step_scope.score_director_mut());
101
102 let step_score = step_scope.calculate_score();
104 step_scope.set_step_score(step_score);
105 }
106
107 step_scope.complete();
108 }
109
110 phase_scope.update_best_solution();
112 }
113
114 fn phase_type_name(&self) -> &'static str {
115 "ConstructionHeuristic"
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::heuristic::selector::{FromSolutionEntitySelector, StaticTypedValueSelector};
123 use crate::phase::construction::{BestFitForager, FirstFitForager, QueuedEntityPlacer};
124 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
125 use solverforge_core::score::SimpleScore;
126 use solverforge_scoring::SimpleScoreDirector;
127 use std::any::TypeId;
128
129 #[derive(Clone, Debug)]
130 struct Queen {
131 column: i32,
132 row: Option<i32>,
133 }
134
135 #[derive(Clone, Debug)]
136 struct NQueensSolution {
137 n: i32,
138 queens: Vec<Queen>,
139 score: Option<SimpleScore>,
140 }
141
142 impl PlanningSolution for NQueensSolution {
143 type Score = SimpleScore;
144
145 fn score(&self) -> Option<Self::Score> {
146 self.score
147 }
148
149 fn set_score(&mut self, score: Option<Self::Score>) {
150 self.score = score;
151 }
152 }
153
154 fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
155 &s.queens
156 }
157
158 fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
159 &mut s.queens
160 }
161
162 fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i32> {
163 s.queens.get(idx).and_then(|q| q.row)
164 }
165
166 fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i32>) {
167 if let Some(queen) = s.queens.get_mut(idx) {
168 queen.row = v;
169 }
170 }
171
172 fn calculate_conflicts(solution: &NQueensSolution) -> SimpleScore {
173 let mut conflicts = 0i64;
174
175 for (i, q1) in solution.queens.iter().enumerate() {
176 if let Some(row1) = q1.row {
177 for (_, q2) in solution.queens.iter().enumerate().skip(i + 1) {
178 if let Some(row2) = q2.row {
179 if row1 == row2 {
180 conflicts += 1;
181 }
182 let col_diff = (q2.column - q1.column).abs();
183 let row_diff = (row2 - row1).abs();
184 if col_diff == row_diff {
185 conflicts += 1;
186 }
187 }
188 }
189 }
190 }
191
192 SimpleScore::of(-conflicts)
193 }
194
195 fn create_test_director(
196 n: i32,
197 ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
198 let queens: Vec<_> = (0..n)
199 .map(|col| Queen {
200 column: col,
201 row: None,
202 })
203 .collect();
204
205 let solution = NQueensSolution {
206 n,
207 queens,
208 score: None,
209 };
210
211 let extractor = Box::new(TypedEntityExtractor::new(
212 "Queen",
213 "queens",
214 get_queens,
215 get_queens_mut,
216 ));
217 let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
218 .with_extractor(extractor);
219
220 let descriptor =
221 SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
222 .with_entity(entity_desc);
223
224 SimpleScoreDirector::with_calculator(solution, descriptor, calculate_conflicts)
225 }
226
227 fn create_placer(
228 values: Vec<i32>,
229 ) -> QueuedEntityPlacer<
230 NQueensSolution,
231 i32,
232 FromSolutionEntitySelector,
233 StaticTypedValueSelector<NQueensSolution, i32>,
234 > {
235 let es = FromSolutionEntitySelector::new(0);
236 let vs = StaticTypedValueSelector::new(values);
237 QueuedEntityPlacer::new(es, vs, get_queen_row, set_queen_row, 0, "row")
238 }
239
240 #[test]
241 fn test_construction_first_fit() {
242 let director = create_test_director(4);
243 let mut solver_scope = SolverScope::new(director);
244
245 let values: Vec<i32> = (0..4).collect();
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 let solution = solver_scope.working_solution();
253 assert_eq!(solution.n, 4);
254 for queen in &solution.queens {
255 assert!(queen.row.is_some(), "Queen should have a row assigned");
256 }
257
258 assert!(solver_scope.best_solution().is_some());
259 }
260
261 #[test]
262 fn test_construction_best_fit() {
263 let director = create_test_director(4);
264 let mut solver_scope = SolverScope::new(director);
265
266 let values: Vec<i32> = (0..4).collect();
267 let placer = create_placer(values);
268 let forager = BestFitForager::new();
269 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
270
271 phase.solve(&mut solver_scope);
272
273 let solution = solver_scope.working_solution();
274 for queen in &solution.queens {
275 assert!(queen.row.is_some(), "Queen should have a row assigned");
276 }
277
278 assert!(solver_scope.best_solution().is_some());
279 assert!(solver_scope.best_score().is_some());
280 }
281
282 #[test]
283 fn test_construction_empty_solution() {
284 let director = create_test_director(0);
285 let mut solver_scope = SolverScope::new(director);
286
287 let values: Vec<i32> = vec![];
288 let placer = create_placer(values);
289 let forager = FirstFitForager::new();
290 let mut phase = ConstructionHeuristicPhase::new(placer, forager);
291
292 phase.solve(&mut solver_scope);
293 }
294}