use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, OVector};
use crate::local_search::{LocalSearchCandidate, LocalSearchStats};
pub trait TerminatingCondition<TInput, TResult>
{
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
stats: &LocalSearchStats<TInput, TResult>,
cycle: usize
) -> bool;
}
// Generic terminating conditions that group multiple conditions
pub struct AndTerminatingConditions<'a, TInput, TResult> {
terminating_conditions: Vec<&'a mut dyn TerminatingCondition<TInput, TResult>>
}
impl<'a, TInput, TResult> AndTerminatingConditions<'a, TInput, TResult> {
pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition<TInput, TResult>>) -> Self {
Self {
terminating_conditions
}
}
pub fn conditions(&self) -> &Vec<&'a mut dyn TerminatingCondition<TInput, TResult>> {
&self.terminating_conditions
}
}
impl<'a, TInput, TResult> TerminatingCondition<TInput, TResult> for AndTerminatingConditions<'a, TInput, TResult> {
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
stats: &LocalSearchStats<TInput, TResult>,
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<TInput, TResult>>
}
impl<'a, TInput, TResult> OrTerminatingConditions<'a, TInput, TResult> {
pub fn new(terminating_conditions: Vec<&'a mut dyn TerminatingCondition<TInput, TResult>>) -> Self {
Self {
terminating_conditions
}
}
pub fn conditions(&self) -> &Vec<&'a mut dyn TerminatingCondition<TInput, TResult>> {
&self.terminating_conditions
}
}
impl<'a, TInput, TResult> TerminatingCondition<TInput, TResult> for OrTerminatingConditions<'a, TInput, TResult> {
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
stats: &LocalSearchStats<TInput, TResult>,
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<TInput, TResult> TerminatingCondition<TInput, TResult> for NoBetterForCyclesTerminatingCondition {
fn should_terminate (
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
_: &LocalSearchStats<TInput, TResult>,
cycle: usize
) -> bool {
(cycle - candidate.cycle) > self.cycles
}
}
pub struct MaximumCyclesTerminatingCondition {
cycles: usize
}
impl MaximumCyclesTerminatingCondition {
pub fn new(cycles: usize) -> Self {
Self {
cycles
}
}
}
impl<TInput, TResult> TerminatingCondition<TInput, TResult> for MaximumCyclesTerminatingCondition {
fn should_terminate (
self: &mut Self,
_: &LocalSearchCandidate<TInput, TResult>,
_: &LocalSearchStats<TInput, TResult>,
cycle: usize
) -> bool {
cycle > self.cycles
}
}
// Terminating conditions based on the absolute fitness of the solution
pub struct LowerThanTerminatingCondition<T: PartialOrd> {
target: T
}
impl<TInput, TResult: PartialOrd> TerminatingCondition<TInput, TResult> for LowerThanTerminatingCondition<TResult> {
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
_: &LocalSearchStats<TInput, TResult>,
_: 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<T> {
target: T,
remember_match: bool,
matched: bool,
}
impl<T> EqualTerminatingCondition<T> {
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<TInput, TResult> TerminatingCondition<TInput, TResult> for EqualTerminatingCondition<TInput>
where
TInput: Clone + PartialEq,
TResult: Clone
{
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<TInput, TResult>,
_: &LocalSearchStats<TInput, TResult>,
_: usize
) -> bool {
let matched = candidate.pos == self.target;
if matched && self.remember_match {
self.matched = true;
}
matched || self.matched
}
}
pub struct CloserThanTerminatingCondition<T> {
target: T,
max_distance: f64,
remember_match: bool,
matched: bool,
}
impl<T> CloserThanTerminatingCondition<T> {
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<D, TResult> TerminatingCondition<OVector<f64, D>, TResult> for CloserThanTerminatingCondition<OVector<f64, D>>
where
D: Dim,
DefaultAllocator: Allocator<D>,
TResult: Clone,
{
fn should_terminate(
self: &mut Self,
candidate: &LocalSearchCandidate<OVector<f64, D>, TResult>,
_: &LocalSearchStats<OVector<f64, D>, 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<Vec<bool>, ()> for DummyTerminatingCondition {
fn should_terminate(
self: &mut Self,
candidate: &crate::local_search::LocalSearchCandidate<Vec<bool>, ()>,
_: &crate::local_search::LocalSearchStats<Vec<bool>, ()>,
_: 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::<Vec<bool>, ()>::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));
}
}