generate.py

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

3070 bytes, as of the version from 2026-11-10 22:53 (current). Recorded here, not run.

"""T710: minimal polynomials of Bettin–Conrey cotangent sums.

Requires SageMath (tested with 10.9) and numberdb (tested with 0.1.10).
Install numberdb with sage -pip install numberdb.
  sage -python generate.py             # preview, no writes
  sage -python generate.py --verify    # compare all stored entries
  sage -python generate.py --publish   # explicitly write to the target
Configure your own API key and session attribution externally for writes.

For k >= 2 compute alpha_k=c_0(1/k) in Q(zeta_lcm(4,k)), using the
positive-angle embedding zeta_L=exp(2*pi*i/L). Clear denominators of its
monic Q-minimal polynomial, divide the coefficient gcd, and choose positive
leading coefficient. This does not rescale alpha_k. Default coverage is all
3 <= k <= 24; k=2 is the omitted zero. All arithmetic is exact, with no
floating input, decimal recognition or polynomial fitting.

Bettin and Conrey, Period functions and cotangent sums (2013), pp. 217,223:
https://doi.org/10.2140/ant.2013.7.215 .
The companion independent_check.py uses the finite Estermann double sum,
products over Galois embeddings, and 512-bit direct cotangent enclosures.
"""
import argparse
from functools import lru_cache
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.number_field.number_field import CyclotomicField

ZX = PolynomialRing(ZZ, 'x')

@lru_cache(maxsize=None)
def algebraic_sum(k):
    k = ZZ(k)
    if k < 2:
        raise ValueError('k must be an integer at least 2')
    L = k.lcm(4)
    K = CyclotomicField(L)
    z = K.gen()
    w, ii = z**(L//k), z**(L//4)
    return -sum((QQ(m)/k)*ii*(w**m+1)/(w**m-1)
                for m in range(1, int(k)))

@lru_cache(maxsize=None)
def polynomial(k):
    a = algebraic_sum(k)
    q = a.minpoly()
    p = ZX(q*q.denominator())
    p = ZX(p/p.content())
    if p.leading_coefficient() < 0:
        p = -p
    assert p(a) == 0 and p.is_irreducible()
    assert p.content() == 1
    assert all(type(c).__name__ == 'Integer' for c in p.list())
    return p

class BettinConreyMinimalPolynomials(numberdb.Generator):
    table = 'T710'
    parameters = ('k',)
    type = 'Z[]'
    digits = 100
    rigour = 'exact'

    def enumerate(self, kmax=24):
        for k in range(3, int(kmax)+1):
            yield {'k': k}

    def value(self, params, digits):
        return polynomial(ZZ(params['k']))

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    mode = parser.add_mutually_exclusive_group()
    mode.add_argument('--publish', action='store_true')
    mode.add_argument('--verify', action='store_true')
    parser.add_argument('--kmax', type=int, default=24)
    args = parser.parse_args()
    g = BettinConreyMinimalPolynomials()
    if args.publish:
        print(g.publish(kmax=args.kmax, removing=False))
    elif args.verify:
        print(g.verify(sample=None, kmax=args.kmax))
    else:
        print(g.preview(kmax=args.kmax))