1use crate::algorithms::astar::CfgGraphNode;
18use std::collections::{HashMap, HashSet};
19
20#[derive(Debug, Clone)]
22pub struct ReachabilityResult {
23 pub reachable: HashMap<u64, HashSet<u64>>,
25 pub total_pairs: usize,
27}
28
29pub fn transitive_closure(nodes: &[CfgGraphNode]) -> ReachabilityResult {
40 let node_map: HashMap<u64, &CfgGraphNode> = nodes.iter().map(|n| (n.id, n)).collect();
41 let mut reachable: HashMap<u64, HashSet<u64>> = HashMap::new();
42 let mut total_pairs = 0;
43
44 for node in nodes {
45 let mut reachable_from_node: HashSet<u64> = HashSet::new();
46 let mut worklist = vec![node.id];
47
48 while let Some(current) = worklist.pop() {
49 if let Some(current_node) = node_map.get(¤t) {
50 for &succ in ¤t_node.successors {
51 if !reachable_from_node.contains(&succ) {
52 reachable_from_node.insert(succ);
53 worklist.push(succ);
54 }
55 }
56 }
57 }
58
59 total_pairs += reachable_from_node.len();
60 reachable.insert(node.id, reachable_from_node);
61 }
62
63 ReachabilityResult {
64 reachable,
65 total_pairs,
66 }
67}
68
69pub fn transitive_reduction(nodes: &[CfgGraphNode]) -> Vec<(u64, u64)> {
83 let closure = transitive_closure(nodes);
84 let mut keep_edges: Vec<(u64, u64)> = Vec::new();
85
86 for node in nodes {
87 for &succ in &node.successors {
88 let mut has_alternative_path = false;
90
91 for &other_succ in &node.successors {
92 if other_succ != succ {
93 if let Some(other_reachable) = closure.reachable.get(&other_succ) {
95 if other_reachable.contains(&succ) {
96 has_alternative_path = true;
97 break;
98 }
99 }
100 }
101 }
102
103 if !has_alternative_path {
104 keep_edges.push((node.id, succ));
105 }
106 }
107 }
108
109 keep_edges
110}
111
112pub fn is_reachable(from: u64, to: u64, closure: &ReachabilityResult) -> bool {
114 closure
115 .reachable
116 .get(&from)
117 .map(|r| r.contains(&to))
118 .unwrap_or(false)
119}
120
121pub fn get_reachable_from(node: u64, closure: &ReachabilityResult) -> HashSet<u64> {
123 closure.reachable.get(&node).cloned().unwrap_or_default()
124}
125
126pub fn get_reachable_to(node: u64, closure: &ReachabilityResult) -> HashSet<u64> {
128 let mut result = HashSet::new();
129 for (&from, reachable) in &closure.reachable {
130 if reachable.contains(&node) {
131 result.insert(from);
132 }
133 }
134 result
135}
136
137pub fn count_ancestors(node: u64, closure: &ReachabilityResult) -> usize {
139 get_reachable_to(node, closure).len()
140}
141
142pub fn count_descendants(node: u64, closure: &ReachabilityResult) -> usize {
144 get_reachable_from(node, closure).len()
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_transitive_closure_linear() {
153 let nodes = vec![
155 CfgGraphNode {
156 id: 0,
157 x: 0.0,
158 y: 0.0,
159 z: 0.0,
160 successors: vec![1],
161 },
162 CfgGraphNode {
163 id: 1,
164 x: 1.0,
165 y: 0.0,
166 z: 0.0,
167 successors: vec![2],
168 },
169 CfgGraphNode {
170 id: 2,
171 x: 2.0,
172 y: 0.0,
173 z: 0.0,
174 successors: vec![3],
175 },
176 CfgGraphNode {
177 id: 3,
178 x: 3.0,
179 y: 0.0,
180 z: 0.0,
181 successors: vec![],
182 },
183 ];
184
185 let result = transitive_closure(&nodes);
186
187 assert_eq!(result.reachable[&0].len(), 3);
188 assert!(result.reachable[&0].contains(&1));
189 assert!(result.reachable[&0].contains(&3));
190 assert_eq!(result.reachable[&3].len(), 0);
191 }
192
193 #[test]
194 fn test_transitive_closure_branching() {
195 let nodes = vec![
197 CfgGraphNode {
198 id: 0,
199 x: 0.0,
200 y: 0.0,
201 z: 0.0,
202 successors: vec![1, 2],
203 },
204 CfgGraphNode {
205 id: 1,
206 x: 1.0,
207 y: 0.0,
208 z: 0.0,
209 successors: vec![3],
210 },
211 CfgGraphNode {
212 id: 2,
213 x: 2.0,
214 y: 0.0,
215 z: 0.0,
216 successors: vec![3],
217 },
218 CfgGraphNode {
219 id: 3,
220 x: 3.0,
221 y: 0.0,
222 z: 0.0,
223 successors: vec![],
224 },
225 ];
226
227 let result = transitive_closure(&nodes);
228
229 assert_eq!(result.reachable[&0].len(), 3);
230 assert!(result.reachable[&0].contains(&1));
231 assert!(result.reachable[&0].contains(&2));
232 assert!(result.reachable[&0].contains(&3));
233 }
234
235 #[test]
236 fn test_transitive_reduction_linear() {
237 let nodes = vec![
239 CfgGraphNode {
240 id: 0,
241 x: 0.0,
242 y: 0.0,
243 z: 0.0,
244 successors: vec![1],
245 },
246 CfgGraphNode {
247 id: 1,
248 x: 1.0,
249 y: 0.0,
250 z: 0.0,
251 successors: vec![2],
252 },
253 CfgGraphNode {
254 id: 2,
255 x: 2.0,
256 y: 0.0,
257 z: 0.0,
258 successors: vec![3],
259 },
260 CfgGraphNode {
261 id: 3,
262 x: 3.0,
263 y: 0.0,
264 z: 0.0,
265 successors: vec![],
266 },
267 ];
268
269 let edges = transitive_reduction(&nodes);
270 assert_eq!(edges.len(), 3); }
272
273 #[test]
274 fn test_transitive_reduction_with_redundant() {
275 let nodes = vec![
277 CfgGraphNode {
278 id: 0,
279 x: 0.0,
280 y: 0.0,
281 z: 0.0,
282 successors: vec![1, 2],
283 },
284 CfgGraphNode {
285 id: 1,
286 x: 1.0,
287 y: 0.0,
288 z: 0.0,
289 successors: vec![2],
290 },
291 CfgGraphNode {
292 id: 2,
293 x: 2.0,
294 y: 0.0,
295 z: 0.0,
296 successors: vec![],
297 },
298 ];
299
300 let edges = transitive_reduction(&nodes);
301 assert_eq!(edges.len(), 2); assert!(edges.contains(&(0, 1)));
303 assert!(edges.contains(&(1, 2)));
304 }
305
306 #[test]
307 fn test_is_reachable() {
308 let nodes = vec![
309 CfgGraphNode {
310 id: 0,
311 x: 0.0,
312 y: 0.0,
313 z: 0.0,
314 successors: vec![1],
315 },
316 CfgGraphNode {
317 id: 1,
318 x: 1.0,
319 y: 0.0,
320 z: 0.0,
321 successors: vec![2],
322 },
323 CfgGraphNode {
324 id: 2,
325 x: 2.0,
326 y: 0.0,
327 z: 0.0,
328 successors: vec![],
329 },
330 ];
331
332 let closure = transitive_closure(&nodes);
333
334 assert!(is_reachable(0, 2, &closure));
335 assert!(is_reachable(0, 1, &closure));
336 assert!(!is_reachable(2, 0, &closure));
337 }
338
339 #[test]
340 fn test_count_ancestors_descendants() {
341 let nodes = vec![
342 CfgGraphNode {
343 id: 0,
344 x: 0.0,
345 y: 0.0,
346 z: 0.0,
347 successors: vec![1, 2],
348 },
349 CfgGraphNode {
350 id: 1,
351 x: 1.0,
352 y: 0.0,
353 z: 0.0,
354 successors: vec![3],
355 },
356 CfgGraphNode {
357 id: 2,
358 x: 2.0,
359 y: 0.0,
360 z: 0.0,
361 successors: vec![3],
362 },
363 CfgGraphNode {
364 id: 3,
365 x: 3.0,
366 y: 0.0,
367 z: 0.0,
368 successors: vec![],
369 },
370 ];
371
372 let closure = transitive_closure(&nodes);
373
374 assert_eq!(count_descendants(0, &closure), 3);
375 assert_eq!(count_ancestors(3, &closure), 3);
376 assert_eq!(count_descendants(3, &closure), 0);
377 }
378
379 #[test]
380 fn test_get_reachable_from() {
381 let nodes = vec![
382 CfgGraphNode {
383 id: 0,
384 x: 0.0,
385 y: 0.0,
386 z: 0.0,
387 successors: vec![1, 2],
388 },
389 CfgGraphNode {
390 id: 1,
391 x: 1.0,
392 y: 0.0,
393 z: 0.0,
394 successors: vec![],
395 },
396 CfgGraphNode {
397 id: 2,
398 x: 2.0,
399 y: 0.0,
400 z: 0.0,
401 successors: vec![],
402 },
403 ];
404
405 let closure = transitive_closure(&nodes);
406 let reachable = get_reachable_from(0, &closure);
407
408 assert_eq!(reachable.len(), 2);
409 assert!(reachable.contains(&1));
410 assert!(reachable.contains(&2));
411 }
412
413 #[test]
414 fn test_total_pairs() {
415 let nodes = vec![
416 CfgGraphNode {
417 id: 0,
418 x: 0.0,
419 y: 0.0,
420 z: 0.0,
421 successors: vec![1],
422 },
423 CfgGraphNode {
424 id: 1,
425 x: 1.0,
426 y: 0.0,
427 z: 0.0,
428 successors: vec![2],
429 },
430 CfgGraphNode {
431 id: 2,
432 x: 2.0,
433 y: 0.0,
434 z: 0.0,
435 successors: vec![],
436 },
437 ];
438
439 let result = transitive_closure(&nodes);
440
441 assert_eq!(result.total_pairs, 3);
443 }
444}