"""For each chord-apex+Kempe colouring, walk K_b and K_c (each in trace order starting from the merged edge), and for every shared vertex v in V(K_b) cap V(K_c) record: i_b(v) = position of v in the K_b walk (mod 2) i_c(v) = position of v in the K_c walk (mod 2) h_phi(v) The proposal: under the constant-Heawood hypothesis, Lemma A forces each cycle's c-edge / b-edge sides to be determined by i mod 2. The CW order at a shared vertex v relates these. We tally the joint distribution of (i_b mod 2, i_c mod 2, h(v)) across all colourings and shared vertices, looking for a parity constraint. Run with: sage experiments/check_shared_parity.py """ import os import sys import time from sage.all import Graph from sage.graphs.graph_generators import graphs HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from check_conj_final_scaled import ( apply_reduction, proper_3_edge_colorings, matches_chord_apex_kempe, trace_kempe_cycle, edge_idx, ) from check_heawood_on_kempe import dual_of, heawood_numbers def walk_positions(walk): """Return dict vertex -> first-position-on-walk.""" pos = {} for k, (_, leave_v) in enumerate(walk): if leave_v not in pos: pos[leave_v] = k return pos def test_one(D): D.is_planar(set_embedding=True) n_col = 0 # Joint distribution: (i_b mod 2, i_c mod 2, h) -> count joint = {} # Per-colouring: count of shared vertices in each of the 4 # (i_b, i_c) parity buckets, summarised. bucket_dist = {} # (n00, n01, n10, n11) -> count # Per-colouring: is sum of i_b parities over shared vertices == # sum of i_c parities (mod 2)? sum_parity_match = 0 sum_parity_total = 0 # Per-colouring: is i_b(v) congruent to i_c(v) (mod 2) for ALL # shared vertices? Or NEVER? Or mixed? all_match = 0 all_diff = 0 mixed = 0 for face in D.faces(): if len(face) != 5: continue for i_red in range(5): res = apply_reduction(D, face, i_red, 9999) if res is None: continue H = res['H']; named = res['named'] H.is_planar(set_embedding=True) edges, colorings = proper_3_edge_colorings(H) cand = [c for c in colorings if matches_chord_apex_kempe(edges, c, named)] for col in cand: n_col += 1 try: h = heawood_numbers(H, edges, col) except RuntimeError: continue merged_idx = edge_idx(edges, named['merged']) a = col[merged_idx] bs = [c for c in range(3) if c != a] walk_b = trace_kempe_cycle(edges, col, merged_idx, (a, bs[0])) walk_c = trace_kempe_cycle(edges, col, merged_idx, (a, bs[1])) pos_b = walk_positions(walk_b) pos_c = walk_positions(walk_c) V_b = set(pos_b.keys()) V_c = set(pos_c.keys()) shared = V_b & V_c buckets = [0, 0, 0, 0] # (i_b, i_c) parities sum_ib = 0 sum_ic = 0 match_count = 0 diff_count = 0 for v in shared: pb = pos_b[v] % 2 pc = pos_c[v] % 2 buckets[2 * pb + pc] += 1 sum_ib = (sum_ib + pb) % 2 sum_ic = (sum_ic + pc) % 2 key = (pb, pc, h[v]) joint[key] = joint.get(key, 0) + 1 if pb == pc: match_count += 1 else: diff_count += 1 if shared: sum_parity_total += 1 if sum_ib == sum_ic: sum_parity_match += 1 if diff_count == 0: all_match += 1 elif match_count == 0: all_diff += 1 else: mixed += 1 bd_key = tuple(buckets) bucket_dist[bd_key] = bucket_dist.get(bd_key, 0) + 1 return n_col, joint, bucket_dist, sum_parity_match, sum_parity_total, all_match, all_diff, mixed def main(max_n=18, time_budget_per_n=1800): print(f"Parity check at shared K_b cap K_c vertices, " f"n in [12, {max_n}]\n") grand_col = 0 grand_joint = {} grand_bucket = {} grand_spm = 0; grand_spt = 0 grand_am = 0; grand_ad = 0; grand_mix = 0 for n in range(12, max_n + 1): start = time.time() try: triangulations = list(graphs.triangulations(n, minimum_degree=5)) except Exception as ex: print(f"n={n}: cannot enumerate ({ex})") continue n_col_n = 0 for tri_idx, G in enumerate(triangulations): if time.time() - start > time_budget_per_n: print(f" n={n}: timeout at tri {tri_idx}/{len(triangulations)}") break G.is_planar(set_embedding=True) D = dual_of(G) (n_col_i, j_i, b_i, spm_i, spt_i, am_i, ad_i, mix_i) = test_one(D) n_col_n += n_col_i for k, v in j_i.items(): grand_joint[k] = grand_joint.get(k, 0) + v for k, v in b_i.items(): grand_bucket[k] = grand_bucket.get(k, 0) + v grand_spm += spm_i; grand_spt += spt_i grand_am += am_i; grand_ad += ad_i; grand_mix += mix_i elapsed = time.time() - start print(f"n={n}: {n_col_n} col., [{elapsed:.0f}s]") sys.stdout.flush() grand_col += n_col_n print() print("=" * 78) print(f"Grand totals (n in [12, {max_n}], {grand_col} colourings):") print(f"\n Joint (i_b mod 2, i_c mod 2, h_phi) distribution over " f"shared vertices:") keys = sorted(grand_joint.keys()) total_shared = sum(grand_joint.values()) for k in keys: v = grand_joint[k] print(f" {k}: {v} ({100*v/max(1,total_shared):.2f}%)") print(f"\n Per-colouring: i_b(v) == i_c(v) (mod 2) for ALL shared v?") print(f" all match: {grand_am}/{grand_col} " f"({100*grand_am/max(1,grand_col):.2f}%)") print(f" all differ: {grand_ad}/{grand_col} " f"({100*grand_ad/max(1,grand_col):.2f}%)") print(f" mixed: {grand_mix}/{grand_col} " f"({100*grand_mix/max(1,grand_col):.2f}%)") print(f"\n Per-colouring: sum_{{v shared}} i_b(v) ≡ sum_{{v shared}} i_c(v) (mod 2)?") print(f" sum-parity match: {grand_spm}/{grand_spt} " f"({100*grand_spm/max(1,grand_spt):.2f}%)") print(f"\n Most common bucket signatures (n00, n01, n10, n11):") bs = sorted(grand_bucket.items(), key=lambda kv: -kv[1])[:8] for k, v in bs: print(f" {k}: {v} ({100*v/max(1,grand_col):.2f}%)") if __name__ == '__main__': main()