Skip to main content

fugue_evo/operators/
mutation.rs

1//! Mutation operators
2//!
3//! This module provides various mutation operators for genetic algorithms.
4
5use rand::Rng;
6use rand_distr::{Distribution, Normal};
7
8use crate::genome::bit_string::BitString;
9use crate::genome::bounds::MultiBounds;
10use crate::genome::permutation::Permutation;
11use crate::genome::real_vector::RealVector;
12use crate::genome::traits::{
13    BinaryGenome, EvolutionaryGenome, PermutationGenome, RealValuedGenome,
14};
15use crate::genome::tree::{Function, Terminal, TreeGenome, TreeNode};
16use crate::operators::traits::{BoundedMutationOperator, MutationOperator};
17
18/// Polynomial mutation (bounded)
19///
20/// Uses the polynomial probability distribution to perturb genes.
21/// Respects bounds and is commonly used with NSGA-II.
22///
23/// Reference: Deb, K. (2001). Multi-Objective Optimization using Evolutionary Algorithms.
24#[derive(Clone, Debug)]
25pub struct PolynomialMutation {
26    /// Distribution index (typically 20-100)
27    /// Higher values = smaller mutations
28    pub eta_m: f64,
29    /// Per-gene mutation probability (default: 1/n)
30    pub mutation_probability: Option<f64>,
31}
32
33impl PolynomialMutation {
34    /// Create a new polynomial mutation with the given distribution index
35    pub fn new(eta_m: f64) -> Self {
36        assert!(eta_m >= 0.0, "Distribution index must be non-negative");
37        Self {
38            eta_m,
39            mutation_probability: None,
40        }
41    }
42
43    /// Set a fixed mutation probability per gene
44    pub fn with_probability(mut self, probability: f64) -> Self {
45        assert!(
46            (0.0..=1.0).contains(&probability),
47            "Probability must be in [0, 1]"
48        );
49        self.mutation_probability = Some(probability);
50        self
51    }
52
53    /// Apply polynomial mutation to a gene
54    fn mutate_gene<R: Rng>(&self, gene: f64, min: f64, max: f64, rng: &mut R) -> f64 {
55        let range = max - min;
56        if range <= 0.0 {
57            return gene;
58        }
59
60        let delta1 = (gene - min) / range;
61        let delta2 = (max - gene) / range;
62
63        let u = rng.gen::<f64>();
64        let delta_q = if u <= 0.5 {
65            let val = 2.0 * u + (1.0 - 2.0 * u) * (1.0 - delta1).powf(self.eta_m + 1.0);
66            val.powf(1.0 / (self.eta_m + 1.0)) - 1.0
67        } else {
68            let val = 2.0 * (1.0 - u) + 2.0 * (u - 0.5) * (1.0 - delta2).powf(self.eta_m + 1.0);
69            1.0 - val.powf(1.0 / (self.eta_m + 1.0))
70        };
71
72        (gene + delta_q * range).clamp(min, max)
73    }
74}
75
76impl MutationOperator<RealVector> for PolynomialMutation {
77    fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
78        // Use default bounds if not specified
79        let default_bounds = MultiBounds::symmetric(1e10, genome.dimension());
80        self.mutate_bounded(genome, &default_bounds, rng);
81    }
82
83    fn mutation_probability(&self) -> f64 {
84        self.mutation_probability.unwrap_or(1.0)
85    }
86}
87
88impl BoundedMutationOperator<RealVector> for PolynomialMutation {
89    fn mutate_bounded<R: Rng>(&self, genome: &mut RealVector, bounds: &MultiBounds, rng: &mut R) {
90        let n = genome.dimension();
91        let prob = self.mutation_probability.unwrap_or(1.0 / n as f64);
92
93        for i in 0..n {
94            if rng.gen::<f64>() < prob {
95                if let Some(bound) = bounds.get(i) {
96                    genome.genes_mut()[i] =
97                        self.mutate_gene(genome.genes()[i], bound.min, bound.max, rng);
98                }
99            }
100        }
101    }
102}
103
104/// Gaussian mutation
105///
106/// Adds Gaussian noise to each gene.
107#[derive(Clone, Debug)]
108pub struct GaussianMutation {
109    /// Standard deviation of the Gaussian noise
110    pub sigma: f64,
111    /// Per-gene mutation probability
112    pub mutation_probability: Option<f64>,
113}
114
115impl GaussianMutation {
116    /// Create a new Gaussian mutation with the given standard deviation
117    pub fn new(sigma: f64) -> Self {
118        assert!(sigma >= 0.0, "Sigma must be non-negative");
119        Self {
120            sigma,
121            mutation_probability: None,
122        }
123    }
124
125    /// Set a fixed mutation probability per gene
126    pub fn with_probability(mut self, probability: f64) -> Self {
127        assert!(
128            (0.0..=1.0).contains(&probability),
129            "Probability must be in [0, 1]"
130        );
131        self.mutation_probability = Some(probability);
132        self
133    }
134}
135
136impl MutationOperator<RealVector> for GaussianMutation {
137    fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
138        let n = genome.dimension();
139        let prob = self.mutation_probability.unwrap_or(1.0 / n as f64);
140        let normal = Normal::new(0.0, self.sigma).unwrap();
141
142        for gene in genome.genes_mut() {
143            if rng.gen::<f64>() < prob {
144                *gene += normal.sample(rng);
145            }
146        }
147    }
148
149    fn mutation_probability(&self) -> f64 {
150        self.mutation_probability.unwrap_or(1.0)
151    }
152}
153
154impl BoundedMutationOperator<RealVector> for GaussianMutation {
155    fn mutate_bounded<R: Rng>(&self, genome: &mut RealVector, bounds: &MultiBounds, rng: &mut R) {
156        let n = genome.dimension();
157        let prob = self.mutation_probability.unwrap_or(1.0 / n as f64);
158        let normal = Normal::new(0.0, self.sigma).unwrap();
159
160        for i in 0..n {
161            if rng.gen::<f64>() < prob {
162                genome.genes_mut()[i] += normal.sample(rng);
163                if let Some(bound) = bounds.get(i) {
164                    genome.genes_mut()[i] = bound.clamp(genome.genes()[i]);
165                }
166            }
167        }
168    }
169}
170
171/// Uniform mutation
172///
173/// Replaces genes with random values within bounds.
174#[derive(Clone, Debug)]
175pub struct UniformMutation {
176    /// Per-gene mutation probability
177    pub mutation_probability: Option<f64>,
178}
179
180impl UniformMutation {
181    /// Create a new uniform mutation
182    pub fn new() -> Self {
183        Self {
184            mutation_probability: None,
185        }
186    }
187
188    /// Set a fixed mutation probability per gene
189    pub fn with_probability(mut self, probability: f64) -> Self {
190        assert!(
191            (0.0..=1.0).contains(&probability),
192            "Probability must be in [0, 1]"
193        );
194        self.mutation_probability = Some(probability);
195        self
196    }
197}
198
199impl Default for UniformMutation {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205impl MutationOperator<RealVector> for UniformMutation {
206    fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
207        let default_bounds = MultiBounds::symmetric(1.0, genome.dimension());
208        self.mutate_bounded(genome, &default_bounds, rng);
209    }
210}
211
212impl BoundedMutationOperator<RealVector> for UniformMutation {
213    fn mutate_bounded<R: Rng>(&self, genome: &mut RealVector, bounds: &MultiBounds, rng: &mut R) {
214        let n = genome.dimension();
215        let prob = self.mutation_probability.unwrap_or(1.0 / n as f64);
216
217        for i in 0..n {
218            if rng.gen::<f64>() < prob {
219                if let Some(bound) = bounds.get(i) {
220                    genome.genes_mut()[i] = rng.gen_range(bound.min..=bound.max);
221                }
222            }
223        }
224    }
225}
226
227/// Bit-flip mutation for bit strings
228///
229/// Flips each bit with a given probability.
230#[derive(Clone, Debug)]
231pub struct BitFlipMutation {
232    /// Per-bit mutation probability (default: 1/n)
233    pub mutation_probability: Option<f64>,
234}
235
236impl BitFlipMutation {
237    /// Create a new bit-flip mutation
238    pub fn new() -> Self {
239        Self {
240            mutation_probability: None,
241        }
242    }
243
244    /// Set a fixed mutation probability per bit
245    pub fn with_probability(mut self, probability: f64) -> Self {
246        assert!(
247            (0.0..=1.0).contains(&probability),
248            "Probability must be in [0, 1]"
249        );
250        self.mutation_probability = Some(probability);
251        self
252    }
253}
254
255impl Default for BitFlipMutation {
256    fn default() -> Self {
257        Self::new()
258    }
259}
260
261impl MutationOperator<BitString> for BitFlipMutation {
262    fn mutate<R: Rng>(&self, genome: &mut BitString, rng: &mut R) {
263        let n = genome.len();
264        let prob = self.mutation_probability.unwrap_or(1.0 / n as f64);
265
266        for i in 0..n {
267            if rng.gen::<f64>() < prob {
268                genome.flip(i);
269            }
270        }
271    }
272
273    fn mutation_probability(&self) -> f64 {
274        self.mutation_probability.unwrap_or(1.0)
275    }
276}
277
278/// Swap mutation for permutation genomes (also works on any genome)
279///
280/// Swaps two random positions in the genome.
281#[derive(Clone, Debug, Default)]
282pub struct SwapMutation {
283    /// Number of swaps to perform
284    pub num_swaps: usize,
285}
286
287impl SwapMutation {
288    /// Create a new swap mutation with a single swap
289    pub fn new() -> Self {
290        Self { num_swaps: 1 }
291    }
292
293    /// Create with multiple swaps
294    pub fn with_swaps(num_swaps: usize) -> Self {
295        Self { num_swaps }
296    }
297}
298
299impl MutationOperator<BitString> for SwapMutation {
300    fn mutate<R: Rng>(&self, genome: &mut BitString, rng: &mut R) {
301        let n = genome.len();
302        if n < 2 {
303            return;
304        }
305
306        for _ in 0..self.num_swaps {
307            let i = rng.gen_range(0..n);
308            let j = rng.gen_range(0..n);
309            if i != j {
310                let temp = genome.bits()[i];
311                genome.bits_mut()[i] = genome.bits()[j];
312                genome.bits_mut()[j] = temp;
313            }
314        }
315    }
316}
317
318impl MutationOperator<RealVector> for SwapMutation {
319    fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
320        let n = genome.dimension();
321        if n < 2 {
322            return;
323        }
324
325        for _ in 0..self.num_swaps {
326            let i = rng.gen_range(0..n);
327            let j = rng.gen_range(0..n);
328            if i != j {
329                genome.genes_mut().swap(i, j);
330            }
331        }
332    }
333}
334
335/// Scramble mutation
336///
337/// Scrambles a random segment of the genome.
338#[derive(Clone, Debug, Default)]
339pub struct ScrambleMutation;
340
341impl ScrambleMutation {
342    /// Create a new scramble mutation
343    pub fn new() -> Self {
344        Self
345    }
346}
347
348impl MutationOperator<BitString> for ScrambleMutation {
349    fn mutate<R: Rng>(&self, genome: &mut BitString, rng: &mut R) {
350        use rand::seq::SliceRandom;
351
352        let n = genome.len();
353        if n < 2 {
354            return;
355        }
356
357        let mut start = rng.gen_range(0..n);
358        let mut end = rng.gen_range(0..n);
359        if start > end {
360            std::mem::swap(&mut start, &mut end);
361        }
362
363        // Extract segment, shuffle, and put back
364        let segment: Vec<bool> = (start..=end).map(|i| genome.bits()[i]).collect();
365        let mut shuffled = segment;
366        shuffled.shuffle(rng);
367
368        for (i, val) in shuffled.into_iter().enumerate() {
369            genome.bits_mut()[start + i] = val;
370        }
371    }
372}
373
374impl MutationOperator<RealVector> for ScrambleMutation {
375    fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
376        use rand::seq::SliceRandom;
377
378        let n = genome.dimension();
379        if n < 2 {
380            return;
381        }
382
383        let mut start = rng.gen_range(0..n);
384        let mut end = rng.gen_range(0..n);
385        if start > end {
386            std::mem::swap(&mut start, &mut end);
387        }
388
389        // Shuffle the segment in place
390        let slice = &mut genome.genes_mut()[start..=end];
391        slice.shuffle(rng);
392    }
393}
394
395// =============================================================================
396// Permutation Mutation Operators
397// =============================================================================
398
399/// Swap mutation for permutation genomes
400///
401/// Swaps two random positions in the permutation.
402/// This is one of the simplest and most commonly used permutation mutations.
403#[derive(Clone, Debug, Default)]
404pub struct PermutationSwapMutation {
405    /// Number of swaps to perform
406    pub num_swaps: usize,
407}
408
409impl PermutationSwapMutation {
410    /// Create a new swap mutation with a single swap
411    pub fn new() -> Self {
412        Self { num_swaps: 1 }
413    }
414
415    /// Create with multiple swaps
416    pub fn with_swaps(num_swaps: usize) -> Self {
417        Self { num_swaps }
418    }
419}
420
421impl MutationOperator<Permutation> for PermutationSwapMutation {
422    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
423        let n = genome.dimension();
424        if n < 2 {
425            return;
426        }
427
428        for _ in 0..self.num_swaps {
429            let i = rng.gen_range(0..n);
430            let j = rng.gen_range(0..n);
431            if i != j {
432                genome.swap(i, j);
433            }
434        }
435    }
436}
437
438/// Insert mutation for permutation genomes
439///
440/// Removes an element from one position and inserts it at another.
441/// This preserves adjacencies better than swap mutation.
442#[derive(Clone, Debug, Default)]
443pub struct InsertMutation {
444    /// Number of insert operations to perform
445    pub num_inserts: usize,
446}
447
448impl InsertMutation {
449    /// Create a new insert mutation with a single insert
450    pub fn new() -> Self {
451        Self { num_inserts: 1 }
452    }
453
454    /// Create with multiple inserts
455    pub fn with_inserts(num_inserts: usize) -> Self {
456        Self { num_inserts }
457    }
458}
459
460impl MutationOperator<Permutation> for InsertMutation {
461    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
462        let n = genome.dimension();
463        if n < 2 {
464            return;
465        }
466
467        for _ in 0..self.num_inserts {
468            let from = rng.gen_range(0..n);
469            let to = rng.gen_range(0..n);
470            if from != to {
471                genome.insert(from, to);
472            }
473        }
474    }
475}
476
477/// Inversion mutation (2-opt) for permutation genomes
478///
479/// Reverses a random segment of the permutation.
480/// This is particularly effective for TSP-like problems as it can
481/// remove crossing edges.
482#[derive(Clone, Debug, Default)]
483pub struct InversionMutation;
484
485impl InversionMutation {
486    /// Create a new inversion mutation
487    pub fn new() -> Self {
488        Self
489    }
490}
491
492impl MutationOperator<Permutation> for InversionMutation {
493    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
494        let n = genome.dimension();
495        if n < 2 {
496            return;
497        }
498
499        let mut start = rng.gen_range(0..n);
500        let mut end = rng.gen_range(0..n);
501        if start > end {
502            std::mem::swap(&mut start, &mut end);
503        }
504
505        genome.reverse_segment(start, end);
506    }
507}
508
509/// Scramble mutation for permutation genomes
510///
511/// Shuffles a random segment of the permutation.
512/// More disruptive than inversion, but still preserves some structure.
513#[derive(Clone, Debug, Default)]
514pub struct PermutationScrambleMutation;
515
516impl PermutationScrambleMutation {
517    /// Create a new scramble mutation
518    pub fn new() -> Self {
519        Self
520    }
521}
522
523impl MutationOperator<Permutation> for PermutationScrambleMutation {
524    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
525        use rand::seq::SliceRandom;
526
527        let n = genome.dimension();
528        if n < 2 {
529            return;
530        }
531
532        let mut start = rng.gen_range(0..n);
533        let mut end = rng.gen_range(0..n);
534        if start > end {
535            std::mem::swap(&mut start, &mut end);
536        }
537
538        // Shuffle the segment
539        let perm = genome.permutation_mut();
540        perm[start..=end].shuffle(rng);
541    }
542}
543
544/// Displacement mutation for permutation genomes
545///
546/// Selects a segment, removes it, and inserts it at a random position.
547/// This is similar to insert mutation but operates on segments.
548#[derive(Clone, Debug, Default)]
549pub struct DisplacementMutation;
550
551impl DisplacementMutation {
552    /// Create a new displacement mutation
553    pub fn new() -> Self {
554        Self
555    }
556}
557
558impl MutationOperator<Permutation> for DisplacementMutation {
559    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
560        let n = genome.dimension();
561        if n < 3 {
562            return;
563        }
564
565        // Select segment
566        let mut start = rng.gen_range(0..n);
567        let mut end = rng.gen_range(0..n);
568        if start > end {
569            std::mem::swap(&mut start, &mut end);
570        }
571
572        let segment_len = end - start + 1;
573        if segment_len >= n {
574            return; // Can't displace the entire permutation
575        }
576
577        // Extract segment
578        let perm = genome.permutation_mut();
579        let segment: Vec<usize> = perm[start..=end].to_vec();
580
581        // Remove segment
582        let remaining: Vec<usize> = perm[..start]
583            .iter()
584            .chain(perm[end + 1..].iter())
585            .copied()
586            .collect();
587
588        // Choose insertion point in remaining
589        let insert_pos = rng.gen_range(0..=remaining.len());
590
591        // Rebuild permutation
592        let new_perm: Vec<usize> = remaining[..insert_pos]
593            .iter()
594            .chain(segment.iter())
595            .chain(remaining[insert_pos..].iter())
596            .copied()
597            .collect();
598
599        perm.copy_from_slice(&new_perm);
600    }
601}
602
603/// Adaptive mutation rate for permutation genomes
604///
605/// Combines multiple mutation operators with configurable probabilities.
606#[derive(Clone, Debug)]
607pub struct AdaptivePermutationMutation {
608    /// Probability of swap mutation
609    pub swap_prob: f64,
610    /// Probability of insert mutation
611    pub insert_prob: f64,
612    /// Probability of inversion mutation
613    pub inversion_prob: f64,
614    /// Probability of scramble mutation
615    pub scramble_prob: f64,
616}
617
618impl AdaptivePermutationMutation {
619    /// Create with default probabilities (each equally likely)
620    pub fn new() -> Self {
621        Self {
622            swap_prob: 0.25,
623            insert_prob: 0.25,
624            inversion_prob: 0.25,
625            scramble_prob: 0.25,
626        }
627    }
628
629    /// Create with custom probabilities
630    pub fn with_probs(swap: f64, insert: f64, inversion: f64, scramble: f64) -> Self {
631        Self {
632            swap_prob: swap,
633            insert_prob: insert,
634            inversion_prob: inversion,
635            scramble_prob: scramble,
636        }
637    }
638}
639
640impl Default for AdaptivePermutationMutation {
641    fn default() -> Self {
642        Self::new()
643    }
644}
645
646impl MutationOperator<Permutation> for AdaptivePermutationMutation {
647    fn mutate<R: Rng>(&self, genome: &mut Permutation, rng: &mut R) {
648        let total = self.swap_prob + self.insert_prob + self.inversion_prob + self.scramble_prob;
649        if total <= 0.0 {
650            return;
651        }
652
653        let r = rng.gen::<f64>() * total;
654        let mut cumulative = 0.0;
655
656        cumulative += self.swap_prob;
657        if r < cumulative {
658            PermutationSwapMutation::new().mutate(genome, rng);
659            return;
660        }
661
662        cumulative += self.insert_prob;
663        if r < cumulative {
664            InsertMutation::new().mutate(genome, rng);
665            return;
666        }
667
668        cumulative += self.inversion_prob;
669        if r < cumulative {
670            InversionMutation::new().mutate(genome, rng);
671            return;
672        }
673
674        PermutationScrambleMutation::new().mutate(genome, rng);
675    }
676}
677
678// =============================================================================
679// Tree (GP) Mutation Operators
680// =============================================================================
681
682/// Point mutation for tree genomes (genetic programming)
683///
684/// Selects a random node and replaces it with a new random node of the same
685/// type. For function nodes, the replacement has the same arity. For terminal
686/// nodes, another random terminal is selected.
687///
688/// This is a non-destructive mutation that preserves tree structure while
689/// changing individual nodes.
690#[derive(Clone, Debug)]
691pub struct PointMutation {
692    /// Per-node mutation probability
693    pub mutation_probability: f64,
694    /// Probability of selecting a function node (vs terminal)
695    pub function_probability: f64,
696}
697
698impl PointMutation {
699    /// Create a new point mutation with default settings
700    ///
701    /// Defaults: 0.1 per-node probability, 0.9 function probability
702    pub fn new() -> Self {
703        Self {
704            mutation_probability: 0.1,
705            function_probability: 0.9,
706        }
707    }
708
709    /// Set the per-node mutation probability
710    pub fn with_probability(mut self, probability: f64) -> Self {
711        assert!(
712            (0.0..=1.0).contains(&probability),
713            "Probability must be in [0, 1]"
714        );
715        self.mutation_probability = probability;
716        self
717    }
718
719    /// Set the function selection probability
720    pub fn with_function_probability(mut self, probability: f64) -> Self {
721        assert!(
722            (0.0..=1.0).contains(&probability),
723            "Probability must be in [0, 1]"
724        );
725        self.function_probability = probability;
726        self
727    }
728
729    /// Mutate a single node, returning a new node of the same type/arity
730    fn mutate_node<T: Terminal, F: Function, R: Rng>(
731        &self,
732        node: &TreeNode<T, F>,
733        rng: &mut R,
734    ) -> TreeNode<T, F> {
735        match node {
736            TreeNode::Terminal(_) => {
737                // Replace with a random terminal
738                TreeNode::Terminal(T::random(rng))
739            }
740            TreeNode::Function(func, children) => {
741                // Find a function with the same arity
742                let target_arity = func.arity();
743                let funcs = F::functions();
744
745                // Filter functions with matching arity
746                let matching_funcs: Vec<&F> =
747                    funcs.iter().filter(|f| f.arity() == target_arity).collect();
748
749                if matching_funcs.is_empty() {
750                    // No matching arity, return original
751                    TreeNode::Function(func.clone(), children.clone())
752                } else {
753                    // Select a random function with the same arity
754                    let new_func = matching_funcs[rng.gen_range(0..matching_funcs.len())].clone();
755                    TreeNode::Function(new_func, children.clone())
756                }
757            }
758        }
759    }
760
761    /// Recursively apply point mutation to tree nodes
762    fn mutate_recursive<T: Terminal, F: Function, R: Rng>(
763        &self,
764        node: &TreeNode<T, F>,
765        rng: &mut R,
766    ) -> TreeNode<T, F> {
767        // Decide if we mutate this node
768        let mutated_node = if rng.gen::<f64>() < self.mutation_probability {
769            self.mutate_node(node, rng)
770        } else {
771            node.clone()
772        };
773
774        // Recurse into children if this is a function node
775        match mutated_node {
776            TreeNode::Terminal(t) => TreeNode::Terminal(t),
777            TreeNode::Function(func, children) => {
778                let new_children: Vec<TreeNode<T, F>> = children
779                    .iter()
780                    .map(|c| self.mutate_recursive(c, rng))
781                    .collect();
782                TreeNode::Function(func, new_children)
783            }
784        }
785    }
786}
787
788impl Default for PointMutation {
789    fn default() -> Self {
790        Self::new()
791    }
792}
793
794impl<T: Terminal, F: Function> MutationOperator<TreeGenome<T, F>> for PointMutation {
795    fn mutate<R: Rng>(&self, genome: &mut TreeGenome<T, F>, rng: &mut R) {
796        let new_root = self.mutate_recursive(&genome.root, rng);
797        genome.root = new_root;
798    }
799
800    fn mutation_probability(&self) -> f64 {
801        self.mutation_probability
802    }
803}
804
805/// Subtree mutation for tree genomes (genetic programming)
806///
807/// Replaces a randomly selected subtree with a new randomly generated subtree.
808/// This is a more disruptive mutation than point mutation.
809#[derive(Clone, Debug)]
810pub struct SubtreeMutation {
811    /// Maximum depth of the generated subtree
812    pub max_subtree_depth: usize,
813    /// Probability of selecting a function node for replacement
814    pub function_probability: f64,
815    /// Terminal probability for grow method
816    pub terminal_probability: f64,
817}
818
819impl SubtreeMutation {
820    /// Create a new subtree mutation with default settings
821    pub fn new() -> Self {
822        Self {
823            max_subtree_depth: 4,
824            function_probability: 0.9,
825            terminal_probability: 0.3,
826        }
827    }
828
829    /// Set the maximum depth for generated subtrees
830    pub fn with_max_depth(mut self, depth: usize) -> Self {
831        self.max_subtree_depth = depth;
832        self
833    }
834
835    /// Set the function selection probability
836    pub fn with_function_probability(mut self, probability: f64) -> Self {
837        assert!(
838            (0.0..=1.0).contains(&probability),
839            "Probability must be in [0, 1]"
840        );
841        self.function_probability = probability;
842        self
843    }
844
845    /// Set the terminal probability for tree generation
846    pub fn with_terminal_probability(mut self, probability: f64) -> Self {
847        assert!(
848            (0.0..=1.0).contains(&probability),
849            "Probability must be in [0, 1]"
850        );
851        self.terminal_probability = probability;
852        self
853    }
854}
855
856impl Default for SubtreeMutation {
857    fn default() -> Self {
858        Self::new()
859    }
860}
861
862impl<T: Terminal, F: Function> MutationOperator<TreeGenome<T, F>> for SubtreeMutation {
863    fn mutate<R: Rng>(&self, genome: &mut TreeGenome<T, F>, rng: &mut R) {
864        // Decide whether to select a function or terminal position
865        let position = if rng.gen::<f64>() < self.function_probability {
866            genome
867                .random_function_position(rng)
868                .unwrap_or_else(|| genome.random_terminal_position(rng).unwrap_or_default())
869        } else {
870            genome
871                .random_terminal_position(rng)
872                .unwrap_or_else(|| genome.random_function_position(rng).unwrap_or_default())
873        };
874
875        // Calculate remaining depth budget
876        let current_depth = position.len();
877        let remaining_depth = genome.max_depth.saturating_sub(current_depth);
878        let subtree_depth = remaining_depth.min(self.max_subtree_depth).max(1);
879
880        // Generate a new subtree using the grow method
881        let new_subtree: TreeGenome<T, F> =
882            TreeGenome::generate_grow(rng, subtree_depth, self.terminal_probability);
883
884        // Replace the subtree
885        genome.root.replace_subtree(&position, new_subtree.root);
886    }
887}
888
889/// Hoist mutation for tree genomes (genetic programming)
890///
891/// Selects a random subtree and replaces the entire tree with it.
892/// This is useful for bloat control.
893#[derive(Clone, Debug, Default)]
894pub struct HoistMutation {
895    /// Probability of selecting a function node
896    pub function_probability: f64,
897}
898
899impl HoistMutation {
900    /// Create a new hoist mutation
901    pub fn new() -> Self {
902        Self {
903            function_probability: 0.5,
904        }
905    }
906
907    /// Set the function selection probability
908    pub fn with_function_probability(mut self, probability: f64) -> Self {
909        assert!(
910            (0.0..=1.0).contains(&probability),
911            "Probability must be in [0, 1]"
912        );
913        self.function_probability = probability;
914        self
915    }
916}
917
918impl<T: Terminal, F: Function> MutationOperator<TreeGenome<T, F>> for HoistMutation {
919    fn mutate<R: Rng>(&self, genome: &mut TreeGenome<T, F>, rng: &mut R) {
920        // Select a random position (prefer function nodes to make it interesting)
921        let position = if rng.gen::<f64>() < self.function_probability {
922            genome
923                .random_function_position(rng)
924                .unwrap_or_else(|| genome.random_terminal_position(rng).unwrap_or_default())
925        } else {
926            genome.random_position(rng)
927        };
928
929        // Skip if selecting the root (no change)
930        if position.is_empty() {
931            return;
932        }
933
934        // Get the subtree and make it the new root
935        if let Some(subtree) = genome.root.get_subtree(&position) {
936            genome.root = subtree.clone();
937        }
938    }
939}
940
941/// Shrink mutation for tree genomes (genetic programming)
942///
943/// Replaces a randomly selected subtree with one of its terminals.
944/// This reduces tree size and helps with bloat control.
945#[derive(Clone, Debug, Default)]
946pub struct ShrinkMutation;
947
948impl ShrinkMutation {
949    /// Create a new shrink mutation
950    pub fn new() -> Self {
951        Self
952    }
953}
954
955impl<T: Terminal, F: Function> MutationOperator<TreeGenome<T, F>> for ShrinkMutation {
956    fn mutate<R: Rng>(&self, genome: &mut TreeGenome<T, F>, rng: &mut R) {
957        // Find a function node to shrink
958        if let Some(func_position) = genome.random_function_position(rng) {
959            // Skip root to maintain some structure
960            if func_position.is_empty() {
961                return;
962            }
963
964            // Get a terminal from within the selected subtree
965            if let Some(subtree) = genome.root.get_subtree(&func_position) {
966                let terminal_positions = subtree.terminal_positions();
967                if !terminal_positions.is_empty() {
968                    // Pick a random terminal from the subtree
969                    let term_pos = &terminal_positions[rng.gen_range(0..terminal_positions.len())];
970
971                    // Get the terminal value
972                    if let Some(terminal_node) = subtree.get_subtree(term_pos) {
973                        let replacement = terminal_node.clone();
974                        // Replace the function node with the terminal
975                        genome.root.replace_subtree(&func_position, replacement);
976                    }
977                }
978            }
979        }
980    }
981}
982
983/// Adaptive mutation for tree genomes (genetic programming)
984///
985/// Combines multiple tree mutations with configurable probabilities.
986#[derive(Clone, Debug)]
987pub struct AdaptiveTreeMutation {
988    /// Probability of point mutation
989    pub point_prob: f64,
990    /// Probability of subtree mutation
991    pub subtree_prob: f64,
992    /// Probability of hoist mutation
993    pub hoist_prob: f64,
994    /// Probability of shrink mutation
995    pub shrink_prob: f64,
996}
997
998impl AdaptiveTreeMutation {
999    /// Create with default probabilities
1000    pub fn new() -> Self {
1001        Self {
1002            point_prob: 0.4,
1003            subtree_prob: 0.3,
1004            hoist_prob: 0.15,
1005            shrink_prob: 0.15,
1006        }
1007    }
1008
1009    /// Create with custom probabilities
1010    pub fn with_probs(point: f64, subtree: f64, hoist: f64, shrink: f64) -> Self {
1011        Self {
1012            point_prob: point,
1013            subtree_prob: subtree,
1014            hoist_prob: hoist,
1015            shrink_prob: shrink,
1016        }
1017    }
1018}
1019
1020impl Default for AdaptiveTreeMutation {
1021    fn default() -> Self {
1022        Self::new()
1023    }
1024}
1025
1026impl<T: Terminal, F: Function> MutationOperator<TreeGenome<T, F>> for AdaptiveTreeMutation {
1027    fn mutate<R: Rng>(&self, genome: &mut TreeGenome<T, F>, rng: &mut R) {
1028        let total = self.point_prob + self.subtree_prob + self.hoist_prob + self.shrink_prob;
1029        if total <= 0.0 {
1030            return;
1031        }
1032
1033        let r = rng.gen::<f64>() * total;
1034        let mut cumulative = 0.0;
1035
1036        cumulative += self.point_prob;
1037        if r < cumulative {
1038            PointMutation::new().mutate(genome, rng);
1039            return;
1040        }
1041
1042        cumulative += self.subtree_prob;
1043        if r < cumulative {
1044            SubtreeMutation::new().mutate(genome, rng);
1045            return;
1046        }
1047
1048        cumulative += self.hoist_prob;
1049        if r < cumulative {
1050            HoistMutation::new().mutate(genome, rng);
1051            return;
1052        }
1053
1054        ShrinkMutation::new().mutate(genome, rng);
1055    }
1056}
1057
1058#[cfg(test)]
1059mod tests {
1060    use super::*;
1061    use approx::assert_relative_eq;
1062
1063    #[test]
1064    fn test_polynomial_mutation_respects_bounds() {
1065        let mut rng = rand::thread_rng();
1066        let bounds = MultiBounds::symmetric(5.0, 10);
1067
1068        for _ in 0..100 {
1069            let mut genome = RealVector::generate(&mut rng, &bounds);
1070            let mutation = PolynomialMutation::new(20.0).with_probability(1.0);
1071            mutation.mutate_bounded(&mut genome, &bounds, &mut rng);
1072
1073            for (i, &gene) in genome.genes().iter().enumerate() {
1074                let bound = bounds.get(i).unwrap();
1075                assert!(
1076                    gene >= bound.min && gene <= bound.max,
1077                    "Gene {} out of bounds: {} not in [{}, {}]",
1078                    i,
1079                    gene,
1080                    bound.min,
1081                    bound.max
1082                );
1083            }
1084        }
1085    }
1086
1087    #[test]
1088    fn test_polynomial_mutation_changes_genome() {
1089        let mut rng = rand::thread_rng();
1090        let bounds = MultiBounds::symmetric(5.0, 10);
1091        let original = RealVector::zeros(10);
1092        let mut genome = original.clone();
1093
1094        let mutation = PolynomialMutation::new(20.0).with_probability(1.0);
1095        mutation.mutate_bounded(&mut genome, &bounds, &mut rng);
1096
1097        // At least some genes should have changed
1098        let changed = genome
1099            .genes()
1100            .iter()
1101            .zip(original.genes())
1102            .filter(|(&a, &b)| a != b)
1103            .count();
1104        assert!(changed > 0, "No genes were mutated");
1105    }
1106
1107    #[test]
1108    fn test_polynomial_mutation_eta_effect() {
1109        let mut rng = rand::thread_rng();
1110        let bounds = MultiBounds::symmetric(1.0, 1);
1111
1112        // Low eta = larger mutations
1113        let low_eta = PolynomialMutation::new(1.0).with_probability(1.0);
1114        // High eta = smaller mutations
1115        let high_eta = PolynomialMutation::new(100.0).with_probability(1.0);
1116
1117        let mut low_total_change = 0.0;
1118        let mut high_total_change = 0.0;
1119        let trials = 1000;
1120
1121        for _ in 0..trials {
1122            let mut genome_low = RealVector::new(vec![0.0]);
1123            let mut genome_high = RealVector::new(vec![0.0]);
1124
1125            low_eta.mutate_bounded(&mut genome_low, &bounds, &mut rng);
1126            high_eta.mutate_bounded(&mut genome_high, &bounds, &mut rng);
1127
1128            low_total_change += genome_low[0].abs();
1129            high_total_change += genome_high[0].abs();
1130        }
1131
1132        assert!(
1133            low_total_change > high_total_change,
1134            "Low eta should produce larger average changes"
1135        );
1136    }
1137
1138    #[test]
1139    fn test_gaussian_mutation_changes_genome() {
1140        let mut rng = rand::thread_rng();
1141        let original = RealVector::zeros(10);
1142        let mut genome = original.clone();
1143
1144        let mutation = GaussianMutation::new(0.1).with_probability(1.0);
1145        mutation.mutate(&mut genome, &mut rng);
1146
1147        assert_ne!(genome, original);
1148    }
1149
1150    #[test]
1151    fn test_gaussian_mutation_bounded() {
1152        let mut rng = rand::thread_rng();
1153        let bounds = MultiBounds::symmetric(1.0, 10);
1154
1155        for _ in 0..100 {
1156            let mut genome = RealVector::zeros(10);
1157            let mutation = GaussianMutation::new(10.0).with_probability(1.0);
1158            mutation.mutate_bounded(&mut genome, &bounds, &mut rng);
1159
1160            for (i, &gene) in genome.genes().iter().enumerate() {
1161                let bound = bounds.get(i).unwrap();
1162                assert!(gene >= bound.min && gene <= bound.max);
1163            }
1164        }
1165    }
1166
1167    #[test]
1168    fn test_uniform_mutation() {
1169        let mut rng = rand::thread_rng();
1170        let bounds = MultiBounds::symmetric(1.0, 10);
1171
1172        for _ in 0..100 {
1173            let mut genome = RealVector::zeros(10);
1174            let mutation = UniformMutation::new().with_probability(1.0);
1175            mutation.mutate_bounded(&mut genome, &bounds, &mut rng);
1176
1177            for (i, &gene) in genome.genes().iter().enumerate() {
1178                let bound = bounds.get(i).unwrap();
1179                assert!(gene >= bound.min && gene <= bound.max);
1180            }
1181        }
1182    }
1183
1184    #[test]
1185    fn test_bit_flip_mutation() {
1186        let mut rng = rand::thread_rng();
1187        let original = BitString::zeros(100);
1188        let mut genome = original.clone();
1189
1190        let mutation = BitFlipMutation::new().with_probability(0.5);
1191        mutation.mutate(&mut genome, &mut rng);
1192
1193        // About half should be flipped
1194        let flipped = genome.count_ones();
1195        assert!(
1196            flipped > 20 && flipped < 80,
1197            "Expected ~50 flips, got {}",
1198            flipped
1199        );
1200    }
1201
1202    #[test]
1203    fn test_bit_flip_mutation_default_probability() {
1204        let mut rng = rand::thread_rng();
1205        let original = BitString::zeros(100);
1206        let mut genome = original.clone();
1207
1208        let mutation = BitFlipMutation::new(); // 1/n probability
1209        mutation.mutate(&mut genome, &mut rng);
1210
1211        // With 1/100 probability, expect ~1 flip on average
1212        // But due to randomness, we just check some change occurred
1213        // over multiple trials
1214        let mut total_flips = 0;
1215        for _ in 0..100 {
1216            let mut g = BitString::zeros(100);
1217            mutation.mutate(&mut g, &mut rng);
1218            total_flips += g.count_ones();
1219        }
1220
1221        // Average should be close to 1
1222        let avg = total_flips as f64 / 100.0;
1223        assert!(avg > 0.5 && avg < 2.0, "Expected avg ~1, got {}", avg);
1224    }
1225
1226    #[test]
1227    fn test_swap_mutation() {
1228        let mut rng = rand::thread_rng();
1229        let mut genome = RealVector::new(vec![0.0, 1.0, 2.0, 3.0, 4.0]);
1230
1231        let mutation = SwapMutation::new();
1232        mutation.mutate(&mut genome, &mut rng);
1233
1234        // The sum should be preserved
1235        let sum: f64 = genome.genes().iter().sum();
1236        assert_relative_eq!(sum, 10.0);
1237    }
1238
1239    #[test]
1240    fn test_swap_mutation_multiple() {
1241        let mut rng = rand::thread_rng();
1242        let original: Vec<f64> = (0..10).map(|i| i as f64).collect();
1243        let mut genome = RealVector::new(original.clone());
1244
1245        let mutation = SwapMutation::with_swaps(5);
1246        mutation.mutate(&mut genome, &mut rng);
1247
1248        // Sum should be preserved
1249        let sum: f64 = genome.genes().iter().sum();
1250        assert_relative_eq!(sum, 45.0);
1251    }
1252
1253    #[test]
1254    fn test_scramble_mutation() {
1255        let mut rng = rand::thread_rng();
1256        let original: Vec<f64> = (0..10).map(|i| i as f64).collect();
1257        let mut genome = RealVector::new(original.clone());
1258
1259        let mutation = ScrambleMutation::new();
1260        mutation.mutate(&mut genome, &mut rng);
1261
1262        // Sum should be preserved
1263        let sum: f64 = genome.genes().iter().sum();
1264        assert_relative_eq!(sum, 45.0);
1265
1266        // All values should still be present
1267        let mut sorted: Vec<f64> = genome.genes().to_vec();
1268        sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
1269        assert_eq!(sorted, original);
1270    }
1271
1272    #[test]
1273    fn test_scramble_mutation_bitstring() {
1274        let mut rng = rand::thread_rng();
1275        let original = BitString::new(vec![true, true, true, false, false, false, true, false]);
1276        let mut genome = original.clone();
1277
1278        let mutation = ScrambleMutation::new();
1279        mutation.mutate(&mut genome, &mut rng);
1280
1281        // Count should be preserved
1282        assert_eq!(genome.count_ones(), original.count_ones());
1283    }
1284
1285    // =========================================================================
1286    // Permutation Mutation Tests
1287    // =========================================================================
1288
1289    #[test]
1290    fn test_permutation_swap_mutation() {
1291        let mut rng = rand::thread_rng();
1292        let original = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1293        let mut genome = original.clone();
1294
1295        let mutation = PermutationSwapMutation::new();
1296        mutation.mutate(&mut genome, &mut rng);
1297
1298        // Should still be valid permutation
1299        assert!(genome.is_valid_permutation());
1300        assert_eq!(genome.dimension(), 8);
1301    }
1302
1303    #[test]
1304    fn test_permutation_swap_mutation_multiple() {
1305        let mut rng = rand::thread_rng();
1306        let original = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1307        let mut genome = original.clone();
1308
1309        let mutation = PermutationSwapMutation::with_swaps(5);
1310        mutation.mutate(&mut genome, &mut rng);
1311
1312        // Should still be valid permutation
1313        assert!(genome.is_valid_permutation());
1314        assert_eq!(genome.dimension(), 10);
1315    }
1316
1317    #[test]
1318    fn test_insert_mutation() {
1319        let mut rng = rand::thread_rng();
1320
1321        for _ in 0..50 {
1322            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1323
1324            let mutation = InsertMutation::new();
1325            mutation.mutate(&mut genome, &mut rng);
1326
1327            assert!(genome.is_valid_permutation());
1328            assert_eq!(genome.dimension(), 8);
1329        }
1330    }
1331
1332    #[test]
1333    fn test_inversion_mutation() {
1334        let mut rng = rand::thread_rng();
1335
1336        for _ in 0..50 {
1337            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1338
1339            let mutation = InversionMutation::new();
1340            mutation.mutate(&mut genome, &mut rng);
1341
1342            assert!(genome.is_valid_permutation());
1343            assert_eq!(genome.dimension(), 8);
1344        }
1345    }
1346
1347    #[test]
1348    fn test_inversion_mutation_reverses_segment() {
1349        use rand::SeedableRng;
1350        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
1351
1352        let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1353
1354        let mutation = InversionMutation::new();
1355        mutation.mutate(&mut genome, &mut rng);
1356
1357        // Should still be valid permutation
1358        assert!(genome.is_valid_permutation());
1359    }
1360
1361    #[test]
1362    fn test_permutation_scramble_mutation() {
1363        let mut rng = rand::thread_rng();
1364
1365        for _ in 0..50 {
1366            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1367
1368            let mutation = PermutationScrambleMutation::new();
1369            mutation.mutate(&mut genome, &mut rng);
1370
1371            assert!(genome.is_valid_permutation());
1372            assert_eq!(genome.dimension(), 8);
1373        }
1374    }
1375
1376    #[test]
1377    fn test_displacement_mutation() {
1378        let mut rng = rand::thread_rng();
1379
1380        for _ in 0..50 {
1381            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
1382
1383            let mutation = DisplacementMutation::new();
1384            mutation.mutate(&mut genome, &mut rng);
1385
1386            assert!(genome.is_valid_permutation());
1387            assert_eq!(genome.dimension(), 10);
1388        }
1389    }
1390
1391    #[test]
1392    fn test_adaptive_permutation_mutation() {
1393        let mut rng = rand::thread_rng();
1394
1395        for _ in 0..100 {
1396            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1397
1398            let mutation = AdaptivePermutationMutation::new();
1399            mutation.mutate(&mut genome, &mut rng);
1400
1401            assert!(genome.is_valid_permutation());
1402            assert_eq!(genome.dimension(), 8);
1403        }
1404    }
1405
1406    #[test]
1407    fn test_adaptive_permutation_mutation_custom_probs() {
1408        let mut rng = rand::thread_rng();
1409
1410        // Test with only inversion mutation
1411        let mutation = AdaptivePermutationMutation::with_probs(0.0, 0.0, 1.0, 0.0);
1412
1413        for _ in 0..50 {
1414            let mut genome = Permutation::new(vec![0, 1, 2, 3, 4, 5, 6, 7]);
1415            mutation.mutate(&mut genome, &mut rng);
1416
1417            assert!(genome.is_valid_permutation());
1418        }
1419    }
1420
1421    // =========================================================================
1422    // Tree (GP) Mutation Tests
1423    // =========================================================================
1424
1425    use crate::genome::tree::{ArithmeticFunction, ArithmeticTerminal};
1426
1427    fn create_test_tree() -> TreeGenome<ArithmeticTerminal, ArithmeticFunction> {
1428        // Create: (+ x0 (* 1.0 x1))
1429        let x0 = TreeNode::terminal(ArithmeticTerminal::Variable(0));
1430        let c1 = TreeNode::terminal(ArithmeticTerminal::Constant(1.0));
1431        let x1 = TreeNode::terminal(ArithmeticTerminal::Variable(1));
1432        let mul = TreeNode::function(ArithmeticFunction::Mul, vec![c1, x1]);
1433        let add = TreeNode::function(ArithmeticFunction::Add, vec![x0, mul]);
1434        TreeGenome::new(add, 5)
1435    }
1436
1437    #[test]
1438    fn test_point_mutation_preserves_structure() {
1439        let mut rng = rand::thread_rng();
1440        let original = create_test_tree();
1441        let original_size = original.size();
1442
1443        for _ in 0..50 {
1444            let mut genome = original.clone();
1445            let mutation = PointMutation::new().with_probability(1.0);
1446            mutation.mutate(&mut genome, &mut rng);
1447
1448            // Point mutation preserves tree structure (size)
1449            assert_eq!(genome.size(), original_size);
1450            assert!(genome.evaluate(&[1.0, 2.0]).is_finite());
1451        }
1452    }
1453
1454    #[test]
1455    fn test_point_mutation_changes_tree() {
1456        let mut rng = rand::thread_rng();
1457        let original = create_test_tree();
1458        let mut any_changed = false;
1459
1460        for _ in 0..100 {
1461            let mut genome = original.clone();
1462            let mutation = PointMutation::new().with_probability(1.0);
1463            mutation.mutate(&mut genome, &mut rng);
1464
1465            // Check if the evaluation changed (indicates mutation occurred)
1466            let orig_val = original.evaluate(&[1.0, 2.0]);
1467            let new_val = genome.evaluate(&[1.0, 2.0]);
1468            if (orig_val - new_val).abs() > 1e-10 {
1469                any_changed = true;
1470                break;
1471            }
1472        }
1473
1474        assert!(
1475            any_changed,
1476            "Point mutation should sometimes change the tree"
1477        );
1478    }
1479
1480    #[test]
1481    fn test_subtree_mutation() {
1482        use rand::SeedableRng;
1483        let mut rng = rand::rngs::StdRng::seed_from_u64(42);
1484        let original = create_test_tree();
1485
1486        for _ in 0..50 {
1487            let mut genome = original.clone();
1488            let mutation = SubtreeMutation::new().with_max_depth(3);
1489            mutation.mutate(&mut genome, &mut rng);
1490
1491            // Tree should still be valid and have at least one node
1492            assert!(genome.size() >= 1);
1493            // Subtree mutation may generate expressions that are NaN/Inf for some inputs
1494            // (e.g., division by zero), so we only check structural validity
1495            let result = genome.evaluate(&[1.0, 2.0]);
1496            assert!(result.is_nan() || result.is_finite());
1497        }
1498    }
1499
1500    #[test]
1501    fn test_hoist_mutation_reduces_tree() {
1502        let mut rng = rand::thread_rng();
1503
1504        // Create a deeper tree
1505        let tree: TreeGenome<ArithmeticTerminal, ArithmeticFunction> =
1506            TreeGenome::generate_full(&mut rng, 4, 5);
1507
1508        let original_size = tree.size();
1509
1510        for _ in 0..50 {
1511            let mut genome = tree.clone();
1512            let mutation = HoistMutation::new();
1513            mutation.mutate(&mut genome, &mut rng);
1514
1515            // Hoist mutation should reduce or maintain tree size
1516            assert!(genome.size() <= original_size);
1517            assert!(genome.evaluate(&[1.0, 2.0]).is_finite());
1518        }
1519    }
1520
1521    #[test]
1522    fn test_shrink_mutation_reduces_tree() {
1523        let mut rng = rand::thread_rng();
1524
1525        // Create a deeper tree
1526        let tree: TreeGenome<ArithmeticTerminal, ArithmeticFunction> =
1527            TreeGenome::generate_full(&mut rng, 4, 5);
1528
1529        for _ in 0..50 {
1530            let mut genome = tree.clone();
1531            let original_size = genome.size();
1532            let mutation = ShrinkMutation::new();
1533            mutation.mutate(&mut genome, &mut rng);
1534
1535            // Shrink mutation should reduce or maintain tree size
1536            assert!(genome.size() <= original_size);
1537            assert!(genome.evaluate(&[1.0, 2.0]).is_finite());
1538        }
1539    }
1540
1541    #[test]
1542    fn test_adaptive_tree_mutation() {
1543        let mut rng = rand::thread_rng();
1544
1545        for _ in 0..100 {
1546            let mut genome: TreeGenome<ArithmeticTerminal, ArithmeticFunction> =
1547                TreeGenome::generate_ramped_half_and_half(&mut rng, 2, 5);
1548
1549            let mutation = AdaptiveTreeMutation::new();
1550            mutation.mutate(&mut genome, &mut rng);
1551
1552            // Tree should still be valid
1553            assert!(genome.size() >= 1);
1554            assert!(genome.evaluate(&[1.0, 2.0]).is_finite());
1555        }
1556    }
1557
1558    #[test]
1559    fn test_adaptive_tree_mutation_custom_probs() {
1560        let mut rng = rand::thread_rng();
1561
1562        // Test with only point mutation
1563        let mutation = AdaptiveTreeMutation::with_probs(1.0, 0.0, 0.0, 0.0);
1564
1565        for _ in 0..50 {
1566            let mut genome: TreeGenome<ArithmeticTerminal, ArithmeticFunction> =
1567                TreeGenome::generate_ramped_half_and_half(&mut rng, 2, 5);
1568            let original_size = genome.size();
1569            mutation.mutate(&mut genome, &mut rng);
1570
1571            // Point mutation preserves structure
1572            assert_eq!(genome.size(), original_size);
1573        }
1574    }
1575}