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