solverforge_solver/phase/construction/
phase.rs

1//! Construction heuristic phase implementation.
2
3use 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
14/// Construction heuristic phase that builds an initial solution.
15///
16/// This phase iterates over uninitialized entities and assigns values
17/// to their planning variables using a greedy approach.
18///
19/// # Type Parameters
20/// * `S` - The planning solution type
21/// * `M` - The move type
22/// * `P` - The entity placer type
23/// * `Fo` - The forager type
24pub 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    /// Creates a new construction heuristic phase.
44    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        // Get all placements (entities that need values assigned)
80        let placements = self.placer.get_placements(phase_scope.score_director());
81
82        for mut placement in placements {
83            // Check early termination
84            if phase_scope.solver_scope().is_terminate_early() {
85                break;
86            }
87
88            let mut step_scope = StepScope::new(&mut phase_scope);
89
90            // Use forager to pick the best move index for this placement
91            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                // Take ownership of the move
97                let m = placement.take_move(idx);
98
99                // Execute the move
100                m.do_move(step_scope.score_director_mut());
101
102                // Calculate and record the step score
103                let step_score = step_scope.calculate_score();
104                step_scope.set_step_score(step_score);
105            }
106
107            step_scope.complete();
108        }
109
110        // Update best solution at end of phase
111        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}