Skip to main content

scirs2_graph/generators/
mod.rs

1//! Graph generation algorithms
2//!
3//! This module provides functions for generating various types of graphs:
4//! - Random graphs (Erdős–Rényi, Barabási–Albert, etc.)
5//! - Regular graphs (complete, star, path, cycle)
6//! - Lattice graphs
7//! - Small-world networks
8
9pub mod advanced;
10pub mod random_graphs;
11pub mod temporal;
12
13pub use advanced::{forest_fire, lfr_benchmark, LfrParams};
14pub use temporal::{temporal_barabasi_albert, temporal_random_walk, TemporalWalk};
15
16pub use random_graphs::{
17    barabasi_albert, chung_lu, erdos_renyi_g_nm, erdos_renyi_g_np, hyperbolic_random_graph,
18    kronecker_graph, random_regular, watts_strogatz,
19};
20
21use scirs2_core::random::prelude::*;
22use std::collections::HashSet;
23
24use crate::base::{DiGraph, Graph};
25use crate::error::{GraphError, Result};
26use scirs2_core::random::seq::SliceRandom;
27
28// Import IndexedRandom for .choose() on slices (rand 0.9+)
29use scirs2_core::rand_prelude::IndexedRandom;
30
31/// Create a new empty undirected graph
32#[allow(dead_code)]
33pub fn create_graph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
34) -> Graph<N, E> {
35    Graph::new()
36}
37
38/// Create a new empty directed graph
39#[allow(dead_code)]
40pub fn create_digraph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
41) -> DiGraph<N, E> {
42    DiGraph::new()
43}
44
45/// Generates an Erdős–Rényi random graph
46///
47/// # Arguments
48/// * `n` - Number of nodes
49/// * `p` - Probability of edge creation between any two nodes
50/// * `rng` - Random number generator
51///
52/// # Returns
53/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
54#[allow(dead_code)]
55pub fn erdos_renyi_graph<R: Rng>(n: usize, p: f64, rng: &mut R) -> Result<Graph<usize, f64>> {
56    if !(0.0..=1.0).contains(&p) {
57        return Err(GraphError::InvalidGraph(
58            "Probability must be between 0 and 1".to_string(),
59        ));
60    }
61
62    let mut graph = Graph::new();
63
64    // Add all nodes
65    for i in 0..n {
66        graph.add_node(i);
67    }
68
69    // Add edges with probability p
70    for i in 0..n {
71        for j in i + 1..n {
72            if rng.random::<f64>() < p {
73                graph.add_edge(i, j, 1.0)?;
74            }
75        }
76    }
77
78    Ok(graph)
79}
80
81/// Generates a Barabási–Albert preferential attachment graph
82///
83/// # Arguments
84/// * `n` - Total number of nodes
85/// * `m` - Number of edges to attach from a new node to existing nodes
86/// * `rng` - Random number generator
87///
88/// # Returns
89/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
90#[allow(dead_code)]
91pub fn barabasi_albert_graph<R: Rng>(n: usize, m: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
92    if m >= n {
93        return Err(GraphError::InvalidGraph(
94            "m must be less than n".to_string(),
95        ));
96    }
97    if m == 0 {
98        return Err(GraphError::InvalidGraph("m must be positive".to_string()));
99    }
100
101    let mut graph = Graph::new();
102
103    // Start with a complete graph of m+1 nodes
104    for i in 0..=m {
105        graph.add_node(i);
106    }
107
108    for i in 0..=m {
109        for j in i + 1..=m {
110            graph.add_edge(i, j, 1.0)?;
111        }
112    }
113
114    // Keep track of node degrees for preferential attachment
115    let mut degrees = vec![m; m + 1];
116    let mut total_degree = m * (m + 1);
117
118    // Add remaining nodes
119    for new_node in (m + 1)..n {
120        graph.add_node(new_node);
121
122        let mut targets = HashSet::new();
123
124        // Select m nodes to connect to based on preferential attachment
125        while targets.len() < m {
126            let mut cumulative_prob = 0.0;
127            let random_value = rng.random::<f64>() * total_degree as f64;
128
129            for (node_id, &degree) in degrees.iter().enumerate() {
130                cumulative_prob += degree as f64;
131                if random_value <= cumulative_prob && !targets.contains(&node_id) {
132                    targets.insert(node_id);
133                    break;
134                }
135            }
136        }
137
138        // Add edges to selected targets
139        for &target in &targets {
140            graph.add_edge(new_node, target, 1.0)?;
141            degrees[target] += 1;
142            total_degree += 2; // Each edge adds 2 to total degree
143        }
144
145        degrees.push(m); // New node has degree m
146    }
147
148    Ok(graph)
149}
150
151/// Generates a complete graph (clique)
152///
153/// # Arguments
154/// * `n` - Number of nodes
155///
156/// # Returns
157/// * `Result<Graph<usize, f64>>` - A complete graph with n nodes
158#[allow(dead_code)]
159pub fn complete_graph(n: usize) -> Result<Graph<usize, f64>> {
160    let mut graph = Graph::new();
161
162    // Add all nodes
163    for i in 0..n {
164        graph.add_node(i);
165    }
166
167    // Add all possible edges
168    for i in 0..n {
169        for j in i + 1..n {
170            graph.add_edge(i, j, 1.0)?;
171        }
172    }
173
174    Ok(graph)
175}
176
177/// Generates a star graph with one central node connected to all others
178///
179/// # Arguments
180/// * `n` - Total number of nodes (must be >= 1)
181///
182/// # Returns
183/// * `Result<Graph<usize, f64>>` - A star graph with node 0 as the center
184#[allow(dead_code)]
185pub fn star_graph(n: usize) -> Result<Graph<usize, f64>> {
186    if n == 0 {
187        return Err(GraphError::InvalidGraph(
188            "Star graph must have at least 1 node".to_string(),
189        ));
190    }
191
192    let mut graph = Graph::new();
193
194    // Add all nodes
195    for i in 0..n {
196        graph.add_node(i);
197    }
198
199    // Connect center (node 0) to all other nodes
200    for i in 1..n {
201        graph.add_edge(0, i, 1.0)?;
202    }
203
204    Ok(graph)
205}
206
207/// Generates a path graph (nodes connected in a line)
208///
209/// # Arguments
210/// * `n` - Number of nodes
211///
212/// # Returns
213/// * `Result<Graph<usize, f64>>` - A path graph with nodes 0, 1, ..., n-1
214#[allow(dead_code)]
215pub fn path_graph(n: usize) -> Result<Graph<usize, f64>> {
216    let mut graph = Graph::new();
217
218    // Add all nodes
219    for i in 0..n {
220        graph.add_node(i);
221    }
222
223    // Connect consecutive nodes
224    for i in 0..n.saturating_sub(1) {
225        graph.add_edge(i, i + 1, 1.0)?;
226    }
227
228    Ok(graph)
229}
230
231/// Generates a random tree with n nodes
232///
233/// Uses a random process to connect nodes while maintaining the tree property
234/// (connected and acyclic). Each tree has exactly n-1 edges.
235///
236/// # Arguments
237/// * `n` - Number of nodes
238/// * `rng` - Random number generator
239///
240/// # Returns
241/// * `Result<Graph<usize, f64>>` - A random tree with nodes 0, 1, ..., n-1
242#[allow(dead_code)]
243pub fn tree_graph<R: Rng>(n: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
244    if n == 0 {
245        return Ok(Graph::new());
246    }
247    if n == 1 {
248        let mut graph = Graph::new();
249        graph.add_node(0);
250        return Ok(graph);
251    }
252
253    let mut graph = Graph::new();
254
255    // Add all nodes
256    for i in 0..n {
257        graph.add_node(i);
258    }
259
260    // Use Prim's algorithm variation to build a random tree
261    let mut in_tree = vec![false; n];
262    let mut tree_nodes = Vec::new();
263
264    // Start with a random node
265    let start = rng.random_range(0..n);
266    in_tree[start] = true;
267    tree_nodes.push(start);
268
269    // Add n-1 edges to complete the tree
270    for _ in 1..n {
271        // Pick a random node already in the tree
272        let tree_node = tree_nodes[rng.random_range(0..tree_nodes.len())];
273
274        // Pick a random node not yet in the tree
275        let candidates: Vec<usize> = (0..n).filter(|&i| !in_tree[i]).collect();
276        if candidates.is_empty() {
277            break;
278        }
279
280        let new_node = candidates[rng.random_range(0..candidates.len())];
281
282        // Add edge and mark node as in tree
283        graph.add_edge(tree_node, new_node, 1.0)?;
284        in_tree[new_node] = true;
285        tree_nodes.push(new_node);
286    }
287
288    Ok(graph)
289}
290
291/// Generates a random spanning tree from an existing graph
292///
293/// Uses Kruskal's algorithm with randomized edge selection to produce
294/// a random spanning tree of the input graph.
295///
296/// # Arguments
297/// * `graph` - The input graph to extract a spanning tree from
298/// * `rng` - Random number generator
299///
300/// # Returns
301/// * `Result<Graph<N, E>>` - A spanning tree of the input graph
302#[allow(dead_code)]
303pub fn random_spanning_tree<N, E, Ix, R>(
304    graph: &Graph<N, E, Ix>,
305    rng: &mut R,
306) -> Result<Graph<N, E, Ix>>
307where
308    N: crate::base::Node + std::fmt::Debug,
309    E: crate::base::EdgeWeight + Clone,
310    Ix: petgraph::graph::IndexType,
311    R: Rng,
312{
313    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
314    if nodes.is_empty() {
315        return Ok(Graph::new());
316    }
317    if nodes.len() == 1 {
318        let mut tree = Graph::new();
319        tree.add_node(nodes[0].clone());
320        return Ok(tree);
321    }
322
323    // Get all edges and shuffle them randomly
324    let mut edges: Vec<_> = graph.edges().into_iter().collect();
325    edges.shuffle(rng);
326
327    let mut tree = Graph::new();
328
329    // Add all nodes to the tree
330    for node in &nodes {
331        tree.add_node(node.clone());
332    }
333
334    // Use Union-Find to track components
335    let mut parent: std::collections::HashMap<N, N> =
336        nodes.iter().map(|n| (n.clone(), n.clone())).collect();
337    let mut rank: std::collections::HashMap<N, usize> =
338        nodes.iter().map(|n| (n.clone(), 0)).collect();
339
340    fn find<N: crate::base::Node>(parent: &mut std::collections::HashMap<N, N>, node: &N) -> N {
341        if parent[node] != *node {
342            let root = find(parent, &parent[node].clone());
343            parent.insert(node.clone(), root.clone());
344        }
345        parent[node].clone()
346    }
347
348    fn union<N: crate::base::Node>(
349        parent: &mut std::collections::HashMap<N, N>,
350        rank: &mut std::collections::HashMap<N, usize>,
351        x: &N,
352        y: &N,
353    ) -> bool {
354        let root_x = find(parent, x);
355        let root_y = find(parent, y);
356
357        if root_x == root_y {
358            return false; // Already in same component
359        }
360
361        // Union by rank
362        match rank[&root_x].cmp(&rank[&root_y]) {
363            std::cmp::Ordering::Less => {
364                parent.insert(root_x, root_y);
365            }
366            std::cmp::Ordering::Greater => {
367                parent.insert(root_y, root_x);
368            }
369            std::cmp::Ordering::Equal => {
370                parent.insert(root_y, root_x.clone());
371                *rank.get_mut(&root_x).expect("Operation failed") += 1;
372            }
373        }
374        true
375    }
376
377    let mut edges_added = 0;
378
379    // Add edges without creating cycles until we have n-1 edges
380    for edge in edges {
381        if union(&mut parent, &mut rank, &edge.source, &edge.target) {
382            tree.add_edge(edge.source, edge.target, edge.weight)?;
383            edges_added += 1;
384            if edges_added == nodes.len() - 1 {
385                break;
386            }
387        }
388    }
389
390    // Check if we have a spanning tree (connected graph)
391    if edges_added != nodes.len() - 1 {
392        return Err(GraphError::InvalidGraph(
393            "Input graph is not connected - cannot create spanning tree".to_string(),
394        ));
395    }
396
397    Ok(tree)
398}
399
400/// Generates a random forest (collection of trees)
401///
402/// Creates a forest by generating multiple random trees and combining them
403/// into a single graph. The trees are disjoint (no edges between different trees).
404///
405/// # Arguments
406/// * `tree_sizes` - Vector specifying the size of each tree in the forest
407/// * `rng` - Random number generator
408///
409/// # Returns
410/// * `Result<Graph<usize, f64>>` - A forest containing the specified trees
411#[allow(dead_code)]
412pub fn forest_graph<R: Rng>(
413    _tree_sizes: &[usize],
414    sizes: &[usize],
415    rng: &mut R,
416) -> Result<Graph<usize, f64>> {
417    let mut forest = Graph::new();
418    let mut node_offset = 0;
419
420    for &tree_size in _tree_sizes {
421        if tree_size == 0 {
422            continue;
423        }
424
425        // Generate a tree with nodes starting from node_offset
426        let tree = tree_graph(tree_size, rng)?;
427
428        // Add nodes to forest with offset
429        for i in 0..tree_size {
430            forest.add_node(node_offset + i);
431        }
432
433        // Add edges with offset
434        for edge in tree.edges() {
435            forest.add_edge(
436                node_offset + edge.source,
437                node_offset + edge.target,
438                edge.weight,
439            )?;
440        }
441
442        node_offset += tree_size;
443    }
444
445    Ok(forest)
446}
447
448/// Generates a cycle graph (circular arrangement of nodes)
449///
450/// # Arguments
451/// * `n` - Number of nodes (must be >= 3 for a meaningful cycle)
452///
453/// # Returns
454/// * `Result<Graph<usize, f64>>` - A cycle graph with nodes 0, 1, ..., n-1
455#[allow(dead_code)]
456pub fn cycle_graph(n: usize) -> Result<Graph<usize, f64>> {
457    if n < 3 {
458        return Err(GraphError::InvalidGraph(
459            "Cycle graph must have at least 3 nodes".to_string(),
460        ));
461    }
462
463    let mut graph = Graph::new();
464
465    // Add all nodes
466    for i in 0..n {
467        graph.add_node(i);
468    }
469
470    // Connect consecutive nodes
471    for i in 0..n {
472        graph.add_edge(i, (i + 1) % n, 1.0)?;
473    }
474
475    Ok(graph)
476}
477
478/// Generates a 2D grid/lattice graph
479///
480/// # Arguments
481/// * `rows` - Number of rows
482/// * `cols` - Number of columns
483///
484/// # Returns
485/// * `Result<Graph<usize, f64>>` - A grid graph where node ID = row * cols + col
486#[allow(dead_code)]
487pub fn grid_2d_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
488    if rows == 0 || cols == 0 {
489        return Err(GraphError::InvalidGraph(
490            "Grid dimensions must be positive".to_string(),
491        ));
492    }
493
494    let mut graph = Graph::new();
495
496    // Add all nodes
497    for i in 0..(rows * cols) {
498        graph.add_node(i);
499    }
500
501    // Add edges to adjacent nodes (4-connectivity)
502    for row in 0..rows {
503        for col in 0..cols {
504            let node_id = row * cols + col;
505
506            // Connect to right neighbor
507            if col + 1 < cols {
508                let right_neighbor = row * cols + (col + 1);
509                graph.add_edge(node_id, right_neighbor, 1.0)?;
510            }
511
512            // Connect to bottom neighbor
513            if row + 1 < rows {
514                let bottom_neighbor = (row + 1) * cols + col;
515                graph.add_edge(node_id, bottom_neighbor, 1.0)?;
516            }
517        }
518    }
519
520    Ok(graph)
521}
522
523/// Generates a 3D grid/lattice graph
524///
525/// # Arguments
526/// * `x_dim` - Size in x dimension
527/// * `y_dim` - Size in y dimension  
528/// * `z_dim` - Size in z dimension
529///
530/// # Returns
531/// * `Result<Graph<usize, f64>>` - A 3D grid graph where node ID = z*x_dim*y_dim + y*x_dim + x
532#[allow(dead_code)]
533pub fn grid_3d_graph(x_dim: usize, y_dim: usize, z_dim: usize) -> Result<Graph<usize, f64>> {
534    if x_dim == 0 || y_dim == 0 || z_dim == 0 {
535        return Err(GraphError::InvalidGraph(
536            "Grid dimensions must be positive".to_string(),
537        ));
538    }
539
540    let mut graph = Graph::new();
541
542    // Add all nodes
543    for i in 0..(x_dim * y_dim * z_dim) {
544        graph.add_node(i);
545    }
546
547    // Connect neighbors in 3D grid
548    for z in 0..z_dim {
549        for y in 0..y_dim {
550            for x in 0..x_dim {
551                let node_id = z * x_dim * y_dim + y * x_dim + x;
552
553                // Connect to right neighbor
554                if x + 1 < x_dim {
555                    let right_neighbor = z * x_dim * y_dim + y * x_dim + (x + 1);
556                    graph.add_edge(node_id, right_neighbor, 1.0)?;
557                }
558
559                // Connect to front neighbor
560                if y + 1 < y_dim {
561                    let front_neighbor = z * x_dim * y_dim + (y + 1) * x_dim + x;
562                    graph.add_edge(node_id, front_neighbor, 1.0)?;
563                }
564
565                // Connect to top neighbor
566                if z + 1 < z_dim {
567                    let top_neighbor = (z + 1) * x_dim * y_dim + y * x_dim + x;
568                    graph.add_edge(node_id, top_neighbor, 1.0)?;
569                }
570            }
571        }
572    }
573
574    Ok(graph)
575}
576
577/// Generates a triangular lattice graph
578///
579/// # Arguments
580/// * `rows` - Number of rows
581/// * `cols` - Number of columns
582///
583/// # Returns
584/// * `Result<Graph<usize, f64>>` - A triangular lattice where each node has up to 6 neighbors
585#[allow(dead_code)]
586pub fn triangular_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
587    if rows == 0 || cols == 0 {
588        return Err(GraphError::InvalidGraph(
589            "Lattice dimensions must be positive".to_string(),
590        ));
591    }
592
593    let mut graph = Graph::new();
594
595    // Add all nodes
596    for i in 0..(rows * cols) {
597        graph.add_node(i);
598    }
599
600    for row in 0..rows {
601        for col in 0..cols {
602            let node_id = row * cols + col;
603
604            // Standard grid connections (4-connected)
605            // Right neighbor
606            if col + 1 < cols {
607                let right_neighbor = row * cols + (col + 1);
608                graph.add_edge(node_id, right_neighbor, 1.0)?;
609            }
610
611            // Bottom neighbor
612            if row + 1 < rows {
613                let bottom_neighbor = (row + 1) * cols + col;
614                graph.add_edge(node_id, bottom_neighbor, 1.0)?;
615            }
616
617            // Diagonal connections for triangular lattice
618            // Bottom-right diagonal
619            if row + 1 < rows && col + 1 < cols {
620                let diag_neighbor = (row + 1) * cols + (col + 1);
621                graph.add_edge(node_id, diag_neighbor, 1.0)?;
622            }
623
624            // Bottom-left diagonal (for even rows)
625            if row + 1 < rows && col > 0 && row % 2 == 0 {
626                let diag_neighbor = (row + 1) * cols + (col - 1);
627                graph.add_edge(node_id, diag_neighbor, 1.0)?;
628            }
629        }
630    }
631
632    Ok(graph)
633}
634
635/// Generates a hexagonal lattice graph (honeycomb structure)
636///
637/// # Arguments
638/// * `rows` - Number of rows
639/// * `cols` - Number of columns
640///
641/// # Returns
642/// * `Result<Graph<usize, f64>>` - A hexagonal lattice where each node has exactly 3 neighbors
643#[allow(dead_code)]
644pub fn hexagonal_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
645    if rows == 0 || cols == 0 {
646        return Err(GraphError::InvalidGraph(
647            "Lattice dimensions must be positive".to_string(),
648        ));
649    }
650
651    let mut graph = Graph::new();
652
653    // Add all nodes
654    for i in 0..(rows * cols) {
655        graph.add_node(i);
656    }
657
658    for row in 0..rows {
659        for col in 0..cols {
660            let node_id = row * cols + col;
661
662            // Hexagonal lattice connections (3 neighbors per node in honeycomb pattern)
663            // This creates a simplified hexagonal structure
664
665            // Right neighbor (horizontal)
666            if col + 1 < cols {
667                let right_neighbor = row * cols + (col + 1);
668                graph.add_edge(node_id, right_neighbor, 1.0)?;
669            }
670
671            // Connect in honeycomb pattern
672            if row % 2 == 0 {
673                // Even _rows: connect down-left and down-right
674                if row + 1 < rows {
675                    if col > 0 {
676                        let down_left = (row + 1) * cols + (col - 1);
677                        graph.add_edge(node_id, down_left, 1.0)?;
678                    }
679                    if col < cols {
680                        let down_right = (row + 1) * cols + col;
681                        graph.add_edge(node_id, down_right, 1.0)?;
682                    }
683                }
684            } else {
685                // Odd _rows: connect down-left and down-right with offset
686                if row + 1 < rows {
687                    let down_left = (row + 1) * cols + col;
688                    graph.add_edge(node_id, down_left, 1.0)?;
689
690                    if col + 1 < cols {
691                        let down_right = (row + 1) * cols + (col + 1);
692                        graph.add_edge(node_id, down_right, 1.0)?;
693                    }
694                }
695            }
696        }
697    }
698
699    Ok(graph)
700}
701
702/// Generates a Watts-Strogatz small-world graph
703///
704/// # Arguments
705/// * `n` - Number of nodes
706/// * `k` - Each node is connected to k nearest neighbors in ring topology (must be even)
707/// * `p` - Probability of rewiring each edge
708/// * `rng` - Random number generator
709///
710/// # Returns
711/// * `Result<Graph<usize, f64>>` - A small-world graph
712#[allow(dead_code)]
713pub fn watts_strogatz_graph<R: Rng>(
714    n: usize,
715    k: usize,
716    p: f64,
717    rng: &mut R,
718) -> Result<Graph<usize, f64>> {
719    if k >= n || !k.is_multiple_of(2) {
720        return Err(GraphError::InvalidGraph(
721            "k must be even and less than n".to_string(),
722        ));
723    }
724    if !(0.0..=1.0).contains(&p) {
725        return Err(GraphError::InvalidGraph(
726            "Probability must be between 0 and 1".to_string(),
727        ));
728    }
729
730    let mut graph = Graph::new();
731
732    // Add all nodes
733    for i in 0..n {
734        graph.add_node(i);
735    }
736
737    // Create regular ring lattice
738    for i in 0..n {
739        for j in 1..=(k / 2) {
740            let neighbor = (i + j) % n;
741            graph.add_edge(i, neighbor, 1.0)?;
742        }
743    }
744
745    // Rewire edges with probability p
746    let edges_to_process: Vec<_> = graph.edges().into_iter().collect();
747
748    for edge in edges_to_process {
749        if rng.random::<f64>() < p {
750            // Remove the original edge (we'll recreate the graph to do this)
751            let mut new_graph = Graph::new();
752
753            // Add all nodes
754            for i in 0..n {
755                new_graph.add_node(i);
756            }
757
758            // Add all edges except the one we're rewiring
759            for existing_edge in graph.edges() {
760                if (existing_edge.source != edge.source || existing_edge.target != edge.target)
761                    && (existing_edge.source != edge.target || existing_edge.target != edge.source)
762                {
763                    new_graph.add_edge(
764                        existing_edge.source,
765                        existing_edge.target,
766                        existing_edge.weight,
767                    )?;
768                }
769            }
770
771            // Add rewired edge to a random node
772            let mut new_target = rng.random_range(0..n);
773            while new_target == edge.source || new_graph.has_node(&new_target) {
774                new_target = rng.random_range(0..n);
775            }
776
777            new_graph.add_edge(edge.source, new_target, 1.0)?;
778            graph = new_graph;
779        }
780    }
781
782    Ok(graph)
783}
784
785/// Generates a graph using the Stochastic Block Model (SBM)
786///
787/// The SBM generates a graph where nodes are divided into communities (blocks)
788/// and edge probabilities depend on which communities the nodes belong to.
789///
790/// # Arguments
791/// * `block_sizes` - Vector specifying the size of each block/community
792/// * `block_matrix` - Probability matrix where entry (i,j) is the probability
793///   of an edge between nodes in block i and block j
794/// * `rng` - Random number generator
795///
796/// # Returns
797/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
798///   where nodes 0..block_sizes\[0\]-1 are in block 0, etc.
799#[allow(dead_code)]
800pub fn stochastic_block_model<R: Rng>(
801    block_sizes: &[usize],
802    block_matrix: &[Vec<f64>],
803    rng: &mut R,
804) -> Result<Graph<usize, f64>> {
805    if block_sizes.is_empty() {
806        return Err(GraphError::InvalidGraph(
807            "At least one block must be specified".to_string(),
808        ));
809    }
810
811    if block_matrix.len() != block_sizes.len() {
812        return Err(GraphError::InvalidGraph(
813            "Block _matrix dimensions must match number of blocks".to_string(),
814        ));
815    }
816
817    for row in block_matrix {
818        if row.len() != block_sizes.len() {
819            return Err(GraphError::InvalidGraph(
820                "Block _matrix must be square".to_string(),
821            ));
822        }
823        for &prob in row {
824            if !(0.0..=1.0).contains(&prob) {
825                return Err(GraphError::InvalidGraph(
826                    "All probabilities must be between 0 and 1".to_string(),
827                ));
828            }
829        }
830    }
831
832    let total_nodes: usize = block_sizes.iter().sum();
833    let mut graph = Graph::new();
834
835    // Add all nodes
836    for i in 0..total_nodes {
837        graph.add_node(i);
838    }
839
840    // Create mapping from node to block
841    let mut node_to_block = vec![0; total_nodes];
842    let mut current_node = 0;
843    for (block_id, &block_size) in block_sizes.iter().enumerate() {
844        for _ in 0..block_size {
845            node_to_block[current_node] = block_id;
846            current_node += 1;
847        }
848    }
849
850    // Generate edges based on block probabilities
851    for i in 0..total_nodes {
852        for j in (i + 1)..total_nodes {
853            let block_i = node_to_block[i];
854            let block_j = node_to_block[j];
855            let prob = block_matrix[block_i][block_j];
856
857            if rng.random::<f64>() < prob {
858                graph.add_edge(i, j, 1.0)?;
859            }
860        }
861    }
862
863    Ok(graph)
864}
865
866/// Generates a simple stochastic block model with two communities
867///
868/// This is a convenience function for creating a two-community SBM with
869/// high intra-community probability and low inter-community probability.
870///
871/// # Arguments
872/// * `n1` - Size of first community
873/// * `n2` - Size of second community
874/// * `p_in` - Probability of edges within communities
875/// * `p_out` - Probability of edges between communities
876/// * `rng` - Random number generator
877///
878/// # Returns
879/// * `Result<Graph<usize, f64>>` - The generated graph
880#[allow(dead_code)]
881pub fn two_community_sbm<R: Rng>(
882    n1: usize,
883    n2: usize,
884    p_in: f64,
885    p_out: f64,
886    rng: &mut R,
887) -> Result<Graph<usize, f64>> {
888    let block_sizes = vec![n1, n2];
889    let block_matrix = vec![vec![p_in, p_out], vec![p_out, p_in]];
890
891    stochastic_block_model(&block_sizes, &block_matrix, rng)
892}
893
894/// Generates a planted partition model (special case of SBM)
895///
896/// In this model, there are k communities of equal size, with high
897/// intra-community probability and low inter-community probability.
898///
899/// # Arguments
900/// * `n` - Total number of nodes (must be divisible by k)
901/// * `k` - Number of communities
902/// * `p_in` - Probability of edges within communities
903/// * `p_out` - Probability of edges between communities
904/// * `rng` - Random number generator
905///
906/// # Returns
907/// * `Result<Graph<usize, f64>>` - The generated graph
908#[allow(dead_code)]
909pub fn planted_partition_model<R: Rng>(
910    n: usize,
911    k: usize,
912    p_in: f64,
913    p_out: f64,
914    rng: &mut R,
915) -> Result<Graph<usize, f64>> {
916    if !n.is_multiple_of(k) {
917        return Err(GraphError::InvalidGraph(
918            "Number of nodes must be divisible by number of communities".to_string(),
919        ));
920    }
921
922    let community_size = n / k;
923    let block_sizes = vec![community_size; k];
924
925    // Create block matrix
926    let mut block_matrix = vec![vec![p_out; k]; k];
927    for (i, row) in block_matrix.iter_mut().enumerate().take(k) {
928        row[i] = p_in;
929    }
930
931    stochastic_block_model(&block_sizes, &block_matrix, rng)
932}
933
934/// Generates a random graph using the Configuration Model
935///
936/// The Configuration Model generates a random graph where each node has a specified degree.
937/// The degree sequence is the sequence of degrees for all nodes. The algorithm creates
938/// "stubs" (half-edges) for each node according to its degree, then randomly connects
939/// the stubs to form edges.
940///
941/// # Arguments
942/// * `degree_sequence` - Vector specifying the degree of each node
943/// * `rng` - Random number generator
944///
945/// # Returns
946/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
947///
948/// # Notes
949/// * The sum of all degrees must be even (since each edge contributes 2 to the total degree)
950/// * Self-loops and multiple edges between the same pair of nodes are possible
951/// * If you want a simple graph (no self-loops or multiple edges), you may need to
952///   regenerate or post-process the result
953#[allow(dead_code)]
954pub fn configuration_model<R: Rng>(
955    degree_sequence: &[usize],
956    rng: &mut R,
957) -> Result<Graph<usize, f64>> {
958    if degree_sequence.is_empty() {
959        return Ok(Graph::new());
960    }
961
962    // Check that sum of degrees is even
963    let total_degree: usize = degree_sequence.iter().sum();
964    if !total_degree.is_multiple_of(2) {
965        return Err(GraphError::InvalidGraph(
966            "Sum of degrees must be even".to_string(),
967        ));
968    }
969
970    let n = degree_sequence.len();
971    let mut graph = Graph::new();
972
973    // Add all nodes
974    for i in 0..n {
975        graph.add_node(i);
976    }
977
978    // Create stubs (half-edges) for each node
979    let mut stubs = Vec::new();
980    for (node_id, &degree) in degree_sequence.iter().enumerate() {
981        for _ in 0..degree {
982            stubs.push(node_id);
983        }
984    }
985
986    // Randomly connect stubs to form edges
987    while stubs.len() >= 2 {
988        // Pick two random stubs
989        let idx1 = rng.random_range(0..stubs.len());
990        let stub1 = stubs.remove(idx1);
991
992        let idx2 = rng.random_range(0..stubs.len());
993        let stub2 = stubs.remove(idx2);
994
995        // Connect the nodes (allow self-loops and multiple edges)
996        graph.add_edge(stub1, stub2, 1.0)?;
997    }
998
999    Ok(graph)
1000}
1001
1002/// Generates a simple random graph using the Configuration Model
1003///
1004/// This variant attempts to generate a simple graph (no self-loops or multiple edges)
1005/// by rejecting problematic edge attempts. If too many rejections occur, it returns
1006/// an error indicating that the degree sequence may not be realizable as a simple graph.
1007///
1008/// # Arguments
1009/// * `degree_sequence` - Vector specifying the degree of each node
1010/// * `rng` - Random number generator
1011/// * `max_attempts` - Maximum number of attempts before giving up
1012///
1013/// # Returns
1014/// * `Result<Graph<usize, f64>>` - The generated simple graph
1015#[allow(dead_code)]
1016pub fn simple_configuration_model<R: Rng>(
1017    degree_sequence: &[usize],
1018    rng: &mut R,
1019    max_attempts: usize,
1020) -> Result<Graph<usize, f64>> {
1021    if degree_sequence.is_empty() {
1022        return Ok(Graph::new());
1023    }
1024
1025    // Check that sum of degrees is even
1026    let total_degree: usize = degree_sequence.iter().sum();
1027    if !total_degree.is_multiple_of(2) {
1028        return Err(GraphError::InvalidGraph(
1029            "Sum of degrees must be even".to_string(),
1030        ));
1031    }
1032
1033    let n = degree_sequence.len();
1034
1035    // Check for degree _sequence constraints for simple graphs
1036    for &degree in degree_sequence {
1037        if degree >= n {
1038            return Err(GraphError::InvalidGraph(
1039                "Node degree cannot exceed n-1 in a simple graph".to_string(),
1040            ));
1041        }
1042    }
1043
1044    let mut _attempts = 0;
1045
1046    while _attempts < max_attempts {
1047        let mut graph = Graph::new();
1048
1049        // Add all nodes
1050        for i in 0..n {
1051            graph.add_node(i);
1052        }
1053
1054        // Create stubs (half-edges) for each node
1055        let mut stubs = Vec::new();
1056        for (node_id, &degree) in degree_sequence.iter().enumerate() {
1057            for _ in 0..degree {
1058                stubs.push(node_id);
1059            }
1060        }
1061
1062        let mut success = true;
1063
1064        // Randomly connect stubs to form edges
1065        while stubs.len() >= 2 && success {
1066            // Pick two random stubs
1067            let idx1 = rng.random_range(0..stubs.len());
1068            let stub1 = stubs[idx1];
1069
1070            let idx2 = rng.random_range(0..stubs.len());
1071            let stub2 = stubs[idx2];
1072
1073            // Check for self-loop or existing edge
1074            if stub1 == stub2 || graph.has_edge(&stub1, &stub2) {
1075                // Try a few more times before giving up on this attempt
1076                let mut retries = 0;
1077                let mut found_valid = false;
1078
1079                while retries < 50 && !found_valid {
1080                    let new_idx2 = rng.random_range(0..stubs.len());
1081                    let new_stub2 = stubs[new_idx2];
1082
1083                    if stub1 != new_stub2 && !graph.has_edge(&stub1, &new_stub2) {
1084                        // Remove stubs and add edge
1085                        // Remove the larger index first to avoid index shifting issues
1086                        if idx1 > new_idx2 {
1087                            stubs.remove(idx1);
1088                            stubs.remove(new_idx2);
1089                        } else {
1090                            stubs.remove(new_idx2);
1091                            stubs.remove(idx1);
1092                        }
1093                        graph.add_edge(stub1, new_stub2, 1.0)?;
1094                        found_valid = true;
1095                    }
1096                    retries += 1;
1097                }
1098
1099                if !found_valid {
1100                    success = false;
1101                }
1102            } else {
1103                // Remove stubs and add edge
1104                // Remove the larger index first to avoid index shifting issues
1105                if idx1 > idx2 {
1106                    stubs.remove(idx1);
1107                    stubs.remove(idx2);
1108                } else {
1109                    stubs.remove(idx2);
1110                    stubs.remove(idx1);
1111                }
1112                graph.add_edge(stub1, stub2, 1.0)?;
1113            }
1114        }
1115
1116        if success && stubs.is_empty() {
1117            return Ok(graph);
1118        }
1119
1120        _attempts += 1;
1121    }
1122
1123    Err(GraphError::InvalidGraph(
1124        "Could not generate simple graph with given degree _sequence after maximum _attempts"
1125            .to_string(),
1126    ))
1127}
1128
1129/// Generates a random geometric graph
1130///
1131/// In a random geometric graph, `n` points are placed uniformly at random
1132/// in the unit square [0,1)^2. An edge is created between two nodes whenever
1133/// their Euclidean distance is at most `radius`.
1134///
1135/// # Arguments
1136/// * `n` - Number of nodes
1137/// * `radius` - Connection radius (typical range: 0.05 to 0.5)
1138/// * `rng` - Random number generator
1139///
1140/// # Returns
1141/// * `Result<Graph<usize, f64>>` - The generated graph where edge weights
1142///   are the Euclidean distances between connected nodes
1143///
1144/// # Applications
1145/// Models wireless sensor networks, ad-hoc networks, and spatial networks
1146/// where connectivity depends on physical proximity.
1147#[allow(dead_code)]
1148pub fn random_geometric_graph<R: Rng>(
1149    n: usize,
1150    radius: f64,
1151    rng: &mut R,
1152) -> Result<Graph<usize, f64>> {
1153    if radius < 0.0 {
1154        return Err(GraphError::InvalidGraph(
1155            "Radius must be non-negative".to_string(),
1156        ));
1157    }
1158
1159    let mut graph = Graph::new();
1160
1161    // Add all nodes
1162    for i in 0..n {
1163        graph.add_node(i);
1164    }
1165
1166    if n == 0 {
1167        return Ok(graph);
1168    }
1169
1170    // Generate random positions in [0,1)^2
1171    let positions: Vec<(f64, f64)> = (0..n)
1172        .map(|_| (rng.random::<f64>(), rng.random::<f64>()))
1173        .collect();
1174
1175    let radius_sq = radius * radius;
1176
1177    // Connect nodes within the radius
1178    for i in 0..n {
1179        for j in (i + 1)..n {
1180            let dx = positions[i].0 - positions[j].0;
1181            let dy = positions[i].1 - positions[j].1;
1182            let dist_sq = dx * dx + dy * dy;
1183
1184            if dist_sq <= radius_sq {
1185                let dist = dist_sq.sqrt();
1186                graph.add_edge(i, j, dist)?;
1187            }
1188        }
1189    }
1190
1191    Ok(graph)
1192}
1193
1194/// Generates a power-law cluster graph (Holme & Kim model)
1195///
1196/// This is a variant of the Barabasi-Albert model that adds a triad formation
1197/// step to increase clustering. After each preferential attachment step, with
1198/// probability `p_triangle`, a triangle is formed by connecting the new node
1199/// to a neighbor of the just-connected node.
1200///
1201/// # Arguments
1202/// * `n` - Total number of nodes (must be > m)
1203/// * `m` - Number of edges to add per new node
1204/// * `p_triangle` - Probability of triad formation step (0 = pure BA, 1 = max clustering)
1205/// * `rng` - Random number generator
1206///
1207/// # Returns
1208/// * `Result<Graph<usize, f64>>` - The generated graph
1209///
1210/// # Reference
1211/// Holme & Kim, "Growing scale-free networks with tunable clustering",
1212/// Physical Review E, 2002.
1213///
1214/// # Properties
1215/// - Scale-free degree distribution (power law)
1216/// - Tunable clustering coefficient via `p_triangle`
1217/// - When `p_triangle = 0`, equivalent to Barabasi-Albert model
1218#[allow(dead_code)]
1219pub fn power_law_cluster_graph<R: Rng>(
1220    n: usize,
1221    m: usize,
1222    p_triangle: f64,
1223    rng: &mut R,
1224) -> Result<Graph<usize, f64>> {
1225    if m == 0 {
1226        return Err(GraphError::InvalidGraph("m must be positive".to_string()));
1227    }
1228    if m >= n {
1229        return Err(GraphError::InvalidGraph(
1230            "m must be less than n".to_string(),
1231        ));
1232    }
1233    if !(0.0..=1.0).contains(&p_triangle) {
1234        return Err(GraphError::InvalidGraph(
1235            "p_triangle must be between 0 and 1".to_string(),
1236        ));
1237    }
1238
1239    let mut graph = Graph::new();
1240
1241    // Start with a complete graph of m+1 nodes
1242    for i in 0..=m {
1243        graph.add_node(i);
1244    }
1245    for i in 0..=m {
1246        for j in (i + 1)..=m {
1247            graph.add_edge(i, j, 1.0)?;
1248        }
1249    }
1250
1251    // Track degrees for preferential attachment
1252    let mut degrees = vec![m; m + 1];
1253    let mut total_degree = m * (m + 1);
1254
1255    // Add remaining nodes one at a time
1256    for new_node in (m + 1)..n {
1257        graph.add_node(new_node);
1258
1259        let mut targets_added: HashSet<usize> = HashSet::new();
1260        let mut edges_to_add = m;
1261
1262        // First edge: always preferential attachment
1263        if edges_to_add > 0 {
1264            let target = select_preferential_attachment(
1265                &degrees,
1266                total_degree,
1267                &targets_added,
1268                new_node,
1269                rng,
1270            );
1271            if let Some(t) = target {
1272                graph.add_edge(new_node, t, 1.0)?;
1273                targets_added.insert(t);
1274                degrees[t] += 1;
1275                total_degree += 2;
1276                edges_to_add -= 1;
1277            }
1278        }
1279
1280        // Remaining edges: either triad formation or preferential attachment
1281        while edges_to_add > 0 {
1282            if rng.random::<f64>() < p_triangle && !targets_added.is_empty() {
1283                // Triad formation: pick a random target already connected,
1284                // then connect to one of its neighbors
1285                let last_target = *targets_added.iter().last().unwrap_or(&0);
1286                let neighbors_of_target = graph.neighbors(&last_target).unwrap_or_default();
1287
1288                // Find a neighbor that is not already targeted or the new node
1289                let candidates: Vec<usize> = neighbors_of_target
1290                    .into_iter()
1291                    .filter(|nb| *nb != new_node && !targets_added.contains(nb))
1292                    .collect();
1293
1294                if let Some(&chosen) = candidates.choose(rng) {
1295                    graph.add_edge(new_node, chosen, 1.0)?;
1296                    targets_added.insert(chosen);
1297                    degrees[chosen] += 1;
1298                    total_degree += 2;
1299                    edges_to_add -= 1;
1300                    continue;
1301                }
1302                // Fall through to preferential attachment if no valid neighbor found
1303            }
1304
1305            // Preferential attachment
1306            let target = select_preferential_attachment(
1307                &degrees,
1308                total_degree,
1309                &targets_added,
1310                new_node,
1311                rng,
1312            );
1313            if let Some(t) = target {
1314                graph.add_edge(new_node, t, 1.0)?;
1315                targets_added.insert(t);
1316                degrees[t] += 1;
1317                total_degree += 2;
1318                edges_to_add -= 1;
1319            } else {
1320                // Cannot find more targets; bail to avoid infinite loop
1321                break;
1322            }
1323        }
1324
1325        degrees.push(targets_added.len());
1326    }
1327
1328    Ok(graph)
1329}
1330
1331/// Helper: select a node via preferential attachment (probability proportional to degree)
1332fn select_preferential_attachment<R: Rng>(
1333    degrees: &[usize],
1334    total_degree: usize,
1335    excluded: &HashSet<usize>,
1336    new_node: usize,
1337    rng: &mut R,
1338) -> Option<usize> {
1339    if total_degree == 0 {
1340        return None;
1341    }
1342
1343    // Try up to 100 times to find a non-excluded target
1344    for _ in 0..100 {
1345        let mut cumulative = 0.0;
1346        let random_value = rng.random::<f64>() * total_degree as f64;
1347
1348        for (node_id, &degree) in degrees.iter().enumerate() {
1349            if node_id == new_node {
1350                continue;
1351            }
1352            cumulative += degree as f64;
1353            if random_value <= cumulative && !excluded.contains(&node_id) {
1354                return Some(node_id);
1355            }
1356        }
1357    }
1358    None
1359}
1360
1361#[cfg(test)]
1362mod tests {
1363    use super::*;
1364
1365    #[test]
1366    fn test_erdos_renyi_graph() {
1367        let mut rng = StdRng::seed_from_u64(42);
1368        let graph = erdos_renyi_graph(10, 0.3, &mut rng).expect("Operation failed");
1369
1370        assert_eq!(graph.node_count(), 10);
1371        // With p=0.3 and 45 possible edges, we expect around 13-14 edges
1372        // but this is random, so we just check it's reasonable
1373        assert!(graph.edge_count() <= 45);
1374    }
1375
1376    #[test]
1377    fn test_complete_graph() {
1378        let graph = complete_graph(5).expect("Operation failed");
1379
1380        assert_eq!(graph.node_count(), 5);
1381        assert_eq!(graph.edge_count(), 10); // n*(n-1)/2 = 5*4/2 = 10
1382    }
1383
1384    #[test]
1385    fn test_star_graph() {
1386        let graph = star_graph(6).expect("Operation failed");
1387
1388        assert_eq!(graph.node_count(), 6);
1389        assert_eq!(graph.edge_count(), 5); // n-1 edges
1390    }
1391
1392    #[test]
1393    fn test_path_graph() {
1394        let graph = path_graph(5).expect("Operation failed");
1395
1396        assert_eq!(graph.node_count(), 5);
1397        assert_eq!(graph.edge_count(), 4); // n-1 edges
1398    }
1399
1400    #[test]
1401    fn test_cycle_graph() {
1402        let graph = cycle_graph(5).expect("Operation failed");
1403
1404        assert_eq!(graph.node_count(), 5);
1405        assert_eq!(graph.edge_count(), 5); // n edges
1406
1407        // Test error case
1408        assert!(cycle_graph(2).is_err());
1409    }
1410
1411    #[test]
1412    fn test_grid_2d_graph() {
1413        let graph = grid_2d_graph(3, 4).expect("Operation failed");
1414
1415        assert_eq!(graph.node_count(), 12); // 3*4 = 12 nodes
1416        assert_eq!(graph.edge_count(), 17); // (3-1)*4 + 3*(4-1) = 8 + 9 = 17 edges
1417    }
1418
1419    #[test]
1420    fn test_grid_3d_graph() {
1421        let graph = grid_3d_graph(2, 2, 2).expect("Operation failed");
1422
1423        assert_eq!(graph.node_count(), 8); // 2*2*2 = 8 nodes
1424                                           // Each internal node connects to 3 neighbors in 3D grid
1425                                           // Expected edges: 3 faces × 2 edges per face + 3 additional connections = 12 edges
1426        assert_eq!(graph.edge_count(), 12);
1427    }
1428
1429    #[test]
1430    fn test_triangular_lattice_graph() {
1431        let graph = triangular_lattice_graph(3, 3).expect("Operation failed");
1432
1433        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1434                                           // Triangular lattice has more edges than regular grid due to diagonal connections
1435        assert!(graph.edge_count() > 12); // More than standard 2D grid edges
1436    }
1437
1438    #[test]
1439    fn test_hexagonal_lattice_graph() {
1440        let graph = hexagonal_lattice_graph(3, 3).expect("Operation failed");
1441
1442        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1443                                           // Hexagonal lattice should have fewer edges than triangular due to honeycomb structure
1444        assert!(graph.edge_count() >= 6);
1445    }
1446
1447    #[test]
1448    fn test_barabasi_albert_graph() {
1449        let mut rng = StdRng::seed_from_u64(42);
1450        let graph = barabasi_albert_graph(10, 2, &mut rng).expect("Operation failed");
1451
1452        assert_eq!(graph.node_count(), 10);
1453        // Should have 3 + 2*7 = 17 edges (3 initial edges + 2 for each of the 7 new nodes)
1454        assert_eq!(graph.edge_count(), 17);
1455    }
1456
1457    #[test]
1458    fn test_stochastic_block_model() {
1459        let mut rng = StdRng::seed_from_u64(42);
1460
1461        // Two blocks of size 3 and 4
1462        let block_sizes = vec![3, 4];
1463        // High intra-block probability, low inter-block probability
1464        let block_matrix = vec![vec![0.8, 0.1], vec![0.1, 0.8]];
1465
1466        let graph = stochastic_block_model(&block_sizes, &block_matrix, &mut rng)
1467            .expect("Operation failed");
1468
1469        assert_eq!(graph.node_count(), 7); // 3 + 4 = 7 nodes
1470
1471        // Check that all nodes are present
1472        for i in 0..7 {
1473            assert!(graph.has_node(&i));
1474        }
1475    }
1476
1477    #[test]
1478    fn test_two_community_sbm() {
1479        let mut rng = StdRng::seed_from_u64(42);
1480
1481        let graph = two_community_sbm(5, 5, 0.8, 0.1, &mut rng).expect("Operation failed");
1482
1483        assert_eq!(graph.node_count(), 10);
1484
1485        // Should have some edges within communities and fewer between
1486        // This is probabilistic so we can't test exact numbers
1487        assert!(graph.edge_count() > 0);
1488    }
1489
1490    #[test]
1491    fn test_planted_partition_model() {
1492        let mut rng = StdRng::seed_from_u64(42);
1493
1494        let graph = planted_partition_model(12, 3, 0.7, 0.1, &mut rng).expect("Operation failed");
1495
1496        assert_eq!(graph.node_count(), 12); // 12 nodes total
1497
1498        // 3 communities of size 4 each
1499        // Should have some edges
1500        assert!(graph.edge_count() > 0);
1501    }
1502
1503    #[test]
1504    fn test_stochastic_block_model_errors() {
1505        let mut rng = StdRng::seed_from_u64(42);
1506
1507        // Empty blocks
1508        assert!(stochastic_block_model(&[], &[], &mut rng).is_err());
1509
1510        // Mismatched dimensions
1511        let block_sizes = vec![3, 4];
1512        let wrong_matrix = vec![vec![0.5]];
1513        assert!(stochastic_block_model(&block_sizes, &wrong_matrix, &mut rng).is_err());
1514
1515        // Invalid probabilities
1516        let bad_matrix = vec![vec![1.5, 0.5], vec![0.5, 0.5]];
1517        assert!(stochastic_block_model(&block_sizes, &bad_matrix, &mut rng).is_err());
1518
1519        // Non-divisible nodes for planted partition
1520        assert!(planted_partition_model(10, 3, 0.5, 0.1, &mut rng).is_err());
1521    }
1522
1523    #[test]
1524    fn test_configuration_model() {
1525        let mut rng = StdRng::seed_from_u64(42);
1526
1527        // Test valid degree sequence (even sum)
1528        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1529        let graph = configuration_model(&degree_sequence, &mut rng).expect("Operation failed");
1530
1531        assert_eq!(graph.node_count(), 4);
1532        // Should have 4 edges (sum of degrees / 2)
1533        assert_eq!(graph.edge_count(), 4);
1534
1535        // Check that each node has the correct degree
1536        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1537            let actual_degree = graph.degree(&i);
1538            assert_eq!(actual_degree, expected_degree);
1539        }
1540    }
1541
1542    #[test]
1543    fn test_configuration_model_errors() {
1544        let mut rng = StdRng::seed_from_u64(42);
1545
1546        // Test odd degree sum (should fail)
1547        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1548        assert!(configuration_model(&odd_degree_sequence, &mut rng).is_err());
1549
1550        // Test empty sequence
1551        let empty_sequence = vec![];
1552        let graph = configuration_model(&empty_sequence, &mut rng).expect("Operation failed");
1553        assert_eq!(graph.node_count(), 0);
1554    }
1555
1556    #[test]
1557    fn test_simple_configuration_model() {
1558        let mut rng = StdRng::seed_from_u64(42);
1559
1560        // Test valid degree sequence for simple graph
1561        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1562        let graph =
1563            simple_configuration_model(&degree_sequence, &mut rng, 100).expect("Operation failed");
1564
1565        assert_eq!(graph.node_count(), 4);
1566        assert_eq!(graph.edge_count(), 4);
1567
1568        // Check that graph is simple (no self-loops)
1569        for i in 0..4 {
1570            assert!(!graph.has_edge(&i, &i), "Graph should not have self-loops");
1571        }
1572
1573        // Check degrees
1574        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1575            let actual_degree = graph.degree(&i);
1576            assert_eq!(actual_degree, expected_degree);
1577        }
1578    }
1579
1580    #[test]
1581    fn test_simple_configuration_model_errors() {
1582        let mut rng = StdRng::seed_from_u64(42);
1583
1584        // Test degree too large for simple graph
1585        let invalid_degree_sequence = vec![4, 2, 2, 2]; // Node 0 has degree 4, but n=4, so max degree is 3
1586        assert!(simple_configuration_model(&invalid_degree_sequence, &mut rng, 10).is_err());
1587
1588        // Test odd degree sum
1589        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1590        assert!(simple_configuration_model(&odd_degree_sequence, &mut rng, 10).is_err());
1591    }
1592
1593    #[test]
1594    fn test_tree_graph() {
1595        let mut rng = StdRng::seed_from_u64(42);
1596
1597        // Test empty tree
1598        let empty_tree = tree_graph(0, &mut rng).expect("Operation failed");
1599        assert_eq!(empty_tree.node_count(), 0);
1600        assert_eq!(empty_tree.edge_count(), 0);
1601
1602        // Test single node tree
1603        let single_tree = tree_graph(1, &mut rng).expect("Operation failed");
1604        assert_eq!(single_tree.node_count(), 1);
1605        assert_eq!(single_tree.edge_count(), 0);
1606
1607        // Test tree with multiple nodes
1608        let tree = tree_graph(5, &mut rng).expect("Operation failed");
1609        assert_eq!(tree.node_count(), 5);
1610        assert_eq!(tree.edge_count(), 4); // n-1 edges for a tree
1611
1612        // Verify all nodes are present
1613        for i in 0..5 {
1614            assert!(tree.has_node(&i));
1615        }
1616    }
1617
1618    #[test]
1619    fn test_random_spanning_tree() {
1620        let mut rng = StdRng::seed_from_u64(42);
1621
1622        // Create a complete graph
1623        let complete = complete_graph(4).expect("Operation failed");
1624
1625        // Generate spanning tree
1626        let spanning_tree = random_spanning_tree(&complete, &mut rng).expect("Operation failed");
1627
1628        assert_eq!(spanning_tree.node_count(), 4);
1629        assert_eq!(spanning_tree.edge_count(), 3); // n-1 edges for spanning tree
1630
1631        // Verify all nodes are present
1632        for i in 0..4 {
1633            assert!(spanning_tree.has_node(&i));
1634        }
1635    }
1636
1637    #[test]
1638    fn test_forest_graph() {
1639        let mut rng = StdRng::seed_from_u64(42);
1640
1641        // Create forest with trees of sizes [3, 2, 4]
1642        let tree_sizes = vec![3, 2, 4];
1643        let forest = forest_graph(&tree_sizes, &tree_sizes, &mut rng).expect("Operation failed");
1644
1645        assert_eq!(forest.node_count(), 9); // 3 + 2 + 4 = 9 nodes
1646        assert_eq!(forest.edge_count(), 6); // (3-1) + (2-1) + (4-1) = 6 edges
1647
1648        // Verify all nodes are present
1649        for i in 0..9 {
1650            assert!(forest.has_node(&i));
1651        }
1652
1653        // Test empty forest
1654        let empty_forest = forest_graph(&[], &[], &mut rng).expect("Operation failed");
1655        assert_eq!(empty_forest.node_count(), 0);
1656        assert_eq!(empty_forest.edge_count(), 0);
1657
1658        // Test forest with empty trees
1659        let forest_with_zeros =
1660            forest_graph(&[0, 3, 0, 2], &[0, 3, 0, 2], &mut rng).expect("Operation failed");
1661        assert_eq!(forest_with_zeros.node_count(), 5); // 3 + 2 = 5 nodes
1662        assert_eq!(forest_with_zeros.edge_count(), 3); // (3-1) + (2-1) = 3 edges
1663    }
1664
1665    #[test]
1666    fn test_random_geometric_graph() {
1667        let mut rng = StdRng::seed_from_u64(42);
1668
1669        let graph = random_geometric_graph(20, 0.4, &mut rng).expect("Operation failed");
1670        assert_eq!(graph.node_count(), 20);
1671        // With radius=0.4, we should have some edges but not all
1672        assert!(graph.edge_count() > 0);
1673        assert!(graph.edge_count() < 20 * 19 / 2);
1674
1675        // Edge weights should be distances (positive)
1676        for edge in graph.edges() {
1677            assert!(edge.weight > 0.0);
1678            assert!(edge.weight <= 0.4 + 1e-10); // within radius
1679        }
1680    }
1681
1682    #[test]
1683    fn test_random_geometric_graph_large_radius() {
1684        let mut rng = StdRng::seed_from_u64(42);
1685
1686        // With large radius, should be almost complete
1687        let graph = random_geometric_graph(5, 2.0, &mut rng).expect("Operation failed");
1688        assert_eq!(graph.node_count(), 5);
1689        // max distance in unit square is sqrt(2) < 2.0, so all pairs connected
1690        assert_eq!(graph.edge_count(), 10); // C(5,2) = 10
1691    }
1692
1693    #[test]
1694    fn test_random_geometric_graph_zero_radius() {
1695        let mut rng = StdRng::seed_from_u64(42);
1696
1697        let graph = random_geometric_graph(10, 0.0, &mut rng).expect("Operation failed");
1698        assert_eq!(graph.node_count(), 10);
1699        assert_eq!(graph.edge_count(), 0); // no edges with zero radius
1700    }
1701
1702    #[test]
1703    fn test_random_geometric_graph_errors() {
1704        let mut rng = StdRng::seed_from_u64(42);
1705        assert!(random_geometric_graph(10, -0.1, &mut rng).is_err());
1706    }
1707
1708    #[test]
1709    fn test_random_geometric_graph_empty() {
1710        let mut rng = StdRng::seed_from_u64(42);
1711        let graph = random_geometric_graph(0, 0.5, &mut rng).expect("Operation failed");
1712        assert_eq!(graph.node_count(), 0);
1713        assert_eq!(graph.edge_count(), 0);
1714    }
1715
1716    #[test]
1717    fn test_power_law_cluster_graph() {
1718        let mut rng = StdRng::seed_from_u64(42);
1719
1720        let graph = power_law_cluster_graph(20, 2, 0.5, &mut rng).expect("Operation failed");
1721        assert_eq!(graph.node_count(), 20);
1722        // Should have at least the initial complete graph edges + m edges per node
1723        assert!(graph.edge_count() > 0);
1724    }
1725
1726    #[test]
1727    fn test_power_law_cluster_no_triangle() {
1728        let mut rng = StdRng::seed_from_u64(42);
1729
1730        // p_triangle = 0 is equivalent to BA model
1731        let graph = power_law_cluster_graph(15, 2, 0.0, &mut rng).expect("Operation failed");
1732        assert_eq!(graph.node_count(), 15);
1733        assert!(graph.edge_count() > 0);
1734    }
1735
1736    #[test]
1737    fn test_power_law_cluster_max_triangle() {
1738        let mut rng = StdRng::seed_from_u64(42);
1739
1740        // p_triangle = 1 maximizes clustering
1741        let graph = power_law_cluster_graph(15, 2, 1.0, &mut rng).expect("Operation failed");
1742        assert_eq!(graph.node_count(), 15);
1743        assert!(graph.edge_count() > 0);
1744    }
1745
1746    #[test]
1747    fn test_power_law_cluster_errors() {
1748        let mut rng = StdRng::seed_from_u64(42);
1749
1750        // m = 0
1751        assert!(power_law_cluster_graph(10, 0, 0.5, &mut rng).is_err());
1752        // m >= n
1753        assert!(power_law_cluster_graph(5, 5, 0.5, &mut rng).is_err());
1754        // invalid p_triangle
1755        assert!(power_law_cluster_graph(10, 2, 1.5, &mut rng).is_err());
1756        assert!(power_law_cluster_graph(10, 2, -0.1, &mut rng).is_err());
1757    }
1758}