mod adapter;
mod config;
mod isolation;
mod metrics;
pub use adapter::MinCutAdapter;
pub use config::MinCutConfig;
pub use isolation::{IsolationRegion, IsolationResult};
pub use metrics::IsolationMetrics;
use std::collections::{HashMap, HashSet};
pub type VertexId = u64;
pub type EdgeId = (VertexId, VertexId);
pub type Weight = f64;
pub type Result<T> = std::result::Result<T, MinCutError>;
#[derive(Debug, Clone, thiserror::Error)]
pub enum MinCutError {
#[error("Edge already exists: ({0}, {1})")]
EdgeExists(VertexId, VertexId),
#[error("Edge not found: ({0}, {1})")]
EdgeNotFound(VertexId, VertexId),
#[error("Vertex not found: {0}")]
VertexNotFound(VertexId),
#[error("Graph is empty")]
EmptyGraph,
#[error("Invalid threshold: {0}")]
InvalidThreshold(f64),
#[error("Cut computation failed: {0}")]
ComputationFailed(String),
#[error("Hierarchy not built - call build() first")]
HierarchyNotBuilt,
}
#[derive(Debug)]
pub struct IncoherenceIsolator {
config: MinCutConfig,
adapter: MinCutAdapter,
edge_weights: HashMap<EdgeId, Weight>,
vertices: HashSet<VertexId>,
hierarchy_built: bool,
metrics: IsolationMetrics,
}
impl IncoherenceIsolator {
pub fn new(config: MinCutConfig) -> Self {
let adapter = MinCutAdapter::new(config.clone());
Self {
config,
adapter,
edge_weights: HashMap::new(),
vertices: HashSet::new(),
hierarchy_built: false,
metrics: IsolationMetrics::new(),
}
}
pub fn default_config() -> Self {
Self::new(MinCutConfig::default())
}
pub fn for_size(expected_vertices: usize) -> Self {
Self::new(MinCutConfig::for_size(expected_vertices))
}
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> Result<()> {
let key = Self::edge_key(u, v);
if self.edge_weights.contains_key(&key) {
return Err(MinCutError::EdgeExists(u, v));
}
self.edge_weights.insert(key, weight);
self.vertices.insert(u);
self.vertices.insert(v);
self.adapter.insert_edge(u, v, weight)?;
if self.hierarchy_built {
self.metrics.record_update();
}
Ok(())
}
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<()> {
let key = Self::edge_key(u, v);
if !self.edge_weights.contains_key(&key) {
return Err(MinCutError::EdgeNotFound(u, v));
}
self.edge_weights.remove(&key);
self.adapter.delete_edge(u, v)?;
if self.hierarchy_built {
self.metrics.record_update();
}
Ok(())
}
pub fn update_weight(&mut self, u: VertexId, v: VertexId, weight: Weight) -> Result<()> {
let key = Self::edge_key(u, v);
if !self.edge_weights.contains_key(&key) {
return Err(MinCutError::EdgeNotFound(u, v));
}
self.adapter.delete_edge(u, v)?;
self.adapter.insert_edge(u, v, weight)?;
self.edge_weights.insert(key, weight);
if self.hierarchy_built {
self.metrics.record_update();
}
Ok(())
}
pub fn build(&mut self) {
if self.edge_weights.is_empty() {
return;
}
self.adapter.build();
self.hierarchy_built = true;
self.metrics.record_build();
}
pub fn min_cut_value(&self) -> Result<f64> {
if !self.hierarchy_built {
return Err(MinCutError::HierarchyNotBuilt);
}
Ok(self.adapter.min_cut_value())
}
pub fn isolate_high_energy(&mut self, threshold: Weight) -> Result<IsolationResult> {
if !self.hierarchy_built {
return Err(MinCutError::HierarchyNotBuilt);
}
if threshold <= 0.0 {
return Err(MinCutError::InvalidThreshold(threshold));
}
let high_energy_edges: Vec<EdgeId> = self
.edge_weights
.iter()
.filter(|(_, &w)| w > threshold)
.map(|(&k, _)| k)
.collect();
if high_energy_edges.is_empty() {
return Ok(IsolationResult::no_isolation());
}
let mut high_energy_vertices: HashSet<VertexId> = HashSet::new();
for (u, v) in &high_energy_edges {
high_energy_vertices.insert(*u);
high_energy_vertices.insert(*v);
}
let cut_result = self.adapter.compute_isolation(&high_energy_vertices)?;
let result = IsolationResult {
isolated_vertices: cut_result.isolated_set,
cut_edges: cut_result.cut_edges,
cut_value: cut_result.cut_value,
num_high_energy_edges: high_energy_edges.len(),
threshold,
is_verified: cut_result.is_verified,
};
self.metrics.record_isolation(&result);
Ok(result)
}
pub fn find_isolated_regions(&mut self, threshold: Weight) -> Result<Vec<IsolationRegion>> {
if !self.hierarchy_built {
return Err(MinCutError::HierarchyNotBuilt);
}
let high_energy_edges: Vec<(EdgeId, Weight)> = self
.edge_weights
.iter()
.filter(|(_, &w)| w > threshold)
.map(|(&k, &w)| (k, w))
.collect();
if high_energy_edges.is_empty() {
return Ok(vec![]);
}
let mut regions: Vec<IsolationRegion> = Vec::new();
let mut visited: HashSet<VertexId> = HashSet::new();
for ((u, v), weight) in &high_energy_edges {
if visited.contains(u) && visited.contains(v) {
continue;
}
let mut component_vertices: HashSet<VertexId> = HashSet::new();
let mut component_edges: Vec<EdgeId> = Vec::new();
let mut queue: Vec<VertexId> = vec![*u, *v];
let mut component_energy = 0.0;
while let Some(vertex) = queue.pop() {
if visited.contains(&vertex) {
continue;
}
visited.insert(vertex);
component_vertices.insert(vertex);
for ((eu, ev), ew) in &high_energy_edges {
if *eu == vertex || *ev == vertex {
if !component_edges.contains(&(*eu, *ev)) {
component_edges.push((*eu, *ev));
component_energy += ew;
}
if !visited.contains(eu) {
queue.push(*eu);
}
if !visited.contains(ev) {
queue.push(*ev);
}
}
}
}
let boundary_edges: Vec<EdgeId> = self
.edge_weights
.keys()
.filter(|(a, b)| {
(component_vertices.contains(a) && !component_vertices.contains(b))
|| (component_vertices.contains(b) && !component_vertices.contains(a))
})
.copied()
.collect();
let boundary_weight: Weight = boundary_edges
.iter()
.filter_map(|e| self.edge_weights.get(e))
.sum();
regions.push(IsolationRegion {
vertices: component_vertices,
internal_edges: component_edges,
boundary_edges,
total_energy: component_energy,
boundary_weight,
region_id: regions.len(),
});
}
Ok(regions)
}
pub fn is_subpolynomial(&self) -> bool {
self.adapter.is_subpolynomial()
}
pub fn recourse_stats(&self) -> RecourseStats {
self.adapter.recourse_stats()
}
pub fn hierarchy_stats(&self) -> HierarchyStats {
self.adapter.hierarchy_stats()
}
pub fn metrics(&self) -> &IsolationMetrics {
&self.metrics
}
pub fn num_vertices(&self) -> usize {
self.vertices.len()
}
pub fn num_edges(&self) -> usize {
self.edge_weights.len()
}
pub fn config(&self) -> &MinCutConfig {
&self.config
}
fn edge_key(u: VertexId, v: VertexId) -> EdgeId {
if u < v {
(u, v)
} else {
(v, u)
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RecourseStats {
pub total_recourse: u64,
pub num_updates: u64,
pub max_single_recourse: u64,
pub avg_update_time_us: f64,
pub theoretical_bound: f64,
}
impl RecourseStats {
pub fn amortized_recourse(&self) -> f64 {
if self.num_updates == 0 {
0.0
} else {
self.total_recourse as f64 / self.num_updates as f64
}
}
pub fn within_bounds(&self) -> bool {
self.amortized_recourse() <= self.theoretical_bound
}
}
#[derive(Debug, Clone, Default)]
pub struct HierarchyStats {
pub num_levels: usize,
pub expanders_per_level: Vec<usize>,
pub total_expanders: usize,
pub avg_expander_size: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_operations() {
let mut isolator = IncoherenceIsolator::default_config();
isolator.insert_edge(1, 2, 0.5).unwrap();
isolator.insert_edge(2, 3, 0.5).unwrap();
isolator.insert_edge(3, 4, 2.0).unwrap(); isolator.insert_edge(4, 5, 0.5).unwrap();
isolator.insert_edge(5, 6, 0.5).unwrap();
assert_eq!(isolator.num_vertices(), 6);
assert_eq!(isolator.num_edges(), 5);
isolator.build();
let cut = isolator.min_cut_value().unwrap();
assert!(cut > 0.0);
}
#[test]
fn test_isolation() {
let mut isolator = IncoherenceIsolator::default_config();
isolator.insert_edge(1, 2, 0.1).unwrap();
isolator.insert_edge(2, 3, 0.1).unwrap();
isolator.insert_edge(3, 1, 0.1).unwrap();
isolator.insert_edge(3, 4, 5.0).unwrap();
isolator.insert_edge(4, 5, 0.1).unwrap();
isolator.insert_edge(5, 6, 0.1).unwrap();
isolator.insert_edge(6, 4, 0.1).unwrap();
isolator.build();
let result = isolator.isolate_high_energy(1.0).unwrap();
assert_eq!(result.num_high_energy_edges, 1);
assert!(result.cut_value >= 0.0);
}
#[test]
fn test_find_regions() {
let mut isolator = IncoherenceIsolator::default_config();
isolator.insert_edge(1, 2, 5.0).unwrap();
isolator.insert_edge(2, 3, 0.1).unwrap();
isolator.insert_edge(10, 11, 5.0).unwrap();
isolator.insert_edge(11, 12, 5.0).unwrap();
isolator.insert_edge(3, 10, 0.1).unwrap();
isolator.build();
let regions = isolator.find_isolated_regions(1.0).unwrap();
assert!(regions.len() >= 1);
}
#[test]
fn test_update_weight() {
let mut isolator = IncoherenceIsolator::default_config();
isolator.insert_edge(1, 2, 0.5).unwrap();
isolator.insert_edge(2, 3, 0.5).unwrap();
isolator.build();
isolator.update_weight(1, 2, 2.0).unwrap();
isolator.build();
assert!(isolator.min_cut_value().is_ok());
}
#[test]
fn test_delete_edge() {
let mut isolator = IncoherenceIsolator::default_config();
isolator.insert_edge(1, 2, 0.5).unwrap();
isolator.insert_edge(2, 3, 0.5).unwrap();
isolator.insert_edge(3, 1, 0.5).unwrap();
assert_eq!(isolator.num_edges(), 3);
isolator.delete_edge(1, 2).unwrap();
assert_eq!(isolator.num_edges(), 2);
}
}