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
87#ifndef PLANARITY_TEST_H
88#define PLANARITY_TEST_H
89
90# include <ah-graph-concepts.H>
91
92#include <algorithm>
93#include <cmath>
94#include <cstddef>
95#include <limits>
96#include <sstream>
97#include <string>
98#include <utility>
99
100#include <ah-errors.H>
101#include <tpl_array.H>
102#include <tpl_dynMapTree.H>
103#include <tpl_dynSetTree.H>
104#include <tpl_graph.H>
105
106namespace Aleph {
118
123inline 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
149{
156 bool compute_embedding = false;
157
162
169
174
180
185
190
195
199 size_t certificate_max_reduction_passes = std::numeric_limits<size_t>::max();
200};
201
212template <AlephGraph GT>
272
277template <AlephGraph GT>
279{
280 using Node = typename GT::Node;
281
282 size_t face_a = 0;
283 size_t face_b = 0;
284 Node *primal_src = nullptr;
285 Node *primal_tgt = nullptr;
286};
287
295template <AlephGraph GT>
322
328{
332 size_t preferred_outer_face = std::numeric_limits<size_t>::max();
333
336
339
341 double relaxation_tolerance = 1e-10;
342
344 double outer_face_radius = 1.0;
345
347 double component_spacing = 3.0;
348
351};
352
357template <AlephGraph GT>
359{
360 using Node = typename GT::Node;
361
363 {
364 Node *node = nullptr;
365 double x = 0;
366 double y = 0;
367 };
368
369 bool has_embedding = false;
370 bool drawing_available = false;
373
374 size_t chosen_outer_face = std::numeric_limits<size_t>::max();
376 size_t crossing_count = 0;
377 size_t num_components = 0;
378
380};
381
387{
389 bool pretty_json = true;
390
393
396
399};
400
405template <AlephGraph GT>
426
433template <class GT>
435{
436 std::string operator()(typename GT::Node *node) const
437 {
438 std::ostringstream out;
439 out << node;
440 return out.str();
441 }
442};
443
444template <class GT>
447
448namespace planarity_detail {
449
450static constexpr size_t Null_Edge = std::numeric_limits<size_t>::max();
451
453{
454 size_t u = 0;
455 size_t v = 0;
456
457 bool operator<(const Edge_Key &other) const noexcept
458 {
459 if (u < other.u)
460 return true;
461 if (other.u < u)
462 return false;
463 return v < other.v;
464 }
465};
466
468{
469 size_t u = 0;
470 size_t v = 0;
471};
472
474{
475 size_t low = Null_Edge;
476 size_t high = Null_Edge;
477
478 bool operator==(const Interval &other) const noexcept = default;
479};
480
482{
485
486 bool operator == (const Conflict_Pair &other) const noexcept = default;
487};
488
489inline bool interval_empty(const Interval &i) noexcept
490{
491 return i.low == Null_Edge;
492}
493
494inline bool pair_empty(const Conflict_Pair &p) noexcept
495{
496 return interval_empty(p.left) and interval_empty(p.right);
497}
498
500{
501 size_t u = 0;
502 size_t v = 0;
503
504 bool operator<(const Dart_Key &other) const noexcept
505 {
506 if (u < other.u)
507 return true;
508 if (other.u < u)
509 return false;
510 return v < other.v;
511 }
512};
513
515{
516 size_t u = Null_Edge;
517 size_t v = Null_Edge;
519};
520
521inline std::string json_escape_string(const std::string &s)
522{
523 std::ostringstream out;
524 for (const char i : s)
525 switch (const auto c = static_cast<unsigned char>(i))
526 {
527 case '\"':
528 out << "\\\"";
529 break;
530 case '\\':
531 out << "\\\\";
532 break;
533 case '\b':
534 out << "\\b";
535 break;
536 case '\f':
537 out << "\\f";
538 break;
539 case '\n':
540 out << "\\n";
541 break;
542 case '\r':
543 out << "\\r";
544 break;
545 case '\t':
546 out << "\\t";
547 break;
548 default:
549 if (c < 0x20)
550 {
551 out << "\\u00";
552 const char *hex = "0123456789abcdef";
553 out << hex[(c >> 4) & 0xF] << hex[c & 0xF];
554 }
555 else
557 break;
558 }
559
560 return out.str();
561}
562
563inline std::string dot_escape_string(const std::string &s)
564{
565 std::ostringstream out;
566 for (const char c : s)
567 if (c == '\"' or c == '\\')
568 out << '\\' << c;
569 else if (c == '\n')
570 out << "\\n";
571 else
572 out << c;
573 return out.str();
574}
575
576inline std::string xml_escape_string(const std::string &s)
577{
578 std::ostringstream out;
579 for (const char i : s)
580 switch (const auto c = static_cast<unsigned char>(i))
581 {
582 case '&':
583 out << "&amp;";
584 break;
585 case '<':
586 out << "&lt;";
587 break;
588 case '>':
589 out << "&gt;";
590 break;
591 case '\"':
592 out << "&quot;";
593 break;
594 case '\'':
595 out << "&apos;";
596 break;
597 default:
598 if (c < 0x20 and c != '\n' and c != '\r' and c != '\t')
599 {
600 const char *hex = "0123456789ABCDEF";
601 out << "&#x";
602 out << hex[(c >> 4) & 0xF] << hex[c & 0xF] << ";";
603 }
604 else
606 break;
607 }
608 return out.str();
609}
610
611template <class PtrT>
612std::string pointer_to_string(const PtrT ptr)
613{
614 std::ostringstream out;
615 out << ptr;
616 return out.str();
617}
618
619template <AlephGraph GT>
623{
624 using Node = typename GT::Node;
625
626 nodes.empty();
627 node_to_id.empty();
628
629 auto add_node = [&](Node *node)
630 {
631 if (node == nullptr or node_to_id.contains(node))
632 return;
633 const size_t id = nodes.size();
634 node_to_id.insert(node, id);
635 nodes.append(node);
636 };
637
638 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes); it.has_curr();
639 it.next_ne())
640 add_node(it.get_curr_ne());
641
642 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator it(
644 it.has_curr(); it.next_ne())
645 {
646 add_node(it.get_curr_ne().src);
647 add_node(it.get_curr_ne().tgt);
648 }
649
650 for (typename Array<typename Planarity_Test_Result<GT>::Path_Witness>::Iterator pit(
651 result.certificate_paths);
652 pit.has_curr(); pit.next_ne())
653 {
654 const auto &path = pit.get_curr_ne();
655 for (typename Array<Node *>::Iterator nit(path.nodes); nit.has_curr(); nit.next_ne())
656 add_node(nit.get_curr_ne());
657
658 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator eit(path.edges);
659 eit.has_curr(); eit.next_ne())
660 {
661 add_node(eit.get_curr_ne().src);
662 add_node(eit.get_curr_ne().tgt);
663 }
664 }
665}
666
667inline double orient2d(const double ax,
668 const double ay,
669 const double bx,
670 const double by,
671 const double cx,
672 const double cy) noexcept
673{
674 return (bx - ax) * (cy - ay) - (by - ay) * (cx - ax);
675}
676
677inline bool bounding_boxes_intersect(const double ax,
678 const double ay,
679 const double bx,
680 const double by,
681 const double cx,
682 const double cy,
683 const double dx,
684 const double dy) noexcept
685{
686 const double min_ab_x = std::min(ax, bx);
687 const double max_ab_x = std::max(ax, bx);
688 const double min_ab_y = std::min(ay, by);
689 const double max_ab_y = std::max(ay, by);
690
691 const double min_cd_x = std::min(cx, dx);
692 const double max_cd_x = std::max(cx, dx);
693 const double min_cd_y = std::min(cy, dy);
694 const double max_cd_y = std::max(cy, dy);
695
698}
699
700inline bool segments_properly_intersect(const double ax,
701 const double ay,
702 const double bx,
703 const double by,
704 const double cx,
705 const double cy,
706 const double dx,
707 const double dy) noexcept
708{
709 if (not bounding_boxes_intersect(ax, ay, bx, by, cx, cy, dx, dy))
710 return false;
711
712 constexpr double eps = 1e-12;
713 const double o1 = orient2d(ax, ay, bx, by, cx, cy);
714 const double o2 = orient2d(ax, ay, bx, by, dx, dy);
715 const double o3 = orient2d(cx, cy, dx, dy, ax, ay);
716 const double o4 = orient2d(cx, cy, dx, dy, bx, by);
717
718 // Strict intersection only (shared endpoints handled outside).
719 return (o1 * o2 < -eps) and (o3 * o4 < -eps);
720}
721
722template <AlephGraph GT, ArcFilter<GT> SA>
724{
725 using Node = typename GT::Node;
726 using Arc = typename GT::Arc;
727
728 const GT &g_;
729 SA sa_;
731
733
736
740
744
747
758
760
761 bool planar_ = true;
762
763 size_t add_oriented_edge(const size_t src, const size_t tgt)
764 {
765 const size_t id = oriented_src_.size();
768
769 side_.append(1);
770 lowpt_.append(0);
771 lowpt2_.append(0);
776
777 return id;
778 }
779
780 size_t other_endpoint(const size_t edge_id, const size_t x) const
781 {
782 const Simple_Edge &e = edges_[edge_id];
783 return e.u == x ? e.v : e.u;
784 }
785
787 {
788 for (size_t i = 1; i < a.size(); ++i)
789 {
790 const size_t key = a[i];
791 size_t j = i;
792 while (j > 0 and key < a[j - 1])
793 {
794 a[j] = a[j - 1];
795 --j;
796 }
797 a[j] = key;
798 }
799 }
800
801 static size_t factorial_bounded(const size_t n, const size_t cap)
802 {
803 if (n <= 1)
804 return 1;
805
806 if (cap == 0)
807 return 1;
808
809 size_t ret = 1;
810 for (size_t i = 2; i <= n; ++i)
811 {
812 if (ret > cap / i)
813 return cap + 1;
814 ret *= i;
815 }
816
817 return ret;
818 }
819
820 size_t count_components() const
821 {
822 if (result_.simplified_num_nodes == 0)
823 return 0;
824
825 Array<char> vis(result_.simplified_num_nodes, 0);
826 size_t comps = 0;
827
828 for (size_t s = 0; s < result_.simplified_num_nodes; ++s)
829 {
830 if (vis[s])
831 continue;
832
833 ++comps;
834 vis[s] = 1;
835
836 Array<size_t> stack;
837 stack.append(s);
838
839 while (not stack.is_empty())
840 {
841 const size_t v = stack.remove_last();
842
843 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
844 {
845 const size_t eid = it.get_curr_ne();
846 const size_t w = other_endpoint(eid, v);
847 if (vis[w])
848 continue;
849
850 vis[w] = 1;
851 stack.append(w);
852 }
853 }
854 }
855
856 return comps;
857 }
858
859 static size_t find_in_array(const Array<size_t> &a, const size_t value)
860 {
861 for (size_t i = 0; i < a.size(); ++i)
862 if (a[i] == value)
863 return i;
864
865 return Null_Edge;
866 }
867
869 const size_t pos,
870 const size_t first,
872 {
873 if (pos >= tail.size())
874 {
875 Array<size_t> order;
876 order.reserve(tail.size() + 1);
877 order.append(first);
878 for (unsigned long i : tail)
879 order.append(i);
880 out.append(std::move(order));
881 return;
882 }
883
884 for (size_t i = pos; i < tail.size(); ++i)
885 {
886 std::swap(tail[pos], tail[i]);
887 generate_permutations(tail, pos + 1, first, out);
888 std::swap(tail[pos], tail[i]);
889 }
890 }
891
893 const size_t isolated_vertices,
894 const size_t num_components,
896 size_t &global_faces,
897 const bool enforce_euler = true) const
898 {
900 Array<size_t> alpha;
901 dart_src.reserve(2 * result_.simplified_num_edges);
902 alpha.reserve(2 * result_.simplified_num_edges);
903
905
906 for (typename Array<Simple_Edge>::Iterator it(edges_); it.has_curr(); it.next_ne())
907 {
908 const Simple_Edge &e = it.get_curr_ne();
909 const size_t d1 = dart_src.size();
910
911 dart_src.append(e.u);
912 alpha.append(d1 + 1);
913 dart_id.insert(Dart_Key{e.u, e.v}, d1);
914
915 dart_src.append(e.v);
916 alpha.append(d1);
917 dart_id.insert(Dart_Key{e.v, e.u}, d1 + 1);
918 }
919
920 Array<size_t> sigma(dart_src.size(), Null_Edge);
921 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
922 {
923 const auto &ord = order[v];
924 if (ord.is_empty())
925 continue;
926
927 for (size_t i = 0; i < ord.size(); ++i)
928 {
929 const size_t to = ord[i];
930 const size_t next = ord[(i + 1) % ord.size()];
931
932 const size_t a = dart_id.find(Dart_Key{v, to});
933 const size_t b = dart_id.find(Dart_Key{v, next});
934 sigma[a] = b;
935 }
936 }
937
938 for (const unsigned long d : sigma)
939 if (d == Null_Edge)
940 return false;
941
942 face_idx.empty();
943
944 Array<char> vis(dart_src.size(), 0);
945 size_t local_faces = 0;
946 for (size_t d = 0; d < dart_src.size(); ++d)
947 {
948 if (vis[d])
949 continue;
950
951 ++local_faces;
953
954 size_t x = d;
955 while (not vis[x])
956 {
957 vis[x] = 1;
958 f.append(dart_src[x]);
959 x = sigma[alpha[x]];
960 }
961
962 face_idx.append(std::move(f));
963 }
964
965 global_faces = local_faces + isolated_vertices - (num_components == 0 ? 0 : (num_components - 1));
966
968 return true;
969
970 const long long lhs = static_cast<long long>(result_.simplified_num_nodes) -
971 static_cast<long long>(result_.simplified_num_edges) + static_cast<long long>(global_faces);
972
973 const long long rhs = static_cast<long long>(num_components) + 1;
974
975 return lhs == rhs;
976 }
977
980 const size_t global_faces,
981 const bool is_lr_linear)
982 {
983 result_.has_combinatorial_embedding = true;
984 result_.embedding_is_lr_linear = is_lr_linear;
985 result_.embedding_num_faces = global_faces;
986
987 result_.embedding_rotation.empty();
988 result_.embedding_rotation.reserve(result_.simplified_num_nodes);
989 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
990 {
992 re.node = nodes_[v];
993
994 const auto &ord = order[v];
995 re.cw_neighbors.reserve(ord.size());
996 for (typename Array<size_t>::Iterator it(ord); it.has_curr(); it.next_ne())
997 re.cw_neighbors.append(nodes_[it.get_curr_ne()]);
998
999 result_.embedding_rotation.append(std::move(re));
1000 }
1001
1002 result_.embedding_faces.empty();
1003 result_.embedding_faces.reserve(face_idx.size());
1004 for (typename Array<Array<size_t>>::Iterator fit(face_idx); fit.has_curr(); fit.next_ne())
1005 {
1006 const auto &f = fit.get_curr_ne();
1008 mapped.reserve(f.size());
1009 for (typename Array<size_t>::Iterator it(f); it.has_curr(); it.next_ne())
1010 mapped.append(nodes_[it.get_curr_ne()]);
1011 result_.embedding_faces.append(std::move(mapped));
1012 }
1013
1014 while (result_.embedding_faces.size() < result_.embedding_num_faces)
1015 result_.embedding_faces.append(Array<Node *>());
1016 }
1017
1018 bool conflicting(const Interval &i, const size_t edge_id) const
1019 {
1020 if (interval_empty(i) or i.high == Null_Edge)
1021 return false;
1022 return lowpt_[i.high] > lowpt_[edge_id];
1023 }
1024
1025 long lowest(const Conflict_Pair &p) const
1026 {
1027 const bool left_empty = interval_empty(p.left);
1028 const bool right_empty = interval_empty(p.right);
1029
1031 return std::numeric_limits<long>::max();
1032 if (left_empty)
1033 return lowpt_[p.right.low];
1034 if (right_empty)
1035 return lowpt_[p.left.low];
1036
1037 return std::min(lowpt_[p.left.low], lowpt_[p.right.low]);
1038 }
1039
1040 void orient_dfs(const size_t v)
1041 {
1042 if (not planar_)
1043 return;
1044
1045 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
1046 {
1047 const size_t uedge = it.get_curr_ne();
1049 continue;
1050
1052 const size_t w = other_endpoint(uedge, v);
1053
1054 const size_t e = add_oriented_edge(v, w);
1056 child_edges_[v].append(e);
1057
1058 lowpt_[e] = height_[v];
1059 lowpt2_[e] = height_[v];
1060
1061 if (height_[w] == -1)
1062 {
1063 parent_edge_[w] = e;
1064 height_[w] = height_[v] + 1;
1065 orient_dfs(w);
1066 }
1067 else
1068 lowpt_[e] = height_[w];
1069
1070 nesting_depth_[e] = 2 * lowpt_[e] + (lowpt2_[e] < height_[v] ? 1 : 0);
1071
1072 if (parent_edge_[v] == Null_Edge)
1073 continue;
1074
1075 const size_t pe = parent_edge_[v];
1076 if (lowpt_[e] < lowpt_[pe])
1077 {
1078 lowpt2_[pe] = std::min(lowpt_[pe], lowpt2_[e]);
1079 lowpt_[pe] = lowpt_[e];
1080 }
1081 else if (lowpt_[e] > lowpt_[pe])
1082 lowpt2_[pe] = std::min(lowpt2_[pe], lowpt_[e]);
1083 else
1084 lowpt2_[pe] = std::min(lowpt2_[pe], lowpt2_[e]);
1085 }
1086 }
1087
1088 void test_dfs(const size_t v)
1089 {
1090 if (not planar_)
1091 return;
1092
1093 const size_t e = parent_edge_[v];
1094
1095 auto &E = child_edges_[v];
1096 for (size_t i = 1; i < E.size(); ++i)
1097 {
1098 const size_t key = E[i];
1099 size_t j = i;
1100 while (j > 0 and nesting_depth_[key] < nesting_depth_[E[j - 1]])
1101 {
1102 E[j] = E[j - 1];
1103 --j;
1104 }
1105 E[j] = key;
1106 }
1107
1108 for (typename Array<size_t>::Iterator it(E); it.has_curr(); it.next_ne())
1109 {
1110 const size_t ei = it.get_curr_ne();
1112
1113 const size_t w = oriented_tgt_[ei];
1114 if (ei == parent_edge_[w])
1115 test_dfs(w);
1116 else
1117 {
1118 lowpt_edge_[ei] = ei;
1120 cp.right.low = ei;
1121 cp.right.high = ei;
1122 stack_.append(cp);
1123 }
1124
1125 if (not planar_)
1126 return;
1127
1128 if (lowpt_[ei] < height_[v])
1129 {
1130 if (const size_t first = E[0]; ei == first)
1131 {
1132 if (e != Null_Edge)
1133 lowpt_edge_[e] = lowpt_edge_[first];
1134 }
1135 else
1136 {
1137 Conflict_Pair p;
1138 do
1139 {
1140 Conflict_Pair q = stack_.get_last();
1141 (void) stack_.remove_last();
1142
1143 if (not interval_empty(q.left))
1144 std::swap(q.left, q.right);
1145
1146 if (not interval_empty(q.left))
1147 {
1148 planar_ = false;
1149 return;
1150 }
1151
1152 if (lowpt_[q.right.low] > lowpt_[e])
1153 {
1154 if (interval_empty(p.right))
1155 p.right.high = q.right.high;
1156 else
1157 ref_[p.right.low] = q.right.high;
1158
1159 p.right.low = q.right.low;
1160 }
1161 else
1162 {
1163 ref_[q.right.low] = lowpt_edge_[e];
1164 }
1165 } while (not(stack_.get_last() == stack_bottom_[ei]));
1166
1167 while (conflicting(stack_.get_last().left, ei) or
1168 conflicting(stack_.get_last().right, ei))
1169 {
1170 Conflict_Pair q = stack_.get_last();
1171 (void) stack_.remove_last();
1172
1173 if (conflicting(q.right, ei))
1174 std::swap(q.left, q.right);
1175
1176 if (conflicting(q.right, ei))
1177 {
1178 planar_ = false;
1179 return;
1180 }
1181
1182 if (p.right.low != Null_Edge)
1183 ref_[p.right.low] = q.right.high;
1184
1185 if (not interval_empty(q.right))
1186 p.right.low = q.right.low;
1187
1188 if (q.left.low != Null_Edge)
1189 side_[q.left.low] = -1;
1190
1191 if (interval_empty(p.left))
1192 p.left.high = q.left.high;
1193 else
1194 ref_[p.left.low] = q.left.high;
1195
1196 p.left.low = q.left.low;
1197 }
1198
1199 if (not pair_empty(p))
1200 stack_.append(p);
1201 }
1202 }
1203 }
1204
1205 if (e == Null_Edge)
1206 return;
1207
1208 const size_t u = oriented_src_[e];
1209 while (stack_.size() > 1 and lowest(stack_.get_last()) == height_[u])
1210 {
1211 Conflict_Pair p = stack_.remove_last();
1212 if (not interval_empty(p.left))
1213 side_[p.left.low] = -1;
1214 }
1215
1216 if (stack_.size() > 1)
1217 {
1218 Conflict_Pair p = stack_.remove_last();
1219
1220 while (p.left.high != Null_Edge and oriented_tgt_[p.left.high] == u)
1221 p.left.high = ref_[p.left.high];
1222
1223 if (p.left.high == Null_Edge and p.left.low != Null_Edge)
1224 {
1225 ref_[p.left.low] = p.right.low;
1226 side_[p.left.low] = -1;
1227 p.left.low = Null_Edge;
1228 }
1229
1230 while (p.right.high != Null_Edge and oriented_tgt_[p.right.high] == u)
1231 p.right.high = ref_[p.right.high];
1232
1233 if (p.right.high == Null_Edge and p.right.low != Null_Edge)
1234 {
1235 ref_[p.right.low] = p.left.low;
1236 p.right.low = Null_Edge;
1237 }
1238
1239 stack_.append(p);
1240 }
1241
1242 if (lowpt_[e] < height_[u])
1243 {
1244 const Conflict_Pair &top = stack_.get_last();
1245 const size_t h_left = top.left.high;
1246 const size_t h_right = top.right.high;
1247
1249 ref_[e] = h_left;
1250 else
1251 ref_[e] = h_right;
1252 }
1253 }
1254
1256 {
1257 if (result_.simplified_num_edges == 0)
1258 return false;
1259
1260 Array<long> comp_id(result_.simplified_num_nodes, -1);
1261 size_t next_comp = 0;
1262
1263 for (size_t s = 0; s < result_.simplified_num_nodes; ++s)
1264 {
1265 if (comp_id[s] != -1)
1266 continue;
1267
1268 Array<size_t> stack;
1269 stack.append(s);
1270 comp_id[s] = static_cast<long>(next_comp);
1271
1272 size_t num_vertices = 0;
1273 size_t degree_sum = 0;
1274
1275 while (not stack.is_empty())
1276 {
1277 const size_t v = stack.remove_last();
1278 ++num_vertices;
1279 degree_sum += incident_edges_[v].size();
1280
1281 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
1282 {
1283 const size_t uedge = it.get_curr_ne();
1284 const size_t w = other_endpoint(uedge, v);
1285 if (comp_id[w] != -1)
1286 continue;
1287
1288 comp_id[w] = static_cast<long>(next_comp);
1289 stack.append(w);
1290 }
1291 }
1292
1293 const size_t num_edges = degree_sum / 2;
1294 if (num_vertices >= 3 and num_edges > 3 * num_vertices - 6)
1295 return true;
1296
1297 ++next_comp;
1298 }
1299
1300 return false;
1301 }
1302
1304 {
1305 result_.num_nodes = g_.get_num_nodes();
1306 result_.input_is_digraph = g_.is_digraph();
1307
1308 nodes_.reserve(result_.num_nodes);
1309 for (Node_Iterator<GT> it(g_); it.has_curr(); it.next_ne())
1310 {
1311 Node *node = it.get_curr_ne();
1312 node_to_idx_.insert(node, nodes_.size());
1313 nodes_.append(node);
1314 }
1315
1316 incident_edges_.reserve(result_.num_nodes);
1317 child_edges_.reserve(result_.num_nodes);
1318 for (size_t i = 0; i < result_.num_nodes; ++i)
1319 {
1321 child_edges_.append(Array<size_t>());
1322 }
1323
1326
1327 for (Arc_Iterator<GT, SA> it(g_, sa_); it.has_curr(); it.next_ne())
1328 {
1329 ++result_.num_input_arcs;
1330 Arc *arc = it.get_curr_ne();
1331 const size_t src = node_to_idx_.find(g_.get_src_node(arc));
1332 const size_t tgt = node_to_idx_.find(g_.get_tgt_node(arc));
1333
1334 if (src == tgt)
1335 {
1336 ++result_.ignored_loops;
1337 continue;
1338 }
1339
1340 const size_t u = std::min(src, tgt);
1341 const size_t v = std::max(src, tgt);
1342 const Edge_Key key{u, v};
1343
1344 if (seen_edges.contains(key))
1345 {
1346 ++result_.ignored_parallel_arcs;
1347 const size_t eid = edge_to_idx.find(key);
1348 simplified_edge_input_arcs_[eid].append(arc);
1349 continue;
1350 }
1351
1352 seen_edges.insert(key);
1353 const size_t eid = edges_.size();
1354 edge_to_idx.insert(key, eid);
1355
1356 edges_.append(Simple_Edge{u, v});
1357 incident_edges_[u].append(eid);
1358 incident_edges_[v].append(eid);
1360 simplified_edge_input_arcs_[eid].append(arc);
1361 }
1362
1363 result_.simplified_num_nodes = result_.num_nodes;
1364 result_.simplified_num_edges = edges_.size();
1365 }
1366
1368 {
1369 if (result_.simplified_num_edges == 0)
1370 return true;
1371
1372 height_ = Array<long>(result_.simplified_num_nodes, -1);
1373 parent_edge_ = Array<size_t>(result_.simplified_num_nodes, Null_Edge);
1374 undirected_edge_seen_ = Array<char>(result_.simplified_num_edges, 0);
1375 undirected_to_oriented_ = Array<size_t>(result_.simplified_num_edges, Null_Edge);
1376
1377 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
1378 {
1379 if (height_[v] != -1)
1380 continue;
1381
1382 roots_.append(v);
1383 height_[v] = 0;
1384 orient_dfs(v);
1385 }
1386
1387 for (typename Array<size_t>::Iterator it(roots_); it.has_curr(); it.next_ne())
1388 {
1389 const size_t root = it.get_curr_ne();
1390 stack_.empty();
1391 stack_.append(Conflict_Pair());
1392 test_dfs(root);
1393 if (not planar_)
1394 return false;
1395 }
1396
1397 return true;
1398 }
1399
1401 {
1403
1404 Tmp_Graph tg;
1406 static_cast<typename Tmp_Graph::Node *>(nullptr));
1407
1408 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
1409 tmp_nodes[i] = tg.insert_node(i);
1410
1411 for (typename Array<Simple_Edge>::Iterator it(simple_edges); it.has_curr(); it.next_ne())
1412 {
1413 const Simple_Edge &e = it.get_curr_ne();
1414 tg.insert_arc(tmp_nodes[e.u], tmp_nodes[e.v], 1);
1415 }
1416
1419 fast_options.compute_nonplanar_certificate = false;
1420 fast_options.embedding_max_combinations = 0;
1421 fast_options.certificate_max_edges = 0;
1422 fast_options.certificate_max_reduction_passes = 0;
1423
1425 fast_options);
1426
1427 return checker.run().is_planar;
1428 }
1429
1431 {
1432 if (branches.size() != 5 or paths.size() != 10)
1433 return false;
1434
1435 Array<Array<char>> mat;
1436 mat.reserve(5);
1437 for (size_t i = 0; i < 5; ++i)
1438 mat.append(Array<char>(static_cast<size_t>(5), static_cast<char>(0)));
1439
1440 Array<size_t> deg(static_cast<size_t>(5), static_cast<size_t>(0));
1441
1442 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1443 {
1444 const Compressed_Path &p = it.get_curr_ne();
1445 const size_t iu = find_in_array(branches, p.u);
1446 const size_t iv = find_in_array(branches, p.v);
1447 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1448 return false;
1449
1450 if (mat[iu][iv])
1451 return false;
1452
1453 mat[iu][iv] = 1;
1454 mat[iv][iu] = 1;
1455 ++deg[iu];
1456 ++deg[iv];
1457 }
1458
1459 for (size_t i = 0; i < 5; ++i)
1460 {
1461 if (deg[i] != 4)
1462 return false;
1463
1464 for (size_t j = i + 1; j < 5; ++j)
1465 if (not mat[i][j])
1466 return false;
1467 }
1468
1469 return true;
1470 }
1471
1473 {
1474 if (branches.size() != 6 or paths.size() != 9)
1475 return false;
1476
1477 Array<Array<char>> mat;
1478 mat.reserve(6);
1479 for (size_t i = 0; i < 6; ++i)
1480 mat.append(Array<char>(static_cast<size_t>(6), static_cast<char>(0)));
1481
1483 adj.reserve(6);
1484 for (size_t i = 0; i < 6; ++i)
1485 adj.append(Array<size_t>());
1486
1487 Array<size_t> deg(static_cast<size_t>(6), static_cast<size_t>(0));
1488
1489 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1490 {
1491 const Compressed_Path &p = it.get_curr_ne();
1492 const size_t iu = find_in_array(branches, p.u);
1493 const size_t iv = find_in_array(branches, p.v);
1494 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1495 return false;
1496
1497 if (mat[iu][iv])
1498 return false;
1499
1500 mat[iu][iv] = 1;
1501 mat[iv][iu] = 1;
1502 adj[iu].append(iv);
1503 adj[iv].append(iu);
1504 ++deg[iu];
1505 ++deg[iv];
1506 }
1507
1508 for (size_t i = 0; i < 6; ++i)
1509 if (deg[i] != 3)
1510 return false;
1511
1512 Array<long> color(static_cast<size_t>(6), static_cast<long>(-1));
1513 for (size_t s = 0; s < 6; ++s)
1514 {
1515 if (color[s] != -1)
1516 continue;
1517
1518 color[s] = 0;
1519 Array<size_t> stack;
1520 stack.append(s);
1521
1522 while (not stack.is_empty())
1523 {
1524 const size_t u = stack.remove_last();
1525 for (typename Array<size_t>::Iterator it(adj[u]); it.has_curr(); it.next_ne())
1526 {
1527 const size_t v = it.get_curr_ne();
1528 if (color[v] == -1)
1529 {
1530 color[v] = 1 - color[u];
1531 stack.append(v);
1532 }
1533 else if (color[v] == color[u])
1534 return false;
1535 }
1536 }
1537 }
1538
1539 size_t left_count = 0;
1540 size_t right_count = 0;
1541 for (size_t i = 0; i < 6; ++i)
1542 if (color[i] == 0)
1543 ++left_count;
1544 else
1545 ++right_count;
1546
1547 if (left_count != 3 or right_count != 3)
1548 return false;
1549
1550 for (size_t i = 0; i < 6; ++i)
1551 for (size_t j = i + 1; j < 6; ++j)
1552 {
1553 if (color[i] == color[j] and mat[i][j])
1554 return false;
1555
1556 if (color[i] != color[j] and not mat[i][j])
1557 return false;
1558 }
1559
1560 return true;
1561 }
1562
1563 size_t find_simplified_edge_id(const size_t u, const size_t v) const
1564 {
1565 const size_t a = std::min(u, v);
1566 const size_t b = std::max(u, v);
1567
1568 for (size_t i = 0; i < edges_.size(); ++i)
1569 if (edges_[i].u == a and edges_[i].v == b)
1570 return i;
1571
1572 return Null_Edge;
1573 }
1574
1576 const size_t v) const
1577 {
1579 if (u < nodes_.size())
1580 w.src = nodes_[u];
1581 if (v < nodes_.size())
1582 w.tgt = nodes_[v];
1583
1584 const size_t eid = find_simplified_edge_id(u, v);
1586 return w;
1587
1588 const auto &arcs = simplified_edge_input_arcs_[eid];
1589 if (not arcs.is_empty())
1590 w.representative_input_arc = arcs[0];
1591
1592 w.input_arcs.reserve(arcs.size());
1593 for (typename Array<Arc *>::Iterator it(arcs); it.has_curr(); it.next_ne())
1594 w.input_arcs.append(it.get_curr_ne());
1595
1596 return w;
1597 }
1598
1600 {
1601 result_.certificate_branch_nodes.empty();
1602 result_.certificate_paths.empty();
1603
1604 result_.certificate_branch_nodes.reserve(branches.size());
1605 for (typename Array<size_t>::Iterator it(branches); it.has_curr(); it.next_ne())
1606 result_.certificate_branch_nodes.append(nodes_[it.get_curr_ne()]);
1607
1608 result_.certificate_paths.reserve(paths.size());
1609 for (typename Array<Compressed_Path>::Iterator it(paths); it.has_curr(); it.next_ne())
1610 {
1611 const Compressed_Path &p = it.get_curr_ne();
1614 for (typename Array<size_t>::Iterator pit(p.nodes); pit.has_curr(); pit.next_ne())
1615 witness.nodes.append(nodes_[pit.get_curr_ne()]);
1616
1617 if (p.nodes.size() >= 2)
1618 {
1619 witness.edges.reserve(p.nodes.size() - 1);
1620 for (size_t i = 1; i < p.nodes.size(); ++i)
1621 witness.edges.append(make_edge_witness(p.nodes[i - 1], p.nodes[i]));
1622 }
1623
1624 result_.certificate_paths.append(std::move(witness));
1625 }
1626 }
1627
1629 {
1630 if (result_.is_planar)
1631 return;
1632
1633 if (edges_.is_empty())
1634 return;
1635
1637 {
1638 result_.certificate_search_truncated = true;
1639 return;
1640 }
1641
1643
1645 result_.certificate_search_truncated = true;
1646 else
1647 {
1648 size_t pass_count = 0;
1649
1650 while (true)
1651 {
1652 bool changed = false;
1653 size_t i = 0;
1654
1655 while (i < witness.size())
1656 {
1658 trial.reserve(witness.size() - 1);
1659 for (size_t j = 0; j < witness.size(); ++j)
1660 if (j != i)
1661 trial.append(witness[j]);
1662
1664 ++i;
1665 else
1666 {
1667 witness = std::move(trial);
1668 changed = true;
1669 }
1670 }
1671
1672 Array<size_t> degree(result_.simplified_num_nodes, static_cast<size_t>(0));
1673 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1674 {
1675 const auto &e = it.get_curr_ne();
1676 ++degree[e.u];
1677 ++degree[e.v];
1678 }
1679
1680 size_t v = 0;
1681 while (v < result_.simplified_num_nodes)
1682 {
1683 if (degree[v] == 0)
1684 {
1685 ++v;
1686 continue;
1687 }
1688
1690 trial.reserve(witness.size());
1691 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1692 {
1693 const auto &e = it.get_curr_ne();
1694 if (e.u == v or e.v == v)
1695 continue;
1696 trial.append(e);
1697 }
1698
1699 if (trial.size() == witness.size())
1700 {
1701 ++v;
1702 continue;
1703 }
1704
1706 ++v;
1707 else
1708 {
1709 witness = std::move(trial);
1710 changed = true;
1711
1712 degree = Array<size_t>(result_.simplified_num_nodes, static_cast<size_t>(0));
1714 wit.next_ne())
1715 {
1716 const auto &e = wit.get_curr_ne();
1717 ++degree[e.u];
1718 ++degree[e.v];
1719 }
1720 v = 0;
1721 }
1722 }
1723
1724 if (not changed)
1725 break;
1726
1727 ++pass_count;
1729 {
1730 result_.certificate_search_truncated = true;
1731 break;
1732 }
1733 }
1734 }
1735
1736 result_.has_nonplanar_certificate = true;
1738
1739 result_.certificate_obstruction_edges.empty();
1740 result_.certificate_obstruction_edges.reserve(witness.size());
1741 for (typename Array<Simple_Edge>::Iterator it(witness); it.has_curr(); it.next_ne())
1742 {
1743 const Simple_Edge &e = it.get_curr_ne();
1744 result_.certificate_obstruction_edges.append(make_edge_witness(e.u, e.v));
1745 }
1746
1747 Array<size_t> degree(result_.simplified_num_nodes, static_cast<size_t>(0));
1749 inc.reserve(result_.simplified_num_nodes);
1750 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
1751 inc.append(Array<size_t>());
1752
1753 for (size_t i = 0; i < witness.size(); ++i)
1754 {
1755 const auto &e = witness[i];
1756 ++degree[e.u];
1757 ++degree[e.v];
1758 inc[e.u].append(i);
1759 inc[e.v].append(i);
1760 }
1761
1763 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
1764 if (degree[v] > 0 and degree[v] != 2)
1765 branches.append(v);
1766
1767 if (branches.size() < 5)
1768 return;
1769
1771 {
1772 result_.certificate_search_truncated = true;
1773 return;
1774 }
1775
1776 Array<char> is_branch(result_.simplified_num_nodes, static_cast<char>(0));
1777 for (typename Array<size_t>::Iterator it(branches); it.has_curr(); it.next_ne())
1778 is_branch[it.get_curr_ne()] = 1;
1779
1780 Array<char> used(witness.size(), static_cast<char>(0));
1782
1783 for (typename Array<size_t>::Iterator bit(branches); bit.has_curr(); bit.next_ne())
1784 {
1785 const size_t b = bit.get_curr_ne();
1786
1787 for (typename Array<size_t>::Iterator eit(inc[b]); eit.has_curr(); eit.next_ne())
1788 {
1789 const size_t first_edge = eit.get_curr_ne();
1790 if (used[first_edge])
1791 continue;
1792
1793 used[first_edge] = 1;
1794
1796 p.u = b;
1797 p.nodes.append(b);
1798
1799 size_t curr = witness[first_edge].u == b ? witness[first_edge].v : witness[first_edge].u;
1800 size_t prev_edge = first_edge;
1801 size_t guard = 0;
1802
1803 while (curr < result_.simplified_num_nodes and not is_branch[curr])
1804 {
1805 p.nodes.append(curr);
1806
1807 if (inc[curr].size() != 2)
1808 break;
1809
1810 const size_t e0 = inc[curr][0];
1811 const size_t e1 = inc[curr][1];
1812 const size_t next_edge = e0 == prev_edge ? e1 : e0;
1813
1814 if (used[next_edge])
1815 break;
1816
1817 used[next_edge] = 1;
1819
1820 const auto &ne = witness[next_edge];
1821 curr = ne.u == curr ? ne.v : ne.u;
1822
1823 ++guard;
1824 if (guard > witness.size())
1825 break;
1826 }
1827
1828 p.v = curr;
1829 if (p.nodes.is_empty() or p.nodes.get_last() != curr)
1830 p.nodes.append(curr);
1831
1832 if (p.u != p.v and p.v < result_.simplified_num_nodes and is_branch[p.v] and
1833 p.nodes.size() >= 2)
1834 paths.append(std::move(p));
1835 }
1836 }
1837
1838 if (paths.is_empty())
1839 return;
1840
1841 const size_t bcount = branches.size();
1844 for (size_t i = 0; i < bcount; ++i)
1845 first_path.append(Array<long>(bcount, static_cast<long>(-1)));
1846
1847 for (size_t i = 0; i < paths.size(); ++i)
1848 {
1849 const Compressed_Path &p = paths[i];
1850 const size_t iu = find_in_array(branches, p.u);
1851 const size_t iv = find_in_array(branches, p.v);
1852 if (iu == Null_Edge or iv == Null_Edge or iu == iv)
1853 continue;
1854
1855 if (first_path[iu][iv] == -1)
1856 {
1857 first_path[iu][iv] = static_cast<long>(i);
1858 first_path[iv][iu] = static_cast<long>(i);
1859 }
1860 }
1861
1862 auto has_pair = [&](const size_t a, const size_t b) -> bool
1863 {
1864 return first_path[a][b] != -1;
1865 };
1866
1867 if (bcount >= 5)
1868 {
1869 for (size_t a = 0; a < bcount; ++a)
1870 for (size_t b = a + 1; b < bcount; ++b)
1871 for (size_t c = b + 1; c < bcount; ++c)
1872 for (size_t d = c + 1; d < bcount; ++d)
1873 for (size_t e = d + 1; e < bcount; ++e)
1874 {
1875 const size_t idx[5] = {a, b, c, d, e};
1876 bool ok = true;
1877 for (size_t i = 0; i < 5 and ok; ++i)
1878 for (size_t j = i + 1; j < 5; ++j)
1879 if (not has_pair(idx[i], idx[j]))
1880 ok = false;
1881
1882 if (not ok)
1883 continue;
1884
1887 for (unsigned long i : idx)
1888 chosen_branches.append(branches[i]);
1889
1892 for (size_t i = 0; i < 5; ++i)
1893 for (size_t j = i + 1; j < 5; ++j)
1894 chosen_paths.append(paths[static_cast<size_t>(first_path[idx[i]][idx[j]])]);
1895
1897 continue;
1898
1901 return;
1902 }
1903 }
1904
1905 if (bcount >= 6)
1906 {
1907 for (size_t a = 0; a < bcount; ++a)
1908 for (size_t b = a + 1; b < bcount; ++b)
1909 for (size_t c = b + 1; c < bcount; ++c)
1910 for (size_t d = c + 1; d < bcount; ++d)
1911 for (size_t e = d + 1; e < bcount; ++e)
1912 for (size_t f = e + 1; f < bcount; ++f)
1913 {
1914 const size_t six[6] = {a, b, c, d, e, f};
1915
1916 const int partitions[10][3] = {{0, 1, 2}, {0, 1, 3}, {0, 1, 4}, {0, 1, 5},
1917 {0, 2, 3}, {0, 2, 4}, {0, 2, 5}, {0, 3, 4},
1918 {0, 3, 5}, {0, 4, 5}};
1919
1920 for (const auto &part : partitions)
1921 {
1922 Array<char> in_left(6, static_cast<char>(0));
1923 in_left[static_cast<size_t>(part[0])] = 1;
1924 in_left[static_cast<size_t>(part[1])] = 1;
1925 in_left[static_cast<size_t>(part[2])] = 1;
1926
1927 size_t left[3];
1928 size_t right[3];
1929 size_t li = 0;
1930 size_t ri = 0;
1931 for (size_t i = 0; i < 6; ++i)
1932 if (in_left[i])
1933 left[li++] = six[i];
1934 else
1935 right[ri++] = six[i];
1936
1937 bool ok = true;
1938 for (size_t i = 0; i < 3 and ok; ++i)
1939 for (size_t j : right)
1940 if (not has_pair(left[i], j))
1941 ok = false;
1942
1943 if (not ok)
1944 continue;
1945
1948 for (unsigned long i : left)
1949 chosen_branches.append(branches[i]);
1950 for (unsigned long i : right)
1951 chosen_branches.append(branches[i]);
1952
1955 for (unsigned long i : left)
1956 for (unsigned long j : right)
1957 chosen_paths.append(paths[static_cast<size_t>(first_path[i][j])]);
1958
1960 continue;
1961
1964 return;
1965 }
1966 }
1967 }
1968 }
1969
1971 {
1972 if (result_.simplified_num_nodes == 0)
1973 {
1974 result_.has_combinatorial_embedding = true;
1975 result_.embedding_is_lr_linear = true;
1976 result_.embedding_num_faces = 0;
1977 return true;
1978 }
1979
1980 Array<Array<size_t>> order;
1981 order.reserve(result_.simplified_num_nodes);
1982 for (size_t i = 0; i < result_.simplified_num_nodes; ++i)
1983 order.append(Array<size_t>());
1984
1985 if (result_.simplified_num_edges == 0)
1986 {
1987 Array<Array<size_t>> faces;
1988 size_t global_faces = 0;
1989 if (not compute_faces_from_rotation(order, result_.simplified_num_nodes,
1990 result_.simplified_num_nodes, faces, global_faces))
1991 return false;
1992
1993 fill_embedding_result(order, faces, global_faces, true);
1994 return true;
1995 }
1996
1997 if (undirected_to_oriented_.size() != result_.simplified_num_edges)
1998 return false;
1999
2000 for (unsigned long ue : undirected_to_oriented_)
2001 if (ue == Null_Edge)
2002 return false;
2003
2005 Array<char> vis(oriented_src_.size(), static_cast<char>(0));
2006
2007 auto sign_of = [&](auto &&self, const size_t e) -> long
2008 {
2009 if (vis[e] == 2)
2010 return final_side[e];
2011 if (vis[e] == 1)
2012 return final_side[e];
2013
2014 vis[e] = 1;
2015 if (ref_[e] != Null_Edge)
2016 final_side[e] *= self(self, ref_[e]);
2017 vis[e] = 2;
2018 return final_side[e];
2019 };
2020
2022 for (size_t e = 0; e < oriented_src_.size(); ++e)
2023 {
2024 const long s = sign_of(sign_of, e);
2025 signed_depth[e] = s < 0 ? -nesting_depth_[e] : nesting_depth_[e];
2026 }
2027
2028 size_t isolated_vertices = 0;
2029 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2030 {
2031 auto &ord = order[v];
2033
2034 if (incident_edges_[v].is_empty())
2036
2037 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
2038 ord.append(it.get_curr_ne());
2039
2040 auto less_local = [&](const size_t ue_a, const size_t ue_b) -> bool
2041 {
2042 const size_t oe_a = undirected_to_oriented_[ue_a];
2043 const size_t oe_b = undirected_to_oriented_[ue_b];
2044
2045 const long a0 = (oriented_src_[oe_a] == v ? signed_depth[oe_a] : -signed_depth[oe_a]);
2046 const long b0 = (oriented_src_[oe_b] == v ? signed_depth[oe_b] : -signed_depth[oe_b]);
2047 if (a0 != b0)
2048 return a0 < b0;
2049
2050 const size_t a1 = other_endpoint(ue_a, v);
2051 const size_t b1 = other_endpoint(ue_b, v);
2052 if (a1 != b1)
2053 return a1 < b1;
2054
2055 return ue_a < ue_b;
2056 };
2057
2058 for (size_t i = 1; i < ord.size(); ++i)
2059 {
2060 const size_t key_ue = ord[i];
2061
2062 size_t j = i;
2063 while (j > 0 and less_local(key_ue, ord[j - 1]))
2064 {
2065 ord[j] = ord[j - 1];
2066 --j;
2067 }
2068 ord[j] = key_ue;
2069 }
2070
2071 for (size_t &i : ord)
2072 i = other_endpoint(i, v);
2073 }
2074
2075 const size_t num_components = count_components();
2076 {
2077 Array<Array<size_t>> faces;
2078 size_t global_faces = 0;
2079 if (compute_faces_from_rotation(order, isolated_vertices, num_components, faces, global_faces))
2080 {
2081 fill_embedding_result(order, faces, global_faces, true);
2082 return true;
2083 }
2084 }
2085
2087 {
2088 result_.embedding_search_truncated = true;
2089 return false;
2090 }
2091
2092 auto reverse_order = [](Array<size_t> &ord)
2093 {
2094 if (ord.size() <= 1)
2095 return;
2096
2097 size_t i = 0;
2098 size_t j = ord.size() - 1;
2099 while (i < j)
2100 {
2101 std::swap(ord[i], ord[j]);
2102 ++i;
2103 --j;
2104 }
2105 };
2106
2107 auto same_order = [](const Array<size_t> &a, const Array<size_t> &b) -> bool
2108 {
2109 if (a.size() != b.size())
2110 return false;
2111
2112 for (size_t i = 0; i < a.size(); ++i)
2113 if (a[i] != b[i])
2114 return false;
2115
2116 return true;
2117 };
2118
2120 {
2121 for (typename Array<Array<size_t>>::Iterator it(opts); it.has_curr(); it.next_ne())
2122 if (same_order(it.get_curr_ne(), cand))
2123 return;
2124
2125 opts.append(cand);
2126 };
2127
2129 {
2130 if (src.size() < 2)
2131 return;
2132
2133 for (size_t i = 0; i + 1 < src.size(); ++i)
2134 {
2135 Array<size_t> cand = src;
2136 std::swap(cand[i], cand[i + 1]);
2138 }
2139 };
2140
2141 auto add_pair_swaps = [&](Array<Array<size_t>> &opts, const Array<size_t> &src)
2142 {
2143 if (src.size() < 2)
2144 return;
2145
2146 for (size_t i = 0; i + 1 < src.size(); ++i)
2147 for (size_t j = i + 1; j < src.size(); ++j)
2148 {
2149 Array<size_t> cand = src;
2150 std::swap(cand[i], cand[j]);
2152 }
2153 };
2154
2156 repair_options.reserve(result_.simplified_num_nodes);
2157
2158 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2159 {
2161 opts.append(order[v]);
2162
2163 if (order[v].size() >= 2)
2164 {
2165 Array<size_t> rev = order[v];
2166 reverse_order(rev);
2168
2169 add_adjacent_swaps(opts, order[v]);
2171
2172 // Dense planar subgraphs often need more than a flip.
2173 // For small local degree, include full pair swaps.
2174 if (order[v].size() <= 5)
2175 {
2176 add_pair_swaps(opts, order[v]);
2177 add_pair_swaps(opts, rev);
2178 }
2179 }
2180
2181 repair_options.append(std::move(opts));
2182 }
2183
2185 vars.reserve(result_.simplified_num_nodes);
2186 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2187 if (repair_options[v].size() > 1)
2188 vars.append(v);
2189
2190 if (vars.is_empty())
2191 return false;
2192
2193 for (size_t i = 1; i < vars.size(); ++i)
2194 {
2195 const size_t key = vars[i];
2196 size_t j = i;
2197 while (j > 0 and repair_options[key].size() > repair_options[vars[j - 1]].size())
2198 {
2199 vars[j] = vars[j - 1];
2200 --j;
2201 }
2202 vars[j] = key;
2203 }
2204
2205 struct Repair_Quality
2206 {
2207 bool available = false;
2208 bool valid_embedding = false;
2209 long long abs_euler_delta = std::numeric_limits<long long>::max();
2210 size_t global_faces = 0;
2211 };
2212
2213 Array<size_t> selected(result_.simplified_num_nodes, static_cast<size_t>(0));
2214 size_t evaluated = 0;
2215 bool truncated = false;
2216
2218 {
2219 candidate.empty();
2220 candidate.reserve(result_.simplified_num_nodes);
2221 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2222 candidate.append(repair_options[v][sel[v]]);
2223 };
2224
2225 auto materialize_embedding = [&](const Array<size_t> &sel) -> bool
2226 {
2229
2230 Array<Array<size_t>> faces;
2231 size_t global_faces = 0;
2232 if (not compute_faces_from_rotation(candidate, isolated_vertices, num_components, faces,
2233 global_faces))
2234 return false;
2235
2237 return true;
2238 };
2239
2240 auto evaluate_quality = [&](const Array<size_t> &sel, Repair_Quality &out) -> bool
2241 {
2243 {
2244 truncated = true;
2245 return false;
2246 }
2247
2248 ++evaluated;
2249
2252
2253 Array<Array<size_t>> faces;
2254 size_t global_faces = 0;
2255 if (not compute_faces_from_rotation(candidate, isolated_vertices, num_components, faces,
2256 global_faces, false))
2257 {
2258 out.available = false;
2259 out.valid_embedding = false;
2260 out.abs_euler_delta = std::numeric_limits<long long>::max();
2261 out.global_faces = 0;
2262 return true;
2263 }
2264
2265 const long long lhs = static_cast<long long>(result_.simplified_num_nodes) -
2266 static_cast<long long>(result_.simplified_num_edges) + static_cast<long long>(global_faces);
2267 const long long rhs = static_cast<long long>(num_components) + 1;
2268
2269 long long delta = lhs - rhs;
2270 if (delta < 0)
2271 delta = -delta;
2272
2273 out.available = true;
2274 out.valid_embedding = (not options_.embedding_validate_with_euler) or delta == 0;
2275 out.abs_euler_delta = delta;
2276 out.global_faces = global_faces;
2277 return true;
2278 };
2279
2280 auto run_coordinate_descent = [&](const Array<size_t> &seed_selected) -> bool
2281 {
2282 selected = seed_selected;
2283
2285 if (not evaluate_quality(selected, current_quality))
2286 return false;
2287
2288 if (current_quality.valid_embedding and materialize_embedding(selected))
2289 return true;
2290
2291 const size_t max_passes = vars.size() == 0 ? 0 : 2 * vars.size();
2292 bool budget_hit = false;
2293
2294 for (size_t pass = 0; pass < max_passes; ++pass)
2295 {
2296 bool improved = false;
2297
2298 for (typename Array<size_t>::Iterator vit(vars); vit.has_curr(); vit.next_ne())
2299 {
2300 const size_t v = vit.get_curr_ne();
2301 const size_t prev_opt = selected[v];
2302 size_t best_opt = prev_opt;
2304
2305 for (size_t i = 0; i < repair_options[v].size(); ++i)
2306 {
2307 if (i == prev_opt)
2308 continue;
2309
2310 selected[v] = i;
2312 if (not evaluate_quality(selected, cand_quality))
2313 {
2314 budget_hit = true;
2315 break;
2316 }
2317
2318 if (not cand_quality.available)
2319 continue;
2320
2321 if (cand_quality.valid_embedding and materialize_embedding(selected))
2322 return true;
2323
2324 if (cand_quality.abs_euler_delta < best_quality.abs_euler_delta or
2325 (cand_quality.abs_euler_delta == best_quality.abs_euler_delta and
2326 cand_quality.global_faces > best_quality.global_faces))
2327 {
2328 best_opt = i;
2330 }
2331 }
2332
2333 if (budget_hit)
2334 break;
2335
2336 selected[v] = best_opt;
2337 if (best_opt != prev_opt)
2338 {
2340 improved = true;
2341 }
2342 }
2343
2345 break;
2346 }
2347
2348 return false;
2349 };
2350
2351 Array<size_t> base_seed(result_.simplified_num_nodes, static_cast<size_t>(0));
2353 return true;
2354
2355 if (truncated)
2356 {
2357 result_.embedding_search_truncated = true;
2358 return false;
2359 }
2360
2362 bool has_reverse_seed = false;
2363 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2364 if (repair_options[v].size() > 1)
2365 {
2366 reverse_seed[v] = 1;
2367 has_reverse_seed = true;
2368 }
2369
2371 return true;
2372
2373 if (truncated)
2374 {
2375 result_.embedding_search_truncated = true;
2376 return false;
2377 }
2378
2379 for (typename Array<size_t>::Iterator vit(vars); vit.has_curr(); vit.next_ne())
2380 {
2381 const size_t v = vit.get_curr_ne();
2382 for (size_t i = 1; i < repair_options[v].size(); ++i)
2383 {
2385 seed[v] = i;
2387 return true;
2388
2389 if (truncated)
2390 {
2391 result_.embedding_search_truncated = true;
2392 return false;
2393 }
2394 }
2395 }
2396
2397 const size_t max_pair_seed_vars = std::min(static_cast<size_t>(4), vars.size());
2398 for (size_t ia = 0; ia < max_pair_seed_vars; ++ia)
2399 for (size_t ib = ia + 1; ib < max_pair_seed_vars; ++ib)
2400 {
2401 const size_t va = vars[ia];
2402 const size_t vb = vars[ib];
2403 for (size_t oa = 1; oa < repair_options[va].size(); ++oa)
2404 for (size_t ob = 1; ob < repair_options[vb].size(); ++ob)
2405 {
2407 seed[va] = oa;
2408 seed[vb] = ob;
2410 return true;
2411
2412 if (truncated)
2413 {
2414 result_.embedding_search_truncated = true;
2415 return false;
2416 }
2417 }
2418 }
2419
2420 if (truncated)
2421 result_.embedding_search_truncated = true;
2422
2423 return false;
2424 }
2425
2427 {
2428 if (result_.simplified_num_nodes == 0)
2429 {
2430 result_.has_combinatorial_embedding = true;
2431 result_.embedding_is_lr_linear = false;
2432 result_.embedding_num_faces = 0;
2433 return true;
2434 }
2435
2437 neighbors.reserve(result_.simplified_num_nodes);
2438
2439 size_t isolated_vertices = 0;
2440 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2441 {
2444 for (typename Array<size_t>::Iterator it(incident_edges_[v]); it.has_curr(); it.next_ne())
2445 ng.append(other_endpoint(it.get_curr_ne(), v));
2446
2448 if (ng.is_empty())
2450
2451 neighbors.append(std::move(ng));
2452 }
2453
2454 if (result_.simplified_num_edges == 0)
2455 {
2456 Array<Array<size_t>> order;
2457 order.reserve(result_.simplified_num_nodes);
2458 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2459 order.append(Array<size_t>());
2460
2461 const size_t num_components = result_.simplified_num_nodes;
2462 Array<Array<size_t>> faces;
2463 size_t global_faces = 0;
2464
2465 if (not compute_faces_from_rotation(order, isolated_vertices, num_components, faces,
2466 global_faces))
2467 return false;
2468
2469 fill_embedding_result(order, faces, global_faces, false);
2470 return true;
2471 }
2472
2474 {
2475 result_.embedding_search_truncated = true;
2476 return false;
2477 }
2478
2480 order_options.reserve(result_.simplified_num_nodes);
2481
2482 size_t combinations = 1;
2483 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2484 {
2485 const auto &neigh = neighbors[v];
2486 const size_t d = neigh.size();
2487
2489 if (d <= 2)
2490 opts.append(neigh);
2491 else
2492 {
2493 const size_t cnt = factorial_bounded(d - 1, options_.embedding_max_combinations);
2496 {
2497 result_.embedding_search_truncated = true;
2498 return false;
2499 }
2500
2501 combinations *= cnt;
2502
2503 const size_t first = neigh[0];
2504 Array<size_t> tail;
2505 tail.reserve(d - 1);
2506 for (size_t i = 1; i < d; ++i)
2507 tail.append(neigh[i]);
2508
2509 generate_permutations(tail, 0, first, opts);
2510 }
2511
2512 order_options.append(std::move(opts));
2513 }
2514
2516 vars.reserve(result_.simplified_num_nodes);
2517 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2518 if (order_options[v].size() > 1)
2519 vars.append(v);
2520
2521 for (size_t i = 1; i < vars.size(); ++i)
2522 {
2523 const size_t key = vars[i];
2524 size_t j = i;
2525 while (j > 0 and order_options[key].size() > order_options[vars[j - 1]].size())
2526 {
2527 vars[j] = vars[j - 1];
2528 --j;
2529 }
2530 vars[j] = key;
2531 }
2532
2533 const size_t num_components = count_components();
2534 Array<size_t> selected(result_.simplified_num_nodes, 0);
2535
2536 auto evaluate = [&]() -> bool
2537 {
2538 Array<Array<size_t>> order;
2539 order.reserve(result_.simplified_num_nodes);
2540 for (size_t v = 0; v < result_.simplified_num_nodes; ++v)
2541 order.append(order_options[v][selected[v]]);
2542
2543 Array<Array<size_t>> faces;
2544 size_t global_faces = 0;
2545 if (not compute_faces_from_rotation(order, isolated_vertices, num_components, faces,
2546 global_faces))
2547 return false;
2548
2549 fill_embedding_result(order, faces, global_faces, false);
2550 return true;
2551 };
2552
2553 auto search = [&](auto &&self, const size_t pos) -> bool
2554 {
2555 if (pos == vars.size())
2556 return evaluate();
2557
2558 const size_t v = vars[pos];
2559 for (size_t i = 0; i < order_options[v].size(); ++i)
2560 {
2561 selected[v] = i;
2562 if (self(self, pos + 1))
2563 return true;
2564 }
2565
2566 return false;
2567 };
2568
2569 return search(search, 0);
2570 }
2571
2573 {
2575 return true;
2576
2578 {
2579 result_.embedding_search_truncated = true;
2580 return false;
2581 }
2582
2584 }
2585
2586public:
2588 : g_(g), sa_(std::move(sa)), options_(std::move(options))
2589 {
2590 // empty
2591 }
2592
2594 {
2596
2598 {
2599 result_.is_planar = false;
2600 result_.failed_euler_bound = true;
2601
2604
2605 return result_;
2606 }
2607
2608 result_.is_planar = run_lr_planarity_test();
2609
2610 if (result_.is_planar)
2611 {
2613 {
2614 result_.embedding_search_truncated = false;
2616 }
2617 }
2619 {
2620 result_.certificate_search_truncated = false;
2622 }
2623
2624 return result_;
2625 }
2626};
2627} // namespace planarity_detail
2628
2655template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
2657 const GT &g,
2658 SA sa = SA(),
2660{
2662}
2663
2668template <AlephGraph GT>
2673
2685template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
2686bool is_planar_graph(const GT &g,
2687 SA sa = SA(),
2689{
2690 return planarity_test<GT, SA>(g, std::move(sa), options).is_planar;
2691}
2692
2697template <AlephGraph GT>
2702
2709template <AlephGraph GT>
2711{
2712 using Node = typename GT::Node;
2713 using Dart_Key = planarity_detail::Dart_Key;
2714 constexpr size_t Null_Edge = planarity_detail::Null_Edge;
2715
2717 << "planar_dual_metadata() requires a planar result with embedding";
2718
2719 const size_t n = result.embedding_rotation.size();
2720 ah_runtime_error_unless(n == result.simplified_num_nodes) << "embedding_rotation size mismatch";
2721
2723 md.has_embedding = true;
2724
2725 if (n == 0)
2726 {
2727 md.num_components = 0;
2728 md.num_faces_local = 0;
2729 md.num_faces_global = 0;
2730 md.faces_are_component_local = false;
2731 return md;
2732 }
2733
2736
2737 DynMapTree<Node *, size_t> node_to_idx;
2738 for (size_t i = 0; i < n; ++i)
2739 {
2740 Node *node = result.embedding_rotation[i].node;
2741 ah_runtime_error_unless(node != nullptr) << "embedding_rotation contains null node";
2742 ah_runtime_error_if(node_to_idx.contains(node))
2743 << "embedding_rotation contains duplicated node pointer";
2744
2745 node_to_idx.insert(node, i);
2746 idx_to_node.append(node);
2747 }
2748
2749 Array<Array<size_t>> order;
2750 order.reserve(n);
2751 for (size_t i = 0; i < n; ++i)
2752 order.append(Array<size_t>());
2753
2754 for (size_t i = 0; i < n; ++i)
2755 {
2757 const auto &re = result.embedding_rotation[i];
2758 for (typename Array<Node *>::Iterator it(re.cw_neighbors); it.has_curr(); it.next_ne())
2759 {
2760 Node *neigh = it.get_curr_ne();
2761 ah_runtime_error_unless(neigh != nullptr) << "embedding_rotation contains null neighbor";
2763 << "embedding_rotation references unknown neighbor node";
2764
2765 const size_t v = node_to_idx.find(neigh);
2766 ah_runtime_error_if(v == i) << "embedding_rotation has self-neighbor in simplified graph";
2767 ah_runtime_error_if(seen.contains(v))
2768 << "embedding_rotation has duplicated neighbor in same rotation";
2769
2770 seen.insert(v);
2771 order[i].append(v);
2772 }
2773 }
2774
2775 Array<long> comp_id(n, static_cast<long>(-1));
2776 size_t num_components = 0;
2777 size_t isolated_vertices = 0;
2778
2779 for (size_t s = 0; s < n; ++s)
2780 {
2781 if (order[s].is_empty())
2783
2784 if (comp_id[s] != -1)
2785 continue;
2786
2787 comp_id[s] = static_cast<long>(num_components);
2788 Array<size_t> stack;
2789 stack.append(s);
2790
2791 while (not stack.is_empty())
2792 {
2793 const size_t u = stack.remove_last();
2794 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
2795 {
2796 const size_t v = it.get_curr_ne();
2797 if (comp_id[v] != -1)
2798 continue;
2799 comp_id[v] = static_cast<long>(num_components);
2800 stack.append(v);
2801 }
2802 }
2803
2804 ++num_components;
2805 }
2806
2807 md.num_components = num_components;
2808
2812 dart_tgt.reserve(2 * result.simplified_num_edges);
2813
2815 for (size_t u = 0; u < n; ++u)
2816 {
2817 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
2818 {
2819 const size_t v = it.get_curr_ne();
2820 const Dart_Key key{u, v};
2821 ah_runtime_error_if(dart_id.contains(key))
2822 << "embedding rotation contains repeated directed edge";
2823
2824 const size_t did = dart_src.size();
2825 dart_id.insert(key, did);
2826 dart_src.append(u);
2827 dart_tgt.append(v);
2828 }
2829 }
2830
2831 ah_runtime_error_unless(dart_src.size() % 2 == 0) << "invalid embedding: odd number of darts";
2832
2833 Array<size_t> alpha(dart_src.size(), Null_Edge);
2834 for (size_t d = 0; d < dart_src.size(); ++d)
2835 {
2836 const Dart_Key rev{dart_tgt[d], dart_src[d]};
2837 ah_runtime_error_unless(dart_id.contains(rev)) << "invalid embedding: missing reverse dart";
2838 alpha[d] = dart_id.find(rev);
2839 }
2840
2841 Array<size_t> sigma(dart_src.size(), Null_Edge);
2842 for (size_t u = 0; u < n; ++u)
2843 {
2844 const auto &ord = order[u];
2845 if (ord.is_empty())
2846 continue;
2847
2848 for (size_t i = 0; i < ord.size(); ++i)
2849 {
2850 const size_t to = ord[i];
2851 const size_t next = ord[(i + 1) % ord.size()];
2852 const size_t a = dart_id.find(Dart_Key{u, to});
2853 const size_t b = dart_id.find(Dart_Key{u, next});
2854 sigma[a] = b;
2855 }
2856 }
2857
2858 for (unsigned long d : sigma)
2859 ah_runtime_error_unless(d != Null_Edge) << "invalid embedding: incomplete sigma permutation";
2860
2861 Array<size_t> face_of_dart(dart_src.size(), Null_Edge);
2862 for (size_t d = 0; d < dart_src.size(); ++d)
2863 {
2864 if (face_of_dart[d] != Null_Edge)
2865 continue;
2866
2867 const size_t fid = md.faces.size();
2869
2870 size_t x = d;
2871 while (face_of_dart[x] == Null_Edge)
2872 {
2873 face_of_dart[x] = fid;
2874
2876 fd.src = idx_to_node[dart_src[x]];
2877 fd.tgt = idx_to_node[dart_tgt[x]];
2878 face.darts.append(fd);
2879
2880 x = sigma[alpha[x]];
2881 }
2882
2883 md.faces.append(std::move(face));
2884 }
2885
2886 for (size_t i = 0; i < isolated_vertices; ++i)
2887 md.faces.append(typename Planar_Dual_Metadata<GT>::Face_Boundary());
2888
2889 md.num_faces_local = md.faces.size();
2890
2891 const size_t m = dart_src.size() / 2;
2892 const size_t num_faces_global = m - n + num_components + 1;
2893 md.num_faces_global = num_faces_global;
2894 md.faces_are_component_local = md.num_faces_local != md.num_faces_global;
2895
2896 md.face_adjacency.reserve(md.num_faces_local);
2897 for (size_t i = 0; i < md.num_faces_local; ++i)
2898 md.face_adjacency.append(Array<size_t>());
2899
2900 md.dual_edges.reserve(m);
2901 for (size_t d = 0; d < dart_src.size(); ++d)
2902 {
2903 const size_t rev = alpha[d];
2904 if (d > rev)
2905 continue;
2906
2907 const size_t f0 = face_of_dart[d];
2908 const size_t f1 = face_of_dart[rev];
2909
2911 info.face_a = f0;
2912 info.face_b = f1;
2913 info.primal_src = idx_to_node[dart_src[d]];
2914 info.primal_tgt = idx_to_node[dart_tgt[d]];
2915
2916 md.dual_edges.append(info);
2917 md.face_adjacency[f0].append(f1);
2918 md.face_adjacency[f1].append(f0);
2919 }
2920
2921 for (size_t f = 0; f < md.face_adjacency.size(); ++f)
2922 {
2923 auto &adj = md.face_adjacency[f];
2924 for (size_t i = 1; i < adj.size(); ++i)
2925 {
2926 const size_t key = adj[i];
2927 size_t j = i;
2928 while (j > 0 and key < adj[j - 1])
2929 {
2930 adj[j] = adj[j - 1];
2931 --j;
2932 }
2933 adj[j] = key;
2934 }
2935
2936 if (adj.is_empty())
2937 continue;
2938
2940 uniq.reserve(adj.size());
2941 uniq.append(adj[0]);
2942 for (size_t i = 1; i < adj.size(); ++i)
2943 if (adj[i] != uniq.get_last())
2944 uniq.append(adj[i]);
2945
2946 adj = std::move(uniq);
2947 }
2948
2949 return md;
2950}
2951
2956template <AlephGraph GT, class DGT = Default_Planar_Dual_Graph<GT>>
2958{
2959 ah_runtime_error_unless(md.has_embedding)
2960 << "build_planar_dual_graph() requires metadata with embedding";
2961
2962 DGT dual;
2964 face_nodes.reserve(md.num_faces_local);
2965
2966 for (size_t f = 0; f < md.num_faces_local; ++f)
2967 face_nodes.append(dual.insert_node(f));
2968
2969 for (typename Array<Planar_Dual_Edge_Info<GT>>::Iterator it(md.dual_edges); it.has_curr();
2970 it.next_ne())
2971 {
2972 const auto &e = it.get_curr_ne();
2973 dual.insert_arc(face_nodes[e.face_a], face_nodes[e.face_b], e);
2974 }
2975
2976 return dual;
2977}
2978
2983template <AlephGraph GT, class DGT = Default_Planar_Dual_Graph<GT>>
2988
3000template <AlephGraph GT>
3002 const Planarity_Test_Result<GT> &result,
3004{
3005 using Node = typename GT::Node;
3007
3008 constexpr size_t Null = std::numeric_limits<size_t>::max();
3009 constexpr double Pi = 3.1415926535897932384626433832795;
3010
3012 << "planar_geometric_drawing() requires a planar result with embedding";
3013
3014 const size_t n = result.embedding_rotation.size();
3015 ah_runtime_error_unless(n == result.simplified_num_nodes) << "embedding_rotation size mismatch";
3016
3018 drawing.has_embedding = true;
3019
3020 if (n == 0)
3021 {
3022 drawing.drawing_available = true;
3023 drawing.drawing_validated_no_crossings = true;
3024 return drawing;
3025 }
3026
3027 if (options.max_outer_faces_to_try == 0)
3028 {
3029 drawing.drawing_search_truncated = true;
3030 return drawing;
3031 }
3032
3033 DynMapTree<Node *, size_t> node_to_idx;
3036
3037 for (size_t i = 0; i < n; ++i)
3038 {
3039 Node *node = result.embedding_rotation[i].node;
3040 ah_runtime_error_unless(node != nullptr) << "embedding_rotation contains null node";
3041 ah_runtime_error_if(node_to_idx.contains(node))
3042 << "embedding_rotation contains duplicated node pointer";
3043
3044 node_to_idx.insert(node, i);
3045 idx_to_node.append(node);
3046 }
3047
3048 Array<Array<size_t>> order;
3049 order.reserve(n);
3050 for (size_t i = 0; i < n; ++i)
3051 order.append(Array<size_t>());
3052
3053 for (size_t i = 0; i < n; ++i)
3054 {
3056 const auto &re = result.embedding_rotation[i];
3057 for (typename Array<Node *>::Iterator it(re.cw_neighbors); it.has_curr(); it.next_ne())
3058 {
3059 Node *neigh = it.get_curr_ne();
3060 ah_runtime_error_unless(neigh != nullptr) << "embedding_rotation contains null neighbor";
3062 << "embedding_rotation references unknown node";
3063
3064 const size_t v = node_to_idx.find(neigh);
3065 ah_runtime_error_if(v == i) << "embedding_rotation has self-neighbor";
3066 ah_runtime_error_if(seen.contains(v)) << "embedding_rotation has duplicated neighbor";
3067
3068 seen.insert(v);
3069 order[i].append(v);
3070 }
3071 }
3072
3073 Array<long> comp_id(n, static_cast<long>(-1));
3074 size_t num_components = 0;
3075 for (size_t s = 0; s < n; ++s)
3076 {
3077 if (comp_id[s] != -1)
3078 continue;
3079
3080 comp_id[s] = static_cast<long>(num_components);
3081 Array<size_t> stack;
3082 stack.append(s);
3083
3084 while (not stack.is_empty())
3085 {
3086 const size_t u = stack.remove_last();
3087 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
3088 {
3089 const size_t v = it.get_curr_ne();
3090 if (comp_id[v] != -1)
3091 continue;
3092 comp_id[v] = static_cast<long>(num_components);
3093 stack.append(v);
3094 }
3095 }
3096
3097 ++num_components;
3098 }
3099
3100 drawing.num_components = num_components;
3101
3103 comp_nodes.reserve(num_components);
3104 for (size_t c = 0; c < num_components; ++c)
3105 comp_nodes.append(Array<size_t>());
3106 for (size_t i = 0; i < n; ++i)
3107 comp_nodes[static_cast<size_t>(comp_id[i])].append(i);
3108
3109 struct Drawing_Edge
3110 {
3111 size_t u = 0;
3112 size_t v = 0;
3113 size_t comp = 0;
3114 };
3115
3116 Array<Drawing_Edge> edges;
3117 edges.reserve(result.simplified_num_edges);
3118 for (size_t u = 0; u < n; ++u)
3119 for (typename Array<size_t>::Iterator it(order[u]); it.has_curr(); it.next_ne())
3120 {
3121 const size_t v = it.get_curr_ne();
3122 if (u < v)
3123 edges.append(Drawing_Edge{u, v, static_cast<size_t>(comp_id[u])});
3124 }
3125
3127 comp_edge_ids.reserve(num_components);
3128 for (size_t c = 0; c < num_components; ++c)
3129 comp_edge_ids.append(Array<size_t>());
3130 for (size_t i = 0; i < edges.size(); ++i)
3131 comp_edge_ids[edges[i].comp].append(i);
3132
3133 const Face_Metadata md = planar_dual_metadata(result);
3134 struct Face_Candidate
3135 {
3136 size_t face_id = Null;
3137 size_t comp = 0;
3139 size_t score = 0;
3140 };
3141
3143 face_candidates.reserve(md.faces.size());
3144
3145 for (size_t fid = 0; fid < md.faces.size(); ++fid)
3146 {
3147 const auto &face = md.faces[fid];
3148 if (face.darts.is_empty())
3149 continue;
3150
3151 Array<size_t> raw;
3152 raw.reserve(face.darts.size());
3153 for (typename Array<typename Face_Metadata::Face_Dart>::Iterator it(face.darts);
3154 it.has_curr(); it.next_ne())
3155 {
3156 const auto &d = it.get_curr_ne();
3157 ah_runtime_error_unless(node_to_idx.contains(d.src))
3158 << "face metadata references unknown node";
3159 raw.append(node_to_idx.find(d.src));
3160 }
3161
3162 if (raw.is_empty())
3163 continue;
3164
3166 cleaned.reserve(raw.size());
3167 for (unsigned long i : raw)
3168 if (cleaned.is_empty() or cleaned.get_last() != i)
3169 cleaned.append(i);
3170
3171 if (cleaned.size() > 1 and cleaned[0] == cleaned.get_last())
3172 (void) cleaned.remove_last();
3173
3177 for (typename Array<size_t>::Iterator it(cleaned); it.has_curr(); it.next_ne())
3178 {
3179 const size_t u = it.get_curr_ne();
3180 if (seen.contains(u))
3181 continue;
3182 seen.insert(u);
3183 unique_cycle.append(u);
3184 }
3185
3186 if (unique_cycle.is_empty())
3187 continue;
3188
3190 cand.face_id = fid;
3191 cand.comp = static_cast<size_t>(comp_id[unique_cycle[0]]);
3192 cand.boundary_nodes = std::move(unique_cycle);
3193 cand.score = cand.boundary_nodes.size();
3194 face_candidates.append(std::move(cand));
3195 }
3196
3198 comp_faces.reserve(num_components);
3199 for (size_t c = 0; c < num_components; ++c)
3200 comp_faces.append(Array<size_t>());
3201
3202 for (size_t i = 0; i < face_candidates.size(); ++i)
3203 comp_faces[face_candidates[i].comp].append(i);
3204
3205 for (size_t c = 0; c < num_components; ++c)
3206 {
3207 auto &faces = comp_faces[c];
3208 for (size_t i = 1; i < faces.size(); ++i)
3209 {
3210 const size_t key = faces[i];
3211 size_t j = i;
3212 while (j > 0 and face_candidates[key].score > face_candidates[faces[j - 1]].score)
3213 {
3214 faces[j] = faces[j - 1];
3215 --j;
3216 }
3217 faces[j] = key;
3218 }
3219 }
3220
3221 auto count_crossings = [&](const size_t comp, const Array<double> &px,
3222 const Array<double> &py) -> size_t
3223 {
3224 size_t total = 0;
3225 const auto &ce = comp_edge_ids[comp];
3226 for (size_t i = 0; i < ce.size(); ++i)
3227 {
3228 const auto &e1 = edges[ce[i]];
3229 for (size_t j = i + 1; j < ce.size(); ++j)
3230 {
3231 const auto &e2 = edges[ce[j]];
3232 if (e1.u == e2.u or e1.u == e2.v or e1.v == e2.u or e1.v == e2.v)
3233 continue;
3234
3236 px[e1.u], py[e1.u], px[e1.v], py[e1.v], px[e2.u], py[e2.u], px[e2.v], py[e2.v]))
3237 ++total;
3238 }
3239 }
3240 return total;
3241 };
3242
3243 auto build_component_layout = [&](const size_t comp, const Array<size_t> &boundary,
3244 Array<double> &px, Array<double> &py, size_t &iterations) -> void
3245 {
3246 const auto &cnodes = comp_nodes[comp];
3247 Array<char> fixed(n, static_cast<char>(0));
3248
3249 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3250 {
3251 const size_t u = it.get_curr_ne();
3252 px[u] = 0;
3253 py[u] = 0;
3254 }
3255
3256 Array<size_t> use_boundary = boundary;
3257 if (use_boundary.size() < 3 and cnodes.size() >= 1)
3258 {
3260 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3261 use_boundary.append(it.get_curr_ne());
3262 }
3263
3264 const double scale = std::max(1.0, std::sqrt(static_cast<double>(cnodes.size())));
3265 const double radius = options.outer_face_radius * scale;
3266
3267 if (use_boundary.is_empty())
3268 {
3269 iterations = 0;
3270 return;
3271 }
3272
3273 for (size_t i = 0; i < use_boundary.size(); ++i)
3274 {
3275 const size_t u = use_boundary[i];
3276 const double ang =
3277 (2.0 * Pi * static_cast<double>(i)) / static_cast<double>(use_boundary.size());
3278 px[u] = radius * std::cos(ang);
3279 py[u] = radius * std::sin(ang);
3280 fixed[u] = 1;
3281 }
3282
3283 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3284 {
3285 const size_t u = it.get_curr_ne();
3286 if (fixed[u])
3287 continue;
3288
3289 double sx = 0;
3290 double sy = 0;
3291 size_t cnt = 0;
3292 for (typename Array<size_t>::Iterator nt(order[u]); nt.has_curr(); nt.next_ne())
3293 {
3294 const size_t v = nt.get_curr_ne();
3295 if (comp_id[v] != static_cast<long>(comp))
3296 continue;
3297 sx += px[v];
3298 sy += py[v];
3299 ++cnt;
3300 }
3301
3302 if (cnt == 0)
3303 {
3304 px[u] = 0.01 * static_cast<double>(u + 1);
3305 py[u] = -0.01 * static_cast<double>(u + 1);
3306 }
3307 else
3308 {
3309 px[u] = sx / static_cast<double>(cnt);
3310 py[u] = sy / static_cast<double>(cnt);
3311 }
3312 }
3313
3314 iterations = 0;
3315 for (size_t iter = 0; iter < options.max_relaxation_iterations; ++iter)
3316 {
3317 ++iterations;
3318 double max_delta = 0;
3319
3320 for (typename Array<size_t>::Iterator it(cnodes); it.has_curr(); it.next_ne())
3321 {
3322 const size_t u = it.get_curr_ne();
3323 if (fixed[u])
3324 continue;
3325
3326 double sx = 0;
3327 double sy = 0;
3328 size_t cnt = 0;
3329 for (typename Array<size_t>::Iterator nt(order[u]); nt.has_curr(); nt.next_ne())
3330 {
3331 const size_t v = nt.get_curr_ne();
3332 if (comp_id[v] != static_cast<long>(comp))
3333 continue;
3334 sx += px[v];
3335 sy += py[v];
3336 ++cnt;
3337 }
3338
3339 if (cnt == 0)
3340 continue;
3341
3342 const double nx = sx / static_cast<double>(cnt);
3343 const double ny = sy / static_cast<double>(cnt);
3344 const double dx = nx - px[u];
3345 const double dy = ny - py[u];
3346 const double delta = std::sqrt(dx * dx + dy * dy);
3347 if (delta > max_delta)
3348 max_delta = delta;
3349
3350 px[u] = nx;
3351 py[u] = ny;
3352 }
3353
3354 if (max_delta <= options.relaxation_tolerance)
3355 break;
3356 }
3357 };
3358
3359 Array<double> gx(n, 0.0);
3360 Array<double> gy(n, 0.0);
3361 double offset_x = 0.0;
3362
3363 for (size_t c = 0; c < num_components; ++c)
3364 {
3366
3367 if (options.preferred_outer_face != Null and options.preferred_outer_face < md.faces.size())
3368 {
3369 for (size_t i = 0; i < candidates.size(); ++i)
3370 if (face_candidates[candidates[i]].face_id == options.preferred_outer_face)
3371 {
3372 const size_t fav = candidates[i];
3373 for (size_t j = i; j > 0; --j)
3374 candidates[j] = candidates[j - 1];
3375 candidates[0] = fav;
3376 break;
3377 }
3378 }
3379
3380 size_t num_candidate_evals = 0;
3381 bool has_best = false;
3382 size_t best_crossings = std::numeric_limits<size_t>::max();
3383 size_t best_iters = 0;
3384 size_t best_face_id = Null;
3385 Array<double> best_x(n, 0.0);
3386 Array<double> best_y(n, 0.0);
3387
3388 auto evaluate_boundary = [&](const Array<size_t> &boundary, const size_t face_id) -> bool
3389 {
3390 if (num_candidate_evals >= options.max_outer_faces_to_try)
3391 {
3392 drawing.drawing_search_truncated = true;
3393 return false;
3394 }
3395
3397
3398 Array<double> cx(n, 0.0);
3399 Array<double> cy(n, 0.0);
3400 size_t iters = 0;
3401 build_component_layout(c, boundary, cx, cy, iters);
3402
3403 const size_t crossings = options.validate_crossings ? count_crossings(c, cx, cy) : 0;
3404
3407 {
3408 has_best = true;
3410 best_iters = iters;
3412 best_x = std::move(cx);
3413 best_y = std::move(cy);
3414 }
3415
3416 return crossings == 0;
3417 };
3418
3419 for (typename Array<size_t>::Iterator fit(candidates); fit.has_curr(); fit.next_ne())
3420 {
3421 const size_t idx = fit.get_curr_ne();
3423 break;
3424 }
3425
3426 if (not has_best)
3427 {
3429 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3430 fallback.append(it.get_curr_ne());
3432 }
3433
3434 ah_runtime_error_unless(has_best) << "unable to build component drawing candidate";
3435
3436 double min_x = std::numeric_limits<double>::max();
3437 double max_x = -std::numeric_limits<double>::max();
3438 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3439 {
3440 const size_t u = it.get_curr_ne();
3441 min_x = std::min(min_x, best_x[u]);
3442 max_x = std::max(max_x, best_x[u]);
3443 }
3444
3445 const double shift_x = offset_x - min_x;
3446 for (typename Array<size_t>::Iterator it(comp_nodes[c]); it.has_curr(); it.next_ne())
3447 {
3448 const size_t u = it.get_curr_ne();
3449 gx[u] = best_x[u] + shift_x;
3450 gy[u] = best_y[u];
3451 }
3452
3453 offset_x += (max_x - min_x) + options.component_spacing;
3454 drawing.crossing_count += best_crossings;
3455 drawing.relaxation_iterations += best_iters;
3456
3457 if (num_components == 1)
3458 drawing.chosen_outer_face = best_face_id;
3459 }
3460
3461 drawing.node_positions.reserve(n);
3462 for (size_t i = 0; i < n; ++i)
3463 {
3465 p.node = idx_to_node[i];
3466 p.x = gx[i];
3467 p.y = gy[i];
3468 drawing.node_positions.append(p);
3469 }
3470
3471 drawing.drawing_available = true;
3472 drawing.drawing_validated_no_crossings =
3473 (not options.validate_crossings) or drawing.crossing_count == 0;
3474 return drawing;
3475}
3476
3483template <AlephGraph GT, class Node_Label = Dft_Certificate_Node_Label<GT>>
3485 const Planarity_Test_Result<GT> &result,
3488{
3489 using Node = typename GT::Node;
3490 using Arc = typename GT::Arc;
3491
3493 << "nonplanar_certificate_to_json() requires non-planar certificate";
3494
3498
3499 std::ostringstream out;
3500 auto newline = [&](const size_t indent)
3501 {
3502 if (not options.pretty_json)
3503 return;
3504 out << '\n';
3505 for (size_t i = 0; i < indent; ++i)
3506 out << " ";
3507 };
3508
3509 auto node_ref = [&](Node *node) -> std::string
3510 {
3511 if (node == nullptr or not node_to_id.contains(node))
3512 return "null";
3513 return "\"n" + std::to_string(node_to_id.find(node)) + "\"";
3514 };
3515
3516 auto arc_ref = [&](Arc *arc) -> std::string
3517 {
3518 if (arc == nullptr)
3519 return "null";
3521 "\"";
3522 };
3523
3524 out << "{";
3525 newline(1);
3526 out << "\"is_planar\": false,";
3527 newline(1);
3528 out << "\"certificate_available\": true,";
3529 newline(1);
3530 out << "\"certificate_type\": \""
3532 newline(1);
3533 out << "\"search_truncated\": " << (result.certificate_search_truncated ? "true" : "false") << ",";
3534
3535 newline(1);
3536 out << "\"nodes\": [";
3537 for (size_t i = 0; i < nodes.size(); ++i)
3538 {
3539 if (i > 0)
3540 out << ",";
3541 newline(2);
3542 out << "{";
3543 out << "\"id\": \"n" << i << "\", ";
3544 out << "\"label\": \"" << planarity_detail::json_escape_string(node_label(nodes[i])) << "\", ";
3545 out << "\"ptr\": \""
3547 << "\"";
3548 out << "}";
3549 }
3550 if (not nodes.is_empty())
3551 newline(1);
3552 out << "],";
3553
3554 newline(1);
3555 out << "\"branch_nodes\": [";
3556 for (size_t i = 0; i < result.certificate_branch_nodes.size(); ++i)
3557 {
3558 if (i > 0)
3559 out << ",";
3560 if (options.pretty_json)
3561 out << ' ';
3563 }
3564 out << "],";
3565
3566 newline(1);
3567 out << "\"obstruction_edges\": [";
3568 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
3569 {
3570 const auto &e = result.certificate_obstruction_edges[i];
3571 if (i > 0)
3572 out << ",";
3573 newline(2);
3574 out << "{";
3575 out << "\"src\": " << node_ref(e.src) << ", ";
3576 out << "\"tgt\": " << node_ref(e.tgt) << ", ";
3577 out << "\"input_arc_count\": " << e.input_arcs.size() << ", ";
3578 out << "\"representative_input_arc\": " << arc_ref(e.representative_input_arc);
3579 out << "}";
3580 }
3581 if (not result.certificate_obstruction_edges.is_empty())
3582 newline(1);
3583 out << "],";
3584
3585 newline(1);
3586 out << "\"paths\": [";
3587 for (size_t i = 0; i < result.certificate_paths.size(); ++i)
3588 {
3589 const auto &path = result.certificate_paths[i];
3590 if (i > 0)
3591 out << ",";
3592 newline(2);
3593 out << "{";
3594
3595 out << "\"nodes\": [";
3596 for (size_t k = 0; k < path.nodes.size(); ++k)
3597 {
3598 if (k > 0)
3599 out << ", ";
3600 out << node_ref(path.nodes[k]);
3601 }
3602 out << "], ";
3603
3604 out << "\"edges\": [";
3605 for (size_t k = 0; k < path.edges.size(); ++k)
3606 {
3607 const auto &e = path.edges[k];
3608 if (k > 0)
3609 out << ", ";
3610 out << "{";
3611 out << "\"src\": " << node_ref(e.src) << ", ";
3612 out << "\"tgt\": " << node_ref(e.tgt) << ", ";
3613 out << "\"input_arc_count\": " << e.input_arcs.size() << ", ";
3614 out << "\"representative_input_arc\": " << arc_ref(e.representative_input_arc);
3615 out << "}";
3616 }
3617 out << "]";
3618 out << "}";
3619 }
3620 if (not result.certificate_paths.is_empty())
3621 newline(1);
3622 out << "]";
3623
3624 newline(0);
3625 out << "}";
3626 return out.str();
3627}
3628
3635template <AlephGraph GT, class Node_Label = Dft_Certificate_Node_Label<GT>>
3637 const Planarity_Test_Result<GT> &result,
3640{
3641 using Node = typename GT::Node;
3642
3644 << "nonplanar_certificate_to_dot() requires non-planar certificate";
3645
3649
3651 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes); it.has_curr();
3652 it.next_ne())
3653 {
3654 Node *node = it.get_curr_ne();
3655 if (node != nullptr and node_to_id.contains(node))
3656 branch_ids.insert(node_to_id.find(node));
3657 }
3658
3659 std::ostringstream out;
3660 out << "graph NonPlanarCertificate {\n";
3661 out << " graph [labelloc=\"t\", label=\""
3663 out << " node [shape=circle, fontname=\"Helvetica\"];\n";
3664 out << " edge [fontname=\"Helvetica\"];\n";
3665
3666 for (size_t i = 0; i < nodes.size(); ++i)
3667 {
3668 out << " n" << i << " [label=\"n" << i << "\\n"
3670 if (branch_ids.contains(i))
3671 out << ", style=\"filled\", fillcolor=\"#fff3bf\"";
3672 out << "];\n";
3673 }
3674
3675 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
3676 {
3677 const auto &e = result.certificate_obstruction_edges[i];
3678 if (e.src == nullptr or e.tgt == nullptr or not node_to_id.contains(e.src) or
3679 not node_to_id.contains(e.tgt))
3680 continue;
3681 const size_t u = node_to_id.find(e.src);
3682 const size_t v = node_to_id.find(e.tgt);
3683 out << " n" << u << " -- n" << v << " [color=\"#d00000\", penwidth=2.2, label=\"obs x"
3684 << e.input_arcs.size() << "\"];\n";
3685 }
3686
3687 if (options.dot_highlight_paths)
3688 {
3689 static const char *kPalette[] = {"#0077b6", "#2a9d8f", "#6a4c93",
3690 "#ef476f", "#ff9f1c", "#588157"};
3691 constexpr size_t palette_size = sizeof(kPalette) / sizeof(kPalette[0]);
3692
3693 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
3694 {
3695 const auto &path = result.certificate_paths[pid];
3696 const char *color = kPalette[pid % palette_size];
3697 for (size_t k = 0; k < path.edges.size(); ++k)
3698 {
3699 const auto &e = path.edges[k];
3700 if (e.src == nullptr or e.tgt == nullptr or not node_to_id.contains(e.src) or
3701 not node_to_id.contains(e.tgt))
3702 continue;
3703 const size_t u = node_to_id.find(e.src);
3704 const size_t v = node_to_id.find(e.tgt);
3705 out << " n" << u << " -- n" << v << " [color=\"" << color
3706 << "\", style=\"dashed\", penwidth=1.4, constraint=false, label=\"p" << pid
3707 << "\"];\n";
3708 }
3709 }
3710 }
3711
3712 out << "}\n";
3713 return out.str();
3714}
3715
3723template <AlephGraph GT>
3725 const Planarity_Test_Result<GT> &result)
3726{
3727 using Node = typename GT::Node;
3728
3731 report.num_branch_nodes = result.certificate_branch_nodes.size();
3732 report.num_obstruction_edges = result.certificate_obstruction_edges.size();
3733 report.num_paths = result.certificate_paths.size();
3734
3735 if (not result.has_nonplanar_certificate)
3736 return report;
3737
3741 report.num_nodes = nodes.size();
3742
3744 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes); it.has_curr();
3745 it.next_ne())
3746 {
3747 Node *node = it.get_curr_ne();
3748 if (node == nullptr or not node_to_id.contains(node))
3749 {
3750 ++report.null_branch_nodes;
3751 continue;
3752 }
3753
3754 const size_t id = node_to_id.find(node);
3755 if (branch_ids.contains(id))
3756 ++report.duplicate_branch_nodes;
3757 else
3758 branch_ids.insert(id);
3759 }
3760
3761 auto make_edge_key = [&](Node *src, Node *tgt, planarity_detail::Edge_Key &key) -> bool
3762 {
3763 if (src == nullptr or tgt == nullptr)
3764 return false;
3765 if (not node_to_id.contains(src) or not node_to_id.contains(tgt))
3766 return false;
3767
3768 size_t u = node_to_id.find(src);
3769 size_t v = node_to_id.find(tgt);
3770 if (u > v)
3771 std::swap(u, v);
3772
3773 key.u = u;
3774 key.v = v;
3775 return true;
3776 };
3777
3779 for (typename Array<typename Planarity_Test_Result<GT>::Edge_Witness>::Iterator it(
3781 it.has_curr(); it.next_ne())
3782 {
3783 const auto &e = it.get_curr_ne();
3785 if (not make_edge_key(e.src, e.tgt, key))
3786 {
3787 ++report.null_obstruction_edge_endpoints;
3788 continue;
3789 }
3790 obstruction_keys.insert(key);
3791 }
3792
3793 for (typename Array<typename Planarity_Test_Result<GT>::Path_Witness>::Iterator pit(
3794 result.certificate_paths);
3795 pit.has_curr(); pit.next_ne())
3796 {
3797 const auto &path = pit.get_curr_ne();
3798
3799 for (typename Array<Node *>::Iterator nit(path.nodes); nit.has_curr(); nit.next_ne())
3800 if (nit.get_curr_ne() == nullptr)
3801 ++report.null_path_nodes;
3802
3803 const size_t expected_nodes = path.edges.size() + (path.nodes.is_empty() ? 0 : 1);
3804 if (path.nodes.size() != expected_nodes)
3805 ++report.path_node_edge_length_mismatch;
3806
3807 for (size_t i = 0; i < path.edges.size(); ++i)
3808 {
3809 const auto &e = path.edges[i];
3810 if (e.src == nullptr or e.tgt == nullptr)
3811 {
3812 ++report.null_path_edge_endpoints;
3813 continue;
3814 }
3815
3816 if (i + 1 >= path.nodes.size() or path.nodes[i] != e.src or path.nodes[i + 1] != e.tgt)
3817 ++report.path_edge_endpoint_mismatch;
3818
3820 if (not make_edge_key(e.src, e.tgt, key))
3821 {
3822 ++report.null_path_edge_endpoints;
3823 continue;
3824 }
3825
3826 if (not obstruction_keys.contains(key))
3827 ++report.path_edge_not_in_obstruction;
3828 }
3829 }
3830
3832 ++report.kuratowski_shape_mismatch;
3834 {
3835 if (result.certificate_branch_nodes.size() != 5 or result.certificate_paths.is_empty())
3836 ++report.kuratowski_shape_mismatch;
3837 }
3839 {
3840 if (result.certificate_branch_nodes.size() != 6 or result.certificate_paths.is_empty())
3841 ++report.kuratowski_shape_mismatch;
3842 }
3843
3844 report.is_valid = report.has_certificate and report.null_branch_nodes == 0 and
3845 report.duplicate_branch_nodes == 0 and report.null_obstruction_edge_endpoints == 0 and
3846 report.null_path_nodes == 0 and report.null_path_edge_endpoints == 0 and
3847 report.path_node_edge_length_mismatch == 0 and report.path_edge_endpoint_mismatch == 0 and
3848 report.path_edge_not_in_obstruction == 0 and report.kuratowski_shape_mismatch == 0;
3849
3850 return report;
3851}
3852
3857template <AlephGraph GT>
3859{
3860 return validate_nonplanar_certificate(result).is_valid;
3861}
3862
3869template <AlephGraph GT, class Node_Label = Dft_Certificate_Node_Label<GT>>
3871 const Planarity_Test_Result<GT> &result,
3874{
3875 using Node = typename GT::Node;
3876
3878 << "nonplanar_certificate_to_graphml() requires non-planar certificate";
3879
3883
3885 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes); it.has_curr();
3886 it.next_ne())
3887 {
3888 Node *node = it.get_curr_ne();
3889 if (node != nullptr and node_to_id.contains(node))
3890 branch_ids.insert(node_to_id.find(node));
3891 }
3892
3893 auto node_ref = [&](Node *node) -> std::string
3894 {
3895 if (node == nullptr or not node_to_id.contains(node))
3896 return "";
3897 return "n" + std::to_string(node_to_id.find(node));
3898 };
3899
3900 std::ostringstream out;
3901 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
3902 out << "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">\n";
3903 out << " <key id=\"k_node_label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>\n";
3904 out << " <key id=\"k_node_branch\" for=\"node\" attr.name=\"branch\" attr.type=\"boolean\"/>\n";
3905 out << " <key id=\"k_node_ptr\" for=\"node\" attr.name=\"ptr\" attr.type=\"string\"/>\n";
3906 out << " <key id=\"k_edge_kind\" for=\"edge\" attr.name=\"kind\" attr.type=\"string\"/>\n";
3907 out << " <key id=\"k_edge_path_id\" for=\"edge\" attr.name=\"path_id\" attr.type=\"int\"/>\n";
3908 out << " <key id=\"k_edge_input_mult\" for=\"edge\" attr.name=\"input_multiplicity\" "
3909 "attr.type=\"int\"/>\n";
3910 out << " <graph id=\"NonPlanarCertificate\" edgedefault=\"undirected\">\n";
3911
3912 for (size_t i = 0; i < nodes.size(); ++i)
3913 {
3914 out << " <node id=\"n" << i << "\">\n";
3915 out << " <data key=\"k_node_label\">"
3917 out << " <data key=\"k_node_branch\">" << (branch_ids.contains(i) ? "true" : "false")
3918 << "</data>\n";
3919 out << " <data key=\"k_node_ptr\">"
3921 << "</data>\n";
3922 out << " </node>\n";
3923 }
3924
3925 size_t edge_id = 0;
3926 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
3927 {
3928 const auto &e = result.certificate_obstruction_edges[i];
3929 const std::string u = node_ref(e.src);
3930 const std::string v = node_ref(e.tgt);
3931 if (u.empty() or v.empty())
3932 continue;
3933
3934 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u << "\" target=\"" << v << "\">\n";
3935 out << " <data key=\"k_edge_kind\">obstruction</data>\n";
3936 out << " <data key=\"k_edge_path_id\">-1</data>\n";
3937 out << " <data key=\"k_edge_input_mult\">" << e.input_arcs.size() << "</data>\n";
3938 out << " </edge>\n";
3939 }
3940
3941 if (options.graphml_include_paths)
3942 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
3943 {
3944 const auto &path = result.certificate_paths[pid];
3945 for (size_t k = 0; k < path.edges.size(); ++k)
3946 {
3947 const auto &e = path.edges[k];
3948 const std::string u = node_ref(e.src);
3949 const std::string v = node_ref(e.tgt);
3950 if (u.empty() or v.empty())
3951 continue;
3952
3953 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u << "\" target=\"" << v
3954 << "\">\n";
3955 out << " <data key=\"k_edge_kind\">path</data>\n";
3956 out << " <data key=\"k_edge_path_id\">" << pid << "</data>\n";
3957 out << " <data key=\"k_edge_input_mult\">" << e.input_arcs.size() << "</data>\n";
3958 out << " </edge>\n";
3959 }
3960 }
3961
3962 out << " </graph>\n";
3963 out << "</graphml>\n";
3964 return out.str();
3965}
3966
3973template <AlephGraph GT, class Node_Label = Dft_Certificate_Node_Label<GT>>
3975 const Planarity_Test_Result<GT> &result,
3978{
3979 using Node = typename GT::Node;
3980
3982 << "nonplanar_certificate_to_gexf() requires non-planar certificate";
3983
3987
3989 for (typename Array<Node *>::Iterator it(result.certificate_branch_nodes); it.has_curr();
3990 it.next_ne())
3991 {
3992 Node *node = it.get_curr_ne();
3993 if (node != nullptr and node_to_id.contains(node))
3994 branch_ids.insert(node_to_id.find(node));
3995 }
3996
3997 auto node_ref = [&](Node *node) -> std::string
3998 {
3999 if (node == nullptr or not node_to_id.contains(node))
4000 return "";
4001 return "n" + std::to_string(node_to_id.find(node));
4002 };
4003
4004 std::ostringstream out;
4005 out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
4006 out << "<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n";
4007 out << " <meta lastmodifieddate=\"2026-02-22\">\n";
4008 out << " <creator>Aleph Planarity_Test.H</creator>\n";
4009 out << " <description>Non-planar certificate</description>\n";
4010 out << " </meta>\n";
4011 out << " <graph mode=\"static\" defaultedgetype=\"undirected\">\n";
4012 out << " <attributes class=\"node\">\n";
4013 out << " <attribute id=\"n_branch\" title=\"branch\" type=\"boolean\"/>\n";
4014 out << " <attribute id=\"n_ptr\" title=\"ptr\" type=\"string\"/>\n";
4015 out << " </attributes>\n";
4016 out << " <attributes class=\"edge\">\n";
4017 out << " <attribute id=\"e_kind\" title=\"kind\" type=\"string\"/>\n";
4018 out << " <attribute id=\"e_path_id\" title=\"path_id\" type=\"integer\"/>\n";
4019 out << " <attribute id=\"e_input_mult\" title=\"input_multiplicity\" type=\"integer\"/>\n";
4020 out << " </attributes>\n";
4021 out << " <nodes>\n";
4022
4023 for (size_t i = 0; i < nodes.size(); ++i)
4024 {
4025 out << " <node id=\"n" << i << "\" label=\""
4027 out << " <attvalues>\n";
4028 out << " <attvalue for=\"n_branch\" value=\""
4029 << (branch_ids.contains(i) ? "true" : "false") << "\"/>\n";
4030 out << " <attvalue for=\"n_ptr\" value=\""
4032 << "\"/>\n";
4033 out << " </attvalues>\n";
4034 out << " </node>\n";
4035 }
4036
4037 out << " </nodes>\n";
4038 out << " <edges>\n";
4039
4040 size_t edge_id = 0;
4041 for (size_t i = 0; i < result.certificate_obstruction_edges.size(); ++i)
4042 {
4043 const auto &e = result.certificate_obstruction_edges[i];
4044 const std::string u = node_ref(e.src);
4045 const std::string v = node_ref(e.tgt);
4046 if (u.empty() or v.empty())
4047 continue;
4048
4049 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u << "\" target=\"" << v
4050 << "\">\n";
4051 out << " <attvalues>\n";
4052 out << " <attvalue for=\"e_kind\" value=\"obstruction\"/>\n";
4053 out << " <attvalue for=\"e_path_id\" value=\"-1\"/>\n";
4054 out << " <attvalue for=\"e_input_mult\" value=\"" << e.input_arcs.size() << "\"/>\n";
4055 out << " </attvalues>\n";
4056 out << " </edge>\n";
4057 }
4058
4059 if (options.gexf_include_paths)
4060 for (size_t pid = 0; pid < result.certificate_paths.size(); ++pid)
4061 {
4062 const auto &path = result.certificate_paths[pid];
4063 for (size_t k = 0; k < path.edges.size(); ++k)
4064 {
4065 const auto &e = path.edges[k];
4066 const std::string u = node_ref(e.src);
4067 const std::string v = node_ref(e.tgt);
4068 if (u.empty() or v.empty())
4069 continue;
4070
4071 out << " <edge id=\"e" << edge_id++ << "\" source=\"" << u << "\" target=\"" << v
4072 << "\">\n";
4073 out << " <attvalues>\n";
4074 out << " <attvalue for=\"e_kind\" value=\"path\"/>\n";
4075 out << " <attvalue for=\"e_path_id\" value=\"" << pid << "\"/>\n";
4076 out << " <attvalue for=\"e_input_mult\" value=\"" << e.input_arcs.size()
4077 << "\"/>\n";
4078 out << " </attvalues>\n";
4079 out << " </edge>\n";
4080 }
4081 }
4082
4083 out << " </edges>\n";
4084 out << " </graph>\n";
4085 out << "</gexf>\n";
4086 return out.str();
4087}
4088
4093template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
4095{
4098
4099public:
4101 : sa_(std::move(sa)), options_(std::move(options))
4102 {
4103 // empty
4104 }
4105
4107 {
4109 }
4110};
4111
4116template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
4118{
4121
4122public:
4124 : sa_(std::move(sa)), options_(std::move(options))
4125 {
4126 // empty
4127 }
4128
4129 bool operator()(const GT &g) const
4130 {
4132 }
4133};
4134} // namespace Aleph
4135
4136#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:255
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:271
C++20 concepts for the protocol shared by graph algorithms.
WeightedDigraph::Node Node
WeightedDigraph::Arc Arc
long double w
Definition btreepic.C:153
size_t size_t int32_t value
Definition ca-c-api.h:116
size_t size_t int32_t * out
Definition ca-c-api.h:120
bool has_curr() const noexcept
Check if there is a current valid item.
Definition array_it.H:231
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
void empty() noexcept
Empties the container.
Definition tpl_array.H:341
constexpr bool is_empty() const noexcept
Checks if the container is empty.
Definition tpl_array.H:359
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
T & get_last() noexcept
return a modifiable reference to the last element.
Definition tpl_array.H:392
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3960
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:429
Filtered iterator on the nodes of a graph.
Definition tpl_graph.H:1207
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
static bool classify_k5(const Array< size_t > &branches, const Array< Compressed_Path > &paths)
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())
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)
static bool classify_k33(const Array< size_t > &branches, const Array< Compressed_Path > &paths)
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
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
static void generate_permutations(Array< size_t > &tail, const size_t pos, const size_t first, Array< Array< size_t > > &out)
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:779
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:737
bool is_digraph() const noexcept
Return true if the graph this is directed.
Definition graph-dry.H:699
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:785
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:4071
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 a comprehensive 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())
High-level boolean check for graph 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.
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
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
size_t size(Node *root) noexcept
and
Check uniqueness with explicit hash + equality functors.
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:1165
Iterator on the items of an array.
Definition tpl_array.H:608
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:1001
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:223
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 options for planarity testing and auxiliary outputs.
size_t certificate_max_branch_nodes_search
Maximum size of the core subgraph allowed for exhaustive Kuratowski pattern searching.
bool embedding_prefer_lr_linear
If true, the algorithm tries to construct the embedding using a linear-time Left-Right (LR) heuristic...
size_t certificate_max_reduction_passes
Maximum number of global passes in the edge-reduction loop for minimal obstruction extraction.
size_t certificate_max_edges
Maximum number of edges in the simplified graph allowed for full non-planarity witness extraction.
bool embedding_allow_bruteforce_fallback
If the LR heuristic fails to find a valid embedding, allows falling back to an exact but potentially ...
bool compute_embedding
If true and the graph is found to be planar, computes a combinatorial embedding (rotation system and ...
bool embedding_validate_with_euler
If true, validates the generated rotation system using Euler's formula ( ) to ensure it is a valid pl...
size_t embedding_max_combinations
Maximum number of candidate rotations to evaluate during embedding search or repair.
bool compute_nonplanar_certificate
If true and the graph is found to be non-planar, attempts to extract a Kuratowski subdivision (K5 or ...
Description of an edge participating in a witness.
Node * src
Source node in primal graph.
Array< Arc * > input_arcs
All primal arcs matching this edge.
Node * tgt
Target node in primal graph.
Arc * representative_input_arc
One arc from primal matching this edge.
Description of a path participating in a Kuratowski witness.
Array< Node * > nodes
Nodes along the path (including endpoints).
Array< Edge_Witness > edges
Sequential edges along the path.
Entry in a combinatorial rotation system.
Array< Node * > cw_neighbors
Neighbors in clockwise order.
Node * node
Primal graph node.
Complete result of a planarity test execution.
Planarity_Certificate_Type certificate_type
Array< Rotation_Entry > embedding_rotation
Full rotation system.
Array< Array< Node * > > embedding_faces
Lists of nodes forming each face.
size_t embedding_num_faces
Total number of faces found.
size_t num_nodes
Original number of nodes in the graph.
size_t simplified_num_nodes
Nodes in simple undirected representation.
size_t num_input_arcs
Number of arcs passing the SA filter.
Array< Node * > certificate_branch_nodes
Branch nodes for Kuratowski witness.
size_t simplified_num_edges
Edges in simple undirected representation.
bool has_nonplanar_certificate
True if witness was extracted.
size_t ignored_parallel_arcs
Count of parallel arcs collapsed.
bool failed_euler_bound
True if rejected by Euler necessary condition ( ).
bool certificate_search_truncated
True if search limit was reached.
Array< Edge_Witness > certificate_obstruction_edges
Edges in minimal obstruction.
bool is_planar
True iff the graph can be drawn without crossings.
bool embedding_search_truncated
True if search limit was reached.
bool input_is_digraph
True if the input was a directed graph.
size_t ignored_loops
Count of self-loops filtered out.
bool embedding_is_lr_linear
True if LR linear heuristic worked.
Array< Path_Witness > certificate_paths
Paths connecting branch nodes.
bool has_combinatorial_embedding
True if embedding is valid.
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:53
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.