1use crate::storage::CsrGraph;
28use crate::NodeId;
29use std::cmp::Ordering;
30use std::collections::{BinaryHeap, HashMap};
31
32#[derive(Clone, Copy)]
34struct State {
35 cost: f32,
36 node: usize,
37}
38
39impl PartialEq for State {
40 fn eq(&self, other: &Self) -> bool {
41 self.cost == other.cost && self.node == other.node
42 }
43}
44
45impl Eq for State {}
46
47impl Ord for State {
48 fn cmp(&self, other: &Self) -> Ordering {
49 other
51 .cost
52 .partial_cmp(&self.cost)
53 .unwrap_or(Ordering::Equal)
54 .then_with(|| self.node.cmp(&other.node))
55 }
56}
57
58impl PartialOrd for State {
59 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
60 Some(self.cmp(other))
61 }
62}
63
64#[must_use]
100pub fn dijkstra(graph: &CsrGraph, source: NodeId) -> HashMap<NodeId, f32> {
101 let n = graph.num_nodes();
102 if n == 0 {
103 return HashMap::new();
104 }
105
106 let source_idx = source.0 as usize;
107 if source_idx >= n {
108 return HashMap::new();
109 }
110
111 let mut distances: HashMap<NodeId, f32> = HashMap::new();
112 let mut heap = BinaryHeap::new();
113
114 distances.insert(source, 0.0);
116 heap.push(State { cost: 0.0, node: source_idx });
117
118 while let Some(State { cost, node }) = heap.pop() {
119 #[allow(clippy::cast_possible_truncation)]
120 let node_id = NodeId(node as u32);
121
122 if let Some(&d) = distances.get(&node_id) {
124 if cost > d {
125 continue;
126 }
127 }
128
129 let (neighbors, weights) = graph.adjacency(node_id);
131 for (i, &neighbor) in neighbors.iter().enumerate() {
132 let neighbor_id = NodeId(neighbor);
133 let weight = weights.get(i).copied().unwrap_or(1.0);
134
135 let next_cost = cost + weight;
136
137 let is_shorter = distances.get(&neighbor_id).map_or(true, |&d| next_cost < d);
139
140 if is_shorter {
141 distances.insert(neighbor_id, next_cost);
142 heap.push(State { cost: next_cost, node: neighbor as usize });
143 }
144 }
145 }
146
147 distances
148}
149
150#[must_use]
183fn reconstruct_path(predecessors: &HashMap<NodeId, NodeId>, target: NodeId) -> Vec<NodeId> {
185 let mut path = vec![target];
186 let mut current = target;
187 while let Some(&pred) = predecessors.get(¤t) {
188 path.push(pred);
189 current = pred;
190 }
191 path.reverse();
192 path
193}
194
195#[must_use]
197pub fn dijkstra_path(
198 graph: &CsrGraph,
199 source: NodeId,
200 target: NodeId,
201) -> Option<(f32, Vec<NodeId>)> {
202 let n = graph.num_nodes();
203 if n == 0 || source.0 as usize >= n || target.0 as usize >= n {
204 return None;
205 }
206
207 if source == target {
208 return Some((0.0, vec![source]));
209 }
210
211 let mut distances: HashMap<NodeId, f32> = HashMap::new();
212 let mut predecessors: HashMap<NodeId, NodeId> = HashMap::new();
213 let mut heap = BinaryHeap::new();
214
215 distances.insert(source, 0.0);
216 heap.push(State { cost: 0.0, node: source.0 as usize });
217
218 while let Some(State { cost, node }) = heap.pop() {
219 #[allow(clippy::cast_possible_truncation)]
220 let node_id = NodeId(node as u32);
221
222 if node_id == target {
223 let path = reconstruct_path(&predecessors, target);
224 return Some((cost, path));
225 }
226
227 if distances.get(&node_id).is_some_and(|&d| cost > d) {
228 continue;
229 }
230
231 let (neighbors, weights) = graph.adjacency(node_id);
232 for (i, &neighbor) in neighbors.iter().enumerate() {
233 let neighbor_id = NodeId(neighbor);
234 let weight = weights.get(i).copied().unwrap_or(1.0);
235 let next_cost = cost + weight;
236
237 if distances.get(&neighbor_id).map_or(true, |&d| next_cost < d) {
238 distances.insert(neighbor_id, next_cost);
239 predecessors.insert(neighbor_id, node_id);
240 heap.push(State { cost: next_cost, node: neighbor as usize });
241 }
242 }
243 }
244
245 None
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_empty_graph() {
254 let graph = CsrGraph::new();
255 let distances = dijkstra(&graph, NodeId(0));
256 assert!(distances.is_empty());
257 }
258
259 #[test]
260 fn test_single_edge() {
261 let edges = vec![(NodeId(0), NodeId(1), 5.0)];
262 let graph = CsrGraph::from_edge_list(&edges).unwrap();
263
264 let distances = dijkstra(&graph, NodeId(0));
265 assert_eq!(distances.get(&NodeId(0)), Some(&0.0));
266 assert_eq!(distances.get(&NodeId(1)), Some(&5.0));
267 }
268
269 #[test]
270 fn test_chain() {
271 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(2), 2.0)];
273 let graph = CsrGraph::from_edge_list(&edges).unwrap();
274
275 let distances = dijkstra(&graph, NodeId(0));
276 assert_eq!(distances.get(&NodeId(0)), Some(&0.0));
277 assert_eq!(distances.get(&NodeId(1)), Some(&1.0));
278 assert_eq!(distances.get(&NodeId(2)), Some(&3.0));
279 }
280
281 #[test]
282 fn test_shorter_path_via_intermediate() {
283 let edges = vec![
286 (NodeId(0), NodeId(1), 1.0),
287 (NodeId(1), NodeId(2), 2.0),
288 (NodeId(0), NodeId(2), 5.0),
289 ];
290 let graph = CsrGraph::from_edge_list(&edges).unwrap();
291
292 let distances = dijkstra(&graph, NodeId(0));
293 assert_eq!(distances.get(&NodeId(2)), Some(&3.0)); }
295
296 #[test]
297 fn test_unreachable_node() {
298 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(2), NodeId(3), 1.0)];
300 let graph = CsrGraph::from_edge_list(&edges).unwrap();
301
302 let distances = dijkstra(&graph, NodeId(0));
303 assert_eq!(distances.get(&NodeId(0)), Some(&0.0));
304 assert_eq!(distances.get(&NodeId(1)), Some(&1.0));
305 assert!(distances.get(&NodeId(2)).is_none());
306 assert!(distances.get(&NodeId(3)).is_none());
307 }
308
309 #[test]
310 fn test_dijkstra_path_simple() {
311 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(1), NodeId(2), 2.0)];
312 let graph = CsrGraph::from_edge_list(&edges).unwrap();
313
314 let result = dijkstra_path(&graph, NodeId(0), NodeId(2));
315 assert!(result.is_some());
316 let (dist, path) = result.unwrap();
317 assert_eq!(dist, 3.0);
318 assert_eq!(path, vec![NodeId(0), NodeId(1), NodeId(2)]);
319 }
320
321 #[test]
322 fn test_dijkstra_path_same_node() {
323 let edges = vec![(NodeId(0), NodeId(1), 1.0)];
324 let graph = CsrGraph::from_edge_list(&edges).unwrap();
325
326 let result = dijkstra_path(&graph, NodeId(0), NodeId(0));
327 assert!(result.is_some());
328 let (dist, path) = result.unwrap();
329 assert_eq!(dist, 0.0);
330 assert_eq!(path, vec![NodeId(0)]);
331 }
332
333 #[test]
334 fn test_dijkstra_path_unreachable() {
335 let edges = vec![(NodeId(0), NodeId(1), 1.0), (NodeId(2), NodeId(3), 1.0)];
336 let graph = CsrGraph::from_edge_list(&edges).unwrap();
337
338 let result = dijkstra_path(&graph, NodeId(0), NodeId(3));
339 assert!(result.is_none());
340 }
341
342 #[test]
343 fn test_dijkstra_path_chooses_shorter() {
344 let edges = vec![
347 (NodeId(0), NodeId(1), 1.0),
348 (NodeId(1), NodeId(2), 2.0),
349 (NodeId(0), NodeId(2), 5.0),
350 ];
351 let graph = CsrGraph::from_edge_list(&edges).unwrap();
352
353 let result = dijkstra_path(&graph, NodeId(0), NodeId(2));
354 assert!(result.is_some());
355 let (dist, path) = result.unwrap();
356 assert_eq!(dist, 3.0);
357 assert_eq!(path, vec![NodeId(0), NodeId(1), NodeId(2)]);
358 }
359
360 #[test]
361 fn test_diamond_shortest_path() {
362 let edges = vec![
369 (NodeId(0), NodeId(1), 1.0),
370 (NodeId(0), NodeId(2), 2.0),
371 (NodeId(1), NodeId(3), 1.0),
372 (NodeId(2), NodeId(3), 5.0),
373 ];
374 let graph = CsrGraph::from_edge_list(&edges).unwrap();
375
376 let result = dijkstra_path(&graph, NodeId(0), NodeId(3));
377 assert!(result.is_some());
378 let (dist, path) = result.unwrap();
379 assert_eq!(dist, 2.0); assert_eq!(path, vec![NodeId(0), NodeId(1), NodeId(3)]);
381 }
382
383 #[test]
384 fn test_cycle_in_graph() {
385 let edges = vec![
387 (NodeId(0), NodeId(1), 1.0),
388 (NodeId(1), NodeId(2), 1.0),
389 (NodeId(2), NodeId(0), 1.0),
390 (NodeId(0), NodeId(3), 10.0),
391 ];
392 let graph = CsrGraph::from_edge_list(&edges).unwrap();
393
394 let distances = dijkstra(&graph, NodeId(0));
395 assert_eq!(distances.get(&NodeId(0)), Some(&0.0));
396 assert_eq!(distances.get(&NodeId(1)), Some(&1.0));
397 assert_eq!(distances.get(&NodeId(2)), Some(&2.0));
398 assert_eq!(distances.get(&NodeId(3)), Some(&10.0));
399 }
400
401 #[test]
402 fn test_source_out_of_bounds() {
403 let edges = vec![(NodeId(0), NodeId(1), 1.0)];
404 let graph = CsrGraph::from_edge_list(&edges).unwrap();
405
406 let distances = dijkstra(&graph, NodeId(100));
407 assert!(distances.is_empty());
408 }
409
410 #[test]
411 fn test_zero_weight_edge() {
412 let edges = vec![(NodeId(0), NodeId(1), 0.0), (NodeId(1), NodeId(2), 0.0)];
413 let graph = CsrGraph::from_edge_list(&edges).unwrap();
414
415 let distances = dijkstra(&graph, NodeId(0));
416 assert_eq!(distances.get(&NodeId(2)), Some(&0.0));
417 }
418}