use crate::base::Graph;
use crate::error::{GraphError, Result};
use scirs2_core::ndarray::Array2;
use scirs2_core::rand_prelude::IndexedRandom;
use scirs2_core::random::prelude::*;
use scirs2_core::random::seq::SliceRandom;
use std::collections::HashSet;
pub fn erdos_renyi_g_np<R: Rng>(n: usize, p: f64, rng: &mut R) -> Result<Graph<usize, f64>> {
if !(0.0..=1.0).contains(&p) {
return Err(GraphError::InvalidGraph(format!(
"erdos_renyi_g_np: p={p} must be in [0,1]"
)));
}
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
for u in 0..n {
for v in (u + 1)..n {
if rng.random::<f64>() < p {
g.add_edge(u, v, 1.0)?;
}
}
}
Ok(g)
}
pub fn erdos_renyi_g_nm<R: Rng>(n: usize, m: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
let max_edges = n.saturating_mul(n.saturating_sub(1)) / 2;
if m > max_edges {
return Err(GraphError::InvalidGraph(format!(
"erdos_renyi_g_nm: m={m} > C({n},2)={max_edges}"
)));
}
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
if m == 0 {
return Ok(g);
}
let mut candidates: Vec<(usize, usize)> = Vec::with_capacity(max_edges);
for u in 0..n {
for v in (u + 1)..n {
candidates.push((u, v));
}
}
for i in 0..m {
let j = i + rng.random_range(0..(candidates.len() - i));
candidates.swap(i, j);
}
for &(u, v) in &candidates[..m] {
g.add_edge(u, v, 1.0)?;
}
Ok(g)
}
pub fn barabasi_albert<R: Rng>(n: usize, m: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
if m == 0 {
return Err(GraphError::InvalidGraph(
"barabasi_albert: m must be ≥ 1".to_string(),
));
}
if n <= m {
return Err(GraphError::InvalidGraph(format!(
"barabasi_albert: n={n} must be > m={m}"
)));
}
let mut g = Graph::new();
for i in 0..=m {
g.add_node(i);
}
for u in 0..=m {
for v in (u + 1)..=m {
g.add_edge(u, v, 1.0)?;
}
}
let mut stubs: Vec<usize> = Vec::with_capacity(n * m * 2);
for i in 0..=m {
for _ in 0..m {
stubs.push(i);
}
}
for new_node in (m + 1)..n {
g.add_node(new_node);
let mut chosen: HashSet<usize> = HashSet::with_capacity(m);
while chosen.len() < m {
let idx = rng.random_range(0..stubs.len());
let target = stubs[idx];
if target != new_node {
chosen.insert(target);
}
}
for &t in &chosen {
g.add_edge(new_node, t, 1.0)?;
stubs.push(t);
stubs.push(new_node);
}
}
Ok(g)
}
pub fn watts_strogatz<R: Rng>(
n: usize,
k: usize,
beta: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if k == 0 || k >= n || !k.is_multiple_of(2) {
return Err(GraphError::InvalidGraph(format!(
"watts_strogatz: k={k} must be even, ≥ 2, and < n={n}"
)));
}
if !(0.0..=1.0).contains(&beta) {
return Err(GraphError::InvalidGraph(format!(
"watts_strogatz: beta={beta} must be in [0,1]"
)));
}
let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); n];
for u in 0..n {
for s in 1..=(k / 2) {
let v = (u + s) % n;
adj[u].insert(v);
adj[v].insert(u);
}
}
for u in 0..n {
for s in 1..=(k / 2) {
let v = (u + s) % n;
if !adj[u].contains(&v) {
continue;
}
if rng.random::<f64>() < beta {
let mut w = rng.random_range(0..n);
let mut attempts = 0usize;
while (w == u || adj[u].contains(&w)) && attempts < n * 4 {
w = rng.random_range(0..n);
attempts += 1;
}
if w == u || adj[u].contains(&w) {
continue;
}
adj[u].remove(&v);
adj[v].remove(&u);
adj[u].insert(w);
adj[w].insert(u);
}
}
}
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
for u in 0..n {
for &v in &adj[u] {
if u < v {
g.add_edge(u, v, 1.0)?;
}
}
}
Ok(g)
}
pub fn random_regular<R: Rng>(n: usize, d: usize, rng: &mut R) -> Option<Graph<usize, f64>> {
if n == 0 || d == 0 {
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
return Some(g);
}
if d >= n {
return None; }
if !(n * d).is_multiple_of(2) {
return None; }
let max_outer_attempts = 100usize;
for _ in 0..max_outer_attempts {
if let Some(g) = try_build_regular(n, d, rng) {
return Some(g);
}
}
None
}
fn try_build_regular<R: Rng>(n: usize, d: usize, rng: &mut R) -> Option<Graph<usize, f64>> {
let mut stubs: Vec<usize> = (0..n).flat_map(|i| std::iter::repeat_n(i, d)).collect();
stubs.shuffle(rng);
let mut adj: Vec<HashSet<usize>> = vec![HashSet::new(); n];
while stubs.len() >= 2 {
let u = stubs[0];
let v = stubs[1];
if u == v || adj[u].contains(&v) {
let swap_idx = 2 + rng.random_range(0..stubs.len().saturating_sub(2).max(1));
if swap_idx < stubs.len() {
stubs.swap(1, swap_idx);
} else {
return None; }
continue;
}
adj[u].insert(v);
adj[v].insert(u);
stubs.drain(0..2);
}
if !stubs.is_empty() {
return None;
}
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
for u in 0..n {
for &v in &adj[u] {
if u < v {
g.add_edge(u, v, 1.0).ok()?;
}
}
}
Some(g)
}
pub fn hyperbolic_random_graph<R: Rng>(
n: usize,
r: f64,
alpha: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if r <= 0.0 {
return Err(GraphError::InvalidGraph(format!(
"hyperbolic_random_graph: r={r} must be > 0"
)));
}
if alpha <= 0.0 {
return Err(GraphError::InvalidGraph(format!(
"hyperbolic_random_graph: alpha={alpha} must be > 0"
)));
}
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
if n < 2 {
return Ok(g);
}
let cosh_alpha_r = (alpha * r).cosh();
let two_pi = std::f64::consts::TAU;
let mut coords: Vec<(f64, f64)> = Vec::with_capacity(n); for _ in 0..n {
let u: f64 = rng.random();
let rho = ((1.0 + u * (cosh_alpha_r - 1.0)).acosh()) / alpha;
let theta = rng.random::<f64>() * two_pi;
coords.push((rho, theta));
}
for i in 0..n {
let (r1, t1) = coords[i];
let (sinh_r1, cosh_r1) = (r1.sinh(), r1.cosh());
for j in (i + 1)..n {
let (r2, t2) = coords[j];
let delta_theta = (t1 - t2).abs();
let delta_theta = if delta_theta > std::f64::consts::PI {
two_pi - delta_theta
} else {
delta_theta
};
let arg = cosh_r1 * r2.cosh() - sinh_r1 * r2.sinh() * delta_theta.cos();
let hyp_dist = if arg <= 1.0 { 0.0 } else { arg.acosh() };
if hyp_dist <= r {
g.add_edge(i, j, hyp_dist)?;
}
}
}
Ok(g)
}
pub fn kronecker_graph<R: Rng>(
initiator: &Array2<f64>,
k: usize,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
let k0 = initiator.nrows();
if k0 == 0 || initiator.ncols() != k0 {
return Err(GraphError::InvalidGraph(
"kronecker_graph: initiator must be a non-empty square matrix".to_string(),
));
}
for val in initiator.iter() {
if !(*val > 0.0 && *val <= 1.0) {
return Err(GraphError::InvalidGraph(format!(
"kronecker_graph: initiator entries must be in (0,1], found {val}"
)));
}
}
if k == 0 {
return Err(GraphError::InvalidGraph(
"kronecker_graph: k must be ≥ 1".to_string(),
));
}
let n = k0.pow(k as u32);
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
for i in 0..n {
for j in (i + 1)..n {
let prob = kronecker_edge_prob(initiator, k0, k, i, j);
if rng.random::<f64>() < prob {
g.add_edge(i, j, 1.0)?;
}
}
}
Ok(g)
}
fn kronecker_edge_prob(
initiator: &Array2<f64>,
k0: usize,
k: usize,
mut i: usize,
mut j: usize,
) -> f64 {
let mut prob = 1.0f64;
for _ in 0..k {
let digit_i = i % k0;
let digit_j = j % k0;
prob *= initiator[[digit_i, digit_j]];
i /= k0;
j /= k0;
}
prob
}
pub fn chung_lu<R: Rng>(degree_seq: &[f64], rng: &mut R) -> Result<Graph<usize, f64>> {
if degree_seq.is_empty() {
return Ok(Graph::new());
}
for &w in degree_seq {
if w < 0.0 || !w.is_finite() {
return Err(GraphError::InvalidGraph(format!(
"chung_lu: degree sequence entries must be non-negative finite, found {w}"
)));
}
}
let n = degree_seq.len();
let total_weight: f64 = degree_seq.iter().sum();
let mut g = Graph::new();
for i in 0..n {
g.add_node(i);
}
if total_weight <= 0.0 {
return Ok(g);
}
for i in 0..n {
for j in (i + 1)..n {
let p = (degree_seq[i] * degree_seq[j] / total_weight).min(1.0);
if p > 0.0 && rng.random::<f64>() < p {
g.add_edge(i, j, 1.0)?;
}
}
}
Ok(g)
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::random::prelude::StdRng;
use scirs2_core::random::SeedableRng;
#[test]
fn test_gnp_basic() {
let mut rng = StdRng::seed_from_u64(1);
let g = erdos_renyi_g_np(10, 0.5, &mut rng).expect("gnp failed");
assert_eq!(g.node_count(), 10);
assert!(g.edge_count() <= 45);
}
#[test]
fn test_gnp_p0_no_edges() {
let mut rng = StdRng::seed_from_u64(2);
let g = erdos_renyi_g_np(15, 0.0, &mut rng).expect("gnp p=0 failed");
assert_eq!(g.edge_count(), 0);
}
#[test]
fn test_gnp_p1_complete() {
let mut rng = StdRng::seed_from_u64(3);
let g = erdos_renyi_g_np(8, 1.0, &mut rng).expect("gnp p=1 failed");
assert_eq!(g.edge_count(), 8 * 7 / 2);
}
#[test]
fn test_gnp_invalid_p() {
let mut rng = StdRng::seed_from_u64(4);
assert!(erdos_renyi_g_np(10, 1.5, &mut rng).is_err());
assert!(erdos_renyi_g_np(10, -0.1, &mut rng).is_err());
}
#[test]
fn test_gnm_exact_edges() {
let mut rng = StdRng::seed_from_u64(5);
for m in [0, 1, 10, 45] {
let g = erdos_renyi_g_nm(10, m, &mut rng).expect("gnm failed");
assert_eq!(g.node_count(), 10);
assert_eq!(g.edge_count(), m);
}
}
#[test]
fn test_gnm_too_many_edges() {
let mut rng = StdRng::seed_from_u64(6);
assert!(erdos_renyi_g_nm(5, 11, &mut rng).is_err()); }
#[test]
fn test_ba_node_count() {
let mut rng = StdRng::seed_from_u64(7);
let g = barabasi_albert(50, 3, &mut rng).expect("ba failed");
assert_eq!(g.node_count(), 50);
}
#[test]
fn test_ba_min_edges() {
let mut rng = StdRng::seed_from_u64(8);
let g = barabasi_albert(20, 2, &mut rng).expect("ba failed");
assert!(g.edge_count() >= 3 + 17 * 2);
}
#[test]
fn test_ba_invalid_params() {
let mut rng = StdRng::seed_from_u64(9);
assert!(barabasi_albert(5, 0, &mut rng).is_err());
assert!(barabasi_albert(3, 5, &mut rng).is_err());
}
#[test]
fn test_ws_lattice() {
let mut rng = StdRng::seed_from_u64(10);
let g = watts_strogatz(20, 4, 0.0, &mut rng).expect("ws failed");
assert_eq!(g.node_count(), 20);
assert_eq!(g.edge_count(), 20 * 4 / 2);
}
#[test]
fn test_ws_random() {
let mut rng = StdRng::seed_from_u64(11);
let g = watts_strogatz(30, 4, 1.0, &mut rng).expect("ws full rewire failed");
assert_eq!(g.node_count(), 30);
assert!(g.edge_count() <= 30 * 4 / 2);
}
#[test]
fn test_ws_invalid_params() {
let mut rng = StdRng::seed_from_u64(12);
assert!(watts_strogatz(10, 3, 0.5, &mut rng).is_err()); assert!(watts_strogatz(10, 10, 0.5, &mut rng).is_err()); assert!(watts_strogatz(10, 4, -0.1, &mut rng).is_err());
}
#[test]
fn test_random_regular_degrees() {
let mut rng = StdRng::seed_from_u64(13);
if let Some(g) = random_regular(10, 3, &mut rng) {
assert_eq!(g.node_count(), 10);
for i in 0..10usize {
assert_eq!(g.degree(&i), 3);
}
}
}
#[test]
fn test_random_regular_infeasible() {
let mut rng = StdRng::seed_from_u64(14);
assert!(random_regular(5, 3, &mut rng).is_none()); assert!(random_regular(4, 4, &mut rng).is_none());
}
#[test]
fn test_hrg_node_count() {
let mut rng = StdRng::seed_from_u64(15);
let g = hyperbolic_random_graph(30, 6.0, 0.75, &mut rng).expect("hrg failed");
assert_eq!(g.node_count(), 30);
}
#[test]
fn test_hrg_small_radius_sparse() {
let mut rng = StdRng::seed_from_u64(16);
let g = hyperbolic_random_graph(50, 0.5, 0.75, &mut rng).expect("hrg small r failed");
assert!(g.edge_count() < 50 * 49 / 2);
}
#[test]
fn test_hrg_invalid_params() {
let mut rng = StdRng::seed_from_u64(17);
assert!(hyperbolic_random_graph(10, -1.0, 0.5, &mut rng).is_err());
assert!(hyperbolic_random_graph(10, 5.0, 0.0, &mut rng).is_err());
}
#[test]
fn test_kronecker_small() {
use scirs2_core::ndarray::array;
let mut rng = StdRng::seed_from_u64(18);
let theta = array![[0.9, 0.5], [0.5, 0.3]];
let g = kronecker_graph(&theta, 3, &mut rng).expect("kronecker failed");
assert_eq!(g.node_count(), 8);
for e in g.edges() {
assert!((e.weight - 1.0).abs() < 1e-9);
}
}
#[test]
fn test_kronecker_invalid() {
use scirs2_core::ndarray::array;
let mut rng = StdRng::seed_from_u64(19);
let non_square = Array2::zeros((2, 3));
assert!(kronecker_graph(&non_square, 2, &mut rng).is_err());
let theta = array![[0.5, 0.5], [0.5, 0.5]];
assert!(kronecker_graph(&theta, 0, &mut rng).is_err());
}
#[test]
fn test_chung_lu_basic() {
let mut rng = StdRng::seed_from_u64(20);
let w = vec![5.0, 4.0, 3.0, 3.0, 2.0, 2.0, 1.0, 1.0];
let g = chung_lu(&w, &mut rng).expect("chung_lu failed");
assert_eq!(g.node_count(), 8);
assert!(g.edge_count() > 0);
}
#[test]
fn test_chung_lu_zero_weights() {
let mut rng = StdRng::seed_from_u64(21);
let w = vec![0.0; 10];
let g = chung_lu(&w, &mut rng).expect("chung_lu zero weights failed");
assert_eq!(g.node_count(), 10);
assert_eq!(g.edge_count(), 0);
}
#[test]
fn test_chung_lu_invalid() {
let mut rng = StdRng::seed_from_u64(22);
assert!(chung_lu(&[-1.0, 2.0], &mut rng).is_err());
assert!(chung_lu(&[f64::INFINITY, 1.0], &mut rng).is_err());
}
#[test]
fn test_chung_lu_empty() {
let mut rng = StdRng::seed_from_u64(23);
let g = chung_lu(&[], &mut rng).expect("chung_lu empty failed");
assert_eq!(g.node_count(), 0);
}
}