diff --git a/Cargo.toml b/Cargo.toml index 5442dd5d..3da8f87f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,14 +21,20 @@ name = "ecrs" path = "src/lib.rs" [features] -default = ["ga"] -ga = ["test_functions", "dep:push-trait", "dep:len-trait", "dep:num-traits"] +default = ["ga", "ga_ops_impls"] +ga = ["dep:push-trait", "dep:len-trait", "dep:num-traits"] +ga_ops_impls = ["ga_impl_crossover", "ga_impl_mutation", "ga_impl_replacement", "ga_impl_selection", "ga_impl_population"] +ga_impl_crossover = ["ga"] +ga_impl_mutation = ["ga"] +ga_impl_replacement = ["ga"] +ga_impl_selection = ["ga"] +ga_impl_population = ["ga"] aco = ["dep:nalgebra", "dep:num"] ff = ["dep:rayon"] pso = ["dep:rayon", "dep:num", "test_functions"] aco_tsp = ["aco"] test_functions = [] - +all = ["ga", "aco", "ff", "pso", "test_functions"] [dependencies] rand = "0.8.5" @@ -44,14 +50,12 @@ push-trait = { version = "0.6.0", optional = true } len-trait = { version = "0.6.1", optional = true } num-traits = { version = "0.2.15", optional = true } - [dev-dependencies] log4rs = "1.2.0" criterion = "0.5.1" clap = { version = "4.2.7", features = ["derive"] } md5 = "0.7.0" - [[bench]] name = "aco_bench" harness = false @@ -72,3 +76,28 @@ lto = "fat" panic = "abort" opt-level = 3 +[[example]] +name = "jssp" +path = "examples/jssp/main.rs" +required-features = ["ga", "ga_ops_impls"] + +[[example]] +name = "ga" +path = "examples/ga.rs" +required-features = ["ga", "ga_ops_impls", "test_functions"] + +[[example]] +name = "ga_ackley_test_function" +path = "examples/ga_ackley_test_function.rs" +required-features = ["ga", "ga_ops_impls", "test_functions"] + +[[example]] +name = "ga_bsc" +path = "examples/ga_bsc.rs" +required-features = ["ga", "ga_ops_impls", "test_functions"] + +[[example]] +name = "ga_rvc" +path = "examples/ga_rvc.rs" +required-features = ["ga", "ga_ops_impls", "test_functions"] + diff --git a/examples/ga.rs b/examples/ga.rs index 74173cc2..e4e082dc 100644 --- a/examples/ga.rs +++ b/examples/ga.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "ga")] use ecrs::{ ga::{self, individual::RealValueIndividual, probe::AggregatedProbe}, prelude::{ @@ -7,15 +6,13 @@ use ecrs::{ }, }; -#[cfg(feature = "ga")] mod util; -#[cfg(feature = "ga")] +#[allow(clippy::ptr_arg)] fn rastrigin_fitness(chromosome: &Vec) -> f64 { 1000.0 * f64::exp(-ecrs::test_functions::rastrigin(chromosome)) } -#[cfg(feature = "ga")] fn main() { let _ = util::init_logging(); @@ -57,8 +54,3 @@ fn main() { println!("{res:?}"); } - -#[cfg(not(feature = "ga"))] -fn main() { - panic!("Required feature \"ga\" is not enabled"); -} diff --git a/examples/ga_ackley_test_function.rs b/examples/ga_ackley_test_function.rs index 066368d7..7ada1a48 100644 --- a/examples/ga_ackley_test_function.rs +++ b/examples/ga_ackley_test_function.rs @@ -1,6 +1,5 @@ -#![cfg(feature = "ga")] - use ecrs::{ga, test_functions}; + mod util; fn main() { @@ -16,8 +15,3 @@ fn main() { println!("4D ackley function zero approximation {best_individual:#?}") } - -#[cfg(not(feature = "ga"))] -fn main() { - panic!("Required feature \"ga\" is not enabled"); -} diff --git a/examples/ga_bsc.rs b/examples/ga_bsc.rs index f8f6acce..832e981a 100644 --- a/examples/ga_bsc.rs +++ b/examples/ga_bsc.rs @@ -1,15 +1,11 @@ -#[cfg(feature = "ga")] use ecrs::ga; -#[cfg(feature = "ga")] mod util; #[allow(clippy::ptr_arg)] -#[cfg(feature = "ga")] pub fn wordmax_fitness(chromosome: &Vec) -> f64 { chromosome.iter().filter(|gene| **gene).count() as f64 } -#[cfg(feature = "ga")] fn main() { let _ = util::init_logging(); @@ -24,8 +20,3 @@ fn main() { println!("Bitstring with most ones: {best_individual:#?}") } - -#[cfg(not(feature = "ga"))] -fn main() { - panic!("Required feature \"ga\" is not enabled"); -} diff --git a/examples/ga_rvc.rs b/examples/ga_rvc.rs index 7c2df823..448fa9a3 100644 --- a/examples/ga_rvc.rs +++ b/examples/ga_rvc.rs @@ -1,15 +1,11 @@ -#[cfg(feature = "ga")] use ecrs::ga; -#[cfg(feature = "ga")] mod util; #[allow(clippy::ptr_arg)] -#[cfg(feature = "ga")] pub fn rastrigin_fitness(chromosome: &Vec) -> f64 { 1000.0 * f64::exp(-rastrigin(chromosome)) } -#[cfg(feature = "ga")] fn rastrigin(chromosome: &[f64]) -> f64 { 10.0 * chromosome.len() as f64 + chromosome.iter().fold(0.0, |sum, x| { @@ -17,7 +13,6 @@ fn rastrigin(chromosome: &[f64]) -> f64 { }) } -#[cfg(feature = "ga")] fn main() { let _ = util::init_logging(); @@ -31,8 +26,3 @@ fn main() { println!("5D Rastrigin function zero approximation {best_individual:#?}") } - -#[cfg(not(feature = "ga"))] -fn main() { - panic!("Required feature \"ga\" is not enabled"); -} diff --git a/examples/jssp/problem/fitness.rs b/examples/jssp/problem/fitness.rs index 097a8130..e5a3c26b 100644 --- a/examples/jssp/problem/fitness.rs +++ b/examples/jssp/problem/fitness.rs @@ -268,12 +268,7 @@ impl JsspFitness { self.calculate_critical_distance(indv, 0, &mut visited) } - fn calculate_critical_distance( - &mut self, - indv: &mut JsspIndividual, - op_id: usize, - visited: &mut Vec, - ) { + fn calculate_critical_distance(&mut self, indv: &mut JsspIndividual, op_id: usize, visited: &mut [bool]) { let mut stack: Vec = Vec::with_capacity(visited.len() * 2); stack.push(op_id); diff --git a/src/ff/auxiliary.rs b/src/ff/auxiliary.rs index cab6860a..6d097d67 100644 --- a/src/ff/auxiliary.rs +++ b/src/ff/auxiliary.rs @@ -1,5 +1,6 @@ use std::f64; +#[allow(clippy::ptr_arg)] pub fn rastrigin(params: &Vec) -> f64 { let mut res = 0 as f64; for param in params.iter() { @@ -8,6 +9,7 @@ pub fn rastrigin(params: &Vec) -> f64 { res + 10_f64 * params.len() as f64 } +#[allow(clippy::ptr_arg)] pub fn cartesian_distance(a: &Vec, b: &[f64]) -> f64 { //Distance between two points let mut res: f64 = 0 as f64; @@ -17,6 +19,7 @@ pub fn cartesian_distance(a: &Vec, b: &[f64]) -> f64 { f64::sqrt(res) } +#[allow(clippy::ptr_arg)] pub fn taxi_measure_distance(a: &Vec, b: &[f64]) -> f64 { let mut res: f64 = 0 as f64; for dimension in 0..a.len() { diff --git a/src/ga.rs b/src/ga.rs index 78d9bb98..a2fe6c9d 100644 --- a/src/ga.rs +++ b/src/ga.rs @@ -105,15 +105,15 @@ //! 1. Fitness function (the algorithm must know what it is optimizing) //! 2. Problem dimension //! 3. Population generator (the algorithm must be able to create initial population) -//! 4. Probe (the logging object -- if you don't want to see any logs other than final result just pass [Empty probe](crate::ga::probes::Empty)) +//! 4. Probe (the logging object -- if you don't want to see any logs other than final result just pass [Empty probe](crate::ga::probe::EmptyProbe)) //! -//! The defaults for operators and parameters are provided for two types of chromosomes: bit string and real valued vector (see docs of [Builder](crage::ga::builder::Builder)), +//! The defaults for operators and parameters are provided for two types of chromosomes: bit string and real valued vector (see docs of [Builder](crate::ga::builder::Builder)), //! but keep in mind that these default options might not be even good for your particular problem as the operators & parameters should be //! tailored individually for each problem. //! -//! * See [probes & configuration](ecrs::ga::probe) -//! * See [population generators](ecrs::ga::population) -//! * See [fitness & configuration](ecrs::ga::fitness) +//! * See [probes & configuration](crate::ga::probe) +//! * See [population generators](crate::ga::population) +//! * See [fitness & configuration](crate::ga::operators::fitness) //! * See [available params](self::GAParams) pub mod builder; diff --git a/src/ga/operators/crossover.rs b/src/ga/operators/crossover.rs index 874e21ac..da10c980 100644 --- a/src/ga/operators/crossover.rs +++ b/src/ga/operators/crossover.rs @@ -1,15 +1,10 @@ -use itertools::{enumerate, Itertools}; -use len_trait::Len; -use std::collections::{HashMap, HashSet}; -use std::hash::Hash; -use std::ops::{Index, IndexMut}; +#[cfg(feature = "ga_impl_crossover")] +pub mod impls; +#[cfg(feature = "ga_impl_crossover")] +pub use impls::*; -use crate::ga::individual::{Chromosome, IndividualTrait}; +use crate::ga::individual::IndividualTrait; use crate::ga::GAMetadata; -use push_trait::{Nothing, Push}; -use rand::prelude::SliceRandom; -use rand::{rngs::ThreadRng, Rng}; - /// # Crossover Operator /// /// This trait defines common behaviour for crossover operators. @@ -28,1074 +23,3 @@ pub trait CrossoverOperator { parent_2: &IndividualT, ) -> (IndividualT, IndividualT); } - -/// # Single point crossover operator -/// -/// This struct implements [CrossoverOperator] trait and can be used with GA. -/// -/// It works by defininig single cutpoint splitting both parent chromosomes in two parts. -/// First child gets `parent_1`'s first part and `parent_2`'s second part. -/// Second child gets `parent_2`'s first part and `parent_1`'s second part. -/// -/// Degenerated case when cutpoint is selected at index 0 or last can occur. -pub struct SinglePoint { - rng: R, -} - -impl SinglePoint { - /// Creates new [SinglePoint] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl SinglePoint { - /// Creates new [SinglePoint] crossover operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl CrossoverOperator for SinglePoint -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng, -{ - /// Returns a tuple of children - /// - /// It works by randomly selecting single cutpoint splitting both parent chromosomes in two parts. - /// First child gets `parent_1`'s first part and `parent_2`'s second part. - /// Second child gets `parent_2`'s first part and `parent_1`'s second part. - /// - /// Degenerated case when cutpoint is selected at index 0 or last can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - let chromosome_len = parent_1.chromosome().len(); - let cut_point = self.rng.gen_range(0..chromosome_len); - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - for locus in 0..cut_point { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - } - - for locus in cut_point..chromosome_len { - child_1_ch.push(parent_2.chromosome()[locus]); - child_2_ch.push(parent_1.chromosome()[locus]); - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Two point crossover operator -/// -/// This struct implements [CrossoverOperator] and can be used with GA. -/// -/// It works by randomly selecting two cutpoints splitting parents chromosomes in three parts. -/// Then it creates children by taking parents chromosome parts interchangeably. -/// Its mechanism is analoguous to [SinglePoint]. -/// -/// Degenerate case when both cutpoints are in the same place or at position 0 or last can occur. -pub struct TwoPoint { - rng: R, -} - -impl TwoPoint { - /// Creates new [TwoPoint] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl TwoPoint { - /// Creates new [TwoPoint] crossover operator with custom RNG - pub fn with_rng(rng: R) -> Self { - TwoPoint { rng } - } -} - -impl CrossoverOperator for TwoPoint -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng, -{ - /// Returns a tuple of children - /// - /// It works by randomly selecting two cutpoints splitting parents chromosomes in three parts. - /// Then it creates children by taking parents chromosome parts interchangeably. - /// Its mechanism is analoguous to [SinglePoint]. - /// - /// Degenerate case when both cutpoints are in the same place or at position 0 or last can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let cut_points = ( - self.rng.gen_range(0..chromosome_len), - self.rng.gen_range(0..chromosome_len), - ); - - let (cut_point_1, cut_point_2) = if cut_points.0 <= cut_points.1 { - (cut_points.0, cut_points.1) - } else { - (cut_points.1, cut_points.0) - }; - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - for locus in 0..cut_point_1 { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - } - - for locus in cut_point_1..cut_point_2 { - child_1_ch.push(parent_2.chromosome()[locus]); - child_2_ch.push(parent_1.chromosome()[locus]); - } - - for locus in cut_point_2..chromosome_len { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Mutli-point crossover operator -/// -/// This struct implements [CrossoverOperator] and can be used with GA. -/// -/// It works analogously to [SinglePoint] or [TwoPoint]. One important difference is that -/// all cutpoints are distinct, thus single or two point crossover with guarantee of distinct cutpoints -/// can be achieved. -pub struct MultiPoint { - cut_points_no: usize, - rng: R, -} - -impl MultiPoint { - /// Creates new [MultiPoint] crossover operator with default RNG - /// - /// ## Arguments - /// - /// * `cut_points_no` - Number of cutpoints (crossover points) - pub fn new(cut_points_no: usize) -> Self { - Self::with_rng(cut_points_no, rand::thread_rng()) - } -} - -impl Default for MultiPoint { - /// Creates new [MultiPoint] crossover operator with 4 cutpoints and default RNG - fn default() -> Self { - Self::with_rng(4, rand::thread_rng()) - } -} - -impl MultiPoint { - /// Creates new [MultiPoint] crossover operator with custom RNG - /// - /// ## Arguments - /// - /// * `cut_points_no` - Number of cutpoints (crossover points) - /// * `rng` - Custom random number generator - pub fn with_rng(cut_points_no: usize, rng: R) -> Self { - assert!(cut_points_no >= 1, "Number of cut points must be >= 1"); - Self { cut_points_no, rng } - } -} - -impl CrossoverOperator for MultiPoint -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng, -{ - /// Returns a tuple of children - /// - /// It works analogously to [SinglePoint] or [TwoPoint]. One important difference is that - /// all cutpoints are distinct, thus single or two point crossover with guarantee of distinct cutpoints - /// can be achieved. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - assert!( - self.cut_points_no <= parent_1.chromosome().len(), - "There can't be more cut points than chromosome length" - ); - assert!(self.cut_points_no >= 1, "Numver of cut points must be >= 1"); - - let chromosome_len = parent_1.chromosome().len(); - - let mut cut_points = - rand::seq::index::sample(&mut self.rng, chromosome_len, self.cut_points_no).into_vec(); - cut_points.sort_unstable(); - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - let (mut curr_parent_1, mut curr_parent_2) = (&parent_1, &parent_2); - - for locus in 0..cut_points[0] { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - (curr_parent_1, curr_parent_2) = (curr_parent_2, curr_parent_1); - } - - for cut_point_idx in 0..self.cut_points_no - 1 { - for locus in cut_points[cut_point_idx]..cut_points[cut_point_idx + 1] { - child_1_ch.push(curr_parent_1.chromosome()[locus]); - child_2_ch.push(curr_parent_2.chromosome()[locus]); - } - (curr_parent_1, curr_parent_2) = (curr_parent_2, curr_parent_1); - } - - for locus in cut_points[self.cut_points_no - 1]..chromosome_len { - child_1_ch.push(curr_parent_1.chromosome()[locus]); - child_2_ch.push(curr_parent_2.chromosome()[locus]); - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Uniform crossover operator -/// -/// This struct implements [CrossoverOperator] and can be used with GA. -/// -/// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first -/// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. -pub struct Uniform { - rng: R, -} - -impl Uniform { - /// Creates new [Uniform] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Uniform { - /// Creates new [Uniform] crossover operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl CrossoverOperator for Uniform -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng + Clone, -{ - /// Returns a tuple of children - /// - /// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first - /// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - let mask = self - .rng - .clone() - .sample_iter(rand::distributions::Uniform::new(0.0, 1.0)) - .take(chromosome_len); - - for (locus, val) in mask.enumerate() { - if val >= 0.5 { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - } else { - child_1_ch.push(parent_2.chromosome()[locus]); - child_2_ch.push(parent_1.chromosome()[locus]); - } - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Parameterized Uniform crossover operator -/// -/// This struct implements [CrossoverOperator] and can be used with GA. -/// -/// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first -/// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. -/// -/// Bias is a probability of drawing a 1 in the bit-mask. -pub struct UniformParameterized { - rng: R, - distr: rand::distributions::Uniform, - bias: f64, -} - -impl UniformParameterized { - pub fn new(bias: f64) -> Self { - Self::with_rng(rand::thread_rng(), bias) - } -} - -impl UniformParameterized { - pub fn with_rng(rng: R, bias: f64) -> Self { - Self { - rng, - distr: rand::distributions::Uniform::new(0.0, 1.0), - bias, - } - } -} - -impl CrossoverOperator for UniformParameterized -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng + Clone, -{ - /// Returns a tuple of children - /// - /// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first - /// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - let mask = self.rng.clone().sample_iter(self.distr).take(chromosome_len); - - for (locus, val) in mask.enumerate() { - if val <= self.bias { - child_1_ch.push(parent_1.chromosome()[locus]); - child_2_ch.push(parent_2.chromosome()[locus]); - } else { - child_1_ch.push(parent_2.chromosome()[locus]); - child_2_ch.push(parent_1.chromosome()[locus]); - } - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Ordered crossover operator -/// -/// This struct implements [CrossoverOperator] trait and can be used with GA. -/// -/// It works by taking a substring from one parent, and filling the missing places with alleles from -/// second parent in the order they appear in. -/// -/// P1 : 2 4 1 3 5
-/// P2 : 5 2 1 4 3
-/// Ch : 5 4 1 3 2 -/// -/// Degenerated case when substring has length equal to genome length can occur. -pub struct OrderedCrossover { - rng: R, -} - -impl OrderedCrossover { - /// Creates new [OrderedCrossover] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl OrderedCrossover { - pub fn with_rng(rng: R) -> Self { - Self { rng } - } - - /// Helper function for [OrderedCrossover::apply] - /// ## Arguments - /// - /// * `p1` - First parent to take part in crossover - /// * `p2` - Second parent to take part in crossover - /// * `begin` - Start (inclusive) of substring to transplant - /// * `end` - End (exclusive) of substring to transplant - fn create_child( - &self, - p1: &IndividualT, - p2: &IndividualT, - begin: usize, - end: usize, - ) -> IndividualT - where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - { - let chromosome_len = p1.chromosome().len(); - - let mut substring_set: HashSet = HashSet::new(); - - for i in begin..end { - substring_set.push(p1.chromosome()[i]); - } - - let mut child_ch = IndividualT::ChromosomeT::default(); - let mut index: usize = 0; - - while child_ch.len() < begin { - let gene = p2.chromosome()[index]; - if !substring_set.contains(&gene) { - child_ch.push(gene); - } - index += 1; - } - - for i in begin..end { - child_ch.push(p1.chromosome()[i]); - } - - while index < chromosome_len { - let gene = p2.chromosome()[index]; - if !substring_set.contains(&gene) { - child_ch.push(gene); - } - index += 1; - } - IndividualT::from(child_ch) - } -} - -impl CrossoverOperator for OrderedCrossover -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - R: Rng, -{ - /// Returns a tuple of children, first child is created by taking a substring from parent_1, - /// second child is created by using a substring from parent_2 - /// - /// It works by taking a substring from one parent, and filling the missing places with alleles from - /// second parent in the order they appear in. - /// - /// P1 : 2 4 1 3 5
- /// P2 : 5 2 1 4 3
- /// Ch : 5 4 1 3 2 - /// - /// Degenerated case when substring has length equal to genome length can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in crossover - /// * `parent_2` - Second parent to take part in crossover - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let begin: usize = self.rng.gen_range(0..chromosome_len); - let end: usize = self.rng.gen_range(begin..=chromosome_len); - - let child_1 = self.create_child(parent_1, parent_2, begin, end); - let child_2 = self.create_child(parent_2, parent_1, begin, end); - - (child_1, child_2) - } -} - -/// # PPX crossover operator -/// -/// This struct implements [CrossoverOperator] trait and can be used with GA. -/// -/// PPX (Precedence Preservative Crossover), genes are taken in order they appear in parent, -/// parent is chosen at random, if gene was already taken from other parent then the next un-taken gene -/// is chosen -/// -/// P1 : 2 4 1 3 5
-/// P2 : 5 2 1 4 3
-/// Gene source: 1 1 2 1 2
-/// Ch : 2 4 5 1 3 -/// -/// Degenerated case when all genes are taken from the same parent. -pub struct Ppx { - rng: R, - distribution: rand::distributions::Standard, -} - -impl Ppx { - /// Creates new [Ppx] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Ppx { - /// Creates new [PPXCrossover] crossover operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { - rng, - distribution: rand::distributions::Standard, - } - } - - /// Helper function for [Ppx::apply] - /// ## Arguments - /// - /// * `p1` - First parent to take part in crossover - /// * `p2` - Second parent to take part in crossover - /// * `take_from_p1` - Which genes should be taken from p1 - fn create_child( - &self, - p1: &IndividualT, - p2: &IndividualT, - take_from_p1: &[bool], - ) -> IndividualT - where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - { - let chromosome_len = p1.chromosome().len(); - - let mut already_taken: HashSet = HashSet::new(); - - let mut child_ch = IndividualT::ChromosomeT::default(); - let mut index_p: [usize; 2] = [0, 0]; - let parents = [p1, p2]; - - while child_ch.len() < chromosome_len { - let index_child = child_ch.len(); - let parent_i = usize::from(!take_from_p1[index_child]); - - while child_ch.len() == index_child { - let gene = parents[parent_i].chromosome()[index_p[parent_i]]; - index_p[parent_i] += 1; - - if !already_taken.contains(&gene) { - already_taken.push(gene); - child_ch.push(gene); - } - } - } - IndividualT::from(child_ch) - } -} - -impl CrossoverOperator for Ppx -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - R: Rng, -{ - /// Returns a tuple of children, first child is created by using parent_1 as first parent, - /// second child is created by using a parent_1 as the second parent. - /// - /// PPX (Precedence Preservative Crossover), genes are taken in order they appear in parent, - /// parent is chosen at random, if gene was already taken from other parent then the next un-taken gene - /// is chosen - /// - /// P1 : 2 4 1 3 5
- /// P2 : 5 2 1 4 3
- /// Gene source: 1 1 2 1 2
- /// Ch : 2 4 5 1 3 - /// - /// Degenerated case when all genes are taken from the same parent can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - one of the parents to take part in crossover - /// * `parent_2` - one of the parents to take part in crossover - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let take_from_p1: Vec = (&mut self.rng) - .sample_iter(self.distribution) - .take(chromosome_len) - .collect_vec(); - - let child_1 = self.create_child(parent_1, parent_2, &take_from_p1); - let child_2 = self.create_child(parent_2, parent_1, &take_from_p1); - - (child_1, child_2) - } -} - -/// # PMX crossover operator -/// -/// This struct implements [CrossoverOperator] trait and can be used with GA. -/// -/// Returns a tuple of children, first child is created by taking a substring from parent_1, -/// second child is created by using a substring from parent_2 -/// -/// It works by taking a substring from one parent, then in second parent we look at genes one by one -/// that would be transplanted if we were transplanting from second parent, if the gene (gene_1) appares in transplanted string from parent one -/// then we ignore it, else: -/// * I. We remember the gene place index (index_1) -/// * II. We look what gene (gene_2) is at this place (index_1) in first parent -/// * III. We look for gene (gene_3) place (index_2) in second parent -/// * IV. If this gene (gene_3) can be found in transplanted genes then we place gene_1 in index_2 place, -/// else we go to step I. with gene_1 = gene_3 -/// -/// P1 : 8 4 7 3 6 2 5 1 9 0
-/// P2 : 0 1 2 3 4 5 6 7 8 9
-/// Ch : 0 7 4 3 6 2 5 1 8 9 -/// -/// Degenerated case when substring has length equal to genome length can occur. -/// -pub struct Pmx { - rng: R, -} - -impl Pmx { - /// Creates new [Pmx] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Pmx { - pub fn with_rng(rng: R) -> Self { - Self { rng } - } - - fn to_val_index_map(&self, chromosome: &ChT) -> HashMap - where - ChT: Chromosome + Index + Push, - GeneT: Copy + Eq + Hash, - { - let chromosome_len = chromosome.len(); - let mut val_index_map: HashMap = HashMap::new(); - - for i in 0..chromosome_len { - val_index_map.push((chromosome[i], i)); - } - - val_index_map - } - - /// Helper function for [Pmx::apply] - /// ## Arguments - /// - /// * `p1` - First parent to take part in crossover - /// * `p2` - Second parent to take part in crossover - /// * `begin` - Start (inclusive) of substring to transplant - /// * `end` - End (exclusive) of substring to transplant - fn create_child( - &self, - p1: &IndividualT, - p2: &IndividualT, - begin: usize, - end: usize, - ) -> IndividualT - where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - { - let chromosome_len = p1.chromosome().len(); - - let mut substring_set: HashSet = HashSet::new(); - let mut new_chromosome: Vec> = vec![None; chromosome_len]; - let val_to_i_p2 = self.to_val_index_map(p2.chromosome()); - - #[allow(clippy::needless_range_loop)] - for i in begin..end { - substring_set.push(p1.chromosome()[i]); - new_chromosome[i] = Some(p1.chromosome()[i]) - } - - for i in begin..end { - let gene = p2.chromosome()[i]; - if substring_set.contains(&gene) { - continue; - } - - let mut j = i; - loop { - let val = &p1.chromosome()[j]; - let gene_place_candidate = val_to_i_p2.get(val).unwrap(); - if !(begin..end).contains(gene_place_candidate) { - new_chromosome[*gene_place_candidate] = Some(gene); - break; - } - j = *gene_place_candidate; - } - } - - let mut child_ch = IndividualT::ChromosomeT::default(); - for (index, gene_opt) in enumerate(new_chromosome) { - match gene_opt { - Some(gene) => child_ch.push(gene), - None => child_ch.push(p2.chromosome()[index]), - }; - } - IndividualT::from(child_ch) - } -} - -impl CrossoverOperator for Pmx -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy + Eq + Hash, - R: Rng, -{ - /// Returns a tuple of children, first child is created by taking a substring from parent_1, - /// second child is created by using a substring from parent_2 - /// - /// It works by taking a substring from one parent, then in second parent we look at genes one by one - /// that would be transplanted if we were transplanting from second parent, if the gene (gene_1) appares in transplanted string from parent one - /// then we ignore it, else: - /// * I. We remember the gene place index (index_1) - /// * II. We look what gene (gene_2) is at this place (index_1) in first parent - /// * III. We look for gene (gene_3) place (index_2) in second parent - /// * IV. If this gene (gene_3) can be found in transplanted genes then we place gene_1 in index_2 place, - /// else we go to step I. with gene_1 = gene_3 - /// - /// P1 : 8 4 7 3 6 2 5 1 9 0
- /// P2 : 0 1 2 3 4 5 6 7 8 9
- /// Ch : 0 7 4 3 6 2 5 1 8 9 - /// - /// Degenerated case when substring has length equal to genome length can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in crossover - /// * `parent_2` - Second parent to take part in crossover - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - assert_eq!( - parent_1.chromosome().len(), - parent_2.chromosome().len(), - "Parent chromosome length must match" - ); - - let chromosome_len = parent_1.chromosome().len(); - - let begin: usize = self.rng.gen_range(0..chromosome_len); - let end: usize = self.rng.gen_range(begin..=chromosome_len); - - let child_1 = self.create_child(parent_1, parent_2, begin, end); - let child_2 = self.create_child(parent_2, parent_1, begin, end); - - (child_1, child_2) - } -} - -/// # Shuffle crossover operator -/// -/// This struct implements [CrossoverOperator] trait and can be used with GA. -/// -/// It works by randomly shuffling both parents chromosome the same way then -/// selecting single cutpoint splitting both parents shuffled chromosomes in two parts. -/// First child gets `parent_1`'s first part and `parent_2`'s second part. -/// Second child gets `parent_2`'s first part and `parent_1`'s second part. -/// Lastly childs chromosomes are de-shuffled. -/// -/// Degenerated case when cutpoint is selected at index 0 or last can occur. -pub struct Shuffle { - rng: R, -} - -impl Shuffle { - /// Creates new [Shuffle] crossover operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Shuffle { - /// Creates new [Shuffle] crossover operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl CrossoverOperator for Shuffle -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index + Push, - GeneT: Copy, - R: Rng, -{ - /// Returns a tuple of children - /// - /// It works by randomly shuffling both parents chromosome the same way then - /// selecting single cutpoint splitting both parents shuffled chromosomes in two parts. - /// First child gets `parent_1`'s first part and `parent_2`'s second part. - /// Second child gets `parent_2`'s first part and `parent_1`'s second part. - /// Lastly childs chromosomes are de-shuffled. - /// - /// Degenerated case when cutpoint is selected at index 0 or last can occur. - /// - /// ## Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - let chromosome_len = parent_1.chromosome().len(); - let cut_point = self.rng.gen_range(0..chromosome_len); - - let mut shuffled = Vec::from_iter(0..chromosome_len); - shuffled.shuffle(&mut self.rng); - let mask = shuffled.iter().map(|x| x < &cut_point).collect_vec(); - - let mut child_1_ch = IndividualT::ChromosomeT::default(); - let mut child_2_ch = IndividualT::ChromosomeT::default(); - - for (i, fist_parent) in enumerate(mask) { - if fist_parent { - child_1_ch.push(parent_1.chromosome()[i]); - child_2_ch.push(parent_2.chromosome()[i]); - } else { - child_1_ch.push(parent_2.chromosome()[i]); - child_2_ch.push(parent_1.chromosome()[i]); - } - } - - (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) - } -} - -/// # Fixed point crossover operator -/// -/// Works just like `SinglePoint`, however the cut point is fixed and chosen apriori instead of -/// being random. -struct FixedPoint { - pub cut_point: usize, -} - -impl FixedPoint { - /// Returns new instance of the `FixedPoint` operator. - /// - /// # Arguments - /// - /// * `cut_point` - index of first gene that will be taken from second parent to first child - pub fn new(cut_point: usize) -> Self { - Self { cut_point } - } -} - -impl CrossoverOperator for FixedPoint -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: IndexMut + Push, - GeneT: Copy, -{ - /// Returns a tuple of children - /// - /// It works by cutting parent chromosomes in single, fixed point and the acting like a single - /// point crossover. - /// - /// # Arguments - /// - /// * `parent_1` - First parent to take part in recombination - /// * `parent_2` - Second parent to take part in recombination - fn apply( - &mut self, - _metadata: &GAMetadata, - parent_1: &IndividualT, - parent_2: &IndividualT, - ) -> (IndividualT, IndividualT) { - let mut child_1 = parent_1.clone(); - let mut child_2 = parent_2.clone(); - - for i in self.cut_point..parent_1.chromosome().len() { - child_1.chromosome_mut()[i] = parent_2.chromosome()[i]; - child_2.chromosome_mut()[i] = parent_1.chromosome()[i]; - } - - (child_1, child_2) - } -} - -#[cfg(test)] -mod test { - use crate::ga::individual::IndividualTrait; - use crate::ga::operators::crossover::Ppx; - use crate::ga::operators::crossover::{CrossoverOperator, FixedPoint, Pmx, Shuffle}; - use crate::ga::{GAMetadata, Individual}; - use std::iter::zip; - - #[test] - fn check_ppx_example() { - let op = Ppx::new(); - let p1 = Individual::from(vec![1, 2, 3, 4, 5, 6]); - let p2 = Individual::from(vec![3, 1, 2, 6, 4, 5]); - let take_from_p1 = [true, false, true, true, false, false]; - - let child = op.create_child(&p1, &p2, &take_from_p1); - - child - .chromosome() - .iter() - .zip([1, 3, 2, 4, 6, 5].iter()) - .for_each(|(x, x_expected)| assert_eq!(x, x_expected)) - } - - #[test] - fn check_pmx_example() { - // https://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/PMXCrossoverOperator.aspx/ - let op = Pmx::new(); - - let p1 = Individual::from(vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]); - let p2 = Individual::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - - let child = op.create_child(&p1, &p2, 3, 8); - for (i, j) in zip(child.chromosome, vec![0, 7, 4, 3, 6, 2, 5, 1, 8, 9]) { - assert_eq!(i, j); - } - } - - #[test] - fn shuffle_gives_appropriate_len() { - let mut op = Shuffle::new(); - - let p1 = Individual::from(vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]); - let p2 = Individual::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - - let (child_1, child_2) = op.apply(&GAMetadata::default(), &p1, &p2); - assert_eq!(child_1.chromosome.len(), 10); - assert_eq!(child_2.chromosome.len(), 10); - } - - #[test] - fn shuffle_fulfills_conditions() { - let mut op = Shuffle::new(); - - let p1 = Individual::from(vec![1, 0, 0, 1, 0, 1, 0, 1, 0, 0]); - let p2 = Individual::from(vec![0, 1, 1, 0, 1, 0, 1, 0, 1, 1]); - - let (c1, c2) = op.apply(&GAMetadata::default(), &p1, &p2); - for (g1, g2) in c1.chromosome.iter().zip(c2.chromosome.iter()) { - assert_eq!(g1 * g2, 0); - assert_eq!(g1 + g2, 1); - } - } - - #[test] - fn fixed_point_works_as_expected() { - let mut op = FixedPoint::new(4); - - let parent_1_chromosome = vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]; - let parent_2_chromosome = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - - let p1 = Individual::from(parent_1_chromosome.clone()); - let p2 = Individual::from(parent_2_chromosome.clone()); - - let (child_1, child_2) = op.apply(&GAMetadata::default(), &p1, &p2); - - let child_1_expected_chromosome = vec![8, 4, 7, 3, 4, 5, 6, 7, 8, 9]; - let child_2_expected_chromosome = vec![0, 1, 2, 3, 6, 2, 5, 1, 9, 0]; - - assert_eq!(child_1.chromosome(), &child_1_expected_chromosome); - assert_eq!(child_2.chromosome(), &child_2_expected_chromosome); - } -} diff --git a/src/ga/operators/crossover/impls.rs b/src/ga/operators/crossover/impls.rs new file mode 100644 index 00000000..a49c173d --- /dev/null +++ b/src/ga/operators/crossover/impls.rs @@ -0,0 +1,1083 @@ +use itertools::{enumerate, Itertools}; +use len_trait::Len; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::ops::{Index, IndexMut}; + +use crate::ga::individual::{Chromosome, IndividualTrait}; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; +use rand::prelude::SliceRandom; +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # Single point crossover operator +/// +/// This struct implements [CrossoverOperator] trait and can be used with GA. +/// +/// It works by defininig single cutpoint splitting both parent chromosomes in two parts. +/// First child gets `parent_1`'s first part and `parent_2`'s second part. +/// Second child gets `parent_2`'s first part and `parent_1`'s second part. +/// +/// Degenerated case when cutpoint is selected at index 0 or last can occur. +pub struct SinglePoint { + rng: R, +} + +impl SinglePoint { + /// Creates new [SinglePoint] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl SinglePoint { + /// Creates new [SinglePoint] crossover operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl CrossoverOperator for SinglePoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns a tuple of children + /// + /// It works by randomly selecting single cutpoint splitting both parent chromosomes in two parts. + /// First child gets `parent_1`'s first part and `parent_2`'s second part. + /// Second child gets `parent_2`'s first part and `parent_1`'s second part. + /// + /// Degenerated case when cutpoint is selected at index 0 or last can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + let chromosome_len = parent_1.chromosome().len(); + let cut_point = self.rng.gen_range(0..chromosome_len); + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + for locus in 0..cut_point { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + } + + for locus in cut_point..chromosome_len { + child_1_ch.push(parent_2.chromosome()[locus]); + child_2_ch.push(parent_1.chromosome()[locus]); + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Two point crossover operator +/// +/// This struct implements [CrossoverOperator] and can be used with GA. +/// +/// It works by randomly selecting two cutpoints splitting parents chromosomes in three parts. +/// Then it creates children by taking parents chromosome parts interchangeably. +/// Its mechanism is analoguous to [SinglePoint]. +/// +/// Degenerate case when both cutpoints are in the same place or at position 0 or last can occur. +pub struct TwoPoint { + rng: R, +} + +impl TwoPoint { + /// Creates new [TwoPoint] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl TwoPoint { + /// Creates new [TwoPoint] crossover operator with custom RNG + pub fn with_rng(rng: R) -> Self { + TwoPoint { rng } + } +} + +impl CrossoverOperator for TwoPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns a tuple of children + /// + /// It works by randomly selecting two cutpoints splitting parents chromosomes in three parts. + /// Then it creates children by taking parents chromosome parts interchangeably. + /// Its mechanism is analoguous to [SinglePoint]. + /// + /// Degenerate case when both cutpoints are in the same place or at position 0 or last can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let cut_points = ( + self.rng.gen_range(0..chromosome_len), + self.rng.gen_range(0..chromosome_len), + ); + + let (cut_point_1, cut_point_2) = if cut_points.0 <= cut_points.1 { + (cut_points.0, cut_points.1) + } else { + (cut_points.1, cut_points.0) + }; + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + for locus in 0..cut_point_1 { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + } + + for locus in cut_point_1..cut_point_2 { + child_1_ch.push(parent_2.chromosome()[locus]); + child_2_ch.push(parent_1.chromosome()[locus]); + } + + for locus in cut_point_2..chromosome_len { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Mutli-point crossover operator +/// +/// This struct implements [CrossoverOperator] and can be used with GA. +/// +/// It works analogously to [SinglePoint] or [TwoPoint]. One important difference is that +/// all cutpoints are distinct, thus single or two point crossover with guarantee of distinct cutpoints +/// can be achieved. +pub struct MultiPoint { + cut_points_no: usize, + rng: R, +} + +impl MultiPoint { + /// Creates new [MultiPoint] crossover operator with default RNG + /// + /// ## Arguments + /// + /// * `cut_points_no` - Number of cutpoints (crossover points) + pub fn new(cut_points_no: usize) -> Self { + Self::with_rng(cut_points_no, rand::thread_rng()) + } +} + +impl Default for MultiPoint { + /// Creates new [MultiPoint] crossover operator with 4 cutpoints and default RNG + fn default() -> Self { + Self::with_rng(4, rand::thread_rng()) + } +} + +impl MultiPoint { + /// Creates new [MultiPoint] crossover operator with custom RNG + /// + /// ## Arguments + /// + /// * `cut_points_no` - Number of cutpoints (crossover points) + /// * `rng` - Custom random number generator + pub fn with_rng(cut_points_no: usize, rng: R) -> Self { + assert!(cut_points_no >= 1, "Number of cut points must be >= 1"); + Self { cut_points_no, rng } + } +} + +impl CrossoverOperator for MultiPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns a tuple of children + /// + /// It works analogously to [SinglePoint] or [TwoPoint]. One important difference is that + /// all cutpoints are distinct, thus single or two point crossover with guarantee of distinct cutpoints + /// can be achieved. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + assert!( + self.cut_points_no <= parent_1.chromosome().len(), + "There can't be more cut points than chromosome length" + ); + assert!(self.cut_points_no >= 1, "Numver of cut points must be >= 1"); + + let chromosome_len = parent_1.chromosome().len(); + + let mut cut_points = + rand::seq::index::sample(&mut self.rng, chromosome_len, self.cut_points_no).into_vec(); + cut_points.sort_unstable(); + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + let (mut curr_parent_1, mut curr_parent_2) = (&parent_1, &parent_2); + + for locus in 0..cut_points[0] { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + (curr_parent_1, curr_parent_2) = (curr_parent_2, curr_parent_1); + } + + for cut_point_idx in 0..self.cut_points_no - 1 { + for locus in cut_points[cut_point_idx]..cut_points[cut_point_idx + 1] { + child_1_ch.push(curr_parent_1.chromosome()[locus]); + child_2_ch.push(curr_parent_2.chromosome()[locus]); + } + (curr_parent_1, curr_parent_2) = (curr_parent_2, curr_parent_1); + } + + for locus in cut_points[self.cut_points_no - 1]..chromosome_len { + child_1_ch.push(curr_parent_1.chromosome()[locus]); + child_2_ch.push(curr_parent_2.chromosome()[locus]); + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Uniform crossover operator +/// +/// This struct implements [CrossoverOperator] and can be used with GA. +/// +/// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first +/// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. +pub struct Uniform { + rng: R, +} + +impl Uniform { + /// Creates new [Uniform] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Uniform { + /// Creates new [Uniform] crossover operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl CrossoverOperator for Uniform +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng + Clone, +{ + /// Returns a tuple of children + /// + /// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first + /// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + let mask = self + .rng + .clone() + .sample_iter(rand::distributions::Uniform::new(0.0, 1.0)) + .take(chromosome_len); + + for (locus, val) in mask.enumerate() { + if val >= 0.5 { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + } else { + child_1_ch.push(parent_2.chromosome()[locus]); + child_2_ch.push(parent_1.chromosome()[locus]); + } + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Parameterized Uniform crossover operator +/// +/// This struct implements [CrossoverOperator] and can be used with GA. +/// +/// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first +/// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. +/// +/// Bias is a probability of drawing a 1 in the bit-mask. +pub struct UniformParameterized { + rng: R, + distr: rand::distributions::Uniform, + bias: f64, +} + +impl UniformParameterized { + pub fn new(bias: f64) -> Self { + Self::with_rng(rand::thread_rng(), bias) + } +} + +impl UniformParameterized { + pub fn with_rng(rng: R, bias: f64) -> Self { + Self { + rng, + distr: rand::distributions::Uniform::new(0.0, 1.0), + bias, + } + } +} + +impl CrossoverOperator for UniformParameterized +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng + Clone, +{ + /// Returns a tuple of children + /// + /// It works by creating a bit-mask of chromosome length. 1 means that gene should be taken from first + /// parent, 0 means that gene should be take from second parent. This is inverted when creating second child. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + let mask = self.rng.clone().sample_iter(self.distr).take(chromosome_len); + + for (locus, val) in mask.enumerate() { + if val <= self.bias { + child_1_ch.push(parent_1.chromosome()[locus]); + child_2_ch.push(parent_2.chromosome()[locus]); + } else { + child_1_ch.push(parent_2.chromosome()[locus]); + child_2_ch.push(parent_1.chromosome()[locus]); + } + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Ordered crossover operator +/// +/// This struct implements [CrossoverOperator] trait and can be used with GA. +/// +/// It works by taking a substring from one parent, and filling the missing places with alleles from +/// second parent in the order they appear in. +/// +/// P1 : 2 4 1 3 5
+/// P2 : 5 2 1 4 3
+/// Ch : 5 4 1 3 2 +/// +/// Degenerated case when substring has length equal to genome length can occur. +pub struct OrderedCrossover { + rng: R, +} + +impl OrderedCrossover { + /// Creates new [OrderedCrossover] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl OrderedCrossover { + pub fn with_rng(rng: R) -> Self { + Self { rng } + } + + /// Helper function for [OrderedCrossover::apply] + /// ## Arguments + /// + /// * `p1` - First parent to take part in crossover + /// * `p2` - Second parent to take part in crossover + /// * `begin` - Start (inclusive) of substring to transplant + /// * `end` - End (exclusive) of substring to transplant + fn create_child( + &self, + p1: &IndividualT, + p2: &IndividualT, + begin: usize, + end: usize, + ) -> IndividualT + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + let chromosome_len = p1.chromosome().len(); + + let mut substring_set: HashSet = HashSet::new(); + + for i in begin..end { + substring_set.push(p1.chromosome()[i]); + } + + let mut child_ch = IndividualT::ChromosomeT::default(); + let mut index: usize = 0; + + while child_ch.len() < begin { + let gene = p2.chromosome()[index]; + if !substring_set.contains(&gene) { + child_ch.push(gene); + } + index += 1; + } + + for i in begin..end { + child_ch.push(p1.chromosome()[i]); + } + + while index < chromosome_len { + let gene = p2.chromosome()[index]; + if !substring_set.contains(&gene) { + child_ch.push(gene); + } + index += 1; + } + IndividualT::from(child_ch) + } +} + +impl CrossoverOperator for OrderedCrossover +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns a tuple of children, first child is created by taking a substring from parent_1, + /// second child is created by using a substring from parent_2 + /// + /// It works by taking a substring from one parent, and filling the missing places with alleles from + /// second parent in the order they appear in. + /// + /// P1 : 2 4 1 3 5
+ /// P2 : 5 2 1 4 3
+ /// Ch : 5 4 1 3 2 + /// + /// Degenerated case when substring has length equal to genome length can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in crossover + /// * `parent_2` - Second parent to take part in crossover + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let begin: usize = self.rng.gen_range(0..chromosome_len); + let end: usize = self.rng.gen_range(begin..=chromosome_len); + + let child_1 = self.create_child(parent_1, parent_2, begin, end); + let child_2 = self.create_child(parent_2, parent_1, begin, end); + + (child_1, child_2) + } +} + +/// # PPX crossover operator +/// +/// This struct implements [CrossoverOperator] trait and can be used with GA. +/// +/// PPX (Precedence Preservative Crossover), genes are taken in order they appear in parent, +/// parent is chosen at random, if gene was already taken from other parent then the next un-taken gene +/// is chosen +/// +/// P1 : 2 4 1 3 5
+/// P2 : 5 2 1 4 3
+/// Gene source: 1 1 2 1 2
+/// Ch : 2 4 5 1 3 +/// +/// Degenerated case when all genes are taken from the same parent. +pub struct Ppx { + rng: R, + distribution: rand::distributions::Standard, +} + +impl Ppx { + /// Creates new [Ppx] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Ppx { + /// Creates new [PPXCrossover] crossover operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { + rng, + distribution: rand::distributions::Standard, + } + } + + /// Helper function for [Ppx::apply] + /// ## Arguments + /// + /// * `p1` - First parent to take part in crossover + /// * `p2` - Second parent to take part in crossover + /// * `take_from_p1` - Which genes should be taken from p1 + fn create_child( + &self, + p1: &IndividualT, + p2: &IndividualT, + take_from_p1: &[bool], + ) -> IndividualT + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + let chromosome_len = p1.chromosome().len(); + + let mut already_taken: HashSet = HashSet::new(); + + let mut child_ch = IndividualT::ChromosomeT::default(); + let mut index_p: [usize; 2] = [0, 0]; + let parents = [p1, p2]; + + while child_ch.len() < chromosome_len { + let index_child = child_ch.len(); + let parent_i = usize::from(!take_from_p1[index_child]); + + while child_ch.len() == index_child { + let gene = parents[parent_i].chromosome()[index_p[parent_i]]; + index_p[parent_i] += 1; + + if !already_taken.contains(&gene) { + already_taken.push(gene); + child_ch.push(gene); + } + } + } + IndividualT::from(child_ch) + } +} + +impl CrossoverOperator for Ppx +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns a tuple of children, first child is created by using parent_1 as first parent, + /// second child is created by using a parent_1 as the second parent. + /// + /// PPX (Precedence Preservative Crossover), genes are taken in order they appear in parent, + /// parent is chosen at random, if gene was already taken from other parent then the next un-taken gene + /// is chosen + /// + /// P1 : 2 4 1 3 5
+ /// P2 : 5 2 1 4 3
+ /// Gene source: 1 1 2 1 2
+ /// Ch : 2 4 5 1 3 + /// + /// Degenerated case when all genes are taken from the same parent can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - one of the parents to take part in crossover + /// * `parent_2` - one of the parents to take part in crossover + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let take_from_p1: Vec = (&mut self.rng) + .sample_iter(self.distribution) + .take(chromosome_len) + .collect_vec(); + + let child_1 = self.create_child(parent_1, parent_2, &take_from_p1); + let child_2 = self.create_child(parent_2, parent_1, &take_from_p1); + + (child_1, child_2) + } +} + +/// # PMX crossover operator +/// +/// This struct implements [CrossoverOperator] trait and can be used with GA. +/// +/// Returns a tuple of children, first child is created by taking a substring from parent_1, +/// second child is created by using a substring from parent_2 +/// +/// It works by taking a substring from one parent, then in second parent we look at genes one by one +/// that would be transplanted if we were transplanting from second parent, if the gene (gene_1) appares in transplanted string from parent one +/// then we ignore it, else: +/// * I. We remember the gene place index (index_1) +/// * II. We look what gene (gene_2) is at this place (index_1) in first parent +/// * III. We look for gene (gene_3) place (index_2) in second parent +/// * IV. If this gene (gene_3) can be found in transplanted genes then we place gene_1 in index_2 place, +/// else we go to step I. with gene_1 = gene_3 +/// +/// P1 : 8 4 7 3 6 2 5 1 9 0
+/// P2 : 0 1 2 3 4 5 6 7 8 9
+/// Ch : 0 7 4 3 6 2 5 1 8 9 +/// +/// Degenerated case when substring has length equal to genome length can occur. +/// +pub struct Pmx { + rng: R, +} + +impl Pmx { + /// Creates new [Pmx] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Pmx { + pub fn with_rng(rng: R) -> Self { + Self { rng } + } + + fn to_val_index_map(&self, chromosome: &ChT) -> HashMap + where + ChT: Chromosome + Index + Push, + GeneT: Copy + Eq + Hash, + { + let chromosome_len = chromosome.len(); + let mut val_index_map: HashMap = HashMap::new(); + + for i in 0..chromosome_len { + val_index_map.push((chromosome[i], i)); + } + + val_index_map + } + + /// Helper function for [Pmx::apply] + /// ## Arguments + /// + /// * `p1` - First parent to take part in crossover + /// * `p2` - Second parent to take part in crossover + /// * `begin` - Start (inclusive) of substring to transplant + /// * `end` - End (exclusive) of substring to transplant + fn create_child( + &self, + p1: &IndividualT, + p2: &IndividualT, + begin: usize, + end: usize, + ) -> IndividualT + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + let chromosome_len = p1.chromosome().len(); + + let mut substring_set: HashSet = HashSet::new(); + let mut new_chromosome: Vec> = vec![None; chromosome_len]; + let val_to_i_p2 = self.to_val_index_map(p2.chromosome()); + + #[allow(clippy::needless_range_loop)] + for i in begin..end { + substring_set.push(p1.chromosome()[i]); + new_chromosome[i] = Some(p1.chromosome()[i]) + } + + for i in begin..end { + let gene = p2.chromosome()[i]; + if substring_set.contains(&gene) { + continue; + } + + let mut j = i; + loop { + let val = &p1.chromosome()[j]; + let gene_place_candidate = val_to_i_p2.get(val).unwrap(); + if !(begin..end).contains(gene_place_candidate) { + new_chromosome[*gene_place_candidate] = Some(gene); + break; + } + j = *gene_place_candidate; + } + } + + let mut child_ch = IndividualT::ChromosomeT::default(); + for (index, gene_opt) in enumerate(new_chromosome) { + match gene_opt { + Some(gene) => child_ch.push(gene), + None => child_ch.push(p2.chromosome()[index]), + }; + } + IndividualT::from(child_ch) + } +} + +impl CrossoverOperator for Pmx +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns a tuple of children, first child is created by taking a substring from parent_1, + /// second child is created by using a substring from parent_2 + /// + /// It works by taking a substring from one parent, then in second parent we look at genes one by one + /// that would be transplanted if we were transplanting from second parent, if the gene (gene_1) appares in transplanted string from parent one + /// then we ignore it, else: + /// * I. We remember the gene place index (index_1) + /// * II. We look what gene (gene_2) is at this place (index_1) in first parent + /// * III. We look for gene (gene_3) place (index_2) in second parent + /// * IV. If this gene (gene_3) can be found in transplanted genes then we place gene_1 in index_2 place, + /// else we go to step I. with gene_1 = gene_3 + /// + /// P1 : 8 4 7 3 6 2 5 1 9 0
+ /// P2 : 0 1 2 3 4 5 6 7 8 9
+ /// Ch : 0 7 4 3 6 2 5 1 8 9 + /// + /// Degenerated case when substring has length equal to genome length can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in crossover + /// * `parent_2` - Second parent to take part in crossover + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + assert_eq!( + parent_1.chromosome().len(), + parent_2.chromosome().len(), + "Parent chromosome length must match" + ); + + let chromosome_len = parent_1.chromosome().len(); + + let begin: usize = self.rng.gen_range(0..chromosome_len); + let end: usize = self.rng.gen_range(begin..=chromosome_len); + + let child_1 = self.create_child(parent_1, parent_2, begin, end); + let child_2 = self.create_child(parent_2, parent_1, begin, end); + + (child_1, child_2) + } +} + +/// # Shuffle crossover operator +/// +/// This struct implements [CrossoverOperator] trait and can be used with GA. +/// +/// It works by randomly shuffling both parents chromosome the same way then +/// selecting single cutpoint splitting both parents shuffled chromosomes in two parts. +/// First child gets `parent_1`'s first part and `parent_2`'s second part. +/// Second child gets `parent_2`'s first part and `parent_1`'s second part. +/// Lastly childs chromosomes are de-shuffled. +/// +/// Degenerated case when cutpoint is selected at index 0 or last can occur. +pub struct Shuffle { + rng: R, +} + +impl Shuffle { + /// Creates new [Shuffle] crossover operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Shuffle { + /// Creates new [Shuffle] crossover operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl CrossoverOperator for Shuffle +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns a tuple of children + /// + /// It works by randomly shuffling both parents chromosome the same way then + /// selecting single cutpoint splitting both parents shuffled chromosomes in two parts. + /// First child gets `parent_1`'s first part and `parent_2`'s second part. + /// Second child gets `parent_2`'s first part and `parent_1`'s second part. + /// Lastly childs chromosomes are de-shuffled. + /// + /// Degenerated case when cutpoint is selected at index 0 or last can occur. + /// + /// ## Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + let chromosome_len = parent_1.chromosome().len(); + let cut_point = self.rng.gen_range(0..chromosome_len); + + let mut shuffled = Vec::from_iter(0..chromosome_len); + shuffled.shuffle(&mut self.rng); + let mask = shuffled.iter().map(|x| x < &cut_point).collect_vec(); + + let mut child_1_ch = IndividualT::ChromosomeT::default(); + let mut child_2_ch = IndividualT::ChromosomeT::default(); + + for (i, fist_parent) in enumerate(mask) { + if fist_parent { + child_1_ch.push(parent_1.chromosome()[i]); + child_2_ch.push(parent_2.chromosome()[i]); + } else { + child_1_ch.push(parent_2.chromosome()[i]); + child_2_ch.push(parent_1.chromosome()[i]); + } + } + + (IndividualT::from(child_1_ch), IndividualT::from(child_2_ch)) + } +} + +/// # Fixed point crossover operator +/// +/// Works just like `SinglePoint`, however the cut point is fixed and chosen apriori instead of +/// being random. +pub struct FixedPoint { + pub cut_point: usize, +} + +impl FixedPoint { + /// Returns new instance of the `FixedPoint` operator. + /// + /// # Arguments + /// + /// * `cut_point` - index of first gene that will be taken from second parent to first child + pub fn new(cut_point: usize) -> Self { + Self { cut_point } + } +} + +impl CrossoverOperator for FixedPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Push, + GeneT: Copy, +{ + /// Returns a tuple of children + /// + /// It works by cutting parent chromosomes in single, fixed point and the acting like a single + /// point crossover. + /// + /// # Arguments + /// + /// * `parent_1` - First parent to take part in recombination + /// * `parent_2` - Second parent to take part in recombination + fn apply( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) { + let mut child_1 = parent_1.clone(); + let mut child_2 = parent_2.clone(); + + for i in self.cut_point..parent_1.chromosome().len() { + child_1.chromosome_mut()[i] = parent_2.chromosome()[i]; + child_2.chromosome_mut()[i] = parent_1.chromosome()[i]; + } + + (child_1, child_2) + } +} + +#[cfg(test)] +mod test { + use crate::ga::individual::IndividualTrait; + use crate::ga::operators::crossover::{CrossoverOperator, FixedPoint, Pmx, Ppx, Shuffle}; + use crate::ga::{GAMetadata, Individual}; + use std::iter::zip; + + #[test] + fn check_ppx_example() { + let op = Ppx::new(); + let p1 = Individual::from(vec![1, 2, 3, 4, 5, 6]); + let p2 = Individual::from(vec![3, 1, 2, 6, 4, 5]); + let take_from_p1 = [true, false, true, true, false, false]; + + let child = op.create_child(&p1, &p2, &take_from_p1); + + child + .chromosome() + .iter() + .zip([1, 3, 2, 4, 6, 5].iter()) + .for_each(|(x, x_expected)| assert_eq!(x, x_expected)) + } + + #[test] + fn check_pmx_example() { + // https://www.rubicite.com/Tutorials/GeneticAlgorithms/CrossoverOperators/PMXCrossoverOperator.aspx/ + let op = Pmx::new(); + + let p1 = Individual::from(vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]); + let p2 = Individual::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + let child = op.create_child(&p1, &p2, 3, 8); + for (i, j) in zip(child.chromosome, vec![0, 7, 4, 3, 6, 2, 5, 1, 8, 9]) { + assert_eq!(i, j); + } + } + + #[test] + fn shuffle_gives_appropriate_len() { + let mut op = Shuffle::new(); + + let p1 = Individual::from(vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]); + let p2 = Individual::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + + let (child_1, child_2) = op.apply(&GAMetadata::default(), &p1, &p2); + assert_eq!(child_1.chromosome.len(), 10); + assert_eq!(child_2.chromosome.len(), 10); + } + + #[test] + fn shuffle_fulfills_conditions() { + let mut op = Shuffle::new(); + + let p1 = Individual::from(vec![1, 0, 0, 1, 0, 1, 0, 1, 0, 0]); + let p2 = Individual::from(vec![0, 1, 1, 0, 1, 0, 1, 0, 1, 1]); + + let (c1, c2) = op.apply(&GAMetadata::default(), &p1, &p2); + for (g1, g2) in c1.chromosome.iter().zip(c2.chromosome.iter()) { + assert_eq!(g1 * g2, 0); + assert_eq!(g1 + g2, 1); + } + } + + #[test] + fn fixed_point_works_as_expected() { + let mut op = FixedPoint::new(4); + + let parent_1_chromosome = vec![8, 4, 7, 3, 6, 2, 5, 1, 9, 0]; + let parent_2_chromosome = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + + let p1 = Individual::from(parent_1_chromosome.clone()); + let p2 = Individual::from(parent_2_chromosome.clone()); + + let (child_1, child_2) = op.apply(&GAMetadata::default(), &p1, &p2); + + let child_1_expected_chromosome = vec![8, 4, 7, 3, 4, 5, 6, 7, 8, 9]; + let child_2_expected_chromosome = vec![0, 1, 2, 3, 6, 2, 5, 1, 9, 0]; + + assert_eq!(child_1.chromosome(), &child_1_expected_chromosome); + assert_eq!(child_2.chromosome(), &child_2_expected_chromosome); + } +} diff --git a/src/ga/operators/mutation.rs b/src/ga/operators/mutation.rs index 2fb0734a..f59f5636 100644 --- a/src/ga/operators/mutation.rs +++ b/src/ga/operators/mutation.rs @@ -1,8 +1,7 @@ -use std::{marker::PhantomData, ops::IndexMut}; - -use len_trait::Len; -use push_trait::{Nothing, Push}; -use rand::{rngs::ThreadRng, Rng}; +#[cfg(feature = "ga_impl_mutation")] +pub mod impls; +#[cfg(feature = "ga_impl_mutation")] +pub use impls::*; use crate::ga::{individual::IndividualTrait, GAMetadata}; @@ -19,391 +18,3 @@ pub trait MutationOperator { /// * `mutation_rate` - probability of gene mutation fn apply(&mut self, metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64); } - -/// # Identity Mutation Operator -/// -/// This struct implements [MutationOperator] trait and can be used with GA. -/// -/// Identity does not perform any changes to the chromosome. Use this if you -/// do not want to mutate your solutions. -pub struct Identity; - -impl Identity { - /// Returns new [Identity] mutation operator - pub fn new() -> Self { - Identity {} - } -} - -impl MutationOperator for Identity { - fn apply(&mut self, _metadata: &GAMetadata, _individual: &mut IndividualT, _mutation_rate: f64) {} -} - -/// ### Flilp bit mutation operator -/// -/// This struct implements [MutationOperator] trait and can be used with GA -/// -/// Genes are muatated by flipping the value - `1` becomes `0` and vice versa -pub struct FlipBit { - rng: R, -} - -impl FlipBit { - /// Returns new instance of [FlipBit] mutation operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl FlipBit { - /// Returns new instance of [FlipBit] mutation operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl MutationOperator for FlipBit -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: IndexMut + Push, - R: Rng, -{ - /// Mutates provided solution in place - /// - /// Genes are muatated by flipping the value - `1` becomes `0` and vice versa - /// - /// ## Arguments - /// - /// * `individual` - mutable reference to to-be-mutated individual - /// * `mutation_rate` - probability of gene mutation - fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { - let distribution = rand::distributions::Uniform::from(0.0..1.0); - let chromosome_ref = individual.chromosome_mut(); - let chromosome_len = chromosome_ref.len(); - - for i in 0..chromosome_len { - if self.rng.sample(distribution) < mutation_rate { - chromosome_ref[i] = !chromosome_ref[i]; - } - } - } -} - -/// ### Interchange mustation operator -/// -/// This struct implements [MutationOperator] trait and can be used with GA -/// -/// If a gene is to be muatated, a new locus is randomly choosen and gene values are interchanged -pub struct Interchange { - rng: R, -} - -impl Interchange { - /// Returns new instance of [Interchange] mutation operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Interchange { - /// Returns new instance of [Interchange] mutation operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl MutationOperator for Interchange -where - G: Copy, - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: IndexMut + Push, - R: Rng, -{ - /// Mutates provided solution in place - /// - /// If a gene is to be muatated, a new locus is randomly choosen and gene values are interchanged - /// - /// ## Arguments - /// - /// * `individual` - mutable reference to to-be-mutated individual - /// * `mutation_rate` - probability of gene mutation - fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { - let chromosome_ref = individual.chromosome_mut(); - let chromosome_len = chromosome_ref.len(); - - let dist = rand::distributions::Uniform::from(0.0..1.0); - let index_dist = rand::distributions::Uniform::from(0..chromosome_len); - - for i in 0..chromosome_len { - if self.rng.sample(dist) < mutation_rate { - let rand_index = rand::thread_rng().sample(index_dist); - let gene = chromosome_ref[rand_index]; - chromosome_ref[rand_index] = chromosome_ref[i]; - chromosome_ref[i] = gene; - } - } - } -} - -/// ### Reversing mutation operator -/// -/// This struct implements [MutationOperator] trait and can be used with GA -/// -/// Random locus is selected and genes next to the selection position are reversed -pub struct Reversing { - rng: R, -} - -impl Reversing { - /// Returns new instance of [Reversing] mutation operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Reversing { - /// Returns new instance of [Reversing] mutation operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -impl MutationOperator for Reversing -where - GeneT: Copy, - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: IndexMut + Push, - R: Rng, -{ - /// Mutates provided solution in place - /// - /// Random locus is selected and genes next to the selection position are reversed - /// - /// ## Arguments - /// - /// * `individual` - mutable reference to to-be-mutated individual - /// * `mutation_rate` - probability of gene mutation - fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { - let dist = rand::distributions::Uniform::from(0.0..1.0); - let chromosome_ref = individual.chromosome_mut(); - let chromosome_len = chromosome_ref.len(); - - for i in 1..chromosome_len { - if self.rng.sample(dist) < mutation_rate { - let gene = chromosome_ref[i]; - chromosome_ref[i] = chromosome_ref[i - 1]; - chromosome_ref[i - 1] = gene; - } - } - } -} - -/// ### [Inversion] mutation operator -/// -/// This struct implements [MutationOperator] trait and can be used with GA -/// -/// Two random locations are chosen marking out a segment of chromosome. -/// Genes from this segment are then rotated around the segment's middle point. -pub struct Inversion { - rng: R, - _marker: PhantomData, -} - -impl Inversion { - /// Returns new instance of [Inversion] mutation operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl Inversion { - /// Returns new instance of [Inversion] mutation operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { - rng, - _marker: PhantomData, - } - } -} - -impl MutationOperator for Inversion -where - IndividualT::ChromosomeT: Len + AsMut<[GeneT]>, -{ - /// Mutates provided solution in place - /// - /// Two random locations are chosen marking out a segment of chromosome. - /// Genes from this segment are then rotated around the segment's middle point. - /// - /// ## Arguments - /// - /// * `individual` - mutable reference to to-be-mutated individual - /// * `mutation_rate` - probability of gene mutation - fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { - let _marker: PhantomData = PhantomData; - - let r: f64 = self.rng.gen(); - - if r > mutation_rate { - return; - } - - let chromosome_len = individual.chromosome().len(); - let mut from: usize = self.rng.gen_range(0..chromosome_len); - let mut to: usize = self.rng.gen_range(from..chromosome_len); - - while from < to { - individual.chromosome_mut().as_mut().swap(from, to); - from += 1; - to -= 1; - } - } -} - -#[cfg(test)] -mod tests { - use crate::ga::{individual::IndividualTrait, GAMetadata, Individual}; - use itertools::Itertools; - use rand::{distributions::Uniform, Rng}; - - use super::{FlipBit, Identity, Interchange, MutationOperator, Reversing}; - - #[test] - fn identity_does_not_change_chromosome() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(30) - .collect_vec(); - - let mut individual = Individual { - chromosome: chromosome.clone(), - fitness: f64::default(), - }; - - let mut identity_mutation = Identity; - - identity_mutation.apply(&GAMetadata::default(), &mut individual, 1.); - - assert_eq!(chromosome, individual.chromosome); - } - - #[test] - fn flipbit_negates_chromosome() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(30) - .map(|val| val > 0.) - .collect_vec(); - - let chromosome_clone = chromosome.clone(); - - let mut individual = Individual { - chromosome, - fitness: f64::default(), - }; - - let mut operator = FlipBit::new(); - - operator.apply(&GAMetadata::default(), &mut individual, 1.); - - for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { - assert_eq!(actual, !*expected); - } - } - - #[test] - fn flipbit_does_not_mutate_rate_0() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(30) - .map(|val| val > 0.) - .collect_vec(); - - let chromosome_clone = chromosome.clone(); - - let mut individual = Individual { - chromosome, - fitness: f64::default(), - }; - - let mut operator = FlipBit::new(); - - operator.apply(&GAMetadata::default(), &mut individual, 0.); - - for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { - assert_eq!(actual, *expected); - } - } - - #[test] - fn interchange_introduces_changes() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(300) - .map(|val| val > 0.) - .collect_vec(); - - let chromosome_clone = chromosome.clone(); - - let mut individual = Individual { - chromosome, - fitness: f64::default(), - }; - - let mut operator = Interchange::new(); - - operator.apply(&GAMetadata::default(), &mut individual, 1.); - let changes = std::iter::zip(chromosome_clone, individual.chromosome()) - .filter(|p| p.0 != *p.1) - .count(); - assert!(changes > 0); - } - - #[test] - fn interchange_does_not_mutate_rate_0() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(30) - .map(|val| val > 0.) - .collect_vec(); - - let chromosome_clone = chromosome.clone(); - - let mut individual = Individual { - chromosome, - fitness: f64::default(), - }; - - let mut operator = Interchange::new(); - - operator.apply(&GAMetadata::default(), &mut individual, 0.); - - for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { - assert_eq!(actual, *expected); - } - } - - #[test] - fn reversing_bubbles_first_gene_when_rate_1() { - let chromosome = rand::thread_rng() - .sample_iter(Uniform::from(-1.0..1.0)) - .take(40) - .collect_vec(); - - let mut individual = Individual { - chromosome, - fitness: f64::default(), - }; - - let first_gene_value = individual.chromosome()[0]; - - let mut operator = Reversing::new(); - - operator.apply(&GAMetadata::default(), &mut individual, 1.0); - - assert_eq!( - first_gene_value, - individual.chromosome()[individual.chromosome().len() - 1] - ); - } -} diff --git a/src/ga/operators/mutation/impls.rs b/src/ga/operators/mutation/impls.rs new file mode 100644 index 00000000..ef8abc94 --- /dev/null +++ b/src/ga/operators/mutation/impls.rs @@ -0,0 +1,397 @@ +use std::{marker::PhantomData, ops::IndexMut}; + +use len_trait::Len; +use push_trait::{Nothing, Push}; +use rand::{rngs::ThreadRng, Rng}; + +use crate::ga::{individual::IndividualTrait, GAMetadata}; + +use super::MutationOperator; + +/// # Identity Mutation Operator +/// +/// This struct implements [MutationOperator] trait and can be used with GA. +/// +/// Identity does not perform any changes to the chromosome. Use this if you +/// do not want to mutate your solutions. +pub struct Identity; + +impl Identity { + /// Returns new [Identity] mutation operator + pub fn new() -> Self { + Identity {} + } +} + +impl MutationOperator for Identity { + fn apply(&mut self, _metadata: &GAMetadata, _individual: &mut IndividualT, _mutation_rate: f64) {} +} + +/// ### Flilp bit mutation operator +/// +/// This struct implements [MutationOperator] trait and can be used with GA +/// +/// Genes are muatated by flipping the value - `1` becomes `0` and vice versa +pub struct FlipBit { + rng: R, +} + +impl FlipBit { + /// Returns new instance of [FlipBit] mutation operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl FlipBit { + /// Returns new instance of [FlipBit] mutation operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl MutationOperator for FlipBit +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Push, + R: Rng, +{ + /// Mutates provided solution in place + /// + /// Genes are muatated by flipping the value - `1` becomes `0` and vice versa + /// + /// ## Arguments + /// + /// * `individual` - mutable reference to to-be-mutated individual + /// * `mutation_rate` - probability of gene mutation + fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { + let distribution = rand::distributions::Uniform::from(0.0..1.0); + let chromosome_ref = individual.chromosome_mut(); + let chromosome_len = chromosome_ref.len(); + + for i in 0..chromosome_len { + if self.rng.sample(distribution) < mutation_rate { + chromosome_ref[i] = !chromosome_ref[i]; + } + } + } +} + +/// ### Interchange mustation operator +/// +/// This struct implements [MutationOperator] trait and can be used with GA +/// +/// If a gene is to be muatated, a new locus is randomly choosen and gene values are interchanged +pub struct Interchange { + rng: R, +} + +impl Interchange { + /// Returns new instance of [Interchange] mutation operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Interchange { + /// Returns new instance of [Interchange] mutation operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl MutationOperator for Interchange +where + G: Copy, + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Push, + R: Rng, +{ + /// Mutates provided solution in place + /// + /// If a gene is to be muatated, a new locus is randomly choosen and gene values are interchanged + /// + /// ## Arguments + /// + /// * `individual` - mutable reference to to-be-mutated individual + /// * `mutation_rate` - probability of gene mutation + fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { + let chromosome_ref = individual.chromosome_mut(); + let chromosome_len = chromosome_ref.len(); + + let dist = rand::distributions::Uniform::from(0.0..1.0); + let index_dist = rand::distributions::Uniform::from(0..chromosome_len); + + for i in 0..chromosome_len { + if self.rng.sample(dist) < mutation_rate { + let rand_index = rand::thread_rng().sample(index_dist); + let gene = chromosome_ref[rand_index]; + chromosome_ref[rand_index] = chromosome_ref[i]; + chromosome_ref[i] = gene; + } + } + } +} + +/// ### Reversing mutation operator +/// +/// This struct implements [MutationOperator] trait and can be used with GA +/// +/// Random locus is selected and genes next to the selection position are reversed +pub struct Reversing { + rng: R, +} + +impl Reversing { + /// Returns new instance of [Reversing] mutation operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Reversing { + /// Returns new instance of [Reversing] mutation operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +impl MutationOperator for Reversing +where + GeneT: Copy, + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Push, + R: Rng, +{ + /// Mutates provided solution in place + /// + /// Random locus is selected and genes next to the selection position are reversed + /// + /// ## Arguments + /// + /// * `individual` - mutable reference to to-be-mutated individual + /// * `mutation_rate` - probability of gene mutation + fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { + let dist = rand::distributions::Uniform::from(0.0..1.0); + let chromosome_ref = individual.chromosome_mut(); + let chromosome_len = chromosome_ref.len(); + + for i in 1..chromosome_len { + if self.rng.sample(dist) < mutation_rate { + let gene = chromosome_ref[i]; + chromosome_ref[i] = chromosome_ref[i - 1]; + chromosome_ref[i - 1] = gene; + } + } + } +} + +/// ### [Inversion] mutation operator +/// +/// This struct implements [MutationOperator] trait and can be used with GA +/// +/// Two random locations are chosen marking out a segment of chromosome. +/// Genes from this segment are then rotated around the segment's middle point. +pub struct Inversion { + rng: R, + _marker: PhantomData, +} + +impl Inversion { + /// Returns new instance of [Inversion] mutation operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl Inversion { + /// Returns new instance of [Inversion] mutation operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { + rng, + _marker: PhantomData, + } + } +} + +impl MutationOperator for Inversion +where + IndividualT::ChromosomeT: Len + AsMut<[GeneT]>, +{ + /// Mutates provided solution in place + /// + /// Two random locations are chosen marking out a segment of chromosome. + /// Genes from this segment are then rotated around the segment's middle point. + /// + /// ## Arguments + /// + /// * `individual` - mutable reference to to-be-mutated individual + /// * `mutation_rate` - probability of gene mutation + fn apply(&mut self, _metadata: &GAMetadata, individual: &mut IndividualT, mutation_rate: f64) { + let _marker: PhantomData = PhantomData; + + let r: f64 = self.rng.gen(); + + if r > mutation_rate { + return; + } + + let chromosome_len = individual.chromosome().len(); + let mut from: usize = self.rng.gen_range(0..chromosome_len); + let mut to: usize = self.rng.gen_range(from..chromosome_len); + + while from < to { + individual.chromosome_mut().as_mut().swap(from, to); + from += 1; + to -= 1; + } + } +} + +#[cfg(test)] +mod tests { + use crate::ga::{individual::IndividualTrait, GAMetadata, Individual}; + use itertools::Itertools; + use rand::{distributions::Uniform, Rng}; + + use super::{FlipBit, Identity, Interchange, MutationOperator, Reversing}; + + #[test] + fn identity_does_not_change_chromosome() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(30) + .collect_vec(); + + let mut individual = Individual { + chromosome: chromosome.clone(), + fitness: f64::default(), + }; + + let mut identity_mutation = Identity; + + identity_mutation.apply(&GAMetadata::default(), &mut individual, 1.); + + assert_eq!(chromosome, individual.chromosome); + } + + #[test] + fn flipbit_negates_chromosome() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(30) + .map(|val| val > 0.) + .collect_vec(); + + let chromosome_clone = chromosome.clone(); + + let mut individual = Individual { + chromosome, + fitness: f64::default(), + }; + + let mut operator = FlipBit::new(); + + operator.apply(&GAMetadata::default(), &mut individual, 1.); + + for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { + assert_eq!(actual, !*expected); + } + } + + #[test] + fn flipbit_does_not_mutate_rate_0() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(30) + .map(|val| val > 0.) + .collect_vec(); + + let chromosome_clone = chromosome.clone(); + + let mut individual = Individual { + chromosome, + fitness: f64::default(), + }; + + let mut operator = FlipBit::new(); + + operator.apply(&GAMetadata::default(), &mut individual, 0.); + + for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { + assert_eq!(actual, *expected); + } + } + + #[test] + fn interchange_introduces_changes() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(300) + .map(|val| val > 0.) + .collect_vec(); + + let chromosome_clone = chromosome.clone(); + + let mut individual = Individual { + chromosome, + fitness: f64::default(), + }; + + let mut operator = Interchange::new(); + + operator.apply(&GAMetadata::default(), &mut individual, 1.); + let changes = std::iter::zip(chromosome_clone, individual.chromosome()) + .filter(|p| p.0 != *p.1) + .count(); + assert!(changes > 0); + } + + #[test] + fn interchange_does_not_mutate_rate_0() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(30) + .map(|val| val > 0.) + .collect_vec(); + + let chromosome_clone = chromosome.clone(); + + let mut individual = Individual { + chromosome, + fitness: f64::default(), + }; + + let mut operator = Interchange::new(); + + operator.apply(&GAMetadata::default(), &mut individual, 0.); + + for (actual, expected) in std::iter::zip(chromosome_clone, individual.chromosome()) { + assert_eq!(actual, *expected); + } + } + + #[test] + fn reversing_bubbles_first_gene_when_rate_1() { + let chromosome = rand::thread_rng() + .sample_iter(Uniform::from(-1.0..1.0)) + .take(40) + .collect_vec(); + + let mut individual = Individual { + chromosome, + fitness: f64::default(), + }; + + let first_gene_value = individual.chromosome()[0]; + + let mut operator = Reversing::new(); + + operator.apply(&GAMetadata::default(), &mut individual, 1.0); + + assert_eq!( + first_gene_value, + individual.chromosome()[individual.chromosome().len() - 1] + ); + } +} diff --git a/src/ga/operators/replacement.rs b/src/ga/operators/replacement.rs index 7614677d..eef4c0ea 100644 --- a/src/ga/operators/replacement.rs +++ b/src/ga/operators/replacement.rs @@ -4,6 +4,11 @@ //! original one and the result of crossover phase to a single one, //! which will be the next generation +#[cfg(feature = "ga_impl_replacement")] +pub mod impls; +#[cfg(feature = "ga_impl_replacement")] +pub use impls::*; + use crate::ga::{individual::IndividualTrait, GAMetadata}; /// # Replacement Operator @@ -43,380 +48,3 @@ pub trait ReplacementOperator { true } } - -/// # BothParents replacement operator -/// -/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. -/// -/// It works simply by replacing parents with their children. In effect, each individual -/// only gets to breed once. -/// -/// **NOTE**: In current implementation, all library-implemented operators assume that -/// at indices i, i+1 in `population` collection there are parents of children i, i+1 -/// from `children` collection. Any violation of this invariant may lead to bugs - it can -/// be considered an undefined behaviour. We'll work towards improving this case in the future. -pub struct BothParents; - -impl BothParents { - /// Returns new instance of [BothParents] replacement operator. - pub fn new() -> Self { - Self - } -} - -impl ReplacementOperator for BothParents { - /// Works simply by replacing parents with their children - /// - /// **NOTE**: In current implementation, all library-implemented operators assume that - /// at indices i, i+1 in `population` collection there are parents of children i, i+1 - /// from `children` collection. Any violation of this invariant may lead to bugs - it can - /// be considered an undefined behaviour. We'll work towards improving this case in the future. - /// - /// ### Arguments - /// - /// * `population` - Original population, input to the crossover phase. - /// This collection should be modified in place by the operator. - /// * `children` - Result of the crossover phase - #[inline(always)] - fn apply( - &mut self, - _metadata: &GAMetadata, - _population: Vec, - children: Vec, - ) -> Vec { - children - } - - /// Returns `true` when the operator requires children to possess valid fitness values. - /// - /// This implementation returns `false`. - #[inline(always)] - fn requires_children_fitness(&self) -> bool { - false - } -} - -/// # Noop replacement operator -/// -/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. -/// -/// It does nothing. Implementation is a noop. -pub struct Noop; - -impl Noop { - /// Returns new instance of [Noop] replacement operator - pub fn new() -> Self { - Self - } -} - -impl ReplacementOperator for Noop { - /// Returns input `population`. - #[inline(always)] - fn apply( - &mut self, - _metadata: &GAMetadata, - population: Vec, - _children: Vec, - ) -> Vec { - population - } - - /// Returns `true` when the operator requires children to possess valid fitness values. - /// - /// This implementation returns `false`. - #[inline(always)] - fn requires_children_fitness(&self) -> bool { - false - } -} - -/// # WeakParent replacement operator -/// -/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. -/// -/// Works by taking two out of four individuals (two parents and two children) with the largest fitness. -/// -/// **NOTE**: In current implementation, all library-implemented operators assume that -/// at indices i, i+1 in `population` collection there are parents of children i, i+1 -/// from `children` collection. Any violation of this invariant may lead to bugs - it can -/// be considered an undefined behaviour. We'll work towards improving this case in the future. -/// -/// **NOTE**: This operator assumes that the size of `population` and `children` are of same size. -/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought -/// of as undefined behaviour. -/// -/// **NOTE**: This operator assumes that the size of `population` and `children` is a even number. -/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought -/// of as undefined behaviour. This restriction will be removed in future versions -/// of the library. -pub struct WeakParent; - -impl WeakParent { - /// Returns new instance of [WeakParent] replacement operator. - pub fn new() -> Self { - Self - } -} - -impl ReplacementOperator for WeakParent { - /// Works by taking two out of four individuals (two parents and two children) with the largest fitness. - /// - /// **NOTE**: In current implementation, all library-implemented operators assume that - /// at indices i, i+1 in `population` collection there are parents of children i, i+1 - /// from `children` collection. Any violation of this invariant may lead to bugs - it can - /// be considered an undefined behaviour. We'll work towards improving this case in the future. - /// - /// **NOTE**: This operator assumes that the size of `population` and `children` are of same size. - /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought - /// of as undefined behaviour. - /// - /// **NOTE**: This operator assumes that the size of `population` and `children` is a even number. - /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought - /// of as undefined behaviour. This restriction will be removed in future versions - /// of the library. - /// - /// ### Arguments - /// - /// * `population` - Original population, input to the crossover phase. - /// This collection should be modified in place by the operator. - /// * `children` - Result of the crossover phase - fn apply( - &mut self, - _metadata: &GAMetadata, - mut population: Vec, - mut children: Vec, - ) -> Vec { - debug_assert_eq!( - population.len(), - children.len(), - "Population and children must be of the same size" - ); - debug_assert!(population.len() % 2 == 0, "Population size must be even"); - - // Unfortunately array windowing is not in stable Rust yet, I believe - // https://doc.rust-lang.org/std/slice/struct.ArrayChunks.html - - // There is for sure a nicer way to implement this ;D - for i in (0..(population.len() - 1)).step_by(2) { - if population[i] < population[i + 1] { - population.swap(i, i + 1); - } - - if children[i] < children[i + 1] { - children.swap(i, i + 1); - } - - if children[i] > population[i] { - population.swap(i, i + 1); - std::mem::swap(&mut children[i], &mut population[i]); - - if children[i + 1] > population[i + 1] { - std::mem::swap(&mut children[i + 1], &mut population[i + 1]); - } - } else if children[i] > population[i + 1] { - std::mem::swap(&mut children[i], &mut population[i + 1]); - } - } - population - } -} - -#[cfg(test)] -mod tests { - use crate::ga::{GAMetadata, Individual}; - - use super::{BothParents, Noop, ReplacementOperator, WeakParent}; - - #[test] - fn noop_has_new_method() { - let _ = Noop::new(); - } - - #[test] - fn both_parents_has_new_method() { - let _ = BothParents::new(); - } - - #[test] - fn weak_parent_swaps_when_children_are_stronger() { - let parents = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 40.0, - }, - ]; - - let children = vec![ - Individual { - chromosome: 0.0, - fitness: 120.0, - }, - Individual { - chromosome: 0.0, - fitness: 100.0, - }, - ]; - - let children_clone = children.clone(); - - let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); - - assert_eq!(result, children_clone); - } - - #[test] - fn weak_parent_does_not_swap_when_parents_are_stronger() { - let parents = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 40.0, - }, - ]; - - let children = vec![ - Individual { - chromosome: 0.0, - fitness: 10.0, - }, - Individual { - chromosome: 0.0, - fitness: 12.0, - }, - ]; - - let parents_clone = parents.clone(); - - let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); - - assert_eq!(result, parents_clone); - } - - #[test] - fn weak_parent_cross_swaps_child_1() { - let parents = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 40.0, - }, - ]; - - let children = vec![ - Individual { - chromosome: 0.0, - fitness: 50.0, - }, - Individual { - chromosome: 0.0, - fitness: 30.0, - }, - ]; - - let expected_result = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 50.0, - }, - ]; - - let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); - - assert_eq!(result, expected_result); - } - - #[test] - fn weak_parent_cross_swaps_child_2() { - let parents = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 40.0, - }, - ]; - - let children = vec![ - Individual { - chromosome: 0.0, - fitness: 30.0, - }, - Individual { - chromosome: 0.0, - fitness: 50.0, - }, - ]; - - let expected_result = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 50.0, - }, - ]; - - let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); - - assert_eq!(result, expected_result); - } - - #[test] - fn weak_parent_takes_two_best() { - let parents = vec![ - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - Individual { - chromosome: 0.0, - fitness: 40.0, - }, - ]; - - let children = vec![ - Individual { - chromosome: 0.0, - fitness: 70.0, - }, - Individual { - chromosome: 0.0, - fitness: 50.0, - }, - ]; - - let expected_result = vec![ - Individual { - chromosome: 0.0, - fitness: 70.0, - }, - Individual { - chromosome: 0.0, - fitness: 60.0, - }, - ]; - - let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); - - assert_eq!(result, expected_result); - } -} diff --git a/src/ga/operators/replacement/impls.rs b/src/ga/operators/replacement/impls.rs new file mode 100644 index 00000000..f53efc8f --- /dev/null +++ b/src/ga/operators/replacement/impls.rs @@ -0,0 +1,380 @@ +use crate::ga::{individual::IndividualTrait, GAMetadata}; + +use super::ReplacementOperator; + +/// # BothParents replacement operator +/// +/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. +/// +/// It works simply by replacing parents with their children. In effect, each individual +/// only gets to breed once. +/// +/// **NOTE**: In current implementation, all library-implemented operators assume that +/// at indices i, i+1 in `population` collection there are parents of children i, i+1 +/// from `children` collection. Any violation of this invariant may lead to bugs - it can +/// be considered an undefined behaviour. We'll work towards improving this case in the future. +pub struct BothParents; + +impl BothParents { + /// Returns new instance of [BothParents] replacement operator. + pub fn new() -> Self { + Self + } +} + +impl ReplacementOperator for BothParents { + /// Works simply by replacing parents with their children + /// + /// **NOTE**: In current implementation, all library-implemented operators assume that + /// at indices i, i+1 in `population` collection there are parents of children i, i+1 + /// from `children` collection. Any violation of this invariant may lead to bugs - it can + /// be considered an undefined behaviour. We'll work towards improving this case in the future. + /// + /// ### Arguments + /// + /// * `population` - Original population, input to the crossover phase. + /// This collection should be modified in place by the operator. + /// * `children` - Result of the crossover phase + #[inline(always)] + fn apply( + &mut self, + _metadata: &GAMetadata, + _population: Vec, + children: Vec, + ) -> Vec { + children + } + + /// Returns `true` when the operator requires children to possess valid fitness values. + /// + /// This implementation returns `false`. + #[inline(always)] + fn requires_children_fitness(&self) -> bool { + false + } +} + +/// # Noop replacement operator +/// +/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. +/// +/// It does nothing. Implementation is a noop. +pub struct Noop; + +impl Noop { + /// Returns new instance of [Noop] replacement operator + pub fn new() -> Self { + Self + } +} + +impl ReplacementOperator for Noop { + /// Returns input `population`. + #[inline(always)] + fn apply( + &mut self, + _metadata: &GAMetadata, + population: Vec, + _children: Vec, + ) -> Vec { + population + } + + /// Returns `true` when the operator requires children to possess valid fitness values. + /// + /// This implementation returns `false`. + #[inline(always)] + fn requires_children_fitness(&self) -> bool { + false + } +} + +/// # WeakParent replacement operator +/// +/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm. +/// +/// Works by taking two out of four individuals (two parents and two children) with the largest fitness. +/// +/// **NOTE**: In current implementation, all library-implemented operators assume that +/// at indices i, i+1 in `population` collection there are parents of children i, i+1 +/// from `children` collection. Any violation of this invariant may lead to bugs - it can +/// be considered an undefined behaviour. We'll work towards improving this case in the future. +/// +/// **NOTE**: This operator assumes that the size of `population` and `children` are of same size. +/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought +/// of as undefined behaviour. +/// +/// **NOTE**: This operator assumes that the size of `population` and `children` is a even number. +/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought +/// of as undefined behaviour. This restriction will be removed in future versions +/// of the library. +pub struct WeakParent; + +impl WeakParent { + /// Returns new instance of [WeakParent] replacement operator. + pub fn new() -> Self { + Self + } +} + +impl ReplacementOperator for WeakParent { + /// Works by taking two out of four individuals (two parents and two children) with the largest fitness. + /// + /// **NOTE**: In current implementation, all library-implemented operators assume that + /// at indices i, i+1 in `population` collection there are parents of children i, i+1 + /// from `children` collection. Any violation of this invariant may lead to bugs - it can + /// be considered an undefined behaviour. We'll work towards improving this case in the future. + /// + /// **NOTE**: This operator assumes that the size of `population` and `children` are of same size. + /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought + /// of as undefined behaviour. + /// + /// **NOTE**: This operator assumes that the size of `population` and `children` is a even number. + /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought + /// of as undefined behaviour. This restriction will be removed in future versions + /// of the library. + /// + /// ### Arguments + /// + /// * `population` - Original population, input to the crossover phase. + /// This collection should be modified in place by the operator. + /// * `children` - Result of the crossover phase + fn apply( + &mut self, + _metadata: &GAMetadata, + mut population: Vec, + mut children: Vec, + ) -> Vec { + debug_assert_eq!( + population.len(), + children.len(), + "Population and children must be of the same size" + ); + debug_assert!(population.len() % 2 == 0, "Population size must be even"); + + // Unfortunately array windowing is not in stable Rust yet, I believe + // https://doc.rust-lang.org/std/slice/struct.ArrayChunks.html + + // There is for sure a nicer way to implement this ;D + for i in (0..(population.len() - 1)).step_by(2) { + if population[i] < population[i + 1] { + population.swap(i, i + 1); + } + + if children[i] < children[i + 1] { + children.swap(i, i + 1); + } + + if children[i] > population[i] { + population.swap(i, i + 1); + std::mem::swap(&mut children[i], &mut population[i]); + + if children[i + 1] > population[i + 1] { + std::mem::swap(&mut children[i + 1], &mut population[i + 1]); + } + } else if children[i] > population[i + 1] { + std::mem::swap(&mut children[i], &mut population[i + 1]); + } + } + population + } +} + +#[cfg(test)] +mod tests { + use crate::ga::{GAMetadata, Individual}; + + use super::{BothParents, Noop, ReplacementOperator, WeakParent}; + + #[test] + fn noop_has_new_method() { + let _ = Noop::new(); + } + + #[test] + fn both_parents_has_new_method() { + let _ = BothParents::new(); + } + + #[test] + fn weak_parent_swaps_when_children_are_stronger() { + let parents = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 40.0, + }, + ]; + + let children = vec![ + Individual { + chromosome: 0.0, + fitness: 120.0, + }, + Individual { + chromosome: 0.0, + fitness: 100.0, + }, + ]; + + let children_clone = children.clone(); + + let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); + + assert_eq!(result, children_clone); + } + + #[test] + fn weak_parent_does_not_swap_when_parents_are_stronger() { + let parents = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 40.0, + }, + ]; + + let children = vec![ + Individual { + chromosome: 0.0, + fitness: 10.0, + }, + Individual { + chromosome: 0.0, + fitness: 12.0, + }, + ]; + + let parents_clone = parents.clone(); + + let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); + + assert_eq!(result, parents_clone); + } + + #[test] + fn weak_parent_cross_swaps_child_1() { + let parents = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 40.0, + }, + ]; + + let children = vec![ + Individual { + chromosome: 0.0, + fitness: 50.0, + }, + Individual { + chromosome: 0.0, + fitness: 30.0, + }, + ]; + + let expected_result = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 50.0, + }, + ]; + + let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); + + assert_eq!(result, expected_result); + } + + #[test] + fn weak_parent_cross_swaps_child_2() { + let parents = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 40.0, + }, + ]; + + let children = vec![ + Individual { + chromosome: 0.0, + fitness: 30.0, + }, + Individual { + chromosome: 0.0, + fitness: 50.0, + }, + ]; + + let expected_result = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 50.0, + }, + ]; + + let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); + + assert_eq!(result, expected_result); + } + + #[test] + fn weak_parent_takes_two_best() { + let parents = vec![ + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + Individual { + chromosome: 0.0, + fitness: 40.0, + }, + ]; + + let children = vec![ + Individual { + chromosome: 0.0, + fitness: 70.0, + }, + Individual { + chromosome: 0.0, + fitness: 50.0, + }, + ]; + + let expected_result = vec![ + Individual { + chromosome: 0.0, + fitness: 70.0, + }, + Individual { + chromosome: 0.0, + fitness: 60.0, + }, + ]; + + let result = WeakParent::new().apply(&GAMetadata::default(), parents, children); + + assert_eq!(result, expected_result); + } +} diff --git a/src/ga/operators/selection.rs b/src/ga/operators/selection.rs index 50be186f..36608156 100644 --- a/src/ga/operators/selection.rs +++ b/src/ga/operators/selection.rs @@ -1,7 +1,7 @@ -use std::{iter::Sum, ops::Index}; - -use num_traits::{identities::Zero, NumAssignOps}; -use rand::{distributions::Standard, prelude::Distribution, rngs::ThreadRng, Rng}; +#[cfg(feature = "ga_impl_selection")] +pub mod impls; +#[cfg(feature = "ga_impl_selection")] +pub use impls::*; use crate::ga::{individual::IndividualTrait, GAMetadata}; @@ -36,604 +36,3 @@ pub trait SelectionOperator { count: usize, ) -> Vec<&'a IndividualT>; } - -/// ### Routelle wheel selection operator -/// -/// This struct implements [SelectionOperator] trait and can be used with GA. -/// -/// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed -/// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined, -/// implementation dependent and might change without any notice. -/// -/// **Note 2**: The same individual can be selected multiple times. -/// -/// 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 { - 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 custom RNG - pub fn with_rng(rng: R) -> Self { - RouletteWheel { 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 -where - IndividualT::FitnessValueT: NumAssignOps + Sum + PartialOrd + Copy, - Standard: Distribution, -{ - /// Returns a vector of references to individuals selected to mating pool - /// - /// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed - /// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined, - /// implementation dependent and might change without any notice. - /// - /// **Note 2**: The same individual can be selected multiple times. - /// - /// 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`. - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - let total_fitness: IndividualT::FitnessValueT = population.iter().map(|indiv| indiv.fitness()).sum(); - - let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); - - for _ in 0..count { - let threshold = total_fitness * self.rng.gen::(); - - let mut crt_sum: IndividualT::FitnessValueT = IndividualT::FitnessValueT::zero(); - for indiv in population { - crt_sum += indiv.fitness(); - - if crt_sum >= threshold { - selected.push(indiv); - break; - } - } - } - selected - } -} - -/// ### Random selection operator -/// -/// This struct implements [SelectionOperator] trait and can be used with GA. -/// -/// Individuals are selected with uniform probability. -/// -/// **Note**: The same individual *can not* be selected mutiple times. -pub struct Random { - rng: R, -} - -impl Random { - /// Returns new instance of [Random] selection operator with default RNG - pub fn new() -> Self { - Random::with_rng(rand::thread_rng()) - } -} - -impl Random { - /// Returns new instance of [Random] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Random { rng } - } -} - -impl SelectionOperator for Random { - /// Returns a vector of references to individuals selected to mating pool. - /// - /// Individuals are selected with uniform probability. - /// - /// **Note**: The same individual *can not* be selected multiple times. - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - // 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); - - for i in indices { - selected.push(&population[i]); - } - selected - } -} - -/// ### Rank selection operator -/// -/// This struct implements [SelectionOperator] trait and can be used with GA. -/// -/// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better -/// 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 { - rng: R, -} - -impl Rank { - /// Returns new instance of [Rank] selection operator with default RNG - pub fn new() -> Self { - Rank::with_rng(rand::thread_rng()) - } -} - -impl Rank { - /// Returns new instance of [Rank] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Rank { rng } - } -} - -impl SelectionOperator for Rank -where - IndividualT::FitnessValueT: PartialOrd, -{ - /// Returns a vector of references to individuals selected to mating pool. - /// - /// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better - /// 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. - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); - - let population_len = population.len(); - for _ in 0..count { - // TODO: Consider creating two random index permutations and then iterating over them - // instead of N times using random. - let p1 = &population[self.rng.gen_range(0..population_len)]; - let p2 = &population[self.rng.gen_range(0..population_len)]; - - selected.push(if p1.fitness() >= p2.fitness() { p1 } else { p2 }) - } - - selected - } -} - -/// ### RankR selection operator -/// -/// This struct implements [SelectionOperator] trait and can be used with GA -/// -/// Individuals are selected in following process: -/// -/// 1. Select two random individuals (uniform distribution) -/// 2. Select random number `R` from [0, 1] (uniform distribution) -/// 3. If `R` < `r` then select first individual, second otherwise -/// 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 { - r: f64, - rng: R, -} - -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()) - } -} - -impl RankR { - /// Returns new instance of [RankR] selection operator with custom RNG - /// - /// ### Arguments - /// - /// * `r` - threshold in range [0, 1]; see [RankR] description for details - /// * `rng` - custom random number generator - pub fn with_rng(r: f64, rng: R) -> Self { - assert!((0.0..=1.0).contains(&r)); - RankR { r, rng } - } -} - -impl SelectionOperator for RankR { - /// Returns a vector of references to individuals selected to mating pool. - /// - /// Individuals are selected in following process: - /// - /// 1. Select two random individuals (uniform distribution) - /// 2. Select random number `R` from [0, 1] (uniform distribution) - /// 3. If `R` < `r` then select first individual, second otherwise - /// 4. Repeat 1-3 necessary number of times to create mating pool of demanded size - /// - /// **Note**: The same individual can be selected multiple times - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - 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); - let distribution_for_rand = rand::distributions::Uniform::from(0.0..1.0); - - for _ in 0..count { - // TODO: Consider creating two random index permutations and then iterating over them - // instead of N times using random. - let p1 = &population[self.rng.sample(distribution_for_ind)]; - let p2 = &population[self.rng.sample(distribution_for_ind)]; - - selected.push(if self.rng.sample(distribution_for_rand) < self.r { - p1 - } else { - p2 - }) - } - selected - } -} - -/// ### Tournament selection operator -/// -/// This struct implements [SelectionOperator] and can be used with GA -/// -/// Individuals are selected by conducting given number of tournaments with single winner: -/// -/// *Note*: The same individual can be selected multiple times -/// -/// 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 { - size_factor: f64, - rng: R, -} - -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()) - } -} - -impl 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 { - assert!((0.0..=1.0).contains(&size_factor)); - Tournament { size_factor, rng } - } -} - -impl 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: - /// - /// 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 - /// - /// *Note*: The same individual can be selected multiple times - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - let tournament_size = (population.len() as f64 * self.size_factor) as usize; - - assert!(tournament_size > 0); - - let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); - - for _ in 0..count { - let tournament_indices = - rand::seq::index::sample(&mut self.rng, population.len(), tournament_size); - // FIXME: Check wheter the tournament_indices is empty or handle option below. - let best_idv = tournament_indices - .into_iter() - .map(|i| &population[i]) - .max() - .unwrap(); - selected.push(best_idv); - } - - selected - } -} - -/// ### Stochastic universal sampling selection operator -/// -/// This struct implements [SelectionOperator] trati and can be used with GA -/// -/// **Note**: This selection operator requires positive fitenss function. No runtime checks -/// are performed to assert this invariant. If aggregated fitenss in whole population is <= the -/// behaviour is undefined, implementation dependent and might change without any notice. -/// -/// **Note**: The same individual can be selected multiple times -/// -/// Individuals are selected in process similar to described below: -/// -/// 1. Individuals are laid on real axis, in order they appear in population, -/// to interval \[0, `total_fitness`\]; each individual is represented by sub -/// interval of lengths equal to its fitness -/// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\]; -/// distance between pointers `d` is `total_fitness` / `mating_pool_size`; -/// first pointer position is selected randomly from interval \[0, `d`\] -/// 3. Iterate over the pointers and select the individuals they point to -/// -/// See the source code for implemenation details -pub struct StochasticUniversalSampling { - rng: R, -} - -impl StochasticUniversalSampling { - /// Returns new instance of [StochasticUniversalSampling] selection operator with default RNG - pub fn new() -> Self { - Self::with_rng(rand::thread_rng()) - } -} - -impl StochasticUniversalSampling { - /// Returns new instance of [StochasticUniversalSampling] selection operator with custom RNG - pub fn with_rng(rng: R) -> Self { - Self { rng } - } -} - -// FIXME: Panics then total_fitness == 0 -// Should this be expected or do we want to handle this? -impl, R: Rng> SelectionOperator - for StochasticUniversalSampling -{ - /// Returns a vector of references to individuals selected to mating pool - /// - /// **Note**: This selection operator requires positive fitenss function. No runtime checks - /// are performed to assert this invariant. If aggregated fitenss in whole population is <= the - /// behaviour is undefined, implementation dependent and might change without any notice. - /// - /// **Note**: The same individual can be selected multiple times - /// - /// Individuals are selected in process similar to described below: - /// - /// 1. Individuals are laid on real axis, in order they appear in population, - /// to interval \[0, `total_fitness`\]; each individual is represented by sub - /// interval of lengths equal to its fitness - /// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\]; - /// distance between pointers `d` is `total_fitness` / `mating_pool_size`; - /// first pointer position is selected randomly from interval \[0, `d`\] - /// 3. Iterate over the pointers and select the individuals they point to - /// - /// See the source code for implemenation details - /// - /// ### Arguments - /// - /// * `metadata` - [crate::ga::GAMetadata] 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, - _metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - let total_fitness: f64 = population.iter().map(|indiv| indiv.fitness()).sum(); - - let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); - - let distance_between_pointers = total_fitness / (count as f64); - - assert!(distance_between_pointers > 0.0); - - let mut pointer_pos = self.rng.gen_range(0.0..=distance_between_pointers); - - let mut curr_sum = 0.0; - for idv in population { - curr_sum += idv.fitness(); - - while curr_sum >= pointer_pos { - selected.push(idv); - pointer_pos += distance_between_pointers; - } - } - - assert_eq!(selected.len(), count); - - selected - } -} - -/// ### Boltzmann selection operator -/// -/// This struct implements [SelectionOperator] trait and can be used with GA -/// -pub struct Boltzmann { - alpha: f64, - max_gen_count: usize, // FIXME: This should be removed after operators are passed whole algorithm state & config - temp_0: f64, - elitism: bool, // FIXME: Make use of elitism strategy - rng: R, -} - -impl Boltzmann { - /// Returns new instance of [Boltzmann] selection operator with default RNG - /// - /// ### Arguments - /// - /// * `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()) - } -} - -impl Boltzmann { - /// Returns new instance of [Boltzmann] selection operator with default RNG - /// - /// ### Arguments - /// - /// * `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 { - assert!( - (0.0..=1.0).contains(&alpha), - "Alpha parameter must be a value from [0, 1] interval" - ); - assert!( - (5.0..=100.0).contains(&temp_0), - "Starting temperature must be a value from [5, 100] interval" - ); - - Boltzmann { - alpha, - max_gen_count, - temp_0, - elitism, - rng, - } - } -} - -impl SelectionOperator for Boltzmann -where - IndividualT: IndividualTrait, - IndividualT::ChromosomeT: Index, - R: Rng, -{ - fn apply<'a>( - &mut self, - metadata: &GAMetadata, - population: &'a [IndividualT], - count: usize, - ) -> Vec<&'a IndividualT> { - let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); - let mut weights: Vec = Vec::with_capacity(count); - - let k = 1.0 + 100.0 * (metadata.generation as f64) / (self.max_gen_count as f64); - let temp = self.temp_0 * (1.0 - self.alpha).powf(k); - - for idv in population { - weights.push((-idv.fitness() / temp).exp()) - } - - let Ok(indices) = - rand::seq::index::sample_weighted(&mut self.rng, population.len(), |i| weights[i], count) - else { - panic!("Some error occured while generating indices. This is most likely an library implementation error. Please file an issue: https://github.com/kkafar/evolutionary-algorithms"); - }; - - for i in indices { - selected.push(&population[i]); - } - - selected - } -} - -#[cfg(test)] -mod test { - use super::{Boltzmann, RankR, Tournament}; - - #[test] - #[should_panic] - fn boltzman_panics_on_too_big_alpha() { - let _operator = Boltzmann::new(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); - } - - #[test] - #[should_panic] - fn boltzman_panics_on_too_low_temp() { - let _operator = Boltzmann::new(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); - } - - #[test] - #[should_panic] - fn tournament_panics_on_wrong_size_factor() { - let _operator = Tournament::new(2.0); - } - - #[test] - #[should_panic] - fn rankr_panics_on_wrong_r() { - let _operator = RankR::new(1.1); - } -} diff --git a/src/ga/operators/selection/impls.rs b/src/ga/operators/selection/impls.rs new file mode 100644 index 00000000..cee770a1 --- /dev/null +++ b/src/ga/operators/selection/impls.rs @@ -0,0 +1,609 @@ +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, GAMetadata}; + +use super::SelectionOperator; + +/// ### Routelle wheel selection operator +/// +/// This struct implements [SelectionOperator] trait and can be used with GA. +/// +/// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed +/// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined, +/// implementation dependent and might change without any notice. +/// +/// **Note 2**: The same individual can be selected multiple times. +/// +/// 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 { + 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 custom RNG + pub fn with_rng(rng: R) -> Self { + RouletteWheel { 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 +where + IndividualT::FitnessValueT: NumAssignOps + Sum + PartialOrd + Copy, + Standard: Distribution, +{ + /// Returns a vector of references to individuals selected to mating pool + /// + /// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed + /// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined, + /// implementation dependent and might change without any notice. + /// + /// **Note 2**: The same individual can be selected multiple times. + /// + /// 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`. + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + let total_fitness: IndividualT::FitnessValueT = population.iter().map(|indiv| indiv.fitness()).sum(); + + let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); + + for _ in 0..count { + let threshold = total_fitness * self.rng.gen::(); + + let mut crt_sum: IndividualT::FitnessValueT = IndividualT::FitnessValueT::zero(); + for indiv in population { + crt_sum += indiv.fitness(); + + if crt_sum >= threshold { + selected.push(indiv); + break; + } + } + } + selected + } +} + +/// ### Random selection operator +/// +/// This struct implements [SelectionOperator] trait and can be used with GA. +/// +/// Individuals are selected with uniform probability. +/// +/// **Note**: The same individual *can not* be selected mutiple times. +pub struct Random { + rng: R, +} + +impl Random { + /// Returns new instance of [Random] selection operator with default RNG + pub fn new() -> Self { + Random::with_rng(rand::thread_rng()) + } +} + +impl Random { + /// Returns new instance of [Random] selection operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Random { rng } + } +} + +impl SelectionOperator for Random { + /// Returns a vector of references to individuals selected to mating pool. + /// + /// Individuals are selected with uniform probability. + /// + /// **Note**: The same individual *can not* be selected multiple times. + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + // 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); + + for i in indices { + selected.push(&population[i]); + } + selected + } +} + +/// ### Rank selection operator +/// +/// This struct implements [SelectionOperator] trait and can be used with GA. +/// +/// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better +/// 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 { + rng: R, +} + +impl Rank { + /// Returns new instance of [Rank] selection operator with default RNG + pub fn new() -> Self { + Rank::with_rng(rand::thread_rng()) + } +} + +impl Rank { + /// Returns new instance of [Rank] selection operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Rank { rng } + } +} + +impl SelectionOperator for Rank +where + IndividualT::FitnessValueT: PartialOrd, +{ + /// Returns a vector of references to individuals selected to mating pool. + /// + /// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better + /// 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. + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); + + let population_len = population.len(); + for _ in 0..count { + // TODO: Consider creating two random index permutations and then iterating over them + // instead of N times using random. + let p1 = &population[self.rng.gen_range(0..population_len)]; + let p2 = &population[self.rng.gen_range(0..population_len)]; + + selected.push(if p1.fitness() >= p2.fitness() { p1 } else { p2 }) + } + + selected + } +} + +/// ### RankR selection operator +/// +/// This struct implements [SelectionOperator] trait and can be used with GA +/// +/// Individuals are selected in following process: +/// +/// 1. Select two random individuals (uniform distribution) +/// 2. Select random number `R` from [0, 1] (uniform distribution) +/// 3. If `R` < `r` then select first individual, second otherwise +/// 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 { + r: f64, + rng: R, +} + +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()) + } +} + +impl RankR { + /// Returns new instance of [RankR] selection operator with custom RNG + /// + /// ### Arguments + /// + /// * `r` - threshold in range [0, 1]; see [RankR] description for details + /// * `rng` - custom random number generator + pub fn with_rng(r: f64, rng: R) -> Self { + assert!((0.0..=1.0).contains(&r)); + RankR { r, rng } + } +} + +impl SelectionOperator for RankR { + /// Returns a vector of references to individuals selected to mating pool. + /// + /// Individuals are selected in following process: + /// + /// 1. Select two random individuals (uniform distribution) + /// 2. Select random number `R` from [0, 1] (uniform distribution) + /// 3. If `R` < `r` then select first individual, second otherwise + /// 4. Repeat 1-3 necessary number of times to create mating pool of demanded size + /// + /// **Note**: The same individual can be selected multiple times + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + 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); + let distribution_for_rand = rand::distributions::Uniform::from(0.0..1.0); + + for _ in 0..count { + // TODO: Consider creating two random index permutations and then iterating over them + // instead of N times using random. + let p1 = &population[self.rng.sample(distribution_for_ind)]; + let p2 = &population[self.rng.sample(distribution_for_ind)]; + + selected.push(if self.rng.sample(distribution_for_rand) < self.r { + p1 + } else { + p2 + }) + } + selected + } +} + +/// ### Tournament selection operator +/// +/// This struct implements [SelectionOperator] and can be used with GA +/// +/// Individuals are selected by conducting given number of tournaments with single winner: +/// +/// *Note*: The same individual can be selected multiple times +/// +/// 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 { + size_factor: f64, + rng: R, +} + +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()) + } +} + +impl 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 { + assert!((0.0..=1.0).contains(&size_factor)); + Tournament { size_factor, rng } + } +} + +impl 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: + /// + /// 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 + /// + /// *Note*: The same individual can be selected multiple times + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + let tournament_size = (population.len() as f64 * self.size_factor) as usize; + + assert!(tournament_size > 0); + + let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); + + for _ in 0..count { + let tournament_indices = + rand::seq::index::sample(&mut self.rng, population.len(), tournament_size); + // FIXME: Check wheter the tournament_indices is empty or handle option below. + let best_idv = tournament_indices + .into_iter() + .map(|i| &population[i]) + .max() + .unwrap(); + selected.push(best_idv); + } + + selected + } +} + +/// ### Stochastic universal sampling selection operator +/// +/// This struct implements [SelectionOperator] trati and can be used with GA +/// +/// **Note**: This selection operator requires positive fitenss function. No runtime checks +/// are performed to assert this invariant. If aggregated fitenss in whole population is <= the +/// behaviour is undefined, implementation dependent and might change without any notice. +/// +/// **Note**: The same individual can be selected multiple times +/// +/// Individuals are selected in process similar to described below: +/// +/// 1. Individuals are laid on real axis, in order they appear in population, +/// to interval \[0, `total_fitness`\]; each individual is represented by sub +/// interval of lengths equal to its fitness +/// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\]; +/// distance between pointers `d` is `total_fitness` / `mating_pool_size`; +/// first pointer position is selected randomly from interval \[0, `d`\] +/// 3. Iterate over the pointers and select the individuals they point to +/// +/// See the source code for implemenation details +pub struct StochasticUniversalSampling { + rng: R, +} + +impl StochasticUniversalSampling { + /// Returns new instance of [StochasticUniversalSampling] selection operator with default RNG + pub fn new() -> Self { + Self::with_rng(rand::thread_rng()) + } +} + +impl StochasticUniversalSampling { + /// Returns new instance of [StochasticUniversalSampling] selection operator with custom RNG + pub fn with_rng(rng: R) -> Self { + Self { rng } + } +} + +// FIXME: Panics then total_fitness == 0 +// Should this be expected or do we want to handle this? +impl, R: Rng> SelectionOperator + for StochasticUniversalSampling +{ + /// Returns a vector of references to individuals selected to mating pool + /// + /// **Note**: This selection operator requires positive fitenss function. No runtime checks + /// are performed to assert this invariant. If aggregated fitenss in whole population is <= the + /// behaviour is undefined, implementation dependent and might change without any notice. + /// + /// **Note**: The same individual can be selected multiple times + /// + /// Individuals are selected in process similar to described below: + /// + /// 1. Individuals are laid on real axis, in order they appear in population, + /// to interval \[0, `total_fitness`\]; each individual is represented by sub + /// interval of lengths equal to its fitness + /// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\]; + /// distance between pointers `d` is `total_fitness` / `mating_pool_size`; + /// first pointer position is selected randomly from interval \[0, `d`\] + /// 3. Iterate over the pointers and select the individuals they point to + /// + /// See the source code for implemenation details + /// + /// ### Arguments + /// + /// * `metadata` - [crate::ga::GAMetadata] 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, + _metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + let total_fitness: f64 = population.iter().map(|indiv| indiv.fitness()).sum(); + + let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); + + let distance_between_pointers = total_fitness / (count as f64); + + assert!(distance_between_pointers > 0.0); + + let mut pointer_pos = self.rng.gen_range(0.0..=distance_between_pointers); + + let mut curr_sum = 0.0; + for idv in population { + curr_sum += idv.fitness(); + + while curr_sum >= pointer_pos { + selected.push(idv); + pointer_pos += distance_between_pointers; + } + } + + assert_eq!(selected.len(), count); + + selected + } +} + +/// ### Boltzmann selection operator +/// +/// This struct implements [SelectionOperator] trait and can be used with GA +/// +pub struct Boltzmann { + alpha: f64, + max_gen_count: usize, // FIXME: This should be removed after operators are passed whole algorithm state & config + temp_0: f64, + elitism: bool, // FIXME: Make use of elitism strategy + rng: R, +} + +impl Boltzmann { + /// Returns new instance of [Boltzmann] selection operator with default RNG + /// + /// ### Arguments + /// + /// * `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()) + } +} + +impl Boltzmann { + /// Returns new instance of [Boltzmann] selection operator with default RNG + /// + /// ### Arguments + /// + /// * `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 { + assert!( + (0.0..=1.0).contains(&alpha), + "Alpha parameter must be a value from [0, 1] interval" + ); + assert!( + (5.0..=100.0).contains(&temp_0), + "Starting temperature must be a value from [5, 100] interval" + ); + + Boltzmann { + alpha, + max_gen_count, + temp_0, + elitism, + rng, + } + } +} + +impl SelectionOperator for Boltzmann +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index, + R: Rng, +{ + fn apply<'a>( + &mut self, + metadata: &GAMetadata, + population: &'a [IndividualT], + count: usize, + ) -> Vec<&'a IndividualT> { + let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); + let mut weights: Vec = Vec::with_capacity(count); + + let k = 1.0 + 100.0 * (metadata.generation as f64) / (self.max_gen_count as f64); + let temp = self.temp_0 * (1.0 - self.alpha).powf(k); + + for idv in population { + weights.push((-idv.fitness() / temp).exp()) + } + + let Ok(indices) = + rand::seq::index::sample_weighted(&mut self.rng, population.len(), |i| weights[i], count) + else { + panic!("Some error occured while generating indices. This is most likely an library implementation error. Please file an issue: https://github.com/kkafar/evolutionary-algorithms"); + }; + + for i in indices { + selected.push(&population[i]); + } + + selected + } +} + +#[cfg(test)] +mod test { + use super::{Boltzmann, RankR, Tournament}; + + #[test] + #[should_panic] + fn boltzman_panics_on_too_big_alpha() { + let _operator = Boltzmann::new(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); + } + + #[test] + #[should_panic] + fn boltzman_panics_on_too_low_temp() { + let _operator = Boltzmann::new(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); + } + + #[test] + #[should_panic] + fn tournament_panics_on_wrong_size_factor() { + let _operator = Tournament::new(2.0); + } + + #[test] + #[should_panic] + fn rankr_panics_on_wrong_r() { + let _operator = RankR::new(1.1); + } +} diff --git a/src/ga/population.rs b/src/ga/population.rs index 95589ee1..21052ea0 100644 --- a/src/ga/population.rs +++ b/src/ga/population.rs @@ -1,10 +1,10 @@ +#[cfg(feature = "ga_impl_population")] +pub mod impls; +#[cfg(feature = "ga_impl_population")] +pub use impls::*; + pub mod tools; -use itertools::Itertools; -use rand::prelude::ThreadRng; -use rand::seq::SliceRandom; -use rand::{thread_rng, Rng}; -use std::fmt::Debug; -use std::ops::{Range, RangeInclusive}; + use std::vec::IntoIter; use super::individual::IndividualTrait; @@ -18,316 +18,3 @@ pub trait PopulationGenerator { self.generate(count).into_iter() } } - -/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. -/// -/// Generates vector of random points from R^(dim) space within passed domain constraints. -pub struct RandomPoints { - dim: usize, - constraints: Vec>, - rng: R, -} - -impl RandomPoints { - /// Returns [RandomPoints] population generator with given constraints and default RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `constraints` -- Ranges for coordinates - pub fn with_constraints(dim: usize, constraints: Vec>) -> Self { - Self::with_constraints_and_rng(dim, constraints, thread_rng()) - } - - /// Returns [RandomPoints] population generator with given constraints and default RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `constraints` -- Ranges for coordinates - /// - /// **NOTE**: However type of `constraints` is `Vec>` this factory method - /// is *IDENTICAL* to `with_constraints`. It is here just to enable inclusive range usage. - /// Maybe better solution would be to extract this method to separate trait and create some - /// kind of function overloading. - pub fn with_constraints_inclusive(dim: usize, constraints: Vec>) -> Self { - let noninclusive = constraints - .into_iter() - .map(|r| *r.start()..*r.end()) - .collect_vec(); - Self::with_constraints_and_rng(dim, noninclusive, thread_rng()) - } - - /// Returns [RandomPoints] population generator with single constraint for all dimensions and - /// default RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `constraint` -- Range for coordinates - pub fn with_single_constraint(dim: usize, constraint: Range) -> Self { - Self::with_constraints(dim, std::iter::repeat(constraint).take(dim).collect()) - } - - /// Returns [RandomPoints] population generator with no explicit constraints and default RNG. - /// Points coords will be from range 0.0..1.0. - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - pub fn new(dim: usize) -> Self { - Self::with_rng(dim, thread_rng()) - } -} - -impl RandomPoints { - /// Returns [RandomPoints] population generator with given constraints and custom RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `constraints` -- Ranges for coordinates - /// * `rng` -- Random numbers generator - pub fn with_constraints_and_rng(dim: usize, constraints: Vec>, rng: R) -> Self { - assert!(dim > 0, "Space dimension must be > 0"); - assert_eq!( - dim, - constraints.len(), - "Number of constraints must match dimension of sampled space" - ); - - RandomPoints { - dim, - constraints, - rng, - } - } - - /// Returns [RandomPoints] population generator with no explicit constraints and custom RNG. - /// Points coords will be from range 0.0..1.0. - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `rng` -- Random numbers generator - pub fn with_rng(dim: usize, rng: R) -> Self { - assert!(dim > 0, "Space dimension must be > 0"); - RandomPoints { - dim, - constraints: Vec::from_iter(std::iter::repeat(0.0..1.0).take(dim)), - rng, - } - } -} - -impl>, R: Rng + Clone> PopulationGenerator - for RandomPoints -{ - /// Generates vector of `count` random points from R^(dim) space within passed domain constraints. - /// If there were no constraints passed then the points coords are from range 0.0..1.0. - /// - /// ### Arguments - /// - /// * `count` -- Number of points to generate - fn generate(&mut self, count: usize) -> Vec { - tools::PointGenerator::with_rng(self.rng.clone()) - .generate_with_constraints(self.dim, count, &self.constraints) - .into_iter() - .map(|chromosome| IndividualT::from(chromosome)) - .collect_vec() - } -} - -/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. -/// -/// Generates vector of random bit-strings. -pub struct BitStrings { - dim: usize, - rng: R, -} - -impl BitStrings { - /// Returns [BitString] population generator wit default RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - pub fn new(dim: usize) -> Self { - Self::with_rng(dim, thread_rng()) - } -} - -impl BitStrings { - /// Returns [BitString] population generator wit custom RNG - /// - /// ### Arguments - /// - /// * `dim` -- Dimension of the sampling space - /// * `rng` -- Random numbers generator - pub fn with_rng(dim: usize, rng: R) -> Self { - assert!(dim > 0, "Space dimension must be > 0"); - BitStrings { dim, rng } - } -} - -impl>, R: Rng> PopulationGenerator - for BitStrings -{ - /// Generates vector of `count` random bitstrings - /// - /// ### Arguments - /// - /// * `count` -- Number of bitstrings to generate - fn generate(&mut self, count: usize) -> Vec { - let mut population: Vec = Vec::with_capacity(count); - - let distr = rand::distributions::Standard; - - for _ in 0..count { - population.push(IndividualT::from( - (&mut self.rng).sample_iter(distr).take(self.dim).collect_vec(), - )); - } - - population - } -} - -/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. -/// -/// Generates random permutations of given vector. -/// Permutations can be repeated -pub struct RandomPermutations { - genes: Vec, - rng: R, -} - -impl RandomPermutations { - /// Returns [RandomPermutations] population generator with default rng. - /// - /// ### Arguments - /// - /// * `genes` - Vector which will be permuted - pub fn new(genes: Vec) -> Self { - Self::with_rng(genes, thread_rng()) - } -} - -impl RandomPermutations { - /// Returns [RandomPermutations] population generator with custom rng. - /// - /// ### Arguments - /// - /// * `genes` - Vector which will be permuted - /// * `rng` - Random numbers generator - pub fn with_rng(genes: Vec, rng: R) -> Self { - RandomPermutations { genes, rng } - } -} - -impl>, GeneT, R> PopulationGenerator - for RandomPermutations -where - GeneT: Copy + Debug + Sync + Send, - R: Rng, -{ - /// Generates vector of `count` random permutations from stored genes. - /// Repeated individual are possible. - /// - /// ### Arguments - /// - /// * `count` -- Number of random permutations to generate - fn generate(&mut self, count: usize) -> Vec { - let mut population: Vec = Vec::with_capacity(count); - - for _ in 0..count { - let mut genome = self.genes.clone(); - genome.shuffle(&mut self.rng); - population.push(IndividualT::from(genome)) - } - - population - } -} - -#[cfg(test)] -mod tests { - use super::{BitStrings, PopulationGenerator, RandomPoints}; - use crate::ga::{individual::IndividualTrait, population::RandomPermutations, Individual}; - use itertools::Itertools; - - #[test] - fn points_have_appropriate_len() { - let dim = 4; - let mut gen = - RandomPoints::with_constraints(dim, vec![(0.0..2.0), (-1.0..1.0), (3.0..10.0), (-5.0..-4.0)]); - let points: Vec>> = gen.generate(30); - - for p in points { - assert_eq!(p.chromosome.len(), dim) - } - } - - #[test] - fn points_follow_explicit_constraints() { - let dim = 4; - let constraints = vec![(0.0..2.0), (-1.0..1.0), (3.0..10.0), (-5.0..-4.0)]; - let mut gen = RandomPoints::with_constraints(dim, constraints.clone()); - let points: Vec>> = gen.generate(30); - - for p in points { - for (v, res) in std::iter::zip(p.chromosome(), &constraints) { - assert!(res.contains(v)); - } - } - } - - #[test] - fn points_follow_implicit_constraints() { - let dim = 30; - let count = 100; - - let mut gen = RandomPoints::new(dim); - let points: Vec>> = gen.generate(count); - - for p in points { - for v in p.chromosome() { - assert!((0.0..1.0).contains(v)); - } - } - } - - #[test] - fn bistrings_have_appropriate_len() { - let dim = 30; - let mut gen = BitStrings::new(dim); - let points: Vec>> = gen.generate(30); - - for p in points { - assert_eq!(p.chromosome().len(), dim) - } - } - - #[test] - fn permutations_have_appropriate_len() { - let dim = 30; - let mut gen = RandomPermutations::new((0..dim).collect_vec()); - let points: Vec>> = gen.generate(30); - - for p in points { - assert_eq!(p.chromosome().len(), dim) - } - } - - #[test] - fn permutations_have_every_gene() { - let dim: usize = 30; - let mut gen = RandomPermutations::new((1..=dim).collect_vec()); - let points: Vec>> = gen.generate(10); - - for p in points { - let sum: usize = p.chromosome().iter().sum(); - assert_eq!(sum, ((dim + 1) * dim) / 2) - } - } -} diff --git a/src/ga/population/impls.rs b/src/ga/population/impls.rs new file mode 100644 index 00000000..5e06540a --- /dev/null +++ b/src/ga/population/impls.rs @@ -0,0 +1,323 @@ +use itertools::Itertools; +use rand::prelude::ThreadRng; +use rand::seq::SliceRandom; +use rand::{thread_rng, Rng}; +use std::fmt::Debug; +use std::ops::{Range, RangeInclusive}; + +use crate::ga::individual::IndividualTrait; + +use super::{tools, PopulationGenerator}; + +/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. +/// +/// Generates vector of random points from R^(dim) space within passed domain constraints. +pub struct RandomPoints { + dim: usize, + constraints: Vec>, + rng: R, +} + +impl RandomPoints { + /// Returns [RandomPoints] population generator with given constraints and default RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `constraints` -- Ranges for coordinates + pub fn with_constraints(dim: usize, constraints: Vec>) -> Self { + Self::with_constraints_and_rng(dim, constraints, thread_rng()) + } + + /// Returns [RandomPoints] population generator with given constraints and default RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `constraints` -- Ranges for coordinates + /// + /// **NOTE**: However type of `constraints` is `Vec>` this factory method + /// is *IDENTICAL* to `with_constraints`. It is here just to enable inclusive range usage. + /// Maybe better solution would be to extract this method to separate trait and create some + /// kind of function overloading. + pub fn with_constraints_inclusive(dim: usize, constraints: Vec>) -> Self { + let noninclusive = constraints + .into_iter() + .map(|r| *r.start()..*r.end()) + .collect_vec(); + Self::with_constraints_and_rng(dim, noninclusive, thread_rng()) + } + + /// Returns [RandomPoints] population generator with single constraint for all dimensions and + /// default RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `constraint` -- Range for coordinates + pub fn with_single_constraint(dim: usize, constraint: Range) -> Self { + Self::with_constraints(dim, std::iter::repeat(constraint).take(dim).collect()) + } + + /// Returns [RandomPoints] population generator with no explicit constraints and default RNG. + /// Points coords will be from range 0.0..1.0. + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + pub fn new(dim: usize) -> Self { + Self::with_rng(dim, thread_rng()) + } +} + +impl RandomPoints { + /// Returns [RandomPoints] population generator with given constraints and custom RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `constraints` -- Ranges for coordinates + /// * `rng` -- Random numbers generator + pub fn with_constraints_and_rng(dim: usize, constraints: Vec>, rng: R) -> Self { + assert!(dim > 0, "Space dimension must be > 0"); + assert_eq!( + dim, + constraints.len(), + "Number of constraints must match dimension of sampled space" + ); + + RandomPoints { + dim, + constraints, + rng, + } + } + + /// Returns [RandomPoints] population generator with no explicit constraints and custom RNG. + /// Points coords will be from range 0.0..1.0. + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `rng` -- Random numbers generator + pub fn with_rng(dim: usize, rng: R) -> Self { + assert!(dim > 0, "Space dimension must be > 0"); + RandomPoints { + dim, + constraints: Vec::from_iter(std::iter::repeat(0.0..1.0).take(dim)), + rng, + } + } +} + +impl>, R: Rng + Clone> PopulationGenerator + for RandomPoints +{ + /// Generates vector of `count` random points from R^(dim) space within passed domain constraints. + /// If there were no constraints passed then the points coords are from range 0.0..1.0. + /// + /// ### Arguments + /// + /// * `count` -- Number of points to generate + fn generate(&mut self, count: usize) -> Vec { + tools::PointGenerator::with_rng(self.rng.clone()) + .generate_with_constraints(self.dim, count, &self.constraints) + .into_iter() + .map(|chromosome| IndividualT::from(chromosome)) + .collect_vec() + } +} + +/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. +/// +/// Generates vector of random bit-strings. +pub struct BitStrings { + dim: usize, + rng: R, +} + +impl BitStrings { + /// Returns [BitString] population generator wit default RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + pub fn new(dim: usize) -> Self { + Self::with_rng(dim, thread_rng()) + } +} + +impl BitStrings { + /// Returns [BitString] population generator wit custom RNG + /// + /// ### Arguments + /// + /// * `dim` -- Dimension of the sampling space + /// * `rng` -- Random numbers generator + pub fn with_rng(dim: usize, rng: R) -> Self { + assert!(dim > 0, "Space dimension must be > 0"); + BitStrings { dim, rng } + } +} + +impl>, R: Rng> PopulationGenerator + for BitStrings +{ + /// Generates vector of `count` random bitstrings + /// + /// ### Arguments + /// + /// * `count` -- Number of bitstrings to generate + fn generate(&mut self, count: usize) -> Vec { + let mut population: Vec = Vec::with_capacity(count); + + let distr = rand::distributions::Standard; + + for _ in 0..count { + population.push(IndividualT::from( + (&mut self.rng).sample_iter(distr).take(self.dim).collect_vec(), + )); + } + + population + } +} + +/// Implements [PopulationGenerator] trait. Can be used with genetic algorithm. +/// +/// Generates random permutations of given vector. +/// Permutations can be repeated +pub struct RandomPermutations { + genes: Vec, + rng: R, +} + +impl RandomPermutations { + /// Returns [RandomPermutations] population generator with default rng. + /// + /// ### Arguments + /// + /// * `genes` - Vector which will be permuted + pub fn new(genes: Vec) -> Self { + Self::with_rng(genes, thread_rng()) + } +} + +impl RandomPermutations { + /// Returns [RandomPermutations] population generator with custom rng. + /// + /// ### Arguments + /// + /// * `genes` - Vector which will be permuted + /// * `rng` - Random numbers generator + pub fn with_rng(genes: Vec, rng: R) -> Self { + RandomPermutations { genes, rng } + } +} + +impl>, GeneT, R> PopulationGenerator + for RandomPermutations +where + GeneT: Copy + Debug + Sync + Send, + R: Rng, +{ + /// Generates vector of `count` random permutations from stored genes. + /// Repeated individual are possible. + /// + /// ### Arguments + /// + /// * `count` -- Number of random permutations to generate + fn generate(&mut self, count: usize) -> Vec { + let mut population: Vec = Vec::with_capacity(count); + + for _ in 0..count { + let mut genome = self.genes.clone(); + genome.shuffle(&mut self.rng); + population.push(IndividualT::from(genome)) + } + + population + } +} + +#[cfg(test)] +mod tests { + use super::{BitStrings, PopulationGenerator, RandomPoints}; + use crate::ga::{individual::IndividualTrait, population::RandomPermutations, Individual}; + use itertools::Itertools; + + #[test] + fn points_have_appropriate_len() { + let dim = 4; + let mut gen = + RandomPoints::with_constraints(dim, vec![(0.0..2.0), (-1.0..1.0), (3.0..10.0), (-5.0..-4.0)]); + let points: Vec>> = gen.generate(30); + + for p in points { + assert_eq!(p.chromosome.len(), dim) + } + } + + #[test] + fn points_follow_explicit_constraints() { + let dim = 4; + let constraints = vec![(0.0..2.0), (-1.0..1.0), (3.0..10.0), (-5.0..-4.0)]; + let mut gen = RandomPoints::with_constraints(dim, constraints.clone()); + let points: Vec>> = gen.generate(30); + + for p in points { + for (v, res) in std::iter::zip(p.chromosome(), &constraints) { + assert!(res.contains(v)); + } + } + } + + #[test] + fn points_follow_implicit_constraints() { + let dim = 30; + let count = 100; + + let mut gen = RandomPoints::new(dim); + let points: Vec>> = gen.generate(count); + + for p in points { + for v in p.chromosome() { + assert!((0.0..1.0).contains(v)); + } + } + } + + #[test] + fn bistrings_have_appropriate_len() { + let dim = 30; + let mut gen = BitStrings::new(dim); + let points: Vec>> = gen.generate(30); + + for p in points { + assert_eq!(p.chromosome().len(), dim) + } + } + + #[test] + fn permutations_have_appropriate_len() { + let dim = 30; + let mut gen = RandomPermutations::new((0..dim).collect_vec()); + let points: Vec>> = gen.generate(30); + + for p in points { + assert_eq!(p.chromosome().len(), dim) + } + } + + #[test] + fn permutations_have_every_gene() { + let dim: usize = 30; + let mut gen = RandomPermutations::new((1..=dim).collect_vec()); + let points: Vec>> = gen.generate(10); + + for p in points { + let sum: usize = p.chromosome().iter().sum(); + assert_eq!(sum, ((dim + 1) * dim) / 2) + } + } +} diff --git a/src/ga/population/tools.rs b/src/ga/population/tools.rs index 700a79ca..77da75e2 100644 --- a/src/ga/population/tools.rs +++ b/src/ga/population/tools.rs @@ -41,6 +41,7 @@ impl PointGenerator { /// Generates `n` random points, each with `dim` coordinates. Each coordinate respects given /// constraint. + #[allow(clippy::ptr_arg)] pub fn generate_with_constraints( &mut self, dim: usize, diff --git a/src/pso/util.rs b/src/pso/util.rs index c8a14c79..ad0ddc1f 100644 --- a/src/pso/util.rs +++ b/src/pso/util.rs @@ -1,4 +1,4 @@ -pub fn print_generic_vector(vector: &Vec) -> String { +pub fn print_generic_vector(vector: &[T]) -> String { let mut text = String::from(""); text += "["; for (index, value) in vector.iter().enumerate() { diff --git a/src/test_functions/mod.rs b/src/test_functions/mod.rs index 84dc0ff0..b965e03f 100644 --- a/src/test_functions/mod.rs +++ b/src/test_functions/mod.rs @@ -9,7 +9,7 @@ use std::{f64, i32}; /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn ackley(x: &Vec) -> f64 { +pub fn ackley(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Ackley function takes an at least one dimensional vector as a parameter." @@ -30,7 +30,7 @@ pub fn ackley(x: &Vec) -> f64 { /// Global minimum:\ /// f(0,0) = -200 -pub fn ackley2(x: &Vec) -> f64 { +pub fn ackley2(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -46,7 +46,7 @@ pub fn ackley2(x: &Vec) -> f64 { /// Global minimum:\ /// f(0, ~ -0.4) = ~ -219.1418 -pub fn ackley3(x: &Vec) -> f64 { +pub fn ackley3(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -69,7 +69,7 @@ pub fn ackley3(x: &Vec) -> f64 { /// Global minimum:\ /// f(2, 0.10578) = ~ −2.02181 -pub fn adijman(x: &Vec) -> f64 { +pub fn adijman(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -85,7 +85,7 @@ pub fn adijman(x: &Vec) -> f64 { /// Global minimum:\ /// f(0, 0, ..., 0) = 0 -pub fn alpine(x: &Vec) -> f64 { +pub fn alpine(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::abs(arg * f64::sin(*arg) + arg / 10_f64) @@ -98,7 +98,7 @@ pub fn alpine(x: &Vec) -> f64 { /// Global minimum:\ /// f(7.917, ..., 7.917) = 2.808 ^ D(imensions) -pub fn alpine2(x: &Vec) -> f64 { +pub fn alpine2(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res *= f64::sqrt(*arg) * f64::sin(*arg) @@ -111,7 +111,7 @@ pub fn alpine2(x: &Vec) -> f64 { /// Global minimum:\ /// f(0.0824, 1.133, 2.3437) = 0.00821487 -pub fn brad(x: &Vec) -> f64 { +pub fn brad(x: &[f64]) -> f64 { assert_eq!( 3, x.len(), @@ -138,7 +138,7 @@ pub fn brad(x: &Vec) -> f64 { /// Global minimum:\ /// f(0, 0) = 1 -pub fn bartels_conn(x: &Vec) -> f64 { +pub fn bartels_conn(x: &[f64]) -> f64 { assert_eq!( 3, x.len(), @@ -153,7 +153,7 @@ pub fn bartels_conn(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(3, 0.5) = 0 -pub fn beale(x: &Vec) -> f64 { +pub fn beale(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -171,7 +171,7 @@ pub fn beale(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10) = 0 -pub fn biggs_exp2(x: &Vec) -> f64 { +pub fn biggs_exp2(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -197,7 +197,7 @@ pub fn biggs_exp2(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10, 5) = 0 -pub fn biggs_exp3(x: &Vec) -> f64 { +pub fn biggs_exp3(x: &[f64]) -> f64 { assert_eq!( 3, x.len(), @@ -225,7 +225,7 @@ pub fn biggs_exp3(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10, 1, 5) = 0 -pub fn biggs_exp4(x: &Vec) -> f64 { +pub fn biggs_exp4(x: &[f64]) -> f64 { assert_eq!( 4, x.len(), @@ -254,7 +254,7 @@ pub fn biggs_exp4(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10, 1, 5, 4) = 0 -pub fn biggs_exp5(x: &Vec) -> f64 { +pub fn biggs_exp5(x: &[f64]) -> f64 { assert_eq!( 5, x.len(), @@ -289,7 +289,7 @@ pub fn biggs_exp5(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10, 1, 5, 4, 3) = 0 -pub fn biggs_exp6(x: &Vec) -> f64 { +pub fn biggs_exp6(x: &[f64]) -> f64 { assert_eq!( 6, x.len(), @@ -325,7 +325,7 @@ pub fn biggs_exp6(x: &Vec) -> f64 { /// Global minimum:\ /// f(4.70104, 3.15294) = f(-1.58214, −3.13024) = −106.764537 -pub fn bird(x: &Vec) -> f64 { +pub fn bird(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -343,7 +343,7 @@ pub fn bird(x: &Vec) -> f64 { /// f(0, 1.25313) = 0.292579 \ /// f(0,0) = 0 -pub fn bohachevsky_n1(x: &Vec) -> f64 { +pub fn bohachevsky_n1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -363,7 +363,7 @@ pub fn bohachevsky_n1(x: &Vec) -> f64 { /// f(0, 1.25313) = 0.292579 \ /// f(0,0) = 0 -pub fn bohachevsky_n2(x: &Vec) -> f64 { +pub fn bohachevsky_n2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -382,7 +382,7 @@ pub fn bohachevsky_n2(x: &Vec) -> f64 { /// f(0, 1.25313) = 0.292579 \ /// f(0,0) = 0 -pub fn bohachevsky_n3(x: &Vec) -> f64 { +pub fn bohachevsky_n3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -399,7 +399,7 @@ pub fn bohachevsky_n3(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(1, 3) = 0 -pub fn booth(x: &Vec) -> f64 { +pub fn booth(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -415,7 +415,7 @@ pub fn booth(x: &Vec) -> f64 { /// Global minimum:\ /// f(1, 10, 1) = 0 -pub fn box_betts(x: &Vec) -> f64 { +pub fn box_betts(x: &[f64]) -> f64 { assert_eq!( 3, x.len(), @@ -442,7 +442,7 @@ pub fn box_betts(x: &Vec) -> f64 { /// # Branin function /// 2-dimensional only -pub fn branin(x: &Vec, a: &f64, b: &f64, c: &f64, r: &f64, s: &f64, t: &f64) -> f64 { +pub fn branin(x: &[f64], a: &f64, b: &f64, c: &f64, r: &f64, s: &f64, t: &f64) -> f64 { assert_eq!( 2, x.len(), @@ -458,7 +458,7 @@ pub fn branin(x: &Vec, a: &f64, b: &f64, c: &f64, r: &f64, s: &f64, t: &f64 /// Global minimum:\ /// f(−3.2, 12.53) = 5.559037 -pub fn branin_rcos(x: &Vec) -> f64 { +pub fn branin_rcos(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -479,7 +479,7 @@ pub fn branin_rcos(x: &Vec) -> f64 { /// Global minimum:\ /// f(−pi, 12.275) = f(pi, 2.275) = f(3 pi, 2.425) = 0.3978873 -pub fn branin_rcos2(x: &Vec) -> f64 { +pub fn branin_rcos2(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -503,7 +503,7 @@ pub fn branin_rcos2(x: &Vec) -> f64 { /// 2-dimensional only /// Global minimum: \ /// f(-pi, 2.275) = 0.397887 -pub fn branin_default(x: &Vec) -> f64 { +pub fn branin_default(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -525,7 +525,7 @@ pub fn branin_default(x: &Vec) -> f64 { /// Global minimum:\ /// f(0, 0) = 0 -pub fn brent(x: &Vec) -> f64 { +pub fn brent(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -543,7 +543,7 @@ pub fn brent(x: &Vec) -> f64 { /// Global minimum:\ /// f(0,..., 0) = 0 -pub fn brown(x: &Vec) -> f64 { +pub fn brown(x: &[f64]) -> f64 { let mut res = 0_f64; for i in 0..x.len() - 1 { res += f64::powf(f64::powi(x[i], 2), f64::powi(x[i + 1], 2) + 1_f64) @@ -557,7 +557,7 @@ pub fn brown(x: &Vec) -> f64 { /// Global minimum: \ /// f(-10, 0) = 0 /// -pub fn bukin_2(x: &Vec) -> f64 { +pub fn bukin_2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -573,7 +573,7 @@ pub fn bukin_2(x: &Vec) -> f64 { /// Global minimum: \ /// f(-10, 0) = 0 /// -pub fn bukin_4(x: &Vec) -> f64 { +pub fn bukin_4(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -588,7 +588,7 @@ pub fn bukin_4(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(-10, 1) = 0 -pub fn bukin_n6(x: &Vec) -> f64 { +pub fn bukin_n6(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -603,7 +603,7 @@ pub fn bukin_n6(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(0, 0) = 0 -pub fn three_hump_camel(x: &Vec) -> f64 { +pub fn three_hump_camel(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -619,7 +619,7 @@ pub fn three_hump_camel(x: &Vec) -> f64 { /// Local mini /// Global minima: 2\ /// f(0.898, -0.7126) = f(0.0898, 0.7126) = -1.0316 -pub fn six_hump_camel(x: &Vec) -> f64 { +pub fn six_hump_camel(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -637,7 +637,7 @@ pub fn six_hump_camel(x: &Vec) -> f64 { /// Global minimum: \ /// f(-7/18, -13/18) = -2000 /// -pub fn chen_bird(x: &Vec) -> f64 { +pub fn chen_bird(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -654,7 +654,7 @@ pub fn chen_bird(x: &Vec) -> f64 { /// Global minimum: \ /// f(−0.3888889, 0.7222222) = -2000 /// -pub fn chen_v(x: &Vec) -> f64 { +pub fn chen_v(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -672,7 +672,7 @@ pub fn chen_v(x: &Vec) -> f64 { /// Global minimum: \ /// f(5.90133, 0.5) = −43.3159 /// -pub fn chichinadze(x: &Vec) -> f64 { +pub fn chichinadze(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -691,7 +691,7 @@ pub fn chichinadze(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0) = 0 /// -pub fn chung_reynolds(x: &Vec) -> f64 { +pub fn chung_reynolds(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(*arg, 2); @@ -705,7 +705,7 @@ pub fn chung_reynolds(x: &Vec) -> f64 { /// 4-dimensional \ /// Global minimum: \ /// f(1,1,1,1) = 0 -pub fn colville(x: &Vec) -> f64 { +pub fn colville(x: &[f64]) -> f64 { assert_eq!( x.len(), 4, @@ -729,7 +729,7 @@ pub fn colville(x: &Vec) -> f64 { /// Global minimum: \ /// f(0,..., 0) = 0 /// -pub fn cosine_mixture(x: &Vec) -> f64 { +pub fn cosine_mixture(x: &[f64]) -> f64 { let mut sum1 = 0_f64; let mut sum2 = 0_f64; for arg in x { @@ -746,7 +746,7 @@ pub fn cosine_mixture(x: &Vec) -> f64 { /// f(1.3491, 1.3491) = -2.06261 \ /// f(-1.3491, 1.3491) = -2.06261 \ /// f(-1.3491, -1.3491) = -2.06261 -pub fn cross_in_tray(x: &Vec) -> f64 { +pub fn cross_in_tray(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -771,7 +771,7 @@ pub fn cross_in_tray(x: &Vec) -> f64 { /// Global minimum: \ /// f(0,..., 0) = 0 /// -pub fn csendes(x: &Vec) -> f64 { +pub fn csendes(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(*arg, 6) * (2_f64 + f64::sin(1_f64 / arg)) @@ -784,7 +784,7 @@ pub fn csendes(x: &Vec) -> f64 { /// Global minimum: \ /// f(-1, 1) = 0 /// -pub fn cube(x: &Vec) -> f64 { +pub fn cube(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -800,7 +800,7 @@ pub fn cube(x: &Vec) -> f64 { /// Global minimum: \ /// f(-1, 1) = 0 /// -pub fn damavandi(x: &Vec) -> f64 { +pub fn damavandi(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -823,7 +823,7 @@ pub fn damavandi(x: &Vec) -> f64 { /// Global minimum: \ /// f(0,..., 0) = 0 /// -pub fn deb1(x: &Vec) -> f64 { +pub fn deb1(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(f64::sin(5_f64 * f64::consts::PI * arg), 6) @@ -835,7 +835,7 @@ pub fn deb1(x: &Vec) -> f64 { /// Global minimum: \ /// f(0,..., 0) = 0 /// -pub fn deb3(x: &Vec) -> f64 { +pub fn deb3(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi( @@ -851,7 +851,7 @@ pub fn deb3(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, +-15) = −24777 /// -pub fn deckkers_aarts(x: &Vec) -> f64 { +pub fn deckkers_aarts(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -868,7 +868,7 @@ pub fn deckkers_aarts(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0, 0, 0) = 0 /// -pub fn devilliers_glasser1(x: &Vec) -> f64 { +pub fn devilliers_glasser1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -898,7 +898,7 @@ pub fn devilliers_glasser1(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0, 0, 0, 0) = 0 /// -pub fn devilliers_glasser2(x: &Vec) -> f64 { +pub fn devilliers_glasser2(x: &[f64]) -> f64 { assert_eq!( x.len(), 5, @@ -929,7 +929,7 @@ pub fn devilliers_glasser2(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f((x_1, ..., x_n)) = 0, where x_i = 2^( -(2^i - 2) / 2^i) -pub fn dixon_price(x: &Vec) -> f64 { +pub fn dixon_price(x: &[f64]) -> f64 { assert!( x.len() >= 2, "Dixon-Price function takes an at least two dimensional vector as a parameter." @@ -944,7 +944,7 @@ pub fn dixon_price(x: &Vec) -> f64 { ///# De Jong n. 5 function /// 2-dimensional only\ -pub fn de_jong_n5(x: &Vec) -> f64 { +pub fn de_jong_n5(x: &[f64]) -> f64 { assert_eq!( 2, x.len(), @@ -961,9 +961,9 @@ pub fn de_jong_n5(x: &Vec) -> f64 { for i in (-32..33).step_by(16) { b.append(&mut vec![i as f64; 5]); } - let a = vec![a1, b]; + let a = [a1, b]; for i in 1..26 { - sum += 1_f64 / i as f64 + f64::powi(x1 - a[1][i], 6) + f64::powi(x2 - a[2][i], 6); + sum += 1_f64 / i as f64 + f64::powi(x1 - a[0][i], 6) + f64::powi(x2 - a[1][i], 6); } f64::powf(0.002 + sum, -1_f64) } @@ -973,7 +973,7 @@ pub fn de_jong_n5(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0, 0, 0, 0) = 0 /// -pub fn dolan(x: &Vec) -> f64 { +pub fn dolan(x: &[f64]) -> f64 { assert_eq!( x.len(), 5, @@ -994,7 +994,7 @@ pub fn dolan(x: &Vec) -> f64 { /// Global minimum:\ /// f(0,0) = -1 -pub fn drop_wave(x: &Vec) -> f64 { +pub fn drop_wave(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1010,7 +1010,7 @@ pub fn drop_wave(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(PI, PI) = -1, where PI = 3.14159... -pub fn easom(x: &Vec) -> f64 { +pub fn easom(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1028,7 +1028,7 @@ pub fn easom(x: &Vec) -> f64 { /// Global minimum: \ /// f(2.842503, 1.920175) = 0.470427 /// -pub fn eavd(x: &Vec) -> f64 { +pub fn eavd(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1046,7 +1046,7 @@ pub fn eavd(x: &Vec) -> f64 { /// Global minimum: \ /// f(2.842503, 1.920175) = 0.470427 /// -pub fn egg_crate(x: &Vec) -> f64 { +pub fn egg_crate(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1061,7 +1061,7 @@ pub fn egg_crate(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(512, 404.2319) = -959.6407 -pub fn eggholder(x: &Vec) -> f64 { +pub fn eggholder(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1077,7 +1077,7 @@ pub fn eggholder(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, ..., 0) = 1 /// -pub fn exponential(x: &Vec) -> f64 { +pub fn exponential(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(*arg, 2) @@ -1090,7 +1090,7 @@ pub fn exponential(x: &Vec) -> f64 { /// Global minimum: \ /// f(1, 10) = 0 /// -pub fn exp2(x: &Vec) -> f64 { +pub fn exp2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1129,7 +1129,7 @@ pub fn forrester_et_al(x: &f64) -> f64 { /// Global minimum: \ /// f(5,4) = 0 /// -pub fn freudenstein_roth(x: &Vec) -> f64 { +pub fn freudenstein_roth(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1146,7 +1146,7 @@ pub fn freudenstein_roth(x: &Vec) -> f64 { /// Global minimum: \ /// f(0.45834282, 0.45834282) = 0.060447 /// -pub fn giunta(x: &Vec) -> f64 { +pub fn giunta(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1165,7 +1165,7 @@ pub fn giunta(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(0, -1) = 3 -pub fn goldstein_price(x: &Vec) -> f64 { +pub fn goldstein_price(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1197,7 +1197,7 @@ pub fn gramacy_lee(x: &f64) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn griewank(x: &Vec) -> f64 { +pub fn griewank(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Griewank function takes an at least one dimensional vector as a parameter." @@ -1215,7 +1215,7 @@ pub fn griewank(x: &Vec) -> f64 { /// 99-dimensional only \ /// Global minimum: \ /// f(50, 25, 1.5, .....) = 0/// -pub fn gulf_research(x: &Vec) -> f64 { +pub fn gulf_research(x: &[f64]) -> f64 { assert_eq!( x.len(), 99, @@ -1246,20 +1246,20 @@ pub fn gulf_research(x: &Vec) -> f64 { /// Global minimum:\ /// f(0.114614, 0.555649, 0.852547) -pub fn hartmann_3d(x: &Vec) -> f64 { +pub fn hartmann_3d(x: &[f64]) -> f64 { assert_eq!( 3, x.len(), "Hartmann 3-dimensional function takes only a three dimensional vector as a parameter." ); let alfa = [1.0, 1.2, 3.0, 3.2]; - let a = vec![ + let a = [ vec![3.0, 10.0, 30.0], vec![0.1, 10.0, 35.0], vec![3.0, 10.0, 30.0], vec![0.1, 10.0, 35.0], ]; - let p = vec![ + let p = [ vec![3.689, 1.17, 2.673], vec![4.699, 4.387, 7.47], vec![1.091, 8.732, 5.547], @@ -1279,20 +1279,20 @@ pub fn hartmann_3d(x: &Vec) -> f64 { ///# Hartmann 4-dimensional function /// 4-dimensional only\ -pub fn hartmann_4d(x: &Vec) -> f64 { +pub fn hartmann_4d(x: &[f64]) -> f64 { assert_eq!( 4, x.len(), "Hartmann 4-dimensional function takes only a three dimensional vector as a parameter." ); let alfa = [1.0, 1.2, 3.0, 3.2]; - let a = vec![ + let a = [ vec![10.0, 3.0, 17.0, 3.5, 1.7, 8.0], vec![0.05, 10.0, 17.0, 0.1, 8.0, 14.0], vec![3.0, 3.5, 1.7, 10.0, 17.0, 8.0], vec![17.0, 8.0, 0.05, 10.0, 0.1, 14.0], ]; - let p = vec![ + let p = [ vec![1.312, 1.696, 5.569, 1.24, 8.283, 5.886], vec![2.329, 4.135, 8.307, 3.736, 1.004, 9.991], vec![2.348, 1.451, 3.522, 2.883, 3.047, 6.650], @@ -1314,20 +1314,20 @@ pub fn hartmann_4d(x: &Vec) -> f64 { /// Global minimum:\ /// f(0.20169, 0.150011, 0.476874, 0.275332, 0.311652, 0.6573) = -3.32237 -pub fn hartmann_6d(x: &Vec) -> f64 { +pub fn hartmann_6d(x: &[f64]) -> f64 { assert_eq!( 6, x.len(), "Hartmann 6-dimensional function takes only a six dimensional vector as a parameter." ); let alfa = [1.0, 1.2, 3.0, 3.2]; - let a = vec![ + let a = [ vec![10.0, 3.0, 17.0, 3.5, 1.7, 8.0], vec![0.05, 10.0, 17.0, 0.1, 8.0, 14.0], vec![3.0, 3.5, 1.7, 10.0, 17.0, 8.0], vec![17.0, 8.0, 0.05, 10.0, 0.1, 14.0], ]; - let p = vec![ + let p = [ vec![1.312, 1.696, 5.569, 1.24, 8.283, 5.886], vec![2.329, 4.135, 8.307, 3.736, 1.004, 9.991], vec![2.348, 1.451, 0.522, 2.883, 3.047, 6.650], @@ -1353,7 +1353,7 @@ pub fn hartmann_6d(x: &Vec) -> f64 { /// f(-2.805118, 3.131312) = 0 \ /// f(-3.779310, -3.283186) = 0 \ /// f(3.584428, -1.848126) = 0 -pub fn himmelblau(x: &Vec) -> f64 { +pub fn himmelblau(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1371,7 +1371,7 @@ pub fn himmelblau(x: &Vec) -> f64 { /// f(8.05502, -9.66459) = -19.2085 \ /// f(-8.05502, 9.66459) = -19.2085 \ /// f(-8.05502, -9.66459) = -19.2085 -pub fn holder_table(x: &Vec) -> f64 { +pub fn holder_table(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1393,7 +1393,7 @@ pub fn holder_table(x: &Vec) -> f64 { /// Global minima: \ /// f(+-9.646168, -+9.646168) = −26.920336 /// -pub fn holder_table2(x: &Vec) -> f64 { +pub fn holder_table2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1415,7 +1415,7 @@ pub fn holder_table2(x: &Vec) -> f64 { /// Global minima: \ /// f(f(+-8.055023472141116, +-9.664590028909654) = −19.20850 /// -pub fn holder_table3(x: &Vec) -> f64 { +pub fn holder_table3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1439,7 +1439,7 @@ pub fn holder_table3(x: &Vec) -> f64 { /// Global minimum: \ /// f(4,2) = ~ −2.3458 /// -pub fn hosaki(x: &Vec) -> f64 { +pub fn hosaki(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1458,7 +1458,7 @@ pub fn hosaki(x: &Vec) -> f64 { /// Global minimum: \ /// f(0.257825, 0.257825) = 124.3612 /// -pub fn jennrich_sampson(x: &Vec) -> f64 { +pub fn jennrich_sampson(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1500,7 +1500,7 @@ pub fn langermann(x: &[f64], m: i32, c: &[f64], a: &[Vec]) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(1, ..., 1) = 0 -pub fn levy(x: &Vec) -> f64 { +pub fn levy(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Levy function takes an at least one dimensional vector as a parameter." @@ -1520,7 +1520,7 @@ pub fn levy(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(1, 1) = 0 -pub fn levy_n13(x: &Vec) -> f64 { +pub fn levy_n13(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1538,7 +1538,7 @@ pub fn levy_n13(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 1.39325) = f(1.39325, 0) = −0.673668 /// -pub fn keane(x: &Vec) -> f64 { +pub fn keane(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1555,7 +1555,7 @@ pub fn keane(x: &Vec) -> f64 { /// Global minimum: \ /// f(1, 1) = 0 /// -pub fn leon(x: &Vec) -> f64 { +pub fn leon(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1570,7 +1570,7 @@ pub fn leon(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(0, 0) = 0 -pub fn matyas(x: &Vec) -> f64 { +pub fn matyas(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1585,7 +1585,7 @@ pub fn matyas(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(-0.54719, -1.54719) = -1.9133 -pub fn mcormick(x: &Vec) -> f64 { +pub fn mcormick(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1601,7 +1601,7 @@ pub fn mcormick(x: &Vec) -> f64 { /// Global minimum: \ /// depends of number of dimensions \ /// for two-dimensional argument: f(2.2, 1.57) = -1.8013 -pub fn michalewicz(x: &Vec) -> f64 { +pub fn michalewicz(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Michalewicz function takes an at least one dimensional vector as a parameter." @@ -1622,7 +1622,7 @@ pub fn michalewicz(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 1, 1, 1) = 0 /// -pub fn miele_cantrell(x: &Vec) -> f64 { +pub fn miele_cantrell(x: &[f64]) -> f64 { assert_eq!( x.len(), 4, @@ -1643,7 +1643,7 @@ pub fn miele_cantrell(x: &Vec) -> f64 { /// # Parsopoulos function /// 2-dimensional only \ -pub fn parsopoulos(x: &Vec) -> f64 { +pub fn parsopoulos(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1659,7 +1659,7 @@ pub fn parsopoulos(x: &Vec) -> f64 { /// Global minimum: \ /// f(+-9.646168, -+9.646168) = −0.96354 /// -pub fn pen_holder(x: &Vec) -> f64 { +pub fn pen_holder(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1687,7 +1687,7 @@ pub fn pen_holder(x: &Vec) -> f64 { /// Multidimensional /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn pathological(x: &Vec) -> f64 { +pub fn pathological(x: &[f64]) -> f64 { let mut res = 0_f64; for i in 1..x.len() - 2 { res += 0.5 @@ -1709,7 +1709,7 @@ pub fn pathological(x: &Vec) -> f64 { /// 10-dimensional only \ /// Global minimum: \ /// f(9.351, ...., 9.351) = ~−45.778 -pub fn paviani(x: &Vec) -> f64 { +pub fn paviani(x: &[f64]) -> f64 { assert_eq!( x.len(), 10, @@ -1753,7 +1753,7 @@ pub fn pinter(x: &[f64]) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(0, 0) = 0.9 -pub fn periodic(x: &Vec) -> f64 { +pub fn periodic(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1769,7 +1769,7 @@ pub fn periodic(x: &Vec) -> f64 { /// 2-dimensional only\ /// Global minimum: \ /// f(1, 0.5, ... , 1/d) = 0 -pub fn perm_0_d_beta(x: &Vec, beta: &f64) -> f64 { +pub fn perm_0_d_beta(x: &[f64], beta: &f64) -> f64 { assert_eq!( x.len(), 2, @@ -1792,7 +1792,7 @@ pub fn perm_0_d_beta(x: &Vec, beta: &f64) -> f64 { /// Global minimum:\ /// f(1,2,...,D) = 0 -pub fn perm_d_beta(x: &Vec, beta: &f64) -> f64 { +pub fn perm_d_beta(x: &[f64], beta: &f64) -> f64 { let d = x.len(); let mut res = 0_f64; for i in 1..d + 1 { @@ -1810,7 +1810,7 @@ pub fn perm_d_beta(x: &Vec, beta: &f64) -> f64 { /// Global minimum: \ /// f(0,..,0) = 0 -pub fn powell(x: &Vec) -> f64 { +pub fn powell(x: &[f64]) -> f64 { assert!( x.len() > 3, "Powell function takes at least a four dimensional vector as a parameter." @@ -1831,7 +1831,7 @@ pub fn powell(x: &Vec) -> f64 { /// Global minimum: \ /// f(0,..,0) = 0 -pub fn powell2(x: &Vec) -> f64 { +pub fn powell2(x: &[f64]) -> f64 { assert!( x.len() > 3, "Powell Singular 2 function takes at least a four dimensional vector as a parameter." @@ -1864,7 +1864,7 @@ pub fn powell_sum(x: &[f64]) -> f64 { /// Global minimum: \ /// f(0,...,0) = 0 -pub fn power_sum(x: &Vec, b: &Vec) -> f64 { +pub fn power_sum(x: &[f64], b: &[f64]) -> f64 { assert_eq!( b.len(), x.len(), @@ -1885,7 +1885,7 @@ pub fn power_sum(x: &Vec, b: &Vec) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(+-5, +-5) = 0 -pub fn price1(x: &Vec) -> f64 { +pub fn price1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1900,7 +1900,7 @@ pub fn price1(x: &Vec) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(0, 0) = 0.9 -pub fn price2(x: &Vec) -> f64 { +pub fn price2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1916,7 +1916,7 @@ pub fn price2(x: &Vec) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(+-5, +-5) = 0 -pub fn price3(x: &Vec) -> f64 { +pub fn price3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1932,7 +1932,7 @@ pub fn price3(x: &Vec) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(0, 0) = 0 -pub fn price4(x: &Vec) -> f64 { +pub fn price4(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1948,7 +1948,7 @@ pub fn price4(x: &Vec) -> f64 { /// Multidimensional /// Global minimum: \ /// f(+-sqrt(i) -pub fn qing(x: &Vec, i: &f64) -> f64 { +pub fn qing(x: &[f64], i: &f64) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(f64::powi(*arg, 2) - i, 2) @@ -1960,7 +1960,7 @@ pub fn qing(x: &Vec, i: &f64) -> f64 { /// Two-dimensional /// Global minimum: \ /// f(0.19388, 0.48513) = −3873.7243. -pub fn quadratic(x: &Vec) -> f64 { +pub fn quadratic(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -1978,7 +1978,7 @@ pub fn quadratic(x: &Vec) -> f64 { // /// Multidimensional // /// Global minimum: \ // /// f(0, ..., 0) = 0 -// pub fn quartic(x: &Vec, i: &f64) -> f64 { +// pub fn quartic(x: &[f64], i: &f64) -> f64 { // let mut res = 0_f64; // for (dim, arg) in x.iter().enumerate() { // res += dim*f64::powi(*arg, 4)+thread_rng().gen_range(0.0..1.0); @@ -1990,7 +1990,7 @@ pub fn quadratic(x: &Vec) -> f64 { /// Multidimensional /// Global minimum: \ /// f(-1, ..., -1) = f(2, ..., 2) = 0 -pub fn quintic(x: &Vec) -> f64 { +pub fn quintic(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::abs( @@ -2021,7 +2021,7 @@ pub fn rana(x: &[f64]) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn rastrigin(x: &Vec) -> f64 { +pub fn rastrigin(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Rastrigin function takes an at least one dimensional vector as a parameter." @@ -2037,7 +2037,7 @@ pub fn rastrigin(x: &Vec) -> f64 { /// # Ripple 1 function /// Two-dimensional -pub fn ripple1(x: &Vec) -> f64 { +pub fn ripple1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2059,7 +2059,7 @@ pub fn ripple1(x: &Vec) -> f64 { /// # Ripple 25 function /// Two-dimensional -pub fn ripple25(x: &Vec) -> f64 { +pub fn ripple25(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2081,6 +2081,7 @@ pub fn ripple25(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(1, ..., 1) = 0 +#[allow(clippy::ptr_arg)] pub fn rosenbrock(x: &Vec) -> f64 { assert!( x.len() >= 2, @@ -2098,7 +2099,7 @@ pub fn rosenbrock(x: &Vec) -> f64 { /// Global minimum: \ /// f(-1, -1) = 0 -pub fn rosenbrock_modified(x: &Vec) -> f64 { +pub fn rosenbrock_modified(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2119,7 +2120,7 @@ pub fn rosenbrock_modified(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0) = 0 -pub fn rotated_ellipse(x: &Vec) -> f64 { +pub fn rotated_ellipse(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2135,7 +2136,7 @@ pub fn rotated_ellipse(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0) = 0 -pub fn rotated_ellipse2(x: &Vec) -> f64 { +pub fn rotated_ellipse2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2150,7 +2151,7 @@ pub fn rotated_ellipse2(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn rotated_hyper_ellipsoid(x: &Vec) -> f64 { +pub fn rotated_hyper_ellipsoid(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Rotated Hyper-Ellipsoid function takes an at least one dimensional vector as a parameter." @@ -2169,7 +2170,7 @@ pub fn rotated_hyper_ellipsoid(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0) = 0 -pub fn rump(x: &Vec) -> f64 { +pub fn rump(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2187,7 +2188,7 @@ pub fn rump(x: &Vec) -> f64 { /// # Salomon function /// Multidimensional -pub fn salomon(x: &Vec) -> f64 { +pub fn salomon(x: &[f64]) -> f64 { let mut res = 0_f64; for arg in x { res += f64::powi(*arg, 2) @@ -2198,7 +2199,7 @@ pub fn salomon(x: &Vec) -> f64 { /// # Sargan function /// Multidimensional -pub fn sargan(x: &Vec) -> f64 { +pub fn sargan(x: &[f64]) -> f64 { let mut res = 0_f64; for (dim, val) in x.iter().enumerate() { let mut innersum = 0_f64; @@ -2216,7 +2217,7 @@ pub fn sargan(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(0, 0) = 0 -pub fn schaffer_n1(x: &Vec) -> f64 { +pub fn schaffer_n1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2232,7 +2233,7 @@ pub fn schaffer_n1(x: &Vec) -> f64 { /// 2-dimensional only \ /// Global minimum: \ /// f(0, 0) = 0 -pub fn schaffer_n2(x: &Vec) -> f64 { +pub fn schaffer_n2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2247,7 +2248,7 @@ pub fn schaffer_n2(x: &Vec) -> f64 { /// # Schaffer N.3 function /// 2-dimensional only \ -pub fn schaffer_n3(x: &Vec) -> f64 { +pub fn schaffer_n3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2269,7 +2270,7 @@ pub fn schaffer_n3(x: &Vec) -> f64 { /// f(0, -1.25313) = 0.292579 \ /// f(1.25313, 0) = 0.292579 \ /// f(-1.25313, 0) = 0.292579 -pub fn schaffer_n4(x: &Vec) -> f64 { +pub fn schaffer_n4(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2289,7 +2290,7 @@ pub fn schaffer_n4(x: &Vec) -> f64 { /// Global minimum: \ /// f(0.78547, 0.78547, 0.78547) = 3 -pub fn schmidt_vetters(x: &Vec) -> f64 { +pub fn schmidt_vetters(x: &[f64]) -> f64 { assert_eq!( x.len(), 3, @@ -2310,7 +2311,7 @@ pub fn schmidt_vetters(x: &Vec) -> f64 { pub fn schumer_steiglitz(x: &[f64]) -> f64 { let mut res = 0_f64; - for (_dim, arg) in x.iter().enumerate() { + for arg in x.iter() { res += f64::powi(*arg, 4) } res @@ -2320,7 +2321,7 @@ pub fn schumer_steiglitz(x: &[f64]) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(420.9687, ..., 420.9687) = 0 -pub fn schwefel(x: &Vec) -> f64 { +pub fn schwefel(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Schwefel function takes an at least one dimensional vector as a parameter." @@ -2341,7 +2342,7 @@ pub fn schwefel(x: &Vec) -> f64 { /// at m=7: f(4,4,4,4) = -10.4029 \ /// at m=10: f(4,4,4,4) = -10.5364 -pub fn shekel(x: &Vec, m: i32, beta: &Vec, c: &Vec>) -> f64 { +pub fn shekel(x: &[f64], m: i32, beta: &[f64], c: &[Vec]) -> f64 { assert_eq!( x.len(), 4, @@ -2367,7 +2368,7 @@ pub fn shekel(x: &Vec, m: i32, beta: &Vec, c: &Vec>) -> f64 { /// Global minimum:\ /// f(4,4,4,4) = -10.5364 -pub fn shekel_default(x: &Vec) -> f64 { +pub fn shekel_default(x: &[f64]) -> f64 { assert_eq!( x.len(), 4, @@ -2376,7 +2377,7 @@ pub fn shekel_default(x: &Vec) -> f64 { let mut res = 0.0; let raw = vec![1.0, 2.0, 2.0, 4.0, 4.0, 6.0, 3.0, 7.0, 5.0, 5.0]; let beta = raw.into_iter().map(|x| 0.1 * x).collect::>(); - let c = vec![ + let c = [ vec![4.0, 1.0, 8.0, 6.0, 3.0, 2.0, 5.0, 8.0, 6.0, 7.0], vec![4.0, 1.0, 8.0, 6.0, 7.0, 9.0, 3.0, 1.0, 2.0, 3.6], vec![4.0, 1.0, 8.0, 6.0, 3.0, 2.0, 5.0, 8.0, 6.0, 7.0], @@ -2399,7 +2400,7 @@ pub fn shekel_default(x: &Vec) -> f64 { /// Global minima: 18\ /// f(x*) = -186.7309 -pub fn shubert(x: &Vec) -> f64 { +pub fn shubert(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2438,7 +2439,7 @@ pub fn sphere(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(-2.903534, ..., -2.903534) = -39.16599n, where n - number of dimensions of argument vector -pub fn styblinski_tang(x: &Vec) -> f64 { +pub fn styblinski_tang(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Styblinski-Tang function takes an at least one dimensional vector as a parameter." @@ -2455,7 +2456,7 @@ pub fn styblinski_tang(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn stretched_v_sine_wave(x: &Vec) -> f64 { +pub fn stretched_v_sine_wave(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Streched V Sine Wave function takes an at least one dimensional vector as a parameter." @@ -2475,7 +2476,7 @@ pub fn stretched_v_sine_wave(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn sum_of_powers(x: &Vec) -> f64 { +pub fn sum_of_powers(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Sum of Different Powers function takes an at least one dimensional vector as a parameter." @@ -2491,7 +2492,7 @@ pub fn sum_of_powers(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn sum_squares(x: &Vec) -> f64 { +pub fn sum_squares(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Sum Squares function takes an at least one dimensional vector as a parameter." @@ -2508,7 +2509,7 @@ pub fn sum_squares(x: &Vec) -> f64 { /// Global minimum: \ /// f(+-pi/2, 0) = −10.872300 /// -pub fn testtube_holder(x: &Vec) -> f64 { +pub fn testtube_holder(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2530,7 +2531,7 @@ pub fn testtube_holder(x: &Vec) -> f64 { /// Global minimum: \ /// f(0, 0) = f(-2, 0) = 0 /// -pub fn trecanni(x: &Vec) -> f64 { +pub fn trecanni(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2546,7 +2547,7 @@ pub fn trecanni(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f((x_1, ..., x_n)) = -n * ( n + 4 ) * ( n - 1 ) / 6, where x_i = i * (n + 1 - i) -pub fn trid(x: &Vec) -> f64 { +pub fn trid(x: &[f64]) -> f64 { assert!( x.len() >= 2, "Trid function takes an at least two dimensional vector as a parameter." @@ -2562,7 +2563,7 @@ pub fn trid(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f((x_1, ..., x_n)) = -n * ( n + 4 ) * ( n - 1 ) / 6, where x_i = i * (n + 1 - i) -pub fn trid10(x: &Vec) -> f64 { +pub fn trid10(x: &[f64]) -> f64 { assert!( x.len() >= 2, "Trid 10 function takes an at least two dimensional vector as a parameter." @@ -2580,7 +2581,7 @@ pub fn trid10(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn trigonometric1(x: &Vec) -> f64 { +pub fn trigonometric1(x: &[f64]) -> f64 { assert!( x.len() >= 2, "Trigonometric 1 function takes an at least two dimensional vector as a parameter." @@ -2603,7 +2604,7 @@ pub fn trigonometric1(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0.9, ..., 0.9) = 0 -pub fn trigonometric2(x: &Vec) -> f64 { +pub fn trigonometric2(x: &[f64]) -> f64 { assert!( x.len() >= 2, "Trigonometric 2 function takes an at least two dimensional vector as a parameter." @@ -2612,7 +2613,7 @@ pub fn trigonometric2(x: &Vec) -> f64 { for arg in x { sum += f64::cos(*arg); } - for (_dim, arg) in x.iter().enumerate() { + for arg in x.iter() { sum += 8_f64 * f64::powi(f64::sin(7_f64 * f64::powi(arg - 0.9, 2)), 2) + 6_f64 * f64::powi(f64::sin(14_f64 * f64::powi(x[0] - 0.9, 2)), 2) + f64::powi(arg - 0.9, 2) @@ -2625,7 +2626,7 @@ pub fn trigonometric2(x: &Vec) -> f64 { /// # Ursem 1 function /// 2-dimensional only \ -pub fn ursem1(x: &Vec) -> f64 { +pub fn ursem1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2639,7 +2640,7 @@ pub fn ursem1(x: &Vec) -> f64 { /// # Ursem 3 function /// 2-dimensional only \ -pub fn ursem3(x: &Vec) -> f64 { +pub fn ursem3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2660,7 +2661,7 @@ pub fn ursem3(x: &Vec) -> f64 { /// # Ursem 4 function /// 2-dimensional only \ -pub fn ursem4(x: &Vec) -> f64 { +pub fn ursem4(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2677,7 +2678,7 @@ pub fn ursem4(x: &Vec) -> f64 { /// # Ursem Waves function /// 2-dimensional only \ -pub fn ursem_waves(x: &Vec) -> f64 { +pub fn ursem_waves(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2694,7 +2695,7 @@ pub fn ursem_waves(x: &Vec) -> f64 { /// Two-dimensional \ /// Global minimum: \ /// f(−0.024403, 0.210612) = −3.30686865. -pub fn trefethen(x: &Vec) -> f64 { +pub fn trefethen(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2714,7 +2715,7 @@ pub fn trefethen(x: &Vec) -> f64 { /// Two-dimensional \ /// Global minimum: \ /// f(−0.024403, 0.210612) = −3.30686865. -pub fn vss(x: &Vec) -> f64 { +pub fn vss(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2737,7 +2738,7 @@ pub fn vss(x: &Vec) -> f64 { /// Global minima: 18\ /// f(−0.0299, 0) = −0.003791 -pub fn wayburn_seader1(x: &Vec) -> f64 { +pub fn wayburn_seader1(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2754,7 +2755,7 @@ pub fn wayburn_seader1(x: &Vec) -> f64 { /// Global minima: 18\ /// f(0.2, 1) = 0 -pub fn wayburn_seader2(x: &Vec) -> f64 { +pub fn wayburn_seader2(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2774,7 +2775,7 @@ pub fn wayburn_seader2(x: &Vec) -> f64 { /// Global minimum:\ /// f(5.611, 6.187) = 21.35 -pub fn wayburn_seader3(x: &Vec) -> f64 { +pub fn wayburn_seader3(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2799,7 +2800,7 @@ pub fn wordmax(chromosome: &Vec) -> f64 { /// Global minimum:\ /// f() = (0, 0, 0) = 0 -pub fn wolfe(x: &Vec) -> f64 { +pub fn wolfe(x: &[f64]) -> f64 { assert_eq!( x.len(), 3, @@ -2815,7 +2816,7 @@ pub fn wolfe(x: &Vec) -> f64 { // /// n-dimensional \ // /// Global minimum: \ // /// f(0, ..., 0) = 0 -// pub fn xin_she_yang_1(x: &Vec) -> f64 { +// pub fn xin_she_yang_1(x: &[f64]) -> f64 { // assert!( // !x.is_empty(), // "Xin-She Yang (Function 1) takes an at least one dimensional vector as a parameter." @@ -2831,14 +2832,14 @@ pub fn wolfe(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn xin_she_yang_2(x: &Vec) -> f64 { +pub fn xin_she_yang_2(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Xin-She Yang function 2 takes an at least one dimensional vector as a parameter." ); let mut temp1 = 0.0; let mut temp2 = 0.0; - for (_index, x_curr) in x.iter().enumerate() { + for x_curr in x.iter() { temp1 += f64::abs(*x_curr); temp2 += f64::sin(f64::powi(*x_curr, 2)) } @@ -2849,7 +2850,7 @@ pub fn xin_she_yang_2(x: &Vec) -> f64 { /// n-dimensional \ /// Global minimum: \ /// f(0, ..., 0) = 0 -pub fn zakharov(x: &Vec) -> f64 { +pub fn zakharov(x: &[f64]) -> f64 { assert!( !x.is_empty(), "Zakharov function takes an at least one dimensional vector as a parameter." @@ -2869,7 +2870,7 @@ pub fn zakharov(x: &Vec) -> f64 { /// Global minima: 18\ /// f(−0.0299, 0) = −0.003791 -pub fn zettl(x: &Vec) -> f64 { +pub fn zettl(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, @@ -2886,7 +2887,7 @@ pub fn zettl(x: &Vec) -> f64 { /// Global minima: 18\ /// f(−1.0465, 0) =~ −0.3523 -pub fn zirilli(x: &Vec) -> f64 { +pub fn zirilli(x: &[f64]) -> f64 { assert_eq!( x.len(), 2, diff --git a/tests/builder_tests.rs b/tests/builder_tests.rs index b9cf118e..b23846b3 100644 --- a/tests/builder_tests.rs +++ b/tests/builder_tests.rs @@ -1,5 +1,24 @@ #![cfg(feature = "ga")] +use std::f64; + +#[allow(clippy::ptr_arg)] +pub fn ackley2(x: &Vec) -> f64 { + assert_eq!( + 2, + x.len(), + "Ackley 2nd function takes only a two dimensional vector as a parameter." + ); + let x1 = x[0]; + let x2 = x[1]; + f64::powf(-200_f64 * f64::consts::E, -0.02) * f64::sqrt(f64::powi(x1, 2) + f64::powi(x2, 2)) +} + +#[allow(clippy::ptr_arg)] +pub fn wordmax(chromosome: &Vec) -> f64 { + chromosome.iter().filter(|gene| **gene).count() as f64 +} + use ecrs::{ ga::{individual::RealValueIndividual, StdoutProbe}, prelude::{ @@ -23,7 +42,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { >() .set_max_generation_count(500) .set_population_size(100) - .set_fitness_fn(ecrs::test_functions::rastrigin) + .set_fitness_fn(ackley2) .set_crossover_operator(ecrs::ga::operators::crossover::SinglePoint::new()) .set_replacement_operator(ecrs::ga::operators::replacement::BothParents) .set_mutation_operator(ecrs::ga::operators::mutation::Identity::new()) @@ -48,7 +67,7 @@ fn generic_does_not_panic_with_some_params_unspecified() { FnBasedFitness, StdoutProbe, >() - .set_fitness_fn(ecrs::test_functions::rastrigin) + .set_fitness_fn(ackley2) .set_crossover_operator(ecrs::ga::operators::crossover::SinglePoint::new()) .set_mutation_operator(ecrs::ga::operators::mutation::Identity::new()) .set_replacement_operator(ecrs::ga::operators::replacement::BothParents) @@ -65,16 +84,10 @@ fn generic_does_not_panic_with_some_params_unspecified() { #[test] fn rvc_does_not_panic_with_some_operators_unspecified() { - let _ = ecrs::ga::Builder::with_rvc() - .dim(10) - .fitness_fn(ecrs::test_functions::ackley) - .build(); + let _ = ecrs::ga::Builder::with_rvc().dim(10).fitness_fn(ackley2).build(); } #[test] fn bsc_does_not_panic_with_some_operators_unsepcified() { - let _ = ecrs::ga::Builder::with_bsc() - .dim(10) - .fitness_fn(ecrs::test_functions::wordmax) - .build(); + let _ = ecrs::ga::Builder::with_bsc().dim(10).fitness_fn(wordmax).build(); }