~ruther/ctu-fee-eoa

ef07dac05f823f189763ad0e33c1d64cb1a1ca49 — Rutherther 2 months ago ba97623
feat: add evolutionary strategies to local search
A env/src/evolutionary_strategy.rs => env/src/evolutionary_strategy.rs +65 -0
@@ 0,0 1,65 @@
use std::convert::Infallible;

use nalgebra::SVector;
use rand_distr::{Normal, NormalError};

use crate::{local_search::LocalSearchCandidate, perturbation::{BoundedPerturbation, PerturbationOperator, RandomDistributionPerturbation}};

pub trait EvolutionaryStrategy<TOut, TPerturbation: PerturbationOperator> {
    type Err;

    fn step(&mut self,
            perturbation: &mut TPerturbation,
            better: bool,
            stats: &Vec<LocalSearchCandidate<TPerturbation::Chromosome, TOut>>
    ) -> Result<(), Self::Err>;
}

fn normal_one_to_five<const LEN: usize>(perturbation: &mut RandomDistributionPerturbation<LEN, Normal<f64>>, better: bool) -> Result<(), NormalError> {
    let exp: f64 = if better { 1.0 } else { 0.0 } - 0.2;
    let sigma = perturbation.std_dev();

    let new_sigma = sigma * exp.exp().powf(1.0 / LEN as f64);

    perturbation.set_std_dev(new_sigma)?;
    Ok(())
}

pub struct OneToFiveStrategy;
impl<const LEN: usize, TOut> EvolutionaryStrategy<TOut, RandomDistributionPerturbation<LEN, Normal<f64>>> for OneToFiveStrategy {
    type Err = NormalError;

    fn step(&mut self,
            perturbation: &mut RandomDistributionPerturbation<LEN, Normal<f64>>,
            better: bool,
            _: &Vec<LocalSearchCandidate<SVector::<f64, LEN>, TOut>>
    ) -> Result<(), Self::Err> {
        normal_one_to_five(perturbation, better)
    }
}

impl<const LEN: usize, TOut> EvolutionaryStrategy<TOut, BoundedPerturbation<LEN, RandomDistributionPerturbation<LEN, Normal<f64>>>> for OneToFiveStrategy {
    type Err = NormalError;

    fn step(&mut self,
            perturbation: &mut BoundedPerturbation<LEN, RandomDistributionPerturbation<LEN, Normal<f64>>>,
            better: bool,
            _: &Vec<LocalSearchCandidate<<BoundedPerturbation<LEN, RandomDistributionPerturbation<LEN, Normal<f64>>> as PerturbationOperator>::Chromosome, TOut>>
    ) -> Result<(), Self::Err> {
        normal_one_to_five(perturbation.inner_mut(), better)
    }

}

pub struct IdentityStrategy;
impl<TOut, TPerturbation: PerturbationOperator> EvolutionaryStrategy<TOut, TPerturbation> for IdentityStrategy {
    type Err = Infallible;

    fn step(&mut self,
            _: &mut TPerturbation,
            _: bool,
            _: &Vec<LocalSearchCandidate<TPerturbation::Chromosome, TOut>>
    ) -> Result<(), Self::Err> {
        Ok(())
    }
}

M env/src/local_search/mod.rs => env/src/local_search/mod.rs +85 -5
@@ 1,5 1,5 @@
use crate::binary_string::BinaryString;

use std::fmt::Debug;
use crate::evolutionary_strategy::{EvolutionaryStrategy, IdentityStrategy};
use crate::fitness::FitnessFunction;
use crate::terminating::TerminatingCondition;
use crate::perturbation::PerturbationOperator;


@@ 43,6 43,35 @@ where
    TPerturbationOperator: PerturbationOperator<Chromosome = TInput>,
    TBetterThanOperator: BetterThanOperator<TResult>,
{
    local_search_first_improving_evolving(
        fit,
        terminating_condition,
        perturbation_operator,
        better_than_operator,
        &mut IdentityStrategy,
        initial
    )
}

