1use std::collections::VecDeque;
22
23use crate::core::graph::EdgeId;
24use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
25
26use super::bellman_ford::bellman_ford_path_to_with_mode;
27use super::dijkstra::{DijkstraMode, dijkstra_path_to_with_mode};
28
29fn incident_for_mode(graph: &Graph, v: VertexId, mode: DijkstraMode) -> IgraphResult<Vec<EdgeId>> {
33 if !graph.is_directed() {
34 return graph.incident(v);
35 }
36 match mode {
37 DijkstraMode::Out => graph.incident(v),
38 DijkstraMode::In => graph.incident_in(v),
39 DijkstraMode::All => {
40 let mut out = graph.incident(v)?;
41 out.extend(graph.incident_in(v)?);
42 Ok(out)
43 }
44 }
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct ShortestPath {
57 pub vertices: Vec<VertexId>,
59 pub edges: Vec<EdgeId>,
61}
62
63fn bfs_single_path(
72 graph: &Graph,
73 from: VertexId,
74 to: VertexId,
75 mode: DijkstraMode,
76) -> IgraphResult<ShortestPath> {
77 let n = graph.vcount() as usize;
78 let mut parent_eids: Vec<i64> = vec![0; n];
79 parent_eids[to as usize] = -1;
80
81 let to_reach = 1_i64;
82 let mut reached = 0_i64;
83
84 let mut q: VecDeque<VertexId> = VecDeque::new();
85 q.push_back(from);
86 if parent_eids[from as usize] < 0 {
87 reached += 1; }
89 parent_eids[from as usize] = 1;
90
91 while reached < to_reach {
92 let Some(act) = q.pop_front() else { break };
93 for edge in incident_for_mode(graph, act, mode)? {
94 let neighbor = graph.edge_other(edge, act)?;
95 let pe = parent_eids[neighbor as usize];
96 if pe <= 0 {
98 if pe < 0 {
99 reached += 1; }
101 parent_eids[neighbor as usize] = i64::from(edge) + 2;
103 q.push_back(neighbor);
104 }
105 }
106 }
107
108 if parent_eids[to as usize] <= 0 {
111 return Ok(ShortestPath {
113 vertices: Vec::new(),
114 edges: Vec::new(),
115 });
116 }
117
118 let mut size = 0_usize;
119 let mut act = to;
120 while parent_eids[act as usize] > 1 {
121 size += 1;
122 let edge = decode_edge(parent_eids[act as usize])?;
123 act = graph.edge_other(edge, act)?;
124 }
125
126 let mut vertices = vec![0 as VertexId; size + 1];
127 let mut edges = vec![0 as EdgeId; size];
128 vertices[size] = to;
129
130 let mut idx = size;
131 let mut act = to;
132 while parent_eids[act as usize] > 1 {
133 idx -= 1;
134 let edge = decode_edge(parent_eids[act as usize])?;
135 act = graph.edge_other(edge, act)?;
136 vertices[idx] = act;
137 edges[idx] = edge;
138 }
139
140 Ok(ShortestPath { vertices, edges })
141}
142
143fn decode_edge(parent_eid: i64) -> IgraphResult<EdgeId> {
145 EdgeId::try_from(parent_eid - 2)
146 .map_err(|_| IgraphError::Internal("get_shortest_path: edge id overflow"))
147}
148
149pub fn get_shortest_path(
191 graph: &Graph,
192 from: VertexId,
193 to: VertexId,
194 weights: Option<&[f64]>,
195 mode: DijkstraMode,
196) -> IgraphResult<ShortestPath> {
197 let n = graph.vcount();
198 if from >= n {
199 return Err(IgraphError::VertexOutOfRange { id: from, n });
200 }
201 if to >= n {
202 return Err(IgraphError::VertexOutOfRange { id: to, n });
203 }
204
205 let Some(w) = weights else {
206 return bfs_single_path(graph, from, to, mode);
207 };
208
209 let m = graph.ecount();
210 if w.len() != m {
211 return Err(IgraphError::InvalidArgument(format!(
212 "get_shortest_path: weights length {} != edge count {m}",
213 w.len()
214 )));
215 }
216 let mut has_negative = false;
217 for (e, &x) in w.iter().enumerate() {
218 if x.is_nan() {
219 return Err(IgraphError::InvalidArgument(format!(
220 "get_shortest_path: weight at edge {e} is NaN"
221 )));
222 }
223 if x < 0.0 {
224 has_negative = true;
225 }
226 }
227
228 let result = if has_negative {
229 bellman_ford_path_to_with_mode(graph, from, to, w, mode)?
230 } else {
231 dijkstra_path_to_with_mode(graph, from, to, w, mode)?
232 };
233
234 Ok(match result {
235 Some((vertices, edges)) => ShortestPath { vertices, edges },
236 None => ShortestPath {
237 vertices: Vec::new(),
238 edges: Vec::new(),
239 },
240 })
241}
242
243#[cfg(test)]
244#[allow(clippy::float_cmp)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn from_equals_to_unweighted() {
250 let mut g = Graph::new(3, false).unwrap();
251 g.add_edge(0, 1).unwrap();
252 let p = get_shortest_path(&g, 1, 1, None, DijkstraMode::Out).unwrap();
253 assert_eq!(p.vertices, vec![1]);
254 assert!(p.edges.is_empty());
255 }
256
257 #[test]
258 fn simple_path_unweighted_undirected() {
259 let mut g = Graph::new(4, false).unwrap();
260 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 3).unwrap(); let p = get_shortest_path(&g, 0, 3, None, DijkstraMode::Out).unwrap();
264 assert_eq!(p.vertices, vec![0, 1, 2, 3]);
265 assert_eq!(p.edges, vec![0, 1, 2]);
266 }
267
268 #[test]
269 fn picks_shorter_of_two_routes() {
270 let mut g = Graph::new(4, false).unwrap();
272 g.add_edge(0, 1).unwrap(); g.add_edge(1, 3).unwrap(); g.add_edge(0, 3).unwrap(); let p = get_shortest_path(&g, 0, 3, None, DijkstraMode::Out).unwrap();
276 assert_eq!(p.vertices, vec![0, 3]);
277 assert_eq!(p.edges, vec![2]);
278 }
279
280 #[test]
281 fn unreachable_returns_empty() {
282 let mut g = Graph::new(3, true).unwrap();
283 g.add_edge(0, 1).unwrap();
284 let p = get_shortest_path(&g, 0, 2, None, DijkstraMode::Out).unwrap();
285 assert!(p.vertices.is_empty());
286 assert!(p.edges.is_empty());
287 }
288
289 #[test]
290 fn directed_mode_in_follows_reverse() {
291 let mut g = Graph::new(3, true).unwrap();
292 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); let out = get_shortest_path(&g, 0, 2, None, DijkstraMode::Out).unwrap();
296 assert_eq!(out.vertices, vec![0, 1, 2]);
297 let none = get_shortest_path(&g, 2, 0, None, DijkstraMode::Out).unwrap();
299 assert!(none.vertices.is_empty());
300 let inp = get_shortest_path(&g, 2, 0, None, DijkstraMode::In).unwrap();
302 assert_eq!(inp.vertices, vec![2, 1, 0]);
303 assert_eq!(inp.edges, vec![1, 0]);
304 }
305
306 #[test]
307 fn directed_mode_all_ignores_direction() {
308 let mut g = Graph::new(3, true).unwrap();
309 g.add_edge(0, 1).unwrap();
310 g.add_edge(1, 2).unwrap();
311 let p = get_shortest_path(&g, 2, 0, None, DijkstraMode::All).unwrap();
312 assert_eq!(p.vertices, vec![2, 1, 0]);
313 }
314
315 #[test]
316 fn weighted_dijkstra_prefers_cheaper_route() {
317 let mut g = Graph::new(4, false).unwrap();
318 g.add_edge(0, 1).unwrap(); g.add_edge(1, 3).unwrap(); g.add_edge(0, 3).unwrap(); let w = vec![1.0, 1.0, 10.0];
322 let p = get_shortest_path(&g, 0, 3, Some(&w), DijkstraMode::Out).unwrap();
323 assert_eq!(p.vertices, vec![0, 1, 3]);
324 assert_eq!(p.edges, vec![0, 1]);
325 }
326
327 #[test]
328 fn weighted_negative_uses_bellman_ford() {
329 let mut g = Graph::new(3, true).unwrap();
330 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 2).unwrap(); let w = vec![1.0, -5.0, 1.0];
335 let p = get_shortest_path(&g, 0, 2, Some(&w), DijkstraMode::Out).unwrap();
336 assert_eq!(p.vertices, vec![0, 1, 2]);
337 assert_eq!(p.edges, vec![0, 1]);
338 }
339
340 #[test]
341 fn weighted_unreachable_returns_empty() {
342 let mut g = Graph::new(3, true).unwrap();
343 g.add_edge(0, 1).unwrap();
344 let w = vec![1.0];
345 let p = get_shortest_path(&g, 0, 2, Some(&w), DijkstraMode::Out).unwrap();
346 assert!(p.vertices.is_empty());
347 assert!(p.edges.is_empty());
348 }
349
350 #[test]
351 fn invalid_source_errors() {
352 let g = Graph::new(2, false).unwrap();
353 assert!(get_shortest_path(&g, 5, 0, None, DijkstraMode::Out).is_err());
354 assert!(get_shortest_path(&g, 0, 5, None, DijkstraMode::Out).is_err());
355 }
356
357 #[test]
358 fn weights_length_mismatch_errors() {
359 let mut g = Graph::new(2, false).unwrap();
360 g.add_edge(0, 1).unwrap();
361 let w = vec![1.0, 2.0];
362 assert!(get_shortest_path(&g, 0, 1, Some(&w), DijkstraMode::Out).is_err());
363 }
364
365 #[test]
366 fn weights_nan_errors() {
367 let mut g = Graph::new(2, false).unwrap();
368 g.add_edge(0, 1).unwrap();
369 let w = vec![f64::NAN];
370 assert!(get_shortest_path(&g, 0, 1, Some(&w), DijkstraMode::Out).is_err());
371 }
372}
373
374#[cfg(all(test, feature = "proptest-harness"))]
375mod proptests {
376 use super::*;
377 use crate::create;
378 use proptest::prelude::*;
379
380 fn arb_graph(max_v: u32) -> impl Strategy<Value = Graph> {
381 (2..=max_v).prop_flat_map(|n| {
382 let max_e = (n as usize)
383 .saturating_mul(n.saturating_sub(1) as usize)
384 .min(20);
385 proptest::collection::vec((0..n, 0..n), 0..=max_e).prop_map(move |edges| {
386 let edge_tuples: Vec<(u32, u32)> = edges.into_iter().collect();
387 create(&edge_tuples, n, false).expect("arb graph")
388 })
389 })
390 }
391
392 proptest! {
393 #[test]
397 fn path_is_a_valid_simple_walk(
398 g in arb_graph(6),
399 from in 0u32..6,
400 to in 0u32..6,
401 ) {
402 let n = g.vcount();
403 prop_assume!(from < n && to < n);
404 let p = get_shortest_path(&g, from, to, None, DijkstraMode::All).expect("ok");
405 if p.vertices.is_empty() {
406 return Ok(());
407 }
408 prop_assert_eq!(p.vertices[0], from);
409 prop_assert_eq!(*p.vertices.last().expect("non-empty"), to);
410 prop_assert_eq!(p.edges.len() + 1, p.vertices.len());
411
412 let mut seen = vec![false; n as usize];
414 for &v in &p.vertices {
415 prop_assert!(!seen[v as usize], "vertex {} repeats", v);
416 seen[v as usize] = true;
417 }
418
419 for (i, &e) in p.edges.iter().enumerate() {
421 let (a, b) = g.edge(e).expect("edge id valid");
422 let (u, v) = (p.vertices[i], p.vertices[i + 1]);
423 prop_assert!(
424 (a == u && b == v) || (a == v && b == u),
425 "edge {} = ({},{}) does not join {} and {}",
426 e, a, b, u, v
427 );
428 }
429 }
430
431 #[test]
434 fn bfs_and_unit_dijkstra_agree_on_length(
435 g in arb_graph(6),
436 from in 0u32..6,
437 to in 0u32..6,
438 ) {
439 let n = g.vcount();
440 prop_assume!(from < n && to < n);
441 let unweighted = get_shortest_path(&g, from, to, None, DijkstraMode::All)
442 .expect("ok");
443 let ones = vec![1.0_f64; g.ecount()];
444 let weighted = get_shortest_path(&g, from, to, Some(&ones), DijkstraMode::All)
445 .expect("ok");
446 prop_assert_eq!(unweighted.vertices.is_empty(), weighted.vertices.is_empty());
447 prop_assert_eq!(unweighted.edges.len(), weighted.edges.len());
448 }
449 }
450}