use std::collections::VecDeque;
use crate::algorithms::paths::dijkstra::DijkstraMode;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
fn validate_weights(graph: &Graph, weights: &[f64]) -> IgraphResult<()> {
let m = graph.ecount();
if weights.len() != m {
return Err(IgraphError::InvalidArgument(format!(
"weights vector size ({}) differs from edge count ({})",
weights.len(),
m
)));
}
for (e, &w) in weights.iter().enumerate() {
if w.is_nan() {
return Err(IgraphError::InvalidArgument(format!(
"weight at edge {e} is NaN"
)));
}
}
Ok(())
}
fn incident_for_mode(graph: &Graph, v: VertexId, mode: DijkstraMode) -> IgraphResult<Vec<EdgeId>> {
if !graph.is_directed() {
return graph.incident(v);
}
match mode {
DijkstraMode::Out => graph.incident(v),
DijkstraMode::In => graph.incident_in(v),
DijkstraMode::All => {
let mut out = graph.incident(v)?;
out.extend(graph.incident_in(v)?);
Ok(out)
}
}
}
pub fn bellman_ford_distances(
graph: &Graph,
source: VertexId,
weights: &[f64],
) -> IgraphResult<Vec<Option<f64>>> {
bellman_ford_distances_with_mode(graph, source, weights, DijkstraMode::Out)
}
pub fn bellman_ford_distances_with_mode(
graph: &Graph,
source: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Vec<Option<f64>>> {
let n = graph.vcount();
if source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
validate_weights(graph, weights)?;
let n_usize = n as usize;
let mut dist: Vec<f64> = vec![f64::INFINITY; n_usize];
dist[source as usize] = 0.0;
let mut queue: VecDeque<VertexId> = VecDeque::with_capacity(n_usize);
let mut in_queue: Vec<bool> = vec![true; n_usize];
let mut num_queued: Vec<u32> = vec![0; n_usize];
for v in 0..n {
queue.push_back(v);
}
let n_u32 = n;
while let Some(j) = queue.pop_front() {
let j_idx = j as usize;
in_queue[j_idx] = false;
num_queued[j_idx] = num_queued[j_idx]
.checked_add(1)
.ok_or(IgraphError::Internal("num_queued overflow"))?;
if num_queued[j_idx] > n_u32 {
return Err(IgraphError::InvalidArgument(
"negative cycle reachable from source while running Bellman-Ford".to_string(),
));
}
if !dist[j_idx].is_finite() {
continue;
}
let incidents = incident_for_mode(graph, j, mode)?;
for eid in incidents {
let w = weights[eid as usize];
if w == f64::INFINITY {
continue;
}
let target = graph.edge_other(eid, j)?;
let target_idx = target as usize;
let altdist = dist[j_idx] + w;
if altdist < dist[target_idx] {
dist[target_idx] = altdist;
if !in_queue[target_idx] {
in_queue[target_idx] = true;
queue.push_back(target);
}
}
}
}
Ok(dist
.into_iter()
.map(|d| if d.is_finite() { Some(d) } else { None })
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn positive_weights_match_dijkstra_triangle() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 2).unwrap();
let weights = [1.0, 4.0, 2.0];
let bf = bellman_ford_distances(&g, 0, &weights).unwrap();
assert_eq!(bf, vec![Some(0.0), Some(1.0), Some(3.0)]);
}
#[test]
fn negative_edge_directed_no_cycle() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = bellman_ford_distances(&g, 0, &[3.0, -1.0]).unwrap();
assert_eq!(d, vec![Some(0.0), Some(3.0), Some(2.0)]);
}
#[test]
fn unreachable_vertex_is_none() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let d = bellman_ford_distances(&g, 0, &[1.0, 1.0]).unwrap();
assert_eq!(d, vec![Some(0.0), Some(1.0), None, None]);
}
#[test]
fn negative_cycle_directed_errors() {
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();
let err = bellman_ford_distances(&g, 0, &[1.0, 1.0, -3.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn negative_cycle_unreachable_does_not_error() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
g.add_edge(3, 2).unwrap();
let d = bellman_ford_distances(&g, 0, &[1.0, -1.0, -1.0]).unwrap();
assert_eq!(d, vec![Some(0.0), Some(1.0), None, None]);
}
#[test]
fn zero_weights_ok() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = bellman_ford_distances(&g, 0, &[0.0, 0.0]).unwrap();
assert_eq!(d, vec![Some(0.0), Some(0.0), Some(0.0)]);
}
#[test]
fn source_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = bellman_ford_distances(&g, 99, &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn weights_size_mismatch_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = bellman_ford_distances(&g, 0, &[1.0, 2.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn nan_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = bellman_ford_distances(&g, 0, &[f64::NAN]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn in_mode_walks_reverse_edges() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let d = bellman_ford_distances_with_mode(&g, 2, &[3.0, -1.0], DijkstraMode::In).unwrap();
assert_eq!(d, vec![Some(2.0), Some(-1.0), Some(0.0)]);
}
#[test]
fn all_mode_ignores_direction() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let d = bellman_ford_distances_with_mode(&g, 1, &[5.0], DijkstraMode::All).unwrap();
assert_eq!(d, vec![Some(5.0), Some(0.0)]);
}
#[test]
fn infinity_weight_ignored() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
let d = bellman_ford_distances(&g, 0, &[1.0, f64::INFINITY]).unwrap();
assert_eq!(d, vec![Some(0.0), Some(1.0), None]);
}
}