ecrs/
ga.rs

1#![cfg(feature = "ga")]
2//! Implementation of genetic algorithm and genetic operators
3//!
4//! #### Description
5//!
6//! Evolutionary computation can be perceived as group of optimization
7//! algorithms behaviour of which is mainly based on naturally occuring
8//! processes. In this case, the process is Darwin's evolution and it's
9//! "the strongest is the most likely to survive" (apologies to all biologists)
10//! rule. This is the basis behind evolutionary (in particular - genetic) algorithms.
11//!
12//! For better grasp of theory we recommend taking a look into literature such as
13//! "Introduction to Genetic Algorithms" by S. N. Sivanandam & S. N. Deepa
14//! (there are plenty more papers & books on this topic though). Here, below
15//! we explain shortly only the most important terminology.
16//!
17//! The basic term used by genetic algorithm is `individual` (see our [`Individual`](crate::ga::individual::Individual) type).
18//! It describes any point (usually called `solution`) from problem domain. Its encoded version
19//! e.g. to bitstring is called `chromosome` (see [`Chromosome`](crate::ga::individual::Chromosome) type).
20//! The `chromosome` is comprised of `genes`. The possible set of values that gene can take is called `allele`.
21//!
22//! Let's look at example.
23//!
24//! Let's say that we want to optimize $f(x) = x^2$ where
25//! $x \in {0, 1, 2, \ldots, 7}$. The possible solutions (individuals) would be any of the values
26//! from domain - 0, 1, 2, 3. Let 1 be an individual then, and `001` be its `chromosome` (enconding).
27//! Then the `allele` would be ${0, 1}$ for each genee (set of possible gene values).
28//!
29//! The overall structure of genetic algorithm:
30//!
31//! 1. Generate initial population
32//! 2. Evalute population (what is the value of fitness function for each individual?)
33//! 3. Apply selection operator
34//! 4. Apply crossover operator
35//! 5. Apply mutation operator
36//! 6. Apply replacement operator
37//! 7. Termination condition satisfied? Yes -> END, no -> go to 2
38//!
39//! The `population` is a set of feasible solutions to the problem (individuals). Usually initial
40//! `population` is created by random generation (see our [population generators](crate::ga::population)).
41//!
42//! Such `population` is later transformed (evolves) by applying series of transformers (operators).
43//!
44//! For description of each operator we recommend reading their docs.
45//!
46//!
47//! * See [selection operators](crate::ga::operators::selection)
48//! * See [crossover operators](crate::ga::operators::crossover)
49//! * See [mutation operators](crate::ga::operators::mutation)
50//! * See [replacement operators](crate::ga::operators::replacement)
51//!
52//! #### Basic usage
53//!
54//! The instance of genetic algorithm is created with usage of its builder (see [Builder](crate::ga::builder::Builder)).
55//!
56//! ```no_run
57//! use ecrs::prelude::*;
58//! use ecrs::ga;
59//!
60//! # fn rastrigin_fitness(chromosome: &Vec<f64>) -> f64 {
61//! #   1000.0 * f64::exp(-ecrs::test_functions::rastrigin(chromosome))
62//! # }
63//!
64//! let mut res = ga::Builder::new::<
65//!     ga::individual::RealValueIndividual,
66//!     mutation::Identity,
67//!     crossover::SinglePoint,
68//!     selection::Boltzmann,
69//!     replacement::WeakParent,
70//!     population::RandomPoints,
71//!     fitness::FnBasedFitness<ga::individual::RealValueIndividual>,
72//!     ga::probe::AggregatedProbe<ga::individual::RealValueIndividual>
73//! >()
74//!   .set_max_generation_count(50_000)
75//!   .set_population_size(100)
76//!   .set_fitness_fn(rastrigin_fitness)
77//!   .set_crossover_operator(ops::crossover::SinglePoint::new())
78//!   .set_replacement_operator(ops::replacement::WeakParent::new())
79//!   .set_mutation_operator(ops::mutation::Identity::new())
80//!   .set_population_generator(population::RandomPoints::with_constraints(
81//!     3,
82//!     vec![-5.12..5.12, -5.12..5.12, -5.12..5.12],
83//!   ))
84//!   .set_selection_operator(ga::operators::selection::Boltzmann::new(0.05, 80.0, 500, false))
85//!   .set_probe(
86//!     ga::probe::AggregatedProbe::new()
87//!       .add_probe(ga::probe::PolicyDrivenProbe::new(
88//!         ga::probe::ElapsedTime::new(std::time::Duration::from_millis(200), std::time::Duration::ZERO),
89//!         ga::probe::StdoutProbe,
90//!       ))
91//!       .add_probe(ga::probe::PolicyDrivenProbe::new(
92//!         ga::probe::GenerationInterval::new(500, 100),
93//!         ga::probe::StdoutProbe,
94//!       )),
95//!   )
96//!   .build();
97//!
98//! // Run the algorithm
99//! let result = res.run();
100//! ```
101//!
102//! Hella, so many configuration steps?! Let me be clear: there are evem more configuration possibilites. But they are **possibilities**!
103//! The minimum you must specify:
104//!
105//! 1. Fitness function (the algorithm must know what it is optimizing)
106//! 2. Problem dimension
107//! 3. Population generator (the algorithm must be able to create initial population)
108//! 4. Probe (the logging object -- if you don't want to see any logs other than final result just pass [Empty probe](crate::ga::probes::Empty))
109//!
110//! The defaults for operators and parameters are provided for two types of chromosomes: bit string and real valued vector (see docs of [Builder](crage::ga::builder::Builder)),
111//! but keep in mind that these default options might not be even good for your particular problem as the operators & parameters should be
112//! tailored individually for each problem.
113//!
114//! * See [probes & configuration](ecrs::ga::probe)
115//! * See [population generators](ecrs::ga::population)
116//! * See [fitness & configuration](ecrs::ga::fitness)
117//! * See [available params](self::GAParams)
118
119pub mod builder;
120pub mod individual;
121pub mod operators;
122pub mod population;
123pub mod probe;
124
125use crate::ga::operators::fitness::Fitness;
126pub use builder::*;
127pub use individual::Individual;
128pub use probe::CsvProbe;
129pub use probe::JsonProbe;
130pub use probe::Probe;
131pub use probe::StdoutProbe;
132use std::marker::PhantomData;
133
134use self::individual::IndividualTrait;
135use self::{
136    operators::{
137        crossover::CrossoverOperator, mutation::MutationOperator, replacement::ReplacementOperator,
138        selection::SelectionOperator,
139    },
140    population::PopulationGenerator,
141};
142
143pub struct GAParams {
144    pub selection_rate: f64,
145    pub mutation_rate: f64,
146    pub population_size: usize,
147    pub generation_limit: usize,
148    pub max_duration: std::time::Duration,
149}
150
151pub struct GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
152where
153    IndividualT: IndividualTrait,
154    MutOpT: MutationOperator<IndividualT>,
155    CrossOpT: CrossoverOperator<IndividualT>,
156    SelOpT: SelectionOperator<IndividualT>,
157    ReplOpT: ReplacementOperator<IndividualT>,
158    PopGenT: PopulationGenerator<IndividualT>,
159    FitnessT: Fitness<IndividualT>,
160    ProbeT: Probe<IndividualT>,
161{
162    pub params: GAParams,
163    pub fitness_fn: FitnessT,
164    pub mutation_operator: MutOpT,
165    pub crossover_operator: CrossOpT,
166    pub selection_operator: SelOpT,
167    pub replacement_operator: ReplOpT,
168    pub population_factory: PopGenT,
169    pub probe: ProbeT,
170    _phantom: PhantomData<IndividualT::ChromosomeT>,
171}
172
173#[derive(Default)]
174pub struct GAMetadata {
175    pub start_time: Option<std::time::Instant>,
176    pub duration: Option<std::time::Duration>,
177    pub generation: usize,
178}
179
180impl GAMetadata {
181    pub fn new(
182        start_time: Option<std::time::Instant>,
183        duration: Option<std::time::Duration>,
184        generation: usize,
185    ) -> Self {
186        GAMetadata {
187            start_time,
188            duration,
189            generation,
190        }
191    }
192}
193
194pub struct GeneticSolver<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
195where
196    IndividualT: IndividualTrait,
197    MutOpT: MutationOperator<IndividualT>,
198    CrossOpT: CrossoverOperator<IndividualT>,
199    SelOpT: SelectionOperator<IndividualT>,
200    ReplOpT: ReplacementOperator<IndividualT>,
201    PopGenT: PopulationGenerator<IndividualT>,
202    FitnessT: Fitness<IndividualT>,
203    ProbeT: Probe<IndividualT>,
204{
205    config: GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>,
206    metadata: GAMetadata,
207}
208
209impl<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
210    GeneticSolver<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
211where
212    IndividualT: IndividualTrait,
213    MutOpT: MutationOperator<IndividualT>,
214    CrossOpT: CrossoverOperator<IndividualT>,
215    SelOpT: SelectionOperator<IndividualT>,
216    ReplOpT: ReplacementOperator<IndividualT>,
217    PopGenT: PopulationGenerator<IndividualT>,
218    FitnessT: Fitness<IndividualT>,
219    ProbeT: Probe<IndividualT>,
220{
221    pub fn new(
222        config: GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>,
223    ) -> Self {
224        assert_eq!(config.params.population_size % 2, 0); // Required for most of operators right
225                                                          // now
226        GeneticSolver {
227            config,
228            metadata: GAMetadata::new(None, None, 0),
229        }
230    }
231
232    #[inline]
233    fn find_best_individual(population: &[IndividualT]) -> &IndividualT {
234        population.iter().min().unwrap()
235    }
236
237    #[inline]
238    fn eval_pop(&mut self, population: &mut [IndividualT]) {
239        population
240            .iter_mut()
241            .filter(|idv| idv.requires_evaluation())
242            .for_each(|idv| *idv.fitness_mut() = (self.config.fitness_fn).apply(idv));
243    }
244
245    #[inline(always)]
246    fn gen_pop(&mut self) -> Vec<IndividualT> {
247        self.config
248            .population_factory
249            .generate(self.config.params.population_size)
250    }
251
252    pub fn run(&mut self) -> Option<IndividualT> {
253        self.metadata.start_time = Some(std::time::Instant::now());
254        self.config.probe.on_start(&self.metadata);
255
256        let mut population = self.gen_pop();
257
258        self.eval_pop(&mut population);
259
260        self.config.probe.on_initial_population_created(&population);
261
262        let mut best_individual_all_time = Self::find_best_individual(&population).clone();
263
264        self.metadata.duration = Some(self.metadata.start_time.unwrap().elapsed());
265        self.config
266            .probe
267            .on_new_best(&self.metadata, &best_individual_all_time);
268
269        for generation_no in 1..=self.config.params.generation_limit {
270            self.metadata.generation = generation_no;
271            self.metadata.duration = Some(self.metadata.start_time.unwrap().elapsed());
272
273            self.config.probe.on_iteration_start(&self.metadata);
274
275            // 2. Evaluate fitness for each individual.
276            self.eval_pop(&mut population);
277
278            // 4. Create mating pool by applying selection operator.
279            let mating_pool: Vec<&IndividualT> =
280                self.config
281                    .selection_operator
282                    .apply(&self.metadata, &population, population.len());
283
284            // 5. From mating pool create new generation (apply crossover & mutation).
285            let mut children: Vec<IndividualT> = Vec::with_capacity(self.config.params.population_size);
286
287            // FIXME: Do not assume that population size is an even number.
288            for parents in mating_pool.chunks(2) {
289                let crt_children = self.config.crossover_operator.apply(parents[0], parents[1]);
290
291                children.push(crt_children.0);
292                children.push(crt_children.1);
293            }
294
295            children.iter_mut().for_each(|child| {
296                self.config
297                    .mutation_operator
298                    .apply(child, self.config.params.mutation_rate)
299            });
300
301            if self.config.replacement_operator.requires_children_fitness() {
302                self.eval_pop(&mut children);
303            }
304
305            // 6. Replacement - merge new generation with old one
306            population = self.config.replacement_operator.apply(population, children);
307
308            // 7. Check for stop condition (Is good enough individual found)? If not goto 2.
309            self.eval_pop(&mut population);
310
311            self.config.probe.on_new_generation(&self.metadata, &population);
312
313            let best_individual = Self::find_best_individual(&population);
314            self.config
315                .probe
316                .on_best_fit_in_generation(&self.metadata, best_individual);
317
318            if *best_individual < best_individual_all_time {
319                best_individual_all_time = best_individual.clone();
320                self.config
321                    .probe
322                    .on_new_best(&self.metadata, &best_individual_all_time);
323            }
324
325            self.config.probe.on_iteration_end(&self.metadata);
326
327            if self.metadata.start_time.unwrap().elapsed() >= self.config.params.max_duration {
328                break;
329            }
330        }
331
332        self.config
333            .probe
334            .on_end(&self.metadata, &population, &best_individual_all_time);
335        Some(best_individual_all_time)
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::GAMetadata;
342
343    #[test]
344    fn gametadata_can_be_constructed_with_new_fn() {
345        GAMetadata::new(None, None, 0);
346    }
347}