From 37fcb3b4383c3b8093211e7599f14c235d43b74e Mon Sep 17 00:00:00 2001 From: Anders Olsson Date: Tue, 26 Oct 2021 21:28:18 +0200 Subject: [PATCH] Did things --- Cargo.toml | 13 +-- benches/parabole.rs | 138 ------------------------ examples/parabole.rs | 126 ---------------------- src/lib.rs | 251 +++++++++++++++++++------------------------ 4 files changed, 110 insertions(+), 418 deletions(-) delete mode 100644 benches/parabole.rs delete mode 100644 examples/parabole.rs diff --git a/Cargo.toml b/Cargo.toml index 30873da..2081ffb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,7 @@ authors = ["logaritmisk "] edition = "2018" [dependencies] -rayon = "1.0" - -[[example]] -name = "parabole" -path = "examples/parabole.rs" - -[dev-dependencies] -criterion = "0.2" rand = "0.6" -[[bench]] -name = "parabole" -harness = false +[dev-dependencies] +approx = "0.3" diff --git a/benches/parabole.rs b/benches/parabole.rs deleted file mode 100644 index 93e2ac2..0000000 --- a/benches/parabole.rs +++ /dev/null @@ -1,138 +0,0 @@ -#[macro_use] -extern crate criterion; -extern crate genetisk; -extern crate rand; -extern crate rayon; - -use std::cmp::Ordering; - -use criterion::Criterion; -use rand::distributions::{IndependentSample, Range}; -use rayon::prelude::*; - -use genetisk::{Individual, Simulation, Wrapper}; - -#[derive(Clone, Copy, Debug)] -struct Fitness(f64); - -impl PartialEq for Fitness { - fn eq(&self, other: &Fitness) -> bool { - (self.0 - other.0).abs() < 0.0001 - } -} - -impl Eq for Fitness {} - -impl PartialOrd for Fitness { - fn partial_cmp(&self, other: &Fitness) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for Fitness { - fn cmp(&self, other: &Fitness) -> Ordering { - self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal) - } -} - -#[derive(Clone, Debug)] -struct Parabole { - x: f64, -} - -impl Individual for Parabole { - type Fitness = Fitness; - - fn mate(&self, other: &Parabole) -> Parabole { - Parabole { - x: (self.x + other.x) / 2.0, - } - } - - fn mutate(&mut self) { - let mut rng = rand::weak_rng(); - - let between = Range::new(-1.0, 1.0); - let offset = between.ind_sample(&mut rng); - - self.x += offset; - } - - fn fitness(&self) -> Self::Fitness { - Fitness(10.0 - ((self.x + 3.0) * (self.x + 3.0))) - } -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("calculate", move |b| { - let initial = (0..1000) - .map(|i| Parabole { x: f64::from(i) }) - .collect::>(); - - let mut simulation = Simulation::with_population(initial); - - b.iter(|| { - simulation.calculate(); - }); - }); - - c.bench_function("evolve", move |b| { - let initial = (0..1000) - .map(|i| Parabole { x: f64::from(i) }) - .collect::>(); - - let population_size = initial.len(); - - let mut simulation = Simulation::with_population(initial); - - b.iter(|| { - simulation.evolve(|parents, population| { - // Mate top 10 to get 5 children. - parents - .iter() - .enumerate() - .filter(|&(n, _)| n % 2 == 0) - .map(|(_, wrapper)| wrapper) - .take(5) - .zip( - parents - .iter() - .enumerate() - .filter(|&(n, _)| n % 2 == 1) - .map(|(_, wrapper)| wrapper) - .take(5), - ) - .map(|(a, b)| a.individual.mate(&b.individual)) - .map(|individual| Wrapper { - individual, - fitness: None, - }) - .for_each(|wrapper| { - population.push(wrapper); - }); - - // Mutate all to get new children. - parents - .par_iter() - .map(|wrapper| wrapper.individual.clone()) - .map(|mut individual| { - individual.mutate(); - - Wrapper { - individual, - fitness: None, - } - }) - .collect_into_vec(population); - - // Add all parents again. - population.extend(parents.iter().cloned()); - }); - - simulation.population.truncate(population_size); - }); - }); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/examples/parabole.rs b/examples/parabole.rs deleted file mode 100644 index e6f5b56..0000000 --- a/examples/parabole.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::cmp::Ordering; - -use genetisk::{Individual, Simulation, Wrapper}; -use rand::distributions::{IndependentSample, Range}; -use rayon::prelude::*; - -#[derive(Clone, Copy, Debug)] -struct Fitness(f64); - -impl PartialEq for Fitness { - fn eq(&self, other: &Fitness) -> bool { - (self.0 - other.0).abs() < 0.0001 - } -} - -impl Eq for Fitness {} - -impl PartialOrd for Fitness { - fn partial_cmp(&self, other: &Fitness) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for Fitness { - fn cmp(&self, other: &Fitness) -> Ordering { - self.0.partial_cmp(&other.0).unwrap_or(Ordering::Equal) - } -} - -#[derive(Clone, Debug)] -struct Parabole { - x: f64, -} - -impl Individual for Parabole { - type Fitness = Fitness; - - fn mate(&self, other: &Parabole) -> Parabole { - Parabole { - x: (self.x + other.x) / 2.0, - } - } - - fn mutate(&mut self) { - let mut rng = rand::weak_rng(); - - let between = Range::new(-1.0, 1.0); - let offset = between.ind_sample(&mut rng); - - self.x += offset; - } - - fn fitness(&self) -> Self::Fitness { - Fitness(10.0 - ((self.x + 3.0) * (self.x + 3.0))) - } -} - -fn main() { - let initial = (0..300) - .map(|i| Parabole { x: i as f64 }) - .collect::>(); - - let population_size = initial.len(); - - let mut simulation = Simulation::with_population(initial); - - simulation.calculate(); - - for _ in 0..250_000 { - simulation.evolve(|parents, population| { - // Mate top 10 to get 5 children. - parents - .iter() - .enumerate() - .filter(|&(n, _)| n % 2 == 0) - .map(|(_, wrapper)| wrapper) - .take(5) - .zip( - parents - .iter() - .enumerate() - .filter(|&(n, _)| n % 2 == 1) - .map(|(_, wrapper)| wrapper) - .take(5), - ) - .map(|(a, b)| a.individual.mate(&b.individual)) - .map(|individual| Wrapper { - individual, - fitness: None, - }) - .for_each(|wrapper| { - population.push(wrapper); - }); - - // Mutate all to get new children. - parents - .par_iter() - .map(|wrapper| wrapper.individual.clone()) - .map(|mut individual| { - individual.mutate(); - - Wrapper { - individual, - fitness: None, - } - }) - .collect_into_vec(population); - - // Add all parents again. - population.extend(parents.iter().cloned()); - }); - - simulation.population.truncate(population_size); - } - - println!( - "{:#?}", - simulation - .population - .iter() - .take(10) - .map(|individual| &individual.individual) - .collect::>() - ); - println!(); -} diff --git a/src/lib.rs b/src/lib.rs index 102fce2..30896c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,170 +1,135 @@ -use rayon::prelude::*; +// https://en.wikipedia.org/wiki/Test_functions_for_optimization -pub trait Individual: Send { - type Fitness: Send + Ord; - - fn fitness(&self) -> Self::Fitness; - - fn mate(&self, other: &Self) -> Self; - fn mutate(&mut self); +/* +pub trait Genotype { + // } -pub trait Select: Send -where - T: Individual, -{ - fn select(&self, population: Vec>) -> Vec>; +pub trait Phenotype { + // } +*/ -pub struct MaximizeSelector { - count: usize, -} +pub mod crossover { + use std::cmp; -impl MaximizeSelector { - pub fn new(count: usize) -> Self { - MaximizeSelector { count } + use rand::distributions::Uniform; + use rand::Rng; + + pub fn single_point(rng: &mut R, left: &[T], right: &[T]) -> (Vec, Vec) + where + T: Clone, + { + let min_len = cmp::min(left.len(), right.len()); + let cross_point = rng.sample(Uniform::from(1..min_len)); + + let first = left[..cross_point] + .iter() + .chain(right[cross_point..].iter()) + .cloned() + .collect::>(); + + let second = right[..cross_point] + .iter() + .chain(left[cross_point..].iter()) + .cloned() + .collect::>(); + + (first, second) } } -impl Select for MaximizeSelector -where - T: Individual, -{ - fn select(&self, mut population: Vec>) -> Vec> { - population.sort_unstable_by(|a, b| b.fitness.cmp(&a.fitness)); +#[cfg(test)] +mod tests { + use std::cmp::Ordering; + use std::f64; - population.into_iter().take(self.count).collect() - } -} + use super::*; -pub struct MinimizeSelector { - count: usize, -} + // use approx::assert_relative_eq; + use rand::distributions::{Standard, Uniform}; + use rand::{thread_rng, Rng}; -impl MinimizeSelector { - pub fn new(count: usize) -> Self { - MinimizeSelector { count } - } -} + #[test] + fn single_objective_sphere() { + let mut rng = thread_rng(); -impl Select for MinimizeSelector -where - T: Individual, -{ - fn select(&self, mut population: Vec>) -> Vec> { - population.sort_unstable_by(|a, b| a.fitness.cmp(&b.fitness)); + fn evaluate(x: &[f64]) -> f64 { + x.iter().map(|x| x.powi(2)).sum() + } - population.into_iter().take(self.count).collect() - } -} + fn generate(rng: &mut R) -> Vec { + let between = Uniform::new_inclusive(-10.0, 10.0); -#[derive(Clone)] -pub struct Wrapper -where - T: Individual, -{ - pub individual: T, - pub fitness: Option, -} + (0..10).map(|_| rng.sample(between)).collect::>() + } -pub struct Simulation -where - T: Individual, -{ - pub population: Vec>, -} + fn mutate(rng: &mut R, individual: &mut Vec) { + let idx = rng.gen_range(0, individual.len()); -impl Simulation -where - T: Individual, -{ - pub fn with_population(initial: Vec) -> Self { - let mut population = Vec::with_capacity(initial.len() * 2); + individual[idx] += rng.sample(Uniform::new_inclusive(-1.0, 1.0)); + } - initial - .into_iter() - .map(|individual| Wrapper { - individual, - fitness: None, - }) - .for_each(|wrapper| { - population.push(wrapper); + // step 1 (generate) + let mut population = (0..100) + .map(|_| (generate(&mut rng), f64::MAX)) + .collect::>(); + + for _ in 0..1000 { + // step 2 (evaluate) + population + .iter_mut() + .for_each(|(individual, fitness)| *fitness = evaluate(&individual)); + + population + .sort_unstable_by(|(_, a), (_, b)| a.partial_cmp(&b).unwrap_or(Ordering::Equal)); + + // step 3 (select) + let parents = population + .iter() + .step_by(2) + .zip(population.iter().skip(1).step_by(2)) + .take(5) + .map(|((a, _), (b, _))| (a, b)) + .collect::>(); + + // step 4 (crossover) + let mut children = Vec::with_capacity(parents.len() * 2); + + parents + .into_iter() + .map(|(left, right)| crossover::single_point(&mut rng, left, right)) + .for_each(|(first, second)| { + children.push((first, f64::MAX)); + children.push((second, f64::MAX)); + }); + + // step 5 (survivial pressure) + population.truncate(population.len() - children.len()); + + // step 6 (mutate) + population.iter_mut().for_each(|(individual, fitness)| { + let random: f64 = rng.sample(Standard); + + if random < 0.3 { + mutate(&mut rng, individual); + + *fitness = f64::MAX; + } }); - Simulation { population } - } + // step 7 (add children) + population.extend_from_slice(&children); + } - #[inline] - pub fn calculate(&mut self) { - self.population + population .iter_mut() - .filter(|wrapper| wrapper.fitness.is_none()) - .for_each(|wrapper| wrapper.fitness = Some(wrapper.individual.fitness())); + .for_each(|(individual, fitness)| *fitness = evaluate(&individual)); - self.population - .sort_unstable_by(|a, b| b.fitness.cmp(&a.fitness)); - } + population.sort_unstable_by(|(_, a), (_, b)| a.partial_cmp(&b).unwrap_or(Ordering::Equal)); - pub fn evolve(&mut self, func: F) - where - F: Fn(&[Wrapper], &mut Vec>), - { - let mut population = Vec::with_capacity(self.population.len()); + println!("{:#?}", population); - func(&self.population, &mut population); - - self.population = population; - - self.calculate(); - } -} - -pub struct ParSimulation -where - T: Individual, -{ - pub population: Vec>, -} - -impl ParSimulation -where - T: Individual, -{ - pub fn with_population(initial: Vec) -> Self { - let mut population = Vec::with_capacity(initial.len() * 2); - - initial - .into_par_iter() - .map(|individual| Wrapper { - individual, - fitness: None, - }) - .collect_into_vec(&mut population); - - ParSimulation { population } - } - - #[inline] - pub fn calculate(&mut self) { - self.population - .par_iter_mut() - .filter(|wrapper| wrapper.fitness.is_none()) - .for_each(|wrapper| wrapper.fitness = Some(wrapper.individual.fitness())); - - self.population - .par_sort_unstable_by(|a, b| b.fitness.cmp(&a.fitness)); - } - - pub fn evolve(&mut self, func: F) - where - F: Fn(&[Wrapper], &mut Vec>), - { - let mut population = Vec::with_capacity(self.population.len()); - - func(&self.population, &mut population); - - self.population = population; - - self.calculate(); + assert!(false); } }