diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/even_program_findings.md b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/even_program_findings.md index dfb3283..840c016 100644 --- a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/even_program_findings.md +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/even_program_findings.md @@ -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 diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep.py b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep.py new file mode 100644 index 0000000..fa573c1 --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep.py @@ -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() diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep_results.txt b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep_results.txt new file mode 100644 index 0000000..6874dcd --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/residue_phase_sweep_results.txt @@ -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)