Skip to main content

yo_graph/algo/
mod.rs

1//! The algorithms, over a [`crate::Snapshot`] (`11` section 8).
2//!
3//! Every one of these is a whole graph computation rather than a traversal: it
4//! reads every node, several times, in an order it chooses. That is the
5//! opposite of what the adjacency plane is built for, which is why they all
6//! take a snapshot and none of them takes a [`crate::Graph`].
7//!
8//! # What is implemented and where it comes from
9//!
10//! These are not folk implementations. Each one is the published algorithm that
11//! is currently the fastest single machine answer for its problem, and the
12//! reference is named in the module that implements it, so a reader can check
13//! the code against the paper rather than against a guess.
14//!
15//! [`bfs()`] is direction optimizing, from Beamer, Asanović and Patterson at SC12,
16//! which is the same algorithm the GAP benchmark suite measures and the reason a
17//! breadth first search over a social graph is not bound by the size of its
18//! frontier.
19//!
20//! [`wcc()`] is Afforest, from Sutton, Ben-Nun and Barak at IPDPS 2018, which
21//! finds the giant component out of a two neighbour sample and then only looks
22//! at the edges of the nodes that are not in it.
23//!
24//! [`pagerank()`] is the pull form, which is the one the GAP suite measures, with
25//! the mass that lands on a dead end handed back out rather than dropped, which
26//! is what the 1999 paper describes and what GAP leaves out.
27//!
28//! [`triangle_count()`] is the ordered count from Schank and Wagner at WEA 2005
29//! under the degree ordering Ortmann and Brandes recommend at ALENEX 2014, so a
30//! hub is intersected against almost nothing rather than against everybody.
31//!
32//! [`sssp()`] is delta stepping, from Meyer and Sanders in the Journal of
33//! Algorithms 2003, which settles a band of nodes at a time instead of one at a
34//! time so the reads are independent of each other and a heap is not in the way.
35//!
36//! [`scc()`] is Tarjan from 1972, which is still the fastest single core answer
37//! for strong components, written with its frames in a `Vec` so a long chain
38//! does not take the process down with it.
39//!
40//! [`leiden()`] is Traag, Waltman and van Eck from 2019, which is Louvain with
41//! the step that stops it handing back a community in two disconnected halves.
42//! [`louvain()`] itself is here to be measured against it, and
43//! [`label_propagation()`] is the one to reach for when even Louvain is too much
44//! work for the size of the graph.
45//!
46//! [`betweenness()`] is Brandes from 2001, sampled over random sources the way
47//! Brandes and Pich describe in 2007, which is the only centrality here that
48//! finds the node whose removal would cut the graph in half.
49//!
50//! # Why they are deterministic
51//!
52//! Several of these sample or shuffle, and all of them draw from
53//! [`yo_common::Rng`] with a fixed seed. A caller who runs the same algorithm
54//! over the same snapshot twice gets the same answer, including the same
55//! representative for a component, the same nodes chosen for a sample and the
56//! same communities. That is worth more than the entropy is: an algorithm whose
57//! answer moves between runs cannot be tested against a reference
58//! implementation and cannot be diffed between two versions of this crate.
59
60pub mod betweenness;
61pub mod bfs;
62pub mod community;
63pub mod label_propagation;
64pub mod pagerank;
65pub mod scc;
66pub mod sssp;
67pub mod triangle;
68pub mod wcc;
69
70pub use betweenness::{Between, betweenness, betweenness_exact, betweenness_with};
71pub use bfs::{UNREACHED, bfs};
72pub use community::{leiden, leiden_with, louvain, louvain_with, modularity, modularity_with};
73pub use label_propagation::{label_propagation, label_propagation_with};
74pub use pagerank::{Rank, pagerank, pagerank_with};
75pub use scc::scc;
76pub use sssp::{UNREACHABLE, sssp, sssp_with};
77pub use triangle::triangle_count;
78pub use wcc::wcc;
79
80/// Which component each node is in.
81///
82/// What counts as a component is the algorithm's business. [`wcc()`] fills this
83/// in with the weakly connected ones, where an edge joins its two ends whichever
84/// way it points, and [`scc()`] with the strongly connected ones, where two
85/// nodes are together only if each can be reached from the other. The shape of
86/// the answer is the same either way, and so is the rule about the label.
87#[derive(Debug, Clone)]
88pub struct Components {
89    /// The representative of each node's component, which is the smallest dense
90    /// id in it.
91    of: Vec<u32>,
92    count: u32,
93}
94
95impl Components {
96    /// The component `node` is in, named by the lowest numbered node in it.
97    ///
98    /// # Panics
99    ///
100    /// If `node` is not a node of the snapshot this was computed from.
101    #[must_use]
102    pub fn of(&self, node: u32) -> u32 {
103        self.of[node as usize]
104    }
105
106    /// Whether two nodes are in the same component.
107    #[must_use]
108    pub fn same(&self, a: u32, b: u32) -> bool {
109        self.of(a) == self.of(b)
110    }
111
112    /// How many components there are, counting an isolated node as its own.
113    #[must_use]
114    pub fn count(&self) -> u32 {
115        self.count
116    }
117
118    /// How many nodes were labelled.
119    #[must_use]
120    pub fn len(&self) -> u32 {
121        self.of.len() as u32
122    }
123
124    /// Whether there were no nodes at all.
125    #[must_use]
126    pub fn is_empty(&self) -> bool {
127        self.of.is_empty()
128    }
129
130    /// The component with the most nodes in it, and how many that is.
131    ///
132    /// `None` for a graph with no nodes. The lowest numbered of them when two
133    /// are the same size, so the answer does not depend on iteration order.
134    #[must_use]
135    pub fn largest(&self) -> Option<(u32, u32)> {
136        let mut size = vec![0u32; self.of.len()];
137        for c in &self.of {
138            size[*c as usize] += 1;
139        }
140        size.iter()
141            .enumerate()
142            .filter(|(_, n)| **n > 0)
143            .max_by_key(|(at, n)| (**n, std::cmp::Reverse(*at)))
144            .map(|(at, n)| (at as u32, *n))
145    }
146
147    /// The label of every node, in dense id order.
148    #[must_use]
149    pub fn labels(&self) -> &[u32] {
150        &self.of
151    }
152}
153
154/// Rename every group after the lowest numbered node in it.
155///
156/// [`wcc()`] and [`scc()`] arrive at that naming on their own, out of how they
157/// are written. The community algorithms do not: they end up with whatever
158/// label happened to win, which is a node id but an arbitrary one, and two runs
159/// that found the same communities would then disagree on paper. This is the
160/// pass that makes the answer depend only on the grouping.
161///
162/// Every label has to be a dense id, which is true of every algorithm here
163/// because a community is named after one of its members.
164pub(crate) fn tidy(mut of: Vec<u32>) -> Components {
165    let mut low = vec![u32::MAX; of.len()];
166    for (node, at) in of.iter().enumerate() {
167        let low = &mut low[*at as usize];
168        *low = (*low).min(node as u32);
169    }
170    let count = low.iter().filter(|low| **low != u32::MAX).count() as u32;
171    for at in &mut of {
172        *at = low[*at as usize];
173    }
174    Components { of, count }
175}
176
177/// A bit per node, which is how a frontier is held when it is big.
178///
179/// A frontier as a list of nodes costs four bytes a node and is read once. A
180/// frontier as a bitmap costs one bit a node whether the node is in it or not,
181/// and can be asked about a node without being searched. Which one is cheaper
182/// depends on how full the frontier is, and a breadth first search over a real
183/// graph goes from one being right to the other being right and back inside a
184/// single search, so [`bfs()`] holds both and converts.
185#[derive(Debug, Clone)]
186pub(crate) struct Bits {
187    words: Vec<u64>,
188    len: u32,
189}
190
191impl Bits {
192    /// Room for `len` bits, all clear.
193    pub(crate) fn new(len: u32) -> Bits {
194        Bits {
195            words: vec![0; (len as usize).div_ceil(64)],
196            len,
197        }
198    }
199
200    /// Clears every bit, without giving the memory back.
201    pub(crate) fn clear(&mut self) {
202        self.words.fill(0);
203    }
204
205    #[inline]
206    pub(crate) fn set(&mut self, at: u32) {
207        self.words[at as usize / 64] |= 1 << (at % 64);
208    }
209
210    #[inline]
211    pub(crate) fn unset(&mut self, at: u32) {
212        self.words[at as usize / 64] &= !(1 << (at % 64));
213    }
214
215    #[inline]
216    pub(crate) fn get(&self, at: u32) -> bool {
217        self.words[at as usize / 64] >> (at % 64) & 1 == 1
218    }
219
220    /// Every set bit, in order.
221    ///
222    /// A word at a time and then a bit at a time inside a word that has
223    /// anything in it, so an empty stretch of a sparse frontier costs one load
224    /// and one compare per sixty four nodes rather than one per node.
225    pub(crate) fn for_each(&self, mut f: impl FnMut(u32)) {
226        for (i, word) in self.words.iter().enumerate() {
227            let mut w = *word;
228            while w != 0 {
229                let at = i as u32 * 64 + w.trailing_zeros();
230                if at >= self.len {
231                    return;
232                }
233                f(at);
234                w &= w - 1;
235            }
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn a_bitmap_holds_what_was_put_in_it() {
246        let mut b = Bits::new(200);
247        for at in [0u32, 1, 63, 64, 65, 199] {
248            b.set(at);
249        }
250        assert!(b.get(64));
251        assert!(!b.get(66));
252
253        let mut seen = Vec::new();
254        b.for_each(|at| seen.push(at));
255        assert_eq!(seen, vec![0, 1, 63, 64, 65, 199]);
256
257        b.clear();
258        assert!(!b.get(0));
259        assert!(!b.get(199));
260    }
261
262    /// The last word has bits past the end of the graph in it, and nothing may
263    /// hand one of them back as a node.
264    #[test]
265    fn a_bit_past_the_end_is_not_a_node() {
266        let mut b = Bits::new(3);
267        b.set(0);
268        b.set(2);
269        // Reaching into the spare bits of the last word, which only this test
270        // can do, because nothing else knows a node it has not been given.
271        b.words[0] |= 1 << 40;
272        let mut seen = Vec::new();
273        b.for_each(|at| seen.push(at));
274        assert_eq!(seen, vec![0, 2]);
275    }
276}