generate.py

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

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

"""Schur Q-polynomials -- numberdb.org/T577.

Run with SageMath (install numberdb once with sage -pip install numberdb):
    sage -python generate.py             # verify every stored entry
    sage -python checks.py               # independent checks, no writes
    sage -python generate.py --fill      # fill the existing draft using API key

The Q normalization is q_0=1 and sum q_r z^r=prod (1+x_i*z)/(1-x_i*z).
Two-row Q functions and a recursively expanded Pfaffian give every polynomial.
All arithmetic is in ZZ[x1,...,xn], with no floating point or division.
See argument-selection.md for the discrete families and finite bounds.
Filling sends entries and source files; it does not publish or offer the table.
"""
import os
import sys
from functools import lru_cache
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing

DEGREE_BOUNDS = {2: 6, 3: 6, 4: 6, 5: 4}

@lru_cache(None)
def ring(n):
    return PolynomialRing(ZZ, n, names=tuple('x%d' % i for i in range(1, n + 1)))


def strict_partitions(d, maximum=None):
    """Strict partitions in descending lexicographic order."""
    if d == 0:
        yield ()
        return
    if maximum is None:
        maximum = d
    for first in range(min(d, maximum), 0, -1):
        for rest in strict_partitions(d - first, first - 1):
            yield (first,) + rest


@lru_cache(None)
def one_row(n, degree):
    R = ring(n)
    q = [R.one()] + [R.zero()] * degree
    for x in R.gens():
        q = [q[r] + sum((2*x**j*q[r-j] for j in range(1, r+1)), R.zero())
             for r in range(degree+1)]
    return tuple(q)


@lru_cache(None)
def schur_q(n, lam):
    """Exact Schur Q for a strict partition, including zero specializations."""
    lam = tuple(lam)
    R = ring(n)
    if not lam:
        return R.one()
    if any(a <= 0 for a in lam) or any(a <= b for a,b in zip(lam,lam[1:])):
        raise ValueError('positive strictly decreasing parts required')
    q = one_row(n, sum(lam))
    padded = lam if len(lam) % 2 == 0 else lam + (0,)

    def pair(a,b):
        return q[a]*q[b] + 2*sum(((-1)**j*q[a+j]*q[b-j]
                                 for j in range(1,b+1)), R.zero())

    def pfaffian(parts):
        if not parts:
            return R.one()
        return sum(((-1)**(j+1)*pair(parts[0],parts[j])
                    *pfaffian(parts[1:j]+parts[j+1:])
                    for j in range(1,len(parts))), R.zero())

    return R(pfaffian(padded))


class SchurQ(numberdb.Generator):
    table = 'T577'
    parameters = ('n', 'lambda')
    type = 'Z[]'
    rigour = 'exact'
    files = ('generate.py', 'checks.py', 'check.py', 'argument-selection.md',
             'numerical-evidence.md', 'skill-evidence.json')

    def enumerate(self):
        for n, bound in sorted(DEGREE_BOUNDS.items()):
            for d in range(2,bound+1):
                for lam in strict_partitions(d):
                    if len(lam) <= n:
                        yield {'n':str(n), 'lambda':','.join(map(str,lam))}

    def value(self, params, digits=100):
        return schur_q(int(params['n']), tuple(map(int,params['lambda'].split(','))))


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN'):
        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)
    generator = SchurQ()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH'):
        print(generator.publish(message='Fill Schur Q-polynomials with exact, independently checked expansions',
                                assisted_by='codex-cli'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)