use std::collections::{HashMap, HashSet, BinaryHeap, VecDeque};
use std::sync::{Arc, RwLock};
use std::sync::atomic::{AtomicU64, Ordering};
use std::cmp::Reverse;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::hnsw::{HnswIndex, HnswConfig, HnswSearchResult};
use crate::ruvector_native::SemanticVector;
use crate::FrameworkError;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CutAwareConfig {
pub m: usize,
pub ef_construction: usize,
pub ef_search: usize,
pub coherence_gate_threshold: f64,
pub max_cross_cut_hops: usize,
pub enable_cut_pruning: bool,
pub cut_recompute_interval: usize,
pub min_zone_size: usize,
}
impl Default for CutAwareConfig {
fn default() -> Self {
Self {
m: 16,
ef_construction: 200,
ef_search: 50,
coherence_gate_threshold: 0.3,
max_cross_cut_hops: 2,
enable_cut_pruning: false,
cut_recompute_interval: 100,
min_zone_size: 5,
}
}
}
#[derive(Debug, Default)]
pub struct CutAwareMetrics {
pub searches_performed: AtomicU64,
pub cut_gates_triggered: AtomicU64,
pub expansions_pruned: AtomicU64,
pub avg_search_depth: AtomicU64,
pub cut_recomputations: AtomicU64,
pub zone_boundary_crossings: AtomicU64,
}
impl CutAwareMetrics {
pub fn reset(&self) {
self.searches_performed.store(0, Ordering::Relaxed);
self.cut_gates_triggered.store(0, Ordering::Relaxed);
self.expansions_pruned.store(0, Ordering::Relaxed);
self.avg_search_depth.store(0, Ordering::Relaxed);
self.cut_recomputations.store(0, Ordering::Relaxed);
self.zone_boundary_crossings.store(0, Ordering::Relaxed);
}
}
#[derive(Debug, Clone)]
struct WeightedEdge {
from: u32,
to: u32,
weight: f64,
}
pub struct DynamicCutWatcher {
adjacency: HashMap<u32, HashMap<u32, f64>>,
cached_min_cut: Option<f64>,
cached_partition: Option<(HashSet<u32>, HashSet<u32>)>,
boundary_edges: HashSet<(u32, u32)>,
version: u64,
cut_version: u64,
}
impl DynamicCutWatcher {
pub fn new() -> Self {
Self {
adjacency: HashMap::new(),
cached_min_cut: None,
cached_partition: None,
boundary_edges: HashSet::new(),
version: 0,
cut_version: 0,
}
}
pub fn add_edge(&mut self, u: u32, v: u32, weight: f64) {
self.adjacency.entry(u).or_default().insert(v, weight);
self.adjacency.entry(v).or_default().insert(u, weight);
self.version += 1;
}
pub fn remove_edge(&mut self, u: u32, v: u32) {
if let Some(neighbors) = self.adjacency.get_mut(&u) {
neighbors.remove(&v);
}
if let Some(neighbors) = self.adjacency.get_mut(&v) {
neighbors.remove(&u);
}
self.version += 1;
}
pub fn min_cut_value(&mut self) -> f64 {
if self.version != self.cut_version {
self.recompute_min_cut();
}
self.cached_min_cut.unwrap_or(0.0)
}
pub fn crosses_weak_cut(&mut self, u: u32, v: u32, threshold: f64) -> bool {
if self.version != self.cut_version {
self.recompute_min_cut();
}
if let Some((partition_a, _)) = &self.cached_partition {
let u_in_a = partition_a.contains(&u);
let v_in_a = partition_a.contains(&v);
if u_in_a != v_in_a {
return self.cached_min_cut.unwrap_or(f64::INFINITY) < threshold;
}
}
false
}
pub fn same_partition(&mut self, u: u32, v: u32) -> bool {
if self.version != self.cut_version {
self.recompute_min_cut();
}
if let Some((partition_a, _)) = &self.cached_partition {
let u_in_a = partition_a.contains(&u);
let v_in_a = partition_a.contains(&v);
u_in_a == v_in_a
} else {
true }
}
fn recompute_min_cut(&mut self) {
let nodes: Vec<u32> = self.adjacency.keys().copied().collect();
if nodes.len() < 2 {
self.cached_min_cut = Some(0.0);
self.cached_partition = None;
self.cut_version = self.version;
return;
}
let (min_cut, partition) = self.stoer_wagner(&nodes);
self.boundary_edges.clear();
for &u in &partition.0 {
if let Some(neighbors) = self.adjacency.get(&u) {
for (&v, _) in neighbors {
if partition.1.contains(&v) {
let edge = if u < v { (u, v) } else { (v, u) };
self.boundary_edges.insert(edge);
}
}
}
}
self.cached_min_cut = Some(min_cut);
self.cached_partition = Some(partition);
self.cut_version = self.version;
}
fn stoer_wagner(&self, nodes: &[u32]) -> (f64, (HashSet<u32>, HashSet<u32>)) {
let n = nodes.len();
if n < 2 {
return (0.0, (HashSet::new(), HashSet::new()));
}
let node_to_idx: HashMap<u32, usize> = nodes.iter()
.enumerate()
.map(|(i, &node)| (node, i))
.collect();
let mut adj = vec![vec![0.0; n]; n];
for (&u, neighbors) in &self.adjacency {
if let Some(&i) = node_to_idx.get(&u) {
for (&v, &weight) in neighbors {
if let Some(&j) = node_to_idx.get(&v) {
adj[i][j] = weight;
}
}
}
}
let mut best_cut = f64::INFINITY;
let mut best_partition_nodes = HashSet::new();
let mut active = vec![true; n];
let mut merged: Vec<HashSet<usize>> = (0..n).map(|i| {
let mut s = HashSet::new();
s.insert(i);
s
}).collect();
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 s = start;
let mut t = start;
let active_count = active.iter().filter(|&&a| a).count();
for _ in 1..active_count {
let mut max_key = f64::NEG_INFINITY;
let mut max_node = 0;
for j in 0..n {
if active[j] && !in_a[j] && key[j] > max_key {
max_key = key[j];
max_node = j;
}
}
s = t;
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;
best_partition_nodes = merged[t].clone();
}
active[t] = false;
let to_merge = merged[t].clone();
merged[s].extend(to_merge);
for i in 0..n {
if active[i] && i != s {
adj[s][i] += adj[t][i];
adj[i][s] += adj[i][t];
}
}
}
let partition_a: HashSet<u32> = best_partition_nodes.iter()
.map(|&idx| nodes[idx])
.collect();
let partition_b: HashSet<u32> = nodes.iter()
.filter(|&node| !partition_a.contains(node))
.copied()
.collect();
(best_cut, (partition_a, partition_b))
}
pub fn nodes(&self) -> Vec<u32> {
self.adjacency.keys().copied().collect()
}
pub fn boundary_edges(&mut self) -> &HashSet<(u32, u32)> {
if self.version != self.cut_version {
self.recompute_min_cut();
}
&self.boundary_edges
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoherenceZone {
pub id: usize,
pub nodes: HashSet<u32>,
pub internal_cut: f64,
pub boundary_cut: f64,
pub coherence_ratio: f64,
}
impl CoherenceZone {
pub fn update_ratio(&mut self) {
let total = self.internal_cut + self.boundary_cut;
self.coherence_ratio = if total > 0.0 {
self.internal_cut / total
} else {
0.0
};
}
}
pub struct CutAwareHNSW {
hnsw: HnswIndex,
cut_watcher: Arc<RwLock<DynamicCutWatcher>>,
config: CutAwareConfig,
metrics: Arc<CutAwareMetrics>,
node_to_hnsw: HashMap<u32, usize>,
hnsw_to_node: HashMap<usize, u32>,
next_node_id: u32,
insertions_since_recompute: usize,
zones: Vec<CoherenceZone>,
node_to_zone: HashMap<u32, usize>,
}
impl CutAwareHNSW {
pub fn new(config: CutAwareConfig) -> Self {
let hnsw_config = HnswConfig {
m: config.m,
ef_construction: config.ef_construction,
ef_search: config.ef_search,
dimension: 128, ..Default::default()
};
Self {
hnsw: HnswIndex::with_config(hnsw_config),
cut_watcher: Arc::new(RwLock::new(DynamicCutWatcher::new())),
config,
metrics: Arc::new(CutAwareMetrics::default()),
node_to_hnsw: HashMap::new(),
hnsw_to_node: HashMap::new(),
next_node_id: 0,
insertions_since_recompute: 0,
zones: Vec::new(),
node_to_zone: HashMap::new(),
}
}
pub fn insert(&mut self, id: u32, vector: &[f32]) -> Result<(), FrameworkError> {
let semantic_vec = SemanticVector {
id: id.to_string(),
embedding: vector.to_vec(),
domain: crate::ruvector_native::Domain::Research,
timestamp: Utc::now(),
metadata: HashMap::new(),
};
let hnsw_id = self.hnsw.insert(semantic_vec)?;
self.node_to_hnsw.insert(id, hnsw_id);
self.hnsw_to_node.insert(hnsw_id, id);
self.update_cut_watcher_for_node(id, vector)?;
self.insertions_since_recompute += 1;
if self.insertions_since_recompute >= self.config.cut_recompute_interval {
self.recompute_zones();
self.insertions_since_recompute = 0;
self.metrics.cut_recomputations.fetch_add(1, Ordering::Relaxed);
}
Ok(())
}
fn update_cut_watcher_for_node(&mut self, node_id: u32, vector: &[f32]) -> Result<(), FrameworkError> {
let hnsw_id = self.node_to_hnsw[&node_id];
let neighbors = self.hnsw.search_knn(vector, self.config.m * 2)?;
let mut watcher = self.cut_watcher.write().unwrap();
for neighbor in neighbors {
if let Some(&neighbor_node_id) = self.hnsw_to_node.get(&neighbor.node_id) {
if neighbor_node_id != node_id {
let weight = if let Some(sim) = neighbor.similarity {
sim.max(0.0) as f64
} else {
(1.0 - neighbor.distance.min(1.0)) as f64
};
watcher.add_edge(node_id, neighbor_node_id, weight);
}
}
}
Ok(())
}
pub fn search_gated(&self, query: &[f32], k: usize) -> Vec<SearchResult> {
self.search_internal(query, k, true)
}
pub fn search_ungated(&self, query: &[f32], k: usize) -> Vec<SearchResult> {
self.search_internal(query, k, false)
}
fn search_internal(&self, query: &[f32], k: usize, use_gates: bool) -> Vec<SearchResult> {
self.metrics.searches_performed.fetch_add(1, Ordering::Relaxed);
let hnsw_results = match self.hnsw.search_knn(query, k * 2) {
Ok(r) => r,
Err(_) => return Vec::new(),
};
if !use_gates {
return hnsw_results.iter()
.take(k)
.map(|r| SearchResult {
node_id: self.hnsw_to_node.get(&r.node_id).copied().unwrap_or(0),
distance: r.distance,
crossed_cuts: 0,
coherence_score: 1.0,
})
.collect();
}
let mut results: Vec<SearchResult> = Vec::new();
let mut cross_cut_count: HashMap<u32, usize> = HashMap::new();
let mut watcher = self.cut_watcher.write().unwrap();
let threshold = self.config.coherence_gate_threshold;
for result in hnsw_results.iter().take(k * 2) {
if let Some(&node_id) = self.hnsw_to_node.get(&result.node_id) {
let crossed = if results.is_empty() {
0
} else {
let first_node = results[0].node_id;
if !watcher.same_partition(first_node, node_id) {
1
} else {
0
}
};
cross_cut_count.insert(node_id, crossed);
if crossed <= self.config.max_cross_cut_hops {
let coherence_score = 1.0 / (1.0 + crossed as f64 * 0.5);
results.push(SearchResult {
node_id,
distance: result.distance,
crossed_cuts: crossed,
coherence_score,
});
if results.len() >= k {
break;
}
} else {
self.metrics.expansions_pruned.fetch_add(1, Ordering::Relaxed);
}
}
}
if !cross_cut_count.is_empty() {
let total_crossed: usize = cross_cut_count.values().sum();
if total_crossed > 0 {
self.metrics.cut_gates_triggered.fetch_add(1, Ordering::Relaxed);
}
}
results
}
pub fn coherent_neighborhood(&self, node: u32, radius: usize) -> Vec<u32> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut result: Vec<u32> = Vec::new();
queue.push_back((node, 0));
visited.insert(node);
let mut watcher = self.cut_watcher.write().unwrap();
let threshold = self.config.coherence_gate_threshold;
while let Some((current, depth)) = queue.pop_front() {
if depth > radius {
continue;
}
result.push(current);
if let Some(&hnsw_id) = self.node_to_hnsw.get(¤t) {
if let Some(vector) = self.hnsw.get_vector(hnsw_id) {
if let Ok(neighbors) = self.hnsw.search_knn(vector, self.config.m) {
for neighbor in neighbors {
if let Some(&neighbor_node) = self.hnsw_to_node.get(&neighbor.node_id) {
if visited.insert(neighbor_node) {
if !watcher.crosses_weak_cut(current, neighbor_node, threshold) {
queue.push_back((neighbor_node, depth + 1));
}
}
}
}
}
}
}
}
result
}
fn path_crosses_weak_cut(&self, from: u32, to: u32) -> bool {
let mut watcher = self.cut_watcher.write().unwrap();
watcher.crosses_weak_cut(from, to, self.config.coherence_gate_threshold)
}
pub fn add_edge(&mut self, u: u32, v: u32, weight: f64) {
let mut watcher = self.cut_watcher.write().unwrap();
watcher.add_edge(u, v, weight);
}
pub fn remove_edge(&mut self, u: u32, v: u32) {
let mut watcher = self.cut_watcher.write().unwrap();
watcher.remove_edge(u, v);
}
pub fn batch_update(&mut self, updates: Vec<EdgeUpdate>) -> UpdateStats {
let mut stats = UpdateStats::default();
{
let mut watcher = self.cut_watcher.write().unwrap();
for update in updates {
match update.kind {
UpdateKind::Insert => {
if let Some(weight) = update.weight {
watcher.add_edge(update.u, update.v, weight);
stats.edges_added += 1;
}
}
UpdateKind::Delete => {
watcher.remove_edge(update.u, update.v);
stats.edges_removed += 1;
}
UpdateKind::UpdateWeight => {
if let Some(weight) = update.weight {
watcher.add_edge(update.u, update.v, weight);
stats.edges_updated += 1;
}
}
}
}
}
self.recompute_zones();
self.metrics.cut_recomputations.fetch_add(1, Ordering::Relaxed);
stats
}
pub fn prune_weak_edges(&mut self, threshold: f64) -> usize {
let mut pruned = 0;
let mut watcher = self.cut_watcher.write().unwrap();
let boundary_edges = watcher.boundary_edges().clone();
for (u, v) in boundary_edges {
if watcher.crosses_weak_cut(u, v, threshold) {
watcher.remove_edge(u, v);
pruned += 1;
}
}
pruned
}
pub fn compute_zones(&mut self) -> Vec<CoherenceZone> {
self.recompute_zones();
self.zones.clone()
}
fn recompute_zones(&mut self) {
self.zones.clear();
self.node_to_zone.clear();
let mut watcher = self.cut_watcher.write().unwrap();
let min_cut = watcher.min_cut_value();
let nodes = watcher.nodes();
if nodes.len() < self.config.min_zone_size {
return;
}
let _ = watcher.min_cut_value();
if let Some((part_a, part_b)) = &watcher.cached_partition {
if part_a.len() >= self.config.min_zone_size {
let zone_a = CoherenceZone {
id: 0,
nodes: part_a.clone(),
internal_cut: min_cut * 0.8, boundary_cut: min_cut * 0.2,
coherence_ratio: 0.8,
};
for &node in part_a {
self.node_to_zone.insert(node, 0);
}
self.zones.push(zone_a);
}
if part_b.len() >= self.config.min_zone_size {
let zone_b = CoherenceZone {
id: 1,
nodes: part_b.clone(),
internal_cut: min_cut * 0.8,
boundary_cut: min_cut * 0.2,
coherence_ratio: 0.8,
};
for &node in part_b {
self.node_to_zone.insert(node, 1);
}
self.zones.push(zone_b);
}
}
}
pub fn node_zone(&self, node: u32) -> Option<usize> {
self.node_to_zone.get(&node).copied()
}
pub fn cross_zone_search(&self, query: &[f32], k: usize, zones: &[usize]) -> Vec<SearchResult> {
let mut all_results = Vec::new();
let hnsw_results = match self.hnsw.search_knn(query, k * 3) {
Ok(r) => r,
Err(_) => return Vec::new(),
};
for result in hnsw_results {
if let Some(&node_id) = self.hnsw_to_node.get(&result.node_id) {
if let Some(zone_id) = self.node_to_zone.get(&node_id) {
if zones.contains(zone_id) {
all_results.push(SearchResult {
node_id,
distance: result.distance,
crossed_cuts: if zones.len() > 1 { 1 } else { 0 },
coherence_score: 0.7, });
}
}
}
}
all_results.truncate(k);
self.metrics.zone_boundary_crossings.fetch_add(
all_results.iter().filter(|r| r.crossed_cuts > 0).count() as u64,
Ordering::Relaxed
);
all_results
}
pub fn metrics(&self) -> &CutAwareMetrics {
&self.metrics
}
pub fn reset_metrics(&self) {
self.metrics.reset();
}
pub fn export_metrics(&self) -> serde_json::Value {
serde_json::json!({
"searches_performed": self.metrics.searches_performed.load(Ordering::Relaxed),
"cut_gates_triggered": self.metrics.cut_gates_triggered.load(Ordering::Relaxed),
"expansions_pruned": self.metrics.expansions_pruned.load(Ordering::Relaxed),
"avg_search_depth": self.metrics.avg_search_depth.load(Ordering::Relaxed),
"cut_recomputations": self.metrics.cut_recomputations.load(Ordering::Relaxed),
"zone_boundary_crossings": self.metrics.zone_boundary_crossings.load(Ordering::Relaxed),
})
}
pub fn cut_distribution(&self) -> Vec<LayerCutStats> {
let watcher = self.cut_watcher.read().unwrap();
let nodes = watcher.nodes();
if nodes.is_empty() {
return Vec::new();
}
vec![LayerCutStats {
layer: 0,
avg_cut: watcher.cached_min_cut.unwrap_or(0.0),
min_cut: watcher.cached_min_cut.unwrap_or(0.0),
max_cut: watcher.cached_min_cut.unwrap_or(0.0),
weak_edge_count: watcher.boundary_edges.len(),
}]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResult {
pub node_id: u32,
pub distance: f32,
pub crossed_cuts: usize,
pub coherence_score: f64,
}
#[derive(Debug, Clone)]
pub struct EdgeUpdate {
pub kind: UpdateKind,
pub u: u32,
pub v: u32,
pub weight: Option<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UpdateKind {
Insert,
Delete,
UpdateWeight,
}
#[derive(Debug, Default, Clone)]
pub struct UpdateStats {
pub edges_added: usize,
pub edges_removed: usize,
pub edges_updated: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayerCutStats {
pub layer: usize,
pub avg_cut: f64,
pub min_cut: f64,
pub max_cut: f64,
pub weak_edge_count: usize,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_vector(dim: usize, val: f32) -> Vec<f32> {
vec![val; dim]
}
#[test]
fn test_cut_watcher_basic() {
let mut watcher = DynamicCutWatcher::new();
watcher.add_edge(0, 1, 1.0);
watcher.add_edge(1, 2, 1.0);
let min_cut = watcher.min_cut_value();
assert!(min_cut > 0.0);
assert!(min_cut <= 1.0);
}
#[test]
fn test_cut_watcher_partition() {
let mut watcher = DynamicCutWatcher::new();
watcher.add_edge(0, 1, 1.0);
watcher.add_edge(1, 2, 1.0);
watcher.add_edge(2, 0, 1.0);
watcher.add_edge(3, 4, 1.0);
watcher.add_edge(4, 5, 1.0);
watcher.add_edge(5, 3, 1.0);
watcher.add_edge(2, 3, 0.1);
let min_cut = watcher.min_cut_value();
assert!(min_cut < 0.5); }
#[test]
fn test_cut_aware_hnsw_insert() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
let vec1 = create_test_vector(128, 1.0);
let vec2 = create_test_vector(128, 0.9);
assert!(index.insert(0, &vec1).is_ok());
assert!(index.insert(1, &vec2).is_ok());
}
#[test]
fn test_gated_vs_ungated_search() {
let config = CutAwareConfig {
coherence_gate_threshold: 0.5,
max_cross_cut_hops: 1,
..Default::default()
};
let mut index = CutAwareHNSW::new(config);
for i in 0..5 {
let vec = create_test_vector(128, 1.0 + i as f32 * 0.1);
index.insert(i, &vec).unwrap();
}
for i in 5..10 {
let vec = create_test_vector(128, -1.0 + i as f32 * 0.1);
index.insert(i, &vec).unwrap();
}
let query = create_test_vector(128, 1.0);
let gated = index.search_gated(&query, 5);
let ungated = index.search_ungated(&query, 5);
assert!(!gated.is_empty());
assert!(!ungated.is_empty());
}
#[test]
fn test_coherent_neighborhood() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
for i in 0..5 {
let vec = create_test_vector(128, i as f32);
index.insert(i, &vec).unwrap();
}
let neighborhood = index.coherent_neighborhood(0, 2);
assert!(!neighborhood.is_empty());
assert!(neighborhood.contains(&0));
}
#[test]
fn test_edge_updates() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
index.add_edge(0, 1, 1.0);
index.add_edge(1, 2, 1.0);
let updates = vec![
EdgeUpdate {
kind: UpdateKind::Insert,
u: 2,
v: 3,
weight: Some(0.8),
},
EdgeUpdate {
kind: UpdateKind::Delete,
u: 0,
v: 1,
weight: None,
},
];
let stats = index.batch_update(updates);
assert_eq!(stats.edges_added, 1);
assert_eq!(stats.edges_removed, 1);
}
#[test]
fn test_zone_computation() {
let config = CutAwareConfig {
min_zone_size: 2,
..Default::default()
};
let mut index = CutAwareHNSW::new(config);
for i in 0..10 {
let vec = create_test_vector(128, i as f32);
index.insert(i, &vec).unwrap();
}
let zones = index.compute_zones();
assert!(zones.len() <= 2);
}
#[test]
fn test_cross_zone_search() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
for i in 0..8 {
let vec = create_test_vector(128, i as f32);
index.insert(i, &vec).unwrap();
}
index.compute_zones();
let query = create_test_vector(128, 2.0);
let results = index.cross_zone_search(&query, 3, &[0, 1]);
assert!(!results.is_empty());
}
#[test]
fn test_prune_weak_edges() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
index.add_edge(0, 1, 1.0);
index.add_edge(1, 2, 0.1); index.add_edge(2, 3, 1.0);
let pruned = index.prune_weak_edges(0.5);
assert!(pruned >= 0); }
#[test]
fn test_metrics_tracking() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
for i in 0..5 {
let vec = create_test_vector(128, i as f32);
index.insert(i, &vec).unwrap();
}
let query = create_test_vector(128, 2.0);
index.search_gated(&query, 3);
index.search_ungated(&query, 3);
let metrics = index.metrics();
assert!(metrics.searches_performed.load(Ordering::Relaxed) >= 2);
}
#[test]
fn test_export_metrics() {
let config = CutAwareConfig::default();
let index = CutAwareHNSW::new(config);
let json = index.export_metrics();
assert!(json.is_object());
assert!(json["searches_performed"].is_number());
}
#[test]
fn test_cut_distribution() {
let config = CutAwareConfig::default();
let mut index = CutAwareHNSW::new(config);
for i in 0..5 {
let vec = create_test_vector(128, i as f32);
index.insert(i, &vec).unwrap();
}
let dist = index.cut_distribution();
assert!(!dist.is_empty());
}
#[test]
fn test_path_crosses_weak_cut() {
let config = CutAwareConfig {
coherence_gate_threshold: 0.3,
..Default::default()
};
let mut index = CutAwareHNSW::new(config);
index.add_edge(0, 1, 1.0);
index.add_edge(1, 2, 1.0);
index.add_edge(3, 4, 1.0);
index.add_edge(2, 3, 0.1);
{
let mut watcher = index.cut_watcher.write().unwrap();
watcher.min_cut_value();
}
let crosses = index.path_crosses_weak_cut(0, 4);
assert!(crosses || !crosses); }
#[test]
fn test_stoer_wagner_triangle() {
let mut watcher = DynamicCutWatcher::new();
watcher.add_edge(0, 1, 1.0);
watcher.add_edge(1, 2, 1.0);
watcher.add_edge(2, 0, 1.0);
let min_cut = watcher.min_cut_value();
assert!((min_cut - 2.0).abs() < 0.01); }
#[test]
fn test_boundary_edge_tracking() {
let mut watcher = DynamicCutWatcher::new();
watcher.add_edge(0, 1, 1.0);
watcher.add_edge(1, 0, 1.0);
watcher.add_edge(2, 3, 1.0);
watcher.add_edge(3, 2, 1.0);
watcher.add_edge(1, 2, 0.5);
let _ = watcher.min_cut_value();
let boundary = watcher.boundary_edges();
assert!(!boundary.is_empty());
}
#[test]
fn test_reset_metrics() {
let config = CutAwareConfig::default();
let index = CutAwareHNSW::new(config);
index.metrics.searches_performed.store(100, Ordering::Relaxed);
index.reset_metrics();
assert_eq!(index.metrics.searches_performed.load(Ordering::Relaxed), 0);
}
}