erring_widths.py

Checks: SIZE: Growing widths observation; pattern.

Cited on: SIZE.

To run it: save erring_widths.py, with erring_label.py beside it; standard library only.

"""
erring_widths.py -- does a width profile growing with position err
locally below rounding's quarter?

QUESTION. Fill [0, c) with m intervals below the overflow label
(N = 2c - 1), the error as in erring_label.py. Rounding (equal widths,
one wide top [uc, c)) errs on a quarter of its fine pairs and tends to
1/10. Does a profile w(t) = g c (t/c)^p, widths at least 1, err less at
the same m, and how does the least error per c^2 fall with m?

THE ARGUMENT (written before this script). A cell of widths a, b sums
over an interval of width a + b at x + y, where the label has width
W(x + y); it errs nowhere unless a cut lies inside. At a phase spread
over the cells it straddles with chance about (a + b)/W, then errs on
between 1/6 (a = b) and 1/4 (a << b) of its pairs. Rounding has
a = b = W, a block twice its label, aligned at the best phase: 1/4.
Under t^p, (a + b)/W averages 2/(p + 1) over the triangle, so the local
error falls like 1/p; the uniform phase is a transplant the run decides.

PREDICTIONS AND KILLS (written before the run).
  W1 at c = 4000, m near 34, 66, 162: some profile with p >= 2 prints
     e/c^2 below rounding's best (u over 0.70..0.90) at the same m.
  W2 p = 2 at m near 160 prints below 0.095; p = 3 below p = 2 at large
     enough m.
  W3 at fixed m the best profile's e/c^2 moves under 2% from c = 2000
     to c = 4000.
  K-floor: the lean "1/10 is the limit" dies if a profile prints
     e/c^2 < 0.095 at c = 4000 and within 2% at c = 2000.
  K-power: the heuristic dies if no profile at p = 2, 3, 4 prints below
     rounding's best at the same m for any m in 34..400 at c = 4000.

  W4 (written after W1..W3's prints) at m = 1000, c = 10000, the best
     profile over p = 1, 2, 3 and top None, 0.8, 0.9 prints below 0.0785
     (m = 400's best), holding within 2% scaled x2, and its p is >= 2.
     Dies (the fall stalls) if the best is above 0.0785.

FINDINGS (from the printed run; both controls passed first).
  K-floor fired: m = 400, p = 1, top 0.8 prints 0.0852 at c = 4000, and
     the same cut set scaled x2 to x16 prints 0.0854 to 0.0857 against
     rounding's 0.1012 to 0.1009. 1/10 is rounding's limit, not a floor:
     at m = 162 the p = 1, top 0.8 cut set prints 0.0905 at every scale
     x1 to x16 (rounding 0.1027 to 0.1029). On the reading it froze, the
     re-tuned c = 2000 prints 0.0837, within 2% of 0.0852.
  K-power survived: at m = 400, p = 2 (top 0.8) prints 0.0785 and p = 3
     (top 0.8) 0.1005 against rounding's 0.1022. At p = 1 a profile beats
     rounding's best from m = 66 (top 0.8: 0.1034 against 0.1068), not at
     m = 34 (best 0.1362 against 0.1120, the widest label 800 of 4000).
  W1 failed: no p >= 2 profile beats rounding at m <= 162; p = 1
     (geometric widths) does from m = 66.
  W2 failed: p = 2 at m = 162 prints 0.1183 at best; p = 3 is above
     p = 2 at every m run (m = 400: 0.1005 against 0.0785). The profile
     spends its labels on singletons near 0 (p = 3, m = 400: 299), so
     larger p needs larger m.
  W3 failed on the re-tuned reading it froze: re-tuned at c/2 the
     profiles print 1.8 to 9.6% lower (m = 400, p = 2: 0.0710 against
     0.0785). The reading kappa_m needs is
     the same cut set scaled.
  W4 held on the row run: m = 1000, c = 10000, p = 2, top 0.9 prints
     0.0723, scaled x2 0.0731 (1.1%), x4 0.0734; rounding 0.1017, 0.1007,
     0.1004. (Its comparison over p = 1, 2, 3 and three tops, and so its
     clause that the best p is >= 2, is not run here.)
  The scaled readings rise with the scale, the increments shrinking:
  0.0785, 0.0793, 0.0796, 0.0798, 0.0799 at m = 400, p = 2, x1 to x16.
  Each cut set's limit as it is scaled is its continuum error, an upper
  bound on kappa_m; the run prints the approach, not the limit. The hand
  estimate for p = 1, top 0.8: the local error at uniform phase, v =
  x/(x + y) uniform, prints phi_1 = 0.1989 (section_phi; 1/4 at a flat
  block, 1/6 at a triangle), so 0.64 x 0.1989/2 + 0.02 = 0.0836, against
  0.0857 at m = 400, x16. The uniform phase is a transplant here; the
  widths are a companding quantizer's (steps 1/G'(t), p = 1 the
  logarithmic compressor of mu-law), and only the constants are the
  run's.

CONTROLS. The fast error equals erring_label.error on every cut set at
N = 12, s = 2..5, and on rounding at c = 2000, w = 100; rounding at
c = 2000 reprints 0.1301 at w = 100.
"""

