back to table · edit · history · where entries came from · files · download
5665 bytes, as of the version from 2026-10-28 09:23 (current). Recorded here, not run.
"""Zonal polynomials -- numberdb.org/T576
Run with SageMath (numberdb must be installed):
sage -pip install numberdb
sage -python generate.py # verify the stored draft/table
sage -python generate.py --fill # write entries with an authorized key
The default enumeration consists of complete low-degree partition slices.
See argument-selection.md for the bounds and their measured readability.
All arithmetic is in QQ[x1,...,xn]. The generator builds monic triangular
Laplace--Beltrami eigenpolynomials, then decomposes (x1+...+xn)^d in this
basis to impose the real zonal C normalization. It does not call Sage's Jack
implementation or use the hook-product conversion used in checks.py.
There is no floating-point arithmetic and no numerical precision choice.
Sources: Jiu and Koutschan, arXiv:2001.11599, Definition 2.11 and (1.3);
DLMF 35.4. The first source fixes the differential operator, the second fixes
the real-matrix normalization. A partition is recorded without zero padding.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from functools import lru_cache
from itertools import permutations
import os
import sys
# Complete degree slices chosen from the measured expanded lengths.
DEGREE_LIMITS = ((2, 6), (3, 6), (4, 5), (5, 4))
def partitions(weight, ceiling=None):
"""All partitions in descending lexicographic order."""
if weight == 0:
yield ()
return
for first in range(min(weight, ceiling or weight), 0, -1):
for tail in partitions(weight-first, first):
yield (first,) + tail
@lru_cache(None)
def ring(n):
return PolynomialRing(QQ, n, names=['x%d' % i for i in range(1, n+1)])
def dominates(lam, mu):
return all(sum(lam[:i]) >= sum(mu[:i])
for i in range(1, max(len(lam), len(mu))+1))
def monomial_symmetric(n, lam):
R = ring(n)
return R({tuple(exponents): QQ(1)
for exponents in set(permutations(lam + (0,)*(n-len(lam))))})
def laplace_beltrami(f):
R = f.parent()
x = R.gens()
df = [f.derivative(v) for v in x]
answer = sum((v*v*df[i].derivative(v) for i,v in enumerate(x)), R.zero())
for i in range(len(x)):
for j in range(i+1,len(x)):
numerator = x[i]**2*df[i] - x[j]**2*df[j]
quotient, remainder = numerator.quo_rem(x[i]-x[j])
assert not remainder, 'symmetry must cancel the denominator'
answer += quotient
return answer
@lru_cache(None)
def degree_slice(n, d):
"""Return every C_lambda of degree d in n alphabet variables."""
R = ring(n)
shapes = tuple(lam for lam in partitions(d) if len(lam) <= n)
monomials = [monomial_symmetric(n,lam) for lam in shapes]
exp = [lam+(0,)*(n-len(lam)) for lam in shapes]
eigenvalues = [sum(a*(a-1+n-i) for i,a in enumerate(lam,1)) for lam in shapes]
actions = [{tuple(e): c for e,c in laplace_beltrami(m).dict().items()}
for m in monomials]
transition = [[a.get(e,QQ(0)) for e in exp] for a in actions]
monic = []
for k,lam in enumerate(shapes):
coeff = [QQ(0)]*len(shapes)
coeff[k] = QQ(1)
for j in range(k+1,len(shapes)):
numerator = sum((coeff[i]*transition[i][j] for i in range(k,j)),QQ(0))
if not dominates(lam,shapes[j]):
assert numerator == 0
continue
denominator = QQ(eigenvalues[k]-eigenvalues[j])
assert denominator > 0
coeff[j] = numerator/denominator
f = sum((c*m for c,m in zip(coeff,monomials)),R.zero())
assert laplace_beltrami(f) == eigenvalues[k]*f, (n,d,lam,coeff,transition,f,laplace_beltrami(f),eigenvalues[k])
monic.append(f)
remainder = sum(R.gens(),R.zero())**d
result = {}
for lam,e,f in zip(shapes,exp,monic):
scale = {tuple(a): c for a,c in remainder.dict().items()}.get(e,QQ(0))
assert scale > 0
result[lam] = R(scale*f)
remainder -= result[lam]
assert remainder == 0
return result
class ZonalPolynomials(numberdb.Generator):
table = 'T576'
parameters = ('n', 'lambda')
type = 'Q[]'
rigour = 'exact'
files = ('generate.py', 'checks.py', 'check.py', 'dry_run.py',
'argument-selection.md')
def enumerate(self):
for n,limit in DEGREE_LIMITS:
for d in range(2,limit+1):
for lam in partitions(d):
if len(lam) <= n:
yield {'n':str(n),'lambda':','.join(map(str,lam))}
def value(self, params, digits):
n = int(params['n'])
lam = tuple(int(a) for a in params['lambda'].split(','))
if n < 1 or not lam or tuple(sorted(lam,reverse=True)) != lam \
or min(lam) <= 0 or len(lam) > n:
raise ValueError('expected a positive alphabet size and a partition of length at most n')
return degree_slice(n,sum(lam))[lam]
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].isupper():
key = key.split('=',1)[1].strip().strip('\"\'')
os.environ['NUMBERDB_API_KEY'] = key
generator = ZonalPolynomials()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(generator.publish(message='Fill exact real zonal C polynomials after independent checks'))
else:
result = generator.verify(sample=None)
print(result)
sys.exit(0 if result.ok else 1)