"""Independent coefficient/Mellin checks for Critical L-values, numberdb.org/T683.

Run: sage -python checks.py
Needs generate.py, roster.json, hecke-nf.json, check.py beside this file.
The independent path reconstructs coefficients from LMFDB exact Hecke data
using Euler recurrences. It never calls PARI lfun, mfcoefs or mfembed. It
uses the catalogue field root isolated by generate.algebra. For odd weight,
the root number is recovered from the Fourier value at i/sqrt(N), rather
than taken from PARI's L-function structure. All sums use 600 coefficients
and 190 decimal working digits. A second 450-term/150-digit calculation
checks the truncation and arithmetic. These remain agreement checks.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.complex_mpfr import ComplexField
from sage.rings.real_mpfr import RealField
from sage.rings.integer_ring import ZZ
import json, math, sys
from functools import lru_cache
from pathlib import Path
from generate import ROSTER, algebra, orbit_values, CriticalNewformLValues
from check import exactness, measure, names_its_rings
BASE=Path(__file__).resolve().parent
NF={r['label']:r for r in json.loads((BASE/'hecke-nf.json').read_text())['data']}
P=PolynomialRing(QQ,'x')


def primes_to(n):
    sieve=[True]*(n+1)
    sieve[0]=sieve[1]=False
    for p in range(2,n+1):
        if sieve[p]:
            yield p
            for j in range(p*p,n+1,p): sieve[j]=False


@lru_cache(maxsize=None)
def independent_coefficients(label, count=600):
    h=NF[label];N,k=h['level'],h['weight'];F=P(h['field_poly']);d=F.degree()
    if h['hecke_ring_power_basis']:
        basis=[P.gen()**i for i in range(d)]
    else:
        basis=[P(v)/QQ(b) for v,b in zip(h['hecke_ring_numerators'],h['hecke_ring_denominators'])]
    def convert(v):
        if isinstance(v,int):v=[v]
        return sum((QQ(c)*b for c,b in zip(v,basis)),P(0))%F
    prime_list=list(primes_to(h['maxp']))
    aps=dict(zip(prime_list,[convert(v) for v in h['ap']]))
    coeff=[P(0)]*(count+1);coeff[1]=P(1)
    row=next(r for r in ROSTER if r['label']==label)
    for p in primes_to(count):
        chi=0 if N%p==0 else (int(ZZ(-N).kronecker(p)) if row['group']=='C' else 1)
        pp=p;prev2=P(1);prev=aps[p]
        while pp<=count:
            coeff[pp]=prev
            prev2,prev=prev,(aps[p]*prev-chi*p**(k-1)*prev2)%F
            pp*=p
    # Reconstruct general products in ascending n after prime powers are fixed.
    for n in range(2,count+1):
        for p in prime_list:
            if n%p==0:
                q=n;pp=1
                while q%p==0:q//=p;pp*=p
                if q>1:coeff[n]=(coeff[pp]*coeff[q])%F
                break
    expected=[P([QQ(c) for c in v]) for v in row['an_power_basis']]
    assert coeff[:len(expected)]==expected,(label,'independent Sturm coefficients')
    return coeff


def independent_values(row, working=190, count=600):
    bits=numberdb.bits(working,losing=32);C=ComplexField(bits);R=RealField(bits)
    N,k=row['level'],row['weight'];pi=R.pi();T=1/R(N).sqrt()
    coeff=independent_coefficients(row['label'],600)[:count+1]
    algebra_data=algebra(row['label'])[2]
    # The incomplete gamma function at a positive integer is evaluated as
    # exp(-x)*(s-1)!*sum_{j=0}^{s-1} x^j/j!, with no numerical quadrature.
    weights={s:[] for s in range(1,k)}
    exponentials=[]
    for n in range(1,count+1):
        c=2*pi*n;x=c*T;ex=(-x).exp();exponentials.append(ex)
        for s in range(1,k):
            term=R(1);total=term
            for j in range(1,s):term=term*x/R(j);total+=term
            weights[s].append(R(math.factorial(s-1))*ex*total/c**s)
    result={}
    for full,(z,is_real) in algebra_data.items():
        a=[C(c(z)) for c in coeff[1:]]
        integrals={s:sum((v*t for v,t in zip(a,weights[s])),C(0)) for s in range(1,k)}
        if row['group']=='C':
            central=sum((v*t for v,t in zip(a,exponentials)),C(0))
            assert abs(central)>R('1e-12'),(full,'root-number control')
            eps=central/central.conjugate()
        else:
            eps=C((-1)**(k//2)*row['fricke_eigenvalue'])
        vals={s:(2*pi)**s/R(math.factorial(s-1))*(integrals[s]+eps*R(N)**(R(k)/2-s)*integrals[k-s].conjugate()) for s in range(1,k)}
        result[full]={'values':vals,'root_number':eps}
    return result


def eta_control(label, factors, count=100):
    # Multiply eta-product factors in ZZ[[q]], coefficient by coefficient.
    a=[0]*(count+1);a[0]=1
    for scale,power in factors:
        for n in range(1,count//scale+1):
            step=scale*n
            for repeat in range(power):
                for j in range(count,step-1,-1):a[j]-=a[j-step]
    shift=sum(scale*power for scale,power in factors)//24
    assert shift==1
    known=[0]*shift+a[:count+1-shift]
    cs=independent_coefficients(label,600)[:count+1]
    assert all(c==n for c,n in zip(cs,known)),label
    return count


def relative_error(x,y):
    return abs(x-y)/max(1,abs(x),abs(y))


def main():
    # Controls precede measurements: independent product expansions must yield
    # known coefficients a2=-4,a3=2 (5.4) and a2=-3,a3=0 (7.3).
    assert independent_coefficients('5.4.a.a')[2:4]==[P(-4),P(2)]
    assert independent_coefficients('7.3.b.a')[2:4]==[P(-3),P(0)]
    for label,factors in [('5.4.a.a',[(1,4),(5,4)]),('7.3.b.a',[(1,3),(7,3)]),('3.6.a.a',[(1,6),(3,6)])]:
        print('CONTROL',label,eta_control(label,factors),flush=True)
    max_errors={'independent':0,'precision':0,'root_number':0,'functional_equation':0,'conjugation':0,'truncation':0}
    entries={};zeros=0;complex_forms=0;fe_max=-10**6
    for row in ROSTER:
        lo=orbit_values(row['label'],160);hi=orbit_values(row['label'],220)
        other=independent_values(row);short=independent_values(row,150,450)
        roots=algebra(row['label'])[2]
        for full,(z,is_real) in roots.items():
            complex_forms+=int(not is_real)
            conjugates=[name for name,(zz,rr) in roots.items() if zz==z.conjugate()]
            assert len(conjugates)==1
            companion=conjugates[0]
            fe_max=max(fe_max,hi[full]['functional_equation_bits'])
            max_errors['root_number']=max(max_errors['root_number'],relative_error(hi[full]['root_number'],other[full]['root_number']))
            for s in range(1,row['weight']):
                v=hi[full]['values'][s]
                max_errors['independent']=max(max_errors['independent'],relative_error(v,other[full]['values'][s]))
                max_errors['precision']=max(max_errors['precision'],relative_error(v,lo[full]['values'][s]))
                max_errors['conjugation']=max(max_errors['conjugation'],relative_error(v,hi[companion]['values'][s].conjugate()))
                max_errors['truncation']=max(max_errors['truncation'],relative_error(other[full]['values'][s],short[full]['values'][s]))
                R=RealField(800);k=row['weight'];N=row['level']
                left=R(N)**(R(s)/2)*R(math.factorial(s-1))/(2*R.pi())**s*v
                right=hi[full]['root_number']*R(N)**(R(k-s)/2)*R(math.factorial(k-s-1))/(2*R.pi())**(k-s)*hi[companion]['values'][k-s]
                max_errors['functional_equation']=max(max_errors['functional_equation'],relative_error(left,right))
                p={'form':full,'s':s};entries[full+','+str(s)]=CriticalNewformLValues().value(p,100)
                zeros+=int(v==0)
        print('CHECKED',row['label'],flush=True)
    for name,error in max_errors.items():
        assert error < RealField(800)('1e-120'),(name,error)
    assert not exactness(entries)
    assert not names_its_rings(str(BASE/'generate.py'))
    evidence={'entries':len(entries),'forms':138,'orbits':90,'exact_zeros':zeros,'nonreal_forms':complex_forms,'max_relative_errors':{k:str(v) for k,v in max_errors.items()},'largest_lfuncheckfeq_bits':fe_max,'measure':measure(entries),'controls':'Exact eta-product coefficients through n=100 for 3.6.a.a, 5.4.a.a, 7.3.b.a; nonzero a2 and a3 controls before measurements','methods':'Fresh PARI at 160 and 220 digits; independent catalogue Euler recurrences and split Mellin sums at 150 digits/450 terms and 190 digits/600 terms','skill_manifest':'agents/runs/20261102T183459Z-build-skills/manifest.json','skill_sha256':'a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278'}
    print('EVIDENCE '+json.dumps(evidence),flush=True)

if __name__=='__main__':main()
