"""Check the written intervals and identities read from NumberDB T658.

Run with SageMath beside readback.json, numerics.jsonl, checks.jsonl, and the
saved companion-controls.json, extracted from the authenticated companion reads.
The saved readback is fetched after filling the draft, never manufactured
from the upload document. Decimal units follow NumberDB's last-place rule.
"""
import json
from decimal import Decimal
from pathlib import Path
import numberdb.sage as numberdb
import mpmath as mp
mp.mp.dps=170
w=Path(__file__).resolve().parent
D=json.loads((w/'readback.json').read_text())
N=D['Numbers']
if isinstance(N,list): N={r['params']['k']:r for r in N}

def pair(item):
    text=str(item['number'] if isinstance(item,dict) else item)
    text=text.split(' + i')[0]
    center=mp.mpf(text)
    radius=mp.mpf(0) if '.' not in text and 'e' not in text.lower() else mp.power(10,int(Decimal(text).as_tuple().exponent))
    return center,radius
V={k:pair(item) for k,item in N.items()}
expected={r['k']:r for r in (json.loads(x) for x in (w/'numerics.jsonl').read_text().splitlines()) if 'k' in r}
assert list(V)==list(expected) and len(V)==37
worst=mp.mpf(0)
for k,(center,radius) in V.items():
    target=mp.mpf(expected[k]['at190'])
    if k=='0':assert center==radius==target==0;continue
    assert len(Decimal(N[k]['number'] if isinstance(N[k],dict) else N[k]).as_tuple().digits)==100
    ulps=abs(center-target)/radius
    assert ulps<1,(k,str(ulps))
    worst=max(worst,ulps)
print(json.dumps({'check':'every written interval contains the 190-digit result','entries':37,'max_error_ulps':mp.nstr(worst,12)}))

def relation(label,terms,target=mp.mpf(0),extra_radius=mp.mpf(0)):
    center=sum(a*V[k][0] for k,a in terms)-target
    radius=sum(abs(a)*V[k][1] for k,a in terms)+extra_radius
    # 130-digit independent transcendental controls are evaluated far beyond
    # the 100 digits of the table, but do not claim certified bounds.
    assert abs(center)<=radius+mp.mpf('1e-125'),(label,mp.nstr(center,8),mp.nstr(radius,8))
    print(json.dumps({'check':label,'residual':mp.nstr(center,8),'stored_radius_sum':mp.nstr(radius,8)}))
L3=(mp.zeta(2,mp.mpf(1)/3)-mp.zeta(2,mp.mpf(2)/3))/9
mu=3*mp.sqrt(3)*L3/(4*mp.pi)
relation('stored factorization k=-1',[('-1',1)],2*mu)
relation('stored spanning-tree evaluation',[('8',1)],5*mu)
Ls={r['conductor']:mp.mpf(r['L2']) for r in (json.loads(x) for x in (w/'checks.jsonl').read_text().splitlines()) if r.get('check')=='elliptic L value'}
for key,c in [('1',mp.mpf(7)/2),('7',21),('-8',35)]:relation('stored conductor 14 '+key,[(key,1)],c*Ls[14]/mp.pi**2)
relation('stored conductor 20 k=4',[('4',1)],10*Ls[20]/mp.pi**2)
relation('stored conductor 20 k=-2',[('-2',1)],15*Ls[20]/mp.pi**2)
relation('stored conductor 36 k=2',[('2',1)],9*Ls[36]/(2*mp.pi**2))
relation('stored quadratic identity',[('4+2*sqrt(5)',2),('8+4*sqrt(5)',-1),('4',-1)])
relation('stored conductor 14 ratio 6',[('7',1),('1',-6)])
relation('stored conductor 14 ratio 10',[('-8',1),('1',-10)])
relation('stored conductor 20 ratio 3/2',[('-2',1),('4',-mp.mpf(3)/2)])

controls=json.loads((w/'companion-controls.json').read_text())
mu_old,eps=pair(controls['T283']['entry'])
relation('companion T283 n=3',[('-1',1)],2*mu_old,2*eps)
relation('companion T283 n=3 to k=8',[('8',1)],5*mu_old,5*eps)
for lattice,mult in [('triangular',1),('honeycomb',2)]:
    center,eps=pair(controls['T153-'+lattice]['entry'])
    relation('companion T153 '+lattice,[('8',1)],mult*center,mult*eps)
center,eps=pair(controls['T145']['entry'])
a=15*mp.sqrt(3)/(4*mp.pi)
relation('companion T145 (3,2,2)',[('8',1)],a*center,a*eps)
center,eps=pair(controls['T649']['entry'])
relation('companion ordinary Hessian N(-1)=2g(1)',[('1',2)],center,eps)
assert abs(V['4'][0]+mp.mpf('1e-90')-mp.mpf(expected['4']['at190']))>V['4'][1]
print(json.dumps({'check':'stored interval rejects 1e-90 perturbation','passed':True}))
print(json.dumps({'check':'all readback checks passed'}))
