Canonicalize tire symmetry and exhaust n=6 level-cycle supports
Quotient (m, k, path, chords, cycle) by the order-2(m+k) dihedral action on the rung sequence; exhaustive sweep over outer 3..7, inner 7..9 yields 19 distinct supports with a unique floor at 252/732 realised by m=3, k=7, single-chord 6-face. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,429 @@
|
||||
"""Support on genuine level-cycle candidates inside a tire.
|
||||
|
||||
Here a tested cycle is not an arbitrary tire boundary. It must be a
|
||||
bounded face cycle of the inner outerplanar graph O, distinct from
|
||||
O's outer-face boundary. This models a level/seam cycle as it appears
|
||||
inside the parent's next-level graph, rather than treating it as an
|
||||
outer boundary of a tire at the same level.
|
||||
|
||||
For each such cycle C, the support is the set of proper 4-colorings of
|
||||
C that extend to a proper 4-coloring of the parent tire graph.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from functools import lru_cache
|
||||
from itertools import combinations, permutations, product
|
||||
from math import comb
|
||||
|
||||
COLORS = (0, 1, 2, 3)
|
||||
|
||||
|
||||
def canonical_edge(u: int, v: int) -> tuple[int, int]:
|
||||
return (u, v) if u < v else (v, u)
|
||||
|
||||
|
||||
def cycle_edges(vertices: list[int]) -> set[tuple[int, int]]:
|
||||
return {
|
||||
canonical_edge(vertices[i], vertices[(i + 1) % len(vertices)])
|
||||
for i in range(len(vertices))
|
||||
}
|
||||
|
||||
|
||||
def chord_crosses(c1: tuple[int, int], c2: tuple[int, int]) -> bool:
|
||||
a, b = sorted(c1)
|
||||
c, d = sorted(c2)
|
||||
return (a < c < b < d) or (c < a < d < b)
|
||||
|
||||
|
||||
def noncrossing_chord_sets(k: int, max_chords: int | None = None):
|
||||
candidates = [
|
||||
(a, b)
|
||||
for a in range(k)
|
||||
for b in range(a + 2, k)
|
||||
if not (a == 0 and b == k - 1)
|
||||
]
|
||||
out = [()]
|
||||
|
||||
def rec(start: int, chosen: tuple[tuple[int, int], ...]) -> None:
|
||||
if max_chords is not None and len(chosen) >= max_chords:
|
||||
return
|
||||
for idx in range(start, len(candidates)):
|
||||
ch = candidates[idx]
|
||||
if any(chord_crosses(ch, old) for old in chosen):
|
||||
continue
|
||||
nxt = chosen + (ch,)
|
||||
out.append(nxt)
|
||||
rec(idx + 1, nxt)
|
||||
|
||||
rec(0, ())
|
||||
return tuple(sorted(set(out), key=lambda cs: (len(cs), cs)))
|
||||
|
||||
|
||||
def lattice_paths(m: int, k: int):
|
||||
n = m + k
|
||||
for outer_positions in combinations(range(n), m):
|
||||
outer_positions = set(outer_positions)
|
||||
yield "".join("O" if i in outer_positions else "I" for i in range(n))
|
||||
|
||||
|
||||
def tire_edges(m: int, k: int, path: str, chords: tuple[tuple[int, int], ...]) -> tuple[tuple[int, int], ...]:
|
||||
outer = list(range(m))
|
||||
inner = list(range(m, m + k))
|
||||
edges = set()
|
||||
edges |= cycle_edges(outer)
|
||||
edges |= cycle_edges(inner)
|
||||
for a, b in chords:
|
||||
edges.add(canonical_edge(inner[a], inner[b]))
|
||||
|
||||
edges.add(canonical_edge(outer[0], inner[0]))
|
||||
i = j = 0
|
||||
for move in path:
|
||||
if move == "O":
|
||||
edges.add(canonical_edge(outer[(i + 1) % m], inner[j % k]))
|
||||
i += 1
|
||||
else:
|
||||
edges.add(canonical_edge(outer[i % m], inner[(j + 1) % k]))
|
||||
j += 1
|
||||
return tuple(sorted(edges))
|
||||
|
||||
|
||||
def proper_cycle_colorings(n: int) -> tuple[tuple[int, ...], ...]:
|
||||
out = []
|
||||
for colors in product(COLORS, repeat=n):
|
||||
if all(colors[i] != colors[(i + 1) % n] for i in range(n)):
|
||||
out.append(colors)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def boundary_support(
|
||||
n_vertices: int,
|
||||
edges: tuple[tuple[int, int], ...],
|
||||
boundary_vertices: tuple[int, ...],
|
||||
) -> frozenset[tuple[int, ...]]:
|
||||
assigned = {0: 0}
|
||||
adj = {v: set() for v in range(n_vertices)}
|
||||
for u, v in edges:
|
||||
adj[u].add(v)
|
||||
adj[v].add(u)
|
||||
|
||||
remaining = [v for v in range(n_vertices) if v not in assigned]
|
||||
remaining.sort(key=lambda v: (-len(adj[v]), v))
|
||||
support = set()
|
||||
|
||||
def dfs(index: int) -> None:
|
||||
if index == len(remaining):
|
||||
support.add(tuple(assigned[v] for v in boundary_vertices))
|
||||
return
|
||||
best_at = index
|
||||
best_options = None
|
||||
for pos in range(index, len(remaining)):
|
||||
v = remaining[pos]
|
||||
used = {assigned[w] for w in adj[v] if w in assigned}
|
||||
options = tuple(c for c in COLORS if c not in used)
|
||||
if best_options is None or len(options) < len(best_options):
|
||||
best_at = pos
|
||||
best_options = options
|
||||
if len(options) <= 1:
|
||||
break
|
||||
if not best_options:
|
||||
return
|
||||
|
||||
remaining[index], remaining[best_at] = remaining[best_at], remaining[index]
|
||||
v = remaining[index]
|
||||
for color in best_options:
|
||||
assigned[v] = color
|
||||
dfs(index + 1)
|
||||
assigned.pop(v)
|
||||
remaining[index], remaining[best_at] = remaining[best_at], remaining[index]
|
||||
|
||||
dfs(0)
|
||||
return frozenset(support)
|
||||
|
||||
|
||||
def rotate_tuple(values: tuple[int, ...], shift: int) -> tuple[int, ...]:
|
||||
n = len(values)
|
||||
return tuple(values[(i + shift) % n] for i in range(n))
|
||||
|
||||
|
||||
def normalize_support(support: frozenset[tuple[int, ...]]) -> frozenset[tuple[int, ...]]:
|
||||
out = set()
|
||||
for state in support:
|
||||
for perm in permutations(COLORS):
|
||||
relabeled = tuple(perm[c] for c in state)
|
||||
for seq in (relabeled, relabeled[::-1]):
|
||||
for shift in range(len(seq)):
|
||||
out.add(rotate_tuple(seq, shift))
|
||||
return frozenset(out)
|
||||
|
||||
|
||||
def describe_chords(chords: tuple[tuple[int, int], ...]) -> str:
|
||||
return ",".join(f"{a}-{b}" for a, b in chords) if chords else "none"
|
||||
|
||||
|
||||
def canonicalize_tire_cycle(
|
||||
m: int,
|
||||
k: int,
|
||||
path: str,
|
||||
chords: tuple[tuple[int, int], ...],
|
||||
cycle: tuple[int, ...],
|
||||
) -> tuple[str, tuple[tuple[int, int], ...], tuple[int, ...]]:
|
||||
"""Canonical form of (tire, level cycle) under tire symmetries.
|
||||
|
||||
The tire has dihedral symmetry of order 2(m+k) on the rung sequence:
|
||||
cyclic shift s = "start at rung s of the original path", and reflection
|
||||
= "traverse in reverse" (the path string reverses, inner cycle direction
|
||||
flips so chord (a,b) becomes ((k-a) mod k, (k-b) mod k)).
|
||||
|
||||
The level cycle is transported by the same rigid relabelling and then
|
||||
normalized under its own D_n (the support is invariant under D_n on the
|
||||
cycle's vertex sequence, so this stage is part of the canonical form).
|
||||
"""
|
||||
n_rung = m + k
|
||||
n_cycle = len(cycle)
|
||||
best = None
|
||||
for reverse in (False, True):
|
||||
if reverse:
|
||||
p_base = path[::-1]
|
||||
cs_base = tuple(
|
||||
canonical_edge((k - a) % k, (k - b) % k) for a, b in chords
|
||||
)
|
||||
c_base = tuple((k - v) % k for v in reversed(cycle))
|
||||
else:
|
||||
p_base = path
|
||||
cs_base = chords
|
||||
c_base = cycle
|
||||
|
||||
prefix_i = [0]
|
||||
for ch in p_base:
|
||||
prefix_i.append(prefix_i[-1] + (1 if ch == "I" else 0))
|
||||
|
||||
for s in range(n_rung):
|
||||
b_s = prefix_i[s]
|
||||
shifted = p_base[s:] + p_base[:s]
|
||||
new_cs = tuple(
|
||||
sorted(
|
||||
canonical_edge((a - b_s) % k, (b - b_s) % k)
|
||||
for a, b in cs_base
|
||||
)
|
||||
)
|
||||
new_c = tuple((v - b_s) % k for v in c_base)
|
||||
norm_c = min(
|
||||
tuple(orient[(r + i) % n_cycle] for i in range(n_cycle))
|
||||
for orient in (new_c, new_c[::-1])
|
||||
for r in range(n_cycle)
|
||||
)
|
||||
cand = (shifted, new_cs, norm_c)
|
||||
if best is None or cand < best:
|
||||
best = cand
|
||||
return best
|
||||
|
||||
|
||||
def interval_vertices(vertices: tuple[int, ...], a: int, b: int) -> tuple[int, ...]:
|
||||
ia = vertices.index(a)
|
||||
out = [a]
|
||||
i = ia
|
||||
while vertices[i] != b:
|
||||
i = (i + 1) % len(vertices)
|
||||
out.append(vertices[i])
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def chord_inside(poly: tuple[int, ...], chord: tuple[int, int]) -> bool:
|
||||
a, b = chord
|
||||
return a in poly and b in poly
|
||||
|
||||
|
||||
def polygon_faces(poly: tuple[int, ...], chords: tuple[tuple[int, int], ...]) -> list[tuple[int, ...]]:
|
||||
"""Cells inside a polygon cut by noncrossing chords."""
|
||||
usable = [ch for ch in chords if chord_inside(poly, ch)]
|
||||
split = None
|
||||
for a, b in usable:
|
||||
if a not in poly or b not in poly:
|
||||
continue
|
||||
ia, ib = poly.index(a), poly.index(b)
|
||||
distance = abs(ia - ib)
|
||||
if distance in (1, len(poly) - 1):
|
||||
continue
|
||||
split = (a, b)
|
||||
break
|
||||
|
||||
if split is None:
|
||||
return [poly]
|
||||
|
||||
a, b = split
|
||||
side1 = interval_vertices(poly, a, b)
|
||||
side2 = interval_vertices(poly, b, a)
|
||||
rest = tuple(ch for ch in usable if ch != split)
|
||||
return polygon_faces(side1, rest) + polygon_faces(side2, rest)
|
||||
|
||||
|
||||
def level_cycles_in_o(k: int, chords: tuple[tuple[int, int], ...]) -> tuple[tuple[int, ...], ...]:
|
||||
"""Bounded face cycles of O that are not the outer boundary of O."""
|
||||
if not chords:
|
||||
return ()
|
||||
outer = tuple(range(k))
|
||||
faces = polygon_faces(outer, chords)
|
||||
out = []
|
||||
for face in faces:
|
||||
if len(face) == k and set(face) == set(outer):
|
||||
continue
|
||||
out.append(face)
|
||||
return tuple(out)
|
||||
|
||||
|
||||
def enumerate_level_cycle_supports(
|
||||
n: int,
|
||||
outer_min: int,
|
||||
outer_max: int,
|
||||
inner_min: int,
|
||||
inner_max: int,
|
||||
max_chords: int | None,
|
||||
max_paths: int | None,
|
||||
canonicalize: bool = True,
|
||||
progress: bool = False,
|
||||
) -> tuple[dict[frozenset[tuple[int, ...]], list[dict]], dict]:
|
||||
supports: dict[frozenset[tuple[int, ...]], list[dict]] = {}
|
||||
seen_canon: set = set()
|
||||
stats = {"raw": 0, "canonical": 0}
|
||||
for m in range(outer_min, outer_max + 1):
|
||||
for k in range(max(inner_min, n), inner_max + 1):
|
||||
for chords in noncrossing_chord_sets(k, max_chords):
|
||||
level_cycles = [cycle for cycle in level_cycles_in_o(k, chords) if len(cycle) == n]
|
||||
if not level_cycles:
|
||||
continue
|
||||
for path_idx, path in enumerate(lattice_paths(m, k)):
|
||||
if max_paths is not None and path_idx >= max_paths:
|
||||
break
|
||||
edges = None
|
||||
for cycle in level_cycles:
|
||||
stats["raw"] += 1
|
||||
if canonicalize:
|
||||
canon = canonicalize_tire_cycle(m, k, path, chords, cycle)
|
||||
if canon in seen_canon:
|
||||
continue
|
||||
seen_canon.add(canon)
|
||||
stats["canonical"] += 1
|
||||
if edges is None:
|
||||
edges = tire_edges(m, k, path, chords)
|
||||
boundary = tuple(m + v for v in cycle)
|
||||
support = normalize_support(boundary_support(m + k, edges, boundary))
|
||||
meta = {
|
||||
"m": m,
|
||||
"k": k,
|
||||
"path": path,
|
||||
"chords": chords,
|
||||
"cycle": cycle,
|
||||
}
|
||||
supports.setdefault(support, []).append(meta)
|
||||
if progress:
|
||||
print(
|
||||
f" m={m} k={k} chords={describe_chords(chords)} "
|
||||
f"raw={stats['raw']} canonical={stats['canonical']} "
|
||||
f"supports={len(supports)}",
|
||||
flush=True,
|
||||
)
|
||||
return supports, stats
|
||||
|
||||
|
||||
def summarize(
|
||||
n: int,
|
||||
supports: dict[frozenset[tuple[int, ...]], list[dict]],
|
||||
examples: int,
|
||||
stats: dict | None = None,
|
||||
) -> None:
|
||||
all_colorings = len(proper_cycle_colorings(n))
|
||||
if not supports:
|
||||
print()
|
||||
print(f"n={n}: no admissible level-cycle candidates in search window")
|
||||
return
|
||||
|
||||
min_size = min(len(support) for support in supports)
|
||||
max_size = max(len(support) for support in supports)
|
||||
min_supports = [support for support in supports if len(support) == min_size]
|
||||
pair_overlaps = [
|
||||
len(a & b)
|
||||
for i, a in enumerate(min_supports)
|
||||
for b in min_supports[i:]
|
||||
]
|
||||
print()
|
||||
print(f"n={n}")
|
||||
if stats:
|
||||
print(f" raw (tire,cycle) combos : {stats['raw']}")
|
||||
print(f" canonical (tire,cycle) classes: {stats['canonical']}")
|
||||
print(f" proper C_n colorings : {all_colorings}")
|
||||
print(f" distinct level-cycle supports : {len(supports)}")
|
||||
print(f" most restrictive support : {min_size}/{all_colorings} ({len(min_supports)} support types)")
|
||||
print(f" least restrictive support : {max_size}/{all_colorings}")
|
||||
print(f" overlap among max-restrict : min {min(pair_overlaps)}, max {max(pair_overlaps)}")
|
||||
print(" examples:")
|
||||
|
||||
printed = 0
|
||||
for support, metas in sorted(supports.items(), key=lambda item: (len(item[0]), len(item[1]))):
|
||||
if len(support) != min_size:
|
||||
continue
|
||||
for meta in metas[:examples]:
|
||||
print(
|
||||
f" size={len(support)} m={meta['m']} k={meta['k']} "
|
||||
f"path={meta['path']} chords={describe_chords(meta['chords'])} "
|
||||
f"cycle={meta['cycle']}"
|
||||
)
|
||||
printed += 1
|
||||
if printed >= examples:
|
||||
return
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--n-min", type=int, default=3)
|
||||
parser.add_argument("--n-max", type=int, default=7)
|
||||
parser.add_argument("--outer-min", type=int, default=3)
|
||||
parser.add_argument("--outer-max", type=int, default=7)
|
||||
parser.add_argument("--inner-min", type=int, default=3)
|
||||
parser.add_argument("--inner-max", type=int, default=9)
|
||||
parser.add_argument("--max-chords", type=int, default=None)
|
||||
parser.add_argument("--max-paths", type=int, default=None)
|
||||
parser.add_argument("--examples", type=int, default=4)
|
||||
parser.add_argument(
|
||||
"--no-canonicalize",
|
||||
action="store_true",
|
||||
help="disable tire automorphism deduplication",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--progress",
|
||||
action="store_true",
|
||||
help="emit a one-line update after each (m, k, chord set)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
print("Level-cycle support inside parent tire inner graph O")
|
||||
print(" tested cycles: bounded face cycles of O, excluding O's outer boundary")
|
||||
print(f" outer length range : {args.outer_min}..{args.outer_max}")
|
||||
print(f" inner O size range : {args.inner_min}..{args.inner_max}")
|
||||
print(f" max chords : {args.max_chords if args.max_chords is not None else 'all'}")
|
||||
print(f" max paths/type : {args.max_paths if args.max_paths is not None else 'all'}")
|
||||
|
||||
for n in range(args.n_min, args.n_max + 1):
|
||||
rough_paths = sum(
|
||||
comb(m + k, m)
|
||||
for m in range(args.outer_min, args.outer_max + 1)
|
||||
for k in range(max(args.inner_min, n), args.inner_max + 1)
|
||||
)
|
||||
print(f"computing n={n} (rough path count before chords: {rough_paths})")
|
||||
supports, stats = enumerate_level_cycle_supports(
|
||||
n,
|
||||
args.outer_min,
|
||||
args.outer_max,
|
||||
args.inner_min,
|
||||
args.inner_max,
|
||||
args.max_chords,
|
||||
args.max_paths,
|
||||
canonicalize=not args.no_canonicalize,
|
||||
progress=args.progress,
|
||||
)
|
||||
summarize(n, supports, args.examples, stats)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,241 @@
|
||||
# Level-Cycle Support Findings
|
||||
|
||||
This is the corrected version of the boundary-overlap test.
|
||||
|
||||
The key modeling change is that a tested cycle must appear in the
|
||||
level-cycle/seam role: as a bounded face cycle inside the parent
|
||||
tire's inner outerplanar graph `O`, not as an arbitrary `B_out` or as
|
||||
the outer-face boundary of `O`.
|
||||
|
||||
Executable:
|
||||
|
||||
```bash
|
||||
python3 papers/coloring_nested_tire_graphs/experiments/level_cycle_support.py
|
||||
```
|
||||
|
||||
## Model
|
||||
|
||||
For a parent tire `T = (B_out, O, E_ann)`, enumerate simple bounded
|
||||
face cycles of `O`.
|
||||
|
||||
A cycle is admissible only if:
|
||||
|
||||
- it is a bounded face cycle of `O`;
|
||||
- it is distinct from the outer-face boundary of `O`;
|
||||
- it is tested in this inner/next-level role, never as an outer
|
||||
boundary of a tire at the same level.
|
||||
|
||||
For each admissible cycle `C`, compute the set of proper 4-colorings
|
||||
of `C` that extend to a proper 4-coloring of the parent tire graph.
|
||||
Supports are normalized under color relabeling and dihedral symmetries
|
||||
of `C`.
|
||||
|
||||
## Capped Validation Run
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 -u papers/coloring_nested_tire_graphs/experiments/level_cycle_support.py \
|
||||
--n-min 3 --n-max 6 --outer-max 5 --inner-max 7 --max-chords 2 --max-paths 30
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
n=3
|
||||
proper C_n colorings : 24
|
||||
distinct level-cycle supports : 1
|
||||
most restrictive support : 24/24
|
||||
overlap among max-restrict : 24
|
||||
|
||||
n=4
|
||||
proper C_n colorings : 84
|
||||
distinct level-cycle supports : 3
|
||||
most restrictive support : 60/84
|
||||
overlap among max-restrict : 60
|
||||
|
||||
n=5
|
||||
proper C_n colorings : 240
|
||||
distinct level-cycle supports : 2
|
||||
most restrictive support : 120/240
|
||||
overlap among max-restrict : 120
|
||||
|
||||
n=6
|
||||
proper C_n colorings : 732
|
||||
distinct level-cycle supports : 13
|
||||
most restrictive support : 252/732
|
||||
overlap among max-restrict : 252
|
||||
```
|
||||
|
||||
The most restrictive admissible `n=4` level-cycle support found here
|
||||
has size `60/84`, and there is only one worst support type in this
|
||||
capped window.
|
||||
|
||||
Representative `n=4` admissible witness:
|
||||
|
||||
```text
|
||||
size=60
|
||||
m=3, k=5
|
||||
path=OOOIIIII
|
||||
chords=0-2
|
||||
cycle=(2, 3, 4, 0)
|
||||
```
|
||||
|
||||
Here `O` is a 5-cycle with chord `0-2`. The tested 4-cycle is the
|
||||
bounded face cycle `(2,3,4,0)`, not the outer-face boundary of `O`.
|
||||
|
||||
## Current Interpretation
|
||||
|
||||
With level-cycle admissibility enforced, the first-pass data no longer
|
||||
supports a zero-overlap obstruction at `n=4`. In the capped search,
|
||||
the maximum-restriction support type is unique for `n=3,4,5,6`, so
|
||||
overlap among maximum restrictions is trivially the whole worst
|
||||
support.
|
||||
|
||||
This is not yet a proof. The exact all-path `n=4` run with
|
||||
`--max-chords 2` was stopped after it ran too long interactively. The
|
||||
next implementation step should canonicalize outerplanar `O` and
|
||||
annular paths, or switch to a transfer/DP support computation, before
|
||||
claiming exhaustive results beyond small capped windows.
|
||||
|
||||
## Canonical Tire Quotient
|
||||
|
||||
The script now quotients out the dihedral symmetry of the tire on the
|
||||
rung sequence. Concretely, a tire `T = (m, k, path, chords)` together
|
||||
with a chosen level cycle `C` admits an order-`2(m+k)` action:
|
||||
|
||||
- cyclic shift `s`: "start counting from rung `s` of the original path".
|
||||
This rotates the inner labelling by `-b_s mod k`, where `b_s` is the
|
||||
number of inner steps in the first `s` moves; chords and `C` are
|
||||
carried by the same shift.
|
||||
- reflection: traverse the tire in the opposite direction. The path
|
||||
string reverses and each inner label `j` flips to `(k - j) mod k`,
|
||||
taking chord `(a, b)` to `((k - a) mod k, (k - b) mod k)` and reversing
|
||||
`C`.
|
||||
|
||||
The canonical form of `(T, C)` is the lex-min `(path, chords, cycle)`
|
||||
under that action, with `C` itself further minimised under its own
|
||||
`D_n` (which the support already quotients out, so this stage is free).
|
||||
|
||||
Two `(T, C)` pairs in the same canonical class produce identical
|
||||
normalised supports, so we compute the support once per class. Sanity
|
||||
checks at `n = 3, 4, 5` reproduce the prior capped support sizes
|
||||
(`24/24`, `60/84`, `120/240`) exactly while reducing raw work by
|
||||
roughly 20×–25×.
|
||||
|
||||
## Exhaustive `n=6` Run
|
||||
|
||||
With the canonical quotient in place we can now exhaust a meaningful
|
||||
window for `n = 6`. The smallest admissible inner ring is `k = 7`
|
||||
(a `k`-cycle plus chords cannot host a non-outer bounded `6`-face for
|
||||
`k < 7`).
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 -u papers/coloring_nested_tire_graphs/experiments/level_cycle_support.py \
|
||||
--n-min 6 --n-max 6 --outer-min 3 --outer-max 6 --inner-min 7 --inner-max 8 \
|
||||
--progress --examples 6
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
n=6
|
||||
raw (tire,cycle) combos : 238506
|
||||
canonical (tire,cycle) classes: 9144
|
||||
proper C_6 colorings : 732
|
||||
distinct level-cycle supports : 17
|
||||
most restrictive support : 252/732 (1 support type)
|
||||
least restrictive support : 732/732
|
||||
overlap among max-restrict : 252
|
||||
```
|
||||
|
||||
Compared to the earlier `--outer-max 5 --inner-max 7 --max-chords 2
|
||||
--max-paths 30` capped run, the distinct-support count climbs from
|
||||
`13` to `17`, but the **minimum support size is unchanged at `252/732`
|
||||
and is still realised by a unique support type**. The minimum is
|
||||
witnessed by the same minimal-`O` configuration that already showed up
|
||||
at smaller `n`:
|
||||
|
||||
```text
|
||||
size=252
|
||||
m=3, k=7
|
||||
chords=0-2
|
||||
cycle=(2, 3, 4, 5, 6, 0)
|
||||
```
|
||||
|
||||
i.e. the bounded `6`-face of a `7`-cycle with one chord, sitting inside
|
||||
the thinnest annulus `(m=3)`. Adding outer length (`m` up to `6`),
|
||||
inner length (`k` up to `8`), and unrestricted chord sets produces
|
||||
strictly *less* restrictive supports — never a new floor.
|
||||
|
||||
### Wider sweep: `outer 3..7, inner 7..9`
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
python3 -u papers/coloring_nested_tire_graphs/experiments/level_cycle_support.py \
|
||||
--n-min 6 --n-max 6 --outer-min 3 --outer-max 7 --inner-min 7 --inner-max 9 \
|
||||
--progress --examples 6
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```text
|
||||
n=6
|
||||
raw (tire,cycle) combos : 5662518
|
||||
canonical (tire,cycle) classes: 186818
|
||||
proper C_6 colorings : 732
|
||||
distinct level-cycle supports : 19
|
||||
most restrictive support : 252/732 (1 support type)
|
||||
least restrictive support : 732/732
|
||||
overlap among max-restrict : 252
|
||||
```
|
||||
|
||||
Adding `k = 9` and `m = 7` brings 2 new distinct supports (`17 → 19`),
|
||||
**both strictly above the floor**. The most restrictive support is
|
||||
**still `252/732`, still unique, and still realised by the same
|
||||
minimal-`O` witness** (`m=3, k=7, chord=0-2, cycle=(2,3,4,5,6,0)`).
|
||||
The `m = 3` configurations dominated the floor in every (`m`, `k`)
|
||||
sweep examined.
|
||||
|
||||
### What this changes
|
||||
|
||||
- The "most restrictive support is unique" claim for `n = 6` is now
|
||||
backed by an exhaustive (not capped) sweep over `m ∈ [3, 7]`,
|
||||
`k ∈ [7, 9]`, all chord sets, all annular paths.
|
||||
- The earlier-feared zero-overlap obstruction at `n = 4` does not
|
||||
reappear at `n = 6` either: overlap among maximum-restriction
|
||||
supports is trivially the whole worst support, because there is only
|
||||
one worst support.
|
||||
- The witness for the floor is structurally the same across
|
||||
`n = 3, 4, 5, 6`: a single chord on a `(n+1)`-cycle, embedded in the
|
||||
thinnest annulus, with the tested cycle taken as the bounded
|
||||
`n`-face. That stability is suggestive (it hints that the worst
|
||||
level-cycle support is governed by a small canonical configuration)
|
||||
but is not yet a theorem.
|
||||
|
||||
### Floor-stability-in-`m` conjecture
|
||||
|
||||
Across every searched `n`, the floor witness has been `m = 3`. This
|
||||
is consistent with a **support-monotonicity** picture: subdividing any
|
||||
outer or annular edge of an `m = 3` floor witness produces an `m = 4`
|
||||
tire whose support strictly contains the original (the new degree-`2`
|
||||
vertex adds coloring freedom, and the subdivided edge's endpoints are
|
||||
no longer adjacent, so they may share a color). By induction the
|
||||
floor over `m ≥ 3` is bounded above by the `m = 3` floor.
|
||||
|
||||
The empirical run confirms this bound is tight up to `m ≤ 7, k ≤ 9` at
|
||||
`n = 6`: no `m ≥ 4` tire produced a support strictly smaller than the
|
||||
`m = 3` floor. A theorem-style statement would be:
|
||||
|
||||
> **(Conjecture)** For every `n`, every `k ≥ n + 1`, and every chord
|
||||
> set on `C_k` containing an `n`-face, the level-cycle support floor
|
||||
> over all annular paths and all `m ≥ 3` is achieved at `m = 3`.
|
||||
|
||||
If true, this collapses the exhaustive search to `m = 3` only and
|
||||
opens the door to closed-form support computation for the worst case.
|
||||
The obstruction to a quick proof is exotic `m ≥ 4` configurations not
|
||||
reachable by subdivision from an `m = 3` floor witness — none has
|
||||
appeared empirically, but their non-existence is not yet ruled out.
|
||||
Reference in New Issue
Block a user