use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector}; use crate::local_search::{LocalSearchCandidate, LocalSearchStats}; pub trait TerminatingCondition { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, stats: &LocalSearchStats, cycle: usize ) -> bool; } // Generic terminating conditions that group multiple conditions pub struct AndTerminatingConditions<'a, TInput, TResult> { terminating_conditions: Vec<&'a mut dyn TerminatingCondition> } impl<'a, TInput, TResult> AndTerminatingConditions<'a, TInput, TResult> { pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition>) -> Self { Self { terminating_conditions } } pub fn conditions(&self) -> &Vec<&'a mut dyn TerminatingCondition> { &self.terminating_conditions } } impl<'a, TInput, TResult> TerminatingCondition for AndTerminatingConditions<'a, TInput, TResult> { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, stats: &LocalSearchStats, cycle: usize ) -> bool { return self.terminating_conditions.iter_mut() .all( |cond| cond.should_terminate(candidate, stats, cycle) ) } } pub struct OrTerminatingConditions<'a, TInput, TResult> { terminating_conditions: Vec<&'a mut dyn TerminatingCondition> } impl<'a, TInput, TResult> OrTerminatingConditions<'a, TInput, TResult> { pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition>) -> Self { Self { terminating_conditions } } pub fn conditions(&self) -> &Vec<&'a mut dyn TerminatingCondition> { &self.terminating_conditions } } impl<'a, TInput, TResult> TerminatingCondition for OrTerminatingConditions<'a, TInput, TResult> { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, stats: &LocalSearchStats, cycle: usize ) -> bool { return self.terminating_conditions.iter_mut() .any( |cond| cond.should_terminate(candidate, stats, cycle) ) } } // Simple terminating conditions based on the number of cycles pub struct NoBetterForCyclesTerminatingCondition { cycles: usize } impl NoBetterForCyclesTerminatingCondition { pub fn new(cycles: usize) -> Self { Self { cycles } } } impl TerminatingCondition for NoBetterForCyclesTerminatingCondition { fn should_terminate ( self: &mut Self, candidate: &LocalSearchCandidate, _: &LocalSearchStats, cycle: usize ) -> bool { (cycle - candidate.cycle) > self.cycles } } pub struct MaximumCyclesTerminatingCondition { cycles: usize } impl MaximumCyclesTerminatingCondition { pub fn new(cycles: usize) -> Self { Self { cycles } } } impl TerminatingCondition for MaximumCyclesTerminatingCondition { fn should_terminate ( self: &mut Self, _: &LocalSearchCandidate, _: &LocalSearchStats, cycle: usize ) -> bool { cycle > self.cycles } } // Terminating conditions based on the absolute fitness of the solution pub struct LowerThanTerminatingCondition { target: T } impl TerminatingCondition for LowerThanTerminatingCondition { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, _: &LocalSearchStats, _: usize ) -> bool { candidate.fit < self.target } } // The following conditions are mainly meant for test environment where the optimal result is known // beforehand. pub struct EqualTerminatingCondition { target: T, remember_match: bool, matched: bool, } impl EqualTerminatingCondition { pub fn new(target: T) -> Self { Self { target, remember_match: false, matched: false, } } pub fn new_remembered(target: T) -> Self { Self { target, remember_match: true, matched: false, } } pub fn reset_match(self: &mut Self) { self.matched = false; } } impl TerminatingCondition for EqualTerminatingCondition where TInput: Clone + PartialEq, TResult: Clone { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, _: &LocalSearchStats, _: usize ) -> bool { let matched = candidate.pos == self.target; if matched && self.remember_match { self.matched = true; } matched || self.matched } } pub struct CloserThanTerminatingCondition { target: T, max_distance: f64, remember_match: bool, matched: bool, } impl CloserThanTerminatingCondition { pub fn new(target: T, max_distance: f64) -> Self { Self { target, max_distance, remember_match: false, matched: false, } } pub fn new_remembered(target: T, max_distance: f64) -> Self { Self { target, max_distance, remember_match: true, matched: false, } } pub fn reset_match(self: &mut Self) { self.matched = false; } } impl TerminatingCondition, TResult> for CloserThanTerminatingCondition> where D: Dim, DefaultAllocator: Allocator, TResult: Clone, { fn should_terminate( self: &mut Self, candidate: &LocalSearchCandidate, TResult>, _: &LocalSearchStats, TResult>, _: usize ) -> bool { let matched = (&candidate.pos - &self.target).magnitude() < self.max_distance; if matched && self.remember_match { self.matched = true; } matched || self.matched } } #[cfg(test)] pub mod tests { use crate::local_search::{LocalSearchCandidate, LocalSearchStats}; use super::{AndTerminatingConditions, EqualTerminatingCondition, NoBetterForCyclesTerminatingCondition, TerminatingCondition}; #[test] fn test_no_better_for_cycles() { let mut no_better_for_cycles = NoBetterForCyclesTerminatingCondition::new(10); let stats = LocalSearchStats::new(); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 0), &stats, 0)); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 0), &stats, 1)); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 0), &stats, 2)); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 0), &stats, 10)); assert!(no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 0), &stats, 11)); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 10), &stats, 10)); assert!(!no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 10), &stats, 20)); assert!(no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 10), &stats, 22)); assert!(no_better_for_cycles.should_terminate(&LocalSearchCandidate::new((), (), 50), &stats, 126)); } #[test] fn test_equal() { let mut equal = EqualTerminatingCondition::new(10); let stats = LocalSearchStats::new(); assert!(!equal.should_terminate(&LocalSearchCandidate::new((), 5, 0), &stats, 0)); assert!(equal.should_terminate(&LocalSearchCandidate::new((), 10, 0), &stats, 0)); assert!(!equal.should_terminate(&LocalSearchCandidate::new((), 11, 0), &stats, 0)); } #[test] fn test_equal_remembering() { let mut equal = EqualTerminatingCondition::new_remembered(10); let stats = LocalSearchStats::new(); assert!(!equal.should_terminate(&LocalSearchCandidate::new((), 5, 0), &stats, 0)); assert!(equal.should_terminate(&LocalSearchCandidate::new((), 10, 0), &stats, 0)); assert!(equal.should_terminate(&LocalSearchCandidate::new((), 11, 0), &stats, 0)); equal.reset_match(); assert!(!equal.should_terminate(&LocalSearchCandidate::new((), 11, 0), &stats, 0)); assert!(equal.should_terminate(&LocalSearchCandidate::new((), 10, 0), &stats, 0)); } struct DummyTerminatingCondition { idx: usize } impl DummyTerminatingCondition { fn new(idx: usize) -> Self { Self { idx } } } impl TerminatingCondition, ()> for DummyTerminatingCondition { fn should_terminate( self: &mut Self, candidate: &crate::local_search::LocalSearchCandidate, ()>, _: &crate::local_search::LocalSearchStats, ()>, _: usize ) -> bool { candidate.pos[self.idx] } } #[test] fn test_and() { let mut first_dummy = DummyTerminatingCondition::new(0); let mut second_dummy = DummyTerminatingCondition::new(1); let mut and_cond = AndTerminatingConditions::, ()>::new(vec![&mut first_dummy, &mut second_dummy]); let stats = LocalSearchStats::new(); let mut terminates = vec![false, false]; assert_eq!(and_cond.conditions().len(), 2); assert!(!and_cond.should_terminate(&LocalSearchCandidate::new((), terminates.clone(), 0), &stats, 0)); terminates[0] = true; terminates[1] = true; assert!(and_cond.should_terminate(&LocalSearchCandidate::new((), terminates.clone(), 0), &stats, 0)); terminates[0] = true; terminates[1] = false; assert!(!and_cond.should_terminate(&LocalSearchCandidate::new((), terminates.clone(), 0), &stats, 0)); terminates[0] = false; terminates[1] = true; assert!(!and_cond.should_terminate(&LocalSearchCandidate::new((), terminates.clone(), 0), &stats, 0)); terminates[0] = false; terminates[1] = false; assert!(!and_cond.should_terminate(&LocalSearchCandidate::new((), terminates.clone(), 0), &stats, 0)); } }