use petgraph::graph::{NodeIndex, UnGraph};
use petgraph::visit::EdgeRef;
use rand::seq::SliceRandom;
use rand::Rng;
use std::collections::HashSet;
#[inline]
fn ekey(a: usize, b: usize) -> (usize, usize) {
if a < b {
(a, b)
} else {
(b, a)
}
}
pub fn configuration_model(degree_seq: &[usize], rng: &mut impl Rng) -> UnGraph<(), ()> {
let n = degree_seq.len();
let sum: usize = degree_seq.iter().sum();
assert!(
sum.is_multiple_of(2),
"configuration model requires an even stub sum; got sum={sum}"
);
let mut g = UnGraph::with_capacity(n, sum / 2);
for _ in 0..n {
g.add_node(());
}
let mut stubs: Vec<usize> = degree_seq
.iter()
.enumerate()
.flat_map(|(i, &d)| std::iter::repeat_n(i, d))
.collect();
stubs.shuffle(rng);
for pair in stubs.chunks_exact(2) {
g.add_edge(NodeIndex::new(pair[0]), NodeIndex::new(pair[1]), ());
}
g
}
pub fn configuration_model_simple(degree_seq: &[usize], rng: &mut impl Rng) -> UnGraph<(), ()> {
let raw = configuration_model(degree_seq, rng);
let n = raw.node_count();
let mut g = UnGraph::with_capacity(n, raw.edge_count());
for _ in 0..n {
g.add_node(());
}
let mut seen: HashSet<(usize, usize)> = HashSet::new();
for e in raw.edge_references() {
let a = e.source().index();
let b = e.target().index();
if a == b {
continue; }
if seen.insert(ekey(a, b)) {
g.add_edge(NodeIndex::new(a), NodeIndex::new(b), ());
}
}
g
}
pub fn double_edge_swap(
graph: &UnGraph<(), ()>,
n_swaps: usize,
rng: &mut impl Rng,
) -> UnGraph<(), ()> {
let n = graph.node_count();
let mut edges: Vec<(usize, usize)> = graph
.edge_references()
.map(|e| (e.source().index(), e.target().index()))
.collect();
let m = edges.len();
let mut edge_set: HashSet<(usize, usize)> = edges.iter().map(|&(a, b)| ekey(a, b)).collect();
if m >= 2 {
for _ in 0..n_swaps {
let i = rng.gen_range(0..m);
let mut j = rng.gen_range(0..m - 1);
if j >= i {
j += 1;
}
let (a, b) = edges[i];
let (c, d) = edges[j];
let (new_e1, new_e2) = if rng.gen_bool(0.5) {
((a, c), (b, d))
} else {
((a, d), (b, c))
};
if new_e1.0 == new_e1.1 || new_e2.0 == new_e2.1 {
continue;
}
let k1 = ekey(new_e1.0, new_e1.1);
let k2 = ekey(new_e2.0, new_e2.1);
let old1 = ekey(a, b);
let old2 = ekey(c, d);
if k1 == old1 || k1 == old2 || k2 == old1 || k2 == old2 {
continue;
}
if k1 == k2 {
continue;
}
if edge_set.contains(&k1) || edge_set.contains(&k2) {
continue;
}
edge_set.remove(&old1);
edge_set.remove(&old2);
edge_set.insert(k1);
edge_set.insert(k2);
edges[i] = new_e1;
edges[j] = new_e2;
}
}
let mut g = UnGraph::with_capacity(n, m);
for _ in 0..n {
g.add_node(());
}
for &(a, b) in &edges {
g.add_edge(NodeIndex::new(a), NodeIndex::new(b), ());
}
g
}
pub fn watts_strogatz(n: usize, k: usize, p: f64, rng: &mut impl Rng) -> UnGraph<(), ()> {
assert!(
k.is_multiple_of(2),
"k must be even for Watts–Strogatz; got k={k}"
);
assert!(
n > k,
"n must be greater than k for the ring lattice; got n={n}, k={k}"
);
assert!(n >= 2, "n must be at least 2; got n={n}");
let half_k = k / 2;
let mut edges: Vec<(usize, usize)> = Vec::with_capacity(n * half_k);
let mut edge_set: HashSet<(usize, usize)> = HashSet::with_capacity(n * half_k);
for i in 0..n {
for j in 1..=half_k {
let nb = (i + j) % n;
edges.push((i, nb));
edge_set.insert(ekey(i, nb));
}
}
for edge in &mut edges {
if p > 0.0 && rng.gen::<f64>() < p {
let (u, old_v) = *edge;
let old_k = ekey(u, old_v);
let mut new_v = None;
for _ in 0..n {
let cand = rng.gen_range(0..n);
if cand != u && !edge_set.contains(&ekey(u, cand)) {
new_v = Some(cand);
break;
}
}
if let Some(v) = new_v {
edge_set.remove(&old_k);
edge_set.insert(ekey(u, v));
*edge = (u, v);
}
}
}
let mut g = UnGraph::with_capacity(n, n * half_k);
for _ in 0..n {
g.add_node(());
}
for &(a, b) in &edges {
g.add_edge(NodeIndex::new(a), NodeIndex::new(b), ());
}
g
}
#[allow(clippy::too_many_arguments)]
pub fn lfr_benchmark(
n: usize,
avg_degree: f64,
max_degree: usize,
mu: f64,
gamma: f64,
beta: f64,
min_community: usize,
max_community: usize,
rng: &mut impl Rng,
) -> (UnGraph<(), ()>, Vec<usize>) {
assert!(n > 0, "n must be positive");
assert!(max_degree > 0, "max_degree must be positive");
assert!(min_community > 0, "min_community must be positive");
assert!(
min_community <= max_community,
"min_community ({min_community}) must be ≤ max_community ({max_community})"
);
assert!(
(0.0..=1.0).contains(&mu),
"mu must be in [0.0, 1.0]; got {mu}"
);
let _ = avg_degree;
let mut degrees: Vec<usize> = (0..n)
.map(|_| sample_discrete_power_law(1, max_degree, gamma, rng))
.collect();
if degrees.iter().sum::<usize>() % 2 != 0 {
let idx = rng.gen_range(0..n);
if degrees[idx] < max_degree {
degrees[idx] += 1;
} else {
degrees[idx] = degrees[idx].saturating_sub(1);
}
}
let community_sizes = gen_community_sizes(n, min_community, max_community, beta, rng);
let num_communities = community_sizes.len();
let mut node_order: Vec<usize> = (0..n).collect();
node_order.shuffle(rng);
let mut community = vec![0usize; n];
{
let mut start = 0;
for (c, &sz) in community_sizes.iter().enumerate() {
for &node in &node_order[start..start + sz] {
community[node] = c;
}
start += sz;
}
}
let mut d_in: Vec<usize> = (0..n)
.map(|i| {
let c = community[i];
let c_size = community_sizes[c];
let max_in = c_size.saturating_sub(1);
let raw = ((1.0 - mu) * degrees[i] as f64).round() as usize;
raw.min(max_in)
})
.collect();
let mut d_ext: Vec<usize> = (0..n).map(|i| degrees[i].saturating_sub(d_in[i])).collect();
let mut community_nodes: Vec<Vec<usize>> = vec![Vec::new(); num_communities];
for (node, &c) in community.iter().enumerate() {
community_nodes[c].push(node);
}
let mut all_edges: Vec<(usize, usize)> = Vec::new();
for (c, nodes) in community_nodes.iter().enumerate() {
if nodes.len() <= 1 {
continue;
}
let mut local_din: Vec<usize> = nodes.iter().map(|&v| d_in[v]).collect();
let sum: usize = local_din.iter().sum();
if !sum.is_multiple_of(2) {
let max_in = community_sizes[c].saturating_sub(1);
if let Some((li, _)) = local_din
.iter()
.enumerate()
.filter(|&(_, &v)| v < max_in)
.min_by_key(|&(_, &v)| v)
{
local_din[li] += 1;
d_in[nodes[li]] += 1;
d_ext[nodes[li]] = degrees[nodes[li]].saturating_sub(d_in[nodes[li]]);
} else if let Some((li, _)) = local_din
.iter()
.enumerate()
.filter(|&(_, &v)| v > 0)
.min_by_key(|&(_, &v)| v)
{
local_din[li] -= 1;
d_in[nodes[li]] -= 1;
d_ext[nodes[li]] = degrees[nodes[li]].saturating_sub(d_in[nodes[li]]);
}
}
let mut stubs: Vec<usize> = local_din
.iter()
.enumerate()
.flat_map(|(li, &d)| std::iter::repeat_n(nodes[li], d))
.collect();
stubs.shuffle(rng);
for pair in stubs.chunks_exact(2) {
all_edges.push((pair[0], pair[1]));
}
}
let mut ext_stubs: Vec<usize> = (0..n)
.flat_map(|i| std::iter::repeat_n(i, d_ext[i]))
.collect();
ext_stubs.shuffle(rng);
let mut matched = vec![false; ext_stubs.len()];
let mut i = 0;
while i < ext_stubs.len() {
if matched[i] {
i += 1;
continue;
}
let u = ext_stubs[i];
let cu = community[u];
let found =
(i + 1..ext_stubs.len()).find(|&j| !matched[j] && community[ext_stubs[j]] != cu);
if let Some(j) = found {
all_edges.push((u, ext_stubs[j]));
matched[i] = true;
matched[j] = true;
}
i += 1;
}
let mut g = UnGraph::with_capacity(n, all_edges.len());
for _ in 0..n {
g.add_node(());
}
for &(a, b) in &all_edges {
g.add_edge(NodeIndex::new(a), NodeIndex::new(b), ());
}
(g, community)
}
fn sample_discrete_power_law(
min_val: usize,
max_val: usize,
exponent: f64,
rng: &mut impl Rng,
) -> usize {
debug_assert!(min_val <= max_val && min_val >= 1);
let mut total = 0.0f64;
let mut cdf: Vec<f64> = (min_val..=max_val)
.map(|x| {
total += (x as f64).powf(-exponent);
total
})
.collect();
let r = rng.gen::<f64>() * total;
*cdf.last_mut().unwrap() = f64::INFINITY;
for (i, &c) in cdf.iter().enumerate() {
if r <= c {
return min_val + i;
}
}
max_val
}
fn gen_community_sizes(
n: usize,
min_c: usize,
max_c: usize,
beta: f64,
rng: &mut impl Rng,
) -> Vec<usize> {
let mut sizes: Vec<usize> = Vec::new();
let mut total = 0usize;
while total < n {
let remaining = n - total;
if remaining < min_c {
match sizes.last_mut() {
Some(last) => *last += remaining,
None => sizes.push(remaining),
}
break;
}
let sz = sample_discrete_power_law(min_c, max_c.min(remaining), beta, rng);
sizes.push(sz);
total += sz;
}
if sizes.is_empty() {
sizes.push(n);
}
sizes
}