generate.py

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

5305 bytes, as of the version from 2026-10-29 13:21 (current). Recorded here, not run.

"""Quantum dimensions of SU(N) WZW representations -- numberdb.org/T609.

Run with SageMath:
    sage -pip install numberdb
    sage -python generate.py                 # verify every stored entry
    sage -python checks.py                   # independent offline checks
    sage -python checks.py --stored          # check the authenticated draft
    sage -python generate.py --fill          # fill the existing draft

For draft reads and --fill set NUMBERDB_API_KEY. A campaign may instead pipe
its key on stdin with NUMBERDB_KEY_FROM_STDIN=1; NUMBERDB_PUBLISH=1 selects
--fill through agents/sage.sh. Filling entries does not publish the table.

The symmetric quantum integer uses q=exp(pi*i/(N+k)), not exp(2*pi*i/(N+k)).
The product runs over positive type-A roots. Exact cyclotomic reduction detects
ALL rational results without inspecting decimal digits. Other values are
computed throughout in Arb real balls. The 96-bit guard is measured in the
attached numerical-evidence.json. The exact cyclotomic field is used only for
rationality, so a default complex embedding never selects the displayed value.

Bounds, rationale, ordering, and the archived skill manifest are recorded in
argument-selection.md. The complete default selection is defined in this file;
no input dataset or helper controls enumeration. checks.py uses character
polynomials and modular normalization independently of the product algorithm.
"""
import os
import sys
from functools import lru_cache

import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.number_field.number_field import CyclotomicField
from sage.rings.real_arb import RealBallField

WORKING_GUARD = 96
LEVEL_BOUNDS = ((2, 20), (3, 8), (4, 5), (5, 4))


def compositions(total, length):
    """Nonnegative tuples with fixed sum, in lexicographic order."""
    if length == 1:
        yield (total,)
    else:
        for first in range(total + 1):
            for tail in compositions(total - first, length - 1):
                yield (first,) + tail


def parse(params):
    N, k = int(params['N']), int(params['k'])
    a = tuple(int(x) for x in str(params['weight']).split(','))
    if N < 2 or k < 1 or len(a) != N - 1 or min(a) < 0 or sum(a) > k:
        raise ValueError('Require N>=2, k>=1, N-1 nonnegative labels with sum<=k')
    return N, k, a


@lru_cache(None)
def cyclotomic_data(m):
    K = CyclotomicField(2 * m)
    q = K.gen()
    qints = tuple((q**a - q**(-a)) / (q - q**(-1)) for a in range(m))
    return K, qints


@lru_cache(None)
def exact_dimension(N, k, a):
    K, qints = cyclotomic_data(N + k)
    value = K.one()
    for i in range(N - 1):
        for j in range(i + 1, N):
            value *= qints[j - i + sum(a[i:j])] / qints[j - i]
    return value


@lru_cache(None)
def sine_data(m, digits):
    R = RealBallField(numberdb.bits(digits, losing=WORKING_GUARD))
    return R, tuple((R.pi() * a / m).sin() for a in range(m))


def dimension(N, k, a, digits):
    exact = exact_dimension(N, k, a)
    coefficients = exact.list()
    if all(c == 0 for c in coefficients[1:]):
        return QQ(coefficients[0])
    R, sines = sine_data(N + k, digits)
    result = R.one()
    for i in range(N - 1):
        for j in range(i + 1, N):
            result *= sines[j - i + sum(a[i:j])] / sines[j - i]
    assert result.is_finite() and result > 0
    assert result.accuracy() >= numberdb.bits(digits)
    return result


class QuantumDimensions(numberdb.Generator):
    table = 'T609'
    parameters = ('N', 'k', 'weight')
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'argument-selection.md',
             'companion-checks.py', 'numerical-evidence.json', 'skill-evidence.json')

    def enumerate(self):
        for N, max_level in LEVEL_BOUNDS:
            for k in range(1, max_level + 1):
                for total in range(k + 1):
                    for a in compositions(total, N - 1):
                        yield {'N': str(N), 'k': str(k),
                               'weight': ','.join(map(str, a))}

    def value(self, params, digits):
        N, k, a = parse(params)
        record = {'number': dimension(N, k, a, digits),
                  'param-latex': '$(' + ','.join(map(str, a)) + ')$'}
        if sum(a) == 0:
            record['comment'] = 'Vacuum representation.'
        elif a == (1,) + (0,) * (N - 2):
            record['comment'] = 'Defining representation.'
        elif a == (0,) * (N - 2) + (1,):
            record['comment'] = 'Dual of the defining representation.'
        return record


def authenticate_stdin():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=', 1)[0].replace('_', '').isupper():
            key = key.split('=', 1)[1].strip().strip('\"\'')
        os.environ['NUMBERDB_API_KEY'] = key


if __name__ == '__main__':
    authenticate_stdin()
    g = QuantumDimensions()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(g.publish(message='Fill complete small-rank integrable alcoves with certified quantum dimensions.'))
    else:
        report = g.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)