face_monochromatic_pairs: investigate (|S|, # pent F_k) joint distribution
experiments/check_S_vs_pent_Fk.py: joint distribution of |S| and
# pentagonal F_k (= count of n_i, n_{i+1}, n_{i+3} = 5, i.e.
"visible" pent F_k via flank/merged faces).
Across all 142,812 chord-apex+Kempe colourings:
- |S| = 0 dominates: 73.9% have full coverage.
- For |S| = 2, 4, 6, 8: distribution of visible pent F_k spans 0-3
with no clean monotone trend.
- |S| = 12, 14 cases NEVER have visible = 0 (= 0 occurrences in
the 0-column for these |S| values).
- The 30 special "|S|=8 hit=8" cases all have full p_G = 11 (= all
5 of v's neighbours degree ≥ 6), not just visible = 0.
So the obvious |S| ↔ # pent F_k coupling doesn't hold uniformly.
The "|S|=8 hit=8 ⇒ p_G = 11" empirical fact is specific to the
conjunction (high |S| + high hit), not to |S| alone.
For a structural proof of "|S|=8 + hit=8 ⇒ p_G = 11", we'd need a
deep Kempe-cycle-structural argument that hitting 8 G'-pentagons
with an 8-vertex S-cycle requires specific local geometry around v.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user