diff --git a/default.tikzstyles b/default.tikzstyles new file mode 100644 index 0000000..6227919 --- /dev/null +++ b/default.tikzstyles @@ -0,0 +1,12 @@ +% TiKZ style file generated by TikZiT. You may edit this file manually, +% but some things (e.g. comments) may be overwritten. To be readable in +% TikZiT, the only non-comment lines must be of the form: +% \tikzstyle{NAME}=[PROPERTY LIST] + +% Node styles +\tikzstyle{dot}=[fill=white, draw=black, shape=circle] + +% Edge styles +\tikzstyle{green edge}=[-, draw={rgb,255: red,0; green,175; blue,0}] +\tikzstyle{red edge}=[-, draw=red] +\tikzstyle{blue edge}=[-, draw=blue] diff --git a/papers/face_monochromatic_pairs/constant_heawood_counterexample.tikz b/papers/face_monochromatic_pairs/constant_heawood_counterexample.tikz new file mode 100644 index 0000000..a2f4be6 --- /dev/null +++ b/papers/face_monochromatic_pairs/constant_heawood_counterexample.tikz @@ -0,0 +1,106 @@ +\begin{tikzpicture} + \begin{pgfonlayer}{nodelayer} + \node [style=none] (0) at (-4, 4.5) {}; + \node [style=none] (1) at (0, 4) {}; + \node [style=none] (2) at (4.5, 4.5) {}; + \node [style=none] (3) at (4, 0) {}; + \node [style=none] (4) at (4.5, -4) {}; + \node [style=none] (5) at (0, -4) {}; + \node [style=none] (6) at (-4, -4) {}; + \node [style=none] (7) at (-4, 0) {}; + \node [style=none] (8) at (-2.5, 1.5) {}; + \node [style=none] (9) at (-2, 2) {}; + \node [style=none] (10) at (-1.5, 2.5) {}; + \node [style=none] (11) at (-1, 3) {}; + \node [style=none] (12) at (-0.5, 3.5) {}; + \node [style=none] (13) at (-3, 1) {}; + \node [style=none] (14) at (-3.5, 0.5) {}; + \node [style=none] (15) at (0.5, 4.5) {}; + \node [style=none] (16) at (0.5, -3.5) {}; + \node [style=none] (17) at (1, -3) {}; + \node [style=none] (18) at (1.5, -2.5) {}; + \node [style=none] (19) at (2, -2) {}; + \node [style=none] (20) at (2.5, -1.5) {}; + \node [style=none] (21) at (3, -1) {}; + \node [style=none] (22) at (3.5, -0.5) {}; + \node [style=none] (23) at (4.5, 0.5) {}; + \node [style=none] (24) at (-3.5, 1.25) {}; + \node [style=none] (25) at (-2.5, 2.25) {}; + \node [style=none] (26) at (-1.5, 3.25) {}; + \node [style=none] (27) at (-0.5, 4.25) {}; + \node [style=none] (28) at (-2, 0) {}; + \node [style=none] (29) at (0, -2) {}; + \node [style=none] (30) at (2, 0) {}; + \node [style=none] (31) at (0, 2) {}; + \node [style=none] (32) at (-1, 1) {}; + \node [style=none] (33) at (1, 3) {}; + \node [style=none] (34) at (1, -1) {}; + \node [style=none] (35) at (3, 1) {}; + \node [style=none] (36) at (1.25, -3.5) {}; + \node [style=none] (37) at (2.25, -2.5) {}; + \node [style=none] (38) at (3.25, -1.5) {}; + \node [style=none] (39) at (4.25, -0.5) {}; + \end{pgfonlayer} + \begin{pgfonlayer}{edgelayer} + \draw [style=blue edge] (0.center) to (15.center); + \draw [style=blue edge] (2.center) to (23.center); + \draw [style=blue edge] (5.center) to (4.center); + \draw [style=blue edge] (7.center) to (6.center); + \draw [style=red edge] (0.center) to (7.center); + \draw [style=red edge] (15.center) to (2.center); + \draw [style=red edge] (23.center) to (4.center); + \draw [style=red edge] (5.center) to (6.center); + \draw [style=red edge] (14.center) to (13.center); + \draw [style=red edge] (8.center) to (9.center); + \draw [style=red edge] (10.center) to (11.center); + \draw [style=red edge] (12.center) to (1.center); + \draw [style=red edge] (16.center) to (17.center); + \draw [style=red edge] (18.center) to (19.center); + \draw [style=red edge] (20.center) to (21.center); + \draw [style=red edge] (22.center) to (3.center); + \draw [style=green edge] (7.center) to (14.center); + \draw [style=green edge] (13.center) to (8.center); + \draw [style=green edge] (9.center) to (10.center); + \draw [style=green edge] (11.center) to (12.center); + \draw [style=green edge] (1.center) to (15.center); + \draw [style=green edge] (5.center) to (16.center); + \draw [style=green edge] (17.center) to (18.center); + \draw [style=green edge] (19.center) to (20.center); + \draw [style=green edge] (21.center) to (22.center); + \draw [style=green edge] (3.center) to (23.center); + \draw [style=green edge, in=-135, out=-45] (6.center) to (4.center); + \draw [style=green edge, bend left=45] (0.center) to (2.center); + \draw [style=blue edge] (24.center) to (14.center); + \draw [style=blue edge] (25.center) to (8.center); + \draw [style=blue edge] (26.center) to (10.center); + \draw [style=blue edge] (27.center) to (12.center); + \draw [style=red edge] (24.center) to (25.center); + \draw [style=red edge] (26.center) to (27.center); + \draw [style=green edge] (25.center) to (26.center); + \draw [style=green edge, bend left] (24.center) to (27.center); + \draw [style=blue edge] (13.center) to (28.center); + \draw [style=blue edge] (9.center) to (32.center); + \draw [style=blue edge] (11.center) to (31.center); + \draw [style=blue edge] (1.center) to (33.center); + \draw [style=blue edge] (29.center) to (17.center); + \draw [style=blue edge] (34.center) to (19.center); + \draw [style=blue edge] (30.center) to (21.center); + \draw [style=blue edge] (35.center) to (3.center); + \draw [style=blue edge] (16.center) to (36.center); + \draw [style=blue edge] (18.center) to (37.center); + \draw [style=blue edge] (20.center) to (38.center); + \draw [style=blue edge] (22.center) to (39.center); + \draw [style=red edge] (36.center) to (37.center); + \draw [style=red edge] (38.center) to (39.center); + \draw [style=green edge] (38.center) to (37.center); + \draw [style=green edge, bend right] (36.center) to (39.center); + \draw [style=red edge] (28.center) to (32.center); + \draw [style=red edge] (31.center) to (33.center); + \draw [style=red edge] (30.center) to (35.center); + \draw [style=red edge] (29.center) to (34.center); + \draw [style=green edge] (32.center) to (34.center); + \draw [style=green edge] (28.center) to (29.center); + \draw [style=green edge] (31.center) to (30.center); + \draw [style=green edge] (33.center) to (35.center); + \end{pgfonlayer} +\end{tikzpicture} diff --git a/papers/face_monochromatic_pairs/experiments/counterexample_conj_5_5.py b/papers/face_monochromatic_pairs/experiments/counterexample_conj_5_5.py new file mode 100644 index 0000000..9bfaaf6 --- /dev/null +++ b/papers/face_monochromatic_pairs/experiments/counterexample_conj_5_5.py @@ -0,0 +1,371 @@ +"""Counterexample to Conjecture 5.5 (transcribed from +`constant_heawood_counterexample.tikz`). + +This script: + (1) builds H from the edge list transcribed from the .tikz file; + (2) verifies H is simple, cubic, planar; + (3) verifies the colour assignment is a proper 3-edge-colouring; + (4) computes h_φ at every vertex via the CW rotation around each + vertex using the planar coordinates given in the .tikz file + (rather than Sage's combinatorial embedding, which may pick a + different rotation system); + (5) identifies the {a,b}-, {a,c}-, {b,c}-Kempe cycles of φ; + (6) reports whether h_φ is constant on each Kempe cycle; + (7) saves a PNG of the planar drawing with edges coloured red / + blue / green at the same positions as the .tikz file. + +Convention: + colour 0 = red = "a", + colour 1 = blue = "b", + colour 2 = green = "c". + +Run with: sage experiments/counterexample_conj_5_5.py +""" +import math +import os + +from sage.all import Graph + +HERE = os.path.dirname(os.path.abspath(__file__)) +OUT_PNG = os.path.join(HERE, '..', 'figures', + 'no-two-constant-kempe-counterexample.png') + +RED, BLUE, GREEN = 0, 1, 2 +COLOUR_NAME = {RED: 'red', BLUE: 'blue', GREEN: 'green'} + +# Vertex positions transcribed verbatim from +# constant_heawood_counterexample.tikz. +POS = { + 0: (-4.0, 4.5), + 1: ( 0.0, 4.0), + 2: ( 4.5, 4.5), + 3: ( 4.0, 0.0), + 4: ( 4.5, -4.0), + 5: ( 0.0, -4.0), + 6: (-4.0, -4.0), + 7: (-4.0, 0.0), + 8: (-2.5, 1.5), + 9: (-2.0, 2.0), + 10: (-1.5, 2.5), + 11: (-1.0, 3.0), + 12: (-0.5, 3.5), + 13: (-3.0, 1.0), + 14: (-3.5, 0.5), + 15: ( 0.5, 4.5), + 16: ( 0.5, -3.5), + 17: ( 1.0, -3.0), + 18: ( 1.5, -2.5), + 19: ( 2.0, -2.0), + 20: ( 2.5, -1.5), + 21: ( 3.0, -1.0), + 22: ( 3.5, -0.5), + 23: ( 4.5, 0.5), + 24: (-3.5, 1.25), + 25: (-2.5, 2.25), + 26: (-1.5, 3.25), + 27: (-0.5, 4.25), + 28: (-2.0, 0.0), + 29: ( 0.0, -2.0), + 30: ( 2.0, 0.0), + 31: ( 0.0, 2.0), + 32: (-1.0, 1.0), + 33: ( 1.0, 3.0), + 34: ( 1.0, -1.0), + 35: ( 3.0, 1.0), + 36: ( 1.25, -3.5), + 37: ( 2.25, -2.5), + 38: ( 3.25, -1.5), + 39: ( 4.25, -0.5), +} + +# Edges with colours, transcribed from the .tikz edgelayer in order. +# Two edges (6-4 and 0-2) are drawn bent in TikZ and represent the +# "outer-face" green arcs; their straight-line geometry in POS would +# pass through the interior, but the embedding treats them as edges of +# the outer face (i.e. they leave each endpoint pointing away from the +# interior). We model this by overriding the angle at vertices 0, 2, +# 6, 4 for these two edges (see CW_ANGLE_OVERRIDE below). +EDGES = [ + # blue (4) + (0, 15, BLUE), (2, 23, BLUE), (5, 4, BLUE), (7, 6, BLUE), + # red (outer 4) + (0, 7, RED), (15, 2, RED), (23, 4, RED), (5, 6, RED), + # red (interior-segments group 1) + (14, 13, RED), (8, 9, RED), (10, 11, RED), (12, 1, RED), + (16, 17, RED), (18, 19, RED), (20, 21, RED), (22, 3, RED), + # green (outer "spokes" connecting outer frame to inner parallelograms) + (7, 14, GREEN), (13, 8, GREEN), (9, 10, GREEN), (11, 12, GREEN), + (1, 15, GREEN), (5, 16, GREEN), (17, 18, GREEN), (19, 20, GREEN), + (21, 22, GREEN), (3, 23, GREEN), + # green outer-face arcs + (6, 4, GREEN), # bottom arc (bent in tikz: out=-135, in=-45) + (0, 2, GREEN), # top arc (bent in tikz: bend left=45) + # inner upper-left "trapezoid" connecting 14, 8, 10, 12 to 24..27 + (24, 14, BLUE), (25, 8, BLUE), (26, 10, BLUE), (27, 12, BLUE), + (24, 25, RED), (26, 27, RED), + (25, 26, GREEN), + (24, 27, GREEN), # bent + # mid-layer edges connecting outer-frame intermediate vertices to + # the inner "diamond" of 28-35 + (13, 28, BLUE), (9, 32, BLUE), (11, 31, BLUE), (1, 33, BLUE), + (29, 17, BLUE), (34, 19, BLUE), (30, 21, BLUE), (35, 3, BLUE), + # lower-right "trapezoid" + (16, 36, BLUE), (18, 37, BLUE), (20, 38, BLUE), (22, 39, BLUE), + (36, 37, RED), (38, 39, RED), + (38, 37, GREEN), + (36, 39, GREEN), # bent + # inner diamond edges (red rungs) + (28, 32, RED), (31, 33, RED), (30, 35, RED), (29, 34, RED), + # inner diamond edges (green rungs) + (32, 34, GREEN), (28, 29, GREEN), (31, 30, GREEN), (33, 35, GREEN), +] + +# For the two outer-face green arcs (and the bent edges 24-27 and +# 36-39), override the angle used in the CW-rotation computation so +# that the cyclic order around each endpoint matches the planar +# embedding actually drawn in tikz rather than the straight-line +# geometry. +# +# Convention: angle is measured in degrees, counterclockwise from +# +x axis. The override gives the direction the edge LEAVES the +# endpoint along the tikz-drawn curve, not toward the other endpoint +# of the straight line. +# +# - 0->2 green arc bends "left" over the top; from 0 it leaves +# roughly upward (north), and from 2 it arrives from the upper-left +# (so 2's local angle is also roughly upward). +# - 6->4 green arc bends "around the bottom"; from 6 it leaves +# roughly downward (south), and from 4 it leaves roughly downward. +# - 24->27 green arc bends "left" above the upper-left ladder +# (in=-135, out=-45 conceptually). +# - 36->39 green arc bends "right" below the lower-right ladder. +CW_ANGLE_OVERRIDE = { + (0, 2): 90.0, # at 0, edge to 2 leaves upward + (2, 0): 90.0, # at 2, edge to 0 leaves upward + (6, 4): -90.0, # at 6, edge to 4 leaves downward + (4, 6): -90.0, # at 4, edge to 6 leaves downward + (24, 27): 135.0, # at 24, edge to 27 leaves upper-left + (27, 24): 135.0, # at 27, edge to 24 leaves upper-left + (36, 39): -45.0, # at 36, edge to 39 leaves lower-right + (39, 36): -45.0, # at 39, edge to 36 leaves lower-right +} + + +def angle_at(v, u): + """Direction (degrees) from v toward u in the planar drawing, + honouring CW_ANGLE_OVERRIDE for bent edges.""" + if (v, u) in CW_ANGLE_OVERRIDE: + return CW_ANGLE_OVERRIDE[(v, u)] + (vx, vy) = POS[v] + (ux, uy) = POS[u] + return math.degrees(math.atan2(uy - vy, ux - vx)) + + +def build_graph(edges): + G = Graph(multiedges=False, loops=False) + for u, v, _ in edges: + G.add_edge(u, v) + return G + + +def edge_colour_map(edges): + return {frozenset((u, v)): c for u, v, c in edges} + + +def is_proper_3_edge_colouring(G, col): + for v in G.vertex_iterator(): + cols = sorted(col[frozenset((v, u))] for u in G.neighbors(v)) + if cols != [RED, BLUE, GREEN]: + return False, v, cols + return True, None, None + + +def cw_neighbours(G, v): + """List of v's neighbours in CW order from the planar coordinates + (CW = decreasing polar angle).""" + nbrs = list(G.neighbors(v)) + nbrs.sort(key=lambda u: -angle_at(v, u)) + return nbrs + + +def heawood_numbers(G, col): + h = {} + for v in G.vertex_iterator(): + nbrs = cw_neighbours(G, v) + cols = [col[frozenset((v, u))] for u in nbrs] + i0 = cols.index(RED) + rot = cols[i0:] + cols[:i0] + if rot == [RED, BLUE, GREEN]: + h[v] = +1 + elif rot == [RED, GREEN, BLUE]: + h[v] = -1 + else: + raise RuntimeError(f"bad rotation at {v}: {cols}") + return h + + +def kempe_cycle(G, col, start_edge, two_colours): + target = set(two_colours) + u0, v0 = start_edge + walk = [u0, v0] + while True: + cur, prev = walk[-1], walk[-2] + nxt = None + for u in G.neighbors(cur): + if u == prev: + continue + if col[frozenset((cur, u))] in target: + nxt = u + break + if nxt is None: + raise RuntimeError(f"Kempe walk stuck at {cur}") + if nxt == walk[0] and cur == walk[-2]: + # safety; shouldn't trigger in normal walks + break + walk.append(nxt) + if walk[-1] == walk[0] and len(walk) > 2: + return walk[:-1] + + +def report(name, cycle, h, two_colours): + h_vals = [h[v] for v in cycle] + plus = sum(1 for x in h_vals if x == +1) + minus = sum(1 for x in h_vals if x == -1) + const = (plus == 0 or minus == 0) + a, b = two_colours + print(f" {name} (colours {{ {COLOUR_NAME[a]}, {COLOUR_NAME[b]} }}):") + print(f" length = {len(cycle)}") + print(f" vertices= {cycle}") + print(f" h_φ = {h_vals}") + print(f" +/- = {plus} (+1) / {minus} (-1)") + print(f" const? {'YES' if const else 'no'}") + return const + + +# Bent edges, matching the tikz embedding. For each, we give a "rad" +# value (positive => arc bulges to the LEFT of the directed edge u->v +# in matplotlib's connectionstyle convention). +BENT_EDGES = { + # In matplotlib's arc3, positive rad bulges to the RIGHT of the + # directed edge from posA to posB. We pick signs so the bulge + # matches the tikz drawing. + frozenset((0, 2)): ('arc', (0, 2), -0.30), # 0→2 rightward, bulge up (left of dir) + frozenset((6, 4)): ('arc', (6, 4), +0.30), # 6→4 rightward, bulge down (right of dir) + frozenset((24, 27)): ('arc', (24, 27), -0.30), # 24→27 up-right, bulge up-left ("bend left") + frozenset((36, 39)): ('arc', (36, 39), +0.30), # 36→39 up-right, bulge down-right ("bend right") +} + + +def draw_png(G, col, out_path): + import matplotlib + matplotlib.use('Agg') + import matplotlib.pyplot as plt + from matplotlib.patches import FancyArrowPatch + + os.makedirs(os.path.dirname(out_path), exist_ok=True) + fig, ax = plt.subplots(figsize=(10, 10), dpi=160) + ax.set_aspect('equal') + ax.axis('off') + + # Draw edges first so vertices sit on top + for e in G.edge_iterator(labels=False): + u, v = e + c = COLOUR_NAME[col[frozenset(e)]] + if frozenset(e) in BENT_EDGES: + _, (a, b), rad = BENT_EDGES[frozenset(e)] + # Orient so the rad sign matches the (a -> b) direction + if (u, v) != (a, b): + rad = -rad + arc = FancyArrowPatch( + POS[u], POS[v], + arrowstyle='-', + connectionstyle=f'arc3,rad={rad}', + color=c, linewidth=2.5, + shrinkA=0, shrinkB=0, + ) + ax.add_patch(arc) + else: + (x1, y1), (x2, y2) = POS[u], POS[v] + ax.plot([x1, x2], [y1, y2], color=c, linewidth=2.5, + solid_capstyle='round') + + # Draw vertices + for v, (x, y) in POS.items(): + ax.plot(x, y, 'o', markersize=16, + markerfacecolor='lightgrey', + markeredgecolor='black', markeredgewidth=1.0) + ax.text(x, y, str(v), ha='center', va='center', fontsize=8) + + # Set bounds with padding (the top arc goes well above 4.5) + xs = [p[0] for p in POS.values()] + ys = [p[1] for p in POS.values()] + pad = 1.5 + ax.set_xlim(min(xs) - pad, max(xs) + pad) + ax.set_ylim(min(ys) - pad, max(ys) + pad) + + fig.tight_layout() + fig.savefig(out_path, dpi=160, bbox_inches='tight') + plt.close(fig) + print(f"\nWrote: {out_path}") + + +def main(): + print("Loading edges from transcribed tikz...") + G = build_graph(EDGES) + col = edge_colour_map(EDGES) + print(f" |V(H)| = {G.order()}, |E(H)| = {G.size()}") + + degs = sorted(set(G.degree())) + print(f" degree set = {degs}") + if degs != [3]: + print(" !! NOT CUBIC"); draw_png(G, col, OUT_PNG); return + print(" cubic ✓") + + is_planar = G.is_planar(set_embedding=True) + print(f" planar (by Sage)? {is_planar}") + if not is_planar: + print(" !! NOT PLANAR"); draw_png(G, col, OUT_PNG); return + + ok, bad, cols = is_proper_3_edge_colouring(G, col) + if not ok: + print(f" !! NOT a proper 3-edge-colouring: {bad} has {cols}") + draw_png(G, col, OUT_PNG); return + print(" proper 3-edge-colouring ✓") + + h = heawood_numbers(G, col) + print("\nHeawood numbers per vertex (using tikz coordinates):") + for v in sorted(h): + print(f" h_φ({v:2d}) = {h[v]:+d}") + + print("\nGlobal Heawood sums:") + plus = sum(1 for v in h if h[v] == +1) + minus = sum(1 for v in h if h[v] == -1) + print(f" total: {plus} (+1) / {minus} (-1), sum = {plus - minus}") + + # Try all three Kempe-cycle pairs and identify the ones sharing an + # edge. We pick an edge of each colour and trace each cycle. + print("\nAll three Kempe-cycle types through some shared edge:") + # find an edge of each colour + edge_by_color = {} + for u, v, c in EDGES: + if c not in edge_by_color: + edge_by_color[c] = (u, v) + + for a in (RED, BLUE, GREEN): + b, c = [x for x in (RED, BLUE, GREEN) if x != a] + e_a = edge_by_color[a] + Kab = kempe_cycle(G, col, e_a, (a, b)) + Kac = kempe_cycle(G, col, e_a, (a, c)) + print(f"\n-- shared edge: colour-{COLOUR_NAME[a]} edge {e_a}") + const_ab = report(f"K_{{a,b}}={{{COLOUR_NAME[a]},{COLOUR_NAME[b]}}}", + Kab, h, (a, b)) + const_ac = report(f"K_{{a,c}}={{{COLOUR_NAME[a]},{COLOUR_NAME[c]}}}", + Kac, h, (a, c)) + if const_ab and const_ac: + print(f" ** counterexample with a={COLOUR_NAME[a]} **") + + draw_png(G, col, OUT_PNG) + + +if __name__ == '__main__': + main() diff --git a/papers/face_monochromatic_pairs/figures/no-two-constant-kempe-counterexample.png b/papers/face_monochromatic_pairs/figures/no-two-constant-kempe-counterexample.png new file mode 100644 index 0000000..1aceb0e Binary files /dev/null and b/papers/face_monochromatic_pairs/figures/no-two-constant-kempe-counterexample.png differ