#[cfg(test)]
mod tests;
pub mod source_anchored;
pub mod tree_packing;
pub mod dynamic;
use crate::algorithm::{self, MinCutConfig};
use crate::graph::{DynamicGraph, VertexId, Weight};
use crate::time_compat::PortableTimestamp;
use std::collections::{BTreeSet, HashMap, HashSet, VecDeque};
use std::hash::{Hash, Hasher};
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub struct FixedWeight(u64);
impl FixedWeight {
const FRAC_BITS: u32 = 32;
#[must_use]
pub fn from_f64(val: f64) -> Self {
let clamped = if val < 0.0 { 0.0 } else { val };
let scaled = clamped * (1u64 << Self::FRAC_BITS) as f64;
Self(scaled as u64)
}
#[must_use]
pub fn to_f64(self) -> f64 {
self.0 as f64 / (1u64 << Self::FRAC_BITS) as f64
}
#[must_use]
pub fn add(self, other: Self) -> Self {
Self(self.0.saturating_add(other.0))
}
#[must_use]
pub fn sub(self, other: Self) -> Self {
Self(self.0.saturating_sub(other.0))
}
#[must_use]
pub fn zero() -> Self {
Self(0)
}
#[must_use]
pub fn raw(self) -> u64 {
self.0
}
}
impl std::fmt::Display for FixedWeight {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:.6}", self.to_f64())
}
}
#[derive(Debug, Clone)]
pub struct CactusVertex {
pub id: u16,
pub original_vertices: Vec<usize>,
pub parent: Option<u16>,
}
#[derive(Debug, Clone)]
pub struct CactusEdge {
pub source: u16,
pub target: u16,
pub weight: FixedWeight,
pub is_cycle_edge: bool,
}
#[derive(Debug, Clone)]
pub struct CactusCycle {
pub vertices: Vec<u16>,
pub edges: Vec<usize>,
}
#[derive(Debug, Clone)]
pub struct CactusGraph {
pub vertices: Vec<CactusVertex>,
pub edges: Vec<CactusEdge>,
pub cycles: Vec<CactusCycle>,
pub vertex_map: HashMap<usize, u16>,
pub root: u16,
pub n_vertices: u16,
pub n_edges: u16,
}
impl CactusGraph {
pub fn build_from_graph(graph: &DynamicGraph) -> Self {
let vertices_ids = graph.vertices();
let edges_list = graph.edges();
if vertices_ids.is_empty() {
return Self::empty();
}
if vertices_ids.len() == 1 {
return Self::singleton(vertices_ids[0] as usize);
}
let mut adj: HashMap<usize, HashMap<usize, f64>> = HashMap::new();
for &v in &vertices_ids {
adj.entry(v as usize).or_default();
}
for e in &edges_list {
*adj.entry(e.source as usize)
.or_default()
.entry(e.target as usize)
.or_insert(0.0) += e.weight;
*adj.entry(e.target as usize)
.or_default()
.entry(e.source as usize)
.or_insert(0.0) += e.weight;
}
let (min_cut_value, min_cut_partitions) = Self::stoer_wagner_all_cuts(&adj);
Self::build_cactus_from_cuts(&vertices_ids, &adj, min_cut_value, &min_cut_partitions)
}
pub fn root_at_lex_smallest(&mut self) {
if self.vertices.is_empty() {
return;
}
let mut best_cactus_id = self.vertices[0].id;
let mut best_orig = usize::MAX;
for cv in &self.vertices {
for &orig in &cv.original_vertices {
if orig < best_orig {
best_orig = orig;
best_cactus_id = cv.id;
}
}
}
if best_cactus_id == self.root {
return; }
self.root = best_cactus_id;
let adj = self.adjacency_list();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(self.root);
visited.insert(self.root);
for cv in &mut self.vertices {
cv.parent = None;
}
while let Some(u) = queue.pop_front() {
if let Some(neighbors) = adj.get(&u) {
for &v in neighbors {
if visited.insert(v) {
if let Some(cv) = self.vertices.iter_mut().find(|c| c.id == v) {
cv.parent = Some(u);
}
queue.push_back(v);
}
}
}
}
}
pub fn canonical_cut(&self) -> CanonicalCutResult {
let all_cuts = self.enumerate_min_cuts();
if all_cuts.is_empty() {
return CanonicalCutResult {
value: f64::INFINITY,
partition: (Vec::new(), Vec::new()),
cut_edges: Vec::new(),
canonical_key: [0u8; 32],
};
}
let mut best: Option<(Vec<usize>, Vec<usize>)> = None;
for (mut s, mut t) in all_cuts {
s.sort_unstable();
t.sort_unstable();
if s.len() > t.len() || (s.len() == t.len() && s > t) {
std::mem::swap(&mut s, &mut t);
}
if let Some((ref bs, _)) = best {
if s < *bs {
best = Some((s, t));
}
} else {
best = Some((s, t));
}
}
let (part_s, part_t) = best.unwrap();
let cut_value = self.compute_cut_value_from_partition(&part_s);
let cut_edges = self.compute_cut_edges(&part_s);
let canonical_key = Self::compute_canonical_key(&part_s);
CanonicalCutResult {
value: cut_value,
partition: (part_s, part_t),
cut_edges,
canonical_key,
}
}
pub fn enumerate_min_cuts(&self) -> Vec<(Vec<usize>, Vec<usize>)> {
let mut result = Vec::new();
if self.vertices.is_empty() {
return result;
}
let adj = self.adjacency_list();
for (idx, edge) in self.edges.iter().enumerate() {
if edge.is_cycle_edge {
continue;
}
let (side_a, side_b) = self.split_at_edge(edge.source, edge.target, &adj);
let orig_a = self.collect_original_vertices(&side_a);
let orig_b = self.collect_original_vertices(&side_b);
if !orig_a.is_empty() && !orig_b.is_empty() {
result.push((orig_a, orig_b));
}
}
for cycle in &self.cycles {
for &edge_idx in &cycle.edges {
if edge_idx >= self.edges.len() {
continue;
}
let e = &self.edges[edge_idx];
let (side_a, side_b) = self.split_at_edge(e.source, e.target, &adj);
let orig_a = self.collect_original_vertices(&side_a);
let orig_b = self.collect_original_vertices(&side_b);
if !orig_a.is_empty() && !orig_b.is_empty() {
result.push((orig_a, orig_b));
}
}
}
if result.is_empty() && self.vertices.len() >= 2 {
let all_orig: Vec<usize> = self
.vertices
.iter()
.flat_map(|v| v.original_vertices.iter().copied())
.collect();
if all_orig.len() >= 2 {
result.push((vec![all_orig[0]], all_orig[1..].to_vec()));
}
}
result
}
fn empty() -> Self {
Self {
vertices: Vec::new(),
edges: Vec::new(),
cycles: Vec::new(),
vertex_map: HashMap::new(),
root: 0,
n_vertices: 0,
n_edges: 0,
}
}
fn singleton(v: usize) -> Self {
let cv = CactusVertex {
id: 0,
original_vertices: vec![v],
parent: None,
};
let mut vertex_map = HashMap::new();
vertex_map.insert(v, 0);
Self {
vertices: vec![cv],
edges: Vec::new(),
cycles: Vec::new(),
vertex_map,
root: 0,
n_vertices: 1,
n_edges: 0,
}
}
fn stoer_wagner_all_cuts(
adj: &HashMap<usize, HashMap<usize, f64>>,
) -> (f64, Vec<(Vec<usize>, Vec<usize>)>) {
let n = adj.len();
if n <= 1 {
return (f64::INFINITY, Vec::new());
}
let node_ids: Vec<usize> = {
let mut v: Vec<usize> = adj.keys().copied().collect();
v.sort_unstable();
v
};
let max_id = *node_ids.last().unwrap();
let mut id_to_idx = vec![usize::MAX; max_id + 1];
for (i, &nid) in node_ids.iter().enumerate() {
id_to_idx[nid] = i;
}
let mut w: Vec<f64> = vec![0.0; n * n];
for (&u, nbrs) in adj {
let ui = id_to_idx[u];
let row = ui * n;
for (&v, &wt) in nbrs {
let vi = id_to_idx[v];
w[row + vi] = wt;
}
}
let mut merged: Vec<Vec<usize>> = node_ids.iter().map(|&v| vec![v]).collect();
let mut active_list: Vec<usize> = (0..n).collect();
let mut active_pos: Vec<usize> = (0..n).collect();
let mut n_active = n;
let mut global_min = f64::INFINITY;
let mut best_partitions: Vec<(Vec<usize>, Vec<usize>)> = Vec::new();
let mut key: Vec<f64> = vec![0.0; n];
let mut in_a: Vec<bool> = vec![false; n];
for _phase in 0..(n - 1) {
if n_active <= 1 {
break;
}
for k in 0..n_active {
let j = active_list[k];
in_a[j] = false;
key[j] = 0.0;
}
let first = active_list[0];
in_a[first] = true;
let first_row = first * n;
for k in 0..n_active {
let j = active_list[k];
key[j] = w[first_row + j];
}
let mut prev = first;
let mut last = first;
for _step in 1..n_active {
let mut best = usize::MAX;
let mut best_key = -1.0f64;
for k in 0..n_active {
let j = active_list[k];
if !in_a[j] && key[j] > best_key {
best_key = key[j];
best = j;
}
}
if best == usize::MAX {
break;
}
in_a[best] = true;
prev = last;
last = best;
let best_row = best * n;
for k in 0..n_active {
let j = active_list[k];
if !in_a[j] {
key[j] += w[best_row + j];
}
}
}
let cut_value = key[last];
if cut_value < global_min - 1e-12 {
global_min = cut_value;
best_partitions.clear();
let part_s: Vec<usize> = merged[last].clone();
let part_t: Vec<usize> = (0..n_active)
.map(|k| active_list[k])
.filter(|&i| i != last)
.flat_map(|i| merged[i].iter().copied())
.collect();
best_partitions.push((part_s, part_t));
} else if (cut_value - global_min).abs() < 1e-12 {
let part_s: Vec<usize> = merged[last].clone();
let part_t: Vec<usize> = (0..n_active)
.map(|k| active_list[k])
.filter(|&i| i != last)
.flat_map(|i| merged[i].iter().copied())
.collect();
best_partitions.push((part_s, part_t));
}
let last_merged = std::mem::take(&mut merged[last]);
merged[prev].extend(last_merged);
let prev_row = prev * n;
let last_row = last * n;
for k in 0..n_active {
let j = active_list[k];
if j != last {
w[prev_row + j] += w[last_row + j];
w[j * n + prev] += w[j * n + last];
}
}
let pos = active_pos[last];
n_active -= 1;
if pos < n_active {
let swapped = active_list[n_active];
active_list[pos] = swapped;
active_pos[swapped] = pos;
}
active_list.truncate(n_active);
}
(global_min, best_partitions)
}
fn build_cactus_from_cuts(
vertices_ids: &[VertexId],
adj: &HashMap<usize, HashMap<usize, f64>>,
min_cut_value: f64,
partitions: &[(Vec<usize>, Vec<usize>)],
) -> Self {
if partitions.is_empty() {
let all: Vec<usize> = vertices_ids.iter().map(|&v| v as usize).collect();
let cv = CactusVertex {
id: 0,
original_vertices: all.clone(),
parent: None,
};
let mut vertex_map = HashMap::new();
for &v in &all {
vertex_map.insert(v, 0);
}
return Self {
vertices: vec![cv],
edges: Vec::new(),
cycles: Vec::new(),
vertex_map,
root: 0,
n_vertices: 1,
n_edges: 0,
};
}
let all_verts: BTreeSet<usize> = vertices_ids.iter().map(|&v| v as usize).collect();
let partition_sets: Vec<HashSet<usize>> = partitions
.iter()
.map(|(side_a, _)| side_a.iter().copied().collect())
.collect();
let mut signatures: HashMap<usize, Vec<bool>> = HashMap::new();
for &v in &all_verts {
let mut sig = Vec::with_capacity(partitions.len());
for set in &partition_sets {
sig.push(set.contains(&v));
}
signatures.insert(v, sig);
}
let mut groups: HashMap<Vec<bool>, Vec<usize>> = HashMap::new();
for (v, sig) in &signatures {
groups.entry(sig.clone()).or_default().push(*v);
}
for g in groups.values_mut() {
g.sort_unstable();
}
let mut cactus_vertices: Vec<CactusVertex> = Vec::new();
let mut vertex_map: HashMap<usize, u16> = HashMap::new();
let mut sorted_groups: Vec<Vec<usize>> = groups.values().cloned().collect();
sorted_groups.sort_by(|a, b| a.first().cmp(&b.first()));
for (i, group) in sorted_groups.iter().enumerate() {
let cid = i as u16;
cactus_vertices.push(CactusVertex {
id: cid,
original_vertices: group.clone(),
parent: None,
});
for &v in group {
vertex_map.insert(v, cid);
}
}
let n_cactus = cactus_vertices.len() as u16;
let mut cactus_edges: Vec<CactusEdge> = Vec::new();
let mut edge_set: HashSet<(u16, u16)> = HashSet::new();
for i in 0..cactus_vertices.len() {
for j in (i + 1)..cactus_vertices.len() {
let ci = cactus_vertices[i].id;
let cj = cactus_vertices[j].id;
let mut separates = false;
for set in &partition_sets {
let i_in_a = set.contains(&cactus_vertices[i].original_vertices[0]);
let j_in_a = set.contains(&cactus_vertices[j].original_vertices[0]);
if i_in_a != j_in_a {
separates = true;
break;
}
}
if !separates {
continue;
}
let mut crossing = 0.0f64;
for &u in &cactus_vertices[i].original_vertices {
if let Some(nbrs) = adj.get(&u) {
for &v in &cactus_vertices[j].original_vertices {
if let Some(&w) = nbrs.get(&v) {
crossing += w;
}
}
}
}
if crossing > 0.0 {
let key = if ci < cj { (ci, cj) } else { (cj, ci) };
if edge_set.insert(key) {
cactus_edges.push(CactusEdge {
source: ci,
target: cj,
weight: FixedWeight::from_f64(crossing),
is_cycle_edge: false,
});
}
}
}
}
let n_edges = cactus_edges.len() as u16;
let cycles = Self::detect_cycles(&cactus_vertices, &mut cactus_edges);
let mut cactus = Self {
vertices: cactus_vertices,
edges: cactus_edges,
cycles,
vertex_map,
root: 0,
n_vertices: n_cactus,
n_edges,
};
cactus.root_at_lex_smallest();
cactus
}
fn detect_cycles(vertices: &[CactusVertex], edges: &mut [CactusEdge]) -> Vec<CactusCycle> {
if vertices.is_empty() || edges.is_empty() {
return Vec::new();
}
let mut adj: HashMap<u16, Vec<(u16, usize)>> = HashMap::new();
for (idx, e) in edges.iter().enumerate() {
adj.entry(e.source).or_default().push((e.target, idx));
adj.entry(e.target).or_default().push((e.source, idx));
}
let mut cycles = Vec::new();
let mut visited = HashSet::new();
let mut parent: HashMap<u16, u16> = HashMap::new();
let mut parent_edge: HashMap<u16, usize> = HashMap::new();
let mut stack: Vec<(u16, Option<u16>)> = Vec::new();
if let Some(start) = vertices.first() {
stack.push((start.id, None));
}
while let Some((u, from)) = stack.pop() {
if visited.contains(&u) {
continue;
}
visited.insert(u);
if let Some(p) = from {
parent.insert(u, p);
}
if let Some(neighbors) = adj.get(&u) {
for &(v, edge_idx) in neighbors {
if !visited.contains(&v) {
parent_edge.insert(v, edge_idx);
stack.push((v, Some(u)));
} else if from != Some(v) {
let mut cycle_verts = vec![u, v];
let mut cycle_edges = vec![edge_idx];
let mut cur = u;
while cur != v {
if let Some(&p) = parent.get(&cur) {
if let Some(&pe) = parent_edge.get(&cur) {
cycle_edges.push(pe);
}
if p != v {
cycle_verts.push(p);
}
cur = p;
} else {
break;
}
}
for &ei in &cycle_edges {
if ei < edges.len() {
edges[ei].is_cycle_edge = true;
}
}
cycles.push(CactusCycle {
vertices: cycle_verts,
edges: cycle_edges,
});
}
}
}
}
cycles
}
fn adjacency_list(&self) -> HashMap<u16, Vec<u16>> {
let mut adj: HashMap<u16, Vec<u16>> = HashMap::new();
for cv in &self.vertices {
adj.entry(cv.id).or_default();
}
for e in &self.edges {
adj.entry(e.source).or_default().push(e.target);
adj.entry(e.target).or_default().push(e.source);
}
adj
}
fn split_at_edge(
&self,
u: u16,
v: u16,
adj: &HashMap<u16, Vec<u16>>,
) -> (HashSet<u16>, HashSet<u16>) {
let mut side_u: HashSet<u16> = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(u);
side_u.insert(u);
while let Some(cur) = queue.pop_front() {
if let Some(neighbors) = adj.get(&cur) {
for &next in neighbors {
if side_u.contains(&next) {
continue;
}
if (cur == u && next == v) || (cur == v && next == u) {
continue;
}
side_u.insert(next);
queue.push_back(next);
}
}
}
let side_v: HashSet<u16> = self
.vertices
.iter()
.map(|cv| cv.id)
.filter(|id| !side_u.contains(id))
.collect();
(side_u, side_v)
}
fn collect_original_vertices(&self, cactus_ids: &HashSet<u16>) -> Vec<usize> {
let mut result: Vec<usize> = self
.vertices
.iter()
.filter(|cv| cactus_ids.contains(&cv.id))
.flat_map(|cv| cv.original_vertices.iter().copied())
.collect();
result.sort_unstable();
result
}
fn compute_cut_value_from_partition(&self, part_s: &[usize]) -> f64 {
let s_set: HashSet<usize> = part_s.iter().copied().collect();
let id_map: HashMap<u16, usize> = self
.vertices
.iter()
.enumerate()
.map(|(i, cv)| (cv.id, i))
.collect();
let mut total = 0.0f64;
for e in &self.edges {
let src_in_s = id_map
.get(&e.source)
.map(|&i| {
self.vertices[i]
.original_vertices
.iter()
.any(|v| s_set.contains(v))
})
.unwrap_or(false);
let tgt_in_s = id_map
.get(&e.target)
.map(|&i| {
self.vertices[i]
.original_vertices
.iter()
.any(|v| s_set.contains(v))
})
.unwrap_or(false);
if src_in_s != tgt_in_s {
total += e.weight.to_f64();
}
}
total
}
fn compute_cut_edges(&self, part_s: &[usize]) -> Vec<(usize, usize, f64)> {
let s_set: HashSet<usize> = part_s.iter().copied().collect();
let id_map: HashMap<u16, usize> = self
.vertices
.iter()
.enumerate()
.map(|(i, cv)| (cv.id, i))
.collect();
let mut cut_edges = Vec::new();
for e in &self.edges {
let src_idx = id_map.get(&e.source).copied();
let tgt_idx = id_map.get(&e.target).copied();
let src_in_s = src_idx
.map(|i| {
self.vertices[i]
.original_vertices
.iter()
.any(|v| s_set.contains(v))
})
.unwrap_or(false);
let tgt_in_s = tgt_idx
.map(|i| {
self.vertices[i]
.original_vertices
.iter()
.any(|v| s_set.contains(v))
})
.unwrap_or(false);
if src_in_s != tgt_in_s {
let su = src_idx.and_then(|i| self.vertices[i].original_vertices.first().copied());
let tv = tgt_idx.and_then(|i| self.vertices[i].original_vertices.first().copied());
if let (Some(su), Some(tv)) = (su, tv) {
cut_edges.push((su, tv, e.weight.to_f64()));
}
}
}
cut_edges
}
fn compute_canonical_key(partition: &[usize]) -> [u8; 32] {
let mut sorted = partition.to_vec();
sorted.sort_unstable();
let mut hasher = std::collections::hash_map::DefaultHasher::new();
sorted.hash(&mut hasher);
let hash1 = hasher.finish();
let mut hasher2 = std::collections::hash_map::DefaultHasher::new();
sorted.len().hash(&mut hasher2);
for &v in &sorted {
v.hash(&mut hasher2);
}
let hash2 = hasher2.finish();
let mut key = [0u8; 32];
key[0..8].copy_from_slice(&hash1.to_le_bytes());
key[8..16].copy_from_slice(&hash2.to_le_bytes());
let mixed1 = hash1.wrapping_mul(0x517cc1b727220a95) ^ hash2;
let mixed2 = hash2.wrapping_mul(0x6c62272e07bb0142) ^ hash1;
key[16..24].copy_from_slice(&mixed1.to_le_bytes());
key[24..32].copy_from_slice(&mixed2.to_le_bytes());
key
}
}
#[derive(Debug, Clone)]
pub struct CanonicalCutResult {
pub value: f64,
pub partition: (Vec<usize>, Vec<usize>),
pub cut_edges: Vec<(usize, usize, f64)>,
pub canonical_key: [u8; 32],
}
#[derive(Debug, Clone)]
pub struct WitnessReceipt {
pub epoch: u64,
pub cut_hash: [u8; 32],
pub cut_value: f64,
pub edge_count: usize,
pub timestamp_ns: u64,
}
pub trait CanonicalMinCut {
fn canonical_cut(&self) -> CanonicalCutResult;
fn cactus_graph(&self) -> CactusGraph;
fn witness_receipt(&self) -> WitnessReceipt;
fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> crate::Result<f64>;
fn delete_edge(&mut self, u: VertexId, v: VertexId) -> crate::Result<f64>;
fn min_cut_value(&self) -> f64;
fn num_vertices(&self) -> usize;
fn num_edges(&self) -> usize;
fn is_connected(&self) -> bool;
}
pub struct CanonicalMinCutImpl {
inner: algorithm::DynamicMinCut,
cactus: Option<CactusGraph>,
epoch: u64,
dirty: bool,
}
impl CanonicalMinCutImpl {
pub fn from_dynamic(inner: algorithm::DynamicMinCut) -> Self {
Self {
inner,
cactus: None,
epoch: 0,
dirty: true,
}
}
pub fn new() -> Self {
Self {
inner: algorithm::DynamicMinCut::new(MinCutConfig::default()),
cactus: None,
epoch: 0,
dirty: true,
}
}
pub fn with_edges(edges: Vec<(VertexId, VertexId, Weight)>) -> crate::Result<Self> {
let inner = algorithm::MinCutBuilder::new()
.exact()
.with_edges(edges)
.build()?;
Ok(Self {
inner,
cactus: None,
epoch: 0,
dirty: true,
})
}
fn ensure_cactus(&mut self) {
if !self.dirty && self.cactus.is_some() {
return;
}
let graph = self.inner.graph();
let g = graph.read();
let mut cactus = CactusGraph::build_from_graph(&g);
drop(g);
cactus.root_at_lex_smallest();
self.cactus = Some(cactus);
self.dirty = false;
}
}
impl Default for CanonicalMinCutImpl {
fn default() -> Self {
Self::new()
}
}
impl CanonicalMinCut for CanonicalMinCutImpl {
fn canonical_cut(&self) -> CanonicalCutResult {
let graph = self.inner.graph();
let g = graph.read();
if let Some(ref cactus) = self.cactus {
if !self.dirty {
return cactus.canonical_cut();
}
}
let mut cactus = CactusGraph::build_from_graph(&g);
drop(g);
cactus.root_at_lex_smallest();
cactus.canonical_cut()
}
fn cactus_graph(&self) -> CactusGraph {
let graph = self.inner.graph();
let g = graph.read();
if let Some(ref cactus) = self.cactus {
if !self.dirty {
return cactus.clone();
}
}
let mut cactus = CactusGraph::build_from_graph(&g);
drop(g);
cactus.root_at_lex_smallest();
cactus
}
fn witness_receipt(&self) -> WitnessReceipt {
let result = self.canonical_cut();
let ts = PortableTimestamp::now().as_secs() * 1_000_000_000;
WitnessReceipt {
epoch: self.epoch,
cut_hash: result.canonical_key,
cut_value: result.value,
edge_count: result.cut_edges.len(),
timestamp_ns: ts,
}
}
fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> crate::Result<f64> {
let val = self.inner.insert_edge(u, v, weight)?;
self.epoch += 1;
self.dirty = true;
self.cactus = None;
Ok(val)
}
fn delete_edge(&mut self, u: VertexId, v: VertexId) -> crate::Result<f64> {
let val = self.inner.delete_edge(u, v)?;
self.epoch += 1;
self.dirty = true;
self.cactus = None;
Ok(val)
}
fn min_cut_value(&self) -> f64 {
self.inner.min_cut_value()
}
fn num_vertices(&self) -> usize {
self.inner.num_vertices()
}
fn num_edges(&self) -> usize {
self.inner.num_edges()
}
fn is_connected(&self) -> bool {
self.inner.is_connected()
}
}