back to table · edit · history · where entries came from · files · download
6280 bytes, as of the version from 2026-11-02 20:37 (current). Recorded here, not run.
"""Petersson norms of higher-level newforms -- numberdb.org/T684.
Run with SageMath and the attachments forms.py and roster.json:
sage -pip install numberdb
sage -python generate.py # verify all stored entries
sage -python generate.py --fill # fill this draft with an API key
All 138 embeddings of the family shared with T683 are included in the integral
and index-normalized conventions. See argument-selection.md. Exact coefficient
fields, isolating rectangles and Sturm coefficients identify the forms.
PARI mfsymbol/mfpetersson uses Haberland period pairings. Larger coefficient
fields are evaluated by sesquilinearity from a rational-basis Gram matrix.
Fresh computations at
160 and 220 decimal working digits support 100 recorded significant digits by
agreement only. This is not certified interval integration. Diagonal imaginary
parts and off-diagonal entries are checked before the real self-products are
returned. The normalization factor is the exact index of Gamma_0(N).
"""
import sys
from functools import lru_cache
import numberdb.sage as numberdb
from sage.libs.pari import pari
from sage.rings.complex_mpfr import ComplexField
from sage.rings.real_mpfr import RealField
from sage.rings.integer_ring import ZZ
from forms import ROSTER, BY_FORM, reconstruct
WORKING_DIGITS=(160,220)
# PARI's default stack is too small for the larger modular-symbol spaces.
pari.allocatemem(200000000, silent=True)
def index(level):
return ZZ(level)*prod_factors(level)
def prod_factors(level):
from sage.rings.rational_field import QQ
result=QQ(1)
for p in ZZ(level).prime_divisors():
result*=QQ(p+1)/QQ(p)
return result
@lru_cache(maxsize=4)
def basis_gram(spec,working):
"""Pair a rational basis before embedding the larger coefficient fields.
All selected characters take rational values. This is sesquilinearity of
the same Petersson pairing, with much lower peak memory than applying
mfsymbol directly to a degree-four or larger coefficient field.
"""
bits=numberdb.bits(working,losing=32);C=ComplexField(bits)
pari.set_real_precision(working+12)
mf=pari.mfinit(list(spec),0);basis=list(pari.mfbasis(mf));n=len(basis)
split=(n+1)//2;result=[[C(0) for _ in range(n)] for _ in range(n)]
first=[]
for f in basis[:split]:
first.append(pari.mfsymbol(mf if not first else first[0],f,precision=bits))
for i,a in enumerate(first):
for j,b in enumerate(first):result[i][j]=C(pari.mfpetersson(a,b))
for j in range(split,n):
b=pari.mfsymbol(first[0],basis[j],precision=bits)
for i,a in enumerate(first):
result[i][j]=C(pari.mfpetersson(a,b))
result[j][i]=C(pari.mfpetersson(b,a))
del b
del a,first
second=[]
for f in basis[split:]:
second.append(pari.mfsymbol(mf if not second else second[0],f,precision=bits))
for i,a in enumerate(second,split):
for j,b in enumerate(second,split):result[i][j]=C(pari.mfpetersson(a,b))
return result
@lru_cache(maxsize=None)
def orbit_norms(label,working):
row=next(r for r in ROSTER if r['label']==label)
bits=numberdb.bits(working,losing=32);C=ComplexField(bits);R=RealField(bits)
pari.set_real_precision(working+12)
mf,f,h,embedded=reconstruct(row)
d=row['degree']
if d>=4:
spec=(row['level'],row['weight'],row['character_discriminant'])
base=basis_gram(spec,working)
coords=pari.mftobasis(mf,f)
vectors=[[C(a) for a in v] for v in pari.mfembed(f,coords,precision=bits)]
assert len(vectors)==d
n=len(base)
matrix=[[sum((vectors[i][a]*vectors[j][b].conjugate()*base[a][b]
for a in range(n) for b in range(n)),C(0))
for j in range(d)] for i in range(d)]
else:
symbol=pari.mfsymbol(mf,f,precision=bits)
gram=pari.mfpetersson(symbol)
matrix=[[C(gram)]] if d==1 else [[C(gram[i,j]) for j in range(d)] for i in range(d)]
scale=max(abs(matrix[i][i]) for i in range(d))
tolerance=R(10)**(-working+20)*scale
for i in range(d):
assert matrix[i][i].real()>0
assert abs(matrix[i][i].imag())<tolerance
for j in range(d):
if i!=j: assert abs(matrix[i][j])<tolerance,(label,i,j,'orthogonality')
fp=pari.mfparams(f)[3]
images=[C(0)] if d==1 else [C(z) for z in pari.mfembed(f,pari.Mod('y',fp),precision=bits)]
values={};used=set()
for full,(root,is_real) in embedded.items():
target=C(h(root))
matches=[j for j,z in enumerate(images) if abs(z-target)<R(10)**(-working+20)]
assert len(matches)==1,(full,'embedding match')
j=matches[0];assert j not in used;used.add(j)
values[full]=matrix[j][j].real()
assert len(used)==d
return values
class PeterssonNorms(numberdb.Generator):
table='T684'
parameters=('form','normalization')
type='R'
digits=100
rigour='heuristic (agreement-checked)'
files=('generate.py','forms.py','roster.json','checks.py','quadrature.py',
'argument-selection.md','numerical-evidence.json','source-provenance.json',
'skill-manifest.json','check.py','dry_run.py','prime_checks.py',
'catalogue_coefficients.py','hecke-nf.json','cc-records.json','catalogue.json')
def enumerate(self):
for row in ROSTER:
for e in row['embeddings']:
for convention in ('integral','index-normalized'):
yield {'form':e['label'],'normalization':convention}
def value(self,params,digits):
assert digits<=100,'Reassess working precision before extending digits.'
row,e=BY_FORM[params['form']]
assert params['normalization'] in ('integral','index-normalized')
multiplier=index(row['level']) if params['normalization']=='integral' else ZZ(1)
return numberdb.agreeing(lambda w:multiplier*orbit_norms(row['label'],w)[params['form']],at=WORKING_DIGITS)
if __name__=='__main__':
if '--fill' in sys.argv:
print(PeterssonNorms().publish(message='Fill both Petersson conventions after numerical checks'))
else:
result=PeterssonNorms().verify(sample=None);print(result)
sys.exit(0 if result.ok else 1)