perf(operators): make CycleCrossover O(n) with per-parent position tables (59K -> 12K instr)
cx_child located each cycle's next value with an O(n) `position` scan, making the walk O(n^2). CX operates on strict permutations of 0..n, so a direct value-indexed position table per parent gives O(1) lookups; a debug_assert documents the range assumption (mirroring ERX). cycle_crossover_vary n=100: 58_762 -> 12_084 (-79%, 4.86x); n=30 -36%. Bit-identical for valid permutations -- all 606 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -527,15 +527,29 @@ fn cx_child(start_parent: &[usize], other_parent: &[usize]) -> Vec<usize> {
|
|||||||
let n = start_parent.len();
|
let n = start_parent.len();
|
||||||
let mut child = vec![0_usize; n];
|
let mut child = vec![0_usize; n];
|
||||||
let mut visited = vec![false; n];
|
let mut visited = vec![false; n];
|
||||||
|
// Position lookup per parent: `pos_*[value]` is the index of that value.
|
||||||
|
// CX operates on strict permutations of `0..n` (see the type docs), so a
|
||||||
|
// direct-indexed table is valid and turns the per-step value lookup from
|
||||||
|
// an O(n) `position` scan into an O(1) index.
|
||||||
|
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;
|
let mut cycle_index = 0_usize;
|
||||||
for seed in 0..n {
|
for seed in 0..n {
|
||||||
if visited[seed] {
|
if visited[seed] {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let (from, switch_through) = if cycle_index % 2 == 0 {
|
let (from, switch_through, from_pos) = if cycle_index % 2 == 0 {
|
||||||
(start_parent, other_parent)
|
(start_parent, other_parent, &pos_start)
|
||||||
} else {
|
} else {
|
||||||
(other_parent, start_parent)
|
(other_parent, start_parent, &pos_other)
|
||||||
};
|
};
|
||||||
let mut k = seed;
|
let mut k = seed;
|
||||||
loop {
|
loop {
|
||||||
@@ -545,11 +559,7 @@ fn cx_child(start_parent: &[usize], other_parent: &[usize]) -> Vec<usize> {
|
|||||||
visited[k] = true;
|
visited[k] = true;
|
||||||
child[k] = from[k];
|
child[k] = from[k];
|
||||||
let next_val = switch_through[k];
|
let next_val = switch_through[k];
|
||||||
let next_pos = from
|
k = from_pos[next_val];
|
||||||
.iter()
|
|
||||||
.position(|&x| x == next_val)
|
|
||||||
.expect("CycleCrossover: parents must share the same value multiset");
|
|
||||||
k = next_pos;
|
|
||||||
}
|
}
|
||||||
cycle_index += 1;
|
cycle_index += 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user