checks.py

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

10240 bytes, as of the version from 2026-10-30 22:26 (current). Recorded here, not run.

"""Independent numerical and structural checks for numberdb.org/T632.

Run sage -python checks.py beside generate.py and check.py. The independent
calculation uses the coexact operator, trigonometric SU(2) traces, period-2p
Hurwitz continuation without scalar factorization, and 175-digit mpmath.
Known Ray-Singer torsion gives log D0=log T+(log Dperp)/2. This computation
shares no multiplicity or continuation code with the scalar generator.
Set NUMBERDB_CHECK_STORED=1 to verify and independently test stored decimals.
Evidence is emitted between EVIDENCE_JSON_START and EVIDENCE_JSON_END.
"""
import os
import sys
import json
from decimal import Decimal
from pathlib import Path
from functools import lru_cache
from math import gcd
import numberdb.sage as numberdb
from numberdb._write import to_text
from sage.rings.real_arb import RealBallField
import mpmath as mp
from generate import ScalarLensDeterminants, cases, presentations, coefficients, multiplicity, logdet
from check import exactness, measure, names_its_rings

HERE = Path(__file__).resolve().parent
mp.mp.dps = 175


@lru_cache(None)
def hurwitz(j,r,period):
    a = mp.mpf(r)/period
    return mp.zeta(-j,a),mp.zeta(-j,a,derivative=1)