from bisect import bisect_right

from erring_label import below, error, cutsets, rounding

CHECKS = []


def check(name, ok, detail=""):
    CHECKS.append(bool(ok))
    tail = f"  ({detail})" if detail else ""
    print(f"  {'PASS' if ok else 'FAIL'}  {name}{tail}")


def fast_error(cuts, N):
    """error(cuts, N), reading only the labels each cell's sums meet."""
    s = len(cuts) - 1
    E = 0
    for a in range(s):
        u1, n1 = cuts[a], cuts[a + 1] - cuts[a]
        for b in range(s):
            u2, n2 = cuts[b], cuts[b + 1] - cuts[b]
            base = u1 + u2
            if base >= N:
                continue
            tot = below(n1, n2, N - base)
            top = min(base + n1 + n2 - 2, N - 1)
            lo = bisect_right(cuts, base) - 1
            hi = bisect_right(cuts, top) - 1
            if lo == hi:
                continue
            best = 0
            for L in range(lo, hi + 1):
                k = (below(n1, n2, min(cuts[L + 1], N) - base)
                     - below(n1, n2, cuts[L] - base))
                best = max(best, k)
            E += tot - best
    return E


def profile(c, p, g, u=None):
    """Cuts of the profile t -> t + max(1, round(g c (t/c)^p)) on [0, U),
    U = c or the top cut u c, then [U, c) as one top label."""
    U = c if u is None else round(u * c)
    cuts = [0]
    t = 0
    while True:
        t = t + max(1, round(g * c * (t / c) ** p))
        if t >= U:
            break
        cuts.append(t)
    cuts.append(U)
    if U < c:
        cuts.append(c)
    return tuple(cuts)


def profile_at(c, p, m, u=None):
    """A profile with m intervals, g found by bisection (None if missed)."""
    lo, hi = 1e-9, 1e3
    for _ in range(200):
        g = (lo * hi) ** 0.5
        k = len(profile(c, p, g, u)) - 1
        if k == m:
            return profile(c, p, g, u)
        if k > m:
            lo = g
        else:
            hi = g
    return None


def round_at(c, m, u):
    """Rounding with m intervals: m - 2 cuts below the top u c."""
    w = u * c / (m - 2)
    cuts = rounding(c, w, u)
    return cuts if len(cuts) - 1 == m else None


def ratio(cuts, c):
    return fast_error(cuts + (2 * c - 1,), 2 * c - 1) / c / c


def section_controls():
    print("controls")
    bad = n = 0
    for s in range(2, 6):
        for cu in cutsets(12, s):
            n += 1
            bad += fast_error(cu, 12) != error(cu, 12)
    check("control: fast error equals error() at N = 12", bad == 0,
          f"{n} cut sets, {bad} faults")
    cu = rounding(2000, 100, 0.8) + (3999,)
    e1, e2 = fast_error(cu, 3999), error(cu, 3999)
    check("control: fast error equals error() on rounding, c = 2000, w = 100",
          e1 == e2 and f"{e1 / 4e6:.4f}" == "0.1301", f"{e1}, {e2}")


