weavatrix_graph/algo/
dominators.rs1use crate::IndexGraphView;
2use std::collections::HashMap;
3use std::hash::Hash;
4
5type Adjacency<Node> = Vec<Vec<Node>>;
6
7#[derive(Debug, Clone)]
8pub struct Dominators<Node> {
9 root: Node,
10 reachable: Vec<Node>,
11 immediate: HashMap<Node, Node>,
12}
13
14impl<Node> Dominators<Node>
15where
16 Node: Copy + Eq + Hash,
17{
18 #[must_use]
19 pub const fn root(&self) -> Node {
20 self.root
21 }
22
23 #[must_use]
24 pub fn reachable_nodes(&self) -> &[Node] {
25 &self.reachable
26 }
27
28 #[must_use]
29 pub fn immediate_dominator(&self, node: Node) -> Option<Node> {
30 (node != self.root)
31 .then(|| self.immediate.get(&node).copied())
32 .flatten()
33 }
34
35 pub fn dominators(&self, node: Node) -> Option<DominatorsIter<'_, Node>> {
36 self.is_reachable(node).then_some(DominatorsIter {
37 result: self,
38 next: Some(node),
39 })
40 }
41
42 pub fn strict_dominators(&self, node: Node) -> Option<DominatorsIter<'_, Node>> {
43 let mut result = self.dominators(node)?;
44 result.next();
45 Some(result)
46 }
47
48 #[must_use]
49 pub fn immediately_dominated_by(&self, node: Node) -> Vec<Node> {
50 self.reachable
51 .iter()
52 .copied()
53 .filter(|candidate| self.immediate_dominator(*candidate) == Some(node))
54 .collect()
55 }
56
57 #[must_use]
58 pub fn dominates(&self, dominator: Node, node: Node) -> bool {
59 self.dominators(node)
60 .is_some_and(|mut chain| chain.any(|candidate| candidate == dominator))
61 }
62
63 fn is_reachable(&self, node: Node) -> bool {
64 node == self.root || self.immediate.contains_key(&node)
65 }
66}
67
68pub struct DominatorsIter<'result, Node> {
69 result: &'result Dominators<Node>,
70 next: Option<Node>,
71}
72
73impl<Node> Iterator for DominatorsIter<'_, Node>
74where
75 Node: Copy + Eq + Hash,
76{
77 type Item = Node;
78
79 fn next(&mut self) -> Option<Self::Item> {
80 let node = self.next?;
81 self.next = (node != self.result.root)
82 .then(|| self.result.immediate.get(&node).copied())
83 .flatten();
84 Some(node)
85 }
86}
87
88#[must_use]
89pub fn dominators<G>(graph: &G, root: G::Node) -> Option<Dominators<G::Node>>
90where
91 G: IndexGraphView,
92{
93 dominators_filtered(graph, root, |_| true)
94}
95
96#[must_use]
97pub fn dominators_filtered<G, F>(
98 graph: &G,
99 root: G::Node,
100 allows_edge: F,
101) -> Option<Dominators<G::Node>>
102where
103 G: IndexGraphView,
104 F: Fn(G::Edge) -> bool,
105{
106 if !graph.contains_node(root) {
107 return None;
108 }
109 let (adjacency, predecessors) = adjacency_pair(graph, &allows_edge);
110 let postorder = reachable_postorder::<G>(root, &adjacency);
111 let reachable = postorder.iter().rev().copied().collect::<Vec<_>>();
112 let mut position = vec![None; graph.node_bound()];
113 for (index, &node) in reachable.iter().enumerate() {
114 position[G::node_slot(node)] = Some(index);
115 }
116 let mut immediate = vec![None; reachable.len()];
117 immediate[0] = Some(0);
118 let mut changed = true;
119 while changed {
120 changed = false;
121 for index in 1..reachable.len() {
122 let node = reachable[index];
123 let candidate = predecessors[G::node_slot(node)]
124 .iter()
125 .filter_map(|predecessor| position[G::node_slot(*predecessor)])
126 .find(|predecessor| immediate[*predecessor].is_some());
127 let Some(mut new_idom) = candidate else {
128 continue;
129 };
130 for predecessor in predecessors[G::node_slot(node)]
131 .iter()
132 .filter_map(|predecessor| position[G::node_slot(*predecessor)])
133 .filter(|predecessor| immediate[*predecessor].is_some())
134 {
135 new_idom = intersect(predecessor, new_idom, &immediate);
136 }
137 if immediate[index] != Some(new_idom) {
138 immediate[index] = Some(new_idom);
139 changed = true;
140 }
141 }
142 }
143 let immediate = reachable
144 .iter()
145 .copied()
146 .enumerate()
147 .skip(1)
148 .filter_map(|(index, node)| immediate[index].map(|parent| (node, reachable[parent])))
149 .collect();
150 Some(Dominators {
151 root,
152 reachable,
153 immediate,
154 })
155}
156
157fn adjacency_pair<G, F>(graph: &G, allows_edge: &F) -> (Adjacency<G::Node>, Adjacency<G::Node>)
158where
159 G: IndexGraphView,
160 F: Fn(G::Edge) -> bool,
161{
162 let mut adjacency = vec![Vec::new(); graph.node_bound()];
163 let mut predecessors = vec![Vec::new(); graph.node_bound()];
164 for (edge, endpoints) in graph.edge_references() {
165 if allows_edge(edge) {
166 adjacency[G::node_slot(endpoints.source())].push(endpoints.target());
167 predecessors[G::node_slot(endpoints.target())].push(endpoints.source());
168 }
169 }
170 (adjacency, predecessors)
171}
172
173fn reachable_postorder<G>(root: G::Node, adjacency: &[Vec<G::Node>]) -> Vec<G::Node>
174where
175 G: IndexGraphView,
176{
177 let mut seen = vec![false; adjacency.len()];
178 let mut order = Vec::new();
179 let mut stack = vec![(root, 0_usize)];
180 seen[G::node_slot(root)] = true;
181 while let Some((node, next)) = stack.last_mut() {
182 let Some(&target) = adjacency[G::node_slot(*node)].get(*next) else {
183 order.push(*node);
184 stack.pop();
185 continue;
186 };
187 *next += 1;
188 let slot = G::node_slot(target);
189 if !seen[slot] {
190 seen[slot] = true;
191 stack.push((target, 0));
192 }
193 }
194 order
195}
196
197fn intersect(mut left: usize, mut right: usize, immediate: &[Option<usize>]) -> usize {
198 while left != right {
199 while left > right {
200 left = immediate[left].expect("processed dominator");
201 }
202 while right > left {
203 right = immediate[right].expect("processed dominator");
204 }
205 }
206 left
207}