1use crate::core::{Graph, IgraphResult, VertexId};
8
9pub fn rewire_edges(graph: &Graph, prob: f64, loops: bool, seed: u64) -> IgraphResult<Graph> {
40 let n = graph.vcount();
41 let directed = graph.is_directed();
42 let ecount = graph.ecount();
43
44 if n == 0 || ecount == 0 || prob == 0.0 {
45 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
47 for eid in 0..ecount {
48 #[allow(clippy::cast_possible_truncation)]
49 let eid_u32 = eid as u32;
50 edges.push(graph.edge(eid_u32)?);
51 }
52 let mut result = Graph::new(n, directed)?;
53 result.add_edges(edges)?;
54 return Ok(result);
55 }
56
57 let mut edge_list: Vec<[VertexId; 2]> = Vec::with_capacity(ecount);
59 for eid in 0..ecount {
60 #[allow(clippy::cast_possible_truncation)]
61 let eid_u32 = eid as u32;
62 let (src, tgt) = graph.edge(eid_u32)?;
63 edge_list.push([src, tgt]);
64 }
65
66 let mut rng_state = seed;
68
69 let endpoints = ecount * 2;
70 let mut to_rewire = geom_sample(&mut rng_state, prob);
72
73 while to_rewire < endpoints {
74 let edge_idx = to_rewire / 2;
75 let endpoint_idx = to_rewire % 2; let other_idx = 1 - endpoint_idx;
78 let other_vertex = edge_list[edge_idx][other_idx];
79
80 let new_vertex = if loops {
81 random_vertex(&mut rng_state, n)
82 } else {
83 random_vertex_excluding(&mut rng_state, n, other_vertex)
84 };
85
86 edge_list[edge_idx][endpoint_idx] = new_vertex;
87 to_rewire += geom_sample(&mut rng_state, prob) + 1;
88 }
89
90 let edges: Vec<(VertexId, VertexId)> = edge_list.iter().map(|e| (e[0], e[1])).collect();
92
93 let mut result = Graph::new(n, directed)?;
94 result.add_edges(edges)?;
95 Ok(result)
96}
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub enum RewireDirectedMode {
101 Out,
103 In,
105}
106
107pub fn rewire_directed_edges(
141 graph: &Graph,
142 prob: f64,
143 loops: bool,
144 mode: RewireDirectedMode,
145 seed: u64,
146) -> IgraphResult<Graph> {
147 use crate::core::error::IgraphError;
148
149 if !(0.0..=1.0).contains(&prob) {
150 return Err(IgraphError::InvalidArgument(
151 "rewiring probability must be in [0.0, 1.0]".to_string(),
152 ));
153 }
154
155 if !graph.is_directed() {
157 return rewire_edges(graph, prob, loops, seed);
158 }
159
160 let n = graph.vcount();
161 let ecount = graph.ecount();
162
163 if n == 0 || ecount == 0 || prob == 0.0 {
164 let mut edges: Vec<(VertexId, VertexId)> = Vec::with_capacity(ecount);
165 for eid in 0..ecount {
166 #[allow(clippy::cast_possible_truncation)]
167 let eid_u32 = eid as u32;
168 edges.push(graph.edge(eid_u32)?);
169 }
170 let mut result = Graph::new(n, true)?;
171 result.add_edges(edges)?;
172 return Ok(result);
173 }
174
175 let offset = match mode {
177 RewireDirectedMode::In => 0,
178 RewireDirectedMode::Out => 1,
179 };
180
181 let mut edge_list: Vec<[VertexId; 2]> = Vec::with_capacity(ecount);
182 for eid in 0..ecount {
183 #[allow(clippy::cast_possible_truncation)]
184 let eid_u32 = eid as u32;
185 let (src, tgt) = graph.edge(eid_u32)?;
186 edge_list.push([src, tgt]);
187 }
188
189 let mut rng_state = seed;
190
191 let mut to_rewire = geom_sample(&mut rng_state, prob);
192 while to_rewire < ecount {
193 let other_vertex = edge_list[to_rewire][1 - offset];
194
195 let new_vertex = if loops {
196 random_vertex(&mut rng_state, n)
197 } else {
198 random_vertex_excluding(&mut rng_state, n, other_vertex)
199 };
200
201 edge_list[to_rewire][offset] = new_vertex;
202 to_rewire += geom_sample(&mut rng_state, prob) + 1;
203 }
204
205 let edges: Vec<(VertexId, VertexId)> = edge_list.iter().map(|e| (e[0], e[1])).collect();
206
207 let mut result = Graph::new(n, true)?;
208 result.add_edges(edges)?;
209 Ok(result)
210}
211
212fn splitmix64(state: &mut u64) -> u64 {
213 *state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
214 let mut z = *state;
215 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
216 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
217 z ^ (z >> 31)
218}
219
220fn random_f64(state: &mut u64) -> f64 {
221 let r = splitmix64(state);
222 (r >> 11) as f64 / (1u64 << 53) as f64
224}
225
226fn random_vertex(state: &mut u64, n: u32) -> VertexId {
227 let r = splitmix64(state);
228 #[allow(clippy::cast_possible_truncation)]
229 let v = (r % u64::from(n)) as u32;
230 v
231}
232
233fn random_vertex_excluding(state: &mut u64, n: u32, exclude: VertexId) -> VertexId {
234 let r = splitmix64(state);
235 #[allow(clippy::cast_possible_truncation)]
236 let mut v = (r % u64::from(n - 1)) as u32;
237 if v >= exclude {
238 v += 1;
239 }
240 v
241}
242
243fn geom_sample(state: &mut u64, prob: f64) -> usize {
245 if prob >= 1.0 {
246 return 0;
247 }
248 let u = random_f64(state);
249 if u == 0.0 {
250 return 0;
251 }
252 let result = ((1.0 - u).ln() / (1.0 - prob).ln()).floor();
254 if result < 0.0 || result.is_nan() {
256 0
257 } else if result > usize::MAX as f64 {
258 usize::MAX
259 } else {
260 result as usize
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn test_prob_zero() {
270 let mut g = Graph::with_vertices(5);
271 g.add_edge(0, 1).unwrap();
272 g.add_edge(1, 2).unwrap();
273
274 let rg = rewire_edges(&g, 0.0, false, 42).unwrap();
275 assert_eq!(rg.vcount(), 5);
276 assert_eq!(rg.ecount(), 2);
277 assert_eq!(rg.edge(0).unwrap(), g.edge(0).unwrap());
278 assert_eq!(rg.edge(1).unwrap(), g.edge(1).unwrap());
279 }
280
281 #[test]
282 fn test_prob_one_no_loops() {
283 let mut g = Graph::with_vertices(10);
284 for i in 0..9u32 {
285 g.add_edge(i, i + 1).unwrap();
286 }
287
288 let rg = rewire_edges(&g, 1.0, false, 123).unwrap();
289 assert_eq!(rg.vcount(), 10);
290 assert_eq!(rg.ecount(), 9);
291 for eid in 0..9u32 {
293 let (s, t) = rg.edge(eid).unwrap();
294 assert_ne!(s, t);
295 }
296 }
297
298 #[test]
299 fn test_deterministic() {
300 let mut g = Graph::with_vertices(10);
301 for i in 0..9u32 {
302 g.add_edge(i, i + 1).unwrap();
303 }
304
305 let r1 = rewire_edges(&g, 0.5, false, 42).unwrap();
306 let r2 = rewire_edges(&g, 0.5, false, 42).unwrap();
307 for eid in 0..9u32 {
309 assert_eq!(r1.edge(eid).unwrap(), r2.edge(eid).unwrap());
310 }
311 }
312
313 #[test]
314 fn test_different_seeds() {
315 let mut g = Graph::with_vertices(100);
316 for i in 0..99u32 {
317 g.add_edge(i, i + 1).unwrap();
318 }
319
320 let r1 = rewire_edges(&g, 0.5, false, 1).unwrap();
321 let r2 = rewire_edges(&g, 0.5, false, 2).unwrap();
322 let mut same_count = 0;
324 for eid in 0..99u32 {
325 if r1.edge(eid).unwrap() == r2.edge(eid).unwrap() {
326 same_count += 1;
327 }
328 }
329 assert!(same_count < 99);
331 }
332
333 #[test]
334 fn test_empty_graph() {
335 let g = Graph::with_vertices(0);
336 let rg = rewire_edges(&g, 0.5, false, 42).unwrap();
337 assert_eq!(rg.vcount(), 0);
338 }
339
340 #[test]
341 fn test_no_edges() {
342 let g = Graph::with_vertices(5);
343 let rg = rewire_edges(&g, 0.5, false, 42).unwrap();
344 assert_eq!(rg.ecount(), 0);
345 }
346
347 #[test]
348 fn test_directed() {
349 let mut g = Graph::new(5, true).unwrap();
350 g.add_edge(0, 1).unwrap();
351 g.add_edge(1, 2).unwrap();
352 g.add_edge(2, 3).unwrap();
353
354 let rg = rewire_edges(&g, 0.5, false, 99).unwrap();
355 assert!(rg.is_directed());
356 assert_eq!(rg.ecount(), 3);
357 }
358
359 #[test]
360 fn test_loops_allowed() {
361 let mut g = Graph::with_vertices(3);
363 g.add_edge(0, 1).unwrap();
364 g.add_edge(1, 2).unwrap();
365
366 let rg = rewire_edges(&g, 1.0, true, 42).unwrap();
367 assert_eq!(rg.vcount(), 3);
368 assert_eq!(rg.ecount(), 2);
369 }
370
371 #[test]
372 fn test_preserves_edge_count() {
373 let mut g = Graph::with_vertices(20);
374 for i in 0..19u32 {
375 g.add_edge(i, i + 1).unwrap();
376 }
377
378 for seed in 0..10u64 {
379 let rg = rewire_edges(&g, 0.3, false, seed).unwrap();
380 assert_eq!(rg.ecount(), 19);
381 }
382 }
383
384 #[test]
387 fn test_directed_rewire_prob_zero() {
388 let mut g = Graph::new(5, true).unwrap();
389 g.add_edge(0, 1).unwrap();
390 g.add_edge(1, 2).unwrap();
391 g.add_edge(2, 3).unwrap();
392
393 let rg = rewire_directed_edges(&g, 0.0, false, RewireDirectedMode::Out, 42).unwrap();
394 assert_eq!(rg.ecount(), 3);
395 for eid in 0..3u32 {
396 assert_eq!(rg.edge(eid).unwrap(), g.edge(eid).unwrap());
397 }
398 }
399
400 #[test]
401 fn test_directed_rewire_preserves_out_degree() {
402 let mut g = Graph::new(10, true).unwrap();
404 for i in 0..9u32 {
405 g.add_edge(i, i + 1).unwrap();
406 }
407
408 let rg = rewire_directed_edges(&g, 1.0, false, RewireDirectedMode::Out, 42).unwrap();
409 assert_eq!(rg.ecount(), 9);
410 assert!(rg.is_directed());
411
412 for v in 0..10u32 {
414 let orig_out: usize = (0..9).filter(|&eid| g.edge(eid).unwrap().0 == v).count();
415 let new_out: usize = (0..9).filter(|&eid| rg.edge(eid).unwrap().0 == v).count();
416 assert_eq!(orig_out, new_out);
417 }
418 }
419
420 #[test]
421 fn test_directed_rewire_preserves_in_degree() {
422 let mut g = Graph::new(10, true).unwrap();
424 for i in 0..9u32 {
425 g.add_edge(i, i + 1).unwrap();
426 }
427
428 let rg = rewire_directed_edges(&g, 1.0, false, RewireDirectedMode::In, 42).unwrap();
429 assert_eq!(rg.ecount(), 9);
430
431 for v in 0..10u32 {
433 let orig_in: usize = (0..9).filter(|&eid| g.edge(eid).unwrap().1 == v).count();
434 let new_in: usize = (0..9).filter(|&eid| rg.edge(eid).unwrap().1 == v).count();
435 assert_eq!(orig_in, new_in);
436 }
437 }
438
439 #[test]
440 fn test_directed_rewire_no_self_loops() {
441 let mut g = Graph::new(5, true).unwrap();
442 g.add_edge(0, 1).unwrap();
443 g.add_edge(1, 2).unwrap();
444 g.add_edge(2, 3).unwrap();
445 g.add_edge(3, 4).unwrap();
446
447 for seed in 0..20u64 {
448 let rg = rewire_directed_edges(&g, 1.0, false, RewireDirectedMode::Out, seed).unwrap();
449 for eid in 0..4u32 {
450 let (s, t) = rg.edge(eid).unwrap();
451 assert_ne!(s, t);
452 }
453 }
454 }
455
456 #[test]
457 fn test_directed_rewire_undirected_fallback() {
458 let mut g = Graph::with_vertices(5);
460 g.add_edge(0, 1).unwrap();
461 g.add_edge(1, 2).unwrap();
462
463 let rg = rewire_directed_edges(&g, 0.5, false, RewireDirectedMode::Out, 42).unwrap();
464 assert!(!rg.is_directed());
465 assert_eq!(rg.ecount(), 2);
466 }
467
468 #[test]
469 fn test_directed_rewire_invalid_prob() {
470 let g = Graph::new(5, true).unwrap();
471 assert!(rewire_directed_edges(&g, -0.1, false, RewireDirectedMode::Out, 42).is_err());
472 assert!(rewire_directed_edges(&g, 1.1, false, RewireDirectedMode::Out, 42).is_err());
473 }
474
475 #[test]
476 fn test_directed_rewire_deterministic() {
477 let mut g = Graph::new(10, true).unwrap();
478 for i in 0..9u32 {
479 g.add_edge(i, i + 1).unwrap();
480 }
481
482 let r1 = rewire_directed_edges(&g, 0.5, false, RewireDirectedMode::Out, 99).unwrap();
483 let r2 = rewire_directed_edges(&g, 0.5, false, RewireDirectedMode::Out, 99).unwrap();
484 for eid in 0..9u32 {
485 assert_eq!(r1.edge(eid).unwrap(), r2.edge(eid).unwrap());
486 }
487 }
488
489 #[test]
490 fn test_directed_rewire_empty() {
491 let g = Graph::new(0, true).unwrap();
492 let rg = rewire_directed_edges(&g, 0.5, false, RewireDirectedMode::Out, 42).unwrap();
493 assert_eq!(rg.vcount(), 0);
494 }
495}