checks.py

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

7785 bytes, as of the version from 2026-10-28 10:24 (current). Recorded here, not run.

"""Independent exact checks for Macdonald polynomials -- numberdb.org/T578.

    sage -python checks.py           # compute and check locally
    sage -python checks.py --stored  # repeat on authenticated database values

Install numberdb and keep generate.py and check.py beside this file.
No floating point tests are used. The difference eigenidentity is tested after
multiplication by the Vandermonde, so no numerical poles or cancellation arise.
Sage's independent Macdonald J algorithm uses determinantal formulas.
"""
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.symbolic.ring import SR
import sage.rings.polynomial.laurent_polynomial_ring
from sage.combinat.sf.sf import SymmetricFunctions
from sage.combinat.partition import Partitions
from sage.env import SAGE_VERSION
from itertools import permutations, combinations
from collections import Counter
from math import prod
from pathlib import Path
import json
import os
import sys
import generate
from check import exactness, measure, agrees_with, names_its_rings


def ring(n):
    return PolynomialRing(ZZ, ['x%d' % (i+1) for i in range(n)] + ['q','t'])


def independent_hook(R, lam):
    q,t=R.gens()[-2:]
    return prod((1-q**(width-column)*t**(sum(a>=column for a in lam)-row+1)
                 for row,width in enumerate(lam,1) for column in range(1,width+1)),start=R.one())


def vandermonde(x):
    return prod((x[i]-x[j] for i in range(len(x)) for j in range(i+1,len(x))),start=x[0].parent().one())


def eigen_residual(f, lam):
    R=f.parent(); x=R.gens()[:-2]; q,t=R.gens()[-2:]; n=len(x)
    v=vandermonde(x)
    lhs=R.zero()
    for i in range(n):
        shifted_v=prod(((t*x[a] if a==i else x[a])-(t*x[b] if b==i else x[b])
                       for a in range(n) for b in range(a+1,n)),start=R.one())
        lhs += shifted_v*f.subs({x[i]:q*x[i]})
    padded=lam+(0,)*(n-len(lam))
    eigen=sum(q**padded[i]*t**(n-1-i) for i in range(n))
    return lhs-eigen*v*f


def schur_alternant(R, lam):
    x=R.gens()[:-2]; n=len(x); padded=lam+(0,)*(n-len(lam)); ans=R.zero()
    for sigma in permutations(range(n)):
        inversions=sum(sigma[i]>sigma[j] for i in range(n) for j in range(i+1,n))
        ans+=(-1)**inversions*prod((x[i]**(padded[sigma[i]]+n-1-sigma[i]) for i in range(n)),start=R.one())
    return R(R.fraction_field()(ans)/vandermonde(x))


def stored_values(doc):
    block=doc['Numbers']
    if isinstance(block,list):
        records=block
    else:
        records=[{'params':{'n':n,'lambda':lam},'number':value['number'] if isinstance(value,dict) else value}
                 for n,group in block.items() for lam,value in group.items()]
    return {(int(r['params']['n']),tuple(map(int,str(r['params']['lambda']).split(',')))):
            ring(int(r['params']['n']))(r['number']) for r in records}


def get_neighbor(tid):
    path=Path(__file__).with_name(tid+'.json')
    return json.loads(path.read_text()) if path.exists() else numberdb.table(tid)


