face_monochromatic_pairs: Heawood numbers, Lemma 5.2 + diagram

- Add Definition 3.1 "Heawood number of a vertex" (+1 if CW colour order
  is (1,2,3), -1 if (1,3,2)) and cite Heawood 1898 in the bibliography.
- Add Lemma 5.2 "Heawood number is constant on the Kempe cycles through
  the merged edge", positioned immediately after Conjecture 5.1. Its
  proof exhibits a (F, e_1, e_2) witness for clauses (1)-(3) of the
  conjecture from any pair (v_0, v_1) of consecutive K-vertices with
  differing Heawood signs, by cases on whether phi(e) = a or b. The
  proof does not invoke Conjecture 5.3 or Theorem 4.X.
- Add a two-panel figure illustrating Case A (b-edges on F_R when
  phi(e) = a) and Case B (a-edges on F_L when phi(e) = b), with the
  cyclic colour orders (a, b, c) at v_0 and (a, c, b) at v_1 visible
  from the angular layout.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 21:54:30 -04:00
parent 41227c6a0f
commit d99f8e23b3
6 changed files with 391 additions and 63 deletions
@@ -0,0 +1,212 @@
"""Two-panel illustration of the proof of Lemma 5.2
(Heawood constant on Kempe cycles through merged).
Each panel shows two consecutive vertices v_0, v_1 on the {a, b}-Kempe
cycle K, joined by an edge e, with h(v_0) = +1 (CW colour order (a, b, c))
and h(v_1) = -1 (CW colour order (a, c, b)).
Left panel (Case A): phi(e) = a. The two b-edges at v_0, v_1 both lie on
the same face F = F_R (right side of e); they form
the witness (e_1, e_2).
Right panel (Case B): phi(e) = b. The two a-edges at v_0, v_1 both lie
on the same face F = F_L (left side of e); they
form the witness (e_1, e_2).
Produces fig_lemma_kempe_heawood.png.
"""
import math
import os
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon
OUT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DARK = '#374151'
GRAY = '#9ca3af'
# Colour code matching earlier figures: a=red/orange, b=blue, c=green.
COL_A = '#ea580c' # 'a'
COL_B = '#2563eb' # 'b'
COL_C = '#16a34a' # 'c'
FACE_FILL = '#fef3c7'
V0 = (-1.6, 0.0)
V1 = ( 1.6, 0.0)
def edge_at(v, angle_deg, length=1.4):
a = math.radians(angle_deg)
return (v[0] + length * math.cos(a), v[1] + length * math.sin(a))
def draw_edge(ax, p, q, color, lw=2.6, zorder=2):
ax.plot([p[0], q[0]], [p[1], q[1]], color=color, lw=lw,
solid_capstyle='round', zorder=zorder)
def draw_vertex(ax, p, color=DARK, size=110, zorder=4):
ax.scatter([p[0]], [p[1]], s=size, color=color, zorder=zorder)
def draw_stub(ax, p, color=DARK, size=45, zorder=4):
ax.scatter([p[0]], [p[1]], s=size, color=color, zorder=zorder)
def label_text(ax, p, text, color=DARK, fontsize=12, dx=0, dy=0,
weight='normal'):
ax.text(p[0] + dx, p[1] + dy, text, ha='center', va='center',
fontsize=fontsize, color=color, zorder=6, weight=weight,
bbox=dict(boxstyle='round,pad=0.18', facecolor='white',
edgecolor='none', alpha=0.85))
def label_edge_midpoint(ax, p, q, text, color, fontsize=11, offset=(0, 0)):
mid = ((p[0] + q[0]) / 2 + offset[0],
(p[1] + q[1]) / 2 + offset[1])
ax.text(mid[0], mid[1], text, ha='center', va='center',
fontsize=fontsize, color=color, zorder=6,
bbox=dict(boxstyle='round,pad=0.16', facecolor='white',
edgecolor='none', alpha=0.9))
def shade_face(ax, pts, color=FACE_FILL, alpha=0.7):
poly = Polygon(pts, facecolor=color, edgecolor='none',
alpha=alpha, zorder=1)
ax.add_patch(poly)
def panel_case_A(ax):
# phi(e) = a. v_0 has CW order (a, b, c) starting from e at 0 deg:
# e (a) at 0 deg, b-edge at 300 deg (southeast), c-edge at 120 deg
# (northwest).
# v_1 has CW order (a, c, b) starting from e at 180 deg:
# e (a) at 180 deg, c-edge at 60 deg (northeast), b-edge at 240 deg
# (south-southwest).
e_color = COL_A
# Other endpoints (stubs) of the non-e edges.
b0 = edge_at(V0, -60) # b-edge at v_0, southeast
c0 = edge_at(V0, 120) # c-edge at v_0, northwest
c1 = edge_at(V1, 60) # c-edge at v_1, northeast
b1 = edge_at(V1, 240) # b-edge at v_1, southwest
# Shade F_R = south face: vertices roughly (b0, V0, V1, b1) plus a
# closing polygon below.
shade_face(ax, [V0, V1, b1, (b1[0] + 0.2, b1[1] - 0.6),
(b0[0] - 0.2, b0[1] - 0.6), b0])
label_text(ax, ((V0[0] + V1[0]) / 2, -1.6), 'face $F$', color=DARK,
fontsize=12, weight='bold')
# Edges
draw_edge(ax, V0, V1, e_color) # e (color a)
draw_edge(ax, V0, b0, COL_B) # b-edge at v_0
draw_edge(ax, V0, c0, COL_C) # c-edge at v_0
draw_edge(ax, V1, c1, COL_C) # c-edge at v_1
draw_edge(ax, V1, b1, COL_B) # b-edge at v_1
# Vertices
draw_vertex(ax, V0, DARK)
draw_vertex(ax, V1, DARK)
draw_stub(ax, b0); draw_stub(ax, c0)
draw_stub(ax, c1); draw_stub(ax, b1)
# Labels
label_text(ax, V0, '$v_0$', dy=0.28, fontsize=12)
label_text(ax, (V0[0] - 0.05, V0[1] - 0.28), '$h_\\varphi\\!=\\!+1$',
color=DARK, fontsize=9)
label_text(ax, V1, '$v_1$', dy=0.28, fontsize=12)
label_text(ax, (V1[0] + 0.05, V1[1] - 0.28), '$h_\\varphi\\!=\\!-1$',
color=DARK, fontsize=9)
label_edge_midpoint(ax, V0, V1, '$e\\!=\\!a$', color=COL_A,
offset=(0, 0.16))
label_edge_midpoint(ax, V0, b0, '$e_1\\!=\\!b$', color=COL_B,
offset=(-0.05, 0.05))
label_edge_midpoint(ax, V0, c0, '$c$', color=COL_C,
offset=(0.05, 0))
label_edge_midpoint(ax, V1, c1, '$c$', color=COL_C,
offset=(-0.05, 0))
label_edge_midpoint(ax, V1, b1, '$e_2\\!=\\!b$', color=COL_B,
offset=(0.05, 0.05))
ax.set_title('Case A: $\\varphi(e) = a$. The two $b$-edges'
' at $v_0, v_1$ lie on $\\partial F$',
fontsize=11, color=DARK, pad=10, fontweight='bold')
def panel_case_B(ax):
# phi(e) = b. v_0 has CW order (a, b, c) with b = e at 0 deg:
# a-edge at 60 deg (northeast), e (b) at 0 deg, c-edge at 300 deg
# (southeast).
# v_1 has CW order (a, c, b) with b = e at 180 deg:
# a-edge at 60 deg (northeast), c-edge at 300 deg (southeast), e
# (b) at 180 deg.
e_color = COL_B
a0 = edge_at(V0, 60) # a-edge at v_0, northeast
c0 = edge_at(V0, -60) # c-edge at v_0, southeast
a1 = edge_at(V1, 120) # a-edge at v_1, northwest
c1 = edge_at(V1, 240) # c-edge at v_1, southwest
# Shade F_L = north face: a0, V0, V1, a1, plus a closing polygon above.
shade_face(ax, [V0, a0, (a0[0] - 0.2, a0[1] + 0.6),
(a1[0] + 0.2, a1[1] + 0.6), a1, V1])
label_text(ax, ((V0[0] + V1[0]) / 2, 1.6), 'face $F$', color=DARK,
fontsize=12, weight='bold')
# Edges
draw_edge(ax, V0, V1, e_color)
draw_edge(ax, V0, a0, COL_A)
draw_edge(ax, V0, c0, COL_C)
draw_edge(ax, V1, a1, COL_A)
draw_edge(ax, V1, c1, COL_C)
# Vertices
draw_vertex(ax, V0, DARK)
draw_vertex(ax, V1, DARK)
draw_stub(ax, a0); draw_stub(ax, c0)
draw_stub(ax, a1); draw_stub(ax, c1)
# Labels
label_text(ax, V0, '$v_0$', dy=0.28, fontsize=12)
label_text(ax, (V0[0] - 0.05, V0[1] - 0.28), '$h_\\varphi\\!=\\!+1$',
color=DARK, fontsize=9)
label_text(ax, V1, '$v_1$', dy=0.28, fontsize=12)
label_text(ax, (V1[0] + 0.05, V1[1] - 0.28), '$h_\\varphi\\!=\\!-1$',
color=DARK, fontsize=9)
label_edge_midpoint(ax, V0, V1, '$e\\!=\\!b$', color=COL_B,
offset=(0, -0.18))
label_edge_midpoint(ax, V0, a0, '$e_1\\!=\\!a$', color=COL_A,
offset=(-0.05, 0))
label_edge_midpoint(ax, V0, c0, '$c$', color=COL_C,
offset=(0.05, 0))
label_edge_midpoint(ax, V1, a1, '$e_2\\!=\\!a$', color=COL_A,
offset=(0.05, 0))
label_edge_midpoint(ax, V1, c1, '$c$', color=COL_C,
offset=(-0.05, 0))
ax.set_title('Case B: $\\varphi(e) = b$. The two $a$-edges'
' at $v_0, v_1$ lie on $\\partial F$',
fontsize=11, color=DARK, pad=10, fontweight='bold')
def main():
plt.rcParams['text.usetex'] = False # keep matplotlib defaults
fig, axes = plt.subplots(1, 2, figsize=(13, 5.5))
for ax in axes:
ax.set_xlim(-3.5, 3.5)
ax.set_ylim(-2.4, 2.4)
ax.set_aspect('equal')
ax.axis('off')
panel_case_A(axes[0])
panel_case_B(axes[1])
plt.subplots_adjust(left=0.02, right=0.98, top=0.90, bottom=0.04,
wspace=0.05)
out = os.path.join(OUT_DIR, 'fig_lemma_kempe_heawood.png')
plt.savefig(out, dpi=180, bbox_inches='tight')
print(f"wrote {out}")
if __name__ == '__main__':
main()