#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss
)]
use std::collections::HashMap;
use rust_igraph::{Graph, degree_sequence_game_configuration};
fn observed_degrees(g: &Graph) -> (Vec<u32>, Vec<u32>) {
let vcount = g.vcount() as usize;
let mut out_deg = vec![0u32; vcount];
let mut in_deg = vec![0u32; vcount];
let ecount = u32::try_from(g.ecount()).expect("ecount fits u32 for demo");
for eid in 0..ecount {
let (src, dst) = g.edge(eid).expect("edge id in bounds for demo");
if g.is_directed() {
out_deg[src as usize] = out_deg[src as usize].saturating_add(1);
in_deg[dst as usize] = in_deg[dst as usize].saturating_add(1);
} else {
out_deg[src as usize] = out_deg[src as usize].saturating_add(1);
out_deg[dst as usize] = out_deg[dst as usize].saturating_add(1);
}
}
(out_deg, in_deg)
}
fn count_self_loops(g: &Graph) -> u32 {
let ecount = u32::try_from(g.ecount()).expect("ecount fits u32 for demo");
let mut loops = 0u32;
for eid in 0..ecount {
let (src, dst) = g.edge(eid).expect("edge id in bounds for demo");
if src == dst {
loops += 1;
}
}
loops
}
fn count_multi_edges(g: &Graph) -> u32 {
let ecount = u32::try_from(g.ecount()).expect("ecount fits u32 for demo");
let mut bag: HashMap<(u32, u32), u32> = HashMap::new();
for eid in 0..ecount {
let (src, dst) = g.edge(eid).expect("edge id in bounds for demo");
let key = if g.is_directed() || src <= dst {
(src, dst)
} else {
(dst, src)
};
*bag.entry(key).or_insert(0) += 1;
}
bag.values().map(|c| c.saturating_sub(1)).sum()
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
{
let n: usize = 200;
let d: u32 = 4;
let seq: Vec<u32> = vec![d; n];
let g = degree_sequence_game_configuration(&seq, None, 0x1234_5678_u64)?;
let (deg, _) = observed_degrees(&g);
let loops = count_self_loops(&g);
let multi = count_multi_edges(&g);
println!("Scenario 1: uniform 4-regular undirected, n = {n}");
println!(
" |E| = {} (expected n·d/2 = {})",
g.ecount(),
n * d as usize / 2
);
println!(
" degree range = [{}, {}] (expected exact {})",
deg.iter().min().unwrap(),
deg.iter().max().unwrap(),
d,
);
println!(
" self-loops = {loops}, multi-edges = {multi} (expected ≈ d/2 = {:.1} loops + a few collisions)",
f64::from(d) / 2.0
);
println!();
}
{
let n: usize = 100;
let seq: Vec<u32> = (0..n)
.map(|r| u32::try_from(n / (r + 1)).expect("term fits u32"))
.map(|d| if d == 0 { 1 } else { d })
.collect();
let sum: u64 = seq.iter().map(|&d| u64::from(d)).sum();
let mut seq = seq;
if sum % 2 != 0 {
seq[0] = seq[0].saturating_add(1);
}
let g = degree_sequence_game_configuration(&seq, None, 0xBEEF_F00D_u64)?;
let (deg, _) = observed_degrees(&g);
let loops = count_self_loops(&g);
let multi = count_multi_edges(&g);
println!("Scenario 2: Zipf-like skewed undirected, n = {n}");
println!(
" Σd_input = {}",
seq.iter().map(|&d| u64::from(d)).sum::<u64>()
);
println!(" |E| = {}", g.ecount());
println!(
" hottest vertex (v=0): input deg = {}, observed deg = {}",
seq[0], deg[0]
);
println!(" self-loops = {loops}, multi-edges = {multi} (concentrated on the hot vertex)");
println!();
}
{
let n_blocks: usize = 10;
let out_pattern: [u32; 5] = [1, 2, 3, 4, 5];
let mut out_seq: Vec<u32> = Vec::with_capacity(n_blocks * 5);
for _ in 0..n_blocks {
out_seq.extend_from_slice(&out_pattern);
}
let in_seq = out_seq.clone();
let g = degree_sequence_game_configuration(&out_seq, Some(&in_seq), 0xDEAD_BEEF_u64)?;
let (obs_out, obs_in) = observed_degrees(&g);
println!("Scenario 3: balanced directed, n = {}", out_seq.len());
println!(
" Σout = Σin = {}",
out_seq.iter().map(|&d| u64::from(d)).sum::<u64>()
);
println!(" |E| = {} (expected = Σout)", g.ecount());
println!(
" out-degree match = {}, in-degree match = {}",
obs_out == out_seq,
obs_in == in_seq
);
println!();
}
println!(
"Multigraphs are expected: stub matching admits self-loops and multi-edges with positive probability."
);
println!(
"For *simple*-graph samples from a fixed degree sequence, use the VL or EDGE_SWITCHING_SIMPLE methods (follow-up AWUs GN-025+)."
);
Ok(())
}