87#ifndef PLANARITY_TEST_H
88#define PLANARITY_TEST_H
130 return "K5_Subdivision";
132 return "K33_Subdivision";
134 return "Minimal_NonPlanar_Obstruction";
212template <AlephGraph GT>
277template <AlephGraph GT>
295template <AlephGraph GT>
357template <AlephGraph GT>
405template <AlephGraph GT>
438 std::ostringstream
out;
448namespace planarity_detail {
450static constexpr size_t Null_Edge = std::numeric_limits<size_t>::max();
523 std::ostringstream
out;
524 for (
const char i : s)
525 switch (
const auto c =
static_cast<unsigned char>(i))
552 const char *hex =
"0123456789abcdef";
553 out << hex[(c >> 4) & 0xF] << hex[c & 0xF];
565 std::ostringstream
out;
566 for (
const char c : s)
567 if (c ==
'\"' or c ==
'\\')
578 std::ostringstream
out;
579 for (
const char i : s)
580 switch (
const auto c =
static_cast<unsigned char>(i))
598 if (c < 0x20
and c !=
'\n' and c !=
'\r' and c !=
'\t')
600 const char *hex =
"0123456789ABCDEF";
602 out << hex[(c >> 4) & 0xF] << hex[c & 0xF] <<
";";
614 std::ostringstream
out;
619template <AlephGraph GT>
633 const size_t id =
nodes.size();
644 it.has_curr(); it.next_ne())
652 pit.has_curr();
pit.next_ne())
654 const auto &path =
pit.get_curr_ne();
659 eit.has_curr();
eit.next_ne())
672 const double cy)
noexcept
684 const double dy)
noexcept
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);
707 const double dy)
noexcept
712 constexpr double eps = 1e-12;
722template <AlephGraph GT, ArcFilter<GT> SA>
783 return e.
u == x ? e.
v : e.
u;
788 for (
size_t i = 1; i < a.
size(); ++i)
790 const size_t key = a[i];
792 while (j > 0
and key < a[j - 1])
810 for (
size_t i = 2; i <= n; ++i)
822 if (
result_.simplified_num_nodes == 0)
828 for (
size_t s = 0; s <
result_.simplified_num_nodes; ++s)
845 const size_t eid = it.get_curr_ne();
861 for (
size_t i = 0; i < a.
size(); ++i)
873 if (pos >= tail.
size())
878 for (
unsigned long i : tail)
880 out.append(std::move(order));
884 for (
size_t i = pos; i < tail.
size(); ++i)
886 std::swap(tail[pos], tail[i]);
888 std::swap(tail[pos], tail[i]);
894 const size_t num_components,
921 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
923 const auto &
ord = order[v];
927 for (
size_t i = 0; i <
ord.size(); ++i)
929 const size_t to =
ord[i];
930 const size_t next =
ord[(i + 1) %
ord.size()];
938 for (
const unsigned long d : sigma)
946 for (
size_t d = 0; d <
dart_src.size(); ++d)
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);
973 const long long rhs =
static_cast<long long>(num_components) + 1;
983 result_.has_combinatorial_embedding =
true;
987 result_.embedding_rotation.empty();
989 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
994 const auto &
ord = order[v];
997 re.cw_neighbors.append(
nodes_[it.get_curr_ne()]);
999 result_.embedding_rotation.append(std::move(
re));
1002 result_.embedding_faces.empty();
1006 const auto &f = fit.get_curr_ne();
1014 while (
result_.embedding_faces.size() <
result_.embedding_num_faces)
1031 return std::numeric_limits<long>::max();
1047 const size_t uedge = it.get_curr_ne();
1096 for (
size_t i = 1; i < E.size(); ++i)
1098 const size_t key = E[i];
1110 const size_t ei = it.get_curr_ne();
1130 if (
const size_t first = E[0];
ei == first)
1257 if (
result_.simplified_num_edges == 0)
1263 for (
size_t s = 0; s <
result_.simplified_num_nodes; ++s)
1283 const size_t uedge = it.get_curr_ne();
1311 Node *node = it.get_curr_ne();
1318 for (
size_t i = 0; i <
result_.num_nodes; ++i)
1330 Arc *arc = it.get_curr_ne();
1340 const size_t u = std::min(src, tgt);
1341 const size_t v = std::max(src, tgt);
1346 ++
result_.ignored_parallel_arcs;
1369 if (
result_.simplified_num_edges == 0)
1377 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
1389 const size_t root = it.get_curr_ne();
1406 static_cast<typename Tmp_Graph::Node *
>(
nullptr));
1408 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
1427 return checker.run().is_planar;
1437 for (
size_t i = 0; i < 5; ++i)
1459 for (
size_t i = 0; i < 5; ++i)
1464 for (
size_t j = i + 1; j < 5; ++j)
1479 for (
size_t i = 0; i < 6; ++i)
1484 for (
size_t i = 0; i < 6; ++i)
1508 for (
size_t i = 0; i < 6; ++i)
1513 for (
size_t s = 0; s < 6; ++s)
1527 const size_t v = it.get_curr_ne();
1541 for (
size_t i = 0; i < 6; ++i)
1550 for (
size_t i = 0; i < 6; ++i)
1551 for (
size_t j = i + 1; j < 6; ++j)
1565 const size_t a = std::min(u, v);
1566 const size_t b = std::max(u, v);
1568 for (
size_t i = 0; i <
edges_.size(); ++i)
1576 const size_t v)
const
1590 w.representative_input_arc =
arcs[0];
1592 w.input_arcs.reserve(
arcs.size());
1594 w.input_arcs.append(it.get_curr_ne());
1601 result_.certificate_branch_nodes.empty();
1602 result_.certificate_paths.empty();
1606 result_.certificate_branch_nodes.append(
nodes_[it.get_curr_ne()]);
1620 for (
size_t i = 1; i < p.
nodes.
size(); ++i)
1638 result_.certificate_search_truncated =
true;
1645 result_.certificate_search_truncated =
true;
1659 for (
size_t j = 0; j <
witness.size(); ++j)
1675 const auto &e = it.get_curr_ne();
1681 while (v <
result_.simplified_num_nodes)
1693 const auto &e = it.get_curr_ne();
1694 if (e.u == v
or e.v == v)
1716 const auto &e =
wit.get_curr_ne();
1730 result_.certificate_search_truncated =
true;
1736 result_.has_nonplanar_certificate =
true;
1750 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
1753 for (
size_t i = 0; i <
witness.size(); ++i)
1763 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
1764 if (degree[v] > 0
and degree[v] != 2)
1772 result_.certificate_search_truncated =
true;
1785 const size_t b = bit.get_curr_ne();
1810 const size_t e0 =
inc[curr][0];
1811 const size_t e1 =
inc[curr][1];
1821 curr = ne.u == curr ? ne.v : ne.u;
1834 paths.
append(std::move(p));
1844 for (
size_t i = 0; i <
bcount; ++i)
1847 for (
size_t i = 0; i < paths.
size(); ++i)
1862 auto has_pair = [&](
const size_t a,
const size_t b) ->
bool
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)
1875 const size_t idx[5] = {a, b, c, d, e};
1877 for (
size_t i = 0; i < 5
and ok; ++i)
1878 for (
size_t j = i + 1; j < 5; ++j)
1887 for (
unsigned long i : idx)
1892 for (
size_t i = 0; i < 5; ++i)
1893 for (
size_t j = i + 1; j < 5; ++j)
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)
1914 const size_t six[6] = {a, b, c, d, e, f};
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}};
1920 for (
const auto &
part : partitions)
1931 for (
size_t i = 0; i < 6; ++i)
1933 left[
li++] =
six[i];
1935 right[
ri++] =
six[i];
1938 for (
size_t i = 0; i < 3
and ok; ++i)
1939 for (
size_t j : right)
1948 for (
unsigned long i : left)
1950 for (
unsigned long i : right)
1955 for (
unsigned long i : left)
1956 for (
unsigned long j : right)
1972 if (
result_.simplified_num_nodes == 0)
1974 result_.has_combinatorial_embedding =
true;
1975 result_.embedding_is_lr_linear =
true;
1976 result_.embedding_num_faces = 0;
1982 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
1985 if (
result_.simplified_num_edges == 0)
2007 auto sign_of = [&](
auto &&self,
const size_t e) ->
long
2029 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2031 auto &
ord = order[v];
2038 ord.append(it.get_curr_ne());
2058 for (
size_t i = 1; i <
ord.size(); ++i)
2071 for (
size_t &i :
ord)
2088 result_.embedding_search_truncated =
true;
2094 if (
ord.size() <= 1)
2098 size_t j =
ord.size() - 1;
2101 std::swap(
ord[i],
ord[j]);
2109 if (a.
size() != b.size())
2112 for (
size_t i = 0; i < a.
size(); ++i)
2133 for (
size_t i = 0; i + 1 < src.size(); ++i)
2146 for (
size_t i = 0; i + 1 < src.size(); ++i)
2147 for (
size_t j = i + 1; j < src.size(); ++j)
2158 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2163 if (order[v].
size() >= 2)
2174 if (order[v].
size() <= 5)
2186 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2190 if (
vars.is_empty())
2193 for (
size_t i = 1; i <
vars.size(); ++i)
2195 const size_t key =
vars[i];
2207 bool available =
false;
2221 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
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;
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;
2269 long long delta = lhs - rhs;
2273 out.available =
true;
2275 out.abs_euler_delta = delta;
2300 const size_t v =
vit.get_curr_ne();
2301 const size_t prev_opt = selected[v];
2357 result_.embedding_search_truncated =
true;
2363 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2375 result_.embedding_search_truncated =
true;
2381 const size_t v =
vit.get_curr_ne();
2391 result_.embedding_search_truncated =
true;
2414 result_.embedding_search_truncated =
true;
2421 result_.embedding_search_truncated =
true;
2428 if (
result_.simplified_num_nodes == 0)
2430 result_.has_combinatorial_embedding =
true;
2431 result_.embedding_is_lr_linear =
false;
2432 result_.embedding_num_faces = 0;
2440 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2454 if (
result_.simplified_num_edges == 0)
2458 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2461 const size_t num_components =
result_.simplified_num_nodes;
2475 result_.embedding_search_truncated =
true;
2483 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2486 const size_t d =
neigh.size();
2497 result_.embedding_search_truncated =
true;
2503 const size_t first =
neigh[0];
2506 for (
size_t i = 1; i < d; ++i)
2517 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2521 for (
size_t i = 1; i <
vars.size(); ++i)
2523 const size_t key =
vars[i];
2540 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2553 auto search = [&](
auto &&self,
const size_t pos) ->
bool
2555 if (pos ==
vars.size())
2558 const size_t v =
vars[pos];
2562 if (self(self, pos + 1))
2579 result_.embedding_search_truncated =
true;
2600 result_.failed_euler_bound =
true;
2614 result_.embedding_search_truncated =
false;
2620 result_.certificate_search_truncated =
false;
2655template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
2668template <AlephGraph GT>
2685template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
2697template <AlephGraph GT>
2709template <AlephGraph GT>
2717 <<
"planar_dual_metadata() requires a planar result with embedding";
2727 md.num_components = 0;
2728 md.num_faces_local = 0;
2729 md.num_faces_global = 0;
2730 md.faces_are_component_local =
false;
2738 for (
size_t i = 0; i < n; ++i)
2743 <<
"embedding_rotation contains duplicated node pointer";
2745 node_to_idx.
insert(node, i);
2751 for (
size_t i = 0; i < n; ++i)
2754 for (
size_t i = 0; i < n; ++i)
2763 <<
"embedding_rotation references unknown neighbor node";
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";
2768 <<
"embedding_rotation has duplicated neighbor in same rotation";
2776 size_t num_components = 0;
2779 for (
size_t s = 0; s < n; ++s)
2781 if (order[s].is_empty())
2787 comp_id[s] =
static_cast<long>(num_components);
2796 const size_t v = it.get_curr_ne();
2799 comp_id[v] =
static_cast<long>(num_components);
2807 md.num_components = num_components;
2815 for (
size_t u = 0; u < n; ++u)
2819 const size_t v = it.get_curr_ne();
2820 const Dart_Key key{u, v};
2822 <<
"embedding rotation contains repeated directed edge";
2834 for (
size_t d = 0; d <
dart_src.size(); ++d)
2842 for (
size_t u = 0; u < n; ++u)
2844 const auto &
ord = order[u];
2848 for (
size_t i = 0; i <
ord.size(); ++i)
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});
2858 for (
unsigned long d : sigma)
2862 for (
size_t d = 0; d <
dart_src.size(); ++d)
2867 const size_t fid =
md.faces.size();
2880 x = sigma[alpha[x]];
2889 md.num_faces_local =
md.faces.size();
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;
2896 md.face_adjacency.reserve(
md.num_faces_local);
2897 for (
size_t i = 0; i <
md.num_faces_local; ++i)
2900 md.dual_edges.reserve(
m);
2901 for (
size_t d = 0; d <
dart_src.size(); ++d)
2903 const size_t rev = alpha[d];
2916 md.dual_edges.append(
info);
2917 md.face_adjacency[
f0].append(f1);
2918 md.face_adjacency[f1].append(
f0);
2921 for (
size_t f = 0; f <
md.face_adjacency.size(); ++f)
2923 auto &adj =
md.face_adjacency[f];
2924 for (
size_t i = 1; i < adj.size(); ++i)
2926 const size_t key = adj[i];
2928 while (j > 0
and key < adj[j - 1])
2930 adj[j] = adj[j - 1];
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]);
2946 adj = std::move(
uniq);
2956template <AlephGraph GT,
class DGT = Default_Planar_Dual_Graph<GT>>
2960 <<
"build_planar_dual_graph() requires metadata with embedding";
2966 for (
size_t f = 0; f <
md.num_faces_local; ++f)
2972 const auto &e = it.get_curr_ne();
2983template <AlephGraph GT,
class DGT = Default_Planar_Dual_Graph<GT>>
3000template <AlephGraph GT>
3008 constexpr size_t Null = std::numeric_limits<size_t>::max();
3009 constexpr double Pi = 3.1415926535897932384626433832795;
3012 <<
"planar_geometric_drawing() requires a planar result with embedding";
3022 drawing.drawing_available =
true;
3023 drawing.drawing_validated_no_crossings =
true;
3027 if (
options.max_outer_faces_to_try == 0)
3029 drawing.drawing_search_truncated =
true;
3037 for (
size_t i = 0; i < n; ++i)
3042 <<
"embedding_rotation contains duplicated node pointer";
3044 node_to_idx.
insert(node, i);
3050 for (
size_t i = 0; i < n; ++i)
3053 for (
size_t i = 0; i < n; ++i)
3062 <<
"embedding_rotation references unknown node";
3064 const size_t v = node_to_idx.
find(
neigh);
3074 size_t num_components = 0;
3075 for (
size_t s = 0; s < n; ++s)
3080 comp_id[s] =
static_cast<long>(num_components);
3089 const size_t v = it.get_curr_ne();
3092 comp_id[v] =
static_cast<long>(num_components);
3100 drawing.num_components = num_components;
3104 for (
size_t c = 0; c < num_components; ++c)
3106 for (
size_t i = 0; i < n; ++i)
3118 for (
size_t u = 0; u < n; ++u)
3121 const size_t v = it.get_curr_ne();
3128 for (
size_t c = 0; c < num_components; ++c)
3130 for (
size_t i = 0; i < edges.
size(); ++i)
3145 for (
size_t fid = 0;
fid <
md.faces.size(); ++
fid)
3147 const auto &face =
md.faces[
fid];
3148 if (face.darts.is_empty())
3152 raw.
reserve(face.darts.size());
3154 it.has_curr(); it.next_ne())
3156 const auto &d = it.get_curr_ne();
3158 <<
"face metadata references unknown node";
3167 for (
unsigned long i : raw)
3179 const size_t u = it.get_curr_ne();
3180 if (
seen.contains(u))
3193 cand.score =
cand.boundary_nodes.size();
3199 for (
size_t c = 0; c < num_components; ++c)
3205 for (
size_t c = 0; c < num_components; ++c)
3208 for (
size_t i = 1; i < faces.size(); ++i)
3210 const size_t key = faces[i];
3214 faces[j] = faces[j - 1];
3226 for (
size_t i = 0; i <
ce.size(); ++i)
3228 const auto &
e1 = edges[
ce[i]];
3229 for (
size_t j = i + 1; j <
ce.size(); ++j)
3231 const auto &
e2 = edges[
ce[j]];
3251 const size_t u = it.get_curr_ne();
3264 const double scale = std::max(1.0, std::sqrt(
static_cast<double>(
cnodes.size())));
3265 const double radius =
options.outer_face_radius *
scale;
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);
3285 const size_t u = it.get_curr_ne();
3294 const size_t v =
nt.get_curr_ne();
3304 px[u] = 0.01 *
static_cast<double>(u + 1);
3305 py[u] = -0.01 *
static_cast<double>(u + 1);
3309 px[u] = sx /
static_cast<double>(cnt);
3310 py[u] = sy /
static_cast<double>(cnt);
3322 const size_t u = it.get_curr_ne();
3331 const size_t v =
nt.get_curr_ne();
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);
3363 for (
size_t c = 0; c < num_components; ++c)
3369 for (
size_t i = 0; i <
candidates.size(); ++i)
3373 for (
size_t j = i; j > 0; --j)
3392 drawing.drawing_search_truncated =
true;
3421 const size_t idx = fit.get_curr_ne();
3436 double min_x = std::numeric_limits<double>::max();
3437 double max_x = -std::numeric_limits<double>::max();
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]);
3448 const size_t u = it.get_curr_ne();
3457 if (num_components == 1)
3461 drawing.node_positions.reserve(n);
3462 for (
size_t i = 0; i < n; ++i)
3468 drawing.node_positions.append(p);
3471 drawing.drawing_available =
true;
3472 drawing.drawing_validated_no_crossings =
3483template <AlephGraph GT,
class Node_Label = Dft_Certificate_Node_Label<GT>>
3493 <<
"nonplanar_certificate_to_json() requires non-planar certificate";
3499 std::ostringstream
out;
3500 auto newline = [&](
const size_t indent)
3505 for (
size_t i = 0; i < indent; ++i)
3513 return "\"n" + std::to_string(
node_to_id.find(node)) +
"\"";
3526 out <<
"\"is_planar\": false,";
3528 out <<
"\"certificate_available\": true,";
3530 out <<
"\"certificate_type\": \""
3536 out <<
"\"nodes\": [";
3537 for (
size_t i = 0; i <
nodes.size(); ++i)
3543 out <<
"\"id\": \"n" << i <<
"\", ";
3545 out <<
"\"ptr\": \""
3555 out <<
"\"branch_nodes\": [";
3567 out <<
"\"obstruction_edges\": [";
3577 out <<
"\"input_arc_count\": " << e.input_arcs.size() <<
", ";
3578 out <<
"\"representative_input_arc\": " <<
arc_ref(e.representative_input_arc);
3586 out <<
"\"paths\": [";
3595 out <<
"\"nodes\": [";
3596 for (
size_t k = 0;
k < path.nodes.size(); ++
k)
3604 out <<
"\"edges\": [";
3605 for (
size_t k = 0;
k < path.edges.size(); ++
k)
3607 const auto &e = path.edges[
k];
3613 out <<
"\"input_arc_count\": " << e.input_arcs.size() <<
", ";
3614 out <<
"\"representative_input_arc\": " <<
arc_ref(e.representative_input_arc);
3635template <AlephGraph GT,
class Node_Label = Dft_Certificate_Node_Label<GT>>
3644 <<
"nonplanar_certificate_to_dot() requires non-planar certificate";
3654 Node *node = it.get_curr_ne();
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";
3666 for (
size_t i = 0; i <
nodes.size(); ++i)
3668 out <<
" n" << i <<
" [label=\"n" << i <<
"\\n"
3671 out <<
", style=\"filled\", fillcolor=\"#fff3bf\"";
3683 out <<
" n" << u <<
" -- n" << v <<
" [color=\"#d00000\", penwidth=2.2, label=\"obs x"
3684 << e.input_arcs.size() <<
"\"];\n";
3687 if (
options.dot_highlight_paths)
3689 static const char *
kPalette[] = {
"#0077b6",
"#2a9d8f",
"#6a4c93",
3690 "#ef476f",
"#ff9f1c",
"#588157"};
3697 for (
size_t k = 0;
k < path.edges.size(); ++
k)
3699 const auto &e = path.edges[
k];
3705 out <<
" n" << u <<
" -- n" << v <<
" [color=\"" <<
color
3706 <<
"\", style=\"dashed\", penwidth=1.4, constraint=false, label=\"p" << pid
3723template <AlephGraph GT>
3747 Node *node = it.get_curr_ne();
3750 ++
report.null_branch_nodes;
3756 ++
report.duplicate_branch_nodes;
3763 if (src ==
nullptr or tgt ==
nullptr)
3781 it.has_curr(); it.next_ne())
3783 const auto &e = it.get_curr_ne();
3785 if (
not make_edge_key(e.src, e.tgt, key))
3787 ++
report.null_obstruction_edge_endpoints;
3795 pit.has_curr();
pit.next_ne())
3797 const auto &path =
pit.get_curr_ne();
3800 if (
nit.get_curr_ne() ==
nullptr)
3801 ++
report.null_path_nodes;
3803 const size_t expected_nodes = path.edges.size() + (path.nodes.is_empty() ? 0 : 1);
3805 ++
report.path_node_edge_length_mismatch;
3807 for (
size_t i = 0; i < path.edges.size(); ++i)
3809 const auto &e = path.edges[i];
3810 if (e.src ==
nullptr or e.tgt ==
nullptr)
3812 ++
report.null_path_edge_endpoints;
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;
3820 if (
not make_edge_key(e.src, e.tgt, key))
3822 ++
report.null_path_edge_endpoints;
3827 ++
report.path_edge_not_in_obstruction;
3832 ++
report.kuratowski_shape_mismatch;
3836 ++
report.kuratowski_shape_mismatch;
3841 ++
report.kuratowski_shape_mismatch;
3845 report.duplicate_branch_nodes == 0
and report.null_obstruction_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;
3857template <AlephGraph GT>
3869template <AlephGraph GT,
class Node_Label = Dft_Certificate_Node_Label<GT>>
3878 <<
"nonplanar_certificate_to_graphml() requires non-planar certificate";
3888 Node *node = it.get_curr_ne();
3897 return "n" + std::to_string(
node_to_id.find(node));
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";
3912 for (
size_t i = 0; i <
nodes.size(); ++i)
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")
3919 out <<
" <data key=\"k_node_ptr\">"
3922 out <<
" </node>\n";
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())
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";
3941 if (
options.graphml_include_paths)
3945 for (
size_t k = 0;
k < path.edges.size(); ++
k)
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())
3953 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u <<
"\" target=\"" << v
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";
3962 out <<
" </graph>\n";
3963 out <<
"</graphml>\n";
3973template <AlephGraph GT,
class Node_Label = Dft_Certificate_Node_Label<GT>>
3982 <<
"nonplanar_certificate_to_gexf() requires non-planar certificate";
3992 Node *node = it.get_curr_ne();
4001 return "n" + std::to_string(
node_to_id.find(node));
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";
4023 for (
size_t i = 0; i <
nodes.size(); ++i)
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=\""
4033 out <<
" </attvalues>\n";
4034 out <<
" </node>\n";
4037 out <<
" </nodes>\n";
4038 out <<
" <edges>\n";
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())
4049 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u <<
"\" target=\"" << v
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";
4059 if (
options.gexf_include_paths)
4063 for (
size_t k = 0;
k < path.edges.size(); ++
k)
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())
4071 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u <<
"\" target=\"" << v
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()
4078 out <<
" </attvalues>\n";
4079 out <<
" </edge>\n";
4083 out <<
" </edges>\n";
4084 out <<
" </graph>\n";
4093template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
4116template <AlephGraph GT, ArcFilter<GT> SA = Dft_Show_Arc<GT>>
Exception handling system with formatted messages for Aleph-w.
#define ah_runtime_error_unless(C)
Throws std::runtime_error if condition does NOT hold.
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
C++20 concepts for the protocol shared by graph algorithms.
WeightedDigraph::Node Node
size_t size_t int32_t value
size_t size_t int32_t * out
bool has_curr() const noexcept
Check if there is a current valid item.
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
void empty() noexcept
Empties the container.
constexpr bool is_empty() const noexcept
Checks if the container is empty.
T & append(const T &data)
Append a copy of data
T & get_last() noexcept
return a modifiable reference to the last element.
void reserve(size_t cap)
Reserves cap cells into the array.
Generic directed graph (digraph) wrapper template.
void insert(Dlink *node) noexcept
Insert node after this.
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.
Graph_Node< Node_Info > Node
The graph type.
Graph_Arc< Arc_Info > Arc
The node class type.
Filtered iterator on the nodes of a graph.
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)
bool build_combinatorial_embedding_bruteforce()
bool fails_component_euler_bound() const
size_t other_endpoint(const size_t edge_id, const size_t x) const
LR_Planarity_Checker(const GT &g, SA sa, Planarity_Test_Options options=Planarity_Test_Options())
size_t add_oriented_edge(const size_t src, const size_t tgt)
Array< Array< Arc * > > simplified_edge_input_arcs_
bool simple_edges_are_planar(const Array< Simple_Edge > &simple_edges) const
Array< size_t > oriented_src_
Array< long > nesting_depth_
static size_t factorial_bounded(const size_t n, const size_t cap)
Array< char > undirected_edge_seen_
void build_underlying_simple_graph()
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)
Planarity_Test_Result< GT > result_
bool build_combinatorial_embedding_linear_lr()
Array< Simple_Edge > edges_
bool run_lr_planarity_test()
void orient_dfs(const size_t v)
Array< size_t > undirected_to_oriented_
Array< Conflict_Pair > stack_bottom_
void build_nonplanar_certificate()
bool build_combinatorial_embedding()
static size_t find_in_array(const Array< size_t > &a, const size_t value)
void test_dfs(const size_t v)
Planarity_Test_Result< GT > run()
Array< size_t > lowpt_edge_
Array< size_t > parent_edge_
static bool classify_k33(const Array< size_t > &branches, const Array< Compressed_Path > &paths)
Array< size_t > oriented_tgt_
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)
Planarity_Test_Options options_
size_t find_simplified_edge_id(const size_t u, const size_t v) const
Array< Conflict_Pair > stack_
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)
DynMapTree< Node *, size_t > node_to_idx_
size_t count_components() const
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)
Array< Array< size_t > > child_edges_
Array< Array< size_t > > incident_edges_
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
bool is_digraph() const noexcept
Return true if the graph this is directed.
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Key * append(const Key &key)
Alias for insert() (copy version).
constexpr size_t size() const noexcept
Returns the number of entries in the table.
__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)
static constexpr size_t palette_size()
DynArray< Graph::Node * > nodes
DynArray< Graph::Arc * > arcs
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().
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().
@ Minimal_NonPlanar_Obstruction
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.
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.
std::string to_string(const time_t t, const std::string &format)
Format a time_t value into a string using format.
void next()
Advance all underlying iterators (bounds-checked).
static long & color(typename GT::Node *p)
static struct argp_option options[]
Filtered iterator on all the arcs of a graph.
Iterator on the items of an array.
Default node labeler for certificate export.
std::string operator()(typename GT::Node *node) const
Default filter for filtered iterators on arcs.
Arc of graph implemented with double-linked adjacency lists.
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.
size_t kuratowski_shape_mismatch
size_t path_node_edge_length_mismatch
size_t null_path_edge_endpoints
size_t path_edge_not_in_obstruction
size_t duplicate_branch_nodes
size_t path_edge_endpoint_mismatch
size_t num_obstruction_edges
size_t null_obstruction_edge_endpoints
Represents a missing value.
Dual-edge payload linked to a primal simplified edge.
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.
bool drawing_validated_no_crossings
Array< Node_Position > node_positions
bool drawing_search_truncated
size_t relaxation_iterations
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)
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.