use crate::core::{Individual, OError};
use crate::utils::vector_min;
static DISTANCE_NAME: &str = "Distance";
pub struct Distance<'a> {
individuals: &'a [Individual],
reference_front: Vec<Individual>,
}
impl<'a> Distance<'a> {
pub fn new(
individuals: &'a [Individual],
reference_front: &[Vec<f64>],
) -> Result<Self, OError> {
if individuals.is_empty() {
return Err(OError::Metric(
DISTANCE_NAME.to_string(),
"The vector of individuals is empty".to_string(),
));
}
if reference_front.is_empty() {
return Err(OError::Metric(
DISTANCE_NAME.to_string(),
"The vector of reference points is empty".to_string(),
));
}
let problem = individuals[0].problem();
if reference_front[0].len() != problem.number_of_objectives() {
return Err(OError::Metric(
DISTANCE_NAME.to_string(),
"Each reference point must have a size equal to the number of objectives"
.to_string(),
));
}
let reference_front = reference_front
.iter()
.map(|values| {
let mut ind = Individual::new(problem.clone());
problem
.objective_names()
.iter()
.zip(values)
.for_each(|(name, value)| ind.update_objective(name, *value).unwrap());
ind
})
.collect();
Ok(Self {
individuals,
reference_front,
})
}
pub fn generational_distance(&self) -> Result<f64, OError> {
Self::_generational_distance(
self.individuals,
&self.reference_front,
Distance::euclidian_distance,
false,
false,
Some(1),
)
}
pub fn inverted_generational_distance(&self) -> Result<f64, OError> {
Self::_generational_distance(
&self.reference_front,
self.individuals,
Distance::euclidian_distance,
true,
false,
Some(1),
)
}
pub fn generational_distance_plus(&self) -> Result<f64, OError> {
Self::_generational_distance(
self.individuals,
&self.reference_front,
Distance::distance_plus,
false,
false,
Some(1),
)
}
pub fn inverted_generational_distance_plus(&self) -> Result<f64, OError> {
Self::_generational_distance(
&self.reference_front,
self.individuals,
Distance::distance_plus,
true,
false,
Some(1),
)
}
pub fn hausdorff_distance(&self) -> Result<f64, OError> {
Ok(f64::max(
Distance::_generational_distance(
self.individuals,
&self.reference_front,
Distance::euclidian_distance,
false,
true,
None,
)?,
Distance::_generational_distance(
&self.reference_front,
self.individuals,
Distance::euclidian_distance,
false,
true,
None,
)?,
))
}
fn euclidian_distance(
a: &Individual,
r: &Individual,
_is_inverse: bool,
) -> Result<f64, OError> {
Ok(a.get_objective_values()?
.iter()
.zip(r.get_objective_values()?)
.map(|(a_k, r_k)| (a_k - r_k).powi(2))
.sum::<f64>()
.sqrt())
}
fn distance_plus(a: &Individual, r: &Individual, is_inverse: bool) -> Result<f64, OError> {
let problem = a.problem();
let distance = problem
.objective_names()
.iter()
.map(|name| {
let mut delta =
a.get_objective_value(name).unwrap() - r.get_objective_value(name).unwrap();
if is_inverse {
delta *= -1.0;
}
if !problem.is_objective_minimised(name).unwrap() {
delta *= -1.0; }
delta.max(0.0).powi(2)
})
.sum::<f64>()
.sqrt();
Ok(distance)
}
fn _generational_distance<F>(
a: &[Individual],
r: &[Individual],
distance_function: F,
is_inverse: bool,
is_hausdorff: bool,
p: Option<u8>,
) -> Result<f64, OError>
where
F: Fn(&Individual, &Individual, bool) -> Result<f64, OError>,
{
let p = p.unwrap_or(1);
let distance_sum = a
.iter()
.map(|a| {
let distances: Vec<f64> = r
.iter()
.map(|r| distance_function(a, r, is_inverse))
.collect::<Result<Vec<f64>, OError>>()?;
let distances: Vec<f64> = distances.iter().map(|d| d.powi(p as i32)).collect();
vector_min(&distances)
})
.sum::<Result<f64, OError>>()?;
let exponent = 1.0 / (p as f64);
if is_hausdorff {
Ok((distance_sum / a.len() as f64).powf(exponent))
} else {
Ok(distance_sum.powf(exponent) / a.len() as f64)
}
}
}
#[cfg(test)]
mod test {
use std::collections::HashMap;
use float_cmp::assert_approx_eq;
use crate::core::test_utils::individuals_from_obj_values_dummy;
use crate::core::ObjectiveDirection;
use crate::metrics::Distance;
#[test]
fn test_distance_ishibuchi_et_al_2015() {
let z = [
vec![0., 10.],
vec![1., 6.],
vec![2., 2.],
vec![6., 1.],
vec![10., 0.],
];
let a = [vec![2., 4.], vec![3., 3.], vec![4., 2.]];
let b = [vec![2., 8.], vec![4., 4.], vec![8., 2.]];
let directions = [ObjectiveDirection::Minimise; 2];
let individuals_a = individuals_from_obj_values_dummy(&a, &directions, None);
let metric = Distance::new(&individuals_a, &z).unwrap();
assert_approx_eq!(
f64,
metric.generational_distance().unwrap(),
1.805,
epsilon = 0.001
);
assert_approx_eq!(
f64,
metric.generational_distance_plus().unwrap(),
1.138,
epsilon = 0.001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance().unwrap(),
3.707,
epsilon = 0.0001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance_plus().unwrap(),
1.483,
epsilon = 0.001
);
let individuals_b = individuals_from_obj_values_dummy(&b, &directions, None);
let metric = Distance::new(&individuals_b, &z).unwrap();
assert_approx_eq!(
f64,
metric.generational_distance().unwrap(),
2.434,
epsilon = 0.001
);
assert_approx_eq!(
f64,
metric.generational_distance_plus().unwrap(),
2.276,
epsilon = 0.001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance().unwrap(),
2.591,
epsilon = 0.001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance_plus().unwrap(),
2.260,
epsilon = 0.001
);
}
#[test]
fn test_distance() {
let ref_points: [Vec<f64>; 5] = [
vec![10.1, 0.3],
vec![1.53, 6.78],
vec![1.3, 1.3],
vec![0.3, 10.1],
vec![9.123, 8.1],
];
let objective_1 = vec![vec![4.1, 1.1], vec![0.3, 9.1], vec![2.54, 4.67]];
let mut expected_1 = HashMap::new();
expected_1.insert("gd", 2.048802387);
expected_1.insert("igd", 3.924499222);
expected_1.insert("gd+", 0.0);
expected_1.insert("igd+", 0.9219999);
expected_1.insert("hausdorff", 3.9244992221);
let objective_2 = vec![vec![8.11, 7.1], vec![5.67, 5.67], vec![0.45, 9.1]];
let mut expected_2 = HashMap::new();
expected_2.insert("gd", 2.218985866);
expected_2.insert("igd", 3.627049929);
expected_2.insert("gd+", 0.05);
expected_2.insert("igd+", 2.80402265);
expected_2.insert("hausdorff", 3.6270499);
let expected = [expected_1, expected_2];
let directions = [ObjectiveDirection::Minimise; 2];
for (objective, expected) in [objective_1, objective_2].iter().zip(expected) {
let individuals = individuals_from_obj_values_dummy(objective, &directions, None);
let metric = Distance::new(&individuals, &ref_points).unwrap();
assert_approx_eq!(
f64,
metric.generational_distance().unwrap(),
*expected.get("gd").unwrap(),
epsilon = 0.00001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance().unwrap(),
*expected.get("igd").unwrap(),
epsilon = 0.00001
);
assert_approx_eq!(
f64,
metric.generational_distance_plus().unwrap(),
*expected.get("gd+").unwrap(),
epsilon = 0.00001
);
assert_approx_eq!(
f64,
metric.inverted_generational_distance_plus().unwrap(),
*expected.get("igd+").unwrap(),
epsilon = 0.00001
);
assert_approx_eq!(
f64,
metric.hausdorff_distance().unwrap(),
*expected.get("hausdorff").unwrap(),
epsilon = 0.00001
);
}
}
}