use crate::configuration::GaConfiguration;
use crate::error::GaError;
use crate::island::configuration::IslandConfiguration;
use crate::island::migration::migrate_pareto;
use crate::nsga2::configuration::Nsga2Configuration;
use crate::nsga2::crowding_distance::assign_crowding_distance;
use crate::nsga2::non_dominated_sort::{assign_ranks, non_dominated_sort};
use crate::nsga2::pareto::{ParetoFront, ParetoIndividual};
use crate::nsga2::ObjectiveFn;
use crate::operations::mutation;
use crate::traits::{ChromosomeT, InitializationFn};
use log::{debug, info};
use rand::Rng;
use rayon::prelude::*;
use std::sync::Arc;
pub struct IslandNsga2Ga<U>
where
U: ChromosomeT,
{
pub island_config: IslandConfiguration,
pub nsga2_config: Nsga2Configuration,
pub ga_config: GaConfiguration,
pub islands: Vec<Vec<ParetoIndividual<U>>>,
pub alleles: Vec<U::Gene>,
pub initialization_fn: Option<Arc<InitializationFn<U::Gene>>>,
pub objective_fns: Vec<Arc<ObjectiveFn<U::Gene>>>,
}
impl<U> IslandNsga2Ga<U>
where
U: ChromosomeT,
{
pub fn new(
island_config: IslandConfiguration,
nsga2_config: Nsga2Configuration,
ga_config: GaConfiguration,
) -> Self {
IslandNsga2Ga {
island_config,
nsga2_config,
ga_config,
islands: Vec::new(),
alleles: Vec::new(),
initialization_fn: None,
objective_fns: Vec::new(),
}
}
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_objective_fns(mut self, fns: Vec<Box<ObjectiveFn<U::Gene>>>) -> Self {
self.objective_fns = fns.into_iter().map(Arc::from).collect();
self
}
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.nsga2_config.num_objectives == 0 {
return Err(GaError::InvalidNsga2Configuration(
"num_objectives must be > 0".to_string(),
));
}
if self.nsga2_config.population_size < 2 {
return Err(GaError::InvalidNsga2Configuration(
"population_size must be >= 2".to_string(),
));
}
if self.island_config.migration_count >= self.nsga2_config.population_size {
return Err(GaError::InvalidIslandConfiguration(format!(
"migration_count ({}) must be < population_size ({})",
self.island_config.migration_count, self.nsga2_config.population_size
)));
}
if self.initialization_fn.is_none() {
return Err(GaError::InvalidNsga2Configuration(
"initialization_fn is required".to_string(),
));
}
if self.objective_fns.len() != self.nsga2_config.num_objectives {
return Err(GaError::InvalidNsga2Configuration(format!(
"Expected {} objective functions, got {}",
self.nsga2_config.num_objectives,
self.objective_fns.len()
)));
}
Ok(())
}
fn initialize_islands(&mut self) -> Result<(), GaError> {
let init_fn = self.initialization_fn.as_ref().ok_or_else(|| {
GaError::InitializationError("No initialization function set".to_string())
})?;
let num_islands = self.island_config.num_islands;
let pop_size = self.nsga2_config.population_size;
let genes_per_chrom = self.ga_config.limit_configuration.genes_per_chromosome;
let alleles_can_repeat = self.ga_config.limit_configuration.alleles_can_be_repeated;
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 chromosomes: Vec<U> = crate::traits::initialize_chromosomes(
pop_size,
genes_per_chrom,
alleles,
Some(alleles_can_repeat),
init_fn,
None,
0,
);
let objective_fns = &self.objective_fns;
let population: Vec<ParetoIndividual<U>> = chromosomes
.into_par_iter()
.map(|chrom| {
let objectives: Vec<f64> =
objective_fns.iter().map(|f| f(chrom.dna())).collect();
ParetoIndividual::new(chrom, objectives)
})
.collect();
self.islands.push(population);
debug!(
target: "island_events",
"Initialized NSGA-II island {} with {} individuals", island_idx, pop_size
);
}
Ok(())
}
fn rank_and_crowd(population: &mut [ParetoIndividual<U>]) {
let all_objectives: Vec<&[f64]> = population
.iter()
.map(|ind| ind.objectives.as_slice())
.collect();
let fronts = non_dominated_sort(&all_objectives);
let mut ranks = vec![0usize; population.len()];
assign_ranks(&mut ranks, &fronts);
for (i, &r) in ranks.iter().enumerate() {
population[i].rank = r;
}
for front in &fronts {
let front_objectives: Vec<&[f64]> = front
.iter()
.map(|&idx| population[idx].objectives.as_slice())
.collect();
let mut front_crowding = vec![0.0; front.len()];
assign_crowding_distance(&front_objectives, &mut front_crowding);
for (local_idx, &global_idx) in front.iter().enumerate() {
population[global_idx].crowding_distance = front_crowding[local_idx];
}
}
}
}
impl<U> IslandNsga2Ga<U>
where
U: ChromosomeT + mutation::ValueMutable,
{
pub fn run(&mut self) -> Result<ParetoFront<U>, GaError> {
self.validate()?;
self.initialize_islands()?;
crate::rng::set_seed(self.ga_config.rng_seed);
let max_gens = self.nsga2_config.max_generations;
let pop_size = self.nsga2_config.population_size;
info!(
target: "island_events",
"Starting Island-NSGA-II: {} islands, {} individuals/island, {} objectives, {} generations",
self.island_config.num_islands,
pop_size,
self.nsga2_config.num_objectives,
max_gens
);
{
use rayon::prelude::*;
self.islands
.par_iter_mut()
.for_each(|island| Self::rank_and_crowd(island));
}
for gen in 0..max_gens {
self.evolve_islands_one_generation(pop_size)?;
if gen > 0
&& self.island_config.migration_interval > 0
&& gen % self.island_config.migration_interval == 0
{
migrate_pareto(&mut self.islands, &self.island_config)?;
debug!(
target: "island_events",
"Pareto migration at generation {}", gen
);
}
}
Ok(self.global_pareto_front())
}
fn evolve_islands_one_generation(&mut self, pop_size: usize) -> Result<(), GaError> {
use crate::operations::{crossover, mutation};
use rayon::prelude::*;
let crossover_config = self.ga_config.crossover_configuration;
let mutation_config = self.ga_config.mutation_configuration;
let crossover_prob = crossover_config.probability_max.unwrap_or(1.0);
let mut_prob = mutation_config.probability_max.unwrap_or(0.1);
let objective_fns = &self.objective_fns;
self.islands.par_iter_mut().try_for_each(|island| {
let mut rng = crate::rng::make_rng();
let mut offspring: Vec<ParetoIndividual<U>> = Vec::with_capacity(pop_size);
while offspring.len() < pop_size {
let parent_a = binary_tournament(island, &mut rng);
let parent_b = binary_tournament(island, &mut rng);
let p: f64 = rng.random();
let mut children = if p <= crossover_prob {
crossover::factory(
&island[parent_a].chromosome,
&island[parent_b].chromosome,
crossover_config,
)?
} else {
vec![
island[parent_a].chromosome.clone(),
island[parent_b].chromosome.clone(),
]
};
for child in children.iter_mut() {
let mp: f64 = rng.random();
if mp <= mut_prob {
mutation::factory_with_params(
mutation_config.method,
child,
mutation_config.step,
mutation_config.sigma,
)?;
}
}
for child in children {
let objectives: Vec<f64> =
objective_fns.iter().map(|f| f(child.dna())).collect();
offspring.push(ParetoIndividual::new(child, objectives));
if offspring.len() >= pop_size {
break;
}
}
}
island.extend(offspring);
Self::rank_and_crowd(island);
island.sort_by(|a, b| {
a.rank.cmp(&b.rank).then_with(|| {
b.crowding_distance
.partial_cmp(&a.crowding_distance)
.unwrap_or(std::cmp::Ordering::Equal)
})
});
island.truncate(pop_size);
Ok(())
})
}
fn global_pareto_front(&self) -> ParetoFront<U> {
let mut combined: Vec<ParetoIndividual<U>> = self
.islands
.iter()
.flat_map(|island| island.iter().cloned())
.collect();
if combined.is_empty() {
return ParetoFront::new(vec![]);
}
Self::rank_and_crowd(&mut combined);
let front_individuals: Vec<ParetoIndividual<U>> =
combined.into_iter().filter(|ind| ind.rank == 0).collect();
ParetoFront::new(front_individuals)
}
}
pub fn binary_tournament<U>(population: &[ParetoIndividual<U>], rng: &mut impl Rng) -> usize
where
U: ChromosomeT,
{
let n = population.len();
let i = rng.random_range(0..n);
let j = rng.random_range(0..n);
let a = &population[i];
let b = &population[j];
if a.rank < b.rank {
i
} else if b.rank < a.rank {
j
} else if a.crowding_distance > b.crowding_distance {
i
} else {
j
}
}