generate.py

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

4283 bytes, as of the version from 2026-10-26 12:11. Recorded here, not run.

"""Values of the Scorer function Gi -- numberdb.org/T514.

Run with SageMath:
    sage -pip install numberdb
    sage -python generate.py             # verify all stored entries (key for draft)
    sage -python checks.py               # independent checks; no key needed
    sage -python generate.py --fill      # fill the draft, NUMBERDB_API_KEY required

The nine rational arguments and their ordering follow family #211. The
normalization is DLMF 9.12.19: Gi''(x) - x Gi(x) = -1/pi. No scaling is applied.

All arithmetic uses Arb balls. Coefficients c_0 and c_1 are DLMF 9.12.6-7,
c_2 = -1/(2*pi), and c_(k+3) = c_k/((k+3)*(k+2)). For each residue class
modulo 3, after the term c_k*x^k has been added, put
q = |x|^3/((k+3)*(k+2)). Once q < 1, every later ratio is <= q, so the
omitted absolute tail is <= |c_k*x^k|*q/(1-q). Each tail is explicitly
added as a symmetric error ball, not merely used as a stopping criterion.

At digits=100 the field has 665 bits (roughly 200 decimal digits), and
each of the three tails is below 10^-150. This deliberately separates
rounding error from truncation error. The checks measure the final ball
accuracy and refuse any enclosure with fewer than 110 decimal digits.
The table writes 100 significant digits, with the last place uncertain by
one as specified by NumberDB. The constants here are not tuned per entry.
"""
import os
import sys

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField

ARGUMENTS = ('0', '1/2', '-1/2', '1', '-1', '2', '-2', '10', '-10')
WORKING_GUARD_BITS = 332
TAIL_GUARD_DIGITS = 50


def scorer_gi(x, digits=100, diagnostics=False):
    x = QQ(x)
    field = RealBallField(numberdb.bits(digits, losing=WORKING_GUARD_BITS))
    z = field(x)
    pi = field.pi()
    initial = (
        1 / (field(3)**(QQ(7)/6) * field(QQ(2)/3).gamma()),
        1 / (field(3)**(QQ(5)/6) * field(QQ(1)/3).gamma()),
        -1 / (2*pi),
    )
    tolerance = field(10)**(-digits-TAIL_GUARD_DIGITS)
    answer = field(0)
    tails = []
    last_indices = []
    for r, coefficient in enumerate(initial):
        term = coefficient * z**r
        k = r
        subtotal = field(0)
        while True:
            subtotal += term
            ratio = field(abs(x)**3) / ((k+3)*(k+2))
            if ratio < 1:
                tail = abs(term)*ratio/(1-ratio)
                if tail < tolerance:
                    subtotal = subtotal.add_error(tail)
                    tails.append(tail)
                    last_indices.append(k)
                    break
            term = term*z**3/((k+3)*(k+2))
            k += 3
            if k > 100000:
                raise ArithmeticError('Taylor series did not reach its tail target')
        answer += subtotal
    assert answer.is_finite()
    assert answer.accuracy() >= numberdb.bits(digits+10, losing=0)
    if diagnostics:
        return answer, {'last_indices': last_indices,
                        'tail_bounds': [str(t) for t in tails],
                        'accuracy_bits': int(answer.accuracy()),
                        'field_bits': int(field.precision())}
    return answer


class ScorerGiValues(numberdb.Generator):
    table = 'T514'
    parameters = ('x',)
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'check.py', 'dry_run.py', 'run_dry.py',
             'checks-evidence.json', 'dry-run.log')

    def enumerate(self):
        for x in ARGUMENTS:
            yield {'x': x}

    def value(self, params, digits):
        value = scorer_gi(params['x'], digits)
        if QQ(params['x']) == 0:
            return {'number': value,
                    'comment': r'At the origin, $\operatorname{Gi}(0)=1/(3^{7/6}\Gamma(2/3))$.'}
        return value


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        os.environ['NUMBERDB_API_KEY'] = sys.stdin.read().strip()
    generator = ScorerGiValues()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill nine Gi values with rigorous Taylor enclosures and independent checks'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)