numgap.py
Checks: GROWTH: The gap read off the completions theorem; rule.
Cited on: GROWTH.
To run it: save numgap.py, with numwall.py beside it; standard library only.
"""numgap.py -- the gap n_L(K) / w_L(K) at a quadratic field, whole.
QUESTION. numwall.py finds n != w only at quadratic fields of composite
discriminant and proves its odd primes (l^(1 + v_l(L)) where
(l - 1)/2 | L and l - 1 does not). What is n / w exactly, at every L?
THE ARGUMENT (written before the engine). D = prod D_l, the prime
discriminants -4, 8, -8 and l* = (-1)^((l - 1)/2) l. v_l(n) is the
largest k with N_l of exponent dividing L mod l^k, N_l the norms of the
local units of O_K (x) Z_l in Z_l^x; v_l(w) is read the same way off
G_l = Gal(K(mu_(l^oo))/K) in Z_l^x; G_l = Z_l^x unless D = D_l, where
G_l is the kernel of chi_D.
l not | D: N_l = Z_l^x, no gap.
l odd, l | D: a^2 - d b^2 = a^2 mod l, so N_l is the squares; at
D = l* G_l is too. Else the gap l^(1 + v_l(L)) when (l - 1)/2 | L
and l - 1 does not.
l = 2, 2 | D: N_2 holds the squares 1 + 8Z_2, and its residues mod 8
are {1, 5} at D_2 = -4, {1, 3} at -8, {1, 7} at 8 (a^2 - d b^2
over the units: a + b odd at -4, a odd at +-8). So N_2 is 1 + 4Z_2
at -4 and +-(1 + 8Z_2) at 8. Mod 2^k, k >= 2, its exponent is the
full group's, max(2, 2^(k - 2)), at -8; at -4 it is 1 at k = 2 and
2^(k - 2) from k = 3; at 8 it is max(2, 2^(k - 3)). At even L that
makes v_2(n) = v_2(L) + 3 at D_2 = 8, one past W(L), and
v_2(W(L)) otherwise. At odd L only k <= 2 can enter
(a 2-group of exponent dividing L is trivial): N_2 is trivial mod
4 exactly at D_2 = -4, so v_2(n) = 2 there and 1 elsewhere. G_2 is
N_2 at D = -4, 8, -8 and Z_2^x otherwise.
THE LAW: n / w = prod over l | D, D_l != D, gamma_l(L), with
gamma_l = l^(1 + v_l(L)) at odd l with (l - 1)/2 | L, l - 1 not | L;
gamma_2 = 2 at D_2 = 8 and L even, and at D_2 = -4 and L odd; 1
otherwise.
DESIGN. Imports numwall.py's fields, w by definition and n by the norm
Adams gcd at its larger box. Every quadratic field of numwall.py (d
squarefree, 2 <= |d| <= 50, and d = -1), every L <= 60.
C CONTROLS, read first: Q(sqrt -15), L = 2: n / w = 5 (numwall.py's
hand values 120 / 24); Q(sqrt -6), L = 2 by hand: norms of units
mod 16 are {1, 7, 9, 15}, exponent 2, and 7^2 = 17 mod 32, so
v_2(n) = 4; squares mod 9 have order 3, so v_3(n) = 1: n = 48,
w = 24. Q(i), L = 1: unit norms are 1 mod 4 and 5 appears mod 8,
G_2 = 1 + 4Z_2: n = w = 4. Q(sqrt 3), L = 1: gamma_3 = 3,
gamma_2 = 2, n / w = 6.
PREDICTIONS AND KILLS, frozen before the engine.
T7 n / w is the law's product at every quadratic (field, L).
So 2 || n / w at every even L exactly at d = 2 mod 8, d != 2, and at
every odd L exactly at d = 3 mod 4, d != -1.
A KILL is any printed T7 miss; a control miss fails the instrument.
FINDINGS (from the printed run, 5/5 checks PASS).
C every control held: Q(sqrt -15) n 120, w 24; Q(sqrt -6) n 48,
w 24; Q(i) at L = 1 n = w = 4; Q(sqrt 3) at L = 1 n 12, w 2.
T7 held at 3660 (field, L), 0 off: 61 fields x 60 L. 2 || n / w at
every even L over d = -46, -38, -30, -22, -14, -6, 10, 26, 34, 42,
the d = 2 mod 8 other than 2, and at every odd L over the 19 d =
3 mod 4, d != -1, from -41 to 47; 2 divides n / w at 870
(field, L) of the 1363 with n != w. The same law with the even-L
2-factor at every L, in a scratch copy, misses 870.
Since w is computed by its definition and the law derives the true
n, the box gcd n is the true n at every quadratic (field, L) run.
RUN RECORD. 3.8 s, peak commit 13.2 MB.
"""
import os
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import numwall as nw # noqa: E402
CHECKS = []
LS = list(range(1, 61))
def check(name, ok, detail=""):
CHECKS.append(bool(ok))
print(f" [{'PASS' if ok else 'FAIL'}] {name}"
+ (f" -- {detail}" if detail else ""))
def prime_discs(D):
"""{l: D_l}, D the product of the D_l."""
out, rest = {}, D
for l in nw.factor(abs(D)):
if l > 2:
out[l] = l if l % 4 == 1 else -l
rest //= out[l]
if D % 2 == 0:
out[2] = rest
return out
def gap_law(D, L):
g = 1
for l, Dl in prime_discs(D).items():
if Dl == D:
continue
if l == 2:
g *= 2 if Dl == (8 if L % 2 == 0 else -4) else 1
elif L % ((l - 1) // 2) == 0 and L % (l - 1):
g *= l ** (1 + nw.vp(L, l))
return g
def run(Ks):
return {(K.name, L): (nw.w_def(K, L), nw.norm_wall(K, L, nw.BOXES[2][1]))
for K in Ks for L in LS}
def section_controls(R):
print("C CONTROLS")
w, n = R[("Q(sqrt-15)", 2)]
check("C Q(sqrt -15), L = 2: n / w = 5", n == 5 * w, f"n {n}, w {w}")
w, n = R[("Q(sqrt-6)", 2)]
check("C Q(sqrt -6), L = 2: n = 48, w = 24", (n, w) == (48, 24),
f"n {n}, w {w}")
w, n = R[("Q(i)", 1)]
check("C Q(i), L = 1: n = w = 4", (n, w) == (4, 4), f"n {n}, w {w}")
w, n = R[("Q(sqrt3)", 1)]
check("C Q(sqrt 3), L = 1: n / w = 6", n == 6 * w, f"n {n}, w {w}")
for D in (-15, -24, 40, 8, -4):
print(f" D = {D}: prime discriminants {prime_discs(D)}")
def section_law(Ks, R):
print("T THE LAW")
miss = []
for K in Ks:
for L in LS:
w, n = R[(K.name, L)]
if n % w or n // w != gap_law(K.D, L):
miss.append((K.name, L, n // w, gap_law(K.D, L)))
check("T7 n / w = the law's product at every quadratic (field, L)",
not miss, f"{len(Ks) * len(LS)} (field, L), {len(miss)} off "
f"{miss[:5]}")
def d(K):
return K.D // (4 if K.D % 4 == 0 else 1)
def two_at_every(K, parity):
return all(nw.vp(R[(K.name, L)][1] // R[(K.name, L)][0], 2) == 1
for L in LS if L % 2 == parity)
print(f" 2 || n / w at every even L: d = "
f"{sorted(d(K) for K in Ks if two_at_every(K, 0))}")
print(f" d = 2 mod 8, d != 2: d = "
f"{sorted(d(K) for K in Ks if K.D % 32 == 8 and K.D != 8)}")
print(f" 2 || n / w at every odd L: d = "
f"{sorted(d(K) for K in Ks if two_at_every(K, 1))}")
print(f" d = 3 mod 4, d != -1: d = "
f"{sorted(d(K) for K in Ks if K.D % 16 == 12 and K.D != -4)}")
some2 = sum(1 for (w, n) in R.values() if nw.vp(n // w, 2))
print(f" (field, L) with 2 | n / w: {some2}; with n != w: "
f"{sum(1 for w, n in R.values() if n != w)}")
def main():
t0 = time.time()
Ks = [K for K in nw.fields() if not K.cyc]
print(f"numgap.py: {len(Ks)} quadratic fields, every L <= 60")
R = run(Ks)
section_controls(R)
section_law(Ks, R)
print(f"{sum(CHECKS)}/{len(CHECKS)} checks PASS "
f"({time.time() - t0:.1f} s)")
return 0 if all(CHECKS) else 1
if __name__ == "__main__":
sys.exit(main())