back to table · edit · history · where entries came from · files · download
5355 bytes, as of the version from 2026-11-02 08:35 (current). Recorded here, not run.
"""O’Nan moonshine coefficients -- numberdb.org/T672.
Run with SageMath (install numberdb once with sage -pip install numberdb):
sage -python generate.py # verify stored entries
sage -python generate.py --fill # fill draft, API key on stdin
The module and normalization are DMO, arXiv:1702.03516v4, Theorems 3.1,
4.1. Positive coefficients use Table 5.1, Proposition 5.1, and Appendix D.
Every positive definite integral form is included, also imprimitive forms.
The trace uses PSL2 stabilizers. A full projective-line coset sum divided
by the level-one stabilizer equals the level-N orbit sum with its correct
stabilizers, by orbit-stabilizer. The integrality theorem and a finite narrow
Arb enclosure identify each output integer, without heuristic rounding.
See argument-selection.md for the deliberate class and grade bounds.
"""
import sys
from functools import lru_cache
from math import gcd,isqrt
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.complex_arb import ComplexBallField
CLASSES=('1A','2A','3A','4A','4B','5A','6A','7A','7B')
MAX_GRADE=200
WORKING_BITS=1024
# q coefficients of the normalized Hauptmoduln in DMO Table 5.1.
FIRST={1:196884,2:276,3:54,4:20,5:9,6:6,7:2}
CORRECTIONS={1:{},2:{1:12,2:-12},3:{1:12,3:-12},
4:{2:12,4:-12},5:{1:6,5:-6},
6:{1:-12,2:8,3:QQ(21)/2,6:-QQ(13)/2},7:{1:4,7:-4}}
def reduced_forms(D):
"""Unique reduced SL2 classes, including imprimitive ones."""
for a in range(1,isqrt(D//3)+1):
for b in range(-a,a+1):
if (b*b+D)%(4*a):
continue
c=(b*b+D)//(4*a)
if c<a or ((abs(b)==a or a==c) and b<0):
continue
yield a,b,c
@lru_cache(None)
def cosets(N):
"""SL2(Z)/Gamma0(N), indexed by first column in P1(Z/N)."""
if N==1:
return ((1,0,0,1),)
units=[u for u in range(N) if gcd(u,N)==1]
seen=set();result=[]
for r in range(N+1):
for t in range(N+1):
if gcd(r,t)!=1:
continue
key=min(((v*r)%N,(v*t)%N) for v in units)
if key in seen:
continue
seen.add(key)
if t==0:
s,u=0,1
else:
u=pow(r,-1,t) if t>1 else 0
s=(r*u-1)//t
assert r*u-s*t==1
result.append((r,s,t,u))
expected=N
for p in range(2,N+1):
if N%p==0 and all(p%j for j in range(2,isqrt(p)+1)):
expected=expected*(p+1)//p
assert len(result)==expected
return tuple(result)
def hauptmodul(N,z):
if N==1:
return z.modular_j()-744
if N==6:
return z.modular_eta()**5*(3*z).modular_eta()/((2*z).modular_eta()*(6*z).modular_eta()**5)+5
e={2:24,3:12,4:8,5:6,7:4}[N]
return (z.modular_eta()/(N*z).modular_eta())**e+e
def trace_data(D,bits=WORKING_BITS):
C=ComplexBallField(bits)
H={N:QQ(0) for N in range(1,8)}
T1={N:C(0) for N in range(1,8)}
T2={N:C(0) for N in range(1,8)}
for a,b,c in reduced_forms(D):
w=3 if a==b==c else 2 if b==0 and a==c else 1
tau=(-C(b)+C(0,1)*C(D).sqrt())/(2*a)
for N in range(1,8):
for r,s,t,u in cosets(N):
if (a*r*r+b*r*t+c*t*t)%N:
continue
H[N]+=QQ(1)/w
z=(u*tau-s)/(-t*tau+r)
J=hauptmodul(N,z)
T1[N]+=J/w
T2[N]+=(J*J-2*FIRST[N])/w
return H,T1,T2
def isolate_integer(ball):
"""Require a finite enclosure tighter than 100 absolute decimal places."""
assert ball.real().is_finite() and ball.imag().is_finite() and ball.imag().contains_zero(), 'nonreal or nonfinite trace'
bound=ball.real().parent()(10)**-110
assert ball.real().parent()(ball.real().rad())<bound and ball.real().parent()(ball.imag().rad())<bound, 'insufficient precision'
# ZZ(ball.real()) succeeds only if its interval contains a unique integer.
z=ZZ(ball.real())
assert z in ball.real()
return z
@lru_cache(None)
def coefficients(D,bits=WORKING_BITS):
H,T1,T2=trace_data(D,bits)
out={}
for N in range(1,8):
v=(T2[N]-T1[N//gcd(N,2)])/2
for level,c in CORRECTIONS[N].items():
v+=c*H[level]
out[N]=(isolate_integer(v),v)
return out
class ONanCoefficients(numberdb.Generator):
table='T672'
parameters=('g','n')
type='Z'
rigour='exact'
digits=100
files=('generate.py','checks.py','source-controls.json','argument-selection.md',
'numerical-evidence.md','skill-evidence.json','companion-controls.json','gap-ON-excerpt.txt')
def enumerate(self):
for g in CLASSES:
for n in range(3,MAX_GRADE+1):
if n%4 in (0,3):
yield {'g':g,'n':str(n)}
def value(self,params,digits):
return coefficients(int(params['n']))[int(params['g'][:-1])][0]
if __name__=='__main__':
if '--fill' in sys.argv:
numberdb.configure(api_key=sys.stdin.read().strip())
print(ONanCoefficients().publish(message='Fill the draft with certified signed ONan traces.'))
else:
report=ONanCoefficients().verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)