| Crates.io | genetic_algorithm |
| lib.rs | genetic_algorithm |
| version | 0.26.0 |
| created_at | 2022-05-10 13:12:50.922349+00 |
| updated_at | 2025-11-24 19:04:25.450172+00 |
| 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 | 2,765,552 |
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>All 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.5, 0.02)) // sort the chromosomes by fitness to determine crossover order. Strive to replace 50% of the population with offspring. Allow 2% through the non-generational best chromosomes gate before selection and replacement
.with_crossover(CrossoverUniform::new(0.7, 0.8)) // crossover all individual genes between 2 chromosomes for offspring with 70% parent selection (30% do not produce offspring) and 80% chance of crossover (20% of parents just clone)
.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 loopMultiRangeGenotype supports heterogeneous chromosomes that mix different gene
semantics (continuous values, numeric values, discrete choices, booleans)
within a single numeric type T.
The library supports various mutation strategies that affect how the genetic algorithm explores the search space. Random leads to the best results overall. Random is the default and is supported by all Genotypes.
But for numeric genotypes (RangeGenotype and MultiRangeGenotype) there are several alternatives. These might converge faster, but are all more sensitive to local optima than Random. The visualization below shows how different mutation types explore a 2D search space when searching for a target point:

The visualization demonstrates:
Run the example with cargo run --example visualize_evolve_mutation_types --release to generate this visualization.
For exhaustive search in smaller spaces, all genotypes have their own permutation implementation, which systematically explore all value combinations.
But for numeric/continues genotypes (RangeGenotype and MultiRangeGenotype) permutation is only possible using Step, StepScaled, and Discrete mutation types (as it needs additional restrictions be become countable):

Run the example with cargo run --example visualize_permutate_mutation_types --release to generate this visualization.
For the Evolve strategy:
with_par_fitness()
in the Builder. But beware that parallelization has it's own overhead and is
not always faster.So framework overhead is mostly Crossover. Practical overhead is mostly Fitness.
Regarding the optionality of genes hashing and chromosomes recycling: For large chromosomes, disabling chromosome recycling and enabling genes hashing leads to a 3x factor in framework overhead. For small chromosomes, neither feature has overhead effects. But do keep in mind that for large chromosomes the Fitness calculation will be even more dominant with regards to the framework overhead as it already is. See examples/evolve_large_genotype
Default configuration for correctness AND performance
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