generate.py

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

4851 bytes, as of the version from 2026-10-27 13:49 (current). Recorded here, not run.

"""Orthogonal Weingarten functions -- numberdb.org/T550.

Run with SageMath (install numberdb with sage -pip install numberdb):
    sage -python generate.py                 # verify every stored entry
    sage -python generate.py --fill-draft    # write entries to the existing draft

Writing needs NUMBERDB_API_KEY; this never offers or publishes the table.
The campaign can instead pipe the key with NUMBERDB_KEY_FROM_STDIN=1 and
set NUMBERDB_PUBLISH=1 to fill its draft through agents/sage.sh.

Values are exact Sage rationals. The zonal polynomial is Jack J at alpha=2.
Collins--Matsumoto, arXiv:0903.5143, equations (3.5)--(3.7), give its power-sum
coefficients as the spherical functions multiplied by pairing-orbit sizes.
Dimensions f^(2 lambda) use the hook-length formula. Every division is exact.

Default coverage: every N=1,...,12 and every partition of 1<=k<=min(6,N),
ordered by N, then k, then reverse lexicographic partition order.
See argument-selection.md for the coverage rationale and independent_checks.py
for pairing-Gram and sphere-moment checks, which do not use Jack polynomials.
"""

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.symbolic.ring import SR  # Initializes symbolic conversions used by Sage.
import sage.rings.polynomial.laurent_polynomial_ring  # Matrix ring dispatch.
from sage.combinat.partition import Partitions
from sage.combinat.sf.sf import SymmetricFunctions
from collections import Counter
from functools import lru_cache
from math import factorial, prod
import os
import sys


@lru_cache(None)
def zonal_expansions(k):
    """Return exact coefficients [p_mu] J_lambda^(2) for all lambda of k."""
    sf = SymmetricFunctions(QQ)
    p = sf.power()
    jack = sf.jack(t=QQ(2)).J()
    return {
        tuple(lam): {tuple(mu): QQ(c) for mu, c in p(jack[lam])}
        for lam in Partitions(k)
    }


def character_dimension(lam):
    """Dimension of the symmetric-group representation with shape lam."""
    hooks = [row - j + sum(next_row > j for next_row in lam[i + 1:])
             for i, row in enumerate(lam) for j in range(row)]
    numerator, denominator = factorial(sum(lam)), prod(hooks)
    assert numerator % denominator == 0
    return numerator // denominator


def pairing_orbit_size(mu):
    k = sum(mu)
    z = prod(part**count * factorial(count)
             for part, count in Counter(mu).items())
    numerator, denominator = 2**k * factorial(k), 2**len(mu) * z
    assert numerator % denominator == 0
    return numerator // denominator


def weingarten(N, mu):
    """The conventional coefficient, including singular positive dimensions."""
    N, mu = int(N), tuple(int(x) for x in mu)
    if N < 1 or not mu or any(x < 1 for x in mu) or mu != tuple(sorted(mu, reverse=True)):
        raise ValueError('N must be positive and mu a nonempty decreasing partition')
    k = sum(mu)
    total = QQ(0)
    for lam, coefficients in zonal_expansions(k).items():
        if len(lam) > N:
            continue
        denominator = prod(N + 2*j - i for i, row in enumerate(lam) for j in range(row))
        assert denominator > 0
        dimension = character_dimension(tuple(2*x for x in lam))
        total += QQ(dimension) * coefficients.get(mu, QQ(0)) / QQ(denominator)
    return (QQ(2**k * factorial(k)) * total
            / QQ(factorial(2*k) * pairing_orbit_size(mu)))


class OrthogonalWeingarten(numberdb.Generator):
    table = 'T550'
    parameters = ('N', 'mu')
    type = 'Q'
    rigour = 'exact'
    files = ('generate.py', 'independent_checks.py', 'argument-selection.md',
             'check.py', 'auxiliary_checks.py', 'program.py',
             'numerical-evidence.md', 'skill-evidence.json')

    def enumerate(self, max_dimension=12, max_degree=6):
        for N in range(1, max_dimension + 1):
            for k in range(1, min(max_degree, N) + 1):
                for mu in sorted((tuple(p) for p in Partitions(k)), reverse=True):
                    yield {'N': str(N), 'mu': ','.join(map(str, mu))}

    def value(self, params, digits):
        return weingarten(int(params['N']), tuple(map(int, params['mu'].split(','))))


def configure_key_from_stdin():
    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('\'"')
        numberdb.configure(api_key=key)


if __name__ == '__main__':
    configure_key_from_stdin()
    generator = OrthogonalWeingarten()
    if '--fill-draft' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill 242 exact orthogonal Weingarten coefficients with independent checks'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)