back to table · edit · history · where entries came from · files · download
4639 bytes, as of the version from 2026-10-31 10:48 (current). Recorded here, not run.
"""Hecke-type Faber polynomials of monstrous McKay–Thompson series -- numberdb.org/T635.
Run with SageMath:
sage -pip install numberdb
sage -python generate.py # verify every stored entry
sage -python generate.py --publish # fill the draft, NUMBERDB_API_KEY set
sage -python checks.py # independent checks, no network
The --publish option sends entries to the existing draft; it does not make
that draft public. The class index uses ATLAS labels, q=exp(2*pi*i*tau),
zero constant coefficient, and P_n(T_g)=q^(-n)+O(q), without division by n.
All arithmetic is exact over ZZ. See argument-selection.md for the range.
The input series are computed from eta products using their logarithmic
coefficients; the polynomials use the Faber recurrence. The separate checks
use direct product multiplication, Laurent cancellation, archived canonical
modular equations and j coefficients, and published polynomial examples.
"""
import os
import sys
from functools import lru_cache
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
R = PolynomialRing(ZZ, 'X')
X = R.gen()
PRIMES = (2, 3, 5, 7, 13)
CLASSES = ('1A',) + tuple(str(p) + suffix for p in PRIMES for suffix in ('A', 'B'))
MAX_DEGREE = 20
def convolution(a, b, limit):
return [sum((a[k] * b[n-k] for k in range(max(0, n-len(b)+1), min(n+1, len(a)))), ZZ(0))
for n in range(limit+1)]
def sigma(n, power=1):
return sum((ZZ(d)**power for d in range(1, n+1) if n % d == 0), ZZ(0))
def from_log_derivative(log_coeffs, limit):
out = [ZZ(1)]
for n in range(1, limit+1):
total = sum((log_coeffs[k] * out[n-k] for k in range(1, n+1)), ZZ(0))
assert total % n == 0
out.append(total // n)
return out
@lru_cache(None)
def coefficients(g, limit):
"""Return c_g(1),...,c_g(limit) as a list with c_g(0)=0."""
if g not in CLASSES:
raise ValueError('Unsupported Monster class: ' + g)
length = limit+1 # q*T requires one additional coefficient
if g == '1A':
e4 = [ZZ(1)] + [240*sigma(n, 3) for n in range(1, length+1)]
reciprocal_delta = from_log_derivative([ZZ(0)]+[24*sigma(n) for n in range(1, length+1)], length)
numerator = convolution(convolution(e4, e4, length), e4, length)
qj = convolution(numerator, reciprocal_delta, length)
assert qj[:2] == [1, 744]
return tuple([ZZ(0)] + qj[2:])
p = int(g[:-1]); e = 24 // (p-1)
logs = [ZZ(0)] + [-e*(sigma(n) - (p*sigma(n//p) if n % p == 0 else 0))
for n in range(1, length+1)]
product = from_log_derivative(logs, length)
assert product[:2] == [1, -e]
c = [ZZ(0)] + product[2:]
if g[-1] == 'A':
inverse = from_log_derivative([-v for v in logs], length)
scale = ZZ(p)**(12 // (p-1))
for n in range(1, limit+1):
c[n] += scale*inverse[n-1]
return tuple(c)
@lru_cache(None)
def faber(g, degree):
if degree < 1:
raise ValueError('The degree must be positive')
c = coefficients(g, degree-1)
polynomials = [R(1), X]
for n in range(2, degree+1):
polynomials.append(X*polynomials[n-1]
- sum((c[k]*polynomials[n-1-k] for k in range(1, n-1)), R(0))
- n*c[n-1])
return polynomials[degree]
class MonstrousFaber(numberdb.Generator):
table = 'T635'
parameters = ('g', 'n')
type = 'Z[]'
rigour = 'exact'
digits = 100
files = ('generate.py', 'checks.py', 'check-data.json', 'argument-selection.md',
'numerical-evidence.md', 'skill-manifest.json')
def enumerate(self, max_degree=MAX_DEGREE):
for g in CLASSES:
for n in range(2, max_degree+1):
yield {'g': g, 'n': n}
def value(self, params, digits):
return faber(str(params['g']), int(params['n']))
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].strip().isidentifier():
key = key.split('=', 1)[1].strip().strip('\"\'')
numberdb.configure(api_key=key)
generator = MonstrousFaber()
if '--publish' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(generator.publish(message='Exact monic Faber polynomials for eleven Monster classes, degrees 2 through 20'))
else:
report = generator.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)