repair_check_T496.py

back to table · edit · history · where entries came from · files · download

4098 bytes, as of the version from 2026-10-25 17:54 (current). Recorded here, not run.

"""Recompute and check the 24 Niemeier automorphism orders, without writing.

Run with SageMath and numberdb installed:
    sage -python repair_check_T496.py < /path/to/numberdb-key
The key is needed while T496 remains a draft and is sent only to its API.

Quotient factors follow Chenevier, The characteristic masses of Niemeier
lattices, Section 5.3 (published pp. 569-575), using the group descriptions
there. The Leech order is the reciprocal identity mass in Table C.5.
Weyl orders are independently computed from positive-root heights. The mass
formula is also evaluated, with rank-8 and rank-16 controls and deliberately
incorrect rank-24 inputs that must fail. No publication or review action is
performed. This script regenerates expected integers only in its JSON output.
"""
import json
import re
import sys
import urllib.request
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.combinat.root_system.root_system import RootSystem
from sage.arith.misc import bernoulli, factorial
from sage.env import SAGE_VERSION

# Orders from the group descriptions in Section 5.3, not from T496.
quotients = {
    'A1^24': ZZ(244823040), 'A2^12': 2*ZZ(95040),
    'A3^8': 2*ZZ(8)*(8-1)*(8-2)*(8-4), 'A4^6': 2*factorial(5),
    'A5^4D4': 2*factorial(4), 'D4^6': 3*factorial(6),
    'A6^4': 2*ZZ(12), 'A7^2D5^2': ZZ(8), 'A8^3': 2*factorial(3),
    'A9^2D6': ZZ(4), 'D6^4': factorial(4), 'A11D7E6': ZZ(2),
    'E6^4': 2*factorial(4), 'A12^2': ZZ(4), 'D8^3': factorial(3),
    'A15D9': ZZ(2), 'A17E7': ZZ(2), 'D10E7^2': ZZ(2),
    'D12^2': ZZ(2), 'A24': ZZ(2), 'D16E8': ZZ(1),
    'E8^3': factorial(3), 'D24': ZZ(1),
}
weyl_cache = {}
def weyl(letter, rank):
    name = (letter, rank)
    if name not in weyl_cache:
        order = QQ(1)
        for root in RootSystem([letter, rank]).root_lattice().positive_roots():
            height = ZZ(root.height())
            order *= QQ(height + 1)/height
        assert order.denominator() == 1
        weyl_cache[name] = ZZ(order)
    return weyl_cache[name]

def generate():
    result = {'Leech': ZZ(8315553613086720000)}
    for label, q in quotients.items():
        value = ZZ(q)
        total_rank = 0
        coxeter_numbers = set()
        for letter, n, m in re.findall(r'([ADE])(\d+)(?:\^(\d+))?',label):
            n, m = int(n), int(m or '1')
            total_rank += n*m
            coxeter_numbers.add(n+1 if letter == 'A' else 2*n-2 if letter == 'D' else {6:12,7:18,8:30}[n])
            value *= weyl(letter,n)**m
        assert total_rank == 24 and len(coxeter_numbers) == 1
        result[label] = value
    return result

def mass(rank):
    k = rank//2
    result = abs(bernoulli(k))/(2*ZZ(k))
    for j in range(1,k):
        result *= abs(bernoulli(2*j))/(4*ZZ(j))
    return result

key = sys.stdin.read().strip()
headers = {'Authorization': 'Bearer '+key} if key else {}
request = urllib.request.Request('https://numberdb.org/api/table?id=T496',headers=headers)
with urllib.request.urlopen(request, timeout=45) as response:
    document = json.load(response)
assert 'Numbers' in document, document.get('error','Missing Numbers')
stored = {label: ZZ(item['number'] if isinstance(item,dict) else item)
          for label,item in document['Numbers'].items()}
expected = generate()
assert stored == expected, 'A stored order or label disagrees with the recomputation'
assert mass(8) == QQ(1)/weyl('E',8)
assert mass(16) == QQ(1)/(2*weyl('E',8)**2) + QQ(1)/weyl('D',16)
total = sum((QQ(1)/n for n in stored.values()),QQ(0))
assert total == mass(24)
assert total - QQ(1)/stored['Leech'] != mass(24)
assert total - QQ(1)/stored['A24'] + QQ(1)/(stored['A24']+1) != mass(24)
assert stored['A24'] == 2*factorial(25)
assert stored['D24'] == ZZ(2)**23*factorial(24)
assert stored['E8^3'] == factorial(3)*weyl('E',8)**3
print(json.dumps({'sage_version':SAGE_VERSION,'checked_orders':len(stored),
    'exact_mass':str(total),'rank_8_and_16_controls':True,
    'mutation_and_omission_rejected':True,
    'expected_orders':{k:str(v) for k,v in expected.items()}},indent=2))