"""T1. Profile screen: where does Voynichese fall among 103 languages (plain and vowel-less) on 14 statistics? Outputs percentile ranks per statistic, a nearest- language ranking on z-scored distance, and the set of statistics on which the manuscript lies outside the attested range (decision rule: outside 2.5–97.5 pct). """ 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 json, csv, math, statistics as st import numpy as np from harness import * C = corpus(); pages = structured() V = flatten(pages) pv = profile(V); pvu = profile(V, units=True) pA = profile(flatten(structured(lang="A"))); pB = profile(flatten(structured(lang="B"))) langs = languages() rows = {} for name, ws in langs.items(): rows[name] = profile(ws[:len(V)]) keys = list(pv.keys()) plain = {k: v for k, v in rows.items() if not k.endswith(".novowel")} nov = {k: v for k, v in rows.items() if k.endswith(".novowel")} out = [] for k in keys: vals = np.array([r[k] for r in plain.values()]); nv = np.array([r[k] for r in nov.values()]) pct = (vals < pv[k]).mean() * 100; pctn = (nv < pv[k]).mean() * 100 lo, hi = np.percentile(vals, [2.5, 97.5]) out.append(dict(statistic=k, voynich=pv[k], voynich_units=pvu[k], voynich_A=pA[k], voynich_B=pB[k], lang_min=vals.min(), lang_p2_5=lo, lang_median=np.median(vals), lang_p97_5=hi, lang_max=vals.max(), pct_rank_plain=pct, pct_rank_novowel=pctn, outside_range=(pv[k] < lo or pv[k] > hi))) with open("out/profile_screen.csv", "w", newline="") as f: w = csv.DictWriter(f, fieldnames=out[0].keys()); w.writeheader(); w.writerows(out) for r in out: print(f"{r['statistic']:14s} V={r['voynich']:.3f} (units {r['voynich_units']:.3f}) langs [{r['lang_min']:.3f} .. {r['lang_max']:.3f}] median {r['lang_median']:.3f} pct {r['pct_rank_plain']:.0f} {'OUTSIDE' if r['outside_range'] else ''}") # nearest languages on z-scored distance (plain + novowel pooled), two feature sets: # (a) all 14; (b) character-level only; (c) word-level only allv = np.array([[r[k] for k in keys] for r in rows.values()]); names = list(rows.keys()) mu, sd = allv.mean(0), allv.std(0) + 1e-9 def rank(feat, target): idx = [keys.index(k) for k in feat] z = (allv[:, idx] - mu[idx]) / sd[idx]; t = (np.array([target[k] for k in feat]) - mu[idx]) / sd[idx] d = np.sqrt(((z - t) ** 2).sum(1)) return sorted(zip(d, names)) sets = {"all": keys, "character-level": ["alphabet", "h1", "h2", "mean_len", "sd_len", "short_words"], "word-level": ["ttr", "hapax", "zipf", "top1", "top10", "word_h2", "adj_repeat"]} res = {} for sname, feat in sets.items(): for tname, target in [("Voynich", pv), ("Voynich (glyph units)", pvu)]: r = rank(feat, target); res[f"{sname}|{tname}"] = [(n, round(float(d), 2)) for d, n in r[:12]] print(f"\n{sname} / {tname}: " + ", ".join(f"{n} {d:.2f}" for d, n in r[:10])) json.dump(res, open("out/profile_nearest.json", "w"), indent=1) with open("out/profile_languages.csv", "w", newline="") as f: w = csv.writer(f); w.writerow(["language"] + keys) w.writerow(["Voynich"] + [pv[k] for k in keys]); w.writerow(["Voynich-units"] + [pvu[k] for k in keys]) for n, r in rows.items(): w.writerow([n] + [r[k] for k in keys]) outside = [r["statistic"] for r in out if r["outside_range"]] log("H-LANG-ANY", "T1-profile", "statistics outside attested range", ";".join(outside), "103 languages ×2 forms", "see note", "Voynich lies outside the 95% range of attested languages on these statistics")