"""Numerical / symbolic checks accompanying the handwritten proofs in results/derivations.md. CS6 boundary identity: direct |dX| equals the layer formula |((U-1) u (U+1) u V) \\ U| + |((V-5) u (V+5) u U) \\ V| (k = 5), random X. T8 deletion map of Lemma P9 (k = 5): for random X with >= 11 consecutive empty columns, deleting the middle column of the run preserves |X| and |dX|. P6 the modular identity behind the isomorphism psi (sympy, symbolic in j). L2 the counting inequalities of the finite reduction (sympy, exact integer arithmetic over the relevant ranges). P7 index bookkeeping of the 12-vertex construction (sympy inequalities). Output: results/lemma_checks.json and a line in results/experiment-log.jsonl. """ import json import os import random import common import sympy as sp from graph_constructor import adjacency, edges_formula, u, v K = 5 def boundary_direct(n, k, X): adj = adjacency(n, k) Xs = set(X) return {w for x in X for w in adj[x] if w not in Xs} def boundary_formula(n, k, X): U = {x for x in X if x < n} V = {x - n for x in X if x >= n} outer = ({(i - 1) % n for i in U} | {(i + 1) % n for i in U} | V) - U inner = ({(i - k) % n for i in V} | {(i + k) % n for i in V} | U) - V return len(outer) + len(inner) def cs6(trials=300, seed=6): rng = random.Random(seed) bad = 0 total = 0 for n in range(13, 31): for _ in range(trials): X = rng.sample(range(2 * n), rng.randint(1, 2 * n - 1)) total += 1 if len(boundary_direct(n, K, X)) != boundary_formula(n, K, X): bad += 1 return {"total": total, "mismatches": bad} def delete_column(n, k, X, col): """Deletes column col (both vertices must be outside X), reindexes columns > col down by one.""" assert all((x % n) != col for x in X) def f(i): return i if i < col else i - 1 X2 = [] for x in X: layer, i = (0, x) if x < n else (1, x - n) X2.append(f(i) if layer == 0 else (n - 1) + f(i)) return X2 def t8(trials=200, seed=8): rng = random.Random(seed) bad = 0 total = 0 for n in range(2 * K + 3, 41): for _ in range(trials): # choose a run of 11 consecutive empty columns starting at a random column a a = rng.randrange(n) empty = {(a + t) % n for t in range(2 * K + 1)} allowed = [w for w in range(2 * n) if (w % n) not in empty] if not allowed: continue X = rng.sample(allowed, rng.randint(1, len(allowed))) # rotate so that the empty run is columns 0..2k, then delete column k (P9 statement) shift = (-a) % n Xr = [((x + shift) % n) if x < n else n + ((x - n + shift) % n) for x in X] assert all((x % n) in range(2 * K + 1) or True for x in Xr) X2 = delete_column(n, K, Xr, K) total += 1 if len(X2) != len(Xr) or len(boundary_direct(n - 1, K, X2)) != len(boundary_direct(n, K, Xr)): bad += 1 return {"total": total, "mismatches": bad} def p6_symbolic(): """For k*l = 1 (mod n): (j+l)*k = j*k + 1 (mod n) symbolically in j: (j+l)k - (jk+1) = lk - 1 = 0 mod n.""" j, k, l, n, m = sp.symbols("j k l n m", integer=True) expr = sp.expand((j + l) * k - (j * k + 1)) ok_symbolic = sp.simplify(expr - (l * k - 1)) == 0 # numeric instances for k = 5, 11 <= n <= 60, gcd(n,5) = 1 inst = [] for nn in range(11, 61): if sp.gcd(nn, 5) != 1: continue ll = pow(5, -1, nn) inst.append(all(((jj + ll) * 5 - (jj * 5 + 1)) % nn == 0 for jj in range(nn))) return {"symbolic_identity": bool(ok_symbolic), "numeric_instances": len(inst), "numeric_all_ok": all(inst)} def l2_symbolic(): """2c - p <= 11 => c <= floor((p+11)/2); gaps <= 10 => n <= 11 c; forces count 2n - s >= p iff n >= (p+s)/2.""" c, p, n, s = sp.symbols("c p n s", integer=True) rows = [] for pp in range(12, 31): cmax = sp.floor((pp + 11) / sp.Integer(2)) # check: every integer c with 2c - pp <= 11 satisfies c <= cmax, and c = cmax satisfies it ok = all((2 * cc - pp <= 11) == (cc <= cmax) for cc in range(0, 60)) nmax = 11 * cmax nmin_forces = sp.ceiling((pp + 11) / sp.Integer(2)) ok2 = all((2 * nn - 11 >= pp) == (nn >= nmin_forces) for nn in range(0, 200)) rows.append({"p": pp, "c_max": int(cmax), "N_max": int(nmax), "n_min_forces": int(nmin_forces), "ok": bool(ok and ok2)}) return rows def p7_symbolic(): """For j >= 12: j - 10 >= 2 and j - 5 >= 7, so the other neighbours of u_{j-1}, v_{j-5} lie in columns < j.""" j = sp.symbols("j", integer=True) return {"j-10>=2 for j>=12": bool(sp.simplify((j - 10) - 2).subs(j, 12) >= 0) and all((jj - 10) >= 2 for jj in range(12, 500)), "j-5>=7 for j>=12": all((jj - 5) >= 7 for jj in range(12, 500))} if __name__ == "__main__": out = {"CS6_boundary_identity": cs6(), "T8_deletion_map": t8(), "P6_isomorphism_identity": p6_symbolic(), "L2_counting": l2_symbolic(), "P7_index_bookkeeping": p7_symbolic(), "sympy_version": sp.__version__, "utc": common.utcnow()} with open(os.path.join(common.RESULTS, "lemma_checks.json"), "w") as f: json.dump(out, f, indent=1) common.log_event("D", "lemma_checks", CS6=out["CS6_boundary_identity"], T8=out["T8_deletion_map"], P6=out["P6_isomorphism_identity"], L2_all_ok=all(r["ok"] for r in out["L2_counting"]), P7=out["P7_index_bookkeeping"]) print(json.dumps({k: v for k, v in out.items() if k != "L2_counting"}, indent=1)) print("L2 rows ok:", all(r["ok"] for r in out["L2_counting"]), [(r["p"], r["c_max"], r["N_max"], r["n_min_forces"]) for r in out["L2_counting"] if r["p"] in (16, 18, 20, 22, 24)])