pub mod configuration;
pub mod migration;
pub mod nsga2;
pub mod topology;
use crate::configuration::{GaConfiguration, ProblemSolving};
use crate::error::GaError;
use crate::island::configuration::IslandConfiguration;
use crate::island::migration::migrate;
use crate::observer::IslandGaObserver;
use crate::operations::mutation;
use crate::population::Population;
use crate::stats::GenerationStats;
use crate::traits::{ChromosomeT, FitnessFn, InitializationFn};
use std::sync::Arc;
pub struct IslandGa<U>
where
U: ChromosomeT,
{
pub island_config: IslandConfiguration,
pub ga_configs: Vec<GaConfiguration>,
pub islands: Vec<Population<U>>,
pub alleles: Vec<U::Gene>,
pub initialization_fn: Option<Arc<InitializationFn<U::Gene>>>,
pub fitness_fn: Option<Arc<FitnessFn<U::Gene>>>,
observer: Option<Arc<dyn IslandGaObserver<U> + Send + Sync>>,
}
impl<U> IslandGa<U>
where
U: ChromosomeT,
{
pub fn new(island_config: IslandConfiguration, ga_config: GaConfiguration) -> Self {
IslandGa {
island_config,
ga_configs: vec![ga_config],
islands: Vec::new(),
alleles: Vec::new(),
initialization_fn: None,
fitness_fn: None,
observer: None,
}
}
pub fn with_heterogeneous_configs(
island_config: IslandConfiguration,
configs: Vec<GaConfiguration>,
) -> Self {
IslandGa {
island_config,
ga_configs: configs,
islands: Vec::new(),
alleles: Vec::new(),
initialization_fn: None,
fitness_fn: None,
observer: None,
}
}
pub fn config_for_island(&self, island_index: usize) -> &GaConfiguration {
if island_index < self.ga_configs.len() {
&self.ga_configs[island_index]
} else {
self.ga_configs
.last()
.expect("ga_configs must not be empty")
}
}
pub fn with_alleles(mut self, alleles: Vec<U::Gene>) -> Self {
self.alleles = alleles;
self
}
pub fn with_initialization_fn<F>(mut self, f: F) -> Self
where
F: Fn(usize, Option<&[U::Gene]>, Option<bool>) -> Vec<U::Gene> + Send + Sync + 'static,
{
self.initialization_fn = Some(Arc::new(f));
self
}
pub fn with_fitness_fn<F>(mut self, f: F) -> Self
where
F: Fn(&[U::Gene]) -> f64 + Send + Sync + 'static,
{
self.fitness_fn = Some(Arc::new(f));
self
}
pub fn with_observer(mut self, obs: Arc<dyn IslandGaObserver<U> + Send + Sync>) -> Self {
self.observer = Some(obs);
self
}
#[inline]
fn notify<F: FnOnce(&dyn IslandGaObserver<U>)>(&self, f: F) {
if let Some(ref obs) = self.observer {
f(obs.as_ref());
}
}
pub fn build(self) -> Result<Self, GaError> {
self.validate()?;
Ok(self)
}
pub fn validate(&self) -> Result<(), GaError> {
if self.island_config.num_islands == 0 {
return Err(GaError::InvalidIslandConfiguration(
"num_islands must be > 0".to_string(),
));
}
if self.island_config.migration_interval == 0 {
return Err(GaError::InvalidIslandConfiguration(
"migration_interval must be > 0".to_string(),
));
}
if self.island_config.migration_count == 0 {
return Err(GaError::InvalidIslandConfiguration(
"migration_count must be > 0".to_string(),
));
}
if self.ga_configs.is_empty() {
return Err(GaError::InvalidIslandConfiguration(
"ga_configs must not be empty".to_string(),
));
}
if self.initialization_fn.is_none() {
return Err(GaError::InvalidIslandConfiguration(
"initialization_fn is required".to_string(),
));
}
if self.fitness_fn.is_none() {
return Err(GaError::InvalidIslandConfiguration(
"fitness_fn is required".to_string(),
));
}
for (i, config) in self.ga_configs.iter().enumerate() {
let pop_size = config.limit_configuration.population_size;
if self.island_config.migration_count >= pop_size {
return Err(GaError::InvalidIslandConfiguration(format!(
"migration_count ({}) must be < population_size ({}) for config index {}",
self.island_config.migration_count, pop_size, i
)));
}
}
Ok(())
}
pub fn initialize(&mut self) -> Result<(), GaError> {
let init_fn = self.initialization_fn.as_ref().ok_or_else(|| {
GaError::InitializationError("No initialization function set".to_string())
})?;
let fitness_fn = self
.fitness_fn
.as_ref()
.ok_or_else(|| GaError::InitializationError("No fitness function set".to_string()))?;
let num_islands = self.island_config.num_islands;
let alleles = if self.alleles.is_empty() {
None
} else {
Some(self.alleles.as_slice())
};
self.islands = Vec::with_capacity(num_islands);
for island_idx in 0..num_islands {
let cfg = self.config_for_island(island_idx);
let pop_size = cfg.limit_configuration.population_size;
let genes_per_chrom = cfg.limit_configuration.genes_per_chromosome;
let alleles_can_repeat = cfg.limit_configuration.alleles_can_be_repeated;
let chromosomes = crate::traits::initialize_chromosomes::<U>(
pop_size,
genes_per_chrom,
alleles,
Some(alleles_can_repeat),
init_fn,
Some(fitness_fn),
0,
);
self.islands.push(Population::new(chromosomes));
}
Ok(())
}
fn global_best(&self, problem_solving: ProblemSolving) -> U {
let mut best: Option<&U> = None;
for island in &self.islands {
for chrom in &island.chromosomes {
let is_better = match best {
None => true,
Some(current_best) => match problem_solving {
ProblemSolving::Minimization | ProblemSolving::FixedFitness => {
chrom.fitness() < current_best.fitness()
}
ProblemSolving::Maximization => chrom.fitness() > current_best.fitness(),
},
};
if is_better {
best = Some(chrom);
}
}
}
best.expect("Islands should not be empty after initialization")
.clone()
}
}
impl<U> IslandGa<U>
where
U: ChromosomeT + mutation::ValueMutable,
{
pub fn run(&mut self) -> Result<U, GaError> {
self.validate()?;
self.initialize()?;
let base_config = self.config_for_island(0);
crate::rng::set_seed(base_config.rng_seed);
let max_generations = base_config.limit_configuration.max_generations;
let problem_solving = base_config.limit_configuration.problem_solving;
let fitness_target = base_config.limit_configuration.fitness_target;
self.notify(|obs| obs.on_island_run_start(0));
for gen in 0..max_generations {
self.evolve_islands_one_generation(gen, problem_solving)?;
if let Some(target) = fitness_target {
let best = self.global_best(problem_solving);
let dist = (best.fitness() - target).abs();
if dist < 1e-10 {
self.notify(|obs| obs.on_island_run_end(0));
return Ok(best);
}
}
if gen > 0
&& self.island_config.migration_interval > 0
&& gen % self.island_config.migration_interval == 0
{
let migration_count = self.island_config.migration_count;
migrate(&mut self.islands, &self.island_config, problem_solving)?;
self.notify(|obs| obs.on_migration_triggered(gen, migration_count));
}
}
self.notify(|obs| obs.on_island_run_end(0));
Ok(self.global_best(problem_solving))
}
fn evolve_islands_one_generation(
&mut self,
gen: usize,
problem_solving: ProblemSolving,
) -> Result<(), GaError> {
use crate::operations::{crossover, mutation, selection, survivor};
use rand::Rng;
use rayon::prelude::*;
let fitness_fn = self
.fitness_fn
.as_ref()
.ok_or_else(|| GaError::ConfigurationError("No fitness function set".to_string()))?;
let fitness_fn = Arc::clone(fitness_fn);
let observer_clone: Option<Arc<dyn IslandGaObserver<U> + Send + Sync>> =
self.observer.as_ref().map(Arc::clone);
let is_maximization = matches!(problem_solving, ProblemSolving::Maximization);
let island_configs: Vec<_> = (0..self.islands.len())
.map(|i| {
let cfg = self.config_for_island(i);
(
cfg.selection_configuration,
cfg.crossover_configuration,
cfg.mutation_configuration,
cfg.survivor,
cfg.limit_configuration,
cfg.number_of_threads,
)
})
.collect();
self.islands
.par_iter_mut()
.enumerate()
.try_for_each(|(idx, island)| {
let (
selection_config,
crossover_config,
mutation_config,
survivor_method,
limit_config,
num_threads,
) = island_configs[idx];
let pop_size = limit_config.population_size;
let parent_pairs =
selection::factory(&island.chromosomes, selection_config, num_threads)?;
let mut rng = crate::rng::make_rng();
let crossover_prob = crossover_config.probability_max.unwrap_or(1.0);
let mut offspring: Vec<U> = Vec::new();
for &(idx_a, idx_b) in &parent_pairs {
let p: f64 = rng.random();
if p <= crossover_prob {
let children = crossover::factory(
&island.chromosomes[idx_a],
&island.chromosomes[idx_b],
crossover_config,
)?;
offspring.extend(children);
} else {
offspring.push(island.chromosomes[idx_a].clone());
offspring.push(island.chromosomes[idx_b].clone());
}
}
let mut_prob = mutation_config.probability_max.unwrap_or(0.1);
for child in offspring.iter_mut() {
let p: f64 = rng.random();
if p <= mut_prob {
mutation::factory_with_params(
mutation_config.method,
child,
mutation_config.step,
mutation_config.sigma,
)?;
}
}
for child in offspring.iter_mut() {
let ff = Arc::clone(&fitness_fn);
child.set_fitness_fn(move |genes| ff(genes));
child.calculate_fitness();
}
island.chromosomes.append(&mut offspring);
survivor::factory(
survivor_method,
&mut island.chromosomes,
pop_size,
limit_config,
)?;
if let Some(ref obs) = observer_clone {
let fitness_values: Vec<f64> =
island.chromosomes.iter().map(|c| c.fitness()).collect();
let stats = GenerationStats::from_fitness_values(
gen,
&fitness_values,
is_maximization,
);
obs.on_island_generation_end(idx, gen, &stats);
}
Ok(())
})
}
}