back to table · edit · history · where entries came from · files · download
7520 bytes, as of the version from 2026-10-31 10:48 (current). Recorded here, not run.
"""Independent checks for T635, including all entries fetched from the draft.
sage -python checks.py # offline checks against generate.py
sage -python checks.py --stored # read T635 using NUMBERDB_API_KEY
This checker reconstructs the input series from canonical modular equations
(T309) and archived j coefficients (T79). It does not use the generator's eta
products, divisor sums, convolution helper, or Faber recurrence. All source
snapshots needed here are in check-data.json. A triangular Laurent cancellation
recovers each polynomial independently. Positive Laurent coefficients also
satisfy the twisted Hecke relation for prime-order Monster classes.
"""
import json
import os
import sys
from pathlib import Path
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
PR = PolynomialRing(ZZ, 'X')
X = PR.gen()
QR = PolynomialRing(ZZ, 'q')
q = QR.gen()
MR = PolynomialRing(ZZ, names=('x', 'y'))
x, y = MR.gens()
CLASSES = ('1A', '2A', '2B', '3A', '3B', '5A', '5B', '7A', '7B', '13A', '13B')
DATA = json.loads(Path(__file__).with_name('check-data.json').read_text())
J = {int(k): ZZ(v) for k,v in DATA['j_coefficients'].items()}
@lru_cache(None)
def independent_coefficients(g, bound=60):
if g == '1A':
return {**{k:J[k] for k in range(-1,bound+1)},0:ZZ(0)}
p = int(g[:-1]); exponent=24//(p-1); scale=ZZ(p)**(12//(p-1))
phi = MR(DATA['canonical_equations'][str(p)])
assert phi.degree(y)==1 and phi.monomial_coefficient(x*y)==-1
a = phi + x*y
assert all(b==0 for (i,b) in a.dict())
# Phi(m,j)=A(m)-m*j=0, m=C*q+..., determines one new coefficient at a time.
coeffs = {int(i):v for (i,b),v in a.dict().items()}
m = QR(0)
for k in range(1,bound+3):
am = QR(0)
for i in range(max(coeffs),-1,-1):
am = (am*m+coeffs.get(i,0)).truncate(k)
known = sum((J[i]*m[k-1-i] for i in range(k-1)), ZZ(0))
m += (am[k-1]-known)*q**k
assert m[1]==scale
assert all(v % scale == 0 for v in m.list())
h = [m[k+1]//scale for k in range(bound+2)]
inv = [ZZ(1)]
for k in range(1,bound+2):
inv.append(-sum((h[i]*inv[k-i] for i in range(1,k+1)), ZZ(0)))
assert inv[1]+exponent == 0
c = {-1:ZZ(1),0:ZZ(0)}
for k in range(1,bound+1):
c[k] = inv[k+1] + (m[k] if g[-1]=='A' else 0)
return c
def laurent_mul(a,b,upper):
result = {}
for i,ai in a.items():
for j,bj in b.items():
if i+j <= upper:
result[i+j] = result.get(i+j,ZZ(0))+ai*bj
return {k:v for k,v in result.items() if v}
@lru_cache(None)
def powers(g,max_degree=20):
c = independent_coefficients(g)
# After any intermediate product at most max_degree further factors occur,
# each with minimum exponent -1. Keeping through max_degree+3 is enough
# to certify final coefficients through q^3.
upper=max_degree+3
series={k:v for k,v in c.items() if k<=upper and v}
result=[{0:ZZ(1)}]
for n in range(1,max_degree+1):
result.append(laurent_mul(result[-1],series,upper))
return result
def evaluate_laurent(poly,g):
result={}
for i,coef in enumerate(poly.list()):
for e,v in powers(g)[i].items():
result[e]=result.get(e,ZZ(0))+coef*v
return {e:v for e,v in result.items() if v}
def independent_polynomial(g,n):
ps=powers(g); residual=dict(ps[n]); result=X**n
for k in range(n-1,-1,-1):
coefficient=residual.get(-k,ZZ(0))
result-=coefficient*X**k
for e,v in ps[k].items():
residual[e]=residual.get(e,ZZ(0))-coefficient*v
return result
def replication_coefficient(g,n,r):
p = 1 if g=='1A' else int(g[:-1])
answer=ZZ(0)
for a in range(1,min(n,r)+1):
if n%a==0 and r%a==0:
power_class='1A' if p==1 or a%p==0 else g
answer+=(n//a)*independent_coefficients(power_class)[n*r//(a*a)]
return answer
def check_one(poly,g,n):
assert poly.degree()==n and poly[n]==1
assert poly[n-1]==0
assert poly == independent_polynomial(g,n), (g,n,'triangular cancellation')
substituted=evaluate_laurent(poly,g)
assert {e:v for e,v in substituted.items() if e<=0} == {-n:ZZ(1)}, (g,n,'principal part')
for r in range(1,4):
assert substituted.get(r,0)==replication_coefficient(g,n,r), (g,n,r,'replication')
def run_checks(values):
# Positive controls establish the input-series normalization and a nonzero
# polynomial answer. Deliberate corruptions must fail before checking data.
assert independent_coefficients('1A')[1]==196884
assert independent_coefficients('2A')[1]==4372
assert independent_coefficients('2B')[2]==-2048
assert independent_coefficients('13A')[1]==12
assert independent_coefficients('13B')[1]==-1
assert independent_polynomial('1A',2)==X**2-393768
assert independent_polynomial('2A',3)==X**3-13116*X-288768
for bad in [X**2-393767,X**2+X-393768,PR(0)]:
try: check_one(bad,'1A',2)
except AssertionError: pass
else: raise AssertionError('Negative control accepted')
expected={(g,n) for g in CLASSES for n in range(2,21)}
assert set(values)==expected
for (g,n),poly in values.items():
check_one(poly,g,n)
# Explicit examples in Bannai-Kojima-Miezaki pp.24-25 use t_1A=T_1A+744
# and t_2A=T_2A+104. Substitute X+shift to compare our zero-constant form.
published={
'1A':(744,[X**2-1488*X+159768,
X**3-2232*X**2+1069956*X-36866976,
X**4-2976*X**3+2533680*X**2-561444608*X+8507424792,
X**5-3720*X**4+4550940*X**3-2028551200*X**2+246683410950*X-1963211493744,
X**6-4464*X**5+7121736*X**4-4850017536*X**3+1304194222980*X**2-96687754014528*X+453039686271072]),
'2A':(104,[X**2-208*X+2072,
X**3-312*X**2+19332*X-49568,
X**4-416*X**3+47408*X**2-1246976*X+1146904,
X**5-520*X**4+86300*X**3-4909600*X**2+65094150*X-26542704,
X**6-624*X**5+136008*X**4-12162304*X**3+397216644*X**2-2958154560*X+614211680])}
for g,(shift,polys) in published.items():
for n,poly in enumerate(polys,2):
assert values[g,n]==poly(X+shift), (g,n,'published example')
return {'entries':len(values),'principal_part_checks':209,
'replication_coefficients':627,'published_polynomial_examples':10,
'negative_controls_rejected':3,'series_source':'T79 and T309, independent of generator eta recurrence'}
def read_stored():
from numberdb._generate import _stored_entries
doc=numberdb.table('T635')
# Use the client parser used by verify, preserving nested parameter groups.
return {(identity.split(',')[0],int(identity.split(',')[1])):PR(value)
for identity,value in _stored_entries(doc).items()}
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)
if '--stored' in sys.argv or os.environ.get('NUMBERDB_CHECK_STORED')=='1':
values=read_stored()
else:
from generate import MonstrousFaber
generator=MonstrousFaber()
values={(p['g'],p['n']):generator.value(p,100) for p in generator.enumerate()}
print(json.dumps(run_checks(values),indent=2))