u_nesting_core/
ga.rs

1//! Genetic Algorithm framework for optimization.
2
3use rand::prelude::*;
4use rayon::prelude::*;
5use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// Configuration for the genetic algorithm.
13#[derive(Debug, Clone)]
14#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
15pub struct GaConfig {
16    /// Population size.
17    pub population_size: usize,
18    /// Maximum number of generations.
19    pub max_generations: u32,
20    /// Crossover rate (0.0 - 1.0).
21    pub crossover_rate: f64,
22    /// Mutation rate (0.0 - 1.0).
23    pub mutation_rate: f64,
24    /// Number of elite individuals to preserve each generation.
25    pub elite_count: usize,
26    /// Tournament size for selection.
27    pub tournament_size: usize,
28    /// Maximum time limit (None = unlimited).
29    pub time_limit: Option<Duration>,
30    /// Target fitness to stop early (None = run all generations).
31    pub target_fitness: Option<f64>,
32    /// Stagnation generations before early stop.
33    pub stagnation_limit: Option<u32>,
34}
35
36impl Default for GaConfig {
37    fn default() -> Self {
38        Self {
39            population_size: 100,
40            max_generations: 500,
41            crossover_rate: 0.85,
42            mutation_rate: 0.05,
43            elite_count: 5,
44            tournament_size: 3,
45            time_limit: None,
46            target_fitness: None,
47            stagnation_limit: Some(50),
48        }
49    }
50}
51
52impl GaConfig {
53    /// Creates a new configuration with default values.
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Sets the population size.
59    pub fn with_population_size(mut self, size: usize) -> Self {
60        self.population_size = size.max(2);
61        self
62    }
63
64    /// Sets the maximum generations.
65    pub fn with_max_generations(mut self, gen: u32) -> Self {
66        self.max_generations = gen;
67        self
68    }
69
70    /// Sets the crossover rate.
71    pub fn with_crossover_rate(mut self, rate: f64) -> Self {
72        self.crossover_rate = rate.clamp(0.0, 1.0);
73        self
74    }
75
76    /// Sets the mutation rate.
77    pub fn with_mutation_rate(mut self, rate: f64) -> Self {
78        self.mutation_rate = rate.clamp(0.0, 1.0);
79        self
80    }
81
82    /// Sets the elite count.
83    pub fn with_elite_count(mut self, count: usize) -> Self {
84        self.elite_count = count;
85        self
86    }
87
88    /// Sets the time limit.
89    pub fn with_time_limit(mut self, duration: Duration) -> Self {
90        self.time_limit = Some(duration);
91        self
92    }
93
94    /// Sets the target fitness.
95    pub fn with_target_fitness(mut self, fitness: f64) -> Self {
96        self.target_fitness = Some(fitness);
97        self
98    }
99}
100
101/// Trait for individuals in the genetic algorithm.
102pub trait Individual: Clone + Send + Sync {
103    /// The fitness type (usually f64).
104    type Fitness: PartialOrd + Copy + Send;
105
106    /// Returns the fitness of this individual.
107    fn fitness(&self) -> Self::Fitness;
108
109    /// Creates a random individual.
110    fn random<R: Rng>(rng: &mut R) -> Self;
111
112    /// Performs crossover with another individual.
113    fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self;
114
115    /// Mutates this individual in place.
116    fn mutate<R: Rng>(&mut self, rng: &mut R);
117}
118
119/// Trait for problem-specific GA operations.
120pub trait GaProblem: Send + Sync {
121    /// The individual type for this problem.
122    type Individual: Individual;
123
124    /// Evaluates the fitness of an individual.
125    fn evaluate(&self, individual: &mut Self::Individual);
126
127    /// Evaluates multiple individuals in parallel.
128    /// Default implementation uses rayon for parallel evaluation.
129    fn evaluate_parallel(&self, individuals: &mut [Self::Individual]) {
130        individuals.par_iter_mut().for_each(|ind| {
131            self.evaluate(ind);
132        });
133    }
134
135    /// Creates an initial population.
136    fn initialize_population<R: Rng>(&self, size: usize, rng: &mut R) -> Vec<Self::Individual> {
137        (0..size).map(|_| Self::Individual::random(rng)).collect()
138    }
139
140    /// Called after each generation (for progress reporting).
141    fn on_generation(
142        &self,
143        _generation: u32,
144        _best: &Self::Individual,
145        _population: &[Self::Individual],
146    ) {
147        // Default: do nothing
148    }
149}
150
151/// Progress information during GA execution.
152#[derive(Debug, Clone)]
153pub struct GaProgress<F> {
154    /// Current generation number.
155    pub generation: u32,
156    /// Maximum generations configured.
157    pub max_generations: u32,
158    /// Best fitness so far.
159    pub best_fitness: F,
160    /// Average fitness of current population.
161    pub avg_fitness: f64,
162    /// Elapsed time since start.
163    pub elapsed: Duration,
164    /// Whether the algorithm is still running.
165    pub running: bool,
166}
167
168/// Result of a GA run.
169#[derive(Debug, Clone)]
170pub struct GaResult<I: Individual> {
171    /// The best individual found.
172    pub best: I,
173    /// Final generation reached.
174    pub generations: u32,
175    /// Total elapsed time.
176    pub elapsed: Duration,
177    /// Whether the target fitness was reached.
178    pub target_reached: bool,
179    /// Fitness history (best fitness per generation).
180    pub history: Vec<f64>,
181}
182
183/// Genetic algorithm runner.
184pub struct GaRunner<P: GaProblem> {
185    config: GaConfig,
186    problem: P,
187    cancelled: Arc<AtomicBool>,
188}
189
190impl<P: GaProblem> GaRunner<P>
191where
192    <P::Individual as Individual>::Fitness: Into<f64>,
193{
194    /// Creates a new GA runner.
195    pub fn new(config: GaConfig, problem: P) -> Self {
196        Self {
197            config,
198            problem,
199            cancelled: Arc::new(AtomicBool::new(false)),
200        }
201    }
202
203    /// Returns a handle to cancel the algorithm.
204    pub fn cancel_handle(&self) -> Arc<AtomicBool> {
205        self.cancelled.clone()
206    }
207
208    /// Runs the genetic algorithm.
209    pub fn run(&self) -> GaResult<P::Individual> {
210        self.run_with_rng(&mut thread_rng())
211    }
212
213    /// Runs the genetic algorithm with a progress callback.
214    pub fn run_with_progress<F>(&self, progress_callback: F) -> GaResult<P::Individual>
215    where
216        F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
217    {
218        self.run_with_rng_and_progress(&mut thread_rng(), Some(progress_callback))
219    }
220
221    /// Runs the genetic algorithm with a specific RNG.
222    pub fn run_with_rng<R: Rng>(&self, rng: &mut R) -> GaResult<P::Individual> {
223        self.run_with_rng_and_progress::<R, fn(GaProgress<<P::Individual as Individual>::Fitness>)>(
224            rng, None,
225        )
226    }
227
228    /// Runs the genetic algorithm with a specific RNG and optional progress callback.
229    pub fn run_with_rng_and_progress<R: Rng, F>(
230        &self,
231        rng: &mut R,
232        progress_callback: Option<F>,
233    ) -> GaResult<P::Individual>
234    where
235        F: Fn(GaProgress<<P::Individual as Individual>::Fitness>),
236    {
237        let start = Instant::now();
238        let mut history = Vec::new();
239
240        // Initialize population
241        let mut population = self
242            .problem
243            .initialize_population(self.config.population_size, rng);
244
245        // Evaluate initial population in parallel
246        self.problem.evaluate_parallel(&mut population);
247
248        // Sort by fitness (descending - higher is better)
249        population.sort_by(|a, b| {
250            b.fitness()
251                .partial_cmp(&a.fitness())
252                .unwrap_or(std::cmp::Ordering::Equal)
253        });
254
255        let mut best = population[0].clone();
256        let mut best_fitness: f64 = best.fitness().into();
257        let mut stagnation_count = 0u32;
258        let mut generation = 0u32;
259        let mut target_reached = false;
260
261        while generation < self.config.max_generations {
262            // Check cancellation
263            if self.cancelled.load(Ordering::Relaxed) {
264                break;
265            }
266
267            // Check time limit
268            if let Some(limit) = self.config.time_limit {
269                if start.elapsed() > limit {
270                    break;
271                }
272            }
273
274            // Check target fitness
275            if let Some(target) = self.config.target_fitness {
276                if best_fitness >= target {
277                    target_reached = true;
278                    break;
279                }
280            }
281
282            // Record history
283            history.push(best_fitness);
284
285            // Create new generation
286            let mut new_population = Vec::with_capacity(self.config.population_size);
287
288            // Elitism: keep the best individuals
289            for individual in population
290                .iter()
291                .take(self.config.elite_count.min(population.len()))
292            {
293                new_population.push(individual.clone());
294            }
295
296            // Fill the rest with crossover and mutation
297            // Generate all children first (sequential due to RNG dependency)
298            let mut children: Vec<P::Individual> =
299                Vec::with_capacity(self.config.population_size - new_population.len());
300
301            while children.len() < self.config.population_size - new_population.len() {
302                // Selection (tournament)
303                let parent1 = self.tournament_select(&population, rng);
304                let parent2 = self.tournament_select(&population, rng);
305
306                // Crossover
307                let mut child = if rng.gen::<f64>() < self.config.crossover_rate {
308                    parent1.crossover(parent2, rng)
309                } else {
310                    parent1.clone()
311                };
312
313                // Mutation
314                if rng.gen::<f64>() < self.config.mutation_rate {
315                    child.mutate(rng);
316                }
317
318                children.push(child);
319            }
320
321            // Evaluate all children in parallel
322            self.problem.evaluate_parallel(&mut children);
323
324            // Add evaluated children to new population
325            new_population.extend(children);
326
327            // Sort new population
328            new_population.sort_by(|a, b| {
329                b.fitness()
330                    .partial_cmp(&a.fitness())
331                    .unwrap_or(std::cmp::Ordering::Equal)
332            });
333
334            // Update best
335            let new_best_fitness: f64 = new_population[0].fitness().into();
336            if new_best_fitness > best_fitness {
337                best = new_population[0].clone();
338                best_fitness = new_best_fitness;
339                stagnation_count = 0;
340            } else {
341                stagnation_count += 1;
342            }
343
344            // Check stagnation
345            if let Some(limit) = self.config.stagnation_limit {
346                if stagnation_count >= limit {
347                    break;
348                }
349            }
350
351            // Callback to GaProblem
352            self.problem
353                .on_generation(generation, &best, &new_population);
354
355            // Progress callback
356            if let Some(ref callback) = progress_callback {
357                let avg_fitness = new_population
358                    .iter()
359                    .map(|ind| ind.fitness().into())
360                    .sum::<f64>()
361                    / new_population.len() as f64;
362
363                callback(GaProgress {
364                    generation,
365                    max_generations: self.config.max_generations,
366                    best_fitness: best.fitness(),
367                    avg_fitness,
368                    elapsed: start.elapsed(),
369                    running: true,
370                });
371            }
372
373            population = new_population;
374            generation += 1;
375        }
376
377        // Final history entry
378        history.push(best_fitness);
379
380        // Final progress callback indicating completion
381        if let Some(ref callback) = progress_callback {
382            let avg_fitness = population
383                .iter()
384                .map(|ind| ind.fitness().into())
385                .sum::<f64>()
386                / population.len().max(1) as f64;
387
388            callback(GaProgress {
389                generation,
390                max_generations: self.config.max_generations,
391                best_fitness: best.fitness(),
392                avg_fitness,
393                elapsed: start.elapsed(),
394                running: false,
395            });
396        }
397
398        GaResult {
399            best,
400            generations: generation,
401            elapsed: start.elapsed(),
402            target_reached,
403            history,
404        }
405    }
406
407    /// Tournament selection.
408    fn tournament_select<'a, R: Rng>(
409        &self,
410        population: &'a [P::Individual],
411        rng: &mut R,
412    ) -> &'a P::Individual {
413        let mut best_idx = rng.gen_range(0..population.len());
414
415        for _ in 1..self.config.tournament_size {
416            let idx = rng.gen_range(0..population.len());
417            if population[idx].fitness() > population[best_idx].fitness() {
418                best_idx = idx;
419            }
420        }
421
422        &population[best_idx]
423    }
424}
425
426/// Chromosome representation for permutation-based problems.
427#[derive(Debug, Clone)]
428pub struct PermutationChromosome {
429    /// The permutation (indices).
430    pub genes: Vec<usize>,
431    /// Additional rotation/orientation genes.
432    pub rotations: Vec<usize>,
433    /// Cached fitness value.
434    fitness: f64,
435}
436
437impl PermutationChromosome {
438    /// Creates a new chromosome with the given size.
439    pub fn new(size: usize, _rotation_options: usize) -> Self {
440        Self {
441            genes: (0..size).collect(),
442            rotations: vec![0; size],
443            fitness: f64::NEG_INFINITY,
444        }
445    }
446
447    /// Creates a random chromosome.
448    pub fn random_with_options<R: Rng>(size: usize, rotation_options: usize, rng: &mut R) -> Self {
449        let mut genes: Vec<usize> = (0..size).collect();
450        genes.shuffle(rng);
451
452        let rotations: Vec<usize> = (0..size)
453            .map(|_| rng.gen_range(0..rotation_options.max(1)))
454            .collect();
455
456        Self {
457            genes,
458            rotations,
459            fitness: f64::NEG_INFINITY,
460        }
461    }
462
463    /// Sets the fitness value.
464    pub fn set_fitness(&mut self, fitness: f64) {
465        self.fitness = fitness;
466    }
467
468    /// Returns the number of genes.
469    pub fn len(&self) -> usize {
470        self.genes.len()
471    }
472
473    /// Returns true if empty.
474    pub fn is_empty(&self) -> bool {
475        self.genes.is_empty()
476    }
477
478    /// Order crossover (OX).
479    pub fn order_crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
480        let n = self.genes.len();
481        if n < 2 {
482            return self.clone();
483        }
484
485        // Select two crossover points
486        let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
487        if p1 > p2 {
488            std::mem::swap(&mut p1, &mut p2);
489        }
490
491        // Copy segment from parent1
492        let mut child_genes = vec![usize::MAX; n];
493        let mut used = vec![false; n];
494
495        for i in p1..=p2 {
496            child_genes[i] = self.genes[i];
497            used[self.genes[i]] = true;
498        }
499
500        // Fill remaining from parent2
501        let mut j = (p2 + 1) % n;
502        for i in 0..n {
503            let idx = (p2 + 1 + i) % n;
504            if child_genes[idx] == usize::MAX {
505                while used[other.genes[j]] {
506                    j = (j + 1) % n;
507                }
508                child_genes[idx] = other.genes[j];
509                used[other.genes[j]] = true;
510                j = (j + 1) % n;
511            }
512        }
513
514        // Crossover rotations (uniform)
515        let rotations: Vec<usize> = self
516            .rotations
517            .iter()
518            .zip(&other.rotations)
519            .map(|(a, b)| if rng.gen() { *a } else { *b })
520            .collect();
521
522        Self {
523            genes: child_genes,
524            rotations,
525            fitness: f64::NEG_INFINITY,
526        }
527    }
528
529    /// Swap mutation.
530    pub fn swap_mutate<R: Rng>(&mut self, rng: &mut R) {
531        if self.genes.len() < 2 {
532            return;
533        }
534
535        let i = rng.gen_range(0..self.genes.len());
536        let j = rng.gen_range(0..self.genes.len());
537        self.genes.swap(i, j);
538        self.fitness = f64::NEG_INFINITY;
539    }
540
541    /// Rotation mutation.
542    pub fn rotation_mutate<R: Rng>(&mut self, rotation_options: usize, rng: &mut R) {
543        if self.rotations.is_empty() || rotation_options <= 1 {
544            return;
545        }
546
547        let idx = rng.gen_range(0..self.rotations.len());
548        self.rotations[idx] = rng.gen_range(0..rotation_options);
549        self.fitness = f64::NEG_INFINITY;
550    }
551
552    /// Inversion mutation (reverses a segment).
553    pub fn inversion_mutate<R: Rng>(&mut self, rng: &mut R) {
554        let n = self.genes.len();
555        if n < 2 {
556            return;
557        }
558
559        let (mut p1, mut p2) = (rng.gen_range(0..n), rng.gen_range(0..n));
560        if p1 > p2 {
561            std::mem::swap(&mut p1, &mut p2);
562        }
563
564        self.genes[p1..=p2].reverse();
565        self.fitness = f64::NEG_INFINITY;
566    }
567}
568
569impl Individual for PermutationChromosome {
570    type Fitness = f64;
571
572    fn fitness(&self) -> f64 {
573        self.fitness
574    }
575
576    fn random<R: Rng>(rng: &mut R) -> Self {
577        // Default: empty, should be overridden by problem
578        Self::random_with_options(0, 1, rng)
579    }
580
581    fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
582        self.order_crossover(other, rng)
583    }
584
585    fn mutate<R: Rng>(&mut self, rng: &mut R) {
586        // 70% swap, 30% inversion
587        if rng.gen::<f64>() < 0.7 {
588            self.swap_mutate(rng);
589        } else {
590            self.inversion_mutate(rng);
591        }
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    #[derive(Clone)]
600    struct SimpleIndividual {
601        value: f64,
602    }
603
604    impl Individual for SimpleIndividual {
605        type Fitness = f64;
606
607        fn fitness(&self) -> f64 {
608            // Maximize: -(x^2), optimal at x=0
609            -self.value * self.value
610        }
611
612        fn random<R: Rng>(rng: &mut R) -> Self {
613            Self {
614                value: rng.gen_range(-100.0..100.0),
615            }
616        }
617
618        fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
619            Self {
620                value: if rng.gen() { self.value } else { other.value },
621            }
622        }
623
624        fn mutate<R: Rng>(&mut self, rng: &mut R) {
625            self.value += rng.gen_range(-10.0..10.0);
626        }
627    }
628
629    struct SimpleProblem;
630
631    impl GaProblem for SimpleProblem {
632        type Individual = SimpleIndividual;
633
634        fn evaluate(&self, _individual: &mut Self::Individual) {
635            // Fitness is computed in Individual::fitness()
636        }
637    }
638
639    #[test]
640    fn test_ga_basic() {
641        let config = GaConfig::default()
642            .with_population_size(50)
643            .with_max_generations(100)
644            .with_target_fitness(-0.01);
645
646        let runner = GaRunner::new(config, SimpleProblem);
647        let result = runner.run();
648
649        // Should find something close to 0
650        assert!(result.best.value.abs() < 5.0);
651    }
652
653    #[test]
654    fn test_permutation_crossover() {
655        let mut rng = thread_rng();
656        let parent1 = PermutationChromosome::random_with_options(10, 4, &mut rng);
657        let parent2 = PermutationChromosome::random_with_options(10, 4, &mut rng);
658
659        let child = parent1.order_crossover(&parent2, &mut rng);
660
661        // Child should be a valid permutation
662        assert_eq!(child.genes.len(), 10);
663        let mut sorted = child.genes.clone();
664        sorted.sort();
665        assert_eq!(sorted, (0..10).collect::<Vec<_>>());
666    }
667
668    #[test]
669    fn test_permutation_mutation() {
670        let mut rng = thread_rng();
671        let mut chromosome = PermutationChromosome::random_with_options(10, 4, &mut rng);
672
673        chromosome.swap_mutate(&mut rng);
674
675        // Should still be a valid permutation
676        let mut sorted = chromosome.genes.clone();
677        sorted.sort();
678        assert_eq!(sorted, (0..10).collect::<Vec<_>>());
679    }
680}