pub mod advanced;
pub mod random_graphs;
pub mod temporal;
pub use advanced::{forest_fire, lfr_benchmark, LfrParams};
pub use temporal::{temporal_barabasi_albert, temporal_random_walk, TemporalWalk};
pub use random_graphs::{
barabasi_albert, chung_lu, erdos_renyi_g_nm, erdos_renyi_g_np, hyperbolic_random_graph,
kronecker_graph, random_regular, watts_strogatz,
};
use scirs2_core::random::prelude::*;
use std::collections::HashSet;
use crate::base::{DiGraph, Graph};
use crate::error::{GraphError, Result};
use scirs2_core::random::seq::SliceRandom;
use scirs2_core::rand_prelude::IndexedRandom;
#[allow(dead_code)]
pub fn create_graph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
) -> Graph<N, E> {
Graph::new()
}
#[allow(dead_code)]
pub fn create_digraph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
) -> DiGraph<N, E> {
DiGraph::new()
}
#[allow(dead_code)]
pub fn erdos_renyi_graph<R: Rng>(n: usize, p: f64, rng: &mut R) -> Result<Graph<usize, f64>> {
if !(0.0..=1.0).contains(&p) {
return Err(GraphError::InvalidGraph(
"Probability must be between 0 and 1".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 0..n {
for j in i + 1..n {
if rng.random::<f64>() < p {
graph.add_edge(i, j, 1.0)?;
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn barabasi_albert_graph<R: Rng>(n: usize, m: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
if m >= n {
return Err(GraphError::InvalidGraph(
"m must be less than n".to_string(),
));
}
if m == 0 {
return Err(GraphError::InvalidGraph("m must be positive".to_string()));
}
let mut graph = Graph::new();
for i in 0..=m {
graph.add_node(i);
}
for i in 0..=m {
for j in i + 1..=m {
graph.add_edge(i, j, 1.0)?;
}
}
let mut degrees = vec![m; m + 1];
let mut total_degree = m * (m + 1);
for new_node in (m + 1)..n {
graph.add_node(new_node);
let mut targets = HashSet::new();
while targets.len() < m {
let mut cumulative_prob = 0.0;
let random_value = rng.random::<f64>() * total_degree as f64;
for (node_id, °ree) in degrees.iter().enumerate() {
cumulative_prob += degree as f64;
if random_value <= cumulative_prob && !targets.contains(&node_id) {
targets.insert(node_id);
break;
}
}
}
for &target in &targets {
graph.add_edge(new_node, target, 1.0)?;
degrees[target] += 1;
total_degree += 2; }
degrees.push(m); }
Ok(graph)
}
#[allow(dead_code)]
pub fn complete_graph(n: usize) -> Result<Graph<usize, f64>> {
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 0..n {
for j in i + 1..n {
graph.add_edge(i, j, 1.0)?;
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn star_graph(n: usize) -> Result<Graph<usize, f64>> {
if n == 0 {
return Err(GraphError::InvalidGraph(
"Star graph must have at least 1 node".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 1..n {
graph.add_edge(0, i, 1.0)?;
}
Ok(graph)
}
#[allow(dead_code)]
pub fn path_graph(n: usize) -> Result<Graph<usize, f64>> {
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 0..n.saturating_sub(1) {
graph.add_edge(i, i + 1, 1.0)?;
}
Ok(graph)
}
#[allow(dead_code)]
pub fn tree_graph<R: Rng>(n: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
if n == 0 {
return Ok(Graph::new());
}
if n == 1 {
let mut graph = Graph::new();
graph.add_node(0);
return Ok(graph);
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
let mut in_tree = vec![false; n];
let mut tree_nodes = Vec::new();
let start = rng.random_range(0..n);
in_tree[start] = true;
tree_nodes.push(start);
for _ in 1..n {
let tree_node = tree_nodes[rng.random_range(0..tree_nodes.len())];
let candidates: Vec<usize> = (0..n).filter(|&i| !in_tree[i]).collect();
if candidates.is_empty() {
break;
}
let new_node = candidates[rng.random_range(0..candidates.len())];
graph.add_edge(tree_node, new_node, 1.0)?;
in_tree[new_node] = true;
tree_nodes.push(new_node);
}
Ok(graph)
}
#[allow(dead_code)]
pub fn random_spanning_tree<N, E, Ix, R>(
graph: &Graph<N, E, Ix>,
rng: &mut R,
) -> Result<Graph<N, E, Ix>>
where
N: crate::base::Node + std::fmt::Debug,
E: crate::base::EdgeWeight + Clone,
Ix: petgraph::graph::IndexType,
R: Rng,
{
let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
if nodes.is_empty() {
return Ok(Graph::new());
}
if nodes.len() == 1 {
let mut tree = Graph::new();
tree.add_node(nodes[0].clone());
return Ok(tree);
}
let mut edges: Vec<_> = graph.edges().into_iter().collect();
edges.shuffle(rng);
let mut tree = Graph::new();
for node in &nodes {
tree.add_node(node.clone());
}
let mut parent: std::collections::HashMap<N, N> =
nodes.iter().map(|n| (n.clone(), n.clone())).collect();
let mut rank: std::collections::HashMap<N, usize> =
nodes.iter().map(|n| (n.clone(), 0)).collect();
fn find<N: crate::base::Node>(parent: &mut std::collections::HashMap<N, N>, node: &N) -> N {
if parent[node] != *node {
let root = find(parent, &parent[node].clone());
parent.insert(node.clone(), root.clone());
}
parent[node].clone()
}
fn union<N: crate::base::Node>(
parent: &mut std::collections::HashMap<N, N>,
rank: &mut std::collections::HashMap<N, usize>,
x: &N,
y: &N,
) -> bool {
let root_x = find(parent, x);
let root_y = find(parent, y);
if root_x == root_y {
return false; }
match rank[&root_x].cmp(&rank[&root_y]) {
std::cmp::Ordering::Less => {
parent.insert(root_x, root_y);
}
std::cmp::Ordering::Greater => {
parent.insert(root_y, root_x);
}
std::cmp::Ordering::Equal => {
parent.insert(root_y, root_x.clone());
*rank.get_mut(&root_x).expect("Operation failed") += 1;
}
}
true
}
let mut edges_added = 0;
for edge in edges {
if union(&mut parent, &mut rank, &edge.source, &edge.target) {
tree.add_edge(edge.source, edge.target, edge.weight)?;
edges_added += 1;
if edges_added == nodes.len() - 1 {
break;
}
}
}
if edges_added != nodes.len() - 1 {
return Err(GraphError::InvalidGraph(
"Input graph is not connected - cannot create spanning tree".to_string(),
));
}
Ok(tree)
}
#[allow(dead_code)]
pub fn forest_graph<R: Rng>(
_tree_sizes: &[usize],
sizes: &[usize],
rng: &mut R,
) -> Result<Graph<usize, f64>> {
let mut forest = Graph::new();
let mut node_offset = 0;
for &tree_size in _tree_sizes {
if tree_size == 0 {
continue;
}
let tree = tree_graph(tree_size, rng)?;
for i in 0..tree_size {
forest.add_node(node_offset + i);
}
for edge in tree.edges() {
forest.add_edge(
node_offset + edge.source,
node_offset + edge.target,
edge.weight,
)?;
}
node_offset += tree_size;
}
Ok(forest)
}
#[allow(dead_code)]
pub fn cycle_graph(n: usize) -> Result<Graph<usize, f64>> {
if n < 3 {
return Err(GraphError::InvalidGraph(
"Cycle graph must have at least 3 nodes".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 0..n {
graph.add_edge(i, (i + 1) % n, 1.0)?;
}
Ok(graph)
}
#[allow(dead_code)]
pub fn grid_2d_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
if rows == 0 || cols == 0 {
return Err(GraphError::InvalidGraph(
"Grid dimensions must be positive".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..(rows * cols) {
graph.add_node(i);
}
for row in 0..rows {
for col in 0..cols {
let node_id = row * cols + col;
if col + 1 < cols {
let right_neighbor = row * cols + (col + 1);
graph.add_edge(node_id, right_neighbor, 1.0)?;
}
if row + 1 < rows {
let bottom_neighbor = (row + 1) * cols + col;
graph.add_edge(node_id, bottom_neighbor, 1.0)?;
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn grid_3d_graph(x_dim: usize, y_dim: usize, z_dim: usize) -> Result<Graph<usize, f64>> {
if x_dim == 0 || y_dim == 0 || z_dim == 0 {
return Err(GraphError::InvalidGraph(
"Grid dimensions must be positive".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..(x_dim * y_dim * z_dim) {
graph.add_node(i);
}
for z in 0..z_dim {
for y in 0..y_dim {
for x in 0..x_dim {
let node_id = z * x_dim * y_dim + y * x_dim + x;
if x + 1 < x_dim {
let right_neighbor = z * x_dim * y_dim + y * x_dim + (x + 1);
graph.add_edge(node_id, right_neighbor, 1.0)?;
}
if y + 1 < y_dim {
let front_neighbor = z * x_dim * y_dim + (y + 1) * x_dim + x;
graph.add_edge(node_id, front_neighbor, 1.0)?;
}
if z + 1 < z_dim {
let top_neighbor = (z + 1) * x_dim * y_dim + y * x_dim + x;
graph.add_edge(node_id, top_neighbor, 1.0)?;
}
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn triangular_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
if rows == 0 || cols == 0 {
return Err(GraphError::InvalidGraph(
"Lattice dimensions must be positive".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..(rows * cols) {
graph.add_node(i);
}
for row in 0..rows {
for col in 0..cols {
let node_id = row * cols + col;
if col + 1 < cols {
let right_neighbor = row * cols + (col + 1);
graph.add_edge(node_id, right_neighbor, 1.0)?;
}
if row + 1 < rows {
let bottom_neighbor = (row + 1) * cols + col;
graph.add_edge(node_id, bottom_neighbor, 1.0)?;
}
if row + 1 < rows && col + 1 < cols {
let diag_neighbor = (row + 1) * cols + (col + 1);
graph.add_edge(node_id, diag_neighbor, 1.0)?;
}
if row + 1 < rows && col > 0 && row % 2 == 0 {
let diag_neighbor = (row + 1) * cols + (col - 1);
graph.add_edge(node_id, diag_neighbor, 1.0)?;
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn hexagonal_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
if rows == 0 || cols == 0 {
return Err(GraphError::InvalidGraph(
"Lattice dimensions must be positive".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..(rows * cols) {
graph.add_node(i);
}
for row in 0..rows {
for col in 0..cols {
let node_id = row * cols + col;
if col + 1 < cols {
let right_neighbor = row * cols + (col + 1);
graph.add_edge(node_id, right_neighbor, 1.0)?;
}
if row % 2 == 0 {
if row + 1 < rows {
if col > 0 {
let down_left = (row + 1) * cols + (col - 1);
graph.add_edge(node_id, down_left, 1.0)?;
}
if col < cols {
let down_right = (row + 1) * cols + col;
graph.add_edge(node_id, down_right, 1.0)?;
}
}
} else {
if row + 1 < rows {
let down_left = (row + 1) * cols + col;
graph.add_edge(node_id, down_left, 1.0)?;
if col + 1 < cols {
let down_right = (row + 1) * cols + (col + 1);
graph.add_edge(node_id, down_right, 1.0)?;
}
}
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn watts_strogatz_graph<R: Rng>(
n: usize,
k: usize,
p: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if k >= n || !k.is_multiple_of(2) {
return Err(GraphError::InvalidGraph(
"k must be even and less than n".to_string(),
));
}
if !(0.0..=1.0).contains(&p) {
return Err(GraphError::InvalidGraph(
"Probability must be between 0 and 1".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
for i in 0..n {
for j in 1..=(k / 2) {
let neighbor = (i + j) % n;
graph.add_edge(i, neighbor, 1.0)?;
}
}
let edges_to_process: Vec<_> = graph.edges().into_iter().collect();
for edge in edges_to_process {
if rng.random::<f64>() < p {
let mut new_graph = Graph::new();
for i in 0..n {
new_graph.add_node(i);
}
for existing_edge in graph.edges() {
if (existing_edge.source != edge.source || existing_edge.target != edge.target)
&& (existing_edge.source != edge.target || existing_edge.target != edge.source)
{
new_graph.add_edge(
existing_edge.source,
existing_edge.target,
existing_edge.weight,
)?;
}
}
let mut new_target = rng.random_range(0..n);
while new_target == edge.source || new_graph.has_node(&new_target) {
new_target = rng.random_range(0..n);
}
new_graph.add_edge(edge.source, new_target, 1.0)?;
graph = new_graph;
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn stochastic_block_model<R: Rng>(
block_sizes: &[usize],
block_matrix: &[Vec<f64>],
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if block_sizes.is_empty() {
return Err(GraphError::InvalidGraph(
"At least one block must be specified".to_string(),
));
}
if block_matrix.len() != block_sizes.len() {
return Err(GraphError::InvalidGraph(
"Block _matrix dimensions must match number of blocks".to_string(),
));
}
for row in block_matrix {
if row.len() != block_sizes.len() {
return Err(GraphError::InvalidGraph(
"Block _matrix must be square".to_string(),
));
}
for &prob in row {
if !(0.0..=1.0).contains(&prob) {
return Err(GraphError::InvalidGraph(
"All probabilities must be between 0 and 1".to_string(),
));
}
}
}
let total_nodes: usize = block_sizes.iter().sum();
let mut graph = Graph::new();
for i in 0..total_nodes {
graph.add_node(i);
}
let mut node_to_block = vec![0; total_nodes];
let mut current_node = 0;
for (block_id, &block_size) in block_sizes.iter().enumerate() {
for _ in 0..block_size {
node_to_block[current_node] = block_id;
current_node += 1;
}
}
for i in 0..total_nodes {
for j in (i + 1)..total_nodes {
let block_i = node_to_block[i];
let block_j = node_to_block[j];
let prob = block_matrix[block_i][block_j];
if rng.random::<f64>() < prob {
graph.add_edge(i, j, 1.0)?;
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn two_community_sbm<R: Rng>(
n1: usize,
n2: usize,
p_in: f64,
p_out: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
let block_sizes = vec![n1, n2];
let block_matrix = vec![vec![p_in, p_out], vec![p_out, p_in]];
stochastic_block_model(&block_sizes, &block_matrix, rng)
}
#[allow(dead_code)]
pub fn planted_partition_model<R: Rng>(
n: usize,
k: usize,
p_in: f64,
p_out: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if !n.is_multiple_of(k) {
return Err(GraphError::InvalidGraph(
"Number of nodes must be divisible by number of communities".to_string(),
));
}
let community_size = n / k;
let block_sizes = vec![community_size; k];
let mut block_matrix = vec![vec![p_out; k]; k];
for (i, row) in block_matrix.iter_mut().enumerate().take(k) {
row[i] = p_in;
}
stochastic_block_model(&block_sizes, &block_matrix, rng)
}
#[allow(dead_code)]
pub fn configuration_model<R: Rng>(
degree_sequence: &[usize],
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if degree_sequence.is_empty() {
return Ok(Graph::new());
}
let total_degree: usize = degree_sequence.iter().sum();
if !total_degree.is_multiple_of(2) {
return Err(GraphError::InvalidGraph(
"Sum of degrees must be even".to_string(),
));
}
let n = degree_sequence.len();
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
let mut stubs = Vec::new();
for (node_id, °ree) in degree_sequence.iter().enumerate() {
for _ in 0..degree {
stubs.push(node_id);
}
}
while stubs.len() >= 2 {
let idx1 = rng.random_range(0..stubs.len());
let stub1 = stubs.remove(idx1);
let idx2 = rng.random_range(0..stubs.len());
let stub2 = stubs.remove(idx2);
graph.add_edge(stub1, stub2, 1.0)?;
}
Ok(graph)
}
#[allow(dead_code)]
pub fn simple_configuration_model<R: Rng>(
degree_sequence: &[usize],
rng: &mut R,
max_attempts: usize,
) -> Result<Graph<usize, f64>> {
if degree_sequence.is_empty() {
return Ok(Graph::new());
}
let total_degree: usize = degree_sequence.iter().sum();
if !total_degree.is_multiple_of(2) {
return Err(GraphError::InvalidGraph(
"Sum of degrees must be even".to_string(),
));
}
let n = degree_sequence.len();
for °ree in degree_sequence {
if degree >= n {
return Err(GraphError::InvalidGraph(
"Node degree cannot exceed n-1 in a simple graph".to_string(),
));
}
}
let mut _attempts = 0;
while _attempts < max_attempts {
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
let mut stubs = Vec::new();
for (node_id, °ree) in degree_sequence.iter().enumerate() {
for _ in 0..degree {
stubs.push(node_id);
}
}
let mut success = true;
while stubs.len() >= 2 && success {
let idx1 = rng.random_range(0..stubs.len());
let stub1 = stubs[idx1];
let idx2 = rng.random_range(0..stubs.len());
let stub2 = stubs[idx2];
if stub1 == stub2 || graph.has_edge(&stub1, &stub2) {
let mut retries = 0;
let mut found_valid = false;
while retries < 50 && !found_valid {
let new_idx2 = rng.random_range(0..stubs.len());
let new_stub2 = stubs[new_idx2];
if stub1 != new_stub2 && !graph.has_edge(&stub1, &new_stub2) {
if idx1 > new_idx2 {
stubs.remove(idx1);
stubs.remove(new_idx2);
} else {
stubs.remove(new_idx2);
stubs.remove(idx1);
}
graph.add_edge(stub1, new_stub2, 1.0)?;
found_valid = true;
}
retries += 1;
}
if !found_valid {
success = false;
}
} else {
if idx1 > idx2 {
stubs.remove(idx1);
stubs.remove(idx2);
} else {
stubs.remove(idx2);
stubs.remove(idx1);
}
graph.add_edge(stub1, stub2, 1.0)?;
}
}
if success && stubs.is_empty() {
return Ok(graph);
}
_attempts += 1;
}
Err(GraphError::InvalidGraph(
"Could not generate simple graph with given degree _sequence after maximum _attempts"
.to_string(),
))
}
#[allow(dead_code)]
pub fn random_geometric_graph<R: Rng>(
n: usize,
radius: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if radius < 0.0 {
return Err(GraphError::InvalidGraph(
"Radius must be non-negative".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..n {
graph.add_node(i);
}
if n == 0 {
return Ok(graph);
}
let positions: Vec<(f64, f64)> = (0..n)
.map(|_| (rng.random::<f64>(), rng.random::<f64>()))
.collect();
let radius_sq = radius * radius;
for i in 0..n {
for j in (i + 1)..n {
let dx = positions[i].0 - positions[j].0;
let dy = positions[i].1 - positions[j].1;
let dist_sq = dx * dx + dy * dy;
if dist_sq <= radius_sq {
let dist = dist_sq.sqrt();
graph.add_edge(i, j, dist)?;
}
}
}
Ok(graph)
}
#[allow(dead_code)]
pub fn power_law_cluster_graph<R: Rng>(
n: usize,
m: usize,
p_triangle: f64,
rng: &mut R,
) -> Result<Graph<usize, f64>> {
if m == 0 {
return Err(GraphError::InvalidGraph("m must be positive".to_string()));
}
if m >= n {
return Err(GraphError::InvalidGraph(
"m must be less than n".to_string(),
));
}
if !(0.0..=1.0).contains(&p_triangle) {
return Err(GraphError::InvalidGraph(
"p_triangle must be between 0 and 1".to_string(),
));
}
let mut graph = Graph::new();
for i in 0..=m {
graph.add_node(i);
}
for i in 0..=m {
for j in (i + 1)..=m {
graph.add_edge(i, j, 1.0)?;
}
}
let mut degrees = vec![m; m + 1];
let mut total_degree = m * (m + 1);
for new_node in (m + 1)..n {
graph.add_node(new_node);
let mut targets_added: HashSet<usize> = HashSet::new();
let mut edges_to_add = m;
if edges_to_add > 0 {
let target = select_preferential_attachment(
°rees,
total_degree,
&targets_added,
new_node,
rng,
);
if let Some(t) = target {
graph.add_edge(new_node, t, 1.0)?;
targets_added.insert(t);
degrees[t] += 1;
total_degree += 2;
edges_to_add -= 1;
}
}
while edges_to_add > 0 {
if rng.random::<f64>() < p_triangle && !targets_added.is_empty() {
let last_target = *targets_added.iter().last().unwrap_or(&0);
let neighbors_of_target = graph.neighbors(&last_target).unwrap_or_default();
let candidates: Vec<usize> = neighbors_of_target
.into_iter()
.filter(|nb| *nb != new_node && !targets_added.contains(nb))
.collect();
if let Some(&chosen) = candidates.choose(rng) {
graph.add_edge(new_node, chosen, 1.0)?;
targets_added.insert(chosen);
degrees[chosen] += 1;
total_degree += 2;
edges_to_add -= 1;
continue;
}
}
let target = select_preferential_attachment(
°rees,
total_degree,
&targets_added,
new_node,
rng,
);
if let Some(t) = target {
graph.add_edge(new_node, t, 1.0)?;
targets_added.insert(t);
degrees[t] += 1;
total_degree += 2;
edges_to_add -= 1;
} else {
break;
}
}
degrees.push(targets_added.len());
}
Ok(graph)
}
fn select_preferential_attachment<R: Rng>(
degrees: &[usize],
total_degree: usize,
excluded: &HashSet<usize>,
new_node: usize,
rng: &mut R,
) -> Option<usize> {
if total_degree == 0 {
return None;
}
for _ in 0..100 {
let mut cumulative = 0.0;
let random_value = rng.random::<f64>() * total_degree as f64;
for (node_id, °ree) in degrees.iter().enumerate() {
if node_id == new_node {
continue;
}
cumulative += degree as f64;
if random_value <= cumulative && !excluded.contains(&node_id) {
return Some(node_id);
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_erdos_renyi_graph() {
let mut rng = StdRng::seed_from_u64(42);
let graph = erdos_renyi_graph(10, 0.3, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 10);
assert!(graph.edge_count() <= 45);
}
#[test]
fn test_complete_graph() {
let graph = complete_graph(5).expect("Operation failed");
assert_eq!(graph.node_count(), 5);
assert_eq!(graph.edge_count(), 10); }
#[test]
fn test_star_graph() {
let graph = star_graph(6).expect("Operation failed");
assert_eq!(graph.node_count(), 6);
assert_eq!(graph.edge_count(), 5); }
#[test]
fn test_path_graph() {
let graph = path_graph(5).expect("Operation failed");
assert_eq!(graph.node_count(), 5);
assert_eq!(graph.edge_count(), 4); }
#[test]
fn test_cycle_graph() {
let graph = cycle_graph(5).expect("Operation failed");
assert_eq!(graph.node_count(), 5);
assert_eq!(graph.edge_count(), 5);
assert!(cycle_graph(2).is_err());
}
#[test]
fn test_grid_2d_graph() {
let graph = grid_2d_graph(3, 4).expect("Operation failed");
assert_eq!(graph.node_count(), 12); assert_eq!(graph.edge_count(), 17); }
#[test]
fn test_grid_3d_graph() {
let graph = grid_3d_graph(2, 2, 2).expect("Operation failed");
assert_eq!(graph.node_count(), 8); assert_eq!(graph.edge_count(), 12);
}
#[test]
fn test_triangular_lattice_graph() {
let graph = triangular_lattice_graph(3, 3).expect("Operation failed");
assert_eq!(graph.node_count(), 9); assert!(graph.edge_count() > 12); }
#[test]
fn test_hexagonal_lattice_graph() {
let graph = hexagonal_lattice_graph(3, 3).expect("Operation failed");
assert_eq!(graph.node_count(), 9); assert!(graph.edge_count() >= 6);
}
#[test]
fn test_barabasi_albert_graph() {
let mut rng = StdRng::seed_from_u64(42);
let graph = barabasi_albert_graph(10, 2, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 10);
assert_eq!(graph.edge_count(), 17);
}
#[test]
fn test_stochastic_block_model() {
let mut rng = StdRng::seed_from_u64(42);
let block_sizes = vec![3, 4];
let block_matrix = vec![vec![0.8, 0.1], vec![0.1, 0.8]];
let graph = stochastic_block_model(&block_sizes, &block_matrix, &mut rng)
.expect("Operation failed");
assert_eq!(graph.node_count(), 7);
for i in 0..7 {
assert!(graph.has_node(&i));
}
}
#[test]
fn test_two_community_sbm() {
let mut rng = StdRng::seed_from_u64(42);
let graph = two_community_sbm(5, 5, 0.8, 0.1, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 10);
assert!(graph.edge_count() > 0);
}
#[test]
fn test_planted_partition_model() {
let mut rng = StdRng::seed_from_u64(42);
let graph = planted_partition_model(12, 3, 0.7, 0.1, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 12);
assert!(graph.edge_count() > 0);
}
#[test]
fn test_stochastic_block_model_errors() {
let mut rng = StdRng::seed_from_u64(42);
assert!(stochastic_block_model(&[], &[], &mut rng).is_err());
let block_sizes = vec![3, 4];
let wrong_matrix = vec![vec![0.5]];
assert!(stochastic_block_model(&block_sizes, &wrong_matrix, &mut rng).is_err());
let bad_matrix = vec![vec![1.5, 0.5], vec![0.5, 0.5]];
assert!(stochastic_block_model(&block_sizes, &bad_matrix, &mut rng).is_err());
assert!(planted_partition_model(10, 3, 0.5, 0.1, &mut rng).is_err());
}
#[test]
fn test_configuration_model() {
let mut rng = StdRng::seed_from_u64(42);
let degree_sequence = vec![2, 2, 2, 2]; let graph = configuration_model(°ree_sequence, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 4);
assert_eq!(graph.edge_count(), 4);
for (i, &expected_degree) in degree_sequence.iter().enumerate() {
let actual_degree = graph.degree(&i);
assert_eq!(actual_degree, expected_degree);
}
}
#[test]
fn test_configuration_model_errors() {
let mut rng = StdRng::seed_from_u64(42);
let odd_degree_sequence = vec![1, 2, 2]; assert!(configuration_model(&odd_degree_sequence, &mut rng).is_err());
let empty_sequence = vec![];
let graph = configuration_model(&empty_sequence, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 0);
}
#[test]
fn test_simple_configuration_model() {
let mut rng = StdRng::seed_from_u64(42);
let degree_sequence = vec![2, 2, 2, 2]; let graph =
simple_configuration_model(°ree_sequence, &mut rng, 100).expect("Operation failed");
assert_eq!(graph.node_count(), 4);
assert_eq!(graph.edge_count(), 4);
for i in 0..4 {
assert!(!graph.has_edge(&i, &i), "Graph should not have self-loops");
}
for (i, &expected_degree) in degree_sequence.iter().enumerate() {
let actual_degree = graph.degree(&i);
assert_eq!(actual_degree, expected_degree);
}
}
#[test]
fn test_simple_configuration_model_errors() {
let mut rng = StdRng::seed_from_u64(42);
let invalid_degree_sequence = vec![4, 2, 2, 2]; assert!(simple_configuration_model(&invalid_degree_sequence, &mut rng, 10).is_err());
let odd_degree_sequence = vec![1, 2, 2]; assert!(simple_configuration_model(&odd_degree_sequence, &mut rng, 10).is_err());
}
#[test]
fn test_tree_graph() {
let mut rng = StdRng::seed_from_u64(42);
let empty_tree = tree_graph(0, &mut rng).expect("Operation failed");
assert_eq!(empty_tree.node_count(), 0);
assert_eq!(empty_tree.edge_count(), 0);
let single_tree = tree_graph(1, &mut rng).expect("Operation failed");
assert_eq!(single_tree.node_count(), 1);
assert_eq!(single_tree.edge_count(), 0);
let tree = tree_graph(5, &mut rng).expect("Operation failed");
assert_eq!(tree.node_count(), 5);
assert_eq!(tree.edge_count(), 4);
for i in 0..5 {
assert!(tree.has_node(&i));
}
}
#[test]
fn test_random_spanning_tree() {
let mut rng = StdRng::seed_from_u64(42);
let complete = complete_graph(4).expect("Operation failed");
let spanning_tree = random_spanning_tree(&complete, &mut rng).expect("Operation failed");
assert_eq!(spanning_tree.node_count(), 4);
assert_eq!(spanning_tree.edge_count(), 3);
for i in 0..4 {
assert!(spanning_tree.has_node(&i));
}
}
#[test]
fn test_forest_graph() {
let mut rng = StdRng::seed_from_u64(42);
let tree_sizes = vec![3, 2, 4];
let forest = forest_graph(&tree_sizes, &tree_sizes, &mut rng).expect("Operation failed");
assert_eq!(forest.node_count(), 9); assert_eq!(forest.edge_count(), 6);
for i in 0..9 {
assert!(forest.has_node(&i));
}
let empty_forest = forest_graph(&[], &[], &mut rng).expect("Operation failed");
assert_eq!(empty_forest.node_count(), 0);
assert_eq!(empty_forest.edge_count(), 0);
let forest_with_zeros =
forest_graph(&[0, 3, 0, 2], &[0, 3, 0, 2], &mut rng).expect("Operation failed");
assert_eq!(forest_with_zeros.node_count(), 5); assert_eq!(forest_with_zeros.edge_count(), 3); }
#[test]
fn test_random_geometric_graph() {
let mut rng = StdRng::seed_from_u64(42);
let graph = random_geometric_graph(20, 0.4, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 20);
assert!(graph.edge_count() > 0);
assert!(graph.edge_count() < 20 * 19 / 2);
for edge in graph.edges() {
assert!(edge.weight > 0.0);
assert!(edge.weight <= 0.4 + 1e-10); }
}
#[test]
fn test_random_geometric_graph_large_radius() {
let mut rng = StdRng::seed_from_u64(42);
let graph = random_geometric_graph(5, 2.0, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 5);
assert_eq!(graph.edge_count(), 10); }
#[test]
fn test_random_geometric_graph_zero_radius() {
let mut rng = StdRng::seed_from_u64(42);
let graph = random_geometric_graph(10, 0.0, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 10);
assert_eq!(graph.edge_count(), 0); }
#[test]
fn test_random_geometric_graph_errors() {
let mut rng = StdRng::seed_from_u64(42);
assert!(random_geometric_graph(10, -0.1, &mut rng).is_err());
}
#[test]
fn test_random_geometric_graph_empty() {
let mut rng = StdRng::seed_from_u64(42);
let graph = random_geometric_graph(0, 0.5, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 0);
assert_eq!(graph.edge_count(), 0);
}
#[test]
fn test_power_law_cluster_graph() {
let mut rng = StdRng::seed_from_u64(42);
let graph = power_law_cluster_graph(20, 2, 0.5, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 20);
assert!(graph.edge_count() > 0);
}
#[test]
fn test_power_law_cluster_no_triangle() {
let mut rng = StdRng::seed_from_u64(42);
let graph = power_law_cluster_graph(15, 2, 0.0, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 15);
assert!(graph.edge_count() > 0);
}
#[test]
fn test_power_law_cluster_max_triangle() {
let mut rng = StdRng::seed_from_u64(42);
let graph = power_law_cluster_graph(15, 2, 1.0, &mut rng).expect("Operation failed");
assert_eq!(graph.node_count(), 15);
assert!(graph.edge_count() > 0);
}
#[test]
fn test_power_law_cluster_errors() {
let mut rng = StdRng::seed_from_u64(42);
assert!(power_law_cluster_graph(10, 0, 0.5, &mut rng).is_err());
assert!(power_law_cluster_graph(5, 5, 0.5, &mut rng).is_err());
assert!(power_law_cluster_graph(10, 2, 1.5, &mut rng).is_err());
assert!(power_law_cluster_graph(10, 2, -0.1, &mut rng).is_err());
}
}