"""Draw a 3-panel illustration of cubic-graph edge contraction: (1) the original cubic graph fragment with edge e = uv highlighted; (2) after deleting e (u, v are degree-2); (3) after smoothing u, v (gone, replaced by single edges). Produces fig_cubic_edge_contraction.png. """ import os import matplotlib.pyplot as plt from matplotlib.patches import FancyArrowPatch OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) DARK = '#374151' GRAY = '#9ca3af' HIGHLIGHT = '#dc2626' # the edge being contracted (panel 1) GHOST = '#fca5a5' # removed edges (panel 2) DEG2 = '#f59e0b' # degree-2 vertices (panel 2) NEW = '#2563eb' # smoothed-in new edges (panel 3) # Positions: u centered at (-1, 0), v at (1, 0); their outer neighbors angled. pos = { 'u': (-1.0, 0.0), 'v': ( 1.0, 0.0), 'a': (-2.2, 1.0), 'b': (-2.2, -1.0), 'c': ( 2.2, 1.0), 'd': ( 2.2, -1.0), } def draw_vertex(ax, p, color, size=110, label=None, label_offset=(0, 0.22)): ax.scatter([p[0]], [p[1]], s=size, color=color, zorder=4) if label is not None: ax.text(p[0] + label_offset[0], p[1] + label_offset[1], label, ha='center', va='center', fontsize=12, zorder=5, color=DARK) def draw_edge(ax, p, q, color, lw=2.0, ls='-', zorder=2): ax.plot([p[0], q[0]], [p[1], q[1]], color=color, lw=lw, ls=ls, zorder=zorder, solid_capstyle='round') def panel_before(ax): # Outer edges (gray) for (x, y) in [('a', 'u'), ('b', 'u'), ('c', 'v'), ('d', 'v')]: draw_edge(ax, pos[x], pos[y], DARK, lw=2.0) # The highlighted edge e = uv draw_edge(ax, pos['u'], pos['v'], HIGHLIGHT, lw=3.2) # Vertices for v in ('a', 'b', 'c', 'd'): draw_vertex(ax, pos[v], DARK, size=60) draw_vertex(ax, pos['u'], DARK, size=120, label='$u$', label_offset=(-0.05, 0.28)) draw_vertex(ax, pos['v'], DARK, size=120, label='$v$', label_offset=(0.05, 0.28)) # Label on the edge mid = ((pos['u'][0] + pos['v'][0]) / 2, (pos['u'][1] + pos['v'][1]) / 2) ax.text(mid[0], mid[1] + 0.25, '$e$', ha='center', va='center', fontsize=13, color=HIGHLIGHT, zorder=5) ax.set_title('(1) cubic plane graph with edge $e = uv$', fontsize=11, color=DARK, pad=8) def panel_after_delete(ax): # Outer edges (gray) for (x, y) in [('a', 'u'), ('b', 'u'), ('c', 'v'), ('d', 'v')]: draw_edge(ax, pos[x], pos[y], DARK, lw=2.0) # Ghost the deleted edge draw_edge(ax, pos['u'], pos['v'], GHOST, lw=2.0, ls=':') # Vertices: u, v are now degree-2 (highlighted color) for v in ('a', 'b', 'c', 'd'): draw_vertex(ax, pos[v], DARK, size=60) draw_vertex(ax, pos['u'], DEG2, size=140, label='$u$', label_offset=(-0.05, 0.32)) draw_vertex(ax, pos['v'], DEG2, size=140, label='$v$', label_offset=(0.05, 0.32)) ax.set_title('(2) delete $e$: $u, v$ now have degree $2$', fontsize=11, color=DARK, pad=8) def panel_after_smooth(ax): # The smoothed-in new edges draw_edge(ax, pos['a'], pos['b'], NEW, lw=3.0) draw_edge(ax, pos['c'], pos['d'], NEW, lw=3.0) # Outer vertices remain for v in ('a', 'b', 'c', 'd'): draw_vertex(ax, pos[v], DARK, size=60) # u, v are gone — show their former positions as faint markers ax.scatter([pos['u'][0], pos['v'][0]], [pos['u'][1], pos['v'][1]], s=140, facecolors='none', edgecolors=GRAY, lw=1.0, linestyles='--', zorder=3) ax.text(pos['u'][0], pos['u'][1] + 0.32, '$u$ gone', ha='center', va='center', fontsize=9, color=GRAY) ax.text(pos['v'][0], pos['v'][1] + 0.32, '$v$ gone', ha='center', va='center', fontsize=9, color=GRAY) ax.set_title('(3) smooth $u, v$: their incident edges merge', fontsize=11, color=DARK, pad=8) def main(): fig, axes = plt.subplots(1, 3, figsize=(13.5, 4.2)) for ax in axes: ax.set_xlim(-3.0, 3.0) ax.set_ylim(-1.7, 1.7) ax.set_aspect('equal') ax.axis('off') panel_before(axes[0]) panel_after_delete(axes[1]) panel_after_smooth(axes[2]) plt.subplots_adjust(left=0.02, right=0.98, top=0.92, bottom=0.04, wspace=0.05) out = os.path.join(OUT_DIR, 'fig_cubic_edge_contraction.png') plt.savefig(out, dpi=180, bbox_inches='tight') print(f"wrote {out}") if __name__ == '__main__': main()