back to table · edit · history · where entries came from · files · download
5932 bytes, as of the version from 2026-10-16 11:15 (current). Recorded here, not run.
"""Reproducible independent checks for Simple Hurwitz numbers -- numberdb.org/T463.
sage -python checks.py
sage -python checks.py --stored
Download generate.py, independent.py, check.py and published-examples.json
beside this file. Offline mode checks freshly computed values. --stored
reads the actual database entries and runs the same checks. A private draft
needs NUMBERDB_API_KEY, or NUMBERDB_KEY_FROM_STDIN=1 and a key on stdin.
The checker does not use the generator's cut-and-join code for its expected
answers. It uses symmetric-group characters, direct small-degree monodromy,
genus-zero Hurwitz's formula, and genus-one ELSV intersection theory.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from fractions import Fraction
from math import factorial, prod, comb
from pathlib import Path
from itertools import combinations_with_replacement
from collections import Counter
import json, os, sys, time
from check import exactness, measure, agrees_with, names_its_rings
import independent as ind
from generate import SimpleHurwitz
HERE=Path(__file__).resolve().parent
def as_qq(value):
"""Compare exact rationals through explicit numerator/denominator conversion."""
return QQ(value.numerator)/QQ(value.denominator)
def one_part(g,d):
r=2*g+d-1
return Fraction(sum((-1)**k*comb(d-1,k)*(d*(d-1-2*k)//2)**r
for k in range(d)),d*factorial(d))
def expected_keys():
out=set()
for d in range(1,11):
for n in range(1,d+1):
for ascending in combinations_with_replacement(range(1,d+1),n):
if sum(ascending)!=d:continue
mu=tuple(reversed(ascending))
for g in range(6):
if 2*g-2+n>0:out.add((g,mu))
return out
def flatten(doc):
values={}
for g,rows in doc['Numbers'].items():
for mu,v in rows.items():
key=(int(g),tuple(map(int,mu.split(','))))
assert key not in values
raw=v['number'] if isinstance(v,dict) else v
values[key]=as_qq(Fraction(str(raw)))
return values
def run(values):
start=time.monotonic()
report={}
# Known controls run before the bulk comparisons.
report['character_orthogonality_equations']=ind.character_controls(10)
assert ind.hurwitz_character(1,(2,))==Fraction(1,2)
assert ind.hurwitz_character(1,(1,1))==1
assert ind.hurwitz_character(0,(1,1,1))==24
assert ind.hurwitz_character(1,(4,))==160
assert ind.hurwitz_character(2,(3,1))==45927
assert ind.elsv_one((4,))==160
assert ind.elsv_one((1,1))==1
assert ind.genus_zero((1,1,1))==24
assert one_part(2,4)==5824
assert ind.hurwitz_character(1,(1,1))!=Fraction(1,2) # unlabelled control
assert ind.hurwitz_character(1,(4,))!=Fraction(160,factorial(5)) # r! control
assert exactness({'bad':0.5})
assert agrees_with({'bad':QQ(2)},lambda k:QQ(1))
report['positive_and_negative_controls']='pass'
assert len(values)==793 and set(values)==expected_keys()
assert not exactness(values)
assert not names_its_rings(str(HERE/'generate.py'))
assert all(v>=0 for v in values.values())
wrong=agrees_with(values,lambda k:as_qq(ind.hurwitz_character(*k)))
assert not wrong,wrong[:4]
report['independent_character_matches']=len(values)
print('All 793 character comparisons pass.',flush=True)
genus0={k:v for k,v in values.items() if k[0]==0}
assert not agrees_with(genus0,lambda k:as_qq(ind.genus_zero(k[1])))
report['genus_zero_formula_matches']=len(genus0)
genus1={k:v for k,v in values.items() if k[0]==1}
assert not agrees_with(genus1,lambda k:as_qq(ind.elsv_one(k[1])))
report['genus_one_elsv_matches']=len(genus1)
single={k:v for k,v in values.items() if len(k[1])==1}
assert not agrees_with(single,lambda k:as_qq(one_part(k[0],k[1][0])))
report['one_part_formula_matches']=len(single)
examples=json.loads((HERE/'published-examples.json').read_text())['records']
for example in examples:
mu=tuple(map(int,example['mu'].split(',')))
aut=prod(factorial(c) for c in Counter(mu).values())
assert values[(example['g'],mu)]==as_qq(aut*Fraction(example['unlabelled'])),example
report['published_example_matches']=len(examples)
direct=0
for d in range(1,5):
rows={k:v for k,v in values.items() if sum(k[1])==d}
R=max(2*g-2+d+len(mu) for g,mu in rows)
counts=ind.direct_monodromy(d,R)
if d==3:assert counts[2][(1,2,0)]==3 # three minimal factorizations
for (g,mu),value in rows.items():
r=2*g-2+d+len(mu)
count=counts[r].get(ind.fixed_sigma(mu),0)
assert value==as_qq(Fraction(count,prod(mu))),(g,mu,value,count)
direct+=1
report['direct_monodromy_matches']=direct
# Every n=1,d=1 entry is zero because a degree-one cover is an isomorphism.
for g in range(1,6):assert values[(g,(1,))]==0
for g in range(1,6):
assert values[(g,(2,))]==QQ(1)/QQ(2)
assert values[(g,(1,1))]==1
report['degree_one_and_two_checks']=15
report['domain_entries']=len(values)
report['measurement']=measure(values)
report['seconds']=time.monotonic()-start
print(json.dumps(report,indent=2,default=str),flush=True)
return report
def main():
if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
os.environ['NUMBERDB_API_KEY']=sys.stdin.read().strip()
if '--stored' in sys.argv:
values=flatten(numberdb.table('T463'))
else:
gen=SimpleHurwitz()
pairs=[((int(p['g']),tuple(map(int,p['mu'].split(',')))),gen.value(p)) for p in gen.enumerate()]
assert len(pairs)==len(dict(pairs))
values=dict(pairs)
run(values)
print('VALUES_JSON='+json.dumps([{'g':g,'mu':','.join(map(str,mu)),'number':str(v)} for (g,mu),v in values.items()]))
if __name__=='__main__':main()