back to table · edit · history · where entries came from · files · download
9173 bytes, as of the version from 2026-10-27 21:54 (current). Recorded here, not run.
"""Exact checks for numberdb.org/T562; run with sage -python checks.py.
Uses the standard campaign check.py for exactness, size and independent
comparison. The mathematical checks use fractions.Fraction independently of
the generator's Sage arithmetic. See source-notes.md for theorem locators.
Optional --stored reads the authenticated draft and T561 from NumberDB;
otherwise T561.json is the archived companion input. Both paths compare all
25 values. Read a token from stdin with NUMBERDB_KEY_FROM_STDIN=1.
"""
import json
import os
import sys
from pathlib import Path
from fractions import Fraction as F
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.env import SAGE_VERSION
from generate import LyapunovSums, hyperelliptic_sum
from check import exactness, measure, agrees_with, names_its_rings
W = Path(__file__).resolve().parent
def rational(value):
v = F(str(value))
return QQ(v.numerator) / QQ(v.denominator)
def kappa(mu):
# The equivalent form m(m+2)/(m+1) = (m+1)-1/(m+1).
return sum((F(m+1)-F(1,m+1) for m in mu), F(0))/12
def partitions(total, maximum=None):
if not total:
yield ()
return
for head in range(min(total, maximum or total), 0, -1):
for tail in partitions(total-head, head):
yield (head,)+tail
def components(g, mu):
# KZ Theorems 1-2, independently enumerate all classified components.
if g == 2:
return ('hyp',)
if g == 3:
return ('hyp','odd') if mu in ((4,),(2,2)) else ('connected',)
hyper = mu in ((2*g-2,), (g-1,g-1))
if all(m % 2 == 0 for m in mu):
return (('hyp',) if hyper else ()) + ('even','odd')
return ('hyp','nonhyp') if hyper else ('connected',)
def signature(params):
return (int(params['g']), tuple(map(int,params['mu'].split(','))), params['component'])
def document_values(document):
block=document['Numbers']; out={}
if isinstance(block,list):
for row in block:
out[signature(row['params'])] = str(row['number'])
else:
for g, profiles in block.items():
for mu, comps in profiles.items():
for c, row in comps.items():
out[(int(g),tuple(map(int,str(mu).split(','))),c)] = str(row['number'] if isinstance(row,dict) else row)
return out
def hyperelliptic_cover(g, mu):
# EKZ Corollary 1, before its closed forms: sphere quadratic orders.
ds = [2*g-3]+[-1]*(2*g+1) if len(mu)==1 else [2*g-2]+[-1]*(2*g+2)
assert sum(ds)==-4
return sum((F(1,d+2) for d in ds if d%2), F(0))/4
def divisor_sum(mu, a, d, marked=()):
# CM Props. 4.5,4.8. Divide the vanishing divisor intersection by C.lambda.
# a + sum b_i/((m_i+1)L) - d*(12 - 12*kappa/L) = 0.
return (sum((F(b,m+1) for m,b in marked), F(0))+12*d*kappa(mu))/(12*d-a)
def cylinder_sum(g, mu, rows):
# EMZ's r=c*zeta(2); EKZ/Vorobets: L=kappa+2 sum(q*r)/(D-1).
dimension=2*g+len(mu)-1
weighted=sum((q*F(r) for q,r in rows),F(0))
return kappa(mu)+2*weighted/(dimension-1)
def run(values, companion):
# Controls must pass before using the independent methods.
assert kappa((2,))==F(2,9)
assert hyperelliptic_cover(2,(2,))==F(4,3)
assert divisor_sum((4,),9,1)==F(8,5)
# EMZ Example 14.7: only the cylinder configuration contributes, r=5/3.
assert cylinder_sum(2,(2,),[(0,'10/3'),(1,'5/3')])==F(4,3)
# Positive control for Arb's containment and finite-width checks.
field=RealBallField(512)
known=field(1)/3
assert known.is_finite() and (QQ(1)/QQ(3) in known) and field(known.rad())<field(2)**(-500)
assert not (QQ(1)/QQ(2) in known)
expected=[(g,mu,c) for g in range(2,5) for mu in partitions(2*g-2) for c in components(g,mu)]
assert list(values)==expected and len(values)==25
assert len({(g,mu) for g,mu,c in values})==18
assert not exactness(values) and not names_its_rings(str(W/'generate.py'))
source={signature(r):F(r['number']) for r in json.loads((W/'source-column.json').read_text())}
assert set(source)==set(values)
assert not agrees_with(values,lambda key:rational(source[key]))
for (g,mu,c),value in values.items():
assert 1 < value < g and value>rational(kappa(mu))
hyp={key:value for key,value in values.items() if key[2]=='hyp'}
assert not agrees_with(hyp,lambda key:rational(hyperelliptic_cover(key[0],key[1])))
for key,v in hyp.items():
assert hyperelliptic_sum(key[0],len(key[1]))==v
# Farkas Thm 4.6, r=1,s=2: normalize the psi coefficient to one.
r,s=1,2
a=F((r+2)*(r*r*s**3-r*r*s+2*r*s**3+6*r*s*s-2*r*s-8*r+s**3+6*s*s+3*s-8),2*(s+r+1)*(r*s+s-2))
c=F(s+1,2)
b=F((s-1)*(s+1)*(r+1)*(r+2)*(r*s+s+4),12*(s+r+1)*(r*s+s-2))
assert (a/c,b/c)==(8,1)
assert divisor_sum((3,3),8,1,[(3,-1),(3,-1)])==F(7,4)
# Eight divisor-intersection calculations plus two odd-spin divisor slopes.
cm={
(3,(4,),'odd'): divisor_sum((4,),9,1),
(3,(3,1),'connected'): divisor_sum((3,1),9,1),
(3,(2,1,1),'connected'): divisor_sum((2,1,1),-1,0,[(2,1),(1,3)]),
(4,(6,),'even'): divisor_sum((6,),30,4,[(6,60)]),
(4,(6,),'odd'): divisor_sum((6,),8,1,[(6,4)]),
(4,(5,1),'connected'): divisor_sum((5,1),8,1,[(5,4)]),
# Farkas Thm 4.6 has positive psi coefficients; CM Eq. (8) misprints signs.
(4,(3,3),'nonhyp'): divisor_sum((3,3),8,1,[(3,1),(3,1)]),
(4,(3,2,1),'connected'): divisor_sum((3,2,1),-1,0,[(3,1),(2,1),(1,3)]),
}
for g,mu in [(3,(2,2)),(4,(2,2,2))]:
slope=F(4*(g+8),g+2)
cm[(g,mu,'odd')]=12*kappa(mu)/(12-slope)
assert len(cm)==10
disagreements=agrees_with({key:values[key] for key in cm},lambda key:rational(cm[key]))
assert not disagreements,disagreements
# EMZ Appendix B.2 p.100: q counts double arrows cyclically (end repeats start).
even_rows=[(0,'180/37'),(1,'45/74'),(1,'225/37'),(0,'225/296'),(2,'5/37')]
odd_rows=[(0,'252/31'),(1,'63/62'),(1,'144/31'),(2,'9/155'),(2,'16/155'),(0,'4/93'),(3,'1/186')]
even=cylinder_sum(4,(2,2,2),even_rows)
assert even==F(737,333) and even!=F(166,75)
assert F(166,75)-even==F(1,8325)
assert rational(even)==values[(4,(2,2,2),'even')]
assert rational(cylinder_sum(4,(2,2,2),odd_rows))==values[(4,(2,2,2),'odd')]
# Separate configuration reconstruction for H(3,3)^nonhyp.
nonhyp_rows=[(1,'3699/1120'),(0,'64/5'),(1,'64/35'),(1,'27/80'),(0,'1/5'),(1,'1/32')]
assert rational(cylinder_sum(4,(3,3),nonhyp_rows))==values[(4,(3,3),'nonhyp')]
# All companion decimal values denote exact center +/- one last-place unit.
# T561 derives its constants from EKZ; this is consistency, not independence.
area=document_values(companion)
assert set(area)==set(values)
max_radius=field(0)
for key,text in area.items():
center=F(text); ulp=F(1,10**len(text.partition('.')[2]))
interval=field(rational(center)); interval=interval.add_error(field(rational(ulp)))
back=field(rational(kappa(key[1])))+field.pi()**2*interval/3
assert back.is_finite() and field(back.rad())<field(10)**(-95)
assert values[key] in back,key
max_radius=max(max_radius,field(back.rad()))
# The checker must detect a corrupted independently checked row.
damaged={key:value for key,value in hyp.items()}
first=next(iter(damaged)); damaged[first]+=1
assert agrees_with(damaged,lambda key:rational(hyperelliptic_cover(key[0],key[1])))
# And an omitted component must not pass completeness.
assert list(values)[:-1]!=expected
return {'entries':len(values),'profiles':18,'exact_source_matches':25,
'hyperelliptic_cover_checks':6,'CM_geometric_checks':10,
'EMZ_configuration_checks':3,'additional_check_unique_entries':17,
'companion_relation_checks':25,'companion_max_radius':str(max_radius),
'source_disagreement':{'EKZ':'737/333','CM_section_6_9':'166/75',
'EMZ_reconstruction':str(even),'difference_CM_minus_EMZ':'1/8325'},
'controls':'passed, including corruption rejection','measurement':measure(values),
'sage':SAGE_VERSION,'python':sys.version.split()[0],
'skill_manifest':'agents/runs/20261027T213908Z-build-skills/manifest.json',
'skill_sha256':'e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c'}
if __name__=='__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
numberdb.configure(api_key=sys.stdin.read().strip())
if '--stored' in sys.argv:
document=numberdb.table('T562')
values={key:rational(v) for key,v in document_values(document).items()}
companion=numberdb.table('T561')
mode='stored'
else:
gen=LyapunovSums(); values={}
for p in gen.enumerate():
v=gen.value(p,100); values[signature(p)]=v['number'] if isinstance(v,dict) else v
companion=json.loads((W/'T561.json').read_text()); mode='generated'
result=run(values,companion); result['mode']=mode
print(json.dumps(result,indent=2))