#[cfg(test)]
use crate::structures::{Chromosome, Gene};
use genetic_algorithms::{
configuration::SelectionConfiguration,
error::GaError,
fitness::FitnessFnWrapper,
operations::selection::{self, fitness_proportionate, random, tournament},
operations::Selection,
traits::SelectionOperator,
};
#[test]
fn test_random_even_selection() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let dna_6 = vec![Gene { id: 11 }, Gene { id: 12 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_6 = Chromosome {
dna: dna_6,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
chromosome_6,
];
let mating_population = random::random(&population, 2);
assert_eq!(mating_population.len(), 3);
}
#[test]
fn test_random_odd_selection() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
];
let mating_population = random::random(&population, 2);
assert_eq!(mating_population.len(), 2);
}
#[test]
fn test_roulette_wheel_selection() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 30.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 40.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
];
let mating_population = fitness_proportionate::roulette_wheel_selection(&population, 2, 2);
assert_ne!(mating_population.len(), 0);
}
#[test]
fn test_stochastic_universal_sampling() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let dna_6 = vec![Gene { id: 11 }, Gene { id: 12 }];
let dna_7 = vec![Gene { id: 13 }, Gene { id: 14 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 30.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 40.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_6 = Chromosome {
dna: dna_6,
fitness: 60.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_7 = Chromosome {
dna: dna_7,
fitness: 70.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
chromosome_6,
chromosome_7,
];
let mut found = false;
for _ in 0..10 {
let mating_population =
fitness_proportionate::stochastic_universal_sampling(&population, 3, 2);
if !mating_population.is_empty() {
found = true;
break;
}
}
assert!(
found,
"stochastic_universal_sampling produced no pairs after 10 attempts"
);
}
#[test]
fn test_tournament_singlethread() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 30.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 40.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
];
let mating_population = tournament::tournament(&population, 2, 1, 2);
assert_eq!(mating_population.len(), 2);
assert_ne!(mating_population.len(), 0);
}
#[test]
fn test_tournament_multithread() {
let dna_1 = vec![Gene { id: 1 }, Gene { id: 2 }];
let dna_2 = vec![Gene { id: 3 }, Gene { id: 4 }];
let dna_3 = vec![Gene { id: 5 }, Gene { id: 6 }];
let dna_4 = vec![Gene { id: 7 }, Gene { id: 8 }];
let dna_5 = vec![Gene { id: 9 }, Gene { id: 10 }];
let chromosome_1 = Chromosome {
dna: dna_1,
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_2 = Chromosome {
dna: dna_2,
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_3 = Chromosome {
dna: dna_3,
fitness: 30.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_4 = Chromosome {
dna: dna_4,
fitness: 40.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let chromosome_5 = Chromosome {
dna: dna_5,
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
};
let population = vec![
chromosome_1,
chromosome_2,
chromosome_3,
chromosome_4,
chromosome_5,
];
let mating_population = tournament::tournament(&population, 2, 2, 2);
assert_eq!(mating_population.len(), 2);
assert_ne!(mating_population.len(), 0);
}
#[test]
fn test_roulette_wheel_favours_higher_fitness() {
let mut chromosomes = Vec::new();
for i in 0..5 {
chromosomes.push(Chromosome {
dna: vec![Gene { id: i }],
fitness: if i == 4 { 1000.0 } else { 1.0 },
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
});
}
let mut high_fitness_count = 0;
let runs = 100;
for _ in 0..runs {
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
for group in &pairs {
if group[0] == 4 {
high_fitness_count += 1;
}
if group[1] == 4 {
high_fitness_count += 1;
}
}
}
assert!(
high_fitness_count > 300,
"High-fitness chromosome should be selected frequently, but was selected {} times out of {}",
high_fitness_count,
runs * 5
);
}
#[test]
fn test_roulette_wheel_returns_correct_pair_count() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 3, 2);
assert_eq!(pairs.len(), 3);
let chromosomes: Vec<Chromosome> = (0..5)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
assert_eq!(pairs.len(), 2);
}
#[test]
fn test_roulette_wheel_zero_total_fitness() {
let chromosomes: Vec<Chromosome> = (0..4)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
assert!(
pairs.is_empty(),
"Roulette wheel should return empty for zero total fitness"
);
}
#[test]
fn test_roulette_wheel_negative_fitness() {
let chromosomes: Vec<Chromosome> = (0..3)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: -10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
assert!(
pairs.is_empty(),
"Roulette wheel should return empty for negative total fitness"
);
}
#[test]
fn test_roulette_wheel_all_equal_fitness() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 3, 2);
assert_eq!(pairs.len(), 3);
for group in &pairs {
assert!(group[0] < 6);
assert!(group[1] < 6);
}
}
#[test]
fn test_sus_returns_requested_couple_count() {
let chromosomes: Vec<Chromosome> = (0..10)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 4, 2);
assert_eq!(pairs.len(), 4);
for group in &pairs {
assert!(group[0] < 10);
assert!(group[1] < 10);
}
}
#[test]
fn test_sus_more_couples_than_chromosomes() {
let chromosomes: Vec<Chromosome> = (0..3)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 10, 2);
assert_eq!(pairs.len(), 10);
for group in &pairs {
assert!(group[0] < 3);
assert!(group[1] < 3);
}
}
#[test]
fn test_sus_empty_population() {
let chromosomes: Vec<Chromosome> = vec![];
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 3, 2);
assert!(pairs.is_empty());
}
#[test]
fn test_sus_zero_couples() {
let chromosomes: Vec<Chromosome> = (0..5)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 0, 2);
assert!(pairs.is_empty());
}
#[test]
fn test_sus_zero_fitness_population() {
let chromosomes: Vec<Chromosome> = (0..5)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 3, 2);
assert!(
pairs.is_empty(),
"SUS should return empty for zero total fitness"
);
}
#[test]
fn test_sus_all_equal_fitness() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 3, 2);
assert_eq!(pairs.len(), 3);
}
#[test]
fn test_random_selection_can_select_last_individual() {
let chromosomes: Vec<Chromosome> = (0..2)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
for _ in 0..20 {
let pairs = random::random(&chromosomes, 2);
assert_eq!(pairs.len(), 1);
let (a, b) = (pairs[0][0], pairs[0][1]);
assert!((a == 0 && b == 1) || (a == 1 && b == 0));
}
}
#[test]
fn test_random_selection_all_individuals_selectable() {
let chromosomes: Vec<Chromosome> = (0..4)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let mut seen = [false; 4];
for _ in 0..100 {
let pairs = random::random(&chromosomes, 2);
for group in &pairs {
seen[group[0]] = true;
seen[group[1]] = true;
}
}
for (i, &was_seen) in seen.iter().enumerate() {
assert!(
was_seen,
"Index {} was never selected by random selection after 100 runs",
i
);
}
}
#[test]
fn test_random_selection_single_chromosome() {
let chromosomes = vec![Chromosome {
dna: vec![Gene { id: 0 }],
fitness: 0.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
}];
let pairs = random::random(&chromosomes, 2);
assert!(
pairs.is_empty(),
"Random selection with 1 chromosome should produce no pairs"
);
}
#[test]
fn test_selection_factory_rejects_nan_fitness() {
let chromosomes: Vec<Chromosome> = vec![
Chromosome {
dna: vec![Gene { id: 1 }],
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
},
Chromosome {
dna: vec![Gene { id: 2 }],
fitness: f64::NAN,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
},
Chromosome {
dna: vec![Gene { id: 3 }],
fitness: 20.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
},
];
let config = SelectionConfiguration {
method: Selection::Random,
number_of_couples: 1,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(
result.is_err(),
"Selection factory should reject NaN fitness"
);
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("NaN fitness"),
"Error should mention NaN fitness, got: {}",
err_msg
);
}
#[test]
fn test_selection_factory_accepts_valid_fitness() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::Random,
number_of_couples: 2,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(
result.is_ok(),
"Selection factory should accept valid fitness, got: {:?}",
result.err()
);
}
#[test]
fn test_selection_factory_empty_population() {
let chromosomes: Vec<Chromosome> = vec![];
let config = SelectionConfiguration {
method: Selection::Random,
number_of_couples: 1,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(
result.is_err(),
"Selection factory should reject empty population"
);
}
#[test]
fn test_selection_factory_single_chromosome() {
let chromosomes = vec![Chromosome {
dna: vec![Gene { id: 0 }],
fitness: 10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
}];
let config = SelectionConfiguration {
method: Selection::Random,
number_of_couples: 1,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(
result.is_err(),
"Selection factory should reject population of size 1"
);
}
#[test]
fn test_selection_factory_two_chromosomes_random() {
let chromosomes: Vec<Chromosome> = (0..2)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::Random,
number_of_couples: 1,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(
result.is_ok(),
"Two chromosomes should be valid for selection"
);
let pairs = result.unwrap();
assert_eq!(pairs.len(), 1);
}
#[test]
fn test_selection_factory_roulette_wheel() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::RouletteWheel,
number_of_couples: 2,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(result.is_ok());
}
#[test]
fn test_selection_factory_sus() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::StochasticUniversalSampling,
number_of_couples: 2,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(result.is_ok());
}
#[test]
fn test_selection_factory_tournament() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::Tournament,
number_of_couples: 2,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(result.is_ok());
}
#[test]
fn test_selection_factory_rank() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::Rank,
number_of_couples: 2,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 2);
assert!(result.is_ok());
}
#[test]
fn test_tournament_couples_zero() {
let chromosomes: Vec<Chromosome> = (0..5)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = tournament::tournament(&chromosomes, 0, 1, 2);
assert!(
pairs.is_empty(),
"Tournament with 0 couples should return empty"
);
}
#[test]
fn test_tournament_couples_exceed_population() {
let chromosomes: Vec<Chromosome> = (0..4)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = tournament::tournament(&chromosomes, 10, 1, 2);
assert!(pairs.len() <= 2, "Should clamp couples to population/2");
}
#[test]
fn test_tournament_population_of_two() {
let chromosomes: Vec<Chromosome> = (0..2)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = tournament::tournament(&chromosomes, 1, 1, 2);
assert_eq!(pairs.len(), 1);
}
#[test]
fn test_tournament_all_equal_fitness() {
let chromosomes: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = tournament::tournament(&chromosomes, 3, 1, 2);
assert_eq!(pairs.len(), 3);
for group in &pairs {
assert!(group[0] < 6);
assert!(group[1] < 6);
}
}
#[test]
fn test_random_selection_empty_population() {
let chromosomes: Vec<Chromosome> = vec![];
let pairs = random::random(&chromosomes, 2);
assert!(
pairs.is_empty(),
"Random selection with empty population should return empty"
);
}
#[test]
fn test_roulette_wheel_single_chromosome() {
let chromosomes = vec![Chromosome {
dna: vec![Gene { id: 0 }],
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
}];
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
assert_eq!(
pairs.len(),
2,
"Roulette wheel with 1 chromosome should return couples pairs"
);
}
#[test]
fn test_roulette_wheel_two_chromosomes() {
let chromosomes: Vec<Chromosome> = (0..2)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::roulette_wheel_selection(&chromosomes, 2, 2);
assert_eq!(pairs.len(), 2);
}
#[test]
fn test_sus_single_chromosome() {
let chromosomes = vec![Chromosome {
dna: vec![Gene { id: 0 }],
fitness: 50.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
}];
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 1, 2);
assert_eq!(
pairs.len(),
1,
"SUS with 1 chromosome and 1 couple should select it twice"
);
}
#[test]
fn test_sus_negative_fitness() {
let chromosomes: Vec<Chromosome> = (0..4)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: -10.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = fitness_proportionate::stochastic_universal_sampling(&chromosomes, 2, 2);
assert!(
pairs.is_empty(),
"SUS should return empty for negative total fitness"
);
}
#[test]
fn test_rank_selection_empty_population() {
let pop: Vec<Chromosome> = vec![];
let pairs = genetic_algorithms::operations::selection::rank::rank_selection(&pop, 3, 2);
assert!(
pairs.is_empty(),
"Rank selection with empty population should return empty"
);
}
#[test]
fn test_rank_selection_couples_zero() {
let pop: Vec<Chromosome> = (0..5)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = genetic_algorithms::operations::selection::rank::rank_selection(&pop, 0, 2);
assert!(
pairs.is_empty(),
"Rank selection with 0 couples should return empty"
);
}
#[test]
fn test_rank_selection_all_equal_fitness() {
let pop: Vec<Chromosome> = (0..6)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 42.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let pairs = genetic_algorithms::operations::selection::rank::rank_selection(&pop, 3, 2);
assert_eq!(pairs.len(), 3);
for group in &pairs {
assert!(group[0] < 6);
assert!(group[1] < 6);
}
}
#[test]
fn test_rank_selection_single_chromosome() {
let pop = vec![Chromosome {
dna: vec![Gene { id: 0 }],
fitness: 42.0,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
}];
let pairs = genetic_algorithms::operations::selection::rank::rank_selection(&pop, 1, 2);
assert!(
pairs.is_empty(),
"Rank selection with 1 chromosome should return empty"
);
}
#[test]
fn test_factory_returns_groups_of_num_parents() {
let chromosomes: Vec<Chromosome> = (0..10)
.map(|i| Chromosome {
dna: vec![Gene { id: i }],
fitness: 10.0 + i as f64,
age: 0,
fitness_fn: FitnessFnWrapper::default(),
fitness_values: vec![],
})
.collect();
let config = SelectionConfiguration {
method: Selection::Tournament,
number_of_couples: 4,
..Default::default()
};
let result = selection::factory(&chromosomes, config, 1, 3);
assert!(
result.is_ok(),
"factory with num_parents=3 should succeed: {:?}",
result.err()
);
let groups = result.unwrap();
assert!(!groups.is_empty(), "Should return at least one group");
for group in &groups {
assert_eq!(
group.len(),
3,
"Each group should have exactly 3 parents, got {} in {:?}",
group.len(),
group
);
for &idx in group {
assert!(idx < chromosomes.len(), "Index {} out of bounds", idx);
}
}
let config2 = SelectionConfiguration {
method: Selection::Tournament,
number_of_couples: 4,
..Default::default()
};
let result2 = selection::factory(&chromosomes, config2, 1, 2);
assert!(
result2.is_ok(),
"factory with num_parents=2 should succeed: {:?}",
result2.err()
);
let groups2 = result2.unwrap();
for group in &groups2 {
assert_eq!(
group.len(),
2,
"Each group should have exactly 2 parents, got {} in {:?}",
group.len(),
group
);
}
}
fn make_small_pop() -> Vec<Chromosome> {
vec![
Chromosome {
dna: vec![Gene { id: 1 }, Gene { id: 2 }],
fitness: 1.0,
age: 0,
fitness_fn: genetic_algorithms::fitness::FitnessFnWrapper::default(),
fitness_values: vec![1.0, 2.0],
},
Chromosome {
dna: vec![Gene { id: 3 }, Gene { id: 4 }],
fitness: 2.0,
age: 0,
fitness_fn: genetic_algorithms::fitness::FitnessFnWrapper::default(),
fitness_values: vec![2.0, 1.0],
},
]
}
#[test]
fn test_lexicase_selection_via_trait_returns_error() {
let pop = make_small_pop();
let result = Selection::Lexicase.select(&pop, 1, 1, 2);
assert!(
matches!(result, Err(GaError::SelectionError(_))),
"Selection::Lexicase via trait should return SelectionError, got {:?}",
result
);
}
#[test]
fn test_epsilon_lexicase_selection_via_trait_returns_error() {
let pop = make_small_pop();
let result = Selection::EpsilonLexicase.select(&pop, 1, 1, 2);
assert!(
matches!(result, Err(GaError::SelectionError(_))),
"Selection::EpsilonLexicase via trait should return SelectionError, got {:?}",
result
);
}