generate.py

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

4207 bytes, as of the version from 2026-10-29 07:39 (current). Recorded here, not run.

"""Dirichlet spectral determinants of Laplacians on hemispheres -- numberdb.org/T603.

Run with SageMath (install numberdb once with sage -pip install numberdb):
    sage -python generate.py             # verify the stored draft/table
    sage -python generate.py --fill      # write entries, NUMBERDB_API_KEY required

The unit-radius, unshifted scalar Dirichlet spectrum is k(k+d-1), with
multiplicity binomial(d+k-2,d-1), k>=1. Theorem 3.2 of Cunha and Freitas,
arXiv:2404.12114v1, is evaluated using exact signed Stirling and central
factorial coefficients and Arb enclosures. d=1 is handled as zeta(2s).
All dimensions 1..20 are included, determinant first and log determinant
second. See argument-selection.md for the finite-prefix rationale.

WORKING_DIGITS=150 is measured against 200-digit recomputations in checks.py.
The independent spectral continuation in independent.py supplies a second
method, not the justification for the proven Arb enclosures.
"""
import os
import sys
from functools import lru_cache
from math import factorial
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.complex_arb import ComplexBallField
from sage.rings.real_arb import RealBallField

WORKING_DIGITS = 150
MAX_DIMENSION = 20


def harmonic(n):
    return sum((QQ(1)/QQ(k) for k in range(1,n+1)),QQ(0))


@lru_cache(None)
def stirling(n,k):
    if n == 0:
        return int(k == 0)
    if k < 1 or k > n:
        return 0
    return stirling(n-1,k-1)-(n-1)*stirling(n-1,k)


def roots_polynomial(roots):
    """Exact coefficient list of product (X-r); no symbolic ring needed."""
    c=[1]
    for r in roots:
        out=[0]*(len(c)+1)
        for k,v in enumerate(c):
            out[k]-=r*v
            out[k+1]+=v
        c=out
    return c


@lru_cache(None)
def logdet(d, working=WORKING_DIGITS):
    if not isinstance(d,int) or d<1:
        raise ValueError('dimension must be a positive integer')
    C=ComplexBallField(numberdb.bits(working))
    R=RealBallField(numberdb.bits(working))
    if d == 1:
        return (2*R.pi()).log()
    # The signed Stirling combination is valid for either parity.
    zp=sum((QQ(stirling(d,k+1)+(-1)**(d-1+k)*stirling(d-1,k))
            /QQ(factorial(d-1))*C(-k).zetaderiv(1)
            for k in range(d)), C(0))
    if d%2:
        n=(d+1)//2
        u=roots_polynomial([j*j for j in range(n-1)])
        correction=-sum((QQ(2*u[i])/QQ(factorial(2*n-2))
                         *QQ((n-1)**(2*i+1))/QQ(2*i)
                         *(harmonic(i-1)/2-harmonic(2*i-1))
                         for i in range(1,n)),QQ(0))
    else:
        n=d//2
        v=roots_polynomial([0]+[(2*j-1)**2 for j in range(1,n)])
        correction=sum((QQ(v[i])/QQ(4**(n-1)*factorial(2*n-1))
                        *QQ((2*n-1)**(2*i))/QQ(4*i)
                        *(harmonic(i-1)/2-harmonic(2*i-1))
                        for i in range(1,n+1)),QQ(0))
    result=-(zp+correction)
    assert result.real().is_finite() and result.imag().is_finite()
    assert result.real().accuracy() > 400 and result.imag().contains_zero()
    return result.real()


class HemisphereDirichlet(numberdb.Generator):
    table='T603'
    parameters=('d','convention')
    type='R'
    digits=100
    rigour='proven'

    def enumerate(self, max_dimension=MAX_DIMENSION):
        for d in range(1,max_dimension+1):
            for convention in ('det','logdet'):
                yield {'d':str(d),'convention':convention}

    def value(self,params,digits):
        L=logdet(int(params['d']),max(WORKING_DIGITS,digits+50))
        if params['convention']=='det':
            return L.exp()
        if params['convention']=='logdet':
            return L
        raise ValueError('unknown convention')


if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        os.environ['NUMBERDB_API_KEY']=sys.stdin.read().strip()
    g=HemisphereDirichlet()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH')=='1':
        print(g.publish(message='computed all 40 entries with exact coefficients and Arb enclosures'))
    else:
        result=g.verify(sample=None)
        print(result)
        sys.exit(0 if result.ok else 1)