back to table · edit · history · where entries came from · files · download
8892 bytes, as of the version from 2026-11-09 14:15 (current). Recorded here, not run.
"""Critical values of Rankin-Selberg L-functions of level-one cusp forms -- numberdb.org/T691.
Run with SageMath (install numberdb once with sage -pip install numberdb):
sage -python generate.py # verify every stored entry
sage -python generate.py --compute # print reproducible numerical evidence
sage -python generate.py --publish # fill this draft, using NUMBERDB_API_KEY
Reading a private draft also requires NUMBERDB_API_KEY with access to it.
The --publish switch submits entries to the existing draft; it does not make
the table public or offer it for review.
The default selection is 66 critical values for f_(k,i) tensor Delta with
12<k<=26. See argument-selection.md and exact-roster.json. Values are rounded
to 100 significant digits only after fresh evaluations at 160 and 220 working
decimal digits agree to 120 relative digits. This is heuristic agreement,
not a certified error bound. PARI's required internal coefficient precision
can exceed its output precision: the quadratic embedding is therefore computed
inside a PARI closure at each requested precision, never supplied as a fixed
precision decimal. Internally h=(k+10)/2 shifts L(s) to L_an(s-h), with
coefficients c_n/n^h and gamma parameters h,h+1,h-11,h-10. This exact change
of coordinates improved the measured 160/220-digit agreement for the first
pair from about 105 to about 164 digits. Arithmetic values are still stored.
check_values.py gives independent numerical checks.
"""
import os
import sys
import json
import time
from functools import lru_cache
from math import isqrt
import numberdb.sage as numberdb
import sage.symbolic.expression
from sage.rings.integer_ring import ZZ
from sage.rings.real_mpfr import RealField
from sage.rings.rational_field import QQ
from sage.rings.power_series_ring import PowerSeriesRing
from sage.libs.pari import pari
from sage.version import version as sage_version
WEIGHTS = (16, 18, 20, 22, 24, 26)
WORKING_DIGITS = (160, 220)
OUTPUT_DIGITS = 100
AGREEMENT_DIGITS = 120
GAMMA = (0, 1, -11, -10)
def add(a, b):
return (a[0] + b[0], a[1] + b[1])
def scale(a, n):
return (a[0] * n, a[1] * n)
def mul(a, b):
# t^2 = 1080*t + 20468736, for t=a_2 of the weight-24 form.
return (a[0]*b[0] + 20468736*a[1]*b[1],
a[0]*b[1] + a[1]*b[0] + 1080*a[1]*b[1])
def eisenstein_coefficients(n, weight):
factor = {4:240, 6:-504}[weight]
c = [ZZ(0)] * (n+1)
c[0] = ZZ(1)
for d in range(1, n+1):
v = ZZ(factor) * ZZ(d)**(weight-1)
for m in range(d, n+1, d):
c[m] += v
return c
@lru_cache(None)
def exact_forms(n):
R = PowerSeriesRing(ZZ, 'q', default_prec=n+1)
e4 = R(eisenstein_coefficients(n,4)).add_bigoh(n+1)
e6 = R(eisenstein_coefficients(n,6)).add_bigoh(n+1)
num = e4**3-e6**2
assert all(num[m] % 1728 == 0 for m in range(n+1))
delta = R([num[m]//1728 for m in range(n+1)]).add_bigoh(n+1)
assert [delta[m] for m in range(6)] == [0,1,-24,252,-1472,4830]
polys = {12:delta,16:delta*e4,18:delta*e6,20:delta*e4**2,
22:delta*e4*e6,24:delta*e4**3-696*delta**2,
26:delta*e4**2*e6}
delta2 = delta**2
return {k:[(p[m],delta2[m] if k==24 else ZZ(0)) for m in range(n+1)]
for k,p in polys.items()}
def prime_sieve(n):
spf=list(range(n+1))
for p in range(2,isqrt(n)+1):
if spf[p]==p:
for m in range(p*p,n+1,p):
if spf[m]==m: spf[m]=p
return spf
@lru_cache(None)
def tensor_coefficients(k, n):
forms=exact_forms(n); f=forms[k]; g=forms[12]; spf=prime_sieve(n)
local={}
for p in range(2,n+1):
if spf[p]!=p: continue
a=f[p]; b=g[p]; A=ZZ(p)**(k-1); B=ZZ(p)**11
ab=mul(a,b)
middle=add(add(scale(mul(a,a),B),scale(mul(b,b),A)),(-2*A*B,ZZ(0)))
den=[(ZZ(1),ZZ(0)),scale(ab,-1),middle,scale(ab,-A*B),((A*B)**2,ZZ(0))]
h=[(ZZ(1),ZZ(0))]; pp=p
while pp<=n:
m=len(h); v=(ZZ(0),ZZ(0))
for r in range(1,min(4,m)+1): v=add(v,mul(den[r],h[m-r]))
h.append(scale(v,-1)); pp*=p
local[p]=h
c=[(ZZ(0),ZZ(0))]*(n+1); c[1]=(ZZ(1),ZZ(0))
for m in range(2,n+1):
p=spf[m]; r=m; e=0
while r%p==0: r//=p; e+=1
c[m]=mul(c[r],local[p][e])
return c
def coefficient_closure(k, i, n):
c=tensor_coefficients(k,n)
name='rankin%d_%d_%d' % (k,i,n)
pari('%sU=%s' % (name, '['+','.join(str(a[0]) for a in c[1:])+']'))
if k!=24:
return pari('n->vector(n,j,%sU[j]/j^%d)' % (name,(k+10)//2))
pari('%sV=%s' % (name, '['+','.join(str(a[1]) for a in c[1:])+']'))
sign='-' if i==1 else '+'
return pari('n->vector(n,j,(%sU[j]+(540%s12*sqrt(144169))*%sV[j])/j^%d)' % (name,sign,name,(k+10)//2))
def objects():
for k in WEIGHTS:
for i in range(1,3 if k==24 else 2): yield k,i
@lru_cache(None)
def compute_object(k, i, working):
bits=numberdb.bits(working)
pari.set_real_precision_bits(bits)
shift=(k+10)//2
gamma=[x+shift for x in GAMMA]
domain=[QQ(1)/QQ(2),QQ(k-13)/QQ(2),0]
template=pari.lfuncreate([pari('n->vector(n,j,1)'),0,gamma,1,1,1])
cost=pari.lfuncost(template,domain,precision=bits)
n=2*int(cost[0])+100
data=pari.lfuncreate([coefficient_closure(k,i,n),0,gamma,1,1,1])
init=pari.lfuninit(data,domain,precision=bits)
R=RealField(bits)
values={s:R(pari.lfun(init,s-shift,precision=bits)) for s in range(12,k)}
assert all((not v.is_NaN() and not v.is_infinity()) and v!=0 for v in values.values())
feq=int(pari.lfuncheckfeq(data,precision=bits))
assert feq <= -bits+64, (k,i,working,feq)
return values, {'k':k,'i':i,'working_digits':working,'bits':bits,
'internal_argument_shift':shift,'gamma_parameters':gamma,
'coefficients':n,'internal_coefficient_bits':int(cost[1]),
'lfuncheckfeq_log2_error':feq}
def decimal_value(v, digits):
return v.str(base=10, digits=digits, no_sci=False)
class RankinSelbergCritical(numberdb.Generator):
table='T691'
parameters=('k','i','ell','j','s')
type='R'
digits=OUTPUT_DIGITS
rigour='heuristic (agreement-checked)'
files=('generate.py','check_values.py','argument-selection.md','exact-roster.json',
'numerical-evidence.json','skill-evidence.json','sources.json',
'preflight-evidence.txt','readback_checks.py')
def enumerate(self):
for k,i in objects():
for s in range(12,k):
yield dict(k=str(k),i=str(i),ell='12',j='1',s=str(s))
def value(self, params, digits):
k,i,ell,j,s=(int(params[t]) for t in self.parameters)
if (k,i) not in set(objects()) or (ell,j)!=(12,1) or not 12<=s<k:
raise ValueError('outside the explicitly selected anchored critical set')
lo=compute_object(k,i,WORKING_DIGITS[0])[0][s]
hi=compute_object(k,i,WORKING_DIGITS[1])[0][s]
R=hi.parent(); rel=abs(R(lo)-hi)/abs(hi)
if rel>=R(10)**(-AGREEMENT_DIGITS):
raise ArithmeticError('working-precision agreement failed at '+str(params))
return decimal_value(hi,digits)
def compute_evidence():
start=time.time(); records=[]; calculations=[]
for k,i in objects():
low,meta1=compute_object(k,i,WORKING_DIGITS[0])
high,meta2=compute_object(k,i,WORKING_DIGITS[1]); calculations.extend([meta1,meta2])
worst=high[12].parent()(0)
for s in range(12,k):
R=high[s].parent(); rel=abs(R(low[s])-high[s])/abs(high[s]); worst=max(worst,rel)
assert rel<R(10)**(-AGREEMENT_DIGITS)
records.append({'params':dict(k=str(k),i=str(i),ell='12',j='1',s=str(s)),
'number':decimal_value(high[s],OUTPUT_DIGITS),
'low':str(low[s]),'high':str(high[s]),'relative_difference':str(rel)})
print('COMPUTED',k,i,'relative difference',worst,'elapsed',time.time()-start,flush=True)
print('NUMERICAL_JSON '+json.dumps({'table':'T691','sage':sage_version,'pari':str(pari.version()),
'numberdb':numberdb.__version__,'calculations':calculations,'records':records}),flush=True)
if __name__=='__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
key=sys.stdin.read().strip()
if '=' in key and key.split('=',1)[0].isidentifier(): key=key.split('=',1)[1].strip().strip('\"\'')
os.environ['NUMBERDB_API_KEY']=key
if '--compute' in sys.argv or os.environ.get('RANKIN_COMPUTE')=='1':
compute_evidence()
elif '--publish' in sys.argv or os.environ.get('NUMBERDB_PUBLISH')=='1':
print(RankinSelbergCritical().publish(message='Fill all 66 critical values with reproducible checks'))
else:
report=RankinSelbergCritical().verify(sample=None); print(report)
sys.exit(0 if report.ok else 1)