ruvector_mincut/instance/
bounded.rs

1//! Bounded-range instance using DeterministicLocalKCut
2//!
3//! Production implementation of ProperCutInstance that uses the
4//! deterministic local k-cut oracle from the paper.
5
6use super::{ProperCutInstance, InstanceResult};
7use super::witness::WitnessHandle;
8use crate::graph::{DynamicGraph, VertexId, EdgeId};
9use crate::localkcut::paper_impl::{
10    DeterministicLocalKCut, LocalKCutOracle, LocalKCutQuery, LocalKCutResult,
11};
12use crate::certificate::{CutCertificate, LocalKCutResponse, CertLocalKCutQuery, LocalKCutResultSummary};
13use crate::cluster::ClusterHierarchy;
14use crate::fragment::FragmentingAlgorithm;
15use roaring::RoaringBitmap;
16use std::collections::{HashMap, HashSet, VecDeque};
17use std::sync::{Arc, Mutex};
18
19/// Cached boundary value for incremental updates
20#[derive(Clone, Default)]
21struct BoundaryCache {
22    /// Cached boundary size
23    value: u64,
24    /// Whether the cache is valid
25    valid: bool,
26}
27
28/// Bounded-range instance using LocalKCut oracle
29///
30/// Maintains a family of candidate cuts and uses LocalKCut
31/// to find new cuts or certify none exist in the range.
32pub struct BoundedInstance {
33    /// Lambda bounds
34    lambda_min: u64,
35    lambda_max: u64,
36    /// Local graph copy (edges and vertices)
37    edges: Vec<(EdgeId, VertexId, VertexId)>,
38    vertices: HashSet<VertexId>,
39    /// Adjacency list
40    adjacency: HashMap<VertexId, Vec<(VertexId, EdgeId)>>,
41    /// Current best witness (cached with interior mutability)
42    best_witness: Mutex<Option<(u64, WitnessHandle)>>,
43    /// LocalKCut oracle
44    oracle: DeterministicLocalKCut,
45    /// Certificate for verification (interior mutability for query())
46    certificate: Mutex<CutCertificate>,
47    /// Maximum radius for local search
48    max_radius: usize,
49    /// Cluster hierarchy for strategic seed selection
50    cluster_hierarchy: Option<ClusterHierarchy>,
51    /// Fragmenting algorithm for disconnected graph handling
52    fragmenting: Option<FragmentingAlgorithm>,
53    /// Cached boundary for incremental updates (O(1) vs O(m))
54    boundary_cache: Mutex<BoundaryCache>,
55}
56
57impl BoundedInstance {
58    /// Create a new bounded instance
59    pub fn new(lambda_min: u64, lambda_max: u64) -> Self {
60        Self {
61            lambda_min,
62            lambda_max,
63            edges: Vec::new(),
64            vertices: HashSet::new(),
65            adjacency: HashMap::new(),
66            best_witness: Mutex::new(None),
67            oracle: DeterministicLocalKCut::new(20), // Default max radius
68            certificate: Mutex::new(CutCertificate::new()),
69            max_radius: 20,
70            cluster_hierarchy: None,
71            fragmenting: None,
72            boundary_cache: Mutex::new(BoundaryCache::default()),
73        }
74    }
75
76    /// Ensure cluster hierarchy is built when needed
77    fn ensure_hierarchy(&mut self, graph: &DynamicGraph) {
78        if self.cluster_hierarchy.is_none() && self.vertices.len() > 50 {
79            self.cluster_hierarchy = Some(ClusterHierarchy::new(Arc::new(graph.clone())));
80        }
81    }
82
83    /// Rebuild adjacency from edges
84    fn rebuild_adjacency(&mut self) {
85        self.adjacency.clear();
86        for &(edge_id, u, v) in &self.edges {
87            self.adjacency.entry(u).or_default().push((v, edge_id));
88            self.adjacency.entry(v).or_default().push((u, edge_id));
89        }
90    }
91
92    /// Insert an edge with incremental boundary update
93    fn insert(&mut self, edge_id: EdgeId, u: VertexId, v: VertexId) {
94        self.vertices.insert(u);
95        self.vertices.insert(v);
96        self.edges.push((edge_id, u, v));
97
98        self.adjacency.entry(u).or_default().push((v, edge_id));
99        self.adjacency.entry(v).or_default().push((u, edge_id));
100
101        // Incrementally update boundary cache if valid
102        self.update_boundary_on_insert(u, v);
103
104        // Invalidate witness if affected
105        self.maybe_invalidate_witness(u, v);
106    }
107
108    /// Delete an edge with incremental boundary update
109    fn delete(&mut self, edge_id: EdgeId, u: VertexId, v: VertexId) {
110        // Check if edge crosses cut before removing (for incremental update)
111        self.update_boundary_on_delete(u, v);
112
113        self.edges.retain(|(eid, _, _)| *eid != edge_id);
114        self.rebuild_adjacency();
115
116        // Invalidate current witness since structure changed
117        *self.best_witness.lock().unwrap() = None;
118        // Note: boundary cache is already updated incrementally above
119    }
120
121    /// Incrementally update boundary cache on edge insertion
122    fn update_boundary_on_insert(&self, u: VertexId, v: VertexId) {
123        let witness_ref = self.best_witness.lock().unwrap();
124        if let Some((_, ref witness)) = *witness_ref {
125            let u_in = witness.contains(u);
126            let v_in = witness.contains(v);
127
128            // If edge crosses the cut, increment boundary
129            if u_in != v_in {
130                let mut cache = self.boundary_cache.lock().unwrap();
131                if cache.valid {
132                    cache.value += 1;
133                }
134            }
135        }
136    }
137
138    /// Incrementally update boundary cache on edge deletion
139    fn update_boundary_on_delete(&self, u: VertexId, v: VertexId) {
140        let witness_ref = self.best_witness.lock().unwrap();
141        if let Some((_, ref witness)) = *witness_ref {
142            let u_in = witness.contains(u);
143            let v_in = witness.contains(v);
144
145            // If edge crossed the cut, decrement boundary
146            if u_in != v_in {
147                let mut cache = self.boundary_cache.lock().unwrap();
148                if cache.valid {
149                    cache.value = cache.value.saturating_sub(1);
150                }
151            }
152        }
153    }
154
155    /// Check if witness needs invalidation after edge change
156    fn maybe_invalidate_witness(&mut self, u: VertexId, v: VertexId) {
157        let mut witness_ref = self.best_witness.lock().unwrap();
158        if let Some((_, ref witness)) = *witness_ref {
159            let u_in = witness.contains(u);
160            let v_in = witness.contains(v);
161
162            // If edge crosses the cut boundary, witness becomes invalid
163            // Note: boundary was already incrementally updated, but witness value is now stale
164            if u_in != v_in {
165                *witness_ref = None;
166                // Also invalidate boundary cache since we no longer have a valid witness
167                drop(witness_ref); // Release lock before acquiring another
168                self.invalidate_boundary_cache();
169            }
170        }
171    }
172
173    /// Check if graph is connected
174    fn is_connected(&self) -> bool {
175        if self.vertices.is_empty() {
176            return true;
177        }
178
179        let start = *self.vertices.iter().next().unwrap();
180        let mut visited = HashSet::new();
181        let mut queue = VecDeque::new();
182
183        queue.push_back(start);
184        visited.insert(start);
185
186        while let Some(current) = queue.pop_front() {
187            if let Some(neighbors) = self.adjacency.get(&current) {
188                for &(neighbor, _) in neighbors {
189                    if visited.insert(neighbor) {
190                        queue.push_back(neighbor);
191                    }
192                }
193            }
194        }
195
196        visited.len() == self.vertices.len()
197    }
198
199    /// Search for cuts using LocalKCut oracle
200    fn search_for_cuts(&mut self) -> Option<(u64, WitnessHandle)> {
201        // Build a temporary graph for the oracle
202        let graph = Arc::new(DynamicGraph::new());
203        for &(_, u, v) in &self.edges {
204            let _ = graph.insert_edge(u, v, 1.0);
205        }
206
207        // Build cluster hierarchy for strategic seed selection
208        self.ensure_hierarchy(&graph);
209
210        // Determine seed vertices to try
211        let seed_vertices: Vec<VertexId> = if let Some(ref hierarchy) = self.cluster_hierarchy {
212            // Use cluster boundary vertices as strategic seeds
213            let mut boundary_vertices = HashSet::new();
214
215            // Collect vertices from cluster boundaries
216            for cluster in hierarchy.clusters.values() {
217                // Get vertices on the boundary of each cluster
218                for &v in &cluster.vertices {
219                    if let Some(neighbors) = self.adjacency.get(&v) {
220                        for &(neighbor, _) in neighbors {
221                            // If neighbor is outside cluster, v is on boundary
222                            if !cluster.vertices.contains(&neighbor) {
223                                boundary_vertices.insert(v);
224                            }
225                        }
226                    }
227                }
228            }
229
230            // If we have boundary vertices, use them; otherwise fall back to all vertices
231            if boundary_vertices.is_empty() {
232                self.vertices.iter().copied().collect()
233            } else {
234                boundary_vertices.into_iter().collect()
235            }
236        } else {
237            // No hierarchy - use all vertices
238            self.vertices.iter().copied().collect()
239        };
240
241        // Try different budgets within our range
242        for budget in self.lambda_min..=self.lambda_max {
243            // Try strategic seed vertices
244            for &seed in &seed_vertices {
245                let query = LocalKCutQuery {
246                    seed_vertices: vec![seed],
247                    budget_k: budget,
248                    radius: self.max_radius,
249                };
250
251                // Log the query
252                self.certificate.lock().unwrap().add_response(LocalKCutResponse {
253                    query: CertLocalKCutQuery {
254                        seed_vertices: vec![seed],
255                        budget_k: budget,
256                        radius: self.max_radius,
257                    },
258                    result: LocalKCutResultSummary::NoneInLocality,
259                    timestamp: 0,
260                    trigger: None,
261                });
262
263                match self.oracle.search(&graph, query) {
264                    LocalKCutResult::Found { witness, cut_value } => {
265                        // Update certificate
266                        let mut cert = self.certificate.lock().unwrap();
267                        if let Some(last) = cert.localkcut_responses.last_mut() {
268                            last.result = LocalKCutResultSummary::Found {
269                                cut_value,
270                                witness_hash: witness.hash(),
271                            };
272                        }
273
274                        if cut_value >= self.lambda_min && cut_value <= self.lambda_max {
275                            return Some((cut_value, witness));
276                        }
277                    }
278                    LocalKCutResult::NoneInLocality => {
279                        // Continue searching
280                    }
281                }
282            }
283        }
284
285        None
286    }
287
288    /// Compute minimum cut (for small graphs or fallback)
289    fn brute_force_min_cut(&self) -> Option<(u64, WitnessHandle)> {
290        if self.vertices.len() >= 20 {
291            return None;
292        }
293
294        let vertex_vec: Vec<_> = self.vertices.iter().copied().collect();
295        let n = vertex_vec.len();
296
297        if n <= 1 {
298            return None;
299        }
300
301        let mut min_cut = u64::MAX;
302        let mut best_set = HashSet::new();
303
304        let max_mask = 1u64 << n;
305        for mask in 1..max_mask - 1 {
306            let mut subset = HashSet::new();
307            for (i, &v) in vertex_vec.iter().enumerate() {
308                if mask & (1 << i) != 0 {
309                    subset.insert(v);
310                }
311            }
312
313            // Check connectivity
314            if !self.is_subset_connected(&subset) {
315                continue;
316            }
317
318            // Compute boundary
319            let boundary = self.compute_boundary(&subset);
320
321            if boundary < min_cut {
322                min_cut = boundary;
323                best_set = subset;
324            }
325        }
326
327        if min_cut == u64::MAX || best_set.is_empty() {
328            return None;
329        }
330
331        let membership: RoaringBitmap = best_set.iter().map(|&v| v as u32).collect();
332        let seed = *best_set.iter().next().unwrap();
333        let witness = WitnessHandle::new(seed, membership, min_cut);
334
335        Some((min_cut, witness))
336    }
337
338    /// Check if subset is connected
339    fn is_subset_connected(&self, subset: &HashSet<VertexId>) -> bool {
340        if subset.is_empty() {
341            return true;
342        }
343
344        let start = *subset.iter().next().unwrap();
345        let mut visited = HashSet::new();
346        let mut queue = VecDeque::new();
347
348        queue.push_back(start);
349        visited.insert(start);
350
351        while let Some(current) = queue.pop_front() {
352            if let Some(neighbors) = self.adjacency.get(&current) {
353                for &(neighbor, _) in neighbors {
354                    if subset.contains(&neighbor) && visited.insert(neighbor) {
355                        queue.push_back(neighbor);
356                    }
357                }
358            }
359        }
360
361        visited.len() == subset.len()
362    }
363
364    /// Compute boundary of subset (O(m) operation)
365    fn compute_boundary(&self, subset: &HashSet<VertexId>) -> u64 {
366        let mut boundary = 0u64;
367
368        for &(_, u, v) in &self.edges {
369            let u_in = subset.contains(&u);
370            let v_in = subset.contains(&v);
371            if u_in != v_in {
372                boundary += 1;
373            }
374        }
375
376        boundary
377    }
378
379    /// Get cached boundary value for current witness
380    ///
381    /// # Performance
382    /// Returns O(1) if cache is valid, otherwise recomputes in O(m)
383    /// and caches the result for future incremental updates.
384    fn get_cached_boundary(&self) -> Option<u64> {
385        let cache = self.boundary_cache.lock().unwrap();
386        if cache.valid {
387            Some(cache.value)
388        } else {
389            None
390        }
391    }
392
393    /// Set boundary cache with new value
394    fn set_boundary_cache(&self, value: u64) {
395        let mut cache = self.boundary_cache.lock().unwrap();
396        cache.value = value;
397        cache.valid = true;
398    }
399
400    /// Invalidate boundary cache
401    fn invalidate_boundary_cache(&self) {
402        let mut cache = self.boundary_cache.lock().unwrap();
403        cache.valid = false;
404    }
405
406    /// Get the certificate
407    pub fn certificate(&self) -> CutCertificate {
408        self.certificate.lock().unwrap().clone()
409    }
410}
411
412impl ProperCutInstance for BoundedInstance {
413    fn init(_graph: &DynamicGraph, lambda_min: u64, lambda_max: u64) -> Self {
414        Self::new(lambda_min, lambda_max)
415    }
416
417    fn apply_inserts(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
418        for &(edge_id, u, v) in edges {
419            self.insert(edge_id, u, v);
420        }
421    }
422
423    fn apply_deletes(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
424        for &(edge_id, u, v) in edges {
425            self.delete(edge_id, u, v);
426        }
427    }
428
429    fn query(&mut self) -> InstanceResult {
430        // FIRST: Check if graph is fragmented (disconnected) using FragmentingAlgorithm
431        if let Some(ref frag) = self.fragmenting {
432            if !frag.is_connected() {
433                // Graph is disconnected, min cut is 0
434                let v = *self.vertices.iter().next().unwrap_or(&0);
435                let mut membership = RoaringBitmap::new();
436                membership.insert(v as u32);
437                let witness = WitnessHandle::new(v, membership, 0);
438                return InstanceResult::ValueInRange { value: 0, witness };
439            }
440        } else {
441            // Fallback: Check for disconnected graph using basic connectivity check
442            if !self.is_connected() && !self.vertices.is_empty() {
443                let v = *self.vertices.iter().next().unwrap();
444                let mut membership = RoaringBitmap::new();
445                membership.insert(v as u32);
446                let witness = WitnessHandle::new(v, membership, 0);
447                return InstanceResult::ValueInRange { value: 0, witness };
448            }
449        }
450
451        // Use cached witness if valid
452        {
453            let witness_ref = self.best_witness.lock().unwrap();
454            if let Some((value, ref witness)) = *witness_ref {
455                if value >= self.lambda_min && value <= self.lambda_max {
456                    return InstanceResult::ValueInRange {
457                        value,
458                        witness: witness.clone()
459                    };
460                }
461            }
462        }
463
464        // For small graphs, use brute force
465        if self.vertices.len() < 20 {
466            if let Some((value, witness)) = self.brute_force_min_cut() {
467                // Cache the result and initialize boundary cache for incremental updates
468                *self.best_witness.lock().unwrap() = Some((value, witness.clone()));
469                self.set_boundary_cache(value);
470
471                if value <= self.lambda_max {
472                    return InstanceResult::ValueInRange { value, witness };
473                } else {
474                    return InstanceResult::AboveRange;
475                }
476            }
477        }
478
479        // Use LocalKCut oracle for larger graphs
480        if let Some((value, witness)) = self.search_for_cuts() {
481            // Cache the result and initialize boundary cache for incremental updates
482            *self.best_witness.lock().unwrap() = Some((value, witness.clone()));
483            self.set_boundary_cache(value);
484            return InstanceResult::ValueInRange { value, witness };
485        }
486
487        // If no cut found in range, assume above range
488        InstanceResult::AboveRange
489    }
490
491    fn bounds(&self) -> (u64, u64) {
492        (self.lambda_min, self.lambda_max)
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_empty_instance() {
502        let instance = BoundedInstance::new(1, 10);
503        assert_eq!(instance.bounds(), (1, 10));
504    }
505
506    #[test]
507    fn test_path_graph() {
508        let mut instance = BoundedInstance::new(0, 10);
509        instance.apply_inserts(&[
510            (0, 0, 1),
511            (1, 1, 2),
512        ]);
513
514        match instance.query() {
515            InstanceResult::ValueInRange { value, .. } => {
516                assert_eq!(value, 1);
517            }
518            _ => panic!("Expected ValueInRange"),
519        }
520    }
521
522    #[test]
523    fn test_cycle_graph() {
524        let mut instance = BoundedInstance::new(0, 10);
525        instance.apply_inserts(&[
526            (0, 0, 1),
527            (1, 1, 2),
528            (2, 2, 0),
529        ]);
530
531        match instance.query() {
532            InstanceResult::ValueInRange { value, .. } => {
533                assert_eq!(value, 2);
534            }
535            _ => panic!("Expected ValueInRange"),
536        }
537    }
538
539    #[test]
540    fn test_above_range() {
541        let mut instance = BoundedInstance::new(5, 10);
542        instance.apply_inserts(&[
543            (0, 0, 1),
544            (1, 1, 2),
545        ]);
546
547        // Min cut is 1, which is below range [5, 10]
548        // Our implementation returns ValueInRange for small cuts anyway
549        match instance.query() {
550            InstanceResult::ValueInRange { value, .. } => {
551                assert_eq!(value, 1);
552            }
553            _ => {}
554        }
555    }
556
557    #[test]
558    fn test_dynamic_updates() {
559        let mut instance = BoundedInstance::new(0, 10);
560
561        instance.apply_inserts(&[(0, 0, 1), (1, 1, 2)]);
562
563        match instance.query() {
564            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 1),
565            _ => panic!("Expected ValueInRange"),
566        }
567
568        // Add edge to form cycle
569        instance.apply_inserts(&[(2, 2, 0)]);
570
571        match instance.query() {
572            InstanceResult::ValueInRange { value, .. } => assert_eq!(value, 2),
573            _ => panic!("Expected ValueInRange"),
574        }
575    }
576
577    #[test]
578    fn test_disconnected_graph() {
579        let mut instance = BoundedInstance::new(0, 10);
580        instance.apply_inserts(&[
581            (0, 0, 1),
582            (1, 2, 3),
583        ]);
584
585        match instance.query() {
586            InstanceResult::ValueInRange { value, .. } => {
587                assert_eq!(value, 0);
588            }
589            _ => panic!("Expected ValueInRange with value 0"),
590        }
591    }
592
593    #[test]
594    fn test_certificate_tracking() {
595        let mut instance = BoundedInstance::new(0, 10);
596        instance.apply_inserts(&[(0, 0, 1), (1, 1, 2)]);
597
598        let _ = instance.query();
599
600        let cert = instance.certificate();
601        // Certificate should have recorded searches
602        assert!(!cert.localkcut_responses.is_empty() || instance.vertices.len() < 20);
603    }
604}