~ruther/ctu-fee-eoa

c7d027ecf955937649ef77c793dff08cfd4a56d5 — Rutherther a month ago c66539e
feat: add new terminating conditions (or, lower than, maximum cycles)
2 files changed, 230 insertions(+), 37 deletions(-)

M env/src/local_search/mod.rs
M env/src/terminating/mod.rs
M env/src/local_search/mod.rs => env/src/local_search/mod.rs +10 -0
@@ 20,6 20,16 @@ pub struct LocalSearchCandidate<TInput, TResult>
    pub cycle: usize
}

impl<TInput, TResult> LocalSearchCandidate<TInput, TResult> {
    pub fn new(fit: TResult, pos: TInput, cycle: usize) -> Self {
        Self {
            fit,
            pos,
            cycle
        }
    }
}

#[derive(Debug, Clone, PartialEq)]
pub struct LocalSearchStats<TInput, TResult> {
    stats: Vec<LocalSearchCandidate<TInput, TResult>>

M env/src/terminating/mod.rs => env/src/terminating/mod.rs +220 -37
@@ 12,6 12,132 @@ pub trait TerminatingCondition<TInput, TResult>
    ) -> 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,


@@ 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<TInput, TResult>>
}
    #[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<TInput, TResult>>) -> 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<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)
            )
    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]
        }
    }
}

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
    #[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));
    }
}