ruvector_mincut/instance/
stub.rs

1//! Stub implementation of ProperCutInstance
2//!
3//! Brute-force reference implementation for testing.
4//! Recomputes minimum cut on every query - O(2^n) worst case.
5//! Only suitable for small graphs (n < 20).
6
7use super::{ProperCutInstance, InstanceResult};
8use super::witness::WitnessHandle;
9use crate::graph::{VertexId, EdgeId, DynamicGraph};
10use roaring::RoaringBitmap;
11use std::collections::{HashMap, HashSet, VecDeque};
12
13/// Stub instance that does brute-force min cut computation
14///
15/// This implementation:
16/// - Stores a local copy of all edges
17/// - Enumerates all proper subsets on each query
18/// - Checks connectivity via BFS
19/// - Computes exact boundary values
20///
21/// # Performance
22///
23/// - Query: O(2^n ยท m) where n = vertices, m = edges
24/// - Only practical for n < 20
25///
26/// # Purpose
27///
28/// Used as a reference implementation to test the wrapper logic
29/// before the real LocalKCut algorithm is ready.
30pub struct StubInstance {
31    /// Lambda bounds
32    lambda_min: u64,
33    lambda_max: u64,
34    /// Local copy of edges for computation
35    edges: Vec<(VertexId, VertexId, EdgeId)>,
36    /// Vertex set
37    vertices: HashSet<VertexId>,
38    /// Adjacency list: vertex -> [(neighbor, edge_id), ...]
39    adjacency: HashMap<VertexId, Vec<(VertexId, EdgeId)>>,
40}
41
42impl StubInstance {
43    /// Create a new stub instance with initial graph state
44    ///
45    /// This is used for direct testing. The wrapper should use `init()` instead.
46    pub fn new(graph: &DynamicGraph, lambda_min: u64, lambda_max: u64) -> Self {
47        let mut instance = Self {
48            lambda_min,
49            lambda_max,
50            edges: Vec::new(),
51            vertices: HashSet::new(),
52            adjacency: HashMap::new(),
53        };
54
55        // Copy initial graph state
56        for edge in graph.edges() {
57            instance.vertices.insert(edge.source);
58            instance.vertices.insert(edge.target);
59            instance.edges.push((edge.source, edge.target, edge.id));
60        }
61
62        instance.rebuild_adjacency();
63        instance
64    }
65
66    /// Create an empty stub instance for use with the wrapper
67    ///
68    /// The wrapper will apply all edges via apply_inserts/apply_deletes.
69    pub fn new_empty(lambda_min: u64, lambda_max: u64) -> Self {
70        Self {
71            lambda_min,
72            lambda_max,
73            edges: Vec::new(),
74            vertices: HashSet::new(),
75            adjacency: HashMap::new(),
76        }
77    }
78
79    /// Compute minimum cut via brute-force enumeration
80    ///
81    /// For each non-empty proper subset S:
82    /// 1. Check if S is connected
83    /// 2. If connected, compute boundary size
84    /// 3. Track minimum
85    ///
86    /// Returns None if graph is empty or disconnected.
87    fn compute_min_cut(&self) -> Option<(u64, WitnessHandle)> {
88        if self.vertices.is_empty() {
89            return None;
90        }
91
92        let n = self.vertices.len();
93        if n == 1 {
94            // Single vertex: no proper cuts
95            return None;
96        }
97
98        // Stub instance only works for small graphs to avoid overflow
99        // For large graphs, we return a large value to signal AboveRange
100        if n >= 20 {
101            // Return a large value that will trigger AboveRange
102            return None;
103        }
104
105        // Check if graph is connected
106        if !self.is_connected() {
107            // Disconnected graph has min cut 0
108            let membership = RoaringBitmap::from_iter(self.vertices.iter().take(1).map(|&v| v as u32));
109            let seed = *self.vertices.iter().next().unwrap();
110            let witness = WitnessHandle::new(seed, membership, 0);
111            return Some((0, witness));
112        }
113
114        let vertex_vec: Vec<VertexId> = self.vertices.iter().copied().collect();
115        let mut min_cut = u64::MAX;
116        let mut best_set = HashSet::new();
117
118        // Enumerate all non-empty proper subsets (2^n - 2 subsets)
119        // We use bitmasks from 1 to 2^n - 2
120        let max_mask = 1u64 << n;
121
122        for mask in 1..max_mask - 1 {
123            // Build subset from bitmask
124            let mut subset = HashSet::new();
125            for (i, &vertex) in vertex_vec.iter().enumerate() {
126                if mask & (1 << i) != 0 {
127                    subset.insert(vertex);
128                }
129            }
130
131            // Check if subset is connected
132            if !self.is_connected_set(&subset) {
133                continue;
134            }
135
136            // Compute boundary
137            let (boundary_value, _boundary_edges) = self.compute_boundary(&subset);
138
139            if boundary_value < min_cut {
140                min_cut = boundary_value;
141                best_set = subset.clone();
142            }
143        }
144
145        if min_cut == u64::MAX {
146            // No proper connected cuts found (shouldn't happen for connected graphs)
147            return None;
148        }
149
150        // Build witness using new API
151        // Convert HashSet to RoaringBitmap (u32)
152        let membership: RoaringBitmap = best_set.iter().map(|&v| v as u32).collect();
153
154        // Use first vertex in set as seed
155        let seed = *best_set.iter().next().unwrap();
156
157        let witness = WitnessHandle::new(seed, membership, min_cut);
158
159        Some((min_cut, witness))
160    }
161
162    /// Check if a subset of vertices is connected
163    ///
164    /// Uses BFS within the subset to check connectivity.
165    fn is_connected_set(&self, vertices: &HashSet<VertexId>) -> bool {
166        if vertices.is_empty() {
167            return true;
168        }
169
170        // Start BFS from arbitrary vertex in the set
171        let start = *vertices.iter().next().unwrap();
172        let mut visited = HashSet::new();
173        let mut queue = VecDeque::new();
174
175        queue.push_back(start);
176        visited.insert(start);
177
178        while let Some(current) = queue.pop_front() {
179            if let Some(neighbors) = self.adjacency.get(&current) {
180                for &(neighbor, _edge_id) in neighbors {
181                    // Only follow edges within the subset
182                    if vertices.contains(&neighbor) && visited.insert(neighbor) {
183                        queue.push_back(neighbor);
184                    }
185                }
186            }
187        }
188
189        // Connected if we visited all vertices in the subset
190        visited.len() == vertices.len()
191    }
192
193    /// Compute boundary of a vertex set
194    ///
195    /// Returns (boundary_value, boundary_edges).
196    /// Boundary = edges with exactly one endpoint in the set.
197    fn compute_boundary(&self, set: &HashSet<VertexId>) -> (u64, Vec<EdgeId>) {
198        let mut boundary_value = 0u64;
199        let mut boundary_edges = Vec::new();
200
201        for &(u, v, edge_id) in &self.edges {
202            let u_in_set = set.contains(&u);
203            let v_in_set = set.contains(&v);
204
205            // Edge is in boundary if exactly one endpoint is in set
206            if u_in_set != v_in_set {
207                boundary_value += 1;
208                boundary_edges.push(edge_id);
209            }
210        }
211
212        (boundary_value, boundary_edges)
213    }
214
215    /// Check if entire graph is connected
216    fn is_connected(&self) -> bool {
217        self.is_connected_set(&self.vertices)
218    }
219
220    /// Rebuild adjacency list from edges
221    fn rebuild_adjacency(&mut self) {
222        self.adjacency.clear();
223
224        for &(u, v, edge_id) in &self.edges {
225            self.adjacency
226                .entry(u)
227                .or_insert_with(Vec::new)
228                .push((v, edge_id));
229
230            self.adjacency
231                .entry(v)
232                .or_insert_with(Vec::new)
233                .push((u, edge_id));
234        }
235    }
236
237    fn insert(&mut self, edge_id: EdgeId, u: VertexId, v: VertexId) {
238        // Add edge to local copy
239        self.vertices.insert(u);
240        self.vertices.insert(v);
241        self.edges.push((u, v, edge_id));
242        self.rebuild_adjacency();
243    }
244
245    fn delete(&mut self, edge_id: EdgeId, _u: VertexId, _v: VertexId) {
246        // Remove edge from local copy
247        self.edges.retain(|(_, _, eid)| *eid != edge_id);
248        self.rebuild_adjacency();
249    }
250}
251
252impl ProperCutInstance for StubInstance {
253    fn init(_graph: &DynamicGraph, lambda_min: u64, lambda_max: u64) -> Self {
254        // For wrapper use: start empty, wrapper will call apply_inserts
255        Self::new_empty(lambda_min, lambda_max)
256    }
257
258    fn apply_inserts(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
259        for &(edge_id, u, v) in edges {
260            self.insert(edge_id, u, v);
261        }
262    }
263
264    fn apply_deletes(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
265        for &(edge_id, u, v) in edges {
266            self.delete(edge_id, u, v);
267        }
268    }
269
270    fn query(&mut self) -> InstanceResult {
271        match self.compute_min_cut() {
272            Some((value, witness)) if value <= self.lambda_max => {
273                InstanceResult::ValueInRange { value, witness }
274            }
275            _ => InstanceResult::AboveRange,
276        }
277    }
278
279    fn bounds(&self) -> (u64, u64) {
280        (self.lambda_min, self.lambda_max)
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::graph::DynamicGraph;
288
289    #[test]
290    fn test_empty_graph() {
291        let graph = DynamicGraph::new();
292        let mut instance = StubInstance::new(&graph, 0, 10);
293
294        let result = instance.query();
295        assert!(matches!(result, InstanceResult::AboveRange));
296    }
297
298    #[test]
299    fn test_single_vertex() {
300        let graph = DynamicGraph::new();
301        graph.add_vertex(1);
302
303        let mut instance = StubInstance::new(&graph, 0, 10);
304        let result = instance.query();
305        assert!(matches!(result, InstanceResult::AboveRange));
306    }
307
308    #[test]
309    fn test_path_graph() {
310        // Path: 1 - 2 - 3
311        let graph = DynamicGraph::new();
312        graph.insert_edge(1, 2, 1.0).unwrap();
313        graph.insert_edge(2, 3, 1.0).unwrap();
314
315        let mut instance = StubInstance::new(&graph, 0, 10);
316
317        let result = instance.query();
318        match result {
319            InstanceResult::ValueInRange { value, .. } => {
320                // Min cut of path is 1
321                assert_eq!(value, 1);
322            }
323            _ => panic!("Expected ValueInRange result"),
324        }
325    }
326
327    #[test]
328    fn test_cycle_graph() {
329        // Cycle: 1 - 2 - 3 - 1
330        let graph = DynamicGraph::new();
331        graph.insert_edge(1, 2, 1.0).unwrap();
332        graph.insert_edge(2, 3, 1.0).unwrap();
333        graph.insert_edge(3, 1, 1.0).unwrap();
334
335        let mut instance = StubInstance::new(&graph, 0, 10);
336
337        let result = instance.query();
338        match result {
339            InstanceResult::ValueInRange { value, .. } => {
340                // Min cut of cycle is 2
341                assert_eq!(value, 2);
342            }
343            _ => panic!("Expected ValueInRange result"),
344        }
345    }
346
347    #[test]
348    fn test_complete_graph_k4() {
349        // Complete graph K4
350        let graph = DynamicGraph::new();
351        for i in 1..=4 {
352            for j in i + 1..=4 {
353                graph.insert_edge(i, j, 1.0).unwrap();
354            }
355        }
356
357        let mut instance = StubInstance::new(&graph, 0, 10);
358
359        let result = instance.query();
360        match result {
361            InstanceResult::ValueInRange { value, .. } => {
362                // Min cut of K4 is 3 (minimum degree)
363                assert_eq!(value, 3);
364            }
365            _ => panic!("Expected ValueInRange result"),
366        }
367    }
368
369    #[test]
370    fn test_disconnected_graph() {
371        // Two separate edges: 1-2 and 3-4
372        let graph = DynamicGraph::new();
373        graph.insert_edge(1, 2, 1.0).unwrap();
374        graph.insert_edge(3, 4, 1.0).unwrap();
375
376        let mut instance = StubInstance::new(&graph, 0, 10);
377
378        let result = instance.query();
379        match result {
380            InstanceResult::ValueInRange { value, .. } => {
381                // Disconnected graph has min cut 0
382                assert_eq!(value, 0);
383            }
384            _ => panic!("Expected ValueInRange with value 0"),
385        }
386    }
387
388    #[test]
389    fn test_bridge_graph() {
390        // Two triangles connected by a bridge
391        let graph = DynamicGraph::new();
392        graph.insert_edge(1, 2, 1.0).unwrap();
393        graph.insert_edge(2, 3, 1.0).unwrap();
394        graph.insert_edge(3, 1, 1.0).unwrap();
395        graph.insert_edge(3, 4, 1.0).unwrap(); // Bridge
396        graph.insert_edge(4, 5, 1.0).unwrap();
397        graph.insert_edge(5, 6, 1.0).unwrap();
398        graph.insert_edge(6, 4, 1.0).unwrap();
399
400        let mut instance = StubInstance::new(&graph, 0, 10);
401
402        let result = instance.query();
403        match result {
404            InstanceResult::ValueInRange { value, .. } => {
405                // Min cut is the bridge (value = 1)
406                assert_eq!(value, 1);
407            }
408            _ => panic!("Expected ValueInRange result"),
409        }
410    }
411
412    #[test]
413    fn test_is_connected_set() {
414        let graph = DynamicGraph::new();
415        graph.insert_edge(1, 2, 1.0).unwrap();
416        graph.insert_edge(2, 3, 1.0).unwrap();
417        graph.insert_edge(3, 4, 1.0).unwrap();
418
419        let instance = StubInstance::new(&graph, 0, 10);
420
421        // Test connected subset
422        let mut subset = HashSet::new();
423        subset.insert(1);
424        subset.insert(2);
425        subset.insert(3);
426        assert!(instance.is_connected_set(&subset));
427
428        // Test disconnected subset (1 and 4 not directly connected)
429        let mut subset = HashSet::new();
430        subset.insert(1);
431        subset.insert(4);
432        assert!(!instance.is_connected_set(&subset));
433
434        // Test single vertex (always connected)
435        let mut subset = HashSet::new();
436        subset.insert(1);
437        assert!(instance.is_connected_set(&subset));
438    }
439
440    #[test]
441    fn test_compute_boundary() {
442        let graph = DynamicGraph::new();
443        let e1 = graph.insert_edge(1, 2, 1.0).unwrap();
444        let e2 = graph.insert_edge(2, 3, 1.0).unwrap();
445        let _e3 = graph.insert_edge(3, 4, 1.0).unwrap();
446
447        let instance = StubInstance::new(&graph, 0, 10);
448
449        // Boundary of {1, 2}
450        let mut set = HashSet::new();
451        set.insert(1);
452        set.insert(2);
453        let (value, edges) = instance.compute_boundary(&set);
454        assert_eq!(value, 1); // Only edge 2-3 crosses
455        assert_eq!(edges.len(), 1);
456        assert!(edges.contains(&e2));
457
458        // Boundary of {2}
459        let mut set = HashSet::new();
460        set.insert(2);
461        let (value, edges) = instance.compute_boundary(&set);
462        assert_eq!(value, 2); // Edges 1-2 and 2-3 cross
463        assert_eq!(edges.len(), 2);
464        assert!(edges.contains(&e1));
465        assert!(edges.contains(&e2));
466    }
467
468    #[test]
469    fn test_dynamic_updates() {
470        let graph = DynamicGraph::new();
471        graph.insert_edge(1, 2, 1.0).unwrap();
472        graph.insert_edge(2, 3, 1.0).unwrap();
473
474        let mut instance = StubInstance::new(&graph, 0, 10);
475
476        // Initial min cut (path: 1-2-3) is 1
477        let result = instance.query();
478        match result {
479            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
480            _ => panic!("Expected ValueInRange"),
481        }
482
483        // Insert edge to form cycle
484        let e3_id = 100; // Mock edge ID
485        instance.apply_inserts(&[(e3_id, 3, 1)]);
486
487        // Now min cut (cycle: 1-2-3-1) is 2
488        let result = instance.query();
489        match result {
490            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 2),
491            _ => panic!("Expected ValueInRange"),
492        }
493
494        // Delete one edge to get back to path
495        instance.apply_deletes(&[(e3_id, 3, 1)]);
496
497        // Min cut should be 1 again
498        let result = instance.query();
499        match result {
500            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
501            _ => panic!("Expected ValueInRange"),
502        }
503    }
504
505    #[test]
506    fn test_range_bounds() {
507        let graph = DynamicGraph::new();
508        graph.insert_edge(1, 2, 1.0).unwrap();
509        graph.insert_edge(2, 3, 1.0).unwrap();
510
511        // Instance with range [2, 5]
512        let mut instance = StubInstance::new(&graph, 2, 5);
513
514        // Min cut is 1, which is below range [2,5], but stub only checks <= lambda_max
515        // so it returns ValueInRange
516        let result = instance.query();
517        // Stub doesn't check lambda_min, so behavior depends on implementation
518        
519        // Instance with range [0, 1]
520        let mut instance = StubInstance::new(&graph, 0, 1);
521
522        // Min cut is 1, which is in range
523        let result = instance.query();
524        match result {
525            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
526            _ => panic!("Expected ValueInRange"),
527        }
528
529        // Instance with range [0, 0]
530        let mut instance = StubInstance::new(&graph, 0, 0);
531
532        // Min cut is 1, which is above range
533        let result = instance.query();
534        assert!(matches!(result, InstanceResult::AboveRange));
535    }
536
537    #[test]
538    fn test_witness_information() {
539        let graph = DynamicGraph::new();
540        graph.insert_edge(1, 2, 1.0).unwrap();
541        graph.insert_edge(2, 3, 1.0).unwrap();
542
543        let mut instance = StubInstance::new(&graph, 0, 10);
544
545        let result = instance.query();
546        match result {
547            InstanceResult::ValueInRange { value, witness } => {
548                assert_eq!(value, 1);
549                assert_eq!(witness.boundary_size(), 1);
550                assert!(witness.cardinality() > 0);
551                assert!(witness.cardinality() < 3); // Proper cut
552            }
553            _ => panic!("Expected ValueInRange with witness"),
554        }
555    }
556}