"""Premise check for entry into the flat regime by ANALOGY (Valeri, 2026-09-21: "рассмотри задачу входа в режим на плоских функциях с точки зрения похожести или аналогичности"). A pure power f = c(x-r)^m is self-similar: zooming about the root maps every configuration of points onto an earlier one (x scaled by L, f by L^m). So the DIMENSIONLESS configuration of the last three points, xi = (x_k - x_{k-1}) / (x_{k-2} - x_{k-1}), phi = (f_k - f_{k-1}) / (f_{k-2} - f_{k-1}), RECURS. At a simple root, zooming in makes f look like its tangent, and the configuration approaches the LINEAR prototype phi = xi (exact for a linear f) instead. The static distance to the line cannot tell them apart (fun1 on a wide bracket looks as non-linear as x^9 - diag_contraction_similarity.py); the question is which prototype the configuration is NEARER to: the line, or its own past. Nearest-prototype judgment, ordinal, no threshold: d_line = |phi - xi| / sqrt(2) (distance to the line phi = xi) d_past = min over earlier states j of |(xi, phi)_k - (xi, phi)_j| (Euclidean) analogous-to-past <=> d_past < d_line Measured at the moments the unkept-promise flag rises (model step, no crossing, r >= 1/4), on the proposed rule (projection) with no regime, via the behaviour-neutral trace hook. Reported per PROBLEM (entry happens once per problem), per group. Nothing in the method changes. python -u diag_flat_analogy.py """ from __future__ import annotations import math from collections import defaultdict from simroot.pure import pure_root from sweep_full import MAXFEV, XTOL, load_problems FLAT = ("fun3", "fun4", "fun5", "fun6") def config(s): x, fx, xp, fp, xp2, fp2 = s["x"], s["fx"], s["xp"], s["fp"], s["xp2"], s["fp2"] if xp2 is None or xp2 == xp or fp2 == fp: return None xi = (x - xp) / (xp2 - xp) phi = (fx - fp) / (fp2 - fp) if not (math.isfinite(xi) and math.isfinite(phi)): return None return xi, phi def group(s, pid): if s == "APS": return "APS" fam = pid.split(".")[0] return "CHA-flat" if fam in FLAT else f"CHA-{fam}" def pair_order(cur, old): """The order the analogy itself implies: between two analogous states the x-scale shrank by L and the f-scale by L^m, so m = ln(f ratio) / ln(x ratio). Scales are the last step |x_k - x_(k-1)| and its |f_k - f_(k-1)|. None when undefined.""" lx = cur[2] / old[2] if old[2] > 0.0 else math.nan lf = cur[3] / old[3] if old[3] > 0.0 else math.nan if not (math.isfinite(lx) and math.isfinite(lf)) or lx <= 0.0 or lf <= 0.0 or lx == 1.0: return None return math.log(lf) / math.log(lx) def main(): per = defaultdict(lambda: [0, 0, 0, 0]) # problems, flag, flag & analogous, + order >= 2 ev = defaultdict(lambda: [0, 0, 0]) # flag events, analogous, analogous with m >= 2 for s, pid, f, a, b, _root in load_problems(): st = {"trace": []} pure_root(f, a, b, XTOL, MAXFEV, similarity="ratio", scale="derived3", guard="project", stats=st) g = group(s, pid) per[g][0] += 1 past = [] # (xi, phi, |dx|, |df|) of earlier states flag_ever = ana_ever = ord_ever = False for s_ in st["trace"]: c = config(s_) fx, fp = s_["fx"], s_["fp"] flag = fp is not None and fp != 0.0 and fx / fp >= 0.25 if c is not None: cur = (c[0], c[1], abs(s_["x"] - s_["xp"]), abs(fx - fp)) if c is not None and flag: d_line = abs(c[1] - c[0]) / math.sqrt(2.0) best = min(past, key=lambda q: math.hypot(c[0] - q[0], c[1] - q[1]), default=None) ana = best is not None and math.hypot(c[0] - best[0], c[1] - best[1]) < d_line m = pair_order(cur, best) if ana else None steep = m is not None and m >= 2.0 ev[g][0] += 1 ev[g][1] += ana ev[g][2] += steep flag_ever = True ana_ever = ana_ever or ana ord_ever = ord_ever or steep if c is not None: past.append(cur) per[g][1] += flag_ever per[g][2] += ana_ever per[g][3] += ord_ever print(f"{'group':<10} {'problems':>8} {'flag':>6} {'flag&analog':>12} {'&m>=2':>7}" f" events: {'flag':>5} {'analog':>7} {'&m>=2':>6}") for g in sorted(per): n, fl, an, od = per[g] e, a, o = ev[g] print(f"{g:<10} {n:8d} {fl:6d} {an:12d} {od:7d} {'':8}{e:5d} {a:7d} {o:6d}") print("diag done") if __name__ == "__main__": main()