use std::cmp::Ordering;
use std::collections::BinaryHeap;
use crate::algorithms::paths::dijkstra::DijkstraMode;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
#[derive(Copy, Clone)]
struct WidestFrontier(f64, VertexId);
impl PartialEq for WidestFrontier {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
impl Eq for WidestFrontier {}
impl Ord for WidestFrontier {
fn cmp(&self, other: &Self) -> Ordering {
self.0.total_cmp(&other.0).then(other.1.cmp(&self.1))
}
}
impl PartialOrd for WidestFrontier {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
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 widest_path_widths(
graph: &Graph,
source: VertexId,
weights: &[f64],
) -> IgraphResult<Vec<Option<f64>>> {
widest_path_widths_with_mode(graph, source, weights, DijkstraMode::Out)
}
pub fn widest_path_widths_with_mode(
graph: &Graph,
source: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Vec<Option<f64>>> {
let (widths, _) = widest_inner(graph, source, weights, mode)?;
Ok(widths
.into_iter()
.map(|w| {
if w == f64::NEG_INFINITY {
None
} else {
Some(w)
}
})
.collect())
}
fn widest_inner(
graph: &Graph,
source: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<(Vec<f64>, Vec<Option<EdgeId>>)> {
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 widths: Vec<f64> = vec![f64::NEG_INFINITY; n_usize];
widths[source as usize] = f64::INFINITY;
let mut parent_eid: Vec<Option<EdgeId>> = vec![None; n_usize];
let mut heap: BinaryHeap<WidestFrontier> = BinaryHeap::new();
heap.push(WidestFrontier(f64::INFINITY, source));
while let Some(WidestFrontier(w, v)) = heap.pop() {
if w < widths[v as usize] {
continue;
}
let incidents = incident_for_mode(graph, v, mode)?;
for eid in incidents {
let edge_w = weights[eid as usize];
if edge_w == f64::NEG_INFINITY {
continue;
}
let other = graph.edge_other(eid, v)?;
let alt = w.min(edge_w);
if alt > widths[other as usize] {
widths[other as usize] = alt;
parent_eid[other as usize] = Some(eid);
heap.push(WidestFrontier(alt, other));
}
}
}
Ok((widths, parent_eid))
}
pub fn widest_path(
graph: &Graph,
from: VertexId,
to: VertexId,
weights: &[f64],
) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
widest_path_with_mode(graph, from, to, weights, DijkstraMode::Out)
}
#[derive(Debug, Clone, PartialEq)]
pub struct WidestPaths {
pub widths: Vec<Option<f64>>,
pub parents: Vec<Option<VertexId>>,
pub inbound_edges: Vec<Option<EdgeId>>,
}
pub fn widest_paths(graph: &Graph, from: VertexId, weights: &[f64]) -> IgraphResult<WidestPaths> {
widest_paths_with_mode(graph, from, weights, DijkstraMode::Out)
}
pub fn widest_paths_with_mode(
graph: &Graph,
from: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<WidestPaths> {
let (raw_widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
let n = raw_widths.len();
let mut parents: Vec<Option<VertexId>> = vec![None; n];
let widths: Vec<Option<f64>> = raw_widths
.iter()
.map(|&w| {
if w == f64::NEG_INFINITY {
None
} else {
Some(w)
}
})
.collect();
for v in 0..n {
if let Some(eid) = parent_eid[v] {
let v_u32 = u32::try_from(v)
.map_err(|_| IgraphError::Internal("vertex index exceeds u32::MAX"))?;
let prev = graph.edge_other(eid, v_u32)?;
parents[v] = Some(prev);
}
}
Ok(WidestPaths {
widths,
parents,
inbound_edges: parent_eid,
})
}
pub fn widest_path_with_mode(
graph: &Graph,
from: VertexId,
to: VertexId,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
let n = graph.vcount();
if to >= n {
return Err(IgraphError::VertexOutOfRange { id: to, n });
}
let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
reconstruct_one(graph, from, to, &widths, &parent_eid)
}
fn reconstruct_one(
graph: &Graph,
from: VertexId,
to: VertexId,
widths: &[f64],
parent_eid: &[Option<EdgeId>],
) -> IgraphResult<Option<(Vec<VertexId>, Vec<EdgeId>)>> {
if from == to {
return Ok(Some((vec![from], Vec::new())));
}
if widths[to as usize] == f64::NEG_INFINITY {
return Ok(None);
}
let mut edges: Vec<EdgeId> = Vec::new();
let mut vertices: Vec<VertexId> = vec![to];
let mut cur = to;
while cur != from {
let eid = parent_eid[cur as usize].ok_or(IgraphError::Internal(
"missing parent edge while walking widest path",
))?;
let prev = graph.edge_other(eid, cur)?;
edges.push(eid);
vertices.push(prev);
cur = prev;
}
vertices.reverse();
edges.reverse();
Ok(Some((vertices, edges)))
}
pub type WidestPathResult = Option<(Vec<VertexId>, Vec<EdgeId>)>;
pub fn widest_paths_to(
graph: &Graph,
from: VertexId,
targets: &[VertexId],
weights: &[f64],
) -> IgraphResult<Vec<WidestPathResult>> {
widest_paths_to_with_mode(graph, from, targets, weights, DijkstraMode::Out)
}
pub fn widest_paths_to_with_mode(
graph: &Graph,
from: VertexId,
targets: &[VertexId],
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Vec<WidestPathResult>> {
let n = graph.vcount();
for &t in targets {
if t >= n {
return Err(IgraphError::VertexOutOfRange { id: t, n });
}
}
let (widths, parent_eid) = widest_inner(graph, from, weights, mode)?;
let mut out = Vec::with_capacity(targets.len());
for &t in targets {
out.push(reconstruct_one(graph, from, t, &widths, &parent_eid)?);
}
Ok(out)
}
pub fn widest_path_widths_floyd_warshall(
graph: &Graph,
weights: &[f64],
) -> IgraphResult<Vec<Vec<Option<f64>>>> {
widest_path_widths_floyd_warshall_with_mode(graph, weights, DijkstraMode::Out)
}
pub fn widest_path_widths_floyd_warshall_with_mode(
graph: &Graph,
weights: &[f64],
mode: DijkstraMode,
) -> IgraphResult<Vec<Vec<Option<f64>>>> {
validate_weights(graph, weights)?;
let vcount = graph.vcount();
let n_us = vcount as usize;
let effective_mode = if graph.is_directed() {
mode
} else {
DijkstraMode::All
};
let (use_out, use_in) = match effective_mode {
DijkstraMode::Out => (true, false),
DijkstraMode::In => (false, true),
DijkstraMode::All => (true, true),
};
let mut mat: Vec<Vec<f64>> = vec![vec![f64::NEG_INFINITY; n_us]; n_us];
for (i, row) in mat.iter_mut().enumerate().take(n_us) {
row[i] = f64::INFINITY;
}
for (e, &w) in weights.iter().enumerate() {
if w == f64::NEG_INFINITY {
continue;
}
let eid = u32::try_from(e)
.map_err(|_| IgraphError::Internal("edge id exceeds u32::MAX in FW widest"))?;
let (s, t) = graph.edge(eid)?;
let (si, ti) = (s as usize, t as usize);
if use_out && mat[si][ti] < w {
mat[si][ti] = w;
}
if use_in && mat[ti][si] < w {
mat[ti][si] = w;
}
}
#[allow(clippy::needless_range_loop)]
for k in 0..n_us {
for j in 0..n_us {
let width_kj = mat[k][j];
if j == k || width_kj == f64::NEG_INFINITY {
continue;
}
for i in 0..n_us {
if i == j || i == k {
continue;
}
let alt = mat[i][k].min(width_kj);
if alt > mat[i][j] {
mat[i][j] = alt;
}
}
}
}
Ok(mat
.into_iter()
.map(|row| {
row.into_iter()
.map(|w| {
if w == f64::NEG_INFINITY {
None
} else {
Some(w)
}
})
.collect()
})
.collect())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn triangle_picks_direct_edge_when_wider() {
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 w = widest_path_widths(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(2.0));
assert_eq!(w[2], Some(4.0));
}
#[test]
fn chain_bottleneck_is_minimum_weight() {
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();
let w = widest_path_widths(&g, 0, &[5.0, 1.0, 3.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(5.0));
assert_eq!(w[2], Some(1.0));
assert_eq!(w[3], Some(1.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 w = widest_path_widths(&g, 0, &[2.0, 3.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(2.0));
assert_eq!(w[2], None);
assert_eq!(w[3], None);
}
#[test]
fn negative_infinity_edge_ignored() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
let w = widest_path_widths(&g, 0, &[f64::NEG_INFINITY, 1.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], None); assert_eq!(w[2], None);
}
#[test]
fn directed_out_mode_default() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let w = widest_path_widths(&g, 0, &[5.0, 3.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(5.0));
assert_eq!(w[2], Some(3.0));
}
#[test]
fn 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 = widest_path_widths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
assert_eq!(w[0], Some(3.0));
assert_eq!(w[1], Some(3.0));
assert_eq!(w[2], Some(f64::INFINITY));
}
#[test]
fn source_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = widest_path_widths(&g, 99, &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn nan_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = widest_path_widths(&g, 0, &[f64::NAN]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn weights_size_mismatch_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = widest_path_widths(&g, 0, &[1.0, 2.0]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn empty_graph_no_edges() {
let g = Graph::with_vertices(3);
let w = widest_path_widths(&g, 0, &[]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], None);
assert_eq!(w[2], None);
}
#[test]
fn negative_weights_allowed_as_bottleneck() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let w = widest_path_widths(&g, 0, &[-1.0, 5.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(-1.0));
assert_eq!(w[2], Some(-1.0));
}
#[test]
fn multiple_parallel_edges_pick_widest() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); let w = widest_path_widths(&g, 0, &[1.0, 5.0, 3.0]).unwrap();
assert_eq!(w[0], Some(f64::INFINITY));
assert_eq!(w[1], Some(5.0));
}
#[test]
fn widest_path_triangle_via_shortcut() {
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 (vs, es) = widest_path(&g, 0, 1, &[1.0, 4.0, 2.0])
.unwrap()
.expect("0→1 is reachable");
assert_eq!(vs, vec![0, 2, 1]);
assert_eq!(es, vec![1, 2]);
}
#[test]
fn widest_path_direct_edge_when_widest() {
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 (vs, es) = widest_path(&g, 0, 2, &[1.0, 4.0, 2.0])
.unwrap()
.expect("0→2 reachable");
assert_eq!(vs, vec![0, 2]);
assert_eq!(es, vec![1]);
}
#[test]
fn widest_path_self_target_returns_trivial() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let (vs, es) = widest_path(&g, 0, 0, &[5.0]).unwrap().unwrap();
assert_eq!(vs, vec![0]);
assert!(es.is_empty());
}
#[test]
fn widest_path_unreachable_target_returns_none() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let result = widest_path(&g, 0, 2, &[1.0, 1.0]).unwrap();
assert!(result.is_none());
}
#[test]
fn widest_path_chain_returns_full_chain() {
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();
let (vs, es) = widest_path(&g, 0, 3, &[1.0, 1.0, 1.0]).unwrap().unwrap();
assert_eq!(vs, vec![0, 1, 2, 3]);
assert_eq!(es, vec![0, 1, 2]);
}
#[test]
fn widest_path_directed_respects_direction() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let (vs, _) = widest_path(&g, 0, 2, &[5.0, 3.0]).unwrap().unwrap();
assert_eq!(vs, vec![0, 1, 2]);
assert!(widest_path(&g, 2, 0, &[5.0, 3.0]).unwrap().is_none());
let (vs, _) = widest_path_with_mode(&g, 2, 0, &[5.0, 3.0], DijkstraMode::In)
.unwrap()
.unwrap();
assert_eq!(vs, vec![2, 1, 0]);
}
#[test]
fn widest_path_from_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = widest_path(&g, 99, 0, &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn widest_path_to_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = widest_path(&g, 0, 99, &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn widest_path_negative_infinity_edge_breaks_chain() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let r = widest_path(&g, 0, 2, &[f64::NEG_INFINITY, 1.0]).unwrap();
assert!(r.is_none());
}
#[test]
fn fw_widest_triangle_matches_dijkstra_per_source() {
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 fw = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
for u in 0..3u32 {
let dij = widest_path_widths(&g, u, &weights).unwrap();
assert_eq!(fw[u as usize], dij, "row {u} mismatch");
}
}
#[test]
fn fw_widest_diagonal_is_infinity() {
let g = Graph::with_vertices(3);
let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
for (i, row) in m.iter().enumerate() {
for (j, entry) in row.iter().enumerate() {
if i == j {
assert_eq!(*entry, Some(f64::INFINITY));
} else {
assert_eq!(*entry, None);
}
}
}
}
#[test]
fn fw_widest_unreachable_components() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let m = widest_path_widths_floyd_warshall(&g, &[5.0, 7.0]).unwrap();
assert_eq!(m[0][1], Some(5.0));
assert_eq!(m[0][2], None);
assert_eq!(m[2][3], Some(7.0));
assert_eq!(m[1][3], None);
}
#[test]
fn fw_widest_directed_respects_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let weights = [5.0, 3.0];
let out = widest_path_widths_floyd_warshall(&g, &weights).unwrap();
assert_eq!(out[0][1], Some(5.0));
assert_eq!(out[0][2], Some(3.0));
assert_eq!(out[2][0], None);
let in_m =
widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::In).unwrap();
assert_eq!(in_m[0][1], None);
assert_eq!(in_m[2][0], Some(3.0));
let all =
widest_path_widths_floyd_warshall_with_mode(&g, &weights, DijkstraMode::All).unwrap();
assert_eq!(all[0][2], Some(3.0));
assert_eq!(all[2][0], Some(3.0));
}
#[test]
fn fw_widest_parallel_edges_keep_widest() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(0, 1).unwrap(); let m = widest_path_widths_floyd_warshall(&g, &[1.0, 5.0, 3.0]).unwrap();
assert_eq!(m[0][1], Some(5.0));
assert_eq!(m[1][0], Some(5.0));
}
#[test]
fn fw_widest_negative_infinity_edge_ignored() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
let m = widest_path_widths_floyd_warshall(&g, &[f64::NEG_INFINITY, 1.0]).unwrap();
assert_eq!(m[0][1], None);
assert_eq!(m[0][2], None);
assert_eq!(m[1][2], Some(1.0));
}
#[test]
fn fw_widest_nan_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = widest_path_widths_floyd_warshall(&g, &[f64::NAN]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn fw_widest_empty_graph_empty_matrix() {
let g = Graph::with_vertices(0);
let m = widest_path_widths_floyd_warshall(&g, &[]).unwrap();
assert!(m.is_empty());
}
#[test]
fn widest_paths_to_triangle_two_targets() {
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 paths = widest_paths_to(&g, 0, &[1, 2], &[1.0, 4.0, 2.0]).unwrap();
assert_eq!(paths.len(), 2);
let (vs1, es1) = paths[0].as_ref().unwrap();
assert_eq!(vs1, &vec![0, 2, 1]);
assert_eq!(es1, &vec![1, 2]);
let (vs2, es2) = paths[1].as_ref().unwrap();
assert_eq!(vs2, &vec![0, 2]);
assert_eq!(es2, &vec![1]);
}
#[test]
fn widest_paths_to_includes_unreachable_as_none() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let paths = widest_paths_to(&g, 0, &[1, 2, 3], &[1.0, 1.0]).unwrap();
assert!(paths[0].is_some());
assert!(paths[1].is_none());
assert!(paths[2].is_none());
}
#[test]
fn widest_paths_to_empty_targets_returns_empty() {
let g = Graph::with_vertices(3);
let paths = widest_paths_to(&g, 0, &[], &[]).unwrap();
assert!(paths.is_empty());
}
#[test]
fn widest_paths_to_self_target_is_trivial() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
let paths = widest_paths_to(&g, 1, &[1, 0], &[5.0]).unwrap();
let (vs0, es0) = paths[0].as_ref().unwrap();
assert_eq!(vs0, &vec![1]);
assert!(es0.is_empty());
let (vs1, es1) = paths[1].as_ref().unwrap();
assert_eq!(vs1, &vec![1, 0]);
assert_eq!(es1, &vec![0]);
}
#[test]
fn widest_paths_to_duplicate_targets_return_same_path() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let paths = widest_paths_to(&g, 0, &[2, 2, 2], &[5.0, 3.0]).unwrap();
assert_eq!(paths.len(), 3);
for p in &paths {
let (vs, _) = p.as_ref().unwrap();
assert_eq!(vs, &vec![0, 1, 2]);
}
}
#[test]
fn widest_paths_to_target_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = widest_paths_to(&g, 0, &[1, 99], &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn widest_paths_to_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 paths =
widest_paths_to_with_mode(&g, 2, &[1, 0], &[5.0, 3.0], DijkstraMode::In).unwrap();
let (vs1, _) = paths[0].as_ref().unwrap();
assert_eq!(vs1, &vec![2, 1]);
let (vs0, _) = paths[1].as_ref().unwrap();
assert_eq!(vs0, &vec![2, 1, 0]);
}
#[test]
fn widest_paths_to_negative_infinity_edge_blocks_target() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap();
let paths = widest_paths_to(&g, 0, &[1, 2], &[f64::NEG_INFINITY, 1.0]).unwrap();
assert!(paths[0].is_none());
assert!(paths[1].is_none());
}
#[test]
fn widest_paths_triangle_struct_fields_consistent() {
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 sp = widest_paths(&g, 0, &[1.0, 4.0, 2.0]).unwrap();
assert_eq!(sp.widths[0], Some(f64::INFINITY));
assert_eq!(sp.parents[0], None);
assert_eq!(sp.inbound_edges[0], None);
assert_eq!(sp.widths[2], Some(4.0));
assert_eq!(sp.parents[2], Some(0));
assert_eq!(sp.inbound_edges[2], Some(1));
assert_eq!(sp.widths[1], Some(2.0));
assert_eq!(sp.parents[1], Some(2));
assert_eq!(sp.inbound_edges[1], Some(2));
}
#[test]
fn widest_paths_widths_match_widest_path_widths() {
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();
let weights = [5.0, 1.0, 3.0];
let sp = widest_paths(&g, 0, &weights).unwrap();
let standalone = widest_path_widths(&g, 0, &weights).unwrap();
assert_eq!(sp.widths, standalone);
}
#[test]
fn widest_paths_unreachable_has_none_in_all_three_fields() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let sp = widest_paths(&g, 0, &[5.0, 7.0]).unwrap();
assert_eq!(sp.widths[2], None);
assert_eq!(sp.parents[2], None);
assert_eq!(sp.inbound_edges[2], None);
assert_eq!(sp.parents[1], Some(0));
assert_eq!(sp.inbound_edges[1], Some(0));
}
#[test]
fn widest_paths_directed_in_mode() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let sp = widest_paths_with_mode(&g, 2, &[5.0, 3.0], DijkstraMode::In).unwrap();
assert_eq!(sp.widths[2], Some(f64::INFINITY));
assert_eq!(sp.widths[1], Some(3.0));
assert_eq!(sp.widths[0], Some(3.0));
assert_eq!(sp.parents[1], Some(2));
assert_eq!(sp.parents[0], Some(1));
}
#[test]
fn widest_paths_source_out_of_range_errors() {
let g = Graph::with_vertices(3);
let err = widest_paths(&g, 99, &[]).unwrap_err();
assert!(matches!(
err,
IgraphError::VertexOutOfRange { id: 99, n: 3 }
));
}
#[test]
fn widest_paths_nan_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let err = widest_paths(&g, 0, &[f64::NAN]).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn widest_paths_spt_endpoints_match_widest_path_chain() {
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();
let weights = [5.0, 1.0, 3.0];
let sp = widest_paths(&g, 0, &weights).unwrap();
let path = widest_path(&g, 0, 3, &weights).unwrap().unwrap();
let mut reconstructed: Vec<u32> = vec![3];
let mut cur = 3usize;
while let Some(prev) = sp.parents[cur] {
reconstructed.push(prev);
cur = prev as usize;
}
reconstructed.reverse();
assert_eq!(reconstructed, path.0);
}
}