//! Operators for binary (`Vec`) decisions. use rand::Rng as _; use crate::core::rng::Rng; use crate::traits::Variation; /// Flip each bit of the first parent independently with probability `p`. /// /// Always returns exactly one child (spec ยง11.3). Panics if `probability` is /// outside `[0.0, 1.0]` or if no parents are provided. #[derive(Debug, Clone)] pub struct BitFlipMutation { /// Per-bit flip probability. Must lie in `[0.0, 1.0]`. pub probability: f64, } impl Variation> for BitFlipMutation { fn vary(&mut self, parents: &[Vec], rng: &mut Rng) -> Vec> { assert!( (0.0..=1.0).contains(&self.probability), "BitFlipMutation probability must be in [0.0, 1.0]", ); assert!( !parents.is_empty(), "BitFlipMutation requires at least one parent", ); let mut child = parents[0].clone(); for bit in child.iter_mut() { if rng.random_bool(self.probability) { *bit = !*bit; } } vec![child] } } #[cfg(test)] mod tests { use super::*; use crate::core::rng::rng_from_seed; #[test] fn probability_zero_changes_nothing() { let mut m = BitFlipMutation { probability: 0.0 }; let mut rng = rng_from_seed(0); let parent = vec![false, true, false, true, true]; let children = m.vary(std::slice::from_ref(&parent), &mut rng); assert_eq!(children.len(), 1); assert_eq!(children[0], parent); } #[test] fn probability_one_flips_everything() { let mut m = BitFlipMutation { probability: 1.0 }; let mut rng = rng_from_seed(0); let parent = vec![false, true, false, true, true]; let children = m.vary(std::slice::from_ref(&parent), &mut rng); let expected: Vec = parent.iter().map(|b| !b).collect(); assert_eq!(children[0], expected); } #[test] #[should_panic(expected = "must be in [0.0, 1.0]")] fn probability_above_one_panics() { let mut m = BitFlipMutation { probability: 1.5 }; let mut rng = rng_from_seed(0); m.vary(&[vec![true]], &mut rng); } }