use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, RwLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, thiserror::Error)]
pub enum DynamicMinCutError {
#[error("Invalid edge: {0}")]
InvalidEdge(String),
#[error("Node not found: {0}")]
NodeNotFound(u32),
#[error("Graph is empty")]
EmptyGraph,
#[error("Disconnected components")]
DisconnectedGraph,
#[error("Invalid configuration: {0}")]
InvalidConfig(String),
#[error("Computation failed: {0}")]
ComputationError(String),
}
#[derive(Debug, Clone)]
struct ETNode {
graph_node: u32,
parent: Option<usize>,
left: Option<usize>,
right: Option<usize>,
size: usize,
edge_tour: Option<(u32, u32)>,
}
impl ETNode {
fn new(graph_node: u32) -> Self {
Self {
graph_node,
parent: None,
left: None,
right: None,
size: 1,
edge_tour: None,
}
}
fn new_edge_tour(u: u32, v: u32) -> Self {
Self {
graph_node: u,
parent: None,
left: None,
right: None,
size: 1,
edge_tour: Some((u, v)),
}
}
}
pub struct EulerTourTree {
nodes: Vec<ETNode>,
node_map: HashMap<u32, Vec<usize>>,
edge_map: HashMap<(u32, u32), Vec<usize>>,
next_idx: usize,
}
impl EulerTourTree {
pub fn new() -> Self {
Self {
nodes: Vec::with_capacity(1000),
node_map: HashMap::new(),
edge_map: HashMap::new(),
next_idx: 0,
}
}
pub fn add_vertex(&mut self, v: u32) {
if !self.node_map.contains_key(&v) {
let idx = self.alloc_node(ETNode::new(v));
self.node_map.entry(v).or_default().push(idx);
}
}
pub fn link(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
if self.connected(u, v) {
return Ok(()); }
self.add_vertex(u);
self.add_vertex(v);
let u_idx = *self.node_map.get(&u)
.and_then(|v| v.first())
.ok_or(DynamicMinCutError::NodeNotFound(u))?;
let v_idx = *self.node_map.get(&v)
.and_then(|v| v.first())
.ok_or(DynamicMinCutError::NodeNotFound(v))?;
let uv_idx = self.alloc_node(ETNode::new_edge_tour(u, v));
let vu_idx = self.alloc_node(ETNode::new_edge_tour(v, u));
let key = if u < v { (u, v) } else { (v, u) };
self.edge_map.entry(key).or_default().extend(&[uv_idx, vu_idx]);
self.splay(u_idx);
self.splay(v_idx);
self.join_trees(u_idx, uv_idx);
self.join_trees(uv_idx, v_idx);
self.join_trees(v_idx, vu_idx);
Ok(())
}
pub fn cut(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
let key = if u < v { (u, v) } else { (v, u) };
let edge_nodes = self.edge_map.remove(&key)
.ok_or(DynamicMinCutError::InvalidEdge(format!("Edge {}-{} not found", u, v)))?;
for &idx in &edge_nodes {
if idx < self.nodes.len() {
self.splay(idx);
self.split_at(idx);
}
}
Ok(())
}
pub fn connected(&self, u: u32, v: u32) -> bool {
let u_idx = match self.node_map.get(&u).and_then(|v| v.first()) {
Some(&idx) => idx,
None => return false,
};
let v_idx = match self.node_map.get(&v).and_then(|v| v.first()) {
Some(&idx) => idx,
None => return false,
};
self.find_root(u_idx) == self.find_root(v_idx)
}
pub fn component_size(&self, v: u32) -> usize {
let idx = match self.node_map.get(&v).and_then(|v| v.first()) {
Some(&idx) => idx,
None => return 0,
};
let root = self.find_root(idx);
if root < self.nodes.len() {
self.nodes[root].size
} else {
1
}
}
fn alloc_node(&mut self, node: ETNode) -> usize {
let idx = self.next_idx;
self.next_idx += 1;
if idx >= self.nodes.len() {
self.nodes.push(node);
} else {
self.nodes[idx] = node;
}
idx
}
fn find_root(&self, mut idx: usize) -> usize {
while let Some(parent) = self.nodes.get(idx).and_then(|n| n.parent) {
idx = parent;
}
idx
}
fn splay(&mut self, idx: usize) {
if idx >= self.nodes.len() {
return;
}
while self.nodes[idx].parent.is_some() {
let p = self.nodes[idx].parent.unwrap();
if self.nodes[p].parent.is_none() {
if self.is_left_child(idx) {
self.rotate_right(p);
} else {
self.rotate_left(p);
}
} else {
let g = self.nodes[p].parent.unwrap();
if self.is_left_child(idx) && self.is_left_child(p) {
self.rotate_right(g);
self.rotate_right(p);
} else if !self.is_left_child(idx) && !self.is_left_child(p) {
self.rotate_left(g);
self.rotate_left(p);
} else if self.is_left_child(idx) {
self.rotate_right(p);
self.rotate_left(g);
} else {
self.rotate_left(p);
self.rotate_right(g);
}
}
}
}
fn is_left_child(&self, idx: usize) -> bool {
if let Some(parent_idx) = self.nodes[idx].parent {
if let Some(left_idx) = self.nodes[parent_idx].left {
return left_idx == idx;
}
}
false
}
fn rotate_left(&mut self, idx: usize) {
if let Some(right_idx) = self.nodes[idx].right {
let parent = self.nodes[idx].parent;
self.nodes[idx].right = self.nodes[right_idx].left;
if let Some(rl) = self.nodes[right_idx].left {
self.nodes[rl].parent = Some(idx);
}
self.nodes[right_idx].left = Some(idx);
self.nodes[right_idx].parent = parent;
self.nodes[idx].parent = Some(right_idx);
if let Some(p) = parent {
if self.nodes[p].left == Some(idx) {
self.nodes[p].left = Some(right_idx);
} else {
self.nodes[p].right = Some(right_idx);
}
}
self.update_size(idx);
self.update_size(right_idx);
}
}
fn rotate_right(&mut self, idx: usize) {
if let Some(left_idx) = self.nodes[idx].left {
let parent = self.nodes[idx].parent;
self.nodes[idx].left = self.nodes[left_idx].right;
if let Some(lr) = self.nodes[left_idx].right {
self.nodes[lr].parent = Some(idx);
}
self.nodes[left_idx].right = Some(idx);
self.nodes[left_idx].parent = parent;
self.nodes[idx].parent = Some(left_idx);
if let Some(p) = parent {
if self.nodes[p].left == Some(idx) {
self.nodes[p].left = Some(left_idx);
} else {
self.nodes[p].right = Some(left_idx);
}
}
self.update_size(idx);
self.update_size(left_idx);
}
}
fn update_size(&mut self, idx: usize) {
let left_size = self.nodes[idx].left.map(|i| self.nodes[i].size).unwrap_or(0);
let right_size = self.nodes[idx].right.map(|i| self.nodes[i].size).unwrap_or(0);
self.nodes[idx].size = 1 + left_size + right_size;
}
fn join_trees(&mut self, left: usize, right: usize) {
self.splay(left);
self.splay(right);
self.nodes[left].right = Some(right);
self.nodes[right].parent = Some(left);
self.update_size(left);
}
fn split_at(&mut self, idx: usize) {
self.splay(idx);
if let Some(right) = self.nodes[idx].right {
self.nodes[right].parent = None;
self.nodes[idx].right = None;
self.update_size(idx);
}
if let Some(left) = self.nodes[idx].left {
self.nodes[left].parent = None;
self.nodes[idx].left = None;
self.update_size(idx);
}
}
}
impl Default for EulerTourTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeUpdate {
pub update_type: EdgeUpdateType,
pub source: u32,
pub target: u32,
pub weight: f64,
pub timestamp: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum EdgeUpdateType {
Insert,
Delete,
WeightChange,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CutWatcherConfig {
pub lambda_bound: usize,
pub change_threshold: f64,
pub use_local_heuristics: bool,
pub update_interval_ms: u64,
pub flow_iterations: usize,
pub ball_radius: usize,
pub conductance_threshold: f64,
}
impl Default for CutWatcherConfig {
fn default() -> Self {
Self {
lambda_bound: 100, change_threshold: 0.15,
use_local_heuristics: true,
update_interval_ms: 1000,
flow_iterations: 50,
ball_radius: 3,
conductance_threshold: 0.3,
}
}
}
pub struct DynamicCutWatcher {
config: CutWatcherConfig,
euler_tree: Arc<RwLock<EulerTourTree>>,
current_lambda: AtomicU64,
lambda_threshold: f64,
local_flow_scores: Arc<RwLock<HashMap<(u32, u32), f64>>>,
pending_updates: Arc<RwLock<VecDeque<EdgeUpdate>>>,
adjacency: Arc<RwLock<HashMap<u32, Vec<(u32, f64)>>>>,
cut_changed_flag: AtomicBool,
last_full_computation: Arc<RwLock<Option<DateTime<Utc>>>>,
}
impl DynamicCutWatcher {
pub fn new(config: CutWatcherConfig) -> Self {
Self {
lambda_threshold: config.change_threshold,
config,
euler_tree: Arc::new(RwLock::new(EulerTourTree::new())),
current_lambda: AtomicU64::new(0),
local_flow_scores: Arc::new(RwLock::new(HashMap::new())),
pending_updates: Arc::new(RwLock::new(VecDeque::new())),
adjacency: Arc::new(RwLock::new(HashMap::new())),
cut_changed_flag: AtomicBool::new(false),
last_full_computation: Arc::new(RwLock::new(None)),
}
}
pub fn insert_edge(&mut self, u: u32, v: u32, weight: f64) -> Result<(), DynamicMinCutError> {
{
let mut tree = self.euler_tree.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
tree.link(u, v)?;
}
{
let mut adj = self.adjacency.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
adj.entry(u).or_default().push((v, weight));
adj.entry(v).or_default().push((u, weight));
}
{
let mut updates = self.pending_updates.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
updates.push_back(EdgeUpdate {
update_type: EdgeUpdateType::Insert,
source: u,
target: v,
weight,
timestamp: Utc::now(),
});
}
self.update_local_flow_score(u, v, weight)?;
Ok(())
}
pub fn delete_edge(&mut self, u: u32, v: u32) -> Result<(), DynamicMinCutError> {
{
let mut tree = self.euler_tree.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
tree.cut(u, v)?;
}
{
let mut adj = self.adjacency.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
if let Some(neighbors) = adj.get_mut(&u) {
neighbors.retain(|(n, _)| *n != v);
}
if let Some(neighbors) = adj.get_mut(&v) {
neighbors.retain(|(n, _)| *n != u);
}
}
{
let mut updates = self.pending_updates.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
updates.push_back(EdgeUpdate {
update_type: EdgeUpdateType::Delete,
source: u,
target: v,
weight: 0.0,
timestamp: Utc::now(),
});
}
{
let mut scores = self.local_flow_scores.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
let key = if u < v { (u, v) } else { (v, u) };
scores.remove(&key);
}
self.cut_changed_flag.store(true, Ordering::Relaxed);
Ok(())
}
pub fn current_mincut(&self) -> f64 {
f64::from_bits(self.current_lambda.load(Ordering::Relaxed))
}
pub fn cut_changed(&self) -> bool {
self.cut_changed_flag.swap(false, Ordering::Relaxed)
}
pub fn is_cut_sensitive(&self, u: u32, v: u32) -> bool {
let scores = match self.local_flow_scores.read() {
Ok(s) => s,
Err(_) => return false,
};
let key = if u < v { (u, v) } else { (v, u) };
if let Some(&score) = scores.get(&key) {
score < self.lambda_threshold * 2.0
} else {
true
}
}
pub fn recompute_exact(&mut self, adj_matrix: &[Vec<f64>]) -> Result<f64, DynamicMinCutError> {
if adj_matrix.is_empty() {
return Err(DynamicMinCutError::EmptyGraph);
}
let mincut = stoer_wagner_mincut(adj_matrix)?;
self.current_lambda.store(mincut.to_bits(), Ordering::Relaxed);
self.cut_changed_flag.store(false, Ordering::Relaxed);
let mut last_comp = self.last_full_computation.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
*last_comp = Some(Utc::now());
Ok(mincut)
}
pub fn process_updates(&mut self) -> Result<usize, DynamicMinCutError> {
let mut updates = self.pending_updates.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
let count = updates.len();
updates.clear();
Ok(count)
}
fn update_local_flow_score(&self, u: u32, v: u32, weight: f64) -> Result<(), DynamicMinCutError> {
let mut scores = self.local_flow_scores.write()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
let key = if u < v { (u, v) } else { (v, u) };
let adj = self.adjacency.read()
.map_err(|e| DynamicMinCutError::ComputationError(format!("Lock error: {}", e)))?;
let deg_u = adj.get(&u).map(|v| v.len()).unwrap_or(1) as f64;
let deg_v = adj.get(&v).map(|v| v.len()).unwrap_or(1) as f64;
let flow_score = weight * (deg_u * deg_v).sqrt();
scores.insert(key, flow_score);
Ok(())
}
pub fn stats(&self) -> WatcherStats {
let tree = self.euler_tree.read().ok();
let updates = self.pending_updates.read().ok();
let last_comp = self.last_full_computation.read().ok().and_then(|r| *r);
WatcherStats {
current_lambda: self.current_mincut(),
pending_updates: updates.map(|u| u.len()).unwrap_or(0),
last_computation: last_comp,
et_tree_size: tree.map(|t| t.nodes.len()).unwrap_or(0),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatcherStats {
pub current_lambda: f64,
pub pending_updates: usize,
pub last_computation: Option<DateTime<Utc>>,
pub et_tree_size: usize,
}
pub struct LocalMinCutProcedure {
ball_radius: usize,
phi_threshold: f64,
}
impl LocalMinCutProcedure {
pub fn new(ball_radius: usize, phi_threshold: f64) -> Self {
Self {
ball_radius,
phi_threshold,
}
}
pub fn local_cut(
&self,
adjacency: &HashMap<u32, Vec<(u32, f64)>>,
v: u32,
k: usize,
) -> Option<LocalCut> {
let ball = self.grow_ball(adjacency, v, k);
if ball.is_empty() {
return None;
}
let cut = self.sweep_cut(adjacency, &ball)?;
Some(cut)
}
pub fn in_weak_region(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, v: u32) -> bool {
let ball = self.grow_ball(adjacency, v, self.ball_radius);
if ball.len() < 2 {
return false;
}
let conductance = self.compute_conductance(adjacency, &ball);
conductance < self.phi_threshold
}
fn grow_ball(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, start: u32, radius: usize) -> Vec<u32> {
let mut ball = HashSet::new();
let mut frontier = vec![start];
ball.insert(start);
for _ in 0..radius {
let mut next_frontier = Vec::new();
for &u in &frontier {
if let Some(neighbors) = adjacency.get(&u) {
for &(v, _) in neighbors {
if ball.insert(v) {
next_frontier.push(v);
}
}
}
}
if next_frontier.is_empty() {
break;
}
frontier = next_frontier;
}
ball.into_iter().collect()
}
fn sweep_cut(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, ball: &[u32]) -> Option<LocalCut> {
if ball.len() < 2 {
return None;
}
let mut sorted: Vec<_> = ball.iter().copied().collect();
sorted.sort_by_key(|&v| {
adjacency.get(&v).map(|n| n.len()).unwrap_or(0)
});
let mut best_cut = f64::INFINITY;
let mut best_partition = HashSet::new();
let mut current_set = HashSet::new();
for (i, &v) in sorted.iter().enumerate() {
current_set.insert(v);
let cut_value = self.compute_cut_value(adjacency, ¤t_set, ball);
if cut_value < best_cut && i > 0 && i < sorted.len() - 1 {
best_cut = cut_value;
best_partition = current_set.clone();
}
}
if best_cut < f64::INFINITY {
Some(LocalCut {
partition: best_partition.into_iter().collect(),
cut_value: best_cut,
conductance: self.compute_conductance(adjacency, ball),
})
} else {
None
}
}
fn compute_cut_value(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, set_s: &HashSet<u32>, ball: &[u32]) -> f64 {
let ball_set: HashSet<_> = ball.iter().copied().collect();
let mut cut = 0.0;
for &u in set_s {
if let Some(neighbors) = adjacency.get(&u) {
for &(v, weight) in neighbors {
if ball_set.contains(&v) && !set_s.contains(&v) {
cut += weight;
}
}
}
}
cut
}
fn compute_conductance(&self, adjacency: &HashMap<u32, Vec<(u32, f64)>>, nodes: &[u32]) -> f64 {
let node_set: HashSet<_> = nodes.iter().copied().collect();
let mut cut_weight = 0.0;
let mut volume = 0.0;
for &u in nodes {
if let Some(neighbors) = adjacency.get(&u) {
for &(v, weight) in neighbors {
volume += weight;
if !node_set.contains(&v) {
cut_weight += weight;
}
}
}
}
if volume < 1e-10 {
0.0
} else {
cut_weight / volume
}
}
}
#[derive(Debug, Clone)]
pub struct LocalCut {
pub partition: Vec<u32>,
pub cut_value: f64,
pub conductance: f64,
}
pub struct CutGatedSearch<'a> {
watcher: &'a DynamicCutWatcher,
coherence_gate: f64,
max_weak_expansions: usize,
}
impl<'a> CutGatedSearch<'a> {
pub fn new(watcher: &'a DynamicCutWatcher, coherence_gate: f64, max_weak_expansions: usize) -> Self {
Self {
watcher,
coherence_gate,
max_weak_expansions,
}
}
pub fn search(
&self,
query: &[f32],
k: usize,
graph: &HNSWGraph,
) -> Result<Vec<(u32, f32)>, DynamicMinCutError> {
if query.len() != graph.dimension {
return Err(DynamicMinCutError::InvalidConfig(
format!("Query dimension {} != graph dimension {}", query.len(), graph.dimension)
));
}
let mut candidates = Vec::new();
let mut visited = HashSet::new();
let mut weak_expansions = 0;
let entry = graph.entry_point;
let entry_dist = self.distance(query, &graph.vectors[entry as usize]);
candidates.push((entry, entry_dist));
visited.insert(entry);
let mut result: Vec<(u32, f32)> = Vec::new();
while let Some((current, current_dist)) = candidates.pop() {
if result.len() >= k && current_dist > result.last().unwrap().1 {
break;
}
if let Some(neighbors) = graph.adjacency.get(¤t) {
for &neighbor in neighbors {
if visited.contains(&neighbor) {
continue;
}
if !self.should_expand(current, neighbor) {
weak_expansions += 1;
if weak_expansions >= self.max_weak_expansions {
continue;
}
}
visited.insert(neighbor);
let dist = self.distance(query, &graph.vectors[neighbor as usize]);
candidates.push((neighbor, dist));
}
}
result.push((current, current_dist));
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
}
result.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
result.truncate(k);
Ok(result)
}
fn should_expand(&self, from: u32, to: u32) -> bool {
if self.watcher.current_mincut() > self.coherence_gate {
return true;
}
!self.watcher.is_cut_sensitive(from, to)
}
fn distance(&self, a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f32>()
.sqrt()
}
}
#[derive(Debug)]
pub struct HNSWGraph {
pub vectors: Vec<Vec<f32>>,
pub adjacency: HashMap<u32, Vec<u32>>,
pub entry_point: u32,
pub dimension: usize,
}
fn stoer_wagner_mincut(adj: &[Vec<f64>]) -> Result<f64, DynamicMinCutError> {
let n = adj.len();
if n < 2 {
return Err(DynamicMinCutError::EmptyGraph);
}
let mut adj = adj.to_vec();
let mut best_cut = f64::INFINITY;
let mut active: Vec<bool> = vec![true; n];
for phase in 0..(n - 1) {
let mut in_a = vec![false; n];
let mut key = vec![0.0; n];
let start = match (0..n).find(|&i| active[i]) {
Some(s) => s,
None => break,
};
in_a[start] = true;
for j in 0..n {
if active[j] && !in_a[j] {
key[j] = adj[start][j];
}
}
let mut t = start;
for _ in 1..=(n - 1 - phase) {
let (max_node, _) = (0..n)
.filter(|&j| active[j] && !in_a[j])
.map(|j| (j, key[j]))
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or((0, 0.0));
t = max_node;
in_a[t] = true;
for j in 0..n {
if active[j] && !in_a[j] {
key[j] += adj[t][j];
}
}
}
let cut_weight = key[t];
if cut_weight < best_cut {
best_cut = cut_weight;
}
active[t] = false;
for i in 0..n {
if active[i] && i != t {
let s = (0..n).filter(|&j| active[j] && in_a[j]).last().unwrap_or(start);
adj[s][i] += adj[t][i];
adj[i][s] += adj[i][t];
}
}
}
Ok(best_cut)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_euler_tour_tree_basic() {
let mut ett = EulerTourTree::new();
ett.add_vertex(0);
ett.add_vertex(1);
ett.add_vertex(2);
assert!(!ett.connected(0, 1));
assert!(!ett.connected(1, 2));
ett.link(0, 1).unwrap();
assert!(ett.connected(0, 1));
assert!(!ett.connected(0, 2));
ett.link(1, 2).unwrap();
assert!(ett.connected(0, 2));
assert_eq!(ett.component_size(0), 3);
assert_eq!(ett.component_size(1), 3);
}
#[test]
fn test_euler_tour_tree_cut() {
let mut ett = EulerTourTree::new();
ett.add_vertex(0);
ett.add_vertex(1);
ett.add_vertex(2);
ett.add_vertex(3);
ett.link(0, 1).unwrap();
ett.link(1, 2).unwrap();
ett.link(2, 3).unwrap();
assert!(ett.connected(0, 3));
ett.cut(1, 2).unwrap();
assert!(!ett.connected(0, 3));
assert!(ett.connected(0, 1));
assert!(ett.connected(2, 3));
}
#[test]
fn test_dynamic_cut_watcher_insert() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.insert_edge(1, 2, 1.0).unwrap();
watcher.insert_edge(2, 0, 1.0).unwrap();
let tree = watcher.euler_tree.read().unwrap();
assert!(tree.connected(0, 1));
assert!(tree.connected(1, 2));
assert!(tree.connected(0, 2));
}
#[test]
fn test_dynamic_cut_watcher_delete() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.insert_edge(1, 2, 1.0).unwrap();
{
let tree = watcher.euler_tree.read().unwrap();
assert!(tree.connected(0, 2));
}
watcher.delete_edge(1, 2).unwrap();
{
let tree = watcher.euler_tree.read().unwrap();
assert!(!tree.connected(0, 2));
assert!(tree.connected(0, 1));
}
}
#[test]
fn test_stoer_wagner_simple() {
let adj = vec![
vec![0.0, 1.0, 1.0],
vec![1.0, 0.0, 1.0],
vec![1.0, 1.0, 0.0],
];
let mincut = stoer_wagner_mincut(&adj).unwrap();
assert!((mincut - 2.0).abs() < 1e-6);
}
#[test]
fn test_stoer_wagner_weighted() {
let adj = vec![
vec![0.0, 5.0, 0.0, 1.0],
vec![5.0, 0.0, 5.0, 0.0],
vec![0.0, 5.0, 0.0, 5.0],
vec![1.0, 0.0, 5.0, 0.0],
];
let mincut = stoer_wagner_mincut(&adj).unwrap();
assert!((mincut - 6.0).abs() < 1e-6); }
#[test]
fn test_local_mincut_ball_growing() {
let mut adjacency = HashMap::new();
adjacency.insert(0, vec![(1, 1.0), (2, 1.0)]);
adjacency.insert(1, vec![(0, 1.0), (2, 1.0), (3, 1.0)]);
adjacency.insert(2, vec![(0, 1.0), (1, 1.0)]);
adjacency.insert(3, vec![(1, 1.0), (4, 1.0)]);
adjacency.insert(4, vec![(3, 1.0)]);
let procedure = LocalMinCutProcedure::new(2, 0.3);
let ball = procedure.grow_ball(&adjacency, 0, 2);
assert!(ball.contains(&0));
assert!(ball.contains(&1));
assert!(ball.contains(&2));
assert!(ball.contains(&3)); }
#[test]
fn test_local_mincut_weak_region() {
let mut adjacency = HashMap::new();
adjacency.insert(0, vec![(1, 1.0), (2, 1.0), (3, 1.0), (4, 1.0)]);
adjacency.insert(1, vec![(0, 1.0)]);
adjacency.insert(2, vec![(0, 1.0)]);
adjacency.insert(3, vec![(0, 1.0)]);
adjacency.insert(4, vec![(0, 1.0)]);
let procedure = LocalMinCutProcedure::new(1, 0.5);
assert!(!procedure.in_weak_region(&adjacency, 0));
assert!(procedure.in_weak_region(&adjacency, 1));
}
#[test]
fn test_local_cut_computation() {
let mut adjacency = HashMap::new();
adjacency.insert(0, vec![(1, 2.0), (2, 1.0)]);
adjacency.insert(1, vec![(0, 2.0), (2, 2.0), (3, 1.0)]);
adjacency.insert(2, vec![(0, 1.0), (1, 2.0), (3, 2.0)]);
adjacency.insert(3, vec![(1, 1.0), (2, 2.0)]);
let procedure = LocalMinCutProcedure::new(3, 0.3);
let cut = procedure.local_cut(&adjacency, 0, 3);
assert!(cut.is_some());
let cut = cut.unwrap();
assert!(cut.cut_value > 0.0);
assert!(!cut.partition.is_empty());
}
#[test]
fn test_cut_watcher_is_sensitive() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.insert_edge(1, 2, 0.1).unwrap();
assert!(watcher.is_cut_sensitive(1, 2));
}
#[test]
fn test_cut_watcher_stats() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.insert_edge(1, 2, 1.0).unwrap();
let stats = watcher.stats();
assert_eq!(stats.pending_updates, 2);
assert!(stats.et_tree_size > 0);
}
#[test]
fn test_cut_watcher_process_updates() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.insert_edge(1, 2, 1.0).unwrap();
watcher.insert_edge(2, 3, 1.0).unwrap();
let processed = watcher.process_updates().unwrap();
assert_eq!(processed, 3);
let processed = watcher.process_updates().unwrap();
assert_eq!(processed, 0);
}
#[test]
fn test_cut_watcher_recompute() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
let adj = vec![
vec![0.0, 1.0, 1.0, 0.0],
vec![1.0, 0.0, 1.0, 1.0],
vec![1.0, 1.0, 0.0, 1.0],
vec![0.0, 1.0, 1.0, 0.0],
];
let mincut = watcher.recompute_exact(&adj).unwrap();
assert!(mincut > 0.0);
assert_eq!(watcher.current_mincut(), mincut);
}
#[test]
fn test_cut_gated_search_basic() {
let config = CutWatcherConfig::default();
let watcher = DynamicCutWatcher::new(config);
let search = CutGatedSearch::new(&watcher, 1.0, 5);
let graph = HNSWGraph {
vectors: vec![
vec![1.0, 0.0, 0.0],
vec![0.9, 0.1, 0.0],
vec![0.0, 1.0, 0.0],
vec![0.0, 0.0, 1.0],
],
adjacency: {
let mut adj = HashMap::new();
adj.insert(0, vec![1, 2]);
adj.insert(1, vec![0, 2]);
adj.insert(2, vec![0, 1, 3]);
adj.insert(3, vec![2]);
adj
},
entry_point: 0,
dimension: 3,
};
let query = vec![1.0, 0.0, 0.0];
let results = search.search(&query, 2, &graph).unwrap();
assert!(!results.is_empty());
assert!(results.len() <= 2);
}
#[test]
fn test_edge_update_serialization() {
let update = EdgeUpdate {
update_type: EdgeUpdateType::Insert,
source: 0,
target: 1,
weight: 1.5,
timestamp: Utc::now(),
};
let json = serde_json::to_string(&update).unwrap();
let parsed: EdgeUpdate = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.source, 0);
assert_eq!(parsed.target, 1);
assert!((parsed.weight - 1.5).abs() < 1e-6);
}
#[test]
fn test_config_defaults() {
let config = CutWatcherConfig::default();
assert_eq!(config.lambda_bound, 100);
assert!(config.use_local_heuristics);
assert!(config.update_interval_ms > 0);
}
#[test]
fn test_ett_multiple_components() {
let mut ett = EulerTourTree::new();
ett.link(0, 1).unwrap();
ett.link(1, 2).unwrap();
ett.link(3, 4).unwrap();
assert!(ett.connected(0, 2));
assert!(ett.connected(3, 4));
assert!(!ett.connected(0, 3));
assert_eq!(ett.component_size(0), 3);
assert_eq!(ett.component_size(3), 2);
}
#[test]
fn test_ett_cycle() {
let mut ett = EulerTourTree::new();
ett.link(0, 1).unwrap();
ett.link(1, 2).unwrap();
ett.link(2, 3).unwrap();
ett.link(3, 0).unwrap();
assert!(ett.connected(0, 2));
assert!(ett.connected(1, 3));
ett.cut(0, 1).unwrap();
assert!(ett.connected(0, 3)); assert!(ett.connected(0, 2)); }
#[test]
fn test_conductance_calculation() {
let mut adjacency = HashMap::new();
adjacency.insert(0, vec![(1, 1.0), (2, 1.0)]);
adjacency.insert(1, vec![(0, 1.0), (2, 1.0)]);
adjacency.insert(2, vec![(0, 1.0), (1, 1.0), (3, 0.5)]);
adjacency.insert(3, vec![(2, 0.5)]);
let procedure = LocalMinCutProcedure::new(2, 0.3);
let nodes = vec![0, 1, 2];
let conductance = procedure.compute_conductance(&adjacency, &nodes);
assert!(conductance > 0.0 && conductance < 1.0);
}
#[test]
fn test_cut_value_computation() {
let mut adjacency = HashMap::new();
adjacency.insert(0, vec![(1, 2.0), (2, 1.0)]);
adjacency.insert(1, vec![(0, 2.0), (2, 3.0)]);
adjacency.insert(2, vec![(0, 1.0), (1, 3.0)]);
let procedure = LocalMinCutProcedure::new(2, 0.3);
let ball = vec![0, 1, 2];
let mut set_s = HashSet::new();
set_s.insert(0);
let cut_value = procedure.compute_cut_value(&adjacency, &set_s, &ball);
assert!((cut_value - 3.0).abs() < 1e-6);
}
#[test]
fn test_watcher_cut_changed_flag() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
assert!(!watcher.cut_changed());
watcher.insert_edge(0, 1, 1.0).unwrap();
watcher.delete_edge(0, 1).unwrap();
assert!(watcher.cut_changed());
assert!(!watcher.cut_changed());
}
}
#[cfg(test)]
mod benchmarks {
use super::*;
use std::time::Instant;
#[test]
fn bench_euler_tour_tree_operations() {
let mut ett = EulerTourTree::new();
let n = 1000;
for i in 0..n {
ett.add_vertex(i);
}
let start = Instant::now();
for i in 0..n-1 {
ett.link(i, i + 1).unwrap();
}
let link_time = start.elapsed();
println!("ETT Link {} edges: {:?} ({:.2} µs/op)",
n-1, link_time, link_time.as_micros() as f64 / (n-1) as f64);
let start = Instant::now();
let queries = 100;
for i in 0..queries {
ett.connected(i % n, (i * 7) % n);
}
let query_time = start.elapsed();
println!("ETT Connectivity {} queries: {:?} ({:.2} µs/op)",
queries, query_time, query_time.as_micros() as f64 / queries as f64);
let start = Instant::now();
for i in 0..10 {
ett.cut(i * 10, i * 10 + 1).ok();
}
let cut_time = start.elapsed();
println!("ETT Cut 10 edges: {:?} ({:.2} µs/op)",
cut_time, cut_time.as_micros() as f64 / 10.0);
}
#[test]
fn bench_dynamic_watcher_updates() {
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
let n = 500;
let start = Instant::now();
for i in 0..n-1 {
watcher.insert_edge(i, i + 1, 1.0).unwrap();
}
let insert_time = start.elapsed();
println!("Dynamic Watcher Insert {} edges: {:?} ({:.2} µs/op)",
n-1, insert_time, insert_time.as_micros() as f64 / (n-1) as f64);
let start = Instant::now();
for i in 0..10 {
watcher.delete_edge(i * 10, i * 10 + 1).ok();
}
let delete_time = start.elapsed();
println!("Dynamic Watcher Delete 10 edges: {:?} ({:.2} µs/op)",
delete_time, delete_time.as_micros() as f64 / 10.0);
}
#[test]
fn bench_stoer_wagner_comparison() {
let n = 50;
let mut adj = vec![vec![0.0; n]; n];
for i in 0..n {
for j in i+1..n {
if (i * 7 + j * 13) % 3 == 0 {
let weight = ((i + j) % 10 + 1) as f64;
adj[i][j] = weight;
adj[j][i] = weight;
}
}
}
let start = Instant::now();
for _ in 0..10 {
stoer_wagner_mincut(&adj).unwrap();
}
let periodic_time = start.elapsed();
println!("Periodic (10 full computations): {:?}", periodic_time);
let config = CutWatcherConfig::default();
let mut watcher = DynamicCutWatcher::new(config);
let start = Instant::now();
for i in 0..n {
for j in i+1..n {
if adj[i][j] > 0.0 {
watcher.insert_edge(i as u32, j as u32, adj[i][j]).unwrap();
}
}
}
for i in 0..10 {
let u = (i * 3) % n;
let v = (i * 7 + 1) % n;
if u != v {
watcher.insert_edge(u as u32, v as u32, 1.0).ok();
}
}
let dynamic_time = start.elapsed();
println!("Dynamic (build + 10 updates): {:?}", dynamic_time);
println!("Speedup: {:.2}x", periodic_time.as_secs_f64() / dynamic_time.as_secs_f64());
}
#[test]
fn bench_local_mincut_procedure() {
let mut adjacency = HashMap::new();
let n = 100;
for i in 0..n {
let mut neighbors = Vec::new();
for j in 1..=5 {
let target = (i + j) % n;
neighbors.push((target, 1.0));
}
adjacency.insert(i, neighbors);
}
let procedure = LocalMinCutProcedure::new(3, 0.3);
let start = Instant::now();
let iterations = 20;
for i in 0..iterations {
procedure.local_cut(&adjacency, i % n, 5);
}
let time = start.elapsed();
println!("Local MinCut {} iterations: {:?} ({:.2} ms/op)",
iterations, time, time.as_millis() as f64 / iterations as f64);
}
}