diff --git a/papers/coloring_nested_tire_graphs/experiments/chain_dp_debug.py b/papers/coloring_nested_tire_graphs/experiments/chain_dp_debug.py new file mode 100644 index 0000000..6084331 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/chain_dp_debug.py @@ -0,0 +1,79 @@ +"""Debug the chain DP: examine the structure of cut tires on a small +example to understand why R_i is empty. +""" +import os +import sys +import itertools +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, find_six_edge_cut, + apply_procedure, compute_nice_layout, +) +from cut_tire_tree import build_tree +from tree_structure_sweep import all_six_edge_cuts + + +def examine_cut(G, cut_idx, max_cuts=10): + cuts = all_six_edge_cuts(G, max_cuts=max_cuts) + if cut_idx >= len(cuts): + return + S, cut_edges = cuts[cut_idx] + base_pos = compute_nice_layout(G) + S0 = S + S1 = frozenset(G.vertices()) - S + print(f'=== Cut #{cut_idx}: |S0|={len(S0)}, |S1|={len(S1)} ===') + + for side, S_side in [('0', S0), ('1', S1)]: + print(f'\n--- Side {side} ---') + try: + H, pos, ed, _, _, _ = apply_procedure( + G, S_side, cut_edges, base_pos, side, + pendant_start_id=max(G.vertices()) + 1 + + (0 if side == '0' else 200)) + except Exception as e: + print(f' apply failed: {e}') + continue + + # Depth distribution + from collections import Counter + depth_count = Counter(ed.values()) + print(f' edge depths: {dict(depth_count)}') + print(f' |H| = {H.order()} vertices, {H.size()} edges') + print(f' max depth: {max(ed.values()) if ed else "N/A"}') + + if not ed: + continue + faces_by_depth, parent_of, _ = build_tree(H, ed) + for d in sorted(faces_by_depth.keys()): + faces = faces_by_depth[d] + print(f' H_{d}: {len(faces)} faces') + for f_idx, face in enumerate(faces[:3]): + verts = set() + for u, v in face: + verts.add(u) + verts.add(v) + # For each vertex, count incident H edges + bdry_v_data = [] + for v in verts: + incident = list(H.neighbors(v)) + incident_depths = [ed[tuple(sorted((v, nb)))] + for nb in incident] + bdry_v_data.append((v, incident_depths)) + # Show first few + print(f' face {f_idx}: |bdry|={len(face)}, ' + f'verts={len(verts)}, sample: {bdry_v_data[:3]}') + + +def main(): + G = graphs.DodecahedralGraph() + G.is_planar(set_embedding=True) + examine_cut(G, cut_idx=2, max_cuts=5) + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/experiments/chain_dp_general.py b/papers/coloring_nested_tire_graphs/experiments/chain_dp_general.py new file mode 100644 index 0000000..3e7e7f6 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/chain_dp_general.py @@ -0,0 +1,358 @@ +"""Generalized chain DP for cut tires (handles branched H_d faces). + +A cut tire T_d^{(f)} is treated as: + - Edge set E_f = edges in the boundary walk of face f of H_d. + - Vertex set V_f = vertices incident to E_f. + - For each v in V_f with deg_{H_d}(v) = 2: one labeled pendant + (in or out depending on the depth of the non-H_d edge at v). + - For each v in V_f with deg_{H_d}(v) = 3: no pendant. + +Proper 3-edge-coloring of T = (E_f ∪ pendants) such that all edges +incident at every v in V_f have distinct colors. (The "underlying" +graph treats E_f and pendants as edges in a planar multigraph; a +branch vertex has 3 incident E_f edges all distinct.) + +Out-spokes projection: tuple of colors on labeled-OUT pendants +(depth d-1 edges). + +Chain DP bottom-up: + - At each tire, enumerate proper 3-edge-colorings. + - Restrict to those compatible with at least one combination + of out-spoke colorings from each child. + - Project onto own out spokes. + +Verify: + (a) At each tire: |A(T)| > 0, contains full S_3 orbit. + (b) R_0 ∩ R_1 non-empty at the cut. +""" +import os +import sys +import itertools +from collections import defaultdict, Counter + +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 +from tree_structure_sweep import all_six_edge_cuts + + +def cut_tire_structure(H, edge_depth, d, face): + """Extract structure of cut tire T_d^{(face)}. + + Returns dict: + 'edges_indexed': list of (e_id, edge_tuple) for cycle edges. + Order: by appearance in face walk (so duplicates + can happen for branch points, but we use each + edge once). + 'pendants_in': dict v -> (pendant_id, depth=d+1 edge) + 'pendants_out': dict v -> (pendant_id, depth=d-1 edge) + 'edges_at_v': dict v -> list of edge_ids incident to v + (each appearance of v in walk = 1 cycle edge) + 'cycle_edges': list of (cycle_edge_id, edge_tuple) + """ + # First normalize: count each unique cycle edge once + cycle_e_set = set() + cycle_edges = [] + for u, v in face: + e = tuple(sorted((u, v))) + if e not in cycle_e_set: + cycle_e_set.add(e) + cycle_edges.append(e) + # Vertices on this face boundary + verts_set = set() + for u, v in cycle_edges: + verts_set.add(u) + verts_set.add(v) + # Edges incident to each vertex (cycle edges only) + edges_at_v = defaultdict(list) + for ce_id, (a, b) in enumerate(cycle_edges): + edges_at_v[a].append(ce_id) + edges_at_v[b].append(ce_id) + # Pendants: at each degree-2 vertex of H, identify the third edge + pendants_in = {} + pendants_out = {} + pendant_counter = [len(cycle_edges)] # next available edge id + for v in verts_set: + H_deg = sum(1 for nb in H.neighbors(v) + if tuple(sorted((v, nb))) in cycle_e_set) + # Wait — that's just degree restricted to face cycle edges. + # But pendant attaches to the H-edge of different depth. + # Look at ALL edges of H incident to v: + for nb in H.neighbors(v): + e_full = tuple(sorted((v, nb))) + d_e = edge_depth.get(e_full) + if d_e is None: + continue + if d_e == d: + continue # skip cycle edges + elif d_e == d + 1: + pid = pendant_counter[0] + pendant_counter[0] += 1 + pendants_in[v] = (pid, e_full) + edges_at_v[v].append(pid) + elif d_e == d - 1: + pid = pendant_counter[0] + pendant_counter[0] += 1 + pendants_out[v] = (pid, e_full) + edges_at_v[v].append(pid) + return { + 'cycle_edges': cycle_edges, + 'pendants_in': pendants_in, + 'pendants_out': pendants_out, + 'edges_at_v': dict(edges_at_v), + 'n_edges': pendant_counter[0], + } + + +def proper_3_colorings_general(struct): + """Enumerate all proper 3-edge-colorings of cut tire. + Edge ids 0..n_cycle_edges-1 are cycle edges; n_cycle...n_edges-1 are + pendants. Constraint: at each vertex v, the colors of edges in + edges_at_v[v] are all distinct.""" + n_e = struct['n_edges'] + edges_at_v = struct['edges_at_v'] + # Edge-coloring constraint reduces to: per vertex, colors distinct. + # Since vertex can have ≤ 3 incident edges in cubic (since each + # vertex in original G' is cubic), and our cut tire is subgraph, + # each v has at most 3 incident edges in the cut tire. + # We need all colors at each v distinct. + out = [] + # Naive enumeration: 3^n_e then filter. n_e is small (~10-50) + # for our test cases, so doable. + if n_e > 12: + return None # too big + for assign in itertools.product([0, 1, 2], repeat=n_e): + ok = True + for v, eids in edges_at_v.items(): + colors = [assign[eid] for eid in eids] + if len(set(colors)) != len(colors): + ok = False + break + if ok: + out.append(assign) + return out + + +def s3_orbit(tup): + """S_3 orbit of color tuple under permutations of {0,1,2}.""" + orbit = set() + for perm in itertools.permutations([0, 1, 2]): + orbit.add(tuple(perm[c] for c in tup)) + return orbit + + +def has_full_s3_orbit(s): + """Does set s of tuples contain a 6-element S_3 orbit?""" + if not s: + return False + for tup in s: + orb = s3_orbit(tup) + if len(orb) == 6 and orb <= s: + return True + return False + + +def chain_dp_general(faces_by_depth, parent_of, H, edge_depth): + """Run chain DP, returns dict node -> A(T) (set of out-spoke + color tuples).""" + # Build structures for each tire + structs = {} + for d, faces in faces_by_depth.items(): + for f_idx, face in enumerate(faces): + structs[(d, f_idx)] = cut_tire_structure(H, edge_depth, d, face) + + # Build children list + children = defaultdict(list) + for node, p in parent_of.items(): + if p is not None and p != ('cut', None): + children[p].append(node) + + # Order by depth (process leaves first) + max_d = max(d for (d, _) in structs) + A = {} + for d in range(max_d, 0, -1): + for (dd, f_idx), struct in list(structs.items()): + if dd != d: + continue + node = (d, f_idx) + cs = proper_3_colorings_general(struct) + if cs is None: + A[node] = None # too big + continue + # Pendant-IN edge ids: those whose pendant_id is in pendants_in + in_pids = {v: pid for v, (pid, _) in struct['pendants_in'].items()} + out_pids = {v: pid for v, (pid, _) in struct['pendants_out'].items()} + in_v_sorted = sorted(in_pids.keys()) + out_v_sorted = sorted(out_pids.keys()) + + # Find correspondence: each pendant-IN edge of this tire + # = full edge in G'_i. We need to find which child cycle + # edge it corresponds to. + # The pendant-IN at v has full edge e (in struct). + # Among children, find child c whose cycle_edges contains e. + child_links = [] # (child_node, parent_v -> child_edge_id) + for ch in children.get(node, []): + if ch not in A: + continue + if A[ch] is None: + child_links.append((ch, None)) # too big, skip + continue + ch_struct = structs[ch] + ch_out_v = sorted(ch_struct['pendants_out'].keys()) + ch_out_full = {v: ch_struct['pendants_out'][v][1] + for v in ch_out_v} + # Match: parent's pendant-IN edge = child's pendant-OUT edge? + # No wait — parent's pendant-IN is a depth-(d+1) edge. + # Child T_{d+1}^{(f')} has cycle edges of depth d+1. + # So parent's pendant-IN edge = a cycle edge of child. + p_v_to_child_cycle_eid = {} + for v_p, (pid_p, e_full_p) in struct['pendants_in'].items(): + # Find edge in child's cycle_edges with same e_full + for ce_id, e_c in enumerate(ch_struct['cycle_edges']): + if e_c == e_full_p: + p_v_to_child_cycle_eid[v_p] = ce_id + break + child_links.append((ch, p_v_to_child_cycle_eid)) + + # For each coloring, check compatibility with each child + achievable = set() + for assign in cs: + # Determine parent's pendant-IN colors + p_in_colors = {v: assign[in_pids[v]] for v in in_pids} + # For each child, check if there's a child-coloring with + # the same colors at the corresponding cycle edges. + # We use child's A in terms of OUT-SPOKE colors, but we + # need to constrain CYCLE colors. So we need to recompute + # per-child compatibility from child's allowed colorings. + # ALTERNATE: instead of using A(ch) (out-spoke restriction), + # track full child colorings. This blows up but is correct. + # Simpler: track child's set of valid (cycle, out) colorings. + # For now we use the WEAKER check: out-spoke compatibility + # via "any child coloring at all", since A(ch) doesn't + # carry cycle-edge info. + # Since the chain compatibility is on PARENT'S IN spokes = + # CHILD'S CYCLE EDGES (not out spokes), we actually need + # the child's projection on cycle edges, not out spokes. + # For this test, let's use child's full coloring set. + ok = True + for ch, link in child_links: + if link is None: + # child too big — skip compatibility check + continue + ch_full_colorings = proper_3_colorings_general(structs[ch]) + if ch_full_colorings is None: + continue + # Find a ch_coloring with matching cycle-edge colors + found = False + for ch_assign in ch_full_colorings: + match = True + for v_p, ce_id_c in link.items(): + if ch_assign[ce_id_c] != p_in_colors[v_p]: + match = False + break + if match: + found = True + break + if not found: + ok = False + break + if ok: + # Project to out spokes + if out_v_sorted: + tup = tuple(assign[out_pids[v]] for v in out_v_sorted) + else: + tup = () + achievable.add(tup) + A[node] = achievable + return A, structs + + +def test_on_graph(G, name, max_cuts=3): + cuts = all_six_edge_cuts(G, max_cuts=max_cuts) + print(f'\n=== {name}: V={G.order()}, {len(cuts)} cuts tested ===', + flush=True) + base_pos = compute_nice_layout(G) + n_tests, n_pertire_orbit_ok, n_root_ok, n_cross_ok = 0, 0, 0, 0 + per_tire_failures = [] + cross_failures = [] + for cut_idx, (S, cut_edges) in enumerate(cuts): + S0, S1 = S, frozenset(G.vertices()) - S + if len(S0) < 4 or len(S1) < 4: + continue + Rs = {} + for side, S_side in [('0', S0), ('1', S1)]: + try: + H, pos, ed, _, _, _ = apply_procedure( + G, S_side, cut_edges, base_pos, side, + pendant_start_id=max(G.vertices()) + 1 + + (0 if side == '0' else 200)) + except Exception: + continue + if not ed: + continue + faces_by_depth, parent_of, _ = build_tree(H, ed) + try: + A, structs = chain_dp_general( + faces_by_depth, parent_of, H, ed) + except Exception as e: + print(f' cut #{cut_idx}/{side}: DP failed: {e}', flush=True) + continue + # Per-tire check + for node, a in A.items(): + if a is None: + continue + n_tests += 1 + if a and has_full_s3_orbit(a): + n_pertire_orbit_ok += 1 + else: + per_tire_failures.append({ + 'cut': cut_idx, 'side': side, 'node': node, + 'n_edges': structs[node]['n_edges'], + '|A|': len(a), + 'has_orbit': has_full_s3_orbit(a) if a else False, + }) + # Roots + R_i = set() + for node in A: + if node[0] == 1 and A[node]: + R_i |= A[node] + if R_i and has_full_s3_orbit(R_i): + n_root_ok += 1 + Rs[side] = R_i + if '0' in Rs and '1' in Rs: + if Rs['0'] & Rs['1']: + n_cross_ok += 1 + else: + cross_failures.append({ + 'cut': cut_idx, + '|R0|': len(Rs['0']), + '|R1|': len(Rs['1']), + }) + print(f' Per-tire A(T) full-orbit count: {n_pertire_orbit_ok}/{n_tests}', + flush=True) + if per_tire_failures: + print(f' First per-tire failures:') + for f in per_tire_failures[:5]: + print(f' {f}') + print(f' R_i has full orbit (root sides): {n_root_ok}', flush=True) + print(f' Cross-cut R_0 ∩ R_1 != empty: {n_cross_ok}', flush=True) + if cross_failures: + print(f' Cross failures: {cross_failures[:3]}') + + +def main(): + G = graphs.DodecahedralGraph() + G.is_planar(set_embedding=True) + test_on_graph(G, 'Dodecahedron', max_cuts=3) + gs = parse_planar_code(HM_FILE) + test_on_graph(gs[0], 'HM_0', max_cuts=3) + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/experiments/chain_dp_test.py b/papers/coloring_nested_tire_graphs/experiments/chain_dp_test.py new file mode 100644 index 0000000..aa7296f --- /dev/null +++ b/papers/coloring_nested_tire_graphs/experiments/chain_dp_test.py @@ -0,0 +1,363 @@ +"""Empirical test of the chain DP for the loose conjecture. + +For each (G, cut, side), build the cut tire forest. For each cut tire +T = D(T) (cycle + pendants), enumerate proper 3-edge-colorings of T. +Tree DP bottom-up: at each node, compute the set of out-spoke +colorings compatible with at least one valid child combination. + +Verify at roots: R_i != empty, contains a full S_3 orbit. +Verify across cut: R_0 ∩ R_1 != empty, contains a full S_3 orbit. + +Per the chain_half_analysis.tex note, this directly tests: + - Conjecture (non-emptiness preservation): A(T_p) non-empty if + children's A's are non-empty. + - Bottom-line: R_0 ∩ R_1 non-empty. +""" +import os +import sys +import itertools +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, find_six_edge_cut, + apply_procedure, compute_nice_layout, +) +from cut_tire_tree import build_tree + + +def order_cycle(cycle_edges): + """Order edges into a cyclic walk. Returns (verts_in_order, + edges_in_order) or (None, None) if not a simple cycle. + edges_in_order[i] connects verts_in_order[i] and verts_in_order[i+1].""" + n = len(cycle_edges) + if n < 2: + return None, None + e_set = {tuple(sorted(e)) for e in cycle_edges} + adj = defaultdict(list) + for u, v in e_set: + adj[u].append(v) + adj[v].append(u) + # Every vertex must have exactly 2 neighbors for a simple cycle + if any(len(adj[v]) != 2 for v in adj): + return None, None + start = next(iter(adj)) + walk = [start] + prev = None + cur = start + while True: + nxt = [w for w in adj[cur] if w != prev] + if not nxt: + return None, None + nxt = nxt[0] + if nxt == start and len(walk) == n: + break + walk.append(nxt) + prev = cur + cur = nxt + if len(walk) > n: + return None, None + if len(walk) != n: + return None, None + edges_in_order = [tuple(sorted((walk[i], walk[(i + 1) % n]))) + for i in range(n)] + return walk, edges_in_order + + +def get_tire_data(H, edge_depth, d, face): + """For cut tire T_d^{(face)}, return (verts_in_order, cycle_edges, + in_spokes, out_spokes) or None if face is not a simple cycle. + + in_spokes: dict {v: spoke_edge} for depth d+1 spokes + out_spokes: dict {v: spoke_edge} for depth d-1 spokes + """ + cycle_e = [tuple(sorted(e)) for e in face] + walk, edges_ord = order_cycle(cycle_e) + if walk is None: + return None + in_sp, out_sp = {}, {} + cycle_e_set = set(edges_ord) + for v in walk: + for nb in H.neighbors(v): + e = tuple(sorted((v, nb))) + if e in cycle_e_set: + continue + d_e = edge_depth.get(e) + if d_e is None: + continue + if d_e == d + 1: + in_sp[v] = e + elif d_e == d - 1: + out_sp[v] = e + return walk, edges_ord, in_sp, out_sp + + +def proper_3_colorings(verts, cycle_edges, in_sp, out_sp): + """Enumerate proper 3-edge-colorings of D(T): cycle + spokes. + Returns list of dicts {edge: color}. + + For each cycle coloring c[i] (i = 0..n-1) with c[i] != c[i+1]: + At vertex v_i, the two cycle edges adjacent are c[i-1] and c[i]. + Spoke at v_i (if present) gets the third color. + """ + n = len(cycle_edges) + out = [] + for assign in itertools.product([0, 1, 2], repeat=n): + # Proper cycle + bad = False + for i in range(n): + if assign[i] == assign[(i + 1) % n]: + bad = True + break + if bad: + continue + col = {cycle_edges[i]: assign[i] for i in range(n)} + # Determine spoke colors + for i, v in enumerate(verts): + c_left = assign[(i - 1) % n] + c_right = assign[i] + third = ({0, 1, 2} - {c_left, c_right}) + if len(third) != 1: + # Degenerate (n=2 case) + bad = True + break + third_c = third.pop() + if v in in_sp: + col[in_sp[v]] = third_c + if v in out_sp: + col[out_sp[v]] = third_c + if not bad: + out.append(col) + return out + + +def s3_orbit(tup): + """Generate S_3 orbit of a color tuple under permutations of {0,1,2}.""" + orbit = set() + for perm in itertools.permutations([0, 1, 2]): + orbit.add(tuple(perm[c] for c in tup)) + return orbit + + +def has_full_s3_orbit(s): + """Does set s (of tuples) contain a 6-element S_3 orbit?""" + for tup in s: + if len(s3_orbit(tup)) == 6 and s3_orbit(tup) <= s: + return True + return False + + +def chain_dp(faces_by_depth, parent_of, H, edge_depth, side_label): + """Run the chain DP on the cut tire forest. + + For each tire T_d^{(f)}, compute: + - tire_data: (verts, cycle_e, in_sp, out_sp) + - colorings: all proper 3-edge-colorings + - A(T): set of (out-spoke-color-tuples), restricted by + compatibility with all children. + + Returns: {node: A(T)}, where each A(T) is a set of tuples. + Also returns dict of issues found. + """ + tire_data = {} + for d, faces in faces_by_depth.items(): + for f_idx, face in enumerate(faces): + td = get_tire_data(H, edge_depth, d, face) + if td is not None: + tire_data[(d, f_idx)] = td + + # Build children list + children = defaultdict(list) + for node, p in parent_of.items(): + if p is not None and p != ('cut', None): + children[p].append(node) + + # Process bottom-up: order nodes by depth descending + nodes_by_depth = defaultdict(list) + for node in tire_data: + nodes_by_depth[node[0]].append(node) + max_d = max(nodes_by_depth) if nodes_by_depth else 0 + + A = {} # node -> set of out-spoke-color tuples + fully_S3 = {} # node -> bool: A(T) contains full S_3 orbit + issues = [] + + for d in range(max_d, 0, -1): + for node in nodes_by_depth[d]: + verts, cycle_e, in_sp, out_sp = tire_data[node] + if not out_sp and d > 1: + # Internal/leaf without out spokes — degenerate + A[node] = set() + continue + cs = proper_3_colorings(verts, cycle_e, in_sp, out_sp) + # Order out-spoke positions (by vertex) + out_sp_verts = sorted(out_sp.keys()) + in_sp_verts = sorted(in_sp.keys()) + + # For each child, build map: parent-in-spoke-vertex (= parent + # boundary vertex with in spoke) -> child-out-spoke-vertex. + # Equivalent: the in-spoke edge of parent = an out-spoke edge + # of one of its children. The shared endpoint is the parent's + # boundary vertex. + child_outsp_maps = [] + for ch in children.get(node, []): + if ch not in tire_data: + continue + ch_verts, ch_cycle, ch_in, ch_out = tire_data[ch] + # Match: parent's in-spoke edge == child's out-spoke edge + p_in_to_ch_out_v = {} + for v_p, e_p in in_sp.items(): + # Find the vertex w of child with out-spoke e_p + for v_c, e_c in ch_out.items(): + if e_p == e_c: + p_in_to_ch_out_v[v_p] = (ch, v_c) + break + child_outsp_maps.append((ch, p_in_to_ch_out_v)) + + # For each coloring, check compatibility with each child's A + achievable_out = set() + for col in cs: + # Extract parent's in-spoke colors + p_in_colors_by_v = {v: col[in_sp[v]] for v in in_sp} + # For each child, verify the corresponding out-spoke + # color (= our in-spoke color) is in A(child) + ok = True + for ch, mapping in child_outsp_maps: + if ch not in A: + ok = False # child has no achievable (shouldn't happen) + break + ch_verts, ch_cycle, ch_in, ch_out = tire_data[ch] + ch_out_verts_ordered = sorted(ch_out.keys()) + # Build the child's out-spoke tuple as we'd need + required_ch_out = {} + for v_p, (ch_id, v_c) in mapping.items(): + if ch_id != ch: + continue + required_ch_out[v_c] = p_in_colors_by_v[v_p] + # If some out-spoke vertex of child isn't covered by + # any parent in spoke, the value is FREE in A. + # So we check: exists tup in A(ch) with tup[i] = + # required_ch_out[v] for each i where v is constrained. + found_any = False + for tup in A[ch]: + match = True + for i, v in enumerate(ch_out_verts_ordered): + if v in required_ch_out and tup[i] != required_ch_out[v]: + match = False + break + if match: + found_any = True + break + if not found_any: + ok = False + break + if ok: + # Extract parent's out-spoke colors as a tuple + if out_sp_verts: + tup = tuple(col[out_sp[v]] for v in out_sp_verts) + achievable_out.add(tup) + else: + # No out spokes (root with empty out projection) — + # represent as singleton empty tuple + achievable_out.add(()) + A[node] = achievable_out + fully_S3[node] = has_full_s3_orbit(achievable_out) + if not achievable_out: + issues.append({ + 'type': 'empty_after_chain', + 'node': node, + 'side': side_label, + 'n_children': len(children.get(node, [])), + }) + + return A, fully_S3, issues, tire_data + + +def run_test(G, name, max_cuts=5): + """Run chain DP test on G across several 6-edge cuts.""" + from tree_structure_sweep import all_six_edge_cuts + cuts = all_six_edge_cuts(G, max_cuts=max_cuts) + print(f'\n=== {name}: V={G.order()}, E={G.size()}, ' + f'{len(cuts)} cuts tested ===', flush=True) + base_pos = compute_nice_layout(G) + n_cuts = 0 + n_empty = 0 + n_no_orbit = 0 + n_crossok = 0 + total_issues = [] + for cut_idx, (S, cut_edges) in enumerate(cuts): + S0 = S + S1 = frozenset(G.vertices()) - S + if len(S0) < 4 or len(S1) < 4: + continue + A_per_side = {} + for side, S_side in [('0', S0), ('1', S1)]: + try: + H, pos, ed, _, _, _ = apply_procedure( + G, S_side, cut_edges, base_pos, side, + pendant_start_id=max(G.vertices()) + 1 + + (0 if side == '0' else 200)) + except Exception as e: + print(f' cut #{cut_idx} side {side}: apply_procedure ' + f'failed: {e}', flush=True) + continue + faces_by_depth, parent_of, _ = build_tree(H, ed) + A, fully_S3, issues, tire_data = chain_dp( + faces_by_depth, parent_of, H, ed, side) + total_issues.extend(issues) + # Look at root tires (depth-1) + root_A = [] + for node in A: + if node[0] == 1: + root_A.append((node, A[node])) + # Combine root A's into R_i: union over roots + R_i = set() + for node, a in root_A: + R_i |= a + A_per_side[side] = R_i + if not R_i: + n_empty += 1 + print(f' cut #{cut_idx} side {side}: R_i empty!', flush=True) + elif not any(len(s3_orbit(t)) == 6 and s3_orbit(t) <= R_i + for t in R_i): + n_no_orbit += 1 + print(f' cut #{cut_idx} side {side}: R_i lacks full S_3 ' + f'orbit (|R_i|={len(R_i)})', flush=True) + if '0' in A_per_side and '1' in A_per_side: + n_cuts += 1 + # Cross-cut check: R_0 and R_1 are on the same 6 cut edges, + # but indexed by which side's roots. Need bijection. + # For now, compare structurally: do they share orbits? + R0, R1 = A_per_side['0'], A_per_side['1'] + common = R0 & R1 + if common: + n_crossok += 1 + else: + print(f' cut #{cut_idx}: R_0 ∩ R_1 = empty ' + f'(|R_0|={len(R0)}, |R_1|={len(R1)})', flush=True) + print(f' cuts tested: {n_cuts}, R_i empty: {n_empty}, ' + f'R_i no full orbit: {n_no_orbit}, R_0 ∩ R_1 non-empty: ' + f'{n_crossok}/{n_cuts}', flush=True) + return total_issues + + +def main(): + # Small test: Dodecahedron + G = graphs.DodecahedralGraph() + G.is_planar(set_embedding=True) + issues_dodec = run_test(G, 'Dodecahedron', max_cuts=5) + + # HM #0 + gs = parse_planar_code(HM_FILE) + issues_hm0 = run_test(gs[0], 'HM_0', max_cuts=5) + + print('\n=== Summary of issues ===', flush=True) + print(f'Dodecahedron: {len(issues_dodec)} issues') + print(f'HM_0: {len(issues_hm0)} issues') + + +if __name__ == '__main__': + main() diff --git a/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.aux b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.aux new file mode 100644 index 0000000..7b0c427 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.aux @@ -0,0 +1,18 @@ +\relax +\@writefile{toc}{\contentsline {paragraph}{Per-tire half.}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Chain half.}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{(1) Leaves.}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{(2) Internal nodes.}{1}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{(3) Roots and the cut.}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{(4) Cross-cut.}{2}{}\protected@file@percent } +\newlabel{conj:non-empty-prop}{{}{2}} +\@writefile{toc}{\contentsline {paragraph}{Why it isn't trivial.}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Why it's plausible.}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What would close the proof.}{2}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What goes wrong.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Consequence for Prop 1.13.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Two ways forward.}{3}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{What this changes in the chain DP.}{4}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Second issue: out-spoke projection loses S$_3$ orbit.}{4}{}\protected@file@percent } +\@writefile{toc}{\contentsline {paragraph}{Third issue: heuristic parent-finding.}{4}{}\protected@file@percent } +\gdef \@abspage@last{4} diff --git a/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.log b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.log new file mode 100644 index 0000000..12ddebc --- /dev/null +++ b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.log @@ -0,0 +1,317 @@ +This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 26 MAY 2026 22:49 +entering extended mode + restricted \write18 enabled. + %&-line parsing enabled. +**chain_half_analysis.tex +(./chain_half_analysis.tex +LaTeX2e <2021-11-15> patch level 1 +L3 programming layer <2022-02-24> +(/usr/local/texlive/2022/texmf-dist/tex/latex/base/article.cls +Document Class: article 2021/10/04 v1.4n Standard LaTeX document class +(/usr/local/texlive/2022/texmf-dist/tex/latex/base/size11.clo +File: size11.clo 2021/10/04 v1.4n Standard LaTeX file (size option) +) +\c@part=\count185 +\c@section=\count186 +\c@subsection=\count187 +\c@subsubsection=\count188 +\c@paragraph=\count189 +\c@subparagraph=\count190 +\c@figure=\count191 +\c@table=\count192 +\abovecaptionskip=\skip47 +\belowcaptionskip=\skip48 +\bibindent=\dimen138 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty +Package: amsmath 2021/10/15 v2.17l AMS math features +\@mathmargin=\skip49 + +For additional information on amsmath, use the `?' option. +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty +Package: amstext 2021/08/26 v2.01 AMS text + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty +File: amsgen.sty 1999/11/30 v2.0 generic functions +\@emptytoks=\toks16 +\ex@=\dimen139 +)) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty +Package: amsbsy 1999/11/29 v1.2d Bold Symbols +\pmbraise@=\dimen140 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty +Package: amsopn 2021/08/26 v2.02 operator names +) +\inf@bad=\count193 +LaTeX Info: Redefining \frac on input line 234. +\uproot@=\count194 +\leftroot@=\count195 +LaTeX Info: Redefining \overline on input line 399. +\classnum@=\count196 +\DOTSCASE@=\count197 +LaTeX Info: Redefining \ldots on input line 496. +LaTeX Info: Redefining \dots on input line 499. +LaTeX Info: Redefining \cdots on input line 620. +\Mathstrutbox@=\box50 +\strutbox@=\box51 +\big@size=\dimen141 +LaTeX Font Info: Redeclaring font encoding OML on input line 743. +LaTeX Font Info: Redeclaring font encoding OMS on input line 744. +\macc@depth=\count198 +\c@MaxMatrixCols=\count199 +\dotsspace@=\muskip16 +\c@parentequation=\count266 +\dspbrk@lvl=\count267 +\tag@help=\toks17 +\row@=\count268 +\column@=\count269 +\maxfields@=\count270 +\andhelp@=\toks18 +\eqnshift@=\dimen142 +\alignsep@=\dimen143 +\tagshift@=\dimen144 +\tagwidth@=\dimen145 +\totwidth@=\dimen146 +\lineht@=\dimen147 +\@envbody=\toks19 +\multlinegap=\skip50 +\multlinetaggap=\skip51 +\mathdisplay@stack=\toks20 +LaTeX Info: Redefining \[ on input line 2938. +LaTeX Info: Redefining \] on input line 2939. +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty +Package: amssymb 2013/01/14 v3.01 AMS font symbols + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty +Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support +\symAMSa=\mathgroup4 +\symAMSb=\mathgroup5 +LaTeX Font Info: Redeclaring math symbol \hbar on input line 98. +LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' +(Font) U/euf/m/n --> U/euf/b/n on input line 106. +)) +(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsthm.sty +Package: amsthm 2020/05/29 v2.20.6 +\thm@style=\toks21 +\thm@bodyfont=\toks22 +\thm@headfont=\toks23 +\thm@notefont=\toks24 +\thm@headpunct=\toks25 +\thm@preskip=\skip52 +\thm@postskip=\skip53 +\thm@headsep=\skip54 +\dth@everypar=\toks26 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty +Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR) + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty +Package: keyval 2014/10/28 v1.15 key=value parser (DPC) +\KV@toks@=\toks27 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty +Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR) + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty +Package: trig 2021/08/11 v1.11 sin cos tan (DPC) +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg +File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration +) +Package graphics Info: Driver file: pdftex.def on input line 107. + +(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def +File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex +)) +\Gin@req@height=\dimen148 +\Gin@req@width=\dimen149 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/geometry/geometry.sty +Package: geometry 2020/01/02 v5.9 Page Geometry + +(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/ifvtex.sty +Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead. + +(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/iftex.sty +Package: iftex 2022/02/03 v1.0f TeX engine tests +)) +\Gm@cnth=\count271 +\Gm@cntv=\count272 +\c@Gm@tempcnt=\count273 +\Gm@bindingoffset=\dimen150 +\Gm@wd@mp=\dimen151 +\Gm@odd@mp=\dimen152 +\Gm@even@mp=\dimen153 +\Gm@layoutwidth=\dimen154 +\Gm@layoutheight=\dimen155 +\Gm@layouthoffset=\dimen156 +\Gm@layoutvoffset=\dimen157 +\Gm@dimlist=\toks28 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/booktabs/booktabs.sty +Package: booktabs 2020/01/12 v1.61803398 Publication quality tables +\heavyrulewidth=\dimen158 +\lightrulewidth=\dimen159 +\cmidrulewidth=\dimen160 +\belowrulesep=\dimen161 +\belowbottomsep=\dimen162 +\aboverulesep=\dimen163 +\abovetopsep=\dimen164 +\cmidrulesep=\dimen165 +\cmidrulekern=\dimen166 +\defaultaddspace=\dimen167 +\@cmidla=\count274 +\@cmidlb=\count275 +\@aboverulesep=\dimen168 +\@belowrulesep=\dimen169 +\@thisruleclass=\count276 +\@lastruleclass=\count277 +\@thisrulewidth=\dimen170 +) +(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def +File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX) +\l__color_backend_stack_int=\count278 +\l__pdf_internal_box=\box52 +) +(./chain_half_analysis.aux) +\openout1 = `chain_half_analysis.aux'. + +LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. +LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 17. +LaTeX Font Info: ... okay on input line 17. + +(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii +[Loading MPS to PDF converter (version 2006.09.02).] +\scratchcounter=\count279 +\scratchdimen=\dimen171 +\scratchbox=\box53 +\nofMPsegments=\count280 +\nofMParguments=\count281 +\everyMPshowfont=\toks29 +\MPscratchCnt=\count282 +\MPscratchDim=\dimen172 +\MPnumerator=\count283 +\makeMPintoPDFobject=\count284 +\everyMPtoPDFconversion=\toks30 +) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty +Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf +Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4 +85. + +(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg +File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv +e +)) +*geometry* driver: auto-detecting +*geometry* detected driver: pdftex +*geometry* verbose mode - [ preamble ] result: +* driver: pdftex +* paper: +* layout: +* layoutoffset:(h,v)=(0.0pt,0.0pt) +* modes: +* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt) +* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt) +* \paperwidth=614.295pt +* \paperheight=794.96999pt +* \textwidth=469.75502pt +* \textheight=650.43001pt +* \oddsidemargin=0.0pt +* \evensidemargin=0.0pt +* \topmargin=-37.0pt +* \headheight=12.0pt +* \headsep=25.0pt +* \topskip=11.0pt +* \footskip=30.0pt +* \marginparwidth=59.0pt +* \marginparsep=10.0pt +* \columnsep=10.0pt +* \skip\footins=10.0pt plus 4.0pt minus 2.0pt +* \hoffset=0.0pt +* \voffset=0.0pt +* \mag=1000 +* \@twocolumnfalse +* \@twosidefalse +* \@mparswitchfalse +* \@reversemarginfalse +* (1in=72.27pt=25.4mm, 1cm=28.453pt) + +LaTeX Font Info: Trying to load font information for U+msa on input line 18. + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd +File: umsa.fd 2013/01/14 v3.01 AMS symbols A +) +LaTeX Font Info: Trying to load font information for U+msb on input line 18. + + +(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd +File: umsb.fd 2013/01/14 v3.01 AMS symbols B +) +Overfull \hbox (31.8519pt too wide) in paragraph at lines 32--37 + \OT1/cmr/bx/n/10.95 Chain half.[] \OT1/cmr/m/n/10.95 Com-pos-ing per-tire pro- +jec-tions through the cut-tire for-est (\OT1/cmtt/m/n/10.95 cut[]tire[]tree[]st +ructure.tex\OT1/cmr/m/n/10.95 , + [] + + +Package amsmath Warning: Foreign command \atop; +(amsmath) \frac or \genfrac should be used instead +(amsmath) on input line 58. + +[1 + +{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] +Overfull \hbox (41.57079pt too wide) in paragraph at lines 134--142 + \OT1/cmr/bx/n/10.95 Why it's plau-si-ble.[] \OT1/cmr/m/n/10.95 Em-pir-i-cal da +ta from the partial- tire-dual chain pi-geon-hole (\OT1/cmtt/m/n/10.95 tire[]fi +ber[]step2.tex\OT1/cmr/m/n/10.95 ): + [] + +[2] [3] [4] (./chain_half_analysis.aux) ) +Here is how much of TeX's memory you used: + 3256 strings out of 478268 + 48448 string characters out of 5846347 + 348617 words of memory out of 5000000 + 21443 multiletter control sequences out of 15000+600000 + 479693 words of font info for 69 fonts, out of 8000000 for 9000 + 1141 hyphenation exceptions out of 8191 + 55i,8n,62p,236b,241s stack positions out of 10000i,1000n,20000p,200000b,200000s +{/usr/local/texlive/2022/texmf-dist/fo +nts/enc/dvips/cm-super/cm-super-ts1.enc} +Output written on chain_half_analysis.pdf (4 pages, 216066 bytes). +PDF statistics: + 103 PDF objects out of 1000 (max. 8388607) + 62 compressed objects within 1 object stream + 0 named destinations out of 1000 (max. 500000) + 1 words of extra memory for PDF output out of 10000 (max. 10000000) + diff --git a/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.pdf b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.pdf new file mode 100644 index 0000000..f037a3b Binary files /dev/null and b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.pdf differ diff --git a/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.tex b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.tex new file mode 100644 index 0000000..ed5b235 --- /dev/null +++ b/papers/coloring_nested_tire_graphs/notes/chain_half_analysis.tex @@ -0,0 +1,279 @@ +\documentclass[11pt]{article} +\usepackage{amsmath,amssymb,amsthm} +\usepackage{graphicx} +\usepackage{geometry} +\usepackage{booktabs} +\geometry{margin=1in} + +\title{Chain half of the loose conjecture: tree DP and where it gates} +\author{} +\date{} + +\newtheorem*{prop}{Proposition} +\newtheorem*{lem}{Lemma} +\newtheorem*{conj}{Conjecture} +\newtheorem*{obs}{Observation} + +\begin{document} +\maketitle + +\section*{Recap} + +The loose chain pigeonhole conjecture (k$\ge 2$ form, +\texttt{cut\_depth\_label.tex}) has two halves: + +\paragraph{Per-tire half.} For every cut tire $T$ with $\ge 2$ +in/out spokes total, the joint projection $\pi(T) \subseteq +\{1,2,3\}^k$ is non-empty, $S_3$-closed, and contains a full $S_3$ +orbit of size $6$. Proven for spoke-only cut tires +($T'_{\mathrm{ann}}$ = simple cycle $C_n$, $n \ge 3$) via Prop 1.13 +of \texttt{paper.tex}. + +\paragraph{Chain half.} Composing per-tire projections through the +cut-tire forest (\texttt{cut\_tire\_tree\_structure.tex}, rigorously +proved) yields $\mathcal{R}_i \ne \emptyset$ on each side $i$ of the +cut, with $\mathcal{R}_0 \cap \mathcal{R}_1 \ne \emptyset$ containing +a common $S_3$-orbit at the cut. + +\section*{Tree DP formulation of the chain half} + +The high-side cut tires of $G'_i$ form a forest (rigorously proved). +Process tires bottom-up: + +\paragraph{(1) Leaves.} A leaf cut tire $T_{d_{\max}}^{(f)}$ has +no children. Its ``achievable projection'' onto its out spokes +(depth-$(d_{\max} - 1)$ direction) is +\[ + A(T) := \pi_{\mathrm{out}}(T), +\] +the projection of the per-tire projection $\pi(T)$ onto the out +spokes alone (in spokes are unconstrained at leaves). By per-tire, +$|A(T)| \ge 6$ with full $S_3$-orbit. + +\paragraph{(2) Internal nodes.} For a cut tire $T_p = T_d^{(f)}$ +with children $T_{c_1}, \dots, T_{c_r}$, define: +\[ + A(T_p) := \left\{\sigma_{\mathrm{out}}(T_p) : \exists\, \chi + \text{ proper edge $3$-coloring of } T_p \text{ such that} + \atop \forall\, j : \chi|_{\text{in-spokes corresponding to } + T_{c_j}} \in A(T_{c_j})'\right\}, +\] +where $A(T_{c_j})'$ is the ``transferred-back'' achievable +projection of child $T_{c_j}$ onto the corresponding parent in-spoke +positions. + +The transfer back: an in spoke of $T_p$ at parent boundary vertex +$v$ corresponds to a depth-$(d+1)$ edge $e^{**}$ in $G'_i$. This +$e^{**}$ is a face-boundary edge of $T_{c_j}$ (the unique child +whose face contains $v$ as a boundary endpoint of $e^{**}$). So the +parent's in-spoke color at $v$ = child's face-boundary-edge color at +$e^{**}$. + +For the projection $A(T_{c_j})$ to constrain parent's in spokes, +we'd need to know how child's spoke colors determine cycle edge +colors at specific positions. In the spoke-only case (Prop 1.13), +each spoke color at $u$ equals the ``third color'' = the unique +color not appearing on the two cycle edges incident at $u$. So +spoke colors determine the constraint set on cycle colors at each +vertex. + +\paragraph{(3) Roots and the cut.} $T_1^{(\cdot)}$ are roots. Their +out spokes are the pendant edges $=$ the cut configuration $\sigma_i$. +The achievable cut configurations: +\[ + \mathcal{R}_i := \bigcup_{\text{roots } T_1^{(f)}} A(T_1^{(f)}) + \quad (\text{or restricted, depending on how root constraints compose}). +\] + +\paragraph{(4) Cross-cut.} $G'$ is properly $3$-edge-colourable iff +$\mathcal{R}_0 \cap \mathcal{R}_1 \ne \emptyset$ (under the +bijection between the two sides' cut edges). + +\section*{What's preserved through the tree DP} + +\subsection*{$S_3$-closure: preserved} + +\begin{lem}[$S_3$-equivariance of tree DP] +If every $A(T_{c_j})$ is $S_3$-closed (under diagonal action on +colours), then $A(T_p)$ is $S_3$-closed. +\end{lem} + +\begin{proof} +The proper-edge-coloring constraint at every vertex is preserved by +$S_3$ acting on colours uniformly. Applying $\pi \in S_3$ to a +valid $\chi$ for $T_p$ gives another valid $\chi$. Compatibility +with children: parent's in spokes are uniformly $\pi$-shifted, and +each child's $A(T_{c_j})$ is $S_3$-closed by hypothesis, so the +shifted parent in spokes still hit $A(T_{c_j})$. Hence $\pi(\sigma) +\in A(T_p)$ for any $\sigma \in A(T_p)$. +\end{proof} + +By induction from leaves (where $A = \pi$ is $S_3$-closed by +per-tire half), every $A(T)$ is $S_3$-closed. This is the easy +half. + +\subsection*{Non-emptiness: open, but constrained} + +\begin{conj}[Non-emptiness preservation] +\label{conj:non-empty-prop} +If every $A(T_{c_j})$ contains a full $S_3$-orbit of size $6$, then +$A(T_p)$ also contains a full $S_3$-orbit. +\end{conj} + +This is the genuine open piece of the chain half. + +\paragraph{Why it isn't trivial.} Two $S_3$-closed subsets of +$\{1, 2, 3\}^k$ can have empty intersection even if both contain +$S_3$-orbits. Example: orbit of $(1, 2, 3)$ vs orbit of $(1, 1, 2)$ +in $\{1,2,3\}^3$ are disjoint. + +So the conjecture would require a structural reason that parent + +children combined always have at least one common assignment with +all 3 colours present (= a full $S_3$-orbit). + +\paragraph{Why it's plausible.} Empirical data from the partial- +tire-dual chain pigeonhole (\texttt{tire\_fiber\_step2.tex}): +$23/23$ pairwise compatibility tests succeeded, with the +intersections containing $S_3$-orbits and structured by ``rainbow'' +or similar canonical orbits +(\texttt{orbit\_decomposition.tex}). These results suggest a +structural reason for non-emptiness; we just don't have a clean +proof. + +\paragraph{What would close the proof.} Show that for spoke-only +cut tires, the per-tire projection $\pi(T)$ has the property: +\emph{for any specified colours on the in spokes that come from +$S_3$-orbits, there is a compatible parent coloring}. Specifically: + +\begin{conj}[Strong per-tire extendibility] +Let $T$ be a spoke-only cut tire with face boundary a simple cycle +$C_n$ ($n \ge 3$). For any $\sigma_{\mathrm{in}} \in \{1, 2, 3\}^{n_{\mathrm{in}}}$ +such that $\sigma_{\mathrm{in}}$ lies in a non-trivial $S_3$-orbit +(i.e.\ uses $\ge 2$ colours and is in $\pi(T)$'s $S_3$-symmetric +support), there exists a proper edge $3$-coloring $\chi$ of $T$ with +$\chi|_{\mathrm{in-spokes}} = \sigma_{\mathrm{in}}$ and +$\chi|_{\mathrm{out-spokes}}$ a non-trivial $S_3$-orbit on the +out-spoke side. +\end{conj} + +If this conjecture holds, the chain DP preserves non-emptiness: for +each non-trivial parent $\sigma_{\mathrm{in}}$ (= child's face +boundary edges) provided by children, parent has a coloring with +out spokes in a non-trivial $S_3$-orbit, hence $A(T_p)$ contains a +full $S_3$-orbit. + +\section*{Empirical next step} + +Cut-tire tree DP empirical test: +\begin{enumerate} + \item For each test graph (HM \#0 through \#5, dodecahedron, + BuckyBall), build the cut tire forest on each side. + \item For each leaf, compute $A(T_{\mathrm{leaf}})$. + \item Bottom-up propagate $A(\cdot)$ to roots. + \item Compare $\mathcal{R}_0 \cap \mathcal{R}_1$ at the cut. +\end{enumerate} + +This is the analogue of \texttt{tire\_fiber\_step2.tex} for the +cut-tire setting. If empirically $\mathcal{R}_0 \cap \mathcal{R}_1 +\ne \emptyset$ universally, the chain half is on firm empirical +ground; the proof would still need Conjecture~\ref{conj:non-empty-prop} +or a structural shortcut. + +\section*{Caveat discovered empirically: cut tires are not spoke-only} + +A first-pass empirical test +(\texttt{experiments/chain\_dp\_test.py}) on the dodecahedron and +Holton--McKay \#0 revealed a structural complication: \emph{cut +tires are not in general spoke-only}. + +\paragraph{What goes wrong.} $H_d$ may have vertices of degree $3$ +(all three incident edges have depth $d$). In a cubic ambient +graph $G'_i$, this happens when three depth-$d$ edges meet at a +single vertex. At such a vertex: +\begin{itemize} +\item There is no third edge available to be a spoke. +\item The face boundary walk of $H_d$ visits the vertex + \emph{twice} (as a branch point). +\end{itemize} +For example, in the dodecahedron, a $6$-edge cut produces $H_1$ +with $1$ face whose boundary has length $20$ but only $11$ distinct +vertices --- $9$ branch-point visits. + +\paragraph{Consequence for Prop 1.13.} The per-tire half (proven +via Prop 1.13) covers \emph{spoke-only} cut tires (face boundary = +simple cycle $C_n$, $n \ge 3$). It does \emph{not} cover branched +cut tires. + +\paragraph{Two ways forward.} +\begin{enumerate} +\item \emph{Restrict.} Identify which graphs $G$ have the property + that every cut tire of every $6$-edge cut is spoke-only. This + is a genuine \emph{a priori} restriction. +\item \emph{Generalise.} Extend the per-tire half to branched cut + tires. Proper edge $3$-coloring on such a structure is + well-defined and probably has a non-empty $S_3$-closed + projection by similar arguments, but the explicit count + $2^n + 2(-1)^n$ no longer applies. +\end{enumerate} + +\paragraph{What this changes in the chain DP.} The chain DP is +still well-formed: enumerate proper $3$-edge-colorings of each cut +tire (now allowing branches), project to out spokes, restrict via +children. The per-tire half just needs the generalized form. + +\paragraph{Second issue: out-spoke projection loses S$_3$ orbit.} +The per-tire half guarantees a full $S_3$ orbit on the \emph{joint} +in+out spoke projection $\pi(T)$. After restricting to OUT spokes +only (which is what the parent uses), the projection $A(T)$ may +contain fewer than $6$ elements --- e.g.\ all out spokes might be +forced to a constant tuple by some structural symmetry, giving +$|A(T)| = 3$ (= $\{(0,0,\dots), (1,1,\dots), (2,2,\dots)\}$). +Initial empirical runs on the dodecahedron and HM \#0 see exactly +this happen at $\sim 20\%$ of cut tires. This is \emph{not} a bug +in the per-tire half; it is a genuine limitation of the OUT-only +projection. + +The correct chain DP formulation should track the joint (in + out) +projection, not just OUT. This is the analogue of +\texttt{tire\_fiber\_step2.tex}'s joint-support tracking. + +\paragraph{Third issue: heuristic parent-finding.} The current +\texttt{cut\_tire\_tree.find\_parent\_face} uses a vertex-overlap +heuristic (smallest face among overlapping candidates). Per the +high-side proposition (\texttt{cut\_tire\_tree\_structure.tex}), +the geometrically-correct parent is unique, but the empirical +script does not enforce this --- it picks by smallest-face +heuristic, which can mis-attribute children to wrong parents. +For a rigorous empirical test, parent assignment should use the +planar embedding's face-in-face containment, not vertex overlap. + +\section*{Net status of the loose conjecture} + +\begin{center} +\small +\begin{tabular}{lll} +\toprule +component & status & note \\ +\midrule +Per-tire half (spoke-only $n \ge 3$) & proven & Prop 1.13 \\ +Per-tire half (branched) & open, needed for generality & \\ +Tree structure (forest, high-side) & proven & \texttt{cut\_tire\_tree\_structure.tex} \\ +Chain DP $S_3$-equivariance & proven & this note, Lemma \\ +Joint vs.\ OUT projection issue & flagged & need joint-support tracking \\ +Chain DP non-emptiness preservation & open & Conj.\ \ref{conj:non-empty-prop} \\ +Bottom-line $\mathcal{R}_0 \cap \mathcal{R}_1 \ne \emptyset$ & open, $G$-colorability gives it & \\ +\bottomrule +\end{tabular} +\end{center} + +The chain half reduces to multiple structural claims: +\begin{itemize} +\item per-tire half for branched cut tires; +\item joint-support DP (track $\pi(T)$, not just OUT projection); +\item non-emptiness preservation (Conj.\ \ref{conj:non-empty-prop} + or Strong per-tire extendibility). +\end{itemize} +None are in hand yet. The $S_3$-equivariance and forest structure +are. The full chain half is genuinely open and requires more work. + +\end{document}