#![cfg(feature = "petgraph")]
use graphops::betweenness_centrality;
use petgraph::prelude::*;
use serde::Deserialize;
const FIXTURE: &str = include_str!("fixtures/rosetta/graphops_betweenness.json");
#[derive(Deserialize)]
struct Fixture {
cases: Vec<Case>,
}
#[derive(Deserialize)]
struct Case {
name: String,
n: usize,
edges: Vec<[usize; 2]>,
expected: Expected,
}
#[derive(Deserialize)]
struct Expected {
betweenness: Vec<f64>,
}
fn vec_close(got: &[f64], want: &[f64], label: &str) {
assert_eq!(got.len(), want.len(), "{label}: length");
for (i, (&g, &w)) in got.iter().zip(want.iter()).enumerate() {
let tol = 1e-9 * (1.0 + w.abs());
let diff = (g - w).abs();
assert!(
diff <= tol,
"{label}[{i}]: graphops={g} networkx={w} diff={diff} tol={tol}"
);
}
}
fn build_digraph(n: usize, edges: &[[usize; 2]]) -> DiGraph<(), ()> {
let mut g: DiGraph<(), ()> = DiGraph::new();
for _ in 0..n {
g.add_node(());
}
for &[u, v] in edges {
g.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
}
g
}
#[test]
fn rosetta_betweenness_matches_networkx() {
let fx: Fixture = serde_json::from_str(FIXTURE).expect("parse rosetta fixture");
assert!(!fx.cases.is_empty(), "fixture has no cases");
for case in &fx.cases {
let g = build_digraph(case.n, &case.edges);
let bc = betweenness_centrality(&g);
vec_close(&bc, &case.expected.betweenness, &case.name);
}
}