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:
+144
@@ -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()
|
||||||
+142
@@ -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()
|
||||||
Reference in New Issue
Block a user