@lru_cache(None)
def coexact_traces(p,q):
    """Trace as C+B*m+A*m^2 on m=r mod 2p, for each group element."""
    period = 2*p
    def su2(h,r,shift):
        # Character of the (m+shift)-dimensional SU(2) representation.
        if h % p == 0:
            sign = (-1)**((r+shift-1)*(h//p))
            return mp.mpf(sign*shift),mp.mpf(sign)
        theta = mp.pi*h/p
        return mp.sin((r+shift)*theta)/mp.sin(theta),mp.mpf(0)
    out = []
    for j in range(p):
        rows = []
        for r in range(2,period+2):
            row = [mp.mpf(0) for _ in range(3)]
            for shift in (-1,1):
                left = su2((1+q)*j,r,shift)
                right = su2((1-q)*j,r,-shift)
                for a in range(2):
                    for b in range(2):
                        row[a+b] += left[a]*right[b]
            rows.append(tuple(row))
        out.append(rows)
    return out


@lru_cache(None)
def coexact_logdet(p,q,u):
    period = 2*p
    traces = coexact_traces(p,q)
    total = mp.mpf(0)
    lp = mp.log(period)
    for ri,r in enumerate(range(2,period+2)):
        coeff = [mp.fsum(mp.cos(2*mp.pi*u*j/p)*traces[j][ri][k]
                         for j in range(p))/p for k in range(3)]
        for k in range(3):
            z,dz = hurwitz(k,r,period)
            total += 2*coeff[k]*period**k*(lp*z-dz)
    return total


def logtorsion(p,q,u):
    if u == 0:
        return mp.log(2*mp.pi**2/p**2)
    v = u*pow(q,-1,p) % p
    return mp.log(4*mp.sin(mp.pi*u/p)*mp.sin(mp.pi*v/p))


def scalar_independent(p,q,u):
    return coexact_logdet(p,q,u)/2+logtorsion(p,q,u)


def monomial_multiplicity(p,q,u,m):
    def count(n):
        if n < 0:
            return 0
        return sum((a-b+q*(c-(n-a-b-c))-u) % p == 0
                   for a in range(n+1) for b in range(n-a+1)
                   for c in range(n-a-b+1))
    return count(m-1)-count(m-3)


def text_number(entry):
    return entry['number'] if isinstance(entry,dict) else entry


def read_companion(tid):
    path = HERE/(tid+'.json')
    return json.loads(path.read_text()) if path.exists() else numberdb.table(tid)


def decimal_agrees(text,value):
    ulp = mp.power(10,Decimal(text).as_tuple().exponent)
    assert abs(mp.mpf(text)-value) < ulp/2, (text,mp.nstr(value,110))
    assert abs(mp.mpf(text)+10*ulp-value) > ulp


def main():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=',1)[0].replace('_','').isalnum():
            key = key.split('=',1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=key)
    g = ScalarLensDeterminants()
    R = RealBallField(700)
    assert not names_its_rings(str(HERE/'generate.py'))
    assert len(list(presentations())) == 46 and len(list(cases())) == 332
    # Known nonzero controls are tested before the broad comparison.
    expected = mp.log(2)-3*mp.zeta(3)/(2*mp.pi**2)
    expected_coexact = -3*mp.zeta(3)/mp.pi**2-2*mp.log(2)
    assert abs(coexact_logdet(2,1,1)-expected_coexact) < mp.mpf('1e-165')
    assert abs(scalar_independent(2,1,1)-expected) < mp.mpf('1e-165')
    arb_expected = R(2).log()-3*R(3).zeta()/(2*R.pi()**2)
    assert logdet(2,1,1).overlaps(arb_expected)
    sphere = R.pi().log()+R(3).zeta()/(2*R.pi()**2)
    assert (logdet(2,1,0)+logdet(2,1,1)).overlaps(sphere)
    print('Nonzero scalar, coexact and sphere controls pass.',flush=True)
    n_sym = n_cover = n_modes = 0
    for p in range(2,21):
        for q in range(1,p):
            if gcd(p,q) != 1:
                continue
            r = pow(q,-1,p)
            for u in range(p):
                c = coefficients(p,q,u)
                assert c == coefficients(p,q,(-u) % p)
                assert c == coefficients(p,p-q,u)
                assert c == coefficients(p,r,(u*r) % p)
                n_sym += 1
            allc = [coefficients(p,q,u) for u in range(p)]
            for i in range(2*p):
                m = i+2
                assert sum(c[i][1] for c in allc) == m*m
                assert sum(c[i][2] for c in allc) == 4*p*m
                assert sum(c[i][3] for c in allc) == 4*p*p
                n_cover += 1
    print('Exact multiplicity symmetries, covering and vanishing even pole pass.',flush=True)
    for p,q,u in cases():
        for m in range(1,11):
            assert multiplicity(p,q,u,m) == monomial_multiplicity(p,q,u,m)
            n_modes += 1
    print('Independent scalar harmonic-monomial counts pass.',flush=True)
    values = {}
    results = {}
    maxdiff = mp.mpf(0)
    min_acc = min_high_acc = 100000
    for p,q,u in cases():
        L = logdet(p,q,u,100)
        H = logdet(p,q,u,150)
        other = scalar_independent(p,q,u)
        diff = abs(mp.mpf(to_text(H,160))-other)
        assert mp.isfinite(other) and diff < mp.mpf('1e-145'),(p,q,u,mp.nstr(diff))
        maxdiff = max(maxdiff,diff)
        key = f'{p},{q},{u}'
        results[key] = {'independent_logdet':mp.nstr(other,160),
                        'independent_coexact_logdet':mp.nstr(coexact_logdet(p,q,u),160),
                        'absolute_logdet_discrepancy':mp.nstr(diff,8)}
        for convention,x,y in [('det',L.exp(),H.exp()),('logdet',L,H)]:
            assert x.is_finite() and y.is_finite() and x.overlaps(y)
            min_acc = min(min_acc,int(x.accuracy()))
            min_high_acc = min(min_high_acc,int(y.accuracy()))
            assert x.accuracy() >= 400 and y.accuracy() >= 550
            values[key+','+convention] = x
        if u == 0:
            print('checked',p,q,flush=True)
    assert not exactness(values)
    for p,q in presentations():
        total = sum(logdet(p,q,min(u,p-u),100) for u in range(p))
        assert total.overlaps(sphere)
    companions = {tid:read_companion(tid) for tid in ['T599','T595','T600','T631']}
    n_companion = 0
    for p,q,u in cases():
        coexact = companions['T631']['Numbers'][str(p)][str(q)][str(u)]
        ref = coexact_logdet(p,q,u)
        for convention,x in [('logdet',ref),('det',mp.exp(ref))]:
            decimal_agrees(text_number(coexact[convention]),x)
            n_companion += 1
        if u:
            t = text_number(companions['T599']['Numbers'][str(p)][str(q)][str(u)])
            exact = mp.mpf(t) if '/' not in t else mp.mpf(t.split('/')[0])/int(t.split('/')[1])
            assert abs(exact-mp.exp(logtorsion(p,q,u))) < mp.mpf('1e-95')
    sphere_mp = mp.log(mp.pi)+mp.zeta(3)/(2*mp.pi**2)
    for tid,x in [('T595',sphere_mp),('T600',scalar_independent(2,1,0))]:
        for convention,y in [('logdet',x),('det',mp.exp(x))]:
            decimal_agrees(text_number(companions[tid]['Numbers']['3'][convention]),y)
    evidence = {
        'table':'T632',
        'skill_manifest':'../20261030T212747Z-build-skills/manifest.json',
        'skill_sha256':'10c1d8bd62d6ddfa2c016375ad82879151f0ab7a9516c2f371432108a85be009',
        'entries':len(values),'presentations':46,'space_character_pairs':332,
        'working_bits':numberdb.bits(100,losing=180),
        'minimum_accuracy_bits':min_acc,'higher_precision_minimum_accuracy_bits':min_high_acc,
        'independent_mpmath_dps':mp.mp.dps,'maximum_logdet_discrepancy':mp.nstr(maxdiff,15),
        'symmetry_cases':n_sym,'covering_polynomial_checks':n_cover,
        'harmonic_monomial_checks':n_modes,'companion_coexact_entries_checked':n_companion,
        'size':measure({k:to_text(v,100) for k,v in values.items()}),
        'versions':g.environment(),'mpmath_version':mp.__version__,
        'stored_checked':False,'results':results}
    if os.environ.get('NUMBERDB_CHECK_STORED') == '1':
        report = g.verify(sample=None)
        print(report)
        assert report.ok
        # check.stored requires a Django database, unavailable on a build
        # machine. Read the authoritative document through numberdb instead.
        document = numberdb.table('T632')
        n = 0
        for p,q,u in cases():
            row = document['Numbers'][str(p)][str(q)][str(u)]
            other = scalar_independent(p,q,u)
            for convention,x in [('det',mp.exp(other)),('logdet',other)]:
                decimal_agrees(text_number(row[convention]),x)
                n += 1
        assert n == len(values)
        # Covering identity on the read-back decimal logarithms.
        document = numberdb.table('T632')
        for p,q in presentations():
            total = mp.mpf(0)
            tolerance = mp.mpf(0)
            for u in range(p):
                t = text_number(document['Numbers'][str(p)][str(q)][str(min(u,p-u))]['logdet'])
                total += mp.mpf(t)
                tolerance += mp.power(10,Decimal(t).as_tuple().exponent)
            assert abs(total-sphere_mp) < tolerance
        evidence['stored_checked'] = True
        evidence['stored_entries_checked'] = n
        evidence['stored_covering_identities_checked'] = 46
    print('EVIDENCE_JSON_START',flush=True)
    print(json.dumps(evidence,indent=2),flush=True)
    print('EVIDENCE_JSON_END',flush=True)


if __name__ == '__main__':
    main()