omena_query/style/cross_file_hypergraph/
reachability.rs1use std::collections::{BTreeMap, BTreeSet, VecDeque};
2
3use super::{HypergraphIFDSSummaryEdgeV0, UnifiedHypergraphHyperedgeV0};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub(in crate::style) struct HypergraphClosurePath<N> {
7 pub origin: N,
8 pub target: N,
9 pub depth: usize,
10 pub path_labels: Vec<String>,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub(in crate::style) enum HypergraphClosureMode {
15 CanonicalFirstTarget,
16 RawAllPaths,
17}
18
19pub trait OmenaUnifiedHypergraphConnectivityOracle {
20 fn reachable_node_ids(
21 &self,
22 start_node_id: &str,
23 hyperedges: &[UnifiedHypergraphHyperedgeV0],
24 ) -> Vec<String>;
25}
26
27#[derive(Debug, Clone, Copy, Default)]
28pub struct BatchHypergraphConnectivityOracle;
29
30impl OmenaUnifiedHypergraphConnectivityOracle for BatchHypergraphConnectivityOracle {
31 fn reachable_node_ids(
32 &self,
33 start_node_id: &str,
34 hyperedges: &[UnifiedHypergraphHyperedgeV0],
35 ) -> Vec<String> {
36 let adjacency = build_adjacency(hyperedges);
37 let mut seen = BTreeSet::new();
38 let mut pending = VecDeque::from([start_node_id.to_string()]);
39 while let Some(current) = pending.pop_front() {
40 for target in adjacency.get(current.as_str()).into_iter().flatten() {
41 if seen.insert(target.clone()) {
42 pending.push_back(target.clone());
43 }
44 }
45 }
46 seen.into_iter().collect()
47 }
48}
49
50pub fn tabulate_hypergraph_ifds_summary_edges(
51 hyperedges: &[UnifiedHypergraphHyperedgeV0],
52 projected_edges: Vec<HypergraphIFDSSummaryEdgeV0>,
53) -> Vec<HypergraphIFDSSummaryEdgeV0> {
54 let hyperedge_ids = hyperedges
55 .iter()
56 .map(|edge| edge.hyperedge_id.as_str())
57 .collect::<BTreeSet<_>>();
58 let mut edges = projected_edges
59 .into_iter()
60 .filter(|edge| hyperedge_ids.contains(edge.hyperedge_id.as_str()))
61 .collect::<Vec<_>>();
62 edges.sort_by(|left, right| {
63 left.projection_edge_id
64 .cmp(&right.projection_edge_id)
65 .then(left.hyperedge_id.cmp(&right.hyperedge_id))
66 });
67 edges
68}
69
70pub(in crate::style) fn collect_hypergraph_transitive_closure_paths<N, F>(
71 graph: &BTreeMap<N, BTreeSet<N>>,
72 mut label: F,
73) -> (Vec<HypergraphClosurePath<N>>, Vec<Vec<String>>)
74where
75 N: Clone + Ord,
76 F: FnMut(&N) -> String,
77{
78 collect_hypergraph_transitive_closure_paths_with_mode(
79 graph,
80 &mut label,
81 HypergraphClosureMode::CanonicalFirstTarget,
82 )
83}
84
85pub(in crate::style) fn collect_hypergraph_transitive_closure_paths_with_mode<N, F>(
86 graph: &BTreeMap<N, BTreeSet<N>>,
87 label: &mut F,
88 mode: HypergraphClosureMode,
89) -> (Vec<HypergraphClosurePath<N>>, Vec<Vec<String>>)
90where
91 N: Clone + Ord,
92 F: FnMut(&N) -> String,
93{
94 let mut closure_paths = Vec::new();
95 let mut cycle_paths = Vec::new();
96 let mut seen_cycles = BTreeSet::new();
97 let first_target = mode == HypergraphClosureMode::CanonicalFirstTarget;
98
99 for start in graph.keys() {
100 let mut visited = BTreeSet::new();
101 let mut pending = VecDeque::from([(start.clone(), vec![start.clone()])]);
102 while let Some((current, path)) = pending.pop_front() {
103 for target in graph.get(¤t).into_iter().flatten() {
104 if let Some(cycle_start) = path.iter().position(|node| node == target) {
105 let mut cycle = path[cycle_start..].to_vec();
106 cycle.push(target.clone());
107 let mut labels = cycle.iter().map(&mut *label).collect::<Vec<_>>();
108 if first_target {
109 labels = canonical_hypergraph_cycle_labels(labels);
110 }
111 if !labels.is_empty() && seen_cycles.insert(labels.clone()) {
112 cycle_paths.push(labels);
113 }
114 continue;
115 }
116 if first_target && !visited.insert(target.clone()) {
117 continue;
118 }
119 let mut edge_path = path.clone();
120 edge_path.push(target.clone());
121 closure_paths.push(HypergraphClosurePath {
122 origin: start.clone(),
123 target: target.clone(),
124 depth: edge_path.len().saturating_sub(1),
125 path_labels: edge_path.iter().map(&mut *label).collect(),
126 });
127 pending.push_back((target.clone(), edge_path));
128 }
129 }
130 }
131 (closure_paths, cycle_paths)
132}
133
134fn canonical_hypergraph_cycle_labels(mut labels: Vec<String>) -> Vec<String> {
135 if labels.len() > 1 && labels.first() == labels.last() {
136 labels.pop();
137 }
138 if labels.is_empty() {
139 return labels;
140 }
141 let mut best = labels.clone();
142 for offset in 1..labels.len() {
143 let mut rotated = labels[offset..].to_vec();
144 rotated.extend_from_slice(&labels[..offset]);
145 best = best.min(rotated);
146 }
147 best.push(best[0].clone());
148 best
149}
150
151fn build_adjacency(
152 hyperedges: &[UnifiedHypergraphHyperedgeV0],
153) -> BTreeMap<&str, BTreeSet<String>> {
154 let mut adjacency = BTreeMap::<&str, BTreeSet<String>>::new();
155 for edge in hyperedges {
156 for tail in &edge.tail_node_ids {
157 adjacency
158 .entry(tail.as_str())
159 .or_default()
160 .insert(edge.head_node_id.clone());
161 }
162 }
163 adjacency
164}