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())
}
pub fn bellman_ford_path_to(
graph: &Graph,
source: VertexId,
target: VertexId,
weights: &[f64],
) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
bellman_ford_path_to_with_mode(graph, source, target, weights, DijkstraMode::Out)
}
pub fn bellman_ford_path_to_with_mode(
graph: &Graph,
source: VertexId,
target: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
let n = graph.vcount();
if source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
if target >= n {
return Err(IgraphError::VertexOutOfRange { id: target, n });
}
validate_weights(graph, weights)?;
if source == target {
return Ok(Some((vec![source], vec![])));
}
let n_usize = n as usize;
let mut dist: Vec<f64> = vec![f64::INFINITY; n_usize];
dist[source as usize] = 0.0;
let mut parent_edge: Vec<Option<EdgeId>> = vec![None; n_usize];
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);
}
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 {
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 neighbor = graph.edge_other(eid, j)?;
let neighbor_idx = neighbor as usize;
let altdist = dist[j_idx] + w;
if altdist < dist[neighbor_idx] {
dist[neighbor_idx] = altdist;
parent_edge[neighbor_idx] = Some(eid);
if !in_queue[neighbor_idx] {
in_queue[neighbor_idx] = true;
queue.push_back(neighbor);
}
}
}
}
if !dist[target as usize].is_finite() {
return Ok(None);
}
let mut edges_rev: Vec<EdgeId> = Vec::new();
let mut vertices_rev: Vec<VertexId> = Vec::new();
let mut cur = target;
vertices_rev.push(cur);
while cur != source {
let eid = parent_edge[cur as usize].ok_or(IgraphError::Internal(
"bellman_ford_path_to: missing parent edge in path reconstruction",
))?;
edges_rev.push(eid);
cur = graph.edge_other(eid, cur)?;
vertices_rev.push(cur);
}
vertices_rev.reverse();
edges_rev.reverse();
Ok(Some((vertices_rev, edges_rev)))
}
pub type BellmanFordPathEntry = Option<(Vec<VertexId>, Vec<EdgeId>)>;
pub fn bellman_ford_paths(
graph: &Graph,
source: VertexId,
targets: &[VertexId],
weights: &[f64],
) -> IgraphResult<Vec<BellmanFordPathEntry>> {
bellman_ford_paths_with_mode(graph, source, targets, weights, DijkstraMode::Out)
}
pub fn bellman_ford_paths_with_mode(
graph: &Graph,
source: VertexId,
targets: &[VertexId],
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Vec<BellmanFordPathEntry>> {
let n = graph.vcount();
if source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
for &t in targets {
if t >= n {
return Err(IgraphError::VertexOutOfRange { id: t, 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 parent_edge: Vec<Option<EdgeId>> = vec![None; n_usize];
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);
}
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 {
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 neighbor = graph.edge_other(eid, j)?;
let neighbor_idx = neighbor as usize;
let altdist = dist[j_idx] + w;
if altdist < dist[neighbor_idx] {
dist[neighbor_idx] = altdist;
parent_edge[neighbor_idx] = Some(eid);
if !in_queue[neighbor_idx] {
in_queue[neighbor_idx] = true;
queue.push_back(neighbor);
}
}
}
}
let mut results: Vec<Option<(Vec<VertexId>, Vec<EdgeId>)>> = Vec::with_capacity(targets.len());
for &target in targets {
if target == source {
results.push(Some((vec![source], vec![])));
continue;
}
if !dist[target as usize].is_finite() {
results.push(None);
continue;
}
let mut edges_rev: Vec<EdgeId> = Vec::new();
let mut vertices_rev: Vec<VertexId> = Vec::new();
let mut cur = target;
vertices_rev.push(cur);
while cur != source {
let eid = parent_edge[cur as usize].ok_or(IgraphError::Internal(
"bellman_ford_paths: missing parent edge in path reconstruction",
))?;
edges_rev.push(eid);
cur = graph.edge_other(eid, cur)?;
vertices_rev.push(cur);
}
vertices_rev.reverse();
edges_rev.reverse();
results.push(Some((vertices_rev, edges_rev)));
}
Ok(results)
}
#[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]);
}
#[test]
fn path_to_simple_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(2, 3).unwrap(); let w = [3.0, -1.0, 5.0, 1.0];
let (vs, es) = bellman_ford_path_to(&g, 0, 3, &w).unwrap().unwrap();
assert_eq!(vs, vec![0, 1, 2, 3]);
assert_eq!(es, vec![0, 1, 3]);
}
#[test]
fn path_to_source_equals_target() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let (vs, es) = bellman_ford_path_to(&g, 0, 0, &[1.0]).unwrap().unwrap();
assert_eq!(vs, vec![0]);
assert!(es.is_empty());
}
#[test]
fn path_to_unreachable() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
let result = bellman_ford_path_to(&g, 0, 2, &[1.0]).unwrap();
assert!(result.is_none());
}
#[test]
fn path_to_negative_cycle_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_path_to(&g, 0, 2, &[1.0, 1.0, -3.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn path_to_prefers_negative_shortcut() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 2).unwrap(); let (vs, es) = bellman_ford_path_to(&g, 0, 2, &[1.0, -1.0, 5.0])
.unwrap()
.unwrap();
assert_eq!(vs, vec![0, 1, 2]);
assert_eq!(es, vec![0, 1]);
}
#[test]
fn path_to_with_in_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); let (vs, es) = bellman_ford_path_to_with_mode(&g, 2, 0, &[3.0, -1.0], DijkstraMode::In)
.unwrap()
.unwrap();
assert_eq!(vs, vec![2, 1, 0]);
assert_eq!(es, vec![1, 0]);
}
#[test]
fn path_to_undirected_negative_cycle() {
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 err = bellman_ford_path_to(&g, 0, 2, &[2.0, -1.0, 5.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn path_to_multiple_hops() {
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(0, 3).unwrap(); let (vs, es) = bellman_ford_path_to(&g, 0, 3, &[1.0, 1.0, 1.0, 10.0])
.unwrap()
.unwrap();
assert_eq!(vs, vec![0, 1, 2, 3]);
assert_eq!(es, vec![0, 1, 2]);
}
#[test]
fn paths_multi_target_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 2).unwrap(); g.add_edge(2, 3).unwrap(); let w = [3.0, -1.0, 5.0, 1.0];
let results = bellman_ford_paths(&g, 0, &[1, 2, 3], &w).unwrap();
assert_eq!(results.len(), 3);
let (vs, es) = results[0].as_ref().unwrap();
assert_eq!(*vs, vec![0, 1]);
assert_eq!(*es, vec![0]);
let (vs, es) = results[1].as_ref().unwrap();
assert_eq!(*vs, vec![0, 1, 2]);
assert_eq!(*es, vec![0, 1]);
let (vs, es) = results[2].as_ref().unwrap();
assert_eq!(*vs, vec![0, 1, 2, 3]);
assert_eq!(*es, vec![0, 1, 3]);
}
#[test]
fn paths_source_in_targets() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let results = bellman_ford_paths(&g, 0, &[0, 2], &[1.0, 1.0]).unwrap();
let (vs, es) = results[0].as_ref().unwrap();
assert_eq!(*vs, vec![0]);
assert!(es.is_empty());
let (vs, es) = results[1].as_ref().unwrap();
assert_eq!(*vs, vec![0, 1, 2]);
assert_eq!(*es, vec![0, 1]);
}
#[test]
fn paths_unreachable_target() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
let results = bellman_ford_paths(&g, 0, &[1, 2, 3], &[1.0]).unwrap();
assert!(results[0].is_some());
assert!(results[1].is_none());
assert!(results[2].is_none());
}
#[test]
fn paths_empty_targets() {
let g = Graph::with_vertices(3);
let results = bellman_ford_paths(&g, 0, &[], &[]).unwrap();
assert!(results.is_empty());
}
#[test]
fn paths_negative_cycle_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_paths(&g, 0, &[2], &[1.0, 1.0, -3.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn paths_duplicate_target() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let results = bellman_ford_paths(&g, 0, &[2, 2], &[1.0, 1.0]).unwrap();
assert_eq!(results.len(), 2);
assert_eq!(results[0], results[1]);
}
#[test]
fn paths_with_in_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); let results =
bellman_ford_paths_with_mode(&g, 2, &[0, 1], &[3.0, -1.0], DijkstraMode::In).unwrap();
let (vs, es) = results[1].as_ref().unwrap();
assert_eq!(*vs, vec![2, 1]);
assert_eq!(*es, vec![1]);
let (vs, es) = results[0].as_ref().unwrap();
assert_eq!(*vs, vec![2, 1, 0]);
assert_eq!(*es, vec![1, 0]);
}
#[test]
fn paths_agrees_with_path_to() {
let mut g = Graph::new(5, true).unwrap();
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 3).unwrap(); g.add_edge(3, 4).unwrap(); g.add_edge(2, 4).unwrap(); let w = [2.0, -1.0, 3.0, 1.0, 1.0];
let multi = bellman_ford_paths(&g, 0, &[1, 2, 3, 4], &w).unwrap();
for (i, &target) in [1u32, 2, 3, 4].iter().enumerate() {
let single = bellman_ford_path_to(&g, 0, target, &w).unwrap();
assert_eq!(multi[i], single, "mismatch for target {target}");
}
}
#[test]
fn paths_target_out_of_range() {
let g = Graph::with_vertices(3);
let err = bellman_ford_paths(&g, 0, &[99], &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
}