def run(stored=False):
    gen=generate.MacdonaldPolynomials()
    enumeration=[(int(p['n']),tuple(map(int,p['lambda'].split(',')))) for p in gen.enumerate()]
    expected=[(n,tuple(map(int,lam))) for n,bound in ((2,4),(3,3),(4,2))
              for d in range(2,bound+1) for lam in Partitions(d) if len(lam)<=n]
    assert enumeration==expected and len(expected)==14
    values=stored_values(numberdb.table('T578')) if stored else {key:generate.polynomial(*key) for key in expected}
    assert list(values)==expected
    assert not exactness(values)
    assert not names_its_rings(str(Path(__file__).with_name('generate.py')))
    # Controls first: D(1) and D(p1) have known eigenvalues; a wrong quadratic fails.
    for n in (2,3,4):
        R=ring(n);x=R.gens()[:-2];q,t=R.gens()[-2:]
        assert eigen_residual(R.one(),())==0
        assert eigen_residual(sum(x),(1,))==0
        assert eigen_residual(sum(z*z for z in x),(2,))!=0
        assert schur_alternant(R,(1,))==sum(x)
        assert schur_alternant(R,(1,1))==sum((a*b for a,b in combinations(x,2)),R.zero())
    print('Controls: constants, p1, e2 and a deliberately wrong eigenfunction pass.',flush=True)
    QT=PolynomialRing(QQ,['q','t']);q0,t0=QT.gens()
    J=SymmetricFunctions(QT.fraction_field()).macdonald(q=q0,t=t0).J()
    def sage_j(key):
        n,lam=key
        return ring(n)(str(J[list(lam)].expand(n,alphabet=['x%d'%(i+1) for i in range(n)])))
    mismatches=agrees_with(values,sage_j)
    assert not mismatches, mismatches
    print('Independent Sage J: all 14 agree.',flush=True)
    schur_doc=get_neighbor('T122'); hall_doc=get_neighbor('T261')
    schurs=stored_values(schur_doc); halls=stored_values(hall_doc)
    counts=Counter()
    for (n,lam),f in values.items():
        R=f.parent();x=R.gens()[:-2];q,t=R.gens()[-2:];d=sum(lam)
        assert all(sum(exp[:n])==d for exp in f.dict())
        for sigma in permutations(range(n)):
            assert f(*[x[i] for i in sigma],q,t)==f
        leading=R.zero(); padded=lam+(0,)*(n-len(lam))
        for exponent,coefficient in f.dict().items():
            shape=tuple(sorted(exponent[:n],reverse=True))
            assert all(sum(shape[:k])<=sum(padded[:k]) for k in range(1,n+1))
            if tuple(exponent[:n])==padded:
                leading+=coefficient*q**exponent[n]*t**exponent[n+1]
        c=independent_hook(R,lam)
        assert leading==c
        assert eigen_residual(f,lam)==0
        counts['difference_eigenidentity']+=1
        s=schur_alternant(R,lam)
        assert f.subs({t:q})==c.subs({t:q})*s
        assert s==R(schurs[(n,lam)])
        counts['schur_specialization_and_corpus']+=1
        b=prod((1-t**j for m in Counter(lam).values() for j in range(1,m+1)),start=R.one())
        assert f.subs({q:0})==b*R(halls[(n,lam)])
        counts['hall_littlewood_corpus']+=1
        reduced=f.subs({x[-1]:0})
        expected_reduced=R.zero() if len(lam)==n else R(generate.polynomial(n-1,lam))
        assert reduced==expected_reduced
        counts['stability']+=1
        if all(a==1 for a in lam):
            elementary=sum((prod(choice,start=R.one()) for choice in combinations(x,d)),R.zero())
            factor=prod((1-t**j for j in range(1,d+1)),start=R.one())
            assert f==factor*elementary
            counts['column']+=1
    # Omitted boundary families, through the largest retained degree.
    for n in range(1,5):
        R=ring(n);x=R.gens()[:-2];q,t=R.gens()[-2:]
        assert R(generate.polynomial(n,()))==1
        assert R(generate.polynomial(n,(1,)))==(1-t)*sum(x)
    R=ring(1);x,q,t=R.gens()
    for d in range(1,5):
        assert generate.polynomial(1,(d,))==x**d*prod((1-q**j*t for j in range(d)),start=R.one())
    result={'table':'T578','mode':'stored' if stored else 'local','entries':len(values),
            'checks':dict(counts),'sage_j':14,'exactness':True,'controls':True,
            'measure':measure(values),'versions':{'sage':SAGE_VERSION,'numberdb':numberdb.__version__},
            'skill_manifest':'../20261028T101159Z-build-skills/manifest.json',
            'skill_sha256':'e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c'}
    print('RESULT_JSON',json.dumps(result),flush=True)
    print('VALUES_JSON',json.dumps([{'params':{'n':str(n),'lambda':','.join(map(str,lam))},'number':str(f)} for (n,lam),f in values.items()]))
    return result


if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        key=sys.stdin.read().strip()
        if '=' in key and key.split('=',1)[0].strip().isidentifier(): key=key.split('=',1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=key)
    run('--stored' in sys.argv)