Add transfer-relation & uniform-family probes (chained-seam / pigeonhole)

Pursue the paper's medial pigeonhole programme (R_T restriction relation,
chain-pigeonhole conjecture) at the data level.

Findings: R_T (outer<->inner boundary necklace, one Kempe-balanced colouring)
is genuinely coupled, not a product of its projections. A uniform per-size
boundary-state family threading every tile EXISTS at n=9 (unique per size, the
balanced-block necklaces 0011/000011/012/00012 -- not monochromatic), but FAILS
at n=12: size-7 seams admit no universal state (|D[7]|=0; near-universal 0001112
realised on 210/211 boundaries, blocked by one tile). So the uniform "same state
everywhere" shortcut breaks once large odd seams appear and universals vanish as
the tile population grows; the per-interface pigeonhole choice is genuinely
needed. Pairwise gluability still holds, so this locates the conjecture's
difficulty rather than obstructing gluing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 23:16:27 -04:00
parent aecbc5ed28
commit b656b6aed3
2 changed files with 286 additions and 0 deletions
@@ -0,0 +1,144 @@
"""Transfer relation R_T and the chained-seam (medial pigeonhole) question.
Each tire carries a restriction relation R_T between its OUTER boundary state
(up-tooth apexes) and its INNER boundary state (singleton down-tooth apexes on
its single inner face), realised jointly by one Kempe-balanced 3-colouring.
Boundary states are taken up to colour permutation AND dihedral symmetry of the
boundary walk (paper Def "medial boundary state") -- i.e. as necklaces.
A nested chain T_0 ⊃ T_1 ⊃ ... glues iff consecutive boundary states match:
inner-state(T_i) = outer-state(T_{i+1}). The chain-pigeonhole conjecture asks
whether such a chain can ever be obstructed.
We restrict to no-bite tiles (single inner face = root, all down teeth
singletons), so outer size p = #up and inner size q = n - p, and study:
1. R_T coupling: is R_T a full product proj_out × proj_in, or does the inner
state genuinely constrain the outer state?
2. Common(p,q): boundary-state pairs realised by EVERY tile of that size class
-- a pair here can be used uniformly regardless of which tile sits there.
3. Uniform pass-through: is there a choice of one state sigma_m per level size m
with (sigma_p, sigma_q) in Common(p,q) for every size class? If so, putting
sigma_m on every level cycle glues ANY such stack with no pigeonhole needed.
Summary numbers only.
Run: python3 kempe_transfer_relation_probe.py --n 9
"""
from __future__ import annotations
import argparse
import itertools
from collections import defaultdict
from full_medial_tire_generator import generate
from kempe_valid_colorings import classify_colorings
from kempe_up_tooth_sequences import canonical_sequence, seq_str
def necklace(seq: tuple[int, ...]) -> tuple[int, ...]:
"""Boundary state: canonical under rotation, reflection, and colour perm."""
n = len(seq)
return min(canonical_sequence(s[r:] + s[:r])
for s in (seq, seq[::-1]) for r in range(n))
def admissible_necklaces(m: int) -> set[tuple[int, ...]]:
out = set()
for combo in itertools.product((0, 1, 2), repeat=m):
if all(combo.count(k) % 2 == m % 2 for k in (0, 1, 2)):
out.add(necklace(combo))
return out
def cuts(neck: tuple[int, ...]) -> int:
"""Cyclic colour-changes -- low = 'blocky', easy-to-present boundary."""
return sum(1 for i in range(len(neck)) if neck[i] != neck[(i + 1) % len(neck)])
def tile_relation(n, g):
"""R_T as a set of (outer-necklace, inner-necklace) pairs for a no-bite tile.
Returns None unless the tile has p>=3 up teeth and q>=3 singleton downs."""
p = len(g.up_edges)
downs = g.singleton_down_edges
if g.bites or p < 3 or len(downs) < 3:
return None
R = set()
for c, v in classify_colorings(g, dedup_colors=True):
if not v.valid:
continue
o = necklace(tuple(c[f"u{e}"] for e in g.up_edges))
i = necklace(tuple(c[f"d{e}"] for e in downs))
R.add((o, i))
return (p, len(downs)), R
def run(args):
n = args.n
by_class: dict[tuple[int, int], list[set]] = defaultdict(list)
for g in generate(n, min_up_teeth=3, dedup=True):
res = tile_relation(n, g)
if res is None:
continue
(p, q), R = res
by_class[(p, q)].append(R)
print(f"n={n}: transfer relation R_T over (outer, inner) boundary necklaces "
f"(no-bite tiles)\n")
print(f"{'(p,q)':>7} {'#tiles':>6} {'|adm_p|':>7} {'|adm_q|':>7} "
f"{'out real':>8} {'in real':>7} {'product?':>8} {'|Common|':>8}")
print("-" * 70)
common: dict[tuple[int, int], set] = {}
for (p, q) in sorted(by_class):
rels = by_class[(p, q)]
adm_p, adm_q = len(admissible_necklaces(p)), len(admissible_necklaces(q))
proj_out = set().union(*[{o for o, _ in R} for R in rels])
proj_in = set().union(*[{i for _, i in R} for R in rels])
# is every tile's R_T a full product of its own projections?
all_product = all(
R == {(o, i) for o in {a for a, _ in R} for i in {b for _, b in R}}
for R in rels
)
com = set.intersection(*rels) if rels else set()
common[(p, q)] = com
print(f"{str((p, q)):>7} {len(rels):>6} {adm_p:>7} {adm_q:>7} "
f"{len(proj_out):>8} {len(proj_in):>7} {str(all_product):>8} {len(com):>8}")
# Uniform pass-through CSP: pick sigma_m per size so (sigma_p, sigma_q) in
# Common(p,q) for every class. Brute force over candidate states per size.
print("\nUniform pass-through (one state per level size, glues any stack):")
sizes = sorted({s for cls in common for s in cls})
cand = {m: sorted({o for (o, _) in common.get((m, n - m), set())} |
{i for (_, i) in common.get((n - m, m), set())} |
admissible_necklaces(m))
for m in sizes}
# constraints: for each class (p,q) present, (sigma_p,sigma_q) in Common(p,q)
classes = [(p, q) for (p, q) in common if common[(p, q)]]
empty_classes = [c for c in common if not common[c]]
if empty_classes:
print(f" classes with EMPTY Common (no pair works for all tiles): {empty_classes}")
found = None
for assignment in itertools.product(*[cand[m] for m in sizes]):
sigma = dict(zip(sizes, assignment))
if all((sigma[p], sigma[q]) in common[(p, q)] for (p, q) in classes):
found = sigma
break
if found:
print(" FEASIBLE -- uniform family exists:")
for m in sizes:
print(f" size {m}: {seq_str(found[m])} (cuts={cuts(found[m])})")
print(" => every no-bite nested stack at this n glues with no pigeonhole.")
else:
print(" INFEASIBLE -- no single state-per-size family glues all tiles;")
print(" the uniform strategy fails, so chaining genuinely couples.")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--n", type=int, default=9)
run(parser.parse_args())
if __name__ == "__main__":
main()
@@ -0,0 +1,142 @@
"""Is there a uniform boundary-state family threading every tile (branches too)?
A *uniform family* assigns one boundary state sigma_m (a necklace) to every
level-cycle size m. A tile is *threaded* if one Kempe-balanced 3-colouring shows
sigma_p on its outer face (p up teeth) AND sigma_{q_k} on every inner face k (each
a >=3-singleton-down face). If a family threads every tile, assigning sigma_m to
every level cycle glues ANY tire tree -- including branching (bite) nodes -- with
no pigeonhole. Infeasibility means the medial pigeonhole is genuinely needed.
This is a constraint-satisfaction search:
* domain D[m] = necklaces realisable on EVERY size-m boundary (outer or inner)
across all tiles -- the per-size universal states;
* per-tile constraint = some Kempe-balanced colouring realises the chosen
sigma on its outer face and all its inner faces simultaneously.
Reports whether a uniform family exists and, if so, one witness.
Run: python3 kempe_uniform_family_probe.py --n 9
"""
from __future__ import annotations
import argparse
from collections import defaultdict
from full_medial_tire_generator import generate, innermost_bite
from kempe_valid_colorings import classify_colorings
from kempe_transfer_relation_probe import necklace, cuts
from kempe_up_tooth_sequences import seq_str
def inner_faces(g):
faces = defaultdict(list)
for e in g.singleton_down_edges:
faces[innermost_bite(e, g.bites)].append(e)
return [sorted(es) for es in faces.values() if len(es) >= 3]
class Tile:
__slots__ = ("p", "qsizes", "joint", "is_bite", "word", "bites")
def __init__(self, g):
faces = inner_faces(g)
self.p = len(g.up_edges)
self.qsizes = [len(f) for f in faces]
self.is_bite = bool(g.bites)
self.word = g.tooth_word
self.bites = ",".join(f"({i},{j})" for i, j in sorted(g.bites)) or "-"
# realisable joint signatures: (outer_neck, (inner_neck_by_face...))
self.joint = set()
for c, v in classify_colorings(g, dedup_colors=True):
if not v.valid:
continue
o = necklace(tuple(c[f"u{e}"] for e in g.up_edges))
ins = tuple(necklace(tuple(c[f"d{e}"] for e in f)) for f in faces)
self.joint.add((o, ins))
def run(args):
n = args.n
tiles = []
realizable = defaultdict(lambda: defaultdict(set)) # size -> "marginal" sets list
per_size_real = defaultdict(list) # size -> list of realizable-necklace sets
for g in generate(n, min_up_teeth=3, dedup=True):
if not inner_faces(g):
continue
t = Tile(g)
tiles.append(t)
# marginal realisable sets per boundary (for domain restriction)
outs = {o for o, _ in t.joint}
per_size_real[t.p].append(outs)
for k, q in enumerate(t.qsizes):
ins_k = {ins[k] for _, ins in t.joint}
per_size_real[q].append(ins_k)
# D[m] = necklaces realisable on EVERY size-m boundary (per-size universal)
D = {m: set.intersection(*sets) for m, sets in per_size_real.items()}
sizes = sorted(D)
n_bite = sum(1 for t in tiles if t.is_bite)
n_branch = sum(1 for t in tiles if len(t.qsizes) >= 2)
print(f"n={n}: uniform-family CSP over {len(tiles)} tiles "
f"({n_bite} bite, {n_branch} branching with >=2 inner faces)\n")
print("per-size universal domain |D[m]|:",
" ".join(f"{m}:{len(D[m])}" for m in sizes))
# Backtracking search over sigma_m in D[m], with per-tile joint constraints.
order = sizes
sigma: dict[int, tuple] = {}
def tile_ok(t):
# all sizes the tile touches must be assigned to test it
if t.p not in sigma or any(q not in sigma for q in t.qsizes):
return True # not yet fully constrained
want = (sigma[t.p], tuple(sigma[q] for q in t.qsizes))
return want in t.joint
def consistent():
return all(tile_ok(t) for t in tiles)
def bt(idx):
if idx == len(order):
return dict(sigma)
m = order[idx]
for s in sorted(D[m]):
sigma[m] = s
if consistent():
res = bt(idx + 1)
if res is not None:
return res
del sigma[m]
return None
sol = bt(0)
if sol:
print("\n=> FEASIBLE: a uniform family threads every tile (branches included).")
for m in sizes:
print(f" size {m}: {seq_str(sol[m])} (cuts={cuts(sol[m])})")
print(" Assigning sigma_m to every level cycle glues any tire tree at "
"this n with no pigeonhole.")
else:
# diagnose: which tiles can't be threaded by any domain choice
print("\n=> INFEASIBLE: no uniform family threads all tiles.")
# find tiles whose joint set never matches the per-size domains
bad = []
for t in tiles:
if not any(o in D.get(t.p, set()) and
all(ins[k] in D.get(t.qsizes[k], set()) for k in range(len(t.qsizes)))
for o, ins in t.joint):
bad.append(t)
print(f" {len(bad)} tile(s) cannot use any per-size universal jointly:")
for t in bad[:20]:
print(f" word={t.word} bites={t.bites} outer={t.p} inner={t.qsizes}")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--n", type=int, default=9)
run(parser.parse_args())
if __name__ == "__main__":
main()