genetic_algorithm_fn/
solution.rs

1use crate::function;
2use core::ops::Add;
3use genetic_algorithm_traits::Individual;
4use rand::distributions::uniform::SampleRange;
5use rand::Rng;
6use std::fmt;
7use std::hash::{Hash, Hasher};
8
9/// Get a random alement from a range.
10///
11/// # Arguments
12///
13/// * `range` - The range that should be sampled.
14fn get_random_elem_from_range<T, R>(range: R) -> Option<T>
15where
16    T: std::cmp::PartialOrd + rand::distributions::uniform::SampleUniform,
17    R: SampleRange<T>,
18{
19    if !range.is_empty() {
20        Some(rand::thread_rng().gen_range::<T, R>(range))
21    } else {
22        None
23    }
24}
25
26/// Average two values.
27/// # Arguments
28///
29/// * `value_1` - The fist value that should be part of the average.
30/// * `value_2` - The second value that should be part of the average.
31fn average<T>(value_1: T, value_2: T) -> f64
32where
33    T: Add<Output = T>,
34    T: Into<f64>,
35{
36    let sum_as_float: f64 = (value_1 + value_2).into();
37    sum_as_float / 2.0
38}
39
40/// Convert a floating point value into a string with 10 decimal places.
41/// # Arguments
42///
43/// * `value` - The floating point value that should be converted.
44fn f64_to_floating_point_precision_string(value: f64) -> String {
45    // We use 10 digits as floating point precision.
46    f64_to_rounded_string(value, 10)
47}
48/// Convert a floating point value into a string with `precision` decimal places.
49/// # Arguments
50///
51/// * `value` - The floating point value that should be converted.
52/// * `precion` - The number of decimal places in the representation.
53fn f64_to_rounded_string(value: f64, precision: usize) -> String {
54    format!("{:.*}", precision, value,)
55}
56/// The `Solution` is an individual for using genetic algorithm to approximate functions. It contains
57/// the specific function values.
58#[derive(Debug, Clone)]
59pub struct Solution {
60    // Function value for `x`.
61    function_values: Vec<f64>,
62}
63
64/// Represent the Solution by Displaying `Solution(x-value, y-value, z-value)`.
65impl fmt::Display for Solution {
66    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
67        write!(formatter, "Solution({:?})", self.function_values)
68    }
69}
70
71/// Compare Solutions by converting the floating points values to a 10 decimal
72/// places representation as string - then compare the strings.
73impl PartialEq for Solution {
74    fn eq(&self, other: &Self) -> bool {
75        self.function_values.len() == other.function_values.len()
76            && (self.function_values.is_empty()
77                || self
78                    .function_values
79                    .iter()
80                    .zip(other.function_values.iter())
81                    .map(|(self_value, other_value)| {
82                        f64_to_floating_point_precision_string(*self_value)
83                            == f64_to_floating_point_precision_string(*other_value)
84                    })
85                    .all(|elem| elem))
86    }
87}
88/// Does not need additional implementation, uses the `eq` function from
89/// `PartialEq`.
90impl Eq for Solution {}
91
92/// To hash a solution, use the representation chosen designed in `fmt::Display`.
93impl Hash for Solution {
94    fn hash<H: Hasher>(&self, state: &mut H) {
95        for single_function_value in &self.function_values {
96            f64_to_floating_point_precision_string(*single_function_value).hash(state);
97        }
98    }
99}
100
101impl Solution {
102    /// Create a new Solution based on function values x,y and z.
103    ///
104    /// # Arguments
105    ///
106    /// * `x` - The value of x that this solution represents.
107    /// * `y` - The value of y that this solution represents.
108    /// * `z` - The value of z that this solution represents.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use genetic_algorithm_fn::solution;
114    /// let my_solution = solution::Solution::new(vec![3.0, 4.0, 5.0]);
115    /// ```
116    pub fn new(function_values: Vec<f64>) -> Self {
117        Self { function_values }
118    }
119    /// Create a random Solution with with values between or equal
120    /// `min` .. `max`.
121    ///
122    /// # Arguments
123    ///
124    /// * `min` - The minimal value of the function arguments in solution.
125    /// * `max` - The maximal value of the function arguments in solution.
126    ///
127    /// # Examples
128    ///
129    /// ```
130    /// use genetic_algorithm_fn::solution;
131    /// let random_solution = solution::Solution::random(3.0..10.0, 3);
132    /// ```
133    //fn get_random_elem_from_range<T, R>(range: R) -> Option<T>
134    pub fn random<R>(range: R, length: usize) -> Self
135    where
136        R: SampleRange<f64> + Clone,
137    {
138        Solution {
139            function_values: (0..length)
140                .map(|_| match get_random_elem_from_range(range.clone()) {
141                    Some(value) => value,
142                    // TODO: Don't use panic, but this function should return
143                    // a result.
144                    None => panic!("Your range is empty!"),
145                })
146                .collect(),
147        }
148    }
149    /// Return the function arguments stored in a solution.
150    ///
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use genetic_algorithm_fn::solution;
156    /// let simple_solution = solution::Solution::new(vec![1.0, 2.0, 3.0]);
157    /// assert_eq!(simple_solution.get_arguments(), vec![1.0, 2.0, 3.0])
158    /// ```
159    pub fn get_arguments(&self) -> Vec<f64> {
160        self.function_values.clone()
161    }
162}
163impl<'a> Individual<'a> for Solution {
164    // The Distance matrix is needed by the individuals to compute their fitness on.
165    type IndividualCost = function::Function;
166    /// Mutate the solution by multiplying a random function argument with a factor between
167    /// 0.8-1.2
168    ///
169    /// # Arguments
170    ///
171    /// * `prob` - The probability with which on of the function values will mutated.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use genetic_algorithm_fn::solution;
177    /// use genetic_algorithm_traits::Individual;
178    ///
179    /// let my_solution = solution::Solution::new(vec![1.0, 2.0, 3.0]);
180    /// println!("Solution before mutation: {}, solution after mutation: {}", my_solution, my_solution.clone().mutate(1.0));
181    /// ```
182    fn mutate(self, prob: f32) -> Self {
183        if get_random_elem_from_range(0.0..1.0).unwrap() > prob {
184            // With probabilty (1-prop) don't do any mutation.
185            self
186        } else {
187            // Sample a random factor to mutate the solutions with that is not 1.0
188            // so that a value is mutated.
189            let mut factor_to_mutate = get_random_elem_from_range(0.8..1.2).unwrap();
190            while factor_to_mutate == 1.0 {
191                factor_to_mutate = get_random_elem_from_range(0.8..1.2).unwrap();
192            }
193            // Remove mutuability.
194            let factor_to_mutate_with = factor_to_mutate;
195            // Sample the argument that we want to mutate.
196            let idx_to_mutate = get_random_elem_from_range(0..self.function_values.len()).unwrap();
197            Solution {
198                function_values: self
199                    .function_values
200                    .iter()
201                    .enumerate()
202                    .map(|(idx, function_value)| {
203                        if idx == idx_to_mutate {
204                            function_value * factor_to_mutate_with
205                        } else {
206                            *function_value
207                        }
208                    })
209                    .collect(),
210            }
211        }
212    }
213    /// Crossover one solution with another. For a lack of creativity, this is currently just taking
214    /// the average of the two solutions.
215    ///
216    /// # Arguments
217    ///
218    /// * `other` - The other Solution you would like to crossover with.
219    ///
220    /// # Examples
221    ///
222    /// ```
223    /// use genetic_algorithm_traits::Individual;
224    /// use genetic_algorithm_fn::solution;
225    ///
226    /// let solution_to_crossover = solution::Solution::new(vec![1.0, 2.0, 3.0]);
227    /// let solution_to_crossover_with = solution::Solution::new(vec![3.0, 2.0, 1.0]);
228    /// println!("{}", solution_to_crossover.crossover(&solution_to_crossover_with));
229    /// ```
230    fn crossover(&self, other: &Solution) -> Self {
231        if self.function_values.len() != other.get_arguments().len() {
232            // TODO: Crossover should return an Option or Result not panic.
233            panic!(
234                "Cannot crossover a Solution with {} elements when the other solution has {} elements",
235                self.function_values.len(),
236                other.get_arguments().len()
237            );
238        }
239        Solution {
240            function_values: self
241                .function_values
242                .iter()
243                .zip(other.function_values.iter())
244                .map(|(self_function_value, other_function_value)| {
245                    average(*self_function_value, *other_function_value)
246                })
247                .collect(),
248        }
249    }
250    /// Compute the fitness of a Solution, that is the specific function value of the `Function`
251    /// for the function arguments stored in `Solution`.
252    ///
253    /// # Arguments
254    ///
255    /// * `function` - The function that is should be used to compute of the function value of the
256    /// solution's arguments.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use genetic_algorithm_fn::solution;
262    /// use genetic_algorithm_fn::function;
263    /// use genetic_algorithm_traits::Individual;
264    ///
265    /// let function_to_optimize = function::Function::new(
266    ///     |x| match x.len() {
267    ///         3 => Ok(x[0] * x[1] * x[2]),
268    ///         _ => Err(function::FunctionError::WrongNumberOfEntries {
269    ///             actual_number_of_entries: x.len(),
270    ///             expected_number_of_entries: 3,
271    ///         }),
272    ///     }
273    /// );
274    /// let this_solution = solution::Solution::new(vec![2.0, 3.0, 5.0]);
275    /// println!("{}", this_solution.fitness(&function_to_optimize));
276    /// ```
277    ///
278    fn fitness(&self, function: &function::Function) -> f64 {
279        function
280            .get_function_value(self.function_values.clone())
281            .unwrap()
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288    mod test_float_to_round_str {
289        use super::*;
290
291        #[test]
292        fn no_rounding_fewer_digts() {
293            assert_eq!(f64_to_rounded_string(1.57, 3), String::from("1.570"))
294        }
295
296        #[test]
297        fn no_rounding_same_digts() {
298            assert_eq!(f64_to_rounded_string(1.572, 3), String::from("1.572"))
299        }
300
301        #[test]
302        fn actual_rounding() {
303            assert_eq!(f64_to_rounded_string(2.38493, 2), String::from("2.38"))
304        }
305        #[test]
306        fn integration_f64_to_floating_point_precision_string() {
307            // The function used is tested anyways, so this is just an
308            // integration making sure the function runs through.
309            f64_to_floating_point_precision_string(0.1323);
310        }
311    }
312
313    mod test_solution {
314        use super::*;
315        use crate::test_objects;
316        #[test]
317        fn test_constructor() {
318            // Ensure the constructor is working.
319            Solution::new(vec![1.0, 2.0, 3.0]);
320        }
321        #[test]
322        fn test_display() {
323            assert_eq!(
324                format!("{}", Solution::new(vec![1.1, 2.2, 3.3])),
325                "Solution([1.1, 2.2, 3.3])",
326            );
327        }
328        #[test]
329        fn fitness() {
330            assert_eq!(
331                Solution::new(vec![2.0, 3.0, 5.0]).fitness(&function::Function::new(
332                    test_objects::triple_multiplication()
333                )),
334                30.0
335            )
336        }
337        mod test_average {
338            use super::*;
339            #[test]
340            fn test_int_average() {
341                assert_eq!(average(1, 2), 1.5)
342            }
343            #[test]
344            fn test_int_average_same_value() {
345                assert_eq!(average(0, 0), 0.0)
346            }
347            #[test]
348            fn test_float_average() {
349                assert_eq!(average(1.0, 2.0), 1.5)
350            }
351            #[test]
352            fn test_float_average_same_value() {
353                assert_eq!(average(0.0, 0.0), 0.0)
354            }
355        }
356        mod test_equality {
357            use super::*;
358            #[test]
359            fn equal_solution_no_record() {
360                assert!(Solution::new(Vec::<f64>::new()) == Solution::new(Vec::<f64>::new()));
361            }
362
363            #[test]
364            fn equal_solutions() {
365                assert!(Solution::new(vec![1.0, 2.0, 3.0]) == Solution::new(vec![1.0, 2.0, 3.0]));
366            }
367            #[test]
368            fn non_equal_solutions() {
369                assert!(
370                    !(Solution::new(vec![1.0, 3.0, 3.0]) == Solution::new(vec![1.0, 2.0, 3.0]))
371                );
372            }
373            #[test]
374            fn non_equal_before_rounding() {
375                assert!(
376                    Solution::new(vec![1.00000000001, 2.0, 3.0])
377                        == Solution::new(vec![1.0, 2.0, 3.0])
378                );
379            }
380            #[test]
381            fn non_equal_before_and_after_rounding() {
382                assert!(
383                    !(Solution::new(vec![1.0000000001, 2.0, 3.0])
384                        == Solution::new(vec![1.0, 2.0, 3.0]))
385                );
386            }
387            #[test]
388            fn non_equal_solutions_different_length() {
389                assert!(!(Solution::new(vec![1.0000000001]) == Solution::new(vec![1.0, 2.0, 3.0])));
390            }
391        }
392        mod get_random_elem_from_range {
393            use super::*;
394            #[test]
395            fn sample_int_range() {
396                get_random_elem_from_range(0..10);
397            }
398            #[test]
399            fn sample_float_range() {
400                get_random_elem_from_range(0.0..1.0);
401            }
402            #[test]
403            fn sample_empty_range() {
404                assert_eq!(get_random_elem_from_range(0..0), None);
405            }
406        }
407        mod test_hash {
408            use super::*;
409            use std::collections::hash_map::DefaultHasher;
410            use std::hash::{Hash, Hasher};
411            fn _create_hash(solution: Solution) -> u64 {
412                let mut s = DefaultHasher::new();
413                solution.hash(&mut s);
414                s.finish()
415            }
416            #[test]
417            fn hash_same_solution() {
418                assert!(
419                    _create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
420                        == _create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
421                );
422            }
423            #[test]
424            fn hash_different_solution() {
425                assert!(
426                    !(_create_hash(Solution::new(vec![1.0, 3.0, 3.0]))
427                        == _create_hash(Solution::new(vec![1.0, 2.0, 3.0])))
428                );
429            }
430            #[test]
431            fn hash_different_solution_but_rounding_makes_them_similiar() {
432                assert!(
433                    _create_hash(Solution::new(vec![1.00000000001, 2.0, 3.0]))
434                        == _create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
435                );
436            }
437            #[test]
438            fn hash_solutions_different_length() {
439                assert!(
440                    !(_create_hash(Solution::new(vec![1.00000000001, 2.0]))
441                        == _create_hash(Solution::new(vec![1.0, 2.0, 3.0])))
442                );
443            }
444        }
445        mod test_mutate {
446            use super::*;
447            #[test]
448            fn no_mutation_applied() {
449                assert_eq!(
450                    Solution::new(vec![1.0, 2.0, 3.0]).mutate(0.0),
451                    Solution::new(vec![1.0, 2.0, 3.0])
452                )
453            }
454            // Run the following test a few times.
455            #[test]
456            #[test]
457            #[test]
458            #[test]
459            #[test]
460            #[test]
461            fn mutation_applied() {
462                let original_solution = Solution::new(vec![1.0, 2.0, 3.0]);
463                let mutated_solution = original_solution.clone().mutate(1.0);
464                // original solution and mutated_solution should be different for exactly
465                // one function paramter.
466                let original_parameters = original_solution.get_arguments();
467                let mutated_parameters = mutated_solution.get_arguments();
468                assert_eq!(
469                    original_parameters
470                        .iter()
471                        .zip(mutated_parameters.iter())
472                        .map(
473                            |(original_parameter, mutated_parameter)| (*original_parameter
474                                == *mutated_parameter)
475                                as usize
476                        )
477                        .sum::<usize>(),
478                    2
479                )
480            }
481        }
482        mod test_crossover {
483            use super::*;
484            #[test]
485            fn same_inidividual_result_in_same_individual() {
486                let solution = Solution::new(vec![1.0, 4.0, 7.0]);
487                assert_eq!(solution.crossover(&solution.clone()), solution);
488            }
489            #[test]
490            fn average_correctly_applied() {
491                let solution_to_crossover = Solution::new(vec![12.0, 3.0, 9.0]);
492                let solution_to_crossover_with = Solution::new(vec![7.0, 6.0, 13.0]);
493                assert_eq!(
494                    solution_to_crossover.crossover(&solution_to_crossover_with),
495                    Solution::new(vec![9.5, 4.5, 11.0])
496                );
497            }
498            #[test]
499            #[should_panic]
500            fn crossover_solution_different_length() {
501                let solution_to_crossover = Solution::new(vec![12.0, 3.0]);
502                let solution_to_crossover_with = Solution::new(vec![7.0, 6.0, 13.0]);
503                assert_eq!(
504                    solution_to_crossover.crossover(&solution_to_crossover_with),
505                    Solution::new(vec![9.5, 4.5, 11.0])
506                );
507            }
508        }
509        mod test_fitness {
510            use super::*;
511            #[test]
512            fn simple_test() {
513                let function_to_maximize =
514                    function::Function::new(test_objects::triple_multiplication());
515                let solution = Solution::new(vec![1.0, 4.0, 7.0]);
516                assert_eq!(solution.fitness(&function_to_maximize), 28.0);
517            }
518        }
519    }
520}