1use std::collections::BinaryHeap;
10
11use crate::core::graph::EdgeId;
12use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
13
14use super::dijkstra::{DijkstraMode, incident_for_mode, validate_weights};
15
16const EPSILON: f64 = 1e-10;
17
18#[derive(PartialEq)]
19struct Frontier(f64, VertexId);
20
21impl Eq for Frontier {}
22
23impl Ord for Frontier {
24 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
25 other.0.total_cmp(&self.0).then(other.1.cmp(&self.1))
26 }
27}
28
29impl PartialOrd for Frontier {
30 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
31 Some(self.cmp(other))
32 }
33}
34
35#[derive(Debug, Clone)]
37pub struct AllShortestPathsDijkstra {
38 pub paths: Vec<Vec<Vec<VertexId>>>,
43 pub edge_paths: Vec<Vec<Vec<EdgeId>>>,
46 pub nrgeo: Vec<u64>,
48}
49
50pub fn get_all_shortest_paths_dijkstra(
82 graph: &Graph,
83 source: VertexId,
84 weights: &[f64],
85) -> IgraphResult<AllShortestPathsDijkstra> {
86 get_all_shortest_paths_dijkstra_with_mode(graph, source, weights, DijkstraMode::Out)
87}
88
89pub fn get_all_shortest_paths_dijkstra_with_mode(
113 graph: &Graph,
114 source: VertexId,
115 weights: &[f64],
116 mode: DijkstraMode,
117) -> IgraphResult<AllShortestPathsDijkstra> {
118 let n = graph.vcount();
119 if source >= n {
120 return Err(IgraphError::VertexOutOfRange { id: source, n });
121 }
122 validate_weights(graph, weights)?;
123
124 let n_us = n as usize;
125
126 let mut dist = vec![-1.0_f64; n_us];
128 let mut parents: Vec<Vec<(VertexId, EdgeId)>> = vec![Vec::new(); n_us];
130 let mut order: Vec<VertexId> = Vec::with_capacity(n_us);
132
133 let mut heap: BinaryHeap<Frontier> = BinaryHeap::new();
134 dist[source as usize] = 0.0;
135 heap.push(Frontier(0.0, source));
136
137 let mut settled = vec![false; n_us];
138
139 while let Some(Frontier(d, v)) = heap.pop() {
140 let v_us = v as usize;
141 if settled[v_us] {
142 continue;
143 }
144 settled[v_us] = true;
145 order.push(v);
146
147 for eid in incident_for_mode(graph, v, mode)? {
148 let w = weights[eid as usize];
149 if !w.is_finite() {
150 continue;
151 }
152 let other = graph.edge_other(eid, v)?;
153 let other_us = other as usize;
154 if settled[other_us] {
155 continue;
156 }
157 let altdist = d + w;
158 let curdist = dist[other_us];
159
160 if curdist < 0.0 {
161 dist[other_us] = altdist;
163 parents[other_us].push((v, eid));
164 heap.push(Frontier(altdist, other));
165 } else if (altdist - curdist).abs() < EPSILON && w > 0.0 {
166 parents[other_us].push((v, eid));
169 } else if altdist < curdist - EPSILON {
170 dist[other_us] = altdist;
172 parents[other_us].clear();
173 parents[other_us].push((v, eid));
174 heap.push(Frontier(altdist, other));
175 }
176 }
177 }
178
179 let mut nrgeo = vec![0_u64; n_us];
181 nrgeo[source as usize] = 1;
182 for &v in order.iter().skip(1) {
183 let v_us = v as usize;
184 for &(pv, _) in &parents[v_us] {
185 nrgeo[v_us] = nrgeo[v_us].saturating_add(nrgeo[pv as usize]);
186 }
187 }
188
189 let mut vertex_paths: Vec<Vec<Vec<VertexId>>> = vec![Vec::new(); n_us];
191 let mut edge_seqs: Vec<Vec<Vec<EdgeId>>> = vec![Vec::new(); n_us];
192
193 vertex_paths[source as usize] = vec![vec![source]];
194 edge_seqs[source as usize] = vec![vec![]];
195
196 for &v in order.iter().skip(1) {
197 let v_us = v as usize;
198 let parent_list: Vec<(VertexId, EdgeId)> = parents[v_us].clone();
199
200 for &(pv, pe) in &parent_list {
201 let pv_us = pv as usize;
202 let prev_vertices: Vec<Vec<VertexId>> = vertex_paths[pv_us].clone();
203 let prev_edges: Vec<Vec<EdgeId>> = edge_seqs[pv_us].clone();
204
205 for mut vpath in prev_vertices {
206 vpath.push(v);
207 vertex_paths[v_us].push(vpath);
208 }
209 for mut epath in prev_edges {
210 epath.push(pe);
211 edge_seqs[v_us].push(epath);
212 }
213 }
214 }
215
216 Ok(AllShortestPathsDijkstra {
217 paths: vertex_paths,
218 edge_paths: edge_seqs,
219 nrgeo,
220 })
221}
222
223#[cfg(test)]
224mod tests {
225 use super::*;
226
227 fn diamond() -> Graph {
228 let mut g = Graph::with_vertices(4);
229 g.add_edge(0, 1).expect("e01");
230 g.add_edge(0, 2).expect("e02");
231 g.add_edge(1, 3).expect("e13");
232 g.add_edge(2, 3).expect("e23");
233 g
234 }
235
236 #[test]
237 fn diamond_equal_weights() {
238 let g = diamond();
239 let w = vec![1.0; 4];
240 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
241
242 assert_eq!(r.paths[0], vec![vec![0]]);
243 assert_eq!(r.nrgeo[0], 1);
244 assert_eq!(r.paths[1].len(), 1);
245 assert_eq!(r.paths[2].len(), 1);
246 assert_eq!(r.paths[3].len(), 2);
247 assert_eq!(r.nrgeo[3], 2);
248
249 let mut sorted = r.paths[3].clone();
250 sorted.sort();
251 assert_eq!(sorted, vec![vec![0, 1, 3], vec![0, 2, 3]]);
252 }
253
254 #[test]
255 fn diamond_asymmetric_weights() {
256 let g = diamond();
257 let w = vec![1.0, 10.0, 1.0, 1.0];
260 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
261 assert_eq!(r.paths[3].len(), 1);
262 assert_eq!(r.paths[3][0], vec![0, 1, 3]);
263 assert_eq!(r.nrgeo[3], 1);
264 }
265
266 #[test]
267 fn single_vertex() {
268 let g = Graph::with_vertices(1);
269 let r = get_all_shortest_paths_dijkstra(&g, 0, &[]).expect("ok");
270 assert_eq!(r.paths[0], vec![vec![0]]);
271 assert_eq!(r.nrgeo[0], 1);
272 }
273
274 #[test]
275 fn disconnected_graph() {
276 let mut g = Graph::with_vertices(3);
277 g.add_edge(0, 1).expect("e");
278 let r = get_all_shortest_paths_dijkstra(&g, 0, &[1.0]).expect("ok");
280 assert!(r.paths[2].is_empty());
281 assert_eq!(r.nrgeo[2], 0);
282 }
283
284 #[test]
285 fn directed_out_mode() {
286 let mut g = Graph::new(3, true).expect("g");
287 g.add_edge(0, 1).expect("e01");
288 g.add_edge(1, 2).expect("e12");
289 g.add_edge(2, 0).expect("e20");
290 let w = vec![1.0; 3];
291 let r =
292 get_all_shortest_paths_dijkstra_with_mode(&g, 0, &w, DijkstraMode::Out).expect("ok");
293 assert_eq!(r.paths[2].len(), 1);
294 assert_eq!(r.paths[2][0], vec![0, 1, 2]);
295 }
296
297 #[test]
298 fn directed_in_mode() {
299 let mut g = Graph::new(3, true).expect("g");
300 g.add_edge(0, 1).expect("e01");
301 g.add_edge(1, 2).expect("e12");
302 g.add_edge(2, 0).expect("e20");
303 let w = vec![1.0; 3];
304 let r = get_all_shortest_paths_dijkstra_with_mode(&g, 2, &w, DijkstraMode::In).expect("ok");
305 assert_eq!(r.paths[0].len(), 1);
306 assert_eq!(r.paths[0][0], vec![2, 1, 0]);
307 }
308
309 #[test]
310 fn edge_paths_match_vertex_paths() {
311 let g = diamond();
312 let w = vec![1.0; 4];
313 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
314
315 for v in 0..4u32 {
316 let v_us = v as usize;
317 assert_eq!(r.paths[v_us].len(), r.edge_paths[v_us].len());
318 for (vp, ep) in r.paths[v_us].iter().zip(r.edge_paths[v_us].iter()) {
319 if vp.len() > 1 {
320 assert_eq!(ep.len(), vp.len() - 1);
321 } else {
322 assert!(ep.is_empty());
323 }
324 }
325 }
326 }
327
328 #[test]
329 fn negative_weight_error() {
330 let mut g = Graph::with_vertices(2);
331 g.add_edge(0, 1).expect("e");
332 let r = get_all_shortest_paths_dijkstra(&g, 0, &[-1.0]);
333 assert!(r.is_err());
334 }
335
336 #[test]
337 fn nan_weight_error() {
338 let mut g = Graph::with_vertices(2);
339 g.add_edge(0, 1).expect("e");
340 let r = get_all_shortest_paths_dijkstra(&g, 0, &[f64::NAN]);
341 assert!(r.is_err());
342 }
343
344 #[test]
345 fn source_out_of_range_error() {
346 let g = Graph::with_vertices(3);
347 let r = get_all_shortest_paths_dijkstra(&g, 5, &[]);
348 assert!(r.is_err());
349 }
350
351 #[test]
352 fn ring_multiple_shortest_paths() {
353 let mut g = Graph::with_vertices(6);
359 for i in 0..6u32 {
360 g.add_edge(i, (i + 1) % 6).expect("edge");
361 }
362 let w = vec![1.0; 6];
363 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
365 assert_eq!(r.paths[3].len(), 2);
366 assert_eq!(r.nrgeo[3], 2);
367 }
368
369 #[test]
370 fn grid_multiple_paths() {
371 let mut g = Graph::with_vertices(6);
373 g.add_edge(0, 1).expect("e"); g.add_edge(1, 2).expect("e"); g.add_edge(3, 4).expect("e"); g.add_edge(4, 5).expect("e"); g.add_edge(0, 3).expect("e"); g.add_edge(1, 4).expect("e"); g.add_edge(2, 5).expect("e"); let w = vec![1.0; 7];
381 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
384 assert_eq!(r.paths[5].len(), 3);
385 assert_eq!(r.nrgeo[5], 3);
386 }
387
388 #[test]
389 fn zero_weight_does_not_create_duplicate_parents() {
390 let mut g = Graph::with_vertices(3);
393 g.add_edge(0, 1).expect("e01");
394 g.add_edge(1, 2).expect("e12");
395 let w = vec![0.0, 1.0];
396 let r = get_all_shortest_paths_dijkstra(&g, 0, &w).expect("ok");
397 assert_eq!(r.paths[1].len(), 1);
398 assert_eq!(r.paths[2].len(), 1);
399 }
400}
401
402#[cfg(all(test, feature = "proptest-harness"))]
403#[allow(clippy::cast_possible_truncation)]
404mod proptests {
405 use super::*;
406 use crate::algorithms::paths::all_shortest_paths::{
407 AllShortestPathsMode, get_all_shortest_paths_with_mode,
408 };
409 use proptest::prelude::*;
410
411 fn arb_small_graph() -> impl Strategy<Value = (Graph, Vec<f64>)> {
412 (3..12u32)
413 .prop_flat_map(|n| {
414 let max_edges = n * (n - 1) / 2;
415 let n_edges = 0..max_edges.min(20);
416 (Just(n), n_edges)
417 })
418 .prop_flat_map(|(n, m)| {
419 let edges = proptest::collection::vec((0..n, 0..n), m as usize);
420 (Just(n), edges)
421 })
422 .prop_flat_map(|(n, edges)| {
423 let n_e = edges.len();
424 let weights = proptest::collection::vec(1.0..10.0_f64, n_e);
425 (Just(n), Just(edges), weights)
426 })
427 .prop_map(|(n, edges, weights)| {
428 let mut g = Graph::with_vertices(n);
429 let mut actual_weights = Vec::new();
430 for (i, &(u, v)) in edges.iter().enumerate() {
431 if u != v && g.add_edge(u, v).is_ok() {
432 actual_weights.push(weights[i]);
433 }
434 }
435 (g, actual_weights)
436 })
437 }
438
439 proptest! {
440 #![proptest_config(ProptestConfig::with_cases(200))]
441
442 #[test]
443 fn nrgeo_matches_path_count((g, w) in arb_small_graph()) {
444 if w.len() != g.ecount() { return Ok(()); }
445 let r = get_all_shortest_paths_dijkstra(&g, 0, &w)?;
446 for v in 0..g.vcount() as usize {
447 prop_assert_eq!(
448 r.nrgeo[v] as usize,
449 r.paths[v].len(),
450 "nrgeo mismatch at vertex {}",
451 v
452 );
453 }
454 }
455
456 #[test]
457 fn unit_weights_match_unweighted((g, _) in arb_small_graph()) {
458 let unit_w = vec![1.0; g.ecount()];
459 let weighted = get_all_shortest_paths_dijkstra(&g, 0, &unit_w)?;
460 let unweighted = get_all_shortest_paths_with_mode(
461 &g, 0, AllShortestPathsMode::All,
462 )?;
463 for v in 0..g.vcount() as usize {
464 prop_assert_eq!(
465 weighted.nrgeo[v],
466 unweighted.nrgeo[v],
467 "nrgeo mismatch at vertex {}",
468 v
469 );
470 }
471 }
472
473 #[test]
474 fn paths_are_valid_walks((g, w) in arb_small_graph()) {
475 if w.len() != g.ecount() { return Ok(()); }
476 let r = get_all_shortest_paths_dijkstra(&g, 0, &w)?;
477 for v in 0..g.vcount() {
478 let v_us = v as usize;
479 for (pi, vpath) in r.paths[v_us].iter().enumerate() {
480 prop_assert!(!vpath.is_empty());
481 prop_assert_eq!(vpath[0], 0, "path {} to {} doesn't start at source", pi, v);
482 prop_assert_eq!(*vpath.last().unwrap(), v, "path {} doesn't end at {}", pi, v);
483
484 let epath = &r.edge_paths[v_us][pi];
485 prop_assert_eq!(epath.len(), vpath.len().saturating_sub(1));
486
487 for (i, &eid) in epath.iter().enumerate() {
488 let (eu, ev) = g.edge(eid).unwrap();
489 let pv = vpath[i];
490 let pv_next = vpath[i + 1];
491 let connects = (eu == pv && ev == pv_next)
492 || (eu == pv_next && ev == pv);
493 prop_assert!(
494 connects,
495 "edge {} ({},{}) doesn't connect path vertices {} and {}",
496 eid, eu, ev, pv, pv_next
497 );
498 }
499 }
500 }
501 }
502 }
503}