diff --git a/papers/face_monochromatic_pairs/experiments/check_S_vs_pent_Fk.py b/papers/face_monochromatic_pairs/experiments/check_S_vs_pent_Fk.py new file mode 100644 index 0000000..0ddfe33 --- /dev/null +++ b/papers/face_monochromatic_pairs/experiments/check_S_vs_pent_Fk.py @@ -0,0 +1,170 @@ +"""Empirical investigation: for each chord-apex+Kempe colouring (NOT +restricted to bad ones), correlate |S| = |V \\ (V(K_b) ∪ V(K_c))| +with the number of pentagonal F_k adjacent to F_v in the parent G'. + +Hypothesis (from the |S|=8 hit=8 finding): there's a coupling where +larger |S| forces fewer pentagonal F_k. + +Concretely: for each colouring, record: + - |S| + - # pentagonal F_k (= # of v's 5 neighbours in G with degree 5) + - # G'-pentagons hit by S (when applicable) + +Aggregate the joint distribution. + +Run with: sage experiments/check_S_vs_pent_Fk.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, + kempe_cycle_set, + edge_idx, +) +from check_heawood_on_kempe import dual_of, vertices_of_kempe + + +def cyclic_neighbour_degrees(G, v): + G.is_planar(set_embedding=True) + emb = G.get_embedding() + return [G.degree(u) for u in emb[v]] + + +def test_one(D, G_parent): + D.is_planar(set_embedding=True) + G_parent.is_planar(set_embedding=True) + joint_dist = {} # (|S|, # pent F_k) -> count + for v_parent in G_parent.vertex_iterator(): + if G_parent.degree(v_parent) != 5: continue + cyc_degs = cyclic_neighbour_degrees(G_parent, v_parent) + n_pent_Fk_all = sum(1 for d in cyc_degs if d == 5) + # For each reduction index i (0 to 4), determine the specific + # pentagonal F_k among (F_0, ..., F_4) (= all 5). + # We need to map v_parent + i_red to a specific reduction. + # Use a different approach: enumerate reductions and check. + # For each pentagonal face F_v of D (= matches v_parent), + # iterate i_red. + # Find D-faces matching v_parent's incident G'-faces: + # (this is harder without explicit dual labelling) + pass + + # Alternative approach: iterate over D's faces and reductions + 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 + # Compute n_k from face lengths in reduced dual + n_pent_Fk = 0 + for f in H.faces(): + fset = {frozenset(e) for e in f} + # F_flank_lower = len n_i - 1; if = 4, n_i = 5 (pentagonal). + if named['side_0'] in fset and named['spike'] in fset: + if len(f) == 4: + n_pent_Fk += 1 + if named['spike'] in fset and named['side_1'] in fset: + if len(f) == 4: + n_pent_Fk += 1 + if (named['side_0'] in fset and named['side_1'] in fset + and named['merged'] in fset): + # F_outer = n_{i+2} + n_{i+4} - 3 + # For F_outer = 7 = 5+5-3, both n_{i+2}, n_{i+4} = 5. + # For F_outer = 8 = 5+6-3 or 6+5-3, one = 5. + # Hmm we can't tell which side from outer length alone. + pass + if (named['merged'] in fset and named['side_0'] not in fset + and named['side_1'] not in fset + and named['spike'] not in fset): + # F_merged = n_{i+3} - 2 + if len(f) == 3: + n_pent_Fk += 1 + # (Note: this counts pent F_k in n_i, n_{i+1}, n_{i+3} but + # not in n_{i+2} or n_{i+4} which are mixed by F_outer.) + # For better counting, use the parent graph. + for col in cand: + 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) + S_size = H.order() - len(V_b | V_c) + key = (S_size, n_pent_Fk) + joint_dist[key] = joint_dist.get(key, 0) + 1 + return joint_dist + + +def main(max_n=20, time_budget_per_n=1800): + print("Joint distribution of (|S|, # pentagonal F_k via flank/merged)\n" + "across all chord-apex+Kempe colourings (NOT restricted to bad).\n" + "\nNote: this counts pent F_k via flank-lower (= n_i = 5),\n" + "flank-upper (= n_{i+1} = 5), and merged (= n_{i+3} = 5);\n" + "it does NOT count pent F_k among n_{i+2}, n_{i+4} (= outer-side)\n" + "which can't be distinguished from F_outer length alone.\n") + grand_dist = {} + 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 = 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}") + break + G.is_planar(set_embedding=True) + D = dual_of(G) + jd = test_one(D, G) + for k, v in jd.items(): + grand_dist[k] = grand_dist.get(k, 0) + v + n_col += v + elapsed = time.time() - start + print(f"n={n}: {n_col} colourings counted [{elapsed:.0f}s]") + sys.stdout.flush() + print() + print("=" * 70) + print("Joint distribution (|S|, # pent F_k visible):\n") + # Print as a table + all_S = sorted(set(k[0] for k in grand_dist)) + all_F = sorted(set(k[1] for k in grand_dist)) + print(f" |S|\\#pent_Fk_visible", end=' ') + for f in all_F: + print(f"{f:>7}", end='') + print(f"{'total':>9}") + for s in all_S: + print(f" |S| = {s:>3} ", end=' ') + total = 0 + for f in all_F: + c = grand_dist.get((s, f), 0) + total += c + print(f"{c:>7}", end='') + print(f"{total:>9}") + print(f" total ", end=' ') + for f in all_F: + col_total = sum(grand_dist.get((s, f), 0) for s in all_S) + print(f"{col_total:>7}", end='') + grand_total = sum(grand_dist.values()) + print(f"{grand_total:>9}") + + +if __name__ == '__main__': + main()