use alloc::{collections::VecDeque, vec::Vec};
use num_traits::AsPrimitive;
use crate::traits::{MonopartiteGraph, UndirectedMonopartiteMonoplexGraph};
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum DiameterError {
#[error("Cannot compute the diameter of a disconnected graph.")]
DisconnectedGraph,
}
impl From<DiameterError>
for crate::errors::monopartite_graph_error::algorithms::MonopartiteAlgorithmError
{
#[inline]
fn from(error: DiameterError) -> Self {
Self::DiameterError(error)
}
}
impl<G: MonopartiteGraph> From<DiameterError> for crate::errors::MonopartiteError<G> {
#[inline]
fn from(error: DiameterError) -> Self {
Self::AlgorithmError(error.into())
}
}
fn bfs_profile<G: UndirectedMonopartiteMonoplexGraph>(
graph: &G,
source: G::NodeId,
distances: &mut [usize],
parents: &mut [Option<G::NodeId>],
queue: &mut VecDeque<G::NodeId>,
order: usize,
) -> Result<(usize, G::NodeId), DiameterError>
where
G::NodeId: AsPrimitive<usize>,
{
distances.fill(usize::MAX);
parents.fill(None);
queue.clear();
distances[source.as_()] = 0;
queue.push_back(source);
let mut visited = 1;
let mut eccentricity = 0;
let mut farthest = source;
while let Some(node) = queue.pop_front() {
let node_distance = distances[node.as_()];
if node_distance > eccentricity
|| (node_distance == eccentricity && node.as_() > farthest.as_())
{
eccentricity = node_distance;
farthest = node;
}
for neighbor in graph.neighbors(node) {
let neighbor_index = neighbor.as_();
if distances[neighbor_index] != usize::MAX {
continue;
}
distances[neighbor_index] = node_distance + 1;
parents[neighbor_index] = Some(node);
visited += 1;
queue.push_back(neighbor);
}
}
if visited != order {
return Err(DiameterError::DisconnectedGraph);
}
Ok((eccentricity, farthest))
}
#[inline]
fn bfs_eccentricity<G: UndirectedMonopartiteMonoplexGraph>(
graph: &G,
source: G::NodeId,
distances: &mut [usize],
parents: &mut [Option<G::NodeId>],
queue: &mut VecDeque<G::NodeId>,
order: usize,
) -> Result<usize, DiameterError>
where
G::NodeId: AsPrimitive<usize>,
{
Ok(bfs_profile(graph, source, distances, parents, queue, order)?.0)
}
fn middle_of_bfs_path<NodeId>(source: NodeId, target: NodeId, parents: &[Option<NodeId>]) -> NodeId
where
NodeId: Copy + AsPrimitive<usize>,
{
let mut path = Vec::new();
let mut current = Some(target);
while let Some(node) = current {
path.push(node);
if node.as_() == source.as_() {
break;
}
current = parents[node.as_()];
}
debug_assert_eq!(path.last().map(|node| node.as_()), Some(source.as_()));
path.reverse();
path[path.len() / 2]
}
fn select_ifub_start<G: UndirectedMonopartiteMonoplexGraph>(
graph: &G,
node_ids: &[G::NodeId],
degrees: &[usize],
distances: &mut [usize],
parents: &mut [Option<G::NodeId>],
queue: &mut VecDeque<G::NodeId>,
order: usize,
) -> Result<(G::NodeId, usize), DiameterError>
where
G::NodeId: AsPrimitive<usize>,
{
let r1 = node_ids
.iter()
.copied()
.zip(degrees.iter().copied())
.max_by_key(|&(node, degree)| (degree, node.as_()))
.map(|(node, _)| node)
.expect("non-empty graph must contain at least one node");
let (_, a1) = bfs_profile(graph, r1, distances, parents, queue, order)?;
let (ecc_a1, b1) = bfs_profile(graph, a1, distances, parents, queue, order)?;
let r2 = middle_of_bfs_path(a1, b1, parents);
let (_, a2) = bfs_profile(graph, r2, distances, parents, queue, order)?;
let (ecc_a2, b2) = bfs_profile(graph, a2, distances, parents, queue, order)?;
let start = middle_of_bfs_path(a2, b2, parents);
Ok((start, ecc_a1.max(ecc_a2)))
}
fn ifub_from_profile<G: UndirectedMonopartiteMonoplexGraph>(
graph: &G,
node_ids: &[G::NodeId],
start_eccentricity: usize,
initial_lower_bound: usize,
distances: &mut [usize],
parents: &mut [Option<G::NodeId>],
queue: &mut VecDeque<G::NodeId>,
) -> Result<usize, DiameterError>
where
G::NodeId: AsPrimitive<usize>,
{
let order = distances.len();
let mut fringe_levels = vec![Vec::new(); start_eccentricity + 1];
for &node in node_ids {
fringe_levels[distances[node.as_()]].push(node);
}
let mut lower_bound = initial_lower_bound.max(start_eccentricity);
let mut upper_bound = start_eccentricity.saturating_mul(2);
let mut level = start_eccentricity;
while upper_bound > lower_bound && level > 0 {
let mut fringe_eccentricity = 0;
for &node in &fringe_levels[level] {
fringe_eccentricity = fringe_eccentricity
.max(bfs_eccentricity(graph, node, distances, parents, queue, order)?);
}
lower_bound = lower_bound.max(fringe_eccentricity);
let next_upper_bound = 2 * (level - 1);
if lower_bound > next_upper_bound {
return Ok(lower_bound);
}
upper_bound = next_upper_bound;
level -= 1;
}
Ok(lower_bound)
}
pub trait Diameter: UndirectedMonopartiteMonoplexGraph + Sized
where
Self::NodeId: AsPrimitive<usize>,
{
#[inline]
fn diameter(&self) -> Result<usize, crate::errors::MonopartiteError<Self>> {
let order = self.number_of_nodes().as_();
if order <= 1 {
return Ok(0);
}
let node_ids: Vec<Self::NodeId> = self.node_ids().collect();
let degrees: Vec<usize> = node_ids.iter().map(|&node| self.degree(node).as_()).collect();
let mut distances = vec![usize::MAX; order];
let mut parents = vec![None; order];
let mut queue = VecDeque::with_capacity(order);
let degree_start = node_ids
.iter()
.copied()
.zip(degrees.iter().copied())
.max_by_key(|&(node, degree)| (degree, node.as_()))
.map(|(node, _)| node)
.expect("non-empty graph must contain at least one node");
let degree_start_eccentricity =
bfs_eccentricity(self, degree_start, &mut distances, &mut parents, &mut queue, order)?;
if degree_start_eccentricity.saturating_mul(2) <= order {
return Ok(ifub_from_profile(
self,
&node_ids,
degree_start_eccentricity,
degree_start_eccentricity,
&mut distances,
&mut parents,
&mut queue,
)?);
}
let (start, lower_bound) = select_ifub_start(
self,
&node_ids,
°rees,
&mut distances,
&mut parents,
&mut queue,
order,
)?;
let start_eccentricity =
bfs_eccentricity(self, start, &mut distances, &mut parents, &mut queue, order)?;
Ok(ifub_from_profile(
self,
&node_ids,
start_eccentricity,
lower_bound.max(degree_start_eccentricity),
&mut distances,
&mut parents,
&mut queue,
)?)
}
}
impl<G> Diameter for G
where
G: UndirectedMonopartiteMonoplexGraph + Sized,
G::NodeId: AsPrimitive<usize>,
{
}