checks.py

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

8939 bytes, as of the version from 2026-10-26 23:52 (current). Recorded here, not run.

"""Independent checks for Airy derivative zeta values -- numberdb.org/T532.

Run: sage -python checks.py. Add --stored to check database values (the
NUMBERDB_CHECK_STORED=1 environment variable is equivalent). Download
check.py and generate.py beside this file. Credentials for a private draft
may be supplied on stdin with NUMBERDB_KEY_FROM_STDIN=1.

The independent computation extracts coefficients of Ai(z)/Ai'(z) by a
512-point Cauchy sum on |z|=1/2 at 600 bits. It shares no recurrence or
Gamma initialization with generate.py. Its alias bound is 2/(2^512-1).

Why that bound holds: DLMF 9.9(i) puts all Ai' zeros on the negative real
axis. On [-1,0], the two alternating Airy power series give Ai(-x)>0:
the initial-value-normalized components are >=1-x^3/6 and
x*(1-x^3/12), respectively, for 0<=x<=1. Therefore Ai''(z)=z Ai(z)<=0
there. The rigorously negative evaluation Ai'(-1) proves no derivative
zero lies on [-1,0], so every positive eigenvalue exceeds 1. By Voros
(43.2), Z_N(2)=-Ai(0)/Ai'(0)<2, verified here in balls. Hence
|[z^m](Ai/Ai')|=Z_N(m+2)<2. Discrete Fourier extraction at order m aliases
only m+j*512 (j>=1), bounded by 2*sum(2^(-j*512)). The alias error is
added to each extracted ball. The finite sum's roundoff is propagated by
Arb. The polynomial 3+2z+z^5 is the control, tested before the Airy sum.

Read numerical-evidence.md and skill-manifest.json for instruction hashes,
measurement results, sources and limitations. These are build checks, not
the separate independent critique or its argument-review sidecar.
"""
import json
import os
import sys
import time
from decimal import Decimal
from fractions import Fraction
import numberdb.sage as numberdb
from numberdb._write import to_text
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.rings.complex_arb import ComplexBallField
from sage.env import SAGE_VERSION
from check import exactness, measure, names_its_rings, agrees_with
from generate import AiryDerivativeZeta, polynomials, read_key_if_requested

COUNT = 512
CHECK_BITS = 600
B = RealBallField(CHECK_BITS)
C = ComplexBallField(CHECK_BITS)


def finite_narrow(v, bound=QQ(1)/10**120):
    if hasattr(v, 'real') and hasattr(v, 'imag'):
        assert v.real().is_finite() and v.imag().is_finite(), 'Nonfinite check ball'
        assert v.real().rad() < bound and v.imag().rad() < bound
    else:
        assert v.is_finite() and v.rad() < bound


def cauchy_values():
    phases = [(C(0, 2)*B.pi()*QQ(k)/QQ(COUNT)).exp() for k in range(COUNT)]
    points = [w/2 for w in phases]

    def extract(samples):
        sums = [C(0) for _ in range(19)]
        for w, sample in zip(phases, samples):
            factor = C(1)
            ratio = 2/w
            for m in range(19):
                sums[m] += sample*factor
                factor *= ratio
        return [s/COUNT for s in sums]

    # A nonzero control prevents a coercion or Fourier-sign bug passing silently.
    control = extract([3+2*z+z**5 for z in points])
    for m, got in enumerate(control):
        expected = {0: 3, 1: 2, 5: 1}.get(m, 0)
        finite_narrow(got)
        assert (expected in got.real()) and got.imag().contains_zero()
    print('Cauchy control: all 19 coefficients of 3+2z+z^5 passed', flush=True)
    assert C(-1).airy_ai_prime().real() < 0
    t = -C(0).airy_ai()/C(0).airy_ai_prime()
    finite_narrow(t)
    assert 1 < t.real() < 2
    # Independent evaluations at nonzero complex arguments.
    samples = []
    for z in points:
        denominator = z.airy_ai_prime()
        assert (0 not in denominator)
        v = z.airy_ai()/denominator
        finite_narrow(v)
        samples.append(v)
    alias = QQ(2)/QQ(2**COUNT-1)
    result = {}
    for m, got in enumerate(extract(samples)):
        finite_narrow(got)
        assert got.imag().contains_zero()
        result[m+2] = ((-1)**(m+1)*got.real()).add_error(B(alias))
        finite_narrow(result[m+2])
    return result, alias


def interval_from_text(text):
    text = str(text)
    if '.' not in text and 'e' not in text.lower():
        q = QQ(text)
        return B(q), q, q
    value = Decimal(text)
    center = QQ(Fraction(value))
    radius = QQ(10)**int(value.as_tuple().exponent)
    return B(center, radius), center-radius, center+radius


