Add Heawood boundary-restriction experiments and findings note

Experiments probing the cluster restriction set R_K / Phi: R_K is a Z/3
zonotope (not a GF(3) subspace), the "richness" invariant is an artifact
of non-shrinking annuli, the interface gluing always works on interior
cycles (forced by 4CT), and the maximal constraint achievable on an
n-cycle is a floor of 2^(n-2) -- already reached by the trivial tire.
Note boundary_restriction_structure.tex writes these up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 02:12:54 -04:00
parent 351ae0cdfe
commit 60c9f1d3a8
8 changed files with 1087 additions and 0 deletions
@@ -0,0 +1,182 @@
"""
Does the richness invariant survive BRANCHING?
For a separating cycle C bounding a disk G_C (away from the source), the achievable
outer-interface set is
Phi(C) = { (lambda*(v))_{v in C} : lambda in {+1,-1}^{F(G_C)},
sum_{f ∋ w} lambda(f) ≡ 0 for every
truly-interior vertex w of G_C }.
This is the exact value the recursive transfer operator produces at C (interior
consistency = all the descendant gluings already performed; seam/boundary vertices
are deferred, exactly as in the recursion). We compute Phi(C) by constrained
enumeration over real triangulations and test the candidate self-similar invariant
non-empty & closed under sign flip & full single-position marginals
separately at BRANCH nodes (region encloses >=2 disjoint deeper sub-tires) and at
LINEAR nodes (one child), to see whether branching breaks it.
"""
import sys
from collections import defaultdict, deque
from itertools import product
import numpy as np
from scipy.spatial import Delaunay
def delaunay(n, rng):
pts = rng.random((n, 2))
tri = Delaunay(pts)
faces = [tuple(int(x) for x in s) for s in tri.simplices]
hull = set(int(v) for e in tri.convex_hull for v in e)
return faces, hull
def build(faces):
adj = defaultdict(set)
efaces = defaultdict(list)
vfaces = defaultdict(list)
for fi, (a, b, c) in enumerate(faces):
adj[a] |= {b, c}; adj[b] |= {a, c}; adj[c] |= {a, b}
for e in ((a, b), (b, c), (a, c)):
efaces[frozenset(e)].append(fi)
for v in (a, b, c):
vfaces[v].append(fi)
fadj = [set() for _ in faces]
for fl in efaces.values():
for i in fl:
for j in fl:
if i != j:
fadj[i].add(j)
return adj, fadj, vfaces
def bfs(adj, src):
lev = {src: 0}; q = deque([src])
while q:
u = q.popleft()
for w in adj[u]:
if w not in lev:
lev[w] = lev[u] + 1; q.append(w)
return lev
def components(face_ids, fadj):
idset = set(face_ids)
seen = set(); comps = []
for s in face_ids:
if s in seen:
continue
comp = []; stack = [s]; seen.add(s)
while stack:
u = stack.pop(); comp.append(u)
for w in fadj[u]:
if w in idset and w not in seen:
seen.add(w); stack.append(w)
comps.append(comp)
return comps
def sign_closed(S):
return all(tuple((3 - x) % 3 for x in s) in S for s in S)
def marginals_full(S, k):
return all({s[i] for s in S} == {0, 1, 2} for i in range(k))
def phi_of_region(comp_faces, faces, vfaces, lev, d, cap):
"""Constrained-enumeration Phi on the outer (level-d) cycle of a region."""
Gc = comp_faces
if not (1 <= len(Gc) <= cap):
return None
Gcset = set(Gc)
verts = sorted(set(v for fi in Gc for v in faces[fi]))
# truly-interior: every global incident face is inside G_C (=> level > d)
interior = [v for v in verts if all(f in Gcset for f in vfaces[v])]
boundary_C = [v for v in verts if lev[v] == d and v not in interior]
if not boundary_C:
return None
F = len(Gc)
fidx = {fi: j for j, fi in enumerate(Gc)}
# incidence rows
Bint = np.zeros((len(interior), F), dtype=np.int64)
for r, w in enumerate(interior):
for fi in vfaces[w]:
if fi in Gcset:
Bint[r, fidx[fi]] = 1
Cinc = np.zeros((len(boundary_C), F), dtype=np.int64)
for r, v in enumerate(boundary_C):
for fi in vfaces[v]:
if fi in Gcset:
Cinc[r, fidx[fi]] = 1
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
if len(interior):
ok = np.all((labs @ Bint.T) % 3 == 0, axis=1)
labs = labs[ok]
if labs.shape[0] == 0:
return set(), len(boundary_C)
outer = (labs @ Cinc.T) % 3
return set(map(tuple, np.unique(outer, axis=0))), len(boundary_C)
def main():
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 0
nprng = np.random.default_rng(seed)
CAP = 18
stats = {True: [0, 0, 0, 0], False: [0, 0, 0, 0]} # branch: [n, nonempty, sign, marg]
examples_fail = []
for _ in range(300):
faces, hull = delaunay(int(nprng.integers(14, 34)), nprng)
adj, fadj, vfaces = build(faces)
lev = bfs(adj, min(hull))
if len(lev) != len(adj):
continue
depth = [min(lev[v] for v in faces[fi]) for fi in range(len(faces))]
maxd = max(depth)
for d in range(1, maxd + 1):
fge = [fi for fi in range(len(faces)) if depth[fi] >= d]
for comp in components(fge, fadj):
if not (1 <= len(comp) <= CAP):
continue
deeper = [fi for fi in comp if depth[fi] >= d + 1]
n_children = len(components(deeper, fadj))
is_branch = n_children >= 2
res = phi_of_region(comp, faces, vfaces, lev, d, CAP)
if res is None:
continue
S, k = res
rec = stats[is_branch]
rec[0] += 1
rec[1] += bool(S)
rec[2] += (bool(S) and sign_closed(S))
rec[3] += (bool(S) and marginals_full(S, k))
if S and not marginals_full(S, k) and len(examples_fail) < 6:
examples_fail.append((is_branch, n_children, len(comp), k,
len(S)))
for branch in (False, True):
n, ne, sg, mg = stats[branch]
tag = "BRANCH (>=2 children)" if branch else "LINEAR (1 child)"
if n:
print(f"{tag}: {n} regions")
print(f" non-empty : {ne}/{n} ({100*ne/n:.1f}%)")
print(f" sign-closed : {sg}/{n} ({100*sg/n:.1f}%)")
print(f" marginals-full : {mg}/{n} ({100*mg/n:.1f}%)")
else:
print(f"{tag}: 0 regions")
if examples_fail:
print("\n marginals-NOT-full examples (branch?,n_children,|G_C|,|C|,|Phi|):")
for e in examples_fail:
print(f" {e}")
else:
print("\n richness (incl. full marginals) held on every region tested.")
if __name__ == "__main__":
main()
@@ -0,0 +1,167 @@
"""
Construct the triangulated disk (= nested tire substructure) that MAXIMALLY
constrains its outer cycle.
For a triangulated disk D with boundary cycle C = (0..n-1), the achievable outer
Heawood set is
Phi(D) = { (lambda*(v))_{v in C} : lambda in {+1,-1}^{faces},
sum_{f ∋ w} lambda(f) ≡ 0 for every interior vertex w } .
Phi depends only on the disk triangulation (no BFS/tree needed). We want the disk
minimising |Phi| -- the worst case for the pigeonhole. Note Phi is always
sign-closed and non-empty, so |Phi| >= 1, and |Phi| = 1 forces Phi = { all-zeros }.
Key local fact: a degree-3 interior vertex (one Apollonian stack) has incident
faces f1,f2,f3 with lambda(f1)+lambda(f2)+lambda(f3) ≡ 0 mod 3 over +/-1 values,
which forces f1=f2=f3. So stacking chains equalities and collapses Phi.
We (a) randomly search disks built by Apollonian stacking, and (b) try a
deterministic deep-stack construction, reporting the smallest Phi found.
"""
import random
import sys
from itertools import product
import numpy as np
def fan_triangulation(n):
"""n-gon (0..n-1) triangulated as a fan from vertex 0. No interior vertex."""
return [(0, i, i + 1) for i in range(1, n - 1)]
def stack(faces, idx, v):
a, b, c = faces[idx]
faces[idx] = (a, b, v)
faces.append((b, c, v))
faces.append((a, c, v))
def phi(faces, n, cap):
"""Phi on boundary 0..n-1; interior = vertices >= n."""
verts = set(v for f in faces for v in f)
interior = sorted(v for v in verts if v >= n)
F = len(faces)
if F > cap:
return None
# incidence
Bint = np.zeros((len(interior), F), dtype=np.int64)
iindex = {w: r for r, w in enumerate(interior)}
Cinc = np.zeros((n, F), dtype=np.int64)
for j, (a, b, c) in enumerate(faces):
for v in (a, b, c):
if v >= n:
Bint[iindex[v], j] = 1
else:
Cinc[v, j] = 1
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
if len(interior):
keep = np.all((labs @ Bint.T) % 3 == 0, axis=1)
labs = labs[keep]
if labs.shape[0] == 0:
return set()
outer = (labs @ Cinc.T) % 3
return set(map(tuple, np.unique(outer, axis=0)))
def disp(s):
return tuple(-1 if int(x) == 2 else int(x) for x in s)
def gf3_rank(rows):
M = [[int(x) % 3 for x in r] for r in rows]
if not M:
return 0
nc = len(M[0]); r = 0
for c in range(nc):
piv = next((i for i in range(r, len(M)) if M[i][c] % 3), None)
if piv is None:
continue
M[r], M[piv] = M[piv], M[r]
inv = M[r][c] % 3
M[r] = [(x * inv) % 3 for x in M[r]]
for i in range(len(M)):
if i != r and M[i][c] % 3:
fct = M[i][c] % 3
M[i] = [(M[i][k] - fct * M[r][k]) % 3 for k in range(nc)]
r += 1
if r == len(M):
break
return r
def describe(P):
P = list(P)
sign_closed = all(tuple((3 - x) % 3 for x in s) in set(P) for s in P)
s0 = P[0]
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in P]
rank = gf3_rank(D)
affine = (len(P) == 3 ** rank)
pow2 = (len(P) & (len(P) - 1)) == 0
return (f"sign-closed={sign_closed} affine-GF3={affine} "
f"|Phi|={len(P)} (power-of-2={pow2}) hull-dim={rank}")
def random_disk(n, n_stacks, rng):
faces = fan_triangulation(n)
nxt = n
for _ in range(n_stacks):
stack(faces, rng.randrange(len(faces)), nxt)
nxt += 1
return faces
def deep_stack_disk(n, n_stacks):
"""Always stack into the most-recently created face -> deep equality chain."""
faces = fan_triangulation(n)
nxt = n
for _ in range(n_stacks):
stack(faces, len(faces) - 1, nxt)
nxt += 1
return faces
def search(n, cap=18, trials=400, seed=0):
rng = random.Random(seed)
best = (10 ** 9, None, None)
max_stacks = (cap - (n - 2)) // 2
# random search
for _ in range(trials):
k = rng.randint(0, max_stacks)
faces = random_disk(n, k, rng)
P = phi(faces, n, cap)
if P is None:
continue
if len(P) < best[0]:
best = (len(P), k, P)
# deterministic deep stack at max depth
for k in range(max_stacks + 1):
faces = deep_stack_disk(n, k)
P = phi(faces, n, cap)
if P is not None and len(P) < best[0]:
best = (len(P), k, P)
size, k, P = best
print(f"n={n}: min |Phi| = {size} (= 2^(n-2) = {2**(n-2)}?) "
f"interior vertices = {k}, max stacks at cap {cap} = {max_stacks}")
print(f" {describe(P)}")
for s in sorted(P)[:6]:
print(f" {disp(s)}")
if len(P) > 6:
print(f" ... (+{len(P)-6} more)")
return size
def main():
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6, 7]
print("Searching for maximally-constraining disks (min |Phi|)\n")
for n in ns:
# bigger cap for small n
cap = 18 if n <= 6 else 16
search(n, cap=cap)
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,129 @@
"""
Search for a UNIVERSAL Heawood boundary sequence for a tire graph.
Fix an outer boundary cycle B_out of length n (the interface at which a tire
glues to its parent). Each way of filling the annulus -- an inner boundary of
size m together with a spoke triangulation ("inner graph") -- gives a tire whose
annular faces induce a set of realisable outer Heawood sequences
R_out(tire) = { (lambda*(v0), ..., lambda*(v_{n-1})) : lambda in {+1,-1}^F }
{0,1,-1}^n .
A *universal sequence* for B_out is one realisable for EVERY inner graph, i.e. a
member of the intersection ∩_tire R_out(tire). If a universal sequence existed,
a parent could always present its negation and glue to any child regardless of
the child's interior.
Note: chords of the inner outerplanar graph O lie inside B_in and bound no
annular face, so they do not change R_out -- only (n, m, spoke-path) do. And
intersecting over a SUBFAMILY of inner graphs can only OVERestimate the true
intersection, so finding the intersection empty over simple-cycle inner fills is
already conclusive that NO universal sequence exists.
"""
import sys
from itertools import combinations, product
import numpy as np
def lattice_paths(n_outer, m_inner):
"""All spoke triangulations: strings with n_outer 'O' moves, m_inner 'I'."""
N = n_outer + m_inner
for opos in combinations(range(N), n_outer):
opos = set(opos)
yield "".join("O" if i in opos else "I" for i in range(N))
def annular_faces(n, m, path):
"""Faces (triangles) of the annulus between outer n-cycle (0..n-1) and inner
m-cycle (n..n+m-1) under the spoke path. Starts at spoke (outer0, inner0)."""
faces = []
i = j = 0
for mv in path:
if mv == "O":
faces.append((i % n, (i + 1) % n, n + (j % m)))
i += 1
else:
faces.append((i % n, n + (j % m), n + ((j + 1) % m)))
j += 1
return faces
def fan_faces(n):
"""m = 1 degenerate inner boundary: a wheel/fan, center = vertex n."""
return [(i, (i + 1) % n, n) for i in range(n)]
def realisable_outer(n, faces):
"""Set of outer Heawood sequences over all +/-1 face labellings."""
F = len(faces)
A = np.zeros((n, F), dtype=np.int64) # outer-vertex x face incidence
for f, tri in enumerate(faces):
for v in tri:
if v < n:
A[v, f] = 1
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
vals = (labs @ A.T) % 3
# display residues in {0, 1, -1}: 2 -> -1
vals = np.where(vals == 2, -1, vals)
return set(tuple(int(x) for x in row) for row in np.unique(vals, axis=0))
def tires_for(n, m_max, fcap):
"""Yield (label, faces) for inner fills of an n-outer tire."""
yield (f"m=1 fan", fan_faces(n))
for m in range(2, m_max + 1):
if n + m > fcap:
continue
for path in lattice_paths(n, m):
yield (f"m={m} {path}", annular_faces(n, m, path))
def run(n, m_max=7, fcap=13):
inter = None
n_tires = 0
min_set = (10**9, None)
shrink_trace = []
for label, faces in tires_for(n, m_max, fcap):
R = realisable_outer(n, faces)
n_tires += 1
if len(R) < min_set[0]:
min_set = (len(R), label)
if inter is None:
inter = set(R)
else:
before = len(inter)
inter &= R
if len(inter) < before:
shrink_trace.append((n_tires, label, len(inter)))
if not inter:
break
print(f"n={n}: {n_tires} tires tried, "
f"smallest single R_out = {min_set[0]} ({min_set[1]})")
if inter:
print(f" UNIVERSAL sequences found: {len(inter)}")
for s in sorted(inter)[:12]:
print(f" {s}")
else:
print(f" NO universal sequence: intersection emptied after "
f"{n_tires} tires")
print(" intersection size as tires were added (last few shrinks):")
for t in shrink_trace[-6:]:
print(f" after tire {t[0]:4d} ({t[1]}): |∩| = {t[2]}")
return bool(inter)
def main():
if len(sys.argv) > 1:
ns = [int(sys.argv[1])]
else:
ns = [3, 4, 5, 6]
print("Searching for universal Heawood boundary sequences\n")
for n in ns:
run(n)
print()
if __name__ == "__main__":
main()
@@ -0,0 +1,172 @@
"""
Transfer operator for the Heawood program, in the cleanest self-similar setting:
a chain of annular tires with n_out = n_in = n. Each tire's labelling map sends
+/-1 face labels to (outer sequence, inner sequence). Gluing a child below means
the parent's inner sequence must negate (mod 3) the child's achievable outer
sequence. So the achievable outer-interface set propagates UP the chain by
Phi(parent) = { outer(lambda) : lambda in {+-1}^F,
inner(lambda) in -Phi(child) }.
This is a monotone set-operator on subsets of (Z/3)^n. Iterating it models a
deepening nested chain; we look for a FIXED POINT (absorbing set) and test which
candidate self-similar invariants the limit satisfies:
* non-empty
* closed under the global sign flip s -> -s
* local marginals: does every position attain all of {0,1,-1}?
* is it an affine GF(3) subspace? (we expect NO -- R_T is a zonotope)
* does a linear/parity constraint cut it out?
Sequences are stored mod 3 in {0,1,2}; printed in {0,1,-1} (2 -> -1).
"""
import sys
from itertools import product
import numpy as np
def annular_tire(n_out, n_in, path):
"""Faces between outer cycle 0..n_out-1 and inner cycle n_out..n_out+n_in-1."""
faces = []
i = j = 0
for mv in path:
if mv == "O":
faces.append((i % n_out, (i + 1) % n_out, n_out + (j % n_in)))
i += 1
else:
faces.append((i % n_out, n_out + (j % n_in), n_out + ((j + 1) % n_in)))
j += 1
return faces
def labelling_pairs(n_out, n_in, faces):
"""All (outer_seq, inner_seq) over lambda in {+1,-1}^F, as Z/3 tuples."""
F = len(faces)
Ao = np.zeros((n_out, F), dtype=np.int64)
Ai = np.zeros((n_in, F), dtype=np.int64)
for f, (a, b, c) in enumerate(faces):
for v in (a, b, c):
if v < n_out:
Ao[v, f] = 1
else:
Ai[v - n_out, f] = 1
labs = np.array(list(product((1, 2), repeat=F)), dtype=np.int64)
outer = (labs @ Ao.T) % 3
inner = (labs @ Ai.T) % 3
return [(tuple(o), tuple(i)) for o, i in zip(outer.tolist(), inner.tolist())]
def make_operator(pairs):
def op(phi_child):
neg = {tuple((3 - x) % 3 for x in s) for s in phi_child}
return {o for (o, inn) in pairs if inn in neg}
return op
def iterate_to_fixed(op, start, max_iter=50):
phi = frozenset(start)
seen = [phi]
for _ in range(max_iter):
nxt = frozenset(op(phi))
if nxt == phi:
return phi, "fixed", len(seen)
if nxt in seen:
return nxt, "cycle", len(seen)
phi = nxt
seen.append(phi)
return phi, "no-converge", len(seen)
# ----------------- invariant tests -------------------------------------------
def disp(s):
return tuple(-1 if x == 2 else x for x in s)
def gf3_rank(rows):
M = [[x % 3 for x in r] for r in rows]
if not M:
return 0
nc = len(M[0]); r = 0
for c in range(nc):
piv = next((i for i in range(r, len(M)) if M[i][c] % 3), None)
if piv is None:
continue
M[r], M[piv] = M[piv], M[r]
inv = M[r][c] % 3 # 1->1, 2->2 are self-inverse mod 3
M[r] = [(x * inv) % 3 for x in M[r]]
for i in range(len(M)):
if i != r and M[i][c] % 3:
f = M[i][c] % 3
M[i] = [(M[i][k] - f * M[r][k]) % 3 for k in range(nc)]
r += 1
if r == len(M):
break
return r
def is_affine(S):
S = list(S)
if len(S) <= 1:
return True
s0 = S[0]
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in S]
return len(S) == 3 ** gf3_rank(D)
def marginals_full(S, n):
return all({s[i] for s in S} == {0, 1, 2} for i in range(n))
def sign_closed(S):
return all(tuple((3 - x) % 3 for x in s) in S for s in S)
def linear_constraints(S, n):
"""Dimension of the space of linear forms vanishing on S-s0 (codim of hull)."""
S = list(S)
if len(S) <= 1:
return n
s0 = S[0]
D = [tuple((np.array(s) - np.array(s0)) % 3) for s in S]
return n - gf3_rank(D)
def analyse(tag, S, n):
print(f" [{tag}] |Phi|={len(S)} of 3^{n}={3**n} "
f"sign-closed={sign_closed(S)} marginals-full={marginals_full(S,n)} "
f"affine={is_affine(S)} hull-codim={linear_constraints(S,n)}")
def run(n, paths=None):
if paths is None:
# a few distinct same-n annular triangulations
paths = ["OI" * n, "O" * n + "I" * n, ("OOI" * n)[:2 * n]]
paths = [p for p in paths if p.count("O") == n and p.count("I") == n]
print(f"=== n={n} ===")
full = set(product((0, 1, 2), repeat=n))
for path in paths:
faces = annular_tire(n, n, path)
pairs = labelling_pairs(n, n, faces)
op = make_operator(pairs)
single = set(o for (o, _) in pairs) # leaf: full single-tire outer set
fixed, how, steps = iterate_to_fixed(op, single)
# also iterate from the universal start (all sequences allowed below)
fixed2, how2, _ = iterate_to_fixed(op, full)
print(f" path={path}: single-tire |outer|={len(single)}; "
f"iterate->{how} in {steps} steps; "
f"same-limit-from-full={fixed==fixed2}")
analyse("limit", fixed, n)
sample = sorted(disp(s) for s in fixed)[:8]
print(f" sample of limit set: {sample}")
print()
def main():
ns = [int(x) for x in sys.argv[1:]] or [4, 5, 6]
print("Transfer-operator fixed points on same-n annular tire chains\n")
for n in ns:
run(n)
if __name__ == "__main__":
main()
@@ -0,0 +1,2 @@
\relax
\gdef \@abspage@last{2}
@@ -0,0 +1,296 @@
This is pdfTeX, Version 3.141592653-2.6-1.40.24 (TeX Live 2022) (preloaded format=pdflatex 2022.10.5) 17 JUN 2026 02:12
entering extended mode
restricted \write18 enabled.
%&-line parsing enabled.
**boundary_restriction_structure.tex
(./boundary_restriction_structure.tex
LaTeX2e <2021-11-15> patch level 1
L3 programming layer <2022-02-24>
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/article.cls
Document Class: article 2021/10/04 v1.4n Standard LaTeX document class
(/usr/local/texlive/2022/texmf-dist/tex/latex/base/size11.clo
File: size11.clo 2021/10/04 v1.4n Standard LaTeX file (size option)
)
\c@part=\count185
\c@section=\count186
\c@subsection=\count187
\c@subsubsection=\count188
\c@paragraph=\count189
\c@subparagraph=\count190
\c@figure=\count191
\c@table=\count192
\abovecaptionskip=\skip47
\belowcaptionskip=\skip48
\bibindent=\dimen138
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsmath.sty
Package: amsmath 2021/10/15 v2.17l AMS math features
\@mathmargin=\skip49
For additional information on amsmath, use the `?' option.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amstext.sty
Package: amstext 2021/08/26 v2.01 AMS text
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsgen.sty
File: amsgen.sty 1999/11/30 v2.0 generic functions
\@emptytoks=\toks16
\ex@=\dimen139
))
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsbsy.sty
Package: amsbsy 1999/11/29 v1.2d Bold Symbols
\pmbraise@=\dimen140
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsmath/amsopn.sty
Package: amsopn 2021/08/26 v2.02 operator names
)
\inf@bad=\count193
LaTeX Info: Redefining \frac on input line 234.
\uproot@=\count194
\leftroot@=\count195
LaTeX Info: Redefining \overline on input line 399.
\classnum@=\count196
\DOTSCASE@=\count197
LaTeX Info: Redefining \ldots on input line 496.
LaTeX Info: Redefining \dots on input line 499.
LaTeX Info: Redefining \cdots on input line 620.
\Mathstrutbox@=\box50
\strutbox@=\box51
\big@size=\dimen141
LaTeX Font Info: Redeclaring font encoding OML on input line 743.
LaTeX Font Info: Redeclaring font encoding OMS on input line 744.
\macc@depth=\count198
\c@MaxMatrixCols=\count199
\dotsspace@=\muskip16
\c@parentequation=\count266
\dspbrk@lvl=\count267
\tag@help=\toks17
\row@=\count268
\column@=\count269
\maxfields@=\count270
\andhelp@=\toks18
\eqnshift@=\dimen142
\alignsep@=\dimen143
\tagshift@=\dimen144
\tagwidth@=\dimen145
\totwidth@=\dimen146
\lineht@=\dimen147
\@envbody=\toks19
\multlinegap=\skip50
\multlinetaggap=\skip51
\mathdisplay@stack=\toks20
LaTeX Info: Redefining \[ on input line 2938.
LaTeX Info: Redefining \] on input line 2939.
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amssymb.sty
Package: amssymb 2013/01/14 v3.01 AMS font symbols
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/amsfonts.sty
Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support
\symAMSa=\mathgroup4
\symAMSb=\mathgroup5
LaTeX Font Info: Redeclaring math symbol \hbar on input line 98.
LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold'
(Font) U/euf/m/n --> U/euf/b/n on input line 106.
))
(/usr/local/texlive/2022/texmf-dist/tex/latex/amscls/amsthm.sty
Package: amsthm 2020/05/29 v2.20.6
\thm@style=\toks21
\thm@bodyfont=\toks22
\thm@headfont=\toks23
\thm@notefont=\toks24
\thm@headpunct=\toks25
\thm@preskip=\skip52
\thm@postskip=\skip53
\thm@headsep=\skip54
\dth@everypar=\toks26
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphicx.sty
Package: graphicx 2021/09/16 v1.2d Enhanced LaTeX Graphics (DPC,SPQR)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/keyval.sty
Package: keyval 2014/10/28 v1.15 key=value parser (DPC)
\KV@toks@=\toks27
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/graphics.sty
Package: graphics 2021/03/04 v1.4d Standard LaTeX Graphics (DPC,SPQR)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics/trig.sty
Package: trig 2021/08/11 v1.11 sin cos tan (DPC)
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-cfg/graphics.cfg
File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration
)
Package graphics Info: Driver file: pdftex.def on input line 107.
(/usr/local/texlive/2022/texmf-dist/tex/latex/graphics-def/pdftex.def
File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex
))
\Gin@req@height=\dimen148
\Gin@req@width=\dimen149
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/geometry/geometry.sty
Package: geometry 2020/01/02 v5.9 Page Geometry
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/ifvtex.sty
Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead.
(/usr/local/texlive/2022/texmf-dist/tex/generic/iftex/iftex.sty
Package: iftex 2022/02/03 v1.0f TeX engine tests
))
\Gm@cnth=\count271
\Gm@cntv=\count272
\c@Gm@tempcnt=\count273
\Gm@bindingoffset=\dimen150
\Gm@wd@mp=\dimen151
\Gm@odd@mp=\dimen152
\Gm@even@mp=\dimen153
\Gm@layoutwidth=\dimen154
\Gm@layoutheight=\dimen155
\Gm@layouthoffset=\dimen156
\Gm@layoutvoffset=\dimen157
\Gm@dimlist=\toks28
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/booktabs/booktabs.sty
Package: booktabs 2020/01/12 v1.61803398 Publication quality tables
\heavyrulewidth=\dimen158
\lightrulewidth=\dimen159
\cmidrulewidth=\dimen160
\belowrulesep=\dimen161
\belowbottomsep=\dimen162
\aboverulesep=\dimen163
\abovetopsep=\dimen164
\cmidrulesep=\dimen165
\cmidrulekern=\dimen166
\defaultaddspace=\dimen167
\@cmidla=\count274
\@cmidlb=\count275
\@aboverulesep=\dimen168
\@belowrulesep=\dimen169
\@thisruleclass=\count276
\@lastruleclass=\count277
\@thisrulewidth=\dimen170
)
(/usr/local/texlive/2022/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def
File: l3backend-pdftex.def 2022-02-07 L3 backend support: PDF output (pdfTeX)
\l__color_backend_stack_int=\count278
\l__pdf_internal_box=\box52
)
No file boundary_restriction_structure.aux.
\openout1 = `boundary_restriction_structure.aux'.
LaTeX Font Info: Checking defaults for OML/cmm/m/it on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for OMS/cmsy/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for OT1/cmr/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for T1/cmr/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for TS1/cmr/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for OMX/cmex/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
LaTeX Font Info: Checking defaults for U/cmr/m/n on input line 18.
LaTeX Font Info: ... okay on input line 18.
(/usr/local/texlive/2022/texmf-dist/tex/context/base/mkii/supp-pdf.mkii
[Loading MPS to PDF converter (version 2006.09.02).]
\scratchcounter=\count279
\scratchdimen=\dimen171
\scratchbox=\box53
\nofMPsegments=\count280
\nofMParguments=\count281
\everyMPshowfont=\toks29
\MPscratchCnt=\count282
\MPscratchDim=\dimen172
\MPnumerator=\count283
\makeMPintoPDFobject=\count284
\everyMPtoPDFconversion=\toks30
) (/usr/local/texlive/2022/texmf-dist/tex/latex/epstopdf-pkg/epstopdf-base.sty
Package: epstopdf-base 2020-01-24 v2.11 Base part for package epstopdf
Package epstopdf-base Info: Redefining graphics rule for `.eps' on input line 4
85.
(/usr/local/texlive/2022/texmf-dist/tex/latex/latexconfig/epstopdf-sys.cfg
File: epstopdf-sys.cfg 2010/07/13 v1.3 Configuration of (r)epstopdf for TeX Liv
e
))
*geometry* driver: auto-detecting
*geometry* detected driver: pdftex
*geometry* verbose mode - [ preamble ] result:
* driver: pdftex
* paper: <default>
* layout: <same size as paper>
* layoutoffset:(h,v)=(0.0pt,0.0pt)
* modes:
* h-part:(L,W,R)=(72.26999pt, 469.75502pt, 72.26999pt)
* v-part:(T,H,B)=(72.26999pt, 650.43001pt, 72.26999pt)
* \paperwidth=614.295pt
* \paperheight=794.96999pt
* \textwidth=469.75502pt
* \textheight=650.43001pt
* \oddsidemargin=0.0pt
* \evensidemargin=0.0pt
* \topmargin=-37.0pt
* \headheight=12.0pt
* \headsep=25.0pt
* \topskip=11.0pt
* \footskip=30.0pt
* \marginparwidth=59.0pt
* \marginparsep=10.0pt
* \columnsep=10.0pt
* \skip\footins=10.0pt plus 4.0pt minus 2.0pt
* \hoffset=0.0pt
* \voffset=0.0pt
* \mag=1000
* \@twocolumnfalse
* \@twosidefalse
* \@mparswitchfalse
* \@reversemarginfalse
* (1in=72.27pt=25.4mm, 1cm=28.453pt)
LaTeX Font Info: Trying to load font information for U+msa on input line 19.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsa.fd
File: umsa.fd 2013/01/14 v3.01 AMS symbols A
)
LaTeX Font Info: Trying to load font information for U+msb on input line 19.
(/usr/local/texlive/2022/texmf-dist/tex/latex/amsfonts/umsb.fd
File: umsb.fd 2013/01/14 v3.01 AMS symbols B
) [1
{/usr/local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] [2]
(./boundary_restriction_structure.aux) )
Here is how much of TeX's memory you used:
3254 strings out of 478268
48506 string characters out of 5846347
347668 words of memory out of 5000000
21442 multiletter control sequences out of 15000+600000
480359 words of font info for 70 fonts, out of 8000000 for 9000
1141 hyphenation exceptions out of 8191
55i,8n,62p,247b,208s stack positions out of 10000i,1000n,20000p,200000b,200000s
</usr/local/texlive/2022/texmf-dist/fon
ts/type1/public/amsfonts/cm/cmbx10.pfb></usr/local/texlive/2022/texmf-dist/font
s/type1/public/amsfonts/cm/cmbx12.pfb></usr/local/texlive/2022/texmf-dist/fonts
/type1/public/amsfonts/cm/cmex10.pfb></usr/local/texlive/2022/texmf-dist/fonts/
type1/public/amsfonts/cm/cmmi10.pfb></usr/local/texlive/2022/texmf-dist/fonts/t
ype1/public/amsfonts/cm/cmmi12.pfb></usr/local/texlive/2022/texmf-dist/fonts/ty
pe1/public/amsfonts/cm/cmmi8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type
1/public/amsfonts/cm/cmr10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/
public/amsfonts/cm/cmr12.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/pu
blic/amsfonts/cm/cmr17.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/publ
ic/amsfonts/cm/cmr8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/
amsfonts/cm/cmss8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/am
sfonts/cm/cmsy10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/ams
fonts/cm/cmsy8.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfo
nts/cm/cmti10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfon
ts/cm/cmtt10.pfb></usr/local/texlive/2022/texmf-dist/fonts/type1/public/amsfont
s/symbols/msbm10.pfb>
Output written on boundary_restriction_structure.pdf (2 pages, 184212 bytes).
PDF statistics:
91 PDF objects out of 1000 (max. 8388607)
54 compressed objects within 1 object stream
0 named destinations out of 1000 (max. 500000)
1 words of extra memory for PDF output out of 10000 (max. 10000000)
@@ -0,0 +1,139 @@
\documentclass[11pt]{article}
\usepackage{amsmath,amssymb,amsthm}
\usepackage{graphicx}
\usepackage{geometry}
\usepackage{booktabs}
\geometry{margin=1in}
\title{Heawood boundary restriction sets:\\
zonotope structure and the $2^{n-2}$ constraint floor}
\author{}
\date{}
\newtheorem*{obs}{Observation}
\newtheorem*{prop}{Proposition}
\newtheorem*{conj}{Conjecture}
\newtheorem*{lem}{Lemma}
\begin{document}
\maketitle
This note records the empirical structure of the Heawood boundary
restriction sets studied in \texttt{paper.tex}, and a clean
\emph{maximal-constraint} result. All claims below are backed by the
experiments in \texttt{experiments/} (filenames given inline). Sequences
live in $(\mathbb{Z}/3)^{\,\cdot}$, displayed in $\{0,1,-1\}$.
\section*{Setup}
Fix a triangulated disk $D$ with boundary cycle $C = (v_0,\dots,v_{n-1})$.
A Heawood face-labelling is $\lambda : \{\text{faces of }D\} \to \{+1,-1\}$,
with induced vertex value $\lambda^{*}(v) = \sum_{f \ni v}\lambda(f) \bmod 3$.
The achievable outer set is
\[
\Phi(D) \;=\; \bigl\{\, (\lambda^{*}(v_0),\dots,\lambda^{*}(v_{n-1}))
\;:\; \lambda \in \{+1,-1\}^{F(D)},\;
\lambda^{*}(w) \equiv 0 \ \forall\ \text{interior } w \,\bigr\}.
\]
This is exactly the value the recursive transfer operator produces at
$C$ (interior consistency $=$ all descendant gluings performed; boundary
deferred). Crucially $\Phi(D)$ depends \emph{only} on the disk
triangulation, not on any BFS/tire-tree labelling.
\section*{1. The restriction sets are zonotopes, not subspaces}
(\texttt{probe\_RK\_structure.py}.) Writing $\lambda = \mathbf{1}+b$ with
$b \in \{0,1\}^F$, the labelling map is $\lambda \mapsto M\mathbf{1}+Mb
\pmod 3$, a linear image of the Boolean cube ($M$ the face/vertex
incidence matrix). Over $3655$ cluster restriction sets $R_{\mathsf K}$:
none was an affine $\mathrm{GF}(3)$ subspace; the map is usually
injective, so $|R_{\mathsf K}| = 2^{|F|}$ (a power of $2$ inside the
column space of size $3^{\operatorname{rank} M}$); the nowhere-zero
constraint $\lambda \neq 0$ shrank the set below the full linear image in
\emph{every} case. The only surviving linear structure is
$R_{\mathsf K} \subseteq \operatorname{col}(M)$ (cokernel relations such
as $\sum_v \lambda^{*}(v) \equiv 0$). So $\Phi$ is a $\mathbb{Z}/3$
zonotope: a projected cube, sign-closed but not closed under addition.
\section*{2. ``Richness'' is not a self-similar invariant}
(\texttt{transfer\_operator.py}, \texttt{branch\_invariant.py}.) In a
homogeneous same-$n$ spoke-only chain the operator saturates: $\Phi$ has
full single-position marginals (every interface vertex independently
attains all of $\{0,1,-1\}$), and the alternating tire reaches the
\emph{entire} space $3^n$. This is an artifact of non-shrinking annuli
with no interior constraints. On genuine triangulations the marginal
fullness holds for only ${\sim}8\%$ of regions: depth (not branching)
shrinks $\Phi$, e.g.\ a region with $|C|=10$ realised only $|\Phi|=400$
of $3^{10}\approx 59000$. Only non-emptiness and sign-closure survive,
both of which are automatic / equivalent to $4$CT. Hence no abundance
(counting) pigeonhole: a working invariant must tolerate \emph{small}
$\Phi$.
\section*{3. The maximal-constraint floor}
(\texttt{maximally\_constrain.py}.) Minimising $|\Phi(D)|$ over disks with
a fixed boundary $n$-cycle:
\begin{center}
\begin{tabular}{ccccc}
\toprule
$n$ & $4$ & $5$ & $6$ & $7$\\
\midrule
$\min |\Phi|$ (search) & $4$ & $8$ & $16$ & $32$\\
fan, $0$ interior vertices & $4$ & $8$ & $16$ & $32$\\
$2^{\,n-2}$ & $4$ & $8$ & $16$ & $32$\\
\bottomrule
\end{tabular}
\end{center}
A search over random and deep-stacked disks (up to $8$ interior vertices)
never beat $2^{n-2}$, and the interior-free triangulation already
attains it. Thus:
\begin{obs}
The outer $n$-cycle cannot be constrained below $2^{n-2}$ achievable
sequences, and no nested structure is needed to reach the floor: a single
trivial tire is already maximal. Deep nesting only approaches the floor
from above.
\end{obs}
The achievability is transparent: in a fan from $v_0$,
\[
\sigma_1 = \lambda_1,\quad
\sigma_i = \lambda_{i-1}+\lambda_i \ (1<i<n-1),\quad
\sigma_{n-1} = \lambda_{n-2},\quad
\sigma_0 = \textstyle\sum_j \lambda_j ,
\]
so $(\lambda_1,\dots,\lambda_{n-2})$ is recoverable from $\sigma$ and the
map is injective onto $2^{n-2}$ sequences. The lower bound over
\emph{all} disks is the substance:
\begin{conj}[Boundary degrees of freedom]
For every triangulated disk $D$ with boundary $n$-cycle,
$|\Phi(D)| \ge 2^{n-2}$. Equivalently, the $n-2$ binary degrees of
freedom carried by the boundary-incident faces survive every interior
Heawood constraint (which relates only interior-incident faces).
\end{conj}
The minimal set is itself a sign-closed zonotope of size $2^{n-2}$, hull
dimension $n-2$, not a $\mathrm{GF}(3)$ subspace --- the same fingerprint
as $\S1$.
\section*{Consequence for the pigeonhole}
Even a maximally-constraining child still presents $2^{n-2}$ outer
options --- exponential in the interface length $n$. So the gluing
problem has the least slack at \emph{short} interfaces ($n=4$ leaves $4$
options, $n=3$ leaves $2$), and is easy at long ones. The crux of the
Heawood programme therefore lives entirely at short level cycles, exactly
where the medial programme's $N(k)$ bound concentrates.
\medskip
\noindent\emph{Meta-remark.} Because $4$CT holds, every actual
triangulation glues, so no experiment can exhibit an obstruction (pair or
chain). The experiments measure \emph{structure} (zonotope type,
constraint floor), not proof difficulty; the difficulty is localised, not
removed.
\end{document}