"""Kontsevich–Zorich sum of Lyapunov exponents -- numberdb.org/T562.

Run with SageMath (install numberdb once with sage -pip install numberdb):
    sage -python generate.py                 # verify every stored entry
    sage -python generate.py --fill          # fill the existing draft only
    sage -python checks.py                  # exact independent checks

--fill requires NUMBERDB_API_KEY. It writes entries and attached files; it
never offers, reviews, or makes a draft public. NUMBERDB_KEY_FROM_STDIN=1
reads a token from stdin; NUMBERDB_PUBLISH=1 is the runner's --fill alias.

Exact data: Eskin–Kontsevich–Zorich, Publ. Math. IHES 120 (2014),
Corollary 2 and Appendix A, p. 326, exact sum column (not experimental
individual exponents). doi:10.1007/s10240-013-0060-3, arXiv:1112.5872.
All 25 classified components in genera 2..4. The table domain is unbounded;
this transcription refuses unsupported nonhyperelliptic cases. Hyperelliptic
values beyond this range can be computed by hyperelliptic_sum().

H(2,2,2)^even = 737/333 is confirmed from EMZ Appendix B.2 configuration
constants, resolving the conflicting 166/75 in Chen–Möller Section 6.9.
See source-notes.md and checks.py. No floating point or rounding is used.
"""
import os
import sys
from fractions import Fraction
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ

# The source's ascending zero profiles are reordered, without changing labels.
ROWS = (
    (2, (2,), 'hyp', '4/3'),
    (2, (1, 1), 'hyp', '3/2'),
    (3, (4,), 'hyp', '9/5'),
    (3, (4,), 'odd', '8/5'),
    (3, (3, 1), 'connected', '7/4'),
    (3, (2, 2), 'hyp', '2'),
    (3, (2, 2), 'odd', '5/3'),
    (3, (2, 1, 1), 'connected', '11/6'),
    (3, (1, 1, 1, 1), 'connected', '53/28'),
    (4, (6,), 'hyp', '16/7'),
    (4, (6,), 'even', '2'),
    (4, (6,), 'odd', '13/7'),
    (4, (5, 1), 'connected', '2'),
    (4, (4, 2), 'even', '32/15'),
    (4, (4, 2), 'odd', '29/15'),
    (4, (4, 1, 1), 'connected', '1137/550'),
    (4, (3, 3), 'hyp', '5/2'),
    (4, (3, 3), 'nonhyp', '2'),
    (4, (3, 2, 1), 'connected', '25/12'),
    (4, (3, 1, 1, 1), 'connected', '66/31'),
    (4, (2, 2, 2), 'even', '737/333'),
    (4, (2, 2, 2), 'odd', '2'),
    (4, (2, 2, 1, 1), 'connected', '5045/2358'),
    (4, (2, 1, 1, 1, 1), 'connected', '131/60'),
    (4, (1, 1, 1, 1, 1, 1), 'connected', '839/377'),
)
DATA = {(g, mu, c): QQ(Fraction(v).numerator) / QQ(Fraction(v).denominator)
        for g, mu, c, v in ROWS}


def hyperelliptic_sum(g, zeros=1):
    """EKZ Corollary 1: one zero of order 2g-2, or two zeros of order g-1."""
    g = ZZ(g)
    if g < 2 or zeros not in (1, 2):
        raise ValueError('Use an integer genus >= 2 and zeros=1 or 2.')
    return QQ(g*g) / QQ(2*g-1) if zeros == 1 else QQ(g+1) / QQ(2)


def lyapunov_sum(g, mu, component):
    """Return the exact component sum; mu must be a non-increasing profile."""
    g = ZZ(g)
    mu = tuple(ZZ(m) for m in mu)
    if g < 2 or not mu or sum(mu) != 2*g-2 or min(mu) < 1:
        raise ValueError('The positive zero orders must sum to 2g-2, with g >= 2.')
    if tuple(sorted(mu, reverse=True)) != mu:
        raise ValueError('Use non-increasing zero orders.')
    try:
        return DATA[(g, mu, component)]
    except KeyError:
        raise ValueError('No transcribed value for this component; do not infer one.') from None


class LyapunovSums(numberdb.Generator):
    table = 'T562'
    parameters = ('g', 'mu', 'component')
    type = 'Q'
    rigour = 'exact'
    digits = 100  # Exact rationals have no finite-precision claim.
    files = ('generate.py', 'checks.py', 'check.py', 'argument-selection.md',
             'source-notes.md', 'source-column.json', 'T561.json', 'sources.json',
             'skill-evidence.json', 'numerical-evidence.json')

    def enumerate(self):
        for g, mu, component, _ in ROWS:
            yield {'g': g, 'mu': ','.join(map(str, mu)), 'component': component}

    def value(self, params, digits=100):
        g = ZZ(params['g'])
        mu = tuple(ZZ(x) for x in params['mu'].split(','))
        component = params['component']
        value = lyapunov_sum(g, mu, component)
        if (g, mu, component) == (4, (2, 2, 2), 'even'):
            return {'number': value, 'comment': r'The value $737/333$ in CITE{EKZ}, Appendix A, follows from the cylinder configurations in CITE{EMZ}, Appendix B.2; it differs from $166/75$ printed in CITE{CM}, Section 6.9.'}
        return value


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        numberdb.configure(api_key=sys.stdin.read().strip())
    generator = LyapunovSums()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Store 25 exact component sums with source reconciliation and independent checks'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)
