solverforge_solver/phase/vnd/
phase.rs1use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use solverforge_core::domain::PlanningSolution;
7use solverforge_scoring::{Director, RecordingDirector};
8
9use crate::heuristic::r#move::Move;
10use crate::heuristic::selector::MoveSelector;
11use crate::phase::control::{
12 settle_search_interrupt, should_interrupt_evaluation, should_interrupt_generation,
13 StepInterrupt,
14};
15use crate::phase::Phase;
16use crate::scope::ProgressCallback;
17use crate::scope::{PhaseScope, SolverScope, StepScope};
18
19pub struct VndPhase<T, M>(pub T, PhantomData<fn() -> M>);
68
69impl<T: Debug, M> Debug for VndPhase<T, M> {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_tuple("VndPhase").field(&self.0).finish()
72 }
73}
74
75impl<T, M> VndPhase<T, M> {
76 pub fn new(neighborhoods: T) -> Self {
77 Self(neighborhoods, PhantomData)
78 }
79}
80
81macro_rules! impl_vnd_phase {
83 ($idx:tt: $MS:ident) => {
85 impl<S, D, BestCb, M, $MS> Phase<S, D, BestCb> for VndPhase<($MS,), M>
86 where
87 S: PlanningSolution,
88 D: Director<S>,
89 BestCb: ProgressCallback<S>,
90 M: Move<S>,
91 $MS: MoveSelector<S, M>,
92 {
93 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
94 let mut phase_scope = PhaseScope::new(solver_scope, 0);
95 let mut current_score = phase_scope.calculate_score();
96
97 loop {
98 let mut step_scope = StepScope::new(&mut phase_scope);
99 let cursor = (self.0).$idx.open_cursor(step_scope.score_director());
100
101 match find_best_improving_move(cursor, &mut step_scope, ¤t_score) {
102 MoveSearchResult::Found(selected_move, selected_score) => {
103 selected_move.do_move(step_scope.score_director_mut());
104 step_scope.set_step_score(selected_score);
105 current_score = selected_score;
106 step_scope.phase_scope_mut().update_best_solution();
107 step_scope.complete();
108 }
109 MoveSearchResult::NotFound => {
110 step_scope.complete();
111 break;
112 }
113 MoveSearchResult::Interrupted => match settle_search_interrupt(&mut step_scope) {
114 StepInterrupt::Restart => continue,
115 StepInterrupt::TerminatePhase => break,
116 },
117 }
118 }
119 }
120
121 fn phase_type_name(&self) -> &'static str {
122 "VariableNeighborhoodDescent"
123 }
124 }
125 };
126
127 ($($idx:tt: $MS:ident),+) => {
129 impl<S, D, BestCb, M, $($MS),+> Phase<S, D, BestCb> for VndPhase<($($MS,)+), M>
130 where
131 S: PlanningSolution,
132 D: Director<S>,
133 BestCb: ProgressCallback<S>,
134 M: Move<S>,
135 $($MS: MoveSelector<S, M>,)+
136 {
137 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
138 const COUNT: usize = impl_vnd_phase!(@count $($idx),+);
139 let mut phase_scope = PhaseScope::new(solver_scope, 0);
140 let mut current_score = phase_scope.calculate_score();
141 let mut k = 0usize;
142
143 while k < COUNT {
144 let mut step_scope = StepScope::new(&mut phase_scope);
145 let search_result = match k {
146 $($idx => {
147 let cursor = (self.0).$idx.open_cursor(step_scope.score_director());
148 find_best_improving_move(cursor, &mut step_scope, ¤t_score)
149 },)+
150 _ => MoveSearchResult::NotFound,
151 };
152
153 match search_result {
154 MoveSearchResult::Found(selected_move, selected_score) => {
155 selected_move.do_move(step_scope.score_director_mut());
156 step_scope.set_step_score(selected_score);
157 current_score = selected_score;
158 step_scope.phase_scope_mut().update_best_solution();
159 step_scope.complete();
160 k = 0; }
162 MoveSearchResult::NotFound => {
163 step_scope.complete();
164 k += 1; }
166 MoveSearchResult::Interrupted => match settle_search_interrupt(&mut step_scope) {
167 StepInterrupt::Restart => continue,
168 StepInterrupt::TerminatePhase => break,
169 },
170 }
171 }
172 }
173
174 fn phase_type_name(&self) -> &'static str {
175 "VariableNeighborhoodDescent"
176 }
177 }
178 };
179
180 (@count $($idx:tt),+) => {
182 0 $(+ { let _ = $idx; 1 })+
183 };
184}
185
186enum MoveSearchResult<M, Sc> {
191 Found(M, Sc),
192 NotFound,
193 Interrupted,
194}
195
196fn find_best_improving_move<S, D, BestCb, M, I>(
197 moves: I,
198 step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
199 current_score: &S::Score,
200) -> MoveSearchResult<M, S::Score>
201where
202 S: PlanningSolution,
203 D: Director<S>,
204 BestCb: ProgressCallback<S>,
205 M: Move<S>,
206 I: IntoIterator<Item = M>,
207{
208 let mut best: Option<(M, S::Score)> = None;
209
210 for (generated, mov) in moves.into_iter().enumerate() {
211 if should_interrupt_generation(step_scope, generated) {
212 return MoveSearchResult::Interrupted;
213 }
214
215 if should_interrupt_evaluation(step_scope, generated) {
216 return MoveSearchResult::Interrupted;
217 }
218
219 if !mov.is_doable(step_scope.score_director()) {
220 continue;
221 }
222
223 let mut recording = RecordingDirector::new(step_scope.score_director_mut());
224 mov.do_move(&mut recording);
225 let move_score = recording.calculate_score();
226 recording.undo_changes();
227
228 if move_score > *current_score {
229 match &best {
230 Some((_, best_score)) if move_score > *best_score => {
231 best = Some((mov, move_score));
232 }
233 None => {
234 best = Some((mov, move_score));
235 }
236 _ => {}
237 }
238 }
239 }
240
241 match best {
242 Some((mov, score)) => MoveSearchResult::Found(mov, score),
243 None => MoveSearchResult::NotFound,
244 }
245}
246
247impl_vnd_phase!(0: MS0);
248impl_vnd_phase!(0: MS0, 1: MS1);
249impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2);
250impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3);
251impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4);
252impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5);
253impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6);
254impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6, 7: MS7);
255
256#[cfg(test)]
257#[path = "phase_tests.rs"]
258mod tests;