"""Detectability check for PREREG_empty_step_2026-09-21.md: empty step (E) and repeating pattern with the period chosen from the data (R). Method untouched - states come from the behaviour-neutral trace hook. python -u diag_empty_step.py """ from __future__ import annotations from scipy.optimize._tstutils import _APS_TESTS_DICTS, _CHANDRUPATLA_TESTS_DICTS from diag_contraction_similarity import CONFIGS, FLAT, auc from simroot.pure import pure_root XTOL, MAXFEV, ACC = 1e-12, 200, 1e-6 def ratio_sim(a, b): hi, lo = max(a, b), min(a, b) return lo / hi if hi > 0.0 else 1.0 def features(f, a, b, r, cfg): """(S_E, S_R) at every traced state; S_R is None until two repeats fit the history.""" st = {"trace": []} x, _n, conv = pure_root(f, a, b, XTOL, MAXFEV, stats=st, **cfg) if not (conv and abs(float(x) - r) <= ACC): return [] out, rhos = [], [] for s in st["trace"]: if s["xp2"] is None or s["fx"] == 0.0 or s["fp"] == 0.0: continue rho = abs(s["fx"] / s["fp"]) if not (rho == rho) or rho == float("inf"): continue rhos.append(rho) s_e = ratio_sim(abs(s["fx"]), abs(s["fp"])) s_r = None k = len(rhos) best = None for p in range(2, k // 2 + 1): # at least two repeats must fit the history sims = [ratio_sim(rhos[j], rhos[j - p]) for j in range(p, k)] v = sum(sims) / len(sims) if best is None or v > best: best = v s_r = best out.append((s_e, s_r)) return out def main(): out = {} for cname, cfg in CONFIGS.items(): groups = {"APS": [], "CHA fun1": [], "CHA flat fun3-6": [], "CHA other": []} for d in _APS_TESTS_DICTS: a, b = (float(v) for v in d["bracket"]) f = (lambda ff, aa: lambda t: ff(t, *aa))(d["f"], d.get("args", ())) groups["APS"] += features(f, a, b, float(d["root"]), cfg) for d in _CHANDRUPATLA_TESTS_DICTS: a, b = (float(v) for v in d["bracket"]) fam = str(d["ID"]).split(".")[0] g = ("CHA flat fun3-6" if fam in FLAT else "CHA fun1" if fam == "fun1" else "CHA other") groups[g] += features(d["f"], a, b, float(d["root"]), cfg) print(f"\n== {cname}") print(f" {'group':<18} {'states':>7} {'E: S_E > 1/2':>13} {'R: S_R > 1/2':>13}" f" {'(R defined)':>12}") res = {"rates": {}, "auc": {}} for g, v in groups.items(): n = max(len(v), 1) vr = [x[1] for x in v if x[1] is not None] e_rate = 100.0 * sum(x[0] > 0.5 for x in v) / n r_rate = 100.0 * sum(y > 0.5 for y in vr) / max(len(vr), 1) res["rates"][g] = (len(v), e_rate, len(vr), r_rate) print(f" {g:<18} {len(v):7d} {e_rate:12.1f}% {r_rate:12.1f}% {len(vr):12d}") flat = groups["CHA flat fun3-6"] print(" AUC, flat states against: E R") for g in ("APS", "CHA fun1", "CHA other"): ae = auc([x[0] for x in flat], [x[0] for x in groups[g]]) ar = auc([x[1] for x in flat if x[1] is not None], [x[1] for x in groups[g] if x[1] is not None]) res["auc"][g] = (ae, ar) print(f" {g:<30} {ae:.3f} {ar:.3f}") out[cname] = res print("\nbars: m>=2 fires 0.2% canonical / 5.8% flat; successive-ratio similarity AUC" " 0.28-0.42") return out if __name__ == "__main__": main()