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}