87# ifndef PLANARITY_TEST_H
88# define PLANARITY_TEST_H
130 return "K5_Subdivision";
132 return "K33_Subdivision";
134 return "Minimal_NonPlanar_Obstruction";
425 std::ostringstream
out;
437 namespace planarity_detail
439 static constexpr size_t Null_Edge = std::numeric_limits<size_t>::max();
520 std::ostringstream
out;
521 for (
size_t i = 0; i < s.size(); ++i)
523 const unsigned char c =
static_cast<unsigned char>(s[i]);
551 const char * hex =
"0123456789abcdef";
552 out << hex[(c >> 4) & 0xF] << hex[c & 0xF];
568 std::ostringstream
out;
569 for (
size_t i = 0; i < s.size(); ++i)
572 if (c ==
'\"' or c ==
'\\')
585 std::ostringstream
out;
586 for (
size_t i = 0; i < s.size(); ++i)
588 const unsigned char c =
static_cast<unsigned char>(s[i]);
607 if (c < 0x20
and c !=
'\n' and c !=
'\r' and c !=
'\t')
609 const char * hex =
"0123456789ABCDEF";
611 out << hex[(c >> 4) & 0xF] << hex[c & 0xF] <<
";";
624 template <
class PtrT>
627 std::ostringstream
out;
648 const size_t id =
nodes.size();
667 const auto & path =
pit.get_curr_ne();
672 eit(path.edges);
eit.has_curr();
eit.next_ne())
682 const double bx,
const double by,
683 const double cx,
const double cy)
noexcept
690 const double bx,
const double by,
691 const double cx,
const double cy,
692 const double dx,
const double dy)
noexcept
699 const double min_cd_x = std::min(cx, dx);
700 const double max_cd_x = std::max(cx, dx);
701 const double min_cd_y = std::min(cy, dy);
702 const double max_cd_y = std::max(cy, dy);
710 const double bx,
const double by,
711 const double cx,
const double cy,
712 const double dx,
const double dy)
noexcept
717 constexpr double eps = 1e-12;
728 template <
class GT,
class SA>
789 return e.
u == x ? e.
v : e.
u;
794 for (
size_t i = 1; i < a.
size(); ++i)
796 const size_t key = a[i];
798 while (j > 0
and key < a[j - 1])
816 for (
size_t i = 2; i <= n; ++i)
828 if (
result_.simplified_num_nodes == 0)
834 for (
size_t s = 0; s <
result_.simplified_num_nodes; ++s)
852 const size_t eid = it.get_curr_ne();
868 for (
size_t i = 0; i < a.
size(); ++i)
880 if (pos >= tail.
size())
885 for (
size_t i = 0; i < tail.
size(); ++i)
887 out.append(std::move(order));
891 for (
size_t i = pos; i < tail.
size(); ++i)
893 std::swap(tail[pos], tail[i]);
895 std::swap(tail[pos], tail[i]);
901 const size_t num_components,
928 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
930 const auto &
ord = order[v];
934 for (
size_t i = 0; i <
ord.size(); ++i)
936 const size_t to =
ord[i];
937 const size_t next =
ord[(i + 1) %
ord.size()];
945 for (
size_t d = 0; d < sigma.
size(); ++d)
953 for (
size_t d = 0; d <
dart_src.size(); ++d)
973 - (num_components == 0 ? 0 : (num_components - 1));
978 const long long lhs =
static_cast<long long>(
result_.simplified_num_nodes)
979 -
static_cast<long long>(
result_.simplified_num_edges)
982 const long long rhs =
static_cast<long long>(num_components) + 1;
992 result_.has_combinatorial_embedding =
true;
996 result_.embedding_rotation.empty();
998 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
1003 const auto &
ord = order[v];
1006 re.cw_neighbors.append(
nodes_[it.get_curr_ne()]);
1008 result_.embedding_rotation.append(std::move(
re));
1011 result_.embedding_faces.empty();
1015 const auto & f = fit.get_curr_ne();
1023 while (
result_.embedding_faces.size() <
result_.embedding_num_faces)
1040 return std::numeric_limits<long>::max();
1056 const size_t uedge = it.get_curr_ne();
1112 for (
size_t i = 1; i < E.size(); ++i)
1114 const size_t key = E[i];
1126 const size_t ei = it.get_curr_ne();
1148 const size_t first = E[0];
1278 if (
result_.simplified_num_edges == 0)
1284 for (
size_t s = 0; s <
result_.simplified_num_nodes; ++s)
1305 const size_t uedge = it.get_curr_ne();
1333 Node * node = it.get_curr_ne();
1340 for (
size_t i = 0; i <
result_.num_nodes; ++i)
1352 Arc * arc = it.get_curr_ne();
1362 const size_t u = std::min(src, tgt);
1363 const size_t v = std::max(src, tgt);
1368 ++
result_.ignored_parallel_arcs;
1391 if (
result_.simplified_num_edges == 0)
1399 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
1411 const size_t root = it.get_curr_ne();
1428 static_cast<typename Tmp_Graph::Node *
>(
nullptr));
1430 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
1449 return checker.run().is_planar;
1460 for (
size_t i = 0; i < 5; ++i)
1482 for (
size_t i = 0; i < 5; ++i)
1487 for (
size_t j = i + 1; j < 5; ++j)
1503 for (
size_t i = 0; i < 6; ++i)
1508 for (
size_t i = 0; i < 6; ++i)
1532 for (
size_t i = 0; i < 6; ++i)
1537 for (
size_t s = 0; s < 6; ++s)
1551 const size_t v = it.get_curr_ne();
1567 for (
size_t i = 0; i < 6; ++i)
1578 for (
size_t i = 0; i < 6; ++i)
1579 for (
size_t j = i + 1; j < 6; ++j)
1593 const size_t a = std::min(u, v);
1594 const size_t b = std::max(u, v);
1596 for (
size_t i = 0; i <
edges_.size(); ++i)
1618 w.representative_input_arc =
arcs[0];
1620 w.input_arcs.reserve(
arcs.size());
1622 w.input_arcs.append(it.get_curr_ne());
1630 result_.certificate_branch_nodes.empty();
1631 result_.certificate_paths.empty();
1635 result_.certificate_branch_nodes.append(
nodes_[it.get_curr_ne()]);
1649 for (
size_t i = 1; i < p.
nodes.
size(); ++i)
1668 result_.certificate_search_truncated =
true;
1676 result_.certificate_search_truncated =
true;
1691 for (
size_t j = 0; j <
witness.size(); ++j)
1709 const auto & e = it.get_curr_ne();
1715 while (v <
result_.simplified_num_nodes)
1727 const auto & e = it.get_curr_ne();
1728 if (e.u == v
or e.v == v)
1749 static_cast<size_t>(0));
1753 const auto & e =
wit.get_curr_ne();
1767 result_.certificate_search_truncated =
true;
1773 result_.has_nonplanar_certificate =
true;
1781 result_.certificate_obstruction_edges.append(
1788 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
1791 for (
size_t i = 0; i <
witness.size(); ++i)
1801 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
1802 if (degree[v] > 0
and degree[v] != 2)
1810 result_.certificate_search_truncated =
true;
1823 const size_t b =
bit.get_curr_ne();
1849 const size_t e0 =
inc[curr][0];
1850 const size_t e1 =
inc[curr][1];
1860 curr =
ne.u == curr ?
ne.v :
ne.u;
1875 paths.
append(std::move(p));
1885 for (
size_t i = 0; i <
bcount; ++i)
1888 for (
size_t i = 0; i < paths.
size(); ++i)
1903 auto has_pair = [&](
const size_t a,
const size_t b) ->
bool
1910 for (
size_t a = 0; a <
bcount; ++a)
1911 for (
size_t b = a + 1; b <
bcount; ++b)
1912 for (
size_t c = b + 1; c <
bcount; ++c)
1913 for (
size_t d = c + 1; d <
bcount; ++d)
1914 for (
size_t e = d + 1; e <
bcount; ++e)
1916 const size_t idx[5] = {a, b, c, d, e};
1918 for (
size_t i = 0; i < 5
and ok; ++i)
1919 for (
size_t j = i + 1; j < 5; ++j)
1928 for (
size_t i = 0; i < 5; ++i)
1933 for (
size_t i = 0; i < 5; ++i)
1934 for (
size_t j = i + 1; j < 5; ++j)
1948 for (
size_t a = 0; a <
bcount; ++a)
1949 for (
size_t b = a + 1; b <
bcount; ++b)
1950 for (
size_t c = b + 1; c <
bcount; ++c)
1951 for (
size_t d = c + 1; d <
bcount; ++d)
1952 for (
size_t e = d + 1; e <
bcount; ++e)
1953 for (
size_t f = e + 1; f <
bcount; ++f)
1955 const size_t six[6] = {a, b, c, d, e, f};
1957 const int partitions[10][3] = {
1958 {0, 1, 2}, {0, 1, 3}, {0, 1, 4}, {0, 1, 5},
1959 {0, 2, 3}, {0, 2, 4}, {0, 2, 5},
1960 {0, 3, 4}, {0, 3, 5}, {0, 4, 5}
1963 for (
const auto &
part : partitions)
1974 for (
size_t i = 0; i < 6; ++i)
1976 left[
li++] =
six[i];
1978 right[
ri++] =
six[i];
1981 for (
size_t i = 0; i < 3
and ok; ++i)
1982 for (
size_t j = 0; j < 3; ++j)
1991 for (
size_t i = 0; i < 3; ++i)
1993 for (
size_t i = 0; i < 3; ++i)
1998 for (
size_t i = 0; i < 3; ++i)
1999 for (
size_t j = 0; j < 3; ++j)
2001 paths[
static_cast<size_t>(
first_path[left[i]][right[j]])]);
2016 if (
result_.simplified_num_nodes == 0)
2018 result_.has_combinatorial_embedding =
true;
2019 result_.embedding_is_lr_linear =
true;
2020 result_.embedding_num_faces = 0;
2026 for (
size_t i = 0; i <
result_.simplified_num_nodes; ++i)
2029 if (
result_.simplified_num_edges == 0)
2053 auto sign_of = [&](
auto && self,
const size_t e) ->
long
2075 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2077 auto &
ord = order[v];
2084 ord.append(it.get_curr_ne());
2108 for (
size_t i = 1; i <
ord.size(); ++i)
2121 for (
size_t i = 0; i <
ord.size(); ++i)
2139 result_.embedding_search_truncated =
true;
2145 if (
ord.size() <= 1)
2149 size_t j =
ord.size() - 1;
2152 std::swap(
ord[i],
ord[j]);
2161 if (a.
size() != b.size())
2164 for (
size_t i = 0; i < a.
size(); ++i)
2175 it.has_curr(); it.next_ne())
2188 for (
size_t i = 0; i + 1 < src.size(); ++i)
2202 for (
size_t i = 0; i + 1 < src.size(); ++i)
2203 for (
size_t j = i + 1; j < src.size(); ++j)
2214 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2219 if (order[v].
size() >= 2)
2230 if (order[v].
size() <= 5)
2242 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2246 if (
vars.is_empty())
2249 for (
size_t i = 1; i <
vars.size(); ++i)
2251 const size_t key =
vars[i];
2279 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2291 num_components, faces,
2316 num_components, faces,
2319 out.available =
false;
2320 out.valid_embedding =
false;
2321 out.abs_euler_delta = std::numeric_limits<long long>::max();
2322 out.global_faces = 0;
2326 const long long lhs =
static_cast<long long>(
result_.simplified_num_nodes)
2327 -
static_cast<long long>(
result_.simplified_num_edges)
2329 const long long rhs =
static_cast<long long>(num_components) + 1;
2331 long long delta =
lhs -
rhs;
2335 out.available =
true;
2338 out.abs_euler_delta = delta;
2364 const size_t v =
vit.get_curr_ne();
2365 const size_t prev_opt = selected[v];
2422 result_.embedding_search_truncated =
true;
2428 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2440 result_.embedding_search_truncated =
true;
2446 const size_t v =
vit.get_curr_ne();
2456 result_.embedding_search_truncated =
true;
2479 result_.embedding_search_truncated =
true;
2486 result_.embedding_search_truncated =
true;
2493 if (
result_.simplified_num_nodes == 0)
2495 result_.has_combinatorial_embedding =
true;
2496 result_.embedding_is_lr_linear =
false;
2497 result_.embedding_num_faces = 0;
2505 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2519 if (
result_.simplified_num_edges == 0)
2523 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2526 const size_t num_components =
result_.simplified_num_nodes;
2540 result_.embedding_search_truncated =
true;
2548 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2551 const size_t d =
neigh.size();
2564 result_.embedding_search_truncated =
true;
2570 const size_t first =
neigh[0];
2573 for (
size_t i = 1; i < d; ++i)
2584 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2588 for (
size_t i = 1; i <
vars.size(); ++i)
2590 const size_t key =
vars[i];
2607 for (
size_t v = 0; v <
result_.simplified_num_nodes; ++v)
2620 auto search = [&](
auto && self,
const size_t pos) ->
bool
2622 if (pos ==
vars.size())
2625 const size_t v =
vars[pos];
2629 if (self(self, pos + 1))
2646 result_.embedding_search_truncated =
true;
2670 result_.failed_euler_bound =
true;
2684 result_.embedding_search_truncated =
false;
2690 result_.certificate_search_truncated =
false;
2774 <<
"planar_dual_metadata() requires a planar result with embedding";
2778 <<
"embedding_rotation size mismatch";
2785 md.num_components = 0;
2786 md.num_faces_local = 0;
2787 md.num_faces_global = 0;
2788 md.faces_are_component_local =
false;
2796 for (
size_t i = 0; i < n; ++i)
2800 <<
"embedding_rotation contains null node";
2802 <<
"embedding_rotation contains duplicated node pointer";
2804 node_to_idx.
insert(node, i);
2810 for (
size_t i = 0; i < n; ++i)
2813 for (
size_t i = 0; i < n; ++i)
2821 <<
"embedding_rotation contains null neighbor";
2823 <<
"embedding_rotation references unknown neighbor node";
2825 const size_t v = node_to_idx.
find(
neigh);
2827 <<
"embedding_rotation has self-neighbor in simplified graph";
2829 <<
"embedding_rotation has duplicated neighbor in same rotation";
2837 size_t num_components = 0;
2840 for (
size_t s = 0; s < n; ++s)
2842 if (order[s].is_empty())
2848 comp_id[s] =
static_cast<long>(num_components);
2857 const size_t v = it.get_curr_ne();
2860 comp_id[v] =
static_cast<long>(num_components);
2868 md.num_components = num_components;
2876 for (
size_t u = 0; u < n; ++u)
2880 const size_t v = it.get_curr_ne();
2881 const Dart_Key key{u, v};
2883 <<
"embedding rotation contains repeated directed edge";
2893 <<
"invalid embedding: odd number of darts";
2896 for (
size_t d = 0; d <
dart_src.size(); ++d)
2900 <<
"invalid embedding: missing reverse dart";
2905 for (
size_t u = 0; u < n; ++u)
2907 const auto &
ord = order[u];
2911 for (
size_t i = 0; i <
ord.size(); ++i)
2913 const size_t to =
ord[i];
2914 const size_t next =
ord[(i + 1) %
ord.size()];
2915 const size_t a =
dart_id.find(Dart_Key{u, to});
2921 for (
size_t d = 0; d < sigma.
size(); ++d)
2923 <<
"invalid embedding: incomplete sigma permutation";
2926 for (
size_t d = 0; d <
dart_src.size(); ++d)
2931 const size_t fid =
md.faces.size();
2942 face.
darts.append(fd);
2944 x = sigma[alpha[x]];
2953 md.num_faces_local =
md.faces.size();
2956 const size_t num_faces_global =
m - n + num_components + 1;
2957 md.num_faces_global = num_faces_global;
2958 md.faces_are_component_local =
md.num_faces_local !=
md.num_faces_global;
2960 md.face_adjacency.reserve(
md.num_faces_local);
2961 for (
size_t i = 0; i <
md.num_faces_local; ++i)
2964 md.dual_edges.reserve(
m);
2965 for (
size_t d = 0; d <
dart_src.size(); ++d)
2967 const size_t rev = alpha[d];
2980 md.dual_edges.append(
info);
2981 md.face_adjacency[
f0].append(f1);
2982 md.face_adjacency[f1].append(
f0);
2985 for (
size_t f = 0; f <
md.face_adjacency.size(); ++f)
2987 auto & adj =
md.face_adjacency[f];
2988 for (
size_t i = 1; i < adj.size(); ++i)
2990 const size_t key = adj[i];
2992 while (j > 0
and key < adj[j - 1])
2994 adj[j] = adj[j - 1];
3005 uniq.append(adj[0]);
3006 for (
size_t i = 1; i < adj.size(); ++i)
3007 if (adj[i] !=
uniq.get_last())
3008 uniq.append(adj[i]);
3010 adj = std::move(
uniq);
3027 <<
"build_planar_dual_graph() requires metadata with embedding";
3033 for (
size_t f = 0; f <
md.num_faces_local; ++f)
3037 it.has_curr(); it.next_ne())
3039 const auto & e = it.get_curr_ne();
3080 constexpr size_t Null = std::numeric_limits<size_t>::max();
3081 constexpr double Pi = 3.1415926535897932384626433832795;
3084 <<
"planar_geometric_drawing() requires a planar result with embedding";
3088 <<
"embedding_rotation size mismatch";
3095 drawing.drawing_available =
true;
3096 drawing.drawing_validated_no_crossings =
true;
3100 if (
options.max_outer_faces_to_try == 0)
3102 drawing.drawing_search_truncated =
true;
3110 for (
size_t i = 0; i < n; ++i)
3114 <<
"embedding_rotation contains null node";
3116 <<
"embedding_rotation contains duplicated node pointer";
3118 node_to_idx.
insert(node, i);
3124 for (
size_t i = 0; i < n; ++i)
3127 for (
size_t i = 0; i < n; ++i)
3135 <<
"embedding_rotation contains null neighbor";
3137 <<
"embedding_rotation references unknown node";
3139 const size_t v = node_to_idx.
find(
neigh);
3141 <<
"embedding_rotation has self-neighbor";
3143 <<
"embedding_rotation has duplicated neighbor";
3151 size_t num_components = 0;
3152 for (
size_t s = 0; s < n; ++s)
3157 comp_id[s] =
static_cast<long>(num_components);
3166 const size_t v = it.get_curr_ne();
3169 comp_id[v] =
static_cast<long>(num_components);
3177 drawing.num_components = num_components;
3181 for (
size_t c = 0; c < num_components; ++c)
3183 for (
size_t i = 0; i < n; ++i)
3195 for (
size_t u = 0; u < n; ++u)
3198 const size_t v = it.get_curr_ne();
3205 for (
size_t c = 0; c < num_components; ++c)
3207 for (
size_t i = 0; i < edges.
size(); ++i)
3222 for (
size_t fid = 0;
fid <
md.faces.size(); ++
fid)
3224 const auto & face =
md.faces[
fid];
3225 if (face.darts.is_empty())
3231 it(face.darts); it.has_curr(); it.next_ne())
3233 const auto & d = it.get_curr_ne();
3235 <<
"face metadata references unknown node";
3236 raw.append(node_to_idx.
find(d.src));
3244 for (
size_t i = 0; i <
raw.size(); ++i)
3256 const size_t u = it.get_curr_ne();
3257 if (
seen.contains(u))
3270 cand.score =
cand.boundary_nodes.size();
3276 for (
size_t c = 0; c < num_components; ++c)
3282 for (
size_t c = 0; c < num_components; ++c)
3285 for (
size_t i = 1; i < faces.size(); ++i)
3287 const size_t key = faces[i];
3292 faces[j] = faces[j - 1];
3305 for (
size_t i = 0; i <
ce.size(); ++i)
3307 const auto &
e1 = edges[
ce[i]];
3308 for (
size_t j = i + 1; j <
ce.size(); ++j)
3310 const auto &
e2 = edges[
ce[j]];
3328 size_t & iterations) ->
void
3335 const size_t u = it.get_curr_ne();
3348 const double scale = std::max(1.0, std::sqrt(
static_cast<double>(
cnodes.size())));
3349 const double radius =
options.outer_face_radius *
scale;
3360 const double ang = (2.0 *
Pi *
static_cast<double>(i))
3362 px[u] = radius * std::cos(
ang);
3363 py[u] = radius * std::sin(
ang);
3369 const size_t u = it.get_curr_ne();
3378 const size_t v =
nt.get_curr_ne();
3388 px[u] = 0.01 *
static_cast<double>(u + 1);
3389 py[u] = -0.01 *
static_cast<double>(u + 1);
3393 px[u] = sx /
static_cast<double>(cnt);
3394 py[u] = sy /
static_cast<double>(cnt);
3406 const size_t u = it.get_curr_ne();
3415 const size_t v =
nt.get_curr_ne();
3426 const double nx = sx /
static_cast<double>(cnt);
3427 const double ny = sy /
static_cast<double>(cnt);
3428 const double dx =
nx -
px[u];
3429 const double dy =
ny -
py[u];
3430 const double delta = std::sqrt(dx * dx + dy * dy);
3447 for (
size_t c = 0; c < num_components; ++c)
3453 for (
size_t i = 0; i <
candidates.size(); ++i)
3457 for (
size_t j = i; j > 0; --j)
3477 drawing.drawing_search_truncated =
true;
3508 const size_t idx = fit.get_curr_ne();
3523 <<
"unable to build component drawing candidate";
3525 double min_x = std::numeric_limits<double>::max();
3526 double max_x = -std::numeric_limits<double>::max();
3529 const size_t u = it.get_curr_ne();
3530 min_x = std::min(min_x,
best_x[u]);
3531 max_x = std::max(max_x,
best_x[u]);
3537 const size_t u = it.get_curr_ne();
3546 if (num_components == 1)
3550 drawing.node_positions.reserve(n);
3551 for (
size_t i = 0; i < n; ++i)
3557 drawing.node_positions.append(p);
3560 drawing.drawing_available =
true;
3561 drawing.drawing_validated_no_crossings =
3586 <<
"nonplanar_certificate_to_json() requires non-planar certificate";
3592 std::ostringstream
out;
3598 for (
size_t i = 0; i <
indent; ++i)
3606 return "\"n" + std::to_string(
node_to_id.find(node)) +
"\"";
3619 newline(1);
out <<
"\"certificate_available\": true,";
3620 newline(1);
out <<
"\"certificate_type\": \""
3627 for (
size_t i = 0; i <
nodes.size(); ++i)
3633 out <<
"\"id\": \"n" << i <<
"\", ";
3634 out <<
"\"label\": \""
3636 out <<
"\"ptr\": \""
3656 newline(1);
out <<
"\"obstruction_edges\": [";
3666 out <<
"\"input_arc_count\": " << e.input_arcs.size() <<
", ";
3667 out <<
"\"representative_input_arc\": " <<
arc_ref(e.representative_input_arc);
3683 out <<
"\"nodes\": [";
3684 for (
size_t k = 0;
k < path.nodes.size(); ++
k)
3692 out <<
"\"edges\": [";
3693 for (
size_t k = 0;
k < path.edges.size(); ++
k)
3695 const auto & e = path.edges[
k];
3701 out <<
"\"input_arc_count\": " << e.input_arcs.size() <<
", ";
3702 out <<
"\"representative_input_arc\": " <<
arc_ref(e.representative_input_arc);
3735 <<
"nonplanar_certificate_to_dot() requires non-planar certificate";
3743 it.has_curr(); it.next_ne())
3745 Node * node = it.get_curr_ne();
3750 std::ostringstream
out;
3751 out <<
"graph NonPlanarCertificate {\n";
3752 out <<
" graph [labelloc=\"t\", label=\""
3755 out <<
" node [shape=circle, fontname=\"Helvetica\"];\n";
3756 out <<
" edge [fontname=\"Helvetica\"];\n";
3758 for (
size_t i = 0; i <
nodes.size(); ++i)
3760 out <<
" n" << i <<
" [label=\"n" << i <<
"\\n"
3763 out <<
", style=\"filled\", fillcolor=\"#fff3bf\"";
3770 if (e.src ==
nullptr or e.tgt ==
nullptr
3775 out <<
" n" << u <<
" -- n" << v
3776 <<
" [color=\"#d00000\", penwidth=2.2, label=\"obs x"
3777 << e.input_arcs.size() <<
"\"];\n";
3780 if (
options.dot_highlight_paths)
3783 "#0077b6",
"#2a9d8f",
"#6a4c93",
"#ef476f",
"#ff9f1c",
"#588157"
3791 for (
size_t k = 0;
k < path.edges.size(); ++
k)
3793 const auto & e = path.edges[
k];
3794 if (e.src ==
nullptr or e.tgt ==
nullptr
3799 out <<
" n" << u <<
" -- n" << v
3800 <<
" [color=\"" <<
color
3801 <<
"\", style=\"dashed\", penwidth=1.4, constraint=false, label=\"p"
3841 it.has_curr(); it.next_ne())
3843 Node * node = it.get_curr_ne();
3846 ++
report.null_branch_nodes;
3852 ++
report.duplicate_branch_nodes;
3857 auto make_edge_key = [&](
Node * src,
Node * tgt,
3860 if (src ==
nullptr or tgt ==
nullptr)
3879 const auto & e = it.get_curr_ne();
3881 if (
not make_edge_key(e.src, e.tgt, key))
3883 ++
report.null_obstruction_edge_endpoints;
3892 const auto & path =
pit.get_curr_ne();
3895 if (
nit.get_curr_ne() ==
nullptr)
3896 ++
report.null_path_nodes;
3898 const size_t expected_nodes = path.edges.size() + (path.nodes.is_empty() ? 0 : 1);
3900 ++
report.path_node_edge_length_mismatch;
3902 for (
size_t i = 0; i < path.edges.size(); ++i)
3904 const auto & e = path.edges[i];
3905 if (e.src ==
nullptr or e.tgt ==
nullptr)
3907 ++
report.null_path_edge_endpoints;
3911 if (i + 1 >= path.nodes.size()
3912 or path.nodes[i] != e.src
3913 or path.nodes[i + 1] != e.tgt)
3914 ++
report.path_edge_endpoint_mismatch;
3917 if (
not make_edge_key(e.src, e.tgt, key))
3919 ++
report.null_path_edge_endpoints;
3924 ++
report.path_edge_not_in_obstruction;
3929 ++
report.kuratowski_shape_mismatch;
3934 ++
report.kuratowski_shape_mismatch;
3940 ++
report.kuratowski_shape_mismatch;
3947 and report.null_obstruction_edge_endpoints == 0
3949 and report.null_path_edge_endpoints == 0
3950 and report.path_node_edge_length_mismatch == 0
3951 and report.path_edge_endpoint_mismatch == 0
3952 and report.path_edge_not_in_obstruction == 0
3953 and report.kuratowski_shape_mismatch == 0;
3989 <<
"nonplanar_certificate_to_graphml() requires non-planar certificate";
3997 it.has_curr(); it.next_ne())
3999 Node * node = it.get_curr_ne();
4008 return "n" + std::to_string(
node_to_id.find(node));
4011 std::ostringstream
out;
4012 out <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
4013 out <<
"<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\">\n";
4014 out <<
" <key id=\"k_node_label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>\n";
4015 out <<
" <key id=\"k_node_branch\" for=\"node\" attr.name=\"branch\" attr.type=\"boolean\"/>\n";
4016 out <<
" <key id=\"k_node_ptr\" for=\"node\" attr.name=\"ptr\" attr.type=\"string\"/>\n";
4017 out <<
" <key id=\"k_edge_kind\" for=\"edge\" attr.name=\"kind\" attr.type=\"string\"/>\n";
4018 out <<
" <key id=\"k_edge_path_id\" for=\"edge\" attr.name=\"path_id\" attr.type=\"int\"/>\n";
4019 out <<
" <key id=\"k_edge_input_mult\" for=\"edge\" attr.name=\"input_multiplicity\" attr.type=\"int\"/>\n";
4020 out <<
" <graph id=\"NonPlanarCertificate\" edgedefault=\"undirected\">\n";
4022 for (
size_t i = 0; i <
nodes.size(); ++i)
4024 out <<
" <node id=\"n" << i <<
"\">\n";
4025 out <<
" <data key=\"k_node_label\">"
4028 out <<
" <data key=\"k_node_branch\">"
4029 << (
branch_ids.contains(i) ?
"true" :
"false") <<
"</data>\n";
4030 out <<
" <data key=\"k_node_ptr\">"
4034 out <<
" </node>\n";
4041 const std::string u =
node_ref(e.src);
4042 const std::string v =
node_ref(e.tgt);
4043 if (u.empty()
or v.empty())
4046 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u
4047 <<
"\" target=\"" << v <<
"\">\n";
4048 out <<
" <data key=\"k_edge_kind\">obstruction</data>\n";
4049 out <<
" <data key=\"k_edge_path_id\">-1</data>\n";
4050 out <<
" <data key=\"k_edge_input_mult\">"
4051 << e.input_arcs.size() <<
"</data>\n";
4052 out <<
" </edge>\n";
4055 if (
options.graphml_include_paths)
4059 for (
size_t k = 0;
k < path.edges.size(); ++
k)
4061 const auto & e = path.edges[
k];
4062 const std::string u =
node_ref(e.src);
4063 const std::string v =
node_ref(e.tgt);
4064 if (u.empty()
or v.empty())
4067 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u
4068 <<
"\" target=\"" << v <<
"\">\n";
4069 out <<
" <data key=\"k_edge_kind\">path</data>\n";
4070 out <<
" <data key=\"k_edge_path_id\">"
4071 << pid <<
"</data>\n";
4072 out <<
" <data key=\"k_edge_input_mult\">"
4073 << e.input_arcs.size() <<
"</data>\n";
4074 out <<
" </edge>\n";
4078 out <<
" </graph>\n";
4079 out <<
"</graphml>\n";
4102 <<
"nonplanar_certificate_to_gexf() requires non-planar certificate";
4110 it.has_curr(); it.next_ne())
4112 Node * node = it.get_curr_ne();
4121 return "n" + std::to_string(
node_to_id.find(node));
4124 std::ostringstream
out;
4125 out <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
4126 out <<
"<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n";
4127 out <<
" <meta lastmodifieddate=\"2026-02-22\">\n";
4128 out <<
" <creator>Aleph Planarity_Test.H</creator>\n";
4129 out <<
" <description>Non-planar certificate</description>\n";
4130 out <<
" </meta>\n";
4131 out <<
" <graph mode=\"static\" defaultedgetype=\"undirected\">\n";
4132 out <<
" <attributes class=\"node\">\n";
4133 out <<
" <attribute id=\"n_branch\" title=\"branch\" type=\"boolean\"/>\n";
4134 out <<
" <attribute id=\"n_ptr\" title=\"ptr\" type=\"string\"/>\n";
4135 out <<
" </attributes>\n";
4136 out <<
" <attributes class=\"edge\">\n";
4137 out <<
" <attribute id=\"e_kind\" title=\"kind\" type=\"string\"/>\n";
4138 out <<
" <attribute id=\"e_path_id\" title=\"path_id\" type=\"integer\"/>\n";
4139 out <<
" <attribute id=\"e_input_mult\" title=\"input_multiplicity\" type=\"integer\"/>\n";
4140 out <<
" </attributes>\n";
4141 out <<
" <nodes>\n";
4143 for (
size_t i = 0; i <
nodes.size(); ++i)
4145 out <<
" <node id=\"n" << i <<
"\" label=\""
4148 out <<
" <attvalues>\n";
4149 out <<
" <attvalue for=\"n_branch\" value=\""
4150 << (
branch_ids.contains(i) ?
"true" :
"false") <<
"\"/>\n";
4151 out <<
" <attvalue for=\"n_ptr\" value=\""
4155 out <<
" </attvalues>\n";
4156 out <<
" </node>\n";
4159 out <<
" </nodes>\n";
4160 out <<
" <edges>\n";
4166 const std::string u =
node_ref(e.src);
4167 const std::string v =
node_ref(e.tgt);
4168 if (u.empty()
or v.empty())
4171 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u
4172 <<
"\" target=\"" << v <<
"\">\n";
4173 out <<
" <attvalues>\n";
4174 out <<
" <attvalue for=\"e_kind\" value=\"obstruction\"/>\n";
4175 out <<
" <attvalue for=\"e_path_id\" value=\"-1\"/>\n";
4176 out <<
" <attvalue for=\"e_input_mult\" value=\""
4177 << e.input_arcs.size() <<
"\"/>\n";
4178 out <<
" </attvalues>\n";
4179 out <<
" </edge>\n";
4182 if (
options.gexf_include_paths)
4186 for (
size_t k = 0;
k < path.edges.size(); ++
k)
4188 const auto & e = path.edges[
k];
4189 const std::string u =
node_ref(e.src);
4190 const std::string v =
node_ref(e.tgt);
4191 if (u.empty()
or v.empty())
4194 out <<
" <edge id=\"e" <<
edge_id++ <<
"\" source=\"" << u
4195 <<
"\" target=\"" << v <<
"\">\n";
4196 out <<
" <attvalues>\n";
4197 out <<
" <attvalue for=\"e_kind\" value=\"path\"/>\n";
4198 out <<
" <attvalue for=\"e_path_id\" value=\""
4200 out <<
" <attvalue for=\"e_input_mult\" value=\""
4201 << e.input_arcs.size() <<
"\"/>\n";
4202 out <<
" </attvalues>\n";
4203 out <<
" </edge>\n";
4207 out <<
" </edges>\n";
4208 out <<
" </graph>\n";
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.
WeightedDigraph::Node Node
List_Graph< Graph_Node< Node_Info >, Graph_Arc< Arc_Info > > GT
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
bool build_combinatorial_embedding_bruteforce()
bool classify_k5(const Array< size_t > &branches, const Array< Compressed_Path > &paths) const
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())
void generate_permutations(Array< size_t > &tail, const size_t pos, const size_t first, Array< Array< size_t > > &out) const
size_t add_oriented_edge(const size_t src, const size_t tgt)
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_
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_
bool classify_k33(const Array< size_t > &branches, const Array< Compressed_Path > &paths) const
Planarity_Test_Result< GT >::Edge_Witness make_edge_witness(const size_t u, const size_t v) const
static void sort_size_t_array(Array< size_t > &a)
DynMapTree< Node *, size_t > node_to_idx_
size_t count_components() const
long lowest(const Conflict_Pair &p) const
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 planarity test on an Aleph graph.
Planar_Dual_Metadata< GT > planar_dual_metadata(const Planarity_Test_Result< GT > &result)
Build face and dual-edge metadata from a planar embedding result.
std::string nonplanar_certificate_to_dot(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as GraphViz DOT.
bool is_planar_graph(const GT &g, SA sa=SA(), const Planarity_Test_Options &options=Planarity_Test_Options())
Convenience boolean API for planarity.
DGT build_planar_dual_graph(const Planar_Dual_Metadata< GT > &md)
Build an Aleph dual graph from face/dual metadata.
std::string nonplanar_certificate_to_graphml(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as GraphML.
Planarity_Certificate_Type
Non-planarity witness classification.
std::string nonplanar_certificate_to_json(const Planarity_Test_Result< GT > &result, Node_Label node_label=Node_Label(), const NonPlanar_Certificate_Export_Options &options=NonPlanar_Certificate_Export_Options())
Export non-planar certificate as JSON.
NonPlanar_Certificate_Validation< GT > validate_nonplanar_certificate(const Planarity_Test_Result< GT > &result)
Validate structural consistency of a non-planar certificate.
bool nonplanar_certificate_is_valid(const Planarity_Test_Result< GT > &result)
Convenience predicate over validate_nonplanar_certificate().
@ 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.
and
Check uniqueness with explicit hash + equality functors.
size_t size(Node *root) noexcept
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
Itor1 search(Itor1 beg, const Itor1 &end, Itor2 searchBeg, const Itor2 &searchEnd, BinaryPredicate op=BinaryPredicate())
Search for a subrange within a range.
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 for planarity test optional outputs.
size_t certificate_max_branch_nodes_search
Skip exact Kuratowski pattern search when branch-core exceeds this size.
bool embedding_prefer_lr_linear
Try LR-first embedding construction (with bounded LR-local repair) before any exhaustive fallback.
size_t certificate_max_reduction_passes
Max global passes in witness edge-reduction loop.
size_t certificate_max_edges
Skip witness extraction when simplified graph has more than this many edges.
bool embedding_allow_bruteforce_fallback
Allow exact embedding fallback if LR-constructive embedding fails validation.
bool compute_embedding
If true and graph is planar, tries to compute combinatorial embedding.
bool embedding_validate_with_euler
Validate candidate rotation system with Euler face check.
size_t embedding_max_combinations
Max evaluations in embedding repair/search phases.
bool compute_nonplanar_certificate
If true and graph is non-planar, tries to extract witness.
Array< Arc * > input_arcs
Arc * representative_input_arc
Array< Edge_Witness > edges
Array< Node * > cw_neighbors
Result of planarity testing.
Planarity_Certificate_Type certificate_type
Array< Rotation_Entry > embedding_rotation
Array< Array< Node * > > embedding_faces
size_t embedding_num_faces
size_t num_nodes
Number of graph nodes.
size_t simplified_num_nodes
Number of nodes in simplified graph.
size_t num_input_arcs
Number of input arcs passing SA filter.
Array< Node * > certificate_branch_nodes
size_t simplified_num_edges
Number of edges in simplified graph.
bool has_nonplanar_certificate
size_t ignored_parallel_arcs
Number of collapsed parallel arcs.
bool failed_euler_bound
True if rejected by Euler necessary bound.
bool certificate_search_truncated
Array< Edge_Witness > certificate_obstruction_edges
bool is_planar
True iff planar.
bool embedding_search_truncated
bool input_is_digraph
True if original graph was directed.
size_t ignored_loops
Number of ignored self-loops.
bool embedding_is_lr_linear
Array< Path_Witness > certificate_paths
bool has_combinatorial_embedding
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.