face_monochromatic_pairs: cycle-side splits and shared-vertex transitions
For each chord-apex+Kempe colouring (n in [12, 18]), record:
(1) (#L, #R) split of c-edge sides along K_b and K_c. #L == #R only
in 35.43% of colourings (the rest have unbalanced sides --
consistent with the empirical Heawood non-constancy).
(2) Ordered sequence of (i_b mod 2, i_c mod 2) parity pairs at
shared K_b cap K_c vertices in K_b walk order, plus a tally of
transitions in the 4-state space.
Two clean structural observations on the transition matrix:
(A) i_b parity strictly alternates between consecutive shared
K_b-vertices. Every transition goes (0, *) -> (1, *) or
(1, *) -> (0, *); transitions within (0, *) or within (1, *) are
never observed. So shared positions on K_b alternate even/odd in
walk order -- the gap on K_b between consecutive shared vertices
is always odd.
(B) From odd-i_b states, i_c parity must flip too: (1, 0) only
transitions to (0, 1) and (1, 1) only to (0, 0). From even-i_b
states, both i_c outcomes occur.
(B) is explained structurally: at an odd-i_b shared vertex K_b leaves
via the a-edge (which is also on K_c), so K_b and K_c traverse the
same edge and K_c advances exactly one step, flipping i_c. At an
even-i_b shared vertex K_b leaves via the b-edge (off K_c), so K_c
advances at its own pace and i_c can be either parity at the next
shared vertex.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
"""Two diagnostics:
|
||||
|
||||
(1) Cycle-closure / winding count on K_b and K_c.
|
||||
For each cycle, count the c-edges (off-cycle) at K-vertices that
|
||||
lie on the local LEFT vs local RIGHT side of the walking direction.
|
||||
Under constancy (h constant on V(K_b) U V(K_c)), Lemma A predicts
|
||||
sides alternate, so #LEFT == #RIGHT == |V(K)|/2 exactly. We tally
|
||||
the empirical (#LEFT, #RIGHT) split for K_b and K_c.
|
||||
|
||||
(2) Ordered sequence of (i_b mod 2, i_c mod 2) parity pairs at shared
|
||||
vertices, sorted by i_b position. We look at the sequence of
|
||||
transitions in the 4-state space {(0,0), (0,1), (1,0), (1,1)},
|
||||
and ask whether the empirical sequences are constrained.
|
||||
|
||||
Run with: sage experiments/check_shared_sequence_and_winding.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,
|
||||
)
|
||||
from check_heawood_on_kempe import dual_of, heawood_numbers
|
||||
from check_heawood_local_side import c_edge_local_side
|
||||
|
||||
|
||||
def walk_positions(walk):
|
||||
pos = {}
|
||||
for k, (_, leave_v) in enumerate(walk):
|
||||
if leave_v not in pos:
|
||||
pos[leave_v] = k
|
||||
return pos
|
||||
|
||||
|
||||
def collect_cycle_sides(H, walk, edges, col, emb, third_color):
|
||||
"""For each step k of walk, compute side ('L' or 'R') of the
|
||||
third_color edge at the leave_v vertex relative to the walking
|
||||
direction (in_edge = walk[k], out_edge = walk[k+1]).
|
||||
Returns list of sides indexed by k.
|
||||
"""
|
||||
L = len(walk)
|
||||
sides = []
|
||||
for k in range(L):
|
||||
v = walk[k][1]
|
||||
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]
|
||||
s = c_edge_local_side(v, third_color, col, edges, emb, u_in, u_out)
|
||||
sides.append(s)
|
||||
return sides
|
||||
|
||||
|
||||
def test_one(D):
|
||||
D.is_planar(set_embedding=True)
|
||||
n_col = 0
|
||||
# (1) Side-split distribution: (#L, #R, L_cycle).
|
||||
kb_split = {}; kc_split = {}
|
||||
# (2) Ordered sequence of (i_b mod 2, i_c mod 2) at shared verts in
|
||||
# K_b walk order: sequences (as tuples).
|
||||
seq_dist = {} # signature tuple -> count
|
||||
# Aggregate transition counts between the 4 parity states
|
||||
transitions = {} # (prev_state, curr_state) -> count
|
||||
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]
|
||||
b_color, c_color = bs[0], bs[1]
|
||||
walk_b = trace_kempe_cycle(edges, col, merged_idx, (a, b_color))
|
||||
walk_c = trace_kempe_cycle(edges, col, merged_idx, (a, c_color))
|
||||
pos_b = walk_positions(walk_b)
|
||||
pos_c = walk_positions(walk_c)
|
||||
shared = set(pos_b.keys()) & set(pos_c.keys())
|
||||
# (1)
|
||||
sides_b = collect_cycle_sides(H, walk_b, edges, col, emb, c_color)
|
||||
sides_c = collect_cycle_sides(H, walk_c, edges, col, emb, b_color)
|
||||
Lb_len = len(walk_b); Lc_len = len(walk_c)
|
||||
Lb = sum(1 for s in sides_b if s == 'L')
|
||||
Rb = sum(1 for s in sides_b if s == 'R')
|
||||
Lc = sum(1 for s in sides_c if s == 'L')
|
||||
Rc = sum(1 for s in sides_c if s == 'R')
|
||||
kb_split[(Lb, Rb)] = kb_split.get((Lb, Rb), 0) + 1
|
||||
kc_split[(Lc, Rc)] = kc_split.get((Lc, Rc), 0) + 1
|
||||
# (2)
|
||||
seq = []
|
||||
for k in range(Lb_len):
|
||||
v = walk_b[k][1]
|
||||
if v in shared:
|
||||
seq.append((k % 2, pos_c[v] % 2))
|
||||
# Signature: count occurrences of each state in order.
|
||||
sig = tuple(seq)
|
||||
seq_dist[sig] = seq_dist.get(sig, 0) + 1
|
||||
# Transitions:
|
||||
for i in range(len(seq)):
|
||||
prev = seq[i - 1] # wraps to seq[-1]
|
||||
curr = seq[i]
|
||||
transitions[(prev, curr)] = transitions.get(
|
||||
(prev, curr), 0) + 1
|
||||
return n_col, kb_split, kc_split, seq_dist, transitions
|
||||
|
||||
|
||||
def main(max_n=18, time_budget_per_n=1800):
|
||||
print(f"Cycle-side splits and shared-vertex sequence, "
|
||||
f"n in [12, {max_n}]\n")
|
||||
grand_col = 0
|
||||
grand_kb = {}; grand_kc = {}
|
||||
grand_seq = {}
|
||||
grand_tr = {}
|
||||
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, kb_i, kc_i, seq_i, tr_i = test_one(D)
|
||||
n_col_n += ni
|
||||
for k, v in kb_i.items(): grand_kb[k] = grand_kb.get(k, 0) + v
|
||||
for k, v in kc_i.items(): grand_kc[k] = grand_kc.get(k, 0) + v
|
||||
for k, v in seq_i.items(): grand_seq[k] = grand_seq.get(k, 0) + v
|
||||
for k, v in tr_i.items(): grand_tr[k] = grand_tr.get(k, 0) + v
|
||||
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 K_b (#L, #R) distribution:")
|
||||
for k, v in sorted(grand_kb.items()):
|
||||
print(f" {k}: {v}")
|
||||
print(f"\n K_c (#L, #R) distribution:")
|
||||
for k, v in sorted(grand_kc.items()):
|
||||
print(f" {k}: {v}")
|
||||
# Check: is #L == #R always?
|
||||
kb_equal = sum(v for (L, R), v in grand_kb.items() if L == R)
|
||||
kc_equal = sum(v for (L, R), v in grand_kc.items() if L == R)
|
||||
total_kb = sum(grand_kb.values())
|
||||
total_kc = sum(grand_kc.values())
|
||||
print(f"\n K_b: #L == #R in {kb_equal}/{total_kb} "
|
||||
f"({100*kb_equal/max(1,total_kb):.2f}%)")
|
||||
print(f" K_c: #L == #R in {kc_equal}/{total_kc} "
|
||||
f"({100*kc_equal/max(1,total_kc):.2f}%)")
|
||||
|
||||
print(f"\n Transition counts in the 4-state parity space "
|
||||
f"((prev) -> (curr)):")
|
||||
states = [(0,0), (0,1), (1,0), (1,1)]
|
||||
for s1 in states:
|
||||
for s2 in states:
|
||||
c = grand_tr.get((s1, s2), 0)
|
||||
print(f" {s1} -> {s2}: {c}")
|
||||
|
||||
print(f"\n Top 10 most common shared-sequence signatures (by K_b order):")
|
||||
top = sorted(grand_seq.items(), key=lambda kv: -kv[1])[:10]
|
||||
for sig, c in top:
|
||||
print(f" {sig}: {c}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user