Crate genetic_algorithm
source ·Expand description
A genetic algorithm implementation for Rust. Inspired by the book Genetic Algorithms in Elixir
There are three main elements to this approach:
- The Genotype (the search space)
 - The Fitness function (the search goal)
 - The Strategy (the search strategy)
 
Terminology:
- Population: a population has 
population_sizenumber of individuals (called chromosomes). - Chromosome: a chromosome has 
genes_sizenumber of genes - Allele: alleles are the possible values of the genes
 - Gene: a gene is a combination of position in the chromosome and value of the gene (allele)
 - Genes: storage trait of the genes for a chromosome, mostly 
Vec<Allele>but alternatives possible - Genotype: Knows how to generate, mutate and crossover chromosomes efficiently
 - Fitness: knows how to determine the fitness of a chromosome
 
All multithreading mechanisms are implemented using rayon::iter and std::sync::mpsc.
§Quick Usage
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: &Chromosome<Self::Genotype>) -> Option<FitnessValue> {
        Some(chromosome.genes.iter().filter(|&value| *value).count() as FitnessValue)
    }
}
// the search strategy
let evolve = Evolve::builder()
    .with_genotype(genotype)
    .with_compete(CompeteElite::new())             // sort the chromosomes by fitness to determine crossover order
    .with_crossover(CrossoverUniform::new(0.5))    // crossover all individual genes between 2 chromosomes for offspring, keep 50% parents around for next generation
    .with_mutate(MutateSingleGene::new(0.2))       // mutate a single gene with a 20% probability per chromosome
    .with_fitness(CountTrue)                       // count the number of true values in the chromosomes
    .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);§Tests
Use .with_rng_seed_from_u64(0) builder step to create deterministic tests results.
§Examples
- N-Queens puzzle https://en.wikipedia.org/wiki/Eight_queens_puzzle
- See examples/evolve_nqueens
 - See examples/hill_climb_nqueens
 UniqueGenotype<u8>with a 64x64 chess board setup- custom 
NQueensFitnessfitness 
 - Knapsack problem: https://en.wikipedia.org/wiki/Knapsack_problem
- See examples/evolve_knapsack
 - See examples/permutate_knapsack
 BinaryGenotype<Item(weight, value)>each gene encodes presence in the knapsack- custom 
KnapsackFitness(&items, weight_limit)fitness 
 - Infinite Monkey theorem: https://en.wikipedia.org/wiki/Infinite_monkey_theorem
- See examples/evolve_monkeys
 ListGenotype<char>100 monkeys randomly typing characters in a loop- custom fitness using hamming distance
 
 - Permutation strategy instead of Evolve strategy for small search spaces, with a 100% guarantee
 - HillClimb strategy instead of Evolve strategy, when crossover is impossible or inefficient
 - Explore vector genes BinaryGenotype versus other storage BitGenotype
 - Explore internal and external multithreading options
 - Custom Fitness function with LRU cache
- See examples/evolve_binary_lru_cache_fitness
 - Note: doesn’t help performance much in this case…
 
 - Custom Reporting implementation
 
§Performance considerations
For the Evolve strategy:
- Compete: no considerations. All competes are basically some form of in-place sorting of some kind. This is relatively fast compared to the rest of the operations.
 - Crossover: the workhorse of internal parts. Crossover touches most genes each generation and clones up to the whole population if you keep all the parents around. See performance tips below.
 - Mutate: no considerations. It touches genes like crossover does, but should be used sparingly anyway; with low gene counts (<10%) and low probability (5-20%)
 - Fitness: can be anything. This fully depends on the user domain. Parallelize
it using 
with_par_fitness()in the Builder. But beware that parallelization has it’s own overhead and is not always faster. 
Performance Tips
- Small genes sizes
- It seems that CrossoverMultiGene with
number_of_crossovers = genes_size / 2andallow_duplicates = trueis the best tradeoff between performance and effect. CrossoverUniform is an alias for the same approach, taking the genes_size from the genotype at runtime. - Keeping the parents around doesn’t matter that much as the cloning is relatively less pronounced (but becomes more prominent for larger population sizes)
 
 - It seems that CrossoverMultiGene with
 - Large genes sizes
- It seems that CrossoverMultiPoint with
number_of_crossovers = genes_size / 9andallow_duplicates = falseis the best tradeoff between performance and effect. - Keeping the parents around has major performance effects and should be avoided. Use low parent_survival_rate or none at all. Explore non-Vec based genotypes like BitGenotype.
 
 - It seems that CrossoverMultiPoint with
 
Modules§
- The chromosome is a container for the genes and caches a fitness score
 - The competition phase, where chromosomes are lined up for pairing in the crossover phase. Excess chromosomes, beyond the target_population_size, are dropped.
 - The crossover phase where every two parent chromosomes create two children chromosomes. The competition phase determines the order and the amount of the parent pairing (overall with fitter first). If you choose to a keep a percentage of the top parents, the parents will compete with their own children and the population is temporarily overbooked and part of it will be discarded in the competition phase of the next generation.
 - When approacking a (local) optimum in the fitness score, the variation in the population goes down dramatically. This reduces the efficiency, but also has the risk of local optimum lock-in. To increase the variation in the population, an extension mechanisms can optionally be used
 - The search goal to optimize towards (maximize or minimize).
 - The search space for the algorithm.
 - The mutation strategy, very important for avoiding local optimum lock-in. But don’t overdo it, as it degenerates the population too much if overused. Use a mutation probability generally between 5% and 20%.
 - The population is a container for Chromosomes
 - solution strategies for finding the best chromosomes.