use ndarray::{Array1, Array2, ArrayView1, Axis};
use crate::genetic::{D12, Fronts, PopulationMOO};
#[inline]
pub fn dominates(f1: &ArrayView1<f64>, f2: &ArrayView1<f64>) -> bool {
let mut better = false;
for (&a, &b) in f1.iter().zip(f2.iter()) {
if a > b {
return false;
} else if a < b {
better = true;
}
}
better
}
#[inline]
pub fn dominates_weak(f1: &ArrayView1<f64>, f2: &ArrayView1<f64>) -> bool {
for (&a, &b) in f1.iter().zip(f2.iter()) {
if a > b {
return false;
}
}
true
}
pub fn fast_non_dominated_sorting(
population_fitness: &Array2<f64>,
min_survivors: usize,
) -> Vec<Vec<usize>> {
let population_size = population_fitness.shape()[0];
let mut domination_count = vec![0; population_size];
let mut dominated_sets = vec![Vec::new(); population_size];
let fitness_rows: Vec<ArrayView1<f64>> = (0..population_size)
.map(|i| population_fitness.index_axis(Axis(0), i))
.collect();
for p in 0..population_size {
for q in (p + 1)..population_size {
if dominates(&fitness_rows[p], &fitness_rows[q]) {
dominated_sets[p].push(q);
domination_count[q] += 1;
} else if dominates(&fitness_rows[q], &fitness_rows[p]) {
dominated_sets[q].push(p);
domination_count[p] += 1;
}
}
}
let mut fronts = Vec::new();
let mut first_front = Vec::new();
for i in 0..population_size {
if domination_count[i] == 0 {
first_front.push(i);
}
}
fronts.push(first_front.clone());
let mut count = first_front.len();
if count >= min_survivors {
return fronts;
}
let mut current_front = first_front;
while !current_front.is_empty() {
let mut next_front = Vec::new();
for &p in ¤t_front {
for &q in &dominated_sets[p] {
domination_count[q] -= 1;
if domination_count[q] == 0 {
next_front.push(q);
}
}
}
if next_front.is_empty() {
break;
}
if count + next_front.len() >= min_survivors {
fronts.push(next_front);
break;
} else {
count += next_front.len();
fronts.push(next_front.clone());
current_front = next_front;
}
}
fronts
}
pub fn build_fronts<ConstrDim>(
population: PopulationMOO<ConstrDim>,
num_survive: usize,
) -> Fronts<ConstrDim>
where
ConstrDim: D12,
{
let sorted_fronts = fast_non_dominated_sorting(&population.fitness, num_survive);
let mut results: Fronts<ConstrDim> = Vec::new();
for (front_index, indices) in sorted_fronts.iter().enumerate() {
let mut population_front = population.selected(&indices);
let rank_arr = Array1::from_elem(indices.len(), front_index);
population_front.set_rank(rank_arr);
results.push(population_front);
}
results
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::{Array2, array};
#[test]
fn test_dominates() {
let a = array![1.0, 2.0, 3.0];
let b = array![2.0, 3.0, 4.0];
assert_eq!(dominates(&a.view(), &b.view()), true);
let a = array![3.0, 3.0, 3.0];
let b = array![2.0, 4.0, 5.0];
assert_eq!(dominates(&a.view(), &b.view()), false);
let a = array![1.0, 2.0, 3.0];
let b = array![2.0, 1.0, 3.0];
assert_eq!(dominates(&a.view(), &b.view()), false);
let a = array![1.0, 2.0, 3.0];
let b = array![1.0, 2.0, 3.0];
assert_eq!(dominates(&a.view(), &b.view()), false);
}
#[test]
fn test_fast_non_dominated_sorting() {
let population_fitness = array![
[1.0, 2.0], [2.0, 1.0], [1.5, 1.5], [3.0, 4.0], [4.0, 3.0] ];
let fronts = fast_non_dominated_sorting(&population_fitness, 5);
let expected_fronts = vec![
vec![0, 1, 2], vec![3, 4], ];
assert_eq!(fronts, expected_fronts);
}
#[test]
fn test_fast_non_dominated_sorting_single_front() {
let population_fitness = array![
[1.0, 2.0], [2.0, 1.0], [1.5, 1.5], ];
let fronts = fast_non_dominated_sorting(&population_fitness, 3);
let expected_fronts = vec![
vec![0, 1, 2], ];
assert_eq!(fronts, expected_fronts);
}
#[test]
fn test_fast_non_dominated_sorting_empty_population() {
let population_fitness: Array2<f64> = Array2::zeros((0, 0));
let fronts = fast_non_dominated_sorting(&population_fitness, 0);
let expected_fronts: Vec<Vec<usize>> = vec![vec![]];
assert_eq!(fronts, expected_fronts);
}
#[test]
fn test_fast_non_dominated_sorting_n_survive_cut() {
let population_fitness = array![
[1.0, 1.0], [2.0, 2.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0] ];
let fronts = fast_non_dominated_sorting(&population_fitness, 2);
let expected_fronts = vec![
vec![0], vec![1, 2], ];
assert_eq!(fronts, expected_fronts);
}
#[test]
fn test_build_fronts_behavior() {
let genes = array![
[1.0, 1.0], [2.0, 2.0], [1.5, 2.5], [2.5, 1.5], [3.0, 3.0] ];
let fitness = genes.clone();
let constraints = Array2::from_elem((5, 2), -1.0);
let population = PopulationMOO::new(genes, fitness, constraints);
let fronts = build_fronts(population, 5);
assert_eq!(
fronts.len(),
3,
"Expected 3 fronts based on the objectives."
);
assert_eq!(
fronts[0].genes.nrows(),
1,
"Front 0 should have 1 individual."
);
assert_eq!(
fronts[1].genes.nrows(),
3,
"Front 1 should have 3 individuals."
);
assert_eq!(
fronts[2].genes.nrows(),
1,
"Front 2 should have 1 individual."
);
for (front_index, front) in fronts.iter().enumerate() {
let rank = front
.rank
.as_ref()
.expect("Each front should have a rank array.");
assert_eq!(
rank.len(),
front.genes.nrows(),
"Rank array length must match the number of individuals."
);
for &r in rank.iter() {
assert_eq!(
r, front_index,
"Each rank value should equal the front index."
);
}
}
for front in fronts.iter() {
for row in front.constraints.outer_iter() {
for &val in row.iter() {
assert!(val <= 0.0, "Constraint violation: value = {val} is not ≤ 0");
}
}
}
}
}