1use std::collections::{HashMap, HashSet, VecDeque};
15use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
16use std::sync::{Arc, RwLock};
17use chrono::{DateTime, Utc};
18use serde::{Deserialize, Serialize};
19
20use crate::ruvector_native::{GraphNode, GraphEdge};
21
22#[derive(Debug, Clone, thiserror::Error)]
24pub enum DynamicMinCutError {
25 #[error("Invalid edge: {0}")]
26 InvalidEdge(String),
27 #[error("Node not found: {0}")]
28 NodeNotFound(u32),
29 #[error("Graph is empty")]
30 EmptyGraph,
31 #[error("Disconnected components")]
32 DisconnectedGraph,
33 #[error("Invalid configuration: {0}")]
34 InvalidConfig(String),
35 #[error("Computation failed: {0}")]
36 ComputationError(String),
37}
38
39#[derive(Debug, Clone)]
47struct ETNode {
48 graph_node: u32,
50 parent: Option<usize>,
52 left: Option<usize>,
54 right: Option<usize>,
56 size: usize,
58 edge_tour: Option<(u32, u32)>,
60}
61
62impl ETNode {
63 fn new(graph_node: u32) -> Self {
64 Self {
65 graph_node,
66 parent: None,
67 left: None,
68 right: None,
69 size: 1,
70 edge_tour: None,
71 }
72 }
73
74 fn new_edge_tour(u: u32, v: u32) -> Self {
75 Self {
76 graph_node: u,
77 parent: None,
78 left: None,
79 right: None,
80 size: 1,
81 edge_tour: Some((u, v)),
82 }
83 }
84}
85
86pub struct EulerTourTree {
90 nodes: Vec<ETNode>,
92 node_map: HashMap<u32, Vec<usize>>,
94 edge_map: HashMap<(u32, u32), Vec<usize>>,
96 next_idx: usize,
98}
99
100impl EulerTourTree {
101 pub fn new() -> Self {
103 Self {
104 nodes: Vec::with_capacity(1000),
105 node_map: HashMap::new(),
106 edge_map: HashMap::new(),
107 next_idx: 0,
108 }
109 }
110
111 pub fn add_vertex(&mut self, v: u32) {
113 if !self.node_map.contains_key(&v) {
114 let idx = self.alloc_node(ETNode::new(v));
115 self.node_map.entry(v).or_default().push(idx);
116 }
117 }
118
119 pub fn link(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
123 if self.connected(u, v) {
124 return Ok(()); }
126
127 self.add_vertex(u);
129 self.add_vertex(v);
130
131 let u_idx = *self.node_map.get(&u)
133 .and_then(|v| v.first())
134 .ok_or(DynamicMinCutError::NodeNotFound(u))?;
135 let v_idx = *self.node_map.get(&v)
136 .and_then(|v| v.first())
137 .ok_or(DynamicMinCutError::NodeNotFound(v))?;
138
139 let uv_idx = self.alloc_node(ETNode::new_edge_tour(u, v));
141 let vu_idx = self.alloc_node(ETNode::new_edge_tour(v, u));
142
143 let key = if u < v { (u, v) } else { (v, u) };
145 self.edge_map.entry(key).or_default().extend(&[uv_idx, vu_idx]);
146
147 self.splay(u_idx);
149 self.splay(v_idx);
150
151 self.join_trees(u_idx, uv_idx);
153 self.join_trees(uv_idx, v_idx);
154 self.join_trees(v_idx, vu_idx);
155
156 Ok(())
157 }
158
159 pub fn cut(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
163 let key = if u < v { (u, v) } else { (v, u) };
164
165 let edge_nodes = self.edge_map.remove(&key)
166 .ok_or(DynamicMinCutError::InvalidEdge(format!("Edge {}-{} not found", u, v)))?;
167
168 for &idx in &edge_nodes {
170 if idx < self.nodes.len() {
171 self.splay(idx);
172 self.split_at(idx);
173 }
174 }
175
176 Ok(())
177 }
178
179 pub fn connected(&self, u: u32, v: u32) -> bool {
183 let u_idx = match self.node_map.get(&u).and_then(|v| v.first()) {
184 Some(&idx) => idx,
185 None => return false,
186 };
187
188 let v_idx = match self.node_map.get(&v).and_then(|v| v.first()) {
189 Some(&idx) => idx,
190 None => return false,
191 };
192
193 self.find_root(u_idx) == self.find_root(v_idx)
194 }
195
196 pub fn component_size(&self, v: u32) -> usize {
200 let idx = match self.node_map.get(&v).and_then(|v| v.first()) {
201 Some(&idx) => idx,
202 None => return 0,
203 };
204
205 let root = self.find_root(idx);
206 if root < self.nodes.len() {
207 self.nodes[root].size
208 } else {
209 1
210 }
211 }
212
213 fn alloc_node(&mut self, node: ETNode) -> usize {
216 let idx = self.next_idx;
217 self.next_idx += 1;
218 if idx >= self.nodes.len() {
219 self.nodes.push(node);
220 } else {
221 self.nodes[idx] = node;
222 }
223 idx
224 }
225
226 fn find_root(&self, mut idx: usize) -> usize {
227 while let Some(parent) = self.nodes.get(idx).and_then(|n| n.parent) {
228 idx = parent;
229 }
230 idx
231 }
232
233 fn splay(&mut self, idx: usize) {
234 if idx >= self.nodes.len() {
235 return;
236 }
237
238 while self.nodes[idx].parent.is_some() {
239 let p = self.nodes[idx].parent.unwrap();
240
241 if self.nodes[p].parent.is_none() {
242 if self.is_left_child(idx) {
244 self.rotate_right(p);
245 } else {
246 self.rotate_left(p);
247 }
248 } else {
249 let g = self.nodes[p].parent.unwrap();
250
251 if self.is_left_child(idx) && self.is_left_child(p) {
252 self.rotate_right(g);
254 self.rotate_right(p);
255 } else if !self.is_left_child(idx) && !self.is_left_child(p) {
256 self.rotate_left(g);
258 self.rotate_left(p);
259 } else if self.is_left_child(idx) {
260 self.rotate_right(p);
262 self.rotate_left(g);
263 } else {
264 self.rotate_left(p);
266 self.rotate_right(g);
267 }
268 }
269 }
270 }
271
272 fn is_left_child(&self, idx: usize) -> bool {
273 if let Some(parent_idx) = self.nodes[idx].parent {
274 if let Some(left_idx) = self.nodes[parent_idx].left {
275 return left_idx == idx;
276 }
277 }
278 false
279 }
280
281 fn rotate_left(&mut self, idx: usize) {
282 if let Some(right_idx) = self.nodes[idx].right {
283 let parent = self.nodes[idx].parent;
284
285 self.nodes[idx].right = self.nodes[right_idx].left;
287 if let Some(rl) = self.nodes[right_idx].left {
288 self.nodes[rl].parent = Some(idx);
289 }
290
291 self.nodes[right_idx].left = Some(idx);
292 self.nodes[right_idx].parent = parent;
293 self.nodes[idx].parent = Some(right_idx);
294
295 if let Some(p) = parent {
296 if self.nodes[p].left == Some(idx) {
297 self.nodes[p].left = Some(right_idx);
298 } else {
299 self.nodes[p].right = Some(right_idx);
300 }
301 }
302
303 self.update_size(idx);
304 self.update_size(right_idx);
305 }
306 }
307
308 fn rotate_right(&mut self, idx: usize) {
309 if let Some(left_idx) = self.nodes[idx].left {
310 let parent = self.nodes[idx].parent;
311
312 self.nodes[idx].left = self.nodes[left_idx].right;
313 if let Some(lr) = self.nodes[left_idx].right {
314 self.nodes[lr].parent = Some(idx);
315 }
316
317 self.nodes[left_idx].right = Some(idx);
318 self.nodes[left_idx].parent = parent;
319 self.nodes[idx].parent = Some(left_idx);
320
321 if let Some(p) = parent {
322 if self.nodes[p].left == Some(idx) {
323 self.nodes[p].left = Some(left_idx);
324 } else {
325 self.nodes[p].right = Some(left_idx);
326 }
327 }
328
329 self.update_size(idx);
330 self.update_size(left_idx);
331 }
332 }
333
334 fn update_size(&mut self, idx: usize) {
335 let left_size = self.nodes[idx].left.map(|i| self.nodes[i].size).unwrap_or(0);
336 let right_size = self.nodes[idx].right.map(|i| self.nodes[i].size).unwrap_or(0);
337 self.nodes[idx].size = 1 + left_size + right_size;
338 }
339
340 fn join_trees(&mut self, left: usize, right: usize) {
341 self.splay(left);
342 self.splay(right);
343 self.nodes[left].right = Some(right);
344 self.nodes[right].parent = Some(left);
345 self.update_size(left);
346 }
347
348 fn split_at(&mut self, idx: usize) {
349 self.splay(idx);
350 if let Some(right) = self.nodes[idx].right {
351 self.nodes[right].parent = None;
352 self.nodes[idx].right = None;
353 self.update_size(idx);
354 }
355 if let Some(left) = self.nodes[idx].left {
356 self.nodes[left].parent = None;
357 self.nodes[idx].left = None;
358 self.update_size(idx);
359 }
360 }
361}
362
363impl Default for EulerTourTree {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct EdgeUpdate {
376 pub update_type: EdgeUpdateType,
377 pub source: u32,
378 pub target: u32,
379 pub weight: f64,
380 pub timestamp: DateTime<Utc>,
381}
382
383#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
384pub enum EdgeUpdateType {
385 Insert,
386 Delete,
387 WeightChange,
388}
389
390#[derive(Debug, Clone, Serialize, Deserialize)]
396pub struct CutWatcherConfig {
397 pub lambda_bound: usize,
399 pub change_threshold: f64,
401 pub use_local_heuristics: bool,
403 pub update_interval_ms: u64,
405 pub flow_iterations: usize,
407 pub ball_radius: usize,
409 pub conductance_threshold: f64,
411}
412
413impl Default for CutWatcherConfig {
414 fn default() -> Self {
415 Self {
416 lambda_bound: 100, change_threshold: 0.15,
418 use_local_heuristics: true,
419 update_interval_ms: 1000,
420 flow_iterations: 50,
421 ball_radius: 3,
422 conductance_threshold: 0.3,
423 }
424 }
425}
426
427pub struct DynamicCutWatcher {
435 config: CutWatcherConfig,
436
437 euler_tree: Arc<RwLock<EulerTourTree>>,
439
440 current_lambda: AtomicU64,
442
443 lambda_threshold: f64,
445
446 local_flow_scores: Arc<RwLock<HashMap<(u32, u32), f64>>>,
448
449 pending_updates: Arc<RwLock<VecDeque<EdgeUpdate>>>,
451
452 adjacency: Arc<RwLock<HashMap<u32, Vec<(u32, f64)>>>>,
454
455 cut_changed_flag: AtomicBool,
457
458 last_full_computation: Arc<RwLock<Option<DateTime<Utc>>>>,
460}
461
462impl DynamicCutWatcher {
463 pub fn new(config: CutWatcherConfig) -> Self {
465 Self {
466 lambda_threshold: config.change_threshold,
467 config,
468 euler_tree: Arc::new(RwLock::new(EulerTourTree::new())),
469 current_lambda: AtomicU64::new(0),
470 local_flow_scores: Arc::new(RwLock::new(HashMap::new())),
471 pending_updates: Arc::new(RwLock::new(VecDeque::new())),
472 adjacency: Arc::new(RwLock::new(HashMap::new())),
473 cut_changed_flag: AtomicBool::new(false),
474 last_full_computation: Arc::new(RwLock::new(None)),
475 }
476 }
477
478 pub fn insert_edge(&mut self, u: u32, v: u32, weight: f64) -> Result<(), DynamicMinCutError> {
482 {
484 let mut tree = self.euler_tree.write()
485 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
486 tree.link(u, v)?;
487 }
488
489 {
491 let mut adj = self.adjacency.write()
492 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
493 adj.entry(u).or_default().push((v, weight));
494 adj.entry(v).or_default().push((u, weight));
495 }
496
497 {
499 let mut updates = self.pending_updates.write()
500 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
501 updates.push_back(EdgeUpdate {
502 update_type: EdgeUpdateType::Insert,
503 source: u,
504 target: v,
505 weight,
506 timestamp: Utc::now(),
507 });
508 }
509
510 self.update_local_flow_score(u, v, weight)?;
512
513 Ok(())
514 }
515
516 pub fn delete_edge(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
520 {
522 let mut tree = self.euler_tree.write()
523 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
524 tree.cut(u, v)?;
525 }
526
527 {
529 let mut adj = self.adjacency.write()
530 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
531 if let Some(neighbors) = adj.get_mut(&u) {
532 neighbors.retain(|(n, _)| *n != v);
533 }
534 if let Some(neighbors) = adj.get_mut(&v) {
535 neighbors.retain(|(n, _)| *n != u);
536 }
537 }
538
539 {
541 let mut updates = self.pending_updates.write()
542 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
543 updates.push_back(EdgeUpdate {
544 update_type: EdgeUpdateType::Delete,
545 source: u,
546 target: v,
547 weight: 0.0,
548 timestamp: Utc::now(),
549 });
550 }
551
552 {
554 let mut scores = self.local_flow_scores.write()
555 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
556 let key = if u < v { (u, v) } else { (v, u) };
557 scores.remove(&key);
558 }
559
560 self.cut_changed_flag.store(true, Ordering::Relaxed);
562
563 Ok(())
564 }
565
566 pub fn current_mincut(&self) -> f64 {
568 f64::from_bits(self.current_lambda.load(Ordering::Relaxed))
569 }
570
571 pub fn cut_changed(&self) -> bool {
573 self.cut_changed_flag.swap(false, Ordering::Relaxed)
574 }
575
576 pub fn is_cut_sensitive(&self, u: u32, v: u32) -> bool {
580 let scores = match self.local_flow_scores.read() {
581 Ok(s) => s,
582 Err(_) => return false,
583 };
584
585 let key = if u < v { (u, v) } else { (v, u) };
586 if let Some(&score) = scores.get(&key) {
587 score < self.lambda_threshold * 2.0
589 } else {
590 true
592 }
593 }
594
595 pub fn recompute_exact(&mut self, adj_matrix: &[Vec<f64>]) -> Result<f64, DynamicMinCutError> {
599 if adj_matrix.is_empty() {
600 return Err(DynamicMinCutError::EmptyGraph);
601 }
602
603 let mincut = stoer_wagner_mincut(adj_matrix)?;
604
605 self.current_lambda.store(mincut.to_bits(), Ordering::Relaxed);
606 self.cut_changed_flag.store(false, Ordering::Relaxed);
607
608 let mut last_comp = self.last_full_computation.write()
609 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
610 *last_comp = Some(Utc::now());
611
612 Ok(mincut)
613 }
614
615 pub fn process_updates(&mut self) -> Result<usize, DynamicMinCutError> {
617 let mut updates = self.pending_updates.write()
618 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
619
620 let count = updates.len();
621 updates.clear();
622
623 Ok(count)
624 }
625
626 fn update_local_flow_score(&self, u: u32, v: u32, weight: f64) -> Result<(), DynamicMinCutError> {
628 let mut scores = self.local_flow_scores.write()
629 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
630
631 let key = if u < v { (u, v) } else { (v, u) };
632
633 let adj = self.adjacency.read()
635 .map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
636
637 let deg_u = adj.get(&u).map(|v| v.len()).unwrap_or(1) as f64;
638 let deg_v = adj.get(&v).map(|v| v.len()).unwrap_or(1) as f64;
639
640 let flow_score = weight * (deg_u * deg_v).sqrt();
641 scores.insert(key, flow_score);
642
643 Ok(())
644 }
645
646 pub fn stats(&self) -> WatcherStats {
648 let tree = self.euler_tree.read().ok();
649 let updates = self.pending_updates.read().ok();
650 let last_comp = self.last_full_computation.read().ok().and_then(|r| *r);
651
652 WatcherStats {
653 current_lambda: self.current_mincut(),
654 pending_updates: updates.map(|u| u.len()).unwrap_or(0),
655 last_computation: last_comp,
656 et_tree_size: tree.map(|t| t.nodes.len()).unwrap_or(0),
657 }
658 }
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize)]
663pub struct WatcherStats {
664 pub current_lambda: f64,
665 pub pending_updates: usize,
666 pub last_computation: Option<DateTime<Utc>>,
667 pub et_tree_size: usize,
668}
669
670pub struct LocalMinCutProcedure {
678 ball_radius: usize,
680 phi_threshold: f64,
682}
683
684impl LocalMinCutProcedure {
685 pub fn new(ball_radius: usize, phi_threshold: f64) -> Self {
687 Self {
688 ball_radius,
689 phi_threshold,
690 }
691 }
692
693 pub fn local_cut(
697 &self,
698 adjacency: &HashMap<u32, Vec<(u32, f64)>>,
699 v: u32,
700 k: usize,
701 ) -> Option<LocalCut> {
702 let ball = self.grow_ball(adjacency, v, k);
704 if ball.is_empty() {
705 return None;
706 }
707
708 let cut = self.sweep_cut(adjacency, &ball)?;
710
711 Some(cut)
712 }
713
714 pub fn in_weak_region(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, v: u32) -> bool {
718 let ball = self.grow_ball(adjacency, v, self.ball_radius);
719 if ball.len() < 2 {
720 return false;
721 }
722
723 let conductance = self.compute_conductance(adjacency, &ball);
724 conductance < self.phi_threshold
725 }
726
727 fn grow_ball(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, start: u32, radius: usize) -> Vec<u32> {
729 let mut ball = HashSet::new();
730 let mut frontier = vec![start];
731 ball.insert(start);
732
733 for _ in 0..radius {
734 let mut next_frontier = Vec::new();
735 for &u in &frontier {
736 if let Some(neighbors) = adjacency.get(&u) {
737 for &(v, _) in neighbors {
738 if ball.insert(v) {
739 next_frontier.push(v);
740 }
741 }
742 }
743 }
744 if next_frontier.is_empty() {
745 break;
746 }
747 frontier = next_frontier;
748 }
749
750 ball.into_iter().collect()
751 }
752
753 fn sweep_cut(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, ball: &[u32]) -> Option<LocalCut> {
755 if ball.len() < 2 {
756 return None;
757 }
758
759 let mut sorted: Vec<_> = ball.iter().copied().collect();
761 sorted.sort_by_key(|&v| {
762 adjacency.get(&v).map(|n| n.len()).unwrap_or(0)
763 });
764
765 let mut best_cut = f64::INFINITY;
766 let mut best_partition = HashSet::new();
767
768 let mut current_set = HashSet::new();
769
770 for (i, &v) in sorted.iter().enumerate() {
771 current_set.insert(v);
772
773 let cut_value = self.compute_cut_value(adjacency, ¤t_set, ball);
775
776 if cut_value < best_cut && i > 0 && i < sorted.len() - 1 {
777 best_cut = cut_value;
778 best_partition = current_set.clone();
779 }
780 }
781
782 if best_cut < f64::INFINITY {
783 Some(LocalCut {
784 partition: best_partition.into_iter().collect(),
785 cut_value: best_cut,
786 conductance: self.compute_conductance(adjacency, ball),
787 })
788 } else {
789 None
790 }
791 }
792
793 fn compute_cut_value(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, set_s: &HashSet<u32>, ball: &[u32]) -> f64 {
795 let ball_set: HashSet<_> = ball.iter().copied().collect();
796 let mut cut = 0.0;
797
798 for &u in set_s {
799 if let Some(neighbors) = adjacency.get(&u) {
800 for &(v, weight) in neighbors {
801 if ball_set.contains(&v) && !set_s.contains(&v) {
802 cut += weight;
803 }
804 }
805 }
806 }
807
808 cut
809 }
810
811 fn compute_conductance(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, nodes: &[u32]) -> f64 {
813 let node_set: HashSet<_> = nodes.iter().copied().collect();
814
815 let mut cut_weight = 0.0;
816 let mut volume = 0.0;
817
818 for &u in nodes {
819 if let Some(neighbors) = adjacency.get(&u) {
820 for &(v, weight) in neighbors {
821 volume += weight;
822 if !node_set.contains(&v) {
823 cut_weight += weight;
824 }
825 }
826 }
827 }
828
829 if volume < 1e-10 {
830 0.0
831 } else {
832 cut_weight / volume
833 }
834 }
835}
836
837#[derive(Debug, Clone)]
839pub struct LocalCut {
840 pub partition: Vec<u32>,
841 pub cut_value: f64,
842 pub conductance: f64,
843}
844
845pub struct CutGatedSearch<'a> {
853 watcher: &'a DynamicCutWatcher,
854 coherence_gate: f64,
856 max_weak_expansions: usize,
858}
859
860impl<'a> CutGatedSearch<'a> {
861 pub fn new(watcher: &'a DynamicCutWatcher, coherence_gate: f64, max_weak_expansions: usize) -> Self {
863 Self {
864 watcher,
865 coherence_gate,
866 max_weak_expansions,
867 }
868 }
869
870 pub fn search(
874 &self,
875 query: &[f32],
876 k: usize,
877 graph: &HNSWGraph,
878 ) -> Result<Vec<(u32, f32)>, DynamicMinCutError> {
879 if query.len() != graph.dimension {
880 return Err(DynamicMinCutError::InvalidConfig(
881 format!("Query dimension {} != graph dimension {}", query.len(), graph.dimension)
882 ));
883 }
884
885 let mut candidates = Vec::new();
886 let mut visited = HashSet::new();
887 let mut weak_expansions = 0;
888
889 let entry = graph.entry_point;
891 let entry_dist = self.distance(query, &graph.vectors[entry as usize]);
892
893 candidates.push((entry, entry_dist));
894 visited.insert(entry);
895
896 let mut result: Vec<(u32, f32)> = Vec::new();
897
898 while let Some((current, current_dist)) = candidates.pop() {
899 if result.len() >= k && current_dist > result.last().unwrap().1 {
900 break;
901 }
902
903 if let Some(neighbors) = graph.adjacency.get(¤t) {
905 for &neighbor in neighbors {
906 if visited.contains(&neighbor) {
907 continue;
908 }
909
910 if !self.should_expand(current, neighbor) {
912 weak_expansions += 1;
913 if weak_expansions >= self.max_weak_expansions {
914 continue;
915 }
916 }
917
918 visited.insert(neighbor);
919 let dist = self.distance(query, &graph.vectors[neighbor as usize]);
920 candidates.push((neighbor, dist));
921 }
922 }
923
924 result.push((current, current_dist));
925 candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
926 }
927
928 result.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
929 result.truncate(k);
930
931 Ok(result)
932 }
933
934 fn should_expand(&self, from: u32, to: u32) -> bool {
938 if self.watcher.current_mincut() > self.coherence_gate {
940 return true;
941 }
942
943 !self.watcher.is_cut_sensitive(from, to)
945 }
946
947 fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
948 a.iter().zip(b.iter())
950 .map(|(x, y)| (x - y).powi(2))
951 .sum::<f32>()
952 .sqrt()
953 }
954}
955
956#[derive(Debug)]
958pub struct HNSWGraph {
959 pub vectors: Vec<Vec<f32>>,
960 pub adjacency: HashMap<u32, Vec<u32>>,
961 pub entry_point: u32,
962 pub dimension: usize,
963}
964
965fn stoer_wagner_mincut(adj: &[Vec<f64>]) -> Result<f64, DynamicMinCutError> {
973 let n = adj.len();
974 if n < 2 {
975 return Err(DynamicMinCutError::EmptyGraph);
976 }
977
978 let mut adj = adj.to_vec();
979 let mut best_cut = f64::INFINITY;
980 let mut active: Vec<bool> = vec![true; n];
981
982 for phase in 0..(n - 1) {
983 let mut in_a = vec![false; n];
984 let mut key = vec![0.0; n];
985
986 let start = match (0..n).find(|&i| active[i]) {
987 Some(s) => s,
988 None => break,
989 };
990 in_a[start] = true;
991
992 for j in 0..n {
993 if active[j] && !in_a[j] {
994 key[j] = adj[start][j];
995 }
996 }
997
998 let mut t = start;
999
1000 for _ in 1..=(n - 1 - phase) {
1001 let (max_node, _) = (0..n)
1002 .filter(|&j| active[j] && !in_a[j])
1003 .map(|j| (j, key[j]))
1004 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
1005 .unwrap_or((0, 0.0));
1006
1007 t = max_node;
1008 in_a[t] = true;
1009
1010 for j in 0..n {
1011 if active[j] && !in_a[j] {
1012 key[j] += adj[t][j];
1013 }
1014 }
1015 }
1016
1017 let cut_weight = key[t];
1018 if cut_weight < best_cut {
1019 best_cut = cut_weight;
1020 }
1021
1022 active[t] = false;
1024 for i in 0..n {
1025 if active[i] && i != t {
1026 let s = (0..n).filter(|&j| active[j] && in_a[j]).last().unwrap_or(start);
1027 adj[s][i] += adj[t][i];
1028 adj[i][s] += adj[i][t];
1029 }
1030 }
1031 }
1032
1033 Ok(best_cut)
1034}
1035
1036#[cfg(test)]
1041mod tests {
1042 use super::*;
1043
1044 #[test]
1045 fn test_euler_tour_tree_basic() {
1046 let mut ett = EulerTourTree::new();
1047
1048 ett.add_vertex(0);
1050 ett.add_vertex(1);
1051 ett.add_vertex(2);
1052
1053 assert!(!ett.connected(0, 1));
1055 assert!(!ett.connected(1, 2));
1056
1057 ett.link(0, 1).unwrap();
1059 assert!(ett.connected(0, 1));
1060 assert!(!ett.connected(0, 2));
1061
1062 ett.link(1, 2).unwrap();
1064 assert!(ett.connected(0, 2));
1065
1066 assert_eq!(ett.component_size(0), 3);
1068 assert_eq!(ett.component_size(1), 3);
1069 }
1070
1071 #[test]
1072 fn test_euler_tour_tree_cut() {
1073 let mut ett = EulerTourTree::new();
1074
1075 ett.add_vertex(0);
1076 ett.add_vertex(1);
1077 ett.add_vertex(2);
1078 ett.add_vertex(3);
1079
1080 ett.link(0, 1).unwrap();
1082 ett.link(1, 2).unwrap();
1083 ett.link(2, 3).unwrap();
1084
1085 assert!(ett.connected(0, 3));
1086
1087 ett.cut(1, 2).unwrap();
1089 assert!(!ett.connected(0, 3));
1090 assert!(ett.connected(0, 1));
1091 assert!(ett.connected(2, 3));
1092 }
1093
1094 #[test]
1095 fn test_dynamic_cut_watcher_insert() {
1096 let config = CutWatcherConfig::default();
1097 let mut watcher = DynamicCutWatcher::new(config);
1098
1099 watcher.insert_edge(0, 1, 1.0).unwrap();
1101 watcher.insert_edge(1, 2, 1.0).unwrap();
1102 watcher.insert_edge(2, 0, 1.0).unwrap();
1103
1104 let tree = watcher.euler_tree.read().unwrap();
1106 assert!(tree.connected(0, 1));
1107 assert!(tree.connected(1, 2));
1108 assert!(tree.connected(0, 2));
1109 }
1110
1111 #[test]
1112 fn test_dynamic_cut_watcher_delete() {
1113 let config = CutWatcherConfig::default();
1114 let mut watcher = DynamicCutWatcher::new(config);
1115
1116 watcher.insert_edge(0, 1, 1.0).unwrap();
1117 watcher.insert_edge(1, 2, 1.0).unwrap();
1118
1119 {
1120 let tree = watcher.euler_tree.read().unwrap();
1121 assert!(tree.connected(0, 2));
1122 }
1123
1124 watcher.delete_edge(1, 2).unwrap();
1125
1126 {
1127 let tree = watcher.euler_tree.read().unwrap();
1128 assert!(!tree.connected(0, 2));
1129 assert!(tree.connected(0, 1));
1130 }
1131 }
1132
1133 #[test]
1134 fn test_stoer_wagner_simple() {
1135 let adj = vec![
1137 vec![0.0, 1.0, 1.0],
1138 vec![1.0, 0.0, 1.0],
1139 vec![1.0, 1.0, 0.0],
1140 ];
1141
1142 let mincut = stoer_wagner_mincut(&adj).unwrap();
1143 assert!((mincut - 2.0).abs() < 1e-6);
1144 }
1145
1146 #[test]
1147 fn test_stoer_wagner_weighted() {
1148 let adj = vec![
1150 vec![0.0, 5.0, 0.0, 1.0],
1151 vec![5.0, 0.0, 5.0, 0.0],
1152 vec![0.0, 5.0, 0.0, 5.0],
1153 vec![1.0, 0.0, 5.0, 0.0],
1154 ];
1155
1156 let mincut = stoer_wagner_mincut(&adj).unwrap();
1157 assert!((mincut - 6.0).abs() < 1e-6); }
1159
1160 #[test]
1161 fn test_local_mincut_ball_growing() {
1162 let mut adjacency = HashMap::new();
1163 adjacency.insert(0, vec![(1, 1.0), (2, 1.0)]);
1164 adjacency.insert(1, vec![(0, 1.0), (2, 1.0), (3, 1.0)]);
1165 adjacency.insert(2, vec![(0, 1.0), (1, 1.0)]);
1166 adjacency.insert(3, vec![(1, 1.0), (4, 1.0)]);
1167 adjacency.insert(4, vec![(3, 1.0)]);
1168
1169 let procedure = LocalMinCutProcedure::new(2, 0.3);
1170 let ball = procedure.grow_ball(&adjacency, 0, 2);
1171
1172 assert!(ball.contains(&0));
1173 assert!(ball.contains(&1));
1174 assert!(ball.contains(&2));
1175 assert!(ball.contains(&3)); }
1177
1178 #[test]
1179 fn test_local_mincut_weak_region() {
1180 let mut adjacency = HashMap::new();
1181 adjacency.insert(0, vec![(1, 1.0), (2, 1.0), (3, 1.0), (4, 1.0)]);
1183 adjacency.insert(1, vec![(0, 1.0)]);
1184 adjacency.insert(2, vec![(0, 1.0)]);
1185 adjacency.insert(3, vec![(0, 1.0)]);
1186 adjacency.insert(4, vec![(0, 1.0)]);
1187
1188 let procedure = LocalMinCutProcedure::new(1, 0.5);
1189
1190 assert!(!procedure.in_weak_region(&adjacency, 0));
1192
1193 assert!(procedure.in_weak_region(&adjacency, 1));
1195 }
1196
1197 #[test]
1198 fn test_local_cut_computation() {
1199 let mut adjacency = HashMap::new();
1200 adjacency.insert(0, vec![(1, 2.0), (2, 1.0)]);
1201 adjacency.insert(1, vec![(0, 2.0), (2, 2.0), (3, 1.0)]);
1202 adjacency.insert(2, vec![(0, 1.0), (1, 2.0), (3, 2.0)]);
1203 adjacency.insert(3, vec![(1, 1.0), (2, 2.0)]);
1204
1205 let procedure = LocalMinCutProcedure::new(3, 0.3);
1206 let cut = procedure.local_cut(&adjacency, 0, 3);
1207
1208 assert!(cut.is_some());
1209 let cut = cut.unwrap();
1210 assert!(cut.cut_value > 0.0);
1211 assert!(!cut.partition.is_empty());
1212 }
1213
1214 #[test]
1215 fn test_cut_watcher_is_sensitive() {
1216 let config = CutWatcherConfig::default();
1217 let mut watcher = DynamicCutWatcher::new(config);
1218
1219 watcher.insert_edge(0, 1, 1.0).unwrap();
1220 watcher.insert_edge(1, 2, 0.1).unwrap(); assert!(watcher.is_cut_sensitive(1, 2));
1224 }
1225
1226 #[test]
1227 fn test_cut_watcher_stats() {
1228 let config = CutWatcherConfig::default();
1229 let mut watcher = DynamicCutWatcher::new(config);
1230
1231 watcher.insert_edge(0, 1, 1.0).unwrap();
1232 watcher.insert_edge(1, 2, 1.0).unwrap();
1233
1234 let stats = watcher.stats();
1235 assert_eq!(stats.pending_updates, 2);
1236 assert!(stats.et_tree_size > 0);
1237 }
1238
1239 #[test]
1240 fn test_cut_watcher_process_updates() {
1241 let config = CutWatcherConfig::default();
1242 let mut watcher = DynamicCutWatcher::new(config);
1243
1244 watcher.insert_edge(0, 1, 1.0).unwrap();
1245 watcher.insert_edge(1, 2, 1.0).unwrap();
1246 watcher.insert_edge(2, 3, 1.0).unwrap();
1247
1248 let processed = watcher.process_updates().unwrap();
1249 assert_eq!(processed, 3);
1250
1251 let processed = watcher.process_updates().unwrap();
1252 assert_eq!(processed, 0);
1253 }
1254
1255 #[test]
1256 fn test_cut_watcher_recompute() {
1257 let config = CutWatcherConfig::default();
1258 let mut watcher = DynamicCutWatcher::new(config);
1259
1260 let adj = vec![
1261 vec![0.0, 1.0, 1.0, 0.0],
1262 vec![1.0, 0.0, 1.0, 1.0],
1263 vec![1.0, 1.0, 0.0, 1.0],
1264 vec![0.0, 1.0, 1.0, 0.0],
1265 ];
1266
1267 let mincut = watcher.recompute_exact(&adj).unwrap();
1268 assert!(mincut > 0.0);
1269 assert_eq!(watcher.current_mincut(), mincut);
1270 }
1271
1272 #[test]
1273 fn test_cut_gated_search_basic() {
1274 let config = CutWatcherConfig::default();
1275 let watcher = DynamicCutWatcher::new(config);
1276
1277 let search = CutGatedSearch::new(&watcher, 1.0, 5);
1278
1279 let graph = HNSWGraph {
1280 vectors: vec![
1281 vec![1.0, 0.0, 0.0],
1282 vec![0.9, 0.1, 0.0],
1283 vec![0.0, 1.0, 0.0],
1284 vec![0.0, 0.0, 1.0],
1285 ],
1286 adjacency: {
1287 let mut adj = HashMap::new();
1288 adj.insert(0, vec![1, 2]);
1289 adj.insert(1, vec![0, 2]);
1290 adj.insert(2, vec![0, 1, 3]);
1291 adj.insert(3, vec![2]);
1292 adj
1293 },
1294 entry_point: 0,
1295 dimension: 3,
1296 };
1297
1298 let query = vec![1.0, 0.0, 0.0];
1299 let results = search.search(&query, 2, &graph).unwrap();
1300
1301 assert!(!results.is_empty());
1302 assert!(results.len() <= 2);
1303 }
1304
1305 #[test]
1306 fn test_edge_update_serialization() {
1307 let update = EdgeUpdate {
1308 update_type: EdgeUpdateType::Insert,
1309 source: 0,
1310 target: 1,
1311 weight: 1.5,
1312 timestamp: Utc::now(),
1313 };
1314
1315 let json = serde_json::to_string(&update).unwrap();
1316 let parsed: EdgeUpdate = serde_json::from_str(&json).unwrap();
1317
1318 assert_eq!(parsed.source, 0);
1319 assert_eq!(parsed.target, 1);
1320 assert!((parsed.weight - 1.5).abs() < 1e-6);
1321 }
1322
1323 #[test]
1324 fn test_config_defaults() {
1325 let config = CutWatcherConfig::default();
1326 assert_eq!(config.lambda_bound, 100);
1327 assert!(config.use_local_heuristics);
1328 assert!(config.update_interval_ms > 0);
1329 }
1330
1331 #[test]
1332 fn test_ett_multiple_components() {
1333 let mut ett = EulerTourTree::new();
1334
1335 ett.link(0, 1).unwrap();
1337 ett.link(1, 2).unwrap();
1338
1339 ett.link(3, 4).unwrap();
1341
1342 assert!(ett.connected(0, 2));
1343 assert!(ett.connected(3, 4));
1344 assert!(!ett.connected(0, 3));
1345
1346 assert_eq!(ett.component_size(0), 3);
1347 assert_eq!(ett.component_size(3), 2);
1348 }
1349
1350 #[test]
1351 fn test_ett_cycle() {
1352 let mut ett = EulerTourTree::new();
1353
1354 ett.link(0, 1).unwrap();
1356 ett.link(1, 2).unwrap();
1357 ett.link(2, 3).unwrap();
1358 ett.link(3, 0).unwrap();
1359
1360 assert!(ett.connected(0, 2));
1362 assert!(ett.connected(1, 3));
1363
1364 ett.cut(0, 1).unwrap();
1366 assert!(ett.connected(0, 3)); assert!(ett.connected(0, 2)); }
1369
1370 #[test]
1371 fn test_conductance_calculation() {
1372 let mut adjacency = HashMap::new();
1373 adjacency.insert(0, vec![(1, 1.0), (2, 1.0)]);
1374 adjacency.insert(1, vec![(0, 1.0), (2, 1.0)]);
1375 adjacency.insert(2, vec![(0, 1.0), (1, 1.0), (3, 0.5)]);
1376 adjacency.insert(3, vec![(2, 0.5)]);
1377
1378 let procedure = LocalMinCutProcedure::new(2, 0.3);
1379 let nodes = vec![0, 1, 2];
1380 let conductance = procedure.compute_conductance(&adjacency, &nodes);
1381
1382 assert!(conductance > 0.0 && conductance < 1.0);
1385 }
1386
1387 #[test]
1388 fn test_cut_value_computation() {
1389 let mut adjacency = HashMap::new();
1390 adjacency.insert(0, vec![(1, 2.0), (2, 1.0)]);
1391 adjacency.insert(1, vec![(0, 2.0), (2, 3.0)]);
1392 adjacency.insert(2, vec![(0, 1.0), (1, 3.0)]);
1393
1394 let procedure = LocalMinCutProcedure::new(2, 0.3);
1395 let ball = vec![0, 1, 2];
1396
1397 let mut set_s = HashSet::new();
1398 set_s.insert(0);
1399
1400 let cut_value = procedure.compute_cut_value(&adjacency, &set_s, &ball);
1401
1402 assert!((cut_value - 3.0).abs() < 1e-6);
1404 }
1405
1406 #[test]
1407 fn test_watcher_cut_changed_flag() {
1408 let config = CutWatcherConfig::default();
1409 let mut watcher = DynamicCutWatcher::new(config);
1410
1411 assert!(!watcher.cut_changed());
1413
1414 watcher.insert_edge(0, 1, 1.0).unwrap();
1416 watcher.delete_edge(0, 1).unwrap();
1417
1418 assert!(watcher.cut_changed());
1419 assert!(!watcher.cut_changed());
1421 }
1422}
1423
1424#[cfg(test)]
1429mod benchmarks {
1430 use super::*;
1431 use std::time::Instant;
1432
1433 #[test]
1434 fn bench_euler_tour_tree_operations() {
1435 let mut ett = EulerTourTree::new();
1436 let n = 1000;
1437
1438 for i in 0..n {
1440 ett.add_vertex(i);
1441 }
1442
1443 let start = Instant::now();
1445 for i in 0..n-1 {
1446 ett.link(i, i + 1).unwrap();
1447 }
1448 let link_time = start.elapsed();
1449 println!("ETT Link {} edges: {:?} ({:.2} µs/op)",
1450 n-1, link_time, link_time.as_micros() as f64 / (n-1) as f64);
1451
1452 let start = Instant::now();
1454 let queries = 100;
1455 for i in 0..queries {
1456 ett.connected(i % n, (i * 7) % n);
1457 }
1458 let query_time = start.elapsed();
1459 println!("ETT Connectivity {} queries: {:?} ({:.2} µs/op)",
1460 queries, query_time, query_time.as_micros() as f64 / queries as f64);
1461
1462 let start = Instant::now();
1464 for i in 0..10 {
1465 ett.cut(i * 10, i * 10 + 1).ok();
1466 }
1467 let cut_time = start.elapsed();
1468 println!("ETT Cut 10 edges: {:?} ({:.2} µs/op)",
1469 cut_time, cut_time.as_micros() as f64 / 10.0);
1470 }
1471
1472 #[test]
1473 fn bench_dynamic_watcher_updates() {
1474 let config = CutWatcherConfig::default();
1475 let mut watcher = DynamicCutWatcher::new(config);
1476
1477 let n = 500;
1478
1479 let start = Instant::now();
1481 for i in 0..n-1 {
1482 watcher.insert_edge(i, i + 1, 1.0).unwrap();
1483 }
1484 let insert_time = start.elapsed();
1485 println!("Dynamic Watcher Insert {} edges: {:?} ({:.2} µs/op)",
1486 n-1, insert_time, insert_time.as_micros() as f64 / (n-1) as f64);
1487
1488 let start = Instant::now();
1490 for i in 0..10 {
1491 watcher.delete_edge(i * 10, i * 10 + 1).ok();
1492 }
1493 let delete_time = start.elapsed();
1494 println!("Dynamic Watcher Delete 10 edges: {:?} ({:.2} µs/op)",
1495 delete_time, delete_time.as_micros() as f64 / 10.0);
1496 }
1497
1498 #[test]
1499 fn bench_stoer_wagner_comparison() {
1500 let n = 50;
1504 let mut adj = vec![vec![0.0; n]; n];
1505 for i in 0..n {
1506 for j in i+1..n {
1507 if (i * 7 + j * 13) % 3 == 0 {
1508 let weight = ((i + j) % 10 + 1) as f64;
1509 adj[i][j] = weight;
1510 adj[j][i] = weight;
1511 }
1512 }
1513 }
1514
1515 let start = Instant::now();
1517 for _ in 0..10 {
1518 stoer_wagner_mincut(&adj).unwrap();
1519 }
1520 let periodic_time = start.elapsed();
1521 println!("Periodic (10 full computations): {:?}", periodic_time);
1522
1523 let config = CutWatcherConfig::default();
1525 let mut watcher = DynamicCutWatcher::new(config);
1526
1527 let start = Instant::now();
1528
1529 for i in 0..n {
1531 for j in i+1..n {
1532 if adj[i][j] > 0.0 {
1533 watcher.insert_edge(i as u32, j as u32, adj[i][j]).unwrap();
1534 }
1535 }
1536 }
1537
1538 for i in 0..10 {
1540 let u = (i * 3) % n;
1541 let v = (i * 7 + 1) % n;
1542 if u != v {
1543 watcher.insert_edge(u as u32, v as u32, 1.0).ok();
1544 }
1545 }
1546
1547 let dynamic_time = start.elapsed();
1548 println!("Dynamic (build + 10 updates): {:?}", dynamic_time);
1549 println!("Speedup: {:.2}x", periodic_time.as_secs_f64() / dynamic_time.as_secs_f64());
1550 }
1551
1552 #[test]
1553 fn bench_local_mincut_procedure() {
1554 let mut adjacency = HashMap::new();
1556 let n = 100;
1557
1558 for i in 0..n {
1559 let mut neighbors = Vec::new();
1560 for j in 1..=5 {
1562 let target = (i + j) % n;
1563 neighbors.push((target, 1.0));
1564 }
1565 adjacency.insert(i, neighbors);
1566 }
1567
1568 let procedure = LocalMinCutProcedure::new(3, 0.3);
1569
1570 let start = Instant::now();
1571 let iterations = 20;
1572 for i in 0..iterations {
1573 procedure.local_cut(&adjacency, i % n, 5);
1574 }
1575 let time = start.elapsed();
1576 println!("Local MinCut {} iterations: {:?} ({:.2} ms/op)",
1577 iterations, time, time.as_millis() as f64 / iterations as f64);
1578 }
1579}