Crates.io | genetic_algorithm |
lib.rs | genetic_algorithm |
version | 0.17.1 |
source | src |
created_at | 2022-05-10 13:12:50.922349 |
updated_at | 2024-10-10 14:00:24.662145 |
description | A genetic algorithm implementation |
homepage | https://github.com/basvanwesting/genetic-algorithm.git |
repository | https://github.com/basvanwesting/genetic-algorithm.git |
max_upload_size | |
id | 583967 |
size | 953,882 |
A genetic algorithm implementation for Rust. Inspired by the book Genetic Algorithms in Elixir
There are three main elements to this approach:
Terminology:
population_size
number of individuals (called chromosomes).genes_size
number of genesVec<Allele>
, but alternatives possibleAll multithreading mechanisms are implemented using rayon::iter and std::sync::mpsc.
See docs.rs
use genetic_algorithm::strategy::evolve::prelude::*;
// the search space
let genotype = BinaryGenotype::builder() // boolean alleles
.with_genes_size(100) // 100 genes per chromosome
.build()
.unwrap();
println!("{}", genotype);
// the search goal to optimize towards (maximize or minimize)
#[derive(Clone, Debug)]
pub struct CountTrue;
impl Fitness for CountTrue {
type Genotype = BinaryGenotype; // Genes = Vec<bool>
fn calculate_for_chromosome(
&mut self,
chromosome: &FitnessChromosome<Self>,
_genotype: &FitnessGenotype<Self>
) -> Option<FitnessValue> {
Some(chromosome.genes.iter().filter(|&value| *value).count() as FitnessValue)
}
}
// the search strategy
let evolve = Evolve::builder()
.with_genotype(genotype)
.with_select(SelectElite::new(0.9)) // sort the chromosomes by fitness to determine crossover order and select 90% of the population for crossover (drop 10% of population)
.with_crossover(CrossoverUniform::new()) // crossover all individual genes between 2 chromosomes for offspring (and restore back to 100% of target population size by keeping the best parents alive)
.with_mutate(MutateSingleGene::new(0.2)) // mutate offspring for a single gene with a 20% probability per chromosome
.with_fitness(CountTrue) // count the number of true values in the chromosomes
.with_fitness_ordering(FitnessOrdering::Maximize) // optional, default is Maximize, aim towards the most true values
.with_target_population_size(100) // evolve with 100 chromosomes
.with_target_fitness_score(100) // goal is 100 times true in the best chromosome
.with_reporter(EvolveReporterSimple::new(100)) // optional builder step, report every 100 generations
.call();
.unwrap()
println!("{}", evolve);
// it's all about the best genes after all
let (best_genes, best_fitness_score) = evolve.best_genes_and_fitness_score().unwrap();
assert_eq!(best_genes, vec![true; 100]);
assert_eq!(best_fitness_score, 100);
Run with cargo run --example [EXAMPLE_BASENAME] --release
UniqueGenotype<u8>
with a 64x64 chess board setupNQueensFitness
fitnessBinaryGenotype<Item(weight, value)>
each gene encodes presence in the knapsackKnapsackFitness(&items, weight_limit)
fitnessListGenotype<char>
100 monkeys randomly typing characters in a loopFor the Evolve strategy:
with_par_fitness()
in the Builder. But beware that parallelization
has it's own overhead and is not always faster.Performance Tips
number_of_crossovers = genes_size / 2
and allow_duplicates = true
is the best tradeoff between performance and
effect. CrossoverUniform is an alias for the same approach, taking the
genes_size from the genotype at runtime.number_of_crossovers = genes_size / 9
and allow_duplicates = false
is the best tradeoff between performance and effect.GPU acceleration
There are two genotypes where Genes (N) and Population (M) are a stored in single contiguous memory range of Alleles (T) with length N*M on the heap. A pointer to this data can be taken to calculate the whole population at once. These are:
Useful in the following strategies where a whole population is calculated:
Possibly a GPU compatible memory layout still needs to be added. The current implementation just provides all the basic building blocks to implement this. Please open a github issue for further support.
Run tests with cargo test
Use .with_rng_seed_from_u64(0)
builder step to create deterministic tests results.
Implemented using criterion. Run benchmarks with cargo bench
Implemented using criterion and pprof.
Uncomment in Cargo.toml
[profile.release]
debug = 1
Run with cargo run --example profile_evolve_binary --release -- --bench --profile-time 5
Find the flamegraph in: ./target/criterion/profile_evolve_binary/profile/flamegraph.svg