generate.py

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

4784 bytes, as of the version from 2026-11-09 23:15 (current). Recorded here, not run.

"""Inverse binomial sums -- numberdb.org/T698.

Run with SageMath or passagemath-symbolics:
    sage -pip install numberdb mpmath
    sage -python generate.py             # verify all stored entries
    sage -python generate.py --submit    # fill this draft, API key required
    sage -python checks.py               # independent checks; read-only

F_s(u) = sum_{j>=1} u^j/(j^s binomial(2*j,j)).
Default selection: s=2,...,10; u=1,-1,2,3,4, in that order for each s.
For |u|<4, Arb sums the defining series and encloses a geometric tail.
At u=4, the log-sine integral is integrated by a convergent power series
with a positive-coefficient majorant. certification.md derives both bounds.
The endpoint algorithm adapts the Euler-product method independently inspected
in T697's attached generate.py, SHA256
531d1e432c82defc5d84670740b0bc4c1b8cb5168fe647621ec7cb3e77c34391.
This file contains the complete numerical algorithm; it imports no companion
source and reads no precomputed values when producing an entry.

Instruction provenance: skill-manifest.json, archived at
agents/runs/20261109T224707Z-build-skills/manifest.json. Both archived skills:
886390324dd1971e381a43cf3b46b822ab0bc67d62f31d8481e29ec79556746b.
"""
import os
import sys
from functools import lru_cache
from math import comb, factorial
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

MAX_ORDER = 10
ARGUMENTS = ('1', '-1', '2', '3', '4')
WORKING_GUARD_BITS = 192
TAIL_GUARD_DIGITS = 60

@lru_cache(maxsize=8)
def endpoint_workspace(digits):
    R = RealBallField(numberdb.bits(digits, losing=WORKING_GUARD_BITS))
    P = PolynomialRing(R, 'x')
    N = numberdb.bits(digits + TAIL_GUARD_DIGITS, losing=0)
    A = (R(2)/R.pi()).log()
    h = P([R(0)] + [R(2*m).zeta()/(R(m)*R(4)**m) for m in range(1,N+1)])
    powers = [P.one()]
    for j in range(1,MAX_ORDER-1):
        powers.append((powers[-1]*h).truncate(N+1))
    J = [[R(1)/(2*m+2) for m in range(N+1)]]
    for d in range(1,MAX_ORDER-1):
        J.append([(A**d+d*J[-1][m])/(2*m+2) for m in range(N+1)])
    return R,N,A,powers,J

def endpoint(s,digits):
    R,N,A,powers,J = endpoint_workspace(digits)
    p = s-2
    total = sum((comb(p,j)*sum((c*J[p-j][m] for m,c in enumerate(powers[j].list())),R(0))
                 for j in range(p+1)),R(0))
    C = 2*R(2).log()
    error = R(0)
    for j in range(1,p+1):
        d = p-j
        moment = sum((R(comb(d,l)*factorial(l))*abs(A)**(d-l)/R(2)**(l+1)
                      for l in range(d+1)),R(0))
        error += comb(p,j)*C**j*R(2)**(-N-1)*moment
    factor = R(2)**p*R.pi()**2/factorial(p)
    return (factor*total).add_error((factor*error).upper())

def direct_series(s,u,digits):
    R = RealBallField(numberdb.bits(digits, losing=WORKING_GUARD_BITS))
    u = QQ(u)
    if u == 0:
        return QQ(0)
    assert abs(u)<4
    r = R(abs(u))/4
    target = R(10)**(-digits-TAIL_GUARD_DIGITS)
    b = R(u)/2
    total = R(0)
    j = 1
    while True:
        term = b/R(j)**s
        total += term
        tail = abs(term)*r/(1-r)
        if tail < target:
            return total.add_error(tail.upper())
        b *= R(u)*(j+1)/(2*(2*j+1))
        j += 1

def evaluate(s,u,digits=100):
    s,u=int(s),QQ(u)
    assert 2<=s<=MAX_ORDER and -4<u<=4
    answer=endpoint(s,digits) if u==4 else direct_series(s,u,digits)
    if u:
        assert answer.is_finite()
        assert answer.accuracy() >= numberdb.bits(digits+20,losing=0)
    return answer

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

    def enumerate(self):
        for s in range(2,MAX_ORDER+1):
            for u in ARGUMENTS:
                yield {'s':str(s),'u':u}

    def value(self,params,digits):
        value=evaluate(params['s'],params['u'],digits)
        if params=={'s':'3','u':'-1'}:
            return {'number':value,'comment':r'This is $-2\zeta(3)/5$, the negative of the usual positive Apéry sum CITE{BBK}.'}
        return value

if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        os.environ['NUMBERDB_API_KEY']=sys.stdin.read().strip()
    generator=InverseBinomial()
    if '--submit' in sys.argv or os.environ.get('NUMBERDB_PUBLISH')=='1':
        print(generator.publish(message='Certified inverse binomial sums with series tails and independent integral checks.'))
    else:
        report=generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)