"""For every reduction (G, v, i) underlying a chord-apex+Kempe colouring (up to |V(G)| ≤ 20), check the degrees of v's five neighbours in G. This determines whether the partial structural proof of the deciding-face conjecture (Theorem~\\ref{thm:deciding-face-partial} in the paper) covers ALL configurations empirically: - If every (G, v) we encounter has at least one neighbour of v with deg_G(·) ≤ 6, the partial proof (which requires at least one n_k ∈ {5, 6}, equivalently at least one u_{k+1} with deg_G ≤ 6) handles every chord-apex+Kempe colouring up to |V(G)| ≤ 20. - If some (G, v) has all five neighbours of degree ≥ 7, we'd need the unproved merged-side argument for those. Reports per n_G: number of (G, v) pairs with all-≥-7 neighbours. Run with: sage experiments/check_v_neighbour_degrees.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) def cyclic_neighbour_degrees(G, v): """Return the cyclic sequence of degrees of v's neighbours, in their CW order around v in G's planar embedding.""" G.is_planar(set_embedding=True) emb = G.get_embedding() return [G.degree(u) for u in emb[v]] def main(max_n=20, time_budget_per_n=1200): print("For every (G, v, i) underlying a chord-apex+Kempe colouring, " "check the\ndegrees of v's neighbours in G.\n") print("Each reduction index i ∈ {0,…,4} picks two consecutive " "neighbours\n(u_{i+1}, u_{i+2}) of v whose degrees become the\n" "two flank-face adjacent G'-face lengths n_i, n_{i+1}.\n") print(f"n_G in [12, {max_n}]\n") grand_triples = 0 # total (G, v, i) triples grand_bad_triples = 0 # both n_i, n_{i+1} ≥ 7 grand_pairs = 0 # (G, v) pairs grand_all_ge_7 = 0 # (G, v) pairs with all 5 neighbours deg ≥ 7 grand_dist_min = {} # min neighbour deg over (G, v) grand_dist_consec = {} # min over consecutive pair (n_i, n_{i+1}) for some i grand_examples = [] 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 triples_n = 0 bad_triples_n = 0 pairs_n = 0 all_ge_7_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 for v in G.vertex_iterator(): if G.degree(v) != 5: continue cyc = cyclic_neighbour_degrees(G, v) pairs_n += 1 m_all = min(cyc) grand_dist_min[m_all] = grand_dist_min.get(m_all, 0) + 1 if m_all >= 7: all_ge_7_n += 1 worst_consec_min = max(min(cyc[i], cyc[(i+1) % 5]) for i in range(5)) grand_dist_consec[worst_consec_min] = ( grand_dist_consec.get(worst_consec_min, 0) + 1) for i in range(5): triples_n += 1 n_i = cyc[(i + 1) % 5] n_ip1 = cyc[(i + 2) % 5] n_ip2 = cyc[(i + 3) % 5] n_ip3 = cyc[(i + 4) % 5] n_ip4 = cyc[(i + 5) % 5] # AUDIT: does the structurally-tight subset (drop n_i=6 case) cover? tight_covered = ( n_i == 5 or n_ip1 == 5 or (n_ip2 == 5 and n_ip4 == 5) ) if not tight_covered: # Record what would be the "audit-uncovered" triples # (= we have proved coverage only on n_i = 5 or n_{i+1} = 5 # or (n_{i+2}, n_{i+4}) = (5, 5)). pass # counted below as "audit_uncovered_n" if n_i >= 7 and n_ip1 >= 7: bad_triples_n += 1 # Check whether F_outer's length condition is OK outer_len = n_ip2 + n_ip4 - 3 outer_ok = (outer_len % 3 != 0) and (n_ip2 == 5 and n_ip4 == 5) if not outer_ok or len(grand_examples) < 10: grand_examples.append({ 'n_G': n, 'tri_idx': tri_idx, 'v': v, 'i': i, 'cyclic_degs': cyc, 'n_i': n_i, 'n_ip1': n_ip1, 'n_ip2': n_ip2, 'n_ip3': n_ip3, 'n_ip4': n_ip4, 'outer_len': outer_len, 'outer_ok': outer_ok, 'graph6': G.canonical_label().graph6_string(), }) elapsed = time.time() - start print(f"n={n}: {pairs_n} (G,v) pairs, {triples_n} (G,v,i) triples; " f"{bad_triples_n} triples with both flank-adj deg ≥ 7 " f"[{elapsed:.0f}s]") sys.stdout.flush() grand_pairs += pairs_n grand_triples += triples_n grand_bad_triples += bad_triples_n grand_all_ge_7 += all_ge_7_n print() print("=" * 70) print(f"Grand totals across n_G in [12, {max_n}]:") print(f" (G, v) pairs: {grand_pairs}") print(f" (G, v, i) triples: {grand_triples}") pct = 100 * grand_all_ge_7 / max(grand_pairs, 1) print(f" pairs with all 5 neighbours deg ≥ 7: " f"{grand_all_ge_7} ({pct:.2f}%)") pct = 100 * grand_bad_triples / max(grand_triples, 1) print(f" triples with BOTH flank-adj deg ≥ 7: " f"{grand_bad_triples} ({pct:.2f}%)") print() print(" Distribution of min(all 5 neighbour degs) per (G, v):") for m in sorted(grand_dist_min): c = grand_dist_min[m] ppct = 100 * c / max(grand_pairs, 1) bar = '#' * max(1, int(ppct)) print(f" {m}: {c:>6} ({ppct:5.2f}%) {bar[:50]}") print() print(" Distribution of MAX over i of " "min(n_i, n_{i+1}) per (G, v)\n" " (i.e., the worst-case consecutive-pair-min --- if this is\n" " ≤ 6, every i has min(n_i, n_{i+1}) ≤ 6 and the partial proof\n" " applies; if it is ≥ 7, at least one i has both flanks ≥ 7):") for m in sorted(grand_dist_consec): c = grand_dist_consec[m] ppct = 100 * c / max(grand_pairs, 1) bar = '#' * max(1, int(ppct)) print(f" {m}: {c:>6} ({ppct:5.2f}%) {bar[:50]}") if grand_examples: print() n_outer_ok = sum(1 for ex in grand_examples if ex['outer_ok']) n_outer_bad = sum(1 for ex in grand_examples if not ex['outer_ok']) print(f" All {len(grand_examples)} (G, v, i) triples with BOTH " f"flank-adj deg ≥ 7:") print(f" {n_outer_ok} have n_{{i+2}}=n_{{i+4}}=5 " f"(F_outer candidate OK)") print(f" {n_outer_bad} do NOT (F_outer might not apply)") print() for ex in grand_examples: tag = "[F_outer OK]" if ex['outer_ok'] else "[F_outer FAILS]" print(f" {tag} n={ex['n_G']}, tri#{ex['tri_idx']}, " f"v={ex['v']}, i={ex['i']}: " f"cyc={ex['cyclic_degs']}, " f"(n_i,n_{{i+1}},n_{{i+2}},n_{{i+3}},n_{{i+4}}) = " f"({ex['n_i']},{ex['n_ip1']},{ex['n_ip2']}," f"{ex['n_ip3']},{ex['n_ip4']}), " f"F_outer len = {ex['outer_len']}") if __name__ == '__main__': main()