use rand::Rng as _;
use rand::seq::SliceRandom;
use crate::core::rng::Rng;
use crate::traits::{Initializer, Variation};
#[derive(Debug, Clone, Copy)]
pub struct ShuffledPermutation {
pub n: usize,
}
impl Initializer<Vec<usize>> for ShuffledPermutation {
fn initialize(&mut self, size: usize, rng: &mut Rng) -> Vec<Vec<usize>> {
(0..size)
.map(|_| {
let mut p: Vec<usize> = (0..self.n).collect();
p.shuffle(rng);
p
})
.collect()
}
}
#[derive(Debug, Clone)]
pub struct ShuffledMultisetPermutation {
pub repeats_per_id: Vec<usize>,
}
impl ShuffledMultisetPermutation {
pub fn new(repeats_per_id: Vec<usize>) -> Self {
Self { repeats_per_id }
}
}
impl Initializer<Vec<usize>> for ShuffledMultisetPermutation {
fn initialize(&mut self, size: usize, rng: &mut Rng) -> Vec<Vec<usize>> {
let template: Vec<usize> = self
.repeats_per_id
.iter()
.enumerate()
.flat_map(|(id, &r)| std::iter::repeat_n(id, r))
.collect();
(0..size)
.map(|_| {
let mut v = template.clone();
v.shuffle(rng);
v
})
.collect()
}
}
#[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]
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InversionMutation;
impl Variation<Vec<usize>> for InversionMutation {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
!parents.is_empty(),
"InversionMutation 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 j = rng.random_range(0..n);
let (lo, hi) = if i <= j { (i, j) } else { (j, i) };
child[lo..=hi].reverse();
}
vec![child]
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertionMutation;
impl Variation<Vec<usize>> for InsertionMutation {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
!parents.is_empty(),
"InsertionMutation requires at least one parent",
);
let mut child = parents[0].clone();
let n = child.len();
if n >= 2 {
let from = rng.random_range(0..n);
let v = child.remove(from);
let to = rng.random_range(0..=child.len());
child.insert(to, v);
}
vec![child]
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ScrambleMutation;
impl Variation<Vec<usize>> for ScrambleMutation {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
!parents.is_empty(),
"ScrambleMutation 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 j = rng.random_range(0..n);
let (lo, hi) = if i <= j { (i, j) } else { (j, i) };
child[lo..=hi].shuffle(rng);
}
vec![child]
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct OrderCrossover;
impl Variation<Vec<usize>> for OrderCrossover {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
parents.len() >= 2,
"OrderCrossover requires at least 2 parents",
);
let p1 = &parents[0];
let p2 = &parents[1];
assert_eq!(
p1.len(),
p2.len(),
"OrderCrossover requires equal-length parents",
);
let n = p1.len();
if n < 2 {
return vec![p1.clone(), p2.clone()];
}
let i = rng.random_range(0..n);
let j = rng.random_range(0..n);
let (lo, hi_inclusive) = if i <= j { (i, j) } else { (j, i) };
let hi = hi_inclusive + 1; vec![ox_child(p1, p2, lo, hi), ox_child(p2, p1, lo, hi)]
}
}
fn ox_child(donor: &[usize], filler: &[usize], lo: usize, hi: usize) -> Vec<usize> {
let n = donor.len();
let mut child = vec![0_usize; n];
let segment = &donor[lo..hi];
child[lo..hi].copy_from_slice(segment);
let mut in_segment = vec![false; n];
for &v in segment {
debug_assert!(v < n, "OrderCrossover requires strict permutations of 0..n");
in_segment[v] = true;
}
let mut fill_pos = hi % n;
let mut filler_pos = hi % n;
let mut placed = hi - lo;
while placed < n {
let v = filler[filler_pos];
if !in_segment[v] {
child[fill_pos] = v;
fill_pos = (fill_pos + 1) % n;
placed += 1;
}
filler_pos = (filler_pos + 1) % n;
}
child
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PartiallyMappedCrossover;
impl Variation<Vec<usize>> for PartiallyMappedCrossover {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
parents.len() >= 2,
"PartiallyMappedCrossover requires at least 2 parents",
);
let p1 = &parents[0];
let p2 = &parents[1];
assert_eq!(
p1.len(),
p2.len(),
"PartiallyMappedCrossover requires equal-length parents",
);
let n = p1.len();
if n < 2 {
return vec![p1.clone(), p2.clone()];
}
let i = rng.random_range(0..n);
let j = rng.random_range(0..n);
let (lo, hi_inclusive) = if i <= j { (i, j) } else { (j, i) };
let hi = hi_inclusive + 1;
vec![pmx_child(p1, p2, lo, hi), pmx_child(p2, p1, lo, hi)]
}
}
fn pmx_child(donor: &[usize], base: &[usize], lo: usize, hi: usize) -> Vec<usize> {
let mut child = base.to_vec();
let n = child.len();
let mut pos = vec![0_usize; n];
for (i, &v) in child.iter().enumerate() {
debug_assert!(
v < n,
"PartiallyMappedCrossover requires strict permutations of 0..n",
);
pos[v] = i;
}
for k in lo..hi {
let v = donor[k];
if child[k] == v {
continue;
}
let cur = pos[v];
let displaced = child[k];
child.swap(k, cur);
pos[v] = k;
pos[displaced] = cur;
}
child
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CycleCrossover;
impl Variation<Vec<usize>> for CycleCrossover {
fn vary(&mut self, parents: &[Vec<usize>], _rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
parents.len() >= 2,
"CycleCrossover requires at least 2 parents",
);
let p1 = &parents[0];
let p2 = &parents[1];
assert_eq!(
p1.len(),
p2.len(),
"CycleCrossover requires equal-length parents",
);
let n = p1.len();
if n == 0 {
return vec![Vec::new(), Vec::new()];
}
vec![cx_child(p1, p2), cx_child(p2, p1)]
}
}
fn cx_child(start_parent: &[usize], other_parent: &[usize]) -> Vec<usize> {
let n = start_parent.len();
let mut child = vec![0_usize; n];
let mut visited = vec![false; n];
let mut pos_start = vec![0_usize; n];
let mut pos_other = vec![0_usize; n];
for (i, (&s, &o)) in start_parent.iter().zip(other_parent.iter()).enumerate() {
debug_assert!(
s < n && o < n,
"CycleCrossover requires strict permutations of 0..n",
);
pos_start[s] = i;
pos_other[o] = i;
}
let mut cycle_index = 0_usize;
for seed in 0..n {
if visited[seed] {
continue;
}
let (from, switch_through, from_pos) = if cycle_index % 2 == 0 {
(start_parent, other_parent, &pos_start)
} else {
(other_parent, start_parent, &pos_other)
};
let mut k = seed;
loop {
if visited[k] {
break;
}
visited[k] = true;
child[k] = from[k];
let next_val = switch_through[k];
k = from_pos[next_val];
}
cycle_index += 1;
}
child
}
#[derive(Debug, Clone, Copy, Default)]
pub struct EdgeRecombinationCrossover;
impl Variation<Vec<usize>> for EdgeRecombinationCrossover {
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
assert!(
parents.len() >= 2,
"EdgeRecombinationCrossover requires at least 2 parents",
);
let p1 = &parents[0];
let p2 = &parents[1];
assert_eq!(
p1.len(),
p2.len(),
"EdgeRecombinationCrossover requires equal-length parents",
);
let n = p1.len();
if n == 0 {
return vec![Vec::new(), Vec::new()];
}
vec![erx_child(p1, p2, p1[0], rng), erx_child(p1, p2, p2[0], rng)]
}
}
fn erx_child(p1: &[usize], p2: &[usize], start: usize, rng: &mut Rng) -> Vec<usize> {
let n = p1.len();
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for tour in [p1, p2] {
for i in 0..n {
let cur = tour[i];
let prev = tour[(i + n - 1) % n];
let next = tour[(i + 1) % n];
debug_assert!(
cur < n && prev < n && next < n,
"ERX requires cities labeled 0..n",
);
for nb in [prev, next] {
if !adj[cur].contains(&nb) {
adj[cur].push(nb);
}
}
}
}
let mut visited = vec![false; n];
let mut child = Vec::with_capacity(n);
let mut current = start;
for _ in 0..n {
child.push(current);
visited[current] = true;
let current_adj = std::mem::take(&mut adj[current]);
for &nb in ¤t_adj {
adj[nb].retain(|&x| x != current);
}
if child.len() == n {
break;
}
let neighbors: Vec<usize> = current_adj
.iter()
.copied()
.filter(|&c| !visited[c])
.collect();
let next = if neighbors.is_empty() {
(0..n)
.find(|&c| !visited[c])
.expect("at least one unvisited city remains")
} else {
let min_deg = neighbors
.iter()
.map(|&c| adj[c].len())
.min()
.expect("non-empty neighbors");
let ties: Vec<usize> = neighbors
.into_iter()
.filter(|&c| adj[c].len() == min_deg)
.collect();
if ties.len() == 1 {
ties[0]
} else {
ties[rng.random_range(0..ties.len())]
}
};
current = next;
}
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
}
fn is_strict_perm(v: &[usize]) -> bool {
let n = v.len();
let mut seen = vec![false; n];
for &x in v {
if x >= n || seen[x] {
return false;
}
seen[x] = true;
}
true
}
fn multiset_eq(a: &[usize], b: &[usize]) -> bool {
sorted(a.to_vec()) == sorted(b.to_vec())
}
#[test]
fn swap_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(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children.len(), 1);
assert_eq!(sorted(children[0].clone()), sorted(parent));
}
#[test]
fn swap_single_element_unchanged() {
let mut m = SwapMutation;
let mut rng = rng_from_seed(0);
let parent = vec![42_usize];
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children[0], parent);
}
#[test]
fn swap_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(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children[0], vec![2, 1]);
}
#[test]
fn shuffled_permutation_returns_requested_count() {
let mut init = ShuffledPermutation { n: 7 };
let mut rng = rng_from_seed(1);
let pop = init.initialize(10, &mut rng);
assert_eq!(pop.len(), 10);
for p in &pop {
assert!(is_strict_perm(p));
assert_eq!(p.len(), 7);
}
}
#[test]
fn shuffled_permutation_n0_yields_empty_vecs() {
let mut init = ShuffledPermutation { n: 0 };
let mut rng = rng_from_seed(1);
let pop = init.initialize(3, &mut rng);
assert_eq!(pop.len(), 3);
assert!(pop.iter().all(|p| p.is_empty()));
}
#[test]
fn shuffled_multiset_preserves_counts() {
let repeats = vec![3_usize, 2, 4, 1]; let mut init = ShuffledMultisetPermutation::new(repeats.clone());
let mut rng = rng_from_seed(2);
let pop = init.initialize(5, &mut rng);
assert_eq!(pop.len(), 5, "initialize must return `size` shuffles");
for p in &pop {
assert_eq!(p.len(), 10);
let mut counts = [0_usize; 4];
for &v in p {
counts[v] += 1;
}
assert_eq!(counts, [3, 2, 4, 1]);
}
}
#[test]
fn inversion_preserves_strict_permutation() {
let mut m = InversionMutation;
let parent: Vec<usize> = (0..9).collect();
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children.len(), 1);
assert!(is_strict_perm(&children[0]));
}
}
#[test]
fn inversion_preserves_multiset() {
let mut m = InversionMutation;
let parent = vec![0_usize, 0, 1, 1, 2, 2];
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert!(multiset_eq(&children[0], &parent));
}
}
#[test]
fn inversion_single_element_unchanged() {
let mut m = InversionMutation;
let mut rng = rng_from_seed(0);
let parent = vec![42_usize];
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children[0], parent);
}
#[test]
fn insertion_preserves_strict_permutation() {
let mut m = InsertionMutation;
let parent: Vec<usize> = (0..7).collect();
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert_eq!(children[0].len(), 7);
assert!(is_strict_perm(&children[0]));
}
}
#[test]
fn insertion_preserves_multiset() {
let mut m = InsertionMutation;
let parent = vec![0_usize, 1, 1, 2, 2, 2];
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert!(multiset_eq(&children[0], &parent));
}
}
#[test]
fn scramble_preserves_strict_permutation() {
let mut m = ScrambleMutation;
let parent: Vec<usize> = (0..8).collect();
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert!(is_strict_perm(&children[0]));
}
}
#[test]
fn scramble_preserves_multiset() {
let mut m = ScrambleMutation;
let parent = vec![0_usize, 1, 1, 2, 3, 3, 3];
for seed in 0..50 {
let mut rng = rng_from_seed(seed);
let children = m.vary(std::slice::from_ref(&parent), &mut rng);
assert!(multiset_eq(&children[0], &parent));
}
}
#[test]
fn ox_returns_two_valid_permutations() {
let mut ox = OrderCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![3, 7, 5, 1, 6, 4, 2, 0];
for seed in 0..30 {
let mut rng = rng_from_seed(seed);
let children = ox.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(children.len(), 2);
for c in &children {
assert!(is_strict_perm(c), "child not a permutation: {:?}", c);
}
}
}
#[test]
fn ox_with_identical_parents_reproduces_parent() {
let mut ox = OrderCrossover;
let p: Vec<usize> = vec![0, 1, 2, 3, 4, 5];
let mut rng = rng_from_seed(0);
let children = ox.vary(&[p.clone(), p.clone()], &mut rng);
assert_eq!(children, vec![p.clone(), p]);
}
#[test]
fn pmx_returns_two_valid_permutations() {
let mut pmx = PartiallyMappedCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![3, 7, 5, 1, 6, 4, 2, 0];
for seed in 0..30 {
let mut rng = rng_from_seed(seed);
let children = pmx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(children.len(), 2);
for c in &children {
assert!(is_strict_perm(c), "child not a permutation: {:?}", c);
}
}
}
#[test]
fn pmx_with_identical_parents_reproduces_parent() {
let mut pmx = PartiallyMappedCrossover;
let p: Vec<usize> = vec![0, 1, 2, 3, 4, 5];
let mut rng = rng_from_seed(0);
let children = pmx.vary(&[p.clone(), p.clone()], &mut rng);
assert_eq!(children, vec![p.clone(), p]);
}
#[test]
fn cx_returns_two_valid_permutations() {
let mut cx = CycleCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![7, 3, 1, 4, 2, 5, 6, 0];
let mut rng = rng_from_seed(0);
let children = cx.vary(&[p1, p2], &mut rng);
assert_eq!(children.len(), 2);
for c in &children {
assert!(is_strict_perm(c));
}
}
#[test]
fn cx_with_identical_parents_reproduces_parent() {
let mut cx = CycleCrossover;
let p: Vec<usize> = vec![0, 1, 2, 3, 4];
let mut rng = rng_from_seed(0);
let children = cx.vary(&[p.clone(), p.clone()], &mut rng);
assert_eq!(children, vec![p.clone(), p]);
}
#[test]
fn erx_returns_two_valid_permutations() {
let mut erx = EdgeRecombinationCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![3, 5, 7, 1, 6, 4, 2, 0];
for seed in 0..30 {
let mut rng = rng_from_seed(seed);
let children = erx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(children.len(), 2);
for c in &children {
assert!(is_strict_perm(c), "child not a permutation: {:?}", c);
}
}
}
#[test]
fn erx_with_identical_parents_walks_the_same_tour() {
let mut erx = EdgeRecombinationCrossover;
let p: Vec<usize> = vec![0, 1, 2, 3, 4, 5];
let mut rng = rng_from_seed(0);
let children = erx.vary(&[p.clone(), p.clone()], &mut rng);
for c in &children {
assert!(is_strict_perm(c));
assert_eq!(c.len(), p.len());
}
}
#[test]
fn inversion_actually_mutates_for_nontrivial_input() {
let mut m = InversionMutation;
let parent: Vec<usize> = (0..8).collect();
let any_changed = (0..30).any(|seed| {
let mut rng = rng_from_seed(seed);
let c = m.vary(std::slice::from_ref(&parent), &mut rng);
c[0] != parent
});
assert!(
any_changed,
"InversionMutation never modified an 8-element parent across 30 seeds"
);
}
#[test]
fn insertion_actually_mutates_for_nontrivial_input() {
let mut m = InsertionMutation;
let parent: Vec<usize> = (0..8).collect();
let any_changed = (0..30).any(|seed| {
let mut rng = rng_from_seed(seed);
let c = m.vary(std::slice::from_ref(&parent), &mut rng);
c[0] != parent
});
assert!(any_changed);
}
#[test]
fn scramble_actually_mutates_for_nontrivial_input() {
let mut m = ScrambleMutation;
let parent: Vec<usize> = (0..8).collect();
let any_changed = (0..30).any(|seed| {
let mut rng = rng_from_seed(seed);
let c = m.vary(std::slice::from_ref(&parent), &mut rng);
c[0] != parent
});
assert!(any_changed);
}
fn child_differs_from_parents<V: Variation<Vec<usize>>>(
mut v: V,
p1: Vec<usize>,
p2: Vec<usize>,
) -> bool {
(0..30).any(|seed| {
let mut rng = rng_from_seed(seed);
let kids = v.vary(&[p1.clone(), p2.clone()], &mut rng);
kids.iter().any(|k| *k != p1 && *k != p2)
})
}
#[test]
fn ox_recombines_for_n3() {
assert!(child_differs_from_parents(
OrderCrossover,
vec![0, 1, 2, 3, 4],
vec![4, 3, 2, 1, 0],
));
}
#[test]
fn pmx_recombines_for_n3() {
assert!(child_differs_from_parents(
PartiallyMappedCrossover,
vec![0, 1, 2, 3, 4],
vec![4, 3, 2, 1, 0],
));
}
#[test]
fn cx_recombines_when_parents_have_multiple_cycles() {
let mut cx = CycleCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3];
let p2: Vec<usize> = vec![2, 3, 0, 1];
let mut rng = rng_from_seed(0);
let kids = cx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert!(kids.iter().any(|k| *k != p1 && *k != p2));
}
#[test]
fn erx_recombines_for_distinct_parents() {
assert!(child_differs_from_parents(
EdgeRecombinationCrossover,
vec![0, 1, 2, 3, 4],
vec![4, 3, 2, 1, 0],
));
}
#[test]
fn ox_produces_pinned_children_for_fixed_seed() {
let mut ox = OrderCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4];
let p2: Vec<usize> = vec![4, 3, 2, 1, 0];
let mut rng = rng_from_seed(7);
let kids = ox.vary(&[p1, p2], &mut rng);
for k in &kids {
assert!(is_strict_perm(k), "child not a permutation: {:?}", k);
assert_eq!(k.len(), 5);
}
}
#[test]
fn pmx_with_specific_pinned_swap() {
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![7, 6, 5, 4, 3, 2, 1, 0];
let mut pmx = PartiallyMappedCrossover;
let mut rng = rng_from_seed(7);
let kids = pmx.vary(&[p1, p2], &mut rng);
assert_eq!(kids.len(), 2);
assert!(is_strict_perm(&kids[0]));
assert!(is_strict_perm(&kids[1]));
}
#[test]
fn cx_with_single_cycle_returns_parents() {
let mut cx = CycleCrossover;
let p1: Vec<usize> = vec![1, 2, 3, 0];
let p2: Vec<usize> = vec![2, 3, 0, 1];
let mut rng = rng_from_seed(0);
let kids = cx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(kids[0], p1);
assert_eq!(kids[1], p2);
}
#[test]
fn cx_with_two_cycles_alternates_parents() {
let mut cx = CycleCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3];
let p2: Vec<usize> = vec![2, 3, 0, 1];
let mut rng = rng_from_seed(0);
let kids = cx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(kids[0], vec![0, 3, 2, 1]);
assert_eq!(kids[1], vec![2, 1, 0, 3]);
}
#[test]
fn erx_output_is_permutation_across_many_seeds() {
let mut erx = EdgeRecombinationCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5];
let p2: Vec<usize> = vec![0, 2, 4, 1, 3, 5];
for seed in 0..20 {
let mut rng = rng_from_seed(seed);
let kids = erx.vary(&[p1.clone(), p2.clone()], &mut rng);
assert_eq!(kids.len(), 2);
for k in &kids {
assert!(is_strict_perm(k), "child not a permutation: {:?}", k);
assert_eq!(k.len(), 6);
}
}
}
#[test]
fn erx_distinct_starts_can_yield_distinct_tours() {
let mut erx = EdgeRecombinationCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6];
let p2: Vec<usize> = vec![6, 5, 4, 3, 2, 1, 0];
let any_distinct = (0..30).any(|seed| {
let mut rng = rng_from_seed(seed);
let kids = erx.vary(&[p1.clone(), p2.clone()], &mut rng);
kids[0] != kids[1]
});
assert!(
any_distinct,
"ERX never produced distinct children across 30 seeds"
);
}
fn tour_edges(tour: &[usize]) -> std::collections::HashSet<(usize, usize)> {
let n = tour.len();
(0..n)
.map(|i| {
let (a, b) = (tour[i], tour[(i + 1) % n]);
if a <= b { (a, b) } else { (b, a) }
})
.collect()
}
#[test]
fn erx_identical_parents_inherit_every_edge() {
let mut erx = EdgeRecombinationCrossover;
let p: Vec<usize> = vec![3, 0, 4, 1, 5, 2, 6];
let p_edges = tour_edges(&p);
for seed in 0..30 {
let mut rng = rng_from_seed(seed);
for child in erx.vary(&[p.clone(), p.clone()], &mut rng) {
assert_eq!(
tour_edges(&child),
p_edges,
"identical-parent child must reuse exactly the parent's edges",
);
}
}
}
#[test]
fn erx_preserves_parent_edges_better_than_order_crossover() {
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let p2: Vec<usize> = vec![9, 7, 5, 3, 1, 8, 6, 4, 2, 0];
let parent_edges: std::collections::HashSet<(usize, usize)> =
tour_edges(&p1).union(&tour_edges(&p2)).copied().collect();
let foreign = |child: &[usize]| tour_edges(child).difference(&parent_edges).count();
let (mut erx_foreign, mut ox_foreign) = (0usize, 0usize);
let mut erx = EdgeRecombinationCrossover;
let mut ox = OrderCrossover;
for seed in 0..40 {
let mut rng = rng_from_seed(seed);
for child in erx.vary(&[p1.clone(), p2.clone()], &mut rng) {
erx_foreign += foreign(&child);
}
let mut rng = rng_from_seed(seed);
for child in ox.vary(&[p1.clone(), p2.clone()], &mut rng) {
ox_foreign += foreign(&child);
}
}
assert!(
erx_foreign < ox_foreign,
"ERX should strand fewer foreign edges than OX \
(ERX={erx_foreign}, OX={ox_foreign})",
);
}
#[test]
fn erx_pinned_output() {
let mut erx = EdgeRecombinationCrossover;
let p1: Vec<usize> = vec![0, 1, 2, 3, 4, 5, 6, 7];
let p2: Vec<usize> = vec![2, 4, 6, 0, 7, 5, 3, 1];
let mut rng = rng_from_seed(123);
let children = erx.vary(&[p1, p2], &mut rng);
assert_eq!(children[0], vec![0, 1, 2, 3, 4, 5, 7, 6]);
assert_eq!(children[1], vec![2, 1, 0, 7, 6, 5, 3, 4]);
}
}