scirs2_graph/
generators.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
9use scirs2_core::random::prelude::*;
10use std::collections::HashSet;
11
12use crate::base::{DiGraph, Graph};
13use crate::error::{GraphError, Result};
14use scirs2_core::random::seq::SliceRandom;
15
16/// Create a new empty undirected graph
17#[allow(dead_code)]
18pub fn create_graph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
19) -> Graph<N, E> {
20    Graph::new()
21}
22
23/// Create a new empty directed graph
24#[allow(dead_code)]
25pub fn create_digraph<N: crate::base::Node + std::fmt::Debug, E: crate::base::EdgeWeight>(
26) -> DiGraph<N, E> {
27    DiGraph::new()
28}
29
30/// Generates an Erdős–Rényi random graph
31///
32/// # Arguments
33/// * `n` - Number of nodes
34/// * `p` - Probability of edge creation between any two nodes
35/// * `rng` - Random number generator
36///
37/// # Returns
38/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
39#[allow(dead_code)]
40pub fn erdos_renyi_graph<R: Rng>(n: usize, p: f64, rng: &mut R) -> Result<Graph<usize, f64>> {
41    if !(0.0..=1.0).contains(&p) {
42        return Err(GraphError::InvalidGraph(
43            "Probability must be between 0 and 1".to_string(),
44        ));
45    }
46
47    let mut graph = Graph::new();
48
49    // Add all nodes
50    for i in 0..n {
51        graph.add_node(i);
52    }
53
54    // Add edges with probability p
55    for i in 0..n {
56        for j in i + 1..n {
57            if rng.random::<f64>() < p {
58                graph.add_edge(i, j, 1.0)?;
59            }
60        }
61    }
62
63    Ok(graph)
64}
65
66/// Generates a Barabási–Albert preferential attachment graph
67///
68/// # Arguments
69/// * `n` - Total number of nodes
70/// * `m` - Number of edges to attach from a new node to existing nodes
71/// * `rng` - Random number generator
72///
73/// # Returns
74/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
75#[allow(dead_code)]
76pub fn barabasi_albert_graph<R: Rng>(n: usize, m: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
77    if m >= n {
78        return Err(GraphError::InvalidGraph(
79            "m must be less than n".to_string(),
80        ));
81    }
82    if m == 0 {
83        return Err(GraphError::InvalidGraph("m must be positive".to_string()));
84    }
85
86    let mut graph = Graph::new();
87
88    // Start with a complete graph of m+1 nodes
89    for i in 0..=m {
90        graph.add_node(i);
91    }
92
93    for i in 0..=m {
94        for j in i + 1..=m {
95            graph.add_edge(i, j, 1.0)?;
96        }
97    }
98
99    // Keep track of node degrees for preferential attachment
100    let mut degrees = vec![m; m + 1];
101    let mut total_degree = m * (m + 1);
102
103    // Add remaining nodes
104    for new_node in (m + 1)..n {
105        graph.add_node(new_node);
106
107        let mut targets = HashSet::new();
108
109        // Select m nodes to connect to based on preferential attachment
110        while targets.len() < m {
111            let mut cumulative_prob = 0.0;
112            let random_value = rng.random::<f64>() * total_degree as f64;
113
114            for (node_id, &degree) in degrees.iter().enumerate() {
115                cumulative_prob += degree as f64;
116                if random_value <= cumulative_prob && !targets.contains(&node_id) {
117                    targets.insert(node_id);
118                    break;
119                }
120            }
121        }
122
123        // Add edges to selected targets
124        for &target in &targets {
125            graph.add_edge(new_node, target, 1.0)?;
126            degrees[target] += 1;
127            total_degree += 2; // Each edge adds 2 to total degree
128        }
129
130        degrees.push(m); // New node has degree m
131    }
132
133    Ok(graph)
134}
135
136/// Generates a complete graph (clique)
137///
138/// # Arguments
139/// * `n` - Number of nodes
140///
141/// # Returns
142/// * `Result<Graph<usize, f64>>` - A complete graph with n nodes
143#[allow(dead_code)]
144pub fn complete_graph(n: usize) -> Result<Graph<usize, f64>> {
145    let mut graph = Graph::new();
146
147    // Add all nodes
148    for i in 0..n {
149        graph.add_node(i);
150    }
151
152    // Add all possible edges
153    for i in 0..n {
154        for j in i + 1..n {
155            graph.add_edge(i, j, 1.0)?;
156        }
157    }
158
159    Ok(graph)
160}
161
162/// Generates a star graph with one central node connected to all others
163///
164/// # Arguments
165/// * `n` - Total number of nodes (must be >= 1)
166///
167/// # Returns
168/// * `Result<Graph<usize, f64>>` - A star graph with node 0 as the center
169#[allow(dead_code)]
170pub fn star_graph(n: usize) -> Result<Graph<usize, f64>> {
171    if n == 0 {
172        return Err(GraphError::InvalidGraph(
173            "Star graph must have at least 1 node".to_string(),
174        ));
175    }
176
177    let mut graph = Graph::new();
178
179    // Add all nodes
180    for i in 0..n {
181        graph.add_node(i);
182    }
183
184    // Connect center (node 0) to all other nodes
185    for i in 1..n {
186        graph.add_edge(0, i, 1.0)?;
187    }
188
189    Ok(graph)
190}
191
192/// Generates a path graph (nodes connected in a line)
193///
194/// # Arguments
195/// * `n` - Number of nodes
196///
197/// # Returns
198/// * `Result<Graph<usize, f64>>` - A path graph with nodes 0, 1, ..., n-1
199#[allow(dead_code)]
200pub fn path_graph(n: usize) -> Result<Graph<usize, f64>> {
201    let mut graph = Graph::new();
202
203    // Add all nodes
204    for i in 0..n {
205        graph.add_node(i);
206    }
207
208    // Connect consecutive nodes
209    for i in 0..n.saturating_sub(1) {
210        graph.add_edge(i, i + 1, 1.0)?;
211    }
212
213    Ok(graph)
214}
215
216/// Generates a random tree with n nodes
217///
218/// Uses a random process to connect nodes while maintaining the tree property
219/// (connected and acyclic). Each tree has exactly n-1 edges.
220///
221/// # Arguments
222/// * `n` - Number of nodes
223/// * `rng` - Random number generator
224///
225/// # Returns
226/// * `Result<Graph<usize, f64>>` - A random tree with nodes 0, 1, ..., n-1
227#[allow(dead_code)]
228pub fn tree_graph<R: Rng>(n: usize, rng: &mut R) -> Result<Graph<usize, f64>> {
229    if n == 0 {
230        return Ok(Graph::new());
231    }
232    if n == 1 {
233        let mut graph = Graph::new();
234        graph.add_node(0);
235        return Ok(graph);
236    }
237
238    let mut graph = Graph::new();
239
240    // Add all nodes
241    for i in 0..n {
242        graph.add_node(i);
243    }
244
245    // Use Prim's algorithm variation to build a random tree
246    let mut in_tree = vec![false; n];
247    let mut tree_nodes = Vec::new();
248
249    // Start with a random node
250    let start = rng.gen_range(0..n);
251    in_tree[start] = true;
252    tree_nodes.push(start);
253
254    // Add n-1 edges to complete the tree
255    for _ in 1..n {
256        // Pick a random node already in the tree
257        let tree_node = tree_nodes[rng.gen_range(0..tree_nodes.len())];
258
259        // Pick a random node not yet in the tree
260        let candidates: Vec<usize> = (0..n).filter(|&i| !in_tree[i]).collect();
261        if candidates.is_empty() {
262            break;
263        }
264
265        let new_node = candidates[rng.gen_range(0..candidates.len())];
266
267        // Add edge and mark node as in tree
268        graph.add_edge(tree_node, new_node, 1.0)?;
269        in_tree[new_node] = true;
270        tree_nodes.push(new_node);
271    }
272
273    Ok(graph)
274}
275
276/// Generates a random spanning tree from an existing graph
277///
278/// Uses Kruskal's algorithm with randomized edge selection to produce
279/// a random spanning tree of the input graph.
280///
281/// # Arguments
282/// * `graph` - The input graph to extract a spanning tree from
283/// * `rng` - Random number generator
284///
285/// # Returns
286/// * `Result<Graph<N, E>>` - A spanning tree of the input graph
287#[allow(dead_code)]
288pub fn random_spanning_tree<N, E, Ix, R>(
289    graph: &Graph<N, E, Ix>,
290    rng: &mut R,
291) -> Result<Graph<N, E, Ix>>
292where
293    N: crate::base::Node + std::fmt::Debug,
294    E: crate::base::EdgeWeight + Clone,
295    Ix: petgraph::graph::IndexType,
296    R: Rng,
297{
298    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
299    if nodes.is_empty() {
300        return Ok(Graph::new());
301    }
302    if nodes.len() == 1 {
303        let mut tree = Graph::new();
304        tree.add_node(nodes[0].clone());
305        return Ok(tree);
306    }
307
308    // Get all edges and shuffle them randomly
309    let mut edges: Vec<_> = graph.edges().into_iter().collect();
310    edges.shuffle(rng);
311
312    let mut tree = Graph::new();
313
314    // Add all nodes to the tree
315    for node in &nodes {
316        tree.add_node(node.clone());
317    }
318
319    // Use Union-Find to track components
320    let mut parent: std::collections::HashMap<N, N> =
321        nodes.iter().map(|n| (n.clone(), n.clone())).collect();
322    let mut rank: std::collections::HashMap<N, usize> =
323        nodes.iter().map(|n| (n.clone(), 0)).collect();
324
325    fn find<N: crate::base::Node>(parent: &mut std::collections::HashMap<N, N>, node: &N) -> N {
326        if parent[node] != *node {
327            let root = find(parent, &parent[node].clone());
328            parent.insert(node.clone(), root.clone());
329        }
330        parent[node].clone()
331    }
332
333    fn union<N: crate::base::Node>(
334        parent: &mut std::collections::HashMap<N, N>,
335        rank: &mut std::collections::HashMap<N, usize>,
336        x: &N,
337        y: &N,
338    ) -> bool {
339        let root_x = find(parent, x);
340        let root_y = find(parent, y);
341
342        if root_x == root_y {
343            return false; // Already in same component
344        }
345
346        // Union by rank
347        match rank[&root_x].cmp(&rank[&root_y]) {
348            std::cmp::Ordering::Less => {
349                parent.insert(root_x, root_y);
350            }
351            std::cmp::Ordering::Greater => {
352                parent.insert(root_y, root_x);
353            }
354            std::cmp::Ordering::Equal => {
355                parent.insert(root_y, root_x.clone());
356                *rank.get_mut(&root_x).unwrap() += 1;
357            }
358        }
359        true
360    }
361
362    let mut edges_added = 0;
363
364    // Add edges without creating cycles until we have n-1 edges
365    for edge in edges {
366        if union(&mut parent, &mut rank, &edge.source, &edge.target) {
367            tree.add_edge(edge.source, edge.target, edge.weight)?;
368            edges_added += 1;
369            if edges_added == nodes.len() - 1 {
370                break;
371            }
372        }
373    }
374
375    // Check if we have a spanning tree (connected graph)
376    if edges_added != nodes.len() - 1 {
377        return Err(GraphError::InvalidGraph(
378            "Input graph is not connected - cannot create spanning tree".to_string(),
379        ));
380    }
381
382    Ok(tree)
383}
384
385/// Generates a random forest (collection of trees)
386///
387/// Creates a forest by generating multiple random trees and combining them
388/// into a single graph. The trees are disjoint (no edges between different trees).
389///
390/// # Arguments
391/// * `tree_sizes` - Vector specifying the size of each tree in the forest
392/// * `rng` - Random number generator
393///
394/// # Returns
395/// * `Result<Graph<usize, f64>>` - A forest containing the specified trees
396#[allow(dead_code)]
397pub fn forest_graph<R: Rng>(
398    _tree_sizes: &[usize],
399    sizes: &[usize],
400    rng: &mut R,
401) -> Result<Graph<usize, f64>> {
402    let mut forest = Graph::new();
403    let mut node_offset = 0;
404
405    for &tree_size in _tree_sizes {
406        if tree_size == 0 {
407            continue;
408        }
409
410        // Generate a tree with nodes starting from node_offset
411        let tree = tree_graph(tree_size, rng)?;
412
413        // Add nodes to forest with offset
414        for i in 0..tree_size {
415            forest.add_node(node_offset + i);
416        }
417
418        // Add edges with offset
419        for edge in tree.edges() {
420            forest.add_edge(
421                node_offset + edge.source,
422                node_offset + edge.target,
423                edge.weight,
424            )?;
425        }
426
427        node_offset += tree_size;
428    }
429
430    Ok(forest)
431}
432
433/// Generates a cycle graph (circular arrangement of nodes)
434///
435/// # Arguments
436/// * `n` - Number of nodes (must be >= 3 for a meaningful cycle)
437///
438/// # Returns
439/// * `Result<Graph<usize, f64>>` - A cycle graph with nodes 0, 1, ..., n-1
440#[allow(dead_code)]
441pub fn cycle_graph(n: usize) -> Result<Graph<usize, f64>> {
442    if n < 3 {
443        return Err(GraphError::InvalidGraph(
444            "Cycle graph must have at least 3 nodes".to_string(),
445        ));
446    }
447
448    let mut graph = Graph::new();
449
450    // Add all nodes
451    for i in 0..n {
452        graph.add_node(i);
453    }
454
455    // Connect consecutive nodes
456    for i in 0..n {
457        graph.add_edge(i, (i + 1) % n, 1.0)?;
458    }
459
460    Ok(graph)
461}
462
463/// Generates a 2D grid/lattice graph
464///
465/// # Arguments
466/// * `rows` - Number of rows
467/// * `cols` - Number of columns
468///
469/// # Returns
470/// * `Result<Graph<usize, f64>>` - A grid graph where node ID = row * cols + col
471#[allow(dead_code)]
472pub fn grid_2d_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
473    if rows == 0 || cols == 0 {
474        return Err(GraphError::InvalidGraph(
475            "Grid dimensions must be positive".to_string(),
476        ));
477    }
478
479    let mut graph = Graph::new();
480
481    // Add all nodes
482    for i in 0..(rows * cols) {
483        graph.add_node(i);
484    }
485
486    // Add edges to adjacent nodes (4-connectivity)
487    for row in 0..rows {
488        for col in 0..cols {
489            let node_id = row * cols + col;
490
491            // Connect to right neighbor
492            if col + 1 < cols {
493                let right_neighbor = row * cols + (col + 1);
494                graph.add_edge(node_id, right_neighbor, 1.0)?;
495            }
496
497            // Connect to bottom neighbor
498            if row + 1 < rows {
499                let bottom_neighbor = (row + 1) * cols + col;
500                graph.add_edge(node_id, bottom_neighbor, 1.0)?;
501            }
502        }
503    }
504
505    Ok(graph)
506}
507
508/// Generates a 3D grid/lattice graph
509///
510/// # Arguments
511/// * `x_dim` - Size in x dimension
512/// * `y_dim` - Size in y dimension  
513/// * `z_dim` - Size in z dimension
514///
515/// # Returns
516/// * `Result<Graph<usize, f64>>` - A 3D grid graph where node ID = z*x_dim*y_dim + y*x_dim + x
517#[allow(dead_code)]
518pub fn grid_3d_graph(x_dim: usize, y_dim: usize, z_dim: usize) -> Result<Graph<usize, f64>> {
519    if x_dim == 0 || y_dim == 0 || z_dim == 0 {
520        return Err(GraphError::InvalidGraph(
521            "Grid dimensions must be positive".to_string(),
522        ));
523    }
524
525    let mut graph = Graph::new();
526
527    // Add all nodes
528    for i in 0..(x_dim * y_dim * z_dim) {
529        graph.add_node(i);
530    }
531
532    // Connect neighbors in 3D grid
533    for z in 0..z_dim {
534        for y in 0..y_dim {
535            for x in 0..x_dim {
536                let node_id = z * x_dim * y_dim + y * x_dim + x;
537
538                // Connect to right neighbor
539                if x + 1 < x_dim {
540                    let right_neighbor = z * x_dim * y_dim + y * x_dim + (x + 1);
541                    graph.add_edge(node_id, right_neighbor, 1.0)?;
542                }
543
544                // Connect to front neighbor
545                if y + 1 < y_dim {
546                    let front_neighbor = z * x_dim * y_dim + (y + 1) * x_dim + x;
547                    graph.add_edge(node_id, front_neighbor, 1.0)?;
548                }
549
550                // Connect to top neighbor
551                if z + 1 < z_dim {
552                    let top_neighbor = (z + 1) * x_dim * y_dim + y * x_dim + x;
553                    graph.add_edge(node_id, top_neighbor, 1.0)?;
554                }
555            }
556        }
557    }
558
559    Ok(graph)
560}
561
562/// Generates a triangular lattice graph
563///
564/// # Arguments
565/// * `rows` - Number of rows
566/// * `cols` - Number of columns
567///
568/// # Returns
569/// * `Result<Graph<usize, f64>>` - A triangular lattice where each node has up to 6 neighbors
570#[allow(dead_code)]
571pub fn triangular_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
572    if rows == 0 || cols == 0 {
573        return Err(GraphError::InvalidGraph(
574            "Lattice dimensions must be positive".to_string(),
575        ));
576    }
577
578    let mut graph = Graph::new();
579
580    // Add all nodes
581    for i in 0..(rows * cols) {
582        graph.add_node(i);
583    }
584
585    for row in 0..rows {
586        for col in 0..cols {
587            let node_id = row * cols + col;
588
589            // Standard grid connections (4-connected)
590            // Right neighbor
591            if col + 1 < cols {
592                let right_neighbor = row * cols + (col + 1);
593                graph.add_edge(node_id, right_neighbor, 1.0)?;
594            }
595
596            // Bottom neighbor
597            if row + 1 < rows {
598                let bottom_neighbor = (row + 1) * cols + col;
599                graph.add_edge(node_id, bottom_neighbor, 1.0)?;
600            }
601
602            // Diagonal connections for triangular lattice
603            // Bottom-right diagonal
604            if row + 1 < rows && col + 1 < cols {
605                let diag_neighbor = (row + 1) * cols + (col + 1);
606                graph.add_edge(node_id, diag_neighbor, 1.0)?;
607            }
608
609            // Bottom-left diagonal (for even rows)
610            if row + 1 < rows && col > 0 && row % 2 == 0 {
611                let diag_neighbor = (row + 1) * cols + (col - 1);
612                graph.add_edge(node_id, diag_neighbor, 1.0)?;
613            }
614        }
615    }
616
617    Ok(graph)
618}
619
620/// Generates a hexagonal lattice graph (honeycomb structure)
621///
622/// # Arguments
623/// * `rows` - Number of rows
624/// * `cols` - Number of columns
625///
626/// # Returns
627/// * `Result<Graph<usize, f64>>` - A hexagonal lattice where each node has exactly 3 neighbors
628#[allow(dead_code)]
629pub fn hexagonal_lattice_graph(rows: usize, cols: usize) -> Result<Graph<usize, f64>> {
630    if rows == 0 || cols == 0 {
631        return Err(GraphError::InvalidGraph(
632            "Lattice dimensions must be positive".to_string(),
633        ));
634    }
635
636    let mut graph = Graph::new();
637
638    // Add all nodes
639    for i in 0..(rows * cols) {
640        graph.add_node(i);
641    }
642
643    for row in 0..rows {
644        for col in 0..cols {
645            let node_id = row * cols + col;
646
647            // Hexagonal lattice connections (3 neighbors per node in honeycomb pattern)
648            // This creates a simplified hexagonal structure
649
650            // Right neighbor (horizontal)
651            if col + 1 < cols {
652                let right_neighbor = row * cols + (col + 1);
653                graph.add_edge(node_id, right_neighbor, 1.0)?;
654            }
655
656            // Connect in honeycomb pattern
657            if row % 2 == 0 {
658                // Even _rows: connect down-left and down-right
659                if row + 1 < rows {
660                    if col > 0 {
661                        let down_left = (row + 1) * cols + (col - 1);
662                        graph.add_edge(node_id, down_left, 1.0)?;
663                    }
664                    if col < cols {
665                        let down_right = (row + 1) * cols + col;
666                        graph.add_edge(node_id, down_right, 1.0)?;
667                    }
668                }
669            } else {
670                // Odd _rows: connect down-left and down-right with offset
671                if row + 1 < rows {
672                    let down_left = (row + 1) * cols + col;
673                    graph.add_edge(node_id, down_left, 1.0)?;
674
675                    if col + 1 < cols {
676                        let down_right = (row + 1) * cols + (col + 1);
677                        graph.add_edge(node_id, down_right, 1.0)?;
678                    }
679                }
680            }
681        }
682    }
683
684    Ok(graph)
685}
686
687/// Generates a Watts-Strogatz small-world graph
688///
689/// # Arguments
690/// * `n` - Number of nodes
691/// * `k` - Each node is connected to k nearest neighbors in ring topology (must be even)
692/// * `p` - Probability of rewiring each edge
693/// * `rng` - Random number generator
694///
695/// # Returns
696/// * `Result<Graph<usize, f64>>` - A small-world graph
697#[allow(dead_code)]
698pub fn watts_strogatz_graph<R: Rng>(
699    n: usize,
700    k: usize,
701    p: f64,
702    rng: &mut R,
703) -> Result<Graph<usize, f64>> {
704    if k >= n || !k.is_multiple_of(2) {
705        return Err(GraphError::InvalidGraph(
706            "k must be even and less than n".to_string(),
707        ));
708    }
709    if !(0.0..=1.0).contains(&p) {
710        return Err(GraphError::InvalidGraph(
711            "Probability must be between 0 and 1".to_string(),
712        ));
713    }
714
715    let mut graph = Graph::new();
716
717    // Add all nodes
718    for i in 0..n {
719        graph.add_node(i);
720    }
721
722    // Create regular ring lattice
723    for i in 0..n {
724        for j in 1..=(k / 2) {
725            let neighbor = (i + j) % n;
726            graph.add_edge(i, neighbor, 1.0)?;
727        }
728    }
729
730    // Rewire edges with probability p
731    let edges_to_process: Vec<_> = graph.edges().into_iter().collect();
732
733    for edge in edges_to_process {
734        if rng.random::<f64>() < p {
735            // Remove the original edge (we'll recreate the graph to do this)
736            let mut new_graph = Graph::new();
737
738            // Add all nodes
739            for i in 0..n {
740                new_graph.add_node(i);
741            }
742
743            // Add all edges except the one we're rewiring
744            for existing_edge in graph.edges() {
745                if (existing_edge.source != edge.source || existing_edge.target != edge.target)
746                    && (existing_edge.source != edge.target || existing_edge.target != edge.source)
747                {
748                    new_graph.add_edge(
749                        existing_edge.source,
750                        existing_edge.target,
751                        existing_edge.weight,
752                    )?;
753                }
754            }
755
756            // Add rewired edge to a random node
757            let mut new_target = rng.gen_range(0..n);
758            while new_target == edge.source || new_graph.has_node(&new_target) {
759                new_target = rng.gen_range(0..n);
760            }
761
762            new_graph.add_edge(edge.source, new_target, 1.0)?;
763            graph = new_graph;
764        }
765    }
766
767    Ok(graph)
768}
769
770/// Generates a graph using the Stochastic Block Model (SBM)
771///
772/// The SBM generates a graph where nodes are divided into communities (blocks)
773/// and edge probabilities depend on which communities the nodes belong to.
774///
775/// # Arguments
776/// * `block_sizes` - Vector specifying the size of each block/community
777/// * `block_matrix` - Probability matrix where entry (i,j) is the probability
778///   of an edge between nodes in block i and block j
779/// * `rng` - Random number generator
780///
781/// # Returns
782/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
783///   where nodes 0..block_sizes[0]-1 are in block 0, etc.
784#[allow(dead_code)]
785pub fn stochastic_block_model<R: Rng>(
786    block_sizes: &[usize],
787    block_matrix: &[Vec<f64>],
788    rng: &mut R,
789) -> Result<Graph<usize, f64>> {
790    if block_sizes.is_empty() {
791        return Err(GraphError::InvalidGraph(
792            "At least one block must be specified".to_string(),
793        ));
794    }
795
796    if block_matrix.len() != block_sizes.len() {
797        return Err(GraphError::InvalidGraph(
798            "Block _matrix dimensions must match number of blocks".to_string(),
799        ));
800    }
801
802    for row in block_matrix {
803        if row.len() != block_sizes.len() {
804            return Err(GraphError::InvalidGraph(
805                "Block _matrix must be square".to_string(),
806            ));
807        }
808        for &prob in row {
809            if !(0.0..=1.0).contains(&prob) {
810                return Err(GraphError::InvalidGraph(
811                    "All probabilities must be between 0 and 1".to_string(),
812                ));
813            }
814        }
815    }
816
817    let total_nodes: usize = block_sizes.iter().sum();
818    let mut graph = Graph::new();
819
820    // Add all nodes
821    for i in 0..total_nodes {
822        graph.add_node(i);
823    }
824
825    // Create mapping from node to block
826    let mut node_to_block = vec![0; total_nodes];
827    let mut current_node = 0;
828    for (block_id, &block_size) in block_sizes.iter().enumerate() {
829        for _ in 0..block_size {
830            node_to_block[current_node] = block_id;
831            current_node += 1;
832        }
833    }
834
835    // Generate edges based on block probabilities
836    for i in 0..total_nodes {
837        for j in (i + 1)..total_nodes {
838            let block_i = node_to_block[i];
839            let block_j = node_to_block[j];
840            let prob = block_matrix[block_i][block_j];
841
842            if rng.random::<f64>() < prob {
843                graph.add_edge(i, j, 1.0)?;
844            }
845        }
846    }
847
848    Ok(graph)
849}
850
851/// Generates a simple stochastic block model with two communities
852///
853/// This is a convenience function for creating a two-community SBM with
854/// high intra-community probability and low inter-community probability.
855///
856/// # Arguments
857/// * `n1` - Size of first community
858/// * `n2` - Size of second community
859/// * `p_in` - Probability of edges within communities
860/// * `p_out` - Probability of edges between communities
861/// * `rng` - Random number generator
862///
863/// # Returns
864/// * `Result<Graph<usize, f64>>` - The generated graph
865#[allow(dead_code)]
866pub fn two_community_sbm<R: Rng>(
867    n1: usize,
868    n2: usize,
869    p_in: f64,
870    p_out: f64,
871    rng: &mut R,
872) -> Result<Graph<usize, f64>> {
873    let block_sizes = vec![n1, n2];
874    let block_matrix = vec![vec![p_in, p_out], vec![p_out, p_in]];
875
876    stochastic_block_model(&block_sizes, &block_matrix, rng)
877}
878
879/// Generates a planted partition model (special case of SBM)
880///
881/// In this model, there are k communities of equal size, with high
882/// intra-community probability and low inter-community probability.
883///
884/// # Arguments
885/// * `n` - Total number of nodes (must be divisible by k)
886/// * `k` - Number of communities
887/// * `p_in` - Probability of edges within communities
888/// * `p_out` - Probability of edges between communities
889/// * `rng` - Random number generator
890///
891/// # Returns
892/// * `Result<Graph<usize, f64>>` - The generated graph
893#[allow(dead_code)]
894pub fn planted_partition_model<R: Rng>(
895    n: usize,
896    k: usize,
897    p_in: f64,
898    p_out: f64,
899    rng: &mut R,
900) -> Result<Graph<usize, f64>> {
901    if !n.is_multiple_of(k) {
902        return Err(GraphError::InvalidGraph(
903            "Number of nodes must be divisible by number of communities".to_string(),
904        ));
905    }
906
907    let community_size = n / k;
908    let block_sizes = vec![community_size; k];
909
910    // Create block matrix
911    let mut block_matrix = vec![vec![p_out; k]; k];
912    for (i, row) in block_matrix.iter_mut().enumerate().take(k) {
913        row[i] = p_in;
914    }
915
916    stochastic_block_model(&block_sizes, &block_matrix, rng)
917}
918
919/// Generates a random graph using the Configuration Model
920///
921/// The Configuration Model generates a random graph where each node has a specified degree.
922/// The degree sequence is the sequence of degrees for all nodes. The algorithm creates
923/// "stubs" (half-edges) for each node according to its degree, then randomly connects
924/// the stubs to form edges.
925///
926/// # Arguments
927/// * `degree_sequence` - Vector specifying the degree of each node
928/// * `rng` - Random number generator
929///
930/// # Returns
931/// * `Result<Graph<usize, f64>>` - The generated graph with node IDs 0..n-1
932///
933/// # Notes
934/// * The sum of all degrees must be even (since each edge contributes 2 to the total degree)
935/// * Self-loops and multiple edges between the same pair of nodes are possible
936/// * If you want a simple graph (no self-loops or multiple edges), you may need to
937///   regenerate or post-process the result
938#[allow(dead_code)]
939pub fn configuration_model<R: Rng>(
940    degree_sequence: &[usize],
941    rng: &mut R,
942) -> Result<Graph<usize, f64>> {
943    if degree_sequence.is_empty() {
944        return Ok(Graph::new());
945    }
946
947    // Check that sum of degrees is even
948    let total_degree: usize = degree_sequence.iter().sum();
949    if !total_degree.is_multiple_of(2) {
950        return Err(GraphError::InvalidGraph(
951            "Sum of degrees must be even".to_string(),
952        ));
953    }
954
955    let n = degree_sequence.len();
956    let mut graph = Graph::new();
957
958    // Add all nodes
959    for i in 0..n {
960        graph.add_node(i);
961    }
962
963    // Create stubs (half-edges) for each node
964    let mut stubs = Vec::new();
965    for (node_id, &degree) in degree_sequence.iter().enumerate() {
966        for _ in 0..degree {
967            stubs.push(node_id);
968        }
969    }
970
971    // Randomly connect stubs to form edges
972    while stubs.len() >= 2 {
973        // Pick two random stubs
974        let idx1 = rng.gen_range(0..stubs.len());
975        let stub1 = stubs.remove(idx1);
976
977        let idx2 = rng.gen_range(0..stubs.len());
978        let stub2 = stubs.remove(idx2);
979
980        // Connect the nodes (allow self-loops and multiple edges)
981        graph.add_edge(stub1, stub2, 1.0)?;
982    }
983
984    Ok(graph)
985}
986
987/// Generates a simple random graph using the Configuration Model
988///
989/// This variant attempts to generate a simple graph (no self-loops or multiple edges)
990/// by rejecting problematic edge attempts. If too many rejections occur, it returns
991/// an error indicating that the degree sequence may not be realizable as a simple graph.
992///
993/// # Arguments
994/// * `degree_sequence` - Vector specifying the degree of each node
995/// * `rng` - Random number generator
996/// * `max_attempts` - Maximum number of attempts before giving up
997///
998/// # Returns
999/// * `Result<Graph<usize, f64>>` - The generated simple graph
1000#[allow(dead_code)]
1001pub fn simple_configuration_model<R: Rng>(
1002    degree_sequence: &[usize],
1003    rng: &mut R,
1004    max_attempts: usize,
1005) -> Result<Graph<usize, f64>> {
1006    if degree_sequence.is_empty() {
1007        return Ok(Graph::new());
1008    }
1009
1010    // Check that sum of degrees is even
1011    let total_degree: usize = degree_sequence.iter().sum();
1012    if !total_degree.is_multiple_of(2) {
1013        return Err(GraphError::InvalidGraph(
1014            "Sum of degrees must be even".to_string(),
1015        ));
1016    }
1017
1018    let n = degree_sequence.len();
1019
1020    // Check for degree _sequence constraints for simple graphs
1021    for &degree in degree_sequence {
1022        if degree >= n {
1023            return Err(GraphError::InvalidGraph(
1024                "Node degree cannot exceed n-1 in a simple graph".to_string(),
1025            ));
1026        }
1027    }
1028
1029    let mut _attempts = 0;
1030
1031    while _attempts < max_attempts {
1032        let mut graph = Graph::new();
1033
1034        // Add all nodes
1035        for i in 0..n {
1036            graph.add_node(i);
1037        }
1038
1039        // Create stubs (half-edges) for each node
1040        let mut stubs = Vec::new();
1041        for (node_id, &degree) in degree_sequence.iter().enumerate() {
1042            for _ in 0..degree {
1043                stubs.push(node_id);
1044            }
1045        }
1046
1047        let mut success = true;
1048
1049        // Randomly connect stubs to form edges
1050        while stubs.len() >= 2 && success {
1051            // Pick two random stubs
1052            let idx1 = rng.gen_range(0..stubs.len());
1053            let stub1 = stubs[idx1];
1054
1055            let idx2 = rng.gen_range(0..stubs.len());
1056            let stub2 = stubs[idx2];
1057
1058            // Check for self-loop or existing edge
1059            if stub1 == stub2 || graph.has_edge(&stub1, &stub2) {
1060                // Try a few more times before giving up on this attempt
1061                let mut retries = 0;
1062                let mut found_valid = false;
1063
1064                while retries < 50 && !found_valid {
1065                    let new_idx2 = rng.gen_range(0..stubs.len());
1066                    let new_stub2 = stubs[new_idx2];
1067
1068                    if stub1 != new_stub2 && !graph.has_edge(&stub1, &new_stub2) {
1069                        // Remove stubs and add edge
1070                        // Remove the larger index first to avoid index shifting issues
1071                        if idx1 > new_idx2 {
1072                            stubs.remove(idx1);
1073                            stubs.remove(new_idx2);
1074                        } else {
1075                            stubs.remove(new_idx2);
1076                            stubs.remove(idx1);
1077                        }
1078                        graph.add_edge(stub1, new_stub2, 1.0)?;
1079                        found_valid = true;
1080                    }
1081                    retries += 1;
1082                }
1083
1084                if !found_valid {
1085                    success = false;
1086                }
1087            } else {
1088                // Remove stubs and add edge
1089                // Remove the larger index first to avoid index shifting issues
1090                if idx1 > idx2 {
1091                    stubs.remove(idx1);
1092                    stubs.remove(idx2);
1093                } else {
1094                    stubs.remove(idx2);
1095                    stubs.remove(idx1);
1096                }
1097                graph.add_edge(stub1, stub2, 1.0)?;
1098            }
1099        }
1100
1101        if success && stubs.is_empty() {
1102            return Ok(graph);
1103        }
1104
1105        _attempts += 1;
1106    }
1107
1108    Err(GraphError::InvalidGraph(
1109        "Could not generate simple graph with given degree _sequence after maximum _attempts"
1110            .to_string(),
1111    ))
1112}
1113
1114#[cfg(test)]
1115mod tests {
1116    use super::*;
1117
1118    #[test]
1119    fn test_erdos_renyi_graph() {
1120        let mut rng = StdRng::seed_from_u64(42);
1121        let graph = erdos_renyi_graph(10, 0.3, &mut rng).unwrap();
1122
1123        assert_eq!(graph.node_count(), 10);
1124        // With p=0.3 and 45 possible edges, we expect around 13-14 edges
1125        // but this is random, so we just check it's reasonable
1126        assert!(graph.edge_count() <= 45);
1127    }
1128
1129    #[test]
1130    fn test_complete_graph() {
1131        let graph = complete_graph(5).unwrap();
1132
1133        assert_eq!(graph.node_count(), 5);
1134        assert_eq!(graph.edge_count(), 10); // n*(n-1)/2 = 5*4/2 = 10
1135    }
1136
1137    #[test]
1138    fn test_star_graph() {
1139        let graph = star_graph(6).unwrap();
1140
1141        assert_eq!(graph.node_count(), 6);
1142        assert_eq!(graph.edge_count(), 5); // n-1 edges
1143    }
1144
1145    #[test]
1146    fn test_path_graph() {
1147        let graph = path_graph(5).unwrap();
1148
1149        assert_eq!(graph.node_count(), 5);
1150        assert_eq!(graph.edge_count(), 4); // n-1 edges
1151    }
1152
1153    #[test]
1154    fn test_cycle_graph() {
1155        let graph = cycle_graph(5).unwrap();
1156
1157        assert_eq!(graph.node_count(), 5);
1158        assert_eq!(graph.edge_count(), 5); // n edges
1159
1160        // Test error case
1161        assert!(cycle_graph(2).is_err());
1162    }
1163
1164    #[test]
1165    fn test_grid_2d_graph() {
1166        let graph = grid_2d_graph(3, 4).unwrap();
1167
1168        assert_eq!(graph.node_count(), 12); // 3*4 = 12 nodes
1169        assert_eq!(graph.edge_count(), 17); // (3-1)*4 + 3*(4-1) = 8 + 9 = 17 edges
1170    }
1171
1172    #[test]
1173    fn test_grid_3d_graph() {
1174        let graph = grid_3d_graph(2, 2, 2).unwrap();
1175
1176        assert_eq!(graph.node_count(), 8); // 2*2*2 = 8 nodes
1177                                           // Each internal node connects to 3 neighbors in 3D grid
1178                                           // Expected edges: 3 faces × 2 edges per face + 3 additional connections = 12 edges
1179        assert_eq!(graph.edge_count(), 12);
1180    }
1181
1182    #[test]
1183    fn test_triangular_lattice_graph() {
1184        let graph = triangular_lattice_graph(3, 3).unwrap();
1185
1186        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1187                                           // Triangular lattice has more edges than regular grid due to diagonal connections
1188        assert!(graph.edge_count() > 12); // More than standard 2D grid edges
1189    }
1190
1191    #[test]
1192    fn test_hexagonal_lattice_graph() {
1193        let graph = hexagonal_lattice_graph(3, 3).unwrap();
1194
1195        assert_eq!(graph.node_count(), 9); // 3*3 = 9 nodes
1196                                           // Hexagonal lattice should have fewer edges than triangular due to honeycomb structure
1197        assert!(graph.edge_count() >= 6);
1198    }
1199
1200    #[test]
1201    fn test_barabasi_albert_graph() {
1202        let mut rng = StdRng::seed_from_u64(42);
1203        let graph = barabasi_albert_graph(10, 2, &mut rng).unwrap();
1204
1205        assert_eq!(graph.node_count(), 10);
1206        // Should have 3 + 2*7 = 17 edges (3 initial edges + 2 for each of the 7 new nodes)
1207        assert_eq!(graph.edge_count(), 17);
1208    }
1209
1210    #[test]
1211    fn test_stochastic_block_model() {
1212        let mut rng = StdRng::seed_from_u64(42);
1213
1214        // Two blocks of size 3 and 4
1215        let block_sizes = vec![3, 4];
1216        // High intra-block probability, low inter-block probability
1217        let block_matrix = vec![vec![0.8, 0.1], vec![0.1, 0.8]];
1218
1219        let graph = stochastic_block_model(&block_sizes, &block_matrix, &mut rng).unwrap();
1220
1221        assert_eq!(graph.node_count(), 7); // 3 + 4 = 7 nodes
1222
1223        // Check that all nodes are present
1224        for i in 0..7 {
1225            assert!(graph.has_node(&i));
1226        }
1227    }
1228
1229    #[test]
1230    fn test_two_community_sbm() {
1231        let mut rng = StdRng::seed_from_u64(42);
1232
1233        let graph = two_community_sbm(5, 5, 0.8, 0.1, &mut rng).unwrap();
1234
1235        assert_eq!(graph.node_count(), 10);
1236
1237        // Should have some edges within communities and fewer between
1238        // This is probabilistic so we can't test exact numbers
1239        assert!(graph.edge_count() > 0);
1240    }
1241
1242    #[test]
1243    fn test_planted_partition_model() {
1244        let mut rng = StdRng::seed_from_u64(42);
1245
1246        let graph = planted_partition_model(12, 3, 0.7, 0.1, &mut rng).unwrap();
1247
1248        assert_eq!(graph.node_count(), 12); // 12 nodes total
1249
1250        // 3 communities of size 4 each
1251        // Should have some edges
1252        assert!(graph.edge_count() > 0);
1253    }
1254
1255    #[test]
1256    fn test_stochastic_block_model_errors() {
1257        let mut rng = StdRng::seed_from_u64(42);
1258
1259        // Empty blocks
1260        assert!(stochastic_block_model(&[], &[], &mut rng).is_err());
1261
1262        // Mismatched dimensions
1263        let block_sizes = vec![3, 4];
1264        let wrong_matrix = vec![vec![0.5]];
1265        assert!(stochastic_block_model(&block_sizes, &wrong_matrix, &mut rng).is_err());
1266
1267        // Invalid probabilities
1268        let bad_matrix = vec![vec![1.5, 0.5], vec![0.5, 0.5]];
1269        assert!(stochastic_block_model(&block_sizes, &bad_matrix, &mut rng).is_err());
1270
1271        // Non-divisible nodes for planted partition
1272        assert!(planted_partition_model(10, 3, 0.5, 0.1, &mut rng).is_err());
1273    }
1274
1275    #[test]
1276    fn test_configuration_model() {
1277        let mut rng = StdRng::seed_from_u64(42);
1278
1279        // Test valid degree sequence (even sum)
1280        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1281        let graph = configuration_model(&degree_sequence, &mut rng).unwrap();
1282
1283        assert_eq!(graph.node_count(), 4);
1284        // Should have 4 edges (sum of degrees / 2)
1285        assert_eq!(graph.edge_count(), 4);
1286
1287        // Check that each node has the correct degree
1288        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1289            let actual_degree = graph.degree(&i);
1290            assert_eq!(actual_degree, expected_degree);
1291        }
1292    }
1293
1294    #[test]
1295    fn test_configuration_model_errors() {
1296        let mut rng = StdRng::seed_from_u64(42);
1297
1298        // Test odd degree sum (should fail)
1299        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1300        assert!(configuration_model(&odd_degree_sequence, &mut rng).is_err());
1301
1302        // Test empty sequence
1303        let empty_sequence = vec![];
1304        let graph = configuration_model(&empty_sequence, &mut rng).unwrap();
1305        assert_eq!(graph.node_count(), 0);
1306    }
1307
1308    #[test]
1309    fn test_simple_configuration_model() {
1310        let mut rng = StdRng::seed_from_u64(42);
1311
1312        // Test valid degree sequence for simple graph
1313        let degree_sequence = vec![2, 2, 2, 2]; // Sum = 8 (even)
1314        let graph = simple_configuration_model(&degree_sequence, &mut rng, 100).unwrap();
1315
1316        assert_eq!(graph.node_count(), 4);
1317        assert_eq!(graph.edge_count(), 4);
1318
1319        // Check that graph is simple (no self-loops)
1320        for i in 0..4 {
1321            assert!(!graph.has_edge(&i, &i), "Graph should not have self-loops");
1322        }
1323
1324        // Check degrees
1325        for (i, &expected_degree) in degree_sequence.iter().enumerate() {
1326            let actual_degree = graph.degree(&i);
1327            assert_eq!(actual_degree, expected_degree);
1328        }
1329    }
1330
1331    #[test]
1332    fn test_simple_configuration_model_errors() {
1333        let mut rng = StdRng::seed_from_u64(42);
1334
1335        // Test degree too large for simple graph
1336        let invalid_degree_sequence = vec![4, 2, 2, 2]; // Node 0 has degree 4, but n=4, so max degree is 3
1337        assert!(simple_configuration_model(&invalid_degree_sequence, &mut rng, 10).is_err());
1338
1339        // Test odd degree sum
1340        let odd_degree_sequence = vec![1, 2, 2]; // Sum = 5 (odd)
1341        assert!(simple_configuration_model(&odd_degree_sequence, &mut rng, 10).is_err());
1342    }
1343
1344    #[test]
1345    fn test_tree_graph() {
1346        let mut rng = StdRng::seed_from_u64(42);
1347
1348        // Test empty tree
1349        let empty_tree = tree_graph(0, &mut rng).unwrap();
1350        assert_eq!(empty_tree.node_count(), 0);
1351        assert_eq!(empty_tree.edge_count(), 0);
1352
1353        // Test single node tree
1354        let single_tree = tree_graph(1, &mut rng).unwrap();
1355        assert_eq!(single_tree.node_count(), 1);
1356        assert_eq!(single_tree.edge_count(), 0);
1357
1358        // Test tree with multiple nodes
1359        let tree = tree_graph(5, &mut rng).unwrap();
1360        assert_eq!(tree.node_count(), 5);
1361        assert_eq!(tree.edge_count(), 4); // n-1 edges for a tree
1362
1363        // Verify all nodes are present
1364        for i in 0..5 {
1365            assert!(tree.has_node(&i));
1366        }
1367    }
1368
1369    #[test]
1370    fn test_random_spanning_tree() {
1371        let mut rng = StdRng::seed_from_u64(42);
1372
1373        // Create a complete graph
1374        let complete = complete_graph(4).unwrap();
1375
1376        // Generate spanning tree
1377        let spanning_tree = random_spanning_tree(&complete, &mut rng).unwrap();
1378
1379        assert_eq!(spanning_tree.node_count(), 4);
1380        assert_eq!(spanning_tree.edge_count(), 3); // n-1 edges for spanning tree
1381
1382        // Verify all nodes are present
1383        for i in 0..4 {
1384            assert!(spanning_tree.has_node(&i));
1385        }
1386    }
1387
1388    #[test]
1389    fn test_forest_graph() {
1390        let mut rng = StdRng::seed_from_u64(42);
1391
1392        // Create forest with trees of sizes [3, 2, 4]
1393        let tree_sizes = vec![3, 2, 4];
1394        let forest = forest_graph(&tree_sizes, &tree_sizes, &mut rng).unwrap();
1395
1396        assert_eq!(forest.node_count(), 9); // 3 + 2 + 4 = 9 nodes
1397        assert_eq!(forest.edge_count(), 6); // (3-1) + (2-1) + (4-1) = 6 edges
1398
1399        // Verify all nodes are present
1400        for i in 0..9 {
1401            assert!(forest.has_node(&i));
1402        }
1403
1404        // Test empty forest
1405        let empty_forest = forest_graph(&[], &[], &mut rng).unwrap();
1406        assert_eq!(empty_forest.node_count(), 0);
1407        assert_eq!(empty_forest.edge_count(), 0);
1408
1409        // Test forest with empty trees
1410        let forest_with_zeros = forest_graph(&[0, 3, 0, 2], &[0, 3, 0, 2], &mut rng).unwrap();
1411        assert_eq!(forest_with_zeros.node_count(), 5); // 3 + 2 = 5 nodes
1412        assert_eq!(forest_with_zeros.edge_count(), 3); // (3-1) + (2-1) = 3 edges
1413    }
1414}