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