Enumerate colour/tread phase over the residue graphs

residue_phase_sweep.py exhaustively enumerates the two colouring control knobs
-- the per-annulus tread phase {0,1}^A and the root-DFS colour order perms(0,1,2)
-- on top of every insertion-site combo, for the graphs the random-phase site
sweep still fails. canonical_coloring_explicit makes this deterministic.

Result (residue_phase_sweep_results.txt): the two hub graphs are RESCUED once
phase is enumerated rather than sampled (so the random-phase fail count overstates
difficulty); the genuine obstructions that survive sites x phases x colour-orders
are exactly the face-leaf graphs (terminal-triangle leaf gadget). Smallest is
seed2 #26 [3,6,3] face (1 combo, 24 settings, all fail at gadget-removal) -- a
minimal obstruction target. Caveat: try_establish is a bounded local Kempe search,
so STILL FAILS means unreachable by the bounded search from canonical-even over
all knob settings, not that no Kempe path exists.

Findings note updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 00:03:49 -04:00
parent 9d296eb9c8
commit faf9e01139
3 changed files with 209 additions and 0 deletions
@@ -85,6 +85,52 @@ knobs. Building this joint solver — and finding the smallest configuration, if
forcing a self-loop or odd cycle — is the next step and the exact thing a proof
would need to rule out.
## Exhausting the control knobs over the residue
The site sweep above counts a graph `ok` if some placement works over only **4
random** colour phases per combo. `residue_phase_sweep.py` takes the graphs that
sweep still fails (the residue) and **exhaustively enumerates the colour/tread
phase and the root-DFS colour order** on top of every insertion site:
```
phases in {0,1}^A (A = # non-root annuli; the tread phases)
colorder in perms(0,1,2) (root-region DFS colour priority)
```
over all site combos (cap 512). Result on the seed-1/seed-2 residue
(`residue_phase_sweep_results.txt`):
```
seed1 #16 [3,8,3,5] hub RESCUED (720 settings, 18 ok)
seed1 #51 [3,7,3,3,7] hub RESCUED (42336 settings, 196 ok)
seed1 #3 [3,7,4,6,3] face STILL FAILS (672 settings, 0 ok)
seed1 #4 [3,4,5,5,3] face STILL FAILS (2400 settings, 0 ok)
seed2 #26 [3,6,3] face STILL FAILS (24 settings, 0 ok)
seed2 #30 [3,3,6,7,3] face STILL FAILS (2016 settings, 0 ok)
seed2 #54 [3,3,5,3] face STILL FAILS (720 settings, 0 ok)
```
Two things fall out:
1. **Phase reachability explains part of the residue.** The two `hub` graphs are
*rescued* once the phase/colour-order is enumerated rather than sampled — they
were never genuine obstructions, just unlucky random phases. So the
random-phase `fail` count overstates the true difficulty.
2. **The genuine obstructions are exactly the `face`-leaf graphs.** Every graph
that survives exhausting sites × phases × colour-orders has `leaf='face'`
i.e. an inner terminal triangle carrying a leaf gadget. The smallest is
`seed2 #26 [3,6,3]` (one site combo, 24 settings, all fail at
`gadget-removal`): a minimal target for the joint solver / an obstruction
hunt. (#26 fails at the gadget step; #3/#4/#30/#54 at `diamond-switch`.)
**Caveat on "STILL FAILS".** `try_establish` is a *bounded* local Kempe search
(≤3 components anchored at the quad support). So `STILL FAILS` means *no (site,
phase, colour-order) lets the bounded search from the canonical-even colouring
reach a descendable one* — not that no Kempe path exists. A descendable colouring
provably exists (M(G) is 3-colourable); whether it is reachable under a principled
(joint, unbounded) switch is the open question, now sharply localised to the
`face`-leaf family.
## Caveats / domain
- Real plantri triangulations mostly `skip:chord-level-edge` under BFS-from-outer
@@ -0,0 +1,154 @@
"""Exhaustively enumerate the colour/tread-phase control knobs over the residue
graphs -- the synthetic ring triangulations the SITE sweep still fails on while
only *sampling* random phases (kempe_even_program_harness.run_graph draws 4
random phase vectors + colour orders per site combo).
For each residue graph we sweep, per insertion-site combo:
phases in {0,1}^A (A = number of non-root annuli; the tread phases)
colorder in perms(0,1,2) (the root-region DFS colour priority)
and ask whether SOME (combo, phases, colorder) yields a full reduction. This
isolates how much of the residue is a phase-reachability artifact vs a genuine
obstruction.
Usage: python3 residue_phase_sweep.py # the seed1/seed2 residue
python3 residue_phase_sweep.py SEED IDX # one graph
"""
import sys
import random
import itertools as it
import kempe_even_program_harness as H
# residue from the random-phase site sweep (seed, idx) -> reported failure
RESIDUE = [(1, 3), (1, 4), (1, 16), (1, 51), (2, 26), (2, 30), (2, 54)]
MAX_COMBOS = 512
def reconstruct(seed, idx):
"""Replay the synthetic loop to get graph #idx (rng state is sequential)."""
rng = random.Random(seed)
g = outer = sizes = leaf = None
for _ in range(idx + 1):
sizes, leaf = H.random_profile(rng)
g, outer = H.ring_triangulation(sizes, leaf, rng)
return g, outer, sizes, leaf
def descend_explicit(template, outer, orig, gadgets, combo, phases, colorder):
gg = template.copy()
dia = [gg.insert_diamond(a, b) for (a, b) in combo]
an = H.Analysis(gg, outer)
if an.degenerate:
return f"skip:post-diamond-{an.degenerate}"
if any(len(c) % 2 for _, c in an.seams):
return "fail:seam-evening"
col, reason = H.canonical_coloring_explicit(gg, an.level, outer, phases, colorder)
if col is None:
return f"fail:canonical-{reason}"
for (w, u, v, x, t) in dia:
adj = H.medial_adj(gg)
quad = H.quad_of(gg, w, u, v)
if quad is None:
return "fail:diamond-quad"
support = [H.e(quad[i], quad[(i + 1) % 4]) for i in range(4)]
if not H.try_establish(col, adj, support,
lambda c: H.diamond_condition(c, quad) is not None):
return "fail:diamond-switch"
H.collapse_degree4(gg, col, w, u, v)
if not H.verify_proper(gg, col):
return "fail:improper-after-diamond"
for (y, z, u, v, x, t) in gadgets:
done = False
for first, second, tri in ((z, y, (x, u, v)), (y, z, (u, v, t))):
gt, ct = gg.copy(), dict(col)
adj = H.medial_adj(gt)
quad = H.quad_of(gt, first, u, v)
if quad is None:
continue
support = [H.e(quad[i], quad[(i + 1) % 4]) for i in range(4)]
if not H.try_establish(ct, adj, support,
lambda c: H.diamond_condition(c, quad) is not None):
continue
H.collapse_degree4(gt, ct, first, u, v)
if not H.verify_proper(gt, ct):
continue
adj2 = H.medial_adj(gt)
a_, b_, c_ = tri
support2 = [H.e(a_, b_), H.e(b_, c_), H.e(c_, a_)]
if not H.try_establish(ct, adj2, support2,
lambda c: H.rainbow_condition(c, tri)):
continue
H.collapse_degree3(gt, ct, second)
if not H.verify_proper(gt, ct):
continue
gg, col = gt, ct
done = True
break
if not done:
return "fail:gadget-removal"
if set(gg.rot) != orig:
return "fail:vertex-mismatch"
if not H.verify_proper(gg, col):
return "fail:final-improper"
return "ok"
def phase_arity(template, outer, combo):
gg = template.copy()
for (a, b) in combo:
gg.insert_diamond(a, b)
an = H.Analysis(gg, outer)
sk, _ = H.coloring_skeleton(gg, an.level, outer)
return len(sk['nonroot']) if sk else None
def sweep(seed, idx):
g, outer, sizes, leaf = reconstruct(seed, idx)
orig = set(g.rot)
prep = H._prep_gadgets(g.copy(), outer)
if isinstance(prep, str):
print(f"seed{seed} #{idx} {sizes} {leaf}: prep -> {prep}")
return
template, an_g, gadgets = prep
sites = H._candidate_sites(an_g)
if sites is None:
print(f"seed{seed} #{idx} {sizes} {leaf}: no diamond site")
return
combos = list(it.product(*sites))
n_full = len(combos)
truncated = n_full > MAX_COMBOS
combos = combos[:MAX_COMBOS]
tried = wins = 0
first = None
for combo in combos:
nph = phase_arity(template, outer, combo)
if nph is None:
continue
for phases in it.product((0, 1), repeat=nph):
for colorder in it.permutations((0, 1, 2)):
tried += 1
res = descend_explicit(template, outer, orig, gadgets,
combo, phases, list(colorder))
if res == "ok":
wins += 1
if first is None:
first = (combo, phases, colorder)
verdict = "RESCUED" if wins else "STILL FAILS"
msg = (f"seed{seed} #{idx} {sizes} {leaf}: {verdict} "
f"(combos={n_full}{'[capped]' if truncated else ''}, "
f"settings tried={tried}, ok={wins})")
if first:
msg += f"\n first ok: site={first[0]} phases={first[1]} colorder={first[2]}"
print(msg)
def main():
if len(sys.argv) == 3:
sweep(int(sys.argv[1]), int(sys.argv[2]))
else:
for seed, idx in RESIDUE:
sweep(seed, idx)
sys.stdout.flush()
if __name__ == "__main__":
main()
@@ -0,0 +1,9 @@
seed1 #3 [3, 7, 4, 6, 3] face: STILL FAILS (combos=7, settings tried=672, ok=0)
seed1 #4 [3, 4, 5, 5, 3] face: STILL FAILS (combos=25, settings tried=2400, ok=0)
seed1 #16 [3, 8, 3, 5] hub: RESCUED (combos=15, settings tried=720, ok=18)
first ok: site=((11, 13), (14, 15)) phases=(0, 1, 0) colorder=(0, 1, 2)
seed1 #51 [3, 7, 3, 3, 7] hub: RESCUED (combos=441, settings tried=42336, ok=196)
first ok: site=((6, 7), (10, 11), (14, 15), (21, 22)) phases=(0, 0, 1, 0) colorder=(0, 1, 2)
seed2 #26 [3, 6, 3] face: STILL FAILS (combos=1, settings tried=24, ok=0)
seed2 #30 [3, 3, 6, 7, 3] face: STILL FAILS (combos=21, settings tried=2016, ok=0)
seed2 #54 [3, 3, 5, 3] face: STILL FAILS (combos=15, settings tried=720, ok=0)