Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
Planarity_Test.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
87# ifndef PLANARITY_TEST_H
88# define PLANARITY_TEST_H
89
90# include <algorithm>
91# include <cmath>
92# include <cstddef>
93# include <limits>
94# include <sstream>
95# include <string>
96# include <utility>
97
98# include <ah-errors.H>
99# include <tpl_array.H>
100# include <tpl_dynMapTree.H>
101# include <tpl_dynSetTree.H>
102# include <tpl_graph.H>
103
104namespace Aleph
105{
117
118
123 inline const char * to_string(const Planarity_Certificate_Type type) noexcept
124 {
125 switch (type)
126 {
128 return "None";
130 return "K5_Subdivision";
132 return "K33_Subdivision";
134 return "Minimal_NonPlanar_Obstruction";
135 }
136
137 return "Unknown";
138 }
139
140
146 {
151 bool compute_embedding = false;
152
157
164
167
170
179
182
185
187 size_t certificate_max_reduction_passes = std::numeric_limits<size_t>::max();
188 };
189
190
196 template <class GT>
252
253
258 template <class GT>
260 {
261 using Node = typename GT::Node;
262
263 size_t face_a = 0;
264 size_t face_b = 0;
265 Node * primal_src = nullptr;
266 Node * primal_tgt = nullptr;
267 };
268
269
277 template <class GT>
304
305
311 {
315 size_t preferred_outer_face = std::numeric_limits<size_t>::max();
316
319
322
324 double relaxation_tolerance = 1e-10;
325
327 double outer_face_radius = 1.0;
328
330 double component_spacing = 3.0;
331
334 };
335
336
341 template <class GT>
343 {
344 using Node = typename GT::Node;
345
347 {
348 Node * node = nullptr;
349 double x = 0;
350 double y = 0;
351 };
352
353 bool has_embedding = false;
354 bool drawing_available = false;
357
358 size_t chosen_outer_face = std::numeric_limits<size_t>::max();
360 size_t crossing_count = 0;
361 size_t num_components = 0;
362
364 };
365
366
372 {
374 bool pretty_json = true;
375
378
381
384 };
385
386
391 template <class GT>
412
413
420 template <class GT>
422 {
423 std::string operator()(typename GT::Node * node) const
424 {
425 std::ostringstream out;
426 out << node;
427 return out.str();
428 }
429 };
430
431
432 template <class GT>
435
436
437 namespace planarity_detail
438 {
439 static constexpr size_t Null_Edge = std::numeric_limits<size_t>::max();
440
441 struct Edge_Key
442 {
443 size_t u = 0;
444 size_t v = 0;
445
446 bool operator<(const Edge_Key & other) const noexcept
447 {
448 if (u < other.u)
449 return true;
450 if (other.u < u)
451 return false;
452 return v < other.v;
453 }
454 };
455
456
458 {
459 size_t u = 0;
460 size_t v = 0;
461 };
462
463
464 struct Interval
465 {
466 size_t low = Null_Edge;
467 size_t high = Null_Edge;
468
469 bool operator==(const Interval & other) const noexcept = default;
470 };
471
472
474 {
477
478 bool operator==(const Conflict_Pair & other) const noexcept = default;
479 };
480
481
482 inline bool interval_empty(const Interval & i) noexcept
483 {
484 return i.low == Null_Edge;
485 }
486
487
488 inline bool pair_empty(const Conflict_Pair & p) noexcept
489 {
490 return interval_empty(p.left) and interval_empty(p.right);
491 }
492
493
494 struct Dart_Key
495 {
496 size_t u = 0;
497 size_t v = 0;
498
499 bool operator<(const Dart_Key & other) const noexcept
500 {
501 if (u < other.u)
502 return true;
503 if (other.u < u)
504 return false;
505 return v < other.v;
506 }
507 };
508
509
511 {
512 size_t u = Null_Edge;
513 size_t v = Null_Edge;
515 };
516
517
518 inline std::string json_escape_string(const std::string & s)
519 {
520 std::ostringstream out;
521 for (size_t i = 0; i < s.size(); ++i)
522 {
523 const unsigned char c = static_cast<unsigned char>(s[i]);
524 switch (c)
525 {
526 case '\"':
527 out << "\\\"";
528 break;
529 case '\\':
530 out << "\\\\";
531 break;
532 case '\b':
533 out << "\\b";
534 break;
535 case '\f':
536 out << "\\f";
537 break;
538 case '\n':
539 out << "\\n";
540 break;
541 case '\r':
542 out << "\\r";
543 break;
544 case '\t':
545 out << "\\t";
546 break;
547 default:
548 if (c < 0x20)
549 {
550 out << "\\u00";
551 const char * hex = "0123456789abcdef";
552 out << hex[(c >> 4) & 0xF] << hex[c & 0xF];
553 }
554 else
555 {
557 }
558 break;
559 }
560 }
561
562 return out.str();
563 }
564
565
566 inline std::string dot_escape_string(const std::string & s)
567 {
568 std::ostringstream out;
569 for (size_t i = 0; i < s.size(); ++i)
570 {
571 const char c = s[i];
572 if (c == '\"' or c == '\\')
573 out << '\\' << c;
574 else if (c == '\n')
575 out << "\\n";
576 else
577 out << c;
578 }
579 return out.str();
580 }
581
582
583 inline std::string xml_escape_string(const std::string & s)
584 {
585 std::ostringstream out;
586 for (size_t i = 0; i < s.size(); ++i)
587 {
588 const unsigned char c = static_cast<unsigned char>(s[i]);
589 switch (c)
590 {
591 case '&':
592 out << "&amp;";
593 break;
594 case '<':
595 out << "&lt;";
596 break;
597 case '>':
598 out << "&gt;";
599 break;
600 case '\"':
601 out << "&quot;";
602 break;
603 case '\'':
604 out << "&apos;";
605 break;
606 default:
607 if (c < 0x20 and c != '\n' and c != '\r' and c != '\t')
608 {
609 const char * hex = "0123456789ABCDEF";
610 out << "&#x";
611 out << hex[(c >> 4) & 0xF] << hex[c & 0xF] << ";";
612 }
613 else
614 {
616 }
617 break;
618 }
619 }
620 return out.str();
621 }
622
623
624 template <class PtrT>
625 std::string pointer_to_string(const PtrT ptr)
626 {
627 std::ostringstream out;
628 out << ptr;
629 return out.str();
630 }
631
632
633 template <class GT>
635 const Planarity_Test_Result<GT> & result,
638 {
639 using Node = typename GT::Node;
640
641 nodes.empty();
642 node_to_id.empty();
643
644 auto add_node = [&](Node * node)
645 {
646 if (node == nullptr or node_to_id.contains(node))
647 return;
648 const size_t id = nodes.size();
649 node_to_id.insert(node, id);
650 nodes.append(node);
651 };
652
653 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes);
654 it.has_curr(); it.next_ne())
655 add_node(it.get_curr_ne());
656
657 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator
658 it(result.certificate_obstruction_edges); it.has_curr(); it.next_ne())
659 {
660 add_node(it.get_curr_ne().src);
661 add_node(it.get_curr_ne().tgt);
662 }
663
664 for (typename Array<typename Planarity_Test_Result<GT>::Path_Witness>::Iterator
665 pit(result.certificate_paths); pit.has_curr(); pit.next_ne())
666 {
667 const auto & path = pit.get_curr_ne();
668 for (typename Array<Node *>::Iterator nit(path.nodes); nit.has_curr(); nit.next_ne())
669 add_node(nit.get_curr_ne());
670
671 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator
672 eit(path.edges); eit.has_curr(); eit.next_ne())
673 {
674 add_node(eit.get_curr_ne().src);
675 add_node(eit.get_curr_ne().tgt);
676 }
677 }
678 }
679
680
681 inline double orient2d(const double ax, const double ay,
682 const double bx, const double by,
683 const double cx, const double cy) noexcept
684 {
685 return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
686 }
687
688
689 inline bool bounding_boxes_intersect(const double ax, const double ay,
690 const double bx, const double by,
691 const double cx, const double cy,
692 const double dx, const double dy) noexcept
693 {
694 const double min_ab_x = std::min(ax, bx);
695 const double max_ab_x = std::max(ax, bx);
696 const double min_ab_y = std::min(ay, by);
697 const double max_ab_y = std::max(ay, by);
698
699 const double min_cd_x = std::min(cx, dx);
700 const double max_cd_x = std::max(cx, dx);
701 const double min_cd_y = std::min(cy, dy);
702 const double max_cd_y = std::max(cy, dy);
703
706 }
707
708
709 inline bool segments_properly_intersect(const double ax, const double ay,
710 const double bx, const double by,
711 const double cx, const double cy,
712 const double dx, const double dy) noexcept
713 {
714 if (not bounding_boxes_intersect(ax, ay, bx, by, cx, cy, dx, dy))
715 return false;
716
717 constexpr double eps = 1e-12;
718 const double o1 = orient2d(ax, ay, bx, by, cx, cy);
719 const double o2 = orient2d(ax, ay, bx, by, dx, dy);
720 const double o3 = orient2d(cx, cy, dx, dy, ax, ay);
721 const double o4 = orient2d(cx, cy, dx, dy, bx, by);
722
723 // Strict intersection only (shared endpoints handled outside).
724 return (o1 * o2 < -eps) and (o3 * o4 < -eps);
725 }
726
727
728 template <class GT, class SA>
730 {
731 using Node = typename GT::Node;
732 using Arc = typename GT::Arc;
733
734 const GT & g_;
737
739
742
746
750
753
764
766
767 bool planar_ = true;
768
769 size_t add_oriented_edge(const size_t src, const size_t tgt)
770 {
771 const size_t id = oriented_src_.size();
774
775 side_.append(1);
776 lowpt_.append(0);
777 lowpt2_.append(0);
782
783 return id;
784 }
785
786 size_t other_endpoint(const size_t edge_id, const size_t x) const
787 {
788 const Simple_Edge & e = edges_[edge_id];
789 return e.u == x ? e.v : e.u;
790 }
791
793 {
794 for (size_t i = 1; i < a.size(); ++i)
795 {
796 const size_t key = a[i];
797 size_t j = i;
798 while (j > 0 and key < a[j - 1])
799 {
800 a[j] = a[j - 1];
801 --j;
802 }
803 a[j] = key;
804 }
805 }
806
807 static size_t factorial_bounded(const size_t n, const size_t cap)
808 {
809 if (n <= 1)
810 return 1;
811
812 if (cap == 0)
813 return 1;
814
815 size_t ret = 1;
816 for (size_t i = 2; i <= n; ++i)
817 {
818 if (ret > cap / i)
819 return cap + 1;
820 ret *= i;
821 }
822
823 return ret;
824 }
825
826 size_t count_components() const
827 {
828 if (result_.simplified_num_nodes == 0)
829 return 0;
830
831 Array<char> vis(result_.simplified_num_nodes, 0);
832 size_t comps = 0;
833
834 for (size_t s = 0; s < result_.simplified_num_nodes; ++s)
835 {
836 if (vis[s])
837 continue;
838
839 ++comps;
840 vis[s] = 1;
841
842 Array<size_t> stack;
843 stack.append(s);
844
845 while (not stack.is_empty())
846 {
847 const size_t v = stack.remove_last();
848
849 for (typename Array<size_t>::Iterator it(incident_edges_[v]);
850 it.has_curr(); it.next_ne())
851 {
852 const size_t eid = it.get_curr_ne();
853 const size_t w = other_endpoint(eid, v);
854 if (vis[w])
855 continue;
856
857 vis[w] = 1;
858 stack.append(w);
859 }
860 }
861 }
862
863 return comps;
864 }
865
866 static size_t find_in_array(const Array<size_t> & a, const size_t value)
867 {
868 for (size_t i = 0; i < a.size(); ++i)
869 if (a[i] == value)
870 return i;
871
872 return Null_Edge;
873 }
874
876 const size_t pos,
877 const size_t first,
878 Array<Array<size_t>> & out) const
879 {
880 if (pos >= tail.size())
881 {
882 Array<size_t> order;
883 order.reserve(tail.size() + 1);
884 order.append(first);
885 for (size_t i = 0; i < tail.size(); ++i)
886 order.append(tail[i]);
887 out.append(std::move(order));
888 return;
889 }
890
891 for (size_t i = pos; i < tail.size(); ++i)
892 {
893 std::swap(tail[pos], tail[i]);
894 generate_permutations(tail, pos + 1, first, out);
895 std::swap(tail[pos], tail[i]);
896 }
897 }
898
900 const size_t isolated_vertices,
901 const size_t num_components,
903 size_t & global_faces,
904 const bool enforce_euler = true) const
905 {
907 Array<size_t> alpha;
908 dart_src.reserve(2 * result_.simplified_num_edges);
909 alpha.reserve(2 * result_.simplified_num_edges);
910
912
913 for (typename Array<Simple_Edge>::Iterator it(edges_); it.has_curr(); it.next_ne())
914 {
915 const Simple_Edge & e = it.get_curr_ne();
916 const size_t d1 = dart_src.size();
917
918 dart_src.append(e.u);
919 alpha.append(d1 + 1);
920 dart_id.insert(Dart_Key{e.u, e.v}, d1);
921
922 dart_src.append(e.v);
923 alpha.append(d1);
924 dart_id.insert(Dart_Key{e.v, e.u}, d1 + 1);
925 }
926
927 Array<size_t> sigma(dart_src.size(), Null_Edge);
928 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
929 {
930 const auto & ord = order[v];
931 if (ord.is_empty())
932 continue;
933
934 for (size_t i = 0; i < ord.size(); ++i)
935 {
936 const size_t to = ord[i];
937 const size_t next = ord[(i + 1) % ord.size()];
938
939 const size_t a = dart_id.find(Dart_Key{v, to});
940 const size_t b = dart_id.find(Dart_Key{v, next});
941 sigma[a] = b;
942 }
943 }
944
945 for (size_t d = 0; d < sigma.size(); ++d)
946 if (sigma[d] == Null_Edge)
947 return false;
948
949 face_idx.empty();
950
951 Array<char> vis(dart_src.size(), 0);
952 size_t local_faces = 0;
953 for (size_t d = 0; d < dart_src.size(); ++d)
954 {
955 if (vis[d])
956 continue;
957
958 ++local_faces;
960
961 size_t x = d;
962 while (not vis[x])
963 {
964 vis[x] = 1;
965 f.append(dart_src[x]);
966 x = sigma[alpha[x]];
967 }
968
969 face_idx.append(std::move(f));
970 }
971
973 - (num_components == 0 ? 0 : (num_components - 1));
974
976 return true;
977
978 const long long lhs = static_cast<long long>(result_.simplified_num_nodes)
979 - static_cast<long long>(result_.simplified_num_edges)
980 + static_cast<long long>(global_faces);
981
982 const long long rhs = static_cast<long long>(num_components) + 1;
983
984 return lhs == rhs;
985 }
986
989 const size_t global_faces,
990 const bool is_lr_linear)
991 {
992 result_.has_combinatorial_embedding = true;
993 result_.embedding_is_lr_linear = is_lr_linear;
994 result_.embedding_num_faces = global_faces;
995
996 result_.embedding_rotation.empty();
997 result_.embedding_rotation.reserve(result_.simplified_num_nodes);
998 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
999 {
1001 re.node = nodes_[v];
1002
1003 const auto & ord = order[v];
1004 re.cw_neighbors.reserve(ord.size());
1005 for (typename Array<size_t>::Iterator it(ord); it.has_curr(); it.next_ne())
1006 re.cw_neighbors.append(nodes_[it.get_curr_ne()]);
1007
1008 result_.embedding_rotation.append(std::move(re));
1009 }
1010
1011 result_.embedding_faces.empty();
1012 result_.embedding_faces.reserve(face_idx.size());
1013 for (typename Array<Array<size_t>>::Iterator fit(face_idx); fit.has_curr(); fit.next_ne())
1014 {
1015 const auto & f = fit.get_curr_ne();
1017 mapped.reserve(f.size());
1018 for (typename Array<size_t>::Iterator it(f); it.has_curr(); it.next_ne())
1019 mapped.append(nodes_[it.get_curr_ne()]);
1020 result_.embedding_faces.append(std::move(mapped));
1021 }
1022
1023 while (result_.embedding_faces.size() < result_.embedding_num_faces)
1024 result_.embedding_faces.append(Array<Node *>());
1025 }
1026
1027 bool conflicting(const Interval & i, const size_t edge_id) const
1028 {
1029 if (interval_empty(i) or i.high == Null_Edge)
1030 return false;
1031 return lowpt_[i.high] > lowpt_[edge_id];
1032 }
1033
1034 long lowest(const Conflict_Pair & p) const
1035 {
1036 const bool left_empty = interval_empty(p.left);
1037 const bool right_empty = interval_empty(p.right);
1038
1040 return std::numeric_limits<long>::max();
1041 if (left_empty)
1042 return lowpt_[p.right.low];
1043 if (right_empty)
1044 return lowpt_[p.left.low];
1045
1046 return std::min(lowpt_[p.left.low], lowpt_[p.right.low]);
1047 }
1048
1049 void orient_dfs(const size_t v)
1050 {
1051 if (not planar_)
1052 return;
1053
1054 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
1055 {
1056 const size_t uedge = it.get_curr_ne();
1058 continue;
1059
1061 const size_t w = other_endpoint(uedge, v);
1062
1063 const size_t e = add_oriented_edge(v, w);
1065 child_edges_[v].append(e);
1066
1067 lowpt_[e] = height_[v];
1068 lowpt2_[e] = height_[v];
1069
1070 if (height_[w] == -1)
1071 {
1072 parent_edge_[w] = e;
1073 height_[w] = height_[v] + 1;
1074 orient_dfs(w);
1075 }
1076 else
1077 {
1078 lowpt_[e] = height_[w];
1079 }
1080
1081 nesting_depth_[e] = 2 * lowpt_[e]
1082 + (lowpt2_[e] < height_[v] ? 1 : 0);
1083
1084 if (parent_edge_[v] == Null_Edge)
1085 continue;
1086
1087 const size_t pe = parent_edge_[v];
1088 if (lowpt_[e] < lowpt_[pe])
1089 {
1090 lowpt2_[pe] = std::min(lowpt_[pe], lowpt2_[e]);
1091 lowpt_[pe] = lowpt_[e];
1092 }
1093 else if (lowpt_[e] > lowpt_[pe])
1094 {
1095 lowpt2_[pe] = std::min(lowpt2_[pe], lowpt_[e]);
1096 }
1097 else
1098 {
1099 lowpt2_[pe] = std::min(lowpt2_[pe], lowpt2_[e]);
1100 }
1101 }
1102 }
1103
1104 void test_dfs(const size_t v)
1105 {
1106 if (not planar_)
1107 return;
1108
1109 const size_t e = parent_edge_[v];
1110
1111 auto & E = child_edges_[v];
1112 for (size_t i = 1; i < E.size(); ++i)
1113 {
1114 const size_t key = E[i];
1115 size_t j = i;
1116 while (j > 0 and nesting_depth_[key] < nesting_depth_[E[j - 1]])
1117 {
1118 E[j] = E[j - 1];
1119 --j;
1120 }
1121 E[j] = key;
1122 }
1123
1124 for (typename Array<size_t>::Iterator it(E); it.has_curr(); it.next_ne())
1125 {
1126 const size_t ei = it.get_curr_ne();
1128
1129 const size_t w = oriented_tgt_[ei];
1130 if (ei == parent_edge_[w])
1131 {
1132 test_dfs(w);
1133 }
1134 else
1135 {
1136 lowpt_edge_[ei] = ei;
1138 cp.right.low = ei;
1139 cp.right.high = ei;
1140 stack_.append(cp);
1141 }
1142
1143 if (not planar_)
1144 return;
1145
1146 if (lowpt_[ei] < height_[v])
1147 {
1148 const size_t first = E[0];
1149 if (ei == first)
1150 {
1151 if (e != Null_Edge)
1152 lowpt_edge_[e] = lowpt_edge_[first];
1153 }
1154 else
1155 {
1156 Conflict_Pair p;
1157 do
1158 {
1159 Conflict_Pair q = stack_.get_last();
1160 (void) stack_.remove_last();
1161
1162 if (not interval_empty(q.left))
1163 std::swap(q.left, q.right);
1164
1165 if (not interval_empty(q.left))
1166 {
1167 planar_ = false;
1168 return;
1169 }
1170
1171 if (lowpt_[q.right.low] > lowpt_[e])
1172 {
1173 if (interval_empty(p.right))
1174 p.right.high = q.right.high;
1175 else
1176 ref_[p.right.low] = q.right.high;
1177
1178 p.right.low = q.right.low;
1179 }
1180 else
1181 {
1182 ref_[q.right.low] = lowpt_edge_[e];
1183 }
1184 }
1185 while (not (stack_.get_last() == stack_bottom_[ei]));
1186
1187 while (conflicting(stack_.get_last().left, ei)
1188 or conflicting(stack_.get_last().right, ei))
1189 {
1190 Conflict_Pair q = stack_.get_last();
1191 (void) stack_.remove_last();
1192
1193 if (conflicting(q.right, ei))
1194 std::swap(q.left, q.right);
1195
1196 if (conflicting(q.right, ei))
1197 {
1198 planar_ = false;
1199 return;
1200 }
1201
1202 if (p.right.low != Null_Edge)
1203 ref_[p.right.low] = q.right.high;
1204
1205 if (not interval_empty(q.right))
1206 p.right.low = q.right.low;
1207
1208 if (q.left.low != Null_Edge)
1209 side_[q.left.low] = -1;
1210
1211 if (interval_empty(p.left))
1212 p.left.high = q.left.high;
1213 else
1214 ref_[p.left.low] = q.left.high;
1215
1216 p.left.low = q.left.low;
1217 }
1218
1219 if (not pair_empty(p))
1220 stack_.append(p);
1221 }
1222 }
1223 }
1224
1225 if (e == Null_Edge)
1226 return;
1227
1228 const size_t u = oriented_src_[e];
1229 while (stack_.size() > 1 and lowest(stack_.get_last()) == height_[u])
1230 {
1231 Conflict_Pair p = stack_.remove_last();
1232 if (not interval_empty(p.left))
1233 side_[p.left.low] = -1;
1234 }
1235
1236 if (stack_.size() > 1)
1237 {
1238 Conflict_Pair p = stack_.remove_last();
1239
1240 while (p.left.high != Null_Edge and oriented_tgt_[p.left.high] == u)
1241 p.left.high = ref_[p.left.high];
1242
1243 if (p.left.high == Null_Edge and p.left.low != Null_Edge)
1244 {
1245 ref_[p.left.low] = p.right.low;
1246 side_[p.left.low] = -1;
1247 p.left.low = Null_Edge;
1248 }
1249
1250 while (p.right.high != Null_Edge and oriented_tgt_[p.right.high] == u)
1251 p.right.high = ref_[p.right.high];
1252
1253 if (p.right.high == Null_Edge and p.right.low != Null_Edge)
1254 {
1255 ref_[p.right.low] = p.left.low;
1256 p.right.low = Null_Edge;
1257 }
1258
1259 stack_.append(p);
1260 }
1261
1262 if (lowpt_[e] < height_[u])
1263 {
1264 const Conflict_Pair & top = stack_.get_last();
1265 const size_t h_left = top.left.high;
1266 const size_t h_right = top.right.high;
1267
1268 if (h_left != Null_Edge
1270 ref_[e] = h_left;
1271 else
1272 ref_[e] = h_right;
1273 }
1274 }
1275
1277 {
1278 if (result_.simplified_num_edges == 0)
1279 return false;
1280
1281 Array<long> comp_id(result_.simplified_num_nodes, -1);
1282 size_t next_comp = 0;
1283
1284 for (size_t s = 0; s < result_.simplified_num_nodes; ++s)
1285 {
1286 if (comp_id[s] != -1)
1287 continue;
1288
1289 Array<size_t> stack;
1290 stack.append(s);
1291 comp_id[s] = static_cast<long>(next_comp);
1292
1293 size_t num_vertices = 0;
1294 size_t degree_sum = 0;
1295
1296 while (not stack.is_empty())
1297 {
1298 const size_t v = stack.remove_last();
1299 ++num_vertices;
1300 degree_sum += incident_edges_[v].size();
1301
1302 for (typename Array<size_t>::Iterator it(incident_edges_[v]);
1303 it.has_curr(); it.next_ne())
1304 {
1305 const size_t uedge = it.get_curr_ne();
1306 const size_t w = other_endpoint(uedge, v);
1307 if (comp_id[w] != -1)
1308 continue;
1309
1310 comp_id[w] = static_cast<long>(next_comp);
1311 stack.append(w);
1312 }
1313 }
1314
1315 const size_t num_edges = degree_sum / 2;
1316 if (num_vertices >= 3 and num_edges > 3 * num_vertices - 6)
1317 return true;
1318
1319 ++next_comp;
1320 }
1321
1322 return false;
1323 }
1324
1326 {
1327 result_.num_nodes = g_.get_num_nodes();
1328 result_.input_is_digraph = g_.is_digraph();
1329
1330 nodes_.reserve(result_.num_nodes);
1331 for (Node_Iterator<GT> it(g_); it.has_curr(); it.next_ne())
1332 {
1333 Node * node = it.get_curr_ne();
1334 node_to_idx_.insert(node, nodes_.size());
1335 nodes_.append(node);
1336 }
1337
1338 incident_edges_.reserve(result_.num_nodes);
1339 child_edges_.reserve(result_.num_nodes);
1340 for (size_t i = 0; i < result_.num_nodes; ++i)
1341 {
1343 child_edges_.append(Array<size_t>());
1344 }
1345
1348
1349 for (Arc_Iterator<GT, SA> it(g_, sa_); it.has_curr(); it.next_ne())
1350 {
1351 ++result_.num_input_arcs;
1352 Arc * arc = it.get_curr_ne();
1353 const size_t src = node_to_idx_.find(g_.get_src_node(arc));
1354 const size_t tgt = node_to_idx_.find(g_.get_tgt_node(arc));
1355
1356 if (src == tgt)
1357 {
1358 ++result_.ignored_loops;
1359 continue;
1360 }
1361
1362 const size_t u = std::min(src, tgt);
1363 const size_t v = std::max(src, tgt);
1364 const Edge_Key key{u, v};
1365
1366 if (seen_edges.contains(key))
1367 {
1368 ++result_.ignored_parallel_arcs;
1369 const size_t eid = edge_to_idx.find(key);
1370 simplified_edge_input_arcs_[eid].append(arc);
1371 continue;
1372 }
1373
1374 seen_edges.insert(key);
1375 const size_t eid = edges_.size();
1376 edge_to_idx.insert(key, eid);
1377
1378 edges_.append(Simple_Edge{u, v});
1379 incident_edges_[u].append(eid);
1380 incident_edges_[v].append(eid);
1382 simplified_edge_input_arcs_[eid].append(arc);
1383 }
1384
1385 result_.simplified_num_nodes = result_.num_nodes;
1386 result_.simplified_num_edges = edges_.size();
1387 }
1388
1390 {
1391 if (result_.simplified_num_edges == 0)
1392 return true;
1393
1394 height_ = Array<long>(result_.simplified_num_nodes, -1);
1395 parent_edge_ = Array<size_t>(result_.simplified_num_nodes, Null_Edge);
1396 undirected_edge_seen_ = Array<char>(result_.simplified_num_edges, 0);
1397 undirected_to_oriented_ = Array<size_t>(result_.simplified_num_edges, Null_Edge);
1398
1399 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
1400 {
1401 if (height_[v] != -1)
1402 continue;
1403
1404 roots_.append(v);
1405 height_[v] = 0;
1406 orient_dfs(v);
1407 }
1408
1409 for (typename Array<size_t>::Iterator it(roots_); it.has_curr(); it.next_ne())
1410 {
1411 const size_t root = it.get_curr_ne();
1412 stack_.empty();
1413 stack_.append(Conflict_Pair());
1414 test_dfs(root);
1415 if (not planar_)
1416 return false;
1417 }
1418
1419 return true;
1420 }
1421
1423 {
1425
1426 Tmp_Graph tg;
1428 static_cast<typename Tmp_Graph::Node *>(nullptr));
1429
1430 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
1431 tmp_nodes[i] = tg.insert_node(i);
1432
1433 for (typename Array<Simple_Edge>::Iterator it(simple_edges); it.has_curr(); it.next_ne())
1434 {
1435 const Simple_Edge & e = it.get_curr_ne();
1436 tg.insert_arc(tmp_nodes[e.u], tmp_nodes[e.v], 1);
1437 }
1438
1441 fast_options.compute_nonplanar_certificate = false;
1442 fast_options.embedding_max_combinations = 0;
1443 fast_options.certificate_max_edges = 0;
1444 fast_options.certificate_max_reduction_passes = 0;
1445
1448
1449 return checker.run().is_planar;
1450 }
1451
1453 const Array<Compressed_Path> & paths) const
1454 {
1455 if (branches.size() != 5 or paths.size() != 10)
1456 return false;
1457
1458 Array<Array<char>> mat;
1459 mat.reserve(5);
1460 for (size_t i = 0; i < 5; ++i)
1461 mat.append(Array<char>(static_cast<size_t>(5), static_cast<char>(0)));
1462
1463 Array<size_t> deg(static_cast<size_t>(5), static_cast<size_t>(0));
1464
1465 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1466 {
1467 const Compressed_Path & p = it.get_curr_ne();
1468 const size_t iu = find_in_array(branches, p.u);
1469 const size_t iv = find_in_array(branches, p.v);
1470 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1471 return false;
1472
1473 if (mat[iu][iv])
1474 return false;
1475
1476 mat[iu][iv] = 1;
1477 mat[iv][iu] = 1;
1478 ++deg[iu];
1479 ++deg[iv];
1480 }
1481
1482 for (size_t i = 0; i < 5; ++i)
1483 {
1484 if (deg[i] != 4)
1485 return false;
1486
1487 for (size_t j = i + 1; j < 5; ++j)
1488 if (not mat[i][j])
1489 return false;
1490 }
1491
1492 return true;
1493 }
1494
1496 const Array<Compressed_Path> & paths) const
1497 {
1498 if (branches.size() != 6 or paths.size() != 9)
1499 return false;
1500
1501 Array<Array<char>> mat;
1502 mat.reserve(6);
1503 for (size_t i = 0; i < 6; ++i)
1504 mat.append(Array<char>(static_cast<size_t>(6), static_cast<char>(0)));
1505
1507 adj.reserve(6);
1508 for (size_t i = 0; i < 6; ++i)
1509 adj.append(Array<size_t>());
1510
1511 Array<size_t> deg(static_cast<size_t>(6), static_cast<size_t>(0));
1512
1513 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1514 {
1515 const Compressed_Path & p = it.get_curr_ne();
1516 const size_t iu = find_in_array(branches, p.u);
1517 const size_t iv = find_in_array(branches, p.v);
1518 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1519 return false;
1520
1521 if (mat[iu][iv])
1522 return false;
1523
1524 mat[iu][iv] = 1;
1525 mat[iv][iu] = 1;
1526 adj[iu].append(iv);
1527 adj[iv].append(iu);
1528 ++deg[iu];
1529 ++deg[iv];
1530 }
1531
1532 for (size_t i = 0; i < 6; ++i)
1533 if (deg[i] != 3)
1534 return false;
1535
1536 Array<long> color(static_cast<size_t>(6), static_cast<long>(-1));
1537 for (size_t s = 0; s < 6; ++s)
1538 {
1539 if (color[s] != -1)
1540 continue;
1541
1542 color[s] = 0;
1543 Array<size_t> stack;
1544 stack.append(s);
1545
1546 while (not stack.is_empty())
1547 {
1548 const size_t u = stack.remove_last();
1549 for (typename Array<size_t>::Iterator it(adj[u]); it.has_curr(); it.next_ne())
1550 {
1551 const size_t v = it.get_curr_ne();
1552 if (color[v] == -1)
1553 {
1554 color[v] = 1 - color[u];
1555 stack.append(v);
1556 }
1557 else if (color[v] == color[u])
1558 {
1559 return false;
1560 }
1561 }
1562 }
1563 }
1564
1565 size_t left_count = 0;
1566 size_t right_count = 0;
1567 for (size_t i = 0; i < 6; ++i)
1568 {
1569 if (color[i] == 0)
1570 ++left_count;
1571 else
1572 ++right_count;
1573 }
1574
1575 if (left_count != 3 or right_count != 3)
1576 return false;
1577
1578 for (size_t i = 0; i < 6; ++i)
1579 for (size_t j = i + 1; j < 6; ++j)
1580 {
1581 if (color[i] == color[j] and mat[i][j])
1582 return false;
1583
1584 if (color[i] != color[j] and not mat[i][j])
1585 return false;
1586 }
1587
1588 return true;
1589 }
1590
1591 size_t find_simplified_edge_id(const size_t u, const size_t v) const
1592 {
1593 const size_t a = std::min(u, v);
1594 const size_t b = std::max(u, v);
1595
1596 for (size_t i = 0; i < edges_.size(); ++i)
1597 if (edges_[i].u == a and edges_[i].v == b)
1598 return i;
1599
1600 return Null_Edge;
1601 }
1602
1604 make_edge_witness(const size_t u, const size_t v) const
1605 {
1607 if (u < nodes_.size())
1608 w.src = nodes_[u];
1609 if (v < nodes_.size())
1610 w.tgt = nodes_[v];
1611
1612 const size_t eid = find_simplified_edge_id(u, v);
1614 return w;
1615
1616 const auto & arcs = simplified_edge_input_arcs_[eid];
1617 if (not arcs.is_empty())
1618 w.representative_input_arc = arcs[0];
1619
1620 w.input_arcs.reserve(arcs.size());
1621 for (typename Array<Arc *>::Iterator it(arcs); it.has_curr(); it.next_ne())
1622 w.input_arcs.append(it.get_curr_ne());
1623
1624 return w;
1625 }
1626
1628 const Array<Compressed_Path> & paths)
1629 {
1630 result_.certificate_branch_nodes.empty();
1631 result_.certificate_paths.empty();
1632
1633 result_.certificate_branch_nodes.reserve(branches.size());
1634 for (typename Array<size_t>::Iterator it(branches); it.has_curr(); it.next_ne())
1635 result_.certificate_branch_nodes.append(nodes_[it.get_curr_ne()]);
1636
1637 result_.certificate_paths.reserve(paths.size());
1638 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1639 {
1640 const Compressed_Path & p = it.get_curr_ne();
1643 for (typename Array<size_t>::Iterator pit(p.nodes); pit.has_curr(); pit.next_ne())
1644 witness.nodes.append(nodes_[pit.get_curr_ne()]);
1645
1646 if (p.nodes.size() >= 2)
1647 {
1648 witness.edges.reserve(p.nodes.size() - 1);
1649 for (size_t i = 1; i < p.nodes.size(); ++i)
1650 witness.edges.append(
1651 make_edge_witness(p.nodes[i - 1], p.nodes[i]));
1652 }
1653
1654 result_.certificate_paths.append(std::move(witness));
1655 }
1656 }
1657
1659 {
1660 if (result_.is_planar)
1661 return;
1662
1663 if (edges_.is_empty())
1664 return;
1665
1667 {
1668 result_.certificate_search_truncated = true;
1669 return;
1670 }
1671
1673
1675 {
1676 result_.certificate_search_truncated = true;
1677 }
1678 else
1679 {
1680 size_t pass_count = 0;
1681
1682 while (true)
1683 {
1684 bool changed = false;
1685 size_t i = 0;
1686
1687 while (i < witness.size())
1688 {
1690 trial.reserve(witness.size() - 1);
1691 for (size_t j = 0; j < witness.size(); ++j)
1692 if (j != i)
1693 trial.append(witness[j]);
1694
1696 {
1697 ++i;
1698 }
1699 else
1700 {
1701 witness = std::move(trial);
1702 changed = true;
1703 }
1704 }
1705
1706 Array<size_t> degree(result_.simplified_num_nodes, static_cast<size_t>(0));
1707 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1708 {
1709 const auto & e = it.get_curr_ne();
1710 ++degree[e.u];
1711 ++degree[e.v];
1712 }
1713
1714 size_t v = 0;
1715 while (v < result_.simplified_num_nodes)
1716 {
1717 if (degree[v] == 0)
1718 {
1719 ++v;
1720 continue;
1721 }
1722
1724 trial.reserve(witness.size());
1725 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1726 {
1727 const auto & e = it.get_curr_ne();
1728 if (e.u == v or e.v == v)
1729 continue;
1730 trial.append(e);
1731 }
1732
1733 if (trial.size() == witness.size())
1734 {
1735 ++v;
1736 continue;
1737 }
1738
1740 {
1741 ++v;
1742 }
1743 else
1744 {
1745 witness = std::move(trial);
1746 changed = true;
1747
1748 degree = Array<size_t>(result_.simplified_num_nodes,
1749 static_cast<size_t>(0));
1751 wit.has_curr(); wit.next_ne())
1752 {
1753 const auto & e = wit.get_curr_ne();
1754 ++degree[e.u];
1755 ++degree[e.v];
1756 }
1757 v = 0;
1758 }
1759 }
1760
1761 if (not changed)
1762 break;
1763
1764 ++pass_count;
1766 {
1767 result_.certificate_search_truncated = true;
1768 break;
1769 }
1770 }
1771 }
1772
1773 result_.has_nonplanar_certificate = true;
1775
1776 result_.certificate_obstruction_edges.empty();
1777 result_.certificate_obstruction_edges.reserve(witness.size());
1778 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1779 {
1780 const Simple_Edge & e = it.get_curr_ne();
1781 result_.certificate_obstruction_edges.append(
1782 make_edge_witness(e.u, e.v));
1783 }
1784
1785 Array<size_t> degree(result_.simplified_num_nodes, static_cast<size_t>(0));
1787 inc.reserve(result_.simplified_num_nodes);
1788 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
1789 inc.append(Array<size_t>());
1790
1791 for (size_t i = 0; i < witness.size(); ++i)
1792 {
1793 const auto & e = witness[i];
1794 ++degree[e.u];
1795 ++degree[e.v];
1796 inc[e.u].append(i);
1797 inc[e.v].append(i);
1798 }
1799
1801 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
1802 if (degree[v] > 0 and degree[v] != 2)
1803 branches.append(v);
1804
1805 if (branches.size() < 5)
1806 return;
1807
1809 {
1810 result_.certificate_search_truncated = true;
1811 return;
1812 }
1813
1814 Array<char> is_branch(result_.simplified_num_nodes, static_cast<char>(0));
1815 for (typename Array<size_t>::Iterator it(branches); it.has_curr(); it.next_ne())
1816 is_branch[it.get_curr_ne()] = 1;
1817
1818 Array<char> used(witness.size(), static_cast<char>(0));
1820
1821 for (typename Array<size_t>::Iterator bit(branches); bit.has_curr(); bit.next_ne())
1822 {
1823 const size_t b = bit.get_curr_ne();
1824
1825 for (typename Array<size_t>::Iterator eit(inc[b]); eit.has_curr(); eit.next_ne())
1826 {
1827 const size_t first_edge = eit.get_curr_ne();
1828 if (used[first_edge])
1829 continue;
1830
1831 used[first_edge] = 1;
1832
1834 p.u = b;
1835 p.nodes.append(b);
1836
1837 size_t curr = witness[first_edge].u == b ? witness[first_edge].v
1838 : witness[first_edge].u;
1839 size_t prev_edge = first_edge;
1840 size_t guard = 0;
1841
1842 while (curr < result_.simplified_num_nodes and not is_branch[curr])
1843 {
1844 p.nodes.append(curr);
1845
1846 if (inc[curr].size() != 2)
1847 break;
1848
1849 const size_t e0 = inc[curr][0];
1850 const size_t e1 = inc[curr][1];
1851 const size_t next_edge = e0 == prev_edge ? e1 : e0;
1852
1853 if (used[next_edge])
1854 break;
1855
1856 used[next_edge] = 1;
1858
1859 const auto & ne = witness[next_edge];
1860 curr = ne.u == curr ? ne.v : ne.u;
1861
1862 ++guard;
1863 if (guard > witness.size())
1864 break;
1865 }
1866
1867 p.v = curr;
1868 if (p.nodes.is_empty() or p.nodes.get_last() != curr)
1869 p.nodes.append(curr);
1870
1871 if (p.u != p.v
1872 and p.v < result_.simplified_num_nodes
1873 and is_branch[p.v]
1874 and p.nodes.size() >= 2)
1875 paths.append(std::move(p));
1876 }
1877 }
1878
1879 if (paths.is_empty())
1880 return;
1881
1882 const size_t bcount = branches.size();
1885 for (size_t i = 0; i < bcount; ++i)
1886 first_path.append(Array<long>(bcount, static_cast<long>(-1)));
1887
1888 for (size_t i = 0; i < paths.size(); ++i)
1889 {
1890 const Compressed_Path & p = paths[i];
1891 const size_t iu = find_in_array(branches, p.u);
1892 const size_t iv = find_in_array(branches, p.v);
1893 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1894 continue;
1895
1896 if (first_path[iu][iv] == -1)
1897 {
1898 first_path[iu][iv] = static_cast<long>(i);
1899 first_path[iv][iu] = static_cast<long>(i);
1900 }
1901 }
1902
1903 auto has_pair = [&](const size_t a, const size_t b) -> bool
1904 {
1905 return first_path[a][b] != -1;
1906 };
1907
1908 if (bcount >= 5)
1909 {
1910 for (size_t a = 0; a < bcount; ++a)
1911 for (size_t b = a + 1; b < bcount; ++b)
1912 for (size_t c = b + 1; c < bcount; ++c)
1913 for (size_t d = c + 1; d < bcount; ++d)
1914 for (size_t e = d + 1; e < bcount; ++e)
1915 {
1916 const size_t idx[5] = {a, b, c, d, e};
1917 bool ok = true;
1918 for (size_t i = 0; i < 5 and ok; ++i)
1919 for (size_t j = i + 1; j < 5; ++j)
1920 if (not has_pair(idx[i], idx[j]))
1921 ok = false;
1922
1923 if (not ok)
1924 continue;
1925
1928 for (size_t i = 0; i < 5; ++i)
1929 chosen_branches.append(branches[idx[i]]);
1930
1933 for (size_t i = 0; i < 5; ++i)
1934 for (size_t j = i + 1; j < 5; ++j)
1935 chosen_paths.append(paths[static_cast<size_t>(first_path[idx[i]][idx[j]])]);
1936
1938 continue;
1939
1942 return;
1943 }
1944 }
1945
1946 if (bcount >= 6)
1947 {
1948 for (size_t a = 0; a < bcount; ++a)
1949 for (size_t b = a + 1; b < bcount; ++b)
1950 for (size_t c = b + 1; c < bcount; ++c)
1951 for (size_t d = c + 1; d < bcount; ++d)
1952 for (size_t e = d + 1; e < bcount; ++e)
1953 for (size_t f = e + 1; f < bcount; ++f)
1954 {
1955 const size_t six[6] = {a, b, c, d, e, f};
1956
1957 const int partitions[10][3] = {
1958 {0, 1, 2}, {0, 1, 3}, {0, 1, 4}, {0, 1, 5},
1959 {0, 2, 3}, {0, 2, 4}, {0, 2, 5},
1960 {0, 3, 4}, {0, 3, 5}, {0, 4, 5}
1961 };
1962
1963 for (const auto & part : partitions)
1964 {
1965 Array<char> in_left(6, static_cast<char>(0));
1966 in_left[static_cast<size_t>(part[0])] = 1;
1967 in_left[static_cast<size_t>(part[1])] = 1;
1968 in_left[static_cast<size_t>(part[2])] = 1;
1969
1970 size_t left[3];
1971 size_t right[3];
1972 size_t li = 0;
1973 size_t ri = 0;
1974 for (size_t i = 0; i < 6; ++i)
1975 if (in_left[i])
1976 left[li++] = six[i];
1977 else
1978 right[ri++] = six[i];
1979
1980 bool ok = true;
1981 for (size_t i = 0; i < 3 and ok; ++i)
1982 for (size_t j = 0; j < 3; ++j)
1983 if (not has_pair(left[i], right[j]))
1984 ok = false;
1985
1986 if (not ok)
1987 continue;
1988
1991 for (size_t i = 0; i < 3; ++i)
1992 chosen_branches.append(branches[left[i]]);
1993 for (size_t i = 0; i < 3; ++i)
1994 chosen_branches.append(branches[right[i]]);
1995
1998 for (size_t i = 0; i < 3; ++i)
1999 for (size_t j = 0; j < 3; ++j)
2000 chosen_paths.append(
2001 paths[static_cast<size_t>(first_path[left[i]][right[j]])]);
2002
2004 continue;
2005
2008 return;
2009 }
2010 }
2011 }
2012 }
2013
2015 {
2016 if (result_.simplified_num_nodes == 0)
2017 {
2018 result_.has_combinatorial_embedding = true;
2019 result_.embedding_is_lr_linear = true;
2020 result_.embedding_num_faces = 0;
2021 return true;
2022 }
2023
2024 Array<Array<size_t>> order;
2025 order.reserve(result_.simplified_num_nodes);
2026 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
2027 order.append(Array<size_t>());
2028
2029 if (result_.simplified_num_edges == 0)
2030 {
2031 Array<Array<size_t>> faces;
2032 size_t global_faces = 0;
2034 result_.simplified_num_nodes,
2035 result_.simplified_num_nodes,
2036 faces, global_faces))
2037 return false;
2038
2039 fill_embedding_result(order, faces, global_faces, true);
2040 return true;
2041 }
2042
2043 if (undirected_to_oriented_.size() != result_.simplified_num_edges)
2044 return false;
2045
2046 for (size_t ue = 0; ue < undirected_to_oriented_.size(); ++ue)
2048 return false;
2049
2051 Array<char> vis(oriented_src_.size(), static_cast<char>(0));
2052
2053 auto sign_of = [&](auto && self, const size_t e) -> long
2054 {
2055 if (vis[e] == 2)
2056 return final_side[e];
2057 if (vis[e] == 1)
2058 return final_side[e];
2059
2060 vis[e] = 1;
2061 if (ref_[e] != Null_Edge)
2062 final_side[e] *= self(self, ref_[e]);
2063 vis[e] = 2;
2064 return final_side[e];
2065 };
2066
2068 for (size_t e = 0; e < oriented_src_.size(); ++e)
2069 {
2070 const long s = sign_of(sign_of, e);
2071 signed_depth[e] = s < 0 ? -nesting_depth_[e] : nesting_depth_[e];
2072 }
2073
2074 size_t isolated_vertices = 0;
2075 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2076 {
2077 auto & ord = order[v];
2079
2080 if (incident_edges_[v].is_empty())
2082
2083 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
2084 ord.append(it.get_curr_ne());
2085
2086 auto less_local = [&](const size_t ue_a, const size_t ue_b) -> bool
2087 {
2088 const size_t oe_a = undirected_to_oriented_[ue_a];
2089 const size_t oe_b = undirected_to_oriented_[ue_b];
2090
2091 const long a0 = (oriented_src_[oe_a] == v
2093 : -signed_depth[oe_a]);
2094 const long b0 = (oriented_src_[oe_b] == v
2096 : -signed_depth[oe_b]);
2097 if (a0 != b0)
2098 return a0 < b0;
2099
2100 const size_t a1 = other_endpoint(ue_a, v);
2101 const size_t b1 = other_endpoint(ue_b, v);
2102 if (a1 != b1)
2103 return a1 < b1;
2104
2105 return ue_a < ue_b;
2106 };
2107
2108 for (size_t i = 1; i < ord.size(); ++i)
2109 {
2110 const size_t key_ue = ord[i];
2111
2112 size_t j = i;
2113 while (j > 0 and less_local(key_ue, ord[j - 1]))
2114 {
2115 ord[j] = ord[j - 1];
2116 --j;
2117 }
2118 ord[j] = key_ue;
2119 }
2120
2121 for (size_t i = 0; i < ord.size(); ++i)
2122 ord[i] = other_endpoint(ord[i], v);
2123 }
2124
2125 const size_t num_components = count_components();
2126 {
2127 Array<Array<size_t>> faces;
2128 size_t global_faces = 0;
2129 if (compute_faces_from_rotation(order, isolated_vertices, num_components,
2130 faces, global_faces))
2131 {
2132 fill_embedding_result(order, faces, global_faces, true);
2133 return true;
2134 }
2135 }
2136
2138 {
2139 result_.embedding_search_truncated = true;
2140 return false;
2141 }
2142
2143 auto reverse_order = [](Array<size_t> & ord)
2144 {
2145 if (ord.size() <= 1)
2146 return;
2147
2148 size_t i = 0;
2149 size_t j = ord.size() - 1;
2150 while (i < j)
2151 {
2152 std::swap(ord[i], ord[j]);
2153 ++i;
2154 --j;
2155 }
2156 };
2157
2158 auto same_order = [](const Array<size_t> & a,
2159 const Array<size_t> & b) -> bool
2160 {
2161 if (a.size() != b.size())
2162 return false;
2163
2164 for (size_t i = 0; i < a.size(); ++i)
2165 if (a[i] != b[i])
2166 return false;
2167
2168 return true;
2169 };
2170
2172 const Array<size_t> & cand)
2173 {
2174 for (typename Array<Array<size_t>>::Iterator it(opts);
2175 it.has_curr(); it.next_ne())
2176 if (same_order(it.get_curr_ne(), cand))
2177 return;
2178
2179 opts.append(cand);
2180 };
2181
2183 const Array<size_t> & src)
2184 {
2185 if (src.size() < 2)
2186 return;
2187
2188 for (size_t i = 0; i + 1 < src.size(); ++i)
2189 {
2190 Array<size_t> cand = src;
2191 std::swap(cand[i], cand[i + 1]);
2193 }
2194 };
2195
2197 const Array<size_t> & src)
2198 {
2199 if (src.size() < 2)
2200 return;
2201
2202 for (size_t i = 0; i + 1 < src.size(); ++i)
2203 for (size_t j = i + 1; j < src.size(); ++j)
2204 {
2205 Array<size_t> cand = src;
2206 std::swap(cand[i], cand[j]);
2208 }
2209 };
2210
2212 repair_options.reserve(result_.simplified_num_nodes);
2213
2214 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2215 {
2217 opts.append(order[v]);
2218
2219 if (order[v].size() >= 2)
2220 {
2221 Array<size_t> rev = order[v];
2222 reverse_order(rev);
2224
2225 add_adjacent_swaps(opts, order[v]);
2227
2228 // Dense planar subgraphs often need more than a flip.
2229 // For small local degree, include full pair swaps.
2230 if (order[v].size() <= 5)
2231 {
2232 add_pair_swaps(opts, order[v]);
2233 add_pair_swaps(opts, rev);
2234 }
2235 }
2236
2237 repair_options.append(std::move(opts));
2238 }
2239
2241 vars.reserve(result_.simplified_num_nodes);
2242 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2243 if (repair_options[v].size() > 1)
2244 vars.append(v);
2245
2246 if (vars.is_empty())
2247 return false;
2248
2249 for (size_t i = 1; i < vars.size(); ++i)
2250 {
2251 const size_t key = vars[i];
2252 size_t j = i;
2253 while (j > 0
2254 and repair_options[key].size() > repair_options[vars[j - 1]].size())
2255 {
2256 vars[j] = vars[j - 1];
2257 --j;
2258 }
2259 vars[j] = key;
2260 }
2261
2262 struct Repair_Quality
2263 {
2264 bool available = false;
2265 bool valid_embedding = false;
2266 long long abs_euler_delta = std::numeric_limits<long long>::max();
2267 size_t global_faces = 0;
2268 };
2269
2270 Array<size_t> selected(result_.simplified_num_nodes, static_cast<size_t>(0));
2271 size_t evaluated = 0;
2272 bool truncated = false;
2273
2274 auto build_candidate_order = [&](const Array<size_t> & sel,
2276 {
2277 candidate.empty();
2278 candidate.reserve(result_.simplified_num_nodes);
2279 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2280 candidate.append(repair_options[v][sel[v]]);
2281 };
2282
2283 auto materialize_embedding = [&](const Array<size_t> & sel) -> bool
2284 {
2287
2288 Array<Array<size_t>> faces;
2289 size_t global_faces = 0;
2291 num_components, faces,
2292 global_faces))
2293 return false;
2294
2296 return true;
2297 };
2298
2299 auto evaluate_quality = [&](const Array<size_t> & sel,
2300 Repair_Quality & out) -> bool
2301 {
2303 {
2304 truncated = true;
2305 return false;
2306 }
2307
2308 ++evaluated;
2309
2312
2313 Array<Array<size_t>> faces;
2314 size_t global_faces = 0;
2316 num_components, faces,
2317 global_faces, false))
2318 {
2319 out.available = false;
2320 out.valid_embedding = false;
2321 out.abs_euler_delta = std::numeric_limits<long long>::max();
2322 out.global_faces = 0;
2323 return true;
2324 }
2325
2326 const long long lhs = static_cast<long long>(result_.simplified_num_nodes)
2327 - static_cast<long long>(result_.simplified_num_edges)
2328 + static_cast<long long>(global_faces);
2329 const long long rhs = static_cast<long long>(num_components) + 1;
2330
2331 long long delta = lhs - rhs;
2332 if (delta < 0)
2333 delta = -delta;
2334
2335 out.available = true;
2336 out.valid_embedding = (not options_.embedding_validate_with_euler)
2337 or delta == 0;
2338 out.abs_euler_delta = delta;
2339 out.global_faces = global_faces;
2340 return true;
2341 };
2342
2343 auto run_coordinate_descent = [&](const Array<size_t> & seed_selected) -> bool
2344 {
2345 selected = seed_selected;
2346
2348 if (not evaluate_quality(selected, current_quality))
2349 return false;
2350
2351 if (current_quality.valid_embedding and materialize_embedding(selected))
2352 return true;
2353
2354 const size_t max_passes = vars.size() == 0 ? 0 : 2 * vars.size();
2355 bool budget_hit = false;
2356
2357 for (size_t pass = 0; pass < max_passes; ++pass)
2358 {
2359 bool improved = false;
2360
2361 for (typename Array<size_t>::Iterator vit(vars);
2362 vit.has_curr(); vit.next_ne())
2363 {
2364 const size_t v = vit.get_curr_ne();
2365 const size_t prev_opt = selected[v];
2366 size_t best_opt = prev_opt;
2368
2369 for (size_t i = 0; i < repair_options[v].size(); ++i)
2370 {
2371 if (i == prev_opt)
2372 continue;
2373
2374 selected[v] = i;
2376 if (not evaluate_quality(selected, cand_quality))
2377 {
2378 budget_hit = true;
2379 break;
2380 }
2381
2382 if (not cand_quality.available)
2383 continue;
2384
2385 if (cand_quality.valid_embedding
2386 and materialize_embedding(selected))
2387 return true;
2388
2389 if (cand_quality.abs_euler_delta < best_quality.abs_euler_delta
2390 or (cand_quality.abs_euler_delta == best_quality.abs_euler_delta
2391 and cand_quality.global_faces > best_quality.global_faces))
2392 {
2393 best_opt = i;
2395 }
2396 }
2397
2398 if (budget_hit)
2399 break;
2400
2401 selected[v] = best_opt;
2402 if (best_opt != prev_opt)
2403 {
2405 improved = true;
2406 }
2407 }
2408
2410 break;
2411 }
2412
2413 return false;
2414 };
2415
2416 Array<size_t> base_seed(result_.simplified_num_nodes, static_cast<size_t>(0));
2418 return true;
2419
2420 if (truncated)
2421 {
2422 result_.embedding_search_truncated = true;
2423 return false;
2424 }
2425
2427 bool has_reverse_seed = false;
2428 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2429 if (repair_options[v].size() > 1)
2430 {
2431 reverse_seed[v] = 1;
2432 has_reverse_seed = true;
2433 }
2434
2436 return true;
2437
2438 if (truncated)
2439 {
2440 result_.embedding_search_truncated = true;
2441 return false;
2442 }
2443
2444 for (typename Array<size_t>::Iterator vit(vars); vit.has_curr(); vit.next_ne())
2445 {
2446 const size_t v = vit.get_curr_ne();
2447 for (size_t i = 1; i < repair_options[v].size(); ++i)
2448 {
2450 seed[v] = i;
2452 return true;
2453
2454 if (truncated)
2455 {
2456 result_.embedding_search_truncated = true;
2457 return false;
2458 }
2459 }
2460 }
2461
2462 const size_t max_pair_seed_vars = std::min(static_cast<size_t>(4), vars.size());
2463 for (size_t ia = 0; ia < max_pair_seed_vars; ++ia)
2464 for (size_t ib = ia + 1; ib < max_pair_seed_vars; ++ib)
2465 {
2466 const size_t va = vars[ia];
2467 const size_t vb = vars[ib];
2468 for (size_t oa = 1; oa < repair_options[va].size(); ++oa)
2469 for (size_t ob = 1; ob < repair_options[vb].size(); ++ob)
2470 {
2472 seed[va] = oa;
2473 seed[vb] = ob;
2475 return true;
2476
2477 if (truncated)
2478 {
2479 result_.embedding_search_truncated = true;
2480 return false;
2481 }
2482 }
2483 }
2484
2485 if (truncated)
2486 result_.embedding_search_truncated = true;
2487
2488 return false;
2489 }
2490
2492 {
2493 if (result_.simplified_num_nodes == 0)
2494 {
2495 result_.has_combinatorial_embedding = true;
2496 result_.embedding_is_lr_linear = false;
2497 result_.embedding_num_faces = 0;
2498 return true;
2499 }
2500
2502 neighbors.reserve(result_.simplified_num_nodes);
2503
2504 size_t isolated_vertices = 0;
2505 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2506 {
2509 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
2510 ng.append(other_endpoint(it.get_curr_ne(), v));
2511
2513 if (ng.is_empty())
2515
2516 neighbors.append(std::move(ng));
2517 }
2518
2519 if (result_.simplified_num_edges == 0)
2520 {
2521 Array<Array<size_t>> order;
2522 order.reserve(result_.simplified_num_nodes);
2523 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2524 order.append(Array<size_t>());
2525
2526 const size_t num_components = result_.simplified_num_nodes;
2527 Array<Array<size_t>> faces;
2528 size_t global_faces = 0;
2529
2530 if (not compute_faces_from_rotation(order, isolated_vertices, num_components,
2531 faces, global_faces))
2532 return false;
2533
2534 fill_embedding_result(order, faces, global_faces, false);
2535 return true;
2536 }
2537
2539 {
2540 result_.embedding_search_truncated = true;
2541 return false;
2542 }
2543
2545 order_options.reserve(result_.simplified_num_nodes);
2546
2547 size_t combinations = 1;
2548 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2549 {
2550 const auto & neigh = neighbors[v];
2551 const size_t d = neigh.size();
2552
2554 if (d <= 2)
2555 {
2556 opts.append(neigh);
2557 }
2558 else
2559 {
2560 const size_t cnt = factorial_bounded(d - 1, options_.embedding_max_combinations);
2563 {
2564 result_.embedding_search_truncated = true;
2565 return false;
2566 }
2567
2568 combinations *= cnt;
2569
2570 const size_t first = neigh[0];
2571 Array<size_t> tail;
2572 tail.reserve(d - 1);
2573 for (size_t i = 1; i < d; ++i)
2574 tail.append(neigh[i]);
2575
2576 generate_permutations(tail, 0, first, opts);
2577 }
2578
2579 order_options.append(std::move(opts));
2580 }
2581
2583 vars.reserve(result_.simplified_num_nodes);
2584 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2585 if (order_options[v].size() > 1)
2586 vars.append(v);
2587
2588 for (size_t i = 1; i < vars.size(); ++i)
2589 {
2590 const size_t key = vars[i];
2591 size_t j = i;
2592 while (j > 0 and order_options[key].size() > order_options[vars[j - 1]].size())
2593 {
2594 vars[j] = vars[j - 1];
2595 --j;
2596 }
2597 vars[j] = key;
2598 }
2599
2600 const size_t num_components = count_components();
2601 Array<size_t> selected(result_.simplified_num_nodes, 0);
2602
2603 auto evaluate = [&]() -> bool
2604 {
2605 Array<Array<size_t>> order;
2606 order.reserve(result_.simplified_num_nodes);
2607 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2608 order.append(order_options[v][selected[v]]);
2609
2610 Array<Array<size_t>> faces;
2611 size_t global_faces = 0;
2612 if (not compute_faces_from_rotation(order, isolated_vertices, num_components,
2613 faces, global_faces))
2614 return false;
2615
2616 fill_embedding_result(order, faces, global_faces, false);
2617 return true;
2618 };
2619
2620 auto search = [&](auto && self, const size_t pos) -> bool
2621 {
2622 if (pos == vars.size())
2623 return evaluate();
2624
2625 const size_t v = vars[pos];
2626 for (size_t i = 0; i < order_options[v].size(); ++i)
2627 {
2628 selected[v] = i;
2629 if (self(self, pos + 1))
2630 return true;
2631 }
2632
2633 return false;
2634 };
2635
2636 return search(search, 0);
2637 }
2638
2640 {
2642 return true;
2643
2645 {
2646 result_.embedding_search_truncated = true;
2647 return false;
2648 }
2649
2651 }
2652
2653 public:
2656 : g_(g),
2657 sa_(std::move(sa)),
2658 options_(std::move(options))
2659 {
2660 // empty
2661 }
2662
2664 {
2666
2668 {
2669 result_.is_planar = false;
2670 result_.failed_euler_bound = true;
2671
2674
2675 return result_;
2676 }
2677
2678 result_.is_planar = run_lr_planarity_test();
2679
2680 if (result_.is_planar)
2681 {
2683 {
2684 result_.embedding_search_truncated = false;
2686 }
2687 }
2689 {
2690 result_.certificate_search_truncated = false;
2692 }
2693
2694 return result_;
2695 }
2696 };
2697 } // namespace planarity_detail
2698
2699
2708 template <class GT,
2709 class SA = Dft_Show_Arc<GT>>
2712 SA sa = SA(),
2714 {
2716 g, std::move(sa), options).run();
2717 }
2718
2719
2724 template <class GT>
2731
2732
2737 template <class GT,
2738 class SA = Dft_Show_Arc<GT>>
2739 bool is_planar_graph(const GT & g,
2740 SA sa = SA(),
2742 {
2743 return planarity_test<GT, SA>(g, std::move(sa), options).is_planar;
2744 }
2745
2746
2751 template <class GT>
2757
2758
2765 template <class GT>
2768 {
2769 using Node = typename GT::Node;
2770 using Dart_Key = planarity_detail::Dart_Key;
2771 constexpr size_t Null_Edge = planarity_detail::Null_Edge;
2772
2774 << "planar_dual_metadata() requires a planar result with embedding";
2775
2776 const size_t n = result.embedding_rotation.size();
2778 << "embedding_rotation size mismatch";
2779
2781 md.has_embedding = true;
2782
2783 if (n == 0)
2784 {
2785 md.num_components = 0;
2786 md.num_faces_local = 0;
2787 md.num_faces_global = 0;
2788 md.faces_are_component_local = false;
2789 return md;
2790 }
2791
2794
2795 DynMapTree<Node *, size_t> node_to_idx;
2796 for (size_t i = 0; i < n; ++i)
2797 {
2798 Node * node = result.embedding_rotation[i].node;
2799 ah_runtime_error_unless(node != nullptr)
2800 << "embedding_rotation contains null node";
2801 ah_runtime_error_if(node_to_idx.contains(node))
2802 << "embedding_rotation contains duplicated node pointer";
2803
2804 node_to_idx.insert(node, i);
2805 idx_to_node.append(node);
2806 }
2807
2808 Array<Array<size_t>> order;
2809 order.reserve(n);
2810 for (size_t i = 0; i < n; ++i)
2811 order.append(Array<size_t>());
2812
2813 for (size_t i = 0; i < n; ++i)
2814 {
2816 const auto & re = result.embedding_rotation[i];
2817 for (typename Array<Node *>::Iterator it(re.cw_neighbors); it.has_curr(); it.next_ne())
2818 {
2819 Node * neigh = it.get_curr_ne();
2820 ah_runtime_error_unless(neigh != nullptr)
2821 << "embedding_rotation contains null neighbor";
2823 << "embedding_rotation references unknown neighbor node";
2824
2825 const size_t v = node_to_idx.find(neigh);
2826 ah_runtime_error_if(v == i)
2827 << "embedding_rotation has self-neighbor in simplified graph";
2828 ah_runtime_error_if(seen.contains(v))
2829 << "embedding_rotation has duplicated neighbor in same rotation";
2830
2831 seen.insert(v);
2832 order[i].append(v);
2833 }
2834 }
2835
2836 Array<long> comp_id(n, static_cast<long>(-1));
2837 size_t num_components = 0;
2838 size_t isolated_vertices = 0;
2839
2840 for (size_t s = 0; s < n; ++s)
2841 {
2842 if (order[s].is_empty())
2844
2845 if (comp_id[s] != -1)
2846 continue;
2847
2848 comp_id[s] = static_cast<long>(num_components);
2849 Array<size_t> stack;
2850 stack.append(s);
2851
2852 while (not stack.is_empty())
2853 {
2854 const size_t u = stack.remove_last();
2855 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
2856 {
2857 const size_t v = it.get_curr_ne();
2858 if (comp_id[v] != -1)
2859 continue;
2860 comp_id[v] = static_cast<long>(num_components);
2861 stack.append(v);
2862 }
2863 }
2864
2865 ++num_components;
2866 }
2867
2868 md.num_components = num_components;
2869
2873 dart_tgt.reserve(2 * result.simplified_num_edges);
2874
2876 for (size_t u = 0; u < n; ++u)
2877 {
2878 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
2879 {
2880 const size_t v = it.get_curr_ne();
2881 const Dart_Key key{u, v};
2882 ah_runtime_error_if(dart_id.contains(key))
2883 << "embedding rotation contains repeated directed edge";
2884
2885 const size_t did = dart_src.size();
2886 dart_id.insert(key, did);
2887 dart_src.append(u);
2888 dart_tgt.append(v);
2889 }
2890 }
2891
2892 ah_runtime_error_unless(dart_src.size() % 2 == 0)
2893 << "invalid embedding: odd number of darts";
2894
2895 Array<size_t> alpha(dart_src.size(), Null_Edge);
2896 for (size_t d = 0; d < dart_src.size(); ++d)
2897 {
2898 const Dart_Key rev{dart_tgt[d], dart_src[d]};
2899 ah_runtime_error_unless(dart_id.contains(rev))
2900 << "invalid embedding: missing reverse dart";
2901 alpha[d] = dart_id.find(rev);
2902 }
2903
2904 Array<size_t> sigma(dart_src.size(), Null_Edge);
2905 for (size_t u = 0; u < n; ++u)
2906 {
2907 const auto & ord = order[u];
2908 if (ord.is_empty())
2909 continue;
2910
2911 for (size_t i = 0; i < ord.size(); ++i)
2912 {
2913 const size_t to = ord[i];
2914 const size_t next = ord[(i + 1) % ord.size()];
2915 const size_t a = dart_id.find(Dart_Key{u, to});
2916 const size_t b = dart_id.find(Dart_Key{u, next});
2917 sigma[a] = b;
2918 }
2919 }
2920
2921 for (size_t d = 0; d < sigma.size(); ++d)
2922 ah_runtime_error_unless(sigma[d] != Null_Edge)
2923 << "invalid embedding: incomplete sigma permutation";
2924
2925 Array<size_t> face_of_dart(dart_src.size(), Null_Edge);
2926 for (size_t d = 0; d < dart_src.size(); ++d)
2927 {
2928 if (face_of_dart[d] != Null_Edge)
2929 continue;
2930
2931 const size_t fid = md.faces.size();
2933
2934 size_t x = d;
2935 while (face_of_dart[x] == Null_Edge)
2936 {
2937 face_of_dart[x] = fid;
2938
2940 fd.src = idx_to_node[dart_src[x]];
2941 fd.tgt = idx_to_node[dart_tgt[x]];
2942 face.darts.append(fd);
2943
2944 x = sigma[alpha[x]];
2945 }
2946
2947 md.faces.append(std::move(face));
2948 }
2949
2950 for (size_t i = 0; i < isolated_vertices; ++i)
2951 md.faces.append(typename Planar_Dual_Metadata<GT>::Face_Boundary());
2952
2953 md.num_faces_local = md.faces.size();
2954
2955 const size_t m = dart_src.size() / 2;
2956 const size_t num_faces_global = m - n + num_components + 1;
2957 md.num_faces_global = num_faces_global;
2958 md.faces_are_component_local = md.num_faces_local != md.num_faces_global;
2959
2960 md.face_adjacency.reserve(md.num_faces_local);
2961 for (size_t i = 0; i < md.num_faces_local; ++i)
2962 md.face_adjacency.append(Array<size_t>());
2963
2964 md.dual_edges.reserve(m);
2965 for (size_t d = 0; d < dart_src.size(); ++d)
2966 {
2967 const size_t rev = alpha[d];
2968 if (d > rev)
2969 continue;
2970
2971 const size_t f0 = face_of_dart[d];
2972 const size_t f1 = face_of_dart[rev];
2973
2975 info.face_a = f0;
2976 info.face_b = f1;
2977 info.primal_src = idx_to_node[dart_src[d]];
2978 info.primal_tgt = idx_to_node[dart_tgt[d]];
2979
2980 md.dual_edges.append(info);
2981 md.face_adjacency[f0].append(f1);
2982 md.face_adjacency[f1].append(f0);
2983 }
2984
2985 for (size_t f = 0; f < md.face_adjacency.size(); ++f)
2986 {
2987 auto & adj = md.face_adjacency[f];
2988 for (size_t i = 1; i < adj.size(); ++i)
2989 {
2990 const size_t key = adj[i];
2991 size_t j = i;
2992 while (j > 0 and key < adj[j - 1])
2993 {
2994 adj[j] = adj[j - 1];
2995 --j;
2996 }
2997 adj[j] = key;
2998 }
2999
3000 if (adj.is_empty())
3001 continue;
3002
3004 uniq.reserve(adj.size());
3005 uniq.append(adj[0]);
3006 for (size_t i = 1; i < adj.size(); ++i)
3007 if (adj[i] != uniq.get_last())
3008 uniq.append(adj[i]);
3009
3010 adj = std::move(uniq);
3011 }
3012
3013 return md;
3014 }
3015
3016
3021 template <class GT,
3023 DGT
3025 {
3026 ah_runtime_error_unless(md.has_embedding)
3027 << "build_planar_dual_graph() requires metadata with embedding";
3028
3029 DGT dual;
3031 face_nodes.reserve(md.num_faces_local);
3032
3033 for (size_t f = 0; f < md.num_faces_local; ++f)
3034 face_nodes.append(dual.insert_node(f));
3035
3036 for (typename Array<Planar_Dual_Edge_Info<GT>>::Iterator it(md.dual_edges);
3037 it.has_curr(); it.next_ne())
3038 {
3039 const auto & e = it.get_curr_ne();
3040 dual.insert_arc(face_nodes[e.face_a], face_nodes[e.face_b], e);
3041 }
3042
3043 return dual;
3044 }
3045
3046
3051 template <class GT,
3053 DGT
3058
3059
3071 template <class GT>
3074 const Planarity_Test_Result<GT> & result,
3076 {
3077 using Node = typename GT::Node;
3079
3080 constexpr size_t Null = std::numeric_limits<size_t>::max();
3081 constexpr double Pi = 3.1415926535897932384626433832795;
3082
3084 << "planar_geometric_drawing() requires a planar result with embedding";
3085
3086 const size_t n = result.embedding_rotation.size();
3088 << "embedding_rotation size mismatch";
3089
3091 drawing.has_embedding = true;
3092
3093 if (n == 0)
3094 {
3095 drawing.drawing_available = true;
3096 drawing.drawing_validated_no_crossings = true;
3097 return drawing;
3098 }
3099
3100 if (options.max_outer_faces_to_try == 0)
3101 {
3102 drawing.drawing_search_truncated = true;
3103 return drawing;
3104 }
3105
3106 DynMapTree<Node *, size_t> node_to_idx;
3109
3110 for (size_t i = 0; i < n; ++i)
3111 {
3112 Node * node = result.embedding_rotation[i].node;
3113 ah_runtime_error_unless(node != nullptr)
3114 << "embedding_rotation contains null node";
3115 ah_runtime_error_if(node_to_idx.contains(node))
3116 << "embedding_rotation contains duplicated node pointer";
3117
3118 node_to_idx.insert(node, i);
3119 idx_to_node.append(node);
3120 }
3121
3122 Array<Array<size_t>> order;
3123 order.reserve(n);
3124 for (size_t i = 0; i < n; ++i)
3125 order.append(Array<size_t>());
3126
3127 for (size_t i = 0; i < n; ++i)
3128 {
3130 const auto & re = result.embedding_rotation[i];
3131 for (typename Array<Node *>::Iterator it(re.cw_neighbors); it.has_curr(); it.next_ne())
3132 {
3133 Node * neigh = it.get_curr_ne();
3134 ah_runtime_error_unless(neigh != nullptr)
3135 << "embedding_rotation contains null neighbor";
3137 << "embedding_rotation references unknown node";
3138
3139 const size_t v = node_to_idx.find(neigh);
3140 ah_runtime_error_if(v == i)
3141 << "embedding_rotation has self-neighbor";
3142 ah_runtime_error_if(seen.contains(v))
3143 << "embedding_rotation has duplicated neighbor";
3144
3145 seen.insert(v);
3146 order[i].append(v);
3147 }
3148 }
3149
3150 Array<long> comp_id(n, static_cast<long>(-1));
3151 size_t num_components = 0;
3152 for (size_t s = 0; s < n; ++s)
3153 {
3154 if (comp_id[s] != -1)
3155 continue;
3156
3157 comp_id[s] = static_cast<long>(num_components);
3158 Array<size_t> stack;
3159 stack.append(s);
3160
3161 while (not stack.is_empty())
3162 {
3163 const size_t u = stack.remove_last();
3164 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
3165 {
3166 const size_t v = it.get_curr_ne();
3167 if (comp_id[v] != -1)
3168 continue;
3169 comp_id[v] = static_cast<long>(num_components);
3170 stack.append(v);
3171 }
3172 }
3173
3174 ++num_components;
3175 }
3176
3177 drawing.num_components = num_components;
3178
3180 comp_nodes.reserve(num_components);
3181 for (size_t c = 0; c < num_components; ++c)
3182 comp_nodes.append(Array<size_t>());
3183 for (size_t i = 0; i < n; ++i)
3184 comp_nodes[static_cast<size_t>(comp_id[i])].append(i);
3185
3186 struct Drawing_Edge
3187 {
3188 size_t u = 0;
3189 size_t v = 0;
3190 size_t comp = 0;
3191 };
3192
3193 Array<Drawing_Edge> edges;
3194 edges.reserve(result.simplified_num_edges);
3195 for (size_t u = 0; u < n; ++u)
3196 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
3197 {
3198 const size_t v = it.get_curr_ne();
3199 if (u < v)
3200 edges.append(Drawing_Edge{u, v, static_cast<size_t>(comp_id[u])});
3201 }
3202
3204 comp_edge_ids.reserve(num_components);
3205 for (size_t c = 0; c < num_components; ++c)
3206 comp_edge_ids.append(Array<size_t>());
3207 for (size_t i = 0; i < edges.size(); ++i)
3208 comp_edge_ids[edges[i].comp].append(i);
3209
3210 const Face_Metadata md = planar_dual_metadata(result);
3211 struct Face_Candidate
3212 {
3213 size_t face_id = Null;
3214 size_t comp = 0;
3216 size_t score = 0;
3217 };
3218
3220 face_candidates.reserve(md.faces.size());
3221
3222 for (size_t fid = 0; fid < md.faces.size(); ++fid)
3223 {
3224 const auto & face = md.faces[fid];
3225 if (face.darts.is_empty())
3226 continue;
3227
3229 raw.reserve(face.darts.size());
3231 it(face.darts); it.has_curr(); it.next_ne())
3232 {
3233 const auto & d = it.get_curr_ne();
3234 ah_runtime_error_unless(node_to_idx.contains(d.src))
3235 << "face metadata references unknown node";
3236 raw.append(node_to_idx.find(d.src));
3237 }
3238
3239 if (raw.is_empty())
3240 continue;
3241
3243 cleaned.reserve(raw.size());
3244 for (size_t i = 0; i < raw.size(); ++i)
3245 if (cleaned.is_empty() or cleaned.get_last() != raw[i])
3246 cleaned.append(raw[i]);
3247
3248 if (cleaned.size() > 1 and cleaned[0] == cleaned.get_last())
3249 (void) cleaned.remove_last();
3250
3254 for (typename Array<size_t>::Iterator it(cleaned); it.has_curr(); it.next_ne())
3255 {
3256 const size_t u = it.get_curr_ne();
3257 if (seen.contains(u))
3258 continue;
3259 seen.insert(u);
3260 unique_cycle.append(u);
3261 }
3262
3263 if (unique_cycle.is_empty())
3264 continue;
3265
3267 cand.face_id = fid;
3268 cand.comp = static_cast<size_t>(comp_id[unique_cycle[0]]);
3269 cand.boundary_nodes = std::move(unique_cycle);
3270 cand.score = cand.boundary_nodes.size();
3271 face_candidates.append(std::move(cand));
3272 }
3273
3275 comp_faces.reserve(num_components);
3276 for (size_t c = 0; c < num_components; ++c)
3277 comp_faces.append(Array<size_t>());
3278
3279 for (size_t i = 0; i < face_candidates.size(); ++i)
3280 comp_faces[face_candidates[i].comp].append(i);
3281
3282 for (size_t c = 0; c < num_components; ++c)
3283 {
3284 auto & faces = comp_faces[c];
3285 for (size_t i = 1; i < faces.size(); ++i)
3286 {
3287 const size_t key = faces[i];
3288 size_t j = i;
3289 while (j > 0
3290 and face_candidates[key].score > face_candidates[faces[j - 1]].score)
3291 {
3292 faces[j] = faces[j - 1];
3293 --j;
3294 }
3295 faces[j] = key;
3296 }
3297 }
3298
3299 auto count_crossings = [&](const size_t comp,
3300 const Array<double> & px,
3301 const Array<double> & py) -> size_t
3302 {
3303 size_t total = 0;
3304 const auto & ce = comp_edge_ids[comp];
3305 for (size_t i = 0; i < ce.size(); ++i)
3306 {
3307 const auto & e1 = edges[ce[i]];
3308 for (size_t j = i + 1; j < ce.size(); ++j)
3309 {
3310 const auto & e2 = edges[ce[j]];
3311 if (e1.u == e2.u or e1.u == e2.v
3312 or e1.v == e2.u or e1.v == e2.v)
3313 continue;
3314
3316 px[e1.u], py[e1.u], px[e1.v], py[e1.v],
3317 px[e2.u], py[e2.u], px[e2.v], py[e2.v]))
3318 ++total;
3319 }
3320 }
3321 return total;
3322 };
3323
3324 auto build_component_layout = [&](const size_t comp,
3325 const Array<size_t> & boundary,
3326 Array<double> & px,
3327 Array<double> & py,
3328 size_t & iterations) -> void
3329 {
3330 const auto & cnodes = comp_nodes[comp];
3331 Array<char> fixed(n, static_cast<char>(0));
3332
3333 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3334 {
3335 const size_t u = it.get_curr_ne();
3336 px[u] = 0;
3337 py[u] = 0;
3338 }
3339
3340 Array<size_t> use_boundary = boundary;
3341 if (use_boundary.size() < 3 and cnodes.size() >= 1)
3342 {
3344 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3345 use_boundary.append(it.get_curr_ne());
3346 }
3347
3348 const double scale = std::max(1.0, std::sqrt(static_cast<double>(cnodes.size())));
3349 const double radius = options.outer_face_radius * scale;
3350
3351 if (use_boundary.is_empty())
3352 {
3353 iterations = 0;
3354 return;
3355 }
3356
3357 for (size_t i = 0; i < use_boundary.size(); ++i)
3358 {
3359 const size_t u = use_boundary[i];
3360 const double ang = (2.0 * Pi * static_cast<double>(i))
3361 / static_cast<double>(use_boundary.size());
3362 px[u] = radius * std::cos(ang);
3363 py[u] = radius * std::sin(ang);
3364 fixed[u] = 1;
3365 }
3366
3367 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3368 {
3369 const size_t u = it.get_curr_ne();
3370 if (fixed[u])
3371 continue;
3372
3373 double sx = 0;
3374 double sy = 0;
3375 size_t cnt = 0;
3376 for (typename Array<size_t>::Iterator nt(order[u]); nt.has_curr(); nt.next_ne())
3377 {
3378 const size_t v = nt.get_curr_ne();
3379 if (comp_id[v] != static_cast<long>(comp))
3380 continue;
3381 sx += px[v];
3382 sy += py[v];
3383 ++cnt;
3384 }
3385
3386 if (cnt == 0)
3387 {
3388 px[u] = 0.01 * static_cast<double>(u + 1);
3389 py[u] = -0.01 * static_cast<double>(u + 1);
3390 }
3391 else
3392 {
3393 px[u] = sx / static_cast<double>(cnt);
3394 py[u] = sy / static_cast<double>(cnt);
3395 }
3396 }
3397
3398 iterations = 0;
3399 for (size_t iter = 0; iter < options.max_relaxation_iterations; ++iter)
3400 {
3401 ++iterations;
3402 double max_delta = 0;
3403
3404 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3405 {
3406 const size_t u = it.get_curr_ne();
3407 if (fixed[u])
3408 continue;
3409
3410 double sx = 0;
3411 double sy = 0;
3412 size_t cnt = 0;
3413 for (typename Array<size_t>::Iterator nt(order[u]); nt.has_curr(); nt.next_ne())
3414 {
3415 const size_t v = nt.get_curr_ne();
3416 if (comp_id[v] != static_cast<long>(comp))
3417 continue;
3418 sx += px[v];
3419 sy += py[v];
3420 ++cnt;
3421 }
3422
3423 if (cnt == 0)
3424 continue;
3425
3426 const double nx = sx / static_cast<double>(cnt);
3427 const double ny = sy / static_cast<double>(cnt);
3428 const double dx = nx - px[u];
3429 const double dy = ny - py[u];
3430 const double delta = std::sqrt(dx * dx + dy * dy);
3431 if (delta > max_delta)
3432 max_delta = delta;
3433
3434 px[u] = nx;
3435 py[u] = ny;
3436 }
3437
3438 if (max_delta <= options.relaxation_tolerance)
3439 break;
3440 }
3441 };
3442
3443 Array<double> gx(n, 0.0);
3444 Array<double> gy(n, 0.0);
3445 double offset_x = 0.0;
3446
3447 for (size_t c = 0; c < num_components; ++c)
3448 {
3450
3451 if (options.preferred_outer_face != Null and options.preferred_outer_face < md.faces.size())
3452 {
3453 for (size_t i = 0; i < candidates.size(); ++i)
3454 if (face_candidates[candidates[i]].face_id == options.preferred_outer_face)
3455 {
3456 const size_t fav = candidates[i];
3457 for (size_t j = i; j > 0; --j)
3458 candidates[j] = candidates[j - 1];
3459 candidates[0] = fav;
3460 break;
3461 }
3462 }
3463
3464 size_t num_candidate_evals = 0;
3465 bool has_best = false;
3466 size_t best_crossings = std::numeric_limits<size_t>::max();
3467 size_t best_iters = 0;
3468 size_t best_face_id = Null;
3469 Array<double> best_x(n, 0.0);
3470 Array<double> best_y(n, 0.0);
3471
3472 auto evaluate_boundary = [&](const Array<size_t> & boundary,
3473 const size_t face_id) -> bool
3474 {
3475 if (num_candidate_evals >= options.max_outer_faces_to_try)
3476 {
3477 drawing.drawing_search_truncated = true;
3478 return false;
3479 }
3480
3482
3483 Array<double> cx(n, 0.0);
3484 Array<double> cy(n, 0.0);
3485 size_t iters = 0;
3486 build_component_layout(c, boundary, cx, cy, iters);
3487
3488 const size_t crossings = options.validate_crossings
3489 ? count_crossings(c, cx, cy)
3490 : 0;
3491
3494 {
3495 has_best = true;
3497 best_iters = iters;
3499 best_x = std::move(cx);
3500 best_y = std::move(cy);
3501 }
3502
3503 return crossings == 0;
3504 };
3505
3506 for (typename Array<size_t>::Iterator fit(candidates); fit.has_curr(); fit.next_ne())
3507 {
3508 const size_t idx = fit.get_curr_ne();
3511 break;
3512 }
3513
3514 if (not has_best)
3515 {
3517 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3518 fallback.append(it.get_curr_ne());
3520 }
3521
3523 << "unable to build component drawing candidate";
3524
3525 double min_x = std::numeric_limits<double>::max();
3526 double max_x = -std::numeric_limits<double>::max();
3527 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3528 {
3529 const size_t u = it.get_curr_ne();
3530 min_x = std::min(min_x, best_x[u]);
3531 max_x = std::max(max_x, best_x[u]);
3532 }
3533
3534 const double shift_x = offset_x - min_x;
3535 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3536 {
3537 const size_t u = it.get_curr_ne();
3538 gx[u] = best_x[u] + shift_x;
3539 gy[u] = best_y[u];
3540 }
3541
3542 offset_x += (max_x - min_x) + options.component_spacing;
3543 drawing.crossing_count += best_crossings;
3544 drawing.relaxation_iterations += best_iters;
3545
3546 if (num_components == 1)
3547 drawing.chosen_outer_face = best_face_id;
3548 }
3549
3550 drawing.node_positions.reserve(n);
3551 for (size_t i = 0; i < n; ++i)
3552 {
3554 p.node = idx_to_node[i];
3555 p.x = gx[i];
3556 p.y = gy[i];
3557 drawing.node_positions.append(p);
3558 }
3559
3560 drawing.drawing_available = true;
3561 drawing.drawing_validated_no_crossings =
3562 (not options.validate_crossings) or drawing.crossing_count == 0;
3563 return drawing;
3564 }
3565
3566
3573 template <class GT,
3575 std::string
3577 const Planarity_Test_Result<GT> & result,
3581 {
3582 using Node = typename GT::Node;
3583 using Arc = typename GT::Arc;
3584
3586 << "nonplanar_certificate_to_json() requires non-planar certificate";
3587
3591
3592 std::ostringstream out;
3593 auto newline = [&](const size_t indent)
3594 {
3595 if (not options.pretty_json)
3596 return;
3597 out << '\n';
3598 for (size_t i = 0; i < indent; ++i)
3599 out << " ";
3600 };
3601
3602 auto node_ref = [&](Node * node) -> std::string
3603 {
3604 if (node == nullptr or not node_to_id.contains(node))
3605 return "null";
3606 return "\"n" + std::to_string(node_to_id.find(node)) + "\"";
3607 };
3608
3609 auto arc_ref = [&](Arc * arc) -> std::string
3610 {
3611 if (arc == nullptr)
3612 return "null";
3615 };
3616
3617 out << "{";
3618 newline(1); out << "\"is_planar\": false,";
3619 newline(1); out << "\"certificate_available\": true,";
3620 newline(1); out << "\"certificate_type\": \""
3622 Aleph::to_string(result.certificate_type)) << "\",";
3623 newline(1); out << "\"search_truncated\": "
3624 << (result.certificate_search_truncated ? "true" : "false") << ",";
3625
3626 newline(1); out << "\"nodes\": [";
3627 for (size_t i = 0; i < nodes.size(); ++i)
3628 {
3629 if (i > 0)
3630 out << ",";
3631 newline(2);
3632 out << "{";
3633 out << "\"id\": \"n" << i << "\", ";
3634 out << "\"label\": \""
3636 out << "\"ptr\": \""
3639 out << "}";
3640 }
3641 if (not nodes.is_empty())
3642 newline(1);
3643 out << "],";
3644
3645 newline(1); out << "\"branch_nodes\": [";
3646 for (size_t i = 0; i < result.certificate_branch_nodes.size(); ++i)
3647 {
3648 if (i > 0)
3649 out << ",";
3650 if (options.pretty_json)
3651 out << ' ';
3653 }
3654 out << "],";
3655
3656 newline(1); out << "\"obstruction_edges\": [";
3657 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
3658 {
3659 const auto & e = result.certificate_obstruction_edges[i];
3660 if (i > 0)
3661 out << ",";
3662 newline(2);
3663 out << "{";
3664 out << "\"src\": " << node_ref(e.src) << ", ";
3665 out << "\"tgt\": " << node_ref(e.tgt) << ", ";
3666 out << "\"input_arc_count\": " << e.input_arcs.size() << ", ";
3667 out << "\"representative_input_arc\": " << arc_ref(e.representative_input_arc);
3668 out << "}";
3669 }
3670 if (not result.certificate_obstruction_edges.is_empty())
3671 newline(1);
3672 out << "],";
3673
3674 newline(1); out << "\"paths\": [";
3675 for (size_t i = 0; i < result.certificate_paths.size(); ++i)
3676 {
3677 const auto & path = result.certificate_paths[i];
3678 if (i > 0)
3679 out << ",";
3680 newline(2);
3681 out << "{";
3682
3683 out << "\"nodes\": [";
3684 for (size_t k = 0; k < path.nodes.size(); ++k)
3685 {
3686 if (k > 0)
3687 out << ", ";
3688 out << node_ref(path.nodes[k]);
3689 }
3690 out << "], ";
3691
3692 out << "\"edges\": [";
3693 for (size_t k = 0; k < path.edges.size(); ++k)
3694 {
3695 const auto & e = path.edges[k];
3696 if (k > 0)
3697 out << ", ";
3698 out << "{";
3699 out << "\"src\": " << node_ref(e.src) << ", ";
3700 out << "\"tgt\": " << node_ref(e.tgt) << ", ";
3701 out << "\"input_arc_count\": " << e.input_arcs.size() << ", ";
3702 out << "\"representative_input_arc\": " << arc_ref(e.representative_input_arc);
3703 out << "}";
3704 }
3705 out << "]";
3706 out << "}";
3707 }
3708 if (not result.certificate_paths.is_empty())
3709 newline(1);
3710 out << "]";
3711
3712 newline(0); out << "}";
3713 return out.str();
3714 }
3715
3716
3723 template <class GT,
3725 std::string
3727 const Planarity_Test_Result<GT> & result,
3731 {
3732 using Node = typename GT::Node;
3733
3735 << "nonplanar_certificate_to_dot() requires non-planar certificate";
3736
3740
3742 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes);
3743 it.has_curr(); it.next_ne())
3744 {
3745 Node * node = it.get_curr_ne();
3746 if (node != nullptr and node_to_id.contains(node))
3747 branch_ids.insert(node_to_id.find(node));
3748 }
3749
3750 std::ostringstream out;
3751 out << "graph NonPlanarCertificate {\n";
3752 out << " graph [labelloc=\"t\", label=\""
3754 Aleph::to_string(result.certificate_type)) << "\"];\n";
3755 out << " node [shape=circle, fontname=\"Helvetica\"];\n";
3756 out << " edge [fontname=\"Helvetica\"];\n";
3757
3758 for (size_t i = 0; i < nodes.size(); ++i)
3759 {
3760 out << " n" << i << " [label=\"n" << i << "\\n"
3762 if (branch_ids.contains(i))
3763 out << ", style=\"filled\", fillcolor=\"#fff3bf\"";
3764 out << "];\n";
3765 }
3766
3767 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
3768 {
3769 const auto & e = result.certificate_obstruction_edges[i];
3770 if (e.src == nullptr or e.tgt == nullptr
3771 or not node_to_id.contains(e.src) or not node_to_id.contains(e.tgt))
3772 continue;
3773 const size_t u = node_to_id.find(e.src);
3774 const size_t v = node_to_id.find(e.tgt);
3775 out << " n" << u << " -- n" << v
3776 << " [color=\"#d00000\", penwidth=2.2, label=\"obs x"
3777 << e.input_arcs.size() << "\"];\n";
3778 }
3779
3780 if (options.dot_highlight_paths)
3781 {
3782 static const char * kPalette[] = {
3783 "#0077b6", "#2a9d8f", "#6a4c93", "#ef476f", "#ff9f1c", "#588157"
3784 };
3785 constexpr size_t palette_size = sizeof(kPalette) / sizeof(kPalette[0]);
3786
3787 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
3788 {
3789 const auto & path = result.certificate_paths[pid];
3790 const char * color = kPalette[pid % palette_size];
3791 for (size_t k = 0; k < path.edges.size(); ++k)
3792 {
3793 const auto & e = path.edges[k];
3794 if (e.src == nullptr or e.tgt == nullptr
3795 or not node_to_id.contains(e.src) or not node_to_id.contains(e.tgt))
3796 continue;
3797 const size_t u = node_to_id.find(e.src);
3798 const size_t v = node_to_id.find(e.tgt);
3799 out << " n" << u << " -- n" << v
3800 << " [color=\"" << color
3801 << "\", style=\"dashed\", penwidth=1.4, constraint=false, label=\"p"
3802 << pid << "\"];\n";
3803 }
3804 }
3805 }
3806
3807 out << "}\n";
3808 return out.str();
3809 }
3810
3811
3819 template <class GT>
3822 {
3823 using Node = typename GT::Node;
3824
3827 report.num_branch_nodes = result.certificate_branch_nodes.size();
3828 report.num_obstruction_edges = result.certificate_obstruction_edges.size();
3829 report.num_paths = result.certificate_paths.size();
3830
3831 if (not result.has_nonplanar_certificate)
3832 return report;
3833
3837 report.num_nodes = nodes.size();
3838
3840 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes);
3841 it.has_curr(); it.next_ne())
3842 {
3843 Node * node = it.get_curr_ne();
3844 if (node == nullptr or not node_to_id.contains(node))
3845 {
3846 ++report.null_branch_nodes;
3847 continue;
3848 }
3849
3850 const size_t id = node_to_id.find(node);
3851 if (branch_ids.contains(id))
3852 ++report.duplicate_branch_nodes;
3853 else
3854 branch_ids.insert(id);
3855 }
3856
3857 auto make_edge_key = [&](Node * src, Node * tgt,
3858 planarity_detail::Edge_Key & key) -> bool
3859 {
3860 if (src == nullptr or tgt == nullptr)
3861 return false;
3862 if (not node_to_id.contains(src) or not node_to_id.contains(tgt))
3863 return false;
3864
3865 size_t u = node_to_id.find(src);
3866 size_t v = node_to_id.find(tgt);
3867 if (u > v)
3868 std::swap(u, v);
3869
3870 key.u = u;
3871 key.v = v;
3872 return true;
3873 };
3874
3876 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator
3877 it(result.certificate_obstruction_edges); it.has_curr(); it.next_ne())
3878 {
3879 const auto & e = it.get_curr_ne();
3881 if (not make_edge_key(e.src, e.tgt, key))
3882 {
3883 ++report.null_obstruction_edge_endpoints;
3884 continue;
3885 }
3886 obstruction_keys.insert(key);
3887 }
3888
3889 for (typename Array<typename Planarity_Test_Result<GT>::Path_Witness>::Iterator
3890 pit(result.certificate_paths); pit.has_curr(); pit.next_ne())
3891 {
3892 const auto & path = pit.get_curr_ne();
3893
3894 for (typename Array<Node *>::Iterator nit(path.nodes); nit.has_curr(); nit.next_ne())
3895 if (nit.get_curr_ne() == nullptr)
3896 ++report.null_path_nodes;
3897
3898 const size_t expected_nodes = path.edges.size() + (path.nodes.is_empty() ? 0 : 1);
3899 if (path.nodes.size() != expected_nodes)
3900 ++report.path_node_edge_length_mismatch;
3901
3902 for (size_t i = 0; i < path.edges.size(); ++i)
3903 {
3904 const auto & e = path.edges[i];
3905 if (e.src == nullptr or e.tgt == nullptr)
3906 {
3907 ++report.null_path_edge_endpoints;
3908 continue;
3909 }
3910
3911 if (i + 1 >= path.nodes.size()
3912 or path.nodes[i] != e.src
3913 or path.nodes[i + 1] != e.tgt)
3914 ++report.path_edge_endpoint_mismatch;
3915
3917 if (not make_edge_key(e.src, e.tgt, key))
3918 {
3919 ++report.null_path_edge_endpoints;
3920 continue;
3921 }
3922
3923 if (not obstruction_keys.contains(key))
3924 ++report.path_edge_not_in_obstruction;
3925 }
3926 }
3927
3929 ++report.kuratowski_shape_mismatch;
3931 {
3932 if (result.certificate_branch_nodes.size() != 5
3933 or result.certificate_paths.is_empty())
3934 ++report.kuratowski_shape_mismatch;
3935 }
3937 {
3938 if (result.certificate_branch_nodes.size() != 6
3939 or result.certificate_paths.is_empty())
3940 ++report.kuratowski_shape_mismatch;
3941 }
3942
3943 report.is_valid =
3944 report.has_certificate
3945 and report.null_branch_nodes == 0
3946 and report.duplicate_branch_nodes == 0
3947 and report.null_obstruction_edge_endpoints == 0
3948 and report.null_path_nodes == 0
3949 and report.null_path_edge_endpoints == 0
3950 and report.path_node_edge_length_mismatch == 0
3951 and report.path_edge_endpoint_mismatch == 0
3952 and report.path_edge_not_in_obstruction == 0
3953 and report.kuratowski_shape_mismatch == 0;
3954
3955 return report;
3956 }
3957
3958
3963 template <class GT>
3964 bool
3966 {
3967 return validate_nonplanar_certificate(result).is_valid;
3968 }
3969
3970
3977 template <class GT,
3979 std::string
3981 const Planarity_Test_Result<GT> & result,
3985 {
3986 using Node = typename GT::Node;
3987
3989 << "nonplanar_certificate_to_graphml() requires non-planar certificate";
3990
3994
3996 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes);
3997 it.has_curr(); it.next_ne())
3998 {
3999 Node * node = it.get_curr_ne();
4000 if (node != nullptr and node_to_id.contains(node))
4001 branch_ids.insert(node_to_id.find(node));
4002 }
4003
4004 auto node_ref = [&](Node * node) -> std::string
4005 {
4006 if (node == nullptr or not node_to_id.contains(node))
4007 return "";
4008 return "n" + std::to_string(node_to_id.find(node));
4009 };
4010
4011 std::ostringstream out;
4012 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
4013 out << "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">\n";
4014 out << " <key id=\"k_node_label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>\n";
4015 out << " <key id=\"k_node_branch\" for=\"node\" attr.name=\"branch\" attr.type=\"boolean\"/>\n";
4016 out << " <key id=\"k_node_ptr\" for=\"node\" attr.name=\"ptr\" attr.type=\"string\"/>\n";
4017 out << " <key id=\"k_edge_kind\" for=\"edge\" attr.name=\"kind\" attr.type=\"string\"/>\n";
4018 out << " <key id=\"k_edge_path_id\" for=\"edge\" attr.name=\"path_id\" attr.type=\"int\"/>\n";
4019 out << " <key id=\"k_edge_input_mult\" for=\"edge\" attr.name=\"input_multiplicity\" attr.type=\"int\"/>\n";
4020 out << " <graph id=\"NonPlanarCertificate\" edgedefault=\"undirected\">\n";
4021
4022 for (size_t i = 0; i < nodes.size(); ++i)
4023 {
4024 out << " <node id=\"n" << i << "\">\n";
4025 out << " <data key=\"k_node_label\">"
4027 << "</data>\n";
4028 out << " <data key=\"k_node_branch\">"
4029 << (branch_ids.contains(i) ? "true" : "false") << "</data>\n";
4030 out << " <data key=\"k_node_ptr\">"
4033 << "</data>\n";
4034 out << " </node>\n";
4035 }
4036
4037 size_t edge_id = 0;
4038 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
4039 {
4040 const auto & e = result.certificate_obstruction_edges[i];
4041 const std::string u = node_ref(e.src);
4042 const std::string v = node_ref(e.tgt);
4043 if (u.empty() or v.empty())
4044 continue;
4045
4046 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u
4047 << "\" target=\"" << v << "\">\n";
4048 out << " <data key=\"k_edge_kind\">obstruction</data>\n";
4049 out << " <data key=\"k_edge_path_id\">-1</data>\n";
4050 out << " <data key=\"k_edge_input_mult\">"
4051 << e.input_arcs.size() << "</data>\n";
4052 out << " </edge>\n";
4053 }
4054
4055 if (options.graphml_include_paths)
4056 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
4057 {
4058 const auto & path = result.certificate_paths[pid];
4059 for (size_t k = 0; k < path.edges.size(); ++k)
4060 {
4061 const auto & e = path.edges[k];
4062 const std::string u = node_ref(e.src);
4063 const std::string v = node_ref(e.tgt);
4064 if (u.empty() or v.empty())
4065 continue;
4066
4067 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u
4068 << "\" target=\"" << v << "\">\n";
4069 out << " <data key=\"k_edge_kind\">path</data>\n";
4070 out << " <data key=\"k_edge_path_id\">"
4071 << pid << "</data>\n";
4072 out << " <data key=\"k_edge_input_mult\">"
4073 << e.input_arcs.size() << "</data>\n";
4074 out << " </edge>\n";
4075 }
4076 }
4077
4078 out << " </graph>\n";
4079 out << "</graphml>\n";
4080 return out.str();
4081 }
4082
4083
4090 template <class GT,
4092 std::string
4094 const Planarity_Test_Result<GT> & result,
4098 {
4099 using Node = typename GT::Node;
4100
4102 << "nonplanar_certificate_to_gexf() requires non-planar certificate";
4103
4107
4109 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes);
4110 it.has_curr(); it.next_ne())
4111 {
4112 Node * node = it.get_curr_ne();
4113 if (node != nullptr and node_to_id.contains(node))
4114 branch_ids.insert(node_to_id.find(node));
4115 }
4116
4117 auto node_ref = [&](Node * node) -> std::string
4118 {
4119 if (node == nullptr or not node_to_id.contains(node))
4120 return "";
4121 return "n" + std::to_string(node_to_id.find(node));
4122 };
4123
4124 std::ostringstream out;
4125 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
4126 out << "<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n";
4127 out << " <meta lastmodifieddate=\"2026-02-22\">\n";
4128 out << " <creator>Aleph Planarity_Test.H</creator>\n";
4129 out << " <description>Non-planar certificate</description>\n";
4130 out << " </meta>\n";
4131 out << " <graph mode=\"static\" defaultedgetype=\"undirected\">\n";
4132 out << " <attributes class=\"node\">\n";
4133 out << " <attribute id=\"n_branch\" title=\"branch\" type=\"boolean\"/>\n";
4134 out << " <attribute id=\"n_ptr\" title=\"ptr\" type=\"string\"/>\n";
4135 out << " </attributes>\n";
4136 out << " <attributes class=\"edge\">\n";
4137 out << " <attribute id=\"e_kind\" title=\"kind\" type=\"string\"/>\n";
4138 out << " <attribute id=\"e_path_id\" title=\"path_id\" type=\"integer\"/>\n";
4139 out << " <attribute id=\"e_input_mult\" title=\"input_multiplicity\" type=\"integer\"/>\n";
4140 out << " </attributes>\n";
4141 out << " <nodes>\n";
4142
4143 for (size_t i = 0; i < nodes.size(); ++i)
4144 {
4145 out << " <node id=\"n" << i << "\" label=\""
4147 << "\">\n";
4148 out << " <attvalues>\n";
4149 out << " <attvalue for=\"n_branch\" value=\""
4150 << (branch_ids.contains(i) ? "true" : "false") << "\"/>\n";
4151 out << " <attvalue for=\"n_ptr\" value=\""
4154 << "\"/>\n";
4155 out << " </attvalues>\n";
4156 out << " </node>\n";
4157 }
4158
4159 out << " </nodes>\n";
4160 out << " <edges>\n";
4161
4162 size_t edge_id = 0;
4163 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
4164 {
4165 const auto & e = result.certificate_obstruction_edges[i];
4166 const std::string u = node_ref(e.src);
4167 const std::string v = node_ref(e.tgt);
4168 if (u.empty() or v.empty())
4169 continue;
4170
4171 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u
4172 << "\" target=\"" << v << "\">\n";
4173 out << " <attvalues>\n";
4174 out << " <attvalue for=\"e_kind\" value=\"obstruction\"/>\n";
4175 out << " <attvalue for=\"e_path_id\" value=\"-1\"/>\n";
4176 out << " <attvalue for=\"e_input_mult\" value=\""
4177 << e.input_arcs.size() << "\"/>\n";
4178 out << " </attvalues>\n";
4179 out << " </edge>\n";
4180 }
4181
4182 if (options.gexf_include_paths)
4183 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
4184 {
4185 const auto & path = result.certificate_paths[pid];
4186 for (size_t k = 0; k < path.edges.size(); ++k)
4187 {
4188 const auto & e = path.edges[k];
4189 const std::string u = node_ref(e.src);
4190 const std::string v = node_ref(e.tgt);
4191 if (u.empty() or v.empty())
4192 continue;
4193
4194 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u
4195 << "\" target=\"" << v << "\">\n";
4196 out << " <attvalues>\n";
4197 out << " <attvalue for=\"e_kind\" value=\"path\"/>\n";
4198 out << " <attvalue for=\"e_path_id\" value=\""
4199 << pid << "\"/>\n";
4200 out << " <attvalue for=\"e_input_mult\" value=\""
4201 << e.input_arcs.size() << "\"/>\n";
4202 out << " </attvalues>\n";
4203 out << " </edge>\n";
4204 }
4205 }
4206
4207 out << " </edges>\n";
4208 out << " </graph>\n";
4209 out << "</gexf>\n";
4210 return out.str();
4211 }
4212
4213
4218 template <class GT,
4219 class SA = Dft_Show_Arc<GT>>
4221 {
4224
4225 public:
4226 explicit Planarity_Test(SA sa = SA(),
4228 : sa_(std::move(sa)),
4229 options_(std::move(options))
4230 {
4231 // empty
4232 }
4233
4235 operator()(const GT & g) const
4236 {
4238 }
4239 };
4240
4241
4246 template <class GT,
4247 class SA = Dft_Show_Arc<GT>>
4249 {
4252
4253 public:
4254 explicit Is_Planar_Graph(SA sa = SA(),
4256 : sa_(std::move(sa)),
4257 options_(std::move(options))
4258 {
4259 // empty
4260 }
4261
4262 bool operator()(const GT & g) const
4263 {
4265 }
4266 };
4267} // namespace Aleph
4268
4269# endif // PLANARITY_TEST_H
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
Definition ah-errors.H:250
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:266
WeightedDigraph::Node Node
WeightedDigraph::Arc Arc
List_Graph< Graph_Node< Node_Info >, Graph_Arc< Arc_Info > > GT
long double w
Definition btreepic.C:153
bool has_curr() const noexcept
Check if there is a current valid item.
Definition array_it.H:219
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:351
void empty() noexcept
Empties the container.
Definition tpl_array.H:336
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:348
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:245
T & get_last() noexcept
return a modifiable reference to the last element.
Definition tpl_array.H:366
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:315
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3854
Generic key-value map implemented on top of a binary search tree.
Pair * insert(const Key &key, const Data &data)
Insert a key-value pair.
bool contains(const Key &key) const noexcept
Data & find(const Key &key)
Find the value associated with key.
Dynamic set backed by balanced binary search trees with automatic memory management.
const size_t & size() const
Returns the cardinality of the set.
Key * insert(const Key &key)
Inserts a key into the dynamic set.
Key & find(const Key &key) const
Returns a modifiable reference to an element within the set.
void next_ne() noexcept
Advances the iterator to the next filtered element (noexcept version).
Functor wrapper for is_planar_graph().
Is_Planar_Graph(SA sa=SA(), Planarity_Test_Options options=Planarity_Test_Options())
bool operator()(const GT &g) const
Planarity_Test_Options options_
Graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:428
Filtered iterator on the nodes of a graph.
Definition tpl_graph.H:1206
Functor wrapper for planarity_test().
Planarity_Test_Options options_
Planarity_Test(SA sa=SA(), Planarity_Test_Options options=Planarity_Test_Options())
Planarity_Test_Result< GT > operator()(const GT &g) const
bool classify_k5(const Array< size_t > &branches, const Array< Compressed_Path > &paths) const
size_t other_endpoint(const size_t edge_id, const size_t x) const
LR_Planarity_Checker(const GT &g, SA sa, Planarity_Test_Options options=Planarity_Test_Options())
void generate_permutations(Array< size_t > &tail, const size_t pos, const size_t first, Array< Array< size_t > > &out) const
size_t add_oriented_edge(const size_t src, const size_t tgt)
bool simple_edges_are_planar(const Array< Simple_Edge > &simple_edges) const
static size_t factorial_bounded(const size_t n, const size_t cap)
bool compute_faces_from_rotation(const Array< Array< size_t > > &order, const size_t isolated_vertices, const size_t num_components, Array< Array< size_t > > &face_idx, size_t &global_faces, const bool enforce_euler=true) const
bool conflicting(const Interval &i, const size_t edge_id) const
void fill_certificate_paths(const Array< size_t > &branches, const Array< Compressed_Path > &paths)
static size_t find_in_array(const Array< size_t > &a, const size_t value)
void fill_embedding_result(const Array< Array< size_t > > &order, const Array< Array< size_t > > &face_idx, const size_t global_faces, const bool is_lr_linear)
size_t find_simplified_edge_id(const size_t u, const size_t v) const
bool classify_k33(const Array< size_t > &branches, const Array< Compressed_Path > &paths) const
Planarity_Test_Result< GT >::Edge_Witness make_edge_witness(const size_t u, const size_t v) const
static void sort_size_t_array(Array< size_t > &a)
long lowest(const Conflict_Pair &p) const
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:737
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:695
bool is_digraph() const noexcept
Return true if the graph this is directed.
Definition graph-dry.H:657
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:743
Key * append(const Key &key)
Alias for insert() (copy version).
Definition hashDry.H:389
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
__gmp_expr< T, __gmp_binary_expr< __gmp_expr< T, U >, unsigned long int, __gmp_root_function > > root(const __gmp_expr< T, U > &expr, unsigned long int l)
Definition gmpfrxx.h:4060
static constexpr size_t palette_size()
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
DynArray< Graph::Arc * > arcs
Definition graphpic.C:408
std::string nonplanar_certificate_to_gexf(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as GEXF.
Planar_Geometric_Drawing< GT > planar_geometric_drawing(const Planarity_Test_Result< GT > &result, const Planar_Geometric_Drawing_Options &options=Planar_Geometric_Drawing_Options())
Build embedding-aware 2D node coordinates from a planar result.
Planarity_Test_Result< GT > planarity_test(const GT &g, SA sa=SA(), const Planarity_Test_Options &options=Planarity_Test_Options())
Execute planarity test on an Aleph graph.
Planar_Dual_Metadata< GT > planar_dual_metadata(const Planarity_Test_Result< GT > &result)
Build face and dual-edge metadata from a planar embedding result.
std::string nonplanar_certificate_to_dot(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as GraphViz DOT.
bool is_planar_graph(const GT &g, SA sa=SA(), const Planarity_Test_Options &options=Planarity_Test_Options())
Convenience boolean API for planarity.
DGT build_planar_dual_graph(const Planar_Dual_Metadata< GT > &md)
Build an Aleph dual graph from face/dual metadata.
std::string nonplanar_certificate_to_graphml(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as GraphML.
Planarity_Certificate_Type
Non-planarity witness classification.
std::string nonplanar_certificate_to_json(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as JSON.
NonPlanar_Certificate_Validation< GT > validate_nonplanar_certificate(const Planarity_Test_Result< GT > &result)
Validate structural consistency of a non-planar certificate.
bool nonplanar_certificate_is_valid(const Planarity_Test_Result< GT > &result)
Convenience predicate over validate_nonplanar_certificate().
void collect_certificate_nodes(const Planarity_Test_Result< GT > &result, Array< typename GT::Node * > &nodes, DynMapTree< typename GT::Node *, size_t > &node_to_id)
std::string json_escape_string(const std::string &s)
std::string dot_escape_string(const std::string &s)
bool pair_empty(const Conflict_Pair &p) noexcept
bool interval_empty(const Interval &i) noexcept
double orient2d(const double ax, const double ay, const double bx, const double by, const double cx, const double cy) noexcept
std::string pointer_to_string(const PtrT ptr)
bool segments_properly_intersect(const double ax, const double ay, const double bx, const double by, const double cx, const double cy, const double dx, const double dy) noexcept
std::string xml_escape_string(const std::string &s)
static constexpr size_t Null_Edge
bool bounding_boxes_intersect(const double ax, const double ay, const double bx, const double by, const double cx, const double cy, const double dx, const double dy) noexcept
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Itor1 search(Itor1 beg, const Itor1 &end, Itor2 searchBeg, const Itor2 &searchEnd, BinaryPredicate op=BinaryPredicate())
Search for a subrange within a range.
Definition ahAlgo.H:309
std::string to_string(const time_t t, const std::string &format)
Format a time_t value into a string using format.
Definition ah-date.H:140
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
static long & color(typename GT::Node *p)
STL namespace.
static struct argp_option options[]
Definition ntreepic.C:1886
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1164
Iterator on the items of an array.
Definition tpl_array.H:470
Default node labeler for certificate export.
std::string operator()(typename GT::Node *node) const
Default filter for filtered iterators on arcs.
Definition tpl_graph.H:1000
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Export options for non-planar certificate serializers.
bool dot_highlight_paths
Include path overlays in DOT output.
bool pretty_json
Pretty-print JSON with indentation/newlines.
bool gexf_include_paths
Include path overlays in GEXF output.
bool graphml_include_paths
Include path overlays in GraphML output.
Structural validation report for non-planar certificates.
Represents a missing value.
Dual-edge payload linked to a primal simplified edge.
Metadata extracted from a planar embedding for face/dual analysis.
Array< Planar_Dual_Edge_Info< GT > > dual_edges
Array< Face_Boundary > faces
Array< Array< size_t > > face_adjacency
Options for embedding-aware 2D drawing extraction.
size_t max_relaxation_iterations
Max relaxation iterations per candidate.
double outer_face_radius
Base radius for boundary-node placement on outer-face polygon.
double component_spacing
Horizontal gap between disconnected component drawings.
double relaxation_tolerance
Relaxation stop threshold in max per-node movement.
size_t preferred_outer_face
Preferred outer-face index (component-local metadata indexing).
size_t max_outer_faces_to_try
Max outer-face candidates evaluated per component.
bool validate_crossings
If true, evaluates straight-edge crossings and keeps best candidate.
Embedding-aware geometric drawing result.
Array< Node_Position > node_positions
Configuration for planarity test optional outputs.
size_t certificate_max_branch_nodes_search
Skip exact Kuratowski pattern search when branch-core exceeds this size.
bool embedding_prefer_lr_linear
Try LR-first embedding construction (with bounded LR-local repair) before any exhaustive fallback.
size_t certificate_max_reduction_passes
Max global passes in witness edge-reduction loop.
size_t certificate_max_edges
Skip witness extraction when simplified graph has more than this many edges.
bool embedding_allow_bruteforce_fallback
Allow exact embedding fallback if LR-constructive embedding fails validation.
bool compute_embedding
If true and graph is planar, tries to compute combinatorial embedding.
bool embedding_validate_with_euler
Validate candidate rotation system with Euler face check.
size_t embedding_max_combinations
Max evaluations in embedding repair/search phases.
bool compute_nonplanar_certificate
If true and graph is non-planar, tries to extract witness.
Array< Node * > cw_neighbors
Node * node
Result of planarity testing.
Planarity_Certificate_Type certificate_type
Array< Rotation_Entry > embedding_rotation
Array< Array< Node * > > embedding_faces
size_t num_nodes
Number of graph nodes.
size_t simplified_num_nodes
Number of nodes in simplified graph.
size_t num_input_arcs
Number of input arcs passing SA filter.
Array< Node * > certificate_branch_nodes
size_t simplified_num_edges
Number of edges in simplified graph.
size_t ignored_parallel_arcs
Number of collapsed parallel arcs.
bool failed_euler_bound
True if rejected by Euler necessary bound.
Array< Edge_Witness > certificate_obstruction_edges
bool is_planar
True iff planar.
bool input_is_digraph
True if original graph was directed.
size_t ignored_loops
Number of ignored self-loops.
Array< Path_Witness > certificate_paths
bool operator==(const Conflict_Pair &other) const noexcept=default
bool operator<(const Dart_Key &other) const noexcept
bool operator<(const Edge_Key &other) const noexcept
bool operator==(const Interval &other) const noexcept=default
TestDigraph::Node * add_node(TestDigraph &g, int value)
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
ValueArg< size_t > seed
Definition testHash.C:48
static int * k
Dynamic array container with automatic resizing.
Dynamic key-value map based on balanced binary search trees.
Dynamic set implementations based on balanced binary search trees.
Generic graph and digraph implementations.