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 that isn't `edge.source`
772            // itself and isn't already connected to it (avoiding a self-loop
773            // or a duplicate parallel edge). `new_graph` was pre-populated
774            // with all `n` nodes above, so a `has_node` check here is always
775            // true regardless of `new_target` — that previously made this
776            // loop unconditionally infinite as soon as any edge was selected
777            // for rewiring (expected for the vast majority of seeds at
778            // p > 0, e.g. ~99.8% of the time for a 30-node, 60-edge graph at
779            // p=0.1). The intent was to check for an existing edge, not node
780            // membership.
781            let mut new_target = rng.random_range(0..n);
782            while new_target == edge.source || new_graph.has_edge(&edge.source, &new_target) {
783                new_target = rng.random_range(0..n);
784            }
785
786            new_graph.add_edge(edge.source, new_target, 1.0)?;
787            graph = new_graph;
788        }
789    }
790
791    Ok(graph)
792}
793
794/// Generates a graph using the Stochastic Block Model (SBM)
795///
796/// The SBM generates a graph where nodes are divided into communities (blocks)
797/// and edge probabilities depend on which communities the nodes belong to.
798///
799/// # Arguments
800/// * `block_sizes` - Vector specifying the size of each block/community
801/// * `block_matrix` - Probability matrix where entry (i,j) is the probability
802///   of an edge between nodes in block i and block j
803/// * `rng` - Random number generator
804///
805/// # Returns
806/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
807///   where nodes 0..block_sizes\[0\]-1 are in block 0, etc.
808#[allow(dead_code)]
809pub fn stochastic_block_model<R: Rng>(
810    block_sizes: &[usize],
811    block_matrix: &[Vec<f64>],
812    rng: &mut R,
813) -> Result<Graph<usize, f64>> {
814    if block_sizes.is_empty() {
815        return Err(GraphError::InvalidGraph(
816            "At least one block must be specified".to_string(),
817        ));
818    }
819
820    if block_matrix.len() != block_sizes.len() {
821        return Err(GraphError::InvalidGraph(
822            "Block _matrix dimensions must match number of blocks".to_string(),
823        ));
824    }
825
826    for row in block_matrix {
827        if row.len() != block_sizes.len() {
828            return Err(GraphError::InvalidGraph(
829                "Block _matrix must be square".to_string(),
830            ));
831        }
832        for &prob in row {
833            if !(0.0..=1.0).contains(&prob) {
834                return Err(GraphError::InvalidGraph(
835                    "All probabilities must be between 0 and 1".to_string(),
836                ));
837            }
838        }
839    }
840
841    let total_nodes: usize = block_sizes.iter().sum();
842    let mut graph = Graph::new();
843
844    // Add all nodes
845    for i in 0..total_nodes {
846        graph.add_node(i);
847    }
848
849    // Create mapping from node to block
850    let mut node_to_block = vec![0; total_nodes];
851    let mut current_node = 0;
852    for (block_id, &block_size) in block_sizes.iter().enumerate() {
853        for _ in 0..block_size {
854            node_to_block[current_node] = block_id;
855            current_node += 1;
856        }
857    }
858
859    // Generate edges based on block probabilities
860    for i in 0..total_nodes {
861        for j in (i + 1)..total_nodes {
862            let block_i = node_to_block[i];
863            let block_j = node_to_block[j];
864            let prob = block_matrix[block_i][block_j];
865
866            if rng.random::<f64>() < prob {
867                graph.add_edge(i, j, 1.0)?;
868            }
869        }
870    }
871
872    Ok(graph)
873}
874
875/// Generates a simple stochastic block model with two communities
876///
877/// This is a convenience function for creating a two-community SBM with
878/// high intra-community probability and low inter-community probability.
879///
880/// # Arguments
881/// * `n1` - Size of first community
882/// * `n2` - Size of second community
883/// * `p_in` - Probability of edges within communities
884/// * `p_out` - Probability of edges between communities
885/// * `rng` - Random number generator
886///
887/// # Returns
888/// * `Result<Graph<usize, f64>>` - The generated graph
889#[allow(dead_code)]
890pub fn two_community_sbm<R: Rng>(
891    n1: usize,
892    n2: usize,
893    p_in: f64,
894    p_out: f64,
895    rng: &mut R,
896) -> Result<Graph<usize, f64>> {
897    let block_sizes = vec![n1, n2];
898    let block_matrix = vec![vec![p_in, p_out], vec![p_out, p_in]];
899
900    stochastic_block_model(&block_sizes, &block_matrix, rng)
901}
902
903/// Generates a planted partition model (special case of SBM)
904///
905/// In this model, there are k communities of equal size, with high
906/// intra-community probability and low inter-community probability.
907///
908/// # Arguments
909/// * `n` - Total number of nodes (must be divisible by k)
910/// * `k` - Number of communities
911/// * `p_in` - Probability of edges within communities
912/// * `p_out` - Probability of edges between communities
913/// * `rng` - Random number generator
914///
915/// # Returns
916/// * `Result<Graph<usize, f64>>` - The generated graph
917#[allow(dead_code)]
918pub fn planted_partition_model<R: Rng>(
919    n: usize,
920    k: usize,
921    p_in: f64,
922    p_out: f64,
923    rng: &mut R,
924) -> Result<Graph<usize, f64>> {
925    if !n.is_multiple_of(k) {
926        return Err(GraphError::InvalidGraph(
927            "Number of nodes must be divisible by number of communities".to_string(),
928        ));
929    }
930
931    let community_size = n / k;
932    let block_sizes = vec![community_size; k];
933
934    // Create block matrix
935    let mut block_matrix = vec![vec![p_out; k]; k];
936    for (i, row) in block_matrix.iter_mut().enumerate().take(k) {
937        row[i] = p_in;
938    }
939
940    stochastic_block_model(&block_sizes, &block_matrix, rng)
941}
942
943/// Generates a random graph using the Configuration Model
944///
945/// The Configuration Model generates a random graph where each node has a specified degree.
946/// The degree sequence is the sequence of degrees for all nodes. The algorithm creates
947/// "stubs" (half-edges) for each node according to its degree, then randomly connects
948/// the stubs to form edges.
949///
950/// # Arguments
951/// * `degree_sequence` - Vector specifying the degree of each node
952/// * `rng` - Random number generator
953///
954/// # Returns
955/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
956///
957/// # Notes
958/// * The sum of all degrees must be even (since each edge contributes 2 to the total degree)
959/// * Self-loops and multiple edges between the same pair of nodes are possible
960/// * If you want a simple graph (no self-loops or multiple edges), you may need to
961///   regenerate or post-process the result
962#[allow(dead_code)]
963pub fn configuration_model<R: Rng>(
964    degree_sequence: &[usize],
965    rng: &mut R,
966) -> Result<Graph<usize, f64>> {
967    if degree_sequence.is_empty() {
968        return Ok(Graph::new());
969    }
970
971    // Check that sum of degrees is even
972    let total_degree: usize = degree_sequence.iter().sum();
973    if !total_degree.is_multiple_of(2) {
974        return Err(GraphError::InvalidGraph(
975            "Sum of degrees must be even".to_string(),
976        ));
977    }
978
979    let n = degree_sequence.len();
980    let mut graph = Graph::new();
981
982    // Add all nodes
983    for i in 0..n {
984        graph.add_node(i);
985    }
986
987    // Create stubs (half-edges) for each node
988    let mut stubs = Vec::new();
989    for (node_id, &degree) in degree_sequence.iter().enumerate() {
990        for _ in 0..degree {
991            stubs.push(node_id);
992        }
993    }
994
995    // Randomly connect stubs to form edges
996    while stubs.len() >= 2 {
997        // Pick two random stubs
998        let idx1 = rng.random_range(0..stubs.len());
999        let stub1 = stubs.remove(idx1);
1000
1001        let idx2 = rng.random_range(0..stubs.len());
1002        let stub2 = stubs.remove(idx2);
1003
1004        // Connect the nodes (allow self-loops and multiple edges)
1005        graph.add_edge(stub1, stub2, 1.0)?;
1006    }
1007
1008    Ok(graph)
1009}
1010
1011/// Generates a simple random graph using the Configuration Model
1012///
1013/// This variant attempts to generate a simple graph (no self-loops or multiple edges)
1014/// by rejecting problematic edge attempts. If too many rejections occur, it returns
1015/// an error indicating that the degree sequence may not be realizable as a simple graph.
1016///
1017/// # Arguments
1018/// * `degree_sequence` - Vector specifying the degree of each node
1019/// * `rng` - Random number generator
1020/// * `max_attempts` - Maximum number of attempts before giving up
1021///
1022/// # Returns
1023/// * `Result<Graph<usize, f64>>` - The generated simple graph
1024#[allow(dead_code)]
1025pub fn simple_configuration_model<R: Rng>(
1026    degree_sequence: &[usize],
1027    rng: &mut R,
1028    max_attempts: usize,
1029) -> Result<Graph<usize, f64>> {
1030    if degree_sequence.is_empty() {
1031        return Ok(Graph::new());
1032    }
1033
1034    // Check that sum of degrees is even
1035    let total_degree: usize = degree_sequence.iter().sum();
1036    if !total_degree.is_multiple_of(2) {
1037        return Err(GraphError::InvalidGraph(
1038            "Sum of degrees must be even".to_string(),
1039        ));
1040    }
1041
1042    let n = degree_sequence.len();
1043
1044    // Check for degree _sequence constraints for simple graphs
1045    for &degree in degree_sequence {
1046        if degree >= n {
1047            return Err(GraphError::InvalidGraph(
1048                "Node degree cannot exceed n-1 in a simple graph".to_string(),
1049            ));
1050        }
1051    }
1052
1053    let mut _attempts = 0;
1054
1055    while _attempts < max_attempts {
1056        let mut graph = Graph::new();
1057
1058        // Add all nodes
1059        for i in 0..n {
1060            graph.add_node(i);
1061        }
1062
1063        // Create stubs (half-edges) for each node
1064        let mut stubs = Vec::new();
1065        for (node_id, &degree) in degree_sequence.iter().enumerate() {
1066            for _ in 0..degree {
1067                stubs.push(node_id);
1068            }
1069        }
1070
1071        let mut success = true;
1072
1073        // Randomly connect stubs to form edges
1074        while stubs.len() >= 2 && success {
1075            // Pick two random stubs
1076            let idx1 = rng.random_range(0..stubs.len());
1077            let stub1 = stubs[idx1];
1078
1079            let idx2 = rng.random_range(0..stubs.len());
1080            let stub2 = stubs[idx2];
1081
1082            // Check for self-loop or existing edge
1083            if stub1 == stub2 || graph.has_edge(&stub1, &stub2) {
1084                // Try a few more times before giving up on this attempt
1085                let mut retries = 0;
1086                let mut found_valid = false;
1087
1088                while retries < 50 && !found_valid {
1089                    let new_idx2 = rng.random_range(0..stubs.len());
1090                    let new_stub2 = stubs[new_idx2];
1091
1092                    if stub1 != new_stub2 && !graph.has_edge(&stub1, &new_stub2) {
1093                        // Remove stubs and add edge
1094                        // Remove the larger index first to avoid index shifting issues
1095                        if idx1 > new_idx2 {
1096                            stubs.remove(idx1);
1097                            stubs.remove(new_idx2);
1098                        } else {
1099                            stubs.remove(new_idx2);
1100                            stubs.remove(idx1);
1101                        }
1102                        graph.add_edge(stub1, new_stub2, 1.0)?;
1103                        found_valid = true;
1104                    }
1105                    retries += 1;
1106                }
1107
1108                if !found_valid {
1109                    success = false;
1110                }
1111            } else {
1112                // Remove stubs and add edge
1113                // Remove the larger index first to avoid index shifting issues
1114                if idx1 > idx2 {
1115                    stubs.remove(idx1);
1116                    stubs.remove(idx2);
1117                } else {
1118                    stubs.remove(idx2);
1119                    stubs.remove(idx1);
1120                }
1121                graph.add_edge(stub1, stub2, 1.0)?;
1122            }
1123        }
1124
1125        if success && stubs.is_empty() {
1126            return Ok(graph);
1127        }
1128
1129        _attempts += 1;
1130    }
1131
1132    Err(GraphError::InvalidGraph(
1133        "Could not generate simple graph with given degree _sequence after maximum _attempts"
1134            .to_string(),
1135    ))
1136}
1137
1138/// Generates a random geometric graph
1139///
1140/// In a random geometric graph, `n` points are placed uniformly at random
1141/// in the unit square [0,1)^2. An edge is created between two nodes whenever
1142/// their Euclidean distance is at most `radius`.
1143///
1144/// # Arguments
1145/// * `n` - Number of nodes
1146/// * `radius` - Connection radius (typical range: 0.05 to 0.5)
1147/// * `rng` - Random number generator
1148///
1149/// # Returns
1150/// * `Result<Graph<usize, f64>>` - The generated graph where edge weights
1151///   are the Euclidean distances between connected nodes
1152///
1153/// # Applications
1154/// Models wireless sensor networks, ad-hoc networks, and spatial networks
1155/// where connectivity depends on physical proximity.
1156#[allow(dead_code)]
1157pub fn random_geometric_graph<R: Rng>(
1158    n: usize,
1159    radius: f64,
1160    rng: &mut R,
1161) -> Result<Graph<usize, f64>> {
1162    if radius < 0.0 {
1163        return Err(GraphError::InvalidGraph(
1164            "Radius must be non-negative".to_string(),
1165        ));
1166    }
1167
1168    let mut graph = Graph::new();
1169
1170    // Add all nodes
1171    for i in 0..n {
1172        graph.add_node(i);
1173    }
1174
1175    if n == 0 {
1176        return Ok(graph);
1177    }
1178
1179    // Generate random positions in [0,1)^2
1180    let positions: Vec<(f64, f64)> = (0..n)
1181        .map(|_| (rng.random::<f64>(), rng.random::<f64>()))
1182        .collect();
1183
1184    let radius_sq = radius * radius;
1185
1186    // Connect nodes within the radius
1187    for i in 0..n {
1188        for j in (i + 1)..n {
1189            let dx = positions[i].0 - positions[j].0;
1190            let dy = positions[i].1 - positions[j].1;
1191            let dist_sq = dx * dx + dy * dy;
1192
1193            if dist_sq <= radius_sq {
1194                let dist = dist_sq.sqrt();
1195                graph.add_edge(i, j, dist)?;
1196            }
1197        }
1198    }
1199
1200    Ok(graph)
1201}
1202
1203/// Generates a power-law cluster graph (Holme & Kim model)
1204///
1205/// This is a variant of the Barabasi-Albert model that adds a triad formation
1206/// step to increase clustering. After each preferential attachment step, with
1207/// probability `p_triangle`, a triangle is formed by connecting the new node
1208/// to a neighbor of the just-connected node.
1209///
1210/// # Arguments
1211/// * `n` - Total number of nodes (must be > m)
1212/// * `m` - Number of edges to add per new node
1213/// * `p_triangle` - Probability of triad formation step (0 = pure BA, 1 = max clustering)
1214/// * `rng` - Random number generator
1215///
1216/// # Returns
1217/// * `Result<Graph<usize, f64>>` - The generated graph
1218///
1219/// # Reference
1220/// Holme & Kim, "Growing scale-free networks with tunable clustering",
1221/// Physical Review E, 2002.
1222///
1223/// # Properties
1224/// - Scale-free degree distribution (power law)
1225/// - Tunable clustering coefficient via `p_triangle`
1226/// - When `p_triangle = 0`, equivalent to Barabasi-Albert model
1227#[allow(dead_code)]
1228pub fn power_law_cluster_graph<R: Rng>(
1229    n: usize,
1230    m: usize,
1231    p_triangle: f64,
1232    rng: &mut R,
1233) -> Result<Graph<usize, f64>> {
1234    if m == 0 {
1235        return Err(GraphError::InvalidGraph("m must be positive".to_string()));
1236    }
1237    if m >= n {
1238        return Err(GraphError::InvalidGraph(
1239            "m must be less than n".to_string(),
1240        ));
1241    }
1242    if !(0.0..=1.0).contains(&p_triangle) {
1243        return Err(GraphError::InvalidGraph(
1244            "p_triangle must be between 0 and 1".to_string(),
1245        ));
1246    }
1247
1248    let mut graph = Graph::new();
1249
1250    // Start with a complete graph of m+1 nodes
1251    for i in 0..=m {
1252        graph.add_node(i);
1253    }
1254    for i in 0..=m {
1255        for j in (i + 1)..=m {
1256            graph.add_edge(i, j, 1.0)?;
1257        }
1258    }
1259
1260    // Track degrees for preferential attachment
1261    let mut degrees = vec![m; m + 1];
1262    let mut total_degree = m * (m + 1);
1263
1264    // Add remaining nodes one at a time
1265    for new_node in (m + 1)..n {
1266        graph.add_node(new_node);
1267
1268        let mut targets_added: HashSet<usize> = HashSet::new();
1269        let mut edges_to_add = m;
1270
1271        // First edge: always preferential attachment
1272        if edges_to_add > 0 {
1273            let target = select_preferential_attachment(
1274                &degrees,
1275                total_degree,
1276                &targets_added,
1277                new_node,
1278                rng,
1279            );
1280            if let Some(t) = target {
1281                graph.add_edge(new_node, t, 1.0)?;
1282                targets_added.insert(t);
1283                degrees[t] += 1;
1284                total_degree += 2;
1285                edges_to_add -= 1;
1286            }
1287        }
1288
1289        // Remaining edges: either triad formation or preferential attachment
1290        while edges_to_add > 0 {
1291            if rng.random::<f64>() < p_triangle && !targets_added.is_empty() {
1292                // Triad formation: pick a random target already connected,
1293                // then connect to one of its neighbors
1294                let last_target = *targets_added.iter().last().unwrap_or(&0);
1295                let neighbors_of_target = graph.neighbors(&last_target).unwrap_or_default();
1296
1297                // Find a neighbor that is not already targeted or the new node
1298                let candidates: Vec<usize> = neighbors_of_target
1299                    .into_iter()
1300                    .filter(|nb| *nb != new_node && !targets_added.contains(nb))
1301                    .collect();
1302
1303                if let Some(&chosen) = candidates.choose(rng) {
1304                    graph.add_edge(new_node, chosen, 1.0)?;
1305                    targets_added.insert(chosen);
1306                    degrees[chosen] += 1;
1307                    total_degree += 2;
1308                    edges_to_add -= 1;
1309                    continue;
1310                }
1311                // Fall through to preferential attachment if no valid neighbor found
1312            }
1313
1314            // Preferential attachment
1315            let target = select_preferential_attachment(
1316                &degrees,
1317                total_degree,
1318                &targets_added,
1319                new_node,
1320                rng,
1321            );
1322            if let Some(t) = target {
1323                graph.add_edge(new_node, t, 1.0)?;
1324                targets_added.insert(t);
1325                degrees[t] += 1;
1326                total_degree += 2;
1327                edges_to_add -= 1;
1328            } else {
1329                // Cannot find more targets; bail to avoid infinite loop
1330                break;
1331            }
1332        }
1333
1334        degrees.push(targets_added.len());
1335    }
1336
1337    Ok(graph)
1338}
1339
1340/// Helper: select a node via preferential attachment (probability proportional to degree)
1341fn select_preferential_attachment<R: Rng>(
1342    degrees: &[usize],
1343    total_degree: usize,
1344    excluded: &HashSet<usize>,
1345    new_node: usize,
1346    rng: &mut R,
1347) -> Option<usize> {
1348    if total_degree == 0 {
1349        return None;
1350    }
1351
1352    // Try up to 100 times to find a non-excluded target
1353    for _ in 0..100 {
1354        let mut cumulative = 0.0;
1355        let random_value = rng.random::<f64>() * total_degree as f64;
1356
1357        for (node_id, &degree) in degrees.iter().enumerate() {
1358            if node_id == new_node {
1359                continue;
1360            }
1361            cumulative += degree as f64;
1362            if random_value <= cumulative && !excluded.contains(&node_id) {
1363                return Some(node_id);
1364            }
1365        }
1366    }
1367    None
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use super::*;
1373
1374    #[test]
1375    fn test_erdos_renyi_graph() {
1376        let mut rng = StdRng::seed_from_u64(42);
1377        let graph = erdos_renyi_graph(10, 0.3, &mut rng).expect("Operation failed");
1378
1379        assert_eq!(graph.node_count(), 10);
1380        // With p=0.3 and 45 possible edges, we expect around 13-14 edges
1381        // but this is random, so we just check it's reasonable
1382        assert!(graph.edge_count() <= 45);
1383    }
1384
1385    #[test]
1386    fn test_complete_graph() {
1387        let graph = complete_graph(5).expect("Operation failed");
1388
1389        assert_eq!(graph.node_count(), 5);
1390        assert_eq!(graph.edge_count(), 10); // n*(n-1)/2 = 5*4/2 = 10
1391    }
1392
1393    #[test]
1394    fn test_star_graph() {
1395        let graph = star_graph(6).expect("Operation failed");
1396
1397        assert_eq!(graph.node_count(), 6);
1398        assert_eq!(graph.edge_count(), 5); // n-1 edges
1399    }
1400
1401    #[test]
1402    fn test_path_graph() {
1403        let graph = path_graph(5).expect("Operation failed");
1404
1405        assert_eq!(graph.node_count(), 5);
1406        assert_eq!(graph.edge_count(), 4); // n-1 edges
1407    }
1408
1409    #[test]
1410    fn test_cycle_graph() {
1411        let graph = cycle_graph(5).expect("Operation failed");
1412
1413        assert_eq!(graph.node_count(), 5);
1414        assert_eq!(graph.edge_count(), 5); // n edges
1415
1416        // Test error case
1417        assert!(cycle_graph(2).is_err());
1418    }
1419
1420    #[test]
1421    fn test_grid_2d_graph() {
1422        let graph = grid_2d_graph(3, 4).expect("Operation failed");
1423
1424        assert_eq!(graph.node_count(), 12); // 3*4 = 12 nodes
1425        assert_eq!(graph.edge_count(), 17); // (3-1)*4 + 3*(4-1) = 8 + 9 = 17 edges
1426    }
1427
1428    #[test]
1429    fn test_grid_3d_graph() {
1430        let graph = grid_3d_graph(2, 2, 2).expect("Operation failed");
1431
1432        assert_eq!(graph.node_count(), 8); // 2*2*2 = 8 nodes
1433                                           // Each internal node connects to 3 neighbors in 3D grid
1434                                           // Expected edges: 3 faces × 2 edges per face + 3 additional connections = 12 edges
1435        assert_eq!(graph.edge_count(), 12);
1436    }
1437
1438    #[test]
1439    fn test_triangular_lattice_graph() {
1440        let graph = triangular_lattice_graph(3, 3).expect("Operation failed");
1441
1442        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1443                                           // Triangular lattice has more edges than regular grid due to diagonal connections
1444        assert!(graph.edge_count() > 12); // More than standard 2D grid edges
1445    }
1446
1447    #[test]
1448    fn test_hexagonal_lattice_graph() {
1449        let graph = hexagonal_lattice_graph(3, 3).expect("Operation failed");
1450
1451        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1452                                           // Hexagonal lattice should have fewer edges than triangular due to honeycomb structure
1453        assert!(graph.edge_count() >= 6);
1454    }
1455
1456    #[test]
1457    fn test_barabasi_albert_graph() {
1458        let mut rng = StdRng::seed_from_u64(42);
1459        let graph = barabasi_albert_graph(10, 2, &mut rng).expect("Operation failed");
1460
1461        assert_eq!(graph.node_count(), 10);
1462        // Should have 3 + 2*7 = 17 edges (3 initial edges + 2 for each of the 7 new nodes)
1463        assert_eq!(graph.edge_count(), 17);
1464    }
1465
1466    #[test]
1467    fn test_stochastic_block_model() {
1468        let mut rng = StdRng::seed_from_u64(42);
1469
1470        // Two blocks of size 3 and 4
1471        let block_sizes = vec![3, 4];
1472        // High intra-block probability, low inter-block probability
1473        let block_matrix = vec![vec![0.8, 0.1], vec![0.1, 0.8]];
1474
1475        let graph = stochastic_block_model(&block_sizes, &block_matrix, &mut rng)
1476            .expect("Operation failed");
1477
1478        assert_eq!(graph.node_count(), 7); // 3 + 4 = 7 nodes
1479
1480        // Check that all nodes are present
1481        for i in 0..7 {
1482            assert!(graph.has_node(&i));
1483        }
1484    }
1485
1486    #[test]
1487    fn test_two_community_sbm() {
1488        let mut rng = StdRng::seed_from_u64(42);
1489
1490        let graph = two_community_sbm(5, 5, 0.8, 0.1, &mut rng).expect("Operation failed");
1491
1492        assert_eq!(graph.node_count(), 10);
1493
1494        // Should have some edges within communities and fewer between
1495        // This is probabilistic so we can't test exact numbers
1496        assert!(graph.edge_count() > 0);
1497    }
1498
1499    #[test]
1500    fn test_planted_partition_model() {
1501        let mut rng = StdRng::seed_from_u64(42);
1502
1503        let graph = planted_partition_model(12, 3, 0.7, 0.1, &mut rng).expect("Operation failed");
1504
1505        assert_eq!(graph.node_count(), 12); // 12 nodes total
1506
1507        // 3 communities of size 4 each
1508        // Should have some edges
1509        assert!(graph.edge_count() > 0);
1510    }
1511
1512    #[test]
1513    fn test_stochastic_block_model_errors() {
1514        let mut rng = StdRng::seed_from_u64(42);
1515
1516        // Empty blocks
1517        assert!(stochastic_block_model(&[], &[], &mut rng).is_err());
1518
1519        // Mismatched dimensions
1520        let block_sizes = vec![3, 4];
1521        let wrong_matrix = vec![vec![0.5]];
1522        assert!(stochastic_block_model(&block_sizes, &wrong_matrix, &mut rng).is_err());
1523
1524        // Invalid probabilities
1525        let bad_matrix = vec![vec![1.5, 0.5], vec![0.5, 0.5]];
1526        assert!(stochastic_block_model(&block_sizes, &bad_matrix, &mut rng).is_err());
1527
1528        // Non-divisible nodes for planted partition
1529        assert!(planted_partition_model(10, 3, 0.5, 0.1, &mut rng).is_err());
1530    }
1531
1532    #[test]
1533    fn test_configuration_model() {
1534        let mut rng = StdRng::seed_from_u64(42);
1535
1536        // Test valid degree sequence (even sum)
1537        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1538        let graph = configuration_model(&degree_sequence, &mut rng).expect("Operation failed");
1539
1540        assert_eq!(graph.node_count(), 4);
1541        // Should have 4 edges (sum of degrees / 2)
1542        assert_eq!(graph.edge_count(), 4);
1543
1544        // Check that each node has the correct degree
1545        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1546            let actual_degree = graph.degree(&i);
1547            assert_eq!(actual_degree, expected_degree);
1548        }
1549    }
1550
1551    #[test]
1552    fn test_configuration_model_errors() {
1553        let mut rng = StdRng::seed_from_u64(42);
1554
1555        // Test odd degree sum (should fail)
1556        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1557        assert!(configuration_model(&odd_degree_sequence, &mut rng).is_err());
1558
1559        // Test empty sequence
1560        let empty_sequence = vec![];
1561        let graph = configuration_model(&empty_sequence, &mut rng).expect("Operation failed");
1562        assert_eq!(graph.node_count(), 0);
1563    }
1564
1565    #[test]
1566    fn test_simple_configuration_model() {
1567        let mut rng = StdRng::seed_from_u64(42);
1568
1569        // Test valid degree sequence for simple graph
1570        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1571        let graph =
1572            simple_configuration_model(&degree_sequence, &mut rng, 100).expect("Operation failed");
1573
1574        assert_eq!(graph.node_count(), 4);
1575        assert_eq!(graph.edge_count(), 4);
1576
1577        // Check that graph is simple (no self-loops)
1578        for i in 0..4 {
1579            assert!(!graph.has_edge(&i, &i), "Graph should not have self-loops");
1580        }
1581
1582        // Check degrees
1583        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1584            let actual_degree = graph.degree(&i);
1585            assert_eq!(actual_degree, expected_degree);
1586        }
1587    }
1588
1589    #[test]
1590    fn test_simple_configuration_model_errors() {
1591        let mut rng = StdRng::seed_from_u64(42);
1592
1593        // Test degree too large for simple graph
1594        let invalid_degree_sequence = vec![4, 2, 2, 2]; // Node 0 has degree 4, but n=4, so max degree is 3
1595        assert!(simple_configuration_model(&invalid_degree_sequence, &mut rng, 10).is_err());
1596
1597        // Test odd degree sum
1598        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1599        assert!(simple_configuration_model(&odd_degree_sequence, &mut rng, 10).is_err());
1600    }
1601
1602    #[test]
1603    fn test_tree_graph() {
1604        let mut rng = StdRng::seed_from_u64(42);
1605
1606        // Test empty tree
1607        let empty_tree = tree_graph(0, &mut rng).expect("Operation failed");
1608        assert_eq!(empty_tree.node_count(), 0);
1609        assert_eq!(empty_tree.edge_count(), 0);
1610
1611        // Test single node tree
1612        let single_tree = tree_graph(1, &mut rng).expect("Operation failed");
1613        assert_eq!(single_tree.node_count(), 1);
1614        assert_eq!(single_tree.edge_count(), 0);
1615
1616        // Test tree with multiple nodes
1617        let tree = tree_graph(5, &mut rng).expect("Operation failed");
1618        assert_eq!(tree.node_count(), 5);
1619        assert_eq!(tree.edge_count(), 4); // n-1 edges for a tree
1620
1621        // Verify all nodes are present
1622        for i in 0..5 {
1623            assert!(tree.has_node(&i));
1624        }
1625    }
1626
1627    #[test]
1628    fn test_random_spanning_tree() {
1629        let mut rng = StdRng::seed_from_u64(42);
1630
1631        // Create a complete graph
1632        let complete = complete_graph(4).expect("Operation failed");
1633
1634        // Generate spanning tree
1635        let spanning_tree = random_spanning_tree(&complete, &mut rng).expect("Operation failed");
1636
1637        assert_eq!(spanning_tree.node_count(), 4);
1638        assert_eq!(spanning_tree.edge_count(), 3); // n-1 edges for spanning tree
1639
1640        // Verify all nodes are present
1641        for i in 0..4 {
1642            assert!(spanning_tree.has_node(&i));
1643        }
1644    }
1645
1646    #[test]
1647    fn test_forest_graph() {
1648        let mut rng = StdRng::seed_from_u64(42);
1649
1650        // Create forest with trees of sizes [3, 2, 4]
1651        let tree_sizes = vec![3, 2, 4];
1652        let forest = forest_graph(&tree_sizes, &tree_sizes, &mut rng).expect("Operation failed");
1653
1654        assert_eq!(forest.node_count(), 9); // 3 + 2 + 4 = 9 nodes
1655        assert_eq!(forest.edge_count(), 6); // (3-1) + (2-1) + (4-1) = 6 edges
1656
1657        // Verify all nodes are present
1658        for i in 0..9 {
1659            assert!(forest.has_node(&i));
1660        }
1661
1662        // Test empty forest
1663        let empty_forest = forest_graph(&[], &[], &mut rng).expect("Operation failed");
1664        assert_eq!(empty_forest.node_count(), 0);
1665        assert_eq!(empty_forest.edge_count(), 0);
1666
1667        // Test forest with empty trees
1668        let forest_with_zeros =
1669            forest_graph(&[0, 3, 0, 2], &[0, 3, 0, 2], &mut rng).expect("Operation failed");
1670        assert_eq!(forest_with_zeros.node_count(), 5); // 3 + 2 = 5 nodes
1671        assert_eq!(forest_with_zeros.edge_count(), 3); // (3-1) + (2-1) = 3 edges
1672    }
1673
1674    #[test]
1675    fn test_random_geometric_graph() {
1676        let mut rng = StdRng::seed_from_u64(42);
1677
1678        let graph = random_geometric_graph(20, 0.4, &mut rng).expect("Operation failed");
1679        assert_eq!(graph.node_count(), 20);
1680        // With radius=0.4, we should have some edges but not all
1681        assert!(graph.edge_count() > 0);
1682        assert!(graph.edge_count() < 20 * 19 / 2);
1683
1684        // Edge weights should be distances (positive)
1685        for edge in graph.edges() {
1686            assert!(edge.weight > 0.0);
1687            assert!(edge.weight <= 0.4 + 1e-10); // within radius
1688        }
1689    }
1690
1691    #[test]
1692    fn test_random_geometric_graph_large_radius() {
1693        let mut rng = StdRng::seed_from_u64(42);
1694
1695        // With large radius, should be almost complete
1696        let graph = random_geometric_graph(5, 2.0, &mut rng).expect("Operation failed");
1697        assert_eq!(graph.node_count(), 5);
1698        // max distance in unit square is sqrt(2) < 2.0, so all pairs connected
1699        assert_eq!(graph.edge_count(), 10); // C(5,2) = 10
1700    }
1701
1702    #[test]
1703    fn test_random_geometric_graph_zero_radius() {
1704        let mut rng = StdRng::seed_from_u64(42);
1705
1706        let graph = random_geometric_graph(10, 0.0, &mut rng).expect("Operation failed");
1707        assert_eq!(graph.node_count(), 10);
1708        assert_eq!(graph.edge_count(), 0); // no edges with zero radius
1709    }
1710
1711    #[test]
1712    fn test_random_geometric_graph_errors() {
1713        let mut rng = StdRng::seed_from_u64(42);
1714        assert!(random_geometric_graph(10, -0.1, &mut rng).is_err());
1715    }
1716
1717    #[test]
1718    fn test_random_geometric_graph_empty() {
1719        let mut rng = StdRng::seed_from_u64(42);
1720        let graph = random_geometric_graph(0, 0.5, &mut rng).expect("Operation failed");
1721        assert_eq!(graph.node_count(), 0);
1722        assert_eq!(graph.edge_count(), 0);
1723    }
1724
1725    #[test]
1726    fn test_power_law_cluster_graph() {
1727        let mut rng = StdRng::seed_from_u64(42);
1728
1729        let graph = power_law_cluster_graph(20, 2, 0.5, &mut rng).expect("Operation failed");
1730        assert_eq!(graph.node_count(), 20);
1731        // Should have at least the initial complete graph edges + m edges per node
1732        assert!(graph.edge_count() > 0);
1733    }
1734
1735    #[test]
1736    fn test_power_law_cluster_no_triangle() {
1737        let mut rng = StdRng::seed_from_u64(42);
1738
1739        // p_triangle = 0 is equivalent to BA model
1740        let graph = power_law_cluster_graph(15, 2, 0.0, &mut rng).expect("Operation failed");
1741        assert_eq!(graph.node_count(), 15);
1742        assert!(graph.edge_count() > 0);
1743    }
1744
1745    #[test]
1746    fn test_power_law_cluster_max_triangle() {
1747        let mut rng = StdRng::seed_from_u64(42);
1748
1749        // p_triangle = 1 maximizes clustering
1750        let graph = power_law_cluster_graph(15, 2, 1.0, &mut rng).expect("Operation failed");
1751        assert_eq!(graph.node_count(), 15);
1752        assert!(graph.edge_count() > 0);
1753    }
1754
1755    #[test]
1756    fn test_power_law_cluster_errors() {
1757        let mut rng = StdRng::seed_from_u64(42);
1758
1759        // m = 0
1760        assert!(power_law_cluster_graph(10, 0, 0.5, &mut rng).is_err());
1761        // m >= n
1762        assert!(power_law_cluster_graph(5, 5, 0.5, &mut rng).is_err());
1763        // invalid p_triangle
1764        assert!(power_law_cluster_graph(10, 2, 1.5, &mut rng).is_err());
1765        assert!(power_law_cluster_graph(10, 2, -0.1, &mut rng).is_err());
1766    }
1767}