use crate::connectivity::DynamicConnectivity;
use crate::graph::{DynamicGraph, EdgeId, VertexId};
use crate::instance::{
BoundedInstance, InstanceResult, ProperCutInstance, StubInstance, WitnessHandle,
};
use std::sync::Arc;
#[cfg(feature = "agentic")]
use crate::compact::{CompactCoreState, CompactEdge};
#[cfg(feature = "agentic")]
use crate::parallel::{
CoreDistributor, CoreExecutor, CoreStrategy, ResultAggregator, SharedCoordinator, NUM_CORES,
};
const RANGE_FACTOR: f64 = 1.2;
const MAX_INSTANCES: usize = 100;
#[derive(Debug, Clone)]
pub enum MinCutResult {
Disconnected,
Value {
cut_value: u64,
witness: WitnessHandle,
},
}
impl MinCutResult {
pub fn value(&self) -> u64 {
match self {
Self::Disconnected => 0,
Self::Value { cut_value, .. } => *cut_value,
}
}
pub fn is_connected(&self) -> bool {
!matches!(self, Self::Disconnected)
}
pub fn witness(&self) -> Option<&WitnessHandle> {
match self {
Self::Disconnected => None,
Self::Value { witness, .. } => Some(witness),
}
}
}
#[derive(Debug, Clone, Copy)]
struct Update {
time: u64,
edge_id: EdgeId,
u: VertexId,
v: VertexId,
}
pub struct MinCutWrapper {
conn_ds: DynamicConnectivity,
instances: Vec<Option<Box<dyn ProperCutInstance>>>,
lambda_min: Vec<u64>,
lambda_max: Vec<u64>,
last_update_time: Vec<u64>,
current_time: u64,
pending_inserts: Vec<Update>,
pending_deletes: Vec<Update>,
graph: Arc<DynamicGraph>,
instance_factory:
Box<dyn Fn(&DynamicGraph, u64, u64) -> Box<dyn ProperCutInstance> + Send + Sync>,
last_min_cut: Option<u64>,
#[cfg(feature = "agentic")]
use_agentic: bool,
}
impl MinCutWrapper {
pub fn new(graph: Arc<DynamicGraph>) -> Self {
Self::with_factory(graph, |g, min, max| {
Box::new(BoundedInstance::init(g, min, max))
})
}
pub fn with_factory<F>(graph: Arc<DynamicGraph>, factory: F) -> Self
where
F: Fn(&DynamicGraph, u64, u64) -> Box<dyn ProperCutInstance> + Send + Sync + 'static,
{
let mut lambda_min = Vec::with_capacity(MAX_INSTANCES);
let mut lambda_max = Vec::with_capacity(MAX_INSTANCES);
for i in 0..MAX_INSTANCES {
let (min, max) = Self::compute_bounds(i);
lambda_min.push(min);
lambda_max.push(max);
}
let mut instances = Vec::with_capacity(MAX_INSTANCES);
for _ in 0..MAX_INSTANCES {
instances.push(None);
}
Self {
conn_ds: DynamicConnectivity::new(),
instances,
lambda_min,
lambda_max,
last_update_time: vec![0; MAX_INSTANCES],
current_time: 0,
pending_inserts: Vec::new(),
pending_deletes: Vec::new(),
graph,
instance_factory: Box::new(factory),
last_min_cut: None,
#[cfg(feature = "agentic")]
use_agentic: false,
}
}
#[cfg(feature = "agentic")]
pub fn with_agentic(mut self, enabled: bool) -> Self {
self.use_agentic = enabled;
self
}
pub fn insert_edge(&mut self, edge_id: EdgeId, u: VertexId, v: VertexId) {
self.current_time += 1;
self.conn_ds.insert_edge(u, v);
self.pending_inserts.push(Update {
time: self.current_time,
edge_id,
u,
v,
});
}
pub fn delete_edge(&mut self, edge_id: EdgeId, u: VertexId, v: VertexId) {
self.current_time += 1;
self.conn_ds.delete_edge(u, v);
self.pending_deletes.push(Update {
time: self.current_time,
edge_id,
u,
v,
});
}
pub fn query(&mut self) -> MinCutResult {
if !self.conn_ds.is_connected() {
return MinCutResult::Disconnected;
}
#[cfg(feature = "agentic")]
if self.use_agentic {
return self.query_parallel();
}
self.process_instances()
}
#[cfg(feature = "agentic")]
fn query_parallel(&self) -> MinCutResult {
let coordinator = SharedCoordinator::new();
let mut aggregator = ResultAggregator::new();
let distributor = CoreDistributor::new(
CoreStrategy::GeometricRanges,
self.graph.num_vertices() as u16,
self.graph.num_edges() as u16,
);
for core_id in 0..NUM_CORES.min(self.graph.num_vertices()) as u8 {
let mut executor = CoreExecutor::init(core_id, Some(&coordinator));
for edge in self.graph.edges() {
executor.add_edge(
edge.source as u16,
edge.target as u16,
(edge.weight * 100.0) as u16,
);
}
let result = executor.process();
aggregator.add_result(result);
}
let best = aggregator.best_result();
if best.min_cut == u16::MAX {
MinCutResult::Disconnected
} else {
let mut membership = roaring::RoaringBitmap::new();
membership.insert(best.witness_seed as u32);
let witness = WitnessHandle::new(
best.witness_seed as u64,
membership,
best.witness_boundary as u64,
);
MinCutResult::Value {
cut_value: best.min_cut as u64,
witness,
}
}
}
fn process_instances(&mut self) -> MinCutResult {
self.pending_inserts.sort_by_key(|u| u.time);
self.pending_deletes.sort_by_key(|u| u.time);
let mut last_in_range: Option<(u64, WitnessHandle)> = None;
let start_idx = self.get_search_start();
for i in start_idx..MAX_INSTANCES {
let is_new_instance = self.instances[i].is_none();
if is_new_instance {
let min = self.lambda_min[i];
let max = self.lambda_max[i];
let instance = (self.instance_factory)(&self.graph, min, max);
self.instances[i] = Some(instance);
}
let instance = self.instances[i].as_mut().unwrap();
let last_time = self.last_update_time[i];
if is_new_instance {
let all_edges: Vec<_> = self
.graph
.edges()
.iter()
.map(|e| (e.id, e.source, e.target))
.collect();
if !all_edges.is_empty() {
instance.apply_inserts(&all_edges);
}
} else {
let inserts: Vec<_> = self
.pending_inserts
.iter()
.filter(|u| u.time > last_time)
.map(|u| (u.edge_id, u.u, u.v))
.collect();
let deletes: Vec<_> = self
.pending_deletes
.iter()
.filter(|u| u.time > last_time)
.map(|u| (u.edge_id, u.u, u.v))
.collect();
if !inserts.is_empty() {
instance.apply_inserts(&inserts);
}
if !deletes.is_empty() {
instance.apply_deletes(&deletes);
}
}
self.last_update_time[i] = self.current_time;
match instance.query() {
InstanceResult::ValueInRange { value, witness } => {
last_in_range = Some((value, witness));
break;
}
InstanceResult::AboveRange => {
continue;
}
}
}
self.pending_inserts.clear();
self.pending_deletes.clear();
match last_in_range {
Some((cut_value, witness)) => {
self.last_min_cut = Some(cut_value);
MinCutResult::Value { cut_value, witness }
}
None => {
self.last_min_cut = None;
use roaring::RoaringBitmap;
let mut membership = RoaringBitmap::new();
membership.insert(0);
let witness = WitnessHandle::new(0, membership, u64::MAX);
MinCutResult::Value {
cut_value: u64::MAX,
witness,
}
}
}
}
fn compute_bounds(i: usize) -> (u64, u64) {
let lambda_min = (RANGE_FACTOR.powi(i as i32)).floor() as u64;
let lambda_max = (RANGE_FACTOR.powi((i + 1) as i32)).floor() as u64;
(lambda_min.max(1), lambda_max.max(1))
}
fn find_instance_for_value(&self, value: u64) -> usize {
let mut lo = 0usize;
let mut hi = MAX_INSTANCES;
while lo < hi {
let mid = lo + (hi - lo) / 2;
if self.lambda_max[mid] < value {
lo = mid + 1;
} else {
hi = mid;
}
}
lo.min(MAX_INSTANCES - 1)
}
fn get_search_start(&self) -> usize {
if let Some(last_value) = self.last_min_cut {
let idx = self.find_instance_for_value(last_value);
idx.saturating_sub(2)
} else {
0
}
}
pub fn num_instances(&self) -> usize {
self.instances.iter().filter(|i| i.is_some()).count()
}
pub fn current_time(&self) -> u64 {
self.current_time
}
pub fn pending_updates(&self) -> usize {
self.pending_inserts.len() + self.pending_deletes.len()
}
pub fn batch_insert_edges(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
self.pending_inserts.reserve(edges.len());
for &(edge_id, u, v) in edges {
self.current_time += 1;
self.conn_ds.insert_edge(u, v);
self.pending_inserts.push(Update {
time: self.current_time,
edge_id,
u,
v,
});
}
}
pub fn batch_delete_edges(&mut self, edges: &[(EdgeId, VertexId, VertexId)]) {
self.pending_deletes.reserve(edges.len());
for &(edge_id, u, v) in edges {
self.current_time += 1;
self.conn_ds.delete_edge(u, v);
self.pending_deletes.push(Update {
time: self.current_time,
edge_id,
u,
v,
});
}
}
pub fn batch_update(
&mut self,
inserts: &[(EdgeId, VertexId, VertexId)],
deletes: &[(EdgeId, VertexId, VertexId)],
) {
self.batch_insert_edges(inserts);
self.batch_delete_edges(deletes);
}
pub fn flush_updates(&mut self) {
if self.pending_updates() == 0 {
return;
}
self.pending_inserts.sort_by_key(|u| u.time);
self.pending_deletes.sort_by_key(|u| u.time);
for i in 0..MAX_INSTANCES {
if let Some(ref mut instance) = self.instances[i] {
let last_time = self.last_update_time[i];
let inserts: Vec<_> = self
.pending_inserts
.iter()
.filter(|u| u.time > last_time)
.map(|u| (u.edge_id, u.u, u.v))
.collect();
let deletes: Vec<_> = self
.pending_deletes
.iter()
.filter(|u| u.time > last_time)
.map(|u| (u.edge_id, u.u, u.v))
.collect();
if !inserts.is_empty() {
instance.apply_inserts(&inserts);
}
if !deletes.is_empty() {
instance.apply_deletes(&deletes);
}
self.last_update_time[i] = self.current_time;
}
}
self.pending_inserts.clear();
self.pending_deletes.clear();
}
pub fn min_cut_value(&mut self) -> u64 {
self.query().value()
}
pub fn query_with_local_kcut(&mut self, source: VertexId) -> (u64, bool) {
use crate::localkcut::deterministic::DeterministicLocalKCut;
let result = self.query();
let cut_value = result.value();
if cut_value == 0 {
return (0, true); }
let volume_bound = self.graph.num_edges().max(1) * 2;
let lambda_max = cut_value * 2;
let mut lkc = DeterministicLocalKCut::new(lambda_max, volume_bound, 2);
for edge in self.graph.edges() {
lkc.insert_edge(edge.source, edge.target, edge.weight);
}
let cuts = lkc.query(source);
let certified = cuts.iter().any(|c| {
let diff = (c.cut_value - cut_value as f64).abs();
diff < 0.001 || c.cut_value <= cut_value as f64
});
(cut_value, certified || cuts.is_empty())
}
pub fn local_cuts(&self, source: VertexId, lambda_max: u64) -> Vec<(f64, Vec<VertexId>)> {
use crate::localkcut::deterministic::DeterministicLocalKCut;
let volume_bound = self.graph.num_edges().max(1) * 2;
let mut lkc = DeterministicLocalKCut::new(lambda_max, volume_bound, 2);
for edge in self.graph.edges() {
lkc.insert_edge(edge.source, edge.target, edge.weight);
}
lkc.query(source)
.into_iter()
.map(|c| (c.cut_value, c.vertices.into_iter().collect()))
.collect()
}
pub fn build_hierarchy(&self) -> crate::cluster::hierarchy::ThreeLevelHierarchy {
use crate::cluster::hierarchy::{HierarchyConfig, ThreeLevelHierarchy};
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
track_mirror_cuts: true,
..Default::default()
});
for edge in self.graph.edges() {
h.insert_edge(edge.source, edge.target, edge.weight);
}
h.build();
h
}
pub fn connectivity_curve(
&self,
ranked_edges: &[(VertexId, VertexId, f64)],
k_max: usize,
) -> Vec<(usize, u64)> {
use crate::algorithm::DynamicMinCut;
let mut temp_mincut = DynamicMinCut::new(crate::MinCutConfig::default());
for edge in self.graph.edges() {
let _ = temp_mincut.insert_edge(edge.source, edge.target, edge.weight);
}
let mut curve = Vec::with_capacity(k_max + 1);
curve.push((0, temp_mincut.min_cut_value() as u64));
for (k, &(u, v, _score)) in ranked_edges.iter().take(k_max).enumerate() {
let _ = temp_mincut.delete_edge(u, v);
let new_cut = temp_mincut.min_cut_value() as u64;
curve.push((k + 1, new_cut));
}
curve
}
pub fn find_elbow(curve: &[(usize, u64)]) -> Option<(usize, u64)> {
if curve.len() < 2 {
return None;
}
let mut max_drop = 0u64;
let mut elbow_k = 0usize;
for i in 1..curve.len() {
let drop = curve[i - 1].1.saturating_sub(curve[i].1);
if drop > max_drop {
max_drop = drop;
elbow_k = curve[i].0;
}
}
if max_drop > 0 {
Some((elbow_k, max_drop))
} else {
None
}
}
pub fn detector_quality(
&self,
ranked_edges: &[(VertexId, VertexId, f64)],
true_cut_size: usize,
) -> f64 {
let k_max = true_cut_size.min(ranked_edges.len());
if k_max == 0 {
return 0.0;
}
let curve = self.connectivity_curve(ranked_edges, k_max);
let initial_cut = curve.first().map(|(_, c)| *c).unwrap_or(0);
let final_cut = curve.last().map(|(_, c)| *c).unwrap_or(0);
if initial_cut == 0 {
0.0
} else {
(initial_cut - final_cut) as f64 / initial_cut as f64
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_bounds() {
let (min, max) = MinCutWrapper::compute_bounds(0);
assert_eq!(min, 1);
assert_eq!(max, 1);
let (min, max) = MinCutWrapper::compute_bounds(1);
assert_eq!(min, 1);
assert_eq!(max, 1);
let (min, max) = MinCutWrapper::compute_bounds(5);
assert_eq!(min, 2);
assert_eq!(max, 2);
let (min, max) = MinCutWrapper::compute_bounds(10);
assert_eq!(min, 6);
assert_eq!(max, 7);
let (min, max) = MinCutWrapper::compute_bounds(20);
assert_eq!(min, 38);
assert_eq!(max, 46);
}
#[test]
fn test_new_wrapper() {
let graph = Arc::new(DynamicGraph::new());
let wrapper = MinCutWrapper::new(graph);
assert_eq!(wrapper.num_instances(), 0); assert_eq!(wrapper.current_time(), 0);
assert_eq!(wrapper.pending_updates(), 0);
}
#[test]
fn test_empty_graph() {
let graph = Arc::new(DynamicGraph::new());
let mut wrapper = MinCutWrapper::new(graph);
let result = wrapper.query();
assert_eq!(result.value(), 0);
}
#[test]
fn test_disconnected_graph() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.insert_edge(0, 1, 2);
wrapper.insert_edge(1, 3, 4);
let result = wrapper.query();
assert_eq!(result.value(), 0);
assert!(matches!(result, MinCutResult::Disconnected));
}
#[test]
fn test_insert_and_query() {
let graph = Arc::new(DynamicGraph::new());
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
graph.insert_edge(1, 2, 1.0).unwrap();
wrapper.insert_edge(0, 1, 2);
assert_eq!(wrapper.pending_updates(), 1);
let result = wrapper.query();
assert!(result.is_connected());
assert_eq!(wrapper.pending_updates(), 0);
}
#[test]
fn test_time_counter() {
let graph = Arc::new(DynamicGraph::new());
let mut wrapper = MinCutWrapper::new(graph);
assert_eq!(wrapper.current_time(), 0);
wrapper.insert_edge(0, 1, 2);
assert_eq!(wrapper.current_time(), 1);
wrapper.delete_edge(0, 1, 2);
assert_eq!(wrapper.current_time(), 2);
wrapper.insert_edge(1, 2, 3);
assert_eq!(wrapper.current_time(), 3);
}
#[test]
fn test_lazy_instantiation() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.insert_edge(0, 1, 2);
wrapper.insert_edge(1, 2, 3);
assert_eq!(wrapper.num_instances(), 0);
let _ = wrapper.query();
assert!(wrapper.num_instances() > 0);
}
#[test]
fn test_result_value() {
use roaring::RoaringBitmap;
let result = MinCutResult::Disconnected;
assert_eq!(result.value(), 0);
assert!(!result.is_connected());
assert!(result.witness().is_none());
let mut membership = RoaringBitmap::new();
membership.insert(1);
membership.insert(2);
let witness = WitnessHandle::new(1, membership, 5);
let result = MinCutResult::Value {
cut_value: 5,
witness: witness.clone(),
};
assert_eq!(result.value(), 5);
assert!(result.is_connected());
assert!(result.witness().is_some());
}
#[test]
fn test_bounds_coverage() {
let (min, _max) = MinCutWrapper::compute_bounds(50);
assert!(min > 1000);
let (min, _max) = MinCutWrapper::compute_bounds(99);
assert!(min > 1_000_000);
}
#[test]
#[cfg(feature = "agentic")]
fn test_agentic_backend() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(0, 1, 1.0).unwrap();
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 0, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph)).with_agentic(true);
wrapper.insert_edge(0, 0, 1);
wrapper.insert_edge(1, 1, 2);
wrapper.insert_edge(2, 2, 0);
let result = wrapper.query();
match result {
MinCutResult::Disconnected => {
}
MinCutResult::Value { cut_value, .. } => {
assert!(cut_value < u64::MAX);
}
}
}
#[test]
fn test_batch_insert_edges() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3), (2, 3, 4)]);
assert_eq!(wrapper.pending_updates(), 3);
assert_eq!(wrapper.current_time(), 3);
let result = wrapper.query();
assert!(result.is_connected());
assert_eq!(wrapper.pending_updates(), 0);
}
#[test]
fn test_batch_delete_edges() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3), (2, 3, 4)]);
let _ = wrapper.query();
wrapper.batch_delete_edges(&[(1, 2, 3)]);
assert_eq!(wrapper.pending_updates(), 1);
let result = wrapper.query();
assert!(result.value() >= 0);
}
#[test]
fn test_batch_update_combined() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3)]);
let _ = wrapper.query();
wrapper.batch_update(
&[(2, 3, 4)], &[(1, 2, 3)], );
assert_eq!(wrapper.pending_updates(), 2);
}
#[test]
fn test_flush_updates() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3)]);
let _ = wrapper.query();
assert_eq!(wrapper.pending_updates(), 0);
wrapper.batch_insert_edges(&[(2, 3, 4)]);
assert_eq!(wrapper.pending_updates(), 1);
wrapper.flush_updates();
assert_eq!(wrapper.pending_updates(), 0);
}
#[test]
fn test_min_cut_value_convenience() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.insert_edge(0, 1, 2);
let value = wrapper.min_cut_value();
assert!(value >= 0);
}
#[test]
fn test_binary_search_instance_lookup() {
let graph = Arc::new(DynamicGraph::new());
let wrapper = MinCutWrapper::new(graph);
assert_eq!(wrapper.find_instance_for_value(1), 0);
let idx = wrapper.find_instance_for_value(2);
assert!(wrapper.lambda_min[idx] <= 2);
assert!(wrapper.lambda_max[idx] >= 2);
let idx = wrapper.find_instance_for_value(100);
assert!(wrapper.lambda_min[idx] <= 100);
assert!(wrapper.lambda_max[idx] >= 100);
}
#[test]
fn test_cached_min_cut_optimization() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3)]);
assert!(wrapper.last_min_cut.is_none());
let result1 = wrapper.query();
assert!(wrapper.last_min_cut.is_some());
wrapper.batch_insert_edges(&[(2, 3, 4)]);
let result2 = wrapper.query();
assert!(result1.is_connected());
assert!(result2.is_connected());
}
#[test]
fn test_query_with_local_kcut() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 1, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3), (2, 3, 1)]);
let (cut_value, certified) = wrapper.query_with_local_kcut(1);
assert!(cut_value >= 0, "Cut value should be non-negative");
assert!(
certified || !certified,
"Certification should complete without panic"
);
}
#[test]
fn test_local_cuts() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3)]);
wrapper.query();
let cuts = wrapper.local_cuts(1, 5);
assert!(cuts.len() >= 0, "Should return some cuts or empty");
}
#[test]
fn test_build_hierarchy() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
graph.insert_edge(4, 1, 1.0).unwrap();
let wrapper = MinCutWrapper::new(Arc::clone(&graph));
let hierarchy = wrapper.build_hierarchy();
let stats = hierarchy.stats();
assert!(stats.num_vertices >= 4, "Hierarchy should have 4 vertices");
}
#[test]
fn test_connectivity_curve_basic() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3)]);
wrapper.query();
let ranked_edges = vec![(1, 2, 1.0), (2, 3, 0.8)];
let curve = wrapper.connectivity_curve(&ranked_edges, 2);
assert_eq!(curve.len(), 3);
assert_eq!(curve[0].0, 0); assert_eq!(curve[1].0, 1); assert_eq!(curve[2].0, 2); }
#[test]
fn test_find_elbow_with_clear_drop() {
let curve = vec![
(0, 10), (1, 9), (2, 3), (3, 2), (4, 2), ];
let elbow = MinCutWrapper::find_elbow(&curve);
assert!(elbow.is_some());
let (k, drop) = elbow.unwrap();
assert_eq!(k, 2); assert_eq!(drop, 6); }
#[test]
fn test_find_elbow_flat_curve() {
let curve = vec![(0, 5), (1, 5), (2, 5), (3, 5)];
let elbow = MinCutWrapper::find_elbow(&curve);
assert!(elbow.is_none()); }
#[test]
fn test_find_elbow_single_point() {
let curve = vec![(0, 5)];
let elbow = MinCutWrapper::find_elbow(&curve);
assert!(elbow.is_none()); }
#[test]
fn test_find_elbow_empty() {
let curve: Vec<(usize, u64)> = vec![];
let elbow = MinCutWrapper::find_elbow(&curve);
assert!(elbow.is_none());
}
#[test]
fn test_detector_quality_perfect() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
wrapper.batch_insert_edges(&[(0, 1, 2), (1, 2, 3), (2, 3, 4)]);
wrapper.query();
let ranked_edges = vec![
(2, 3, 1.0), (1, 2, 0.5),
(3, 4, 0.3),
];
let quality = wrapper.detector_quality(&ranked_edges, 1);
assert!(quality >= 0.0);
assert!(quality <= 1.0);
}
#[test]
fn test_detector_quality_zero_cut() {
let graph = Arc::new(DynamicGraph::new());
let wrapper = MinCutWrapper::new(Arc::clone(&graph));
let ranked_edges: Vec<(u64, u64, f64)> = vec![];
let quality = wrapper.detector_quality(&ranked_edges, 1);
assert_eq!(quality, 0.0);
}
#[test]
fn test_connectivity_curve_empty_graph() {
let graph = Arc::new(DynamicGraph::new());
let wrapper = MinCutWrapper::new(Arc::clone(&graph));
let ranked_edges = vec![(1, 2, 1.0)];
let curve = wrapper.connectivity_curve(&ranked_edges, 2);
assert!(!curve.is_empty());
assert_eq!(curve[0].0, 0); }
}