back to table · edit · history · where entries came from · files · download
6554 bytes, as of the version from 2026-11-02 07:16 (current). Recorded here, not run.
"""Genus-character twisted traces of singular moduli -- numberdb.org/T671.
Run with SageMath (install numberdb once with sage -pip install numberdb):
sage -python generate.py # verify all stored entries
sage -python generate.py --fill # fill the existing private draft
sage -python checks.py # offline independent checks
The trace uses all reduced positive definite integral forms, including
imprimitive forms. J_m has principal part q^(-m), with no constant term.
Jenkins, Theorem 5.1, identifies t_m(D,d)/sqrt(D) with an integer coefficient,
for positive fundamental D and every negative order discriminant -d.
We isolate that integer using rigorous Arb CM sums; we do not infer
integrality from near-integer decimal digits. Exact zero is retained.
The default selection is all D*d <= 300 and 1 <= m <= 4, in both invertible
normalizations. See argument-selection.md for each parameter's rationale.
The 768-bit working precision leaves over 100 absolute decimal places in
all coefficient-isolation balls at the default range. Every call checks
finiteness and a radius below 10^(-110), and raises instead of guessing.
Returned irrational traces use numberdb.bits(digits, losing=96) bits, so
rounding error is carried in a ball throughout. The attached checks measure
the actual accuracy, test known answers, and use independent Hecke sums.
"""
import os
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.real_arb import RealBallField
from sage.rings.complex_arb import ComplexBallField
WORKING_BITS = 768
OUTPUT_GUARD_BITS = 96
def pairs(bound=300):
for D in range(5, bound // 3 + 1):
if not ZZ(D).is_fundamental_discriminant():
continue
for d in range(3, bound // D + 1):
if d % 4 in (0, 3):
yield D, d
@lru_cache(None)
def reduced_forms(N):
"""Unique PSL2(Z) representatives of discriminant -N, no primitivity filter."""
out = []
for a in range(1, isqrt(N // 3) + 1):
for b in range(-a, a + 1):
if (b*b + N) % (4*a):
continue
c = (b*b + N) // (4*a)
if c < a or (b < 0 and (abs(b) == a or a == c)):
continue
out.append((a, b, c))
return tuple(out)
def weight(Q):
a,b,c = Q
return 3 if a == b == c else 2 if a == c and b == 0 else 1
@lru_cache(None)
def character(D, Q):
a,b,c = Q
if gcd(gcd(gcd(a,b),c),D) > 1:
return ZZ(0)
for n in (a,c,a+b+c):
if gcd(n,D) == 1:
return ZZ(D).kronecker(n)
# Residue representatives modulo D suffice, by the local definition and CRT.
for x in range(D):
for y in range(D):
n = a*x*x+b*x*y+c*y*y
if n > 0 and gcd(n,D) == 1:
return ZZ(D).kronecker(n)
raise ArithmeticError('No represented integer coprime to D')
def faber(m, J):
if m == 1: return J
if m == 2: return J*J - 393768
if m == 3: return J**3 - 590652*J - 64481280
if m == 4: return J**4 - 787536*J**2 - 85975040*J + 74069419032
raise ValueError('This generator implements the first four Hecke orders')
@lru_cache(None)
def cm_j(N, Q, bits=WORKING_BITS):
a,b,c = Q
C = ComplexBallField(bits)
tau = (C(-b) + C(0,1)*C(N).sqrt())/(2*a)
value = tau.modular_j()
if not (value.real().is_finite() and value.imag().is_finite()):
raise ArithmeticError('Nonfinite CM value')
return value
@lru_cache(None)
def coefficient_ball(m,D,d,bits=WORKING_BITS):
C = ComplexBallField(bits)
total = C(0)
for Q in reduced_forms(D*d):
chi = character(D,Q)
if chi:
total += chi*faber(m,cm_j(D*d,Q,bits)-744)/weight(Q)
result = total/C(D).sqrt()
if not (result.real().is_finite() and result.imag().is_finite()) or not result.imag().contains_zero():
raise ArithmeticError('Trace is not enclosed as a finite real')
limit = QQ(1)/ZZ(10)**110
if result.real().rad() >= limit or result.imag().rad() >= limit:
raise ArithmeticError('CM enclosure is too wide for certification')
return result.real()
@lru_cache(None)
def coefficient(m,D,d):
b = coefficient_ball(m,D,d)
n = ZZ(b.mid().round())
# The theorem supplies integrality; this interval identifies its unique integer.
if n not in b or n-1 in b or n+1 in b:
raise ArithmeticError('Normalized trace does not isolate one integer')
return n
class TwistedTraces(numberdb.Generator):
table = 'T671'
parameters = ('m','D','d','normalisation')
type = 'R'
digits = 100
rigour = 'proven'
files = ('generate.py','checks.py','argument-selection.md','numerical-evidence.md',
'skill-evidence.json','companions.json')
def enumerate(self, bound=300, max_m=4):
if max_m > 4:
raise ValueError('Extend the Faber implementation before raising max_m')
for m in range(1,max_m+1):
for D,d in pairs(bound):
for norm in ('trace','coefficient'):
yield dict(m=m,D=D,d=d,normalisation=norm)
def value(self,params,digits):
m,D,d = (int(params[k]) for k in ('m','D','d'))
if m < 1 or D <= 1 or not ZZ(D).is_fundamental_discriminant() or d < 3 or d%4 not in (0,3):
raise ValueError('Arguments are outside the mathematical domain')
A = coefficient(m,D,d)
norm = params['normalisation']
if norm == 'coefficient' or A == 0:
value = A
elif norm == 'trace':
R = RealBallField(numberdb.bits(digits,losing=OUTPUT_GUARD_BITS))
value = A*R(D).sqrt()
else:
raise ValueError('Unknown normalization')
return {'number':value,'param-latex':'$t_m$' if norm=='trace' else '$A_m$'}
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
gen = TwistedTraces()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(gen.publish(message='Add certified genus-character traces and exact coefficients'))
else:
report = gen.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)