1use std::collections::VecDeque;
7use std::sync::OnceLock;
8
9use grafeo_common::types::{NodeId, Value};
10use grafeo_common::utils::error::Result;
11use grafeo_common::utils::hash::{FxHashMap, FxHashSet};
12use grafeo_core::graph::Direction;
13use grafeo_core::graph::lpg::LpgStore;
14
15use super::super::{AlgorithmResult, ParameterDef, ParameterType, Parameters};
16use super::traits::{Control, GraphAlgorithm, NodeValueResultBuilder, TraversalEvent};
17
18pub fn bfs(store: &LpgStore, start: NodeId) -> Vec<NodeId> {
35 let mut visited = Vec::new();
36 bfs_with_visitor(store, start, |event| -> Control<()> {
37 if let TraversalEvent::Discover(node) = event {
38 visited.push(node);
39 }
40 Control::Continue
41 });
42 visited
43}
44
45pub fn bfs_with_visitor<B, F>(store: &LpgStore, start: NodeId, mut visitor: F) -> Option<B>
60where
61 F: FnMut(TraversalEvent) -> Control<B>,
62{
63 let mut discovered: FxHashSet<NodeId> = FxHashSet::default();
64 let mut queue: VecDeque<NodeId> = VecDeque::new();
65
66 store.get_node(start)?;
68
69 discovered.insert(start);
71 queue.push_back(start);
72
73 match visitor(TraversalEvent::Discover(start)) {
74 Control::Break(b) => return Some(b),
75 Control::Prune => {
76 match visitor(TraversalEvent::Finish(start)) {
78 Control::Break(b) => return Some(b),
79 _ => return None,
80 }
81 }
82 Control::Continue => {}
83 }
84
85 while let Some(node) = queue.pop_front() {
86 for (neighbor, edge_id) in store.edges_from(node, Direction::Outgoing) {
88 if discovered.insert(neighbor) {
89 match visitor(TraversalEvent::TreeEdge {
91 source: node,
92 target: neighbor,
93 edge: edge_id,
94 }) {
95 Control::Break(b) => return Some(b),
96 Control::Prune => continue, Control::Continue => {}
98 }
99
100 match visitor(TraversalEvent::Discover(neighbor)) {
101 Control::Break(b) => return Some(b),
102 Control::Prune => continue, Control::Continue => {}
104 }
105
106 queue.push_back(neighbor);
107 } else {
108 match visitor(TraversalEvent::NonTreeEdge {
110 source: node,
111 target: neighbor,
112 edge: edge_id,
113 }) {
114 Control::Break(b) => return Some(b),
115 _ => {}
116 }
117 }
118 }
119
120 match visitor(TraversalEvent::Finish(node)) {
122 Control::Break(b) => return Some(b),
123 _ => {}
124 }
125 }
126
127 None
128}
129
130pub fn bfs_layers(store: &LpgStore, start: NodeId) -> Vec<Vec<NodeId>> {
141 let mut layers: Vec<Vec<NodeId>> = Vec::new();
142 let mut discovered: FxHashSet<NodeId> = FxHashSet::default();
143 let mut current_layer: Vec<NodeId> = Vec::new();
144 let mut next_layer: Vec<NodeId> = Vec::new();
145
146 if store.get_node(start).is_none() {
147 return layers;
148 }
149
150 discovered.insert(start);
151 current_layer.push(start);
152
153 while !current_layer.is_empty() {
154 layers.push(current_layer.clone());
155
156 for &node in ¤t_layer {
157 for (neighbor, _) in store.edges_from(node, Direction::Outgoing) {
158 if discovered.insert(neighbor) {
159 next_layer.push(neighbor);
160 }
161 }
162 }
163
164 current_layer.clear();
165 std::mem::swap(&mut current_layer, &mut next_layer);
166 }
167
168 layers
169}
170
171#[derive(Clone, Copy, PartialEq, Eq)]
177enum NodeColor {
178 White,
180 Gray,
182 Black,
184}
185
186pub fn dfs(store: &LpgStore, start: NodeId) -> Vec<NodeId> {
199 let mut finished = Vec::new();
200 dfs_with_visitor(store, start, |event| -> Control<()> {
201 if let TraversalEvent::Finish(node) = event {
202 finished.push(node);
203 }
204 Control::Continue
205 });
206 finished
207}
208
209pub fn dfs_with_visitor<B, F>(store: &LpgStore, start: NodeId, mut visitor: F) -> Option<B>
223where
224 F: FnMut(TraversalEvent) -> Control<B>,
225{
226 let mut color: FxHashMap<NodeId, NodeColor> = FxHashMap::default();
227
228 let mut stack: Vec<(NodeId, Vec<(NodeId, grafeo_common::types::EdgeId)>, usize)> = Vec::new();
231
232 store.get_node(start)?;
234
235 color.insert(start, NodeColor::Gray);
237 match visitor(TraversalEvent::Discover(start)) {
238 Control::Break(b) => return Some(b),
239 Control::Prune => {
240 color.insert(start, NodeColor::Black);
241 match visitor(TraversalEvent::Finish(start)) {
242 Control::Break(b) => return Some(b),
243 _ => return None,
244 }
245 }
246 Control::Continue => {}
247 }
248
249 let neighbors: Vec<_> = store.edges_from(start, Direction::Outgoing).collect();
250 stack.push((start, neighbors, 0));
251
252 while let Some((node, neighbors, idx)) = stack.last_mut() {
253 if *idx >= neighbors.len() {
254 let node = *node;
256 stack.pop();
257 color.insert(node, NodeColor::Black);
258 match visitor(TraversalEvent::Finish(node)) {
259 Control::Break(b) => return Some(b),
260 _ => {}
261 }
262 continue;
263 }
264
265 let (neighbor, edge_id) = neighbors[*idx];
266 *idx += 1;
267
268 match color.get(&neighbor).copied().unwrap_or(NodeColor::White) {
269 NodeColor::White => {
270 match visitor(TraversalEvent::TreeEdge {
272 source: *node,
273 target: neighbor,
274 edge: edge_id,
275 }) {
276 Control::Break(b) => return Some(b),
277 Control::Prune => continue,
278 Control::Continue => {}
279 }
280
281 color.insert(neighbor, NodeColor::Gray);
282 match visitor(TraversalEvent::Discover(neighbor)) {
283 Control::Break(b) => return Some(b),
284 Control::Prune => {
285 color.insert(neighbor, NodeColor::Black);
286 match visitor(TraversalEvent::Finish(neighbor)) {
287 Control::Break(b) => return Some(b),
288 _ => {}
289 }
290 continue;
291 }
292 Control::Continue => {}
293 }
294
295 let neighbor_neighbors: Vec<_> =
296 store.edges_from(neighbor, Direction::Outgoing).collect();
297 stack.push((neighbor, neighbor_neighbors, 0));
298 }
299 NodeColor::Gray => {
300 match visitor(TraversalEvent::BackEdge {
302 source: *node,
303 target: neighbor,
304 edge: edge_id,
305 }) {
306 Control::Break(b) => return Some(b),
307 _ => {}
308 }
309 }
310 NodeColor::Black => {
311 match visitor(TraversalEvent::NonTreeEdge {
313 source: *node,
314 target: neighbor,
315 edge: edge_id,
316 }) {
317 Control::Break(b) => return Some(b),
318 _ => {}
319 }
320 }
321 }
322 }
323
324 None
325}
326
327pub fn dfs_all(store: &LpgStore) -> Vec<NodeId> {
331 let mut finished = Vec::new();
332 let mut visited: FxHashSet<NodeId> = FxHashSet::default();
333
334 for node_id in store.node_ids() {
335 if visited.contains(&node_id) {
336 continue;
337 }
338
339 dfs_with_visitor(store, node_id, |event| -> Control<()> {
340 match event {
341 TraversalEvent::Discover(n) => {
342 visited.insert(n);
343 }
344 TraversalEvent::Finish(n) => {
345 finished.push(n);
346 }
347 _ => {}
348 }
349 Control::Continue
350 });
351 }
352
353 finished
354}
355
356static BFS_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
362
363fn bfs_params() -> &'static [ParameterDef] {
364 BFS_PARAMS.get_or_init(|| {
365 vec![ParameterDef {
366 name: "start".to_string(),
367 description: "Starting node ID".to_string(),
368 param_type: ParameterType::NodeId,
369 required: true,
370 default: None,
371 }]
372 })
373}
374
375pub struct BfsAlgorithm;
377
378impl GraphAlgorithm for BfsAlgorithm {
379 fn name(&self) -> &str {
380 "bfs"
381 }
382
383 fn description(&self) -> &str {
384 "Breadth-first search traversal from a starting node"
385 }
386
387 fn parameters(&self) -> &[ParameterDef] {
388 bfs_params()
389 }
390
391 fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
392 let start_id = params.get_int("start").ok_or_else(|| {
393 grafeo_common::utils::error::Error::InvalidValue("start parameter required".to_string())
394 })?;
395
396 let start = NodeId::new(start_id as u64);
397 let layers = bfs_layers(store, start);
398
399 let mut result = AlgorithmResult::new(vec!["node_id".to_string(), "distance".to_string()]);
400
401 for (distance, layer) in layers.iter().enumerate() {
402 for &node in layer {
403 result.add_row(vec![
404 Value::Int64(node.0 as i64),
405 Value::Int64(distance as i64),
406 ]);
407 }
408 }
409
410 Ok(result)
411 }
412}
413
414static DFS_PARAMS: OnceLock<Vec<ParameterDef>> = OnceLock::new();
416
417fn dfs_params() -> &'static [ParameterDef] {
418 DFS_PARAMS.get_or_init(|| {
419 vec![ParameterDef {
420 name: "start".to_string(),
421 description: "Starting node ID".to_string(),
422 param_type: ParameterType::NodeId,
423 required: true,
424 default: None,
425 }]
426 })
427}
428
429pub struct DfsAlgorithm;
431
432impl GraphAlgorithm for DfsAlgorithm {
433 fn name(&self) -> &str {
434 "dfs"
435 }
436
437 fn description(&self) -> &str {
438 "Depth-first search traversal from a starting node"
439 }
440
441 fn parameters(&self) -> &[ParameterDef] {
442 dfs_params()
443 }
444
445 fn execute(&self, store: &LpgStore, params: &Parameters) -> Result<AlgorithmResult> {
446 let start_id = params.get_int("start").ok_or_else(|| {
447 grafeo_common::utils::error::Error::InvalidValue("start parameter required".to_string())
448 })?;
449
450 let start = NodeId::new(start_id as u64);
451 let finished = dfs(store, start);
452
453 let mut builder = NodeValueResultBuilder::with_capacity("finish_order", finished.len());
454 for (order, node) in finished.iter().enumerate() {
455 builder.push(*node, Value::Int64(order as i64));
456 }
457
458 Ok(builder.build())
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465
466 fn create_test_graph() -> LpgStore {
467 let store = LpgStore::new();
468
469 let n0 = store.create_node(&["Node"]);
475 let n1 = store.create_node(&["Node"]);
476 let n2 = store.create_node(&["Node"]);
477 let n3 = store.create_node(&["Node"]);
478 let n4 = store.create_node(&["Node"]);
479
480 store.create_edge(n0, n1, "EDGE");
481 store.create_edge(n0, n3, "EDGE");
482 store.create_edge(n1, n2, "EDGE");
483 store.create_edge(n1, n4, "EDGE");
484 store.create_edge(n3, n4, "EDGE");
485
486 store
487 }
488
489 #[test]
490 fn test_bfs_simple() {
491 let store = create_test_graph();
492 let visited = bfs(&store, NodeId::new(0));
493
494 assert!(!visited.is_empty());
495 assert_eq!(visited[0], NodeId::new(0));
496 }
498
499 #[test]
500 fn test_bfs_layers() {
501 let store = create_test_graph();
502 let layers = bfs_layers(&store, NodeId::new(0));
503
504 assert!(!layers.is_empty());
505 assert_eq!(layers[0], vec![NodeId::new(0)]);
506 }
508
509 #[test]
510 fn test_dfs_simple() {
511 let store = create_test_graph();
512 let finished = dfs(&store, NodeId::new(0));
513
514 assert!(!finished.is_empty());
515 }
517
518 #[test]
519 fn test_bfs_nonexistent_start() {
520 let store = LpgStore::new();
521 let visited = bfs(&store, NodeId::new(999));
522 assert!(visited.is_empty());
523 }
524
525 #[test]
526 fn test_dfs_nonexistent_start() {
527 let store = LpgStore::new();
528 let finished = dfs(&store, NodeId::new(999));
529 assert!(finished.is_empty());
530 }
531
532 #[test]
533 fn test_bfs_early_termination() {
534 let store = create_test_graph();
535 let target = NodeId::new(2);
536
537 let found = bfs_with_visitor(&store, NodeId::new(0), |event| {
538 if let TraversalEvent::Discover(node) = event
539 && node == target
540 {
541 return Control::Break(true);
542 }
543 Control::Continue
544 });
545
546 assert_eq!(found, Some(true));
547 }
548
549 #[test]
550 fn test_bfs_visits_all_reachable() {
551 let store = create_test_graph();
552 let visited = bfs(&store, NodeId::new(0));
553 assert_eq!(visited.len(), 5);
555 }
556
557 #[test]
558 fn test_bfs_layers_distances() {
559 let store = create_test_graph();
560 let layers = bfs_layers(&store, NodeId::new(0));
561
562 assert_eq!(layers.len(), 3);
566 assert_eq!(layers[0].len(), 1);
567 assert_eq!(layers[1].len(), 2);
568 assert_eq!(layers[2].len(), 2);
569 }
570
571 #[test]
572 fn test_bfs_layers_empty_graph() {
573 let store = LpgStore::new();
574 let layers = bfs_layers(&store, NodeId::new(0));
575 assert!(layers.is_empty());
576 }
577
578 #[test]
579 fn test_bfs_single_node() {
580 let store = LpgStore::new();
581 let n0 = store.create_node(&["Node"]);
582 let visited = bfs(&store, n0);
583 assert_eq!(visited, vec![n0]);
584 }
585
586 #[test]
587 fn test_bfs_layers_single_node() {
588 let store = LpgStore::new();
589 let n0 = store.create_node(&["Node"]);
590 let layers = bfs_layers(&store, n0);
591 assert_eq!(layers.len(), 1);
592 assert_eq!(layers[0], vec![n0]);
593 }
594
595 #[test]
596 fn test_bfs_with_visitor_prune_on_start() {
597 let store = create_test_graph();
598 let result: Option<()> = bfs_with_visitor(&store, NodeId::new(0), |event| {
599 if let TraversalEvent::Discover(node) = event
600 && node == NodeId::new(0)
601 {
602 return Control::Prune;
603 }
604 Control::Continue
605 });
606 assert!(result.is_none());
608 }
609
610 #[test]
611 fn test_bfs_with_visitor_collects_tree_edges() {
612 let store = create_test_graph();
613 let mut tree_edges = Vec::new();
614
615 bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
616 if let TraversalEvent::TreeEdge { source, target, .. } = event {
617 tree_edges.push((source, target));
618 }
619 Control::Continue
620 });
621
622 assert_eq!(tree_edges.len(), 4);
624 }
625
626 #[test]
627 fn test_bfs_with_visitor_detects_non_tree_edges() {
628 let store = create_test_graph();
629 let mut non_tree_edges = Vec::new();
630
631 bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
632 if let TraversalEvent::NonTreeEdge { source, target, .. } = event {
633 non_tree_edges.push((source, target));
634 }
635 Control::Continue
636 });
637
638 assert!(!non_tree_edges.is_empty());
640 }
641
642 #[test]
643 fn test_dfs_visits_all_reachable() {
644 let store = create_test_graph();
645 let finished = dfs(&store, NodeId::new(0));
646 assert_eq!(finished.len(), 5);
647 }
648
649 #[test]
650 fn test_dfs_post_order() {
651 let store = create_test_graph();
652 let finished = dfs(&store, NodeId::new(0));
653 assert_eq!(*finished.last().unwrap(), NodeId::new(0));
655 }
656
657 #[test]
658 fn test_dfs_with_visitor_early_termination() {
659 let store = create_test_graph();
660 let found = dfs_with_visitor(&store, NodeId::new(0), |event| {
661 if let TraversalEvent::Discover(node) = event
662 && node == NodeId::new(4)
663 {
664 return Control::Break(node);
665 }
666 Control::Continue
667 });
668 assert_eq!(found, Some(NodeId::new(4)));
669 }
670
671 #[test]
672 fn test_dfs_with_visitor_prune() {
673 let store = create_test_graph();
674 let mut discovered = Vec::new();
675
676 dfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
677 if let TraversalEvent::Discover(node) = event {
678 discovered.push(node);
679 if node == NodeId::new(1) {
680 return Control::Prune; }
682 }
683 Control::Continue
684 });
685
686 assert!(discovered.contains(&NodeId::new(0)));
688 assert!(discovered.contains(&NodeId::new(1)));
689 assert!(!discovered.contains(&NodeId::new(2)));
691 }
692
693 #[test]
694 fn test_dfs_with_visitor_back_edge() {
695 let store = LpgStore::new();
697 let n0 = store.create_node(&["Node"]);
698 let n1 = store.create_node(&["Node"]);
699 let n2 = store.create_node(&["Node"]);
700 store.create_edge(n0, n1, "EDGE");
701 store.create_edge(n1, n2, "EDGE");
702 store.create_edge(n2, n0, "EDGE");
703
704 let mut back_edges = Vec::new();
705 dfs_with_visitor(&store, n0, |event| -> Control<()> {
706 if let TraversalEvent::BackEdge { source, target, .. } = event {
707 back_edges.push((source, target));
708 }
709 Control::Continue
710 });
711
712 assert_eq!(back_edges.len(), 1);
714 assert_eq!(back_edges[0], (n2, n0));
715 }
716
717 #[test]
718 fn test_dfs_single_node() {
719 let store = LpgStore::new();
720 let n0 = store.create_node(&["Node"]);
721 let finished = dfs(&store, n0);
722 assert_eq!(finished, vec![n0]);
723 }
724
725 #[test]
726 fn test_dfs_all_visits_all_components() {
727 let store = LpgStore::new();
728 let n0 = store.create_node(&["Node"]);
730 let n1 = store.create_node(&["Node"]);
731 store.create_edge(n0, n1, "EDGE");
732
733 let n2 = store.create_node(&["Node"]);
735 let n3 = store.create_node(&["Node"]);
736 store.create_edge(n2, n3, "EDGE");
737
738 let finished = dfs_all(&store);
739 assert_eq!(finished.len(), 4);
740 }
741
742 #[test]
743 fn test_dfs_all_empty_graph() {
744 let store = LpgStore::new();
745 let finished = dfs_all(&store);
746 assert!(finished.is_empty());
747 }
748
749 #[test]
750 fn test_bfs_prune_tree_edge() {
751 let store = create_test_graph();
752 let mut discovered = Vec::new();
753
754 bfs_with_visitor(&store, NodeId::new(0), |event| -> Control<()> {
755 match event {
756 TraversalEvent::TreeEdge { target, .. } => {
757 if target == NodeId::new(1) {
758 return Control::Prune; }
760 Control::Continue
761 }
762 TraversalEvent::Discover(node) => {
763 discovered.push(node);
764 Control::Continue
765 }
766 _ => Control::Continue,
767 }
768 });
769
770 assert!(discovered.contains(&NodeId::new(0)));
772 assert!(!discovered.contains(&NodeId::new(1)));
773 }
774
775 #[test]
776 fn test_dfs_with_visitor_prune_start() {
777 let store = create_test_graph();
778 let result: Option<()> = dfs_with_visitor(&store, NodeId::new(0), |event| {
779 if let TraversalEvent::Discover(node) = event
780 && node == NodeId::new(0)
781 {
782 return Control::Prune;
783 }
784 Control::Continue
785 });
786 assert!(result.is_none());
787 }
788}