"""Why does the analogy entry come late on the flat families? (Valeri, 2026-09-21) The entry is flag AND _analogous_and_steep(cur, memory). The method calls the second only when the flag has risen on a model step (short-circuit `and`), so a spy on it sees exactly the flag moments. At each one it records how many evaluations of f were spent and which condition held back the entry: no-config the last three points give no (xi, phi) no-memory nothing to compare with yet unlike-past the configuration is nearer to the line than to any past one m<2 analogous, but the pair's similarity exponent is below 2 (value kept) ENTER Compared with flatstickexit, which enters at the first flag. Nothing in the method changes; problems are the 20 flat CHA problems (fun3-6), proposed rule (projection). python -u diag_analogy_late.py """ from __future__ import annotations import math from collections import Counter as Tally import simroot.pure as P from sweep_full import MAXFEV, XTOL, load_problems FLAT = ("fun3", "fun4", "fun5", "fun6") _orig = P._analogous_and_steep LOG = [] CALLS = [0] def reason(cur, past): if cur is None: return "no-config", None if not past: return "no-memory", None xi, phi = cur[0], cur[1] best = min(past, key=lambda q: math.hypot(xi - q[0], phi - q[1])) d_past = math.hypot(xi - best[0], phi - best[1]) d_line = abs(phi - xi) / math.sqrt(2.0) if not d_past < d_line: return "unlike-past", (d_past, d_line) lx, lf = cur[2] / best[2], cur[3] / best[3] m = math.log(lf) / math.log(lx) if lx not in (0.0, 1.0) and lf > 0.0 else math.nan return ("ENTER" if m >= 2.0 else "m<2"), m def spy(cur, past): ok = _orig(cur, past) why, val = reason(cur, past) LOG.append((CALLS[0], why, val, len(past), ok)) return ok def counted(f): def g(x): CALLS[0] += 1 return f(x) return g def run(f, a, b, gate): st = {} CALLS[0] = 0 _x, n, _ok = P.pure_root(counted(f), a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard="project", prestep=gate, stats=st) return n def main(): P._analogous_and_steep = spy tally = Tally() ms_before = [] print(f"{'problem':<10} {'exit: n':>7} {'analog: n':>9} {'first flag @':>12} " f"{'entry @':>8} reasons before entry") for s, pid, f, a, b, _root in load_problems(): if s != "CHA" or pid.split(".")[0] not in FLAT: continue n_exit = run(f, a, b, "flatstickexit") LOG.clear() n_an = run(f, a, b, "flatanalog") first = LOG[0][0] if LOG else None entry = next((e for e, why, *_ in LOG if why == "ENTER"), None) before = [(why, val) for e, why, val, _, _ in LOG if entry is None or e < entry] for why, val in before: tally[why] += 1 if why == "m<2" and val is not None and math.isfinite(val): ms_before.append(val) print(f"{pid:<10} {n_exit:7d} {n_an:9d} {str(first):>12} {str(entry):>8} " + " ".join(why for why, _ in before)) print("\nreasons at flag moments before entry, all 20 problems:", dict(tally)) if ms_before: ms = sorted(ms_before) print("m read at analogous-but-refused moments:", " ".join(f"{m:.2f}" for m in ms)) print("diag done") if __name__ == "__main__": main()