1use crate::error::GraphError;
5use crate::graph::Graph;
6use core::cmp::Reverse;
7use sim_lib_discrete_algebra::{AlgebraLimits, BoolRing, Matrix, MinPlus};
8use std::collections::BinaryHeap;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct PathResult<W> {
13 pub distances: Vec<Option<W>>,
15 pub predecessors: Vec<Option<usize>>,
17}
18
19fn out_arcs<N, W: Clone>(graph: &Graph<N, W>, node: usize) -> Vec<(usize, W)> {
21 let undirected = !graph.is_directed();
22 let mut arcs = Vec::new();
23 for e in &graph.edges {
24 if e.source == node {
25 arcs.push((e.target, e.weight.clone()));
26 } else if undirected && e.target == node {
27 arcs.push((e.source, e.weight.clone()));
28 }
29 }
30 arcs
31}
32
33pub fn dijkstra<N>(graph: &Graph<N, u64>, source: usize) -> Result<PathResult<u64>, GraphError> {
53 graph.validate()?;
54 let n = graph.node_count();
55 if source >= n {
56 return Err(GraphError::NodeOutOfRange {
57 node: source,
58 count: n,
59 });
60 }
61 let mut dist = vec![None; n];
62 let mut pred = vec![None; n];
63 let mut heap: BinaryHeap<Reverse<(u64, usize)>> = BinaryHeap::new();
64 dist[source] = Some(0);
65 heap.push(Reverse((0, source)));
66 while let Some(Reverse((d, u))) = heap.pop() {
67 if dist[u].is_some_and(|best| d > best) {
68 continue;
69 }
70 for (v, w) in out_arcs(graph, u) {
71 let Some(nd) = d.checked_add(w) else {
74 continue;
75 };
76 if dist[v].is_none_or(|best| nd < best) {
77 dist[v] = Some(nd);
78 pred[v] = Some(u);
79 heap.push(Reverse((nd, v)));
80 }
81 }
82 }
83 Ok(PathResult {
84 distances: dist,
85 predecessors: pred,
86 })
87}
88
89pub fn bellman_ford<N>(
92 graph: &Graph<N, i64>,
93 source: usize,
94) -> Result<(PathResult<i64>, bool), GraphError> {
95 graph.validate()?;
96 let n = graph.node_count();
97 if source >= n {
98 return Err(GraphError::NodeOutOfRange {
99 node: source,
100 count: n,
101 });
102 }
103 let undirected = !graph.is_directed();
104 let mut arcs: Vec<(usize, usize, i64)> = Vec::new();
106 for e in &graph.edges {
107 arcs.push((e.source, e.target, e.weight));
108 if undirected {
109 arcs.push((e.target, e.source, e.weight));
110 }
111 }
112 let mut dist: Vec<Option<i64>> = vec![None; n];
113 let mut pred = vec![None; n];
114 dist[source] = Some(0);
115 for _ in 0..n.saturating_sub(1) {
116 let mut changed = false;
117 for &(a, b, w) in &arcs {
118 if let Some(da) = dist[a] {
119 let Some(nd) = da.checked_add(w) else {
122 continue;
123 };
124 if dist[b].is_none_or(|best| nd < best) {
125 dist[b] = Some(nd);
126 pred[b] = Some(a);
127 changed = true;
128 }
129 }
130 }
131 if !changed {
132 break;
133 }
134 }
135 let mut negative_cycle = false;
136 for &(a, b, w) in &arcs {
137 if let Some(da) = dist[a]
138 && let Some(nd) = da.checked_add(w)
139 && dist[b].is_none_or(|best| nd < best)
140 {
141 negative_cycle = true;
142 break;
143 }
144 }
145 Ok((
146 PathResult {
147 distances: dist,
148 predecessors: pred,
149 },
150 negative_cycle,
151 ))
152}
153
154pub fn all_pairs_shortest_paths<N>(graph: &Graph<N, i64>) -> Result<Matrix<MinPlus>, GraphError> {
157 graph.validate()?;
158 let n = graph.node_count();
159 let undirected = !graph.is_directed();
160 let mut m = Matrix::filled(n, n, MinPlus::Inf);
161 for e in &graph.edges {
162 m.data[e.source * n + e.target] = min_plus_add(m.data[e.source * n + e.target], e.weight);
164 if undirected {
165 m.data[e.target * n + e.source] =
166 min_plus_add(m.data[e.target * n + e.source], e.weight);
167 }
168 }
169 Ok(m.closure(AlgebraLimits::default())?)
170}
171
172fn min_plus_add(cur: MinPlus, w: i64) -> MinPlus {
173 use sim_lib_discrete_algebra::Semiring;
174 cur.add(&MinPlus::Fin(w))
175}
176
177pub fn reachability<N, W>(graph: &Graph<N, W>) -> Result<Matrix<BoolRing>, GraphError> {
179 graph.validate()?;
180 let n = graph.node_count();
181 let undirected = !graph.is_directed();
182 let mut m = Matrix::filled(n, n, BoolRing(false));
183 for e in &graph.edges {
184 m.data[e.source * n + e.target] = BoolRing(true);
185 if undirected {
186 m.data[e.target * n + e.source] = BoolRing(true);
187 }
188 }
189 Ok(m.closure(AlgebraLimits::default())?)
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195 use crate::edge::Directedness;
196
197 #[test]
198 fn dijkstra_row_equals_all_pairs_row() {
199 let edges = [(0usize, 1usize, 1u64), (1, 2, 2), (0, 2, 5), (2, 3, 1)];
201 let mut gu: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
202 let mut gi: Graph<u8, i64> = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
203 for &(s, t, w) in &edges {
204 gu.add_edge(s, t, w).unwrap();
205 gi.add_edge(s, t, w as i64).unwrap();
206 }
207 let dj = dijkstra(&gu, 0).unwrap();
208 let ap = all_pairs_shortest_paths(&gi).unwrap();
209 for j in 0..4 {
210 let from_closure = match ap.data[j] {
211 MinPlus::Fin(d) => Some(d as u64),
212 MinPlus::Inf => None,
213 };
214 assert_eq!(dj.distances[j], from_closure, "node {j}");
215 }
216 }
217
218 #[test]
219 fn bellman_ford_handles_negative_edge_without_cycle() {
220 let mut g: Graph<u8, i64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
221 g.add_edge(0, 1, 4).unwrap();
222 g.add_edge(0, 2, 5).unwrap();
223 g.add_edge(2, 1, -3).unwrap(); let (res, neg) = bellman_ford(&g, 0).unwrap();
225 assert!(!neg);
226 assert_eq!(res.distances[1], Some(2));
227 }
228
229 #[test]
230 fn bellman_ford_detects_negative_cycle() {
231 let mut g: Graph<u8, i64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
232 g.add_edge(0, 1, 1).unwrap();
233 g.add_edge(1, 0, -2).unwrap(); let (_res, neg) = bellman_ford(&g, 0).unwrap();
235 assert!(neg);
236 }
237
238 #[test]
239 fn near_max_weights_do_not_wrap_distance() {
240 let mut gu: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
242 gu.add_edge(0, 1, u64::MAX - 1).unwrap();
243 gu.add_edge(1, 2, u64::MAX - 1).unwrap();
244 let dj = dijkstra(&gu, 0).unwrap();
245 assert_eq!(dj.distances[1], Some(u64::MAX - 1));
246 assert_eq!(dj.distances[2], None);
248
249 let mut gi: Graph<u8, i64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
251 gi.add_edge(0, 1, i64::MAX - 1).unwrap();
252 gi.add_edge(1, 2, i64::MAX - 1).unwrap();
253 let (res, neg) = bellman_ford(&gi, 0).unwrap();
254 assert!(!neg);
255 assert_eq!(res.distances[1], Some(i64::MAX - 1));
256 assert_eq!(res.distances[2], None);
257 }
258
259 #[test]
260 fn reachability_is_transitive() {
261 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
262 g.add_edge(0, 1, 1).unwrap();
263 g.add_edge(1, 2, 1).unwrap();
264 let r = reachability(&g).unwrap();
265 assert_eq!(r.data[2], BoolRing(true)); assert_eq!(r.data[6], BoolRing(false)); }
268}