From 78b8de609ffd9254f1d27d20ab14d6a1b66282d6 Mon Sep 17 00:00:00 2001 From: Kacper Kafara Date: Sat, 4 May 2024 14:18:17 +0200 Subject: [PATCH] feat!: allow for using adaptive values with operators by introducing ValueProvider trait (#482) - **Add ValueProvider trait** - **Migrate RouletteWheel** - **Migrate Random** - **Migrate Rank** - **Migrate RankR** - **Migrate Tournament** - **Migrate SUS** - **Migrate Boltzmann** - **Update docs** - **Fix tests in selection impls module** - **Fix leftovers** ## Description ## Linked issues Closes #193 ## Important implementation details --- coco/src/main.rs | 4 +- examples/ga.rs | 6 +- examples/jssp/main.rs | 2 +- examples/jssp/problem/selection.rs | 1 - src/ga.rs | 9 +- src/ga/builder/bitstring.rs | 6 +- src/ga/builder/realvalued.rs | 6 +- src/ga/operators/selection.rs | 8 +- src/ga/operators/selection/impls.rs | 286 +++++++++++++++++----------- src/ga/value_provider.rs | 9 + src/ga/value_provider/flat.rs | 65 +++++++ src/pso/particle.rs | 2 +- src/pso/swarm.rs | 4 +- tests/builder_tests.rs | 8 +- tests/selection_tests.rs | 18 +- 15 files changed, 283 insertions(+), 151 deletions(-) create mode 100644 src/ga/value_provider.rs create mode 100644 src/ga/value_provider/flat.rs diff --git a/coco/src/main.rs b/coco/src/main.rs index f1dc63da..49583ef2 100644 --- a/coco/src/main.rs +++ b/coco/src/main.rs @@ -103,7 +103,7 @@ fn ecrs_ga_search(problem: &mut Problem, _max_budget: usize, _random_generator: RealValueIndividual, Reversing, Uniform, - Tournament, + Tournament, WeakParent, RandomPoints, adapter::CocoFitness, @@ -118,7 +118,7 @@ fn ecrs_ga_search(problem: &mut Problem, _max_budget: usize, _random_generator: dimension, constraints, )) - .set_selection_operator(selection::Tournament::new(0.2)) + .set_selection_operator(selection::Tournament::new(0.2, population_size)) .set_crossover_operator(crossover::Uniform::new()) .set_mutation_operator(mutation::Reversing::new(0.05)) .set_replacement_operator(replacement::WeakParent::new()) diff --git a/examples/ga.rs b/examples/ga.rs index e4e082dc..42d3da3b 100644 --- a/examples/ga.rs +++ b/examples/ga.rs @@ -20,7 +20,7 @@ fn main() { RealValueIndividual, Identity, SinglePoint, - Boltzmann, + Boltzmann, WeakParent, RandomPoints, FnBasedFitness, @@ -36,7 +36,9 @@ fn main() { 3, vec![-5.12..5.12, -5.12..5.12, -5.12..5.12], )) - .set_selection_operator(ga::operators::selection::Boltzmann::new(0.05, 80.0, 500, false)) + .set_selection_operator(ga::operators::selection::Boltzmann::new( + 100, 0.05, 80.0, 500, false, + )) .set_probe( ga::probe::AggregatedProbe::new() .add_probe(ga::probe::PolicyDrivenProbe::new( diff --git a/examples/jssp/main.rs b/examples/jssp/main.rs index 85bba7b8..a492097a 100644 --- a/examples/jssp/main.rs +++ b/examples/jssp/main.rs @@ -113,7 +113,7 @@ fn run_jssp_solver(instance: JsspInstance, config: Config) { // } ga::Builder::new() - .set_selection_operator(selection::Rank::new()) + .set_selection_operator(selection::Rank::new(pop_size)) .set_crossover_operator(JsspCrossover::new()) .set_mutation_operator(mutation::Identity::new()) .set_population_generator(JsspPopProvider::new(instance.clone())) diff --git a/examples/jssp/problem/selection.rs b/examples/jssp/problem/selection.rs index f1ad7d31..f8757fcf 100644 --- a/examples/jssp/problem/selection.rs +++ b/examples/jssp/problem/selection.rs @@ -15,7 +15,6 @@ impl SelectionOperator for EmptySelection { &mut self, _metrics: &ecrs::ga::Metrics, _population: &'a [JsspIndividual], - _count: usize, ) -> Vec<&'a JsspIndividual> { Vec::new() } diff --git a/src/ga.rs b/src/ga.rs index 47b7400e..71af3e0d 100644 --- a/src/ga.rs +++ b/src/ga.rs @@ -65,7 +65,7 @@ //! ga::individual::RealValueIndividual, //! mutation::Identity, //! crossover::SinglePoint, -//! selection::Boltzmann, +//! selection::Boltzmann, //! replacement::WeakParent, //! population::RandomPoints, //! fitness::FnBasedFitness, @@ -81,7 +81,7 @@ //! 3, //! vec![-5.12..5.12, -5.12..5.12, -5.12..5.12], //! )) -//! .set_selection_operator(ga::operators::selection::Boltzmann::new(0.05, 80.0, 500, false)) +//! .set_selection_operator(ga::operators::selection::Boltzmann::new(100, 0.05, 80.0, 500, false)) //! .set_probe( //! ga::probe::AggregatedProbe::new() //! .add_probe(ga::probe::PolicyDrivenProbe::new( @@ -122,6 +122,7 @@ pub mod operators; pub mod population; pub mod probe; pub(crate) mod timer; +pub mod value_provider; use crate::ga::operators::fitness::Fitness; pub use builder::*; @@ -308,9 +309,7 @@ where // 4. Create mating pool by applying selection operator. self.timer.start(); let mating_pool: Vec<&IndividualT> = - self.config - .selection_operator - .apply(&self.metrics, &population, population.len()); + self.config.selection_operator.apply(&self.metrics, &population); self.metrics.selection_dur = Some(self.timer.elapsed()); // 5. From mating pool create new generation (apply crossover & mutation). diff --git a/src/ga/builder/bitstring.rs b/src/ga/builder/bitstring.rs index 9e5fbc95..002e830f 100644 --- a/src/ga/builder/bitstring.rs +++ b/src/ga/builder/bitstring.rs @@ -25,7 +25,7 @@ pub struct BitStringBuilder> { Individual, FlipBit, SinglePoint, - Tournament, + Tournament, BothParents, BitStrings, F, @@ -126,7 +126,7 @@ impl> BitStringBuilder { Individual, FlipBit, SinglePoint, - Tournament, + Tournament, BothParents, BitStrings, F, @@ -150,7 +150,7 @@ impl> BitStringBuilder { .get_or_insert_with(|| FlipBit::new(0.05)); self.config .selection_operator - .get_or_insert_with(|| Tournament::new(0.2)); + .get_or_insert_with(|| Tournament::new(0.2, self.config.params.population_size.unwrap())); self.config .replacement_operator .get_or_insert_with(BothParents::new); diff --git a/src/ga/builder/realvalued.rs b/src/ga/builder/realvalued.rs index bb39d2d9..86c0b6b8 100644 --- a/src/ga/builder/realvalued.rs +++ b/src/ga/builder/realvalued.rs @@ -24,7 +24,7 @@ pub struct RealValuedBuilder> { Individual, Interchange, SinglePoint, - Tournament, + Tournament, BothParents, RandomPoints, F, @@ -125,7 +125,7 @@ impl> RealValuedBuilder { Individual, Interchange, SinglePoint, - Tournament, + Tournament, BothParents, RandomPoints, F, @@ -149,7 +149,7 @@ impl> RealValuedBuilder { .get_or_insert_with(|| Interchange::new(0.05)); self.config .selection_operator - .get_or_insert_with(|| Tournament::new(0.2)); + .get_or_insert_with(|| Tournament::new(0.2, self.config.params.population_size.unwrap())); self.config .replacement_operator .get_or_insert_with(BothParents::new); diff --git a/src/ga/operators/selection.rs b/src/ga/operators/selection.rs index b50d7bf5..6743d6f2 100644 --- a/src/ga/operators/selection.rs +++ b/src/ga/operators/selection.rs @@ -28,11 +28,5 @@ pub trait SelectionOperator { /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT>; + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT>; } diff --git a/src/ga/operators/selection/impls.rs b/src/ga/operators/selection/impls.rs index a977197d..b1950c10 100644 --- a/src/ga/operators/selection/impls.rs +++ b/src/ga/operators/selection/impls.rs @@ -3,7 +3,7 @@ use std::{iter::Sum, ops::Index}; use num_traits::{identities::Zero, NumAssignOps}; use rand::{distributions::Standard, prelude::Distribution, rngs::ThreadRng, Rng}; -use crate::ga::{individual::IndividualTrait, Metrics}; +use crate::ga::{individual::IndividualTrait, value_provider::ValueProvider, Metrics}; use super::SelectionOperator; @@ -19,28 +19,40 @@ use super::SelectionOperator; /// /// Individuals are selected with probability proportional to their fitness value. More specifically: /// probability of selecting chromosome `C` from population `P` is `fitness(C)` / `sum_of_fitness_in_whole_population`. -pub struct RouletteWheel { +pub struct RouletteWheel, R: Rng = ThreadRng> { + selection_size: SizeValue, rng: R, } -impl RouletteWheel { - /// Returns new instance of [RouletteWheel] selection operator with default RNG - pub fn new() -> Self { - RouletteWheel::with_rng(rand::thread_rng()) +impl> RouletteWheel { + /// Returns new instance of [RouletteWheel] selection operator with default RNG. + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(selection_size: SizeValue) -> Self { + RouletteWheel::with_rng(selection_size, rand::thread_rng()) } } -impl RouletteWheel { +impl, R: Rng> RouletteWheel { /// Returns new instance of [RouletteWheel] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - RouletteWheel { rng } + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn with_rng(selection_size: SizeValue, rng: R) -> Self { + RouletteWheel { selection_size, rng } } } // FIXME: It will return empty vector if total_fitness == 0 // WORKING CHANGE: crt >= threshold instead of crt_sum > threshold // But this should be resolved some other way -impl SelectionOperator for RouletteWheel +impl, R: Rng> SelectionOperator + for RouletteWheel where IndividualT::FitnessValueT: NumAssignOps + Sum + PartialOrd + Copy, Standard: Distribution, @@ -60,14 +72,9 @@ where /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { let total_fitness: IndividualT::FitnessValueT = population.iter().map(|indiv| indiv.fitness()).sum(); + let count = self.selection_size.get(metrics); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); @@ -95,25 +102,38 @@ where /// Individuals are selected with uniform probability. /// /// **Note**: The same individual *can not* be selected mutiple times. -pub struct Random { +pub struct Random, R: Rng = ThreadRng> { + selection_size: SizeValue, rng: R, } -impl Random { +impl> Random { /// Returns new instance of [Random] selection operator with default RNG - pub fn new() -> Self { - Random::with_rng(rand::thread_rng()) + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(selection_size: SizeValue) -> Self { + Random::with_rng(selection_size, rand::thread_rng()) } } -impl Random { +impl, R: Rng> Random { /// Returns new instance of [Random] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Random { rng } + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn with_rng(selection_size: SizeValue, rng: R) -> Self { + Random { selection_size, rng } } } -impl SelectionOperator for Random { +impl, R: Rng> SelectionOperator + for Random +{ /// Returns a vector of references to individuals selected to mating pool. /// /// Individuals are selected with uniform probability. @@ -124,13 +144,8 @@ impl SelectionOperator for Ra /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); // We must use index API, as we want to return vector of references, not vector of actual items let indices = rand::seq::index::sample(&mut self.rng, population.len(), count); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); @@ -150,25 +165,37 @@ impl SelectionOperator for Ra /// rated individual from selected pair goes to mating pool. In case of equal fitness - only one goes to mating pool. /// /// **Note**: The same individual *can* be selected multiple times. -pub struct Rank { +pub struct Rank, R: Rng = ThreadRng> { + selection_size: SizeValue, rng: R, } -impl Rank { +impl> Rank { /// Returns new instance of [Rank] selection operator with default RNG - pub fn new() -> Self { - Rank::with_rng(rand::thread_rng()) + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(selection_size: SizeValue) -> Self { + Rank::with_rng(selection_size, rand::thread_rng()) } } -impl Rank { +impl, R: Rng> Rank { /// Returns new instance of [Rank] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Rank { rng } + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn with_rng(selection_size: SizeValue, rng: R) -> Self { + Rank { selection_size, rng } } } -impl SelectionOperator for Rank +impl, R: Rng> SelectionOperator + for Rank where IndividualT::FitnessValueT: PartialOrd, { @@ -183,13 +210,8 @@ where /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); let population_len = population.len(); @@ -218,36 +240,47 @@ where /// 4. Repeat 1-3 necessary number of times to create mating pool of demanded size /// /// **Note**: The same individual can be selected multiple times -pub struct RankR { +pub struct RankR, R: Rng = ThreadRng> { r: f64, + selection_size: SizeValue, rng: R, } -impl RankR { +impl> RankR { /// Returns new instance of [RankR] selection operator with default RNG /// /// ### Arguments /// /// * `r` - threshold in range [0, 1]; see [RankR] description for explaination - pub fn new(r: f64) -> Self { - RankR::with_rng(r, rand::thread_rng()) + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(r: f64, selection_size: SizeValue) -> Self { + RankR::with_rng(r, selection_size, rand::thread_rng()) } } -impl RankR { +impl, R: Rng> RankR { /// Returns new instance of [RankR] selection operator with custom RNG /// /// ### Arguments /// /// * `r` - threshold in range [0, 1]; see [RankR] description for details + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce /// * `rng` - custom random number generator - pub fn with_rng(r: f64, rng: R) -> Self { + pub fn with_rng(r: f64, selection_size: SizeValue, rng: R) -> Self { assert!((0.0..=1.0).contains(&r)); - RankR { r, rng } + RankR { + r, + selection_size, + rng, + } } } -impl SelectionOperator for RankR { +impl, R: Rng> SelectionOperator + for RankR +{ /// Returns a vector of references to individuals selected to mating pool. /// /// Individuals are selected in following process: @@ -263,13 +296,8 @@ impl SelectionOperator for Ra /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); let population_len = population.len(); let distribution_for_ind = rand::distributions::Uniform::from(0..population_len); @@ -302,35 +330,46 @@ impl SelectionOperator for Ra /// 1. Select `ceil(size_factor * population_size)` distinct, random individuals /// 2. Select one with the highest fitness /// 3. Repeat 1-2 number of times necessary to fill mating pool -pub struct Tournament { +pub struct Tournament, R: Rng = ThreadRng> { size_factor: f64, + selection_size: SizeValue, rng: R, } -impl Tournament { +impl> Tournament { /// Returns new instance of [Tournament] selection operator with default RNG /// /// ### Arguments /// /// * `size_factor` - part of population to take part in tournament for choosing single individual; must be in range [0, 1] - pub fn new(size_factor: f64) -> Self { - Tournament::with_rng(size_factor, rand::thread_rng()) + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(size_factor: f64, selection_size: SizeValue) -> Self { + Self::with_rng(size_factor, selection_size, rand::thread_rng()) } } -impl Tournament { +impl, R: Rng> Tournament { /// Returns new instance of [Tournament] selection operator with custom RNG /// /// ### Arguments /// /// * `size_factor` - part of population to take part in tournament for choosing single individual; must be in range [0, 1] - pub fn with_rng(size_factor: f64, rng: R) -> Self { + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn with_rng(size_factor: f64, selection_size: SizeValue, rng: R) -> Self { assert!((0.0..=1.0).contains(&size_factor)); - Tournament { size_factor, rng } + Tournament { + size_factor, + selection_size, + rng, + } } } -impl SelectionOperator for Tournament { +impl, R: Rng> SelectionOperator + for Tournament +{ /// Returns a vector of references to individuals selected to mating pool /// /// Individuals are selected by conducting given number of tournaments with single winner: @@ -346,12 +385,8 @@ impl SelectionOperator for To /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); let tournament_size = (population.len() as f64 * self.size_factor) as usize; let tournament_size = tournament_size.max(1); @@ -394,28 +429,39 @@ impl SelectionOperator for To /// 3. Iterate over the pointers and select the individuals they point to /// /// See the source code for implemenation details -pub struct StochasticUniversalSampling { +pub struct StochasticUniversalSampling, R: Rng = ThreadRng> { + selection_size: SizeValue, rng: R, } -impl StochasticUniversalSampling { +impl> StochasticUniversalSampling { /// Returns new instance of [StochasticUniversalSampling] selection operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn new(selection_size: SizeValue) -> Self { + Self::with_rng(selection_size, rand::thread_rng()) } } -impl StochasticUniversalSampling { +impl, R: Rng> StochasticUniversalSampling { /// Returns new instance of [StochasticUniversalSampling] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } + /// + /// ## Arguments + /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce + pub fn with_rng(selection_size: SizeValue, rng: R) -> Self { + Self { selection_size, rng } } } // FIXME: Panics then total_fitness == 0 // Should this be expected or do we want to handle this? -impl, R: Rng> SelectionOperator - for StochasticUniversalSampling +impl, SizeValue: ValueProvider, R: Rng> + SelectionOperator for StochasticUniversalSampling { /// Returns a vector of references to individuals selected to mating pool /// @@ -441,13 +487,8 @@ impl, R: Rng> SelectionOperato /// /// * `metrics` - [crate::ga::Metrics] information on current stage of the algorithm (iteration, elapsed time, etc.) /// * `population` - individuals to choose mating pool from - /// * `count` - target number of individuals in mating pool - fn apply<'a>( - &mut self, - _metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); let total_fitness: f64 = population.iter().map(|indiv| indiv.fitness()).sum(); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); @@ -478,7 +519,8 @@ impl, R: Rng> SelectionOperato /// /// This struct implements [SelectionOperator] trait and can be used with GA /// -pub struct Boltzmann { +pub struct Boltzmann, R: Rng = ThreadRng> { + selection_size: SizeValue, alpha: f64, max_gen_count: usize, // FIXME: This should be removed after operators are passed whole algorithm state & config temp_0: f64, @@ -486,31 +528,55 @@ pub struct Boltzmann { rng: R, } -impl Boltzmann { +impl> Boltzmann { /// Returns new instance of [Boltzmann] selection operator with default RNG /// /// ### Arguments /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce /// * `alpha` - prameter that controlls temperature scaling; must be in [0, 1] range /// * `temp_0` - initial temperature for the operator /// * `max_gen_count` - maximum number of generations GA can run; this param will be removed in future version of the library /// * `elitism` - set to true to ensure that best individuals end in mating pool no matter operator results; **not supported yet** - pub fn new(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool) -> Self { - Self::with_rng(alpha, temp_0, max_gen_count, elitism, rand::thread_rng()) + pub fn new( + selection_size: SizeValue, + alpha: f64, + temp_0: f64, + max_gen_count: usize, + elitism: bool, + ) -> Self { + Self::with_rng( + selection_size, + alpha, + temp_0, + max_gen_count, + elitism, + rand::thread_rng(), + ) } } -impl Boltzmann { +impl, R: Rng> Boltzmann { /// Returns new instance of [Boltzmann] selection operator with default RNG /// /// ### Arguments /// + /// * `selection_size` - value provider deciding how many individuals will selection operator + /// produce /// * `alpha` - prameter that controlls temperature scaling; must be in [0, 1] range /// * `temp_0` - initial temperature for the operator /// * `max_gen_count` - maximum number of generations GA can run; this param will be removed in future version of the library /// * `elitism` - set to true to ensure that best individuals end in mating pool no matter operator results; **not supported yet** /// * `rng` - custom random number generator - pub fn with_rng(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool, rng: R) -> Self { + pub fn with_rng( + selection_size: SizeValue, + alpha: f64, + temp_0: f64, + max_gen_count: usize, + elitism: bool, + rng: R, + ) -> Self { assert!( (0.0..=1.0).contains(&alpha), "Alpha parameter must be a value from [0, 1] interval" @@ -521,6 +587,7 @@ impl Boltzmann { ); Boltzmann { + selection_size, alpha, max_gen_count, temp_0, @@ -530,18 +597,15 @@ impl Boltzmann { } } -impl SelectionOperator for Boltzmann +impl SelectionOperator for Boltzmann where IndividualT: IndividualTrait, IndividualT::ChromosomeT: Index, + SizeValue: ValueProvider, R: Rng, { - fn apply<'a>( - &mut self, - metrics: &Metrics, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { + fn apply<'a>(&mut self, metrics: &Metrics, population: &'a [IndividualT]) -> Vec<&'a IndividualT> { + let count = self.selection_size.get(metrics); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); let mut weights: Vec = Vec::with_capacity(count); @@ -573,36 +637,36 @@ mod test { #[test] #[should_panic] fn boltzman_panics_on_too_big_alpha() { - let _operator = Boltzmann::new(5.0, 10.0, 300, false); + let _operator = Boltzmann::new(100, 5.0, 10.0, 300, false); } #[test] #[should_panic] fn boltzman_panics_on_too_small_alpha() { - let _operator = Boltzmann::new(-0.1, 10.0, 300, false); + let _operator = Boltzmann::new(100, -0.1, 10.0, 300, false); } #[test] #[should_panic] fn boltzman_panics_on_too_low_temp() { - let _operator = Boltzmann::new(0.5, 4.0, 300, false); + let _operator = Boltzmann::new(100, 0.5, 4.0, 300, false); } #[test] #[should_panic] fn boltzman_panics_on_too_high_temp() { - let _operator = Boltzmann::new(0.5, 400.0, 300, false); + let _operator = Boltzmann::new(100, 0.5, 400.0, 300, false); } #[test] #[should_panic] fn tournament_panics_on_wrong_size_factor() { - let _operator = Tournament::new(2.0); + let _operator = Tournament::new(2.0, 100); } #[test] #[should_panic] fn rankr_panics_on_wrong_r() { - let _operator = RankR::new(1.1); + let _operator = RankR::new(1.1, 100); } } diff --git a/src/ga/value_provider.rs b/src/ga/value_provider.rs new file mode 100644 index 00000000..d776fbc8 --- /dev/null +++ b/src/ga/value_provider.rs @@ -0,0 +1,9 @@ +pub mod flat; + +use super::Metrics; + +// The `sized` bound here was not thinked through. It is possible +// to resign from it +pub trait ValueProvider { + fn get(&mut self, metrics: &Metrics) -> T; +} diff --git a/src/ga/value_provider/flat.rs b/src/ga/value_provider/flat.rs new file mode 100644 index 00000000..e12d621f --- /dev/null +++ b/src/ga/value_provider/flat.rs @@ -0,0 +1,65 @@ +use crate::ga::Metrics; + +use super::ValueProvider; + +pub struct FlatValue { + value: T, +} + +impl ValueProvider for FlatValue { + #[inline] + fn get(&mut self, _metrics: &Metrics) -> T { + self.value.clone() + } +} + +// WE MUST ADD PLATFORM CHECKS HERE, TO VERIFY WHETHER GIVEN TYPES +// ARE AVAILABLE ON GIVEN PLATFORM... + +impl ValueProvider for usize { + fn get(&mut self, _metrics: &Metrics) -> usize { + *self + } +} + +impl ValueProvider for isize { + fn get(&mut self, _metrics: &Metrics) -> isize { + *self + } +} + +impl ValueProvider for i32 { + fn get(&mut self, _metrics: &Metrics) -> i32 { + *self + } +} + +impl ValueProvider for i64 { + fn get(&mut self, _metrics: &Metrics) -> i64 { + *self + } +} + +impl ValueProvider for i8 { + fn get(&mut self, _metrics: &Metrics) -> i8 { + *self + } +} + +impl ValueProvider for u8 { + fn get(&mut self, _metrics: &Metrics) -> u8 { + *self + } +} + +impl ValueProvider for f32 { + fn get(&mut self, _metrics: &Metrics) -> f32 { + *self + } +} + +impl ValueProvider for f64 { + fn get(&mut self, _metrics: &Metrics) -> f64 { + *self + } +} diff --git a/src/pso/particle.rs b/src/pso/particle.rs index 6728a40a..061a1665 100644 --- a/src/pso/particle.rs +++ b/src/pso/particle.rs @@ -89,7 +89,7 @@ impl Particle { updated_position.push(updated_x_i); } - self.position = updated_position.clone(); + self.position.clone_from(&updated_position); self.value = function(&self.position); if self.value < self.best_position_value { diff --git a/src/pso/swarm.rs b/src/pso/swarm.rs index cd896487..4c242c49 100644 --- a/src/pso/swarm.rs +++ b/src/pso/swarm.rs @@ -36,7 +36,7 @@ impl Swarm { let mut best_position = particles[0].clone().best_position; for particle in &particles { if function(&particle.position) < function(&best_position) { - best_position = particle.position.clone(); + best_position.clone_from(&particle.position); } } @@ -74,7 +74,7 @@ impl Swarm { pub fn update_best_position(&mut self, function: fn(&Vec) -> f64) { self.particles.iter_mut().for_each(|particle| { if function(&particle.best_position) < function(&self.best_position) { - self.best_position = particle.best_position.clone(); + self.best_position.clone_from(&particle.best_position); self.best_position_value = particle.best_position_value; } }); diff --git a/tests/builder_tests.rs b/tests/builder_tests.rs index b23846b3..c29c3ea3 100644 --- a/tests/builder_tests.rs +++ b/tests/builder_tests.rs @@ -34,7 +34,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { RealValueIndividual, Identity, SinglePoint, - Boltzmann, + Boltzmann, BothParents, RandomPoints, FnBasedFitness, @@ -51,7 +51,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { vec![-5.12..5.12, -5.12..5.12, -5.12..5.12], )) .set_selection_operator(ecrs::ga::operators::selection::Boltzmann::new( - 0.05, 80.0, 500, false, + 100, 0.05, 80.0, 500, false, )) .set_probe(ecrs::ga::probe::StdoutProbe) .build(); @@ -61,7 +61,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { RealValueIndividual, Identity, SinglePoint, - Boltzmann, + Boltzmann, BothParents, RandomPoints, FnBasedFitness, @@ -76,7 +76,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { vec![-5.12..5.12, -5.12..5.12, -5.12..5.12], )) .set_selection_operator(ecrs::ga::operators::selection::Boltzmann::new( - 0.05, 80.0, 500, false, + 100, 0.05, 80.0, 500, false, )) .set_probe(ecrs::ga::probe::StdoutProbe) .build(); diff --git a/tests/selection_tests.rs b/tests/selection_tests.rs index 173e4eb2..d2a216f1 100644 --- a/tests/selection_tests.rs +++ b/tests/selection_tests.rs @@ -26,7 +26,7 @@ fn random_selection_returns_demanded_size() { let expected_selection_size = expected_population_size / 2; - let selected = Random::new().apply(&metrics, &population, expected_selection_size); + let selected = Random::new(expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -51,7 +51,7 @@ fn roulette_whell_returns_demanded_size() { let expected_selection_size = expected_population_size / 2; - let selected = RouletteWheel::new().apply(&metrics, &population, expected_selection_size); + let selected = RouletteWheel::new(expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -76,7 +76,7 @@ fn rank_returns_demanded_size() { let expected_selection_size = expected_population_size / 2; - let selected = Rank::new().apply(&metrics, &population, expected_selection_size); + let selected = Rank::new(expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -101,7 +101,7 @@ fn rankr_returns_demanded_size() { let expected_selection_size = expected_population_size / 2; - let selected = RankR::new(0.5).apply(&metrics, &population, expected_selection_size); + let selected = RankR::new(0.5, expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -126,7 +126,7 @@ fn tournament_returns_demanded_size() { let expected_selection_size = expected_population_size / 2; - let selected = Tournament::new(0.2).apply(&metrics, &population, expected_selection_size); + let selected = Tournament::new(0.2, expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -156,7 +156,7 @@ fn sus_returns_demanded_size_when_fitness_positive() { let expected_selection_size = expected_population_size / 2; - let selected = StochasticUniversalSampling::new().apply(&metrics, &population, expected_selection_size); + let selected = StochasticUniversalSampling::new(expected_selection_size).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -188,7 +188,7 @@ fn boltzmann_returns_demanded_size() { // FIXME: We must add mocking! let metrics = Metrics::new(Some(std::time::Instant::now()), None, 40); - let selected = Boltzmann::new(0.2, 6.0, 300, true).apply(&metrics, &population, expected_selection_size); + let selected = Boltzmann::new(expected_selection_size, 0.2, 6.0, 300, true).apply(&metrics, &population); assert_eq!( expected_selection_size, @@ -203,9 +203,9 @@ fn random_returns_whole_population_in_order() { let dim = 21; let population: Vec = RandomPoints::new(dim).generate(population_size); - let mut operator = Random::with_rng(rand::rngs::mock::StepRng::new(0, 1)); + let mut operator = Random::with_rng(population_size, rand::rngs::mock::StepRng::new(0, 1)); - let selected = operator.apply(&Metrics::default(), &population, population_size); + let selected = operator.apply(&Metrics::default(), &population); for (expected, actual) in std::iter::zip(&population, selected) { assert_eq!(expected, actual);