1use std::collections::HashSet;
6
7use p2panda_core::traits::OperationId;
8use petgraph::algo::has_path_connecting;
9use petgraph::graphmap::DiGraphMap;
10use petgraph::visit::{Dfs, Reversed};
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 split_bubble<OP>(
86 graph: &DiGraphMap<OP, ()>,
87 bubble: &HashSet<OP>,
88 target: OP,
89) -> (HashSet<OP>, HashSet<OP>, Vec<OP>)
90where
91 OP: OperationId + Ord,
92{
93 let mut concurrent = bubble.clone();
95 let mut successors = Vec::new();
96 let mut dfs = Dfs::new(&graph, target);
97 while let Some(id) = dfs.next(&graph) {
98 concurrent.remove(&id);
99 successors.push(id);
100 }
101
102 let mut predecessors = HashSet::new();
104 let reversed = Reversed(graph);
105 let mut dfs_rev = Dfs::new(&reversed, target);
106 while let Some(id) = dfs_rev.next(&reversed) {
107 concurrent.remove(&id);
108 predecessors.insert(id);
109 }
110
111 (concurrent, predecessors, successors)
112}
113
114pub fn has_path<OP>(graph: &DiGraphMap<OP, ()>, from: OP, to: OP) -> bool
118where
119 OP: OperationId + Ord,
120{
121 from != to && has_path_connecting(graph, from, to, None)
122}
123
124pub fn is_concurrent<OP>(graph: &DiGraphMap<OP, ()>, a: OP, b: OP) -> bool
128where
129 OP: OperationId + Ord,
130{
131 a != b && !has_path(graph, a, b) && !has_path(graph, b, a)
132}
133
134#[cfg(test)]
135mod tests {
136 use std::collections::HashSet;
137
138 use petgraph::{graph::DiGraph, prelude::DiGraphMap};
139
140 use crate::graph::concurrent_bubbles;
141
142 #[test]
143 fn test_linear_chain_no_concurrency() {
144 let mut graph = DiGraphMap::new();
145 graph.add_edge(1, 2, ());
146 graph.add_edge(2, 3, ());
147 graph.add_edge(3, 4, ());
148
149 let bubbles = concurrent_bubbles(&graph);
150 assert!(bubbles.is_empty());
151 }
152
153 #[test]
154 fn test_bubble() {
155 let mut graph = DiGraphMap::new();
156 graph.add_edge(1, 2, ());
157 graph.add_edge(1, 3, ());
158 graph.add_edge(2, 4, ());
159 graph.add_edge(3, 4, ());
160
161 let bubbles = concurrent_bubbles(&graph);
162
163 assert_eq!(bubbles.len(), 1);
165 let expected: HashSet<_> = [2, 3].into_iter().collect();
166 assert_eq!(bubbles[0], expected);
167 }
168
169 #[test]
170 fn test_two_bubbles() {
171 let mut graph = DiGraphMap::new();
172 graph.add_edge(1, 2, ());
174 graph.add_edge(1, 3, ());
175 graph.add_edge(2, 4, ());
176 graph.add_edge(3, 4, ());
177 graph.add_edge(4, 5, ());
179 graph.add_edge(4, 6, ());
180 graph.add_edge(5, 7, ());
181 graph.add_edge(6, 7, ());
182
183 let bubbles = concurrent_bubbles(&graph);
184 assert_eq!(bubbles.len(), 2);
185
186 let b1: HashSet<_> = [2, 3].into_iter().collect();
187 let b2: HashSet<_> = [5, 6].into_iter().collect();
188
189 assert!(bubbles.contains(&b1));
190 assert!(bubbles.contains(&b2));
191 }
192
193 #[test]
194 fn complex_bubble() {
195 let mut graph = DiGraph::new();
208
209 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([
223 (a, b),
224 (a, c),
225 (b, d),
226 (b, e),
227 (d, g),
228 (e, g),
229 (c, f),
230 (f, h),
231 (h, i),
232 (g, i),
233 (i, j),
234 ]);
235
236 let graph_map = DiGraphMap::from_graph(graph);
237 let concurrent_bubbles = concurrent_bubbles(&graph_map);
238
239 assert_eq!(concurrent_bubbles.len(), 1);
240 let bubble = concurrent_bubbles.first().unwrap();
241 for id in &["B", "C", "D", "E", "F", "G", "H"] {
242 assert!(bubble.contains(id));
243 }
244 }
245}