1use fxhash::{FxHashMap, FxHashSet};
2use std::ops::ControlFlow;
3use uni_common::core::id::{Eid, Vid};
4
5use super::nfa::{NfaStateId, PathMode, PathSelector};
6
7#[derive(Debug, Clone)]
13pub struct PredRec {
14 pub src_vid: Vid,
15 pub src_state: NfaStateId,
16 pub eid: Eid,
17 pub next: i32,
19}
20
21pub struct PredecessorDag {
27 pred_pool: Vec<PredRec>,
29
30 pred_head: FxHashMap<(Vid, NfaStateId, u32), i32>,
33
34 first_depth: FxHashMap<(Vid, NfaStateId), u32>,
36
37 selector: PathSelector,
39}
40
41impl PredecessorDag {
42 pub fn new(selector: PathSelector) -> Self {
44 Self {
45 pred_pool: Vec::new(),
46 pred_head: FxHashMap::default(),
47 first_depth: FxHashMap::default(),
48 selector,
49 }
50 }
51
52 pub fn is_layered(&self) -> bool {
54 matches!(self.selector, PathSelector::All | PathSelector::Any)
55 }
56
57 pub fn add_predecessor(
63 &mut self,
64 dst: Vid,
65 dst_state: NfaStateId,
66 src: Vid,
67 src_state: NfaStateId,
68 eid: Eid,
69 depth: u32,
70 ) {
71 let first = self.first_depth.entry((dst, dst_state)).or_insert(depth);
73 if depth < *first {
74 *first = depth;
75 }
76
77 if !self.is_layered() && depth > *self.first_depth.get(&(dst, dst_state)).unwrap() {
79 return;
80 }
81
82 let key = (dst, dst_state, depth);
84 let current_head = self.pred_head.get(&key).copied().unwrap_or(-1);
85
86 let new_idx = self.pred_pool.len() as i32;
88 self.pred_pool.push(PredRec {
89 src_vid: src,
90 src_state,
91 eid,
92 next: current_head,
93 });
94
95 self.pred_head.insert(key, new_idx);
97 }
98
99 #[expect(
105 clippy::too_many_arguments,
106 reason = "path enumeration requires full traversal context"
107 )]
108 pub fn enumerate_paths<F>(
109 &self,
110 source: Vid,
111 target: Vid,
112 accepting_state: NfaStateId,
113 min_depth: u32,
114 max_depth: u32,
115 mode: &PathMode,
116 yield_path: &mut F,
117 ) where
118 F: FnMut(&[Vid], &[Eid]) -> ControlFlow<()>,
119 {
120 for depth in min_depth..=max_depth {
121 if depth == 0 {
123 if source == target && yield_path(&[source], &[]).is_break() {
124 return;
125 }
126 continue;
127 }
128
129 if !self
130 .pred_head
131 .contains_key(&(target, accepting_state, depth))
132 {
133 continue;
134 }
135
136 let mut nodes = Vec::with_capacity(depth as usize + 1);
137 let mut edges = Vec::with_capacity(depth as usize);
138 let mut node_set = FxHashSet::default();
139 let mut edge_set = FxHashSet::default();
140
141 nodes.push(target);
143 if matches!(mode, PathMode::Acyclic | PathMode::Simple) {
144 node_set.insert(target);
145 }
146
147 if self
148 .dfs_backward(
149 source,
150 target,
151 accepting_state,
152 depth,
153 &mut nodes,
154 &mut edges,
155 &mut node_set,
156 &mut edge_set,
157 mode,
158 yield_path,
159 )
160 .is_break()
161 {
162 return;
163 }
164 }
165 }
166
167 pub fn has_trail_valid_path(
171 &self,
172 source: Vid,
173 target: Vid,
174 accepting_state: NfaStateId,
175 min_depth: u32,
176 max_depth: u32,
177 ) -> bool {
178 let mut found = false;
179 self.enumerate_paths(
180 source,
181 target,
182 accepting_state,
183 min_depth,
184 max_depth,
185 &PathMode::Trail,
186 &mut |_nodes, _edges| {
187 found = true;
188 ControlFlow::Break(())
189 },
190 );
191 found
192 }
193
194 #[expect(
196 clippy::too_many_arguments,
197 reason = "recursive DFS carries full path state"
198 )]
199 fn dfs_backward<F>(
200 &self,
201 source: Vid,
202 current_vid: Vid,
203 current_state: NfaStateId,
204 remaining_depth: u32,
205 nodes: &mut Vec<Vid>,
206 edges: &mut Vec<Eid>,
207 node_set: &mut FxHashSet<Vid>,
208 edge_set: &mut FxHashSet<Eid>,
209 mode: &PathMode,
210 yield_path: &mut F,
211 ) -> ControlFlow<()>
212 where
213 F: FnMut(&[Vid], &[Eid]) -> ControlFlow<()>,
214 {
215 if remaining_depth == 0 {
216 if current_vid == source {
217 let fwd_nodes: Vec<Vid> = nodes.iter().rev().copied().collect();
219 let fwd_edges: Vec<Eid> = edges.iter().rev().copied().collect();
220 return yield_path(&fwd_nodes, &fwd_edges);
221 }
222 return ControlFlow::Continue(());
223 }
224
225 let key = (current_vid, current_state, remaining_depth);
226 let Some(&head) = self.pred_head.get(&key) else {
227 return ControlFlow::Continue(());
228 };
229
230 let mut idx = head;
231 while idx >= 0 {
232 let pred = &self.pred_pool[idx as usize];
233
234 let skip = match mode {
236 PathMode::Walk => false,
237 PathMode::Trail => edge_set.contains(&pred.eid),
238 PathMode::Acyclic => node_set.contains(&pred.src_vid),
239 PathMode::Simple => {
240 node_set.contains(&pred.src_vid)
242 && !(remaining_depth == 1 && pred.src_vid == source)
243 }
244 };
245
246 if skip {
247 idx = pred.next;
248 continue;
249 }
250
251 nodes.push(pred.src_vid);
253 edges.push(pred.eid);
254
255 if matches!(mode, PathMode::Trail) {
256 edge_set.insert(pred.eid);
257 }
258 if matches!(mode, PathMode::Acyclic | PathMode::Simple) {
259 node_set.insert(pred.src_vid);
260 }
261
262 let result = self.dfs_backward(
264 source,
265 pred.src_vid,
266 pred.src_state,
267 remaining_depth - 1,
268 nodes,
269 edges,
270 node_set,
271 edge_set,
272 mode,
273 yield_path,
274 );
275
276 nodes.pop();
278 edges.pop();
279
280 if matches!(mode, PathMode::Trail) {
281 edge_set.remove(&pred.eid);
282 }
283 if matches!(mode, PathMode::Acyclic | PathMode::Simple) {
284 node_set.remove(&pred.src_vid);
285 }
286
287 if result.is_break() {
288 return ControlFlow::Break(());
289 }
290
291 idx = pred.next;
292 }
293
294 ControlFlow::Continue(())
295 }
296
297 pub fn pool_len(&self) -> usize {
299 self.pred_pool.len()
300 }
301
302 pub fn first_depth_of(&self, vid: Vid, state: NfaStateId) -> Option<u32> {
304 self.first_depth.get(&(vid, state)).copied()
305 }
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311
312 fn vid(n: u64) -> Vid {
313 Vid::new(n)
314 }
315 fn eid(n: u64) -> Eid {
316 Eid::new(n)
317 }
318
319 fn collect_paths(
321 dag: &PredecessorDag,
322 source: Vid,
323 target: Vid,
324 accepting_state: NfaStateId,
325 min_depth: u32,
326 max_depth: u32,
327 mode: &PathMode,
328 ) -> Vec<(Vec<Vid>, Vec<Eid>)> {
329 let mut paths = Vec::new();
330 dag.enumerate_paths(
331 source,
332 target,
333 accepting_state,
334 min_depth,
335 max_depth,
336 mode,
337 &mut |nodes, edges| {
338 paths.push((nodes.to_vec(), edges.to_vec()));
339 ControlFlow::Continue(())
340 },
341 );
342 paths
343 }
344
345 #[test]
348 fn test_pred_dag_add_single() {
349 let mut dag = PredecessorDag::new(PathSelector::All);
350 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(10), 1);
351 assert_eq!(dag.pool_len(), 1);
352 assert!(dag.pred_head.contains_key(&(vid(2), 1, 1)));
353 }
354
355 #[test]
356 fn test_pred_dag_add_chain() {
357 let mut dag = PredecessorDag::new(PathSelector::All);
359 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
360 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(11), 2);
361 assert_eq!(dag.pool_len(), 2);
362 assert!(dag.pred_head.contains_key(&(vid(1), 1, 1)));
363 assert!(dag.pred_head.contains_key(&(vid(2), 2, 2)));
364 }
365
366 #[test]
367 fn test_pred_dag_multiple_preds() {
368 let mut dag = PredecessorDag::new(PathSelector::All);
370 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 1);
371 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(11), 1);
372 assert_eq!(dag.pool_len(), 2);
373 let head = dag.pred_head[&(vid(2), 1, 1)];
375 assert!(head >= 0);
376 let first = &dag.pred_pool[head as usize];
377 assert!(first.next >= 0); }
379
380 #[test]
381 fn test_pred_dag_first_depth() {
382 let mut dag = PredecessorDag::new(PathSelector::All);
383 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 3);
384 assert_eq!(dag.first_depth_of(vid(2), 1), Some(3));
385
386 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(11), 2);
387 assert_eq!(dag.first_depth_of(vid(2), 1), Some(2));
388
389 dag.add_predecessor(vid(2), 1, vid(3), 0, eid(12), 5);
391 assert_eq!(dag.first_depth_of(vid(2), 1), Some(2));
392 }
393
394 #[test]
397 fn test_pred_dag_layered_stores_all() {
398 let mut dag = PredecessorDag::new(PathSelector::All);
399 assert!(dag.is_layered());
400
401 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 2);
403 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(11), 3);
404 assert_eq!(dag.pool_len(), 2);
405 assert!(dag.pred_head.contains_key(&(vid(2), 1, 2)));
406 assert!(dag.pred_head.contains_key(&(vid(2), 1, 3)));
407 }
408
409 #[test]
410 fn test_pred_dag_shortest_skips() {
411 let mut dag = PredecessorDag::new(PathSelector::AnyShortest);
412 assert!(!dag.is_layered());
413
414 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 2);
416 assert_eq!(dag.pool_len(), 1);
417
418 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(11), 3);
420 assert_eq!(dag.pool_len(), 1); dag.add_predecessor(vid(2), 1, vid(3), 0, eid(12), 2);
424 assert_eq!(dag.pool_len(), 2);
425 }
426
427 #[test]
428 fn test_pred_dag_selector_switch() {
429 let build = |selector: PathSelector| -> usize {
431 let mut dag = PredecessorDag::new(selector);
432 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 2);
433 dag.add_predecessor(vid(2), 1, vid(1), 0, eid(11), 3);
434 dag.add_predecessor(vid(2), 1, vid(3), 0, eid(12), 4);
435 dag.pool_len()
436 };
437
438 assert_eq!(build(PathSelector::All), 3); assert_eq!(build(PathSelector::AnyShortest), 1); }
441
442 #[test]
445 fn test_pred_dag_linear_walk() {
446 let mut dag = PredecessorDag::new(PathSelector::All);
448 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
449 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(11), 2);
450
451 let paths = collect_paths(&dag, vid(0), vid(2), 2, 2, 2, &PathMode::Walk);
452 assert_eq!(paths.len(), 1);
453 assert_eq!(paths[0].0, vec![vid(0), vid(1), vid(2)]);
454 assert_eq!(paths[0].1, vec![eid(10), eid(11)]);
455 }
456
457 #[test]
458 fn test_pred_dag_diamond_walk() {
459 let mut dag = PredecessorDag::new(PathSelector::All);
461 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
463 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(11), 1);
464 dag.add_predecessor(vid(3), 2, vid(1), 1, eid(12), 2);
466 dag.add_predecessor(vid(3), 2, vid(2), 1, eid(13), 2);
467
468 let paths = collect_paths(&dag, vid(0), vid(3), 2, 2, 2, &PathMode::Walk);
469 assert_eq!(paths.len(), 2);
470
471 let mut sorted: Vec<_> = paths.iter().map(|(n, _)| n.clone()).collect();
472 sorted.sort();
473 assert!(sorted.contains(&vec![vid(0), vid(1), vid(3)]));
474 assert!(sorted.contains(&vec![vid(0), vid(2), vid(3)]));
475 }
476
477 #[test]
478 fn test_pred_dag_multiple_depths() {
479 let mut dag = PredecessorDag::new(PathSelector::All);
482 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(10), 1);
484 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(11), 1);
486 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(12), 2);
487
488 let paths1 = collect_paths(&dag, vid(0), vid(2), 1, 1, 1, &PathMode::Walk);
490 assert_eq!(paths1.len(), 1);
491 assert_eq!(paths1[0].0, vec![vid(0), vid(2)]); let paths2 = collect_paths(&dag, vid(0), vid(2), 2, 2, 2, &PathMode::Walk);
495 assert_eq!(paths2.len(), 1);
496 assert_eq!(paths2[0].0, vec![vid(0), vid(1), vid(2)]); assert_eq!(paths1.len() + paths2.len(), 2);
500 }
501
502 #[test]
503 fn test_pred_dag_fan_out() {
504 let mut dag = PredecessorDag::new(PathSelector::All);
506 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
507 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(11), 1);
508 dag.add_predecessor(vid(3), 1, vid(0), 0, eid(12), 1);
509 dag.add_predecessor(vid(4), 2, vid(1), 1, eid(13), 2);
510 dag.add_predecessor(vid(4), 2, vid(2), 1, eid(14), 2);
511 dag.add_predecessor(vid(4), 2, vid(3), 1, eid(15), 2);
512
513 let paths = collect_paths(&dag, vid(0), vid(4), 2, 2, 2, &PathMode::Walk);
514 assert_eq!(paths.len(), 3);
515 }
516
517 #[test]
520 fn test_pred_dag_trail_no_repeat() {
521 let mut dag = PredecessorDag::new(PathSelector::All);
524 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
526 dag.add_predecessor(vid(0), 2, vid(1), 1, eid(2), 2);
528 dag.add_predecessor(vid(1), 3, vid(0), 2, eid(1), 3);
530
531 let walk_paths = collect_paths(&dag, vid(0), vid(1), 3, 3, 3, &PathMode::Walk);
533 assert_eq!(walk_paths.len(), 1);
534 assert_eq!(walk_paths[0].1, vec![eid(1), eid(2), eid(1)]);
535
536 let trail_paths = collect_paths(&dag, vid(0), vid(1), 3, 3, 3, &PathMode::Trail);
538 assert_eq!(trail_paths.len(), 0);
539 }
540
541 #[test]
542 fn test_pred_dag_trail_allows_node_repeat() {
543 let mut dag = PredecessorDag::new(PathSelector::All);
546 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
547 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(2), 2);
548 dag.add_predecessor(vid(1), 3, vid(2), 2, eid(3), 3);
549
550 let paths = collect_paths(&dag, vid(0), vid(1), 3, 3, 3, &PathMode::Trail);
551 assert_eq!(paths.len(), 1);
552 assert_eq!(paths[0].0, vec![vid(0), vid(1), vid(2), vid(1)]);
553 assert_eq!(paths[0].1, vec![eid(1), eid(2), eid(3)]);
554 }
555
556 #[test]
557 fn test_pred_dag_trail_diamond() {
558 let mut dag = PredecessorDag::new(PathSelector::All);
560 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
561 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(11), 1);
562 dag.add_predecessor(vid(3), 2, vid(1), 1, eid(12), 2);
563 dag.add_predecessor(vid(3), 2, vid(2), 1, eid(13), 2);
564
565 let paths = collect_paths(&dag, vid(0), vid(3), 2, 2, 2, &PathMode::Trail);
567 assert_eq!(paths.len(), 2);
568 }
569
570 #[test]
571 fn test_pred_dag_trail_cycle_2_hop() {
572 let mut dag = PredecessorDag::new(PathSelector::All);
577 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
578 dag.add_predecessor(vid(0), 2, vid(1), 1, eid(2), 2);
579
580 let paths = collect_paths(&dag, vid(0), vid(0), 2, 2, 2, &PathMode::Trail);
581 assert_eq!(paths.len(), 1);
582 assert_eq!(paths[0].0, vec![vid(0), vid(1), vid(0)]);
583 assert_eq!(paths[0].1, vec![eid(1), eid(2)]);
584 }
585
586 #[test]
589 fn test_pred_dag_acyclic_filter() {
590 let mut dag = PredecessorDag::new(PathSelector::All);
592 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
593 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(2), 2);
594 dag.add_predecessor(vid(0), 3, vid(2), 2, eid(3), 3);
595
596 let walk_paths = collect_paths(&dag, vid(0), vid(0), 3, 3, 3, &PathMode::Walk);
598 assert_eq!(walk_paths.len(), 1);
599
600 let acyclic_paths = collect_paths(&dag, vid(0), vid(0), 3, 3, 3, &PathMode::Acyclic);
602 assert_eq!(acyclic_paths.len(), 0);
603 }
604
605 #[test]
606 fn test_pred_dag_acyclic_diamond() {
607 let mut dag = PredecessorDag::new(PathSelector::All);
609 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
610 dag.add_predecessor(vid(2), 1, vid(0), 0, eid(11), 1);
611 dag.add_predecessor(vid(3), 2, vid(1), 1, eid(12), 2);
612 dag.add_predecessor(vid(3), 2, vid(2), 1, eid(13), 2);
613
614 let paths = collect_paths(&dag, vid(0), vid(3), 2, 2, 2, &PathMode::Acyclic);
615 assert_eq!(paths.len(), 2);
616 }
617
618 #[test]
621 fn test_has_trail_valid_true() {
622 let mut dag = PredecessorDag::new(PathSelector::All);
624 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
625 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(11), 2);
626
627 assert!(dag.has_trail_valid_path(vid(0), vid(2), 2, 2, 2));
628 }
629
630 #[test]
631 fn test_has_trail_valid_false() {
632 let mut dag = PredecessorDag::new(PathSelector::All);
634 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
635 dag.add_predecessor(vid(0), 2, vid(1), 1, eid(2), 2);
636 dag.add_predecessor(vid(1), 3, vid(0), 2, eid(1), 3); assert!(!dag.has_trail_valid_path(vid(0), vid(1), 3, 3, 3));
639 }
640
641 #[test]
642 fn test_has_trail_valid_one_of_many() {
643 let mut dag = PredecessorDag::new(PathSelector::All);
645 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(1), 1);
647 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(2), 2);
648 dag.add_predecessor(vid(3), 1, vid(0), 0, eid(3), 1);
650 dag.add_predecessor(vid(2), 2, vid(3), 1, eid(4), 2);
651
652 assert!(dag.has_trail_valid_path(vid(0), vid(2), 2, 2, 2));
654 }
655
656 #[test]
659 fn test_pred_dag_early_stop() {
660 let mut dag = PredecessorDag::new(PathSelector::All);
663 for i in 1..=10u64 {
664 dag.add_predecessor(Vid::new(i), 1, vid(0), 0, Eid::new(i), 1);
665 dag.add_predecessor(vid(99), 2, Vid::new(i), 1, Eid::new(100 + i), 2);
666 }
667
668 let mut count = 0;
669 dag.enumerate_paths(
670 vid(0),
671 vid(99),
672 2,
673 2,
674 2,
675 &PathMode::Walk,
676 &mut |_nodes, _edges| {
677 count += 1;
678 if count >= 3 {
679 ControlFlow::Break(())
680 } else {
681 ControlFlow::Continue(())
682 }
683 },
684 );
685 assert_eq!(count, 3); }
687
688 #[test]
689 fn test_pred_dag_empty_enumerate() {
690 let dag = PredecessorDag::new(PathSelector::All);
692 let paths = collect_paths(&dag, vid(0), vid(1), 0, 1, 5, &PathMode::Walk);
693 assert!(paths.is_empty());
694 }
695
696 #[test]
697 fn test_pred_dag_zero_length() {
698 let dag = PredecessorDag::new(PathSelector::All);
700 let paths = collect_paths(&dag, vid(5), vid(5), 0, 0, 0, &PathMode::Walk);
701 assert_eq!(paths.len(), 1);
702 assert_eq!(paths[0].0, vec![vid(5)]);
703 assert!(paths[0].1.is_empty());
704 }
705
706 #[test]
709 fn test_pred_dag_path_order() {
710 let mut dag = PredecessorDag::new(PathSelector::All);
713 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(10), 1);
714 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(20), 2);
715 dag.add_predecessor(vid(3), 3, vid(2), 2, eid(30), 3);
716
717 let paths = collect_paths(&dag, vid(0), vid(3), 3, 3, 3, &PathMode::Walk);
718 assert_eq!(paths.len(), 1);
719 assert_eq!(paths[0].0, vec![vid(0), vid(1), vid(2), vid(3)]);
721 assert_eq!(paths[0].1, vec![eid(10), eid(20), eid(30)]);
722 }
723
724 #[test]
725 fn test_pred_dag_eid_in_path() {
726 let mut dag = PredecessorDag::new(PathSelector::All);
729 dag.add_predecessor(vid(1), 1, vid(0), 0, eid(100), 1);
730 dag.add_predecessor(vid(2), 2, vid(1), 1, eid(200), 2);
731 dag.add_predecessor(vid(3), 3, vid(2), 2, eid(300), 3);
732
733 let paths = collect_paths(&dag, vid(0), vid(3), 3, 3, 3, &PathMode::Walk);
734 assert_eq!(paths.len(), 1);
735
736 let (nodes, edges) = &paths[0];
738 assert_eq!(nodes.len(), edges.len() + 1);
739 assert_eq!(edges[0], eid(100)); assert_eq!(edges[1], eid(200)); assert_eq!(edges[2], eid(300)); }
743}