generate.py

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

5981 bytes, as of the version from 2026-11-02 09:34 (current). Recorded here, not run.

"""Thompson moonshine coefficients -- numberdb.org/T673.

Run with SageMath (or a compatible passagemath installation):
    sage -pip install numberdb
    sage -python generate.py                 # verify all stored entries
    sage -python generate.py --publish       # fill the existing draft

The second command writes numbers, not publication status; it requires
NUMBERDB_API_KEY. In the campaign, use agents/sage.sh and supply the key on
stdin with NUMBERDB_KEY_FROM_STDIN=1. NUMBERDB_PUBLISH=1 selects filling.

Download eta-data.json beside this file. Its rational weights and eta
exponents are frozen from Michael H. Mertens's supplement to Griffin--Mertens,
DOI 10.1186/s40687-016-0084-7. The original inputs and their interpretation are
in source-inputs.json and source-notes.md. All arithmetic is exact. For each
eta quotient, its Euler product is expanded by the logarithmic-derivative
recurrence n*a[n] = -sum(b[k]*a[n-k], k=1..n), where
b[k] = sum(r[d]*d*sigma_1(k/d), d|k). Integer division is asserted exact.
The rational linear combinations are checked for integrality and plus-space
support before any coefficient is returned. No numerical rounding is used.

The default selection contains all eight Thompson classes of order at most
five, and every nonnegative grade n <= 201 with n == 0 or 1 mod 4. The polar
coefficient at -3 is computed as a control and described in the document.
See argument-selection.md for the two discrete indices and practical bounds.
"""
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
import json
import os
from pathlib import Path
import sys
from functools import lru_cache

CLASSES = ('1A', '2A', '3A', '3B', '3C', '4A', '4B', '5A')
MAX_GRADE = 201
ROOT = Path(__file__).resolve().parent


def rational(text):
    parts = text.split('/')
    return QQ(ZZ(parts[0])) if len(parts) == 1 else QQ(ZZ(parts[0])) / QQ(ZZ(parts[1]))


@lru_cache(maxsize=None)
def eta_series(exponents, bound):
    shift, rem = divmod(sum(d * r for d, r in exponents), 24)
    assert rem == 0
    count = bound - shift
    if count < 0:
        return shift, ()
    sigma = [ZZ(0)] * (count + 1)
    for d in range(1, count + 1):
        for k in range(d, count + 1, d):
            sigma[k] += d
    b = [ZZ(0)] * (count + 1)
    for d, r in exponents:
        for k in range(d, count + 1, d):
            b[k] += r * d * sigma[k // d]
    a = [ZZ(1)]
    for n in range(1, count + 1):
        quotient, remainder = divmod(-sum(b[k] * a[n-k] for k in range(1,n+1)), n)
        assert remainder == 0
        a.append(ZZ(quotient))
    return shift, tuple(a)


@lru_cache(maxsize=None)
def coefficients(bound=MAX_GRADE):
    data = json.loads((ROOT / 'eta-data.json').read_text())
    assert tuple(data) == CLASSES
    result = {}
    for g in CLASSES:
        output = {n: QQ(0) for n in range(-3, bound + 1)}
        for term in data[g]['terms']:
            eta = tuple(tuple(pair) for pair in term['eta_exponents'])
            assert sum(r for d, r in eta) == 1
            shift, series = eta_series(eta, bound)
            assert shift >= -3
            weight = rational(term['coefficient'])
            for j, a in enumerate(series):
                output[shift+j] += weight * a
        assert output[-3] == 2
        assert all(a.denominator() == 1 for a in output.values())
        assert all(a == 0 for n,a in output.items() if n % 4 not in (0,1))
        assert output[1] == 0
        result[g] = {n: ZZ(a) for n,a in output.items()}
    return result


class ThompsonCoefficients(numberdb.Generator):
    table = 'T673'
    parameters = ('g', 'n')
    type = 'Z'
    rigour = 'exact'
    files = ('generate.py', 'eta-data.json', 'checks.py', 'source-controls.json',
             'source-inputs.json', 'source-notes.md', 'argument-selection.md',
             'skill-evidence.json', 'numerical-evidence.json', 'cm_checks.py',
             'cm-evidence.json', 'rebuild_eta_data.py')

    def enumerate(self, maximum=MAX_GRADE):
        if maximum < 0:
            raise ValueError('The maximum grade must be nonnegative')
        for g in CLASSES:
            for n in range(maximum + 1):
                if n % 4 in (0, 1):
                    yield {'g': g, 'n': str(n)}

    def value(self, params, digits=100):
        g, n = params['g'], int(params['n'])
        if g not in CLASSES or n < 0 or n % 4 not in (0, 1):
            raise ValueError('Unsupported class or inadmissible nonnegative grade')
        value = coefficients(max(MAX_GRADE, n))[g][n]
        comments = {
            0: r'This is the HREF{Degrees_of_the_irreducible_characters_of_the_sporadic_simple_groups#Th,2}[degree of the 248-dimensional irreducible representation].',
            4: r'The even component is the sum of the two irreducible representations of degree $27000$.',
            5: r'The odd component is the sum of two irreducible representations of degree $85995$, so its supertrace is negative.',
            21: r'The sign is negative. The positive sign in CITE{HR}, Table 6, is a typographical error; Table 1 and the exact modular-form construction give $-29466050250$.'
        }
        if g == '1A' and n in comments:
            return {'number': value, 'comment': comments[n]}
        return value


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        token = sys.stdin.read().strip()
        if '=' in token and token.split('=',1)[0].isupper():
            token = token.split('=',1)[1].strip().strip('\'"')
        os.environ['NUMBERDB_API_KEY'] = token
        numberdb.configure(api_key=token)
    generator = ThompsonCoefficients()
    if '--publish' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Compute exact Thompson supertraces from the published eta bases'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)