Skip to main content

ruvector_sparsifier/
backbone.rs

1//! Backbone (spanning forest) maintenance via union-find.
2//!
3//! The [`Backbone`] keeps a set of *always-present* edges that guarantee
4//! global connectivity in the sparsifier. Non-backbone edges can be freely
5//! sampled and dropped; backbone edges are never removed unless the
6//! corresponding edge is deleted from the full graph.
7
8use std::collections::HashSet;
9
10use serde::{Deserialize, Serialize};
11
12use crate::traits::BackboneStrategy;
13
14// ---------------------------------------------------------------------------
15// Union-Find
16// ---------------------------------------------------------------------------
17
18/// Weighted union-find (disjoint-set) with path compression and union by rank.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20struct UnionFind {
21    parent: Vec<usize>,
22    rank: Vec<usize>,
23    num_components: usize,
24}
25
26impl UnionFind {
27    fn new(n: usize) -> Self {
28        Self {
29            parent: (0..n).collect(),
30            rank: vec![0; n],
31            num_components: n,
32        }
33    }
34
35    fn ensure_capacity(&mut self, n: usize) {
36        while self.parent.len() < n {
37            let id = self.parent.len();
38            self.parent.push(id);
39            self.rank.push(0);
40            self.num_components += 1;
41        }
42    }
43
44    fn find(&mut self, mut x: usize) -> usize {
45        while self.parent[x] != x {
46            self.parent[x] = self.parent[self.parent[x]]; // path halving
47            x = self.parent[x];
48        }
49        x
50    }
51
52    /// Union two sets. Returns `true` if they were in different components.
53    fn union(&mut self, a: usize, b: usize) -> bool {
54        let ra = self.find(a);
55        let rb = self.find(b);
56        if ra == rb {
57            return false;
58        }
59        // Union by rank.
60        match self.rank[ra].cmp(&self.rank[rb]) {
61            std::cmp::Ordering::Less => self.parent[ra] = rb,
62            std::cmp::Ordering::Greater => self.parent[rb] = ra,
63            std::cmp::Ordering::Equal => {
64                self.parent[rb] = ra;
65                self.rank[ra] += 1;
66            }
67        }
68        self.num_components -= 1;
69        true
70    }
71
72    fn connected(&mut self, a: usize, b: usize) -> bool {
73        self.find(a) == self.find(b)
74    }
75}
76
77// ---------------------------------------------------------------------------
78// Backbone
79// ---------------------------------------------------------------------------
80
81/// A spanning-forest backbone that maintains connectivity guarantees.
82///
83/// When an edge is inserted, it is added to the backbone forest if and only
84/// if it connects two previously disconnected components. When a backbone
85/// edge is deleted, the backbone attempts to find a replacement edge from
86/// the set of known non-backbone edges to restore connectivity.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Backbone {
89    /// Union-find for connectivity tracking.
90    uf: UnionFind,
91    /// Set of edges `(min(u,v), max(u,v))` that are in the backbone.
92    backbone_edges: HashSet<(usize, usize)>,
93    /// Set of all known non-backbone edges for replacement search.
94    non_backbone_edges: HashSet<(usize, usize)>,
95}
96
97impl Backbone {
98    /// Create a new backbone for up to `n` vertices.
99    pub fn new(n: usize) -> Self {
100        Self {
101            uf: UnionFind::new(n),
102            backbone_edges: HashSet::new(),
103            non_backbone_edges: HashSet::new(),
104        }
105    }
106
107    /// Canonical edge key with `u <= v`.
108    #[inline]
109    fn edge_key(u: usize, v: usize) -> (usize, usize) {
110        if u <= v { (u, v) } else { (v, u) }
111    }
112
113    /// Rebuild the union-find from the current backbone edges.
114    ///
115    /// This is O(backbone_edges * alpha(n)) and is used after a backbone
116    /// edge deletion when no replacement is found.
117    fn rebuild_uf(&mut self) {
118        let n = self.uf.parent.len();
119        self.uf = UnionFind::new(n);
120        for &(u, v) in &self.backbone_edges {
121            self.uf.union(u, v);
122        }
123    }
124}
125
126impl BackboneStrategy for Backbone {
127    fn insert_edge(&mut self, u: usize, v: usize, _weight: f64) -> bool {
128        let key = Self::edge_key(u, v);
129        self.ensure_capacity(u.max(v) + 1);
130
131        if !self.uf.connected(u, v) {
132            // Bridge: add to backbone.
133            self.uf.union(u, v);
134            self.backbone_edges.insert(key);
135            true
136        } else {
137            // Not a bridge: track as non-backbone.
138            self.non_backbone_edges.insert(key);
139            false
140        }
141    }
142
143    fn delete_edge(&mut self, u: usize, v: usize, _weight: f64) -> bool {
144        let key = Self::edge_key(u, v);
145
146        if self.non_backbone_edges.remove(&key) {
147            // Non-backbone edge removed; nothing to repair.
148            return false;
149        }
150
151        if !self.backbone_edges.remove(&key) {
152            // Edge was not tracked at all.
153            return false;
154        }
155
156        // Backbone edge removed. Rebuild UF without it and try to find
157        // a replacement from non-backbone edges.
158        self.rebuild_uf();
159
160        // Try to find a replacement edge that reconnects the components.
161        let mut replacement = None;
162        for &(a, b) in &self.non_backbone_edges {
163            if !self.uf.connected(a, b) {
164                replacement = Some((a, b));
165                break;
166            }
167        }
168
169        if let Some((a, b)) = replacement {
170            let rkey = Self::edge_key(a, b);
171            self.non_backbone_edges.remove(&rkey);
172            self.backbone_edges.insert(rkey);
173            self.uf.union(a, b);
174        }
175
176        true
177    }
178
179    fn is_backbone_edge(&self, u: usize, v: usize) -> bool {
180        self.backbone_edges.contains(&Self::edge_key(u, v))
181    }
182
183    fn num_components(&self) -> usize {
184        self.uf.num_components
185    }
186
187    fn connected(&mut self, u: usize, v: usize) -> bool {
188        if u >= self.uf.parent.len() || v >= self.uf.parent.len() {
189            return false;
190        }
191        self.uf.connected(u, v)
192    }
193
194    fn backbone_edge_count(&self) -> usize {
195        self.backbone_edges.len()
196    }
197
198    fn ensure_capacity(&mut self, n: usize) {
199        self.uf.ensure_capacity(n);
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206
207    #[test]
208    fn test_backbone_connectivity() {
209        let mut bb = Backbone::new(4);
210        // 0-1-2-3 path
211        assert!(bb.insert_edge(0, 1, 1.0)); // bridge
212        assert!(bb.insert_edge(1, 2, 1.0)); // bridge
213        assert!(bb.insert_edge(2, 3, 1.0)); // bridge
214        assert!(!bb.insert_edge(0, 3, 1.0)); // cycle, not bridge
215
216        assert_eq!(bb.num_components(), 1);
217        assert_eq!(bb.backbone_edge_count(), 3);
218        assert!(bb.is_backbone_edge(0, 1));
219        assert!(!bb.is_backbone_edge(0, 3));
220    }
221
222    #[test]
223    fn test_backbone_deletion_with_replacement() {
224        let mut bb = Backbone::new(3);
225        bb.insert_edge(0, 1, 1.0); // backbone
226        bb.insert_edge(1, 2, 1.0); // backbone
227        bb.insert_edge(0, 2, 1.0); // non-backbone (cycle)
228
229        assert_eq!(bb.backbone_edge_count(), 2);
230
231        // Delete backbone edge 0-1; should find replacement 0-2.
232        let modified = bb.delete_edge(0, 1, 1.0);
233        assert!(modified);
234        assert_eq!(bb.num_components(), 1);
235        // The replacement edge should now be in backbone.
236        assert!(bb.is_backbone_edge(0, 2));
237    }
238
239    #[test]
240    fn test_backbone_deletion_no_replacement() {
241        let mut bb = Backbone::new(3);
242        bb.insert_edge(0, 1, 1.0);
243        bb.insert_edge(1, 2, 1.0);
244
245        // Delete backbone edge; no replacement available.
246        let modified = bb.delete_edge(0, 1, 1.0);
247        assert!(modified);
248        assert_eq!(bb.num_components(), 2);
249    }
250
251    #[test]
252    fn test_auto_grow() {
253        let mut bb = Backbone::new(0);
254        assert!(bb.insert_edge(5, 10, 1.0));
255        assert!(bb.connected(5, 10));
256        assert_eq!(bb.backbone_edge_count(), 1);
257    }
258}