generate.py

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

3535 bytes, as of the version from 2026-10-26 23:02. Recorded here, not run.

"""Airy zeta values -- numberdb.org/T531.

Run with SageMath (or compatible passagemath):
    sage -pip install numberdb          # once
    sage -python generate.py             # verify all stored entries, read only
    sage -python generate.py --fill      # fill this draft, with NUMBERDB_API_KEY

In the campaign use agents/sage.sh; NUMBERDB_KEY_FROM_STDIN=1 reads the key
from stdin, and NUMBERDB_PUBLISH=1 is the wrapper's entry-fill switch.
Neither entry-fill route publishes or offers the draft.

Z_D(n) = sum_k (-a_k)^(-n), where a_k are the negative Ai zeros.
Default selection: every integer 2 <= n <= 20. See argument-selection.md.
The genus-one product gives Ai'(z)/Ai(z) = -kappa +
sum_{n>=2} (-1)^(n-1) Z_D(n) z^(n-1). The Riccati equation q'=z-q^2
computes each coefficient as an exact polynomial over QQ in kappa.
Evaluate these polynomials in Arb balls with kappa =
3^(1/3) Gamma(2/3)/Gamma(1/3); all arguments are exact rationals.
The 96-bit guard is measured over the full selection by checks.py.
No roots are truncated and no analytic continuation is used for these values.
"""
import os
import sys
from functools import lru_cache

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing

WORKING_GUARD = 96
MAX_ORDER = 20


@lru_cache(maxsize=None)
def zeta_polynomials(max_order):
    """Exact rational polynomials P_n(X) with Z_D(n)=P_n(kappa)."""
    ring = PolynomialRing(QQ, 'X')
    x = ring.gen()
    q = [-x]
    for m in range(max_order - 1):
        rhs = ring(1 if m == 1 else 0)
        rhs -= sum((q[j] * q[m-j] for j in range(m+1)), ring.zero())
        q.append(rhs * (QQ(1) / QQ(m+1)))
    return {n: (-1)**(n-1) * q[n-1] for n in range(2, max_order+1)}


def kappa_ball(precision):
    field = RealBallField(precision)
    return (field(3)**(QQ(1)/QQ(3)) * field(QQ(2)/QQ(3)).gamma()
            / field(QQ(1)/QQ(3)).gamma())


def evaluate(n, precision):
    value = zeta_polynomials(n)[n](kappa_ball(precision))
    if not value.is_finite() or not value > 0:
        raise ArithmeticError('Airy zeta must be a finite positive ball')
    return value


class AiryZeta(numberdb.Generator):
    table = 'T531'
    parameters = ('n',)
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'check.py', 'argument-selection.md',
             'numerical-evidence.md', 'numerical-evidence.json', 'skill-manifest.json')

    def enumerate(self, max_order=MAX_ORDER):
        for n in range(2, max_order+1):
            yield {'n': str(n)}

    def value(self, params, digits):
        n = int(params['n'])
        if n < 2:
            raise ValueError('The unregularized sum requires n >= 2')
        return evaluate(n, numberdb.bits(digits, losing=WORKING_GUARD))


def main():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        token = sys.stdin.read().strip()
        if token.startswith('NUMBERDB_') and '=' in token:
            token = token.split('=', 1)[1].strip().strip('\"\'')
        os.environ['NUMBERDB_API_KEY'] = token
    generator = AiryZeta()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        outcome = generator.publish(message='Add nineteen certified Airy spectral moments at 100 digits')
        print(outcome)
    report = generator.verify(sample=None)
    print(report)
    return 0 if report.ok else 1


if __name__ == '__main__':
    sys.exit(main())