use crate::error::GaError;
mod generational_distance;
mod hypervolume;
mod inverted_generational_distance;
mod spread;
pub use generational_distance::generational_distance;
pub use hypervolume::hypervolume;
pub use inverted_generational_distance::inverted_generational_distance;
pub use spread::spread;
pub(crate) fn validate_non_empty(name: &str, points: &[Vec<f64>]) -> Result<(), GaError> {
if points.is_empty() {
return Err(GaError::InvalidIndicatorConfiguration(format!(
"{} must not be empty",
name
)));
}
Ok(())
}
pub(crate) fn validate_dimension_consistency(points: &[Vec<f64>]) -> Result<usize, GaError> {
if points.is_empty() {
return Ok(0);
}
let dim = points[0].len();
if dim == 0 {
return Err(GaError::InvalidIndicatorConfiguration(
"Points must have at least 1 dimension".to_string(),
));
}
for (i, point) in points.iter().enumerate().skip(1) {
if point.len() != dim {
return Err(GaError::InvalidIndicatorConfiguration(format!(
"Dimension mismatch at index {}: expected {} dimensions, got {}",
i,
dim,
point.len()
)));
}
}
Ok(dim)
}
pub(crate) fn validate_dimension(
name: &str,
point: &[f64],
expected_dim: usize,
) -> Result<(), GaError> {
if point.len() != expected_dim {
return Err(GaError::InvalidIndicatorConfiguration(format!(
"{} has {} dimensions, expected {}",
name,
point.len(),
expected_dim
)));
}
Ok(())
}
#[inline]
pub(crate) fn squared_euclidean_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| (x - y) * (x - y)).sum()
}
pub(crate) fn nearest_distance(point: &[f64], front: &[Vec<f64>], power: f64) -> f64 {
let min_sq_dist = front
.iter()
.map(|ref_point| squared_euclidean_distance(point, ref_point))
.fold(f64::INFINITY, f64::min);
min_sq_dist.powf(power / 2.0)
}