use std::fmt::Debug;
use solverforge_core::domain::PlanningSolution;
use super::Acceptor;
pub struct StepCountingHillClimbingAcceptor<S: PlanningSolution> {
step_count_limit: u64,
steps_since_improvement: u64,
best_score: Option<S::Score>,
}
impl<S: PlanningSolution> Debug for StepCountingHillClimbingAcceptor<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StepCountingHillClimbingAcceptor")
.field("step_count_limit", &self.step_count_limit)
.field("steps_since_improvement", &self.steps_since_improvement)
.finish()
}
}
impl<S: PlanningSolution> Clone for StepCountingHillClimbingAcceptor<S> {
fn clone(&self) -> Self {
Self {
step_count_limit: self.step_count_limit,
steps_since_improvement: self.steps_since_improvement,
best_score: self.best_score,
}
}
}
impl<S: PlanningSolution> StepCountingHillClimbingAcceptor<S> {
pub fn new(step_count_limit: u64) -> Self {
Self {
step_count_limit,
steps_since_improvement: 0,
best_score: None,
}
}
}
impl<S: PlanningSolution> Default for StepCountingHillClimbingAcceptor<S> {
fn default() -> Self {
Self::new(100)
}
}
impl<S: PlanningSolution> Acceptor<S> for StepCountingHillClimbingAcceptor<S> {
fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
if move_score > last_step_score {
return true;
}
self.steps_since_improvement < self.step_count_limit
}
fn phase_started(&mut self, initial_score: &S::Score) {
self.best_score = Some(*initial_score);
self.steps_since_improvement = 0;
}
fn step_ended(&mut self, step_score: &S::Score) {
let improved = match &self.best_score {
Some(best) => step_score > best,
None => true,
};
if improved {
self.best_score = Some(*step_score);
self.steps_since_improvement = 0;
} else {
self.steps_since_improvement += 1;
}
}
fn phase_ended(&mut self) {
self.best_score = None;
self.steps_since_improvement = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use solverforge_core::score::SoftScore;
#[derive(Clone)]
struct TestSolution {
score: Option<SoftScore>,
}
impl PlanningSolution for TestSolution {
type Score = SoftScore;
fn score(&self) -> Option<Self::Score> {
self.score
}
fn set_score(&mut self, score: Option<Self::Score>) {
self.score = score;
}
}
#[test]
fn test_accepts_improving_moves() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(5);
acceptor.phase_started(&SoftScore::of(-100));
assert!(acceptor.is_accepted(&SoftScore::of(-100), &SoftScore::of(-50)));
}
#[test]
fn test_accepts_non_improving_within_limit() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(5);
acceptor.phase_started(&SoftScore::of(-100));
assert!(acceptor.is_accepted(&SoftScore::of(-100), &SoftScore::of(-110)));
}
#[test]
fn test_rejects_after_limit_exceeded() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
acceptor.phase_started(&SoftScore::of(-100));
assert!(acceptor.is_accepted(&SoftScore::of(-100), &SoftScore::of(-110))); acceptor.step_ended(&SoftScore::of(-110));
assert!(acceptor.is_accepted(&SoftScore::of(-110), &SoftScore::of(-120))); acceptor.step_ended(&SoftScore::of(-120));
assert!(acceptor.is_accepted(&SoftScore::of(-120), &SoftScore::of(-130))); acceptor.step_ended(&SoftScore::of(-130));
assert!(!acceptor.is_accepted(&SoftScore::of(-130), &SoftScore::of(-140)));
}
#[test]
fn test_resets_on_improvement() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-110));
acceptor.step_ended(&SoftScore::of(-120));
assert_eq!(acceptor.steps_since_improvement, 2);
acceptor.step_ended(&SoftScore::of(-50));
assert_eq!(acceptor.steps_since_improvement, 0);
acceptor.step_ended(&SoftScore::of(-60));
acceptor.step_ended(&SoftScore::of(-70));
assert!(acceptor.is_accepted(&SoftScore::of(-70), &SoftScore::of(-80)));
}
#[test]
fn test_improving_always_accepted_even_after_limit() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(2);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-110));
acceptor.step_ended(&SoftScore::of(-120));
assert!(!acceptor.is_accepted(&SoftScore::of(-120), &SoftScore::of(-130)));
assert!(acceptor.is_accepted(&SoftScore::of(-120), &SoftScore::of(-50)));
}
#[test]
fn test_phase_reset() {
let mut acceptor = StepCountingHillClimbingAcceptor::<TestSolution>::new(3);
acceptor.phase_started(&SoftScore::of(-100));
acceptor.step_ended(&SoftScore::of(-110));
acceptor.step_ended(&SoftScore::of(-120));
acceptor.phase_ended();
acceptor.phase_started(&SoftScore::of(-200));
assert_eq!(acceptor.steps_since_improvement, 0);
}
}