"""T19. The mixed-key (Tranchedino-type) family against the fingerprint battery. Plaintext: Latin of the manuscript's genres, section by section (mixedkey.sectioned_plaintext). (1) Map of the family: every point of a parameter grid is generated once into the full layout and scored on every statistic (exploratory: shows whether ANY member reaches the manuscript). (2) Pre-registered test (P1): for each spacing practice, the point nearest the manuscript on the FIT statistics of the training quires is re-generated 20 times and scored on the HELD-OUT statistics. Writes out/mixed_grid.csv, out/mixed_scores.csv. Usage: python3 t_mixed_fingerprint.py [n_sims] [grid|test|both] """ import os, sys _here = os.path.dirname(os.path.abspath(__file__)) # data/analysis, or a subfolder of it sys.path.insert(0, _here if os.path.exists(os.path.join(_here, "paths.py")) else os.path.dirname(_here)) from paths import * # noqa: repository locations import csv, itertools, json, sys, time import numpy as np from harness import structured, split, flatten, log import generators as G, battery as B, mixedkey as MK n_sims = int(sys.argv[1]) if len(sys.argv) > 1 else 20; what = sys.argv[2] if len(sys.argv) > 2 else "both" SPACINGS = sys.argv[3].split(",") if len(sys.argv) > 3 else ["word", "split", "stream"] GRID_STATS = ["order_gain", "heldout_bigram_gain", "page_locality_z", "edge_mi", "ff_rho", "ff_nn5", "rep4", "line_initial_js"] # the map omits the slow near-twin statistics pages = structured(); tr, te = split(pages); lay = G.layout_of(pages); lay_tr = G.layout_of(tr); trw = flatten(tr) target_fit = {k: fn(tr) for k, fn in B.FIT.items()}; target = B.all_stats(pages) GRID = [dict(spacing=sp, k_v=kv, k_c=kc, n_syl=ns, n_tri=nt, n_nom=nn, max_sign_units=mu, p_null=pn, p_syl=0.85) for sp in SPACINGS for kv, kc in ((2, 1), (3, 2), (6, 4)) for ns in (60, 300) for nt in (0, 200) for nn in (0, 80, 300) for mu in (3,) for pn in (0.0, 0.1)] GRID_FN = "out/mixed_grid_" + "_".join(SPACINGS) + ".csv" def fit_dist(st): return sum(((st[k] - target_fit[k]) / (abs(target_fit[k]) + 1e-9)) ** 2 for k in target_fit) ** 0.5 if what in ("grid", "both"): rows = []; t0 = time.time() for i, P in enumerate(GRID): pt = MK.gen_tranchedino_sectioned(trw, lay_tr, seed=0, **P); fs = {k: fn(pt) for k, fn in B.FIT.items()} pf = MK.gen_tranchedino_sectioned(trw, lay, seed=0, **P); st = B.all_stats(pf, GRID_STATS) rows.append(dict(**{k: P[k] for k in P}, fit_distance=round(fit_dist(fs), 4), **{f"fit_{k}": round(v, 4) for k, v in fs.items()}, **{k: round(float(v), 4) for k, v in st.items()})) if i % 5 == 0: print(f"{i}/{len(GRID)} {time.time()-t0:.0f}s", flush=True) with open(GRID_FN, "w", newline="") as f: w = csv.DictWriter(f, fieldnames=rows[0].keys()); w.writeheader(); w.writerows(rows) with open(GRID_FN, "w", newline="") as f: w = csv.DictWriter(f, fieldnames=rows[0].keys()); w.writeheader(); w.writerows(rows) if what in ("test", "both"): import glob as _g grid = [r for fn in _g.glob("out/mixed_grid_*.csv") for r in csv.DictReader(open(fn))] score_rows = [] for sp in SPACINGS: g = sorted([r for r in grid if r["spacing"] == sp], key=lambda r: float(r["fit_distance"]))[0] P = dict(spacing=sp, k_v=int(g["k_v"]), k_c=int(g["k_c"]), n_syl=int(g["n_syl"]), n_tri=int(g["n_tri"]), n_nom=int(g["n_nom"]), max_sign_units=int(g["max_sign_units"]), p_null=float(g["p_null"]), p_syl=0.85) print("fitted", P, "distance", g["fit_distance"], flush=True) sims = [B.all_stats(MK.gen_tranchedino_sectioned(trw, lay, seed=100 + s, **P)) for s in range(n_sims)] for k in target: v = np.array([float(x[k]) for x in sims]); mu, sd = v.mean(), v.std() + 1e-9; z = (float(target[k]) - mu) / sd score_rows.append(dict(generator=f"mixed_{sp}", params=json.dumps(P), statistic=k, manuscript=round(float(target[k]), 4), sim_mean=round(mu, 4), sim_sd=round(sd, 4), z=round(z, 2), fails=abs(z) > 2)) print(" " + " ".join(f"{r['statistic']}={r['sim_mean']:.3f}({r['z']:+.1f})" for r in score_rows if r["generator"] == f"mixed_{sp}"), flush=True) with open("out/mixed_scores.csv", "w", newline="") as f: w = csv.DictWriter(f, fieldnames=score_rows[0].keys()); w.writeheader(); w.writerows(score_rows) held = list(B.HELD) summ = {sp: [r["statistic"] for r in score_rows if r["generator"] == f"mixed_{sp}" and r["fails"] and r["statistic"] in held] for sp in ("word", "split", "stream")} log("H-CIPHER-MIXED", "T19-mixed-fingerprint", "held-out statistics failed by the fitted mixed key, per spacing practice (|z|>2 of 12)", "; ".join(f"{sp}: {len(v)}" for sp, v in summ.items()), f"{n_sims} simulations each; section-matched Latin plaintext", "see note", "out/mixed_scores.csv")