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, VertexId, EdgeId, Weight};
10use crate::wrapper::{MinCutWrapper, MinCutResult};
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(
73        neighbors: &[(usize, Vec<(usize, f64)>)],
74    ) -> Self {
75        let graph = Arc::new(DynamicGraph::new());
76
77        for &(vertex, ref nn_list) in neighbors {
78            for &(neighbor, distance) in nn_list {
79                // Use 1/distance as weight (closer = stronger connection)
80                let weight = if distance > 0.0 { 1.0 / distance } else { 1.0 };
81                let _ = graph.insert_edge(vertex as u64, neighbor as u64, weight);
82            }
83        }
84
85        Self::new(graph)
86    }
87
88    /// Compute minimum cut
89    pub fn min_cut(&mut self) -> u64 {
90        if let Some(cached) = self.cached_min_cut {
91            return cached;
92        }
93
94        let result = self.wrapper.query();
95        let value = result.value();
96        self.cached_min_cut = Some(value);
97        value
98    }
99
100    /// Get partition (two sides of minimum cut)
101    pub fn partition(&mut self) -> Option<(Vec<VertexId>, Vec<VertexId>)> {
102        if let Some(ref cached) = self.cached_partition {
103            return Some(cached.clone());
104        }
105
106        let result = self.wrapper.query();
107        match result {
108            MinCutResult::Disconnected => {
109                // Return empty partition for disconnected graph
110                Some((Vec::new(), Vec::new()))
111            }
112            MinCutResult::Value { witness, .. } => {
113                let (side_a, side_b) = witness.materialize_partition();
114                let partition = (
115                    side_a.into_iter().collect(),
116                    side_b.into_iter().collect(),
117                );
118                self.cached_partition = Some(partition.clone());
119                Some(partition)
120            }
121        }
122    }
123
124    /// Check if graph is well-connected (min cut above threshold)
125    pub fn is_well_connected(&mut self, threshold: u64) -> bool {
126        self.min_cut() >= threshold
127    }
128
129    /// Find bridge edges (edges whose removal disconnects graph)
130    pub fn find_bridges(&self) -> Vec<EdgeId> {
131        let mut bridges = Vec::new();
132
133        for edge in self.graph.edges() {
134            // Temporarily remove edge and check connectivity
135            // This is expensive but correct
136            let test_graph = Arc::new(DynamicGraph::new());
137            for e in self.graph.edges() {
138                if e.id != edge.id {
139                    let _ = test_graph.insert_edge(e.source, e.target, e.weight);
140                }
141            }
142
143            let mut test_wrapper = MinCutWrapper::new(test_graph);
144            if test_wrapper.query().value() == 0 {
145                bridges.push(edge.id);
146            }
147        }
148
149        bridges
150    }
151
152    /// Invalidate cache after graph modification
153    pub fn invalidate_cache(&mut self) {
154        self.cached_min_cut = None;
155        self.cached_partition = None;
156    }
157
158    /// Add edge and invalidate cache
159    pub fn add_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> crate::Result<EdgeId> {
160        let edge_id = self.graph.insert_edge(u, v, weight)?;
161        self.wrapper.insert_edge(edge_id, u, v);
162        self.invalidate_cache();
163        Ok(edge_id)
164    }
165
166    /// Remove edge and invalidate cache
167    pub fn remove_edge(&mut self, u: VertexId, v: VertexId) -> crate::Result<()> {
168        let edge = self.graph.delete_edge(u, v)?;
169        self.wrapper.delete_edge(edge.id, u, v);
170        self.invalidate_cache();
171        Ok(())
172    }
173}
174
175/// Community detection using minimum cut
176pub struct CommunityDetector {
177    analyzer: RuVectorGraphAnalyzer,
178    communities: Vec<Vec<VertexId>>,
179}
180
181impl CommunityDetector {
182    /// Create a new community detector for the given graph
183    pub fn new(graph: Arc<DynamicGraph>) -> Self {
184        Self {
185            analyzer: RuVectorGraphAnalyzer::new(graph),
186            communities: Vec::new(),
187        }
188    }
189
190    /// Detect communities using recursive minimum cut
191    pub fn detect(&mut self, min_community_size: usize) -> &[Vec<VertexId>] {
192        self.communities.clear();
193
194        // Get all vertices
195        let vertices = self.analyzer.graph.vertices();
196
197        // Recursively partition
198        self.recursive_partition(vertices, min_community_size);
199
200        &self.communities
201    }
202
203    fn recursive_partition(&mut self, vertices: Vec<VertexId>, min_size: usize) {
204        if vertices.len() <= min_size {
205            if !vertices.is_empty() {
206                self.communities.push(vertices);
207            }
208            return;
209        }
210
211        // Build subgraph
212        let subgraph = Arc::new(DynamicGraph::new());
213        let vertex_set: std::collections::HashSet<_> = vertices.iter().copied().collect();
214
215        for edge in self.analyzer.graph.edges() {
216            if vertex_set.contains(&edge.source) && vertex_set.contains(&edge.target) {
217                let _ = subgraph.insert_edge(edge.source, edge.target, edge.weight);
218            }
219        }
220
221        // Compute minimum cut
222        let mut sub_analyzer = RuVectorGraphAnalyzer::new(subgraph);
223        let min_cut = sub_analyzer.min_cut();
224
225        // If well-connected, keep as single community
226        if min_cut > (vertices.len() as u64 / 4) {
227            self.communities.push(vertices);
228            return;
229        }
230
231        // Split and recurse
232        if let Some((side_a, side_b)) = sub_analyzer.partition() {
233            if !side_a.is_empty() {
234                self.recursive_partition(side_a, min_size);
235            }
236            if !side_b.is_empty() {
237                self.recursive_partition(side_b, min_size);
238            }
239        } else {
240            self.communities.push(vertices);
241        }
242    }
243
244    /// Get detected communities
245    pub fn communities(&self) -> &[Vec<VertexId>] {
246        &self.communities
247    }
248}
249
250/// Graph partitioner for distributed processing
251pub struct GraphPartitioner {
252    graph: Arc<DynamicGraph>,
253    num_partitions: usize,
254}
255
256impl GraphPartitioner {
257    /// Create a new graph partitioner
258    pub fn new(graph: Arc<DynamicGraph>, num_partitions: usize) -> Self {
259        Self { graph, num_partitions }
260    }
261
262    /// Partition graph to minimize edge cuts
263    pub fn partition(&self) -> Vec<Vec<VertexId>> {
264        let mut partitions = vec![Vec::new(); self.num_partitions];
265        let vertices = self.graph.vertices();
266
267        if vertices.is_empty() {
268            return partitions;
269        }
270
271        // Use recursive bisection
272        self.recursive_bisect(&vertices, &mut partitions, 0, self.num_partitions);
273
274        partitions
275    }
276
277    fn recursive_bisect(
278        &self,
279        vertices: &[VertexId],
280        partitions: &mut [Vec<VertexId>],
281        start_idx: usize,
282        count: usize,
283    ) {
284        if count == 1 {
285            partitions[start_idx].extend(vertices.iter().copied());
286            return;
287        }
288
289        // Build subgraph
290        let subgraph = Arc::new(DynamicGraph::new());
291        let vertex_set: std::collections::HashSet<_> = vertices.iter().copied().collect();
292
293        for edge in self.graph.edges() {
294            if vertex_set.contains(&edge.source) && vertex_set.contains(&edge.target) {
295                let _ = subgraph.insert_edge(edge.source, edge.target, edge.weight);
296            }
297        }
298
299        // Find minimum cut partition
300        let mut analyzer = RuVectorGraphAnalyzer::new(subgraph);
301
302        if let Some((side_a, side_b)) = analyzer.partition() {
303            let half = count / 2;
304            self.recursive_bisect(&side_a, partitions, start_idx, half);
305            self.recursive_bisect(&side_b, partitions, start_idx + half, count - half);
306        } else {
307            // Can't partition further, put all in first partition
308            partitions[start_idx].extend(vertices.iter().copied());
309        }
310    }
311
312    /// Compute edge cut between partitions
313    pub fn edge_cut(&self, partitions: &[Vec<VertexId>]) -> usize {
314        let mut partition_map = std::collections::HashMap::new();
315        for (i, partition) in partitions.iter().enumerate() {
316            for &v in partition {
317                partition_map.insert(v, i);
318            }
319        }
320
321        let mut cut = 0;
322        for edge in self.graph.edges() {
323            let src_part = partition_map.get(&edge.source);
324            let tgt_part = partition_map.get(&edge.target);
325
326            if src_part != tgt_part {
327                cut += 1;
328            }
329        }
330
331        cut
332    }
333}
334
335/// Agentic chip accelerated analyzer
336#[cfg(feature = "agentic")]
337pub struct AgenticAnalyzer {
338    coordinator: SharedCoordinator,
339}
340
341#[cfg(feature = "agentic")]
342impl AgenticAnalyzer {
343    pub fn new() -> Self {
344        let coordinator = SharedCoordinator::new();
345        Self { coordinator }
346    }
347
348    /// Distribute graph across cores and compute
349    pub fn compute(&mut self, graph: &DynamicGraph) -> u16 {
350        // Create cores with reference to coordinator
351        let mut cores: Vec<CoreExecutor> = (0..NUM_CORES as u8)
352            .map(|id| CoreExecutor::init(id, Some(&self.coordinator)))
353            .collect();
354
355        // Distribute edges to cores
356        for edge in graph.edges() {
357            // Simple round-robin distribution
358            let core_idx = (edge.id as usize) % NUM_CORES;
359            cores[core_idx].add_edge(
360                edge.source as u16,
361                edge.target as u16,
362                (edge.weight * 100.0) as u16,
363            );
364        }
365
366        // Process all cores
367        for core in &mut cores {
368            core.process();
369        }
370
371        // Return global minimum
372        self.coordinator.global_min_cut.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![
432            1.0, 0.9, 0.1,
433            0.9, 1.0, 0.8,
434            0.1, 0.8, 1.0,
435        ];
436
437        let analyzer = RuVectorGraphAnalyzer::from_similarity_matrix(&similarities, 3, 0.5);
438
439        // Should have edges for similarities >= 0.5
440        assert_eq!(analyzer.graph.num_edges(), 2); // 0-1 and 1-2
441    }
442}