#genetic-algorithm #genetic #algorithm #rust

genetic-rs

A small crate for quickstarting genetic algorithm projects

8 releases (4 breaking)

0.5.1 Apr 12, 2024
0.5.0 Apr 12, 2024
0.4.0 Feb 27, 2024
0.3.0 Feb 13, 2024
0.1.1 Jan 15, 2024

#526 in Algorithms

Download history 18/week @ 2024-01-14 5/week @ 2024-01-21 5/week @ 2024-01-28 19/week @ 2024-02-04 52/week @ 2024-02-11 70/week @ 2024-02-18 178/week @ 2024-02-25 9/week @ 2024-03-03 12/week @ 2024-03-10 3/week @ 2024-03-31 173/week @ 2024-04-07 118/week @ 2024-04-14

294 downloads per month
Used in neat

MIT license

40KB
539 lines

genetic-rs

github crates.io docs.rs

A small crate for quickstarting genetic algorithm projects.

How to Use

note: if you are interested in implementing NEAT with this, try out the neat crate

Features

First off, this crate comes with the builtin and genrand features by default. If you want to add the builtin crossover reproduction extension, you can do so by adding the crossover feature. If you want it to be parallelized, you can add the rayon feature. If you want your crossover to be speciated, you can add the speciation feature.

Once you have eveything imported as you wish, you can define your genome and impl the required traits:

#[derive(Clone, Debug)] // clone is currently a required derive for pruning nextgens.
struct MyGenome {
    field1: f32,
}

// required in all of the builtin functions as requirements of `DivsionReproduction` and `CrossoverReproduction`
impl RandomlyMutable for MyGenome {
    fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) {
        self.field1 += rng.gen::<f32>() * rate;
    }
}

// required for `division_pruning_nextgen`.
impl DivsionReproduction for MyGenome {
    fn divide(&self, rng: &mut impl rand::Rng) -> Self {
        let mut child = self.clone();
        child.mutate(0.25, rng); // use a constant mutation rate when spawning children in pruning algorithms.
        child
    }
}

// required for the builtin pruning algorithms.
impl Prunable for MyGenome {
    fn despawn(self) {
        // unneccessary to implement this function, but it can be useful for debugging and cleaning up genomes.
        println!("{:?} died", self);
    }
}

// helper trait that allows us to use `Vec::gen_random` for the initial population.
impl GenerateRandom for MyGenome {
    fn gen_random(rng: &mut impl rand::Rng) -> Self {
        Self { field1: rng.gen() }
    }
}

Once you have a struct, you must create your fitness function:

fn my_fitness_fn(ent: &MyGenome) -> f32 {
    // this just means that the algorithm will try to create as big a number as possible due to fitness being directly taken from the field.
    // in a more complex genetic algorithm, you will want to utilize `ent` to test them and generate a reward.
    ent.field1
}

Once you have your reward function, you can create a GeneticSim object to manage and control the evolutionary steps:

fn main() {
    let mut rng = rand::thread_rng();
    let mut sim = GeneticSim::new(
        // you must provide a random starting population. 
        // size will be preserved in builtin nextgen fns, but it is not required to keep a constant size if you were to build your own nextgen function.
        // in this case, you do not need to specify a type for `Vec::gen_random` because of the input of `my_fitness_fn`.
        Vec::gen_random(&mut rng, 100),
        my_fitness_fn,
        division_pruning_nextgen,
    );
 
    // perform evolution (100 gens)
    for _ in 0..100 {
        sim.next_generation(); // in a genetic algorithm with state, such as a physics simulation, you'd want to do things with `sim.genomes` in between these calls
    }
 
    dbg!(sim.genomes);
}

That is the minimal code for a working pruning-based genetic algorithm. You can read the docs or check the examples for more complicated systems.

License

This project falls under the MIT license.

Dependencies

~270–650KB
~12K SLoC