use crate::Snapshot;
use crate::algo::bfs::UNREACHED;
use yo_common::Rng;
pub const PIVOTS: u32 = 256;
const SEED: u64 = 0xb173_eee0;
#[derive(Debug, Clone)]
pub struct Between {
of: Vec<f64>,
pivots: u32,
exact: bool,
}
impl Between {
#[must_use]
pub fn of(&self, node: u32) -> f64 {
self.of[node as usize]
}
#[must_use]
pub fn scores(&self) -> &[f64] {
&self.of
}
#[must_use]
pub fn pivots(&self) -> u32 {
self.pivots
}
#[must_use]
pub fn exact(&self) -> bool {
self.exact
}
#[must_use]
pub fn top(&self, n: usize) -> Vec<(u32, f64)> {
let mut all: Vec<(u32, f64)> = self
.of
.iter()
.enumerate()
.map(|(node, score)| (node as u32, *score))
.collect();
all.sort_unstable_by(|a, b| b.1.total_cmp(&a.1).then(a.0.cmp(&b.0)));
all.truncate(n);
all
}
}
#[must_use]
pub fn betweenness(g: &Snapshot) -> Between {
betweenness_with(g, PIVOTS)
}
#[must_use]
pub fn betweenness_with(g: &Snapshot, pivots: u32) -> Between {
let n = g.nodes();
if pivots >= n {
return betweenness_exact(g);
}
let mut from: Vec<u32> = (0..n).collect();
let mut rng = Rng::new(SEED);
for at in 0..pivots as usize {
let take = at + (rng.next_u64() % (n as u64 - at as u64)) as usize;
from.swap(at, take);
}
from.truncate(pivots as usize);
let mut c = accumulate(g, &from);
let scale = f64::from(n) / f64::from(pivots);
for score in &mut c.of {
*score *= scale;
}
c
}
#[must_use]
pub fn betweenness_exact(g: &Snapshot) -> Between {
let all: Vec<u32> = (0..g.nodes()).collect();
let mut c = accumulate(g, &all);
c.exact = true;
c
}
fn accumulate(g: &Snapshot, from: &[u32]) -> Between {
let n = g.nodes() as usize;
let mut of = vec![0f64; n];
if n == 0 {
return Between {
of,
pivots: 0,
exact: false,
};
}
let mut depth = vec![UNREACHED; n];
let mut paths = vec![0f64; n];
let mut owed = vec![0f64; n];
let mut order: Vec<u32> = Vec::new();
for src in from {
order.clear();
depth[*src as usize] = 0;
paths[*src as usize] = 1.0;
let mut head = 0usize;
order.push(*src);
while head < order.len() {
let node = order[head];
head += 1;
let next = depth[node as usize] + 1;
for to in g.out(node) {
if depth[*to as usize] == UNREACHED {
depth[*to as usize] = next;
order.push(*to);
}
if depth[*to as usize] == next {
paths[*to as usize] += paths[node as usize];
}
}
}
for node in order.iter().rev() {
if depth[*node as usize] > 0 {
let share = (1.0 + owed[*node as usize]) / paths[*node as usize];
let back = depth[*node as usize] - 1;
for to in g.into_(*node) {
if depth[*to as usize] == back {
owed[*to as usize] += paths[*to as usize] * share;
}
}
}
if node != src {
of[*node as usize] += owed[*node as usize];
}
}
for node in &order {
depth[*node as usize] = UNREACHED;
paths[*node as usize] = 0.0;
owed[*node as usize] = 0.0;
}
}
Between {
of,
pivots: from.len() as u32,
exact: false,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::NO_PROPS;
use crate::{Graph, Snapshot};
use yo_common::Rng;
fn linked(edges: &[(u64, u64)]) -> Graph {
let mut g = Graph::new();
for (from, to) in edges {
g.link(*from, *to, 1, NO_PROPS).expect("an edge");
}
g
}
fn undirected(edges: &[(u64, u64)]) -> Graph {
let mut both: Vec<(u64, u64)> = Vec::new();
for (a, b) in edges {
both.push((*a, *b));
both.push((*b, *a));
}
linked(&both)
}
fn reference(g: &Snapshot) -> Vec<f64> {
let n = g.nodes() as usize;
let count = |src: u32, back: bool| {
let mut far = vec![u32::MAX; n];
let mut paths = vec![0f64; n];
far[src as usize] = 0;
paths[src as usize] = 1.0;
let mut order = vec![src];
let mut head = 0;
while head < order.len() {
let node = order[head];
head += 1;
let next = far[node as usize] + 1;
let near = if back { g.into_(node) } else { g.out(node) };
for to in near {
if far[*to as usize] == u32::MAX {
far[*to as usize] = next;
order.push(*to);
}
if far[*to as usize] == next {
paths[*to as usize] += paths[node as usize];
}
}
}
(far, paths)
};
let out: Vec<(Vec<u32>, Vec<f64>)> = (0..n as u32).map(|s| count(s, false)).collect();
let into: Vec<(Vec<u32>, Vec<f64>)> = (0..n as u32).map(|s| count(s, true)).collect();
let mut of = vec![0f64; n];
for (s, (far_s, count_s)) in out.iter().enumerate() {
for (t, (far_t, count_t)) in into.iter().enumerate() {
if s == t || far_s[t] == u32::MAX {
continue;
}
let (far, all) = (far_s[t], count_s[t]);
for (v, of) in of.iter_mut().enumerate() {
if v == s || v == t {
continue;
}
let (there, back) = (far_s[v], far_t[v]);
if there == u32::MAX || back == u32::MAX || there + back != far {
continue;
}
*of += count_s[v] * count_t[v] / all;
}
}
}
of
}
#[test]
fn the_middle_of_a_chain() {
let s = Snapshot::of(&undirected(&[(1, 2), (2, 3)]));
let c = betweenness_exact(&s);
assert!((c.of(s.dense(2).expect("2")) - 2.0).abs() < 1e-9);
assert_eq!(c.of(s.dense(1).expect("1")), 0.0);
assert_eq!(c.of(s.dense(3).expect("3")), 0.0);
assert!(c.exact());
}
#[test]
fn the_bridge_between_two_halves() {
let mut edges = Vec::new();
for a in 0..5u64 {
for b in a + 1..5 {
edges.push((a, b));
edges.push((a + 10, b + 10));
}
}
edges.push((4, 10));
let s = Snapshot::of(&undirected(&edges));
let c = betweenness_exact(&s);
let top = c.top(2);
let ends = [s.dense(4).expect("4"), s.dense(10).expect("10")];
assert!(ends.contains(&top[0].0), "{top:?}");
assert!(ends.contains(&top[1].0), "{top:?}");
}
#[test]
fn a_clique_spreads_it_evenly() {
let mut edges = Vec::new();
for a in 0..6u64 {
for b in a + 1..6 {
edges.push((a, b));
}
}
let s = Snapshot::of(&undirected(&edges));
let c = betweenness_exact(&s);
assert!(c.scores().iter().all(|score| score.abs() < 1e-9));
}
#[test]
fn it_agrees_with_the_definition() {
let mut rng = Rng::new(0xb17e);
for case in 0..40 {
let nodes = 2 + rng.next_u64() % 25;
let edges: Vec<(u64, u64)> = (0..nodes * 2)
.map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
.collect();
let s = Snapshot::of(&linked(&edges));
let (mine, theirs) = (betweenness_exact(&s), reference(&s));
for node in 0..s.nodes() {
let apart = (mine.of(node) - theirs[node as usize]).abs();
assert!(apart < 1e-9, "case {case}, node {node}, {apart} out");
}
}
}
#[test]
fn it_agrees_with_the_definition_both_ways() {
let mut rng = Rng::new(0xb17f);
for case in 0..30 {
let nodes = 3 + rng.next_u64() % 20;
let edges: Vec<(u64, u64)> = (0..nodes)
.map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
.collect();
let s = Snapshot::of(&undirected(&edges));
let (mine, theirs) = (betweenness_exact(&s), reference(&s));
for node in 0..s.nodes() {
assert!(
(mine.of(node) - theirs[node as usize]).abs() < 1e-9,
"case {case}, node {node}"
);
}
}
}
#[test]
fn the_estimate_finds_the_bridge() {
let mut edges = Vec::new();
for group in 0..2u64 {
for a in 0..30u64 {
for b in a + 1..30 {
edges.push((group * 100 + a, group * 100 + b));
}
}
}
edges.push((29, 100));
let s = Snapshot::of(&undirected(&edges));
let sampled = betweenness_with(&s, 20);
let exact = betweenness_exact(&s);
assert!(!sampled.exact());
assert_eq!(sampled.pivots(), 20);
let ends = [s.dense(29).expect("29"), s.dense(100).expect("100")];
assert!(ends.contains(&sampled.top(1)[0].0));
assert!(ends.contains(&exact.top(1)[0].0));
}
#[test]
fn the_estimate_is_close() {
let mut rng = Rng::new(0xb180);
let nodes = 200u64;
let edges: Vec<(u64, u64)> = (0..nodes * 4)
.map(|_| (rng.next_u64() % nodes, rng.next_u64() % nodes))
.collect();
let s = Snapshot::of(&undirected(&edges));
let exact = betweenness_exact(&s);
let sampled = betweenness_with(&s, 100);
let most = exact.top(1)[0].1;
let apart: Vec<f64> = (0..s.nodes())
.map(|node| (sampled.of(node) - exact.of(node)).abs())
.collect();
let mean = apart.iter().sum::<f64>() / f64::from(s.nodes());
let worst = apart.iter().copied().fold(0f64, f64::max);
assert!(mean < most / 20.0, "{mean} on average out of {most}");
assert!(worst < most / 3.0, "{worst} at worst out of {most}");
}
#[test]
fn asking_for_everybody_is_the_exact_answer() {
let s = Snapshot::of(&undirected(&[(1, 2), (2, 3), (3, 4)]));
let all = betweenness_with(&s, 99);
assert!(all.exact());
assert_eq!(all.scores(), betweenness_exact(&s).scores());
}
#[test]
fn nothing_at_all() {
let c = betweenness(&Snapshot::default());
assert!(c.scores().is_empty());
assert!(c.top(3).is_empty());
assert_eq!(c.pivots(), 0);
}
#[test]
fn a_graph_with_no_edges() {
let mut g = Graph::new();
for id in 0..4u64 {
g.add_node(id).expect("a node");
}
let c = betweenness(&Snapshot::of(&g));
assert!(c.scores().iter().all(|score| *score == 0.0));
}
#[test]
fn one_way_edges_are_read_one_way() {
let s = Snapshot::of(&linked(&[(1, 2), (2, 3)]));
let c = betweenness_exact(&s);
assert!((c.of(s.dense(2).expect("2")) - 1.0).abs() < 1e-9);
}
#[test]
fn a_tie_is_shared() {
let s = Snapshot::of(&linked(&[(1, 2), (1, 3), (2, 4), (3, 4)]));
let c = betweenness_exact(&s);
assert!((c.of(s.dense(2).expect("2")) - 0.5).abs() < 1e-9);
assert!((c.of(s.dense(3).expect("3")) - 0.5).abs() < 1e-9);
}
#[test]
fn two_runs_agree() {
let mut rng = Rng::new(0xb181);
let edges: Vec<(u64, u64)> = (0..200)
.map(|_| (rng.next_u64() % 60, rng.next_u64() % 60))
.collect();
let s = Snapshot::of(&undirected(&edges));
assert_eq!(
betweenness_with(&s, 10).scores(),
betweenness_with(&s, 10).scores()
);
}
}