From ef07dac05f823f189763ad0e33c1d64cb1a1ca49 Mon Sep 17 00:00:00 2001 From: Rutherther Date: Fri, 10 Oct 2025 17:20:37 +0200 Subject: [PATCH] feat: add evolutionary strategies to local search --- env/src/evolutionary_strategy.rs | 65 +++++++++++++++++++++++ env/src/local_search/mod.rs | 90 ++++++++++++++++++++++++++++++-- env/src/main.rs | 1 + env/src/perturbation/mod.rs | 4 ++ 4 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 env/src/evolutionary_strategy.rs diff --git a/env/src/evolutionary_strategy.rs b/env/src/evolutionary_strategy.rs new file mode 100644 index 0000000000000000000000000000000000000000..d235f57bde2253d85d0e7cbfb4369d3720d4a31b --- /dev/null +++ b/env/src/evolutionary_strategy.rs @@ -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 { + type Err; + + fn step(&mut self, + perturbation: &mut TPerturbation, + better: bool, + stats: &Vec> + ) -> Result<(), Self::Err>; +} + +fn normal_one_to_five(perturbation: &mut RandomDistributionPerturbation>, 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 EvolutionaryStrategy>> for OneToFiveStrategy { + type Err = NormalError; + + fn step(&mut self, + perturbation: &mut RandomDistributionPerturbation>, + better: bool, + _: &Vec, TOut>> + ) -> Result<(), Self::Err> { + normal_one_to_five(perturbation, better) + } +} + +impl EvolutionaryStrategy>>> for OneToFiveStrategy { + type Err = NormalError; + + fn step(&mut self, + perturbation: &mut BoundedPerturbation>>, + better: bool, + _: &Vec>> as PerturbationOperator>::Chromosome, TOut>> + ) -> Result<(), Self::Err> { + normal_one_to_five(perturbation.inner_mut(), better) + } + +} + +pub struct IdentityStrategy; +impl EvolutionaryStrategy for IdentityStrategy { + type Err = Infallible; + + fn step(&mut self, + _: &mut TPerturbation, + _: bool, + _: &Vec> + ) -> Result<(), Self::Err> { + Ok(()) + } +} diff --git a/env/src/local_search/mod.rs b/env/src/local_search/mod.rs index 157f5b50206b61a1a7549f91bb519ccc3d26958c..b8eddd97753cceba88cee8b14f0f946cd719423e 100644 --- a/env/src/local_search/mod.rs +++ b/env/src/local_search/mod.rs @@ -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; @@ -42,6 +42,35 @@ where TTerminatingCondition: TerminatingCondition, TPerturbationOperator: PerturbationOperator, TBetterThanOperator: BetterThanOperator, +{ + 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, TErr> +where + TResult: Clone, + TInput: Clone, + TFit: FitnessFunction, + TTerminatingCondition: TerminatingCondition, + TPerturbationOperator: PerturbationOperator, + TEvolutionaryStrategy: EvolutionaryStrategy, + TBetterThanOperator: BetterThanOperator, + >::Err: Debug { let mut best_candidate = LocalSearchCandidate { pos: initial.clone(), @@ -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::::from_vec(vec![-10.0, 10.0]); + let max = SVector::::from_vec(vec![10.0, 10.0]); + let min = -SVector::::from_vec(vec![10.0, 10.0]); + + let linear = Linear::new(7.0, SVector::::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::::zeros(), + ).unwrap(); + + println!("{:?}", result); + + assert_eq!( + result.best_candidate.fit, + -0.0 + ); + + assert_eq!( + result.best_candidate.pos, + optimum + ); + } } diff --git a/env/src/main.rs b/env/src/main.rs index 725fbe6defc500d97e77e590678d0845b440ee36..5f502741379018bae7842ff6a07973e64d2f12bb 100644 --- a/env/src/main.rs +++ b/env/src/main.rs @@ -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; diff --git a/env/src/perturbation/mod.rs b/env/src/perturbation/mod.rs index d60530d8cb9b0810d6af655d74f67ea929cf2e40..74a9ee43d7c15206fecee24da613bad86a4145e0 100644 --- a/env/src/perturbation/mod.rs +++ b/env/src/perturbation/mod.rs @@ -153,6 +153,10 @@ impl>> } } + pub fn inner_mut(&mut self) -> &mut T { + &mut self.perturbation + } + fn within_bounds(&self, chromosome: &SVector) -> bool { chromosome.iter() .zip(self.min_max.iter())