fn local_search_first_improving_evolving<
        TInput, TResult, TErr, TFit, TTerminatingCondition, TPerturbationOperator, TBetterThanOperator, TEvolutionaryStrategy>(
    fit: &TFit,
    terminating_condition: &mut TTerminatingCondition,
    perturbation_operator: &mut TPerturbationOperator,
    better_than_operator: &TBetterThanOperator,
    evolutionary_strategy: &mut TEvolutionaryStrategy,
    initial: &TInput
) -> Result<LocalSearchResult<TInput, TResult>, TErr>
where
    TResult: Clone,
    TInput: Clone,
    TFit: FitnessFunction<In = TInput, Out = TResult, Err = TErr>,
    TTerminatingCondition: TerminatingCondition<TInput, TResult>,
    TPerturbationOperator: PerturbationOperator<Chromosome = TInput>,
    TEvolutionaryStrategy: EvolutionaryStrategy<TResult, TPerturbationOperator>,
    TBetterThanOperator: BetterThanOperator<TResult>,
    <TEvolutionaryStrategy as EvolutionaryStrategy<TResult, TPerturbationOperator>>::Err: Debug
{
    let mut best_candidate = LocalSearchCandidate {
        pos: initial.clone(),
        fit: fit.fit(&initial)?,


@@ 57,7 86,7 @@ where
        let perturbed_fit = fit.fit(&perturbed)?;

        // Minimize
        if better_than_operator.better_than(&perturbed_fit, &best_candidate.fit) {
        let better = if better_than_operator.better_than(&perturbed_fit, &best_candidate.fit) {
            best_candidate = LocalSearchCandidate {
                pos: perturbed.clone(),
                fit: perturbed_fit,


@@ 65,7 94,18 @@ where
            };

            stats.push(best_candidate.clone());
        }

            true
        } else {
            false
        };

        evolutionary_strategy.step(
            perturbation_operator,
            better,
            &stats)
        // TODO
            .expect("Evolution failed.");

        cycle += 1;
    }


@@ 81,7 121,7 @@ where
pub mod tests {
    use nalgebra::SVector;

    use crate::{binary_string::{BinaryString, Bounds}, comparison::MinimizingOperator, fitness::{one_max::OneMax, real::Linear, rosenbrock::Rosenbrock, sphere::Sphere, BinaryFitnessWrapper}, local_search::local_search_first_improving, perturbation::{BinaryStringBitPerturbation, BoundedPerturbation, BoundedPerturbationStrategy, PatternPerturbation, RandomDistributionPerturbation}, terminating::{AndTerminatingConditions, EqualTerminatingCondition, NoBetterForCyclesTerminatingCondition}};
    use crate::{binary_string::{BinaryString, Bounds}, comparison::MinimizingOperator, evolutionary_strategy::OneToFiveStrategy, fitness::{one_max::OneMax, real::Linear, rosenbrock::Rosenbrock, sphere::Sphere, BinaryFitnessWrapper}, local_search::{local_search_first_improving, local_search_first_improving_evolving}, perturbation::{BinaryStringBitPerturbation, BoundedPerturbation, BoundedPerturbationStrategy, PatternPerturbation, RandomDistributionPerturbation}, terminating::{AndTerminatingConditions, EqualTerminatingCondition, NoBetterForCyclesTerminatingCondition}};

    #[test]
    fn test_local_search_sphere() {


@@ 295,4 335,44 @@ pub mod tests {
            optimum
        );
    }

    #[test]
    fn test_local_search_linear_onetofive() {
        let optimum = SVector::<f64, 2>::from_vec(vec![-10.0, 10.0]);
        let max = SVector::<f64, 2>::from_vec(vec![10.0, 10.0]);
        let min = -SVector::<f64, 2>::from_vec(vec![10.0, 10.0]);

        let linear = Linear::new(7.0, SVector::<f64, 2>::from_vec(vec![0.2, -0.5]));

        let result = local_search_first_improving_evolving(
            &linear,
            &mut
                AndTerminatingConditions::new(
                    vec![
                        &mut EqualTerminatingCondition::new_remembered(optimum.clone()),
                        &mut NoBetterForCyclesTerminatingCondition::new(100)
                    ]
                ),
            &mut BoundedPerturbation::new(
                RandomDistributionPerturbation::normal(0.5).unwrap(),
                min,
                max,
                BoundedPerturbationStrategy::Retry(10)),
            &MinimizingOperator::new(),
            &mut OneToFiveStrategy,
            &SVector::<f64, 2>::zeros(),
        ).unwrap();

        println!("{:?}", result);

        assert_eq!(
            result.best_candidate.fit,
            -0.0
        );

        assert_eq!(
            result.best_candidate.pos,
            optimum
        );
    }
}

M env/src/main.rs => env/src/main.rs +1 -0
@@ 4,6 4,7 @@ pub mod perturbation;
pub mod comparison;
pub mod local_search;
pub mod binary_string;
pub mod evolutionary_strategy;

#[cfg(test)]
mod test_infra;

M env/src/perturbation/mod.rs => env/src/perturbation/mod.rs +4 -0
@@ 153,6 153,10 @@ impl<const LEN: usize, T: PerturbationOperator<Chromosome = SVector<f64, LEN>>> 
        }
    }

    pub fn inner_mut(&mut self) -> &mut T {
        &mut self.perturbation
    }

    fn within_bounds(&self, chromosome: &SVector<f64, LEN>) -> bool {
        chromosome.iter()
            .zip(self.min_max.iter())