diff --git a/examples/jssp/problem/crossover.rs b/examples/jssp/problem/crossover.rs index d87d6ef..de23a3b 100644 --- a/examples/jssp/problem/crossover.rs +++ b/examples/jssp/problem/crossover.rs @@ -2,6 +2,7 @@ use ecrs::{ ga::{individual::IndividualTrait, GAMetadata}, prelude::crossover::CrossoverOperator, }; +use push_trait::PushBack; use rand::{thread_rng, Rng}; use super::individual::JsspIndividual; @@ -16,10 +17,8 @@ impl JsspCrossover { distr: rand::distributions::Uniform::new(0.0, 1.0), } } -} -impl CrossoverOperator for JsspCrossover { - fn apply( + fn apply_single( &mut self, _metadata: &GAMetadata, parent_1: &JsspIndividual, @@ -53,16 +52,30 @@ impl CrossoverOperator for JsspCrossover { } } +impl CrossoverOperator for JsspCrossover { + fn apply(&mut self, metadata: &GAMetadata, selected: &[&JsspIndividual]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} + pub struct NoopCrossover; impl NoopCrossover { pub fn new() -> Self { Self } -} -impl CrossoverOperator for NoopCrossover { - fn apply( + fn apply_single( &mut self, _metadata: &GAMetadata, parent_1: &JsspIndividual, @@ -71,3 +84,19 @@ impl CrossoverOperator for NoopCrossover { (parent_1.clone(), parent_2.clone()) } } + +impl CrossoverOperator for NoopCrossover { + fn apply(&mut self, metadata: &GAMetadata, selected: &[&JsspIndividual]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga.rs b/src/ga.rs index a2fe6c9..caea319 100644 --- a/src/ga.rs +++ b/src/ga.rs @@ -316,19 +316,9 @@ where self.metadata.selection_dur = Some(self.timer.elapsed()); // 5. From mating pool create new generation (apply crossover & mutation). - let mut children: Vec = Vec::with_capacity(self.config.params.population_size); - // FIXME: Do not assume that population size is an even number. self.timer.start(); - for parents in mating_pool.chunks(2) { - let crt_children = - self.config - .crossover_operator - .apply(&self.metadata, parents[0], parents[1]); - - children.push(crt_children.0); - children.push(crt_children.1); - } + let mut children = self.config.crossover_operator.apply(&self.metadata, &mating_pool); self.metadata.crossover_dur = Some(self.timer.elapsed()); self.timer.start(); diff --git a/src/ga/operators/crossover.rs b/src/ga/operators/crossover.rs index da10c98..a8baba4 100644 --- a/src/ga/operators/crossover.rs +++ b/src/ga/operators/crossover.rs @@ -5,21 +5,32 @@ pub use impls::*; use crate::ga::individual::IndividualTrait; use crate::ga::GAMetadata; + /// # Crossover Operator /// /// This trait defines common behaviour for crossover operators. /// You can implement this trait to provide your custom crossover operator to the GA. pub trait CrossoverOperator { - /// Returns a tuple of children + /// FIXME: Understand lifetimes here! + // fn apply_iter<'i, InputIter, OutputIter>( + // &mut self, + // metadata: &GAMetadata, + // selected: InputIter, + // ) -> OutputIter + // where + // InputIter: Iterator, + // OutputIter: Iterator, + // IndividualT: 'i; + + /// Apply crossover operator to the selected population part. /// /// ## 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); + /// * `metadata` - metadata provided by the GA runtime, + /// * `selected` - result of running selection operator, + /// + /// ## Returns + /// + /// Vector of individuals created during the crossover stage. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec; } diff --git a/src/ga/operators/crossover/impls.rs b/src/ga/operators/crossover/impls.rs index a49c173..716f738 100644 --- a/src/ga/operators/crossover/impls.rs +++ b/src/ga/operators/crossover/impls.rs @@ -1,1003 +1,26 @@ -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) - } -} +pub mod fixed_point; +pub mod multi_point; +pub mod ordered; +pub mod pmx; +pub mod ppx; +pub mod shuffle; +pub mod single_point; +pub mod two_point; +pub mod uniform; +pub mod uniform_parameterized; + +pub use fixed_point::FixedPoint; +pub use multi_point::MultiPoint; +pub use ordered::OrderedCrossover; +pub use pmx::Pmx; +pub use ppx::Ppx; +pub use shuffle::Shuffle; +pub use single_point::SinglePoint; +pub use two_point::TwoPoint; +pub use uniform::Uniform; +pub use uniform_parameterized::UniformParameterized; #[cfg(test)] mod test { @@ -1043,7 +66,9 @@ mod test { 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); + let children = op.apply(&GAMetadata::default(), &[&p1, &p2]); + let child_1 = &children[0]; + let child_2 = &children[1]; assert_eq!(child_1.chromosome.len(), 10); assert_eq!(child_2.chromosome.len(), 10); } @@ -1055,8 +80,10 @@ mod test { 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()) { + let children = op.apply(&GAMetadata::default(), &[&p1, &p2]); + let child_1 = &children[0]; + let child_2 = &children[1]; + for (g1, g2) in child_1.chromosome.iter().zip(child_2.chromosome.iter()) { assert_eq!(g1 * g2, 0); assert_eq!(g1 + g2, 1); } @@ -1072,7 +99,9 @@ mod test { 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 children = op.apply(&GAMetadata::default(), &[&p1, &p2]); + let child_1 = &children[0]; + let child_2 = &children[1]; 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]; diff --git a/src/ga/operators/crossover/impls/fixed_point.rs b/src/ga/operators/crossover/impls/fixed_point.rs new file mode 100644 index 0000000..9db8bb3 --- /dev/null +++ b/src/ga/operators/crossover/impls/fixed_point.rs @@ -0,0 +1,89 @@ +use len_trait::Len; + +use std::ops::IndexMut; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; + +use super::CrossoverOperator; + +/// # 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 } + } + + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Len, + GeneT: Copy, + { + 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) + } +} + +impl CrossoverOperator for FixedPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: IndexMut + Len, + GeneT: Copy, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// It works by cutting parent chromosomes in single, fixed point and the acting like a single + /// point crossover. + /// + /// ## Arguments + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/multi_point.rs b/src/ga/operators/crossover/impls/multi_point.rs new file mode 100644 index 0000000..843c622 --- /dev/null +++ b/src/ga/operators/crossover/impls/multi_point.rs @@ -0,0 +1,152 @@ +use len_trait::Len; + +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 } + } + + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for MultiPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/ordered.rs b/src/ga/operators/crossover/impls/ordered.rs new file mode 100644 index 0000000..942873c --- /dev/null +++ b/src/ga/operators/crossover/impls/ordered.rs @@ -0,0 +1,179 @@ +use len_trait::Len; +use std::collections::HashSet; +use std::hash::Hash; +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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) + } + + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + 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) + } +} + +impl CrossoverOperator for OrderedCrossover +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/pmx.rs b/src/ga/operators/crossover/impls/pmx.rs new file mode 100644 index 0000000..42162b3 --- /dev/null +++ b/src/ga/operators/crossover/impls/pmx.rs @@ -0,0 +1,223 @@ +use itertools::enumerate; +use len_trait::Len; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::ops::Index; + +use crate::ga::individual::{Chromosome, IndividualTrait}; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 + pub(super) 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) + } + + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + 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) + } +} + +impl CrossoverOperator for Pmx +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/ppx.rs b/src/ga/operators/crossover/impls/ppx.rs new file mode 100644 index 0000000..8e6c47e --- /dev/null +++ b/src/ga/operators/crossover/impls/ppx.rs @@ -0,0 +1,182 @@ +use itertools::Itertools; +use len_trait::Len; +use std::collections::HashSet; +use std::hash::Hash; +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 + pub(super) 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) + } + + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + { + 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) + } +} + +impl CrossoverOperator for Ppx +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy + Eq + Hash, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/shuffle.rs b/src/ga/operators/crossover/impls/shuffle.rs new file mode 100644 index 0000000..658d958 --- /dev/null +++ b/src/ga/operators/crossover/impls/shuffle.rs @@ -0,0 +1,127 @@ +use itertools::{enumerate, Itertools}; +use len_trait::Len; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; +use rand::prelude::SliceRandom; +use rand::{rngs::ThreadRng, Rng}; +use std::ops::Index; + +use super::CrossoverOperator; + +/// # 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 Shuffle { + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for Shuffle +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/single_point.rs b/src/ga/operators/crossover/impls/single_point.rs new file mode 100644 index 0000000..dbcf730 --- /dev/null +++ b/src/ga/operators/crossover/impls/single_point.rs @@ -0,0 +1,116 @@ +use len_trait::Len; +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +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 SinglePoint { + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for SinglePoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/two_point.rs b/src/ga/operators/crossover/impls/two_point.rs new file mode 100644 index 0000000..d486d77 --- /dev/null +++ b/src/ga/operators/crossover/impls/two_point.rs @@ -0,0 +1,138 @@ +use len_trait::Len; + +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 TwoPoint { + /// 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for TwoPoint +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/uniform.rs b/src/ga/operators/crossover/impls/uniform.rs new file mode 100644 index 0000000..6e060d5 --- /dev/null +++ b/src/ga/operators/crossover/impls/uniform.rs @@ -0,0 +1,122 @@ +use len_trait::Len; + +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 Uniform +where + 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for Uniform +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng + Clone, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/crossover/impls/uniform_parameterized.rs b/src/ga/operators/crossover/impls/uniform_parameterized.rs new file mode 100644 index 0000000..065404e --- /dev/null +++ b/src/ga/operators/crossover/impls/uniform_parameterized.rs @@ -0,0 +1,124 @@ +use len_trait::Len; + +use std::ops::Index; + +use crate::ga::individual::IndividualTrait; +use crate::ga::GAMetadata; +use push_trait::{Nothing, Push}; + +use rand::{rngs::ThreadRng, Rng}; + +use super::CrossoverOperator; + +/// # 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 UniformParameterized +where + 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_single( + &mut self, + _metadata: &GAMetadata, + parent_1: &IndividualT, + parent_2: &IndividualT, + ) -> (IndividualT, IndividualT) + where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + { + 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)) + } +} + +impl CrossoverOperator for UniformParameterized +where + IndividualT: IndividualTrait, + IndividualT::ChromosomeT: Index + Push, + GeneT: Copy, + R: Rng + Clone, +{ + /// Returns vector of owned individuals which were created in result of applying crossover + /// operator. + /// + /// 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 + /// + /// * `metadata` - algorithm state metadata, see the structure details for more info, + /// * `selected` - references to individuals selected during selection step. + fn apply(&mut self, metadata: &GAMetadata, selected: &[&IndividualT]) -> Vec { + assert!(selected.len() & 1 == 0); + + let mut output = Vec::with_capacity(selected.len()); + + for parents in selected.chunks(2) { + let (child_1, child_2) = self.apply_single(metadata, parents[0], parents[1]); + output.push(child_1); + output.push(child_2); + } + + output + } +} diff --git a/src/ga/operators/selection/impls.rs b/src/ga/operators/selection/impls.rs index cee770a..84fe5ed 100644 --- a/src/ga/operators/selection/impls.rs +++ b/src/ga/operators/selection/impls.rs @@ -353,8 +353,7 @@ impl SelectionOperator for To count: usize, ) -> Vec<&'a IndividualT> { let tournament_size = (population.len() as f64 * self.size_factor) as usize; - - assert!(tournament_size > 0); + let tournament_size = tournament_size.max(1); let mut selected: Vec<&IndividualT> = Vec::with_capacity(count); diff --git a/tests/crossover_tests.rs b/tests/crossover_tests.rs index dad43ca..f64e384 100644 --- a/tests/crossover_tests.rs +++ b/tests/crossover_tests.rs @@ -14,7 +14,9 @@ fn operator_takes_values_from_parents> let parents = RandomPoints::new(30).generate(2); assert_eq!(parents.len(), 2, "Expected population of size 2"); - let (child_1, child_2) = operator.apply(&GAMetadata::default(), &parents[0], &parents[1]); + let children = operator.apply(&GAMetadata::default(), &[&parents[0], &parents[1]]); + let child_1 = &children[0]; + let child_2 = &children[1]; for (i, (gene_1, gene_2)) in std::iter::zip(child_1.chromosome(), child_2.chromosome()).enumerate() { assert!(parents[0].chromosome()[i] == *gene_1 || parents[1].chromosome()[i] == *gene_1); assert!(parents[0].chromosome()[i] == *gene_2 || parents[1].chromosome()[i] == *gene_2); @@ -50,7 +52,9 @@ fn ppx_test() { let p1 = Individual::from((0..10).collect_vec()); let p2 = Individual::from((0..10).rev().collect_vec()); - let (c1, c2) = op.apply(&GAMetadata::default(), &p1, &p2); + let children = op.apply(&GAMetadata::default(), &[&p1, &p2]); + let c1 = &children[0]; + let c2 = &children[1]; c1.chromosome .iter()