use crate::graph::{VertexId, Weight};
use std::collections::{HashMap, HashSet, VecDeque};
pub type Phi = f64;
#[derive(Debug, Clone)]
pub struct HierarchyConfig {
pub phi: Phi,
pub max_expander_size: usize,
pub min_expander_size: usize,
pub target_precluster_size: usize,
pub max_boundary_ratio: f64,
pub track_mirror_cuts: bool,
}
impl Default for HierarchyConfig {
fn default() -> Self {
Self {
phi: 0.1,
max_expander_size: 500,
min_expander_size: 5,
target_precluster_size: 100,
max_boundary_ratio: 0.3,
track_mirror_cuts: true,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HierarchyLevel {
Expander,
Precluster,
Cluster,
}
#[derive(Debug, Clone)]
pub struct Expander {
pub id: u64,
pub vertices: HashSet<VertexId>,
pub internal_edges: Vec<(VertexId, VertexId)>,
pub boundary_edges: Vec<(VertexId, VertexId)>,
pub volume: usize,
pub expansion_ratio: f64,
pub precluster_id: Option<u64>,
}
impl Expander {
pub fn new(id: u64, vertices: HashSet<VertexId>) -> Self {
Self {
id,
vertices,
internal_edges: Vec::new(),
boundary_edges: Vec::new(),
volume: 0,
expansion_ratio: 0.0,
precluster_id: None,
}
}
pub fn size(&self) -> usize {
self.vertices.len()
}
pub fn contains(&self, v: VertexId) -> bool {
self.vertices.contains(&v)
}
pub fn boundary_sparsity(&self) -> f64 {
if self.volume == 0 {
return 0.0;
}
self.boundary_edges.len() as f64 / self.volume as f64
}
}
#[derive(Debug, Clone)]
pub struct Precluster {
pub id: u64,
pub expanders: Vec<u64>,
pub vertices: HashSet<VertexId>,
pub boundary_edges: Vec<(VertexId, VertexId)>,
pub volume: usize,
pub cluster_id: Option<u64>,
}
impl Precluster {
pub fn new(id: u64) -> Self {
Self {
id,
expanders: Vec::new(),
vertices: HashSet::new(),
boundary_edges: Vec::new(),
volume: 0,
cluster_id: None,
}
}
pub fn add_expander(&mut self, expander: &Expander) {
self.expanders.push(expander.id);
self.vertices.extend(&expander.vertices);
self.volume += expander.volume;
}
pub fn size(&self) -> usize {
self.vertices.len()
}
pub fn boundary_ratio(&self) -> f64 {
if self.volume == 0 {
return 0.0;
}
self.boundary_edges.len() as f64 / self.volume as f64
}
}
#[derive(Debug, Clone)]
pub struct MirrorCut {
pub source_expander: u64,
pub target_expander: u64,
pub cut_value: f64,
pub cut_edges: Vec<(VertexId, VertexId)>,
pub certified: bool,
}
#[derive(Debug, Clone)]
pub struct HierarchyCluster {
pub id: u64,
pub preclusters: Vec<u64>,
pub vertices: HashSet<VertexId>,
pub boundary_edges: Vec<(VertexId, VertexId)>,
pub mirror_cuts: Vec<MirrorCut>,
pub internal_min_cut: f64,
}
impl HierarchyCluster {
pub fn new(id: u64) -> Self {
Self {
id,
preclusters: Vec::new(),
vertices: HashSet::new(),
boundary_edges: Vec::new(),
mirror_cuts: Vec::new(),
internal_min_cut: f64::INFINITY,
}
}
pub fn add_precluster(&mut self, precluster: &Precluster) {
self.preclusters.push(precluster.id);
self.vertices.extend(&precluster.vertices);
}
pub fn size(&self) -> usize {
self.vertices.len()
}
}
#[derive(Debug)]
pub struct ThreeLevelHierarchy {
config: HierarchyConfig,
expanders: HashMap<u64, Expander>,
preclusters: HashMap<u64, Precluster>,
clusters: HashMap<u64, HierarchyCluster>,
vertex_expander: HashMap<VertexId, u64>,
next_id: u64,
adjacency: HashMap<VertexId, HashMap<VertexId, Weight>>,
pub global_min_cut: f64,
}
impl ThreeLevelHierarchy {
pub fn new(config: HierarchyConfig) -> Self {
Self {
config,
expanders: HashMap::new(),
preclusters: HashMap::new(),
clusters: HashMap::new(),
vertex_expander: HashMap::new(),
next_id: 1,
adjacency: HashMap::new(),
global_min_cut: f64::INFINITY,
}
}
pub fn with_defaults() -> Self {
Self::new(HierarchyConfig::default())
}
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) {
self.adjacency.entry(u).or_default().insert(v, weight);
self.adjacency.entry(v).or_default().insert(u, weight);
}
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
if let Some(neighbors) = self.adjacency.get_mut(&u) {
neighbors.remove(&v);
}
if let Some(neighbors) = self.adjacency.get_mut(&v) {
neighbors.remove(&u);
}
}
pub fn vertices(&self) -> Vec<VertexId> {
self.adjacency.keys().copied().collect()
}
pub fn neighbors(&self, v: VertexId) -> Vec<(VertexId, Weight)> {
self.adjacency
.get(&v)
.map(|n| n.iter().map(|(&v, &w)| (v, w)).collect())
.unwrap_or_default()
}
pub fn degree(&self, v: VertexId) -> usize {
self.adjacency.get(&v).map_or(0, |n| n.len())
}
pub fn build(&mut self) {
let vertices: HashSet<_> = self.vertices().into_iter().collect();
if vertices.is_empty() {
return;
}
self.build_expanders(&vertices);
self.build_preclusters();
self.build_clusters();
if self.config.track_mirror_cuts {
self.compute_mirror_cuts();
}
self.update_global_min_cut();
}
fn build_expanders(&mut self, vertices: &HashSet<VertexId>) {
self.expanders.clear();
self.vertex_expander.clear();
let mut remaining: HashSet<_> = vertices.iter().copied().collect();
while !remaining.is_empty() {
let start = *remaining.iter().next().unwrap();
let expander_vertices = self.grow_expander(start, &remaining);
if expander_vertices.is_empty() {
remaining.remove(&start);
continue;
}
let id = self.next_id;
self.next_id += 1;
let mut expander = Expander::new(id, expander_vertices.clone());
self.compute_expander_properties(&mut expander);
for &v in &expander_vertices {
self.vertex_expander.insert(v, id);
remaining.remove(&v);
}
self.expanders.insert(id, expander);
}
}
fn grow_expander(&self, start: VertexId, available: &HashSet<VertexId>) -> HashSet<VertexId> {
let mut expander = HashSet::new();
let mut queue = VecDeque::new();
let mut volume = 0usize;
queue.push_back(start);
expander.insert(start);
volume += self.degree(start);
while let Some(v) = queue.pop_front() {
if expander.len() >= self.config.max_expander_size {
break;
}
for (neighbor, _) in self.neighbors(v) {
if !available.contains(&neighbor) || expander.contains(&neighbor) {
continue;
}
let new_volume = volume + self.degree(neighbor);
let boundary_after = self.count_boundary(&expander, &[neighbor]);
let expansion = if new_volume > 0 {
boundary_after as f64 / (new_volume.min(expander.len() * 2)) as f64
} else {
0.0
};
if expansion >= self.config.phi * 0.5
|| expander.len() < self.config.min_expander_size
{
expander.insert(neighbor);
volume = new_volume;
queue.push_back(neighbor);
}
}
}
expander
}
fn count_boundary(&self, vertices: &HashSet<VertexId>, additional: &[VertexId]) -> usize {
let mut full_set = vertices.clone();
for &v in additional {
full_set.insert(v);
}
let mut boundary = 0;
for &v in &full_set {
for (neighbor, _) in self.neighbors(v) {
if !full_set.contains(&neighbor) {
boundary += 1;
}
}
}
boundary
}
fn compute_expander_properties(&self, expander: &mut Expander) {
let mut internal = Vec::new();
let mut boundary = Vec::new();
let mut volume = 0;
for &v in &expander.vertices {
let neighbors = self.neighbors(v);
volume += neighbors.len();
for (neighbor, _) in neighbors {
if expander.vertices.contains(&neighbor) {
if v < neighbor {
internal.push((v, neighbor));
}
} else {
boundary.push((v, neighbor));
}
}
}
expander.internal_edges = internal;
expander.boundary_edges = boundary;
expander.volume = volume;
let min_vol = expander.volume.min(expander.vertices.len() * 2);
expander.expansion_ratio = if min_vol > 0 {
expander.boundary_edges.len() as f64 / min_vol as f64
} else {
0.0
};
}
fn build_preclusters(&mut self) {
self.preclusters.clear();
let expander_ids: Vec<_> = self.expanders.keys().copied().collect();
let mut assigned = HashSet::new();
for &exp_id in &expander_ids {
if assigned.contains(&exp_id) {
continue;
}
let id = self.next_id;
self.next_id += 1;
let mut precluster = Precluster::new(id);
if let Some(expander) = self.expanders.get_mut(&exp_id) {
precluster.add_expander(expander);
expander.precluster_id = Some(id);
assigned.insert(exp_id);
}
for &other_id in &expander_ids {
if assigned.contains(&other_id) {
continue;
}
if precluster.size() >= self.config.target_precluster_size {
break;
}
if self.expanders_adjacent(exp_id, other_id) {
if let Some(expander) = self.expanders.get_mut(&other_id) {
precluster.add_expander(expander);
expander.precluster_id = Some(id);
assigned.insert(other_id);
}
}
}
self.compute_precluster_boundary(&mut precluster);
self.preclusters.insert(id, precluster);
}
}
fn expanders_adjacent(&self, exp1: u64, exp2: u64) -> bool {
let e1 = match self.expanders.get(&exp1) {
Some(e) => e,
None => return false,
};
let e2 = match self.expanders.get(&exp2) {
Some(e) => e,
None => return false,
};
for &v in &e1.vertices {
for (neighbor, _) in self.neighbors(v) {
if e2.vertices.contains(&neighbor) {
return true;
}
}
}
false
}
fn compute_precluster_boundary(&self, precluster: &mut Precluster) {
precluster.boundary_edges.clear();
for &v in &precluster.vertices {
for (neighbor, _) in self.neighbors(v) {
if !precluster.vertices.contains(&neighbor) {
precluster.boundary_edges.push((v, neighbor));
}
}
}
}
fn build_clusters(&mut self) {
self.clusters.clear();
let precluster_ids: Vec<_> = self.preclusters.keys().copied().collect();
let mut assigned = HashSet::new();
for &pre_id in &precluster_ids {
if assigned.contains(&pre_id) {
continue;
}
let id = self.next_id;
self.next_id += 1;
let mut cluster = HierarchyCluster::new(id);
if let Some(precluster) = self.preclusters.get_mut(&pre_id) {
cluster.add_precluster(precluster);
precluster.cluster_id = Some(id);
assigned.insert(pre_id);
}
for &other_id in &precluster_ids {
if assigned.contains(&other_id) {
continue;
}
if self.preclusters_adjacent(pre_id, other_id) {
if let Some(precluster) = self.preclusters.get_mut(&other_id) {
cluster.add_precluster(precluster);
precluster.cluster_id = Some(id);
assigned.insert(other_id);
}
}
}
self.compute_cluster_boundary(&mut cluster);
self.clusters.insert(id, cluster);
}
}
fn preclusters_adjacent(&self, pre1: u64, pre2: u64) -> bool {
let p1 = match self.preclusters.get(&pre1) {
Some(p) => p,
None => return false,
};
let p2 = match self.preclusters.get(&pre2) {
Some(p) => p,
None => return false,
};
for &v in &p1.vertices {
for (neighbor, _) in self.neighbors(v) {
if p2.vertices.contains(&neighbor) {
return true;
}
}
}
false
}
fn compute_cluster_boundary(&self, cluster: &mut HierarchyCluster) {
cluster.boundary_edges.clear();
for &v in &cluster.vertices {
for (neighbor, _) in self.neighbors(v) {
if !cluster.vertices.contains(&neighbor) {
cluster.boundary_edges.push((v, neighbor));
}
}
}
}
fn compute_mirror_cuts(&mut self) {
let expander_ids: Vec<_> = self.expanders.keys().copied().collect();
for cluster in self.clusters.values_mut() {
cluster.mirror_cuts.clear();
}
for (i, &exp1) in expander_ids.iter().enumerate() {
for &exp2 in expander_ids.iter().skip(i + 1) {
if !self.expanders_adjacent(exp1, exp2) {
continue;
}
let (cut_value, cut_edges) = self.compute_expander_cut(exp1, exp2);
let mirror = MirrorCut {
source_expander: exp1,
target_expander: exp2,
cut_value,
cut_edges,
certified: false,
};
if let Some(pre_id) = self.expanders.get(&exp1).and_then(|e| e.precluster_id) {
if let Some(cluster_id) =
self.preclusters.get(&pre_id).and_then(|p| p.cluster_id)
{
if let Some(cluster) = self.clusters.get_mut(&cluster_id) {
cluster.mirror_cuts.push(mirror);
}
}
}
}
}
for cluster in self.clusters.values_mut() {
if let Some(min_mirror) = cluster
.mirror_cuts
.iter()
.map(|m| m.cut_value)
.min_by(|a, b| a.partial_cmp(b).unwrap())
{
cluster.internal_min_cut = cluster.internal_min_cut.min(min_mirror);
}
}
}
fn compute_expander_cut(&self, exp1: u64, exp2: u64) -> (f64, Vec<(VertexId, VertexId)>) {
let e1 = match self.expanders.get(&exp1) {
Some(e) => e,
None => return (0.0, Vec::new()),
};
let e2 = match self.expanders.get(&exp2) {
Some(e) => e,
None => return (0.0, Vec::new()),
};
let mut cut_edges = Vec::new();
let mut cut_value = 0.0;
for &v in &e1.vertices {
for (neighbor, weight) in self.neighbors(v) {
if e2.vertices.contains(&neighbor) {
cut_edges.push((v, neighbor));
cut_value += weight;
}
}
}
(cut_value, cut_edges)
}
fn update_global_min_cut(&mut self) {
let mut min_cut = f64::INFINITY;
for cluster in self.clusters.values() {
let boundary_cut: f64 = cluster
.boundary_edges
.iter()
.map(|&(u, v)| {
self.adjacency
.get(&u)
.and_then(|n| n.get(&v))
.copied()
.unwrap_or(1.0)
})
.sum();
min_cut = min_cut.min(boundary_cut);
min_cut = min_cut.min(cluster.internal_min_cut);
}
self.global_min_cut = min_cut;
}
pub fn handle_edge_insert(&mut self, u: VertexId, v: VertexId, weight: Weight) {
self.insert_edge(u, v, weight);
if self.expanders.is_empty() {
return;
}
let exp_u = self.vertex_expander.get(&u).copied();
let exp_v = self.vertex_expander.get(&v).copied();
match (exp_u, exp_v) {
(Some(eu), Some(ev)) if eu == ev => {
self.update_expander_edges(eu);
}
(Some(eu), Some(ev)) => {
self.update_expander_edges(eu);
self.update_expander_edges(ev);
self.update_mirror_cut_between(eu, ev);
}
(Some(eu), None) => {
self.try_add_vertex_to_expander(v, eu);
self.update_expander_edges(eu);
}
(None, Some(ev)) => {
self.try_add_vertex_to_expander(u, ev);
self.update_expander_edges(ev);
}
(None, None) => {
self.create_expander_for_new_vertices(&[u, v]);
}
}
self.propagate_updates(&[u, v]);
self.update_global_min_cut();
}
pub fn handle_edge_delete(&mut self, u: VertexId, v: VertexId) {
self.delete_edge(u, v);
if self.expanders.is_empty() {
return;
}
let exp_u = self.vertex_expander.get(&u).copied();
let exp_v = self.vertex_expander.get(&v).copied();
if let (Some(eu), Some(ev)) = (exp_u, exp_v) {
if eu == ev {
self.check_and_split_expander(eu);
} else {
self.update_expander_edges(eu);
self.update_expander_edges(ev);
self.update_mirror_cut_between(eu, ev);
}
}
self.propagate_updates(&[u, v]);
self.update_global_min_cut();
}
fn update_expander_edges(&mut self, exp_id: u64) {
let vertices = match self.expanders.get(&exp_id) {
Some(e) => e.vertices.clone(),
None => return,
};
let mut internal = Vec::new();
let mut boundary = Vec::new();
let mut volume = 0;
for &v in &vertices {
let neighbors = self.neighbors(v);
volume += neighbors.len();
for (neighbor, _) in neighbors {
if vertices.contains(&neighbor) {
if v < neighbor {
internal.push((v, neighbor));
}
} else {
boundary.push((v, neighbor));
}
}
}
if let Some(expander) = self.expanders.get_mut(&exp_id) {
expander.internal_edges = internal;
expander.boundary_edges = boundary;
expander.volume = volume;
let min_vol = volume.min(vertices.len() * 2);
expander.expansion_ratio = if min_vol > 0 {
expander.boundary_edges.len() as f64 / min_vol as f64
} else {
0.0
};
}
}
fn try_add_vertex_to_expander(&mut self, v: VertexId, exp_id: u64) {
if let Some(expander) = self.expanders.get(&exp_id) {
let new_volume = expander.volume + self.degree(v);
let new_boundary = self.count_boundary_with_vertex(exp_id, v);
let expansion = if new_volume > 0 {
new_boundary as f64 / new_volume as f64
} else {
0.0
};
if expansion >= self.config.phi * 0.5 {
if let Some(exp) = self.expanders.get_mut(&exp_id) {
exp.vertices.insert(v);
self.vertex_expander.insert(v, exp_id);
}
} else {
self.create_expander_for_new_vertices(&[v]);
}
}
}
fn count_boundary_with_vertex(&self, exp_id: u64, v: VertexId) -> usize {
let expander = match self.expanders.get(&exp_id) {
Some(e) => e,
None => return 0,
};
let mut extended = expander.vertices.clone();
extended.insert(v);
let mut boundary = 0;
for &u in &extended {
for (neighbor, _) in self.neighbors(u) {
if !extended.contains(&neighbor) {
boundary += 1;
}
}
}
boundary
}
fn create_expander_for_new_vertices(&mut self, vertices: &[VertexId]) {
let id = self.next_id;
self.next_id += 1;
let vertex_set: HashSet<_> = vertices.iter().copied().collect();
let mut expander = Expander::new(id, vertex_set);
self.compute_expander_properties(&mut expander);
for &v in vertices {
self.vertex_expander.insert(v, id);
}
self.expanders.insert(id, expander);
self.assign_expander_to_precluster(id);
}
fn assign_expander_to_precluster(&mut self, exp_id: u64) {
for (pre_id, precluster) in &self.preclusters {
for &other_exp in &precluster.expanders {
if self.expanders_adjacent(exp_id, other_exp) {
if let Some(exp) = self.expanders.get_mut(&exp_id) {
exp.precluster_id = Some(*pre_id);
}
return;
}
}
}
let id = self.next_id;
self.next_id += 1;
let mut precluster = Precluster::new(id);
if let Some(expander) = self.expanders.get_mut(&exp_id) {
precluster.add_expander(expander);
expander.precluster_id = Some(id);
}
self.compute_precluster_boundary(&mut precluster);
self.preclusters.insert(id, precluster);
self.assign_precluster_to_cluster(id);
}
fn assign_precluster_to_cluster(&mut self, pre_id: u64) {
for (cluster_id, cluster) in &self.clusters {
for &other_pre in &cluster.preclusters {
if self.preclusters_adjacent(pre_id, other_pre) {
if let Some(pre) = self.preclusters.get_mut(&pre_id) {
pre.cluster_id = Some(*cluster_id);
}
return;
}
}
}
let id = self.next_id;
self.next_id += 1;
let mut cluster = HierarchyCluster::new(id);
if let Some(precluster) = self.preclusters.get_mut(&pre_id) {
cluster.add_precluster(precluster);
precluster.cluster_id = Some(id);
}
self.compute_cluster_boundary(&mut cluster);
self.clusters.insert(id, cluster);
}
fn check_and_split_expander(&mut self, exp_id: u64) {
if let Some(expander) = self.expanders.get(&exp_id) {
if expander.expansion_ratio < self.config.phi * 0.3 {
self.update_expander_edges(exp_id);
}
}
}
fn update_mirror_cut_between(&mut self, exp1: u64, exp2: u64) {
let (cut_value, cut_edges) = self.compute_expander_cut(exp1, exp2);
let cluster_id = self
.expanders
.get(&exp1)
.and_then(|e| e.precluster_id)
.and_then(|pid| self.preclusters.get(&pid))
.and_then(|p| p.cluster_id);
if let Some(cid) = cluster_id {
if let Some(cluster) = self.clusters.get_mut(&cid) {
let found = cluster.mirror_cuts.iter_mut().find(|m| {
(m.source_expander == exp1 && m.target_expander == exp2)
|| (m.source_expander == exp2 && m.target_expander == exp1)
});
if let Some(mirror) = found {
mirror.cut_value = cut_value;
mirror.cut_edges = cut_edges;
} else if !cut_edges.is_empty() {
cluster.mirror_cuts.push(MirrorCut {
source_expander: exp1,
target_expander: exp2,
cut_value,
cut_edges,
certified: false,
});
}
if let Some(min_mirror) = cluster
.mirror_cuts
.iter()
.map(|m| m.cut_value)
.min_by(|a, b| a.partial_cmp(b).unwrap())
{
cluster.internal_min_cut = min_mirror;
}
}
}
}
fn propagate_updates(&mut self, vertices: &[VertexId]) {
let mut affected_expanders = HashSet::new();
for &v in vertices {
if let Some(&exp_id) = self.vertex_expander.get(&v) {
affected_expanders.insert(exp_id);
}
}
let mut affected_preclusters = HashSet::new();
for &exp_id in &affected_expanders {
if let Some(expander) = self.expanders.get(&exp_id) {
if let Some(pre_id) = expander.precluster_id {
affected_preclusters.insert(pre_id);
}
}
}
for &pre_id in &affected_preclusters {
if let Some(precluster) = self.preclusters.get_mut(&pre_id) {
precluster.vertices.clear();
precluster.volume = 0;
for &exp_id in &precluster.expanders {
if let Some(expander) = self.expanders.get(&exp_id) {
precluster.vertices.extend(&expander.vertices);
precluster.volume += expander.volume;
}
}
}
let precluster = self.preclusters.get(&pre_id).cloned();
if let Some(mut pre) = precluster {
self.compute_precluster_boundary(&mut pre);
self.preclusters.insert(pre_id, pre);
}
}
let mut affected_clusters = HashSet::new();
for &pre_id in &affected_preclusters {
if let Some(precluster) = self.preclusters.get(&pre_id) {
if let Some(cluster_id) = precluster.cluster_id {
affected_clusters.insert(cluster_id);
}
}
}
for &cluster_id in &affected_clusters {
if let Some(cluster) = self.clusters.get_mut(&cluster_id) {
cluster.vertices.clear();
for &pre_id in &cluster.preclusters {
if let Some(precluster) = self.preclusters.get(&pre_id) {
cluster.vertices.extend(&precluster.vertices);
}
}
}
let cluster = self.clusters.get(&cluster_id).cloned();
if let Some(mut c) = cluster {
self.compute_cluster_boundary(&mut c);
self.clusters.insert(cluster_id, c);
}
}
}
pub fn certify_mirror_cuts(&mut self) {
use crate::localkcut::deterministic::DeterministicLocalKCut;
let mut certifications: Vec<(u64, usize, bool)> = Vec::new();
for (cluster_id, cluster) in &self.clusters {
for (mirror_idx, mirror) in cluster.mirror_cuts.iter().enumerate() {
let exp1 = match self.expanders.get(&mirror.source_expander) {
Some(e) => e.vertices.clone(),
None => continue,
};
let exp2 = match self.expanders.get(&mirror.target_expander) {
Some(e) => e.vertices.clone(),
None => continue,
};
let combined: HashSet<_> = exp1.union(&exp2).copied().collect();
let lambda_max = (mirror.cut_value.ceil() as u64).max(1) * 2;
let volume = combined.len() * 10;
let mut lkc = DeterministicLocalKCut::new(lambda_max, volume, 2);
for &u in &combined {
for (v, w) in self.neighbors(u) {
if combined.contains(&v) && u < v {
lkc.insert_edge(u, v, w);
}
}
}
let mut min_verified_cut = f64::INFINITY;
let boundary_verts: Vec<_> = mirror.cut_edges.iter().map(|(u, _)| *u).collect();
for v in boundary_verts {
let cuts = lkc.query(v);
for cut in cuts {
let cut_separates = {
let in_exp1 = cut.vertices.iter().any(|&u| exp1.contains(&u));
let in_exp2 = cut.vertices.iter().any(|&u| exp2.contains(&u));
let not_in_exp1 = exp1.iter().any(|&u| !cut.vertices.contains(&u));
let not_in_exp2 = exp2.iter().any(|&u| !cut.vertices.contains(&u));
(in_exp1 && not_in_exp2) || (in_exp2 && not_in_exp1)
};
if cut_separates {
min_verified_cut = min_verified_cut.min(cut.cut_value);
}
}
}
let certified = if min_verified_cut < f64::INFINITY {
let diff = (mirror.cut_value - min_verified_cut).abs();
diff < 0.001 || min_verified_cut <= mirror.cut_value
} else {
true
};
certifications.push((*cluster_id, mirror_idx, certified));
}
}
for (cluster_id, mirror_idx, certified) in certifications {
if let Some(cluster) = self.clusters.get_mut(&cluster_id) {
if let Some(mirror) = cluster.mirror_cuts.get_mut(mirror_idx) {
mirror.certified = certified;
}
}
}
}
pub fn num_certified_mirror_cuts(&self) -> usize {
self.clusters
.values()
.flat_map(|c| &c.mirror_cuts)
.filter(|m| m.certified)
.count()
}
pub fn num_mirror_cuts(&self) -> usize {
self.clusters.values().map(|c| c.mirror_cuts.len()).sum()
}
pub fn get_vertex_expander(&self, v: VertexId) -> Option<&Expander> {
self.vertex_expander
.get(&v)
.and_then(|&id| self.expanders.get(&id))
}
pub fn get_expanders(&self) -> &HashMap<u64, Expander> {
&self.expanders
}
pub fn get_preclusters(&self) -> &HashMap<u64, Precluster> {
&self.preclusters
}
pub fn get_clusters(&self) -> &HashMap<u64, HierarchyCluster> {
&self.clusters
}
pub fn stats(&self) -> HierarchyStats {
HierarchyStats {
num_expanders: self.expanders.len(),
num_preclusters: self.preclusters.len(),
num_clusters: self.clusters.len(),
num_vertices: self.adjacency.len(),
num_edges: self.adjacency.values().map(|n| n.len()).sum::<usize>() / 2,
global_min_cut: self.global_min_cut,
avg_expander_size: if self.expanders.is_empty() {
0.0
} else {
self.expanders.values().map(|e| e.size()).sum::<usize>() as f64
/ self.expanders.len() as f64
},
}
}
}
impl Default for ThreeLevelHierarchy {
fn default() -> Self {
Self::with_defaults()
}
}
#[derive(Debug, Clone)]
pub struct HierarchyStats {
pub num_expanders: usize,
pub num_preclusters: usize,
pub num_clusters: usize,
pub num_vertices: usize,
pub num_edges: usize,
pub global_min_cut: f64,
pub avg_expander_size: f64,
}
#[cfg(test)]
mod tests {
use super::*;
fn build_path(h: &mut ThreeLevelHierarchy, n: usize) {
for i in 0..n - 1 {
h.insert_edge(i as u64, (i + 1) as u64, 1.0);
}
}
fn build_clique(h: &mut ThreeLevelHierarchy, vertices: &[u64]) {
for i in 0..vertices.len() {
for j in i + 1..vertices.len() {
h.insert_edge(vertices[i], vertices[j], 1.0);
}
}
}
#[test]
fn test_hierarchy_empty() {
let mut h = ThreeLevelHierarchy::with_defaults();
h.build();
assert_eq!(h.expanders.len(), 0);
assert_eq!(h.preclusters.len(), 0);
assert_eq!(h.clusters.len(), 0);
}
#[test]
fn test_hierarchy_path() {
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
min_expander_size: 2,
max_expander_size: 5,
..Default::default()
});
build_path(&mut h, 10);
h.build();
assert!(h.expanders.len() >= 1);
assert!(h.preclusters.len() >= 1);
assert!(h.clusters.len() >= 1);
let stats = h.stats();
assert_eq!(stats.num_vertices, 10);
assert_eq!(stats.num_edges, 9);
}
#[test]
fn test_hierarchy_clique() {
let mut h = ThreeLevelHierarchy::with_defaults();
build_clique(&mut h, &[1, 2, 3, 4, 5]);
h.build();
assert!(h.expanders.len() >= 1);
for v in 1..=5 {
assert!(h.get_vertex_expander(v).is_some());
}
}
#[test]
fn test_hierarchy_two_cliques() {
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
min_expander_size: 2,
..Default::default()
});
build_clique(&mut h, &[1, 2, 3, 4]);
build_clique(&mut h, &[5, 6, 7, 8]);
h.insert_edge(4, 5, 1.0);
h.build();
assert!(h.expanders.len() >= 1);
assert!(h.global_min_cut <= 2.0);
}
#[test]
fn test_mirror_cuts() {
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
track_mirror_cuts: true,
min_expander_size: 2,
..Default::default()
});
build_clique(&mut h, &[1, 2, 3]);
build_clique(&mut h, &[4, 5, 6]);
h.insert_edge(3, 4, 2.0);
h.insert_edge(2, 5, 1.0);
h.build();
let stats = h.stats();
assert!(stats.num_expanders >= 1);
}
#[test]
fn test_expander_properties() {
let mut h = ThreeLevelHierarchy::with_defaults();
build_clique(&mut h, &[1, 2, 3, 4]);
h.build();
for expander in h.expanders.values() {
assert!(expander.size() > 0);
assert!(expander.volume > 0);
}
}
#[test]
fn test_stats() {
let mut h = ThreeLevelHierarchy::with_defaults();
build_path(&mut h, 5);
h.build();
let stats = h.stats();
assert_eq!(stats.num_vertices, 5);
assert_eq!(stats.num_edges, 4);
assert!(stats.avg_expander_size > 0.0);
}
#[test]
fn test_incremental_insert() {
let mut h = ThreeLevelHierarchy::with_defaults();
build_clique(&mut h, &[1, 2, 3, 4]);
h.build();
let initial_expanders = h.expanders.len();
h.handle_edge_insert(4, 5, 1.0);
assert!(h.expanders.len() >= initial_expanders);
let stats = h.stats();
assert!(stats.num_vertices >= 5);
assert!(stats.num_edges >= 7);
}
#[test]
fn test_incremental_delete() {
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
min_expander_size: 2,
..Default::default()
});
build_clique(&mut h, &[1, 2, 3]);
build_clique(&mut h, &[4, 5, 6]);
h.insert_edge(3, 4, 1.0); h.build();
let initial_min_cut = h.global_min_cut;
h.handle_edge_delete(3, 4);
assert!(h.global_min_cut <= initial_min_cut || h.global_min_cut.is_infinite());
}
#[test]
fn test_incremental_within_expander() {
let mut h = ThreeLevelHierarchy::with_defaults();
build_clique(&mut h, &[1, 2, 3, 4]);
h.build();
let initial_expanders = h.expanders.len();
h.handle_edge_insert(1, 4, 2.0);
assert_eq!(h.expanders.len(), initial_expanders);
}
#[test]
fn test_propagate_updates() {
let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
min_expander_size: 2,
..Default::default()
});
build_clique(&mut h, &[1, 2, 3]);
h.build();
h.handle_edge_insert(3, 4, 1.0);
h.handle_edge_insert(4, 5, 1.0);
assert!(h.expanders.len() >= 1);
assert!(h.preclusters.len() >= 1);
assert!(h.clusters.len() >= 1);
}
}