1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//! `ecc` (ALGO-PR-031) — edge clustering coefficient on Zachary's
//! karate club.
//!
//! Run with `cargo run --release --example ecc_karate`.
//!
//! Computes the Radicchi 2004 edge clustering coefficient for every
//! edge of the karate club, under both the k=3 (triangle-based) and
//! k=4 (square-based) variants in their default (`offset=false,
//! normalize=true`) form. For each variant we print:
//!
//! * the minimum, maximum, and mean coefficient (skipping NaN
//! entries — those mark edges with a degree-1 endpoint, where the
//! denominator vanishes),
//! * a small leaderboard of the five edges with the highest
//! coefficient (the "deeply embedded" edges Radicchi's algorithm
//! keeps), and the five with the lowest (the candidate
//! inter-community bridges).
//!
//! The first four edges (in `karate.edges` id order) should reproduce
//! `0.875, 0.555556, 1.0, 1.0` — the leading entries of
//! `references/igraph/tests/unit/igraph_ecc.out` for `IGRAPH_ECC_3`.
#![allow(clippy::cast_precision_loss)]
use std::fs::File;
use std::path::PathBuf;
use rust_igraph::{Graph, ecc, read_edgelist};
fn summarise(label: &str, g: &Graph, values: &[f64]) {
let mut finite: Vec<(u32, f64)> = values
.iter()
.enumerate()
.filter_map(|(i, &v)| {
if v.is_finite() {
Some((u32::try_from(i).expect("edge id fits in u32"), v))
} else {
None
}
})
.collect();
let nan_count = values.len() - finite.len();
let n = finite.len();
let (mut lo, mut hi, mut sum) = (f64::INFINITY, f64::NEG_INFINITY, 0.0);
for &(_, v) in &finite {
lo = lo.min(v);
hi = hi.max(v);
sum += v;
}
let mean = if n == 0 { f64::NAN } else { sum / n as f64 };
println!("\n{label}:");
println!(
" edges: {} (finite) / {} (NaN: {})",
n,
values.len(),
nan_count
);
println!(" min = {lo:.6} max = {hi:.6} mean = {mean:.6}");
// Top-5 most embedded (highest coefficient): community-internal.
finite.sort_by(|a, b| b.1.partial_cmp(&a.1).expect("no NaN here"));
println!(" top-5 (most embedded):");
for &(eid, v) in finite.iter().take(5) {
let (u, w) = g.edge(eid).expect("edge id in range");
println!(" eid {eid:>3}: ({u:>2},{w:>2}) C = {v:.6}");
}
// Bottom-5 least embedded: candidate inter-community bridges.
finite.sort_by(|a, b| a.1.partial_cmp(&b.1).expect("no NaN here"));
println!(" bottom-5 (likely bridges):");
for &(eid, v) in finite.iter().take(5) {
let (u, w) = g.edge(eid).expect("edge id in range");
println!(" eid {eid:>3}: ({u:>2},{w:>2}) C = {v:.6}");
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("fixtures/karate.edges");
let g = read_edgelist(File::open(path)?)?;
println!("Karate club: {} vertices, {} edges", g.vcount(), g.ecount());
let c3 = ecc(&g, None, 3, false, true)?;
let c4 = ecc(&g, None, 4, false, true)?;
// First four entries mirror references/igraph/tests/unit/igraph_ecc.out
// line 73 (IGRAPH_ECC_3 on karate): 0.875 0.555556 1 1.
println!(
"\nFirst four k=3 values (matches igraph C .out): {:.6} {:.6} {:.6} {:.6}",
c3[0], c3[1], c3[2], c3[3]
);
summarise("k=3 (triangle-based)", &g, &c3);
summarise("k=4 (square-based)", &g, &c4);
Ok(())
}