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