generate.py

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

5292 bytes, as of the version from 2026-10-30 08:21 (current). Recorded here, not run.

"""Neumann spectral determinants of regular polygons -- numberdb.org/T621.

Run with SageMath or passagemath and numberdb installed:
    sage -pip install numberdb           # once
    sage -python generate.py             # verify every stored entry
    sage -python generate.py --fill      # fill the existing draft, API key required

The operator is -d_x^2-d_y^2 with homogeneous Neumann boundary conditions on the
regular n-gon of circumradius one, omitting its constant zero mode. Entries are det and its natural log,
in that order for n=3,...,30. See argument-selection.md for the selection.

Aurell--Salomonson (hep-th/9304031), (23)--(24), gives the polygon formula.
Kalvin (1910.00104v2), (3.15) and Lemma A.1, gives a finite corner formula.
All divisions of exact coefficients use QQ. Arb encloses the exact rational
arguments and carries errors through log-Gamma, zeta derivative, and exp.
The 180 guard bits are checked by checks.py, including the decimal intervals.
The finite Dirichlet corner computation is adapted from the attached
generator of numberdb.org/T620, fetched 2026-10-30. Kalvin (2610.35383v1),
Theorem 1.1, gives det_N/det_D = area * (1-2/n)^(-n/2).
The independent integral and separated-spectrum controls are in checks.py.

This script writes entries only when --fill is explicit. It cannot offer,
review, or make a table public. Authentication may be piped on stdin with
NUMBERDB_KEY_FROM_STDIN=1; otherwise use NUMBERDB_API_KEY.
"""
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.complex_arb import ComplexBallField

WORKING_GUARD = 180


@lru_cache(maxsize=None)
def ingredients(prec):
    field = RealBallField(prec)
    z = ComplexBallField(prec)(-1).zetaderiv(1)
    assert z.real().is_finite() and z.imag().is_finite() and z.imag().contains_zero()
    return field, z.real()


def corner(a, prec):
    """F(a), with a positive exact rational; no floating-point coefficients."""
    a = QQ(a)
    p, q = a.numerator(), a.denominator()
    field, zp = ingredients(prec)
    half = QQ(1) / 2
    dedekind = sum(((QQ(j) / p - half) * (QQ((j*q) % p) / p - half)
                   for j in range(1, p)), QQ(0))
    b = zp / (p*q) - field(q).log() / (12*p*q)
    b += (QQ(1)/4 + dedekind) * field(QQ(q)/p).log()
    for k in range(1, p):
        b += (half - QQ(k)/p) * field(QQ((k*q) % p)/p).log_gamma()
    for j in range(1, q):
        b += (half - QQ(j)/q) * field(QQ((j*p) % q)/q).log_gamma()
    f = b - a*zp + (a-1/a)/12 * field(2).log()
    f -= (a-1)/4 * (2*field.pi()).log()
    assert f.is_finite()
    return f


@lru_cache(maxsize=None)
def dirichlet_logdet(n, prec):
    n = int(n)
    if n < 3:
        raise ValueError('The number of sides must be at least three')
    field, _ = ingredients(prec)
    h = QQ(1)/n
    a = 1-2*h
    c = QQ(n-1)/(6*(n-2))
    log_scale = (field(1+h).log_gamma() + field(a).log_gamma()
                 - field(1-h).log_gamma())
    result = 2*c*log_scale + field(n).log()/(3*(n-2)) - n*corner(a, prec)
    assert result.is_finite()
    return result


@lru_cache(maxsize=None)
def polygon_logdet(n, prec):
    """Log determinant of the positive Neumann spectrum, circumradius one."""
    n = int(n)
    if n < 3:
        raise ValueError('The number of sides must be at least three')
    field, _ = ingredients(prec)
    alpha = QQ(n-2)/n
    area = QQ(n)/2 * (2*field.pi()/n).sin()
    result = dirichlet_logdet(n, prec) + area.log() - QQ(n)/2*field(alpha).log()
    assert result.is_finite()
    return result


class PolygonNeumannDeterminants(numberdb.Generator):
    table = 'T621'
    parameters = ('n', 'convention')
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'family_checks.py',
             'argument-selection.md', 'derivation.md', 'skill-evidence.json',
             'sources.json', 'checks-prefill-evidence.json', 'dependency-versions.json')

    def enumerate(self, maximum=30):
        for n in range(3, maximum+1):
            for convention in ('det', 'logdet'):
                yield {'n': str(n), 'convention': convention}

    def value(self, params, digits):
        value = polygon_logdet(int(params['n']), numberdb.bits(digits, losing=WORKING_GUARD))
        if params['convention'] == 'det':
            return value.exp()
        if params['convention'] == 'logdet':
            return value
        raise ValueError('Unknown convention')


def authenticated_client():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=', 1)[0].replace('_', '').isupper():
            key = key.split('=', 1)[1].strip().strip('\"\'')
        return numberdb.Client(api_key=key)
    return numberdb.Client()


if __name__ == '__main__':
    gen = PolygonNeumannDeterminants()
    client = authenticated_client()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(gen.publish(client=client, message='Fill 56 certified Neumann polygon determinants and logarithms'))
    else:
        report = gen.verify(sample=None, client=client)
        print(report)
        sys.exit(0 if report.ok else 1)