use std::collections::VecDeque;
use crate::id::{GraphNodeId, index_of, position};
use super::error::GraphAnalysisError;
use super::index::{AnalysisContext, SelectedIndexes};
use super::limits::GraphAnalysisLimits;
#[derive(Clone, Copy, Debug)]
pub struct BetweennessCentrality {
node: GraphNodeId,
raw: f64,
normalized: f64,
}
impl BetweennessCentrality {
pub fn node(self) -> GraphNodeId {
self.node
}
pub fn raw(self) -> f64 {
self.raw
}
pub fn normalized(self) -> f64 {
self.normalized
}
}
struct Brandes {
totals: Box<[f64]>,
distance: Box<[Option<u32>]>,
routes: Box<[f64]>,
predecessors: Box<[Vec<GraphNodeId>]>,
share: Box<[f64]>,
order: Vec<GraphNodeId>,
queue: VecDeque<GraphNodeId>,
}
impl Brandes {
fn empty(width: usize) -> Self {
Self {
totals: vec![0.0; width].into_boxed_slice(),
distance: vec![None; width].into_boxed_slice(),
routes: vec![0.0; width].into_boxed_slice(),
predecessors: vec![Vec::new(); width].into_boxed_slice(),
share: vec![0.0; width].into_boxed_slice(),
order: Vec::with_capacity(width),
queue: VecDeque::new(),
}
}
fn walk(&mut self, indexes: &SelectedIndexes) {
for at in 0..self.totals.len() {
self.round(indexes, GraphNodeId::new(position(at)));
}
}
fn round(&mut self, indexes: &SelectedIndexes, source: GraphNodeId) {
self.restart(source);
self.measure(indexes);
self.credit(source);
}
fn restart(&mut self, source: GraphNodeId) {
self.distance.iter_mut().for_each(|steps| *steps = None);
self.routes.iter_mut().for_each(|routes| *routes = 0.0);
self.share.iter_mut().for_each(|share| *share = 0.0);
self.predecessors.iter_mut().for_each(Vec::clear);
self.order.clear();
self.queue.clear();
self.settle(source, 0);
add(&mut self.routes, source, 1.0);
}
fn measure(&mut self, indexes: &SelectedIndexes) {
while let Some(node) = self.queue.pop_front() {
self.relax(indexes, node);
}
}
fn relax(&mut self, indexes: &SelectedIndexes, node: GraphNodeId) {
let next = match self.steps(node) {
Some(steps) => steps + 1,
None => return,
};
for entry in indexes.outgoing(node) {
self.follow((node, entry.node()), next);
}
}
fn follow(&mut self, step: (GraphNodeId, GraphNodeId), next: u32) {
let (from, reached) = step;
match self.steps(reached) {
None => self.settle(reached, next),
Some(steps) if steps == next => (),
Some(_) => return,
}
self.join(reached, from);
}
fn settle(&mut self, node: GraphNodeId, steps: u32) {
match self.distance.get_mut(index_of(node.index())) {
Some(slot) => *slot = Some(steps),
None => return,
}
self.order.push(node);
self.queue.push_back(node);
}
fn join(&mut self, node: GraphNodeId, from: GraphNodeId) {
let arriving = held(&self.routes, from);
add(&mut self.routes, node, arriving);
if let Some(list) = self.predecessors.get_mut(index_of(node.index())) {
list.push(from);
}
}
fn steps(&self, node: GraphNodeId) -> Option<u32> {
self.distance.get(index_of(node.index())).copied().flatten()
}
fn credit(&mut self, source: GraphNodeId) {
let Self {
totals,
routes,
predecessors,
share,
order,
..
} = self;
for node in order.iter().rev() {
pass_back((&routes[..], &predecessors[..]), share, *node);
award((totals, &share[..]), source, *node);
}
}
fn scored(self) -> Box<[BetweennessCentrality]> {
let pairs = ordered_pairs(self.totals.len());
self.totals
.iter()
.enumerate()
.map(|(at, score)| BetweennessCentrality {
node: GraphNodeId::new(position(at)),
raw: *score,
normalized: against(*score, pairs),
})
.collect()
}
}
pub(crate) fn betweenness(
analysis: &AnalysisContext<'_>,
) -> Result<Box<[BetweennessCentrality]>, GraphAnalysisError> {
let indexes = analysis.indexes();
admitted_work(indexes, analysis.limits())?;
let mut pass = Brandes::empty(indexes.node_count());
pass.walk(indexes);
Ok(pass.scored())
}
pub(crate) fn admitted_work(
indexes: &SelectedIndexes,
limits: GraphAnalysisLimits,
) -> Result<(), GraphAnalysisError> {
let nodes = u64::from(position(indexes.node_count()));
let required = nodes
.checked_add(u64::from(indexes.selected_edges()))
.and_then(|round| nodes.checked_mul(round))
.ok_or(GraphAnalysisError::BetweennessWorkOverflow)?;
match required <= limits.max_betweenness_work() {
true => Ok(()),
false => Err(GraphAnalysisError::BetweennessWorkLimitExceeded {
required,
limit: limits.max_betweenness_work(),
}),
}
}
fn pass_back(reached: (&[f64], &[Vec<GraphNodeId>]), share: &mut [f64], node: GraphNodeId) {
let (routes, predecessors) = reached;
let carried = (1.0 + held(share, node)) / held(routes, node);
for from in entering(predecessors, node) {
add(share, *from, held(routes, *from) * carried);
}
}
fn award(state: (&mut [f64], &[f64]), source: GraphNodeId, node: GraphNodeId) {
let (totals, share) = state;
match node == source {
true => (),
false => add(totals, node, held(share, node)),
}
}
fn entering(predecessors: &[Vec<GraphNodeId>], node: GraphNodeId) -> &[GraphNodeId] {
match predecessors.get(index_of(node.index())) {
Some(list) => list,
None => &[],
}
}
fn held(values: &[f64], node: GraphNodeId) -> f64 {
values
.get(index_of(node.index()))
.copied()
.unwrap_or_default()
}
fn add(values: &mut [f64], node: GraphNodeId, amount: f64) {
if let Some(slot) = values.get_mut(index_of(node.index())) {
*slot += amount;
}
}
fn ordered_pairs(nodes: usize) -> Option<f64> {
match nodes > 2 {
true => Some((nodes - 1) as f64 * (nodes - 2) as f64),
false => None,
}
}
fn against(raw: f64, pairs: Option<f64>) -> f64 {
match pairs {
Some(count) => raw / count,
None => 0.0,
}
}