use crate::configuration::GaConfiguration;
use crate::error::GaError;
use crate::observer::{ExtensionEvent, GaObserver};
#[allow(deprecated)]
use crate::reporter::Reporter;
use crate::stats::GenerationStats;
use crate::traits::{FitnessFn, InitializationFn};
use crate::validators::validator_factory as ValidatorFactory;
use crate::{
configuration::{LimitConfiguration, LogLevel, ProblemSolving},
operations::{crossover, extension, mutation, selection, survivor, Extension},
population::Population,
traits::{
ChromosomeT, ConfigurationT, CrossoverConfig, ElitismConfig, ExtensionConfig, GeneT,
MutationConfig, NichingConfig, SelectionConfig, StoppingConfig,
},
};
use rand::Rng;
use rayon::prelude::*;
use std::fmt::Debug;
use std::ops::ControlFlow;
use std::sync::Arc;
use std::time::Instant;
#[cfg(feature = "serde")]
pub trait MaybeSerialize: serde::Serialize {}
#[cfg(feature = "serde")]
impl<T: serde::Serialize> MaybeSerialize for T {}
#[cfg(not(feature = "serde"))]
pub trait MaybeSerialize {}
#[cfg(not(feature = "serde"))]
impl<T> MaybeSerialize for T {}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TerminationCause {
GenerationLimitReached,
FitnessTargetReached,
StagnationReached,
ConvergenceReached,
TimeLimitReached,
CallbackRequested,
NotTerminated,
}
#[allow(deprecated)]
pub struct Ga<U>
where
U: ChromosomeT,
{
pub configuration: GaConfiguration,
pub alleles: Vec<U::Gene>,
pub population: Population<U>,
pub termination_cause: TerminationCause,
pub initialization_fn: Option<Arc<InitializationFn<U::Gene>>>,
pub fitness_fn: Option<Arc<FitnessFn<U::Gene>>>,
stats: Vec<GenerationStats>,
dynamic_mutation_probability: f64,
fitness_cache_size: Option<usize>,
reporter: Option<Box<dyn Reporter<U> + Send>>,
observer: Option<Arc<dyn GaObserver<U> + Send + Sync>>,
}
#[allow(deprecated)]
impl<U> Default for Ga<U>
where
U: ChromosomeT,
{
fn default() -> Self {
Ga {
configuration: GaConfiguration {
..Default::default()
},
population: Population::new_empty(),
alleles: Vec::new(),
termination_cause: TerminationCause::NotTerminated,
initialization_fn: None,
fitness_fn: None,
stats: Vec::new(),
dynamic_mutation_probability: 1.0,
fitness_cache_size: None,
reporter: None,
observer: None,
}
}
}
impl<U> SelectionConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_number_of_couples(mut self, number_of_couples: usize) -> Self {
self.configuration.selection_configuration.number_of_couples = number_of_couples;
self
}
fn with_selection_method(mut self, selection_method: crate::operations::Selection) -> Self {
self.configuration.selection_configuration.method = selection_method;
self
}
}
impl<U> CrossoverConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_crossover_number_of_points(mut self, number_of_points: usize) -> Self {
self.configuration.crossover_configuration.number_of_points = Some(number_of_points);
self
}
fn with_crossover_probability_max(mut self, probability_max: f64) -> Self {
self.configuration.crossover_configuration.probability_max = Some(probability_max);
self
}
fn with_crossover_probability_min(mut self, probability_min: f64) -> Self {
self.configuration.crossover_configuration.probability_min = Some(probability_min);
self
}
fn with_crossover_method(mut self, method: crossover::Crossover) -> Self {
self.configuration.crossover_configuration.method = method;
self
}
fn with_sbx_eta(mut self, eta: f64) -> Self {
self.configuration.crossover_configuration.sbx_eta = Some(eta);
self
}
fn with_blend_alpha(mut self, alpha: f64) -> Self {
self.configuration.crossover_configuration.blend_alpha = Some(alpha);
self
}
}
impl<U> MutationConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_mutation_probability_max(mut self, probability_max: f64) -> Self {
self.configuration.mutation_configuration.probability_max = Some(probability_max);
self
}
fn with_mutation_probability_min(mut self, probability_min: f64) -> Self {
self.configuration.mutation_configuration.probability_min = Some(probability_min);
self
}
fn with_mutation_method(mut self, method: crate::operations::Mutation) -> Self {
self.configuration.mutation_configuration.method = method;
self
}
fn with_mutation_step(mut self, step: f64) -> Self {
self.configuration.mutation_configuration.step = Some(step);
self
}
fn with_mutation_sigma(mut self, sigma: f64) -> Self {
self.configuration.mutation_configuration.sigma = Some(sigma);
self
}
fn with_dynamic_mutation(mut self, enabled: bool) -> Self {
self.configuration.mutation_configuration.dynamic_mutation = enabled;
self
}
fn with_mutation_target_cardinality(mut self, target: f64) -> Self {
self.configuration.mutation_configuration.target_cardinality = Some(target);
self
}
fn with_mutation_probability_step(mut self, step: f64) -> Self {
self.configuration.mutation_configuration.probability_step = Some(step);
self
}
}
impl<U> StoppingConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_max_generations(mut self, max_generations: usize) -> Self {
self.configuration.limit_configuration.max_generations = max_generations;
self
}
fn with_fitness_target(mut self, fitness_target: f64) -> Self {
self.configuration.limit_configuration.fitness_target = Some(fitness_target);
self
}
fn with_stopping_criteria(mut self, criteria: crate::configuration::StoppingCriteria) -> Self {
self.configuration.stopping_criteria = criteria;
self
}
}
impl<U> NichingConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_niching_enabled(mut self, enabled: bool) -> Self {
self.configuration
.niching_configuration
.get_or_insert_with(crate::niching::configuration::NichingConfiguration::default)
.enabled = enabled;
self
}
fn with_niching_sigma_share(mut self, sigma_share: f64) -> Self {
self.configuration
.niching_configuration
.get_or_insert_with(crate::niching::configuration::NichingConfiguration::default)
.sigma_share = sigma_share;
self
}
fn with_niching_alpha(mut self, alpha: f64) -> Self {
self.configuration
.niching_configuration
.get_or_insert_with(crate::niching::configuration::NichingConfiguration::default)
.alpha = alpha;
self
}
}
impl<U> ElitismConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_elitism(mut self, elitism_count: usize) -> Self {
self.configuration.elitism_count = elitism_count;
self
}
}
impl<U> ExtensionConfig for Ga<U>
where
U: ChromosomeT,
{
fn with_extension_method(mut self, method: crate::operations::Extension) -> Self {
self.configuration
.extension_configuration
.get_or_insert_with(
crate::extension::configuration::ExtensionConfiguration::default,
)
.method = method;
self
}
fn with_extension_diversity_threshold(mut self, threshold: f64) -> Self {
self.configuration
.extension_configuration
.get_or_insert_with(
crate::extension::configuration::ExtensionConfiguration::default,
)
.diversity_threshold = threshold;
self
}
fn with_extension_survival_rate(mut self, rate: f64) -> Self {
self.configuration
.extension_configuration
.get_or_insert_with(
crate::extension::configuration::ExtensionConfiguration::default,
)
.survival_rate = rate;
self
}
fn with_extension_mutation_rounds(mut self, rounds: usize) -> Self {
self.configuration
.extension_configuration
.get_or_insert_with(
crate::extension::configuration::ExtensionConfiguration::default,
)
.mutation_rounds = rounds;
self
}
fn with_extension_elite_count(mut self, count: usize) -> Self {
self.configuration
.extension_configuration
.get_or_insert_with(
crate::extension::configuration::ExtensionConfiguration::default,
)
.elite_count = count;
self
}
}
impl<U> ConfigurationT for Ga<U>
where
U: ChromosomeT,
{
fn new() -> Self {
Self::default()
}
fn with_adaptive_ga(mut self, adaptive_ga: bool) -> Self {
self.configuration.adaptive_ga = adaptive_ga;
self
}
fn with_threads(mut self, number_of_threads: usize) -> Self {
self.configuration.number_of_threads = number_of_threads;
self
}
fn with_logs(mut self, log_level: LogLevel) -> Self {
self.configuration.log_level = log_level;
self
}
fn with_survivor_method(mut self, method: crate::operations::Survivor) -> Self {
self.configuration.survivor = method;
self
}
fn with_problem_solving(mut self, problem_solving: ProblemSolving) -> Self {
self.configuration.limit_configuration.problem_solving = problem_solving;
self
}
fn with_population_size(mut self, population_size: usize) -> Self {
self.configuration.limit_configuration.population_size = population_size;
self.configuration.selection_configuration.number_of_couples =
if self.configuration.selection_configuration.number_of_couples == 0 {
self.configuration.limit_configuration.population_size / 2
} else {
self.configuration.selection_configuration.number_of_couples
};
self
}
fn with_genes_per_chromosome(mut self, genes_per_chromosome: usize) -> Self {
self.configuration.limit_configuration.genes_per_chromosome = genes_per_chromosome;
self
}
fn with_needs_unique_ids(mut self, needs_unique_ids: bool) -> Self {
self.configuration.limit_configuration.needs_unique_ids = needs_unique_ids;
self
}
fn with_alleles_can_be_repeated(mut self, alleles_can_be_repeated: bool) -> Self {
self.configuration
.limit_configuration
.alleles_can_be_repeated = alleles_can_be_repeated;
self
}
fn with_save_progress(mut self, save_progress: bool) -> Self {
self.configuration.save_progress_configuration.save_progress = save_progress;
self
}
fn with_save_progress_interval(mut self, save_progress_interval: usize) -> Self {
self.configuration
.save_progress_configuration
.save_progress_interval = save_progress_interval;
self
}
fn with_save_progress_path(mut self, save_progress_path: String) -> Self {
self.configuration
.save_progress_configuration
.save_progress_path = save_progress_path;
self
}
fn with_rng_seed(mut self, seed: u64) -> Self {
self.configuration.rng_seed = Some(seed);
self
}
}
impl<U> Ga<U>
where
U: ChromosomeT
+ Send
+ Sync
+ 'static
+ Clone
+ Debug
+ mutation::ValueMutable
+ MaybeSerialize,
U::Gene: 'static + Debug,
{
pub fn build(mut self) -> Result<Self, GaError> {
if self.configuration.selection_configuration.number_of_couples == 0
&& self.configuration.limit_configuration.population_size > 0
{
self.configuration.selection_configuration.number_of_couples =
self.configuration.limit_configuration.population_size / 2;
}
ValidatorFactory::validate::<U>(
Some(&self.configuration),
None,
if self.alleles.is_empty() {
None
} else {
Some(&self.alleles)
},
)?;
if let Some(cache_size) = self.fitness_cache_size {
if let Some(fitness_fn) = self.fitness_fn.take() {
self.fitness_fn =
Some(crate::fitness::cache::wrap_with_cache(fitness_fn, cache_size));
}
}
Ok(self)
}
pub fn with_alleles(mut self, alleles: Vec<U::Gene>) -> Self {
self.alleles = alleles;
self
}
pub fn with_population(mut self, population: Population<U>) -> Self {
if self.configuration.selection_configuration.number_of_couples == 0 {
self.configuration.selection_configuration.number_of_couples = population.size() / 2;
}
self.population = population;
self
}
pub fn with_fitness_fn<F>(mut self, fitness_fn: F) -> Self
where
F: Fn(&[U::Gene]) -> f64 + Send + Sync + 'static,
{
self.fitness_fn = Some(Arc::new(fitness_fn));
self
}
#[allow(deprecated)]
#[deprecated(
since = "2.2.0",
note = "use with_observer() instead. Reporter will be removed in v3.0.0."
)]
pub fn with_reporter(mut self, reporter: Box<dyn Reporter<U> + Send>) -> Self {
self.reporter = Some(reporter);
self
}
pub fn with_observer(mut self, observer: Arc<dyn GaObserver<U> + Send + Sync>) -> Self {
self.observer = Some(observer);
self
}
#[inline]
fn notify<F: FnOnce(&dyn GaObserver<U>)>(&self, f: F) {
if let Some(ref obs) = self.observer {
f(obs.as_ref());
}
}
pub fn with_fitness_cache_size(mut self, size: usize) -> Self {
self.fitness_cache_size = Some(size);
self
}
pub fn with_initialization_fn<F>(mut self, initialization_fn: F) -> Self
where
U: ChromosomeT + Send + Sync + 'static + Clone,
F: Fn(usize, Option<&[U::Gene]>, Option<bool>) -> Vec<U::Gene> + Send + Sync + 'static,
{
self.initialization_fn = Some(Arc::new(initialization_fn));
self
}
pub fn initialization(&mut self) -> Result<&mut Self, GaError>
where
U: ChromosomeT + Send + Sync + 'static + Clone,
{
if self.initialization_fn.is_none() {
return Err(GaError::InitializationError(
"No initialization function set".to_string(),
));
}
ValidatorFactory::validate::<U>(Some(&self.configuration), None, Some(&self.alleles))?;
let population_size = self.configuration.limit_configuration.population_size;
let genes_per_chromosome = self.configuration.limit_configuration.genes_per_chromosome;
let needs_unique_ids = self.configuration.limit_configuration.needs_unique_ids;
let init_fn = self.initialization_fn.as_ref().unwrap();
let fitness_fn = self.fitness_fn.as_ref().unwrap();
let chromosomes = crate::traits::initialize_chromosomes_par::<U>(
population_size,
genes_per_chromosome,
if self.alleles.is_empty() {
None
} else {
Some(&self.alleles)
},
Some(needs_unique_ids),
init_fn,
Some(fitness_fn),
0,
);
let new_population = Population::new(chromosomes);
if self.configuration.selection_configuration.number_of_couples == 0 {
self.configuration.selection_configuration.number_of_couples =
new_population.size() / 2;
}
self.population = new_population;
Ok(self)
}
pub fn run(&mut self) -> Result<&Population<U>, GaError> {
self.run_with_callback(
None::<
fn(&usize, &Population<U>, &GenerationStats, &TerminationCause) -> ControlFlow<()>,
>,
0,
)
}
#[allow(deprecated)]
pub fn run_with_callback<F>(
&mut self,
callback: Option<F>,
generations_to_callback: usize,
) -> Result<&Population<U>, GaError>
where
U: ChromosomeT + Send + Sync + 'static + Clone,
F: Fn(&usize, &Population<U>, &GenerationStats, &TerminationCause) -> ControlFlow<()>,
{
ValidatorFactory::validate::<U>(Some(&self.configuration), None, Some(&self.alleles))?;
crate::rng::set_seed(self.configuration.rng_seed);
if self.population.size() == 0 && self.initialization_fn.is_some() {
self.initialization()?;
} else if self.population.size() == 0 && self.initialization_fn.is_none() {
return Err(GaError::InitializationError(
"No initialization function set".to_string(),
));
}
let log_level = match self.configuration.log_level {
LogLevel::Off => log::LevelFilter::Off,
LogLevel::Error => log::LevelFilter::Error,
LogLevel::Warn => log::LevelFilter::Warn,
LogLevel::Info => log::LevelFilter::Info,
LogLevel::Debug => log::LevelFilter::Debug,
LogLevel::Trace => log::LevelFilter::Trace,
};
let _ = env_logger::Builder::from_default_env()
.filter_level(log_level)
.try_init();
if self.configuration.adaptive_ga {
self.population.recalculate_aga();
}
if self.configuration.mutation_configuration.dynamic_mutation {
self.dynamic_mutation_probability = self
.configuration
.mutation_configuration
.probability_max
.unwrap_or(1.0);
}
let initial_population_size = self.population.size();
let mut age = 0usize;
self.population.fitness_calculation(
self.configuration.number_of_threads,
self.configuration.limit_configuration.problem_solving,
);
let mut generation_callback_count = 0usize;
self.stats.clear();
let is_maximization = matches!(
self.configuration.limit_configuration.problem_solving,
ProblemSolving::Maximization
);
let start_time = Instant::now();
let mut best_fitness_so_far = self.population.best_chromosome.fitness();
let mut stagnation_count: usize = 0;
if let Some(ref mut r) = self.reporter {
r.on_start();
}
self.notify(|obs| obs.on_run_start());
for i in 0..self.configuration.limit_configuration.max_generations {
age += 1;
self.notify(|obs| obs.on_generation_start(i));
let t_sel = if self.observer.is_some() { Some(Instant::now()) } else { None };
let parents = selection::factory(
&self.population.chromosomes,
self.configuration.selection_configuration,
self.configuration.number_of_threads,
)?;
if let Some(t) = t_sel {
self.notify(|obs| obs.on_selection_complete(i, t.elapsed(), parents.len()));
}
let dynamic_prob = if self.configuration.mutation_configuration.dynamic_mutation {
Some(self.dynamic_mutation_probability)
} else {
None
};
let t_cx = if self.observer.is_some() { Some(Instant::now()) } else { None };
let mut offspring = parent_crossover(
&parents,
&self.population.chromosomes,
&self.configuration,
age,
self.population.f_max,
self.population.f_avg,
dynamic_prob,
)?;
if let Some(t) = t_cx {
let elapsed = t.elapsed();
let offspring_count = offspring.len();
let pop_size = self.population.chromosomes.len();
self.notify(|obs| obs.on_crossover_complete(i, elapsed, offspring_count));
self.notify(|obs| obs.on_mutation_complete(i, elapsed, pop_size));
self.notify(|obs| obs.on_fitness_evaluation_complete(i, elapsed, pop_size));
}
self.population.add_chromosomes(&mut offspring);
let elite = if self.configuration.elitism_count > 0 {
extract_elite(
&self.population.chromosomes,
self.configuration.elitism_count,
self.configuration.limit_configuration.problem_solving,
)
} else {
Vec::new()
};
let t_surv = if self.observer.is_some() { Some(Instant::now()) } else { None };
survivor::factory(
self.configuration.survivor,
&mut self.population.chromosomes,
initial_population_size,
self.configuration.limit_configuration,
)?;
if let Some(t) = t_surv {
let pop_size = self.population.chromosomes.len();
self.notify(|obs| obs.on_survivor_selection_complete(i, t.elapsed(), pop_size));
}
if !elite.is_empty() {
reinsert_elite(
&mut self.population.chromosomes,
elite,
self.configuration.limit_configuration.problem_solving,
);
}
if self.configuration.adaptive_ga {
self.population.recalculate_aga();
}
if let Some(ref niching_config) = self.configuration.niching_configuration {
if niching_config.enabled {
let mut fitness_values: Vec<f64> = self
.population
.chromosomes
.iter()
.map(|c| c.fitness())
.collect();
let dna_slices: Vec<&[U::Gene]> = self
.population
.chromosomes
.iter()
.map(|c| c.dna())
.collect();
let distances = crate::niching::sharing::compute_distance_matrix(
&dna_slices,
|dna_a: &[U::Gene], dna_b: &[U::Gene]| {
let max_len = dna_a.len().max(dna_b.len());
if max_len == 0 {
return 0.0;
}
let mut diff = 0usize;
for idx in 0..max_len {
let id_a = dna_a.get(idx).map(|g| g.id()).unwrap_or(-1);
let id_b = dna_b.get(idx).map(|g| g.id()).unwrap_or(-1);
if id_a != id_b {
diff += 1;
}
}
diff as f64
},
);
crate::niching::sharing::apply_fitness_sharing(
&mut fitness_values,
&distances,
niching_config.sigma_share,
niching_config.alpha,
);
for (chromosome, &shared_fitness) in self
.population
.chromosomes
.iter_mut()
.zip(fitness_values.iter())
{
chromosome.set_fitness(shared_fitness);
}
}
}
{
let ps = self.configuration.limit_configuration.problem_solving;
let chromosomes = &self.population.chromosomes;
if let Some(best_idx) = best_chromosome_index(chromosomes, ps) {
if !self.population.best_chromosome_is_set {
self.population.best_chromosome =
self.population.chromosomes[best_idx].clone();
self.population.best_chromosome_is_set = true;
} else {
let candidate = self.population.chromosomes[best_idx].fitness();
let current = self.population.best_chromosome.fitness();
let better = match ps {
ProblemSolving::Maximization | ProblemSolving::FixedFitness => {
candidate > current
}
ProblemSolving::Minimization => candidate < current,
};
if better {
self.population.best_chromosome =
self.population.chromosomes[best_idx].clone();
}
}
}
}
let fitness_values: Vec<f64> = self
.population
.chromosomes
.iter()
.map(|c| c.fitness())
.collect();
let gen_stats =
GenerationStats::from_fitness_values(i, &fitness_values, is_maximization);
self.stats.push(gen_stats.clone());
if self.configuration.mutation_configuration.dynamic_mutation {
let target = self
.configuration
.mutation_configuration
.target_cardinality
.unwrap_or(0.5);
let step = self
.configuration
.mutation_configuration
.probability_step
.unwrap_or(0.01);
let p_max = self
.configuration
.mutation_configuration
.probability_max
.unwrap_or(1.0);
let p_min = self
.configuration
.mutation_configuration
.probability_min
.unwrap_or(0.0);
self.dynamic_mutation_probability = mutation::dynamic_probability(
self.dynamic_mutation_probability,
gen_stats.diversity,
target,
step,
p_max,
p_min,
);
if let Some(last) = self.stats.last_mut() {
last.dynamic_mutation_probability = Some(self.dynamic_mutation_probability);
}
}
if let Some(ref ext_config) = self.configuration.extension_configuration {
if ext_config.method != Extension::Noop
&& gen_stats.diversity < ext_config.diversity_threshold
{
extension::factory(
ext_config.method,
&mut self.population.chromosomes,
initial_population_size,
self.configuration.limit_configuration.problem_solving,
ext_config,
)?;
self.notify(|obs| obs.on_extension_triggered(ExtensionEvent {
generation: i,
diversity: gen_stats.diversity,
extension_type: ext_config.method.as_str(),
threshold: ext_config.diversity_threshold,
}));
if self.population.chromosomes.len() < initial_population_size {
if let Some(ref init_fn) = self.initialization_fn {
let deficit =
initial_population_size - self.population.chromosomes.len();
for _ in 0..deficit {
let alleles_ref = if self.alleles.is_empty() {
None
} else {
Some(self.alleles.as_slice())
};
let genes = init_fn(
self.configuration
.limit_configuration
.genes_per_chromosome,
alleles_ref,
Some(
self.configuration
.limit_configuration
.alleles_can_be_repeated,
),
);
let mut new_chromosome = U::new();
new_chromosome.set_dna(std::borrow::Cow::Owned(genes));
if let Some(ref ff) = self.fitness_fn {
let ff_clone = Arc::clone(ff);
new_chromosome
.set_fitness_fn(move |genes| ff_clone(genes));
}
new_chromosome.calculate_fitness();
new_chromosome.set_age(0);
self.population.chromosomes.push(new_chromosome);
}
}
}
for c in self.population.chromosomes.iter_mut() {
if c.fitness().is_nan() {
c.calculate_fitness();
}
}
}
}
if let Some(ref mut r) = self.reporter {
r.on_generation_complete(&gen_stats);
}
let notify_stats = self.stats.last().cloned().unwrap_or(gen_stats.clone());
self.notify(|obs| obs.on_generation_end(¬ify_stats));
#[cfg(feature = "serde")]
{
let spc = &self.configuration.save_progress_configuration;
if spc.save_progress
&& spc.save_progress_interval > 0
&& (i + 1) % spc.save_progress_interval == 0
{
let ckpt = crate::checkpoint::Checkpoint {
population: self.population.clone(),
configuration: self.configuration.clone(),
generation: i,
stats: self.stats.clone(),
};
let path = std::path::Path::new(&spc.save_progress_path)
.join(format!("checkpoint_gen_{}.json", i + 1));
if let Err(e) = crate::checkpoint::save_checkpoint(&ckpt, &path) {
log::warn!("Failed to save checkpoint at generation {}: {}", i + 1, e);
}
}
}
if let Some(func) = &callback {
if (generation_callback_count + 1) == generations_to_callback {
if func(&i, &self.population, &gen_stats, &self.termination_cause).is_break() {
self.termination_cause = TerminationCause::CallbackRequested;
break;
}
generation_callback_count = 0;
} else {
generation_callback_count += 1;
}
}
if limit_reached(
self.configuration.limit_configuration,
&self.population.chromosomes,
) {
self.termination_cause = TerminationCause::FitnessTargetReached;
if let Some(func) = &callback {
let _ = func(&i, &self.population, &gen_stats, &self.termination_cause);
}
break;
}
let current_best = self.population.best_chromosome.fitness();
let improved = match self.configuration.limit_configuration.problem_solving {
ProblemSolving::Maximization => current_best > best_fitness_so_far,
ProblemSolving::Minimization => current_best < best_fitness_so_far,
_ => (current_best - best_fitness_so_far).abs() > f64::EPSILON,
};
if improved {
best_fitness_so_far = current_best;
stagnation_count = 0;
if let Some(ref mut r) = self.reporter {
r.on_new_best(i, self.population.best_chromosome.clone());
}
self.notify(|obs| obs.on_new_best(i, self.population.best_chromosome.clone()));
} else {
stagnation_count += 1;
self.notify(|obs| obs.on_stagnation(i, stagnation_count));
}
if let Some(max_stagnation) =
self.configuration.stopping_criteria.stagnation_generations
{
if stagnation_count >= max_stagnation {
self.termination_cause = TerminationCause::StagnationReached;
if let Some(func) = &callback {
let _ = func(&i, &self.population, &gen_stats, &self.termination_cause);
}
break;
}
}
if let Some(threshold) = self.configuration.stopping_criteria.convergence_threshold {
if gen_stats.fitness_std_dev < threshold {
self.termination_cause = TerminationCause::ConvergenceReached;
if let Some(func) = &callback {
let _ = func(&i, &self.population, &gen_stats, &self.termination_cause);
}
break;
}
}
if let Some(max_secs) = self.configuration.stopping_criteria.max_duration_secs {
if start_time.elapsed().as_secs_f64() >= max_secs {
self.termination_cause = TerminationCause::TimeLimitReached;
if let Some(func) = &callback {
let _ = func(&i, &self.population, &gen_stats, &self.termination_cause);
}
break;
}
}
}
if self.termination_cause == TerminationCause::NotTerminated {
self.termination_cause = TerminationCause::GenerationLimitReached;
}
if let Some(ref mut r) = self.reporter {
r.on_finish(self.termination_cause, &self.stats);
}
self.notify(|obs| obs.on_run_end(self.termination_cause, &self.stats));
if let Some(func) = &callback {
if self.termination_cause == TerminationCause::GenerationLimitReached {
let final_stats = self.stats.last().cloned().unwrap_or_else(|| {
GenerationStats::from_fitness_values(0, &[], is_maximization)
});
let _ = func(
&self.configuration.limit_configuration.max_generations,
&self.population,
&final_stats,
&self.termination_cause,
);
}
}
Ok(&self.population)
}
pub fn stats(&self) -> &[GenerationStats] {
&self.stats
}
}
fn limit_reached<U>(limit: LimitConfiguration, chromosomes: &[U]) -> bool
where
U: ChromosomeT,
{
let mut result = false;
if limit.problem_solving == ProblemSolving::Minimization {
for chromosome in chromosomes {
if chromosome.fitness() == 0.0 {
result = true;
break;
}
}
} else if limit.problem_solving == ProblemSolving::FixedFitness {
if let Some(target) = limit.fitness_target {
for chromosome in chromosomes {
if chromosome.fitness() == target {
result = true;
break;
}
}
}
}
result
}
fn parent_crossover<U>(
parents: &[(usize, usize)],
chromosomes: &[U],
configuration: &GaConfiguration,
age: usize,
f_max: f64,
f_avg: f64,
dynamic_mutation_prob: Option<f64>,
) -> Result<Vec<U>, GaError>
where
U: ChromosomeT + Send + Sync + 'static + Clone + mutation::ValueMutable,
{
let crossover_probability_config =
if let Some(p) = configuration.crossover_configuration.probability_max {
if !configuration.adaptive_ga {
Some(p)
} else {
None
}
} else {
Some(1.0)
};
let mutation_probability_config = if let Some(dp) = dynamic_mutation_prob {
Some(dp)
} else if let Some(p) = configuration.mutation_configuration.probability_max {
if !configuration.adaptive_ga {
Some(p)
} else {
None
}
} else {
Some(1.0)
};
let results: Vec<Result<Vec<U>, GaError>> = parents
.par_iter()
.map(|(key, value)| {
let mut rng = crate::rng::make_rng();
let parent_1 = chromosomes.get(*key).ok_or_else(|| {
GaError::SelectionError(format!(
"Selection returned out-of-bounds index {} (population size {})",
key,
chromosomes.len()
))
})?.clone();
let parent_2 = chromosomes.get(*value).ok_or_else(|| {
GaError::SelectionError(format!(
"Selection returned out-of-bounds index {} (population size {})",
value,
chromosomes.len()
))
})?.clone();
let crossover_probability = rng.random_range(0.0..1.0);
let effective_crossover_prob =
if let Some(p) = crossover_probability_config {
p
} else {
crossover::aga_probability(
&parent_1,
&parent_2,
f_max,
f_avg,
configuration.crossover_configuration.probability_max.unwrap_or(1.0),
configuration.crossover_configuration.probability_min.unwrap_or(0.0),
)
};
let mut mutation_probability = rng.random_range(0.0..1.0);
let effective_mutation_prob =
if let Some(p) = mutation_probability_config {
p
} else {
mutation::aga_probability(
&parent_1,
&parent_2,
f_avg,
configuration.mutation_configuration.probability_max.unwrap_or(1.0),
configuration.mutation_configuration.probability_min.unwrap_or(0.0),
)
};
let mut child_1: U;
let mut child_2: U;
if crossover_probability <= effective_crossover_prob {
let mut children = crossover::factory(&parent_1, &parent_2, configuration.crossover_configuration)?;
child_2 = children.pop().ok_or_else(|| {
GaError::CrossoverError("Crossover returned fewer than 2 children".to_string())
})?;
child_1 = children.pop().ok_or_else(|| {
GaError::CrossoverError("Crossover returned fewer than 2 children".to_string())
})?;
} else {
child_1 = parent_1;
child_2 = parent_2;
}
if mutation_probability < effective_mutation_prob {
mutation::factory_with_params(
configuration.mutation_configuration.method,
&mut child_1,
configuration.mutation_configuration.step,
configuration.mutation_configuration.sigma,
)?;
}
mutation_probability = rng.random_range(0.0..1.0);
if mutation_probability <= effective_mutation_prob {
mutation::factory_with_params(
configuration.mutation_configuration.method,
&mut child_2,
configuration.mutation_configuration.step,
configuration.mutation_configuration.sigma,
)?;
}
child_1.calculate_fitness();
child_2.calculate_fitness();
child_1.set_age(age);
child_2.set_age(age);
Ok(vec![child_1, child_2])
})
.collect();
let mut offspring = Vec::new();
for result in results {
offspring.extend(result?);
}
Ok(offspring)
}
fn extract_elite<U: ChromosomeT>(
chromosomes: &[U],
count: usize,
problem_solving: ProblemSolving,
) -> Vec<U> {
if count == 0 || chromosomes.is_empty() {
return Vec::new();
}
let k = count.min(chromosomes.len());
let mut indices: Vec<usize> = (0..chromosomes.len()).collect();
let cmp_fn = |a: &usize, b: &usize| {
let cmp = chromosomes[*a]
.fitness()
.partial_cmp(&chromosomes[*b].fitness())
.unwrap_or(std::cmp::Ordering::Equal);
match problem_solving {
ProblemSolving::Maximization => cmp.reverse(),
_ => cmp,
}
};
indices.select_nth_unstable_by(k - 1, cmp_fn);
indices.truncate(k);
indices.iter().map(|&i| chromosomes[i].clone()).collect()
}
fn reinsert_elite<U: ChromosomeT>(
chromosomes: &mut [U],
elite: Vec<U>,
problem_solving: ProblemSolving,
) {
chromosomes.sort_by(|a, b| {
let cmp = a
.fitness()
.partial_cmp(&b.fitness())
.unwrap_or(std::cmp::Ordering::Equal);
match problem_solving {
ProblemSolving::Maximization => cmp.reverse(),
_ => cmp,
}
});
let pop_len = chromosomes.len();
let count = elite.len().min(pop_len);
for (i, elite_individual) in elite.into_iter().take(count).enumerate() {
let replace_idx = pop_len - 1 - i;
chromosomes[replace_idx] = elite_individual;
}
}
fn best_chromosome_index<U: ChromosomeT>(
chromosomes: &[U],
problem_solving: ProblemSolving,
) -> Option<usize> {
if chromosomes.is_empty() {
return None;
}
let mut best = 0;
let mut best_fit = chromosomes[0].fitness();
for (i, c) in chromosomes.iter().enumerate().skip(1) {
let f = c.fitness();
let is_better = match problem_solving {
ProblemSolving::Maximization | ProblemSolving::FixedFitness => f > best_fit,
ProblemSolving::Minimization => f < best_fit,
};
if is_better {
best = i;
best_fit = f;
}
}
Some(best)
}