Skip to main content

backhopper_xref_graph/
relation.rs

1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Vertex sets and binary relations.
6//!
7//! The algebra here is what xref-style analyses need: union, intersection,
8//! difference, forward and backward image, transitive closure, and edge
9//! reversal.
10//!
11//! Everything is keyed on `Vertex` and stored in `BTreeSet` so iteration
12//! order is deterministic.
13
14use std::collections::{BTreeSet, btree_set};
15use std::iter::Map;
16
17use serde::{Deserialize, Serialize};
18
19use crate::vertex::Vertex;
20
21#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
22#[serde(transparent)]
23pub struct VertexSet {
24    inner: BTreeSet<Vertex>,
25}
26
27impl VertexSet {
28    /// An empty set.
29    pub fn new() -> Self {
30        Self::default()
31    }
32
33    /// Insert `v`. Returns `true` if it was not already present.
34    pub fn insert(&mut self, v: Vertex) -> bool {
35        self.inner.insert(v)
36    }
37
38    /// True when `v` is in the set.
39    pub fn contains(&self, v: &Vertex) -> bool {
40        self.inner.contains(v)
41    }
42
43    /// Number of elements.
44    pub fn len(&self) -> usize {
45        self.inner.len()
46    }
47
48    /// True when there are no elements.
49    pub fn is_empty(&self) -> bool {
50        self.inner.is_empty()
51    }
52
53    /// Iterate in canonical (ascending) order.
54    pub fn iter(&self) -> impl Iterator<Item = &Vertex> {
55        self.inner.iter()
56    }
57
58    /// Set union: `{ v | v in self or v in other }`.
59    pub fn union(&self, other: &VertexSet) -> VertexSet {
60        VertexSet {
61            inner: self.inner.union(&other.inner).cloned().collect(),
62        }
63    }
64
65    /// Set intersection: `{ v | v in self and v in other }`.
66    pub fn intersection(&self, other: &VertexSet) -> VertexSet {
67        VertexSet {
68            inner: self.inner.intersection(&other.inner).cloned().collect(),
69        }
70    }
71
72    /// Set difference: `{ v | v in self and v not in other }`.
73    pub fn difference(&self, other: &VertexSet) -> VertexSet {
74        VertexSet {
75            inner: self.inner.difference(&other.inner).cloned().collect(),
76        }
77    }
78}
79
80impl FromIterator<Vertex> for VertexSet {
81    fn from_iter<I: IntoIterator<Item = Vertex>>(iter: I) -> Self {
82        Self {
83            inner: iter.into_iter().collect(),
84        }
85    }
86}
87
88impl<'a> IntoIterator for &'a VertexSet {
89    type Item = &'a Vertex;
90    type IntoIter = btree_set::Iter<'a, Vertex>;
91
92    fn into_iter(self) -> Self::IntoIter {
93        self.inner.iter()
94    }
95}
96
97#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(transparent)]
99pub struct Relation {
100    inner: BTreeSet<(Vertex, Vertex)>,
101}
102
103impl Relation {
104    /// An empty relation.
105    pub fn new() -> Self {
106        Self::default()
107    }
108
109    /// Insert the edge `(src, tgt)`. Returns `true` if it was not already present.
110    pub fn insert(&mut self, src: Vertex, tgt: Vertex) -> bool {
111        self.inner.insert((src, tgt))
112    }
113
114    /// True when `(src, tgt)` is in the relation.
115    pub fn contains(&self, src: &Vertex, tgt: &Vertex) -> bool {
116        self.inner.contains(&(src.clone(), tgt.clone()))
117    }
118
119    /// Number of edges.
120    pub fn len(&self) -> usize {
121        self.inner.len()
122    }
123
124    /// True when there are no edges.
125    pub fn is_empty(&self) -> bool {
126        self.inner.is_empty()
127    }
128
129    /// Iterate edges in canonical (lexicographic) order.
130    pub fn iter(&self) -> impl Iterator<Item = (&Vertex, &Vertex)> {
131        self.inner.iter().map(|(a, b)| (a, b))
132    }
133
134    /// Edge set union.
135    pub fn union(&self, other: &Relation) -> Relation {
136        Relation {
137            inner: self.inner.union(&other.inner).cloned().collect(),
138        }
139    }
140
141    /// Edge set intersection.
142    pub fn intersection(&self, other: &Relation) -> Relation {
143        Relation {
144            inner: self.inner.intersection(&other.inner).cloned().collect(),
145        }
146    }
147
148    /// Edge set difference.
149    pub fn difference(&self, other: &Relation) -> Relation {
150        Relation {
151            inner: self.inner.difference(&other.inner).cloned().collect(),
152        }
153    }
154
155    /// The set of vertices that appear as a target on some edge.
156    pub fn targets(&self) -> VertexSet {
157        self.inner.iter().map(|(_, t)| t.clone()).collect()
158    }
159
160    /// Forward image: `{ y | exists x in sources, (x, y) in self }`.
161    pub fn image(&self, sources: &VertexSet) -> VertexSet {
162        self.inner
163            .iter()
164            .filter(|(s, _)| sources.contains(s))
165            .map(|(_, t)| t.clone())
166            .collect()
167    }
168
169    /// Backward image: `{ x | exists y in targets, (x, y) in self }`.
170    pub fn preimage(&self, targets: &VertexSet) -> VertexSet {
171        self.inner
172            .iter()
173            .filter(|(_, t)| targets.contains(t))
174            .map(|(s, _)| s.clone())
175            .collect()
176    }
177
178    /// Reverse every edge: maps `(s, t)` to `(t, s)`.
179    pub fn reversed(&self) -> Relation {
180        Relation {
181            inner: self
182                .inner
183                .iter()
184                .map(|(s, t)| (t.clone(), s.clone()))
185                .collect(),
186        }
187    }
188
189    /// Irreflexive transitive closure (E+).
190    ///
191    /// Tarjan's Strongly Connected Component algorithm, then DAG
192    /// reachability propagation: O(V + E + sum(component-size^2)).
193    pub fn transitive_closure(&self) -> Relation {
194        closure::compute(self)
195    }
196}
197
198impl FromIterator<(Vertex, Vertex)> for Relation {
199    fn from_iter<I: IntoIterator<Item = (Vertex, Vertex)>>(iter: I) -> Self {
200        Self {
201            inner: iter.into_iter().collect(),
202        }
203    }
204}
205
206impl<'a> IntoIterator for &'a Relation {
207    type Item = (&'a Vertex, &'a Vertex);
208    type IntoIter = Map<
209        btree_set::Iter<'a, (Vertex, Vertex)>,
210        fn(&'a (Vertex, Vertex)) -> (&'a Vertex, &'a Vertex),
211    >;
212
213    fn into_iter(self) -> Self::IntoIter {
214        self.inner.iter().map(|(a, b)| (a, b))
215    }
216}
217
218mod closure {
219    use std::collections::{BTreeMap, BTreeSet};
220
221    use crate::relation::Relation;
222    use crate::vertex::Vertex;
223
224    pub(super) fn compute(r: &Relation) -> Relation {
225        let adj = build_adj(r);
226        let sccs = tarjan_sccs(&adj);
227        let mut comp_of: BTreeMap<&Vertex, usize> = BTreeMap::new();
228        for (idx, scc) in sccs.iter().enumerate() {
229            for v in scc {
230                comp_of.insert(v, idx);
231            }
232        }
233        let mut cond_adj: Vec<BTreeSet<usize>> = vec![BTreeSet::new(); sccs.len()];
234        for (s, ts) in &adj {
235            let ci = comp_of[s];
236            for t in ts {
237                let cj = comp_of[t];
238                if ci != cj {
239                    cond_adj[ci].insert(cj);
240                }
241            }
242        }
243        let topo = reverse_topo(&cond_adj);
244        let mut reach: Vec<BTreeSet<usize>> = vec![BTreeSet::new(); sccs.len()];
245        for &c in &topo {
246            let mut rr = BTreeSet::new();
247            for &nc in &cond_adj[c] {
248                rr.insert(nc);
249                rr.extend(reach[nc].iter().copied());
250            }
251            reach[c] = rr;
252        }
253        let mut out: BTreeSet<(Vertex, Vertex)> = BTreeSet::new();
254        for (idx, scc) in sccs.iter().enumerate() {
255            // True iff every vertex in the SCC reaches itself: size > 1, or self-loop.
256            let intra_cycle =
257                scc.len() > 1 || adj.get(&scc[0]).is_some_and(|out| out.contains(&scc[0]));
258            for v in scc {
259                if intra_cycle {
260                    for u in scc {
261                        out.insert(((*v).clone(), (*u).clone()));
262                    }
263                }
264                for &nc in &reach[idx] {
265                    for u in &sccs[nc] {
266                        out.insert(((*v).clone(), (*u).clone()));
267                    }
268                }
269            }
270        }
271        Relation { inner: out }
272    }
273
274    fn build_adj(r: &Relation) -> BTreeMap<&Vertex, Vec<&Vertex>> {
275        let mut adj: BTreeMap<&Vertex, Vec<&Vertex>> = BTreeMap::new();
276        for (s, t) in r.iter() {
277            adj.entry(s).or_default().push(t);
278            adj.entry(t).or_default();
279        }
280        adj
281    }
282
283    // Iterative Tarjan over ids into a sorted vertex list: no stack overflow on
284    // deep graphs, and the SCC output keeps the recursive version's ordering.
285    fn tarjan_sccs<'a>(adj: &BTreeMap<&'a Vertex, Vec<&'a Vertex>>) -> Vec<Vec<&'a Vertex>> {
286        let nodes: Vec<&'a Vertex> = adj.keys().copied().collect();
287        let id_of: BTreeMap<&Vertex, usize> =
288            nodes.iter().enumerate().map(|(i, v)| (*v, i)).collect();
289        let neighbours: Vec<Vec<usize>> = nodes
290            .iter()
291            .map(|v| adj[v].iter().map(|w| id_of[w]).collect())
292            .collect();
293        let n = nodes.len();
294
295        const UNVISITED: usize = usize::MAX;
296        let mut indices = vec![UNVISITED; n];
297        let mut lowlinks = vec![0usize; n];
298        let mut on_stack = vec![false; n];
299        let mut tarjan_stack: Vec<usize> = Vec::new();
300        let mut next_index = 0usize;
301        let mut out: Vec<Vec<&'a Vertex>> = Vec::new();
302
303        // Each frame is (vertex, index of the next child to visit).
304        let mut work: Vec<(usize, usize)> = Vec::new();
305        for start in 0..n {
306            if indices[start] != UNVISITED {
307                continue;
308            }
309            work.push((start, 0));
310            while let Some(&(v, child)) = work.last() {
311                if child == 0 {
312                    indices[v] = next_index;
313                    lowlinks[v] = next_index;
314                    next_index += 1;
315                    tarjan_stack.push(v);
316                    on_stack[v] = true;
317                }
318                let mut recursed = false;
319                let mut i = child;
320                while i < neighbours[v].len() {
321                    let w = neighbours[v][i];
322                    if indices[w] == UNVISITED {
323                        work.last_mut().unwrap().1 = i + 1;
324                        work.push((w, 0));
325                        recursed = true;
326                        break;
327                    }
328                    if on_stack[w] {
329                        lowlinks[v] = lowlinks[v].min(indices[w]);
330                    }
331                    i += 1;
332                }
333                if recursed {
334                    continue;
335                }
336                if lowlinks[v] == indices[v] {
337                    let mut scc = Vec::new();
338                    loop {
339                        let w = tarjan_stack.pop().expect("stack non-empty during SCC pop");
340                        on_stack[w] = false;
341                        scc.push(nodes[w]);
342                        if w == v {
343                            break;
344                        }
345                    }
346                    scc.sort();
347                    out.push(scc);
348                }
349                work.pop();
350                if let Some(&(parent, _)) = work.last() {
351                    lowlinks[parent] = lowlinks[parent].min(lowlinks[v]);
352                }
353            }
354        }
355        out
356    }
357
358    fn reverse_topo(adj: &[BTreeSet<usize>]) -> Vec<usize> {
359        let n = adj.len();
360        let mut state = vec![0u8; n];
361        let mut order = Vec::new();
362        for v in 0..n {
363            if state[v] == 0 {
364                dfs(v, adj, &mut state, &mut order);
365            }
366        }
367        order
368    }
369
370    fn dfs(v: usize, adj: &[BTreeSet<usize>], state: &mut [u8], order: &mut Vec<usize>) {
371        state[v] = 1;
372        for &u in &adj[v] {
373            if state[u] == 0 {
374                dfs(u, adj, state, order);
375            }
376        }
377        state[v] = 2;
378        order.push(v);
379    }
380}