use crate::function;
use core::ops::Add;
use genetic_algorithm_traits::Individual;
use rand::distributions::uniform::SampleRange;
use rand::Rng;
use std::fmt;
use std::hash::{Hash, Hasher};
fn get_random_elem_from_range<T, R>(range: R) -> Option<T>
where
T: std::cmp::PartialOrd + rand::distributions::uniform::SampleUniform,
R: SampleRange<T>,
{
if !range.is_empty() {
Some(rand::thread_rng().gen_range::<T, R>(range))
} else {
None
}
}
fn average<T>(value_1: T, value_2: T) -> f64
where
T: Add<Output = T>,
T: Into<f64>,
{
let sum_as_float: f64 = (value_1 + value_2).into();
sum_as_float / 2.0
}
fn f64_to_floating_point_precision_string(value: f64) -> String {
f64_to_rounded_string(value, 10)
}
fn f64_to_rounded_string(value: f64, precision: usize) -> String {
format!("{:.*}", precision, value,)
}
#[derive(Debug, Clone)]
pub struct Solution {
function_values: Vec<f64>,
}
impl fmt::Display for Solution {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "Solution({:?})", self.function_values)
}
}
impl PartialEq for Solution {
fn eq(&self, other: &Self) -> bool {
self.function_values.len() == other.function_values.len()
&& (self.function_values.is_empty()
|| self
.function_values
.iter()
.zip(other.function_values.iter())
.map(|(self_value, other_value)| {
f64_to_floating_point_precision_string(*self_value)
== f64_to_floating_point_precision_string(*other_value)
})
.all(|elem| elem))
}
}
impl Eq for Solution {}
impl Hash for Solution {
fn hash<H: Hasher>(&self, state: &mut H) {
for single_function_value in &self.function_values {
f64_to_floating_point_precision_string(*single_function_value).hash(state);
}
}
}
impl Solution {
pub fn new(function_values: Vec<f64>) -> Self {
Self { function_values }
}
pub fn random<R>(range: R, length: usize) -> Self
where
R: SampleRange<f64> + Clone,
{
Solution {
function_values: (0..length)
.map(|_| match get_random_elem_from_range(range.clone()) {
Some(value) => value,
None => panic!("Your range is empty!"),
})
.collect(),
}
}
pub fn get_arguments(&self) -> Vec<f64> {
self.function_values.clone()
}
}
impl<'a> Individual<'a> for Solution {
type IndividualCost = function::Function;
fn mutate(self, prob: f32) -> Self {
if get_random_elem_from_range(0.0..1.0).unwrap() > prob {
self
} else {
let mut factor_to_mutate = get_random_elem_from_range(0.8..1.2).unwrap();
while factor_to_mutate == 1.0 {
factor_to_mutate = get_random_elem_from_range(0.8..1.2).unwrap();
}
let factor_to_mutate_with = factor_to_mutate;
let idx_to_mutate = get_random_elem_from_range(0..self.function_values.len()).unwrap();
Solution {
function_values: self
.function_values
.iter()
.enumerate()
.map(|(idx, function_value)| {
if idx == idx_to_mutate {
function_value * factor_to_mutate_with
} else {
*function_value
}
})
.collect(),
}
}
}
fn crossover(&self, other: &Solution) -> Self {
if self.function_values.len() != other.get_arguments().len() {
panic!(
"Cannot crossover a Solution with {} elements when the other solution has {} elements",
self.function_values.len(),
other.get_arguments().len()
);
}
Solution {
function_values: self
.function_values
.iter()
.zip(other.function_values.iter())
.map(|(self_function_value, other_function_value)| {
average(*self_function_value, *other_function_value)
})
.collect(),
}
}
fn fitness(&self, function: &function::Function) -> f64 {
function
.get_function_value(self.function_values.clone())
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
mod test_float_to_round_str {
use super::*;
#[test]
fn no_rounding_fewer_digts() {
assert_eq!(f64_to_rounded_string(1.57, 3), String::from("1.570"))
}
#[test]
fn no_rounding_same_digts() {
assert_eq!(f64_to_rounded_string(1.572, 3), String::from("1.572"))
}
#[test]
fn actual_rounding() {
assert_eq!(f64_to_rounded_string(2.38493, 2), String::from("2.38"))
}
#[test]
fn integration_f64_to_floating_point_precision_string() {
f64_to_floating_point_precision_string(0.1323);
}
}
mod test_solution {
use super::*;
use crate::test_objects;
#[test]
fn test_constructor() {
Solution::new(vec![1.0, 2.0, 3.0]);
}
#[test]
fn test_display() {
assert_eq!(
format!("{}", Solution::new(vec![1.1, 2.2, 3.3])),
"Solution([1.1, 2.2, 3.3])",
);
}
#[test]
fn fitness() {
assert_eq!(
Solution::new(vec![2.0, 3.0, 5.0]).fitness(&function::Function::new(
test_objects::triple_multiplication()
)),
30.0
)
}
mod test_average {
use super::*;
#[test]
fn test_int_average() {
assert_eq!(average(1, 2), 1.5)
}
#[test]
fn test_int_average_same_value() {
assert_eq!(average(0, 0), 0.0)
}
#[test]
fn test_float_average() {
assert_eq!(average(1.0, 2.0), 1.5)
}
#[test]
fn test_float_average_same_value() {
assert_eq!(average(0.0, 0.0), 0.0)
}
}
mod test_equality {
use super::*;
#[test]
fn equal_solution_no_record() {
assert!(Solution::new(Vec::<f64>::new()) == Solution::new(Vec::<f64>::new()));
}
#[test]
fn equal_solutions() {
assert!(Solution::new(vec![1.0, 2.0, 3.0]) == Solution::new(vec![1.0, 2.0, 3.0]));
}
#[test]
fn non_equal_solutions() {
assert!(
!(Solution::new(vec![1.0, 3.0, 3.0]) == Solution::new(vec![1.0, 2.0, 3.0]))
);
}
#[test]
fn non_equal_before_rounding() {
assert!(
Solution::new(vec![1.00000000001, 2.0, 3.0])
== Solution::new(vec![1.0, 2.0, 3.0])
);
}
#[test]
fn non_equal_before_and_after_rounding() {
assert!(
!(Solution::new(vec![1.0000000001, 2.0, 3.0])
== Solution::new(vec![1.0, 2.0, 3.0]))
);
}
#[test]
fn non_equal_solutions_different_length() {
assert!(!(Solution::new(vec![1.0000000001]) == Solution::new(vec![1.0, 2.0, 3.0])));
}
}
mod get_random_elem_from_range {
use super::*;
#[test]
fn sample_int_range() {
get_random_elem_from_range(0..10);
}
#[test]
fn sample_float_range() {
get_random_elem_from_range(0.0..1.0);
}
#[test]
fn sample_empty_range() {
assert_eq!(get_random_elem_from_range(0..0), None);
}
}
mod test_hash {
use super::*;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
fn _create_hash(solution: Solution) -> u64 {
let mut s = DefaultHasher::new();
solution.hash(&mut s);
s.finish()
}
#[test]
fn hash_same_solution() {
assert!(
_create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
== _create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
);
}
#[test]
fn hash_different_solution() {
assert!(
!(_create_hash(Solution::new(vec![1.0, 3.0, 3.0]))
== _create_hash(Solution::new(vec![1.0, 2.0, 3.0])))
);
}
#[test]
fn hash_different_solution_but_rounding_makes_them_similiar() {
assert!(
_create_hash(Solution::new(vec![1.00000000001, 2.0, 3.0]))
== _create_hash(Solution::new(vec![1.0, 2.0, 3.0]))
);
}
#[test]
fn hash_solutions_different_length() {
assert!(
!(_create_hash(Solution::new(vec![1.00000000001, 2.0]))
== _create_hash(Solution::new(vec![1.0, 2.0, 3.0])))
);
}
}
mod test_mutate {
use super::*;
#[test]
fn no_mutation_applied() {
assert_eq!(
Solution::new(vec![1.0, 2.0, 3.0]).mutate(0.0),
Solution::new(vec![1.0, 2.0, 3.0])
)
}
#[test]
#[test]
#[test]
#[test]
#[test]
#[test]
fn mutation_applied() {
let original_solution = Solution::new(vec![1.0, 2.0, 3.0]);
let mutated_solution = original_solution.clone().mutate(1.0);
let original_parameters = original_solution.get_arguments();
let mutated_parameters = mutated_solution.get_arguments();
assert_eq!(
original_parameters
.iter()
.zip(mutated_parameters.iter())
.map(
|(original_parameter, mutated_parameter)| (*original_parameter
== *mutated_parameter)
as usize
)
.sum::<usize>(),
2
)
}
}
mod test_crossover {
use super::*;
#[test]
fn same_inidividual_result_in_same_individual() {
let solution = Solution::new(vec![1.0, 4.0, 7.0]);
assert_eq!(solution.crossover(&solution.clone()), solution);
}
#[test]
fn average_correctly_applied() {
let solution_to_crossover = Solution::new(vec![12.0, 3.0, 9.0]);
let solution_to_crossover_with = Solution::new(vec![7.0, 6.0, 13.0]);
assert_eq!(
solution_to_crossover.crossover(&solution_to_crossover_with),
Solution::new(vec![9.5, 4.5, 11.0])
);
}
#[test]
#[should_panic]
fn crossover_solution_different_length() {
let solution_to_crossover = Solution::new(vec![12.0, 3.0]);
let solution_to_crossover_with = Solution::new(vec![7.0, 6.0, 13.0]);
assert_eq!(
solution_to_crossover.crossover(&solution_to_crossover_with),
Solution::new(vec![9.5, 4.5, 11.0])
);
}
}
mod test_fitness {
use super::*;
#[test]
fn simple_test() {
let function_to_maximize =
function::Function::new(test_objects::triple_multiplication());
let solution = Solution::new(vec![1.0, 4.0, 7.0]);
assert_eq!(solution.fitness(&function_to_maximize), 28.0);
}
}
}
}