perf(operators): make ERX neighbor removal O(degree) per step (397K -> 140K instr)
Edge Recombination Crossover scrubbed `current` from every one of the n adjacency lists on each step of the walk -- an O(n^2) pass. The parent-tour adjacency relation is symmetric (b in adj[a] iff a in adj[b]), so `current` only ever appears in the lists of its own neighbors. Taking adj[current] out with mem::take and retaining only over those lists is O(degree). edge_recombination_crossover_vary n=100: 397_214 -> 140_403 (-65%, 2.83x); n=30: 62_708 -> 39_987 (-36%). Output bit-identical -- all 606 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -642,14 +642,19 @@ fn erx_child(p1: &[usize], p2: &[usize], start: usize, rng: &mut Rng) -> Vec<usi
|
||||
for _ in 0..n {
|
||||
child.push(current);
|
||||
visited[current] = true;
|
||||
// Remove `current` from every adjacency list so it isn't picked again.
|
||||
for list in adj.iter_mut() {
|
||||
list.retain(|&x| x != current);
|
||||
// Remove `current` from the adjacency lists it appears in. The
|
||||
// parent-tour adjacency relation is symmetric (`b ∈ adj[a]` iff
|
||||
// `a ∈ adj[b]`), so `current` appears only in the lists of its own
|
||||
// neighbors — taking `adj[current]` out and scrubbing just those
|
||||
// lists is O(degree), not O(n) over every list.
|
||||
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> = adj[current]
|
||||
let neighbors: Vec<usize> = current_adj
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&c| !visited[c])
|
||||
|
||||
Reference in New Issue
Block a user