use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
use solverforge_core::Score;
use super::Acceptor;
pub struct MoveTabuAcceptor {
move_tabu_size: usize,
move_tabu_list: Vec<u64>,
current_step_move: Option<u64>,
aspiration_enabled: bool,
best_score: Option<i64>,
}
impl Debug for MoveTabuAcceptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MoveTabuAcceptor")
.field("move_tabu_size", &self.move_tabu_size)
.field("tabu_list_len", &self.move_tabu_list.len())
.field("aspiration_enabled", &self.aspiration_enabled)
.finish()
}
}
impl Clone for MoveTabuAcceptor {
fn clone(&self) -> Self {
Self {
move_tabu_size: self.move_tabu_size,
move_tabu_list: self.move_tabu_list.clone(),
current_step_move: self.current_step_move,
aspiration_enabled: self.aspiration_enabled,
best_score: self.best_score,
}
}
}
impl MoveTabuAcceptor {
pub fn new(move_tabu_size: usize) -> Self {
assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
Self {
move_tabu_size,
move_tabu_list: Vec::with_capacity(move_tabu_size),
current_step_move: None,
aspiration_enabled: true,
best_score: None,
}
}
pub fn without_aspiration(move_tabu_size: usize) -> Self {
assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
Self {
move_tabu_size,
move_tabu_list: Vec::with_capacity(move_tabu_size),
current_step_move: None,
aspiration_enabled: false,
best_score: None,
}
}
pub fn record_move(&mut self, move_hash: u64) {
self.current_step_move = Some(move_hash);
}
pub fn is_move_tabu(&self, move_hash: u64) -> bool {
self.move_tabu_list.contains(&move_hash)
}
pub fn aspiration_enabled(&self) -> bool {
self.aspiration_enabled
}
fn score_to_i64<S: PlanningSolution>(score: &S::Score) -> i64 {
let levels = score.to_level_numbers();
*levels.last().unwrap_or(&0)
}
}
impl Default for MoveTabuAcceptor {
fn default() -> Self {
Self::new(7)
}
}
impl<S: PlanningSolution> Acceptor<S> for MoveTabuAcceptor {
fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
if self.aspiration_enabled {
if let Some(best) = self.best_score {
let move_value = Self::score_to_i64::<S>(move_score);
if move_value > best {
return true; }
}
}
if move_score > last_step_score {
return true;
}
if move_score >= last_step_score {
return true;
}
false
}
fn phase_started(&mut self, initial_score: &S::Score) {
self.move_tabu_list.clear();
self.current_step_move = None;
self.best_score = Some(Self::score_to_i64::<S>(initial_score));
}
fn phase_ended(&mut self) {
self.move_tabu_list.clear();
}
fn step_started(&mut self) {
self.current_step_move = None;
}
fn step_ended(&mut self, step_score: &S::Score) {
if let Some(move_hash) = self.current_step_move {
if self.move_tabu_list.len() >= self.move_tabu_size {
self.move_tabu_list.remove(0);
}
self.move_tabu_list.push(move_hash);
}
self.current_step_move = None;
let step_value = Self::score_to_i64::<S>(step_score);
if let Some(best) = self.best_score {
if step_value > best {
self.best_score = Some(step_value);
}
} else {
self.best_score = Some(step_value);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use solverforge_core::score::SoftScore;
#[derive(Clone)]
struct TestSolution;
impl PlanningSolution for TestSolution {
type Score = SoftScore;
fn score(&self) -> Option<Self::Score> {
None
}
fn set_score(&mut self, _: Option<Self::Score>) {}
}
#[test]
fn test_new_acceptor() {
let acceptor = MoveTabuAcceptor::new(5);
assert!(!acceptor.is_move_tabu(42));
assert!(acceptor.aspiration_enabled());
}
#[test]
fn test_without_aspiration() {
let acceptor = MoveTabuAcceptor::without_aspiration(5);
assert!(!acceptor.aspiration_enabled());
}
#[test]
fn test_record_and_check() {
let mut acceptor = MoveTabuAcceptor::new(5);
Acceptor::<TestSolution>::phase_started(&mut acceptor, &SoftScore::of(0));
acceptor.record_move(42);
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(acceptor.is_move_tabu(42));
assert!(!acceptor.is_move_tabu(99));
}
#[test]
fn test_tabu_expiration() {
let mut acceptor = MoveTabuAcceptor::new(2);
Acceptor::<TestSolution>::phase_started(&mut acceptor, &SoftScore::of(0));
acceptor.record_move(1);
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(acceptor.is_move_tabu(1));
Acceptor::<TestSolution>::step_started(&mut acceptor);
acceptor.record_move(2);
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(acceptor.is_move_tabu(1));
assert!(acceptor.is_move_tabu(2));
Acceptor::<TestSolution>::step_started(&mut acceptor);
acceptor.record_move(3);
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(!acceptor.is_move_tabu(1)); assert!(acceptor.is_move_tabu(2));
assert!(acceptor.is_move_tabu(3));
}
#[test]
fn test_accepts_improving_move() {
let mut acceptor = MoveTabuAcceptor::new(5);
let last_score = SoftScore::of(-10);
let move_score = SoftScore::of(-5);
assert!(Acceptor::<TestSolution>::is_accepted(
&mut acceptor,
&last_score,
&move_score
));
}
#[test]
fn test_phase_clears_tabu() {
let mut acceptor = MoveTabuAcceptor::new(5);
Acceptor::<TestSolution>::phase_started(&mut acceptor, &SoftScore::of(0));
acceptor.record_move(42);
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(acceptor.is_move_tabu(42));
Acceptor::<TestSolution>::phase_ended(&mut acceptor);
assert!(!acceptor.is_move_tabu(42));
}
#[test]
fn test_no_move_recorded_no_tabu() {
let mut acceptor = MoveTabuAcceptor::new(5);
Acceptor::<TestSolution>::phase_started(&mut acceptor, &SoftScore::of(0));
Acceptor::<TestSolution>::step_ended(&mut acceptor, &SoftScore::of(0));
assert!(!acceptor.is_move_tabu(42));
}
}