generate.py

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

10416 bytes, as of the version from 2026-09-21 05:18 (current). Recorded here, not run.

"""Arithmetic factors $a_k$ in the moments of the Riemann zeta function -- numberdb.org/T377

This generator fills T377 with the arithmetic Euler-product factors in the
Keating-Snaith and CFKRS leading constants for zeta moments.

Run it with SageMath:

    $ sage -pip install numberdb          # once
    $ sage -python generate.py            # check the table against this code
    $ sage -python generate.py --publish  # fill the draft, with NUMBERDB_API_KEY set

In this checkout, use the repository wrapper:

    $ agents/sage.sh generators/arithmetic-factors-zeta-moments/generate.py
    $ cat "$NUMBERDB_KEY_FILE" | NUMBERDB_KEY_FROM_STDIN=1 NUMBERDB_PUBLISH=1 agents/sage.sh generators/arithmetic-factors-zeta-moments/generate.py
"""

import os
import sys
from fractions import Fraction

import numberdb.sage as numberdb
from sage.libs.pari import pari
from sage.rings.complex_arb import ComplexBallField
from sage.rings.rational_field import QQ
from sage.rings.real_mpfr import RealField


TABLE = os.environ.get("NUMBERDB_TABLE", "T377")
DIGITS = 100
AGREEMENT_DIGITS = (140, 160)
SETTINGS = {
    140: {"small": 1000, "large": 100000, "order": 160,
          "residual_order": 35, "mobius_terms": 280},
    160: {"small": 2000, "large": 200000, "order": 220,
          "residual_order": 45, "mobius_terms": 360},
}
K_VALUES = tuple(Fraction(n, 2) for n in range(1, 13)) + tuple(
    Fraction(n) for n in range(7, 13))

_SUMS = {}


def _key_from_stdin():
    if os.environ.get("NUMBERDB_KEY_FROM_STDIN") != "1":
        return
    token = sys.stdin.read().strip()
    if "=" in token and token.split("=", 1)[0].isupper():
        token = token.split("=", 1)[1].strip().strip("'\"")
    if token:
        os.environ["NUMBERDB_API_KEY"] = token


def _fraction(text):
    return Fraction(str(text))


