use std::collections::VecDeque;
use crate::algorithms::paths::distances::distances;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EccMode {
Out,
In,
All,
}
fn bfs_distances(graph: &Graph, source: VertexId, mode: EccMode) -> IgraphResult<Vec<Option<u32>>> {
let n = graph.vcount();
if n == 0 {
return Ok(Vec::new());
}
if source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
let n_us = n as usize;
let mut dist: Vec<Option<u32>> = vec![None; n_us];
let mut queue: VecDeque<VertexId> = VecDeque::new();
dist[source as usize] = Some(0);
queue.push_back(source);
let directed = graph.is_directed();
while let Some(v) = queue.pop_front() {
let next = dist[v as usize]
.expect("dequeued vertex has been visited")
.checked_add(1)
.ok_or(IgraphError::Internal(
"distance overflow (graph diameter exceeds u32::MAX)",
))?;
let neighbours: Vec<VertexId> = if directed {
match mode {
EccMode::Out => graph.out_neighbors_vec(v)?,
EccMode::In => graph.in_neighbors_vec(v)?,
EccMode::All => {
let mut out = graph.out_neighbors_vec(v)?;
out.extend(graph.in_neighbors_vec(v)?);
out
}
}
} else {
graph.neighbors(v)?
};
for w in neighbours {
if dist[w as usize].is_none() {
dist[w as usize] = Some(next);
queue.push_back(w);
}
}
}
Ok(dist)
}
pub fn eccentricity(graph: &Graph) -> IgraphResult<Vec<u32>> {
let n = graph.vcount();
let mut out = vec![0u32; n as usize];
for v in 0..n {
let d = distances(graph, v)?;
let max = d.iter().filter_map(|x| *x).max().unwrap_or(0);
out[v as usize] = max;
}
Ok(out)
}
pub fn radius(graph: &Graph) -> IgraphResult<Option<u32>> {
let n = graph.vcount();
if n == 0 {
return Ok(None);
}
let ecc = eccentricity(graph)?;
Ok(ecc.into_iter().min())
}
pub fn diameter(graph: &Graph) -> IgraphResult<Option<u32>> {
let n = graph.vcount();
if n == 0 {
return Ok(None);
}
let ecc = eccentricity(graph)?;
Ok(ecc.into_iter().max())
}
pub fn eccentricity_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Vec<u32>> {
let n = graph.vcount();
let mut out = vec![0u32; n as usize];
for v in 0..n {
let d = bfs_distances(graph, v, mode)?;
let max = d.iter().filter_map(|x| *x).max().unwrap_or(0);
out[v as usize] = max;
}
Ok(out)
}
pub fn radius_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Option<u32>> {
if graph.vcount() == 0 {
return Ok(None);
}
let ecc = eccentricity_with_mode(graph, mode)?;
Ok(ecc.into_iter().min())
}
pub fn diameter_with_mode(graph: &Graph, mode: EccMode) -> IgraphResult<Option<u32>> {
if graph.vcount() == 0 {
return Ok(None);
}
let ecc = eccentricity_with_mode(graph, mode)?;
Ok(ecc.into_iter().max())
}
fn ecc_mode_to_dijkstra(mode: EccMode) -> crate::algorithms::paths::dijkstra::DijkstraMode {
use crate::algorithms::paths::dijkstra::DijkstraMode;
match mode {
EccMode::Out => DijkstraMode::Out,
EccMode::In => DijkstraMode::In,
EccMode::All => DijkstraMode::All,
}
}
pub fn eccentricity_weighted_with_mode(
graph: &Graph,
weights: &[f64],
mode: EccMode,
) -> IgraphResult<Vec<f64>> {
use crate::algorithms::paths::dijkstra::dijkstra_distances_with_mode;
let n = graph.vcount();
let mut out = vec![0.0_f64; n as usize];
for v in 0..n {
let d = dijkstra_distances_with_mode(graph, v, weights, ecc_mode_to_dijkstra(mode))?;
let max = d
.iter()
.filter_map(|x| *x)
.fold(0.0_f64, |a, b| if b > a { b } else { a });
out[v as usize] = max;
}
Ok(out)
}
pub fn radius_weighted_with_mode(
graph: &Graph,
weights: &[f64],
mode: EccMode,
) -> IgraphResult<Option<f64>> {
if graph.vcount() == 0 {
return Ok(None);
}
let ecc = eccentricity_weighted_with_mode(graph, weights, mode)?;
Ok(ecc
.into_iter()
.fold(None, |acc, x| Some(acc.map_or(x, |a: f64| a.min(x)))))
}
pub fn diameter_weighted_with_mode(
graph: &Graph,
weights: &[f64],
mode: EccMode,
) -> IgraphResult<Option<f64>> {
if graph.vcount() == 0 {
return Ok(None);
}
let ecc = eccentricity_weighted_with_mode(graph, weights, mode)?;
Ok(ecc
.into_iter()
.fold(None, |acc, x| Some(acc.map_or(x, |a: f64| a.max(x)))))
}
pub fn eccentricity_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Vec<f64>> {
eccentricity_weighted_with_mode(graph, weights, EccMode::Out)
}
pub fn radius_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Option<f64>> {
radius_weighted_with_mode(graph, weights, EccMode::Out)
}
pub fn diameter_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Option<f64>> {
diameter_weighted_with_mode(graph, weights, EccMode::Out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_graph_radii_are_none() {
let g = Graph::with_vertices(0);
assert_eq!(radius(&g).unwrap(), None);
assert_eq!(diameter(&g).unwrap(), None);
assert!(eccentricity(&g).unwrap().is_empty());
}
#[test]
fn singleton_has_zero_eccentricity() {
let g = Graph::with_vertices(1);
assert_eq!(eccentricity(&g).unwrap(), vec![0]);
assert_eq!(radius(&g).unwrap(), Some(0));
assert_eq!(diameter(&g).unwrap(), Some(0));
}
#[test]
fn isolated_vertices_each_have_eccentricity_zero() {
let g = Graph::with_vertices(5);
assert_eq!(eccentricity(&g).unwrap(), vec![0; 5]);
assert_eq!(radius(&g).unwrap(), Some(0));
assert_eq!(diameter(&g).unwrap(), Some(0));
}
#[test]
fn path_5_diameter_4_radius_2() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(eccentricity(&g).unwrap(), vec![4, 3, 2, 3, 4]);
assert_eq!(radius(&g).unwrap(), Some(2));
assert_eq!(diameter(&g).unwrap(), Some(4));
}
#[test]
fn cycle_4_eccentricity_uniform_2() {
let mut g = Graph::with_vertices(4);
for i in 0..4u32 {
g.add_edge(i, (i + 1) % 4).unwrap();
}
assert_eq!(eccentricity(&g).unwrap(), vec![2, 2, 2, 2]);
assert_eq!(radius(&g).unwrap(), Some(2));
assert_eq!(diameter(&g).unwrap(), Some(2));
}
#[test]
fn star_centre_has_eccentricity_1_leaves_have_2() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
assert_eq!(eccentricity(&g).unwrap(), vec![1, 2, 2, 2]);
assert_eq!(radius(&g).unwrap(), Some(1));
assert_eq!(diameter(&g).unwrap(), Some(2));
}
#[test]
fn disconnected_components_use_max_within_components() {
let mut g = Graph::with_vertices(5);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(3, 4).unwrap();
assert_eq!(eccentricity(&g).unwrap(), vec![2, 1, 2, 1, 1]);
assert_eq!(radius(&g).unwrap(), Some(1));
assert_eq!(diameter(&g).unwrap(), Some(2));
}
#[test]
fn directed_path_uses_out_edges() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(eccentricity(&g).unwrap(), vec![2, 1, 0]);
assert_eq!(diameter(&g).unwrap(), Some(2));
}
#[test]
fn self_loop_does_not_inflate_eccentricity() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(eccentricity(&g).unwrap(), vec![1, 1]);
assert_eq!(diameter(&g).unwrap(), Some(1));
}
#[test]
fn legacy_apis_match_with_mode_out() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
assert_eq!(
eccentricity(&g).unwrap(),
eccentricity_with_mode(&g, EccMode::Out).unwrap()
);
assert_eq!(
radius(&g).unwrap(),
radius_with_mode(&g, EccMode::Out).unwrap()
);
assert_eq!(
diameter(&g).unwrap(),
diameter_with_mode(&g, EccMode::Out).unwrap()
);
}
#[test]
fn undirected_all_modes_agree() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
for m in [EccMode::Out, EccMode::In, EccMode::All] {
assert_eq!(
eccentricity_with_mode(&g, m).unwrap(),
vec![3, 2, 2, 3],
"mode {m:?}"
);
assert_eq!(radius_with_mode(&g, m).unwrap(), Some(2));
assert_eq!(diameter_with_mode(&g, m).unwrap(), Some(3));
}
}
#[test]
fn directed_path_in_mode_reverses() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(
eccentricity_with_mode(&g, EccMode::Out).unwrap(),
vec![2, 1, 0]
);
assert_eq!(
eccentricity_with_mode(&g, EccMode::In).unwrap(),
vec![0, 1, 2]
);
}
#[test]
fn directed_path_all_mode_treats_undirected() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert_eq!(
eccentricity_with_mode(&g, EccMode::All).unwrap(),
vec![2, 1, 2]
);
assert_eq!(radius_with_mode(&g, EccMode::All).unwrap(), Some(1));
assert_eq!(diameter_with_mode(&g, EccMode::All).unwrap(), Some(2));
}
#[test]
fn directed_cycle_all_modes_uniform() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
assert_eq!(
eccentricity_with_mode(&g, EccMode::Out).unwrap(),
vec![2, 2, 2]
);
assert_eq!(
eccentricity_with_mode(&g, EccMode::In).unwrap(),
vec![2, 2, 2]
);
assert_eq!(
eccentricity_with_mode(&g, EccMode::All).unwrap(),
vec![1, 1, 1]
);
}
#[test]
fn directed_disconnected_in_out_is_zero_for_sinks_sources() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
assert_eq!(
eccentricity_with_mode(&g, EccMode::Out).unwrap(),
vec![1, 0, 0]
);
assert_eq!(
eccentricity_with_mode(&g, EccMode::In).unwrap(),
vec![0, 1, 0]
);
assert_eq!(
eccentricity_with_mode(&g, EccMode::All).unwrap(),
vec![1, 1, 0]
);
}
#[test]
fn radius_diameter_match_min_max_of_eccentricity() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 0).unwrap();
for m in [EccMode::Out, EccMode::In, EccMode::All] {
let ecc = eccentricity_with_mode(&g, m).unwrap();
assert_eq!(radius_with_mode(&g, m).unwrap(), ecc.iter().copied().min());
assert_eq!(
diameter_with_mode(&g, m).unwrap(),
ecc.iter().copied().max()
);
}
}
#[test]
fn empty_graph_with_mode_is_none() {
let g_u = Graph::with_vertices(0);
let g_d = Graph::new(0, true).unwrap();
for m in [EccMode::Out, EccMode::In, EccMode::All] {
assert_eq!(radius_with_mode(&g_u, m).unwrap(), None);
assert_eq!(diameter_with_mode(&g_u, m).unwrap(), None);
assert!(eccentricity_with_mode(&g_u, m).unwrap().is_empty());
assert_eq!(radius_with_mode(&g_d, m).unwrap(), None);
assert_eq!(diameter_with_mode(&g_d, m).unwrap(), None);
assert!(eccentricity_with_mode(&g_d, m).unwrap().is_empty());
}
}
#[test]
fn weighted_path_eccentricity() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let r = eccentricity_weighted(&g, &[1.0, 2.5]).unwrap();
assert_eq!(r, vec![3.5, 2.5, 3.5]);
assert_eq!(radius_weighted(&g, &[1.0, 2.5]).unwrap(), Some(2.5));
assert_eq!(diameter_weighted(&g, &[1.0, 2.5]).unwrap(), Some(3.5));
}
#[test]
fn weighted_singleton_zero() {
let g = Graph::with_vertices(1);
assert_eq!(eccentricity_weighted(&g, &[]).unwrap(), vec![0.0]);
assert_eq!(radius_weighted(&g, &[]).unwrap(), Some(0.0));
assert_eq!(diameter_weighted(&g, &[]).unwrap(), Some(0.0));
}
#[test]
fn weighted_isolated_vertices_zero() {
let g = Graph::with_vertices(4);
assert_eq!(eccentricity_weighted(&g, &[]).unwrap(), vec![0.0; 4]);
assert_eq!(radius_weighted(&g, &[]).unwrap(), Some(0.0));
assert_eq!(diameter_weighted(&g, &[]).unwrap(), Some(0.0));
}
#[test]
fn weighted_disconnected_unconn_true_semantics() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let r = eccentricity_weighted(&g, &[1.0, 4.0]).unwrap();
assert_eq!(r, vec![1.0, 1.0, 4.0, 4.0]);
assert_eq!(radius_weighted(&g, &[1.0, 4.0]).unwrap(), Some(1.0));
assert_eq!(diameter_weighted(&g, &[1.0, 4.0]).unwrap(), Some(4.0));
}
#[test]
fn weighted_directed_in_mode_reverses() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let w = vec![1.0, 2.0];
assert_eq!(
eccentricity_weighted_with_mode(&g, &w, EccMode::Out).unwrap(),
vec![3.0, 2.0, 0.0]
);
assert_eq!(
eccentricity_weighted_with_mode(&g, &w, EccMode::In).unwrap(),
vec![0.0, 1.0, 3.0]
);
assert_eq!(
eccentricity_weighted_with_mode(&g, &w, EccMode::All).unwrap(),
vec![3.0, 2.0, 3.0]
);
}
#[test]
fn weighted_undirected_modes_agree() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
let w = vec![1.0, 2.0, 4.0];
for m in [EccMode::Out, EccMode::In, EccMode::All] {
assert_eq!(
eccentricity_weighted_with_mode(&g, &w, m).unwrap(),
vec![3.0, 2.0, 3.0],
"mode {m:?}"
);
}
}
#[test]
fn weighted_negative_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(eccentricity_weighted(&g, &[-1.0]).is_err());
}
#[test]
fn weighted_empty_graph_radius_diameter_none() {
let g = Graph::with_vertices(0);
assert_eq!(radius_weighted(&g, &[]).unwrap(), None);
assert_eq!(diameter_weighted(&g, &[]).unwrap(), None);
assert!(eccentricity_weighted(&g, &[]).unwrap().is_empty());
}
#[test]
fn weighted_with_mode_default_matches_out() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let w = vec![1.0, 2.5];
assert_eq!(
eccentricity_weighted(&g, &w).unwrap(),
eccentricity_weighted_with_mode(&g, &w, EccMode::Out).unwrap()
);
}
}