1use crate::Snapshot;
66use crate::algo::bfs::UNREACHED;
67use yo_common::Rng;
68
69pub const PIVOTS: u32 = 256;
76
77const SEED: u64 = 0xb173_eee0;
78
79#[derive(Debug, Clone)]
81pub struct Between {
82 of: Vec<f64>,
83 pivots: u32,
84 exact: bool,
85}
86
87impl Between {
88 #[must_use]
94 pub fn of(&self, node: u32) -> f64 {
95 self.of[node as usize]
96 }
97
98 #[must_use]
100 pub fn scores(&self) -> &[f64] {
101 &self.of
102 }
103
104 #[must_use]
106 pub fn pivots(&self) -> u32 {
107 self.pivots
108 }
109
110 #[must_use]
113 pub fn exact(&self) -> bool {
114 self.exact
115 }
116
117 #[must_use]
122 pub fn top(&self, n: usize) -> Vec<(u32, f64)> {
123 let mut all: Vec<(u32, f64)> = self
124 .of
125 .iter()
126 .enumerate()
127 .map(|(node, score)| (node as u32, *score))
128 .collect();
129 all.sort_unstable_by(|a, b| b.1.total_cmp(&a.1).then(a.0.cmp(&b.0)));
130 all.truncate(n);
131 all
132 }
133}
134
135#[must_use]
137pub fn betweenness(g: &Snapshot) -> Between {
138 betweenness_with(g, PIVOTS)
139}
140
141#[must_use]
147pub fn betweenness_with(g: &Snapshot, pivots: u32) -> Between {
148 let n = g.nodes();
149 if pivots >= n {
150 return betweenness_exact(g);
151 }
152 let mut from: Vec<u32> = (0..n).collect();
153 let mut rng = Rng::new(SEED);
155 for at in 0..pivots as usize {
156 let take = at + (rng.next_u64() % (n as u64 - at as u64)) as usize;
157 from.swap(at, take);
158 }
159 from.truncate(pivots as usize);
160
161 let mut c = accumulate(g, &from);
162 let scale = f64::from(n) / f64::from(pivots);
164 for score in &mut c.of {
165 *score *= scale;
166 }
167 c
168}
169
170#[must_use]
176pub fn betweenness_exact(g: &Snapshot) -> Between {
177 let all: Vec<u32> = (0..g.nodes()).collect();
178 let mut c = accumulate(g, &all);
179 c.exact = true;
180 c
181}
182
183fn accumulate(g: &Snapshot, from: &[u32]) -> Between {
185 let n = g.nodes() as usize;
186 let mut of = vec![0f64; n];
187 if n == 0 {
188 return Between {
189 of,
190 pivots: 0,
191 exact: false,
192 };
193 }
194
195 let mut depth = vec![UNREACHED; n];
200 let mut paths = vec![0f64; n];
201 let mut owed = vec![0f64; n];
202 let mut order: Vec<u32> = Vec::new();
203
204 for src in from {
205 order.clear();
206 depth[*src as usize] = 0;
207 paths[*src as usize] = 1.0;
208
209 let mut head = 0usize;
212 order.push(*src);
213 while head < order.len() {
214 let node = order[head];
215 head += 1;
216 let next = depth[node as usize] + 1;
217 for to in g.out(node) {
218 if depth[*to as usize] == UNREACHED {
219 depth[*to as usize] = next;
220 order.push(*to);
221 }
222 if depth[*to as usize] == next {
223 paths[*to as usize] += paths[node as usize];
224 }
225 }
226 }
227
228 for node in order.iter().rev() {
233 if depth[*node as usize] > 0 {
234 let share = (1.0 + owed[*node as usize]) / paths[*node as usize];
235 let back = depth[*node as usize] - 1;
236 for to in g.into_(*node) {
237 if depth[*to as usize] == back {
238 owed[*to as usize] += paths[*to as usize] * share;
239 }
240 }
241 }
242 if node != src {
243 of[*node as usize] += owed[*node as usize];
244 }
245 }
246
247 for node in &order {
248 depth[*node as usize] = UNREACHED;
249 paths[*node as usize] = 0.0;
250 owed[*node as usize] = 0.0;
251 }
252 }
253
254 Between {
255 of,
256 pivots: from.len() as u32,
257 exact: false,
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::graph::NO_PROPS;
265 use crate::{Graph, Snapshot};
266 use yo_common::Rng;
267
268 fn linked(edges: &[(u64, u64)]) -> Graph {
269 let mut g = Graph::new();
270 for (from, to) in edges {
271 g.link(*from, *to, 1, NO_PROPS).expect("an edge");
272 }
273 g
274 }
275
276 fn undirected(edges: &[(u64, u64)]) -> Graph {
278 let mut both: Vec<(u64, u64)> = Vec::new();
279 for (a, b) in edges {
280 both.push((*a, *b));
281 both.push((*b, *a));
282 }
283 linked(&both)
284 }
285
286 fn reference(g: &Snapshot) -> Vec<f64> {
289 let n = g.nodes() as usize;
290 let count = |src: u32, back: bool| {
292 let mut far = vec![u32::MAX; n];
293 let mut paths = vec![0f64; n];
294 far[src as usize] = 0;
295 paths[src as usize] = 1.0;
296 let mut order = vec![src];
297 let mut head = 0;
298 while head < order.len() {
299 let node = order[head];
300 head += 1;
301 let next = far[node as usize] + 1;
302 let near = if back { g.into_(node) } else { g.out(node) };
303 for to in near {
304 if far[*to as usize] == u32::MAX {
305 far[*to as usize] = next;
306 order.push(*to);
307 }
308 if far[*to as usize] == next {
309 paths[*to as usize] += paths[node as usize];
310 }
311 }
312 }
313 (far, paths)
314 };
315
316 let out: Vec<(Vec<u32>, Vec<f64>)> = (0..n as u32).map(|s| count(s, false)).collect();
317 let into: Vec<(Vec<u32>, Vec<f64>)> = (0..n as u32).map(|s| count(s, true)).collect();
318
319 let mut of = vec![0f64; n];
320 for (s, (far_s, count_s)) in out.iter().enumerate() {
321 for (t, (far_t, count_t)) in into.iter().enumerate() {
322 if s == t || far_s[t] == u32::MAX {
323 continue;
324 }
325 let (far, all) = (far_s[t], count_s[t]);
326 for (v, of) in of.iter_mut().enumerate() {
327 if v == s || v == t {
328 continue;
329 }
330 let (there, back) = (far_s[v], far_t[v]);
331 if there == u32::MAX || back == u32::MAX || there + back != far {
332 continue;
333 }
334 *of += count_s[v] * count_t[v] / all;
335 }
336 }
337 }
338 of
339 }
340
341 #[test]
342 fn the_middle_of_a_chain() {
343 let s = Snapshot::of(&undirected(&[(1, 2), (2, 3)]));
346 let c = betweenness_exact(&s);
347 assert!((c.of(s.dense(2).expect("2")) - 2.0).abs() < 1e-9);
348 assert_eq!(c.of(s.dense(1).expect("1")), 0.0);
349 assert_eq!(c.of(s.dense(3).expect("3")), 0.0);
350 assert!(c.exact());
351 }
352
353 #[test]
354 fn the_bridge_between_two_halves() {
355 let mut edges = Vec::new();
356 for a in 0..5u64 {
357 for b in a + 1..5 {
358 edges.push((a, b));
359 edges.push((a + 10, b + 10));
360 }
361 }
362 edges.push((4, 10));
363 let s = Snapshot::of(&undirected(&edges));
364 let c = betweenness_exact(&s);
365 let top = c.top(2);
366 let ends = [s.dense(4).expect("4"), s.dense(10).expect("10")];
367 assert!(ends.contains(&top[0].0), "{top:?}");
368 assert!(ends.contains(&top[1].0), "{top:?}");
369 }
370
371 #[test]
372 fn a_clique_spreads_it_evenly() {
373 let mut edges = Vec::new();
374 for a in 0..6u64 {
375 for b in a + 1..6 {
376 edges.push((a, b));
377 }
378 }
379 let s = Snapshot::of(&undirected(&edges));
380 let c = betweenness_exact(&s);
381 assert!(c.scores().iter().all(|score| score.abs() < 1e-9));
383 }
384
385 #[test]
386 fn it_agrees_with_the_definition() {
387 let mut rng = Rng::new(0xb17e);
388 for case in 0..40 {
389 let nodes = 2 + rng.next_u64() % 25;
390 let edges: Vec<(u64, u64)> = (0..nodes * 2)
391 .map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
392 .collect();
393 let s = Snapshot::of(&linked(&edges));
394 let (mine, theirs) = (betweenness_exact(&s), reference(&s));
395 for node in 0..s.nodes() {
396 let apart = (mine.of(node) - theirs[node as usize]).abs();
397 assert!(apart < 1e-9, "case {case}, node {node}, {apart} out");
398 }
399 }
400 }
401
402 #[test]
405 fn it_agrees_with_the_definition_both_ways() {
406 let mut rng = Rng::new(0xb17f);
407 for case in 0..30 {
408 let nodes = 3 + rng.next_u64() % 20;
409 let edges: Vec<(u64, u64)> = (0..nodes)
410 .map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
411 .collect();
412 let s = Snapshot::of(&undirected(&edges));
413 let (mine, theirs) = (betweenness_exact(&s), reference(&s));
414 for node in 0..s.nodes() {
415 assert!(
416 (mine.of(node) - theirs[node as usize]).abs() < 1e-9,
417 "case {case}, node {node}"
418 );
419 }
420 }
421 }
422
423 #[test]
426 fn the_estimate_finds_the_bridge() {
427 let mut edges = Vec::new();
428 for group in 0..2u64 {
429 for a in 0..30u64 {
430 for b in a + 1..30 {
431 edges.push((group * 100 + a, group * 100 + b));
432 }
433 }
434 }
435 edges.push((29, 100));
436 let s = Snapshot::of(&undirected(&edges));
437 let sampled = betweenness_with(&s, 20);
438 let exact = betweenness_exact(&s);
439 assert!(!sampled.exact());
440 assert_eq!(sampled.pivots(), 20);
441
442 let ends = [s.dense(29).expect("29"), s.dense(100).expect("100")];
443 assert!(ends.contains(&sampled.top(1)[0].0));
444 assert!(ends.contains(&exact.top(1)[0].0));
445 }
446
447 #[test]
453 fn the_estimate_is_close() {
454 let mut rng = Rng::new(0xb180);
455 let nodes = 200u64;
456 let edges: Vec<(u64, u64)> = (0..nodes * 4)
457 .map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
458 .collect();
459 let s = Snapshot::of(&undirected(&edges));
460 let exact = betweenness_exact(&s);
461 let sampled = betweenness_with(&s, 100);
462 let most = exact.top(1)[0].1;
463
464 let apart: Vec<f64> = (0..s.nodes())
465 .map(|node| (sampled.of(node) - exact.of(node)).abs())
466 .collect();
467 let mean = apart.iter().sum::<f64>() / f64::from(s.nodes());
468 let worst = apart.iter().copied().fold(0f64, f64::max);
469 assert!(mean < most / 20.0, "{mean} on average out of {most}");
470 assert!(worst < most / 3.0, "{worst} at worst out of {most}");
471 }
472
473 #[test]
474 fn asking_for_everybody_is_the_exact_answer() {
475 let s = Snapshot::of(&undirected(&[(1, 2), (2, 3), (3, 4)]));
476 let all = betweenness_with(&s, 99);
477 assert!(all.exact());
478 assert_eq!(all.scores(), betweenness_exact(&s).scores());
479 }
480
481 #[test]
482 fn nothing_at_all() {
483 let c = betweenness(&Snapshot::default());
484 assert!(c.scores().is_empty());
485 assert!(c.top(3).is_empty());
486 assert_eq!(c.pivots(), 0);
487 }
488
489 #[test]
490 fn a_graph_with_no_edges() {
491 let mut g = Graph::new();
492 for id in 0..4u64 {
493 g.add_node(id).expect("a node");
494 }
495 let c = betweenness(&Snapshot::of(&g));
496 assert!(c.scores().iter().all(|score| *score == 0.0));
497 }
498
499 #[test]
501 fn one_way_edges_are_read_one_way() {
502 let s = Snapshot::of(&linked(&[(1, 2), (2, 3)]));
505 let c = betweenness_exact(&s);
506 assert!((c.of(s.dense(2).expect("2")) - 1.0).abs() < 1e-9);
507 }
508
509 #[test]
511 fn a_tie_is_shared() {
512 let s = Snapshot::of(&linked(&[(1, 2), (1, 3), (2, 4), (3, 4)]));
514 let c = betweenness_exact(&s);
515 assert!((c.of(s.dense(2).expect("2")) - 0.5).abs() < 1e-9);
516 assert!((c.of(s.dense(3).expect("3")) - 0.5).abs() < 1e-9);
517 }
518
519 #[test]
520 fn two_runs_agree() {
521 let mut rng = Rng::new(0xb181);
522 let edges: Vec<(u64, u64)> = (0..200)
523 .map(|_| (rng.next_u64() % 60, rng.next_u64() % 60))
524 .collect();
525 let s = Snapshot::of(&undirected(&edges));
526 assert_eq!(
527 betweenness_with(&s, 10).scores(),
528 betweenness_with(&s, 10).scores()
529 );
530 }
531}