diff --git a/src/gp/args.rs b/src/gp/args.rs index 3943c15..9ad402a 100644 --- a/src/gp/args.rs +++ b/src/gp/args.rs @@ -12,10 +12,10 @@ pub enum ClosingType { #[allow(dead_code)] struct PushArgs { - alignment_deviation: usize, // For alternation, std dev of deviation of index when alternating - alternation_rate: Decimal, // For alternation, prob of switching parents at each location - closes: ClosingType, // How push should automatically place Gene::Close into a plushy - dont_end: bool, // If true, keep running until limit regardless of success + alignment_deviation: Decimal, // For alternation, std dev of deviation of index when alternating + alternation_rate: usize, // For alternation, prob of switching parents at each location. A number 0-100 + closes: ClosingType, // How push should automatically place Gene::Close into a plushy + dont_end: bool, // If true, keep running until limit regardless of success // downsample: bool, // Whether or not to downsample. TODO later with all the related args elitism: bool, // Whether to always add the best individual to next generation error_function: fn(&PushArgs, DataFrame, Vec) -> Series, // The error function diff --git a/src/gp/mod.rs b/src/gp/mod.rs index 2939b9e..54a0b8c 100644 --- a/src/gp/mod.rs +++ b/src/gp/mod.rs @@ -1,5 +1,6 @@ pub mod args; pub mod genome; +pub mod utils; pub mod variation; // pub fn gp_loop diff --git a/src/gp/utils.rs b/src/gp/utils.rs new file mode 100644 index 0000000..3b609eb --- /dev/null +++ b/src/gp/utils.rs @@ -0,0 +1,18 @@ +use rand::Rng; +use rust_decimal::prelude::*; + +pub fn gaussian_noise_factor(rng: &mut impl Rng) -> Decimal { + let u0f64: f64 = rng.random(); + let u1f64: f64 = rng.random(); + + let u0: Decimal = FromPrimitive::from_f64(u0f64).unwrap(); + let u1: Decimal = FromPrimitive::from_f64(u1f64).unwrap(); + + let u0 = if u0 == dec!(0.0) { + FromPrimitive::from_f64(f64::EPSILON).unwrap() + } else { + u0 + }; + + (dec!(-2.0) * u0.ln()).sqrt().unwrap() * (dec!(2.0) * rust_decimal::Decimal::PI * u1).cos() +} diff --git a/src/gp/variation.rs b/src/gp/variation.rs index 22a49a7..fa3392e 100644 --- a/src/gp/variation.rs +++ b/src/gp/variation.rs @@ -1,3 +1,10 @@ +use crate::gp::utils::gaussian_noise_factor; +use crate::push::state::Gene; +use rand::Rng; +use rust_decimal::Decimal; +use rust_decimal::prelude::ToPrimitive; +use std::iter::zip; + pub enum Variation { Crossover, Alternation, @@ -6,3 +13,239 @@ pub enum Variation { UniformReplacement, UniformDeletion, } + +fn is_crossover_padding(gene: &Gene) -> bool { + match gene { + Gene::CrossoverPadding => true, + _ => false, + } +} + +fn crossover(plushy0: Vec, plushy1: Vec, mut rng: impl Rng) -> Vec { + let mut shorter: Vec; + let longer: Vec; + let mut new_plushy: Vec = vec![]; + + if plushy0.len() >= plushy1.len() { + shorter = plushy1; + longer = plushy0; + } else { + shorter = plushy0; + longer = plushy1; + } + + for _ in 0..(longer.len() - shorter.len()) { + shorter.push(Gene::CrossoverPadding) + } + + // Add genes here + for (sgene, lgene) in zip(shorter, longer) { + if rng.random_range(0..=99) < 50 { + new_plushy.push(sgene) + } else { + new_plushy.push(lgene) + } + } + + new_plushy + .into_iter() + .filter(|gene| !is_crossover_padding(gene)) + .collect() +} + +fn tail_aligned_crossover(plushy0: Vec, plushy1: Vec, mut rng: impl Rng) -> Vec { + let mut shorter: Vec; + let longer: Vec; + let mut new_plushy: Vec = vec![]; + + if plushy0.len() >= plushy1.len() { + shorter = plushy1; + longer = plushy0; + } else { + shorter = plushy0; + longer = plushy1; + } + + for _ in 0..(longer.len() - shorter.len()) { + shorter.insert(0, Gene::CrossoverPadding) + } + + // Add genes here + for (sgene, lgene) in zip(shorter, longer) { + if rng.random_range(0..=99) < 50 { + new_plushy.push(sgene) + } else { + new_plushy.push(lgene) + } + } + + new_plushy + .into_iter() + .filter(|gene| !is_crossover_padding(gene)) + .collect() +} + +fn alternation( + plushy0: Vec, + plushy1: Vec, + alternation_rate: usize, + alignment_deviation: Decimal, + mut rng: impl Rng, +) -> Vec { + let mut use_plushy0: bool = true; + let mut result_plushy: Vec = vec![]; + let mut iteration_budget = plushy0.len() + plushy1.len(); + let mut n: i128 = 0; + loop { + if use_plushy0 { + if n >= plushy0.len() as i128 { + return result_plushy; + } + } else { + if n >= plushy1.len() as i128 { + return result_plushy; + } + } + if iteration_budget <= 0 { + return result_plushy; + } + if rng.random_range(0..=99) < alternation_rate { + let pre_usize = alignment_deviation * gaussian_noise_factor(&mut rng); + n = 0i128.max(n + ToPrimitive::to_i128(&(pre_usize).round()).unwrap()); + use_plushy0 = !use_plushy0; + } else { + result_plushy.push(if use_plushy0 { + plushy0[n as usize].clone() + } else { + plushy1[n as usize].clone() + }); + n += 1; + } + iteration_budget -= 1; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::instructions::common::*; + use crate::instructions::numeric::*; + use crate::push::state::Gene; + use rand::SeedableRng; + use rand::rngs::StdRng; + use rust_decimal::dec; + + #[test] + fn crossover_test() { + let rng = StdRng::seed_from_u64(42); + let plushy0 = vec![ + Gene::StateFunc(exec_swap), + Gene::StateFunc(float_tan), + Gene::StateFunc(int_pop), + Gene::Close, + Gene::StateFunc(exec_flush), + Gene::Close, + Gene::StateFunc(boolean_pop), + Gene::StateFunc(vector_int_swap), + Gene::StateFunc(vector_char_pop), + ]; + let plushy1 = vec![ + Gene::StateFunc(string_swap), + Gene::StateFunc(float_arctan), + Gene::StateFunc(char_pop), + Gene::GeneChar('a'), + Gene::StateFunc(code_flush), + Gene::GeneInt(1), + Gene::StateFunc(float_pop), + ]; + let res_plushy = crossover(plushy0, plushy1, rng); + assert_eq!( + vec![ + Gene::StateFunc(string_swap), + Gene::StateFunc(float_tan), + Gene::StateFunc(char_pop), + Gene::Close, + Gene::StateFunc(exec_flush), + Gene::Close, + Gene::StateFunc(boolean_pop), + Gene::StateFunc(vector_char_pop), + ], + res_plushy + ) + } + + #[test] + fn tail_aligned_crossover_test() { + let rng = StdRng::seed_from_u64(42); + let plushy0 = vec![ + Gene::StateFunc(exec_swap), + Gene::StateFunc(float_tan), + Gene::StateFunc(int_pop), + Gene::Close, + Gene::StateFunc(exec_flush), + Gene::Close, + Gene::StateFunc(boolean_pop), + Gene::StateFunc(vector_int_swap), + Gene::StateFunc(vector_char_pop), + ]; + let plushy1 = vec![ + Gene::StateFunc(string_swap), + Gene::StateFunc(float_arctan), + Gene::StateFunc(char_pop), + Gene::GeneChar('a'), + Gene::StateFunc(code_flush), + Gene::GeneInt(1), + Gene::StateFunc(float_pop), + ]; + let res_plushy = tail_aligned_crossover(plushy0, plushy1, rng); + assert_eq!( + vec![ + Gene::StateFunc(float_tan), + Gene::StateFunc(string_swap), + Gene::Close, + Gene::StateFunc(exec_flush), + Gene::Close, + Gene::StateFunc(boolean_pop), + Gene::GeneInt(1), + Gene::StateFunc(vector_char_pop), + ], + res_plushy + ) + } + + #[test] + fn alternation_test() { + let rng = StdRng::seed_from_u64(42); + let plushy0 = vec![ + Gene::StateFunc(exec_swap), + Gene::StateFunc(float_tan), + Gene::StateFunc(int_pop), + Gene::Close, + Gene::StateFunc(exec_flush), + Gene::Close, + Gene::StateFunc(boolean_pop), + Gene::StateFunc(vector_int_swap), + Gene::StateFunc(vector_char_pop), + ]; + let plushy1 = vec![ + Gene::StateFunc(string_swap), + Gene::StateFunc(float_arctan), + Gene::StateFunc(char_pop), + Gene::GeneChar('a'), + Gene::StateFunc(code_flush), + Gene::GeneInt(1), + Gene::StateFunc(float_pop), + ]; + let res_plushy = alternation(plushy0, plushy1, 50, dec!(2.0), rng); + assert_eq!( + vec![ + Gene::StateFunc(char_pop), + Gene::GeneChar('a'), + Gene::StateFunc(boolean_pop), + Gene::StateFunc(vector_int_swap), + Gene::StateFunc(vector_char_pop), + ], + res_plushy + ); + } +}