ruvector_mincut/integration/
mod.rs1#![allow(missing_docs)]
8
9use crate::graph::{DynamicGraph, EdgeId, VertexId, Weight};
10use crate::wrapper::{MinCutResult, MinCutWrapper};
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(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 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 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 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 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 pub fn is_well_connected(&mut self, threshold: u64) -> bool {
121 self.min_cut() >= threshold
122 }
123
124 pub fn find_bridges(&self) -> Vec<EdgeId> {
126 let mut bridges = Vec::new();
127
128 for edge in self.graph.edges() {
129 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 pub fn invalidate_cache(&mut self) {
149 self.cached_min_cut = None;
150 self.cached_partition = None;
151 }
152
153 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 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
170pub struct CommunityDetector {
172 analyzer: RuVectorGraphAnalyzer,
173 communities: Vec<Vec<VertexId>>,
174}
175
176impl CommunityDetector {
177 pub fn new(graph: Arc<DynamicGraph>) -> Self {
179 Self {
180 analyzer: RuVectorGraphAnalyzer::new(graph),
181 communities: Vec::new(),
182 }
183 }
184
185 pub fn detect(&mut self, min_community_size: usize) -> &[Vec<VertexId>] {
187 self.communities.clear();
188
189 let vertices = self.analyzer.graph.vertices();
191
192 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 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 let mut sub_analyzer = RuVectorGraphAnalyzer::new(subgraph);
218 let min_cut = sub_analyzer.min_cut();
219
220 if min_cut > (vertices.len() as u64 / 4) {
222 self.communities.push(vertices);
223 return;
224 }
225
226 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 pub fn communities(&self) -> &[Vec<VertexId>] {
241 &self.communities
242 }
243}
244
245pub struct GraphPartitioner {
247 graph: Arc<DynamicGraph>,
248 num_partitions: usize,
249}
250
251impl GraphPartitioner {
252 pub fn new(graph: Arc<DynamicGraph>, num_partitions: usize) -> Self {
254 Self {
255 graph,
256 num_partitions,
257 }
258 }
259
260 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 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 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 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 partitions[start_idx].extend(vertices.iter().copied());
307 }
308 }
309
310 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#[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 pub fn compute(&mut self, graph: &DynamicGraph) -> u16 {
348 let mut cores: Vec<CoreExecutor> = (0..NUM_CORES as u8)
350 .map(|id| CoreExecutor::init(id, Some(&self.coordinator)))
351 .collect();
352
353 for edge in graph.edges() {
355 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 for core in &mut cores {
366 core.process();
367 }
368
369 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 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![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 assert_eq!(analyzer.graph.num_edges(), 2); }
438}