use crate::configuration::{GaConfiguration, ProblemSolving};
use crate::error::GaError;
use crate::operations;
use crate::population::Population;
use crate::traits::{GeneT, LinearChromosome, OperatorCompat};
use crate::chromosomes::ChromosomeLength;
use std::collections::HashSet;
pub fn validate<U>(
configuration: Option<&GaConfiguration>,
population: Option<&Population<U>>,
_alleles: Option<&[U::Gene]>,
) -> Result<(), GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
U::Gene: 'static,
{
if let Some(population) = population {
same_dna_length(population)?;
}
if let Some(configuration) = configuration {
if configuration.limit_configuration.problem_solving == ProblemSolving::FixedFitness {
fitness_target_is_some(
configuration,
configuration
.limit_configuration
.problem_solving
.to_string(),
)?;
}
if let Some(population) = population {
if configuration.crossover_configuration.method == operations::Crossover::Cycle {
unique_gene_ids(population)?;
}
}
if configuration.adaptive_ga {
aga_crossover_probabilities(configuration)?;
}
number_of_couples_is_set(configuration)?;
validate_chromosome_length(configuration)?;
validate_length_penalty(configuration)?;
}
Ok(())
}
pub fn unique_gene_ids<U>(population: &Population<U>) -> Result<(), GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
{
for (chromosome_number, chromosome) in population.chromosomes.iter().enumerate() {
let mut seen = HashSet::with_capacity(chromosome.dna().len());
for (gene_number, gene) in chromosome.dna().iter().enumerate() {
if !seen.insert(gene.id()) {
return Err(GaError::ValidationError(format!(
"Gene id must be unique within the DNA. The chromosome #{} has a duplicate gene id {} at gene #{}",
chromosome_number, gene.id(), gene_number
)));
}
}
}
Ok(())
}
pub fn fitness_target_is_some(
configuration: &GaConfiguration,
problem_type: String,
) -> Result<(), GaError> {
if configuration.limit_configuration.fitness_target.is_none() {
return Err(GaError::ConfigurationError(format!(
"For {} problems, fitness_target must be set.",
problem_type
)));
}
Ok(())
}
pub fn same_dna_length<U>(population: &Population<U>) -> Result<(), GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
{
let Some(first) = population.chromosomes.first() else {
return Ok(());
};
let expected_len = first.dna().len();
for (i, chromosome) in population.chromosomes.iter().enumerate().skip(1) {
let len = chromosome.dna().len();
if len != expected_len {
return Err(GaError::ValidationError(format!(
"All the chromosomes must have the same dna length. Chromosome #0 has a dna with length {} and chromosome #{} has a dna with length {}.",
expected_len, i, len
)));
}
}
Ok(())
}
pub fn chromosome_length_not_bigger_than_alleles<U>(
alleles: &[U::Gene],
genes_per_chromosome: usize,
) -> Result<(), GaError>
where
U: LinearChromosome + Send + Sync + 'static + Clone,
{
if genes_per_chromosome > alleles.len() {
return Err(GaError::ConfigurationError(
"The number of genes within a chromosome should not be higher than the different alleles.".to_string()));
}
Ok(())
}
pub fn aga_crossover_probabilities(configuration: &GaConfiguration) -> Result<(), GaError> {
if configuration
.crossover_configuration
.probability_max
.is_none()
|| configuration
.crossover_configuration
.probability_min
.is_none()
{
return Err(GaError::ConfigurationError(
"For Adaptive Genetic Algorithms, the probability_max and probability_min in the crossover_configuration are mandatory.".to_string()));
} else if configuration.crossover_configuration.probability_max
<= configuration.crossover_configuration.probability_min
{
return Err(GaError::ConfigurationError(
"For Adaptive Genetic Algorithms, the probability_max must be greater than probability_min in the crossover_configuration.".to_string()));
}
Ok(())
}
pub fn number_of_couples_is_set(configuration: &GaConfiguration) -> Result<(), GaError> {
if configuration.selection_configuration.number_of_couples == 0 {
return Err(GaError::ConfigurationError(
"The number of couples must be set.".to_string(),
));
}
Ok(())
}
pub fn operator_compat_check<U>(configuration: &GaConfiguration) -> Result<(), GaError>
where
U: LinearChromosome + OperatorCompat + Send + Sync + 'static + Clone,
{
if let Some(valid) = U::valid_crossovers() {
if !valid.contains(&configuration.crossover_configuration.method) {
return Err(GaError::ConfigurationError(format!(
"Crossover::{:?} is not valid for this chromosome type. Valid crossovers: {:?}",
configuration.crossover_configuration.method, valid
)));
}
}
if let Some(valid) = U::valid_mutations() {
if !valid.contains(&configuration.mutation_configuration.method) {
return Err(GaError::ConfigurationError(format!(
"Mutation::{:?} is not valid for this chromosome type. Valid mutations: {:?}",
configuration.mutation_configuration.method, valid
)));
}
}
Ok(())
}
pub fn validate_chromosome_length(configuration: &GaConfiguration) -> Result<(), GaError> {
match configuration.limit_configuration.chromosome_length {
ChromosomeLength::Variable { min, max } => {
if min == 0 {
return Err(GaError::ConfigurationError(
"ChromosomeLength::Variable requires min >= 1, got min = 0".to_string(),
));
}
if min > max {
return Err(GaError::ConfigurationError(format!(
"ChromosomeLength::Variable requires min <= max, got min = {}, max = {}",
min, max
)));
}
}
ChromosomeLength::Fixed(_) => {}
}
Ok(())
}
pub fn validate_length_penalty(configuration: &GaConfiguration) -> Result<(), GaError> {
if let Some(penalty) = configuration.length_penalty {
if penalty < 0.0 {
return Err(GaError::ConfigurationError(format!(
"length_penalty must be >= 0.0, got {}",
penalty
)));
}
}
Ok(())
}