diff --git a/papers/coloring_nested_tire_graphs/experiments/draw_uniqueness_break.py b/papers/coloring_nested_tire_graphs/experiments/draw_uniqueness_break.py new file mode 100644 index 0000000..eeb4547 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/draw_uniqueness_break.py @@ -0,0 +1,226 @@ +"""Draw an empirical uniqueness-break case using TRUE planar +embedding coordinates (not spring layout). + +We need a LOW-SIDE face f of H_d such that f's interior contains +H_{d-1} edges from multiple H_{d-1} faces. + +The most reliable low-side face: the OUTER (unbounded) face of H_d. +For d >= 2, the outer face of H_d contains all of H_{d-1} (= depth- +(d-1) subgraph) plus pendants. + +Algorithm: + - For HM_0 cut #1 side 1 (a known interesting case), use the + primal planar embedding for vertex positions. + - Identify the outer face of H_2 (= largest face by area, or by + Sage's outer-face-of-embedding). + - Verify it contains H_1 edges from multiple H_1 faces in its + interior region. + - Draw. +""" +import os, sys +from collections import defaultdict + +import matplotlib.pyplot as plt +from sage.all import Graph + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +from cut_depth_label import ( + parse_planar_code, HM_FILE, + apply_procedure, compute_nice_layout, +) +from cut_tire_tree import compute_H_d_faces +from tree_structure_sweep import all_six_edge_cuts + + +def planar_layout_for_H(H): + """Return planar layout of H using Sage's planar embedding.""" + # Try planar layout + H2 = H.copy() + try: + H2.is_planar(set_embedding=True) + # Use Sage's planar layout + pos = H2.layout(layout='planar') + # pos values are (x, y) but Sage returns dict v -> (x,y) + return pos + except Exception as e: + print(f' Planar layout failed: {e}') + return None + + +def main(): + gs = parse_planar_code(HM_FILE) + G = gs[0] + G.is_planar(set_embedding=True) + base_pos = compute_nice_layout(G) + cuts = all_six_edge_cuts(G, max_cuts=5) + S, cut_edges = cuts[1] + S1 = frozenset(G.vertices()) - S + H, _, ed, _, _, _ = apply_procedure( + G, S1, cut_edges, base_pos, '1', + pendant_start_id=max(G.vertices()) + 501) + print(f'|V(H)|={H.order()}, |E(H)|={H.size()}') + print(f'Depths: {sorted(set(ed.values()))}') + + # Use Sage's planar layout for H + pos = planar_layout_for_H(H) + if pos is None: + pos = H.layout(layout='spring') + print(f'Got positions for {len(pos)} vertices') + + # Compute faces of H_1, H_2, H_3 + faces_1, _ = compute_H_d_faces(H, ed, 1) + faces_2, _ = compute_H_d_faces(H, ed, 2) + faces_3, _ = compute_H_d_faces(H, ed, 3) + print(f'H_1: {len(faces_1)} faces, lengths {[len(f) for f in faces_1]}') + print(f'H_2: {len(faces_2)} faces, lengths {[len(f) for f in faces_2]}') + print(f'H_3: {len(faces_3)} faces, lengths {[len(f) for f in faces_3]}') + + # Identify the LOW-side face of H_2. + # A low-side face contains depth-<2 edges (= pendants + H_1 edges). + # Equivalently: it's the face whose interior in the planar embedding + # contains the pendant vertices. + pendant_verts = {v for e, d in ed.items() if d == 0 for v in e} + # We pick the face whose boundary walk encloses the pendants. + # In Sage's planar embedding, this is the "outer" face often. + # Heuristic: the H_2 face NOT containing H_3 edges in its interior. + # Or: the largest face by vertex count. + # Most reliable: the face that, when removed from the plane, the + # pendants are in the remaining unbounded region. + # Use Sage's faces() — the first face is the outer face by default. + # For now, pick the face with the most vertices. + + target_face_idx = max(range(len(faces_2)), + key=lambda i: len(set(v for u, v in faces_2[i]) + | set(u for u, v in faces_2[i]))) + target_face = faces_2[target_face_idx] + target_verts = set() + for u, v in target_face: + target_verts.add(u); target_verts.add(v) + print(f'\nTarget face (H_2 face {target_face_idx}): {len(target_face)} ' + f'edges, {len(target_verts)} verts') + print(f' vertices: {sorted(target_verts)}') + + # Identify H_1 edges in the "interior" of the target face. + # Heuristic: H_1 edges whose endpoints are NOT on the target face + # boundary, OR endpoints on the boundary but the edge "points + # inward" by planar embedding. + # For simplicity, look at all H_1 edges with at least one endpoint + # on target_verts and the OTHER endpoint NOT on H_2's outer + # boundary. + h1_in_interior = [] + h1_face_of_edge = {} + for fi, walk in enumerate(faces_1): + for u, v in walk: + e = tuple(sorted((u, v))) + h1_face_of_edge.setdefault(e, []).append(fi) + + for e, d in ed.items(): + if d != 1: + continue + # An H_1 edge "in target face's interior" if at least one + # endpoint is in target_verts (= boundary of target face). + if e[0] in target_verts or e[1] in target_verts: + h1_in_interior.append(e) + + h1_in_int_by_face = defaultdict(list) + for e in h1_in_interior: + for fi in h1_face_of_edge.get(e, []): + h1_in_int_by_face[fi].append(e) + print(f'\nH_1 edges adjacent to target face boundary:') + for fi, eds in sorted(h1_in_int_by_face.items()): + print(f' H_1 face {fi}: {len(set(eds))} edges') + + # Draw + fig, ax = plt.subplots(figsize=(11, 10)) + # All H edges in light gray as background + for u, v in H.edges(labels=False): + e = tuple(sorted((u, v))) + x1, y1 = pos[u]; x2, y2 = pos[v] + de = ed.get(e, -1) + if de == 0: + color = '#888888'; lw = 0.8; alpha = 0.4 + elif de == 1: + color = '#4477CC'; lw = 2.0; alpha = 0.8 + elif de == 2: + color = '#DD7733'; lw = 3.5; alpha = 1.0 + elif de == 3: + color = '#999999'; lw = 0.8; alpha = 0.3 + else: + color = '#CCCCCC'; lw = 0.5; alpha = 0.2 + ax.plot([x1, x2], [y1, y2], color=color, linewidth=lw, + alpha=alpha, zorder=2) + + # Highlight target H_2 face boundary + for u, v in target_face: + x1, y1 = pos[u]; x2, y2 = pos[v] + ax.plot([x1, x2], [y1, y2], color='#DD7733', linewidth=5.5, + alpha=1.0, zorder=4, solid_capstyle='round') + + # H_1 edges colored by their H_1 face + h1_colors = {'face 0': '#22AA88', 'face 1': '#AA22AA', + 'face 2': '#225599'} + color_list = ['#22AA88', '#AA22AA', '#225599'] + for fi, eds in sorted(h1_in_int_by_face.items()): + col = color_list[fi % len(color_list)] + for e in set(eds): + u, v = e + x1, y1 = pos[u]; x2, y2 = pos[v] + ax.plot([x1, x2], [y1, y2], color=col, linewidth=4.0, + alpha=0.95, zorder=5, solid_capstyle='round') + + # Vertices + for v in H.vertices(): + x, y = pos[v] + in_target = v in target_verts + is_pendant = v in pendant_verts + if is_pendant: + ax.plot(x, y, 'o', color='#CC3333', markersize=6, zorder=6) + elif in_target: + ax.plot(x, y, 'o', color='#DD7733', markersize=7, zorder=6, + markeredgecolor='black', markeredgewidth=1) + ax.annotate(str(v), (x, y), textcoords='offset points', + xytext=(7, 7), fontsize=9, color='black', zorder=7) + else: + ax.plot(x, y, 'ko', markersize=4, zorder=6) + + from matplotlib.lines import Line2D + legend = [ + Line2D([0], [0], color='#DD7733', lw=5, + label=f'$H_2$ face {target_face_idx} boundary ' + f'({len(target_face)} edges)'), + Line2D([0], [0], color='#22AA88', lw=4, + label=f'$H_1$ edges in face 0'), + Line2D([0], [0], color='#AA22AA', lw=4, + label=f'$H_1$ edges in face 1'), + Line2D([0], [0], color='#225599', lw=4, + label=f'$H_1$ edges in face 2'), + Line2D([0], [0], color='#4477CC', lw=1.5, label='other $H_1$ edges'), + Line2D([0], [0], color='#888888', lw=0.8, label='pendants ($d=0$)'), + Line2D([0], [0], color='#999999', lw=0.8, label='$H_3+$ edges'), + Line2D([0], [0], marker='o', color='#CC3333', lw=0, + label='pendant vertex', markersize=8), + Line2D([0], [0], marker='o', color='#DD7733', lw=0, + label='$H_2$ face vertex', markersize=8, + markeredgecolor='black'), + ] + ax.legend(handles=legend, loc='upper left', fontsize=8, + framealpha=0.95) + + ax.set_aspect('equal') + ax.axis('off') + n_h1_faces = len(h1_in_int_by_face) + ax.set_title( + f'HM_0 cut #1 side 1, $d=2$: this $H_2$ face has $H_1$ edges ' + f'from {n_h1_faces} different $H_1$ faces adjacent\n' + f'(planar embedding from Sage; $H_2$ face shown in orange; ' + f'$H_1$ edges grouped by face)') + + out = os.path.join(os.path.dirname(HERE), 'notes', + 'uniqueness_break_example.pdf') + fig.savefig(out, bbox_inches='tight', dpi=120) + print(f'\nWrote {out}') + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/experiments/find_uniqueness_break.py b/papers/coloring_nested_tire_graphs/experiments/find_uniqueness_break.py new file mode 100644 index 0000000..47d4039 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/find_uniqueness_break.py @@ -0,0 +1,171 @@ +"""Find an empirical case where the low-side face of H_d spans +multiple faces of H_{d-1} (= the case high-side restriction is +needed for). + +For each (G, cut, side), iterate (d, face) and check: + - Is `face` the low-side face of H_d? + - Does face's interior contain H_{d-1} edges from multiple + H_{d-1} faces? + +Output: a clear description of the case + edges of H_d, H_{d-1}, +and their face structures, so we can draw it. +""" +import os, sys +from collections import defaultdict + +from sage.all import Graph, graphs + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +from cut_depth_label import ( + parse_planar_code, HM_FILE, + apply_procedure, compute_nice_layout, +) +from cut_tire_tree import build_tree, compute_H_d_faces +from tree_structure_sweep import all_six_edge_cuts + + +def classify_face(face, edge_depth, d): + """Returns 'low', 'high', or 'mixed' based on adjacency to non-Hd + edges by depth around the face vertices.""" + verts = set() + for u, v in face: + verts.add(u); verts.add(v) + seen_lower = False + seen_higher = False + # For each vertex in face, look at incident edges NOT on this face + face_e_set = {tuple(sorted(e)) for e in face} + # Approach: any incident edge with depth < d → "lower" hint; + # any with depth > d → "higher" hint. (The face's interior could + # contain stuff via vertex-adjacency.) + return None # not used, we'll classify differently + + +def find_face_of_edge(faces, e_norm): + """Find which face(s) of a graph have edge e_norm in their walk.""" + matches = [] + for fi, walk in enumerate(faces): + for u, v in walk: + if tuple(sorted((u, v))) == e_norm: + matches.append(fi) + break + return matches + + +def analyze_cut(G, cut_idx, cuts, base_pos): + S, cut_edges = cuts[cut_idx] + S0, S1 = S, frozenset(G.vertices()) - S + for side, S_side in [('0', S0), ('1', S1)]: + if len(S_side) < 6 or len(S_side) > G.order() - 6: + continue + try: + H, _, ed, _, _, _ = apply_procedure( + G, S_side, cut_edges, base_pos, side, + pendant_start_id=max(G.vertices()) + 1 + ( + 0 if side == '0' else 500)) + except Exception: + continue + if not ed: + continue + max_d = max(ed.values()) + for d in range(2, max_d + 1): + faces_d, H_d = compute_H_d_faces(H, ed, d) + faces_dm1, H_dm1 = compute_H_d_faces(H, ed, d - 1) + if not faces_d or not faces_dm1 or len(faces_dm1) < 2: + continue + # For each face of H_d, find which H_{d-1} edges are + # "inside" it (= H_{d-1} edges with both endpoints among + # face's vertex closure, intuitively). + # Simpler: a low-side face of H_d is one whose interior + # contains depth-= 2 and len(face) >= 6 and \ + len(unique_edges) >= 4: + print(f'\n=== FOUND: side {side}, d={d}, face {fi} of ' + f'H_d spans {len(faces_touched)} H_{{d-1}} ' + f'faces ===', flush=True) + print(f' Cut: |S0|={len(S0)}, |S1|={len(S1)}') + print(f' H_d face boundary edges ({len(face)}):', + flush=True) + for u, v in face[:8]: + print(f' ({u},{v})') + if len(face) > 8: + print(f' ... ({len(face)-8} more)') + print(f' H_{{d-1}} edges adjacent to face ' + f'(broken across H_{{d-1}} faces):') + for fjk in sorted(faces_touched): + print(f' H_{{d-1}} face {fjk}: ' + f'{[(min(e),max(e)) for e in edge_to_face[fjk][:5]]}') + return { + 'side': side, 'd': d, 'face_idx': fi, + 'face_walk': face, 'face_verts': face_verts, + 'H_d_edges': [(u,v) for u,v in face], + 'H_dm1_faces': {fjk: edge_to_face[fjk] + for fjk in faces_touched}, + 'all_H_d_faces': faces_d, + 'all_H_dm1_faces': faces_dm1, + 'edge_depth': ed, + } + return None + + +def main(): + gs = parse_planar_code(HM_FILE) + candidates = [('Dodecahedron', graphs.DodecahedralGraph())] + for i, g in enumerate(gs): + candidates.append((f'HM_{i}', g)) + for name, G in candidates: + G.is_planar(set_embedding=True) + base_pos = compute_nice_layout(G) + cuts = all_six_edge_cuts(G, max_cuts=30) + print(f'\n=== {name}: testing {len(cuts)} cuts ===', flush=True) + for ci in range(len(cuts)): + res = analyze_cut(G, ci, cuts, base_pos) + if res: + print(f'\nFOUND case in cut #{ci} of {name}', flush=True) + res['graph_name'] = name + res['cut_idx'] = ci + return res + print('No example found.') + return None + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.aux b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.aux index f712371..7cc3a1c 100644 --- a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.aux +++ b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.aux @@ -1,14 +1,15 @@ \relax \@writefile{toc}{\contentsline {paragraph}{Why low-side faces break uniqueness.}{1}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{The coverage gap.}{2}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Resolution.}{2}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Setup.}{2}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{The low-side face of $H_1$.}{2}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Picture.}{2}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{What $T_\partial $ looks like in special cases.}{3}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Role in the chain DP.}{4}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{What this closes.}{5}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{What it leaves open.}{5}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Why the chain DP can still work.}{5}{}\protected@file@percent } -\@writefile{toc}{\contentsline {paragraph}{Logical status of the extended framework.}{5}{}\protected@file@percent } -\gdef \@abspage@last{5} +\@writefile{toc}{\contentsline {paragraph}{An empirically observed case.}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{The coverage gap.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Resolution.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Setup.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{The low-side face of $H_1$.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Picture.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What $T_\partial $ looks like in special cases.}{4}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Role in the chain DP.}{5}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What this closes.}{6}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What it leaves open.}{6}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Why the chain DP can still work.}{6}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Logical status of the extended framework.}{6}{}\protected@file@percent } +\gdef \@abspage@last{6} diff --git a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.log b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.log index 3a3462d..065bb24 100644 --- a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.log +++ b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.log @@ -1,4 +1,4 @@ -This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 26 MAY 2026 23:36 +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 26 MAY 2026 23:44 entering extended mode restricted \write18 enabled. %&-line parsing enabled. @@ -583,44 +583,49 @@ LaTeX Font Info: Trying to load font information for U+msb on input line 20. File: umsb.fd 2013/01/14 v3.01 AMS symbols B ) [1 -{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [2] [3] - [4] [5] -(./boundary_cut_tire.aux) ) +{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] + +File: uniqueness_break_example.pdf Graphic file (type pdf) + +Package pdftex.def Info: uniqueness_break_example.pdf used on input line 107. +(pdftex.def) Requested size: 446.26582pt x 470.60703pt. + [2 <./uniqueness_break_example.pdf>] [3] [4] [5] [6] (./boundary_cut_tire.aux) + ) Here is how much of TeX's memory you used: - 14740 strings out of 478268 - 301161 string characters out of 5846347 - 568755 words of memory out of 5000000 - 32695 multiletter control sequences out of 15000+600000 + 14747 strings out of 478268 + 301412 string characters out of 5846347 + 568832 words of memory out of 5000000 + 32701 multiletter control sequences out of 15000+600000 482413 words of font info for 79 fonts, out of 8000000 for 9000 1141 hyphenation exceptions out of 8191 84i,6n,89p,452b,713s stack positions out of 10000i,1000n,20000p,200000b,200000s -{/usr/local/texlive/2022/texmf-dist/fonts/enc/dvips/ -cm-super/cm-super-ts1.enc} - -Output written on boundary_cut_tire.pdf (5 pages, 251070 bytes). +{/usr/local/texlive/2022/texmf-dist/fonts/enc/dvips/cm-super/cm-super-ts1.enc +} +< +/usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfonts/cm/cmmi10.pfb> +Output written on boundary_cut_tire.pdf (6 pages, 274500 bytes). PDF statistics: - 134 PDF objects out of 1000 (max. 8388607) - 82 compressed objects within 1 object stream + 193 PDF objects out of 1000 (max. 8388607) + 93 compressed objects within 1 object stream 0 named destinations out of 1000 (max. 500000) - 13 words of extra memory for PDF output out of 10000 (max. 10000000) + 18 words of extra memory for PDF output out of 10000 (max. 10000000) diff --git a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.pdf b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.pdf index 2c487e6..fd144ad 100644 Binary files a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.pdf and b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.pdf differ diff --git a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.tex b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.tex index 28d6ee6..5b33880 100644 --- a/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.tex +++ b/papers/coloring_nested_tire_graphs/notes/boundary_cut_tire.tex @@ -85,6 +85,37 @@ By contrast, face $A$ (high-side, inside the inner cycle) sits entirely inside face $X$ of $H_{d-1}$. Unique parent. This is why the forest proposition restricts to high-side faces. +\paragraph{An empirically observed case.} Holton--McKay graph +$\mathrm{HM}_0$, $6$-edge cut \#$1$, side $1$ (with $|S_1| = 28$). +At $d = 2$: $H_2$ has three faces of lengths $4, 4, 12$; $H_1$ also +has three faces of lengths $4, 4, 12$. The outer face of $H_2$ +(the length-$12$ one, $7$ vertices: $\{16,19,20,21,23,24,28\}$) is +low-side --- in its interior live the depth-$0$ pendants and all +of the depth-$1$ ($H_1$) edges. Those $H_1$ edges are spread +across all three faces of $H_1$: +\begin{itemize} +\item $H_1$ face $0$: contributes edge $(15,19)$. +\item $H_1$ face $1$: contributes edge $(17,21)$. +\item $H_1$ face $2$: contributes edges $(23, 27), (28, 33), (24, 29), + (28, 34)$. +\end{itemize} +So this single low-side face of $H_2$ has $H_1$ edges from +\emph{three} different $H_1$ faces adjacent to its boundary --- no +single $H_1$ face contains all of them, hence no unique parent. + +\begin{center} +\includegraphics[width=0.95\textwidth]{uniqueness_break_example.pdf} +\end{center} + +The orange ring is the boundary of the low-side $H_2$ face. The +three coloured edges (green, purple, blue) are $H_1$ edges +adjacent to the orange ring's vertices, coloured by which $H_1$ +face they belong to. The diagram is laid out using Sage's planar +embedding of $H$. If we tried to assign this $H_2$ face a single +``parent'' in $H_1$, none of the three $H_1$ faces would contain +it. $T_\partial$ exists precisely to handle this low-side +exception. + \paragraph{The coverage gap.} Empirically (\texttt{chain\_dp\_joint.py} on the dodecahedron, cut $\#0$, side $0$): when $|S_i|$ is small, $H_1$ on side $i$ can be a diff --git a/papers/coloring_nested_tire_graphs/notes/uniqueness_break_example.pdf b/papers/coloring_nested_tire_graphs/notes/uniqueness_break_example.pdf new file mode 100644 index 0000000..c10d37d Binary files /dev/null and b/papers/coloring_nested_tire_graphs/notes/uniqueness_break_example.pdf differ