#![allow(
clippy::cast_precision_loss,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_lossless,
clippy::many_single_char_names
)]
use rust_igraph::{Graph, cited_type_game};
fn count_per_type(types: &[u32], num_types: usize) -> Vec<u32> {
let mut counts = vec![0u32; num_types];
for &t in types {
counts[t as usize] += 1;
}
counts
}
fn in_degree_per_type(g: &Graph, types: &[u32], num_types: usize) -> Vec<u64> {
let mut deg = vec![0u64; num_types];
let m = u32::try_from(g.ecount()).expect("ecount fits in u32 for example");
for eid in 0..m {
let (_s, d) = g.edge(eid).expect("edge id in bounds for example");
deg[types[d as usize] as usize] += 1;
}
deg
}
fn count_loops_and_multi(g: &Graph) -> (u64, u64) {
use std::collections::HashMap;
let m = u32::try_from(g.ecount()).expect("ecount fits in u32 for example");
let directed = g.is_directed();
let mut loops: u64 = 0;
let mut counter: HashMap<(u32, u32), u32> = HashMap::with_capacity(m as usize);
for eid in 0..m {
let (s, d) = g.edge(eid).expect("edge id in bounds for example");
if s == d {
loops += 1;
}
let pair = if directed || s <= d { (s, d) } else { (d, s) };
*counter.entry(pair).or_insert(0) += 1;
}
let multi: u64 = counter
.values()
.filter(|&&c| c >= 2)
.map(|&c| u64::from(c))
.sum();
(loops, multi)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let n: u32 = 2_000;
let num_types: u32 = 3;
let edges_per_step: u32 = 3;
let types: Vec<u32> = (0..n).map(|v| v % num_types).collect();
let pref = vec![10.0, 1.0, 0.05];
let g = cited_type_game(n, &types, &pref, edges_per_step, true, 0xC17E_0E70_u64)?;
let counts = count_per_type(&types, num_types as usize);
let in_deg = in_degree_per_type(&g, &types, num_types as usize);
let (loops, multi_edges) = count_loops_and_multi(&g);
let m = g.ecount();
println!(
"cited_type: n = {n}, types = {num_types} (round-robin), edges_per_step = {edges_per_step}, pref = [10.0, 1.0, 0.05]"
);
println!(" edges = {m}");
println!(" expected edges (n-1)*eps = {}", (n - 1) * edges_per_step);
println!(" vertex counts per type = {counts:?}");
println!(" self-loops = {loops}");
println!(" edges in multi-bundles = {multi_edges}");
println!(" in-degree share per type:");
for t in 0..num_types as usize {
let cnt = counts[t];
let deg = in_deg[t];
let share = if m == 0 {
0.0
} else {
100.0 * deg as f64 / m as f64
};
let mean = if cnt == 0 {
0.0
} else {
deg as f64 / cnt as f64
};
println!(
" type {t} pref = {:>6} count = {cnt:>4} in_deg = {deg:>5} share = {share:>5.1}% mean_in_deg = {mean:.2}",
pref[t]
);
}
let total: f64 = pref.iter().sum();
println!(" pref-implied share targets:");
for (t, &p) in pref.iter().enumerate().take(num_types as usize) {
println!(" type {t}: {:>5.1}%", 100.0 * p / total);
}
Ok(())
}