Skip to main content

ruvector_mincut/integration/
mod.rs

1//! RuVector Integration Layer
2//!
3//! Connects the minimum cut algorithm to the ruvector ecosystem.
4//! Provides seamless integration with vector databases and graph processing.
5
6// Integration module - allow missing docs for internal helpers
7#![allow(missing_docs)]
8
9use crate::graph::{DynamicGraph, EdgeId, VertexId, Weight};
10use crate::wrapper::{MinCutResult, MinCutWrapper};
11use std::sync::Arc;
12
13// Agentic chip support (feature-gated)
14#[cfg(feature = "agentic")]
15use crate::parallel::{CoreExecutor, SharedCoordinator, NUM_CORES};
16
17/// Graph connectivity analysis for ruvector
18///
19/// Useful for:
20/// - Detecting communities in vector similarity graphs
21/// - Finding cut points in knowledge graphs
22/// - Partitioning data for distributed processing
23pub struct RuVectorGraphAnalyzer {
24    graph: Arc<DynamicGraph>,
25    wrapper: MinCutWrapper,
26    /// Cached analysis results
27    cached_min_cut: Option<u64>,
28    cached_partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
29}
30
31impl RuVectorGraphAnalyzer {
32    /// Create analyzer for a graph
33    pub fn new(graph: Arc<DynamicGraph>) -> Self {
34        let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
35
36        // Sync wrapper with existing graph edges
37        for edge in graph.edges() {
38            wrapper.insert_edge(edge.id, edge.source, edge.target);
39        }
40
41        Self {
42            graph,
43            wrapper,
44            cached_min_cut: None,
45            cached_partition: None,
46        }
47    }
48
49    /// Build graph from similarity matrix
50    ///
51    /// Creates edges between vectors with similarity above threshold
52    pub fn from_similarity_matrix(
53        similarities: &[f64],
54        num_vectors: usize,
55        threshold: f64,
56    ) -> Self {
57        let graph = Arc::new(DynamicGraph::new());
58
59        for i in 0..num_vectors {
60            for j in (i + 1)..num_vectors {
61                let sim = similarities[i * num_vectors + j];
62                if sim >= threshold {
63                    let _ = graph.insert_edge(i as u64, j as u64, sim);
64                }
65            }
66        }
67
68        Self::new(graph)
69    }
70
71    /// Build k-NN graph from vectors
72    pub fn from_knn(neighbors: &[(usize, Vec<(usize, f64)>)]) -> Self {
73        let graph = Arc::new(DynamicGraph::new());
74
75        for &(vertex, ref nn_list) in neighbors {
76            for &(neighbor, distance) in nn_list {
77                // Use 1/distance as weight (closer = stronger connection)
78                let weight = if distance > 0.0 { 1.0 / distance } else { 1.0 };
79                let _ = graph.insert_edge(vertex as u64, neighbor as u64, weight);
80            }
81        }
82
83        Self::new(graph)
84    }
85
86    /// Compute minimum cut
87    pub fn min_cut(&mut self) -> u64 {
88        if let Some(cached) = self.cached_min_cut {
89            return cached;
90        }
91
92        let result = self.wrapper.query();
93        let value = result.value();
94        self.cached_min_cut = Some(value);
95        value
96    }
97
98    /// Get partition (two sides of minimum cut)
99    pub fn partition(&mut self) -> Option<(Vec<VertexId>, Vec<VertexId>)> {
100        if let Some(ref cached) = self.cached_partition {
101            return Some(cached.clone());
102        }
103
104        let result = self.wrapper.query();
105        match result {
106            MinCutResult::Disconnected => {
107                // Return empty partition for disconnected graph
108                Some((Vec::new(), Vec::new()))
109            }
110            MinCutResult::Value { witness, .. } => {
111                let (side_a, side_b) = witness.materialize_partition();
112                let partition = (side_a.into_iter().collect(), side_b.into_iter().collect());
113                self.cached_partition = Some(partition.clone());
114                Some(partition)
115            }
116        }
117    }
118
119    /// Check if graph is well-connected (min cut above threshold)
120    pub fn is_well_connected(&mut self, threshold: u64) -> bool {
121        self.min_cut() >= threshold
122    }
123
124    /// Find bridge edges (edges whose removal disconnects graph)
125    pub fn find_bridges(&self) -> Vec<EdgeId> {
126        let mut bridges = Vec::new();
127
128        for edge in self.graph.edges() {
129            // Temporarily remove edge and check connectivity
130            // This is expensive but correct
131            let test_graph = Arc::new(DynamicGraph::new());
132            for e in self.graph.edges() {
133                if e.id != edge.id {
134                    let _ = test_graph.insert_edge(e.source, e.target, e.weight);
135                }
136            }
137
138            let mut test_wrapper = MinCutWrapper::new(test_graph);
139            if test_wrapper.query().value() == 0 {
140                bridges.push(edge.id);
141            }
142        }
143
144        bridges
145    }
146
147    /// Invalidate cache after graph modification
148    pub fn invalidate_cache(&mut self) {
149        self.cached_min_cut = None;
150        self.cached_partition = None;
151    }
152
153    /// Add edge and invalidate cache
154    pub fn add_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> crate::Result<EdgeId> {
155        let edge_id = self.graph.insert_edge(u, v, weight)?;
156        self.wrapper.insert_edge(edge_id, u, v);
157        self.invalidate_cache();
158        Ok(edge_id)
159    }
160
161    /// Remove edge and invalidate cache
162    pub fn remove_edge(&mut self, u: VertexId, v: VertexId) -> crate::Result<()> {
163        let edge = self.graph.delete_edge(u, v)?;
164        self.wrapper.delete_edge(edge.id, u, v);
165        self.invalidate_cache();
166        Ok(())
167    }
168}
169
170/// Community detection using minimum cut
171pub struct CommunityDetector {
172    analyzer: RuVectorGraphAnalyzer,
173    communities: Vec<Vec<VertexId>>,
174}
175
176impl CommunityDetector {
177    /// Create a new community detector for the given graph
178    pub fn new(graph: Arc<DynamicGraph>) -> Self {
179        Self {
180            analyzer: RuVectorGraphAnalyzer::new(graph),
181            communities: Vec::new(),
182        }
183    }
184
185    /// Detect communities using recursive minimum cut
186    pub fn detect(&mut self, min_community_size: usize) -> &[Vec<VertexId>] {
187        self.communities.clear();
188
189        // Get all vertices
190        let vertices = self.analyzer.graph.vertices();
191
192        // Recursively partition
193        self.recursive_partition(vertices, min_community_size);
194
195        &self.communities
196    }
197
198    fn recursive_partition(&mut self, vertices: Vec<VertexId>, min_size: usize) {
199        if vertices.len() <= min_size {
200            if !vertices.is_empty() {
201                self.communities.push(vertices);
202            }
203            return;
204        }
205
206        // Build subgraph
207        let subgraph = Arc::new(DynamicGraph::new());
208        let vertex_set: std::collections::HashSet<_> = vertices.iter().copied().collect();
209
210        for edge in self.analyzer.graph.edges() {
211            if vertex_set.contains(&edge.source) && vertex_set.contains(&edge.target) {
212                let _ = subgraph.insert_edge(edge.source, edge.target, edge.weight);
213            }
214        }
215
216        // Compute minimum cut
217        let mut sub_analyzer = RuVectorGraphAnalyzer::new(subgraph);
218        let min_cut = sub_analyzer.min_cut();
219
220        // If well-connected, keep as single community
221        if min_cut > (vertices.len() as u64 / 4) {
222            self.communities.push(vertices);
223            return;
224        }
225
226        // Split and recurse
227        if let Some((side_a, side_b)) = sub_analyzer.partition() {
228            if !side_a.is_empty() {
229                self.recursive_partition(side_a, min_size);
230            }
231            if !side_b.is_empty() {
232                self.recursive_partition(side_b, min_size);
233            }
234        } else {
235            self.communities.push(vertices);
236        }
237    }
238
239    /// Get detected communities
240    pub fn communities(&self) -> &[Vec<VertexId>] {
241        &self.communities
242    }
243}
244
245/// Graph partitioner for distributed processing
246pub struct GraphPartitioner {
247    graph: Arc<DynamicGraph>,
248    num_partitions: usize,
249}
250
251impl GraphPartitioner {
252    /// Create a new graph partitioner
253    pub fn new(graph: Arc<DynamicGraph>, num_partitions: usize) -> Self {
254        Self {
255            graph,
256            num_partitions,
257        }
258    }
259
260    /// Partition graph to minimize edge cuts
261    pub fn partition(&self) -> Vec<Vec<VertexId>> {
262        let mut partitions = vec![Vec::new(); self.num_partitions];
263        let vertices = self.graph.vertices();
264
265        if vertices.is_empty() {
266            return partitions;
267        }
268
269        // Use recursive bisection
270        self.recursive_bisect(&vertices, &mut partitions, 0, self.num_partitions);
271
272        partitions
273    }
274
275    fn recursive_bisect(
276        &self,
277        vertices: &[VertexId],
278        partitions: &mut [Vec<VertexId>],
279        start_idx: usize,
280        count: usize,
281    ) {
282        if count == 1 {
283            partitions[start_idx].extend(vertices.iter().copied());
284            return;
285        }
286
287        // Build subgraph
288        let subgraph = Arc::new(DynamicGraph::new());
289        let vertex_set: std::collections::HashSet<_> = vertices.iter().copied().collect();
290
291        for edge in self.graph.edges() {
292            if vertex_set.contains(&edge.source) && vertex_set.contains(&edge.target) {
293                let _ = subgraph.insert_edge(edge.source, edge.target, edge.weight);
294            }
295        }
296
297        // Find minimum cut partition
298        let mut analyzer = RuVectorGraphAnalyzer::new(subgraph);
299
300        if let Some((side_a, side_b)) = analyzer.partition() {
301            let half = count / 2;
302            self.recursive_bisect(&side_a, partitions, start_idx, half);
303            self.recursive_bisect(&side_b, partitions, start_idx + half, count - half);
304        } else {
305            // Can't partition further, put all in first partition
306            partitions[start_idx].extend(vertices.iter().copied());
307        }
308    }
309
310    /// Compute edge cut between partitions
311    pub fn edge_cut(&self, partitions: &[Vec<VertexId>]) -> usize {
312        let mut partition_map = std::collections::HashMap::new();
313        for (i, partition) in partitions.iter().enumerate() {
314            for &v in partition {
315                partition_map.insert(v, i);
316            }
317        }
318
319        let mut cut = 0;
320        for edge in self.graph.edges() {
321            let src_part = partition_map.get(&edge.source);
322            let tgt_part = partition_map.get(&edge.target);
323
324            if src_part != tgt_part {
325                cut += 1;
326            }
327        }
328
329        cut
330    }
331}
332
333/// Agentic chip accelerated analyzer
334#[cfg(feature = "agentic")]
335pub struct AgenticAnalyzer {
336    coordinator: SharedCoordinator,
337}
338
339#[cfg(feature = "agentic")]
340impl AgenticAnalyzer {
341    pub fn new() -> Self {
342        let coordinator = SharedCoordinator::new();
343        Self { coordinator }
344    }
345
346    /// Distribute graph across cores and compute
347    pub fn compute(&mut self, graph: &DynamicGraph) -> u16 {
348        // Create cores with reference to coordinator
349        let mut cores: Vec<CoreExecutor> = (0..NUM_CORES as u8)
350            .map(|id| CoreExecutor::init(id, Some(&self.coordinator)))
351            .collect();
352
353        // Distribute edges to cores
354        for edge in graph.edges() {
355            // Simple round-robin distribution
356            let core_idx = (edge.id as usize) % NUM_CORES;
357            cores[core_idx].add_edge(
358                edge.source as u16,
359                edge.target as u16,
360                (edge.weight * 100.0) as u16,
361            );
362        }
363
364        // Process all cores
365        for core in &mut cores {
366            core.process();
367        }
368
369        // Return global minimum
370        self.coordinator
371            .global_min_cut
372            .load(std::sync::atomic::Ordering::Acquire)
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_graph_analyzer() {
382        let graph = Arc::new(DynamicGraph::new());
383        graph.insert_edge(0, 1, 1.0).unwrap();
384        graph.insert_edge(1, 2, 1.0).unwrap();
385        graph.insert_edge(2, 0, 1.0).unwrap();
386
387        let mut analyzer = RuVectorGraphAnalyzer::new(graph);
388        assert_eq!(analyzer.min_cut(), 2);
389    }
390
391    #[test]
392    fn test_community_detector() {
393        let graph = Arc::new(DynamicGraph::new());
394        // Two triangles connected by weak edge
395        graph.insert_edge(0, 1, 1.0).unwrap();
396        graph.insert_edge(1, 2, 1.0).unwrap();
397        graph.insert_edge(2, 0, 1.0).unwrap();
398        graph.insert_edge(3, 4, 1.0).unwrap();
399        graph.insert_edge(4, 5, 1.0).unwrap();
400        graph.insert_edge(5, 3, 1.0).unwrap();
401        graph.insert_edge(2, 3, 0.1).unwrap(); // Weak bridge
402
403        let mut detector = CommunityDetector::new(graph);
404        let communities = detector.detect(2);
405
406        // Should find 2 communities
407        assert!(communities.len() >= 1);
408    }
409
410    #[test]
411    fn test_graph_partitioner() {
412        let graph = Arc::new(DynamicGraph::new());
413        for i in 0..9 {
414            graph.insert_edge(i, i + 1, 1.0).unwrap();
415        }
416
417        let partitioner = GraphPartitioner::new(Arc::clone(&graph), 2);
418        let partitions = partitioner.partition();
419
420        assert_eq!(partitions.len(), 2);
421
422        let total_vertices: usize = partitions.iter().map(|p| p.len()).sum();
423        // Total should be at most the number of vertices in graph
424        // Partitioning may not cover all vertices if min-cut fails
425        assert!(total_vertices <= 10);
426        assert!(total_vertices > 0);
427    }
428
429    #[test]
430    fn test_from_similarity_matrix() {
431        let similarities = vec![1.0, 0.9, 0.1, 0.9, 1.0, 0.8, 0.1, 0.8, 1.0];
432
433        let analyzer = RuVectorGraphAnalyzer::from_similarity_matrix(&similarities, 3, 0.5);
434
435        // Should have edges for similarities >= 0.5
436        assert_eq!(analyzer.graph.num_edges(), 2); // 0-1 and 1-2
437    }
438}