Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
planarity_test_example.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
43# include <algorithm>
44# include <cstdlib>
45# include <filesystem>
46# include <fstream>
47# include <iostream>
48# include <string>
49
50# include <Planarity_Test.H>
51# include <tpl_graph.H>
52
53using namespace std;
54using namespace Aleph;
55
56namespace
57{
58 namespace fs = std::filesystem;
59
63
71 void write_file_or_warn(const string & path,
72 const string & content,
73 const string & label)
74 {
75 ofstream f(path);
76 if (not f.is_open())
77 {
78 cerr << " could not open " << label << " file: " << path << '\n';
79 return;
80 }
81
82 f << content;
83 if (not f.good())
84 {
85 cerr << " error writing " << label << " to: " << path << '\n';
86 }
87 else
88 {
89 f.close();
90 if (f.fail())
91 cerr << " error closing " << label << " file: " << path << '\n';
92 else
93 cout << " certificate " << label << " written to: " << path << '\n';
94 }
95 }
96
104 void print_helper_commands(const fs::path & output_dir,
105 const string & graphml_path,
106 const string & gexf_path)
107 {
108 cout << " external validator command:\n";
109 cout << " ruby scripts/planarity_certificate_validator.rb"
110 << " --input " << graphml_path
111 << " --input " << gexf_path << '\n';
112 cout << " optional Gephi adapter command:\n";
113 cout << " ruby scripts/planarity_certificate_validator.rb"
114 << " --input " << graphml_path
115 << " --gephi"
116 << " --gephi-cmd \"gephi --headless --import {input}\"" << '\n';
117 cout << " optional Gephi catalog template command:\n";
118 cout << " ruby scripts/planarity_certificate_validator.rb"
119 << " --input " << graphml_path
120 << " --gephi --require-gephi"
121 << " --gephi-template portable.python-file-exists" << '\n';
122 cout << " optional render profile catalog command:\n";
123 cout << " ruby scripts/planarity_certificate_validator.rb"
124 << " --list-gephi-render-profiles --json" << '\n';
125
126 const string render_dir =
127 (output_dir / "aleph_planarity_renders").string();
128 const string ci_report =
129 (output_dir / "aleph_planarity_ci_report.json").string();
130 const string ci_render_report =
131 (output_dir / "aleph_planarity_ci_render_report.json").string();
132 const string visual_render_dir =
133 (output_dir / "aleph_planarity_visual_renders").string();
134 const string visual_report =
135 (output_dir / "aleph_planarity_visual_diff_report.json").string();
136 const string nightly_comparison_json =
137 (output_dir / "gephi_nightly_comparison.json").string();
138 const string nightly_alert_md =
139 (output_dir / "gephi_nightly_alert.md").string();
140
141 cout << " optional render validation command:\n";
142 cout << " ruby scripts/planarity_certificate_validator.rb"
143 << " --input " << graphml_path
144 << " --render-gephi --require-render"
145 << " --render-profile portable.python-render-svg"
146 << " --render-output-dir " << render_dir << '\n';
147 cout << " optional CI batch command:\n";
148 cout << " ruby scripts/planarity_certificate_ci_batch.rb"
149 << " --input " << graphml_path
150 << " --input " << gexf_path
151 << " --gephi --require-gephi"
152 << " --gephi-template portable.python-file-exists"
153 << " --report " << ci_report << " --print-summary"
154 << '\n';
155 cout << " optional CI batch render command:\n";
156 cout << " ruby scripts/planarity_certificate_ci_batch.rb"
157 << " --input " << graphml_path
158 << " --render-gephi --require-render"
159 << " --render-profile portable.python-render-svg"
160 << " --render-output-dir " << render_dir
161 << " --report " << ci_render_report
162 << " --print-summary" << '\n';
163 cout << " optional visual golden diff command:\n";
164 cout << " ruby scripts/planarity_certificate_ci_visual_diff.rb"
165 << " --input " << graphml_path
166 << " --profile portable.python-render-svg"
167 << " --profile portable.python-render-pdf"
168 << " --render-output-dir " << visual_render_dir
169 << " --report " << visual_report
170 << " --print-summary" << '\n';
171 cout << " optional nightly real-Gephi workflow:\n";
172 cout << " .github/workflows/planarity_gephi_nightly.yml"
173 << " (workflow_dispatch / weekly schedule; multi-tag matrix)"
174 << '\n';
175 cout << " optional nightly regression notify command:\n";
176 cout << " ruby scripts/planarity_gephi_regression_notify.rb"
177 << " --report-json " << nightly_comparison_json
178 << " --output-md " << nightly_alert_md
179 << " --repository lrleon/Aleph-w"
180 << " --run-url https://github.com/lrleon/Aleph-w/actions/runs/123"
181 << " --webhook-env ALEPH_PLANARITY_ALERT_WEBHOOK --print-summary"
182 << '\n';
183 }
184
199 template <class GT>
200 void print_result(const string & title,
202 {
203 cout << title << '\n';
204 cout << " planar: " << (r.is_planar ? "yes" : "no") << '\n';
205 cout << " input nodes/arcs: " << r.num_nodes << "/" << r.num_input_arcs << '\n';
206 cout << " simplified nodes/edges: "
207 << r.simplified_num_nodes << "/" << r.simplified_num_edges << '\n';
208 cout << " ignored loops: " << r.ignored_loops << '\n';
209 cout << " ignored parallel arcs: " << r.ignored_parallel_arcs << '\n';
210 if (r.failed_euler_bound)
211 cout << " rejected by Euler bound before LR test\n";
212
213 cout << " embedding available: "
214 << (r.has_combinatorial_embedding ? "yes" : "no") << '\n';
215 if (r.embedding_search_truncated)
216 cout << " embedding search truncated by options\n";
217 if (r.has_combinatorial_embedding)
218 {
219 cout << " embedding faces: " << r.embedding_num_faces << '\n';
220 cout << " embedding mode: "
221 << (r.embedding_is_lr_linear ? "LR-linear" : "fallback-exact")
222 << '\n';
223 }
224
225 cout << " non-planar certificate available: "
226 << (r.has_nonplanar_certificate ? "yes" : "no") << '\n';
227 if (r.certificate_search_truncated)
228 cout << " certificate search truncated by options\n";
229 if (r.has_nonplanar_certificate)
230 {
231 cout << " certificate type: "
232 << Aleph::to_string(r.certificate_type) << '\n';
233 cout << " witness edges: "
234 << r.certificate_obstruction_edges.size() << '\n';
235 if (r.certificate_obstruction_edges.size() > 0)
236 cout << " first witness edge input-arc multiplicity: "
237 << r.certificate_obstruction_edges[0].input_arcs.size() << '\n';
238 if (r.certificate_type == Planarity_Certificate_Type::K5_Subdivision
239 or r.certificate_type == Planarity_Certificate_Type::K33_Subdivision)
240 {
241 cout << " branch nodes: " << r.certificate_branch_nodes.size() << '\n';
242 cout << " witness paths: " << r.certificate_paths.size() << '\n';
243 if (r.certificate_paths.size() > 0)
244 cout << " first witness path edges: "
245 << r.certificate_paths[0].edges.size() << '\n';
246 }
247 }
248
249 cout << '\n';
250 }
251
252
262 template <class GT>
264 {
265 cout << " geometric drawing available: "
266 << (d.drawing_available ? "yes" : "no") << '\n';
268 return;
269
270 cout << " drawing crossings: " << d.crossing_count << '\n';
271 cout << " drawing validated no-crossings: "
272 << (d.drawing_validated_no_crossings ? "yes" : "no") << '\n';
273 cout << " drawing relaxation iterations: " << d.relaxation_iterations << '\n';
274
275 const size_t show = std::min(static_cast<size_t>(3), d.node_positions.size());
276 for (size_t i = 0; i < show; ++i)
277 {
278 const auto & p = d.node_positions[i];
279 cout << " node[" << i << "] @" << p.x << ", " << p.y << '\n';
280 }
281 }
282}
283
284
301int main(int argc, char ** argv)
302{
303 const char * env_out_dir = std::getenv("PLANARITY_OUT_DIR");
304 const fs::path output_dir =
305 (argc > 1 and argv[1] != nullptr and argv[1][0] != '\0')
306 ? fs::path(argv[1])
307 : ((env_out_dir != nullptr and env_out_dir[0] != '\0')
308 ? fs::path(env_out_dir)
309 : fs::path("/tmp"));
310
311 std::error_code mkdir_ec;
312 fs::create_directories(output_dir, mkdir_ec);
313 if (mkdir_ec)
314 cout << "warning: could not ensure output directory exists: "
315 << output_dir.string() << '\n';
316
318 opts.compute_embedding = true;
319 opts.compute_nonplanar_certificate = true;
322
323 cout << "Planarity test on Aleph graphs\n";
324 cout << "=============================\n\n";
325 cout << "Certificate output directory: " << output_dir.string() << "\n\n";
326
327 {
328 UGraph g;
329 auto * a = g.insert_node("A");
330 auto * b = g.insert_node("B");
331 auto * c = g.insert_node("C");
332 auto * d = g.insert_node("D");
333 auto * e = g.insert_node("E");
334
335 // Planar graph (pentagon + one chord)
336 g.insert_arc(a, b, 1);
337 g.insert_arc(b, c, 1);
338 g.insert_arc(c, d, 1);
339 g.insert_arc(d, e, 1);
340 g.insert_arc(e, a, 1);
341 g.insert_arc(a, c, 1);
342
343 const auto r = planarity_test(g, strict_lr_opts);
344 print_result("Planar sample (strict LR embedding)", r);
345
346 if (r.has_combinatorial_embedding)
347 {
348 const auto md = planar_dual_metadata(r);
350 cout << " dual graph nodes/arcs (local faces): "
351 << dual.get_num_nodes() << "/" << dual.get_num_arcs() << "\n\n";
352
353 const auto drawing = planar_geometric_drawing(r);
355 cout << '\n';
356 }
357 else
358 {
359 cout << " dual metadata skipped (strict LR did not build embedding)\n\n";
360 }
361 }
362
363 {
364 UGraph g;
365 auto * n0 = g.insert_node("0");
366 auto * n1 = g.insert_node("1");
367 auto * n2 = g.insert_node("2");
368 auto * n3 = g.insert_node("3");
369 auto * n4 = g.insert_node("4");
370
371 // Dense planar sample: K5 minus one edge.
372 g.insert_arc(n0, n2, 1);
373 g.insert_arc(n0, n3, 1);
374 g.insert_arc(n0, n4, 1);
375 g.insert_arc(n1, n2, 1);
376 g.insert_arc(n1, n3, 1);
377 g.insert_arc(n1, n4, 1);
378 g.insert_arc(n2, n3, 1);
379 g.insert_arc(n2, n4, 1);
380 g.insert_arc(n3, n4, 1);
381
382 const auto r = planarity_test(g, strict_lr_opts);
383 print_result("Planar dense sample (strict LR embedding)", r);
384
385 if (r.has_combinatorial_embedding)
386 {
387 const auto md = planar_dual_metadata(r);
388 cout << " dual metadata local/global faces: "
389 << md.num_faces_local << "/" << md.num_faces_global << "\n\n";
390
391 const auto drawing = planar_geometric_drawing(r);
393 cout << '\n';
394 }
395 else
396 {
397 cout << " dual metadata skipped (embedding unavailable)\n\n";
398 }
399 }
400
401 {
402 UGraph g;
403 auto * u0 = g.insert_node("u0");
404 auto * u1 = g.insert_node("u1");
405 auto * u2 = g.insert_node("u2");
406 auto * v0 = g.insert_node("v0");
407 auto * v1 = g.insert_node("v1");
408 auto * v2 = g.insert_node("v2");
409
410 // K3,3 (non-planar)
411 g.insert_arc(u0, v0, 1); g.insert_arc(u0, v1, 1); g.insert_arc(u0, v2, 1);
412 g.insert_arc(u1, v0, 1); g.insert_arc(u1, v1, 1); g.insert_arc(u1, v2, 1);
413 g.insert_arc(u2, v0, 1); g.insert_arc(u2, v1, 1); g.insert_arc(u2, v2, 1);
414
415 const auto r = planarity_test(g, opts);
416 print_result("Non-planar sample (K3,3)", r);
417
418 if (r.has_nonplanar_certificate)
419 {
420 const auto json = nonplanar_certificate_to_json(r);
421 const auto dot = nonplanar_certificate_to_dot(r);
424 const auto vr = validate_nonplanar_certificate(r);
425
426 const string json_path =
427 (output_dir / "planarity_k33_certificate.json").string();
428 const string dot_path =
429 (output_dir / "planarity_k33_certificate.dot").string();
430 const string graphml_path =
431 (output_dir / "planarity_k33_certificate.graphml").string();
432 const string gexf_path =
433 (output_dir / "planarity_k33_certificate.gexf").string();
434
435 write_file_or_warn(json_path, json, "JSON");
436 write_file_or_warn(dot_path, dot, "DOT");
439
440 cout << " certificate JSON bytes: " << json.size() << '\n';
441 cout << " certificate DOT bytes: " << dot.size() << '\n';
442 cout << " certificate GraphML bytes: " << graphml.size() << '\n';
443 cout << " certificate GEXF bytes: " << gexf.size() << '\n';
444 cout << " certificate structural validation: "
445 << (vr.is_valid ? "valid" : "invalid") << '\n';
446
448
449 if (not vr.is_valid)
450 {
451 cout << " null branch nodes: " << vr.null_branch_nodes << '\n';
452 cout << " path/edge shape mismatches: "
453 << vr.path_node_edge_length_mismatch << '\n';
454 cout << " path edges not in obstruction: "
455 << vr.path_edge_not_in_obstruction << '\n';
456 }
457 cout << '\n';
458 }
459 }
460
461 {
462 DGraph g;
463 auto * s = g.insert_node("S");
464 auto * t = g.insert_node("T");
465 auto * x = g.insert_node("X");
466
467 // Underlying simple graph is just a triangle (planar),
468 // even with loops and parallel directed arcs.
469 g.insert_arc(s, t, 1);
470 g.insert_arc(t, x, 1);
471 g.insert_arc(x, s, 1);
472
473 g.insert_arc(s, s, 1); // loop, ignored
474 g.insert_arc(t, t, 1); // loop, ignored
475
476 g.insert_arc(s, t, 1); // parallel, collapsed
477 g.insert_arc(t, s, 1); // opposite orientation, same undirected edge
478
479 print_result("Digraph normalization sample", planarity_test(g, opts));
480 }
481
482 return 0;
483}
Planarity testing on Aleph graphs.
long double vr
Definition btreepic.C:150
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3854
Graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:428
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:524
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
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.
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.
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.
void print_result(const string &label, const Multipoly &poly)
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
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.
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
STL namespace.
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Embedding-aware geometric drawing result.
Array< Node_Position > node_positions
Configuration for planarity test optional outputs.
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.
Result of planarity testing.
gsl_rng * r
Generic graph and digraph implementations.