def check_identities(values):
    for n in range(2, 21):
        assert values[n] > 0
    for n in range(2, 20):
        assert values[n] > values[n+1]
    for n in range(3, 20):
        assert values[n-1]*values[n+1] > values[n]**2
    for n in range(4, 21):
        residual = (n-2)*values[n] - sum((values[j]*values[n-j] for j in range(2,n-1)), B(0))
        assert residual.contains_zero(), ('recurrence',n)
    # Published identities with the reflection-form Gamma expression for kappa.
    t_reflect = B(3)**(QQ(1)/6)*B(QQ(1)/3).gamma()**2/(2*B.pi())
    assert values[2].overlaps(t_reflect)
    assert (1 in values[3])
    low = {4: t_reflect**2/2, 5: 2*t_reflect/3, 6: (t_reflect**3+1)/4}
    for n,v in low.items():
        assert values[n].overlaps(v)


def main():
    started = time.monotonic()
    read_key_if_requested()
    if os.environ.get('NUMBERDB_API_KEY'):
        numberdb.configure(api_key=os.environ['NUMBERDB_API_KEY'])
    g=AiryDerivativeZeta()
    params=list(g.enumerate())
    assert params == [{'n':str(n)} for n in range(2,21)]
    produced={int(p['n']):g.value(p,100) for p in params}
    values={n:(v['number'] if isinstance(v,dict) else v) for n,v in produced.items()}
    assert not exactness(produced)
    assert not exactness({n:polynomials(20)[n] for n in range(2,21)})
    assert not names_its_rings(os.path.join(os.path.dirname(__file__),'generate.py'))
    assert not agrees_with({3:values[3]}, lambda n:QQ(1))
    text={n:to_text(v,digits=100) for n,v in values.items()}
    high={n:g.value({'n':str(n)},160) for n in range(2,21)}
    for n,v in high.items():
        v=v['number'] if isinstance(v,dict) else v
        if n!=3:
            finite_narrow(v)
            assert values[n].overlaps(v)
    independent, alias=cauchy_values()
    for n,v in values.items():
        if n==3:
            assert v==1 and text[n]=='1' and (1 in independent[n])
        else:
            finite_narrow(v)
            assert v.overlaps(independent[n]), ('Cauchy comparison',n)
            _,lo,hi=interval_from_text(text[n])
            assert QQ(independent[n].lower()) >= lo and QQ(independent[n].upper()) <= hi
    check_identities({n:B(v) for n,v in values.items()})
    print('All 19 values: higher precision, Cauchy enclosures, stored spelling and identities passed')
    result={'sage':SAGE_VERSION,'numberdb':numberdb.__version__,
            'working_bits':numberdb.bits(100,losing=96),
            'higher_bits':numberdb.bits(160,losing=96),
            'check_bits':CHECK_BITS,'cauchy_points':COUNT,
            'alias_bound':str(alias),
            'min_accuracy_bits':min(int(v.accuracy()) for n,v in values.items() if n!=3),
            'worst_relative_radius':max(float(v.rad()/abs(v.center())) for n,v in values.items() if n!=3),
            'measure':measure(text),
            'values':{str(n):text[n] for n in range(2,21)},
            'independent_balls':{str(n):str(v) for n,v in independent.items()}}
    if '--stored' in sys.argv or os.environ.get('NUMBERDB_CHECK_STORED')=='1':
        document=numberdb.table('T532')
        records=document['Numbers']
        if isinstance(records,dict):
            records=[{'params':{'n':k}, **(v if isinstance(v,dict) else {'number':v})} for k,v in records.items()]
        written={int(r['params']['n']):str(r['number']) for r in records}
        assert list(written)==list(range(2,21))
        stored_balls={}
        for n,t in written.items():
            ball,lo,hi=interval_from_text(t); stored_balls[n]=ball
            if n==3:
                assert t=='1' and (1 in independent[n])
            else:
                assert QQ(independent[n].lower()) >= lo and QQ(independent[n].upper()) <= hi
        check_identities(stored_balls)
        # Test the actual published program, not a remembered copy.
        import contextlib
        import io
        namespace = {}
        with contextlib.redirect_stdout(io.StringIO()):
            exec(document['Programs']['program-sage']['code'], namespace)
        assert list(namespace['Z']) == list(range(2,21))
        for n, v in namespace['Z'].items():
            assert B(v).overlaps(independent[n]), ('program', n)
        assert namespace['Z'][3] == QQ(1)
        report=g.verify(sample=None); assert report.ok,report
        result['stored_check']={'entries':len(written),'verify':str(report),'all_independent_enclosures_contained':True}
        print('Read-back checks: all 19 independent enclosures, recurrence and inequalities passed')
    result['seconds']=time.monotonic()-started
    print('EVIDENCE_JSON '+json.dumps(result,sort_keys=True))

if __name__=='__main__':
    main()