1use std::collections::{HashMap, hash_map::Entry};
6
7#[cfg(test)]
8use super::DEFAULT_MAX_VISITED;
9use crate::csr::CsrIndex;
10use crate::overlay_delta::GraphOverlayDelta;
11use crate::path_overlay::shortest_path_overlay;
12use crate::path_params::ShortestPathParams;
13
14impl CsrIndex {
15 pub fn shortest_path(
31 &self,
32 params: ShortestPathParams<'_>,
33 overlay: Option<&GraphOverlayDelta>,
34 ) -> Option<Vec<String>> {
35 Self::shortest_path_on(Some(self), params, overlay)
36 }
37
38 pub fn shortest_path_on(
42 partition: Option<&Self>,
43 params: ShortestPathParams<'_>,
44 overlay: Option<&GraphOverlayDelta>,
45 ) -> Option<Vec<String>> {
46 match (partition, overlay) {
47 (_, Some(ov)) if !ov.is_empty() => shortest_path_overlay(partition, params, ov),
48 (Some(csr), _) => csr.shortest_path_dense(params),
49 (None, _) => None,
50 }
51 }
52
53 fn shortest_path_dense(&self, params: ShortestPathParams<'_>) -> Option<Vec<String>> {
65 let ShortestPathParams {
66 src,
67 dst,
68 label_filter,
69 max_depth,
70 max_visited,
71 frontier_bitmap,
72 } = params;
73 let src_id = *self.node_to_id.get(src)?;
74 let dst_id = *self.node_to_id.get(dst)?;
75 if src_id == dst_id {
76 return Some(vec![src.to_string()]);
77 }
78
79 let labels = self.label_filter(label_filter);
80 let in_bitmap = |id: u32| {
83 id == src_id
84 || id == dst_id
85 || frontier_bitmap.is_none_or(|bm| {
86 bm.contains(nodedb_types::Surrogate::new(self.node_surrogate_raw(id)))
87 })
88 };
89 let within_depth =
90 |path: Vec<String>| (path.len().saturating_sub(1) <= max_depth).then_some(path);
91 let mut fwd_parent: HashMap<u32, u32> = HashMap::new();
92 let mut bwd_parent: HashMap<u32, u32> = HashMap::new();
93 fwd_parent.insert(src_id, src_id);
94 bwd_parent.insert(dst_id, dst_id);
95
96 let mut fwd_frontier: Vec<u32> = vec![src_id];
97 let mut bwd_frontier: Vec<u32> = vec![dst_id];
98
99 for _round in 0..max_depth.div_ceil(2) {
100 if fwd_parent.len() + bwd_parent.len() >= max_visited {
101 break;
102 }
103
104 let mut candidates: Vec<(u32, u32)> = Vec::new();
109 for &node in &fwd_frontier {
110 self.record_access(node);
111 for (lid, neighbor) in self.dense_iter_out(node) {
112 if labels.keeps(lid) && in_bitmap(neighbor) {
113 candidates.push((neighbor, node));
114 }
115 }
116 }
117 self.sort_edges_by_name(&mut candidates);
118 let mut next_fwd = Vec::new();
119 for (neighbor, node) in candidates {
120 if let Entry::Vacant(e) = fwd_parent.entry(neighbor) {
121 e.insert(node);
122 next_fwd.push(neighbor);
123 }
124 if bwd_parent.contains_key(&neighbor) {
125 return within_depth(self.reconstruct_path(neighbor, &fwd_parent, &bwd_parent));
126 }
127 }
128 fwd_frontier = next_fwd;
129
130 let mut candidates: Vec<(u32, u32)> = Vec::new();
131 for &node in &bwd_frontier {
132 self.record_access(node);
133 for (lid, neighbor) in self.dense_iter_in(node) {
134 if labels.keeps(lid) && in_bitmap(neighbor) {
135 candidates.push((neighbor, node));
136 }
137 }
138 }
139 self.sort_edges_by_name(&mut candidates);
140 let mut next_bwd = Vec::new();
141 for (neighbor, node) in candidates {
142 if let Entry::Vacant(e) = bwd_parent.entry(neighbor) {
143 e.insert(node);
144 next_bwd.push(neighbor);
145 }
146 if fwd_parent.contains_key(&neighbor) {
147 return within_depth(self.reconstruct_path(neighbor, &fwd_parent, &bwd_parent));
148 }
149 }
150 bwd_frontier = next_bwd;
151
152 if fwd_frontier.is_empty() && bwd_frontier.is_empty() {
153 break;
154 }
155 }
156 None
157 }
158
159 fn sort_edges_by_name(&self, edges: &mut [(u32, u32)]) {
161 edges.sort_by(|a, b| {
162 let name = |id: u32| self.node_name_checked(id);
163 (name(a.0), name(a.1)).cmp(&(name(b.0), name(b.1)))
164 });
165 }
166
167 fn reconstruct_path(
168 &self,
169 meeting: u32,
170 fwd_parent: &HashMap<u32, u32>,
171 bwd_parent: &HashMap<u32, u32>,
172 ) -> Vec<String> {
173 let mut fwd_path = Vec::new();
174 let mut current = meeting;
175 loop {
176 fwd_path.push(current);
177 let parent = fwd_parent[¤t];
178 if parent == current {
179 break;
180 }
181 current = parent;
182 }
183 fwd_path.reverse();
184
185 current = bwd_parent[&meeting];
186 if current != meeting {
187 loop {
188 fwd_path.push(current);
189 let parent = bwd_parent[¤t];
190 if parent == current {
191 break;
192 }
193 current = parent;
194 }
195 }
196
197 fwd_path
198 .into_iter()
199 .map(|id| self.id_to_node[id as usize].clone())
200 .collect()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use crate::test_support::{chain_csr, long_chain_csr, test_memory};
208
209 fn path_params<'a>(
210 src: &'a str,
211 dst: &'a str,
212 label_filter: &'a [&'a str],
213 max_depth: usize,
214 frontier_bitmap: Option<&'a nodedb_types::SurrogateBitmap>,
215 ) -> ShortestPathParams<'a> {
216 ShortestPathParams {
217 src,
218 dst,
219 label_filter,
220 max_depth,
221 max_visited: DEFAULT_MAX_VISITED,
222 frontier_bitmap,
223 }
224 }
225
226 #[test]
227 fn shortest_path_direct() {
228 let csr = chain_csr();
229 let path = csr
230 .shortest_path(path_params("a", "c", &["KNOWS"], 5, None), None)
231 .unwrap();
232 assert_eq!(path, vec!["a", "b", "c"]);
233 }
234
235 #[test]
236 fn shortest_path_takes_the_smallest_named_tie() {
237 let mut csr = CsrIndex::new(test_memory());
239 csr.add_edge("a", "L", "z").unwrap();
240 csr.add_edge("z", "L", "d").unwrap();
241 csr.add_edge("a", "L", "b").unwrap();
242 csr.add_edge("b", "L", "d").unwrap();
243 let path = csr
244 .shortest_path(path_params("a", "d", &[], 5, None), None)
245 .unwrap();
246 assert_eq!(path, vec!["a", "b", "d"]);
247 }
248
249 #[test]
250 fn shortest_path_over_a_long_chain() {
251 let csr = long_chain_csr();
252 let path = csr
253 .shortest_path(path_params("n0", "n50", &["NEXT"], 100, None), None)
254 .unwrap();
255 assert_eq!(path.len(), 51);
256 assert!(
258 csr.shortest_path(path_params("n0", "n50", &["NEXT"], 49, None), None)
259 .is_none()
260 );
261 assert!(
262 csr.shortest_path(path_params("n0", "n50", &["NEXT"], 50, None), None)
263 .is_some()
264 );
265 }
266
267 #[test]
268 fn shortest_path_same_node() {
269 let csr = chain_csr();
270 let path = csr
271 .shortest_path(path_params("a", "a", &[], 5, None), None)
272 .unwrap();
273 assert_eq!(path, vec!["a"]);
274 }
275
276 #[test]
277 fn shortest_path_unreachable() {
278 let csr = chain_csr();
279 let path = csr.shortest_path(path_params("d", "a", &["KNOWS"], 5, None), None);
280 assert!(path.is_none());
281 }
282
283 #[test]
284 fn shortest_path_depth_limit() {
285 let csr = chain_csr();
286 let path = csr.shortest_path(path_params("a", "d", &["KNOWS"], 1, None), None);
287 assert!(path.is_none());
288 }
289
290 #[test]
294 fn shortest_path_frontier_bitmap_blocks_intermediate() {
295 use nodedb_types::{Surrogate, SurrogateBitmap};
296
297 let mut csr = chain_csr();
298 csr.set_node_surrogate("b", Surrogate::new(10));
299 csr.set_node_surrogate("c", Surrogate::new(20));
300
301 let mut bm = SurrogateBitmap::new();
303 bm.insert(Surrogate::new(20)); let path = csr.shortest_path(path_params("a", "c", &["KNOWS"], 5, Some(&bm)), None);
306 assert!(path.is_none());
309 }
310
311 #[test]
314 fn the_frontier_bitmap_never_gates_the_endpoints() {
315 use nodedb_types::{Surrogate, SurrogateBitmap};
316
317 let mut csr = CsrIndex::new(test_memory());
318 csr.add_edge("a", "L", "c").unwrap();
319 csr.set_node_surrogate("a", Surrogate::new(1));
320 csr.set_node_surrogate("c", Surrogate::new(3));
321 let mut bm = SurrogateBitmap::new();
322 bm.insert(Surrogate::new(99));
323
324 let path = csr.shortest_path(path_params("a", "c", &[], 1, Some(&bm)), None);
325 assert_eq!(path, Some(vec!["a".to_string(), "c".to_string()]));
326 }
327
328 fn params<'a>(labels: &'a [&'a str], depth: usize) -> ShortestPathParams<'a> {
329 ShortestPathParams {
330 src: "a",
331 dst: "d",
332 label_filter: labels,
333 max_depth: depth,
334 max_visited: DEFAULT_MAX_VISITED,
335 frontier_bitmap: None,
336 }
337 }
338
339 #[test]
340 fn dense_path_matches_any_listed_label() {
341 let mut csr = CsrIndex::new(test_memory());
342 csr.add_edge("a", "FIRST", "b").unwrap();
343 csr.add_edge("b", "SECOND", "d").unwrap();
344 assert_eq!(
345 csr.shortest_path(params(&["FIRST", "SECOND"], 2), None),
346 Some(vec!["a".into(), "b".into(), "d".into()])
347 );
348 assert!(
349 csr.shortest_path(params(&["FIRST", "SECOND"], 1), None)
350 .is_none()
351 );
352 assert!(csr.shortest_path(params(&["FIRST"], 2), None).is_none());
353 assert!(csr.shortest_path(params(&["unknown"], 2), None).is_none());
354 assert!(csr.shortest_path(params(&[], 2), None).is_some());
355 assert!(
356 csr.shortest_path(params(&["unknown", "FIRST", "SECOND"], 2), None)
357 .is_some()
358 );
359 assert!(
360 csr.shortest_path(params(&["FIRST", "SECOND"], 0), None)
361 .is_none()
362 );
363 }
364
365 #[test]
368 fn an_unknown_label_in_the_set_does_not_widen_it() {
369 let mut csr = CsrIndex::new(test_memory());
370 csr.add_edge("a", "FIRST", "b").unwrap();
371 csr.add_edge("b", "SECOND", "d").unwrap();
372 csr.add_edge("a", "OTHER", "d").unwrap();
373 assert_eq!(
374 csr.shortest_path(params(&["FIRST", "SECOND", "unknown"], 2), None),
375 Some(vec!["a".into(), "b".into(), "d".into()])
376 );
377 assert!(
378 csr.shortest_path(params(&["FIRST", "unknown"], 2), None)
379 .is_none()
380 );
381 assert!(
382 csr.shortest_path(params(&["unknown", "never"], 2), None)
383 .is_none()
384 );
385 }
386
387 #[test]
388 fn overlay_path_matches_labels_without_durable_ids() {
389 let csr = CsrIndex::new(test_memory());
390 let mut overlay = GraphOverlayDelta::new();
391 overlay.stage_edge("a", "FIRST", "x");
392 overlay.stage_edge("x", "SECOND", "d");
393 assert_eq!(
394 csr.shortest_path(params(&["FIRST", "SECOND"], 2), Some(&overlay)),
395 Some(vec!["a".into(), "x".into(), "d".into()])
396 );
397 assert!(
398 csr.shortest_path(params(&["FIRST", "SECOND"], 1), Some(&overlay))
399 .is_none()
400 );
401 assert!(
402 csr.shortest_path(params(&["unknown"], 2), Some(&overlay))
403 .is_none()
404 );
405 assert!(
406 csr.shortest_path(params(&["FIRST"], 2), Some(&overlay))
407 .is_none()
408 );
409 assert!(csr.shortest_path(params(&[], 2), Some(&overlay)).is_some());
410 assert!(
411 csr.shortest_path(params(&["FIRST", "SECOND"], 0), Some(&overlay))
412 .is_none()
413 );
414 }
415
416 #[test]
417 fn mixed_labels_preserve_forward_and_backward_tombstones() {
418 let mut csr = CsrIndex::new(test_memory());
419 csr.add_edge("a", "FIRST", "b").unwrap();
420 csr.add_edge("b", "SECOND", "d").unwrap();
421 let labels = ["FIRST", "SECOND"];
422 for (src, label, dst) in [("a", "FIRST", "b"), ("b", "SECOND", "d")] {
423 let mut overlay = GraphOverlayDelta::new();
424 overlay.stage_tombstone(src, label, dst);
425 assert!(
426 csr.shortest_path(params(&labels, 2), Some(&overlay))
427 .is_none()
428 );
429 }
430 }
431
432 #[test]
433 fn odd_length_paths_count_every_edge() {
434 let mut csr = CsrIndex::new(test_memory());
435 csr.add_edge("a", "FIRST", "b").unwrap();
436 csr.add_edge("b", "SECOND", "c").unwrap();
437 csr.add_edge("c", "FIRST", "d").unwrap();
438 let labels = ["FIRST", "SECOND"];
439 assert!(csr.shortest_path(params(&labels, 2), None).is_none());
440 assert_eq!(
441 csr.shortest_path(params(&labels, 3), None),
442 Some(vec!["a".into(), "b".into(), "c".into(), "d".into()])
443 );
444
445 let staged_csr = CsrIndex::new(test_memory());
446 let mut overlay = GraphOverlayDelta::new();
447 overlay.stage_edge("a", "FIRST", "b");
448 overlay.stage_edge("b", "SECOND", "c");
449 overlay.stage_edge("c", "FIRST", "d");
450 assert!(
451 staged_csr
452 .shortest_path(params(&labels, 2), Some(&overlay))
453 .is_none()
454 );
455 assert_eq!(
456 staged_csr.shortest_path(params(&labels, 3), Some(&overlay)),
457 Some(vec!["a".into(), "b".into(), "c".into(), "d".into()])
458 );
459 }
460}