diff --git a/core/dr-face/src/cluster.rs b/core/dr-face/src/cluster.rs index 4a3137f..8f578e3 100644 --- a/core/dr-face/src/cluster.rs +++ b/core/dr-face/src/cluster.rs @@ -265,8 +265,9 @@ fn build( pairs: &[neighbours::Pair], ) -> Vec { let mut engine = Engine::new(faces, cal, min_probability); - for component in components(faces.len(), pairs) { - engine.agglomerate(&component, pairs); + let parts = components(faces.len(), pairs); + for (component, edges) in parts.members.iter().zip(&parts.edges) { + engine.agglomerate(component, edges); } engine.finish() } @@ -405,10 +406,9 @@ impl<'a> Engine<'a> { if component.len() < 2 { return; } - let members: HashSet = component.iter().copied().collect(); - let mut heap = BinaryHeap::new(); - for p in pairs.iter().filter(|p| members.contains(&p.i)) { + let mut heap = BinaryHeap::with_capacity(pairs.len()); + for p in pairs { self.links.insert( key(p.i, p.j), Link { @@ -632,7 +632,7 @@ fn key(a: usize, b: usize) -> (usize, usize) { /// separate and much smaller agglomeration. Returned with the members of each /// component ascending, and the components themselves in order of their lowest /// member — the determinism the merge order inherits. -fn components(n: usize, pairs: &[neighbours::Pair]) -> Vec> { +fn components(n: usize, pairs: &[neighbours::Pair]) -> Components { let mut parent: Vec = (0..n).collect(); fn find(parent: &mut [usize], mut x: usize) -> usize { @@ -659,9 +659,44 @@ fn components(n: usize, pairs: &[neighbours::Pair]) -> Vec> { let r = find(&mut parent, i); by_root.entry(r).or_default().push(i); } - let mut out: Vec> = by_root.into_values().filter(|c| c.len() > 1).collect(); - out.sort_unstable_by_key(|c| c[0]); - out + let mut members: Vec> = by_root.into_values().filter(|c| c.len() > 1).collect(); + members.sort_unstable_by_key(|c| c[0]); + + // Each pair filed under its component, in one pass. + // + // **This is not bookkeeping, it is the cost of the whole stage.** Each + // component used to scan the entire pair list for the ones that were its + // own: 475 components against 804,499 pairs on the reference library, 382 + // million set lookups, and 3.06 s of a 5.93 s regroup spent before a single + // merge was considered. A pair can only ever join two faces of one + // component — that is what a component *is* — so one pass over the list + // places every pair exactly where it is needed. + let mut slot_of = vec![usize::MAX; n]; + for (slot, c) in members.iter().enumerate() { + for &m in c { + slot_of[m] = slot; + } + } + let mut edges: Vec> = vec![Vec::new(); members.len()]; + for p in pairs { + // Ordering within a component follows the global order, which is what + // the seeded heap's tiebreak — and so the determinism promise — rests + // on. + if let Some(slot) = slot_of.get(p.i).copied().filter(|&s| s != usize::MAX) { + edges[slot].push(*p); + } + } + + Components { members, edges } +} + +/// The connected components of the pair graph, each with the pairs inside it. +/// +/// The two halves are parallel: `members[k]` and `edges[k]` describe the same +/// component. +struct Components { + members: Vec>, + edges: Vec>, } #[cfg(test)] mod tests {