samyama_graph_algorithms/
pathfinding.rs1use super::common::{GraphView, NodeId};
6use std::collections::{HashMap, VecDeque, BinaryHeap};
7use std::cmp::Ordering;
8
9#[derive(Debug, Clone)]
11pub struct PathResult {
12 pub source: NodeId,
13 pub target: NodeId,
14 pub path: Vec<NodeId>,
15 pub cost: f64,
16}
17
18pub fn bfs(
20 view: &GraphView,
21 source: NodeId,
22 target: NodeId,
23) -> Option<PathResult> {
24 let source_idx = *view.node_to_index.get(&source)?;
25 let target_idx = *view.node_to_index.get(&target)?;
26
27 let mut queue = VecDeque::new();
28 let mut visited = HashMap::new(); queue.push_back(source_idx);
31 visited.insert(source_idx, None);
32
33 while let Some(current_idx) = queue.pop_front() {
34 if current_idx == target_idx {
35 let mut path = Vec::new();
37 let mut curr = Some(target_idx);
38 while let Some(idx) = curr {
39 path.push(view.index_to_node[idx]);
40 if let Some(parent) = visited.get(&idx) {
41 curr = *parent;
42 } else {
43 curr = None;
44 }
45 }
46 path.reverse();
47 return Some(PathResult {
48 source,
49 target,
50 cost: (path.len() - 1) as f64,
51 path,
52 });
53 }
54
55 for &next_idx in view.successors(current_idx) {
56 if !visited.contains_key(&next_idx) {
57 visited.insert(next_idx, Some(current_idx));
58 queue.push_back(next_idx);
59 }
60 }
61 }
62
63 None
64}
65
66#[derive(Copy, Clone, PartialEq)]
68struct State {
69 cost: f64,
70 node_idx: usize,
71}
72
73impl Eq for State {}
74
75impl Ord for State {
76 fn cmp(&self, other: &Self) -> Ordering {
77 other.cost.partial_cmp(&self.cost).unwrap_or(Ordering::Equal)
79 }
80}
81
82impl PartialOrd for State {
83 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
84 Some(self.cmp(other))
85 }
86}
87
88pub fn dijkstra(
92 view: &GraphView,
93 source: NodeId,
94 target: NodeId,
95) -> Option<PathResult> {
96 let source_idx = *view.node_to_index.get(&source)?;
97 let target_idx = *view.node_to_index.get(&target)?;
98
99 let mut dist = HashMap::new();
100 let mut parent = HashMap::new();
101 let mut heap = BinaryHeap::new();
102
103 dist.insert(source_idx, 0.0);
104 heap.push(State { cost: 0.0, node_idx: source_idx });
105
106 while let Some(State { cost, node_idx }) = heap.pop() {
107 if node_idx == target_idx {
108 let mut path = Vec::new();
110 let mut curr = Some(target_idx);
111 while let Some(idx) = curr {
112 path.push(view.index_to_node[idx]);
113 curr = parent.get(&idx).cloned().flatten();
114 }
115 path.reverse();
116 return Some(PathResult {
117 source,
118 target,
119 path,
120 cost,
121 });
122 }
123
124 if cost > *dist.get(&node_idx).unwrap_or(&f64::INFINITY) {
125 continue;
126 }
127
128 let edges = view.successors(node_idx);
129 let weights = view.weights(node_idx);
130
131 for (i, &next_idx) in edges.iter().enumerate() {
132 let weight = if let Some(w) = weights {
133 w[i]
134 } else {
135 1.0
136 };
137
138 if weight < 0.0 { continue; }
139
140 let next_cost = cost + weight;
141
142 if next_cost < *dist.get(&next_idx).unwrap_or(&f64::INFINITY) {
143 dist.insert(next_idx, next_cost);
144 parent.insert(next_idx, Some(node_idx));
145 heap.push(State { cost: next_cost, node_idx: next_idx });
146 }
147 }
148 }
149
150 None
151}
152
153pub fn bfs_all_shortest_paths(
155 view: &GraphView,
156 source: NodeId,
157 target: NodeId,
158) -> Vec<PathResult> {
159 let source_idx = match view.node_to_index.get(&source) {
160 Some(&idx) => idx,
161 None => return vec![],
162 };
163 let target_idx = match view.node_to_index.get(&target) {
164 Some(&idx) => idx,
165 None => return vec![],
166 };
167
168 if source_idx == target_idx {
169 return vec![PathResult {
170 source,
171 target,
172 path: vec![source],
173 cost: 0.0,
174 }];
175 }
176
177 let mut parents: HashMap<usize, Vec<usize>> = HashMap::new();
179 let mut distance: HashMap<usize, usize> = HashMap::new();
180 let mut queue = VecDeque::new();
181
182 queue.push_back(source_idx);
183 distance.insert(source_idx, 0);
184
185 let mut target_distance: Option<usize> = None;
186
187 while let Some(current) = queue.pop_front() {
188 let current_dist = distance[¤t];
189
190 if let Some(td) = target_distance {
192 if current_dist >= td {
193 continue;
194 }
195 }
196
197 for &next_idx in view.successors(current) {
198 let next_dist = current_dist + 1;
199
200 if let Some(&existing_dist) = distance.get(&next_idx) {
201 if next_dist == existing_dist {
202 parents.entry(next_idx).or_default().push(current);
204 }
205 } else {
207 distance.insert(next_idx, next_dist);
209 parents.insert(next_idx, vec![current]);
210 queue.push_back(next_idx);
211
212 if next_idx == target_idx {
213 target_distance = Some(next_dist);
214 }
215 }
216 }
217 }
218
219 if !distance.contains_key(&target_idx) {
221 return vec![];
222 }
223
224 let mut all_paths = Vec::new();
226 let mut stack: Vec<(usize, Vec<usize>)> = vec![(target_idx, vec![target_idx])];
227
228 while let Some((node, partial_path)) = stack.pop() {
229 if node == source_idx {
230 let mut path: Vec<NodeId> = partial_path.iter()
231 .rev()
232 .map(|&idx| view.index_to_node[idx])
233 .collect();
234 all_paths.push(PathResult {
235 source,
236 target,
237 cost: (path.len() - 1) as f64,
238 path,
239 });
240 continue;
241 }
242
243 if let Some(parent_list) = parents.get(&node) {
244 for &parent in parent_list {
245 let mut new_path = partial_path.clone();
246 new_path.push(parent);
247 stack.push((parent, new_path));
248 }
249 }
250 }
251
252 all_paths
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use std::collections::HashMap;
259 use crate::common::GraphView;
260
261 #[test]
262 fn test_bfs() {
263 let index_to_node = vec![1, 2, 3];
265 let mut node_to_index = HashMap::new();
266 node_to_index.insert(1, 0);
267 node_to_index.insert(2, 1);
268 node_to_index.insert(3, 2);
269
270 let mut outgoing = vec![vec![]; 3];
271 outgoing[0].push(1);
272 outgoing[1].push(2);
273
274 let view = GraphView::from_adjacency_list(
275 3,
276 index_to_node,
277 node_to_index,
278 outgoing,
279 vec![vec![]; 3],
280 None,
281 );
282
283 let result = bfs(&view, 1, 3).unwrap();
284 assert_eq!(result.path, vec![1, 2, 3]);
285 assert_eq!(result.cost, 2.0);
286 }
287
288 #[test]
289 fn test_dijkstra() {
290 let index_to_node = vec![1, 2, 3];
292 let mut node_to_index = HashMap::new();
293 node_to_index.insert(1, 0);
294 node_to_index.insert(2, 1);
295 node_to_index.insert(3, 2);
296
297 let mut outgoing = vec![vec![]; 3];
298 let mut weights = vec![vec![]; 3];
299
300 outgoing[0].push(1); weights[0].push(10.0);
301 outgoing[0].push(2); weights[0].push(50.0); outgoing[1].push(2); weights[1].push(5.0);
303
304 let view = GraphView::from_adjacency_list(
305 3,
306 index_to_node,
307 node_to_index,
308 outgoing,
309 vec![vec![]; 3],
310 Some(weights),
311 );
312
313 let result = dijkstra(&view, 1, 3).unwrap();
314 assert_eq!(result.path, vec![1, 2, 3]);
315 assert_eq!(result.cost, 15.0);
316 }
317
318 #[test]
319 fn test_bfs_all_shortest_paths() {
320 let index_to_node = vec![1, 2, 3, 4];
322 let mut node_to_index = HashMap::new();
323 node_to_index.insert(1, 0);
324 node_to_index.insert(2, 1);
325 node_to_index.insert(3, 2);
326 node_to_index.insert(4, 3);
327
328 let mut outgoing = vec![vec![]; 4];
329 outgoing[0] = vec![1, 2]; outgoing[1] = vec![3]; outgoing[2] = vec![3]; let view = GraphView::from_adjacency_list(
334 4,
335 index_to_node,
336 node_to_index,
337 outgoing,
338 vec![vec![]; 4],
339 None,
340 );
341
342 let results = bfs_all_shortest_paths(&view, 1, 4);
343 assert_eq!(results.len(), 2, "Should find 2 shortest paths in diamond graph");
344 for r in &results {
345 assert_eq!(r.cost, 2.0);
346 assert_eq!(r.path.len(), 3);
347 assert_eq!(r.path[0], 1);
348 assert_eq!(r.path[2], 4);
349 }
350 }
351
352 #[test]
353 fn test_bfs_all_shortest_paths_no_path() {
354 let index_to_node = vec![1, 2];
355 let mut node_to_index = HashMap::new();
356 node_to_index.insert(1, 0);
357 node_to_index.insert(2, 1);
358
359 let view = GraphView::from_adjacency_list(
360 2,
361 index_to_node,
362 node_to_index,
363 vec![vec![], vec![]],
364 vec![vec![], vec![]],
365 None,
366 );
367
368 let results = bfs_all_shortest_paths(&view, 1, 2);
369 assert!(results.is_empty());
370 }
371}