From c56da7bb23a7579d557f8f9bc53b504cb2dcf731 Mon Sep 17 00:00:00 2001 From: didericis Date: Thu, 11 Jun 2026 22:47:46 -0400 Subject: [PATCH] Add interface-admissibility probe; confirm parity characterization at n=12 For each interface size m, compare the realized census vocabulary (outer up-tooth apexes and inner singleton-down apexes) against the full parity-admissible set. At n=12, m=3..8 every parity-admissible sequence is realized on both faces (counts 1,4,10,31,91,274; none missing), and up==down throughout -- the n=9 result is n-independent and scales to m=8. Validated against the known n=9 answer before running n=12. Co-Authored-By: Claude Opus 4.8 --- .../kempe_interface_admissibility_probe.py | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 papers/medial_tire_decompositions_of_plane_triangulations/experiments/kempe_interface_admissibility_probe.py diff --git a/papers/medial_tire_decompositions_of_plane_triangulations/experiments/kempe_interface_admissibility_probe.py b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/kempe_interface_admissibility_probe.py new file mode 100644 index 0000000..0f28fe8 --- /dev/null +++ b/papers/medial_tire_decompositions_of_plane_triangulations/experiments/kempe_interface_admissibility_probe.py @@ -0,0 +1,105 @@ +"""Does the realized gluing-interface vocabulary still equal the full +parity-admissible set at larger n? + +For a given n, and each interface size m, compare: + * REALIZED: the distinct canonical apex colour sequences (census reading, + i.e. orientation-honest) that Kempe-balanced colourings actually present on + an interface of m apexes -- separately for the outer face (up-tooth apexes) + and an inner face (singleton down-tooth apexes on one face); + * ADMISSIBLE: every colour-permutation-canonical length-m sequence whose three + colour counts share m's parity (the outer-face Kempe-parity necessary + condition). + +At n=9 realized == admissible for every m (3..6), and up == down. This probe +checks whether that persists. Summary numbers only -- no notes, no figures. + +Run: python3 kempe_interface_admissibility_probe.py --n 12 --m 3 4 5 6 +""" + +from __future__ import annotations + +import argparse +import itertools +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_up_tooth_sequences import ( + canonical_sequence, + dihedral_reading_sequences, + seq_str, +) + + +def admissible_sequences(m: int) -> set[tuple[int, ...]]: + """Every canonical length-m sequence with all three colour counts sharing + m's parity (the parity-admissible interface alphabet).""" + out: set[tuple[int, ...]] = set() + for combo in itertools.product((0, 1, 2), repeat=m): + counts = [combo.count(k) for k in (0, 1, 2)] + if all(c % 2 == m % 2 for c in counts): + out.add(canonical_sequence(combo)) + return out + + +def realized(n: int, m: int): + """(up_set, down_set): census sequences realized on outer / inner interfaces + of exactly m apexes, over all Kempe-balanced colourings of all M(T).""" + up: set[tuple[int, ...]] = set() + down: set[tuple[int, ...]] = set() + up_graphs = down_configs = 0 + 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: + up_graphs += 1 + for c in valid: + up |= dihedral_reading_sequences(n, c, g.up_edges, "u") + for edges in down_faces: + down_configs += 1 + for c in valid: + down |= dihedral_reading_sequences(n, c, edges, "d") + return up, down, up_graphs, down_configs + + +def run(args): + n = args.n + print(f"n={n}: realized gluing-interface vocabulary vs full parity-admissible set\n") + header = f"{'m':>2} {'admiss':>7} | {'up real':>8} {'up=adm':>7} {'#M(T)':>6} | " \ + f"{'dn real':>8} {'dn=adm':>7} {'#cfg':>6} | {'up=dn':>6} {'sec':>5}" + print(header) + print("-" * len(header)) + for m in args.m: + t0 = time.time() + adm = admissible_sequences(m) + up, down, ng, nc = realized(n, m) + dt = time.time() - t0 + print(f"{m:>2} {len(adm):>7} | {len(up):>8} {str(up == adm):>7} {ng:>6} | " + f"{len(down):>8} {str(down == adm):>7} {nc:>6} | " + f"{str(up == down):>6} {dt:>5.0f}") + sys.stdout.flush() + if up != adm: + missing = sorted(seq_str(s) for s in (adm - up)) + print(f" up missing {len(missing)}: {missing[:12]}{' ...' if len(missing) > 12 else ''}") + if down != adm: + missing = sorted(seq_str(s) for s in (adm - down)) + print(f" down missing {len(missing)}: {missing[:12]}{' ...' if len(missing) > 12 else ''}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--n", type=int, default=12) + parser.add_argument("--m", type=int, nargs="+", default=[3, 4, 5, 6]) + run(parser.parse_args()) + + +if __name__ == "__main__": + main()