generate.py

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

4059 bytes, as of the version from 2026-10-29 03:59 (current). Recorded here, not run.

"""Ray–Singer torsion of lens spaces -- numberdb.org/T599.

Run with SageMath and the numberdb client installed:
    sage -pip install numberdb
    sage -python generate.py                 # verify every stored entry
    sage -python generate.py --fill          # write numbers to the existing draft
    sage -python checks.py                  # independent local checks

Writing requires NUMBERDB_API_KEY, or NUMBERDB_KEY_FROM_STDIN=1 and a key
on stdin. --fill sends entries and attached sources; it does not publish or
ask for review. The default selection is documented in argument-selection.md.

Kitaoka, SIGMA 18 (2022), 091, Proposition 5.1: for the action (1,q)
and complex character u, use q^{-1} modulo p, without squaring or inverting T.
The calculation uses Arb sine balls at max(500, bits(digits)+160) bits.
Rational values are proved by reducing T^2 modulo the cyclotomic polynomial;
only when that remainder is a rational square is an exact rational returned.
No inference of exactness is made from approximate digits.
"""
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from functools import lru_cache
from math import gcd
import os
import sys

WORKING_GUARD = 160
MINIMUM_BITS = 500
P_MAX = 20
POLYNOMIALS = PolynomialRing(QQ, 'x')
X = POLYNOMIALS.gen()


def check_domain(p, q, u):
    if p < 2 or not (1 <= q < p and gcd(p, q) == 1 and 1 <= u < p):
        raise ValueError('Require p >= 2, 1 <= q,u < p, and gcd(p,q)=1')


@lru_cache(None)
def cyclotomic(p):
    return POLYNOMIALS.cyclotomic_polynomial(p)


def exact_rational(p, q, u):
    """All rational cases, decided by exact arithmetic, not recognition."""
    r = pow(q, -1, p)
    v = (u * r) % p
    squared = ((2-X**u-X**(p-u)) * (2-X**v-X**(p-v))) % cyclotomic(p)
    if squared.degree() == 0:
        c = QQ(squared[0])
        if c > 0 and c.is_square():
            return c.sqrt()
    return None


def torsion_ball(p, q, u, digits=100):
    check_domain(p, q, u)
    field = RealBallField(max(MINIMUM_BITS, numberdb.bits(digits, losing=WORKING_GUARD)))
    v = (u * pow(q, -1, p)) % p
    value = 4 * (field.pi() * field(QQ(u)/p)).sin() * (field.pi() * field(QQ(v)/p)).sin()
    assert value.is_finite() and value > 0
    assert abs(value) > field(value.rad()) * (ZZ(2)**numberdb.bits(digits, losing=32))
    return value


class LensTorsion(numberdb.Generator):
    table = 'T599'
    parameters = ('p', 'q', 'u')
    type = 'R'
    digits = 100
    rigour = 'proven'
    files = ('generate.py', 'checks.py', 'argument-selection.md', 'numerical-evidence.json',
             'skill-evidence.json', 'sources.md')

    def enumerate(self, p_max=P_MAX):
        for p in range(2, p_max + 1):
            for q in range(1, p):
                if gcd(p, q) != 1:
                    continue
                r = pow(q, -1, p)
                if q != min(q, p-q, r, p-r):
                    continue
                for u in range(1, p//2 + 1):
                    yield {'p': p, 'q': q, 'u': u}

    def value(self, params, digits):
        p, q, u = (int(params[k]) for k in self.parameters)
        check_domain(p, q, u)
        exact = exact_rational(p, q, u)
        return exact if exact is not None else torsion_ball(p, q, u, digits)


if __name__ == '__main__':
    client = None
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        key = sys.stdin.read().strip()
        if '=' in key and key.split('=', 1)[0].isupper():
            key = key.split('=', 1)[1].strip().strip('\"\'')
        client = numberdb.Client(api_key=key)
    generator = LensTorsion()
    if '--fill' in sys.argv:
        print(generator.publish(client=client, message='Compute complex-line lens-space torsion with certified balls and exact rational cases'))
    else:
        report = generator.verify(sample=None, client=client)
        print(report)
        sys.exit(0 if report.ok else 1)