From 3e09533161d615961cfbfcd548e9b9b6b77bb7f3 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:41:03 +0000 Subject: [PATCH 1/9] start implementing rand::Rng to randomization-based functions --- Cargo.toml | 6 +++--- src/builtin.rs | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 08a72ca..7a6e1bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,15 @@ categories = ["algorithms", "science", "simulation"] [features] default = ["builtin", "genrand"] builtin = [] -crossover = ["dep:rand"] -genrand = ["dep:rand"] +crossover = [] +genrand = [] rayon = ["dep:rayon"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] replace_with = "0.1.7" -rand = { version = "0.8.5", optional = true } +rand = "0.8.5" rayon = { version = "1.8.0", optional = true } [dev-dependencies] diff --git a/src/builtin.rs b/src/builtin.rs index 6096dc5..ba9113c 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,21 +1,21 @@ /// Used in all of the builtin [next_gen]s to randomly mutate entities a given amount pub trait RandomlyMutable { /// Mutate the entity with a given mutation rate (0..1) - fn mutate(&mut self, rate: f32); + fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng); } /// Used in dividually-reproducing [next_gen]s pub trait DivisionReproduction: RandomlyMutable { /// Create a new child with mutation. Similar to [RandomlyMutable::mutate], but returns a new instance instead of modifying the original. /// If it is simply returning a cloned and mutated version, consider using a constant mutation rate. - fn spawn_child(&self) -> Self; + fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self; } /// Used in crossover-reproducing [next_gen]s #[cfg(feature = "crossover")] pub trait CrossoverReproduction: RandomlyMutable { /// Use crossover reproduction to create a new entity. - fn spawn_child(&self, other: &Self) -> Self; + fn spawn_child(&self, other: &Self, rng: &mut impl Rng) -> Self; } /// Used in pruning [next_gen]s @@ -29,7 +29,6 @@ pub trait Prunable: Sized { pub mod next_gen { use super::*; - #[cfg(feature = "crossover")] use rand::prelude::*; #[cfg(feature = "rayon")] use rayon::prelude::*; /// When making a new generation, it mutates each entity a certain amount depending on their reward. @@ -39,12 +38,13 @@ pub mod next_gen { rewards.sort_by(|(_, r1), (_, r2)| r1.partial_cmp(r2).unwrap()); let len = rewards.len() as f32; + let mut rng = rand::thread_rng(); rewards .into_iter() .enumerate() .map(|(i, (mut e, _))| { - e.mutate(i as f32 / len); + e.mutate(i as f32 / len, &mut rng); e }) .collect() @@ -55,12 +55,13 @@ pub mod next_gen { rewards.sort_by(|(_, r1), (_, r2)| r1.partial_cmp(r2).unwrap()); let len = rewards.len() as f32; + let mut rng = rand::thread_rng(); rewards .into_par_iter() .enumerate() .map(|(i, (mut e, _))| { - e.mutate(i as f32 / len); + e.mutate(i as f32 / len, &mut rng); e }) .collect() @@ -72,6 +73,8 @@ pub mod next_gen { let population_size = rewards.len(); let mut next_gen = pruning_helper(rewards); + let mut rng = rand::thread_rng(); + let mut og_champions = next_gen .clone() // TODO remove if possible. currently doing so because `next_gen` is borrowed as mutable later .into_iter() @@ -80,7 +83,7 @@ pub mod next_gen { while next_gen.len() < population_size { let e = og_champions.next().unwrap(); - next_gen.push(e.spawn_child()); + next_gen.push(e.spawn_child(&mut rng)); } next_gen @@ -105,13 +108,13 @@ pub mod next_gen { while next_gen.len() < population_size { let e1 = og_champs_cycle.next().unwrap(); - let e2 = og_champions[rand::gen::(0..og_champions.len()-1)]; + let e2 = og_champions[rng.gen::(0..og_champions.len()-1)]; if !S && e1 == e2 { continue; } - next_gen.push(e1.spawn_child(&e2)); + next_gen.push(e1.spawn_child(&e2, &mut rng)); } next_gen From 3a182426a4fe15c30413ae6cb3b18d9ddf0e7523 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:31:38 +0000 Subject: [PATCH 2/9] update docs to use rng --- README.md | 8 ++++---- src/lib.rs | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 541f49f..8a0c4a2 100644 --- a/README.md +++ b/README.md @@ -14,16 +14,16 @@ struct MyEntity { // required in all of the builtin functions as requirements of `DivsionReproduction` and `CrossoverReproduction` impl RandomlyMutable for MyEntity { - fn mutate(&mut self, rate: f32) { - self.field1 += fastrand::f32() * rate; + fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) { + self.field1 += rng.gen::() * rate; } } // required for `asexual_pruning_nextgen`. impl DivsionReproduction for MyEntity { - fn spawn_child(&self) -> Self { + fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { let mut child = self.clone(); - child.mutate(0.25); // use a constant mutation rate when spawning children in pruning algorithms. + child.mutate(0.25, rng); // use a constant mutation rate when spawning children in pruning algorithms. child } } diff --git a/src/lib.rs b/src/lib.rs index 4414daf..ce4791c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,16 +15,16 @@ //! //! // required in all of the builtin functions as requirements of `DivisionReproduction` and `CrossoverReproduction` //! impl RandomlyMutable for MyEntity { -//! fn mutate(&mut self, rate: f32) { -//! self.field1 += fastrand::f32() * rate; +//! fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) { +//! self.field1 += rng.gen::() * rate; //! } //! } //! //! // required for `division_pruning_nextgen`. //! impl DivisionReproduction for MyEntity { -//! fn spawn_child(&self) -> Self { +//! fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { //! let mut child = self.clone(); -//! child.mutate(0.25); // use a constant mutation rate when spawning children in pruning algorithms. +//! child.mutate(0.25, rng); // use a constant mutation rate when spawning children in pruning algorithms. //! child //! } //! } @@ -105,16 +105,16 @@ pub mod prelude; /// } /// /// impl RandomlyMutable for MyEntity { -/// fn mutate(&mut self, rate: f32) { -/// self.a += fastrand::f32() * rate; -/// self.b += fastrand::f32() * rate; +/// fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) { +/// self.a += rng.gen::() * rate; +/// self.b += rng.gen::() * rate; /// } /// } /// /// impl DivisionReproduction for MyEntity { -/// fn spawn_child(&self) -> Self { +/// fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { /// let mut child = self.clone(); -/// child.mutate(0.25); // you'll generally want to use a constant mutation rate for mutating children. +/// child.mutate(0.25, rng); // you'll generally want to use a constant mutation rate for mutating children. /// child /// } /// } From 87371f28a1f98c4f6d9804f9b397daa65dc09528 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:33:20 +0000 Subject: [PATCH 3/9] update test & examples to use rng --- Cargo.lock | 7 ------- Cargo.toml | 3 --- examples/readme_ex.rs | 8 ++++---- src/lib.rs | 8 ++++---- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e33780e..86697ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,17 +39,10 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - [[package]] name = "genetic-rs" version = "0.1.1" dependencies = [ - "fastrand", "rand", "rayon", "replace_with", diff --git a/Cargo.toml b/Cargo.toml index 7a6e1bf..8f67f52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,3 @@ rayon = ["dep:rayon"] replace_with = "0.1.7" rand = "0.8.5" rayon = { version = "1.8.0", optional = true } - -[dev-dependencies] -fastrand = "2.0.1" diff --git a/examples/readme_ex.rs b/examples/readme_ex.rs index e148825..1745139 100644 --- a/examples/readme_ex.rs +++ b/examples/readme_ex.rs @@ -10,16 +10,16 @@ struct MyEntity { // required in all of the builtin functions as requirements of `DivisionReproduction` and `CrossoverReproduction`. impl RandomlyMutable for MyEntity { - fn mutate(&mut self, rate: f32) { - self.field1 += fastrand::f32() * rate; + fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) { + self.field1 += rng.gen::() * rate; } } // required for `division_pruning_nextgen`. impl DivisionReproduction for MyEntity { - fn spawn_child(&self) -> Self { + fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { let mut child = self.clone(); - child.mutate(0.25); // use a constant mutation rate when spawning children in pruning algorithms. + child.mutate(0.25, rng); // use a constant mutation rate when spawning children in pruning algorithms. child } } diff --git a/src/lib.rs b/src/lib.rs index ce4791c..4879f18 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -267,15 +267,15 @@ mod tests { struct MyEntity(f32); impl RandomlyMutable for MyEntity { - fn mutate(&mut self, rate: f32) { - self.0 += fastrand::f32() * rate; + fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) { + self.0 += rng.gen::() * rate; } } impl DivisionReproduction for MyEntity { - fn spawn_child(&self) -> Self { + fn spawn_child(&self, rng: &mut impl rand::Rng) -> Self { let mut child = self.clone(); - child.mutate(0.25); + child.mutate(0.25, rng); child } } From cc05b34097bb8d47191a657f1424c230c9cdc958 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Thu, 18 Jan 2024 14:34:13 +0000 Subject: [PATCH 4/9] update CI/CD to run on any branch --- .github/workflows/ci-cd.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index df9fafa..4f62fd7 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -4,7 +4,6 @@ on: push: branches: [main] pull_request: - branches: [main] jobs: test: From a89737ca5027fe6f8b2a73e1ddea010c37f87956 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:33:52 +0000 Subject: [PATCH 5/9] wrap fns in Arc instead of Box --- src/lib.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4879f18..88d0b2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,6 +84,7 @@ //! This project falls under the `MIT` license. use replace_with::replace_with_or_abort; +use std::sync::Arc; /// Built-in nextgen functions and traits to go with them. #[cfg(feature = "builtin")] pub mod builtin; @@ -157,8 +158,8 @@ where { /// The current population of entities pub entities: Vec, - fitness: Box f32>, - next_gen: Box) -> Vec>, + fitness: Arc f32>, + next_gen: Arc) -> Vec>, } impl GeneticSim @@ -174,8 +175,8 @@ where ) -> Self { Self { entities: starting_entities, - fitness: Box::new(fitness), - next_gen: Box::new(next_gen), + fitness: Arc::new(fitness), + next_gen: Arc::new(next_gen), } } @@ -315,7 +316,7 @@ mod tests { } #[test] - fn a_prune() { + fn d_prune() { let mut rng = rand::thread_rng(); let mut sim = GeneticSim::new( Vec::gen_random(&mut rng, 1000), @@ -329,4 +330,13 @@ mod tests { dbg!(sim.entities); } + + #[test] + fn send_sim() { + let sim = Arc::new(GeneticSim::new(vec![()], |_| 0., division_pruning_nextgen)); + + let thing = move || { + sim + }; + } } \ No newline at end of file From c8e59b763fcf8606373d81efc0b9268e1bf9a647 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Mon, 22 Jan 2024 12:35:58 +0000 Subject: [PATCH 6/9] fix test --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 88d0b2b..eba9854 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,9 +333,9 @@ mod tests { #[test] fn send_sim() { - let sim = Arc::new(GeneticSim::new(vec![()], |_| 0., division_pruning_nextgen)); + let sim = Arc::new(GeneticSim::new(vec![()], |_| 0., |_| vec![()])); - let thing = move || { + let _thing = move || { sim }; } From 5947a119522baad98e487acc16527a44133be2d6 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:27:51 +0000 Subject: [PATCH 7/9] update test to expose the real error --- src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index eba9854..4709c1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -333,10 +333,12 @@ mod tests { #[test] fn send_sim() { - let sim = Arc::new(GeneticSim::new(vec![()], |_| 0., |_| vec![()])); + let mut sim = Arc::new(GeneticSim::new(vec![()], |_| 0., |_| vec![()])); - let _thing = move || { - sim - }; + let h = std::thread::spawn(move || { + sim.next_generation(); + }); + + h.join(); } } \ No newline at end of file From 99c5ea75c2ee1dfd3a265e97152cd77d9565d906 Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:32:23 +0000 Subject: [PATCH 8/9] fix the multithreading issue --- src/lib.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4709c1a..e3af018 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -84,7 +84,6 @@ //! This project falls under the `MIT` license. use replace_with::replace_with_or_abort; -use std::sync::Arc; /// Built-in nextgen functions and traits to go with them. #[cfg(feature = "builtin")] pub mod builtin; @@ -158,8 +157,8 @@ where { /// The current population of entities pub entities: Vec, - fitness: Arc f32>, - next_gen: Arc) -> Vec>, + fitness: Box f32 + Send + Sync + 'static>, + next_gen: Box) -> Vec + Send + Sync + 'static>, } impl GeneticSim @@ -170,13 +169,13 @@ where /// a given fitness function, and a given nextgen function. pub fn new( starting_entities: Vec, - fitness: impl Fn(&E) -> f32 + 'static, - next_gen: impl Fn(Vec<(E, f32) >) -> Vec + 'static + fitness: impl Fn(&E) -> f32 + Send + Sync + 'static, + next_gen: impl Fn(Vec<(E, f32) >) -> Vec + Send + Sync + 'static ) -> Self { Self { entities: starting_entities, - fitness: Arc::new(fitness), - next_gen: Arc::new(next_gen), + fitness: Box::new(fitness), + next_gen: Box::new(next_gen), } } @@ -333,12 +332,12 @@ mod tests { #[test] fn send_sim() { - let mut sim = Arc::new(GeneticSim::new(vec![()], |_| 0., |_| vec![()])); + let mut sim = GeneticSim::new(vec![()], |_| 0., |_| vec![()]); let h = std::thread::spawn(move || { sim.next_generation(); }); - h.join(); + h.join().unwrap(); } } \ No newline at end of file From 2ac06fbcd8f0218e8d874860babeb22db5680a3b Mon Sep 17 00:00:00 2001 From: Tristan Murphy <72839119+HyperCodec@users.noreply.github.com> Date: Fri, 26 Jan 2024 12:53:01 +0000 Subject: [PATCH 9/9] add shields.io badges (not sure if wf working) --- .github/workflows/ci-cd.yml | 1 + README.md | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 4f62fd7..9ba12bd 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -8,6 +8,7 @@ on: jobs: test: runs-on: ubuntu-latest + environment: testing name: Run Unit Tests steps: - uses: actions/checkout@v4 diff --git a/README.md b/README.md index 8a0c4a2..295b8b0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # genetic-rs +![Crates.io Total Downloads](https://img.shields.io/crates/d/genetic-rs) +![GitHub deployments](https://img.shields.io/github/deployments/HyperCodec/genetic-rs/testing) + A small crate for quickstarting genetic algorithm projects ### How to Use