solverforge_solver/phase/vnd/
phase.rs1use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::{RecordingScoreDirector, ScoreDirector};
8
9use crate::heuristic::r#move::{Move, MoveArena};
10use crate::heuristic::selector::MoveSelector;
11use crate::phase::Phase;
12use crate::scope::{PhaseScope, SolverScope, StepScope};
13
14pub struct VndPhase<T, M>(pub T, PhantomData<M>);
63
64impl<T: Debug, M> Debug for VndPhase<T, M> {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_tuple("VndPhase").field(&self.0).finish()
67 }
68}
69
70impl<T, M> VndPhase<T, M> {
71 pub fn new(neighborhoods: T) -> Self {
73 Self(neighborhoods, PhantomData)
74 }
75}
76
77macro_rules! impl_vnd_phase {
79 ($idx:tt: $MS:ident) => {
81 impl<S, D, M, $MS> Phase<S, D> for VndPhase<($MS,), M>
82 where
83 S: PlanningSolution,
84 D: ScoreDirector<S>,
85 M: Move<S>,
86 $MS: MoveSelector<S, M>,
87 {
88 fn solve(&mut self, solver_scope: &mut SolverScope<S, D>) {
89 let mut arena = MoveArena::<M>::new();
90 let mut phase_scope = PhaseScope::new(solver_scope, 0);
91 let mut current_score = phase_scope.calculate_score();
92
93 loop {
94 let mut step_scope = StepScope::new(&mut phase_scope);
95 arena.reset();
96 arena.extend((self.0).$idx.iter_moves(step_scope.score_director()));
97
98 let best_index = find_best_improving_move_index(&arena, &mut step_scope, ¤t_score);
99
100 if let Some((selected_index, selected_score)) = best_index {
101 let selected_move = arena.take(selected_index);
102 selected_move.do_move(step_scope.score_director_mut());
103 step_scope.set_step_score(selected_score);
104 current_score = selected_score;
105 step_scope.phase_scope_mut().update_best_solution();
106 step_scope.complete();
107 } else {
108 step_scope.complete();
109 break;
110 }
111 }
112 }
113
114 fn phase_type_name(&self) -> &'static str {
115 "VariableNeighborhoodDescent"
116 }
117 }
118 };
119
120 ($($idx:tt: $MS:ident),+) => {
122 impl<S, D, M, $($MS),+> Phase<S, D> for VndPhase<($($MS,)+), M>
123 where
124 S: PlanningSolution,
125 D: ScoreDirector<S>,
126 M: Move<S>,
127 $($MS: MoveSelector<S, M>,)+
128 {
129 fn solve(&mut self, solver_scope: &mut SolverScope<S, D>) {
130 const COUNT: usize = impl_vnd_phase!(@count $($idx),+);
131 let mut arena = MoveArena::<M>::new();
132 let mut phase_scope = PhaseScope::new(solver_scope, 0);
133 let mut current_score = phase_scope.calculate_score();
134 let mut k = 0usize;
135
136 while k < COUNT {
137 let mut step_scope = StepScope::new(&mut phase_scope);
138 arena.reset();
139
140 match k {
142 $($idx => arena.extend((self.0).$idx.iter_moves(step_scope.score_director())),)+
143 _ => {}
144 }
145
146 let best_index = find_best_improving_move_index(&arena, &mut step_scope, ¤t_score);
147
148 if let Some((selected_index, selected_score)) = best_index {
149 let selected_move = arena.take(selected_index);
150 selected_move.do_move(step_scope.score_director_mut());
151 step_scope.set_step_score(selected_score);
152 current_score = selected_score;
153 step_scope.phase_scope_mut().update_best_solution();
154 step_scope.complete();
155 k = 0; } else {
157 step_scope.complete();
158 k += 1; }
160 }
161 }
162
163 fn phase_type_name(&self) -> &'static str {
164 "VariableNeighborhoodDescent"
165 }
166 }
167 };
168
169 (@count $($idx:tt),+) => {
171 0 $(+ { let _ = $idx; 1 })+
172 };
173}
174
175fn find_best_improving_move_index<S, D, M>(
179 arena: &MoveArena<M>,
180 step_scope: &mut StepScope<'_, '_, '_, S, D>,
181 current_score: &S::Score,
182) -> Option<(usize, S::Score)>
183where
184 S: PlanningSolution,
185 D: ScoreDirector<S>,
186 M: Move<S>,
187{
188 let mut best: Option<(usize, S::Score)> = None;
189
190 for i in 0..arena.len() {
191 let m = arena.get(i).unwrap();
192
193 if !m.is_doable(step_scope.score_director()) {
194 continue;
195 }
196
197 let mut recording = RecordingScoreDirector::new(step_scope.score_director_mut());
198 m.do_move(&mut recording);
199 let move_score = recording.calculate_score();
200 recording.undo_changes();
201
202 if move_score > *current_score {
203 match &best {
204 Some((_, best_score)) if move_score > *best_score => {
205 best = Some((i, move_score));
206 }
207 None => {
208 best = Some((i, move_score));
209 }
210 _ => {}
211 }
212 }
213 }
214
215 best
216}
217
218impl_vnd_phase!(0: MS0);
219impl_vnd_phase!(0: MS0, 1: MS1);
220impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2);
221impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3);
222impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4);
223impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5);
224impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6);
225impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6, 7: MS7);
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::heuristic::r#move::ChangeMove;
231 use crate::heuristic::selector::ChangeMoveSelector;
232 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
233 use solverforge_core::score::SimpleScore;
234 use solverforge_scoring::SimpleScoreDirector;
235 use std::any::TypeId;
236
237 #[derive(Clone, Debug)]
238 struct Queen {
239 column: i32,
240 row: Option<i32>,
241 }
242
243 #[derive(Clone, Debug)]
244 struct NQueensSolution {
245 queens: Vec<Queen>,
246 score: Option<SimpleScore>,
247 }
248
249 impl PlanningSolution for NQueensSolution {
250 type Score = SimpleScore;
251
252 fn score(&self) -> Option<Self::Score> {
253 self.score
254 }
255
256 fn set_score(&mut self, score: Option<Self::Score>) {
257 self.score = score;
258 }
259 }
260
261 fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
262 &s.queens
263 }
264 fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
265 &mut s.queens
266 }
267
268 fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i32> {
269 s.queens.get(idx).and_then(|q| q.row)
270 }
271
272 fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i32>) {
273 if let Some(queen) = s.queens.get_mut(idx) {
274 queen.row = v;
275 }
276 }
277
278 fn calculate_conflicts(solution: &NQueensSolution) -> SimpleScore {
279 let mut conflicts = 0i64;
280
281 for (i, q1) in solution.queens.iter().enumerate() {
282 if let Some(row1) = q1.row {
283 for q2 in solution.queens.iter().skip(i + 1) {
284 if let Some(row2) = q2.row {
285 if row1 == row2 {
286 conflicts += 1;
287 }
288 let col_diff = (q2.column - q1.column).abs();
289 let row_diff = (row2 - row1).abs();
290 if col_diff == row_diff {
291 conflicts += 1;
292 }
293 }
294 }
295 }
296 }
297
298 SimpleScore::of(-conflicts)
299 }
300
301 fn create_director(
302 rows: &[i32],
303 ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
304 let queens: Vec<_> = rows
305 .iter()
306 .enumerate()
307 .map(|(col, &row)| Queen {
308 column: col as i32,
309 row: Some(row),
310 })
311 .collect();
312
313 let solution = NQueensSolution {
314 queens,
315 score: None,
316 };
317
318 let extractor = Box::new(TypedEntityExtractor::new(
319 "Queen",
320 "queens",
321 get_queens,
322 get_queens_mut,
323 ));
324 let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
325 .with_extractor(extractor);
326
327 let descriptor =
328 SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
329 .with_entity(entity_desc);
330
331 SimpleScoreDirector::with_calculator(solution, descriptor, calculate_conflicts)
332 }
333
334 type NQueensMove = ChangeMove<NQueensSolution, i32>;
335
336 fn create_move_selector(
337 values: Vec<i32>,
338 ) -> ChangeMoveSelector<
339 NQueensSolution,
340 i32,
341 crate::heuristic::selector::FromSolutionEntitySelector,
342 crate::heuristic::selector::StaticTypedValueSelector<NQueensSolution, i32>,
343 > {
344 ChangeMoveSelector::simple(get_queen_row, set_queen_row, 0, "row", values)
345 }
346
347 #[test]
348 fn test_vnd_improves_solution() {
349 let director = create_director(&[0, 0, 0, 0]);
350 let mut solver_scope = SolverScope::new(director);
351
352 let initial_score = solver_scope.calculate_score();
353 assert!(initial_score < SimpleScore::of(0));
354
355 let values: Vec<i32> = (0..4).collect();
356 let mut phase: VndPhase<_, NQueensMove> = VndPhase::new((
357 create_move_selector(values.clone()),
358 create_move_selector(values),
359 ));
360
361 phase.solve(&mut solver_scope);
362
363 let final_score = solver_scope.best_score().copied().unwrap_or(initial_score);
364 assert!(final_score >= initial_score);
365 }
366
367 #[test]
368 fn test_vnd_single_neighborhood() {
369 let director = create_director(&[0, 0, 0, 0]);
370 let mut solver_scope = SolverScope::new(director);
371
372 let initial_score = solver_scope.calculate_score();
373
374 let values: Vec<i32> = (0..4).collect();
375 let mut phase: VndPhase<_, NQueensMove> = VndPhase::new((create_move_selector(values),));
376
377 phase.solve(&mut solver_scope);
378
379 let final_score = solver_scope.best_score().copied().unwrap_or(initial_score);
380 assert!(final_score >= initial_score);
381 }
382}