Add tile-overlap probe: per-tile interface subsets always glue
Each tile realises only a subset of the parity-admissible alphabet on its rim, and tiles genuinely omit interfaces (n=12 m=8: max 273/274, min 43). But any two tiles always glue: interface subsets always overlap (n=9 m=3-6, n=12 m=3-8) -- usually via a global universal seam present on every inner+outer rim, and where none exists (n=12 m=7) the worst pair still shares 14 seams. The universal seams are the low-complexity ones (<=2 colours, single contiguous block). No local gluing obstruction; any obstruction must be global across a nested stack. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+131
@@ -0,0 +1,131 @@
|
||||
"""Per-tile interface subsets and gluing overlap.
|
||||
|
||||
The achievable interface alphabet for size m is the full parity-admissible set
|
||||
(see kempe_interface_admissibility_probe.py). But each individual tile realizes
|
||||
only a SUBSET of it on its rim. Gluing tile A inside tile B along an m-seam
|
||||
needs A's outer-rim subset and B's inner-face subset to share a common sequence
|
||||
(census sets are already orientation-closed, so plain set intersection is the
|
||||
right test).
|
||||
|
||||
For each n and interface size m this reports, separately for outer (up-tooth)
|
||||
and inner (down-face) interfaces:
|
||||
* subset-size distribution (how much of the alphabet each tile realizes);
|
||||
* number of distinct subsets (= gluing "signatures");
|
||||
* the universal sequences (in EVERY tile's subset) for up, for down, and the
|
||||
global universal (in every up AND every down subset -- a seam any inner tile
|
||||
can use inside any outer tile);
|
||||
* whether every (inner up-rim, outer down-face) pair overlaps -- i.e. can any
|
||||
two tiles be glued at this seam size -- and the worst (smallest-overlap) pair.
|
||||
|
||||
Summary numbers only.
|
||||
|
||||
Run: python3 kempe_tile_overlap_probe.py --n 9 --m 3 4 5 6
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import statistics
|
||||
import sys
|
||||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from full_medial_tire_generator import generate, innermost_bite
|
||||
from kempe_valid_colorings import classify_colorings
|
||||
from kempe_interface_admissibility_probe import admissible_sequences
|
||||
from kempe_up_tooth_sequences import dihedral_reading_sequences, seq_str
|
||||
|
||||
|
||||
def collect(n: int, m: int):
|
||||
"""Per-tile up-subsets and down-subsets (census sequence sets) for size m."""
|
||||
up_subsets: list[frozenset] = []
|
||||
down_subsets: list[frozenset] = []
|
||||
for g in generate(n, min_up_teeth=3, dedup=True):
|
||||
do_up = len(g.up_edges) == m
|
||||
faces = defaultdict(list)
|
||||
for e in g.singleton_down_edges:
|
||||
faces[innermost_bite(e, g.bites)].append(e)
|
||||
down_faces = [sorted(es) for es in faces.values() if len(es) == m]
|
||||
if not do_up and not down_faces:
|
||||
continue
|
||||
valid = [c for c, v in classify_colorings(g, dedup_colors=True) if v.valid]
|
||||
if do_up:
|
||||
s: set = set()
|
||||
for c in valid:
|
||||
s |= dihedral_reading_sequences(n, c, g.up_edges, "u")
|
||||
up_subsets.append(frozenset(s))
|
||||
for edges in down_faces:
|
||||
s = set()
|
||||
for c in valid:
|
||||
s |= dihedral_reading_sequences(n, c, edges, "d")
|
||||
down_subsets.append(frozenset(s))
|
||||
return up_subsets, down_subsets
|
||||
|
||||
|
||||
def min_pairwise_overlap(ups, downs):
|
||||
"""Smallest |U ∩ D| over all (up, down) pairs, and a witnessing pair size."""
|
||||
worst = None
|
||||
for u in ups:
|
||||
for d in downs:
|
||||
k = len(u & d)
|
||||
if worst is None or k < worst:
|
||||
worst = k
|
||||
if worst == 0:
|
||||
return 0
|
||||
return worst if worst is not None else None
|
||||
|
||||
|
||||
def describe(label, subsets, adm):
|
||||
sizes = sorted(len(s) for s in subsets)
|
||||
universal = frozenset.intersection(*subsets) if subsets else frozenset()
|
||||
n_full = sum(1 for s in subsets if len(s) == len(adm))
|
||||
sigs = len(set(subsets))
|
||||
return {
|
||||
"n": len(subsets),
|
||||
"min": sizes[0] if sizes else 0,
|
||||
"med": int(statistics.median(sizes)) if sizes else 0,
|
||||
"max": sizes[-1] if sizes else 0,
|
||||
"sigs": sigs,
|
||||
"universal": universal,
|
||||
"n_full": n_full,
|
||||
}
|
||||
|
||||
|
||||
def run(args):
|
||||
n = args.n
|
||||
print(f"n={n}: per-tile interface subsets and gluing overlap\n")
|
||||
for m in args.m:
|
||||
t0 = time.time()
|
||||
adm = admissible_sequences(m)
|
||||
ups, downs = collect(n, m)
|
||||
u = describe("up", ups, adm)
|
||||
d = describe("down", downs, adm)
|
||||
gu = (frozenset.intersection(*ups) if ups else frozenset()) & \
|
||||
(frozenset.intersection(*downs) if downs else frozenset())
|
||||
# can any inner tile glue inside any outer tile?
|
||||
if gu:
|
||||
glue = f"yes (global universal {sorted(seq_str(x) for x in gu)})"
|
||||
else:
|
||||
mo = min_pairwise_overlap(ups, downs)
|
||||
glue = f"{'yes' if mo and mo > 0 else 'NO'} (min up×down overlap = {mo})"
|
||||
dt = time.time() - t0
|
||||
print(f"m={m} |adm|={len(adm)}")
|
||||
print(f" up : {u['n']:>4} tiles subset size {u['min']}..{u['med']}..{u['max']}"
|
||||
f" ({u['n_full']} realise all) {u['sigs']} signatures"
|
||||
f" universal={sorted(seq_str(x) for x in u['universal'])}")
|
||||
print(f" down : {d['n']:>4} tiles subset size {d['min']}..{d['med']}..{d['max']}"
|
||||
f" ({d['n_full']} realise all) {d['sigs']} signatures"
|
||||
f" universal={sorted(seq_str(x) for x in d['universal'])}")
|
||||
print(f" glue-any-pair: {glue} ({dt:.0f}s)\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--n", type=int, default=9)
|
||||
parser.add_argument("--m", type=int, nargs="+", default=[3, 4, 5, 6])
|
||||
run(parser.parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user