1use std::collections::HashSet;
6
7use petgraph::graphmap::DiGraphMap;
8use petgraph::visit::{Dfs, Reversed};
9
10use crate::traits::OperationId;
11
12fn concurrent_bubble<OP>(
14 graph: &DiGraphMap<OP, ()>,
15 target: OP,
16 processed: &mut HashSet<OP>,
17) -> HashSet<OP>
18where
19 OP: OperationId + Ord,
20{
21 let mut bubble = HashSet::new();
22 bubble.insert(target);
23
24 concurrent_operations(graph, target)
25 .into_iter()
26 .for_each(|op| {
27 if processed.insert(op) {
28 bubble.extend(concurrent_bubble(graph, op, processed).iter())
29 }
30 });
31
32 bubble
33}
34
35pub fn concurrent_bubbles<OP>(graph: &DiGraphMap<OP, ()>) -> Vec<HashSet<OP>>
37where
38 OP: OperationId + Ord,
39{
40 let mut processed: HashSet<OP> = HashSet::new();
41 let mut bubbles = Vec::new();
42
43 graph.nodes().for_each(|target| {
44 if processed.insert(target) {
45 let bubble = concurrent_bubble(graph, target, &mut processed);
46 if bubble.len() > 1 {
47 bubbles.push(bubble)
48 }
49 }
50 });
51
52 bubbles
53}
54
55fn concurrent_operations<OP>(graph: &DiGraphMap<OP, ()>, target: OP) -> HashSet<OP>
60where
61 OP: OperationId + Ord,
62{
63 let mut successors = HashSet::new();
65 let mut dfs = Dfs::new(&graph, target);
66 while let Some(nx) = dfs.next(&graph) {
67 successors.insert(nx);
68 }
69
70 let mut predecessors = HashSet::new();
72 let reversed = Reversed(graph);
73 let mut dfs_rev = Dfs::new(&reversed, target);
74 while let Some(nx) = dfs_rev.next(&reversed) {
75 predecessors.insert(nx);
76 }
77
78 let relatives: HashSet<_> = successors.union(&predecessors).cloned().collect();
79
80 graph.nodes().filter(|n| !relatives.contains(n)).collect()
82}
83
84pub fn has_path<OP>(graph: &DiGraphMap<OP, ()>, from: OP, to: OP) -> bool
88where
89 OP: OperationId + Ord,
90{
91 let mut dfs = Dfs::new(graph, from);
92 while let Some(node) = dfs.next(graph) {
93 if node == to {
94 return true;
95 }
96 }
97 false
98}
99
100#[cfg(test)]
101mod tests {
102 use std::collections::HashSet;
103
104 use petgraph::{graph::DiGraph, prelude::DiGraphMap};
105
106 use crate::graph::concurrent_bubbles;
107
108 #[test]
109 fn test_linear_chain_no_concurrency() {
110 let mut graph = DiGraphMap::new();
111 graph.add_edge(1, 2, ());
112 graph.add_edge(2, 3, ());
113 graph.add_edge(3, 4, ());
114
115 let bubbles = concurrent_bubbles(&graph);
116 assert!(bubbles.is_empty());
117 }
118
119 #[test]
120 fn test_bubble() {
121 let mut graph = DiGraphMap::new();
122 graph.add_edge(1, 2, ());
123 graph.add_edge(1, 3, ());
124 graph.add_edge(2, 4, ());
125 graph.add_edge(3, 4, ());
126
127 let bubbles = concurrent_bubbles(&graph);
128
129 assert_eq!(bubbles.len(), 1);
131 let expected: HashSet<_> = [2, 3].into_iter().collect();
132 assert_eq!(bubbles[0], expected);
133 }
134
135 #[test]
136 fn test_two_bubbles() {
137 let mut graph = DiGraphMap::new();
138 graph.add_edge(1, 2, ());
140 graph.add_edge(1, 3, ());
141 graph.add_edge(2, 4, ());
142 graph.add_edge(3, 4, ());
143 graph.add_edge(4, 5, ());
145 graph.add_edge(4, 6, ());
146 graph.add_edge(5, 7, ());
147 graph.add_edge(6, 7, ());
148
149 let bubbles = concurrent_bubbles(&graph);
150 assert_eq!(bubbles.len(), 2);
151
152 let b1: HashSet<_> = [2, 3].into_iter().collect();
153 let b2: HashSet<_> = [5, 6].into_iter().collect();
154
155 assert!(bubbles.contains(&b1));
156 assert!(bubbles.contains(&b2));
157 }
158
159 #[test]
160 fn complex_bubble() {
161 let mut graph = DiGraph::new();
174
175 let a = graph.add_node("A"); let b = graph.add_node("B"); let c = graph.add_node("C"); let d = graph.add_node("D"); let e = graph.add_node("E"); let f = graph.add_node("F"); let g = graph.add_node("G"); let h = graph.add_node("H"); let i = graph.add_node("I"); let j = graph.add_node("J"); graph.extend_with_edges(&[
189 (a, b),
190 (a, c),
191 (b, d),
192 (b, e),
193 (d, g),
194 (e, g),
195 (c, f),
196 (f, h),
197 (h, i),
198 (g, i),
199 (i, j),
200 ]);
201
202 let graph_map = DiGraphMap::from_graph(graph);
203 let concurrent_bubbles = concurrent_bubbles(&graph_map);
204
205 assert_eq!(concurrent_bubbles.len(), 1);
206 let bubble = concurrent_bubbles.first().unwrap();
207 for id in &["B", "C", "D", "E", "F", "G", "H"] {
208 assert!(bubble.contains(id));
209 }
210 }
211}