feat(operators): add SwapMutation for permutations

Variation that clones the first parent (a Vec<usize> permutation) and
swaps two distinct random indices when len >= 2 (spec §11.4). Tests
confirm the multiset of contents is preserved.
This commit is contained in:
2026-05-04 19:22:07 -06:00
parent b97ec4f7ab
commit 0cbef6be1b
3 changed files with 74 additions and 1 deletions
+2
View File
@@ -1,7 +1,9 @@
//! Built-in operators for common decision types. //! Built-in operators for common decision types.
pub mod binary; pub mod binary;
pub mod permutation;
pub mod real; pub mod real;
pub use binary::*; pub use binary::*;
pub use permutation::*;
pub use real::*; pub use real::*;
+71
View File
@@ -0,0 +1,71 @@
//! Operators for permutation (`Vec<usize>`) decisions.
use rand::Rng as _;
use crate::core::rng::Rng;
use crate::traits::Variation;
/// Swap two distinct random indices in the first parent (spec §11.4).
///
/// If the parent has length `< 2` the child is returned unchanged.
#[derive(Debug, Clone, Copy, Default)]
pub struct SwapMutation;
impl Variation<Vec<usize>> for SwapMutation {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
!parents.is_empty(),
"SwapMutation requires at least one parent",
);
let mut child = parents[0].clone();
let n = child.len();
if n >= 2 {
let i = rng.random_range(0..n);
let mut j = rng.random_range(0..n);
while j == i {
j = rng.random_range(0..n);
}
child.swap(i, j);
}
vec![child]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::rng::rng_from_seed;
fn sorted(mut v: Vec<usize>) -> Vec<usize> {
v.sort();
v
}
#[test]
fn preserves_multiset_contents() {
let mut m = SwapMutation;
let mut rng = rng_from_seed(11);
let parent = vec![0_usize, 1, 2, 3, 4];
let children = m.vary(&[parent.clone()], &mut rng);
assert_eq!(children.len(), 1);
assert_eq!(sorted(children[0].clone()), sorted(parent));
}
#[test]
fn single_element_unchanged() {
let mut m = SwapMutation;
let mut rng = rng_from_seed(0);
let parent = vec![42_usize];
let children = m.vary(&[parent.clone()], &mut rng);
assert_eq!(children[0], parent);
}
#[test]
fn two_elements_always_swapped() {
let mut m = SwapMutation;
let mut rng = rng_from_seed(0);
let parent = vec![1_usize, 2];
let children = m.vary(&[parent.clone()], &mut rng);
assert_eq!(children[0], vec![2, 1]);
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ pub use crate::pareto::{
pareto_compare, pareto_front, pareto_compare, pareto_front,
}; };
pub use crate::operators::{BitFlipMutation, GaussianMutation, RealBounds}; pub use crate::operators::{BitFlipMutation, GaussianMutation, RealBounds, SwapMutation};