use std::fmt::Debug;
use std::marker::PhantomData;
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::{Director, RecordingDirector};
use crate::heuristic::r#move::{Move, MoveArena};
use crate::heuristic::selector::MoveSelector;
use crate::phase::Phase;
use crate::scope::ProgressCallback;
use crate::scope::{PhaseScope, SolverScope, StepScope};
pub struct VndPhase<T, M>(pub T, PhantomData<fn() -> M>);
impl<T: Debug, M> Debug for VndPhase<T, M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("VndPhase").field(&self.0).finish()
}
}
impl<T, M> VndPhase<T, M> {
pub fn new(neighborhoods: T) -> Self {
Self(neighborhoods, PhantomData)
}
}
macro_rules! impl_vnd_phase {
($idx:tt: $MS:ident) => {
impl<S, D, BestCb, M, $MS> Phase<S, D, BestCb> for VndPhase<($MS,), M>
where
S: PlanningSolution,
D: Director<S>,
BestCb: ProgressCallback<S>,
M: Move<S>,
$MS: MoveSelector<S, M>,
{
fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
let mut arena = MoveArena::<M>::new();
let mut phase_scope = PhaseScope::new(solver_scope, 0);
let mut current_score = phase_scope.calculate_score();
loop {
let mut step_scope = StepScope::new(&mut phase_scope);
arena.reset();
arena.extend((self.0).$idx.iter_moves(step_scope.score_director()));
let best_index = find_best_improving_move_index(&arena, &mut step_scope, ¤t_score);
if let Some((selected_index, selected_score)) = best_index {
let selected_move = arena.take(selected_index);
selected_move.do_move(step_scope.score_director_mut());
step_scope.set_step_score(selected_score);
current_score = selected_score;
step_scope.phase_scope_mut().update_best_solution();
step_scope.complete();
} else {
step_scope.complete();
break;
}
}
}
fn phase_type_name(&self) -> &'static str {
"VariableNeighborhoodDescent"
}
}
};
($($idx:tt: $MS:ident),+) => {
impl<S, D, BestCb, M, $($MS),+> Phase<S, D, BestCb> for VndPhase<($($MS,)+), M>
where
S: PlanningSolution,
D: Director<S>,
BestCb: ProgressCallback<S>,
M: Move<S>,
$($MS: MoveSelector<S, M>,)+
{
fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
const COUNT: usize = impl_vnd_phase!(@count $($idx),+);
let mut arena = MoveArena::<M>::new();
let mut phase_scope = PhaseScope::new(solver_scope, 0);
let mut current_score = phase_scope.calculate_score();
let mut k = 0usize;
while k < COUNT {
let mut step_scope = StepScope::new(&mut phase_scope);
arena.reset();
match k {
$($idx => arena.extend((self.0).$idx.iter_moves(step_scope.score_director())),)+
_ => {}
}
let best_index = find_best_improving_move_index(&arena, &mut step_scope, ¤t_score);
if let Some((selected_index, selected_score)) = best_index {
let selected_move = arena.take(selected_index);
selected_move.do_move(step_scope.score_director_mut());
step_scope.set_step_score(selected_score);
current_score = selected_score;
step_scope.phase_scope_mut().update_best_solution();
step_scope.complete();
k = 0; } else {
step_scope.complete();
k += 1; }
}
}
fn phase_type_name(&self) -> &'static str {
"VariableNeighborhoodDescent"
}
}
};
(@count $($idx:tt),+) => {
0 $(+ { let _ = $idx; 1 })+
};
}
fn find_best_improving_move_index<S, D, BestCb, M>(
arena: &MoveArena<M>,
step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
current_score: &S::Score,
) -> Option<(usize, S::Score)>
where
S: PlanningSolution,
D: Director<S>,
BestCb: ProgressCallback<S>,
M: Move<S>,
{
let mut best: Option<(usize, S::Score)> = None;
for i in 0..arena.len() {
let m = arena.get(i).unwrap();
if !m.is_doable(step_scope.score_director()) {
continue;
}
let mut recording = RecordingDirector::new(step_scope.score_director_mut());
m.do_move(&mut recording);
let move_score = recording.calculate_score();
recording.undo_changes();
if move_score > *current_score {
match &best {
Some((_, best_score)) if move_score > *best_score => {
best = Some((i, move_score));
}
None => {
best = Some((i, move_score));
}
_ => {}
}
}
}
best
}
impl_vnd_phase!(0: MS0);
impl_vnd_phase!(0: MS0, 1: MS1);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6);
impl_vnd_phase!(0: MS0, 1: MS1, 2: MS2, 3: MS3, 4: MS4, 5: MS5, 6: MS6, 7: MS7);
#[cfg(test)]
mod tests {
use super::*;
use crate::heuristic::r#move::ChangeMove;
use crate::heuristic::selector::ChangeMoveSelector;
use crate::test_utils::{
create_nqueens_director, get_queen_row, set_queen_row, NQueensSolution,
};
type NQueensMove = ChangeMove<NQueensSolution, i64>;
fn create_move_selector(
values: Vec<i64>,
) -> ChangeMoveSelector<
NQueensSolution,
i64,
crate::heuristic::selector::FromSolutionEntitySelector,
crate::heuristic::selector::StaticValueSelector<NQueensSolution, i64>,
> {
ChangeMoveSelector::simple(get_queen_row, set_queen_row, 0, "row", values)
}
#[test]
fn test_vnd_improves_solution() {
let director = create_nqueens_director(&[0, 0, 0, 0]);
let mut solver_scope = SolverScope::new(director);
let initial_score = solver_scope.calculate_score();
let values: Vec<i64> = (0..4).collect();
let mut phase: VndPhase<_, NQueensMove> = VndPhase::new((
create_move_selector(values.clone()),
create_move_selector(values),
));
phase.solve(&mut solver_scope);
let final_score = solver_scope.best_score().copied().unwrap_or(initial_score);
assert!(final_score >= initial_score);
}
#[test]
fn test_vnd_single_neighborhood() {
let director = create_nqueens_director(&[0, 0, 0, 0]);
let mut solver_scope = SolverScope::new(director);
let initial_score = solver_scope.calculate_score();
let values: Vec<i64> = (0..4).collect();
let mut phase: VndPhase<_, NQueensMove> = VndPhase::new((create_move_selector(values),));
phase.solve(&mut solver_scope);
let final_score = solver_scope.best_score().copied().unwrap_or(initial_score);
assert!(final_score >= initial_score);
}
}