use crate::error::GraphError;
use crate::graph::Graph;
use core::cmp::Reverse;
use sim_lib_discrete_algebra::{AlgebraLimits, BoolRing, Matrix, MinPlus};
use std::collections::BinaryHeap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PathResult<W> {
pub distances: Vec<Option<W>>,
pub predecessors: Vec<Option<usize>>,
}
fn out_arcs<N, W: Clone>(graph: &Graph<N, W>, node: usize) -> Vec<(usize, W)> {
let undirected = !graph.is_directed();
let mut arcs = Vec::new();
for e in &graph.edges {
if e.source == node {
arcs.push((e.target, e.weight.clone()));
} else if undirected && e.target == node {
arcs.push((e.source, e.weight.clone()));
}
}
arcs
}
pub fn dijkstra<N>(graph: &Graph<N, u64>, source: usize) -> Result<PathResult<u64>, GraphError> {
graph.validate()?;
let n = graph.node_count();
if source >= n {
return Err(GraphError::NodeOutOfRange {
node: source,
count: n,
});
}
let mut dist = vec![None; n];
let mut pred = vec![None; n];
let mut heap: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();
dist[source] = Some(0);
heap.push(Reverse((0, source)));
while let Some(Reverse((d, u))) = heap.pop() {
if dist[u].is_some_and(|best| d > best) {
continue;
}
for (v, w) in out_arcs(graph, u) {
let nd = d + w;
if dist[v].is_none_or(|best| nd < best) {
dist[v] = Some(nd);
pred[v] = Some(u);
heap.push(Reverse((nd, v)));
}
}
}
Ok(PathResult {
distances: dist,
predecessors: pred,
})
}
pub fn bellman_ford<N>(
graph: &Graph<N, i64>,
source: usize,
) -> Result<(PathResult<i64>, bool), GraphError> {
graph.validate()?;
let n = graph.node_count();
if source >= n {
return Err(GraphError::NodeOutOfRange {
node: source,
count: n,
});
}
let undirected = !graph.is_directed();
let mut arcs: Vec<(usize, usize, i64)> = Vec::new();
for e in &graph.edges {
arcs.push((e.source, e.target, e.weight));
if undirected {
arcs.push((e.target, e.source, e.weight));
}
}
let mut dist: Vec<Option<i64>> = vec![None; n];
let mut pred = vec![None; n];
dist[source] = Some(0);
for _ in 0..n.saturating_sub(1) {
let mut changed = false;
for &(a, b, w) in &arcs {
if let Some(da) = dist[a] {
let nd = da + w;
if dist[b].is_none_or(|best| nd < best) {
dist[b] = Some(nd);
pred[b] = Some(a);
changed = true;
}
}
}
if !changed {
break;
}
}
let mut negative_cycle = false;
for &(a, b, w) in &arcs {
if let Some(da) = dist[a]
&& dist[b].is_none_or(|best| da + w < best)
{
negative_cycle = true;
break;
}
}
Ok((
PathResult {
distances: dist,
predecessors: pred,
},
negative_cycle,
))
}
pub fn all_pairs_shortest_paths<N>(graph: &Graph<N, i64>) -> Result<Matrix<MinPlus>, GraphError> {
graph.validate()?;
let n = graph.node_count();
let undirected = !graph.is_directed();
let mut m = Matrix::filled(n, n, MinPlus::Inf);
for e in &graph.edges {
m.data[e.source * n + e.target] = min_plus_add(m.data[e.source * n + e.target], e.weight);
if undirected {
m.data[e.target * n + e.source] =
min_plus_add(m.data[e.target * n + e.source], e.weight);
}
}
Ok(m.closure(AlgebraLimits::default())?)
}
fn min_plus_add(cur: MinPlus, w: i64) -> MinPlus {
use sim_lib_discrete_algebra::Semiring;
cur.add(&MinPlus::Fin(w))
}
pub fn reachability<N, W>(graph: &Graph<N, W>) -> Result<Matrix<BoolRing>, GraphError> {
graph.validate()?;
let n = graph.node_count();
let undirected = !graph.is_directed();
let mut m = Matrix::filled(n, n, BoolRing(false));
for e in &graph.edges {
m.data[e.source * n + e.target] = BoolRing(true);
if undirected {
m.data[e.target * n + e.source] = BoolRing(true);
}
}
Ok(m.closure(AlgebraLimits::default())?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::edge::Directedness;
#[test]
fn dijkstra_row_equals_all_pairs_row() {
let edges = [(0usize, 1usize, 1u64), (1, 2, 2), (0, 2, 5), (2, 3, 1)];
let mut gu: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
let mut gi: Graph<u8, i64> = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
for &(s, t, w) in &edges {
gu.add_edge(s, t, w).unwrap();
gi.add_edge(s, t, w as i64).unwrap();
}
let dj = dijkstra(&gu, 0).unwrap();
let ap = all_pairs_shortest_paths(&gi).unwrap();
for j in 0..4 {
let from_closure = match ap.data[j] {
MinPlus::Fin(d) => Some(d as u64),
MinPlus::Inf => None,
};
assert_eq!(dj.distances[j], from_closure, "node {j}");
}
}
#[test]
fn bellman_ford_handles_negative_edge_without_cycle() {
let mut g: Graph<u8, i64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
g.add_edge(0, 1, 4).unwrap();
g.add_edge(0, 2, 5).unwrap();
g.add_edge(2, 1, -3).unwrap(); let (res, neg) = bellman_ford(&g, 0).unwrap();
assert!(!neg);
assert_eq!(res.distances[1], Some(2));
}
#[test]
fn bellman_ford_detects_negative_cycle() {
let mut g: Graph<u8, i64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
g.add_edge(0, 1, 1).unwrap();
g.add_edge(1, 0, -2).unwrap(); let (_res, neg) = bellman_ford(&g, 0).unwrap();
assert!(neg);
}
#[test]
fn reachability_is_transitive() {
let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
g.add_edge(0, 1, 1).unwrap();
g.add_edge(1, 2, 1).unwrap();
let r = reachability(&g).unwrap();
assert_eq!(r.data[2], BoolRing(true)); assert_eq!(r.data[6], BoolRing(false)); }
}