use crate::configuration::SelectionConfiguration;
use crate::error::GaError;
use crate::traits::{ChromosomeT, SelectionOperator, VectorFitness};
pub use self::boltzmann::boltzmann_selection;
pub use self::clearing::clearing_selection;
pub use self::fitness_proportionate::roulette_wheel_selection;
pub use self::fitness_proportionate::stochastic_universal_sampling;
pub use self::lexicase::{epsilon_lexicase_selection, lexicase_selection};
pub use self::random::random;
pub use self::rank::rank_selection;
pub use self::tournament::tournament;
pub use self::truncation::truncation_selection;
use super::Selection;
pub mod boltzmann;
pub mod clearing;
pub mod fitness_proportionate;
pub mod lexicase;
pub mod random;
pub mod rank;
pub mod tournament;
pub mod truncation;
impl SelectionOperator for Selection {
fn select<U>(
&self,
chromosomes: &[U],
number_of_couples: usize,
number_of_threads: usize,
num_parents: usize,
) -> Result<Vec<Vec<usize>>, GaError>
where
U: ChromosomeT + Sync + Send + 'static + Clone,
{
match self {
Selection::Random => Ok(random(chromosomes, num_parents)),
Selection::RouletteWheel => Ok(roulette_wheel_selection(
chromosomes,
number_of_couples,
num_parents,
)),
Selection::StochasticUniversalSampling => Ok(stochastic_universal_sampling(
chromosomes,
number_of_couples,
num_parents,
)),
Selection::Tournament => Ok(tournament(
chromosomes,
number_of_couples,
number_of_threads,
num_parents,
)),
Selection::Rank => Ok(rank_selection(chromosomes, number_of_couples, num_parents)),
Selection::Boltzmann => Ok(boltzmann_selection(
chromosomes,
number_of_couples,
1.0,
num_parents,
)),
Selection::Truncation => Ok(truncation_selection(
chromosomes,
number_of_couples,
num_parents,
)),
Selection::Clearing => {
crate::log_warn!(target: "selection_events",
"Selection::Clearing called through SelectionOperator trait: \
niche_radius defaults to 0.1 (configured value ignored). \
Use selection::factory for the full configuration.");
Ok(clearing_selection(
chromosomes,
0.1,
number_of_couples,
num_parents,
))
}
Selection::Lexicase | Selection::EpsilonLexicase => Err(GaError::SelectionError(
"Selection::Lexicase/EpsilonLexicase cannot be called through the \
SelectionOperator trait: use selection::factory_lexicase. \
Island-model and NSGA-II paths do not support VectorFitness."
.to_string(),
)),
}
}
}
pub fn factory<U>(
chromosomes: &[U],
configuration: SelectionConfiguration,
number_of_threads: usize,
num_parents: usize,
) -> Result<Vec<Vec<usize>>, GaError>
where
U: ChromosomeT + Sync + Send + 'static + Clone,
{
if chromosomes.len() < 2 {
return Err(GaError::SelectionError(format!(
"Population size {} is too small for selection (minimum 2)",
chromosomes.len()
)));
}
for (i, chromosome) in chromosomes.iter().enumerate() {
if chromosome.fitness().is_nan() {
return Err(GaError::SelectionError(format!(
"Chromosome at index {} has NaN fitness. All chromosomes must have valid fitness before selection.",
i
)));
}
}
let groups = match configuration.method {
Selection::Boltzmann => boltzmann_selection(
chromosomes,
configuration.number_of_couples,
configuration.boltzmann_temperature,
num_parents,
),
Selection::Clearing => clearing_selection(
chromosomes,
configuration.niche_radius,
configuration.number_of_couples,
num_parents,
),
Selection::Lexicase | Selection::EpsilonLexicase => {
return Err(GaError::ConfigurationError(
"Use selection::factory_lexicase for Lexicase/EpsilonLexicase; \
standard factory() does not support VectorFitness bound."
.into(),
));
}
_ => configuration.method.select(
chromosomes,
configuration.number_of_couples,
number_of_threads,
num_parents,
)?,
};
Ok(groups)
}
pub fn factory_lexicase<U>(
chromosomes: &mut [U],
configuration: SelectionConfiguration,
_number_of_threads: usize,
) -> Result<Vec<Vec<usize>>, GaError>
where
U: ChromosomeT + VectorFitness + Sync + Send + 'static + Clone,
{
if chromosomes.len() < 2 {
return Err(GaError::SelectionError(
"Population must have at least 2 chromosomes".into(),
));
}
if chromosomes[0].fitness_values().is_empty() {
return Err(GaError::SelectionError(
"fitness_values() is empty — call set_fitness_values in calculate_fitness".into(),
));
}
for (i, c) in chromosomes.iter().enumerate() {
if c.fitness_values().iter().any(|&s| s.is_nan()) {
return Err(GaError::SelectionError(format!(
"NaN in fitness_values at chromosome {}",
i
)));
}
}
let groups = match configuration.method {
Selection::Lexicase => lexicase_selection(chromosomes, configuration.number_of_couples, 2),
Selection::EpsilonLexicase => epsilon_lexicase_selection(
chromosomes,
configuration.number_of_couples,
if configuration.epsilon == 0.0 {
None
} else {
Some(configuration.epsilon)
},
2,
),
_ => {
return Err(GaError::ConfigurationError(
"factory_lexicase called with non-lexicase selection method".into(),
));
}
};
for c in chromosomes.iter_mut() {
let scores = c.fitness_values().to_vec();
if !scores.is_empty() {
let mean = scores.iter().sum::<f64>() / scores.len() as f64;
c.set_fitness(mean);
}
}
Ok(groups)
}