From be38bf5bd76e4bf63b784bfff673a734f8904374 Mon Sep 17 00:00:00 2001 From: logaritmisk Date: Tue, 19 Dec 2017 09:45:31 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 3 + .gitlab-ci.yml | 14 +++++ Cargo.toml | 17 ++++++ examples/parabole.rs | 134 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 130 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 Cargo.toml create mode 100644 examples/parabole.rs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e13de17 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target/ + +Cargo.lock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..e92d30c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,14 @@ +image: "rust:latest" + +variables: + CARGO_HOME: $CI_PROJECT_DIR/cargo + +test:cargo: + script: + - rustc --version && cargo --version + - time cargo test --verbose --jobs 1 --release + + cache: + paths: + - target/ + - cargo/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..64d6715 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "genetisk" +version = "0.1.0" +authors = ["logaritmisk "] + +[dependencies] +rayon = "0.9" + +[[example]] +name = "parabole" +path = "examples/parabole.rs" + +[dev-dependencies] +rand = "0.3" + +[profile.release] +lto = true diff --git a/examples/parabole.rs b/examples/parabole.rs new file mode 100644 index 0000000..bf718d2 --- /dev/null +++ b/examples/parabole.rs @@ -0,0 +1,134 @@ +extern crate rand; +extern crate rayon; +extern crate genetisk; + +use std::cmp::Ordering; + + +use rand::distributions::{IndependentSample, Range}; +use rayon::prelude::*; + +use genetisk::{Individual, Wrapper, MaximizeSelector, MinimizeSelector, Select, Simulation}; + + +#[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 between = Range::new(-1.0, 1.0); + + let mut rng = rand::thread_rng(); + 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: 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: individual, + fitness: None, + } + }) + .collect_into(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 new file mode 100644 index 0000000..8d40f13 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,130 @@ +extern crate rayon; + + +use rayon::prelude::*; + + +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 Select: Send +where + T: Individual +{ + fn select(&self, population: Vec>) -> Vec>; +} + + +pub struct MaximizeSelector { + count: usize, +} + +impl MaximizeSelector { + pub fn new(count: usize) -> Self { + MaximizeSelector { + count: count, + } + } +} + +impl Select for MaximizeSelector +where + T: Individual +{ + fn select(&self, mut population: Vec>) -> Vec> { + population.par_sort_unstable_by(|a, b| b.fitness.cmp(&a.fitness)); + + population.into_iter().take(self.count).collect() + } +} + + +pub struct MinimizeSelector { + count: usize, +} + +impl MinimizeSelector { + pub fn new(count: usize) -> Self { + MinimizeSelector { + count: count, + } + } +} + +impl Select for MinimizeSelector +where + T: Individual +{ + fn select(&self, mut population: Vec>) -> Vec> { + population.par_sort_unstable_by(|a, b| a.fitness.cmp(&b.fitness)); + + population.into_iter().take(self.count).collect() + } +} + + +#[derive(Clone)] +pub struct Wrapper +where + T: Individual, +{ + pub individual: T, + pub fitness: Option, +} + + +pub struct Simulation +where + T: Individual, +{ + pub population: Vec>, +} + +impl Simulation +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: individual, + fitness: None, + }) + .collect_into(&mut population); + + Simulation { + population: population, + } + } + + 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(); + } +}