diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/draw_failing_graph.py b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/draw_failing_graph.py new file mode 100644 index 0000000..040f08f --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/draw_failing_graph.py @@ -0,0 +1,145 @@ +"""Straight-line planar drawing of the minimal genuine obstruction found by the +even-level-cycle programme: the ring triangulation sizes=[3,6,3], leaf='face' +(generator random.Random(2), the 27th graph), 12 vertices. It survives +exhausting insertion sites x tread phases x root colour-orders (residue_phase_ +sweep.py: 24 settings, 0 ok) and fails at the leaf-gadget removal step. + +Embedding: networkx planar_layout (a canonical-ordering straight-line embedding +of a planar graph), recentred. We additionally VERIFY no two non-incident edges +cross before drawing. Every triangulation on >=4 vertices is 3-connected, so a +crossing-free straight-line embedding is guaranteed to exist. +""" +import os, random +import numpy as np +import networkx as nx +import matplotlib +matplotlib.use("Agg") +import matplotlib.pyplot as plt +from matplotlib.patches import Polygon +import kempe_even_program_harness as H + +HERE = os.path.dirname(os.path.abspath(__file__)) +LEVCOL = {0: "#d9d9d9", 1: "#9ecae1", 2: "#fc9272"} # by BFS level + + +def reconstruct(seed, idx): + rng = random.Random(seed) + for _ in range(idx + 1): + sizes, leaf = H.random_profile(rng) + g, outer = H.ring_triangulation(sizes, leaf, rng) + return g, outer + + +def planar_pos(g): + nxg = nx.Graph() + nxg.add_nodes_from(g.rot) + for ed in g.edges(): + a, b = tuple(ed); nxg.add_edge(a, b) + ok, _ = nx.check_planarity(nxg) + assert ok, "graph is not planar?!" + pos = nx.planar_layout(nxg) + pts = np.array([pos[v] for v in g.rot]) + c = pts.mean(axis=0); s = np.abs(pts - c).max() + return {v: ((pos[v][0] - c[0]) / s, (pos[v][1] - c[1]) / s) for v in g.rot} + + +def seg_cross(p, q, r, s): + def o(a, b, c): + return (b[0]-a[0])*(c[1]-a[1]) - (b[1]-a[1])*(c[0]-a[0]) + d1, d2, d3, d4 = o(r, s, p), o(r, s, q), o(p, q, r), o(p, q, s) + return ((d1 > 0) != (d2 > 0)) and ((d3 > 0) != (d4 > 0)) + + +def verify_planar(g, pos): + edges = [tuple(e) for e in g.edges()] + bad = [] + for i in range(len(edges)): + a, b = edges[i] + for j in range(i + 1, len(edges)): + c, d = edges[j] + if len({a, b, c, d}) < 4: + continue + if seg_cross(pos[a], pos[b], pos[c], pos[d]): + bad.append((edges[i], edges[j])) + return bad + + +g, outer = reconstruct(2, 26) +g.check() +an = H.Analysis(g.copy(), outer) +pos = planar_pos(g) +bad = verify_planar(g, pos) +print("crossing edge-pairs:", bad if bad else "NONE -- valid straight-line planar embedding") +assert not bad, "embedding has crossings" + +terminal = tuple(an.terminal[0]) +odd_seam = [c for k, c in an.seams if len(c) % 2][0] +faces = [tuple(f) for f in g.faces()] +outer_set = frozenset(outer) + +fig, axes = plt.subplots(1, 2, figsize=(13.5, 6.8)) +xs = [p[0] for p in pos.values()]; ys = [p[1] for p in pos.values()] +mx = max(abs(min(xs)), abs(max(xs))); my = max(abs(min(ys)), abs(max(ys))) +for ax in axes: + ax.set_aspect("equal"); ax.axis("off") + ax.set_xlim(-mx - 0.25, mx + 0.25); ax.set_ylim(-my - 0.25, my + 0.35) + + +def draw_faces(ax): + for f in faces: + if frozenset(f) == outer_set: + continue + ax.add_patch(Polygon([pos[v] for v in f], closed=True, + facecolor="#fbfbfb", edgecolor="none", zorder=0)) + + +def draw_edges(ax, bold=None): + bold = bold or set() + for ed in g.edges(): + a, b = tuple(ed) + hot = frozenset((a, b)) in bold + ax.plot([pos[a][0], pos[b][0]], [pos[a][1], pos[b][1]], + color="#cc2222" if hot else "#7a7a7a", + lw=2.8 if hot else 1.1, zorder=2) + + +def draw_verts(ax, by_level=False): + for v, p in pos.items(): + fc = LEVCOL[an.level[v]] if by_level else "white" + ax.plot(*p, "o", ms=20, mfc=fc, mec="#222222", mew=1.6, zorder=4) + ax.annotate(str(v), p, ha="center", va="center", fontsize=10, + fontweight="bold", zorder=5) + + +draw_faces(axes[0]); draw_edges(axes[0]); draw_verts(axes[0]) +axes[0].set_title("A. ring [3,6,3] + face leaf, 12 vertices\n" + "straight-line planar embedding (verified crossing-free)", + fontsize=10) + +draw_faces(axes[1]) +seam_edges = {frozenset((odd_seam[i], odd_seam[(i+1) % len(odd_seam)])) + for i in range(len(odd_seam))} +axes[1].add_patch(Polygon([pos[v] for v in terminal], closed=True, + facecolor="#fee0d2", edgecolor="none", zorder=1)) +draw_edges(axes[1], bold=seam_edges) +draw_verts(axes[1], by_level=True) +tc = np.mean([pos[v] for v in terminal], axis=0) +axes[1].annotate("terminal triangle " + "-".join(map(str, terminal)) + + "\n(level-2 odd seam; carries the leaf\ngadget whose removal " + "STILL FAILS)", + xy=tc, xytext=(0.02, 0.99), textcoords="axes fraction", + ha="left", va="top", fontsize=8, color="#a63603", + arrowprops=dict(arrowstyle="->", color="#a63603", lw=1.2), + zorder=6) +axes[1].set_title("B. BFS levels from source 0-1-2 " + "(grey 0 / blue 1 / red 2)\nodd level-2 seam " + + "-".join(map(str, odd_seam)) + " bold red", + fontsize=10) + +fig.suptitle("Minimal genuine obstruction (seed2 #26): the programme fails here " + "even after exhausting\nsites x tread-phases x root colour-orders " + "(24 settings, 0 ok) -- a face-leaf / gadget case.", fontsize=10) +fig.tight_layout(rect=(0, 0, 1, 0.9)) +out = os.path.join(HERE, "failing_graph_seed2_26.png") +fig.savefig(out, dpi=160) +print("wrote", out) diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/failing_graph_seed2_26.png b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/failing_graph_seed2_26.png new file mode 100644 index 0000000..b67ad14 Binary files /dev/null and b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/failing_graph_seed2_26.png differ