solverforge_solver/phase/construction/
mod.rs

1//! Construction heuristic phase
2//!
3//! Builds an initial solution by assigning values to uninitialized
4//! planning variables one at a time.
5
6mod forager;
7mod placer;
8
9use std::fmt::Debug;
10use std::marker::PhantomData;
11
12use solverforge_core::domain::PlanningSolution;
13
14use crate::heuristic::r#move::Move;
15use crate::phase::Phase;
16use crate::scope::{PhaseScope, SolverScope, StepScope};
17
18pub use forager::{
19    BestFitForager, ConstructionForager, FirstFeasibleForager, FirstFitForager,
20    StrongestFitForager, WeakestFitForager,
21};
22pub use placer::{EntityPlacer, Placement, QueuedEntityPlacer, SortedEntityPlacer};
23
24/// Construction heuristic phase configuration.
25#[derive(Debug, Clone)]
26pub struct ConstructionHeuristicConfig {
27    /// The forager type to use.
28    pub forager_type: ForagerType,
29}
30
31impl Default for ConstructionHeuristicConfig {
32    fn default() -> Self {
33        Self {
34            forager_type: ForagerType::FirstFit,
35        }
36    }
37}
38
39/// Type of forager to use in construction.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum ForagerType {
42    /// Accept the first feasible move.
43    FirstFit,
44    /// Evaluate all moves and pick the best.
45    BestFit,
46}
47
48/// Construction heuristic phase that builds an initial solution.
49///
50/// This phase iterates over uninitialized entities and assigns values
51/// to their planning variables using a greedy approach.
52///
53/// # Type Parameters
54/// * `S` - The planning solution type
55/// * `M` - The move type
56pub struct ConstructionHeuristicPhase<S, M>
57where
58    S: PlanningSolution,
59    M: Move<S>,
60{
61    /// The entity placer.
62    placer: Box<dyn EntityPlacer<S, M>>,
63    /// The forager for selecting moves.
64    forager: Box<dyn ConstructionForager<S, M>>,
65    _phantom: PhantomData<M>,
66}
67
68impl<S, M> ConstructionHeuristicPhase<S, M>
69where
70    S: PlanningSolution,
71    M: Move<S>,
72{
73    /// Creates a new construction heuristic phase.
74    pub fn new(
75        placer: Box<dyn EntityPlacer<S, M>>,
76        forager: Box<dyn ConstructionForager<S, M>>,
77    ) -> Self {
78        Self {
79            placer,
80            forager,
81            _phantom: PhantomData,
82        }
83    }
84}
85
86impl<S, M> Debug for ConstructionHeuristicPhase<S, M>
87where
88    S: PlanningSolution,
89    M: Move<S>,
90{
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        f.debug_struct("ConstructionHeuristicPhase")
93            .field("placer", &self.placer)
94            .field("forager", &self.forager)
95            .finish()
96    }
97}
98
99impl<S, M> Phase<S> for ConstructionHeuristicPhase<S, M>
100where
101    S: PlanningSolution,
102    M: Move<S>,
103{
104    fn solve(&mut self, solver_scope: &mut SolverScope<S>) {
105        let mut phase_scope = PhaseScope::new(solver_scope, 0);
106
107        // Get all placements (entities that need values assigned)
108        let placements = self.placer.get_placements(phase_scope.score_director());
109
110        for placement in placements {
111            // Check early termination
112            if phase_scope.solver_scope().is_terminate_early() {
113                break;
114            }
115
116            let mut step_scope = StepScope::new(&mut phase_scope);
117
118            // Use forager to pick the best move for this placement
119            let selected_move = self
120                .forager
121                .pick_move(&placement, step_scope.score_director_mut());
122
123            if let Some(m) = selected_move {
124                // Execute the move
125                m.do_move(step_scope.score_director_mut());
126
127                // Calculate and record the step score
128                let step_score = step_scope.calculate_score();
129                step_scope.set_step_score(step_score);
130            }
131
132            step_scope.complete();
133        }
134
135        // Update best solution at end of phase
136        phase_scope.update_best_solution();
137    }
138
139    fn phase_type_name(&self) -> &'static str {
140        "ConstructionHeuristic"
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use crate::heuristic::r#move::ChangeMove;
148    use crate::heuristic::selector::{FromSolutionEntitySelector, StaticTypedValueSelector};
149    use crate::manager::{ConstructionPhaseFactory, SolverPhaseFactory};
150    use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
151    use solverforge_core::score::SimpleScore;
152    use solverforge_scoring::SimpleScoreDirector;
153    use std::any::TypeId;
154
155    #[derive(Clone, Debug)]
156    struct Queen {
157        column: i32,
158        row: Option<i32>,
159    }
160
161    #[derive(Clone, Debug)]
162    struct NQueensSolution {
163        n: i32,
164        queens: Vec<Queen>,
165        score: Option<SimpleScore>,
166    }
167
168    impl PlanningSolution for NQueensSolution {
169        type Score = SimpleScore;
170
171        fn score(&self) -> Option<Self::Score> {
172            self.score
173        }
174
175        fn set_score(&mut self, score: Option<Self::Score>) {
176            self.score = score;
177        }
178    }
179
180    fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
181        &s.queens
182    }
183
184    fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
185        &mut s.queens
186    }
187
188    // Typed getter - zero erasure
189    fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i32> {
190        s.queens.get(idx).and_then(|q| q.row)
191    }
192
193    // Typed setter - zero erasure
194    fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i32>) {
195        if let Some(queen) = s.queens.get_mut(idx) {
196            queen.row = v;
197        }
198    }
199
200    fn calculate_conflicts(solution: &NQueensSolution) -> SimpleScore {
201        let mut conflicts = 0i64;
202
203        for (i, q1) in solution.queens.iter().enumerate() {
204            if let Some(row1) = q1.row {
205                for (_, q2) in solution.queens.iter().enumerate().skip(i + 1) {
206                    if let Some(row2) = q2.row {
207                        // Same row conflict
208                        if row1 == row2 {
209                            conflicts += 1;
210                        }
211                        // Diagonal conflict
212                        let col_diff = (q2.column - q1.column).abs();
213                        let row_diff = (row2 - row1).abs();
214                        if col_diff == row_diff {
215                            conflicts += 1;
216                        }
217                    }
218                }
219            }
220        }
221
222        SimpleScore::of(-conflicts)
223    }
224
225    fn create_test_director(
226        n: i32,
227    ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
228        let queens: Vec<_> = (0..n)
229            .map(|col| Queen {
230                column: col,
231                row: None,
232            })
233            .collect();
234
235        let solution = NQueensSolution {
236            n,
237            queens,
238            score: None,
239        };
240
241        let extractor = Box::new(TypedEntityExtractor::new(
242            "Queen",
243            "queens",
244            get_queens,
245            get_queens_mut,
246        ));
247        let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
248            .with_extractor(extractor);
249
250        let descriptor =
251            SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
252                .with_entity(entity_desc);
253
254        SimpleScoreDirector::with_calculator(solution, descriptor, calculate_conflicts)
255    }
256
257    type NQueensMove = ChangeMove<NQueensSolution, i32>;
258
259    fn create_placer(values: Vec<i32>) -> Box<dyn EntityPlacer<NQueensSolution, NQueensMove>> {
260        let es = Box::new(FromSolutionEntitySelector::new(0));
261        let vs = Box::new(StaticTypedValueSelector::new(values));
262        Box::new(QueuedEntityPlacer::new(
263            es,
264            vs,
265            get_queen_row,
266            set_queen_row,
267            0,
268            "row",
269        ))
270    }
271
272    #[test]
273    fn test_construction_first_fit() {
274        let director = create_test_director(4);
275        let mut solver_scope = SolverScope::new(Box::new(director));
276
277        let values: Vec<i32> = (0..4).collect();
278        let factory =
279            ConstructionPhaseFactory::<NQueensSolution, NQueensMove, _>::first_fit(move || {
280                create_placer(values.clone())
281            });
282        let mut phase = factory.create_phase();
283
284        phase.solve(&mut solver_scope);
285
286        // Check that all queens have rows assigned
287        let solution = solver_scope.working_solution();
288        assert_eq!(solution.n, 4);
289        for queen in &solution.queens {
290            assert!(queen.row.is_some(), "Queen should have a row assigned");
291        }
292
293        // Best solution should be set
294        assert!(solver_scope.best_solution().is_some());
295    }
296
297    #[test]
298    fn test_construction_best_fit() {
299        let director = create_test_director(4);
300        let mut solver_scope = SolverScope::new(Box::new(director));
301
302        let values: Vec<i32> = (0..4).collect();
303        let factory =
304            ConstructionPhaseFactory::<NQueensSolution, NQueensMove, _>::best_fit(move || {
305                create_placer(values.clone())
306            });
307        let mut phase = factory.create_phase();
308
309        phase.solve(&mut solver_scope);
310
311        // Check that all queens have rows assigned
312        let solution = solver_scope.working_solution();
313        for queen in &solution.queens {
314            assert!(queen.row.is_some(), "Queen should have a row assigned");
315        }
316
317        // Best solution should be set
318        assert!(solver_scope.best_solution().is_some());
319        assert!(solver_scope.best_score().is_some());
320    }
321
322    #[test]
323    fn test_construction_empty_solution() {
324        let director = create_test_director(0);
325        let mut solver_scope = SolverScope::new(Box::new(director));
326
327        let values: Vec<i32> = vec![];
328        let factory =
329            ConstructionPhaseFactory::<NQueensSolution, NQueensMove, _>::first_fit(move || {
330                create_placer(values.clone())
331            });
332        let mut phase = factory.create_phase();
333
334        // Should not panic
335        phase.solve(&mut solver_scope);
336    }
337}