"""Atiyah–Patodi–Singer rho invariants of lens spaces -- numberdb.org/T623.

Run with SageMath and the numberdb package:
    sage -pip install numberdb
    sage -python generate.py              # verify every stored entry
    sage -python generate.py --fill       # fill the existing draft with a key
    sage -python checks.py                # independent local checks

For a private draft, set NUMBERDB_API_KEY or pipe its key on stdin with
NUMBERDB_KEY_FROM_STDIN=1. --fill writes entries and attachments; it does not
publish or offer the draft. The default selection is justified in
argument-selection.md. All values are exact rationals, with no rounding.

Toffoli (2022), Proposition 2.10, labels the character by zeta_p**(k*q).
Here u is the exponent on the quotient generator, so k = u*q**(-1) mod p.
The sawtooth is zero at integers. No realification or kernel correction is
made. Independent checks use weighted lattice points and 512-bit Arb balls.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from math import gcd
import os
import sys


def rho(p, q, u):
    """Exact complex rank-one APS rho, on the full character domain."""
    p, q, u = int(p), int(q), int(u)
    if p < 2 or not 1 <= q < p or gcd(p, q) != 1 or not 0 <= u < p:
        raise ValueError('Require p>=2, 1<=q<p, gcd(p,q)=1, 0<=u<p')
    k = (u * pow(q, -1, p)) % p

    def saw(n):
        residue = n % p
        return QQ(0) if residue == 0 else QQ(residue)/QQ(p) - QQ(1)/QQ(2)

    return -4*sum((saw(q*j) for j in range(1, k)), QQ(0)) - 2*saw(q*k)


class LensSpaceRho(numberdb.Generator):
    table = 'T623'
    parameters = ('p', 'q', 'u')
    type = 'Q'
    rigour = 'exact'
    digits = 100
    files = ('generate.py', 'checks.py', 'check.py', 'argument-selection.md',
             'numerical-evidence.json', 'skill-manifest.json')

    def enumerate(self, max_p=20):
        for p in range(2, int(max_p)+1):
            for q in range(1, p):
                if gcd(p, q) == 1 and q <= pow(q, -1, p):
                    for u in range(1, p//2+1):
                        yield {'p': p, 'q': q, 'u': u}

    def value(self, params, digits):
        return rho(params['p'], params['q'], params['u'])


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        os.environ['NUMBERDB_API_KEY'] = sys.stdin.read().strip()
    generator = LensSpaceRho()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill exact lens-space rho invariants after independent checks.'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)
