# Pillar 1: v1.1 (implemented) vs v1.2 (proposed) on synthetic scenarios, 300 seeds each. import math, random, statistics, sys GRID = [600 * 72 ** (j / 199) for j in range(200)] TWO_PI = 2 * math.pi def qcont(xs, q): xs = sorted(xs); h = (len(xs) - 1) * q; lo = math.floor(h); hi = math.ceil(h) return xs[lo] + (h - lo) * (xs[hi] - xs[lo]) def core(ts, grid): t0 = ts[0]; n = len(ts); Ps = [] for p in grid: f = 1 / p; c = s = 0.0 for t in ts: ph = TWO_PI * f * (t - t0); c += math.cos(ph); s += math.sin(ph) Ps.append((c * c + s * s) / n) return Ps def disp(ts): d = [b - a for a, b in zip(ts, ts[1:])] med = qcont(d, .5) if med <= 1e-9: return None, med madm = qcont([abs(x - med) for x in d], .5) return madm / med, med def v11(ts): ts = sorted(ts) if len(ts) < 20: return 0.0, 'insufficient_data', None Ps = core(ts, GRID); pmax = max(Ps) sig = min(1, max(0, (pmax - math.log(200)) / (6 * math.log(10)))) dn, med = disp(ts) if dn is None: return 0.5 * sig, 'degenerate_deltas', None reg = min(1, max(0, 1 - 2 * dn)) per = max(p for p, v in zip(GRID, Ps) if v >= .5 * pmax) return 0.5 * sig + 0.5 * reg, 'ok', per def v12(ts, cycles=3, gate=0.25): ts = sorted(ts) if len(ts) < 20: return 0.0, 'insufficient_data', None span = ts[-1] - ts[0] grid = [p for p in GRID if p <= span / cycles] if not grid: return 0.0, 'insufficient_span', None Ps = core(ts, grid); pmax = max(Ps); M = len(grid) sig = min(1, max(0, (pmax - math.log(M)) / (6 * math.log(10)))) dn, med = disp(ts) if dn is None: return 0.0, 'degenerate_deltas', None reg = min(1, max(0, 1 - 2 * dn)) g = min(1.0, reg / gate) per = max(p for p, v in zip(grid, Ps) if v >= .5 * pmax) return 0.5 * sig * g + 0.5 * reg, 'ok', per