"""For each chord-apex+Kempe colouring, dissect WHERE the constancy hypothesis fails: (1) Is h_phi constant on V(K_b) alone? On V(K_c) alone? On the intersection V(K_b) cap V(K_c)? (2) Per colouring, distribution of (#+1, #-1) shared vertices. (3) For each colouring, identify the "minority Heawood" shared vertices (= those whose h differs from the more frequent value on V(K_b) cup V(K_c)). How many are there? Are they concentrated at specific structural positions (v_n, A_{i+1}, A_{i+3}, A_{i+4})? (4) Is h(v_n) always equal to / always different from the majority? Under the constancy hypothesis, all of these distributions would be trivial (everything same Heawood, 0 minority vertices). Their non-trivial empirical structure exposes where Path 4's "trap" mechanism would have to apply. Run with: sage experiments/check_constancy_obstruction.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_3_8_scaled import ( apply_reduction, proper_3_edge_colorings, matches_chord_apex_kempe, trace_kempe_cycle, edge_idx, kempe_cycle_set, ) from check_heawood_on_kempe import ( dual_of, heawood_numbers, vertices_of_kempe, ) def named_vertices(named, v_n=9999): """Recover A_i, A_{i+1}, ..., A_{i+4}.""" def other(fs, v): return next(iter(fs - {v})) A_i = other(named['side_0'], v_n) A_i1 = other(named['spike'], v_n) A_i2 = other(named['side_1'], v_n) A_i3, A_i4 = sorted(named['merged']) return A_i, A_i1, A_i2, A_i3, A_i4 def test_one(D): D.is_planar(set_embedding=True) n_col = 0 rec = { 'const_kb': 0, 'const_kc': 0, 'const_cap': 0, # (#+1, #-1) on V(K_b) cup V(K_c) histogram 'plus_minus_dist': {}, # # minority Heawood vertices on V(K_b) cup V(K_c) 'minority_count': {}, # Is v_n minority? majority? 'v_n_pos': {'minority': 0, 'majority': 0, 'tie': 0}, # Position-tallies of minority for each "named" vertex 'minority_at': {'v_n': 0, 'A_i': 0, 'A_i1': 0, 'A_i2': 0, 'A_i3': 0, 'A_i4': 0}, # Is h(v_n) == h(A_{i+3}) == h(A_{i+4}) always? 'v_n_eq_merged_endpts': 0, # Is h constant on the 6 named vertices {v_n, A_0..A_4}? 'const_on_named6': 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)] v_n = 9999 A_i, A_i1, A_i2, A_i3, A_i4 = named_vertices(named, v_n) 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] kc_b = kempe_cycle_set(edges, col, merged_idx, (a, bs[0])) kc_c = kempe_cycle_set(edges, col, merged_idx, (a, bs[1])) V_b = vertices_of_kempe(edges, kc_b) V_c = vertices_of_kempe(edges, kc_c) V_union = V_b | V_c V_cap = V_b & V_c h_b_vals = {h[v] for v in V_b} h_c_vals = {h[v] for v in V_c} h_cap_vals = {h[v] for v in V_cap} if len(h_b_vals) == 1: rec['const_kb'] += 1 if len(h_c_vals) == 1: rec['const_kc'] += 1 if len(h_cap_vals) == 1: rec['const_cap'] += 1 plus = sum(1 for v in V_union if h[v] == 1) minus = sum(1 for v in V_union if h[v] == -1) rec['plus_minus_dist'][(plus, minus)] = \ rec['plus_minus_dist'].get((plus, minus), 0) + 1 # Majority sign on union if plus > minus: maj = 1 elif minus > plus: maj = -1 else: maj = 0 minority = sum(1 for v in V_union if h[v] != maj and maj != 0) if maj == 0: minority = min(plus, minus) rec['minority_count'][minority] = \ rec['minority_count'].get(minority, 0) + 1 # v_n's position hv = h.get(v_n) if hv is not None and maj != 0: if hv == maj: rec['v_n_pos']['majority'] += 1 else: rec['v_n_pos']['minority'] += 1 else: rec['v_n_pos']['tie'] += 1 # Named vertices: minority count for name, vv in [('v_n', v_n), ('A_i', A_i), ('A_i1', A_i1), ('A_i2', A_i2), ('A_i3', A_i3), ('A_i4', A_i4)]: if vv in V_union and maj != 0 and h[vv] != maj: rec['minority_at'][name] += 1 # h(v_n) == h(A_{i+3}) == h(A_{i+4}) ? if h.get(v_n) == h.get(A_i3) == h.get(A_i4): rec['v_n_eq_merged_endpts'] += 1 # h constant on the 6 named vertices? named6 = [v_n, A_i, A_i1, A_i2, A_i3, A_i4] named6_vals = {h[vv] for vv in named6 if vv in h} if len(named6_vals) == 1: rec['const_on_named6'] += 1 return n_col, rec def merge_into(g, r): for k in ('const_kb', 'const_kc', 'const_cap', 'v_n_eq_merged_endpts', 'const_on_named6'): g[k] += r[k] for k in ('plus_minus_dist', 'minority_count'): for kk, vv in r[k].items(): g[k][kk] = g[k].get(kk, 0) + vv for sub in ('v_n_pos', 'minority_at'): for kk, vv in r[sub].items(): g[sub][kk] = g[sub].get(kk, 0) + vv def main(max_n=18, time_budget_per_n=1800): print(f"Constancy obstruction analysis, n in [12, {max_n}]\n") grand = { 'const_kb': 0, 'const_kc': 0, 'const_cap': 0, 'plus_minus_dist': {}, 'minority_count': {}, 'v_n_pos': {'minority': 0, 'majority': 0, 'tie': 0}, 'minority_at': {'v_n': 0, 'A_i': 0, 'A_i1': 0, 'A_i2': 0, 'A_i3': 0, 'A_i4': 0}, 'v_n_eq_merged_endpts': 0, 'const_on_named6': 0, } grand_col = 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) ni, ri = test_one(D) n_col_n += ni merge_into(grand, ri) 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 h constant on V(K_b): {grand['const_kb']}/{grand_col}") print(f" h constant on V(K_c): {grand['const_kc']}/{grand_col}") print(f" h constant on V(K_b) cap V(K_c): {grand['const_cap']}/{grand_col}") print(f" h constant on {{v_n, A_0..A_4}}: " f"{grand['const_on_named6']}/{grand_col}") print(f" h(v_n) == h(A_{{i+3}}) == h(A_{{i+4}}): " f"{grand['v_n_eq_merged_endpts']}/{grand_col}") print(f"\n Minority count (= #vertices on V(K_b) cup V(K_c) with " f"non-majority h) distribution:") for k, v in sorted(grand['minority_count'].items()): pct = 100 * v / max(1, grand_col) print(f" {k:>3}: {v} ({pct:.2f}%)") print(f"\n v_n status on V(K_b) cup V(K_c):") print(f" in majority: {grand['v_n_pos']['majority']} " f"({100*grand['v_n_pos']['majority']/max(1,grand_col):.2f}%)") print(f" in minority: {grand['v_n_pos']['minority']} " f"({100*grand['v_n_pos']['minority']/max(1,grand_col):.2f}%)") print(f" tie: {grand['v_n_pos']['tie']} " f"({100*grand['v_n_pos']['tie']/max(1,grand_col):.2f}%)") print(f"\n How often each named vertex is in the minority:") for name, c in grand['minority_at'].items(): print(f" {name:>5}: {c}/{grand_col} " f"({100*c/max(1,grand_col):.2f}%)") if __name__ == '__main__': main()