tailsplit.py
Checks: FLATTEN: Every rank's band of failing depths ends theorem; rule; pattern.
Cited on: FLATTEN.
To run it: save tailsplit.py, with bandend.py, flatten.py beside it; needs mpmath, numpy, sympy.
"""tailsplit.py -- which loss makes the dual tail's start J_t outgrow
r^(3/2)?
QUESTION. dualtail.py's tail passes at order s when m_s > I_r sum_m
|b_m| Lh_(s+m), Lh_n = 2n I_n (n >= 1), 2^J (n = 0); the exact
certificate passes when m_s > H_ch ||Q_s c*||_1. So the tail's test is
T = A/(l1 l2 l3): A = DUAL_s/H_ch, l1 = I_r/H_ch, l2 = sum |b_m|
L_(s+m)/||Q_s c*||_1 (the triangle inequality), l3 = sum |b_m|
Lh_(s+m)/sum |b_m| L_(s+m) (the variation lemma), L_n = ||(1 + x)^J
(1 - x)^n||_1. Read at the binding order s = r - 3.
BY HAND (the Gaussian limit at fixed r, a TRANSPLANT; closed forms by
quadrature, limits(), f = He_s phi + b2 He_(s+2) phi, b1 = 0 by
parity): l1 ->
sqrt(2 pi) 2^(r/2-1) Gamma(r/2)/(pi max|He_(r-1) phi|), 1 at odd r; l2
-> sum |b_m| int|He_(s+m) phi|/int|f|; l3 -> sum |b_m| 2n 2^(n/2-1)
Gamma(n/2)/pi over sum |b_m| int|He_n phi|/sqrt(2 pi). Their product
Lam is 4.39 .. 13.76 at ranks 5..10 against J_t/J_c = 5.11 .. 14.95; at
r = 20, 30, 40 l2 is 7.58, 9.43, 10.96 and l3 3.78, 4.71, 5.48.
PREDICTIONS (frozen before the engine). Q1 the exact l1, l2, l3 at
J = J_t(r) within 15% of their limits at ranks 5..10. Q2 l1 in
[0.95, 1.2] there. Q3 Lam(J_t)/(J_t/J_c) in [0.75, 1.25].
KILL: l1 carries the growth: l1(J_t(10))/l1(J_t(5)) > 1.5, or an l1 at
J_t outside [0.8, 1.5].
CONTROL, read first: the exact T >= 1 at J_t(r) and < 1 at 0.9 J_t(r),
ranks 5..10. THE PLANT: Lh_n = n I_n must lift T(0.9 J_t) above 1 at
some rank.
FINDINGS (copied from the printed output).
(1) Control: T(J_t) = 1.0030, 1.0021, 1.0018, 1.0009, 1.0006, 1.0004
and T(0.9 J_t) = 0.8975 .. 0.8983 at ranks 5..10: the exact split
is the tail's own test, its rational bounds losing under 0.3%. The
plant reads 1.7950 .. 1.7966, caught at every rank.
(2) The kill misses; Q1, Q2, Q3 hold. At J_t, ranks 5..10: l1 =
1.0000, 1.1054, 1.0043, 1.0795, 1.0024, 1.0625; l2 = 3.1758,
4.0202, 4.0715, 4.7702, 4.8021, 5.4077; l3 = 1.5488, 1.7910,
2.0063, 2.1986, 2.3728, 2.5336; Lam = 4.919, 7.959, 8.204,
11.321, 11.422, 14.557 against J_t/J_c = 5.106 .. 14.953 (Lam over
it 0.949 .. 0.975). Exact over limit: l1 and l3 within 0.4%, l2
1.116 falling to 1.058. The plant reads nothing: n I_n halves l3
and doubles T by arithmetic (2 x 0.8983 = 1.7966); the control's
content is T at J_t and 0.9 J_t.
(3) So J_t is about Lam J_c: the tail's start is the certificate's
times the triangle inequality and the variation lemma; the
champion's mean over its height, l1, stays within 11% of 1. In the
limit (limits(), r = 5..10, 20, 30, 40) l2 = 2.8448 .. 10.9643 and
l3 = 1.5431 .. 5.4799, their local slopes in log r falling from
0.846, 0.715 (r = 5..10) to 0.532, 0.535 (r = 20..40): about
sqrt(r) each, Lam = 61.011 at r = 40. A tail from O(J_c) needs
||Q_s c*||_1 bounded within a constant, not term by term.
RUN RECORD. 2.4 s, every rank exact in rationals but the Beta values
(30 digits). The limits first ran as a one-off quadrature outside the
script; limits() now prints them, the same to the printed digits; 3.2 s
in all.
"""
import math
import os
import sys
import time
from fractions import Fraction
from math import comb
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
import mpmath as mp # noqa: E402
from bandend import Q, pmul, champ_height, gram, solve # noqa: E402
mp.mp.dps = 30
JT = {5: 240, 6: 478, 7: 817, 8: 1267, 9: 1839, 10: 2542} # dualtail.py
JC = {5: 47, 6: 57, 7: 97, 8: 108, 9: 157, 10: 170} # bandend.py
def limits(r):
"""(l1, l2, l3) in the Gaussian limit at s = r - 3, by quadrature on
a grid of step 2e-4 over |y| <= 40: f = He_s phi + b1 He_(s+1) phi +
b2 He_(s+2) phi least in L2 (b1 = 0 by parity)."""
import numpy as np
from numpy.polynomial import hermite_e as He
y = np.linspace(-40, 40, 400001)
dy = y[1] - y[0]
phi = np.exp(-y ** 2 / 2)
def h(n):
c = np.zeros(n + 1)
c[n] = 1
return He.hermeval(y, c) * phi
s = r - 3
fs = [h(s + m) for m in range(3)]
G = np.array([[np.sum(fs[i] * fs[j]) * dy for j in range(3)]
for i in range(3)])
beta = np.r_[1, np.linalg.solve(G[1:, 1:], -G[1:, 0])]
f = sum(beta[m] * fs[m] for m in range(3))
l1s = [np.sum(np.abs(g)) * dy for g in fs]
l2 = (sum(abs(beta[m]) * l1s[m] for m in range(3))
/ (np.sum(np.abs(f)) * dy))
l1 = (math.sqrt(2 * math.pi) * 2 ** (r / 2 - 1) * math.gamma(r / 2)
/ (math.pi * np.max(np.abs(h(r - 1)))))
def ih(n):
return 2 ** (n / 2 - 1) * math.gamma(n / 2) / math.pi
l3 = (sum(abs(beta[m]) * 2 * (s + m) * ih(s + m) for m in range(3))
/ sum(abs(beta[m]) * l1s[m] / math.sqrt(2 * math.pi)
for m in range(3)))
return float(l1), float(l2), float(l3)
def I(n, J):
"""2^(J+n-1) B((J + 1)/2, n/2)/pi, the mean modulus on the circle."""
return (mp.mpf(2) ** (J + n - 1) * mp.beta(mp.mpf(J + 1) / 2,
mp.mpf(n) / 2) / mp.pi)
def split(r, J, plant=False):
s = r - 3
d = r - 1 - s
y = solve(gram(J, s, d), [1] * (d + 1))
tot = sum(y)
c = [v / tot for v in y]
m = 1 / tot
b = [(-1) ** k * sum(comb(i, k) * c[i] for i in range(k, d + 1))
for k in range(d + 1)]
l1q = sum(abs(v) for v in pmul(Q(J, s), c))
H = champ_height(r, J)
L = [sum(abs(v) for v in Q(J, s + k)) for k in range(d + 1)]
k2 = 1 if plant else 2
Lh = [k2 * (s + k) * I(s + k, J) if s + k else mp.mpf(2) ** J
for k in range(d + 1)]
fr = lambda q: mp.mpf(q.numerator) / q.denominator # noqa: E731
A = fr(m / (l1q * H))
l1 = I(r, J) / H
sb = sum(abs(fr(b[k])) * L[k] for k in range(d + 1))
l2 = sb / fr(l1q)
l3 = sum(abs(fr(b[k])) * Lh[k] for k in range(d + 1)) / sb
return float(A), float(l1), float(l2), float(l3), float(A / (l1 * l2 * l3))
def main():
t0 = time.time()
ctl, plant = True, False
rows = {}
for r in range(5, 11):
at = split(r, JT[r])
lo = split(r, int(0.9 * JT[r]))
pl = split(r, int(0.9 * JT[r]), plant=True)
ctl &= at[4] >= 1 and lo[4] < 1
plant |= pl[4] >= 1
rows[r] = at
print("r = %2d T(J_t) %.4f T(0.9 J_t) %.4f plant %.4f [%.1f s]"
% (r, at[4], lo[4], pl[4], time.time() - t0), flush=True)
print("CONTROL T >= 1 at J_t and < 1 at 0.9 J_t, ranks 5..10: %s;"
" the plant lifts T(0.9 J_t) above 1 at some rank: %s"
% (ctl, plant), flush=True)
LIM = {}
for r in (5, 6, 7, 8, 9, 10, 20, 30, 40):
LIM[r] = limits(r)
a, b, c = LIM[r]
print("limit r = %2d l1 %.4f l2 %.4f l3 %.4f Lam %.3f"
% (r, a, b, c, a * b * c), flush=True)
for r1, r2 in ((5, 10), (10, 20), (20, 40)):
print("local slope over r = %d..%d: l2 %.3f, l3 %.3f" % (
r1, r2, *(math.log(LIM[r2][i] / LIM[r1][i]) / math.log(r2 / r1)
for i in (1, 2))))
q1 = q2 = q3 = True
for r, (A, l1, l2, l3, T) in rows.items():
lam = l1 * l2 * l3
rel = [x / y for x, y in zip((l1, l2, l3), LIM[r])]
q1 &= all(0.85 <= v <= 1.15 for v in rel)
q2 &= 0.95 <= l1 <= 1.2
q3 &= 0.75 <= lam / (JT[r] / JC[r]) <= 1.25
print("r = %2d J_t %4d A %.3f l1 %.4f l2 %.4f l3 %.4f Lam %.3f"
" J_t/J_c %.3f (Lam over it %.3f) exact/limit %.3f %.3f %.3f"
% (r, JT[r], A, l1, l2, l3, lam, JT[r] / JC[r],
lam / (JT[r] / JC[r]), *rel))
kill = rows[10][1] / rows[5][1] > 1.5 or any(
not 0.8 <= v[1] <= 1.5 for v in rows.values())
print("Q1 within 15%% of the limits: %s; Q2 l1 in [0.95, 1.2]: %s; Q3"
" Lam/(J_t/J_c) in [0.75, 1.25]: %s; KILL (l1 carries the"
" growth) %s [%.1f s]" % (q1, q2, q3, "fires" if kill else
"misses", time.time() - t0))
return not (ctl and plant) or kill
if __name__ == "__main__":
sys.exit(1 if main() else 0)