checks.py

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

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

"""Independent checks of Jack polynomials -- numberdb.org/T575.

Run beside generate.py and check.py:
    sage -python checks.py
    sage -python checks.py stored-table.json

A saved table document can also be nested under Numbers, as returned by the API.
The generator is imported only to obtain locally computed values. Validation
uses Sage's Jack implementation, a differential operator, and a separate Schur
alternant. No numerical tolerances or floating-point calculations are used.
"""
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  # Initialize symbolic code before Symmetrica.
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 math import factorial
from collections import Counter
from pathlib import Path
import json
import sys
from check import exactness, agrees_with, measure, names_its_rings

B = PolynomialRing(QQ, 'a')
S = SymmetricFunctions(B.fraction_field())
J = S.jack(t=B.gen()).J()


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


def expected_identities():
    return [(n,tuple(lam)) for n in [2,3,4]
            for d in range(2,(4 if n==4 else 5)+1)
            for lam in Partitions(d) if len(lam)<=n]


def sage_jack(key):
    n, shape = key
    return ring(n)(str(J[list(shape)].expand(n,alphabet=['x%d' % i for i in range(1,n+1)])))


def determinant(rows):
    n = len(rows)
    R = rows[0][0].parent()
    out = R(0)
    for order in permutations(range(n)):
        inversions = sum(order[i]>order[j] for i in range(n) for j in range(i+1,n))
        term = R((-1)**inversions)
        for i,j in enumerate(order):
            term *= rows[i][j]
        out += term
    return out


def schur(n, shape):
    R=ring(n); xs=R.gens()[:-1]
    padded=shape+(0,)*(n-len(shape))
    top=determinant([[xs[i]**(padded[j]+n-j-1) for j in range(n)] for i in range(n)])
    bottom=determinant([[xs[i]**(n-j-1) for j in range(n)] for i in range(n)])
    q,r=top.quo_rem(bottom)
    assert r==0
    return q


def scalar_hook(n,shape):
    R=ring(n); a=R.gen(n); out=R(1)
    conjugate=[sum(part>=j for part in shape) for j in range(1,shape[0]+1)]
    for i,part in enumerate(shape,1):
        for j in range(1,part+1):
            out *= (part-j)*a+conjugate[j-1]-i+1
    return out


def differential(f,n):
    xs=f.parent().gens()[:-1]; a=f.parent().gen(n)
    out=a*sum((x*x*f.derivative(x).derivative(x) for x in xs), f.parent()(0))
    for i in range(n):
        for j in range(i+1,n):
            num=xs[i]**2*f.derivative(xs[i])-xs[j]**2*f.derivative(xs[j])
            q,r=num.quo_rem(xs[i]-xs[j])
            assert r==0
            out+=2*q
    return out


def compositions(total,n):
    if n==1:
        yield (total,)
    else:
        for first in range(total+1):
            for tail in compositions(total-first,n-1):
                yield (first,)+tail


def row_formula(n,d):
    R=ring(n); xs=R.gens()[:-1]; a=R.gen(n); out=R(0)
    for powers in compositions(d,n):
        denominator=1
        for power in powers:
            denominator*=factorial(power)
        coefficient=ZZ(factorial(d))//denominator
        term=R(coefficient)
        for x,k in zip(xs,powers):
            term*=x**k
            for j in range(k):
                term*=1+j*a
        out+=term
    return out


