Draw the minimal failing graph with a verified planar embedding
draw_failing_graph.py renders seed2 #26 (ring [3,6,3]+face leaf, 12 vertices), the smallest graph the programme fails on after exhausting sites x tread-phases x root colour-orders. Uses networkx planar_layout for a straight-line embedding and verifies no two non-incident edges cross before drawing. Panel A: plain embedding; panel B: BFS levels with the odd level-2 seam (the inner triangle 9-11-10) bold, the terminal leaf face shaded -- the face-leaf/gadget spot where removal fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+145
@@ -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)
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 156 KiB |
Reference in New Issue
Block a user