genetic_algorithm/
lib.rs

1//! A genetic algorithm implementation for Rust.
2//! Inspired by the book [Genetic Algorithms in Elixir](https://pragprog.com/titles/smgaelixir/genetic-algorithms-in-elixir/)
3//!
4//! There are three main elements to this approach:
5//! * The [Genotype](crate::genotype) (the search space)
6//! * The [Fitness](crate::fitness) function (the search goal)
7//! * The [Strategy](crate::strategy) (the search strategy)
8//!     * [Evolve](crate::strategy::evolve::Evolve) (evolution strategy)
9//!     * [Permutate](crate::strategy::permutate::Permutate) (for small search spaces, with a 100% guarantee)
10//!     * [HillClimb](crate::strategy::hill_climb::HillClimb) (when search space is convex with little local optima or when crossover is impossible/inefficient)
11//!
12//! Terminology:
13//! * [Population](crate::population): a population has `population_size` number of individuals (called chromosomes).
14//! * [Chromosome](crate::chromosome): a chromosome has `genes_size` number of genes
15//! * [Allele](crate::genotype::Allele): alleles are the possible values of the genes
16//! * Gene: a gene is a combination of position in the chromosome and value of the gene (allele)
17//! * [Genes](crate::genotype::Genes): storage trait of the genes for a chromosome, mostly `Vec<Allele>` but alternatives possible
18//! * [Genotype](crate::genotype): Knows how to generate, mutate and crossover chromosomes efficiently
19//! * [Fitness](crate::fitness): knows how to determine the fitness of a chromosome
20//!
21//! All multithreading mechanisms are implemented using [rayon::iter] and [std::sync::mpsc].
22//!
23//! ## Quick Usage
24//!
25//! ```rust
26//! use genetic_algorithm::strategy::evolve::prelude::*;
27//!
28//! // the search space
29//! let genotype = BinaryGenotype::builder() // boolean alleles
30//!     .with_genes_size(100)                // 100 genes per chromosome
31//!     .build()
32//!     .unwrap();
33//!
34//! println!("{}", genotype);
35//!
36//! // the search goal to optimize towards (maximize or minimize)
37//! #[derive(Clone, Debug)]
38//! pub struct CountTrue;
39//! impl Fitness for CountTrue {
40//!     type Genotype = BinaryGenotype; // Genes = Vec<bool>
41//!     fn calculate_for_chromosome(
42//!         &mut self,
43//!         chromosome: &FitnessChromosome<Self>,
44//!         _genotype: &FitnessGenotype<Self>
45//!     ) -> Option<FitnessValue> {
46//!         Some(chromosome.genes.iter().filter(|&value| *value).count() as FitnessValue)
47//!     }
48//! }
49//!
50//! // the search strategy
51//! let evolve = Evolve::builder()
52//!     .with_genotype(genotype)
53//!     .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)
54//!     .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)
55//!     .with_mutate(MutateSingleGene::new(0.2))          // mutate offspring for a single gene with a 20% probability per chromosome
56//!     .with_fitness(CountTrue)                          // count the number of true values in the chromosomes
57//!     .with_fitness_ordering(FitnessOrdering::Maximize) // optional, default is Maximize, aim towards the most true values
58//!     .with_target_population_size(100)                 // evolve with 100 chromosomes
59//!     .with_target_fitness_score(100)                   // goal is 100 times true in the best chromosome
60//!     .with_reporter(EvolveReporterSimple::new(100))    // optional builder step, report every 100 generations
61//!     .call()
62//!     .unwrap();
63//!
64//! println!("{}", evolve);
65//!
66//! // it's all about the best genes after all
67//! let (best_genes, best_fitness_score) = evolve.best_genes_and_fitness_score().unwrap();
68//! assert_eq!(best_genes, vec![true; 100]);
69//! assert_eq!(best_fitness_score, 100);
70//! ```
71//!
72//! ## Tests
73//!
74//! Use `.with_rng_seed_from_u64(0)` builder step to create deterministic tests results.
75//!
76//! ## Examples
77//!
78//! * N-Queens puzzle <https://en.wikipedia.org/wiki/Eight_queens_puzzle>
79//!     * See [examples/evolve_nqueens](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/evolve_nqueens.rs)
80//!     * See [examples/hill_climb_nqueens](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/hill_climb_nqueens.rs)
81//!     * `UniqueGenotype<u8>` with a 64x64 chess board setup
82//!     * custom `NQueensFitness` fitness
83//! * Knapsack problem: <https://en.wikipedia.org/wiki/Knapsack_problem>
84//!     * See [examples/evolve_knapsack](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/evolve_knapsack.rs)
85//!     * See [examples/permutate_knapsack](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/permutate_knapsack.rs)
86//!     * `BinaryGenotype<Item(weight, value)>` each gene encodes presence in the knapsack
87//!     * custom `KnapsackFitness(&items, weight_limit)` fitness
88//! * Infinite Monkey theorem: <https://en.wikipedia.org/wiki/Infinite_monkey_theorem>
89//!     * See [examples/evolve_monkeys](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/evolve_monkeys.rs)
90//!     * `ListGenotype<char>` 100 monkeys randomly typing characters in a loop
91//!     * custom fitness using hamming distance
92//! * Permutation strategy instead of Evolve strategy for small search spaces, with a 100% guarantee
93//!     * See [examples/permutate_knapsack](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/permutate_knapsack.rs)
94//!     * See [examples/permutate_scrabble](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/permutate_scrabble.rs)
95//! * HillClimb strategy instead of Evolve strategy, when crossover is impossible or inefficient
96//!     * See [examples/hill_climb_nqueens](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/hill_climb_nqueens.rs)
97//!     * See [examples/hill_climb_table_seating](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/hill_climb_table_seating.rs)
98//! * Explore vector genes [BinaryGenotype](genotype::BinaryGenotype) versus other storage [BitGenotype](genotype::BitGenotype)
99//!     * See [examples/evolve_bit_v_binary](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/evolve_bit_v_binary.rs)
100//! * Explore internal and external multithreading options
101//!     * See [examples/explore_multithreading](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/explore_multithreading.rs)
102//! * Use superset StrategyBuilder for easier switching in implementation
103//!     * See [examples/explore_strategies](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/explore_strategies.rs)
104//! * Use fitness LRU cache
105//!     * See [examples/evolve_binary_cache_fitness](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/evolve_binary_cache_fitness.rs)
106//!     * _Note: doesn't help performance much in this case... or any case, better fix your population diversity_
107//! * Custom Reporting implementation
108//!     * See [examples/permutate_scrabble](https://github.com/basvanwesting/genetic-algorithm/blob/main/examples/permutate_scrabble.rs)
109//!
110//! ## Performance considerations
111//!
112//! For the [Evolve](strategy::evolve::Evolve) strategy:
113//!
114//! * [Reporting](strategy::reporter): start with [EvolveReporterSimple](strategy::evolve::EvolveReporterSimple) for basic understanding of:
115//!   * fitness v. framework overhead
116//!   * staleness and population characteristics (cardinality etc.)
117//! * [Select](select): no considerations. All selects are basically some form of in-place
118//!   sorting of some kind. This is relatively fast compared to the rest of the
119//!   operations.
120//! * [Crossover](crossover): the workhorse of internal parts. Crossover touches most genes each
121//!   generation and clones up to the whole population to restore lost population size in selection.
122//!   See performance tips below.
123//!   It also calculates new genes hashes if enabled on the [Genotype](genotype), which has a
124//!   relatively high overhead on the main Evolve loop.
125//! * [Mutate](mutate): no considerations. It touches genes like crossover does, but should
126//!   be used sparingly anyway; with low gene counts (<10%) and low probability (5-20%)
127//! * [Fitness](fitness): can be anything. This fully depends on the user domain. Parallelize
128//!   it using `with_par_fitness()` in the Builder. But beware that parallelization
129//!   has it's own overhead and is not always faster.
130//!
131//! **Performance Tips**
132//!
133//! * Small genes sizes
134//!   * It seems that [CrossoverMultiGene](crossover::CrossoverMultiGene) with
135//!     `number_of_crossovers = genes_size / 2` and `allow_duplicates = true`
136//!     is the best tradeoff between performance and effect.
137//!     [CrossoverUniform](crossover::CrossoverUniform) is an alias for the same approach,
138//!     taking the genes_size from the genotype at runtime.
139//!   * Restoring the population doesn't matter that much as the cloning is relatively less
140//!     pronounced (but becomes more prominent for larger population sizes)
141//! * Large genes sizes
142//!   * It seems that [CrossoverMultiPoint](crossover::CrossoverMultiPoint) with
143//!     `number_of_crossovers = genes_size / 9` and `allow_duplicates = false` is
144//!     the best tradeoff between performance and effect.
145//!   * Restoring the population has considerable performance effects. Use a high
146//!     selection_rate or even 100%, so there is little parent cloning. Explore non-Vec based
147//!     genotypes like [BitGenotype](genotype::BitGenotype).
148//!
149//! **GPU acceleration**
150//!
151//! There are two genotypes where Genes (N) and Population (M) are a stored in single contiguous
152//! memory range of Alleles (T) with length N*M on the heap. A pointer to this data can be taken to
153//! calculate the whole population at once. These are:
154//! * [DynamicMatrixGenotype](genotype::DynamicMatrixGenotype)
155//! * [StaticMatrixGenotype](genotype::StaticMatrixGenotype)
156//!
157//! Useful in the following strategies where a whole population is calculated:
158//! * [Evolve](crate::strategy::evolve::Evolve)
159//! * [HillClimb](crate::strategy::hill_climb::HillClimb)-[SteepestAscent](crate::strategy::hill_climb::HillClimbVariant::SteepestAscent)
160//!
161//! Possibly a GPU compatible memory layout still needs to be added. The current implementation
162//! just provides all the basic building blocks to implement this. Please open a
163//! [github](https://github.com/basvanwesting/genetic-algorithm) issue for further support.
164pub mod allele;
165pub mod chromosome;
166pub mod crossover;
167pub mod errors;
168pub mod extension;
169pub mod fitness;
170pub mod genotype;
171pub mod mutate;
172pub mod population;
173pub mod select;
174pub mod strategy;