def _prime_sieve(bound):
    sieve = bytearray(b"\x01") * (bound + 1)
    sieve[:2] = b"\x00\x00"
    p = 2
    while p * p <= bound:
        if sieve[p]:
            start = p * p
            sieve[start:bound + 1:p] = b"\x00" * (((bound - start) // p) + 1)
        p += 1
    return [i for i in range(2, bound + 1) if sieve[i]]


def _mobius(n):
    x = n
    p = 2
    factors = 0
    while p * p <= x:
        if x % p == 0:
            x //= p
            factors += 1
            if x % p == 0:
                return 0
        p += 1 if p == 2 else 2
    if x > 1:
        factors += 1
    return -1 if factors % 2 else 1


def _log_factor_coefficients(k, order):
    """Exact coefficients of log((1 - x)^(k^2) * 2F1(k,k;1;x))."""
    k = Fraction(k)
    h = [Fraction(0) for _ in range(order + 1)]
    h[0] = Fraction(1)
    coefficient = Fraction(1)
    for m in range(1, order + 1):
        coefficient *= (k + (m - 1)) ** 2
        coefficient /= m ** 2
        h[m] = coefficient

    log_h = [Fraction(0) for _ in range(order + 1)]
    for n in range(1, order + 1):
        lower = sum(
            (j * log_h[j] * h[n - j] for j in range(1, n)),
            Fraction(0),
        )
        log_h[n] = h[n] - lower / n

    k_squared = k * k
    return [Fraction(0)] + [
        log_h[n] - k_squared / n for n in range(1, order + 1)
    ]


def _prime_zeta(s, field, terms):
    total = field(0)
    for n in range(1, terms + 1):
        mu = _mobius(n)
        if mu:
            total += field(mu) * field(n * s).zeta().log() / field(n)
    return total


def _prime_power_sums(field, small, large, order):
    key = (field.prec(), small, large, order)
    if key in _SUMS:
        return _SUMS[key]

    primes = _prime_sieve(large)
    small_primes = [p for p in primes if p <= small]
    medium = [field(0) for _ in range(order + 1)]
    prefix = [field(0) for _ in range(order + 1)]
    for p in primes:
        inv = field(1) / field(p)
        power = field(1)
        for j in range(1, order + 1):
            power *= inv
            if p > small:
                medium[j] += power
            prefix[j] += power

    _SUMS[key] = (small_primes, medium, prefix)
    return _SUMS[key]


def _local_factor(k, p, complex_field, real_field):
    x = complex_field(QQ(1) / p)
    value = ((1 - x) ** complex_field(k * k)
             * x.hypergeometric([complex_field(k), complex_field(k)],
                                [complex_field(1)]))
    real = value.real()
    if real.lower() <= 0:
        raise ArithmeticError(
            "non-positive local factor for k=%s, p=%s: %s" % (k, p, real))
    return real_field(real.mid())


def _log_arithmetic_factor(k, working_digits):
    settings = SETTINGS[working_digits]
    real_field = RealField(numberdb.bits(working_digits, losing=192))
    complex_field = ComplexBallField(numberdb.bits(working_digits, losing=192))
    qk = QQ(k.numerator) / QQ(k.denominator)

    small_primes, medium, prefix = _prime_power_sums(
        real_field,
        settings["small"],
        settings["large"],
        settings["order"],
    )
    total = real_field(0)
    for p in small_primes:
        total += _local_factor(qk, p, complex_field, real_field).log()

    coefficients = _log_factor_coefficients(k, settings["order"])
    for j in range(2, settings["order"] + 1):
        coefficient = coefficients[j]
        if not coefficient:
            continue
        tail = medium[j]
        if j <= settings["residual_order"]:
            tail += (_prime_zeta(j, real_field, settings["mobius_terms"])
                     - prefix[j])
        total += (real_field(coefficient.numerator)
                  / real_field(coefficient.denominator)) * tail
    return total


def _decimal(value, digits):
    return format(value, ".%de" % (digits - 1))


def _arithmetic_factor_decimal(k, working_digits):
    return _decimal(_log_arithmetic_factor(k, working_digits).exp(),
                    working_digits)


def _integer_factor_pari(k, digits=120):
    pari("default(realprecision, %d)" % digits)
    expression = (
        "(1 - 1/p)^((%d - 1)^2) * "
        "sum(j = 0, %d - 1, binomial(%d - 1, j)^2/p^j)"
        % (k, k, k)
    )
    return str(pari("prodeulerrat(%s, 1, 2)" % expression))


def _value_comment(k):
    if k == Fraction(1):
        return r"Every local factor is $1$, so $a_1=1$."
    if k == Fraction(2):
        return r"Here the finite local factor is $1-p^{-2}$, so $a_2=1/\zeta(2)=6/\pi^2$."
    return None


def _assert_relative_close(label, left, right, field, tolerance):
    left = field(left)
    right = field(right)
    scale = max(field(1), abs(right))
    if abs(left - right) / scale > tolerance:
        raise ArithmeticError(
            "%s control failed: %s versus %s" % (label, left, right))


def run_private_checks():
    coefficients = _log_factor_coefficients(Fraction(1), 20)
    if any(coefficients[1:]):
        raise ArithmeticError("the k=1 logarithmic local factor is not zero")

    field = RealField(numberdb.bits(120, losing=128))
    a2 = _arithmetic_factor_decimal(Fraction(2), 160)
    _assert_relative_close("a_2=6/pi^2", a2,
                           field(6) / (field.pi() ** 2),
                           field, field(10) ** -100)

    for k in range(2, 13):
        computed = _arithmetic_factor_decimal(Fraction(k), 160)
        controlled = _integer_factor_pari(k)
        _assert_relative_close("PARI integer k=%d" % k, computed, controlled,
                               field, field(10) ** -90)


class ZetaMomentArithmeticFactors(numberdb.Generator):

    table = TABLE
    parameters = ("k",)
    type = "R"
    digits = DIGITS
    rigour = "heuristic (agreement-checked)"

    def enumerate(self):
        for k in K_VALUES:
            yield {"k": str(k)}

    def value(self, params, digits):
        k = _fraction(params["k"])
        if k == Fraction(1):
            return {"number": 1, "comment": _value_comment(k)}

        number = numberdb.agreeing(
            lambda working: _arithmetic_factor_decimal(k, working),
            at=AGREEMENT_DIGITS,
        )
        entry = {"number": number}
        comment = _value_comment(k)
        if comment:
            entry["comment"] = comment
        if k == Fraction(2):
            entry["equals"] = (
                r"HREF{Named_rational_Euler_products_over_primes#squarefree-density}"
                r"[$6/\pi^2$]"
            )
        return entry


def fill_draft_once(generator, message):
    """Fill a fresh draft without the empty upsert probe."""
    from numberdb._generate import (
        _check_precision,
        _check_rigour,
        _producer,
        _run_name,
        _source_files,
    )
    from numberdb._write import Entries, attach, submit_entries, to_text

    table = generator.table
    run = _run_name(generator)
    entries = Entries(*generator.parameters)

    for params in generator.enumerate():
        params = dict(params)
        wanted = generator.digits_for(params)
        entry = generator._entry(params, wanted)
        value = entry["number"]
        identity = ",".join(str(params[name]) for name in generator.parameters)
        _check_rigour(generator, table, identity, value)

        written = to_text(value, wanted, generator.format)
        _check_precision(table, identity, written, wanted, lowering=False)

        record = dict(entry)
        record.pop("digits", None)
        entries.add(**params, **record, digits=wanted)

    answer = submit_entries(
        table,
        entries,
        message=message,
        produced_by=_producer(
            generator,
            assisted_by=os.environ.get("NUMBERDB_ASSISTED_BY", "codex-cli"),
        ),
        upsert=False,
        run=run,
        rigour=generator.rigour,
    )

    files = _source_files(generator)
    stored = []
    for name, body in sorted(files.items()):
        attach(table, name, body, run=run, message=message,
               rigour=generator.rigour)
        stored.append(name)

    return {
        "tid": answer.get("tid", table),
        "revision": answer.get("revision"),
        "entries": len(entries),
        "files": stored,
    }


if __name__ == "__main__":
    _key_from_stdin()
    run_private_checks()
    generator = ZetaMomentArithmeticFactors()
    if os.environ.get("NUMBERDB_PUBLISH") == "1" or "--publish" in sys.argv:
        print(fill_draft_once(
            generator,
            message="fill zeta moment arithmetic factors"))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)