use crate::Snapshot;
use crate::algo::Bits;
pub const UNREACHED: u32 = u32::MAX;
const ALPHA: u64 = 15;
const BETA: u32 = 18;
#[must_use]
pub fn bfs(g: &Snapshot, src: u32) -> Vec<u32> {
search(g, src, false)
}
#[must_use]
pub fn bfs_both(g: &Snapshot, src: u32) -> Vec<u32> {
search(g, src, true)
}
fn search(g: &Snapshot, src: u32, both: bool) -> Vec<u32> {
let n = g.nodes();
let mut depth = vec![UNREACHED; n as usize];
if src >= n {
return depth;
}
depth[src as usize] = 0;
let mut frontier = vec![src];
let mut next = Vec::new();
let mut curr_bits = Bits::new(n);
let mut next_bits = Bits::new(n);
let mut scout = u64::from(degree(g, src, both));
let mut left = g.edges() * if both { 2 } else { 1 };
let mut d = 1;
while !frontier.is_empty() {
if scout > left / ALPHA {
curr_bits.clear();
for node in &frontier {
curr_bits.set(*node);
}
let mut awake = frontier.len() as u32;
let mut done = false;
loop {
let woke = bottom_up(g, &depth, &curr_bits, &mut next_bits, both);
if woke == 0 {
done = true;
break;
}
next_bits.for_each(|node| depth[node as usize] = d);
std::mem::swap(&mut curr_bits, &mut next_bits);
next_bits.clear();
d += 1;
let grew = woke >= awake;
awake = woke;
if !grew && woke <= n / BETA {
break;
}
}
if done {
break;
}
frontier.clear();
curr_bits.for_each(|node| frontier.push(node));
scout = frontier
.iter()
.map(|n| u64::from(degree(g, *n, both)))
.sum();
left = left.saturating_sub(scout);
continue;
}
left = left.saturating_sub(scout);
scout = 0;
next.clear();
for node in &frontier {
g.prefetch(*node);
}
for node in &frontier {
for to in g.out(*node) {
if depth[*to as usize] == UNREACHED {
depth[*to as usize] = d;
scout += u64::from(degree(g, *to, both));
next.push(*to);
}
}
if both {
for to in g.into_(*node) {
if depth[*to as usize] == UNREACHED {
depth[*to as usize] = d;
scout += u64::from(degree(g, *to, both));
next.push(*to);
}
}
}
}
std::mem::swap(&mut frontier, &mut next);
d += 1;
}
depth
}
fn bottom_up(g: &Snapshot, depth: &[u32], curr: &Bits, next: &mut Bits, both: bool) -> u32 {
let mut woke = 0;
for node in 0..g.nodes() {
if depth[node as usize] != UNREACHED {
continue;
}
let found = g.into_(node).iter().any(|from| curr.get(*from))
|| (both && g.out(node).iter().any(|from| curr.get(*from)));
if found {
next.set(node);
woke += 1;
}
}
woke
}
#[inline]
fn degree(g: &Snapshot, node: u32, both: bool) -> u32 {
if both {
g.out_degree(node) + g.in_degree(node)
} else {
g.out_degree(node)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::NO_PROPS;
use crate::{Graph, Snapshot};
use yo_common::Rng;
fn reference(g: &Snapshot, src: u32) -> Vec<u32> {
let mut depth = vec![UNREACHED; g.nodes() as usize];
depth[src as usize] = 0;
let mut queue = std::collections::VecDeque::from([src]);
while let Some(node) = queue.pop_front() {
for to in g.out(node) {
if depth[*to as usize] == UNREACHED {
depth[*to as usize] = depth[node as usize] + 1;
queue.push_back(*to);
}
}
}
depth
}
fn linked(edges: &[(u64, u64)]) -> Graph {
let mut g = Graph::new();
for (src, dst) in edges {
g.link(*src, *dst, 1, NO_PROPS).unwrap();
}
g
}
#[test]
fn a_chain_is_as_deep_as_it_is_long() {
let mut g = Graph::new();
for i in 0..999u64 {
g.link(i, i + 1, 1, NO_PROPS).unwrap();
}
let s = Snapshot::of(&g);
let depth = bfs(&s, 0);
for i in 0..1000u32 {
assert_eq!(depth[i as usize], i, "at {i}");
}
}
#[test]
fn what_the_edges_do_not_point_at_is_unreached() {
let s = Snapshot::of(&linked(&[(0, 1), (1, 2), (3, 2)]));
let depth = bfs(&s, 0);
assert_eq!(depth, vec![0, 1, 2, UNREACHED]);
assert_eq!(bfs_both(&s, 0), vec![0, 1, 2, 3]);
assert_eq!(bfs(&s, 3), vec![UNREACHED, UNREACHED, 1, 0]);
}
#[test]
fn a_source_that_is_not_a_node_reaches_nothing() {
let s = Snapshot::of(&linked(&[(0, 1)]));
assert_eq!(bfs(&s, 7), vec![UNREACHED, UNREACHED]);
let empty = Snapshot::of(&Graph::new());
assert!(bfs(&empty, 0).is_empty());
}
#[test]
fn a_self_loop_and_a_cycle_do_not_change_a_depth() {
let s = Snapshot::of(&linked(&[(0, 0), (0, 1), (1, 2), (2, 0)]));
assert_eq!(bfs(&s, 0), vec![0, 1, 2]);
}
#[test]
fn the_two_directions_agree_on_a_graph_that_switches() {
let mut rng = Rng::new(0x5eed);
let mut g = Graph::new();
let n = 20_000u64;
for i in 1..n {
g.link(0, i, 1, NO_PROPS).unwrap();
}
for _ in 0..100_000 {
let src = rng.next_u64() % n;
let dst = rng.next_u64() % n;
g.link(src, dst, 1, NO_PROPS).unwrap();
}
let s = Snapshot::of(&g);
assert_eq!(bfs(&s, 0), reference(&s, 0), "from the hub");
let far = s.dense(n - 1).unwrap();
assert_eq!(bfs(&s, far), reference(&s, far), "from a leaf");
}
#[test]
fn a_grid_is_the_distance_you_would_walk() {
let side = 40u64;
let mut g = Graph::new();
let at = |r: u64, c: u64| r * side + c;
for r in 0..side {
for c in 0..side {
if c + 1 < side {
g.link(at(r, c), at(r, c + 1), 1, NO_PROPS).unwrap();
}
if r + 1 < side {
g.link(at(r, c), at(r + 1, c), 1, NO_PROPS).unwrap();
}
}
}
let s = Snapshot::of(&g);
let depth = bfs(&s, 0);
for r in 0..side {
for c in 0..side {
let node = s.dense(at(r, c)).unwrap();
assert_eq!(depth[node as usize] as u64, r + c, "at {r},{c}");
}
}
}
#[test]
fn it_agrees_with_a_plain_search_on_a_hundred_random_graphs() {
let mut rng = Rng::new(0xa11ce);
for trial in 0..100 {
let n = 1 + rng.below(60) as u64;
let m = rng.below(200);
let mut g = Graph::new();
for i in 0..n {
g.add_node(i).unwrap();
}
for _ in 0..m {
let src = rng.next_u64() % n;
let dst = rng.next_u64() % n;
g.link(src, dst, 1, NO_PROPS).unwrap();
}
let s = Snapshot::of(&g);
for src in 0..s.nodes() {
assert_eq!(bfs(&s, src), reference(&s, src), "trial {trial} from {src}");
}
}
}
}