parity.py
Checks: PRINCIPAL: The archimedean term and the kind means theorem; observation.
Cited on: PRINCIPAL.
To run it: save parity.py, with module_law.py, origin.py, principal.py, residual.py beside it; needs numpy, scipy.
"""parity.py -- the kind means of residual.py read as counts, log L(1)
and log D.
QUESTION. residual.py reads rho_chi = [PI(x, chi) + m(loglog x + gamma)
- log L*(0, chi)]/PI(x, 1) per class character of a real field and finds
the odd mean falling with log|D| at 250 (R3) and the even rising. By
orthogonality the odd mean of PI(x, chi) is PI_1 - PI_k0, a count of the
trivial class against k0; by the functional equation log L*(0, chi) is
(1/2) log D + log L(1, chi) + c. Which of the three carries the slope?
THE DERIVATION (before code). chi(k0) = +-1, h+/2 odd characters and
h+/2 - 1 even nontrivial (even'):
(a) mean_odd rho = (resT - resK)/h+, mean_even' rho = (resT + resK)/
(h+ - 2), and with no k0 mean rho = resT/(h+ - 1), resT and resK
the trivial and k0 cells of residual.py: the kind means restate the
cells.
(b) mean_odd PI(x, chi) = PI_1 - PI_k0, mean_even' PI(x, chi) =
((h+/2)(PI_1 + PI_k0) - PI)/(h+/2 - 1), with no k0 (h+ PI_1 -
PI)/(h+ - 1).
(c) log L*(0, chi) = (1/2) log D + log L(1, chi) + c, c_o = -2 log pi,
c_e = -log 4 (Lambda(s) = D^(s/2) Gamma_R(s + a)^2 L(s), root
number 1), so per field
PI mean_odd rho = (PI_1 - PI_k0) - B_o - (1/2) log D + 2 log pi,
B the kind's mean of log L(1, chi).
(d) At a fixed cut the counts are bounded and B = o(log D)
(Brauer-Siegel over the Galois fields H+, H, known; with no k0,
H = H+ and the even' sum is over zeta_H+/zeta_K), so every kind
mean is -(1/2 + o(1)) log D/PI as D -> oo.
(e) R3's slope is the field-weighted OLS of mean_odd rho (weight h+/2)
and splits exactly: y = A + B + C, A = (PI_1 - PI_k0)/PI, B =
-B_o/PI, C = (-(1/2) log D + 2 log pi)/PI. Hand: PI(250, 1) about
58, s_C about -0.0088; R3 -0.0035; the gap about +0.0053, +0.31
counts per unit log D. By hand none of the counts trends with D
(an inert p gives +1 at every D, a reduced split P lies in 1 or k0
as +p or -p sits in the principal cycle, alternating), so the
guess is that B carries it.
PREDICTIONS, frozen before the engine.
P1 (b) per field in rho units (divided by PI), odd and even', at 250
and 10^4, to 1e-10; (a) the same.
P2 s_C at 250 within -0.0110 to -0.0070, and s_y equal to the
per-character OLS slope (R3) to 1e-12.
P3 printed: s_A, s_B, s_C with se, the gap in counts; the even' and
no-k0 means alike; PI_1 - PI_k0, B_o by |D| band.
KILL COUNTS. "The counts carry the gap": fires if s_A < 0.5 (s_y -
s_C). The hand guess is that it fires.
C1 THE BREAK: in (b)'s odd identity k0's count replaced by another
class of order 2's (2-rank >= 2): fails by more than 1e-3 at every
such field.
C2 THE KILL'S CONTROL, read before the verdict: A permuted across
fields fires KILL COUNTS in at least 190 of 200 seeds; A permuted
plus a planted slope equal to the gap misses in at least 190.
CHANGED BEFORE THE RUN, nothing read: KILL COUNTS fires iff s_A < s_B
(half of s_A + s_B), so C2 as frozen tested the data's signs, not the
kill. C2 now permutes A and B both, killing both trends, and plants the
data's gap s_A + s_B on B (the kill fires in at least 190 of 200) or on
A (it misses in at least 190). After the rehearsal, C1 took the
largest gap over the cuts per field, as "fails at every such field"
means; it had taken the least, which a cut with PI_k0 = PI_c zeroes.
FINDINGS, the real fields with h+ >= 2 to |D| <= 48000 (12599; 10323
with k0), cut 250 unless named.
F0 P1 held: the counts' identities to 1.27e-14, the restatement to
1.59e-14 (rho units); C1 breaks it at 5771 fields, least gap
0.0090; C2 fires 200/200 with the gap on B, misses 200/200 on A.
F1 THE SPLIT (P2 held: s_y = R3 = -0.003535; s_C -0.00832, <PI>
61.36). Odd: s_y -0.00353 +- 0.00065 = counts +0.00508 +- 0.00068
+ log L(1) -0.00030 +- 0.00007 + log D -0.00832 +- 0.00006. KILL
COUNTS MISSED: the counts carry the gap and more (+0.29 per unit
log D in PI_1 - PI_k0 against B_o's +0.017); the hand guess that
log L(1) carries it was wrong. Even': +0.00944 = +0.01915 -
0.00150 - 0.00821; no k0: +0.00896 = +0.01879 - 0.00144 - 0.00838.
At 10^4 every s_y within 0.0006 of 0, the counts' part +0.0007 to
+0.0010, log D's -0.0004; the count per unit log D +0.88 (odd),
+0.90 (even'), +1.26 (no k0), past (1/2) log D's pace.
F2 BY |D| BAND (odd, character-weighted): PI_1 - PI_k0 +2.53, +3.17,
+3.38, +3.41, +3.53 at |D| to 4000, 8000, 16000, 32000, 48000,
against (1/2) log D 3.73, 4.34, 4.69, 5.04, 5.30 and PI near 61.4
throughout; B_o +0.165 to +0.216. The count gains 0.640 from the
first band to the second, against (1/2) log D's 0.608, and 0.366
from the second to the last, against 0.953. The odd mean reads
+0.0151, +0.0162, +0.0137, +0.0084, +0.0060, its one clear fall
from the third band to the fourth (about 3 se by hand; no band se
is printed), still far above (d)'s -(1/2) log D/PI, -0.061 to
-0.086 by band (by hand from the band means).
The parts' band means, means of ratios, rebuild the mean's only to
within 0.0011, too coarse to carry the first two values' rise.
TIERS. (a), (b) properties; (d) a theorem given Brauer-Siegel; F1, F2
observations over this population; why the count rises with |D| is
not derived.
RUN RECORD. A rehearsal at --d 600: 3/5 (C1 as the least over cuts, C2
at 110 fields 180 and 178 of 200). Full: 5/5, 92.6 s, peak commit
149.3 MB under a memory guard (512 MB). From code/: 5/5, 92.2 s, 149.3 MB,
every print as above.
"""
import os
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")
import sys
import time
from math import log, pi
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import numpy as np # noqa: E402
import residual as R # noqa: E402 (parses --d, --dreal from argv)
from module_law import check, section, CHECKS # noqa: E402
P, O = R.P, R.O
CUTS = P.CUTS
J250, J10K = R.J250, R.J10K
SEEDS = 200
BANDS = ((0, 4000), (4000, 8000), (8000, 16000), (16000, 32000),
(32000, 10 ** 9))
C_O, C_E = -2 * log(pi), -log(4)
def read():
"""Per real field with h+ >= 2 the kind means and their parts, per
cut; and the identities' worst gaps."""
rows, worst, brk = [], {"b": 0.0, "a": 0.0}, []
rho_o_chars = [] # (log D, rho_o at 250)
for D in range(5, R.REAL_D + 1):
if not P.fundamental(D):
continue
G = P.Group(D)
h = G.h
if h < 2:
continue
_, cor = P.counts(G)
cor = np.array(cor) # (cuts, h)
tot = cor.sum(axis=1)
chis = O.characters(G)
m, L, _ = O.lstar(G, chis)
nt = R.nontrivial(chis)
lx = np.log(np.log(np.array(CUTS, float))) + O.GAMMA
PIchi = np.real(cor @ chis.T) # (cuts, h)
lL0 = np.log(np.abs(L))
rho = (PIchi + np.outer(lx, m) - lL0[None, :]) / tot[:, None]
hask0 = G.k0 != G.e
resT = rho[:, nt].sum(axis=1)
lD = log(D)
row = {"D": D, "lD": lD, "h": h, "k0": hask0, "PI": tot}
if hask0:
ck = np.real(chis[:, G.k0])
odd = [i for i in nt if ck[i] < 0]
ev = [i for i in nt if ck[i] > 0]
resK = (ck[nt][None, :] * rho[:, nt]).sum(axis=1)
d_odd = cor[:, G.e] - cor[:, G.k0]
worst["b"] = max(worst["b"], np.max(np.abs(
PIchi[:, odd].mean(axis=1) - d_odd) / tot))
yo = rho[:, odd].mean(axis=1)
worst["a"] = max(worst["a"], np.max(np.abs(
yo - (resT - resK) / h)))
Bo = (lL0[odd] - 0.5 * lD - C_O).mean()
row["o"] = (len(odd), yo, d_odd / tot, -Bo / tot,
(-0.5 * lD - C_O) / tot, d_odd, Bo)
for i in odd:
rho_o_chars.append((lD, rho[J250, i]))
others = [c for c in G.two if c not in (G.e, G.k0)]
if others:
c = others[0]
brk.append(np.max(np.abs(PIchi[:, odd].mean(axis=1)
- (cor[:, G.e] - cor[:, c]))
/ tot))
if ev:
ne = ((h / 2) * (cor[:, G.e] + cor[:, G.k0]) - tot) / (h / 2 - 1)
worst["b"] = max(worst["b"], np.max(np.abs(
PIchi[:, ev].mean(axis=1) - ne) / tot))
ye = rho[:, ev].mean(axis=1)
worst["a"] = max(worst["a"], np.max(np.abs(
ye - (resT + resK) / (h - 2))))
Be = (lL0[ev] - 0.5 * lD - C_E).mean()
row["e"] = (len(ev), ye, ne / tot, -Be / tot,
(2 * lx - 0.5 * lD - C_E) / tot, ne, Be)
else:
nn = (h * cor[:, G.e] - tot) / (h - 1)
worst["b"] = max(worst["b"], np.max(np.abs(
PIchi[:, nt].mean(axis=1) - nn) / tot))
yn = rho[:, nt].mean(axis=1)
worst["a"] = max(worst["a"], np.max(np.abs(yn - resT / (h - 1))))
Bn = (lL0[nt] - 0.5 * lD - C_E).mean()
row["n"] = (len(nt), yn, nn / tot, -Bn / tot,
(2 * lx - 0.5 * lD - C_E) / tot, nn, Bn)
rows.append(row)
return rows, worst, brk, np.array(rho_o_chars)
def wslope(x, y, w):
"""Weighted OLS slope of y on x and its field-level (HC0) se."""
xm = np.sum(w * x) / np.sum(w)
ym = np.sum(w * y) / np.sum(w)
sxx = np.sum(w * (x - xm) ** 2)
s = np.sum(w * (x - xm) * (y - ym)) / sxx
e = y - ym - s * (x - xm)
return s, np.sqrt(np.sum((w * (x - xm) * e) ** 2)) / sxx
def kind_arrays(rows, key, j):
sel = [r for r in rows if key in r]
x = np.array([r["lD"] for r in sel])
w = np.array([r[key][0] for r in sel], float)
y, A, B, C = (np.array([r[key][k][j] for r in sel]) for k in (1, 2, 3, 4))
cnt = np.array([r[key][5][j] for r in sel])
BL = np.array([r[key][6] for r in sel])
PI = np.array([r["PI"][j] for r in sel])
return x, w, y, A, B, C, cnt, BL, PI
def kills(sA, sB):
return sA < 0.5 * (sA + sB)
def main():
t0 = time.time()
section("A THE IDENTITIES")
rows, worst, brk, ro = read()
print(f" {len(rows)} real fields with h+ >= 2, |D| <= {R.REAL_D}"
f" [{time.time() - t0:.0f} s]")
check("P1 (b) the counts: the kind means of PI(x, chi), in rho units",
worst["b"] < 1e-10, f"worst {worst['b']:.2e}")
check("P1 (a) the restatement: the kind means of rho are the cells",
worst["a"] < 1e-10, f"worst {worst['a']:.2e}")
check("C1 the break: another class of order 2 in place of k0",
len(brk) > 0 and min(brk) > 1e-3,
f"{len(brk)} fields, least gap {min(brk) if brk else 0:.4f}")
section("B THE SLOPE ON log D, SPLIT")
out = {}
for key, name in (("o", "odd"), ("e", "even'"), ("n", "no k0")):
for j in (J250, J10K):
x, w, y, A, B, C, cnt, BL, PI = kind_arrays(rows, key, j)
sy, ey = wslope(x, y, w)
parts = [wslope(x, v, w) for v in (A, B, C)]
sA, sB, sC = (p[0] for p in parts)
add = abs(sy - (sA + sB + sC))
pm = np.sum(w * PI) / np.sum(w)
print(f" {name:6s} cut {CUTS[j]:5d}: {len(x)} fields, {int(w.sum())}"
f" characters, <PI> {pm:.2f}; s_y {sy:+.5f}+-{ey:.5f} ="
f" counts {sA:+.5f}+-{parts[0][1]:.5f} + log L(1)"
f" {sB:+.5f}+-{parts[1][1]:.5f} + log D {sC:+.5f}"
f"+-{parts[2][1]:.5f} (adds to {add:.1e})")
gc = wslope(x, cnt, w)[0]
gb = wslope(x, BL, w)[0]
print(f" in counts per unit log D: the count {gc:+.4f},"
f" B {gb:+.4f}; the gap s_y - s_C {sy - sC:+.5f} x <PI>"
f" = {(sy - sC) * pm:+.3f}")
out[(key, j)] = (x, w, y, A, B, C, cnt, BL, PI, sy, sA, sB, sC)
x, w, y, A, B, C, cnt, BL, PI, sy, sA, sB, sC = out[("o", J250)]
X = np.column_stack([np.ones(len(ro)), ro[:, 0]])
r3 = np.linalg.lstsq(X, ro[:, 1], rcond=None)[0][1]
check("P2 s_y is the per-character OLS slope (R3)",
abs(sy - r3) < 1e-12, f"{sy:+.6f} against {r3:+.6f}")
print(f" P2 s_C at 250 within -0.0110 to -0.0070: "
f"{'HELD' if -0.0110 <= sC <= -0.0070 else 'MISSED'} ({sC:+.5f})")
print(" P3 by |D| band, odd, character-weighted at 250:")
for lo, hi in BANDS:
s = (np.exp(x) > lo) & (np.exp(x) <= hi)
if s.sum() < 2:
continue
ww = w[s]
def mw(v):
return np.sum(ww * v[s]) / ww.sum()
print(f" {lo:6d}-{min(hi, R.REAL_D):6d}: {s.sum():5d} fields;"
f" PI_1 - PI_k0 {mw(cnt):+.3f}, B_o {mw(BL):+.3f},"
f" (1/2) log D {mw(0.5 * x):.3f}, PI {mw(PI):.2f},"
f" mean rho_o {mw(y):+.4f}")
section("C KILL COUNTS, ITS CONTROL FIRST")
rng = np.random.default_rng(1662)
gap = sA + sB
xm = np.sum(w * x) / np.sum(w)
f_perm = f_plant = 0
for _ in range(SEEDS):
Ap, Bp = rng.permutation(A), rng.permutation(B)
tilt = gap * (x - xm)
f_perm += kills(wslope(x, Ap, w)[0], wslope(x, Bp + tilt, w)[0])
f_plant += kills(wslope(x, Ap + tilt, w)[0], wslope(x, Bp, w)[0])
check("C2 the kill's control: the gap planted on B fires, on A misses",
f_perm >= 190 and SEEDS - f_plant >= 190,
f"on B fires {f_perm}/{SEEDS}, on A misses"
f" {SEEDS - f_plant}/{SEEDS}")
v = kills(sA, sB)
print(f" KILL COUNTS: s_A {sA:+.5f} against half the gap"
f" {0.5 * gap:+.5f}: {'FIRED' if v else 'MISSED'}")
print(f"\n{sum(CHECKS)}/{len(CHECKS)} checks PASS, "
f"{time.time() - t0:.1f} s")
raise SystemExit(0 if all(CHECKS) else 1)
if __name__ == "__main__":
main()