Skip to main content

fugue_evo/algorithms/
island.rs

1//! Island Model Parallelism
2//!
3//! Implements a distributed evolutionary algorithm where multiple populations
4//! (islands) evolve independently with periodic migration of individuals.
5//!
6//! Note: This module requires the `parallel` feature to be enabled.
7
8use rand::SeedableRng;
9use rayon::prelude::*;
10use serde::{Deserialize, Serialize};
11use std::sync::Arc;
12
13use crate::error::{EvoResult, EvolutionError, OperatorResult};
14use crate::fitness::traits::{Fitness, FitnessValue};
15use crate::genome::bounds::MultiBounds;
16use crate::genome::traits::EvolutionaryGenome;
17use crate::operators::traits::{CrossoverOperator, MutationOperator, SelectionOperator};
18use crate::population::individual::Individual;
19use crate::population::population::Population;
20
21/// Migration topology determines which islands exchange individuals
22#[derive(Clone, Debug, Serialize, Deserialize)]
23pub enum MigrationTopology {
24    /// Ring topology: each island sends to the next
25    Ring,
26    /// Fully connected: every island can send to any other
27    FullyConnected,
28    /// Random: each island sends to a randomly chosen island
29    Random,
30    /// Star: all islands send to/receive from a central hub
31    Star { hub_index: usize },
32}
33
34impl Default for MigrationTopology {
35    fn default() -> Self {
36        Self::Ring
37    }
38}
39
40impl MigrationTopology {
41    /// Get the target islands for migration from a given source island
42    pub fn targets(&self, source: usize, num_islands: usize) -> Vec<usize> {
43        match self {
44            Self::Ring => vec![(source + 1) % num_islands],
45            Self::FullyConnected => (0..num_islands).filter(|&i| i != source).collect(),
46            Self::Random => (0..num_islands).filter(|&i| i != source).collect(),
47            Self::Star { hub_index } => {
48                if source == *hub_index {
49                    (0..num_islands).filter(|&i| i != source).collect()
50                } else {
51                    vec![*hub_index]
52                }
53            }
54        }
55    }
56}
57
58/// Migration policy determines which and how many individuals migrate
59#[derive(Clone, Debug, Serialize, Deserialize)]
60pub enum MigrationPolicy {
61    /// Best k individuals migrate
62    Best(usize),
63    /// Random k individuals migrate
64    Random(usize),
65    /// Best k individuals replace worst k on target
66    BestReplaceWorst(usize),
67}
68
69impl Default for MigrationPolicy {
70    fn default() -> Self {
71        Self::Best(1)
72    }
73}
74
75/// Configuration for the island model
76#[derive(Clone, Debug)]
77pub struct IslandModelConfig {
78    /// Number of islands
79    pub num_islands: usize,
80    /// Population size per island
81    pub island_population_size: usize,
82    /// Migration interval (generations between migrations)
83    pub migration_interval: usize,
84    /// Migration topology
85    pub topology: MigrationTopology,
86    /// Migration policy
87    pub policy: MigrationPolicy,
88    /// Bounds for genome generation
89    pub bounds: MultiBounds,
90    /// Number of elites to preserve per island
91    pub elitism: usize,
92}
93
94impl Default for IslandModelConfig {
95    fn default() -> Self {
96        Self {
97            num_islands: 4,
98            island_population_size: 50,
99            migration_interval: 10,
100            topology: MigrationTopology::Ring,
101            policy: MigrationPolicy::Best(1),
102            bounds: MultiBounds::symmetric(5.0, 10),
103            elitism: 1,
104        }
105    }
106}
107
108/// State of a single island
109pub struct Island<G, F = f64>
110where
111    G: EvolutionaryGenome,
112    F: FitnessValue,
113{
114    /// Island index
115    pub index: usize,
116    /// Current population
117    pub population: Population<G, F>,
118    /// Best individual found on this island
119    pub best: Option<Individual<G, F>>,
120    /// Current generation (local to island)
121    pub generation: usize,
122    /// Total fitness evaluations on this island
123    pub evaluations: usize,
124}
125
126impl<G, F> Island<G, F>
127where
128    G: EvolutionaryGenome,
129    F: FitnessValue,
130{
131    /// Create a new island with a random population
132    pub fn new<R: rand::Rng>(index: usize, size: usize, bounds: &MultiBounds, rng: &mut R) -> Self {
133        Self {
134            index,
135            population: Population::random(size, bounds, rng),
136            best: None,
137            generation: 0,
138            evaluations: 0,
139        }
140    }
141
142    /// Create an island with an existing population
143    pub fn with_population(index: usize, population: Population<G, F>) -> Self {
144        Self {
145            index,
146            population,
147            best: None,
148            generation: 0,
149            evaluations: 0,
150        }
151    }
152
153    /// Run one generation of evolution on this island
154    pub fn evolve_one_generation<Fit, Sel, Cross, Mut, R>(
155        &mut self,
156        fitness: &Fit,
157        selection: &Sel,
158        crossover: &Cross,
159        mutation: &Mut,
160        elitism: usize,
161        rng: &mut R,
162    ) -> EvoResult<()>
163    where
164        Fit: Fitness<Genome = G, Value = F>,
165        Sel: SelectionOperator<G>,
166        Cross: CrossoverOperator<G>,
167        Mut: MutationOperator<G>,
168        R: rand::Rng,
169    {
170        // Evaluate population
171        self.population.evaluate(fitness);
172        self.evaluations += self.population.len();
173
174        // Update best
175        if let Some(current_best) = self.population.best() {
176            match &self.best {
177                None => self.best = Some(current_best.clone()),
178                Some(best) if current_best.is_better_than(best) => {
179                    self.best = Some(current_best.clone());
180                }
181                _ => {}
182            }
183        }
184
185        // Preserve elites
186        self.population.sort_by_fitness();
187        let elites: Vec<_> = self.population.iter().take(elitism).cloned().collect();
188
189        // Prepare selection pool: (genome, fitness) pairs
190        let selection_pool: Vec<(G, f64)> = self
191            .population
192            .iter()
193            .filter_map(|ind| {
194                ind.fitness
195                    .as_ref()
196                    .map(|f| (ind.genome.clone(), f.to_f64()))
197            })
198            .collect();
199
200        if selection_pool.len() < 2 {
201            return Err(EvolutionError::EmptyPopulation);
202        }
203
204        // Selection and reproduction
205        let mut offspring = Vec::with_capacity(self.population.len());
206
207        while offspring.len() < self.population.len() - elitism {
208            // Select parents
209            let idx1 = selection.select(&selection_pool, rng);
210            let idx2 = selection.select(&selection_pool, rng);
211            let parent1 = &selection_pool[idx1].0;
212            let parent2 = &selection_pool[idx2].0;
213
214            // Crossover
215            let (mut child1, mut child2) = match crossover.crossover(parent1, parent2, rng) {
216                OperatorResult::Success((c1, c2)) | OperatorResult::Repaired((c1, c2), _) => {
217                    (c1, c2)
218                }
219                OperatorResult::Failed(_) => (parent1.clone(), parent2.clone()),
220            };
221
222            // Mutation
223            mutation.mutate(&mut child1, rng);
224            mutation.mutate(&mut child2, rng);
225
226            offspring.push(Individual::new(child1));
227            if offspring.len() < self.population.len() - elitism {
228                offspring.push(Individual::new(child2));
229            }
230        }
231
232        // Replace population with elites + offspring
233        let mut new_population = Population::new();
234        for elite in elites {
235            new_population.push(elite);
236        }
237        for child in offspring {
238            new_population.push(child);
239        }
240        self.population = new_population;
241
242        self.generation += 1;
243        Ok(())
244    }
245
246    /// Get emigrants for migration (individuals leaving this island)
247    pub fn get_emigrants<R: rand::Rng>(
248        &self,
249        policy: &MigrationPolicy,
250        rng: &mut R,
251    ) -> Vec<Individual<G, F>> {
252        match policy {
253            MigrationPolicy::Best(k) => {
254                let mut sorted: Vec<_> = self.population.iter().cloned().collect();
255                sorted.sort_by(|a, b| match (a.fitness.as_ref(), b.fitness.as_ref()) {
256                    (Some(fa), Some(fb)) => fb.partial_cmp(fa).unwrap_or(std::cmp::Ordering::Equal),
257                    (Some(_), None) => std::cmp::Ordering::Less,
258                    (None, Some(_)) => std::cmp::Ordering::Greater,
259                    (None, None) => std::cmp::Ordering::Equal,
260                });
261                sorted.into_iter().take(*k).collect()
262            }
263            MigrationPolicy::Random(k) => {
264                use rand::seq::SliceRandom;
265                let mut individuals: Vec<_> = self.population.iter().cloned().collect();
266                individuals.shuffle(rng);
267                individuals.into_iter().take(*k).collect()
268            }
269            MigrationPolicy::BestReplaceWorst(k) => {
270                let mut sorted: Vec<_> = self.population.iter().cloned().collect();
271                sorted.sort_by(|a, b| match (a.fitness.as_ref(), b.fitness.as_ref()) {
272                    (Some(fa), Some(fb)) => fb.partial_cmp(fa).unwrap_or(std::cmp::Ordering::Equal),
273                    (Some(_), None) => std::cmp::Ordering::Less,
274                    (None, Some(_)) => std::cmp::Ordering::Greater,
275                    (None, None) => std::cmp::Ordering::Equal,
276                });
277                sorted.into_iter().take(*k).collect()
278            }
279        }
280    }
281
282    /// Accept immigrants (individuals coming to this island)
283    pub fn accept_immigrants<R: rand::Rng>(
284        &mut self,
285        immigrants: Vec<Individual<G, F>>,
286        policy: &MigrationPolicy,
287        rng: &mut R,
288    ) {
289        match policy {
290            MigrationPolicy::Best(_) | MigrationPolicy::Random(_) => {
291                // Replace random individuals
292                for immigrant in immigrants {
293                    let idx = rng.gen_range(0..self.population.len());
294                    self.population[idx] = immigrant;
295                }
296            }
297            MigrationPolicy::BestReplaceWorst(k) => {
298                // Replace worst individuals
299                self.population.sort_by_fitness();
300                let pop_len = self.population.len();
301                for (i, immigrant) in immigrants.into_iter().enumerate() {
302                    if i < *k && pop_len > i {
303                        self.population[pop_len - 1 - i] = immigrant;
304                    }
305                }
306            }
307        }
308    }
309}
310
311/// Island Model Evolutionary Algorithm
312pub struct IslandModel<G, Fit, Sel, Cross, Mut, F = f64>
313where
314    G: EvolutionaryGenome,
315    F: FitnessValue,
316{
317    /// Configuration
318    pub config: IslandModelConfig,
319    /// Islands
320    pub islands: Vec<Island<G, F>>,
321    /// Fitness function (shared)
322    pub fitness: Arc<Fit>,
323    /// Selection operator
324    pub selection: Sel,
325    /// Crossover operator
326    pub crossover: Cross,
327    /// Mutation operator
328    pub mutation: Mut,
329    /// Global best individual
330    pub global_best: Option<Individual<G, F>>,
331    /// Total generations (global)
332    pub generation: usize,
333    /// Total evaluations across all islands
334    pub total_evaluations: usize,
335}
336
337impl<G, Fit, Sel, Cross, Mut, F> IslandModel<G, Fit, Sel, Cross, Mut, F>
338where
339    G: EvolutionaryGenome,
340    F: FitnessValue,
341    Fit: Fitness<Genome = G, Value = F> + Send + Sync,
342    Sel: SelectionOperator<G> + Clone + Send + Sync,
343    Cross: CrossoverOperator<G> + Clone + Send + Sync,
344    Mut: MutationOperator<G> + Clone + Send + Sync,
345{
346    /// Create a new island model
347    pub fn new<R: rand::Rng>(
348        config: IslandModelConfig,
349        fitness: Fit,
350        selection: Sel,
351        crossover: Cross,
352        mutation: Mut,
353        rng: &mut R,
354    ) -> Self {
355        let islands: Vec<_> = (0..config.num_islands)
356            .map(|i| {
357                let mut island_rng = rand::rngs::StdRng::from_seed(rng.gen());
358                Island::new(
359                    i,
360                    config.island_population_size,
361                    &config.bounds,
362                    &mut island_rng,
363                )
364            })
365            .collect();
366
367        Self {
368            config,
369            islands,
370            fitness: Arc::new(fitness),
371            selection,
372            crossover,
373            mutation,
374            global_best: None,
375            generation: 0,
376            total_evaluations: 0,
377        }
378    }
379
380    /// Run evolution for a specified number of generations
381    pub fn run<R: rand::Rng>(
382        &mut self,
383        max_generations: usize,
384        rng: &mut R,
385    ) -> EvoResult<&Individual<G, F>> {
386        for _ in 0..max_generations {
387            self.step(rng)?;
388        }
389
390        self.global_best
391            .as_ref()
392            .ok_or(EvolutionError::EmptyPopulation)
393    }
394
395    /// Perform one generation step on all islands
396    pub fn step<R: rand::Rng>(&mut self, rng: &mut R) -> EvoResult<()> {
397        // Evolve each island independently (can be parallelized)
398        let elitism = self.config.elitism;
399        let fitness = Arc::clone(&self.fitness);
400        let selection = self.selection.clone();
401        let crossover = self.crossover.clone();
402        let mutation = self.mutation.clone();
403
404        // Parallel evolution of islands
405        self.islands.par_iter_mut().for_each(|island| {
406            let mut island_rng = rand::rngs::StdRng::from_entropy();
407            let _ = island.evolve_one_generation(
408                fitness.as_ref(),
409                &selection,
410                &crossover,
411                &mutation,
412                elitism,
413                &mut island_rng,
414            );
415        });
416
417        // Update global best
418        for island in &self.islands {
419            if let Some(island_best) = &island.best {
420                match &self.global_best {
421                    None => self.global_best = Some(island_best.clone()),
422                    Some(global) if island_best.is_better_than(global) => {
423                        self.global_best = Some(island_best.clone());
424                    }
425                    _ => {}
426                }
427            }
428        }
429
430        self.generation += 1;
431        self.total_evaluations = self.islands.iter().map(|i| i.evaluations).sum();
432
433        // Migration
434        if self
435            .generation
436            .is_multiple_of(self.config.migration_interval)
437        {
438            self.migrate(rng);
439        }
440
441        Ok(())
442    }
443
444    /// Perform migration between islands
445    fn migrate<R: rand::Rng>(&mut self, rng: &mut R) {
446        let num_islands = self.islands.len();
447        let policy = &self.config.policy;
448        let topology = &self.config.topology;
449
450        // Collect emigrants from each island
451        let emigrants: Vec<Vec<Individual<G, F>>> = self
452            .islands
453            .iter()
454            .map(|island| island.get_emigrants(policy, &mut rand::rngs::StdRng::from_entropy()))
455            .collect();
456
457        // Route emigrants to target islands
458        for (source, source_emigrants) in emigrants.into_iter().enumerate() {
459            let targets = topology.targets(source, num_islands);
460
461            if targets.is_empty() {
462                continue;
463            }
464
465            match topology {
466                MigrationTopology::Random => {
467                    // Pick one random target
468                    let target = targets[rng.gen_range(0..targets.len())];
469                    self.islands[target].accept_immigrants(source_emigrants, policy, rng);
470                }
471                _ => {
472                    // Send to first target
473                    if let Some(&target) = targets.first() {
474                        self.islands[target].accept_immigrants(
475                            source_emigrants.clone(),
476                            policy,
477                            rng,
478                        );
479                    }
480                }
481            }
482        }
483    }
484
485    /// Get a combined population from all islands
486    pub fn combined_population(&self) -> Population<G, F> {
487        let mut combined = Population::new();
488        for island in &self.islands {
489            for individual in island.population.iter() {
490                combined.push(individual.clone());
491            }
492        }
493        combined
494    }
495
496    /// Get statistics about each island
497    pub fn island_statistics(&self) -> Vec<IslandStats<F>> {
498        self.islands
499            .iter()
500            .map(|island| {
501                let (sum, best) = island
502                    .population
503                    .iter()
504                    .filter_map(|i| i.fitness.clone())
505                    .fold((0.0, None::<F>), |(sum, best), f| {
506                        let new_best = match best {
507                            None => Some(f.clone()),
508                            Some(b) if f.is_better_than(&b) => Some(f.clone()),
509                            b => b,
510                        };
511                        (sum + f.to_f64(), new_best)
512                    });
513
514                let count = island
515                    .population
516                    .iter()
517                    .filter(|i| i.fitness.is_some())
518                    .count();
519
520                IslandStats {
521                    index: island.index,
522                    generation: island.generation,
523                    evaluations: island.evaluations,
524                    population_size: island.population.len(),
525                    mean_fitness: if count > 0 { sum / count as f64 } else { 0.0 },
526                    best_fitness: best,
527                }
528            })
529            .collect()
530    }
531}
532
533/// Statistics for a single island
534#[derive(Clone, Debug)]
535pub struct IslandStats<F: FitnessValue> {
536    /// Island index
537    pub index: usize,
538    /// Current generation
539    pub generation: usize,
540    /// Total evaluations
541    pub evaluations: usize,
542    /// Population size
543    pub population_size: usize,
544    /// Mean fitness
545    pub mean_fitness: f64,
546    /// Best fitness on this island
547    pub best_fitness: Option<F>,
548}
549
550/// Builder for IslandModel
551pub struct IslandModelBuilder<G, Fit, Sel, Cross, Mut, F = f64>
552where
553    G: EvolutionaryGenome,
554    F: FitnessValue,
555{
556    config: IslandModelConfig,
557    fitness: Option<Fit>,
558    selection: Option<Sel>,
559    crossover: Option<Cross>,
560    mutation: Option<Mut>,
561    _phantom: std::marker::PhantomData<(G, F)>,
562}
563
564impl<G, Fit, Sel, Cross, Mut, F> IslandModelBuilder<G, Fit, Sel, Cross, Mut, F>
565where
566    G: EvolutionaryGenome,
567    F: FitnessValue,
568    Fit: Fitness<Genome = G, Value = F> + Send + Sync,
569    Sel: SelectionOperator<G> + Clone + Send + Sync,
570    Cross: CrossoverOperator<G> + Clone + Send + Sync,
571    Mut: MutationOperator<G> + Clone + Send + Sync,
572{
573    /// Create a new builder
574    pub fn new() -> Self {
575        Self {
576            config: IslandModelConfig::default(),
577            fitness: None,
578            selection: None,
579            crossover: None,
580            mutation: None,
581            _phantom: std::marker::PhantomData,
582        }
583    }
584
585    /// Set number of islands
586    pub fn num_islands(mut self, n: usize) -> Self {
587        self.config.num_islands = n;
588        self
589    }
590
591    /// Set population size per island
592    pub fn island_population_size(mut self, size: usize) -> Self {
593        self.config.island_population_size = size;
594        self
595    }
596
597    /// Set migration interval
598    pub fn migration_interval(mut self, interval: usize) -> Self {
599        self.config.migration_interval = interval;
600        self
601    }
602
603    /// Set migration topology
604    pub fn topology(mut self, topology: MigrationTopology) -> Self {
605        self.config.topology = topology;
606        self
607    }
608
609    /// Set migration policy
610    pub fn migration_policy(mut self, policy: MigrationPolicy) -> Self {
611        self.config.policy = policy;
612        self
613    }
614
615    /// Set bounds
616    pub fn bounds(mut self, bounds: MultiBounds) -> Self {
617        self.config.bounds = bounds;
618        self
619    }
620
621    /// Set elitism
622    pub fn elitism(mut self, n: usize) -> Self {
623        self.config.elitism = n;
624        self
625    }
626
627    /// Set fitness function
628    pub fn fitness(mut self, fitness: Fit) -> Self {
629        self.fitness = Some(fitness);
630        self
631    }
632
633    /// Set selection operator
634    pub fn selection(mut self, selection: Sel) -> Self {
635        self.selection = Some(selection);
636        self
637    }
638
639    /// Set crossover operator
640    pub fn crossover(mut self, crossover: Cross) -> Self {
641        self.crossover = Some(crossover);
642        self
643    }
644
645    /// Set mutation operator
646    pub fn mutation(mut self, mutation: Mut) -> Self {
647        self.mutation = Some(mutation);
648        self
649    }
650
651    /// Build the island model
652    pub fn build<R: rand::Rng>(
653        self,
654        rng: &mut R,
655    ) -> EvoResult<IslandModel<G, Fit, Sel, Cross, Mut, F>> {
656        let fitness = self.fitness.ok_or(EvolutionError::Configuration(
657            "Fitness function is required".to_string(),
658        ))?;
659        let selection = self.selection.ok_or(EvolutionError::Configuration(
660            "Selection operator is required".to_string(),
661        ))?;
662        let crossover = self.crossover.ok_or(EvolutionError::Configuration(
663            "Crossover operator is required".to_string(),
664        ))?;
665        let mutation = self.mutation.ok_or(EvolutionError::Configuration(
666            "Mutation operator is required".to_string(),
667        ))?;
668
669        Ok(IslandModel::new(
670            self.config,
671            fitness,
672            selection,
673            crossover,
674            mutation,
675            rng,
676        ))
677    }
678}
679
680impl<G, Fit, Sel, Cross, Mut, F> Default for IslandModelBuilder<G, Fit, Sel, Cross, Mut, F>
681where
682    G: EvolutionaryGenome,
683    F: FitnessValue,
684    Fit: Fitness<Genome = G, Value = F> + Send + Sync,
685    Sel: SelectionOperator<G> + Clone + Send + Sync,
686    Cross: CrossoverOperator<G> + Clone + Send + Sync,
687    Mut: MutationOperator<G> + Clone + Send + Sync,
688{
689    fn default() -> Self {
690        Self::new()
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697    use crate::fitness::benchmarks::Sphere;
698    use crate::genome::real_vector::RealVector;
699    use crate::operators::crossover::BlxAlphaCrossover;
700    use crate::operators::mutation::GaussianMutation;
701    use crate::operators::selection::TournamentSelection;
702    use rand::SeedableRng;
703
704    #[test]
705    fn test_migration_topology_ring() {
706        let topology = MigrationTopology::Ring;
707        assert_eq!(topology.targets(0, 4), vec![1]);
708        assert_eq!(topology.targets(1, 4), vec![2]);
709        assert_eq!(topology.targets(3, 4), vec![0]);
710    }
711
712    #[test]
713    fn test_migration_topology_fully_connected() {
714        let topology = MigrationTopology::FullyConnected;
715        let targets = topology.targets(0, 4);
716        assert_eq!(targets.len(), 3);
717        assert!(!targets.contains(&0));
718    }
719
720    #[test]
721    fn test_migration_topology_star() {
722        let topology = MigrationTopology::Star { hub_index: 0 };
723        // Non-hub sends to hub
724        assert_eq!(topology.targets(1, 4), vec![0]);
725        assert_eq!(topology.targets(2, 4), vec![0]);
726        // Hub sends to all
727        let hub_targets = topology.targets(0, 4);
728        assert_eq!(hub_targets.len(), 3);
729    }
730
731    #[test]
732    fn test_island_creation() {
733        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
734        let bounds = MultiBounds::symmetric(5.0, 5);
735        let island: Island<RealVector> = Island::new(0, 10, &bounds, &mut rng);
736
737        assert_eq!(island.index, 0);
738        assert_eq!(island.population.len(), 10);
739        assert_eq!(island.generation, 0);
740    }
741
742    #[test]
743    fn test_island_model_builder() {
744        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
745        let bounds = MultiBounds::symmetric(5.0, 5);
746
747        let result = IslandModelBuilder::<RealVector, _, _, _, _>::new()
748            .num_islands(4)
749            .island_population_size(20)
750            .migration_interval(5)
751            .topology(MigrationTopology::Ring)
752            .migration_policy(MigrationPolicy::Best(2))
753            .bounds(bounds)
754            .elitism(1)
755            .fitness(Sphere::new(5))
756            .selection(TournamentSelection::new(3))
757            .crossover(BlxAlphaCrossover::new(0.5))
758            .mutation(GaussianMutation::new(0.1))
759            .build(&mut rng);
760
761        assert!(result.is_ok());
762        let model = result.unwrap();
763        assert_eq!(model.islands.len(), 4);
764    }
765
766    #[test]
767    fn test_island_model_evolution() {
768        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
769        let bounds = MultiBounds::symmetric(5.0, 5);
770
771        let mut model = IslandModelBuilder::<RealVector, _, _, _, _>::new()
772            .num_islands(2)
773            .island_population_size(20)
774            .migration_interval(5)
775            .topology(MigrationTopology::Ring)
776            .migration_policy(MigrationPolicy::Best(1))
777            .bounds(bounds)
778            .elitism(1)
779            .fitness(Sphere::new(5))
780            .selection(TournamentSelection::new(2))
781            .crossover(BlxAlphaCrossover::new(0.5))
782            .mutation(GaussianMutation::new(0.1))
783            .build(&mut rng)
784            .unwrap();
785
786        // Run for 10 generations
787        let result = model.run(10, &mut rng);
788        assert!(result.is_ok());
789
790        assert_eq!(model.generation, 10);
791        assert!(model.global_best.is_some());
792    }
793
794    #[test]
795    fn test_island_statistics() {
796        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
797        let bounds = MultiBounds::symmetric(5.0, 5);
798
799        let mut model = IslandModelBuilder::<RealVector, _, _, _, _>::new()
800            .num_islands(3)
801            .island_population_size(10)
802            .migration_interval(10)
803            .bounds(bounds)
804            .fitness(Sphere::new(5))
805            .selection(TournamentSelection::new(2))
806            .crossover(BlxAlphaCrossover::new(0.5))
807            .mutation(GaussianMutation::new(0.1))
808            .build(&mut rng)
809            .unwrap();
810
811        model.run(5, &mut rng).unwrap();
812
813        let stats = model.island_statistics();
814        assert_eq!(stats.len(), 3);
815        for stat in &stats {
816            assert_eq!(stat.generation, 5);
817            assert_eq!(stat.population_size, 10);
818        }
819    }
820}