def validate(values):
    # Nonzero controls, before trusting any checker.
    R=ring(2); x,y,a=R.gens()
    assert schur(2,(2,))==x*x+x*y+y*y
    assert scalar_hook(2,(2,))==a+1
    assert differential(x+y,2)==2*(x+y)
    known=(a+1)*(x*x+y*y)+2*x*y
    assert row_formula(2,2)==known
    assert sage_jack((2,(2,)))==known
    assert differential(known,2)==(2*a+4)*known
    assert differential(known+x*y,2)!=(2*a+4)*(known+x*y)
    assert list(values)==expected_identities(), 'Missing, extra or misordered parameter identities'
    assert not exactness(values)
    disagreements=agrees_with(values,sage_jack)
    assert not disagreements, disagreements
    counts=Counter()
    for (n,shape),f in values.items():
        R=ring(n); f=R(f); xs=R.gens()[:-1]; a=R.gen(n); d=sum(shape)
        assert all(sum(e[:-1])==d for e in f.dict())
        assert all(c>0 for c in f.coefficients())
        for i in range(n-1):
            images=list(R.gens()); images[i],images[i+1]=images[i+1],images[i]
            assert f(*images)==f
        padded=shape+(0,)*(n-len(shape))
        leading=sum((c*a**tuple(e)[-1] for e,c in f.dict().items() if tuple(e[:-1])==padded), R(0))
        assert leading==scalar_hook(n,shape)
        for e in f.dict():
            mu=sorted(e[:-1],reverse=True)
            assert all(sum(mu[:i])<=sum(padded[:i]) for i in range(1,n+1))
        eigen=a*sum(k*(k-1) for k in shape)+2*sum((n-i-1)*k for i,k in enumerate(shape))
        assert differential(f,n)==eigen*f
        candidates=[tuple(mu) for mu in Partitions(d) if len(mu)<=n]
        eigenvalues=[a*sum(k*(k-1) for k in mu)+2*sum((n-i-1)*k for i,k in enumerate(mu)) for mu in candidates]
        assert len(set(eigenvalues))==len(candidates), 'Eigenoperator degeneracy needs another check'
        assert f.subs({a:1})==scalar_hook(n,shape).subs({a:1})*schur(n,shape)
        counts['sage_jack']+=1; counts['differential_eigenidentity']+=1
        counts['schur_specialization']+=1; counts['symmetry_homogeneity_triangularity_normalization']+=1
        if len(shape)==1:
            assert f==row_formula(n,d)
            counts['row_formula']+=1
        if all(k==1 for k in shape):
            elementary=R(0)
            for chosen in combinations(xs,d):
                term=R(1)
                for x in chosen: term*=x
                elementary+=term
            assert f==factorial(d)*elementary
            counts['column_formula']+=1
        if len(shape)==n:
            assert f.subs({xs[-1]:0})==0
            counts['alphabet_vanishing']+=1
        if n>2 and (n-1,shape) in values:
            assert f.subs({xs[-1]:0})==R(values[n-1,shape])
            counts['alphabet_stability']+=1
    # Compare every available Schur entry with the separately downloaded corpus.
    corpus=Path(__file__).with_name('T122.json')
    if corpus.exists():
        doc=json.loads(corpus.read_text())
        for (n,shape),f in values.items():
            spelling=','.join(map(str,shape)); source=doc['Numbers'].get(str(n),{}).get(spelling)
            if source is not None:
                if isinstance(source,dict): source=source['number']
                assert schur(n,shape)==ring(n)(source)
                counts['stored_T122_schur']+=1
    return {'table':'T575','entries':len(values),'checks':dict(counts),
            'measurement':measure(values),'controls':'known nonzero and deliberately corrupted polynomials passed',
            'sage_version':SAGE_VERSION,'numberdb_version':numberdb.__version__}


def read_document(path):
    doc=json.loads(Path(path).read_text()); out={}
    entries=doc.get('Numbers',doc.get('Data',[]))
    if isinstance(entries,list):
        for record in entries:
            n=int(record['params']['n']); shape=tuple(map(int,str(record['params']['lambda']).split(',')))
            out[n,shape]=ring(n)(record['number'])
    else:
        for n,group in entries.items():
            for shape,entry in group.items():
                if isinstance(entry,dict): entry=entry['number']
                out[int(n),tuple(map(int,shape.split(',')))]=ring(int(n))(entry)
    return out


if __name__=='__main__':
    if len(sys.argv)>1:
        values=read_document(sys.argv[1])
    else:
        from generate import JackPolynomials, jack
        assert not names_its_rings(str(Path(__file__).with_name('generate.py')))
        G=JackPolynomials()
        values={(int(p['n']),tuple(map(int,p['lambda'].split(',')))):G.value(p,100) for p in G.enumerate()}
        # Boundary formulas excluded from storage, verified for all measured degrees.
        for n in [1,2,3,4]:
            R=ring(n); assert jack(n,())==1
            assert jack(n,(1,))==sum(R.gens()[:-1])
        for d in range(2,6): assert jack(1,(d,))==row_formula(1,d)
    print(json.dumps(validate(values),indent=2))