ruvector_mincut/integration/
mod.rs1#![allow(missing_docs)]
8
9use crate::graph::{DynamicGraph, VertexId, EdgeId, Weight};
10use crate::wrapper::{MinCutWrapper, MinCutResult};
11use std::sync::Arc;
12
13#[cfg(feature = "agentic")]
15use crate::parallel::{CoreExecutor, SharedCoordinator, NUM_CORES};
16
17pub struct RuVectorGraphAnalyzer {
24 graph: Arc<DynamicGraph>,
25 wrapper: MinCutWrapper,
26 cached_min_cut: Option<u64>,
28 cached_partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
29}
30
31impl RuVectorGraphAnalyzer {
32 pub fn new(graph: Arc<DynamicGraph>) -> Self {
34 let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
35
36 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 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 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 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 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 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 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 pub fn is_well_connected(&mut self, threshold: u64) -> bool {
126 self.min_cut() >= threshold
127 }
128
129 pub fn find_bridges(&self) -> Vec<EdgeId> {
131 let mut bridges = Vec::new();
132
133 for edge in self.graph.edges() {
134 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 pub fn invalidate_cache(&mut self) {
154 self.cached_min_cut = None;
155 self.cached_partition = None;
156 }
157
158 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 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
175pub struct CommunityDetector {
177 analyzer: RuVectorGraphAnalyzer,
178 communities: Vec<Vec<VertexId>>,
179}
180
181impl CommunityDetector {
182 pub fn new(graph: Arc<DynamicGraph>) -> Self {
184 Self {
185 analyzer: RuVectorGraphAnalyzer::new(graph),
186 communities: Vec::new(),
187 }
188 }
189
190 pub fn detect(&mut self, min_community_size: usize) -> &[Vec<VertexId>] {
192 self.communities.clear();
193
194 let vertices = self.analyzer.graph.vertices();
196
197 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 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 let mut sub_analyzer = RuVectorGraphAnalyzer::new(subgraph);
223 let min_cut = sub_analyzer.min_cut();
224
225 if min_cut > (vertices.len() as u64 / 4) {
227 self.communities.push(vertices);
228 return;
229 }
230
231 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 pub fn communities(&self) -> &[Vec<VertexId>] {
246 &self.communities
247 }
248}
249
250pub struct GraphPartitioner {
252 graph: Arc<DynamicGraph>,
253 num_partitions: usize,
254}
255
256impl GraphPartitioner {
257 pub fn new(graph: Arc<DynamicGraph>, num_partitions: usize) -> Self {
259 Self { graph, num_partitions }
260 }
261
262 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 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 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 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 partitions[start_idx].extend(vertices.iter().copied());
309 }
310 }
311
312 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#[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 pub fn compute(&mut self, graph: &DynamicGraph) -> u16 {
350 let mut cores: Vec<CoreExecutor> = (0..NUM_CORES as u8)
352 .map(|id| CoreExecutor::init(id, Some(&self.coordinator)))
353 .collect();
354
355 for edge in graph.edges() {
357 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 for core in &mut cores {
368 core.process();
369 }
370
371 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 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(); let mut detector = CommunityDetector::new(graph);
404 let communities = detector.detect(2);
405
406 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 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 assert_eq!(analyzer.graph.num_edges(), 2); }
442}