use crate::graph::Graph;
use std::collections::HashMap;
pub fn degree_histogram(graph: &Graph) -> HashMap<usize, usize> {
let mut hist: HashMap<usize, usize> = HashMap::new();
for &node in graph.adjacency().keys() {
*hist.entry(graph.degree(node)).or_insert(0) += 1;
}
hist
}
pub fn degree_ccdf(graph: &Graph) -> Vec<(usize, f64)> {
let hist = degree_histogram(graph);
let n = graph.node_count() as f64;
if n == 0.0 { return vec![]; }
let mut degrees: Vec<usize> = hist.keys().copied().collect();
degrees.sort();
let total: usize = hist.values().sum();
let mut ccdf = Vec::new();
let mut cum = 0usize;
for &d in °rees {
cum += hist[&d];
ccdf.push((d, 1.0 - cum as f64 / total as f64));
}
ccdf
}
pub fn power_law_fit(graph: &Graph) -> Option<(f64, usize)> {
let mut degrees: Vec<f64> = graph.nodes().iter()
.map(|&n| graph.degree(n) as f64)
.filter(|&d| d > 0.0)
.collect();
degrees.sort_by(|a, b| a.partial_cmp(b).unwrap());
if degrees.len() < 2 { return None; }
let unique_mins: Vec<f64> = {
let mut u: Vec<f64> = degrees.clone();
u.dedup();
u
};
let mut best_alpha = 0.0;
let mut best_xmin = 0.0;
let mut best_ks = f64::INFINITY;
for &xmin in &unique_mins {
let tail: Vec<f64> = degrees.iter().filter(|&&d| d >= xmin).copied().collect();
let nt = tail.len();
if nt < 2 || nt < degrees.len() / 10 { continue; }
let sum_log: f64 = tail.iter().map(|&d| (d / (xmin - 0.5)).ln()).sum();
if sum_log <= 0.0 { continue; }
let alpha = 1.0 + nt as f64 / sum_log;
if !alpha.is_finite() || alpha <= 1.0 { continue; }
let ks = compute_ks(&tail, alpha, xmin);
if ks < best_ks {
best_ks = ks;
best_alpha = alpha;
best_xmin = xmin;
}
}
if best_alpha == 0.0 { return None; }
Some((best_alpha, best_xmin as usize))
}
fn compute_ks(tail: &[f64], alpha: f64, xmin: f64) -> f64 {
let n = tail.len();
if n == 0 { return f64::INFINITY; }
let mut sorted = tail.to_vec();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mut max_diff = 0.0;
for (i, &d) in sorted.iter().enumerate() {
let empirical = (i + 1) as f64 / n as f64;
let theoretical = 1.0 - zeta_approx(alpha, xmin, d);
let diff = (empirical - theoretical).abs();
if diff > max_diff {
max_diff = diff;
}
}
max_diff
}
fn zeta_approx(alpha: f64, xmin: f64, x: f64) -> f64 {
let mut num = 0.0;
let mut den = 0.0;
for k in (xmin as usize)..=(x as usize + 10) {
let term = (k as f64).powf(-alpha);
den += term;
if k as f64 <= x {
num += term;
}
}
if den == 0.0 { return 0.0; }
num / den
}
pub fn is_scale_free(graph: &Graph) -> bool {
if let Some((alpha, _)) = power_law_fit(graph) {
alpha > 1.0 && alpha < 6.0
} else {
false
}
}
pub fn degree_gini(graph: &Graph) -> f64 {
let mut degrees: Vec<f64> = graph.nodes().iter()
.map(|&n| graph.degree(n) as f64)
.collect();
if degrees.is_empty() { return 0.0; }
degrees.sort_by(|a, b| a.partial_cmp(b).unwrap());
let n = degrees.len() as f64;
let mean = degrees.iter().sum::<f64>() / n;
if mean == 0.0 { return 0.0; }
let mut sum_diff = 0.0;
for i in 0..degrees.len() {
for j in 0..degrees.len() {
sum_diff += (degrees[i] - degrees[j]).abs();
}
}
sum_diff / (2.0 * n * n * mean)
}