1use crate::algorithms::constructors::ring::ring_graph;
30use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
31
32pub fn mycielskian(graph: &Graph, k: u32) -> IgraphResult<Graph> {
66 let directed = graph.is_directed();
67 let (mut vcount, mut ecount, mut edges) = stage_input_edges(graph)?;
68 let mut k_remaining = k;
69
70 if vcount == 0 && k_remaining > 0 {
72 vcount = 1;
73 k_remaining -= 1;
74 }
75
76 if vcount == 1 && k_remaining > 0 {
78 vcount = 2;
79 ecount = ecount.checked_add(1).ok_or_else(|| {
80 IgraphError::InvalidArgument(
81 "mycielskian: singleton promotion ecount + 1 overflows u32".to_string(),
82 )
83 })?;
84 edges.push(0);
85 edges.push(1);
86 k_remaining -= 1;
87 }
88
89 let (final_v, final_e) = project_counts(vcount, ecount, k_remaining)?;
90
91 let final_buffer_len = usize::try_from(final_e)
93 .ok()
94 .and_then(|m| m.checked_mul(2))
95 .ok_or_else(|| {
96 IgraphError::InvalidArgument(format!(
97 "mycielskian: final edge buffer size 2 * {final_e} overflows usize"
98 ))
99 })?;
100 edges.resize(final_buffer_len, 0);
101
102 run_iterations(&mut edges, vcount, ecount, k_remaining)?;
103
104 let mut result = Graph::new(final_v, directed)?;
105 let pairs: Vec<(VertexId, VertexId)> = edges
106 .chunks_exact(2)
107 .map(|pair| (pair[0], pair[1]))
108 .collect();
109 result.add_edges(pairs)?;
110 Ok(result)
111}
112
113fn stage_input_edges(graph: &Graph) -> IgraphResult<(u32, u32, Vec<u32>)> {
117 let ecount = u32::try_from(graph.ecount()).map_err(|_| {
118 IgraphError::InvalidArgument(format!(
119 "mycielskian: input ecount = {} exceeds u32::MAX",
120 graph.ecount()
121 ))
122 })?;
123 let cap = usize::try_from(ecount)
124 .ok()
125 .and_then(|m| m.checked_mul(2))
126 .ok_or_else(|| {
127 IgraphError::InvalidArgument(format!(
128 "mycielskian: input edge buffer size 2 * {ecount} overflows usize"
129 ))
130 })?;
131 let mut edges: Vec<u32> = Vec::with_capacity(cap);
132 for eid in 0..graph.ecount() {
133 let id = u32::try_from(eid).map_err(|_| {
134 IgraphError::InvalidArgument(format!("mycielskian: edge id {eid} exceeds u32::MAX"))
135 })?;
136 let (u, v) = graph.edge(id).map_err(|_| {
137 IgraphError::InvalidArgument(format!("mycielskian: edge id {eid} not in range"))
138 })?;
139 edges.push(u);
140 edges.push(v);
141 }
142 Ok((graph.vcount(), ecount, edges))
143}
144
145fn project_counts(vcount: u32, ecount: u32, k: u32) -> IgraphResult<(u32, u32)> {
149 let mut v = vcount;
150 let mut e = ecount;
151 for _ in 0..k {
152 e = e
153 .checked_mul(3)
154 .and_then(|x| x.checked_add(v))
155 .ok_or_else(|| {
156 IgraphError::InvalidArgument(format!(
157 "mycielskian: ecount overflow at iter with vcount = {v}, ecount = {e}"
158 ))
159 })?;
160 v = v
161 .checked_mul(2)
162 .and_then(|x| x.checked_add(1))
163 .ok_or_else(|| {
164 IgraphError::InvalidArgument(format!("mycielskian: vcount overflow (2 * {v} + 1)"))
165 })?;
166 }
167 Ok((v, e))
168}
169
170fn run_iterations(edges: &mut [u32], vcount: u32, ecount: u32, k: u32) -> IgraphResult<()> {
173 let mut edge_index: usize = 2usize
174 .checked_mul(usize::try_from(ecount).map_err(|_| {
175 IgraphError::InvalidArgument(format!("mycielskian: ecount {ecount} too large"))
176 })?)
177 .ok_or_else(|| {
178 IgraphError::InvalidArgument("mycielskian: edge_index overflow".to_string())
179 })?;
180 let mut offset: u32 = vcount;
181
182 for _ in 0..k {
183 let prev_vcount: u32 = offset;
184 let w: u32 = offset.checked_mul(2).ok_or_else(|| {
185 IgraphError::InvalidArgument(format!("mycielskian: w = 2 * {offset} overflows u32"))
186 })?;
187 let last_edge_index: usize = edge_index;
188
189 let mut j = 0usize;
191 while j < last_edge_index {
192 let v1 = edges[j];
193 let v2 = edges[j + 1];
194 let v1_shadow = offset.checked_add(v1).ok_or_else(|| {
195 IgraphError::InvalidArgument(format!(
196 "mycielskian: shadow id {offset} + {v1} overflows u32"
197 ))
198 })?;
199 let v2_shadow = offset.checked_add(v2).ok_or_else(|| {
200 IgraphError::InvalidArgument(format!(
201 "mycielskian: shadow id {offset} + {v2} overflows u32"
202 ))
203 })?;
204 edges[edge_index] = v1;
205 edges[edge_index + 1] = v2_shadow;
206 edges[edge_index + 2] = v2;
207 edges[edge_index + 3] = v1_shadow;
208 edge_index += 4;
209 j += 2;
210 }
211
212 let mut star = prev_vcount;
214 while star < w {
215 edges[edge_index] = star;
216 edges[edge_index + 1] = w;
217 edge_index += 2;
218 star += 1;
219 }
220
221 offset = offset
223 .checked_mul(2)
224 .and_then(|o| o.checked_add(1))
225 .ok_or_else(|| {
226 IgraphError::InvalidArgument(format!(
227 "mycielskian: offset update (2 * {offset} + 1) overflows u32"
228 ))
229 })?;
230 }
231 Ok(())
232}
233
234pub fn mycielski_graph(k: u32) -> IgraphResult<Graph> {
262 if k <= 1 {
263 return Graph::new(k, false);
265 }
266 let p2 = ring_graph(2, false, false, false)?;
268 mycielskian(&p2, k - 2)
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274 use std::collections::BTreeSet;
275
276 fn canonical_edges(g: &Graph) -> BTreeSet<(VertexId, VertexId)> {
277 let mut s = BTreeSet::new();
278 for eid in 0..u32::try_from(g.ecount()).unwrap() {
279 let (a, b) = g.edge(eid).unwrap();
280 s.insert(if a <= b { (a, b) } else { (b, a) });
281 }
282 s
283 }
284
285 fn degree(g: &Graph, v: VertexId) -> usize {
286 g.neighbors(v).unwrap().len()
287 }
288
289 fn assert_simple_undirected(g: &Graph) {
290 assert!(!g.is_directed());
291 let mut seen = BTreeSet::new();
292 for eid in 0..u32::try_from(g.ecount()).unwrap() {
293 let (a, b) = g.edge(eid).unwrap();
294 assert_ne!(a, b, "self-loop on edge {eid}: ({a},{b})");
295 let key = if a <= b { (a, b) } else { (b, a) };
296 assert!(seen.insert(key), "parallel edge {key:?}");
297 }
298 }
299
300 #[test]
301 fn k_zero_is_identity_undirected() {
302 let mut g = Graph::new(4, false).unwrap();
303 g.add_edges([(0u32, 1u32), (1, 2), (2, 3), (3, 0)]).unwrap();
304 let r = mycielskian(&g, 0).unwrap();
305 assert_eq!(r.vcount(), 4);
306 assert_eq!(r.ecount(), 4);
307 assert!(!r.is_directed());
308 assert_eq!(canonical_edges(&r), canonical_edges(&g));
309 }
310
311 #[test]
312 fn k_zero_is_identity_directed() {
313 let mut g = Graph::new(3, true).unwrap();
314 g.add_edges([(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
315 let r = mycielskian(&g, 0).unwrap();
316 assert_eq!(r.vcount(), 3);
317 assert_eq!(r.ecount(), 3);
318 assert!(r.is_directed());
319 }
320
321 #[test]
322 fn null_graph_promoted_to_singleton() {
323 let null = Graph::new(0, false).unwrap();
324 let r = mycielskian(&null, 1).unwrap();
325 assert_eq!(r.vcount(), 1);
326 assert_eq!(r.ecount(), 0);
327 }
328
329 #[test]
330 fn null_graph_two_iterations_is_p2() {
331 let null = Graph::new(0, false).unwrap();
333 let r = mycielskian(&null, 2).unwrap();
334 assert_eq!(r.vcount(), 2);
335 assert_eq!(r.ecount(), 1);
336 assert_eq!(canonical_edges(&r), [(0u32, 1u32)].into_iter().collect());
337 }
338
339 #[test]
340 fn singleton_promoted_to_p2() {
341 let single = Graph::new(1, false).unwrap();
342 let r = mycielskian(&single, 1).unwrap();
343 assert_eq!(r.vcount(), 2);
344 assert_eq!(r.ecount(), 1);
345 assert_eq!(canonical_edges(&r), [(0u32, 1u32)].into_iter().collect());
346 }
347
348 #[test]
349 fn p2_one_iteration_is_c5() {
350 let mut p2 = Graph::new(2, false).unwrap();
352 p2.add_edges([(0u32, 1u32)]).unwrap();
353 let r = mycielskian(&p2, 1).unwrap();
354 assert_eq!(r.vcount(), 5);
355 assert_eq!(r.ecount(), 5);
356 assert_simple_undirected(&r);
357 for v in 0..r.vcount() {
359 assert_eq!(degree(&r, v), 2, "C_5 vertex {v} degree");
360 }
361 }
362
363 #[test]
364 fn mycielski_graph_m0_null() {
365 let m = mycielski_graph(0).unwrap();
366 assert_eq!(m.vcount(), 0);
367 assert_eq!(m.ecount(), 0);
368 assert!(!m.is_directed());
369 }
370
371 #[test]
372 fn mycielski_graph_m1_singleton() {
373 let m = mycielski_graph(1).unwrap();
374 assert_eq!(m.vcount(), 1);
375 assert_eq!(m.ecount(), 0);
376 }
377
378 #[test]
379 fn mycielski_graph_m2_p2() {
380 let m = mycielski_graph(2).unwrap();
381 assert_eq!(m.vcount(), 2);
382 assert_eq!(m.ecount(), 1);
383 assert_eq!(canonical_edges(&m), [(0u32, 1u32)].into_iter().collect());
384 }
385
386 #[test]
387 fn mycielski_graph_m3_c5() {
388 let m = mycielski_graph(3).unwrap();
389 assert_eq!(m.vcount(), 5);
390 assert_eq!(m.ecount(), 5);
391 assert_simple_undirected(&m);
392 for v in 0..m.vcount() {
393 assert_eq!(degree(&m, v), 2);
394 }
395 }
396
397 #[test]
398 fn mycielski_graph_m4_grotzsch() {
399 let m = mycielski_graph(4).unwrap();
401 assert_eq!(m.vcount(), 11);
402 assert_eq!(m.ecount(), 20);
403 assert_simple_undirected(&m);
404 for eid in 0..u32::try_from(m.ecount()).unwrap() {
406 let (u, v) = m.edge(eid).unwrap();
407 let nu: BTreeSet<VertexId> = m.neighbors(u).unwrap().into_iter().collect();
408 let nv: BTreeSet<VertexId> = m.neighbors(v).unwrap().into_iter().collect();
409 let inter: BTreeSet<&VertexId> = nu.intersection(&nv).collect();
410 assert!(inter.is_empty(), "triangle through edge ({u},{v})");
411 }
412 }
413
414 #[test]
415 fn mycielski_graph_m5_counts() {
416 let m = mycielski_graph(5).unwrap();
425 assert_eq!(m.vcount(), 23);
426 assert_eq!(m.ecount(), 71);
427 assert_simple_undirected(&m);
428 }
429
430 #[test]
431 fn directed_iteration_preserves_directedness() {
432 let mut g = Graph::new(2, true).unwrap();
433 g.add_edges([(0u32, 1u32)]).unwrap();
434 let r = mycielskian(&g, 1).unwrap();
435 assert!(r.is_directed());
436 assert_eq!(r.vcount(), 5);
438 assert_eq!(r.ecount(), 5);
439 }
440
441 #[test]
442 fn recurrence_after_two_iterations_on_k4() {
443 let mut k4 = Graph::new(4, false).unwrap();
445 k4.add_edges([(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])
446 .unwrap();
447 let one = mycielskian(&k4, 1).unwrap();
448 assert_eq!(one.vcount(), 9);
449 assert_eq!(one.ecount(), 22);
450 let two = mycielskian(&k4, 2).unwrap();
451 assert_eq!(two.vcount(), 19);
452 assert_eq!(two.ecount(), 75);
453 }
454
455 #[test]
456 fn apex_vertex_degree_equals_prev_vcount() {
457 let mut g = Graph::new(4, false).unwrap();
460 g.add_edges([(0u32, 1u32), (1, 2), (2, 3)]).unwrap(); let r = mycielskian(&g, 1).unwrap();
462 let apex = r.vcount() - 1;
464 let nbrs: BTreeSet<VertexId> = r.neighbors(apex).unwrap().into_iter().collect();
465 assert_eq!(nbrs, (4u32..8).collect());
466 }
467
468 #[test]
469 fn shadow_vertex_mirrors_original_neighbours_plus_apex() {
470 let mut k3 = Graph::new(3, false).unwrap();
476 k3.add_edges([(0u32, 1u32), (1, 2), (0, 2)]).unwrap();
477 let r = mycielskian(&k3, 1).unwrap();
478 assert_eq!(r.vcount(), 7);
479 assert_eq!(r.ecount(), 12);
480 let nbrs_u0: BTreeSet<VertexId> = r.neighbors(3).unwrap().into_iter().collect();
483 assert_eq!(nbrs_u0, [1u32, 2, 6].into_iter().collect());
484 }
485
486 #[test]
487 fn negative_k_rejected_at_type_level() {
488 let null = Graph::new(0, false).unwrap();
492 let r = mycielskian(&null, 64);
493 assert!(matches!(r, Err(IgraphError::InvalidArgument(_))));
494 }
495
496 #[test]
497 fn empty_input_k_zero_is_null_graph() {
498 let null = Graph::new(0, false).unwrap();
499 let r = mycielskian(&null, 0).unwrap();
500 assert_eq!(r.vcount(), 0);
501 assert_eq!(r.ecount(), 0);
502 }
503}
504
505#[cfg(all(test, feature = "proptest-harness"))]
506mod proptest_tests {
507 use super::*;
508 use proptest::prelude::*;
509 use std::collections::BTreeSet;
510
511 fn arb_small_graph() -> impl Strategy<Value = Graph> {
513 (0u32..=8).prop_flat_map(|n| {
514 let pairs = if n <= 1 {
516 0
517 } else {
518 (n * (n - 1) / 2) as usize
519 };
520 prop::collection::vec(any::<bool>(), pairs).prop_map(move |flags| {
521 let mut g = Graph::new(n, false).unwrap();
522 if n >= 2 {
523 let mut pair_idx = 0usize;
524 let mut edges = Vec::new();
525 for i in 0..n {
526 for j in (i + 1)..n {
527 if flags[pair_idx] {
528 edges.push((i, j));
529 }
530 pair_idx += 1;
531 }
532 }
533 g.add_edges(edges).unwrap();
534 }
535 g
536 })
537 })
538 }
539
540 proptest! {
541 #[test]
542 fn k_zero_preserves_counts(g in arb_small_graph()) {
543 let r = mycielskian(&g, 0).unwrap();
544 prop_assert_eq!(r.vcount(), g.vcount());
545 prop_assert_eq!(r.ecount(), g.ecount());
546 prop_assert_eq!(r.is_directed(), g.is_directed());
547 }
548
549 #[test]
550 fn vcount_recurrence_holds(g in arb_small_graph(), k in 0u32..=4) {
551 let r = mycielskian(&g, k).unwrap();
552 let mut vc = g.vcount();
555 let mut ec = u32::try_from(g.ecount()).unwrap();
556 let mut k_left = k;
557 if vc == 0 && k_left > 0 { vc = 1; k_left -= 1; }
558 if vc == 1 && k_left > 0 { vc = 2; ec += 1; k_left -= 1; }
559 for _ in 0..k_left {
560 ec = ec.checked_mul(3).unwrap().checked_add(vc).unwrap();
561 vc = vc.checked_mul(2).unwrap().checked_add(1).unwrap();
562 }
563 prop_assert_eq!(r.vcount(), vc);
564 prop_assert_eq!(u32::try_from(r.ecount()).unwrap(), ec);
565 }
566
567 #[test]
568 fn no_edge_references_out_of_range(g in arb_small_graph(), k in 0u32..=3) {
569 let r = mycielskian(&g, k).unwrap();
570 for eid in 0..u32::try_from(r.ecount()).unwrap() {
571 let (u, v) = r.edge(eid).unwrap();
572 prop_assert!(u < r.vcount());
573 prop_assert!(v < r.vcount());
574 }
575 }
576
577 #[test]
578 fn mycielski_graph_recurrence(k in 0u32..=6) {
579 let m = mycielski_graph(k).unwrap();
580 let (vc, ec) = match k {
582 0 => (0u32, 0u32),
583 1 => (1, 0),
584 _ => {
585 let mut vc = 2u32;
586 let mut ec = 1u32;
587 for _ in 0..(k - 2) {
588 ec = ec.checked_mul(3).unwrap().checked_add(vc).unwrap();
589 vc = vc.checked_mul(2).unwrap().checked_add(1).unwrap();
590 }
591 (vc, ec)
592 }
593 };
594 prop_assert_eq!(m.vcount(), vc);
595 prop_assert_eq!(u32::try_from(m.ecount()).unwrap(), ec);
596 prop_assert!(!m.is_directed());
597 }
598
599 #[test]
600 fn shadow_of_simple_graph_is_simple(g in arb_small_graph(), k in 0u32..=3) {
601 let r = mycielskian(&g, k).unwrap();
605 let mut seen = BTreeSet::new();
606 for eid in 0..u32::try_from(r.ecount()).unwrap() {
607 let (u, v) = r.edge(eid).unwrap();
608 prop_assert_ne!(u, v);
609 let key = if u <= v { (u, v) } else { (v, u) };
610 prop_assert!(seen.insert(key));
611 }
612 }
613
614 #[test]
615 fn apex_vertex_is_universal_to_shadow_block(g in arb_small_graph(), k in 1u32..=3) {
616 let r = mycielskian(&g, k).unwrap();
617 let apex = r.vcount() - 1;
623 prop_assert!(r.neighbors(apex).unwrap().len() > 0 || g.vcount() == 0);
624 }
625 }
626}