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 {
|
for _ in 0..n {
|
||||||
child.push(current);
|
child.push(current);
|
||||||
visited[current] = true;
|
visited[current] = true;
|
||||||
// Remove `current` from every adjacency list so it isn't picked again.
|
// Remove `current` from the adjacency lists it appears in. The
|
||||||
for list in adj.iter_mut() {
|
// parent-tour adjacency relation is symmetric (`b ∈ adj[a]` iff
|
||||||
list.retain(|&x| x != current);
|
// `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 {
|
if child.len() == n {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let neighbors: Vec<usize> = adj[current]
|
let neighbors: Vec<usize> = current_adj
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter(|&c| !visited[c])
|
.filter(|&c| !visited[c])
|
||||||
|
|||||||
Reference in New Issue
Block a user