back to table · edit · history · where entries came from · files · download
5029 bytes, as of the version from 2026-11-01 06:59 (current). Recorded here, not run.
"""Zagier traces of singular moduli -- numberdb.org/T643.
Run with SageMath and the numberdb package:
sage -pip install numberdb # once
sage -python generate.py # verify all stored entries
sage -python generate.py --fill # fill the existing draft
The fill operation needs NUMBERDB_API_KEY. It does not publish the draft.
For stdin credentials set NUMBERDB_KEY_FROM_STDIN=1. The campaign wrapper
also accepts NUMBERDB_PUBLISH=1 to select filling rather than verification.
Exact class polynomials are computed by FLINT/Arb via Sage. For every square
f^2 dividing d with e=d/f^2 a positive discriminant magnitude, compute the
trace of P_m(x-744) in Q[x]/H_{-e}, weighted by 1/3 for e=3, 1/2 for e=4,
and 1 otherwise. The square-divisor sum includes all imprimitive forms.
The trace is the sum of diagonal coefficients of multiplication by P_m(x-744).
All final rationals are required to be integers, with no floating rounding.
Faber polynomials are built from E4^3/Delta using finite integer products.
Default enumeration is m=1..4, then d=3..200 with d=0 or 3 modulo 4.
These are noncanonical finite extents of discrete and arithmetic families;
see argument-selection.md. No downloaded data or helper file is required.
"""
import os
import sys
from functools import lru_cache
from math import isqrt
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.schemes.elliptic_curves.cm import hilbert_class_polynomial
M_MAX = 4
D_MAX = 200
ZX = PolynomialRing(ZZ, 'X')
X = ZX.gen()
Qx = PolynomialRing(QQ, 'x')
x = Qx.gen()
def multiply(a, b, bound):
out = [ZZ(0)] * (bound + 1)
for i, ai in enumerate(a[:bound + 1]):
for j, bj in enumerate(b[:bound + 1 - i]):
out[i + j] += ai * bj
return out
@lru_cache(None)
def j_positive_coefficients(limit):
"""Return coefficients c(n) of J=q^-1+sum c(n)q^n, 1<=n<=limit."""
bound = limit + 1
e4 = [ZZ(1)] + [ZZ(0)] * bound
for divisor in range(1, bound + 1):
for n in range(divisor, bound + 1, divisor):
e4[n] += 240 * ZZ(divisor)**3
numerator = multiply(multiply(e4, e4, bound), e4, bound)
denominator = [ZZ(1)] + [ZZ(0)] * bound
for n in range(1, bound + 1):
for repetition in range(24):
for k in range(bound, n - 1, -1):
denominator[k] -= denominator[k - n]
quotient = [ZZ(1)] + [ZZ(0)] * bound
for n in range(1, bound + 1):
quotient[n] = numerator[n] - sum(
denominator[k] * quotient[n - k] for k in range(1, n + 1))
assert quotient[1] == 744
return tuple(quotient[n + 1] for n in range(1, limit + 1))
@lru_cache(None)
def faber(m):
m = int(m)
if m < 1:
raise ValueError('Hecke order must be positive')
c = (ZZ(0),) + j_positive_coefficients(m - 1)
polynomials = {1: X}
for n in range(2, m + 1):
polynomials[n] = (X * polynomials[n - 1]
- sum(c[k] * polynomials[n - 1 - k]
for k in range(1, n - 1)) - n * c[n - 1])
return polynomials[m]
@lru_cache(None)
def class_polynomial(e):
return Qx(hilbert_class_polynomial(-ZZ(e), algorithm='arb'))
@lru_cache(None)
def primitive_trace(m, e):
hpoly = class_polynomial(e)
degree = hpoly.degree()
expression = Qx(faber(m)(x - 744)) % hpoly
trace = QQ(0)
for k in range(degree):
trace += expression[k]
expression = (x * expression) % hpoly
return trace
def trace(m, d):
m, d = int(m), int(d)
if m < 1 or d < 3 or d % 4 not in (0, 3):
raise ValueError('Expected m>=1 and d>0 congruent to 0 or 3 modulo 4')
total = QQ(0)
for f in range(1, isqrt(d) + 1):
if d % (f * f):
continue
e = d // (f * f)
if e % 4 not in (0, 3):
continue
weight = 3 if e == 3 else 2 if e == 4 else 1
total += primitive_trace(m, e) / QQ(weight)
assert total.denominator() == 1, (m, d, total)
return ZZ(total)
class ZagierTraces(numberdb.Generator):
table = 'T643'
parameters = ('m', 'd')
type = 'Z'
rigour = 'exact'
def enumerate(self, m_max=M_MAX, d_max=D_MAX):
for m in range(1, m_max + 1):
for d in range(3, d_max + 1):
if d % 4 in (0, 3):
yield {'m': m, 'd': d}
def value(self, params, digits):
return trace(params['m'], params['d'])
if __name__ == '__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
numberdb.configure(api_key=sys.stdin.read().strip())
generator = ZagierTraces()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(generator.publish(message='Fill the draft with exact weighted CM traces'))
else:
report = generator.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)