face_monochromatic_pairs: instrument K_b cap K_c size and per-cycle flip counts
For each chord-apex+Kempe colouring, record:
- |V(K_b)|, |V(K_c)|, |V(K_b) cap V(K_c)|, |V(K_b) cup V(K_c)|
- "Flip count" on each cycle: #consecutive pairs whose third-colour
edges lie on opposite local sides (= #same-Heawood pairs by Lemma A).
Results (n in [12, 18], 13,800 colourings):
- |V(K_b) cap V(K_c)| is NEVER 2 -- always >= 6. The two Kempe cycles
through merged share many vertices.
- Distributions of flip_Kb and flip_Kc are identical multisets
(consistent with b <-> c symmetry of the construction).
- But per-colouring, flip_Kb == flip_Kc only 39.65% of the time --
the symmetry is statistical, not pointwise.
- Max observed flip count is 20, never the maximum possible |V(K)|.
Consistent with h_phi never being constant on V(K_b) U V(K_c).
The substantial overlap of K_b and K_c (>= 6 shared vertices) means
the constancy hypothesis would impose simultaneous alternation
constraints from both cycles at every shared vertex -- the topological
"trap" the proof needs to exploit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+198
@@ -0,0 +1,198 @@
|
||||
"""For every chord-apex+Kempe colouring of every reduced dual we can
|
||||
enumerate, record:
|
||||
|
||||
(1) |V(K_b)|, |V(K_c)|, |V(K_b) cap V(K_c)|, |V(K_b) cup V(K_c)|.
|
||||
In particular: is |V(K_b) cap V(K_c)| typically 2 (just the
|
||||
merged endpoints) or larger?
|
||||
|
||||
(2) "Side-flip count" on each Kempe cycle: walking K_b in cycle
|
||||
order, count the number of consecutive K_b-pairs (v_0, v_1)
|
||||
where the c-edges at v_0 and v_1 lie on opposite local sides.
|
||||
Same for K_c with b-edges.
|
||||
|
||||
Under Lemma A (now empirically confirmed), the flip count
|
||||
equals the number of "same-Heawood" consecutive pairs on the
|
||||
cycle. So:
|
||||
- constant Heawood on K_b => flip count = |V(K_b)| (max)
|
||||
- perfectly alternating h on K_b => flip count = 0
|
||||
Anything in between corresponds to a Heawood pattern that's
|
||||
neither constant nor perfectly alternating.
|
||||
|
||||
We tally the flip counts for K_b and K_c, and whether they
|
||||
match or differ across the same colouring.
|
||||
|
||||
Run with: sage experiments/check_kempe_intersection_and_alternation.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
|
||||
from check_heawood_local_side import c_edge_local_side
|
||||
|
||||
|
||||
def cycle_data(H, edges, col, emb, merged_idx, a, b):
|
||||
"""Return (V_cycle, side_flips) for the {a, b}-Kempe cycle through
|
||||
the edge at merged_idx. side_flips counts consecutive pairs whose
|
||||
third-colour edges lie on opposite local sides (= #{same-h pairs}
|
||||
via Lemma A)."""
|
||||
walk = trace_kempe_cycle(edges, col, merged_idx, (a, b))
|
||||
if not walk:
|
||||
return set(), 0
|
||||
L = len(walk)
|
||||
third_color = 3 - a - b
|
||||
sides = []
|
||||
V_cycle = set()
|
||||
for k in range(L):
|
||||
v = walk[k][1]
|
||||
V_cycle.add(v)
|
||||
in_e = edges[walk[k][0]]
|
||||
out_e = edges[walk[(k + 1) % L][0]]
|
||||
u_in = in_e[0] if in_e[1] == v else in_e[1]
|
||||
u_out = out_e[0] if out_e[1] == v else out_e[1]
|
||||
side = c_edge_local_side(v, third_color, col, edges, emb,
|
||||
u_in, u_out)
|
||||
sides.append(side)
|
||||
flips = 0
|
||||
for k in range(L):
|
||||
s0 = sides[k]; s1 = sides[(k + 1) % L]
|
||||
if s0 is not None and s1 is not None and s0 != s1:
|
||||
flips += 1
|
||||
return V_cycle, flips
|
||||
|
||||
|
||||
def test_one(D):
|
||||
D.is_planar(set_embedding=True)
|
||||
n_col = 0
|
||||
rec = {
|
||||
'cap_size': {}, # |V(K_b) cap V(K_c)| histogram
|
||||
'cap_eq_2': 0, # how often the intersection is exactly 2
|
||||
'cap_size_minus_2': {},# |V(K_b) cap V(K_c)| - 2 histogram
|
||||
'kb_size': {},
|
||||
'kc_size': {},
|
||||
'union_size': {},
|
||||
'flip_kb': {}, # K_b side-flip count
|
||||
'flip_kc': {}, # K_c side-flip count
|
||||
'flip_match': 0, # how often flip_Kb == flip_Kc
|
||||
'flip_match_pct_bucket': {}, # bucket of |fkb - fkc| / max
|
||||
}
|
||||
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)
|
||||
emb = H.get_embedding()
|
||||
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
|
||||
merged_idx = edge_idx(edges, named['merged'])
|
||||
a = col[merged_idx]
|
||||
bs = [c for c in range(3) if c != a]
|
||||
Vb, fb = cycle_data(H, edges, col, emb, merged_idx, a, bs[0])
|
||||
Vc, fc = cycle_data(H, edges, col, emb, merged_idx, a, bs[1])
|
||||
cap = Vb & Vc
|
||||
un = Vb | Vc
|
||||
rec['cap_size'][len(cap)] = rec['cap_size'].get(len(cap), 0) + 1
|
||||
rec['cap_size_minus_2'][len(cap) - 2] = \
|
||||
rec['cap_size_minus_2'].get(len(cap) - 2, 0) + 1
|
||||
if len(cap) == 2:
|
||||
rec['cap_eq_2'] += 1
|
||||
rec['kb_size'][len(Vb)] = rec['kb_size'].get(len(Vb), 0) + 1
|
||||
rec['kc_size'][len(Vc)] = rec['kc_size'].get(len(Vc), 0) + 1
|
||||
rec['union_size'][len(un)] = rec['union_size'].get(len(un), 0) + 1
|
||||
rec['flip_kb'][fb] = rec['flip_kb'].get(fb, 0) + 1
|
||||
rec['flip_kc'][fc] = rec['flip_kc'].get(fc, 0) + 1
|
||||
if fb == fc:
|
||||
rec['flip_match'] += 1
|
||||
return n_col, rec
|
||||
|
||||
|
||||
def merge_into(grand, n_rec):
|
||||
for key in ('cap_size', 'cap_size_minus_2', 'kb_size', 'kc_size',
|
||||
'union_size', 'flip_kb', 'flip_kc'):
|
||||
for k, v in n_rec[key].items():
|
||||
grand[key][k] = grand[key].get(k, 0) + v
|
||||
grand['cap_eq_2'] += n_rec['cap_eq_2']
|
||||
grand['flip_match'] += n_rec['flip_match']
|
||||
|
||||
|
||||
def main(max_n=18, time_budget_per_n=1800):
|
||||
print(f"K_b/K_c intersection sizes and side-flip counts, "
|
||||
f"n in [12, {max_n}]\n")
|
||||
grand = {
|
||||
'cap_size': {}, 'cap_size_minus_2': {}, 'cap_eq_2': 0,
|
||||
'kb_size': {}, 'kc_size': {}, 'union_size': {},
|
||||
'flip_kb': {}, 'flip_kc': {}, 'flip_match': 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
|
||||
n_rec = {
|
||||
'cap_size': {}, 'cap_size_minus_2': {}, 'cap_eq_2': 0,
|
||||
'kb_size': {}, 'kc_size': {}, 'union_size': {},
|
||||
'flip_kb': {}, 'flip_kc': {}, 'flip_match': 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(n_rec, ri)
|
||||
elapsed = time.time() - start
|
||||
print(f"n={n}: {n_col_n} col., [{elapsed:.0f}s]")
|
||||
print(f" |V(K_b) cap V(K_c)| dist: {sorted(n_rec['cap_size'].items())}")
|
||||
print(f" cap == 2: {n_rec['cap_eq_2']}/{n_col_n}")
|
||||
print(f" flip_kb dist: {sorted(n_rec['flip_kb'].items())}")
|
||||
print(f" flip_kc dist: {sorted(n_rec['flip_kc'].items())}")
|
||||
print(f" flip_kb == flip_kc: {n_rec['flip_match']}/{n_col_n}")
|
||||
sys.stdout.flush()
|
||||
grand_col += n_col_n
|
||||
merge_into(grand, n_rec)
|
||||
|
||||
print()
|
||||
print("=" * 78)
|
||||
print(f"Grand totals (n in [12, {max_n}], {grand_col} colourings):")
|
||||
print(f" |V(K_b) cap V(K_c)| distribution: "
|
||||
f"{sorted(grand['cap_size'].items())}")
|
||||
cap_2 = grand['cap_eq_2']
|
||||
print(f" |V(K_b) cap V(K_c)| == 2: {cap_2}/{grand_col} "
|
||||
f"({100*cap_2/max(1,grand_col):.2f}%)")
|
||||
print(f" |V(K_b)| dist: {sorted(grand['kb_size'].items())}")
|
||||
print(f" |V(K_c)| dist: {sorted(grand['kc_size'].items())}")
|
||||
print(f" |V(K_b) cup V(K_c)| dist: {sorted(grand['union_size'].items())}")
|
||||
print(f" flip_kb dist: {sorted(grand['flip_kb'].items())}")
|
||||
print(f" flip_kc dist: {sorted(grand['flip_kc'].items())}")
|
||||
fm = grand['flip_match']
|
||||
print(f" flip_kb == flip_kc: {fm}/{grand_col} "
|
||||
f"({100*fm/max(1,grand_col):.2f}%)")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user