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}