Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation fixes #12

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest
environment: testing
name: Run Unit Tests
steps:
- uses: actions/checkout@v4
Expand Down
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,13 @@ 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]
fastrand = "2.0.1"
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -14,16 +17,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::<f32>() * 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
}
}
Expand Down
8 changes: 4 additions & 4 deletions examples/readme_ex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<f32>() * 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
}
}
Expand Down
21 changes: 12 additions & 9 deletions src/builtin.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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()
Expand All @@ -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
Expand All @@ -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::<usize>(0..og_champions.len()-1)];
let e2 = og_champions[rng.gen::<usize>(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
Expand Down
47 changes: 29 additions & 18 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<f32>() * 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
//! }
//! }
Expand Down Expand Up @@ -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::<f32>() * rate;
/// self.b += rng.gen::<f32>() * 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
/// }
/// }
Expand Down Expand Up @@ -157,8 +157,8 @@ where
{
/// The current population of entities
pub entities: Vec<E>,
fitness: Box<dyn Fn(&E) -> f32>,
next_gen: Box<dyn Fn(Vec<(E, f32)>) -> Vec<E>>,
fitness: Box<dyn Fn(&E) -> f32 + Send + Sync + 'static>,
next_gen: Box<dyn Fn(Vec<(E, f32)>) -> Vec<E> + Send + Sync + 'static>,
}

impl<E> GeneticSim<E>
Expand All @@ -169,8 +169,8 @@ where
/// a given fitness function, and a given nextgen function.
pub fn new(
starting_entities: Vec<E>,
fitness: impl Fn(&E) -> f32 + 'static,
next_gen: impl Fn(Vec<(E, f32) >) -> Vec<E> + 'static
fitness: impl Fn(&E) -> f32 + Send + Sync + 'static,
next_gen: impl Fn(Vec<(E, f32) >) -> Vec<E> + Send + Sync + 'static
) -> Self {
Self {
entities: starting_entities,
Expand Down Expand Up @@ -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::<f32>() * 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
}
}
Expand Down Expand Up @@ -315,7 +315,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),
Expand All @@ -329,4 +329,15 @@ mod tests {

dbg!(sim.entities);
}

#[test]
fn send_sim() {
let mut sim = GeneticSim::new(vec![()], |_| 0., |_| vec![()]);

let h = std::thread::spawn(move || {
sim.next_generation();
});

h.join().unwrap();
}
}