use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
use solverforge_core::Score;
use super::Acceptor;
pub struct DiversifiedLateAcceptanceAcceptor<S: PlanningSolution> {
late_acceptance_size: usize,
score_history: Vec<Option<S::Score>>,
current_index: usize,
best_score: Option<S::Score>,
tolerance: f64,
}
impl<S: PlanningSolution> Debug for DiversifiedLateAcceptanceAcceptor<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DiversifiedLateAcceptanceAcceptor")
.field("late_acceptance_size", &self.late_acceptance_size)
.field("current_index", &self.current_index)
.field("tolerance", &self.tolerance)
.finish()
}
}
impl<S: PlanningSolution> Clone for DiversifiedLateAcceptanceAcceptor<S> {
fn clone(&self) -> Self {
Self {
late_acceptance_size: self.late_acceptance_size,
score_history: self.score_history.clone(),
current_index: self.current_index,
best_score: self.best_score,
tolerance: self.tolerance,
}
}
}
impl<S: PlanningSolution> DiversifiedLateAcceptanceAcceptor<S> {
pub fn new(late_acceptance_size: usize, tolerance: f64) -> Self {
assert!(
late_acceptance_size > 0,
"late_acceptance_size must be > 0, got 0"
);
Self {
late_acceptance_size,
score_history: vec![None; late_acceptance_size],
current_index: 0,
best_score: None,
tolerance,
}
}
pub fn with_default_tolerance(late_acceptance_size: usize) -> Self {
Self::new(late_acceptance_size, 0.01)
}
}
impl<S: PlanningSolution> Default for DiversifiedLateAcceptanceAcceptor<S> {
fn default() -> Self {
Self::new(400, 0.01)
}
}
impl<S: PlanningSolution> Acceptor<S> for DiversifiedLateAcceptanceAcceptor<S> {
fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
if move_score >= last_step_score {
return true;
}
if let Some(late_score) = &self.score_history[self.current_index] {
if move_score >= late_score {
return true;
}
} else {
return true;
}
if let Some(best) = &self.best_score {
let abs_best = best.abs();
let threshold = *best - abs_best.multiply(self.tolerance);
if move_score >= &threshold {
return true;
}
}
false
}
fn phase_started(&mut self, initial_score: &S::Score) {
for slot in &mut self.score_history {
*slot = Some(*initial_score);
}
self.current_index = 0;
self.best_score = Some(*initial_score);
}
fn step_ended(&mut self, step_score: &S::Score) {
if let Some(best) = &self.best_score {
if step_score > best {
self.best_score = Some(*step_score);
}
} else {
self.best_score = Some(*step_score);
}
self.score_history[self.current_index] = Some(*step_score);
self.current_index = (self.current_index + 1) % self.late_acceptance_size;
}
}
#[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_accepts_improving_moves() {
let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(5, 0.1);
acceptor.phase_started(&SoftScore::of(-100));
assert!(acceptor.is_accepted(&SoftScore::of(-100), &SoftScore::of(-90)));
}
#[test]
fn test_accepts_late_equal() {
let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
acceptor.phase_started(&SoftScore::of(-100));
assert!(acceptor.is_accepted(&SoftScore::of(-90), &SoftScore::of(-100)));
}
#[test]
fn test_diversification_accepts_within_tolerance() {
let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-80));
acceptor.step_ended(&SoftScore::of(-70));
acceptor.step_ended(&SoftScore::of(-60));
assert!(acceptor.is_accepted(&SoftScore::of(-60), &SoftScore::of(-65)));
}
#[test]
fn test_rejects_outside_tolerance() {
let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.05);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-40));
acceptor.step_ended(&SoftScore::of(-40));
acceptor.step_ended(&SoftScore::of(-40));
assert!(!acceptor.is_accepted(&SoftScore::of(-40), &SoftScore::of(-50)));
}
#[test]
fn test_history_cycles() {
let mut acceptor = DiversifiedLateAcceptanceAcceptor::<TestSolution>::new(3, 0.1);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-80));
acceptor.step_ended(&SoftScore::of(-70));
acceptor.step_ended(&SoftScore::of(-60));
assert!(acceptor.is_accepted(&SoftScore::of(-60), &SoftScore::of(-75)));
}
}