genetic_algorithm_fn/
solutions.rs

1use crate::function::Function;
2use crate::solution::Solution;
3use genetic_algorithm_traits::{Individual, Population};
4use rand::distributions::uniform::SampleRange;
5use std::fmt;
6
7use crossbeam_utils::thread;
8use std::collections::HashSet;
9use std::convert::From;
10use std::time::Instant;
11
12/// The `Solution` is the container for your current pool of `solution`'s.
13#[derive(Debug, Clone, PartialEq)]
14pub struct Solutions {
15    /// The unique solutions that currently exist.
16    solutions: HashSet<Solution>,
17}
18// Convert a Vector of solution's to a `Solutions`-object.
19impl From<Vec<Solution>> for Solutions {
20    /// Create a new Population from a vector of solutions.
21    ///
22    /// # Arguments
23    ///
24    /// * `solutions` - The solutions you collected so far and would like to put into your
25    /// Solutions.
26    ///
27    /// # Examples
28    ///
29    /// ```
30    /// use genetic_algorithm_fn::solutions;
31    /// use genetic_algorithm_fn::solution;
32    ///
33    /// let my_solutions = solutions::Solutions::from(vec![
34    ///     solution::Solution::new(vec![1.0, 2.0, 3.0]),
35    ///     solution::Solution::new(vec![1.0, 2.0, 4.0])
36    /// ]);
37    /// println!("Current solutions: {}", my_solutions);
38    /// ```
39    fn from(solution: Vec<Solution>) -> Self {
40        Solutions {
41            solutions: solution.into_iter().collect(),
42        }
43    }
44}
45
46/// Represent the Solution by displaying `Solutions([solution-1, solution-2]).
47impl fmt::Display for Solutions {
48    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
49        write!(
50            formatter,
51            "Solutions([\n\t{}\n])",
52            self.solutions
53                .iter()
54                .map(|solution| solution.to_string())
55                .collect::<Vec<String>>()
56                .join(",\n\t")
57        )
58    }
59}
60
61impl Solutions {
62    /// Create a pool of random solutions.
63    ///
64    /// # Arguments
65    ///
66    /// * `n_solutions` - The number of solutions your population should contain.
67    ///
68    /// # Examples
69    ///
70    /// ```
71    /// use genetic_algorithm_fn::solutions;
72    /// println!("{}", solutions::Solutions::random(5, 1.0..10.0, 3));
73    /// ```
74    pub fn random<R>(n_solutions: usize, range: R, length: usize) -> Self
75    where
76        R: SampleRange<f64> + Clone,
77    {
78        let mut routes = HashSet::new();
79
80        while routes.len() < n_solutions {
81            routes.insert(Solution::random(range.clone(), length));
82        }
83
84        Solutions { solutions: routes }
85    }
86}
87
88impl<'a> Population<'a> for Solutions {
89    type Individual = Solution;
90    type IndividualCollection = std::collections::hash_set::Iter<'a, Solution>;
91
92    /// Given your pool, compute the fitness of your individuals to solve the
93    /// problem at hand.
94    ///
95    /// # Arguments
96    ///
97    /// * `n` - How many individuals to keep?
98    /// * `function` - The distances between nodes that is neccessary to computes how well the route
99    /// work in terms of the Function to maximize.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use genetic_algorithm_fn::solutions;
105    /// use genetic_algorithm_fn::function;
106    /// use genetic_algorithm_traits::Population;
107    ///
108    /// let function_to_optimize = function::Function::new(
109    ///     |x| match x.len() {
110    ///         3 => Ok(x[0] * x[1] * x[2]),
111    ///         _ => Err(function::FunctionError::WrongNumberOfEntries {
112    ///             actual_number_of_entries: x.len(),
113    ///             expected_number_of_entries: 3,
114    ///         }),
115    ///     }
116    /// );
117    /// let all_solutions = solutions::Solutions::random(30, 1.0..10.0, 3);
118    /// println!("Best 5 solutions: {}", all_solutions.get_fittest_population(5, &function_to_optimize));
119    /// ```
120    fn get_fittest_population(&self, n: usize, function: &Function) -> Solutions {
121        Solutions::from(self.get_n_fittest(n, function))
122    }
123    /// Evolve your population.
124    ///
125    /// The evolution process consists of the following stages:
126    /// 1) `crossover` between all 1,...,n solutions excluding the solution itself.
127    /// 2) `mutate` is applied to all individuals.
128    ///
129    /// # Arguments
130    ///
131    /// * `mutate_prob` - The probabilty of an inviduals beeing mutated. Is applied via `individuals.mutate`.
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use genetic_algorithm_fn::solutions;
137    /// use genetic_algorithm_traits::Population;
138    ///
139    /// let all_solutions = solutions::Solutions::random(2, 1.0..10.0, 3);
140    /// println!("The evolved invdividuals are {}", all_solutions.evolve(0.5));
141    ///
142    /// ```
143    fn evolve(&self, mutate_prob: f32) -> Solutions {
144        Solutions {
145            solutions: HashSet::from_iter(self.evolve_individuals(mutate_prob).into_iter()),
146        }
147    }
148    /// Iterate over the individuals of your population.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use genetic_algorithm_fn::solutions;
154    /// use genetic_algorithm_traits::Population;
155    ///
156    /// let all_solutions = solutions::Solutions::random(5, 1.0..10.0, 3);
157    /// all_solutions.iter().map(|solution| println!("{}", solution));
158    /// ```
159    fn iter(&'a self) -> std::collections::hash_set::Iter<Solution> {
160        self.solutions.iter()
161    }
162}
163
164/// Given an initial population evolve it for `n_generations` while keeping `size_generation`
165/// individuals. The final population will be returned.
166///
167/// # Arguments
168///
169/// * `initial_population` - Your initial population that should be evolved.
170/// * `n_generations` - How many times should your population be evolved?
171/// * `size_generation` - How many individuals should be kept after evolving it.
172/// * `distance_matrix` - The distance matrix on which the fitness will be computed on.
173///
174pub fn evolve_population(
175    initial_population: Solutions,
176    n_generations: usize,
177    size_generation: usize,
178    function: &Function,
179    n_jobs: usize,
180) -> Solutions {
181    if n_jobs == 0 {
182        // single-thread
183        (0..n_generations).fold(initial_population, |pop, _| {
184            pop.evolve(0.5)
185                .get_fittest_population(size_generation, function)
186        })
187    } else {
188        // multi-threaded execution
189        thread::scope(|s| {
190            let mut result = Vec::new();
191            // Schedule the threads.
192            for _ in 0..n_jobs {
193                let this_population = initial_population.clone();
194                result.push(s.spawn(move |_| -> Vec<Solution> {
195                    (0..((n_generations / n_jobs) + 1))
196                        .fold(this_population, |pop, _| {
197                            pop.evolve(0.5)
198                                .get_fittest_population(size_generation, function)
199                        })
200                        .get_n_fittest(size_generation, function)
201                }))
202            }
203            // Collect the results from the tread-handles.
204            Solutions::from(
205                result
206                    .into_iter()
207                    .map(|thread| thread.join().unwrap())
208                    .flatten()
209                    .collect::<Vec<Solution>>(),
210            )
211        })
212        .unwrap()
213    }
214}
215/// Compute the time in milliseconds that it takes for a genetic algorithm to run.
216///
217/// # Arguments
218///
219/// * `n_generations` - How many generations should the algorithm evolve?
220/// * `size_generation` - How many individuals should be selected at the end of each
221/// evolution step.
222/// * `dist_mat` - What is the distance matrix for your TSP.
223///
224/// ```
225pub fn benchmark_population<R>(
226    n_generations: usize,
227    size_generation: usize,
228    function: &Function,
229    n_jobs: usize,
230    sample_range: R,
231) -> (u64, f64)
232where
233    R: SampleRange<f64> + Clone,
234{
235    // End-to-end test: does the error of the route get down?
236    let before = Instant::now();
237    let final_population = evolve_population(
238        Solutions::random(size_generation, sample_range, 3),
239        n_generations,
240        size_generation,
241        function,
242        n_jobs,
243    );
244    let duration = before.elapsed();
245    let nanos = duration.subsec_nanos() as u64;
246    (
247        (1000 * 1000 * 1000 * duration.as_secs() + nanos) / (1000 * 1000),
248        final_population.get_n_fittest(1, function)[0].fitness(function),
249    )
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::solution;
256
257    #[test]
258    fn test_formatter() {
259        assert_eq!(
260            format!(
261                "{}",
262                Solutions::from(vec![solution::Solution::new(vec![1.1, 2.2, 3.3]),])
263            ),
264            "Solutions([\n\tSolution([1.1, 2.2, 3.3])\n])"
265        )
266    }
267}