1use crate::core::{Graph, IgraphResult};
11
12pub fn is_planar(graph: &Graph) -> IgraphResult<bool> {
43 let n = graph.vcount() as usize;
44
45 if n <= 4 {
46 return Ok(true);
47 }
48
49 let (adj, simple_edge_count) = build_simple_undirected_adj(graph);
51
52 if simple_edge_count > 3 * n - 6 {
54 return Ok(false);
55 }
56
57 let mut visited = vec![false; n];
59 for start in 0..n {
60 if visited[start] {
61 continue;
62 }
63 if !test_component_planar(&adj, start, &mut visited) {
64 return Ok(false);
65 }
66 }
67
68 Ok(true)
69}
70
71fn build_simple_undirected_adj(graph: &Graph) -> (Vec<Vec<usize>>, usize) {
74 let n = graph.vcount() as usize;
75 let ecount = graph.ecount();
76 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
77
78 for eid in 0..ecount {
79 #[allow(clippy::cast_possible_truncation)]
80 if let Ok((u, v)) = graph.edge(eid as u32) {
81 let u = u as usize;
82 let v = v as usize;
83 if u != v {
84 adj[u].push(v);
85 adj[v].push(u);
86 }
87 }
88 }
89
90 for list in &mut adj {
92 list.sort_unstable();
93 list.dedup();
94 }
95
96 let edge_count = adj.iter().map(Vec::len).sum::<usize>() / 2;
97 (adj, edge_count)
98}
99
100const NONE: u32 = u32::MAX;
102
103#[derive(Clone, Copy)]
107struct Interval {
108 low: u32,
109 high: u32,
110}
111
112impl Interval {
113 fn empty_interval() -> Self {
114 Self {
115 low: NONE,
116 high: NONE,
117 }
118 }
119
120 fn new(low: u32, high: u32) -> Self {
121 Self { low, high }
122 }
123
124 fn is_empty(self) -> bool {
125 self.low == NONE && self.high == NONE
126 }
127
128 fn conflicting(self, b: u32, lowpt: &[u32]) -> bool {
129 !self.is_empty() && lowpt[self.high as usize] > lowpt[b as usize]
130 }
131}
132
133#[derive(Clone, Copy)]
135struct ConflictPair {
136 left: Interval,
137 right: Interval,
138}
139
140impl ConflictPair {
141 fn new(left: Interval, right: Interval) -> Self {
142 Self { left, right }
143 }
144
145 fn swap(&mut self) {
146 std::mem::swap(&mut self.left, &mut self.right);
147 }
148
149 fn lowest(&self, lowpt: &[u32]) -> u32 {
150 if self.left.is_empty() {
151 return lowpt[self.right.low as usize];
152 }
153 if self.right.is_empty() {
154 return lowpt[self.left.low as usize];
155 }
156 std::cmp::min(
157 lowpt[self.left.low as usize],
158 lowpt[self.right.low as usize],
159 )
160 }
161}
162
163struct LrState {
165 oriented_edges: Vec<(usize, usize, bool)>,
166 out_edges: Vec<Vec<u32>>,
167 height: Vec<i32>,
168 parent_edge: Vec<u32>,
169 lowpt: Vec<u32>,
170 lowpt2: Vec<u32>,
171}
172
173#[allow(clippy::too_many_lines)]
175fn test_component_planar(adj: &[Vec<usize>], start: usize, visited: &mut [bool]) -> bool {
176 let n = adj.len();
177 let mut state = orient_and_compute_lowpoints(adj, start, visited, n);
178
179 let num_edges = state.oriented_edges.len();
180 let comp_size = state.oriented_edges.iter().filter(|e| e.2).count() + 1;
181 if comp_size <= 4 {
182 return true;
183 }
184 if num_edges > 3 * comp_size - 6 {
185 return false;
186 }
187
188 let ordered_adjs = compute_nesting_and_sort(&state, n);
190
191 lr_testing(&mut state, &ordered_adjs, n)
193}
194
195#[allow(clippy::too_many_lines)]
197fn orient_and_compute_lowpoints(
198 adj: &[Vec<usize>],
199 start: usize,
200 visited: &mut [bool],
201 n: usize,
202) -> LrState {
203 let mut oriented_edges: Vec<(usize, usize, bool)> = Vec::new();
204 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
205 let mut height: Vec<i32> = vec![-1; n];
206 let mut parent_edge: Vec<u32> = vec![NONE; n];
207
208 {
210 let mut oriented_set: std::collections::HashSet<(usize, usize)> =
211 std::collections::HashSet::new();
212 let mut stack: Vec<(usize, usize)> = Vec::new();
213 height[start] = 0;
214 visited[start] = true;
215 stack.push((start, 0));
216
217 while let Some(&mut (v, ref mut idx)) = stack.last_mut() {
218 if *idx < adj[v].len() {
219 let w = adj[v][*idx];
220 *idx += 1;
221 let key = if v < w { (v, w) } else { (w, v) };
222 if oriented_set.contains(&key) {
223 continue;
224 }
225 oriented_set.insert(key);
226 #[allow(clippy::cast_possible_truncation)]
227 let eidx = oriented_edges.len() as u32;
228 out_edges[v].push(eidx);
229 if height[w] == -1 {
230 oriented_edges.push((v, w, true));
231 parent_edge[w] = eidx;
232 height[w] = height[v] + 1;
233 visited[w] = true;
234 stack.push((w, 0));
235 } else {
236 oriented_edges.push((v, w, false));
237 }
238 } else {
239 stack.pop();
240 }
241 }
242 }
243
244 let num_edges = oriented_edges.len();
245
246 #[allow(clippy::cast_sign_loss)]
248 let mut lowpt: Vec<u32> = vec![0; num_edges];
249 let mut lowpt2: Vec<u32> = vec![0; num_edges];
250
251 for eidx in 0..num_edges {
252 let (source, target, is_tree) = oriented_edges[eidx];
253 #[allow(clippy::cast_sign_loss)]
254 if is_tree {
255 lowpt[eidx] = height[source] as u32;
256 lowpt2[eidx] = height[source] as u32;
257 } else {
258 lowpt[eidx] = height[target] as u32;
259 lowpt2[eidx] = height[source] as u32;
260 }
261 }
262
263 let comp_size = oriented_edges.iter().filter(|e| e.2).count() + 1;
265 let mut post_order: Vec<usize> = Vec::with_capacity(comp_size);
266 {
267 let mut stack: Vec<(usize, usize)> = vec![(start, 0)];
268 while let Some(&mut (v, ref mut idx)) = stack.last_mut() {
269 if *idx < out_edges[v].len() {
270 let eidx = out_edges[v][*idx] as usize;
271 *idx += 1;
272 let (_, w, is_tree) = oriented_edges[eidx];
273 if is_tree {
274 stack.push((w, 0));
275 }
276 } else {
277 post_order.push(v);
278 stack.pop();
279 }
280 }
281 }
282
283 for &v in &post_order {
285 let e = parent_edge[v];
286 if e == NONE {
287 continue;
288 }
289 let e_idx = e as usize;
290 for &child_eidx in &out_edges[v] {
291 let cidx = child_eidx as usize;
292 let clp = lowpt[cidx];
293 let clp2 = lowpt2[cidx];
294 match clp.cmp(&lowpt[e_idx]) {
295 std::cmp::Ordering::Less => {
296 lowpt2[e_idx] = std::cmp::min(lowpt[e_idx], clp2);
297 lowpt[e_idx] = clp;
298 }
299 std::cmp::Ordering::Greater => {
300 lowpt2[e_idx] = std::cmp::min(lowpt2[e_idx], clp);
301 }
302 std::cmp::Ordering::Equal => {
303 lowpt2[e_idx] = std::cmp::min(lowpt2[e_idx], clp2);
304 }
305 }
306 }
307 }
308
309 LrState {
310 oriented_edges,
311 out_edges,
312 height,
313 parent_edge,
314 lowpt,
315 lowpt2,
316 }
317}
318
319fn compute_nesting_and_sort(state: &LrState, n: usize) -> Vec<Vec<u32>> {
321 let num_edges = state.oriented_edges.len();
322 let mut nesting_depth: Vec<i32> = vec![0; num_edges];
323
324 #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
325 for (eidx, nd) in nesting_depth.iter_mut().enumerate().take(num_edges) {
326 let (source, _, _) = state.oriented_edges[eidx];
327 *nd = 2 * (state.lowpt[eidx] as i32);
328 if state.lowpt2[eidx] < state.height[source] as u32 {
329 *nd += 1;
330 }
331 }
332
333 let mut ordered_adjs: Vec<Vec<u32>> = vec![Vec::new(); n];
334 for (v, slot) in ordered_adjs.iter_mut().enumerate().take(n) {
335 if state.height[v] == -1 {
336 continue;
337 }
338 let mut edges: Vec<u32> = state.out_edges[v].clone();
339 edges.sort_by_key(|&e| nesting_depth[e as usize]);
340 *slot = edges;
341 }
342 ordered_adjs
343}
344
345fn lr_testing(state: &mut LrState, ordered_adjs: &[Vec<u32>], n: usize) -> bool {
347 let num_edges = state.oriented_edges.len();
348 let mut ref_edge: Vec<u32> = vec![NONE; num_edges];
349 let mut stack: Vec<ConflictPair> = Vec::new();
350 let mut stack_bottom: Vec<usize> = vec![0; num_edges];
351 let mut lowpt_edge: Vec<u32> = vec![NONE; num_edges];
352
353 let mut dfs_stack: Vec<usize> = Vec::new();
354 for v in 0..n {
356 if state.height[v] == 0 {
357 dfs_stack.push(v);
358 break;
359 }
360 }
361
362 let mut ind: Vec<usize> = vec![0; n];
363 let mut skip_init_edge: Vec<bool> = vec![false; num_edges];
364
365 while let Some(&v) = dfs_stack.last() {
366 let e = state.parent_edge[v];
367 let mut skip_final = false;
368
369 while ind[v] < ordered_adjs[v].len() {
370 let ei = ordered_adjs[v][ind[v]];
371 let ei_usize = ei as usize;
372
373 if !skip_init_edge[ei_usize] {
374 stack_bottom[ei_usize] = stack.len();
375 let (_, w, is_tree) = state.oriented_edges[ei_usize];
376
377 if is_tree {
378 skip_init_edge[ei_usize] = true;
379 dfs_stack.push(w);
380 skip_final = true;
381 break;
382 }
383 lowpt_edge[ei_usize] = ei;
384 stack.push(ConflictPair::new(
385 Interval::empty_interval(),
386 Interval::new(ei, ei),
387 ));
388 }
389
390 #[allow(clippy::cast_sign_loss)]
392 if state.lowpt[ei_usize] < state.height[v] as u32 {
393 if ind[v] == 0 {
394 if e != NONE {
395 lowpt_edge[e as usize] = lowpt_edge[ei_usize];
396 }
397 } else if e != NONE
398 && !add_constraints(
399 ei,
400 e,
401 &state.lowpt,
402 &mut ref_edge,
403 &mut stack,
404 &stack_bottom,
405 &lowpt_edge,
406 )
407 {
408 return false;
409 }
410 }
411
412 ind[v] += 1;
413 }
414
415 if !skip_final {
416 dfs_stack.pop();
417 if e != NONE {
418 remove_back_edges(
419 e,
420 &state.oriented_edges,
421 &state.height,
422 &state.lowpt,
423 &mut ref_edge,
424 &mut stack,
425 );
426 }
427 }
428 }
429
430 true
431}
432
433fn add_constraints(
436 ei: u32,
437 e: u32,
438 lowpt: &[u32],
439 ref_edge: &mut [u32],
440 stack: &mut Vec<ConflictPair>,
441 stack_bottom: &[usize],
442 lowpt_edge: &[u32],
443) -> bool {
444 let mut p = ConflictPair::new(Interval::empty_interval(), Interval::empty_interval());
445
446 loop {
448 let Some(mut q) = stack.pop() else {
449 return false;
450 };
451 if !q.left.is_empty() {
452 q.swap();
453 }
454 if !q.left.is_empty() {
455 return false;
456 }
457 if lowpt[q.right.low as usize] > lowpt[e as usize] {
458 if p.right.is_empty() {
459 p.right = q.right;
460 } else {
461 ref_edge[p.right.low as usize] = q.right.high;
462 p.right.low = q.right.low;
463 }
464 } else {
465 ref_edge[q.right.low as usize] = lowpt_edge[e as usize];
466 }
467 if stack.len() == stack_bottom[ei as usize] {
468 break;
469 }
470 }
471
472 while let Some(top) = stack.last() {
474 if !top.left.conflicting(ei, lowpt) && !top.right.conflicting(ei, lowpt) {
475 break;
476 }
477 let mut q = stack.pop().unwrap();
478 if q.right.conflicting(ei, lowpt) {
479 q.swap();
480 }
481 if q.right.conflicting(ei, lowpt) {
482 return false;
483 }
484 if p.right.low != NONE {
486 ref_edge[p.right.low as usize] = q.right.high;
487 }
488 if q.right.low != NONE {
489 p.right.low = q.right.low;
490 }
491 if p.right.is_empty() && q.right.low != NONE {
492 p.right = Interval::new(q.right.low, q.right.high);
493 }
494
495 if p.left.is_empty() {
497 p.left = q.left;
498 } else {
499 ref_edge[p.left.low as usize] = q.left.high;
500 p.left.low = q.left.low;
501 }
502 }
503
504 if !p.left.is_empty() || !p.right.is_empty() {
505 stack.push(p);
506 }
507 true
508}
509
510fn remove_back_edges(
512 e: u32,
513 oriented_edges: &[(usize, usize, bool)],
514 height: &[i32],
515 lowpt: &[u32],
516 ref_edge: &mut [u32],
517 stack: &mut Vec<ConflictPair>,
518) {
519 let u = oriented_edges[e as usize].0;
520 #[allow(clippy::cast_sign_loss)]
521 let h_u = height[u] as u32;
522
523 while let Some(top) = stack.last() {
525 if top.lowest(lowpt) != h_u {
526 break;
527 }
528 stack.pop();
529 }
530
531 if let Some(p) = stack.last_mut() {
532 while p.left.high != NONE {
534 let h_edge = p.left.high as usize;
535 let (_, target, _) = oriented_edges[h_edge];
536 if target != u {
537 break;
538 }
539 p.left.high = ref_edge[h_edge];
540 }
541 if p.left.high == NONE && p.left.low != NONE {
542 ref_edge[p.left.low as usize] = p.right.low;
543 p.left.low = NONE;
544 }
545
546 while p.right.high != NONE {
548 let h_edge = p.right.high as usize;
549 let (_, target, _) = oriented_edges[h_edge];
550 if target != u {
551 break;
552 }
553 p.right.high = ref_edge[h_edge];
554 }
555 if p.right.high == NONE && p.right.low != NONE {
556 ref_edge[p.right.low as usize] = p.left.low;
557 p.right.low = NONE;
558 }
559
560 if p.left.is_empty() && p.right.is_empty() {
562 stack.pop();
563 }
564 }
565
566 if lowpt[e as usize] < h_u {
568 if let Some(top) = stack.last() {
569 let hl = top.left.high;
570 let hr = top.right.high;
571 if hl != NONE && (hr == NONE || lowpt[hl as usize] > lowpt[hr as usize]) {
572 ref_edge[e as usize] = hl;
573 } else if hr != NONE {
574 ref_edge[e as usize] = hr;
575 }
576 }
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn empty_graph() {
586 let g = Graph::with_vertices(0);
587 assert!(is_planar(&g).unwrap());
588 }
589
590 #[test]
591 fn single_vertex() {
592 let g = Graph::with_vertices(1);
593 assert!(is_planar(&g).unwrap());
594 }
595
596 #[test]
597 fn single_edge() {
598 let mut g = Graph::with_vertices(2);
599 g.add_edge(0, 1).unwrap();
600 assert!(is_planar(&g).unwrap());
601 }
602
603 #[test]
604 fn triangle() {
605 let mut g = Graph::with_vertices(3);
606 g.add_edge(0, 1).unwrap();
607 g.add_edge(1, 2).unwrap();
608 g.add_edge(2, 0).unwrap();
609 assert!(is_planar(&g).unwrap());
610 }
611
612 #[test]
613 fn k4_planar() {
614 let mut g = Graph::with_vertices(4);
615 for i in 0..4u32 {
616 for j in (i + 1)..4 {
617 g.add_edge(i, j).unwrap();
618 }
619 }
620 assert!(is_planar(&g).unwrap());
621 }
622
623 #[test]
624 fn k5_not_planar() {
625 let mut g = Graph::with_vertices(5);
626 for i in 0..5u32 {
627 for j in (i + 1)..5 {
628 g.add_edge(i, j).unwrap();
629 }
630 }
631 assert!(!is_planar(&g).unwrap());
632 }
633
634 #[test]
635 fn k33_not_planar() {
636 let mut g = Graph::with_vertices(6);
637 for i in 0..3u32 {
638 for j in 3..6u32 {
639 g.add_edge(i, j).unwrap();
640 }
641 }
642 assert!(!is_planar(&g).unwrap());
643 }
644
645 #[test]
646 fn petersen_not_planar() {
647 let mut g = Graph::with_vertices(10);
648 for i in 0..5u32 {
650 g.add_edge(i, (i + 1) % 5).unwrap();
651 }
652 for i in 0..5u32 {
654 g.add_edge(5 + i, 5 + (i + 2) % 5).unwrap();
655 }
656 for i in 0..5u32 {
658 g.add_edge(i, 5 + i).unwrap();
659 }
660 assert!(!is_planar(&g).unwrap());
661 }
662
663 #[test]
664 fn tree_planar() {
665 let mut g = Graph::with_vertices(10);
666 for i in 1..10u32 {
667 g.add_edge(i, i / 2).unwrap();
668 }
669 assert!(is_planar(&g).unwrap());
670 }
671
672 #[test]
673 fn cycle_planar() {
674 let mut g = Graph::with_vertices(20);
675 for i in 0..20u32 {
676 g.add_edge(i, (i + 1) % 20).unwrap();
677 }
678 assert!(is_planar(&g).unwrap());
679 }
680
681 #[test]
682 fn grid_4x4_planar() {
683 let mut g = Graph::with_vertices(16);
684 for r in 0..4u32 {
685 for c in 0..4u32 {
686 let v = r * 4 + c;
687 if c + 1 < 4 {
688 g.add_edge(v, v + 1).unwrap();
689 }
690 if r + 1 < 4 {
691 g.add_edge(v, v + 4).unwrap();
692 }
693 }
694 }
695 assert!(is_planar(&g).unwrap());
696 }
697
698 #[test]
699 fn self_loops_ignored() {
700 let mut g = Graph::with_vertices(5);
701 for i in 0..5u32 {
702 for j in (i + 1)..5 {
703 g.add_edge(i, j).unwrap();
704 }
705 }
706 g.add_edge(0, 0).unwrap();
707 assert!(!is_planar(&g).unwrap());
708 }
709
710 #[test]
711 fn disconnected_planar() {
712 let mut g = Graph::with_vertices(6);
713 g.add_edge(0, 1).unwrap();
714 g.add_edge(1, 2).unwrap();
715 g.add_edge(2, 0).unwrap();
716 g.add_edge(3, 4).unwrap();
717 g.add_edge(4, 5).unwrap();
718 g.add_edge(5, 3).unwrap();
719 assert!(is_planar(&g).unwrap());
720 }
721
722 #[test]
723 fn disconnected_non_planar() {
724 let mut g = Graph::with_vertices(6);
725 for i in 0..5u32 {
726 for j in (i + 1)..5 {
727 g.add_edge(i, j).unwrap();
728 }
729 }
730 assert!(!is_planar(&g).unwrap());
731 }
732
733 #[test]
734 fn wheel_5_planar() {
735 let mut g = Graph::with_vertices(6);
736 for i in 1..6u32 {
737 g.add_edge(0, i).unwrap();
738 }
739 for i in 1..5u32 {
740 g.add_edge(i, i + 1).unwrap();
741 }
742 g.add_edge(5, 1).unwrap();
743 assert!(is_planar(&g).unwrap());
744 }
745
746 #[test]
747 fn k5_minus_edge_planar() {
748 let mut g = Graph::with_vertices(5);
749 for i in 0..5u32 {
750 for j in (i + 1)..5 {
751 if !(i == 0 && j == 4) {
752 g.add_edge(i, j).unwrap();
753 }
754 }
755 }
756 assert!(is_planar(&g).unwrap());
757 }
758
759 #[test]
760 fn k33_minus_edge_planar() {
761 let mut g = Graph::with_vertices(6);
762 for i in 0..3u32 {
763 for j in 3..6u32 {
764 if !(i == 0 && j == 3) {
765 g.add_edge(i, j).unwrap();
766 }
767 }
768 }
769 assert!(is_planar(&g).unwrap());
770 }
771
772 #[test]
773 fn icosahedron_planar() {
774 let mut g = Graph::with_vertices(12);
775 let edges: &[(u32, u32)] = &[
776 (0, 1),
777 (0, 2),
778 (0, 3),
779 (0, 4),
780 (0, 5),
781 (1, 2),
782 (2, 3),
783 (3, 4),
784 (4, 5),
785 (5, 1),
786 (1, 6),
787 (2, 6),
788 (2, 7),
789 (3, 7),
790 (3, 8),
791 (4, 8),
792 (4, 9),
793 (5, 9),
794 (5, 10),
795 (1, 10),
796 (6, 7),
797 (7, 8),
798 (8, 9),
799 (9, 10),
800 (10, 6),
801 (6, 11),
802 (7, 11),
803 (8, 11),
804 (9, 11),
805 (10, 11),
806 ];
807 for &(u, v) in edges {
808 g.add_edge(u, v).unwrap();
809 }
810 assert!(is_planar(&g).unwrap());
811 }
812}