quantrs2_tytan/analysis/
graph.rs

1//! Graph generation utilities for testing and examples
2//!
3//! This module provides tools for generating various types of graphs
4//! for testing optimization algorithms.
5
6use scirs2_core::random::rngs::StdRng;
7use scirs2_core::random::{Rng, SeedableRng};
8use std::collections::HashSet;
9
10/// Generate a random graph with specified edge probability
11pub fn generate_graph(
12    n_nodes: usize,
13    edge_probability: f64,
14) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
15    if !(0.0..=1.0).contains(&edge_probability) {
16        return Err("Edge probability must be between 0 and 1".into());
17    }
18
19    let mut rng = StdRng::seed_from_u64(42);
20    let mut edges = Vec::new();
21
22    // Generate edges with given probability
23    for i in 0..n_nodes {
24        for j in (i + 1)..n_nodes {
25            if rng.gen::<f64>() < edge_probability {
26                edges.push((i, j));
27            }
28        }
29    }
30
31    Ok(edges)
32}
33
34/// Generate a complete graph
35pub fn generate_complete_graph(n_nodes: usize) -> Vec<(usize, usize)> {
36    let mut edges = Vec::new();
37
38    for i in 0..n_nodes {
39        for j in (i + 1)..n_nodes {
40            edges.push((i, j));
41        }
42    }
43
44    edges
45}
46
47/// Generate a cycle graph
48pub fn generate_cycle_graph(n_nodes: usize) -> Vec<(usize, usize)> {
49    let mut edges = Vec::new();
50
51    if n_nodes < 3 {
52        return edges;
53    }
54
55    // Connect nodes in a cycle
56    for i in 0..n_nodes {
57        edges.push((i, (i + 1) % n_nodes));
58    }
59
60    edges
61}
62
63/// Generate a planar graph using grid structure
64pub fn generate_grid_graph(rows: usize, cols: usize) -> Vec<(usize, usize)> {
65    let mut edges = Vec::new();
66
67    // Helper to convert 2D coordinates to node index
68    let node_index = |r, c| r * cols + c;
69
70    // Add horizontal edges
71    for r in 0..rows {
72        for c in 0..(cols - 1) {
73            edges.push((node_index(r, c), node_index(r, c + 1)));
74        }
75    }
76
77    // Add vertical edges
78    for r in 0..(rows - 1) {
79        for c in 0..cols {
80            edges.push((node_index(r, c), node_index(r + 1, c)));
81        }
82    }
83
84    edges
85}
86
87/// Generate a bipartite graph
88pub fn generate_bipartite_graph(
89    left_nodes: usize,
90    right_nodes: usize,
91    edge_probability: f64,
92) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
93    if !(0.0..=1.0).contains(&edge_probability) {
94        return Err("Edge probability must be between 0 and 1".into());
95    }
96
97    let mut rng = StdRng::seed_from_u64(42);
98    let mut edges = Vec::new();
99
100    // Connect left nodes to right nodes with given probability
101    for i in 0..left_nodes {
102        for j in 0..right_nodes {
103            if rng.gen::<f64>() < edge_probability {
104                edges.push((i, left_nodes + j));
105            }
106        }
107    }
108
109    Ok(edges)
110}
111
112/// Generate a regular graph (all nodes have same degree)
113pub fn generate_regular_graph(
114    n_nodes: usize,
115    degree: usize,
116) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
117    if degree >= n_nodes {
118        return Err("Degree must be less than number of nodes".into());
119    }
120
121    if (n_nodes * degree) % 2 != 0 {
122        return Err("n_nodes * degree must be even for regular graph".into());
123    }
124
125    let mut rng = StdRng::seed_from_u64(42);
126    let mut edges = HashSet::new();
127    let mut node_degrees = vec![0; n_nodes];
128
129    // Simple algorithm: randomly connect nodes until each has the desired degree
130    let max_attempts = n_nodes * degree * 10;
131    let mut attempts = 0;
132
133    while node_degrees.iter().any(|&d| d < degree) && attempts < max_attempts {
134        attempts += 1;
135
136        // Find nodes that need more connections
137        let available: Vec<usize> = (0..n_nodes).filter(|&i| node_degrees[i] < degree).collect();
138
139        if available.len() < 2 {
140            continue;
141        }
142
143        // Pick two random nodes that need connections
144        let i = available[rng.gen_range(0..available.len())];
145        let j = available[rng.gen_range(0..available.len())];
146
147        if i != j && !edges.contains(&(i.min(j), i.max(j))) {
148            edges.insert((i.min(j), i.max(j)));
149            node_degrees[i] += 1;
150            node_degrees[j] += 1;
151        }
152    }
153
154    if node_degrees.iter().any(|&d| d < degree) {
155        return Err("Failed to generate regular graph with given parameters".into());
156    }
157
158    Ok(edges.into_iter().collect())
159}
160
161/// Generate a tree graph (connected acyclic graph)
162pub fn generate_tree_graph(n_nodes: usize) -> Vec<(usize, usize)> {
163    let mut rng = StdRng::seed_from_u64(42);
164    let mut edges = Vec::new();
165
166    if n_nodes == 0 {
167        return edges;
168    }
169
170    // Use Prüfer sequence to generate random tree
171    // For now, simple approach: connect each new node to a random existing node
172    for i in 1..n_nodes {
173        let parent = rng.gen_range(0..i);
174        edges.push((parent, i));
175    }
176
177    edges
178}
179
180/// Calculate graph properties
181pub struct GraphProperties {
182    pub n_nodes: usize,
183    pub n_edges: usize,
184    pub avg_degree: f64,
185    pub max_degree: usize,
186    pub min_degree: usize,
187    pub is_connected: bool,
188    pub density: f64,
189}
190
191/// Analyze graph properties
192pub fn analyze_graph(n_nodes: usize, edges: &[(usize, usize)]) -> GraphProperties {
193    let mut degrees = vec![0; n_nodes];
194
195    for (i, j) in edges {
196        degrees[*i] += 1;
197        degrees[*j] += 1;
198    }
199
200    let avg_degree = degrees.iter().sum::<usize>() as f64 / n_nodes as f64;
201    let max_degree = *degrees.iter().max().unwrap_or(&0);
202    let min_degree = *degrees.iter().min().unwrap_or(&0);
203
204    // Simple connectivity check (BFS from node 0)
205    let is_connected = if n_nodes > 0 {
206        let mut visited = vec![false; n_nodes];
207        let mut queue = vec![0];
208        visited[0] = true;
209
210        while let Some(node) = queue.pop() {
211            for (i, j) in edges {
212                if *i == node && !visited[*j] {
213                    visited[*j] = true;
214                    queue.push(*j);
215                } else if *j == node && !visited[*i] {
216                    visited[*i] = true;
217                    queue.push(*i);
218                }
219            }
220        }
221
222        visited.iter().all(|&v| v)
223    } else {
224        true
225    };
226
227    let max_edges = n_nodes * (n_nodes - 1) / 2;
228    let density = if max_edges > 0 {
229        edges.len() as f64 / max_edges as f64
230    } else {
231        0.0
232    };
233
234    GraphProperties {
235        n_nodes,
236        n_edges: edges.len(),
237        avg_degree,
238        max_degree,
239        min_degree,
240        is_connected,
241        density,
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_complete_graph() {
251        let edges = generate_complete_graph(5);
252        assert_eq!(edges.len(), 10); // 5 * 4 / 2
253    }
254
255    #[test]
256    fn test_cycle_graph() {
257        let edges = generate_cycle_graph(5);
258        assert_eq!(edges.len(), 5);
259    }
260
261    #[test]
262    fn test_grid_graph() {
263        let edges = generate_grid_graph(3, 3);
264        // 3x3 grid has 12 edges: 6 horizontal + 6 vertical
265        assert_eq!(edges.len(), 12);
266    }
267
268    #[test]
269    fn test_tree_graph() {
270        let edges = generate_tree_graph(10);
271        assert_eq!(edges.len(), 9); // n-1 edges for n nodes
272    }
273}