From c7d027ecf955937649ef77c793dff08cfd4a56d5 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sat, 18 Oct 2025 14:37:03 +0200 Subject: [PATCH] feat: add new terminating conditions (or, lower than, maximum cycles) --- env/src/local_search/mod.rs | 10 ++ env/src/terminating/mod.rs | 257 ++++++++++++++++++++++++++++++------ 2 files changed, 230 insertions(+), 37 deletions(-) diff --git a/env/src/local_search/mod.rs b/env/src/local_search/mod.rs index 1fbd85286f4bf6c4a650ee98414aa42ac225a705..f2774fdc1e0502b7b5be9cc3cbf086e566790037 100644 --- a/env/src/local_search/mod.rs +++ b/env/src/local_search/mod.rs @@ -20,6 +20,16 @@ pub struct LocalSearchCandidate pub cycle: usize } +impl LocalSearchCandidate { + pub fn new(fit: TResult, pos: TInput, cycle: usize) -> Self { + Self { + fit, + pos, + cycle + } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct LocalSearchStats { stats: Vec> diff --git a/env/src/terminating/mod.rs b/env/src/terminating/mod.rs index fcbef0395fc0a83fc9b0bd671311f7356361988a..6654ce7abc135441bc181e35b3640d94a1170290 100644 --- a/env/src/terminating/mod.rs +++ b/env/src/terminating/mod.rs @@ -12,6 +12,132 @@ pub trait TerminatingCondition ) -> 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, @@ -114,51 +240,108 @@ where } } -pub struct NoBetterForCyclesTerminatingCondition { - cycles: usize -} +#[cfg(test)] +pub mod tests { + use crate::local_search::{LocalSearchCandidate, LocalSearchStats}; -impl NoBetterForCyclesTerminatingCondition { - pub fn new(cycles: usize) -> Self { - Self { - cycles - } + 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)); } -} -pub struct AndTerminatingConditions<'a, TInput, TResult> { - terminating_conditions: Vec<&'a mut dyn TerminatingCondition> -} + #[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)); + } -impl<'a, TInput, TResult> AndTerminatingConditions<'a, TInput, TResult> { - pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition>) -> Self { - Self { - terminating_conditions + #[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<'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) - ) + 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] + } } -} -impl TerminatingCondition for NoBetterForCyclesTerminatingCondition { - fn should_terminate ( - self: &mut Self, - candidate: &LocalSearchCandidate, - _: &LocalSearchStats, - cycle: usize - ) -> bool { - (cycle - candidate.cycle) > self.cycles + #[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)); } }