Skip to main content

proof_engine/graph/
generators.rs

1use super::graph_core::{Graph, GraphKind, NodeId};
2use glam::Vec2;
3
4fn pseudo_random(seed: u64, i: u64) -> f64 {
5    let mut x = seed.wrapping_mul(6364136223846793005).wrapping_add(i.wrapping_mul(1442695040888963407));
6    x ^= x >> 33;
7    x = x.wrapping_mul(0xff51afd7ed558ccd);
8    x ^= x >> 33;
9    (x as f64) / (u64::MAX as f64)
10}
11
12/// Watts-Strogatz small-world graph.
13/// Start with a ring lattice where each node connects to k nearest neighbors,
14/// then rewire each edge with probability beta.
15pub fn watts_strogatz(n: usize, k: usize, beta: f64) -> Graph<(), ()> {
16    let mut g = Graph::new(GraphKind::Undirected);
17    if n == 0 { return g; }
18    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
19
20    // Create ring lattice: each node connected to k/2 neighbors on each side
21    let half_k = k / 2;
22    let mut edges: Vec<(usize, usize)> = Vec::new();
23    for i in 0..n {
24        for j in 1..=half_k {
25            let target = (i + j) % n;
26            edges.push((i, target));
27        }
28    }
29
30    // Add edges, potentially rewired
31    let mut seed_counter: u64 = 42;
32    for (i, target) in edges {
33        let r = pseudo_random(seed_counter, seed_counter);
34        seed_counter += 1;
35        if r < beta {
36            // Rewire: pick random target that isn't self or existing neighbor
37            let mut new_target = i;
38            for attempt in 0..100 {
39                let candidate = (pseudo_random(seed_counter, attempt as u64) * n as f64) as usize % n;
40                seed_counter += 1;
41                if candidate != i && g.find_edge(nodes[i], nodes[candidate]).is_none() {
42                    new_target = candidate;
43                    break;
44                }
45            }
46            if new_target != i {
47                g.add_edge(nodes[i], nodes[new_target], ());
48            }
49        } else {
50            if g.find_edge(nodes[i], nodes[target]).is_none() {
51                g.add_edge(nodes[i], nodes[target], ());
52            }
53        }
54    }
55    g
56}
57
58/// Barabasi-Albert preferential attachment graph.
59/// Start with m+1 fully connected nodes, then add n-(m+1) nodes,
60/// each connecting to m existing nodes with probability proportional to degree.
61pub fn barabasi_albert(n: usize, m: usize) -> Graph<(), ()> {
62    let mut g = Graph::new(GraphKind::Undirected);
63    if n == 0 || m == 0 { return g; }
64
65    let initial = (m + 1).min(n);
66    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
67
68    // Fully connect initial nodes
69    for i in 0..initial {
70        for j in (i + 1)..initial {
71            g.add_edge(nodes[i], nodes[j], ());
72        }
73    }
74
75    let mut seed_counter: u64 = 1337;
76    // Degree list for preferential attachment (repeated entries = higher probability)
77    let mut degree_list: Vec<usize> = Vec::new();
78    for i in 0..initial {
79        for _ in 0..(initial - 1) {
80            degree_list.push(i);
81        }
82    }
83
84    for i in initial..n {
85        let mut targets: Vec<usize> = Vec::new();
86        let mut connected = std::collections::HashSet::new();
87        for _ in 0..m {
88            if degree_list.is_empty() { break; }
89            for attempt in 0..100 {
90                let idx = (pseudo_random(seed_counter, attempt as u64) * degree_list.len() as f64) as usize % degree_list.len();
91                seed_counter += 1;
92                let target = degree_list[idx];
93                if target != i && !connected.contains(&target) {
94                    connected.insert(target);
95                    targets.push(target);
96                    break;
97                }
98            }
99        }
100        for &t in &targets {
101            g.add_edge(nodes[i], nodes[t], ());
102            degree_list.push(i);
103            degree_list.push(t);
104        }
105    }
106    g
107}
108
109/// Erdos-Renyi random graph G(n, p).
110/// Each possible edge exists independently with probability p.
111pub fn erdos_renyi(n: usize, p: f64) -> Graph<(), ()> {
112    let mut g = Graph::new(GraphKind::Undirected);
113    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
114    let mut seed_counter: u64 = 7919;
115    for i in 0..n {
116        for j in (i + 1)..n {
117            let r = pseudo_random(seed_counter, (i * n + j) as u64);
118            seed_counter += 1;
119            if r < p {
120                g.add_edge(nodes[i], nodes[j], ());
121            }
122        }
123    }
124    g
125}
126
127/// Complete graph K_n: every pair of nodes connected.
128pub fn complete_graph(n: usize) -> Graph<(), ()> {
129    let mut g = Graph::new(GraphKind::Undirected);
130    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
131    for i in 0..n {
132        for j in (i + 1)..n {
133            g.add_edge(nodes[i], nodes[j], ());
134        }
135    }
136    g
137}
138
139/// Cycle graph C_n.
140pub fn cycle_graph(n: usize) -> Graph<(), ()> {
141    let mut g = Graph::new(GraphKind::Undirected);
142    if n == 0 { return g; }
143    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
144    for i in 0..n {
145        g.add_edge(nodes[i], nodes[(i + 1) % n], ());
146    }
147    g
148}
149
150/// Path graph P_n.
151pub fn path_graph(n: usize) -> Graph<(), ()> {
152    let mut g = Graph::new(GraphKind::Undirected);
153    if n == 0 { return g; }
154    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
155    for i in 0..(n - 1) {
156        g.add_edge(nodes[i], nodes[i + 1], ());
157    }
158    g
159}
160
161/// Star graph S_n: one center connected to n-1 leaves.
162pub fn star_graph(n: usize) -> Graph<(), ()> {
163    let mut g = Graph::new(GraphKind::Undirected);
164    if n == 0 { return g; }
165    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
166    for i in 1..n {
167        g.add_edge(nodes[0], nodes[i], ());
168    }
169    g
170}
171
172/// Grid graph with given rows and cols.
173pub fn grid_graph(rows: usize, cols: usize) -> Graph<(), ()> {
174    let mut g = Graph::new(GraphKind::Undirected);
175    if rows == 0 || cols == 0 { return g; }
176    let mut nodes = Vec::new();
177    for r in 0..rows {
178        for c in 0..cols {
179            let pos = Vec2::new(c as f32 * 50.0, r as f32 * 50.0);
180            nodes.push(g.add_node_with_pos((), pos));
181        }
182    }
183    for r in 0..rows {
184        for c in 0..cols {
185            let idx = r * cols + c;
186            if c + 1 < cols {
187                g.add_edge(nodes[idx], nodes[idx + 1], ());
188            }
189            if r + 1 < rows {
190                g.add_edge(nodes[idx], nodes[idx + cols], ());
191            }
192        }
193    }
194    g
195}
196
197/// Complete binary tree of given depth (0 = just root).
198pub fn binary_tree(depth: usize) -> Graph<(), ()> {
199    let mut g = Graph::new(GraphKind::Undirected);
200    let n = (1 << (depth + 1)) - 1; // 2^(depth+1) - 1 nodes
201    let nodes: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
202    for i in 0..n {
203        let left = 2 * i + 1;
204        let right = 2 * i + 2;
205        if left < n {
206            g.add_edge(nodes[i], nodes[left], ());
207        }
208        if right < n {
209            g.add_edge(nodes[i], nodes[right], ());
210        }
211    }
212    g
213}
214
215/// Petersen graph: 10 nodes, 15 edges.
216pub fn petersen_graph() -> Graph<(), ()> {
217    let mut g = Graph::new(GraphKind::Undirected);
218    let nodes: Vec<NodeId> = (0..10).map(|_| g.add_node(())).collect();
219    // Outer cycle: 0-1-2-3-4-0
220    for i in 0..5 {
221        g.add_edge(nodes[i], nodes[(i + 1) % 5], ());
222    }
223    // Inner pentagram: 5-7-9-6-8-5
224    g.add_edge(nodes[5], nodes[7], ());
225    g.add_edge(nodes[7], nodes[9], ());
226    g.add_edge(nodes[9], nodes[6], ());
227    g.add_edge(nodes[6], nodes[8], ());
228    g.add_edge(nodes[8], nodes[5], ());
229    // Spokes: i <-> i+5
230    for i in 0..5 {
231        g.add_edge(nodes[i], nodes[i + 5], ());
232    }
233    g
234}
235
236/// Complete bipartite graph K_{m,n}.
237pub fn complete_bipartite(m: usize, n: usize) -> Graph<(), ()> {
238    let mut g = Graph::new(GraphKind::Undirected);
239    let left: Vec<NodeId> = (0..m).map(|_| g.add_node(())).collect();
240    let right: Vec<NodeId> = (0..n).map(|_| g.add_node(())).collect();
241    for &l in &left {
242        for &r in &right {
243            g.add_edge(l, r, ());
244        }
245    }
246    g
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    #[test]
254    fn test_complete_graph_edges() {
255        let g = complete_graph(5);
256        assert_eq!(g.node_count(), 5);
257        assert_eq!(g.edge_count(), 10); // 5*4/2
258    }
259
260    #[test]
261    fn test_complete_graph_1() {
262        let g = complete_graph(1);
263        assert_eq!(g.node_count(), 1);
264        assert_eq!(g.edge_count(), 0);
265    }
266
267    #[test]
268    fn test_cycle_graph() {
269        let g = cycle_graph(6);
270        assert_eq!(g.node_count(), 6);
271        assert_eq!(g.edge_count(), 6);
272        // Every node has degree 2
273        for nid in g.node_ids() {
274            assert_eq!(g.degree(nid), 2);
275        }
276    }
277
278    #[test]
279    fn test_path_graph() {
280        let g = path_graph(5);
281        assert_eq!(g.node_count(), 5);
282        assert_eq!(g.edge_count(), 4);
283        let ids = g.node_ids();
284        assert_eq!(g.degree(ids[0]), 1);
285        assert_eq!(g.degree(ids[2]), 2);
286        assert_eq!(g.degree(ids[4]), 1);
287    }
288
289    #[test]
290    fn test_star_graph() {
291        let g = star_graph(6);
292        assert_eq!(g.node_count(), 6);
293        assert_eq!(g.edge_count(), 5);
294        let ids = g.node_ids();
295        assert_eq!(g.degree(ids[0]), 5); // center
296    }
297
298    #[test]
299    fn test_grid_graph() {
300        let g = grid_graph(3, 4);
301        assert_eq!(g.node_count(), 12);
302        // horizontal: 3*3 = 9, vertical: 2*4 = 8 => 17
303        assert_eq!(g.edge_count(), 17);
304    }
305
306    #[test]
307    fn test_binary_tree() {
308        let g = binary_tree(3);
309        // 2^4 - 1 = 15 nodes, 14 edges
310        assert_eq!(g.node_count(), 15);
311        assert_eq!(g.edge_count(), 14);
312    }
313
314    #[test]
315    fn test_petersen_graph() {
316        let g = petersen_graph();
317        assert_eq!(g.node_count(), 10);
318        assert_eq!(g.edge_count(), 15);
319        // Every node in Petersen graph has degree 3
320        for nid in g.node_ids() {
321            assert_eq!(g.degree(nid), 3);
322        }
323    }
324
325    #[test]
326    fn test_complete_bipartite() {
327        let g = complete_bipartite(3, 4);
328        assert_eq!(g.node_count(), 7);
329        assert_eq!(g.edge_count(), 12); // 3*4
330    }
331
332    #[test]
333    fn test_erdos_renyi_bounds() {
334        let g = erdos_renyi(20, 0.5);
335        assert_eq!(g.node_count(), 20);
336        // With p=0.5, expect roughly n*(n-1)/4 = 95 edges, but it's random
337        assert!(g.edge_count() > 0);
338    }
339
340    #[test]
341    fn test_watts_strogatz() {
342        let g = watts_strogatz(20, 4, 0.0);
343        assert_eq!(g.node_count(), 20);
344        // With beta=0, should be a ring lattice with k/2*n = 2*20 = 40 edges
345        assert!(g.edge_count() > 0);
346    }
347
348    #[test]
349    fn test_barabasi_albert() {
350        let g = barabasi_albert(20, 2);
351        assert_eq!(g.node_count(), 20);
352        assert!(g.edge_count() > 0);
353    }
354
355    #[test]
356    fn test_empty_generators() {
357        assert_eq!(complete_graph(0).node_count(), 0);
358        assert_eq!(cycle_graph(0).node_count(), 0);
359        assert_eq!(path_graph(0).node_count(), 0);
360        assert_eq!(star_graph(0).node_count(), 0);
361        assert_eq!(grid_graph(0, 5).node_count(), 0);
362    }
363}