use crate::error::Result;
use crate::graph::{DynamicGraph, VertexId, Weight};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct DecompositionNode {
pub id: usize,
pub level: usize,
pub vertices: HashSet<VertexId>,
pub parent: Option<usize>,
pub children: Vec<usize>,
pub cut_value: f64,
pub dirty: bool,
}
impl DecompositionNode {
fn new_leaf(id: usize, vertex: VertexId) -> Self {
let mut vertices = HashSet::new();
vertices.insert(vertex);
Self {
id,
level: 0,
vertices,
parent: None,
children: Vec::new(),
cut_value: f64::INFINITY,
dirty: true,
}
}
fn new_internal(id: usize, level: usize, children: Vec<usize>) -> Self {
Self {
id,
level,
vertices: HashSet::new(), parent: None,
children,
cut_value: f64::INFINITY,
dirty: true,
}
}
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
}
pub fn size(&self) -> usize {
self.vertices.len()
}
}
pub struct HierarchicalDecomposition {
nodes: Vec<DecompositionNode>,
vertex_to_leaf: HashMap<VertexId, usize>,
root: Option<usize>,
min_cut: f64,
height: usize,
graph: Arc<DynamicGraph>,
next_node_id: usize,
}
impl HierarchicalDecomposition {
pub fn build(graph: Arc<DynamicGraph>) -> Result<Self> {
let mut decomp = Self {
nodes: Vec::new(),
vertex_to_leaf: HashMap::new(),
root: None,
min_cut: f64::INFINITY,
height: 0,
graph,
next_node_id: 0,
};
decomp.build_hierarchy()?;
decomp.min_cut = decomp.propagate_updates();
Ok(decomp)
}
pub fn min_cut_value(&self) -> f64 {
self.min_cut
}
pub fn min_cut_partition(&self) -> (HashSet<VertexId>, HashSet<VertexId>) {
if self.root.is_none() {
return (HashSet::new(), HashSet::new());
}
let (min_node_idx, _) = self.find_min_cut_node();
let node = &self.nodes[min_node_idx];
let partition_a = node.vertices.clone();
let all_vertices: HashSet<VertexId> = self.graph.vertices().into_iter().collect();
let partition_b: HashSet<VertexId> =
all_vertices.difference(&partition_a).copied().collect();
(partition_a, partition_b)
}
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, _weight: Weight) -> Result<f64> {
if let Some(lca_idx) = self.lca_node(u, v) {
self.mark_dirty(lca_idx);
}
self.min_cut = self.propagate_updates();
Ok(self.min_cut)
}
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<f64> {
if let Some(lca_idx) = self.lca_node(u, v) {
self.mark_dirty(lca_idx);
}
self.min_cut = self.propagate_updates();
Ok(self.min_cut)
}
fn propagate_updates(&mut self) -> f64 {
if let Some(root_idx) = self.root {
self.recompute_subtree(root_idx);
}
self.find_min_cut_value()
}
fn recompute_subtree(&mut self, node_idx: usize) {
let children = self.nodes[node_idx].children.clone();
for child_idx in children {
if self.nodes[child_idx].dirty {
self.recompute_subtree(child_idx);
}
}
if self.nodes[node_idx].dirty {
let cut_value = self.compute_cut(node_idx);
self.nodes[node_idx].cut_value = cut_value;
self.nodes[node_idx].dirty = false;
}
}
fn lca_node(&self, u: VertexId, v: VertexId) -> Option<usize> {
let u_leaf = self.vertex_to_leaf.get(&u)?;
let v_leaf = self.vertex_to_leaf.get(&v)?;
if u_leaf == v_leaf {
return Some(*u_leaf);
}
let mut u_ancestors = HashSet::new();
let mut current = Some(*u_leaf);
while let Some(node_idx) = current {
u_ancestors.insert(node_idx);
current = self.nodes[node_idx].parent;
}
let mut current = Some(*v_leaf);
while let Some(node_idx) = current {
if u_ancestors.contains(&node_idx) {
return Some(node_idx);
}
current = self.nodes[node_idx].parent;
}
None
}
fn mark_dirty(&mut self, node_idx: usize) {
let mut current = Some(node_idx);
while let Some(idx) = current {
self.nodes[idx].dirty = true;
current = self.nodes[idx].parent;
}
}
fn build_hierarchy(&mut self) -> Result<()> {
let vertices = self.graph.vertices();
if vertices.is_empty() {
return Ok(());
}
for vertex in &vertices {
let node_id = self.next_node_id;
self.next_node_id += 1;
let leaf = DecompositionNode::new_leaf(node_id, *vertex);
let leaf_idx = self.nodes.len();
self.nodes.push(leaf);
self.vertex_to_leaf.insert(*vertex, leaf_idx);
}
let leaf_indices: Vec<usize> = (0..vertices.len()).collect();
if !leaf_indices.is_empty() {
self.root = Some(self.build_subtree(&leaf_indices, 1)?);
}
Ok(())
}
fn build_subtree(&mut self, indices: &[usize], level: usize) -> Result<usize> {
if indices.len() == 1 {
self.nodes[indices[0]].level = 0;
self.height = self.height.max(level - 1);
return Ok(indices[0]);
}
let mid = indices.len() / 2;
let left_indices = &indices[..mid];
let right_indices = &indices[mid..];
let left_idx = self.build_subtree(left_indices, level + 1)?;
let right_idx = self.build_subtree(right_indices, level + 1)?;
let node_id = self.next_node_id;
self.next_node_id += 1;
let mut internal =
DecompositionNode::new_internal(node_id, level, vec![left_idx, right_idx]);
internal.vertices.extend(&self.nodes[left_idx].vertices);
internal.vertices.extend(&self.nodes[right_idx].vertices);
let internal_idx = self.nodes.len();
self.nodes.push(internal);
self.nodes[left_idx].parent = Some(internal_idx);
self.nodes[right_idx].parent = Some(internal_idx);
self.height = self.height.max(level);
Ok(internal_idx)
}
fn compute_cut(&self, node_idx: usize) -> f64 {
let node = &self.nodes[node_idx];
if node.vertices.len() == self.graph.num_vertices() {
return f64::INFINITY;
}
self.compute_global_cut(&node.vertices)
}
fn compute_global_cut(&self, vertices: &HashSet<VertexId>) -> f64 {
let mut cut_weight = 0.0;
for &u in vertices {
for (v, _edge_id) in self.graph.neighbors(u) {
if !vertices.contains(&v) {
if let Some(weight) = self.graph.edge_weight(u, v) {
cut_weight += weight;
}
}
}
}
cut_weight
}
fn find_min_cut_node(&self) -> (usize, f64) {
let mut min_idx = 0;
let mut min_value = f64::INFINITY;
for (idx, node) in self.nodes.iter().enumerate() {
if node.cut_value < min_value {
min_value = node.cut_value;
min_idx = idx;
}
}
(min_idx, min_value)
}
fn find_min_cut_value(&self) -> f64 {
self.find_min_cut_node().1
}
pub fn height(&self) -> usize {
self.height
}
pub fn num_nodes(&self) -> usize {
self.nodes.len()
}
}
#[derive(Debug, Clone)]
pub struct LevelInfo {
pub level: usize,
pub num_nodes: usize,
pub avg_cut: f64,
}
impl HierarchicalDecomposition {
pub fn level_info(&self) -> Vec<LevelInfo> {
let mut levels: HashMap<usize, Vec<f64>> = HashMap::new();
for node in &self.nodes {
levels
.entry(node.level)
.or_insert_with(Vec::new)
.push(node.cut_value);
}
let mut result: Vec<LevelInfo> = levels
.into_iter()
.map(|(level, cut_values)| {
let num_nodes = cut_values.len();
let finite_cuts: Vec<f64> = cut_values
.iter()
.filter(|&&v| v.is_finite())
.copied()
.collect();
let avg_cut = if finite_cuts.is_empty() {
f64::INFINITY
} else {
finite_cuts.iter().sum::<f64>() / finite_cuts.len() as f64
};
LevelInfo {
level,
num_nodes,
avg_cut,
}
})
.collect();
result.sort_by_key(|info| info.level);
result
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_simple_graph() -> Arc<DynamicGraph> {
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();
graph
}
fn create_disconnectable_graph() -> Arc<DynamicGraph> {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 2.0).unwrap();
graph.insert_edge(2, 3, 2.0).unwrap();
graph.insert_edge(3, 1, 2.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
graph.insert_edge(4, 5, 2.0).unwrap();
graph.insert_edge(5, 6, 2.0).unwrap();
graph.insert_edge(6, 4, 2.0).unwrap();
graph
}
#[test]
fn test_build_empty_graph() {
let graph = Arc::new(DynamicGraph::new());
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert_eq!(decomp.num_nodes(), 0);
assert_eq!(decomp.height(), 0);
}
#[test]
fn test_build_single_vertex() {
let graph = Arc::new(DynamicGraph::new());
graph.add_vertex(1);
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert_eq!(decomp.num_nodes(), 1);
assert_eq!(decomp.height(), 0);
assert!(decomp.min_cut_value().is_infinite());
}
#[test]
fn test_build_triangle() {
let graph = create_simple_graph();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert_eq!(decomp.num_nodes(), 5);
assert!(decomp.height() <= 2);
assert_eq!(decomp.min_cut_value(), 2.0);
}
#[test]
fn test_build_disconnectable() {
let graph = create_disconnectable_graph();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert_eq!(decomp.num_nodes(), 11);
let min_cut = decomp.min_cut_value();
assert!(min_cut.is_finite() && min_cut >= 1.0);
}
#[test]
fn test_min_cut_partition() {
let graph = create_disconnectable_graph();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
let (partition_a, partition_b) = decomp.min_cut_partition();
assert_eq!(partition_a.len() + partition_b.len(), 6);
assert!(partition_a.len() >= 1 && partition_a.len() <= 5);
assert!(partition_b.len() >= 1 && partition_b.len() <= 5);
let intersection: HashSet<_> = partition_a.intersection(&partition_b).collect();
assert!(intersection.is_empty());
}
#[test]
fn test_lca_node() {
let graph = create_simple_graph();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
let lca = decomp.lca_node(1, 1);
assert!(lca.is_some());
let lca = decomp.lca_node(1, 2);
assert!(lca.is_some());
let lca = decomp.lca_node(1, 3);
assert!(lca.is_some());
}
#[test]
fn test_mark_dirty() {
let graph = create_simple_graph();
let mut decomp = HierarchicalDecomposition::build(graph).unwrap();
for node in &decomp.nodes {
assert!(
!node.dirty,
"Node {} should not be dirty after build",
node.id
);
}
let leaf_idx = *decomp.vertex_to_leaf.get(&1).unwrap();
decomp.mark_dirty(leaf_idx);
let mut current = Some(leaf_idx);
while let Some(idx) = current {
assert!(decomp.nodes[idx].dirty, "Node {} should be dirty", idx);
current = decomp.nodes[idx].parent;
}
}
#[test]
fn test_insert_edge() {
let graph = create_simple_graph();
let mut decomp = HierarchicalDecomposition::build(graph.clone()).unwrap();
let old_min_cut = decomp.min_cut_value();
graph.insert_edge(1, 4, 5.0).unwrap();
graph.insert_edge(2, 4, 5.0).unwrap();
let mut decomp = HierarchicalDecomposition::build(graph.clone()).unwrap();
let baseline = decomp.min_cut_value();
graph.insert_edge(3, 4, 3.0).unwrap();
let new_min_cut = decomp.insert_edge(3, 4, 3.0).unwrap();
assert!(new_min_cut.is_finite());
}
#[test]
fn test_delete_edge() {
let graph = create_disconnectable_graph();
let mut decomp = HierarchicalDecomposition::build(graph.clone()).unwrap();
let old_min_cut = decomp.min_cut_value();
assert!(old_min_cut.is_finite());
graph.delete_edge(1, 2).unwrap();
let new_min_cut = decomp.delete_edge(1, 2).unwrap();
assert!(new_min_cut.is_finite());
}
#[test]
fn test_level_info() {
let graph = create_simple_graph();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
let levels = decomp.level_info();
assert!(!levels.is_empty());
for i in 1..levels.len() {
assert!(levels[i].level > levels[i - 1].level);
}
let total_nodes: usize = levels.iter().map(|l| l.num_nodes).sum();
assert_eq!(total_nodes, decomp.num_nodes());
}
#[test]
fn test_balanced_tree() {
let graph = Arc::new(DynamicGraph::new());
for i in 1..=15 {
graph.add_vertex(i);
}
for i in 1..15 {
graph.insert_edge(i, i + 1, 1.0).unwrap();
}
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert!(
decomp.height() <= 4,
"Height {} should be <= 4",
decomp.height()
);
let leaf_count = decomp.nodes.iter().filter(|n| n.level == 0).count();
assert_eq!(leaf_count, 15);
}
#[test]
fn test_compute_cut() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 3, 1.0).unwrap();
graph.insert_edge(2, 4, 1.0).unwrap();
let decomp = HierarchicalDecomposition::build(graph).unwrap();
let min_cut = decomp.min_cut_value();
assert!(min_cut.is_finite() && min_cut <= 2.0);
}
#[test]
fn test_large_tree() {
let graph = Arc::new(DynamicGraph::new());
for i in 1..=100 {
graph.add_vertex(i);
}
for i in 1..100 {
graph.insert_edge(i, i + 1, 1.0).unwrap();
}
let decomp = HierarchicalDecomposition::build(graph).unwrap();
assert!(
decomp.height() <= 7,
"Height {} should be <= 7",
decomp.height()
);
assert_eq!(decomp.min_cut_value(), 1.0);
assert_eq!(decomp.num_nodes(), 199);
}
#[test]
fn test_propagate_updates() {
let graph = create_simple_graph();
let mut decomp = HierarchicalDecomposition::build(graph).unwrap();
for i in 0..decomp.nodes.len() {
decomp.nodes[i].dirty = true;
}
let min_cut = decomp.propagate_updates();
for node in &decomp.nodes {
assert!(!node.dirty);
}
assert_eq!(min_cut, 2.0);
}
}