use crate::core::{Graph, IgraphResult};
use super::st_edge_connectivity::st_edge_connectivity;
use crate::algorithms::connectivity::components::connected_components;
use crate::algorithms::connectivity::strong::strongly_connected_components;
pub fn edge_connectivity(graph: &Graph, checks: bool) -> IgraphResult<i64> {
let n = graph.vcount();
if n <= 1 {
return Ok(0);
}
if checks {
let connected = if graph.is_directed() {
strongly_connected_components(graph)?.count == 1
} else {
connected_components(graph)?.count == 1
};
if !connected {
return Ok(0);
}
let min_one = if graph.is_directed() {
let mut hit = false;
for v in 0..n {
let out = graph.out_neighbors_vec(v)?.len();
let in_ = graph.in_neighbors_vec(v)?.len();
if out == 1 || in_ == 1 {
hit = true;
break;
}
}
hit
} else {
let mut hit = false;
for v in 0..n {
if graph.degree(v)? == 1 {
hit = true;
break;
}
}
hit
};
if min_one {
return Ok(1);
}
}
let mut min_lambda = i64::MAX;
let directed = graph.is_directed();
for v in 1..n {
let f = st_edge_connectivity(graph, 0, v)?;
if f < min_lambda {
min_lambda = f;
if min_lambda == 0 {
return Ok(0);
}
}
if directed {
let f2 = st_edge_connectivity(graph, v, 0)?;
if f2 < min_lambda {
min_lambda = f2;
if min_lambda == 0 {
return Ok(0);
}
}
}
}
Ok(min_lambda)
}
pub fn adhesion(graph: &Graph, checks: bool) -> IgraphResult<i64> {
edge_connectivity(graph, checks)
}
#[cfg(test)]
mod tests {
use super::*;
fn ring_graph_n(n: u32, directed: bool) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for i in 0..n {
let j = (i + 1) % n;
g.add_edge(i, j).expect("edge");
}
g
}
fn path_graph_n(n: u32, directed: bool) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
for i in 0..(n - 1) {
g.add_edge(i, i + 1).expect("edge");
}
g
}
fn complete_undirected(n: u32) -> Graph {
let mut g = Graph::new(n, false).expect("graph");
for i in 0..n {
for j in (i + 1)..n {
g.add_edge(i, j).expect("edge");
}
}
g
}
fn complete_directed_mutual(n: u32) -> Graph {
let mut g = Graph::new(n, true).expect("graph");
for i in 0..n {
for j in 0..n {
if i != j {
g.add_edge(i, j).expect("edge");
}
}
}
g
}
#[test]
fn empty_graph_returns_zero() {
let g = Graph::new(0, false).expect("graph");
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn single_vertex_returns_zero() {
let g = Graph::new(1, false).expect("graph");
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn two_disconnected_vertices_return_zero() {
let g = Graph::new(2, false).expect("graph");
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn k2_undirected_returns_one() {
let mut g = Graph::new(2, false).expect("graph");
g.add_edge(0, 1).expect("edge");
assert_eq!(edge_connectivity(&g, true).expect("ec"), 1);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 1);
}
#[test]
fn path_5v_undirected_returns_one() {
let g = path_graph_n(5, false);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 1);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 1);
assert_eq!(adhesion(&g, true).expect("ec"), 1);
}
#[test]
fn two_isolated_edges_undirected_returns_zero() {
let mut g = Graph::new(4, false).expect("graph");
g.add_edge(0, 1).expect("edge");
g.add_edge(2, 3).expect("edge");
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn ring_5v_undirected_returns_two() {
let g = ring_graph_n(5, false);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 2);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 2);
assert_eq!(adhesion(&g, true).expect("ec"), 2);
}
#[test]
fn complete_undirected_5v_returns_four() {
let g = complete_undirected(5);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 4);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 4);
}
#[test]
fn complete_directed_mutual_4v_returns_three() {
let g = complete_directed_mutual(4);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 3);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 3);
}
#[test]
fn multigraph_two_parallel_edges_doubles_lambda() {
let mut g = Graph::new(2, false).expect("graph");
g.add_edge(0, 1).expect("edge");
g.add_edge(0, 1).expect("edge");
assert_eq!(edge_connectivity(&g, false).expect("ec"), 2);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 2);
}
#[test]
fn out_tree_3ary_10v_returns_zero() {
let edges: &[(u32, u32)] = &[
(0, 1),
(0, 2),
(0, 3),
(1, 4),
(1, 5),
(1, 6),
(2, 7),
(2, 8),
(2, 9),
];
let mut g = Graph::new(10, true).expect("graph");
for &(u, v) in edges {
g.add_edge(u, v).expect("edge");
}
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn undirected_tree_3ary_10v_returns_one() {
let edges: &[(u32, u32)] = &[
(0, 1),
(0, 2),
(0, 3),
(1, 4),
(1, 5),
(1, 6),
(2, 7),
(2, 8),
(2, 9),
];
let mut g = Graph::new(10, false).expect("graph");
for &(u, v) in edges {
g.add_edge(u, v).expect("edge");
}
assert_eq!(edge_connectivity(&g, true).expect("ec"), 1);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 1);
}
#[test]
fn directed_cycle_6v_returns_one() {
let g = ring_graph_n(6, true);
assert_eq!(edge_connectivity(&g, true).expect("ec"), 1);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 1);
}
#[test]
fn cycle_with_chord_undirected_returns_two() {
let mut g = Graph::new(4, false).expect("graph");
for (u, v) in [(0u32, 1u32), (1, 2), (2, 3), (3, 0), (0, 2)] {
g.add_edge(u, v).expect("edge");
}
assert_eq!(edge_connectivity(&g, true).expect("ec"), 2);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 2);
}
#[test]
fn c_fixture_directed_6v_equals_one() {
let mut g = Graph::new(6, true).expect("graph");
let arcs = [
(0u32, 1u32),
(0, 2),
(1, 2),
(1, 3),
(2, 4),
(3, 4),
(3, 5),
(4, 5),
];
for (u, v) in arcs {
g.add_edge(u, v).expect("edge");
}
assert_eq!(edge_connectivity(&g, true).expect("ec"), 0);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 0);
}
#[test]
fn c_fixture_undirected_6v_equals_two() {
let mut g = Graph::new(6, false).expect("graph");
let arcs = [
(0u32, 1u32),
(0, 2),
(1, 2),
(1, 3),
(2, 4),
(3, 4),
(3, 5),
(4, 5),
];
for (u, v) in arcs {
g.add_edge(u, v).expect("edge");
}
assert_eq!(edge_connectivity(&g, true).expect("ec"), 2);
assert_eq!(edge_connectivity(&g, false).expect("ec"), 2);
}
#[test]
fn checks_false_matches_checks_true_on_small_graphs() {
let fixtures: Vec<Graph> = vec![
ring_graph_n(6, false),
ring_graph_n(6, true),
path_graph_n(5, false),
complete_undirected(4),
complete_directed_mutual(4),
];
for g in fixtures {
let with_checks = edge_connectivity(&g, true).expect("ec");
let without = edge_connectivity(&g, false).expect("ec");
assert_eq!(
with_checks,
without,
"checks={{true,false}} disagree on n={}, dir={}",
g.vcount(),
g.is_directed()
);
}
}
#[test]
fn adhesion_alias_matches_edge_connectivity() {
let fixtures: Vec<Graph> = vec![
ring_graph_n(7, false),
complete_undirected(5),
path_graph_n(4, false),
];
for g in fixtures {
assert_eq!(
edge_connectivity(&g, true).expect("ec"),
adhesion(&g, true).expect("ec"),
"adhesion/edge_connectivity mismatch on n={}",
g.vcount(),
);
}
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptests {
use super::*;
use crate::core::Graph;
use proptest::prelude::*;
fn xorshift(mut r: u64) -> u64 {
r ^= r << 13;
r ^= r >> 7;
r ^= r << 17;
r
}
fn build_random(seed: u64, n: u32, m_max: u32, directed: bool) -> Graph {
let mut g = Graph::new(n, directed).expect("graph");
let mut state = seed | 1;
for _ in 0..m_max {
state = xorshift(state);
let u = u32::try_from(state % u64::from(n)).expect("modulo fits");
state = xorshift(state);
let v = u32::try_from(state % u64::from(n)).expect("modulo fits");
if u == v {
continue;
}
g.add_edge(u, v).expect("edge");
}
g
}
proptest! {
#[test]
fn ec_nonnegative_and_bounded_by_n_minus_one(
seed in any::<u64>(),
n in 2u32..7,
m in 0u32..14,
directed in any::<bool>(),
) {
let g = build_random(seed, n, m, directed);
let ec = edge_connectivity(&g, true).expect("ec");
prop_assert!(ec >= 0, "ec must be non-negative, got {ec}");
prop_assert!(ec as u64 <= u64::from(m),
"ec={ec} > m={m} (n={n}, seed={seed})");
}
#[test]
fn checks_true_matches_checks_false(
seed in any::<u64>(),
n in 2u32..6,
m in 0u32..12,
directed in any::<bool>(),
) {
let g = build_random(seed, n, m, directed);
let with_checks = edge_connectivity(&g, true).expect("ec");
let without = edge_connectivity(&g, false).expect("ec");
prop_assert_eq!(with_checks, without,
"checks=true {} != checks=false {} (n={}, m={}, directed={}, seed={})",
with_checks, without, n, m, directed, seed);
}
#[test]
fn ec_bounded_by_min_degree_undirected(
seed in any::<u64>(),
n in 3u32..6,
m in 1u32..10,
) {
let g = build_random(seed, n, m, false);
let mut min_deg = u32::MAX;
for v in 0..n {
let d = u32::try_from(g.degree(v).expect("degree")).unwrap_or(u32::MAX);
if d < min_deg { min_deg = d; }
}
let ec = edge_connectivity(&g, true).expect("ec");
prop_assert!(ec <= i64::from(min_deg),
"ec={ec} > min_deg={min_deg} (n={n}, m={m}, seed={seed})");
}
#[test]
fn ec_bounded_by_any_pair_st_edge_connectivity(
seed in any::<u64>(),
n in 3u32..5,
m in 1u32..9,
directed in any::<bool>(),
) {
use super::super::st_edge_connectivity::st_edge_connectivity;
let g = build_random(seed, n, m, directed);
let ec = edge_connectivity(&g, true).expect("ec");
for s in 0..n {
for t in 0..n {
if s == t { continue; }
let st = st_edge_connectivity(&g, s, t).expect("st_ec");
prop_assert!(ec <= st,
"ec={ec} > st_ec({s},{t})={st} (n={n}, m={m}, dir={directed}, seed={seed})");
}
}
}
}
}