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}