US = (0.70, 0.74, 0.78, 0.80, 0.82, 0.86, 0.90)


def best_round(c, m):
    out = []
    for u in US:
        cu = round_at(c, m, u)
        if cu:
            out.append((ratio(cu, c), u))
    return min(out) if out else (None, None)


def section_profiles(c, ms, ps, tops):
    print(f"profiles against rounding, c = {c}")
    rows = {}
    for m in ms:
        r, ru = best_round(c, m)
        print(f"    m = {m:3d}  rounding best {r:.4f} (u = {ru})")
        for p in ps:
            for u in tops:
                cu = profile_at(c, p, m, u)
                if cu is None:
                    print(f"      p = {p}  top {u}: no g gives m = {m}")
                    continue
                e = ratio(cu, c)
                rows[(m, p, u)] = e
                ones = sum(1 for i in range(len(cu) - 1)
                           if cu[i + 1] - cu[i] == 1)
                print(f"      p = {p:3}  top {str(u):4}  e/c^2 = {e:.4f}"
                      f"{'  < rounding' if e < r else ''}"
                      f"  singletons {ones}, widest {max(cu[i + 1] - cu[i] for i in range(len(cu) - 1))}")
        rows[(m, "round")] = r
    return rows


def section_scale(cases, c=4000, ks=(1, 2, 4)):
    """W3: the same shape at fixed m, scaled; and as frozen, re-tuned at
    c/2."""
    print(f"fixed m: the c = {c} cut set scaled, and re-tuned at {c // 2}")
    for m, p, u in cases:
        cu = profile_at(c, p, m, u)
        row = [ratio(tuple(k * x for x in cu), c * k) for k in ks]
        re = profile_at(c // 2, p, m, u)
        r2 = ratio(re, c // 2) if re else float("nan")
        rr = [best_round(c * k, m)[0] for k in ks]
        print(f"    m = {m} p = {p} top {u}: scaled "
              + ", ".join(f"x{k}" for k in ks) + " "
              + ", ".join(f"{x:.4f}" for x in row)
              + f"; re-tuned c = {c // 2} {r2:.4f}; rounding "
              + ", ".join(f"{x:.4f}" for x in rr))


def section_phi(n=600):
    """The p = 1 local error at uniform phase: U[0, a] + U[0, 1 - a]
    against a cut at a uniform offset of [0, 1), a = min(v, 1 - v), the
    mean of min(F, 1 - F) over the offset and v, both by midpoints."""
    def cdf(t, a, b):
        if a == 0:
            return t / b
        if t <= a:
            return t * t / (2 * a * b)
        if t <= b:
            return (t - a / 2) / b
        s = a + b - t
        return 1 - s * s / (2 * a * b)
    tot = 0.0
    for i in range(n):
        v = (i + 0.5) / n
        a, b = min(v, 1 - v), max(v, 1 - v)
        tot += sum(min(x, 1 - x) for x in
                   (cdf((j + 0.5) / n, a, b) for j in range(n))) / n
    phi = tot / n
    print(f"phi_1 = {phi:.4f} (flat block 1/4, triangle 1/6); at u = 0.8 "
          f"the estimate 0.64 phi/2 + 0.02 = {0.64 * phi / 2 + 0.02:.4f}")


def main():
    section_controls()
    if not all(CHECKS):
        print("\nCONTROL FAILED: nothing below is read.")
        raise SystemExit(1)
    section_profiles(4000, (34, 66, 162, 400), (1, 2, 3), (None, 0.8, 0.9))
    section_scale([(162, 1, 0.8), (400, 1, 0.8), (400, 2, 0.8)],
                  ks=(1, 2, 4, 8, 16))
    section_scale([(1000, 2, 0.9)], c=10000, ks=(1, 2, 4))
    section_phi()
    print(f"{sum(CHECKS)}/{len(CHECKS)} checks passed")
    raise SystemExit(0 if all(CHECKS) else 1)


if __name__ == "__main__":
    main()