1use std::collections::HashSet;
6
7use petgraph::algo::has_path_connecting;
8use petgraph::graphmap::DiGraphMap;
9use petgraph::visit::{Dfs, Reversed};
10
11use crate::traits::OperationId;
12
13fn concurrent_bubble<OP>(
15 graph: &DiGraphMap<OP, ()>,
16 target: OP,
17 processed: &mut HashSet<OP>,
18) -> HashSet<OP>
19where
20 OP: OperationId + Ord,
21{
22 let mut bubble = HashSet::new();
23 bubble.insert(target);
24
25 concurrent_operations(graph, target)
26 .into_iter()
27 .for_each(|op| {
28 if processed.insert(op) {
29 bubble.extend(concurrent_bubble(graph, op, processed).iter())
30 }
31 });
32
33 bubble
34}
35
36pub fn concurrent_bubbles<OP>(graph: &DiGraphMap<OP, ()>) -> Vec<HashSet<OP>>
38where
39 OP: OperationId + Ord,
40{
41 let mut processed: HashSet<OP> = HashSet::new();
42 let mut bubbles = Vec::new();
43
44 graph.nodes().for_each(|target| {
45 if processed.insert(target) {
46 let bubble = concurrent_bubble(graph, target, &mut processed);
47 if bubble.len() > 1 {
48 bubbles.push(bubble)
49 }
50 }
51 });
52
53 bubbles
54}
55
56fn concurrent_operations<OP>(graph: &DiGraphMap<OP, ()>, target: OP) -> HashSet<OP>
61where
62 OP: OperationId + Ord,
63{
64 let mut successors = HashSet::new();
66 let mut dfs = Dfs::new(&graph, target);
67 while let Some(nx) = dfs.next(&graph) {
68 successors.insert(nx);
69 }
70
71 let mut predecessors = HashSet::new();
73 let reversed = Reversed(graph);
74 let mut dfs_rev = Dfs::new(&reversed, target);
75 while let Some(nx) = dfs_rev.next(&reversed) {
76 predecessors.insert(nx);
77 }
78
79 let relatives: HashSet<_> = successors.union(&predecessors).cloned().collect();
80
81 graph.nodes().filter(|n| !relatives.contains(n)).collect()
83}
84
85pub fn split_bubble<OP>(
87 graph: &DiGraphMap<OP, ()>,
88 bubble: &HashSet<OP>,
89 target: OP,
90) -> (HashSet<OP>, HashSet<OP>, Vec<OP>)
91where
92 OP: OperationId + Ord,
93{
94 let mut concurrent = bubble.clone();
96 let mut successors = Vec::new();
97 let mut dfs = Dfs::new(&graph, target);
98 while let Some(id) = dfs.next(&graph) {
99 concurrent.remove(&id);
100 successors.push(id);
101 }
102
103 let mut predecessors = HashSet::new();
105 let reversed = Reversed(graph);
106 let mut dfs_rev = Dfs::new(&reversed, target);
107 while let Some(id) = dfs_rev.next(&reversed) {
108 concurrent.remove(&id);
109 predecessors.insert(id);
110 }
111
112 (concurrent, predecessors, successors)
113}
114
115pub fn has_path<OP>(graph: &DiGraphMap<OP, ()>, from: OP, to: OP) -> bool
119where
120 OP: OperationId + Ord,
121{
122 from != to && has_path_connecting(graph, from, to, None)
123}
124
125pub fn is_concurrent<OP>(graph: &DiGraphMap<OP, ()>, a: OP, b: OP) -> bool
129where
130 OP: OperationId + Ord,
131{
132 a != b && !has_path(graph, a, b) && !has_path(graph, b, a)
133}
134
135#[cfg(test)]
136mod tests {
137 use std::collections::HashSet;
138
139 use petgraph::{graph::DiGraph, prelude::DiGraphMap};
140
141 use crate::graph::concurrent_bubbles;
142 use crate::traits::OperationId;
143
144 impl OperationId for &str {}
145
146 #[test]
147 fn test_linear_chain_no_concurrency() {
148 let mut graph = DiGraphMap::new();
149 graph.add_edge(1, 2, ());
150 graph.add_edge(2, 3, ());
151 graph.add_edge(3, 4, ());
152
153 let bubbles = concurrent_bubbles(&graph);
154 assert!(bubbles.is_empty());
155 }
156
157 #[test]
158 fn test_bubble() {
159 let mut graph = DiGraphMap::new();
160 graph.add_edge(1, 2, ());
161 graph.add_edge(1, 3, ());
162 graph.add_edge(2, 4, ());
163 graph.add_edge(3, 4, ());
164
165 let bubbles = concurrent_bubbles(&graph);
166
167 assert_eq!(bubbles.len(), 1);
169 let expected: HashSet<_> = [2, 3].into_iter().collect();
170 assert_eq!(bubbles[0], expected);
171 }
172
173 #[test]
174 fn test_two_bubbles() {
175 let mut graph = DiGraphMap::new();
176 graph.add_edge(1, 2, ());
178 graph.add_edge(1, 3, ());
179 graph.add_edge(2, 4, ());
180 graph.add_edge(3, 4, ());
181 graph.add_edge(4, 5, ());
183 graph.add_edge(4, 6, ());
184 graph.add_edge(5, 7, ());
185 graph.add_edge(6, 7, ());
186
187 let bubbles = concurrent_bubbles(&graph);
188 assert_eq!(bubbles.len(), 2);
189
190 let b1: HashSet<_> = [2, 3].into_iter().collect();
191 let b2: HashSet<_> = [5, 6].into_iter().collect();
192
193 assert!(bubbles.contains(&b1));
194 assert!(bubbles.contains(&b2));
195 }
196
197 #[test]
198 fn complex_bubble() {
199 let mut graph = DiGraph::new();
212
213 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([
227 (a, b),
228 (a, c),
229 (b, d),
230 (b, e),
231 (d, g),
232 (e, g),
233 (c, f),
234 (f, h),
235 (h, i),
236 (g, i),
237 (i, j),
238 ]);
239
240 let graph_map = DiGraphMap::from_graph(graph);
241 let concurrent_bubbles = concurrent_bubbles(&graph_map);
242
243 assert_eq!(concurrent_bubbles.len(), 1);
244 let bubble = concurrent_bubbles.first().unwrap();
245 for id in &["B", "C", "D", "E", "F", "G", "H"] {
246 assert!(bubble.contains(id));
247 }
248 }
249}