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