"""Multiple zeta values of length 4 -- numberdb.org/T465.

Run with SageMath (or passagemath with its modular multiple-zeta module):

    sage -pip install numberdb
    sage -python generate.py               # verify every stored entry
    sage -python checks.py                 # independent checks, no API writes
    sage -python generate.py --fill        # fill with NUMBERDB_API_KEY set

The four parameters use descending summation indices. Sage's multiple-zeta
indices are reversed. All admissible length-four indices of weight <= 12 are
included. The two PARI caches are explicitly recomputed at 150 and 200 decimal
working digits (plus 16 guard bits), rather than rounding one shared cache to
two precisions. The measured agreement and independent evidence are attached
in checks.py and build-evidence.json. Values are written to 100 significant
digits under the heuristic (agreement-checked) rigour level.

--fill writes entries to the existing table; it does not publish the draft.
"""
from functools import lru_cache
import os
import sys
import numberdb.sage as numberdb
from sage.modular.multiple_zeta import MultizetaValues

MAX_WEIGHT = 12
WORKING_DIGITS = (150, 200)
GUARD_BITS = 16
PARAMETERS = ('s1', 's2', 's3', 's4')


def indices(max_weight=MAX_WEIGHT):
    """Natural increasing parameter order, with no holes at any weight."""
    for a in range(2, max_weight - 2):
        for b in range(1, max_weight - a - 1):
            for c in range(1, max_weight - a - b):
                for d in range(1, max_weight - a - b - c + 1):
                    yield (a, b, c, d)


@lru_cache(maxsize=2)
def working_values(working):
    """A fresh PARI zetamultall computation at this working precision."""
    precision = numberdb.bits(working, losing=GUARD_BITS)
    cache = MultizetaValues()
    cache.reset(max_weight=MAX_WEIGHT, prec=precision)
    return {index: cache(tuple(reversed(index)), prec=precision)
            for index in indices()}


class LengthFourMZV(numberdb.Generator):
    table = 'T465'
    parameters = PARAMETERS
    type = 'R'
    digits = 100
    rigour = 'heuristic (agreement-checked)'
    files = ('generate.py', 'checks.py', 'check.py', 'build-evidence.json')

    def enumerate(self):
        for index in indices():
            yield dict(zip(self.parameters, index))

    def value(self, params, digits):
        if digits > 100:
            raise ValueError('This generator is validated for 100 stored digits.')
        index = tuple(int(params[name]) for name in self.parameters)
        value = numberdb.agreeing(
            lambda working: str(working_values(working)[index]),
            at=WORKING_DIGITS)
        # Equalities are theorems, not numerical duplicate guesses.
        annotations = {}
        if index == (2, 1, 1, 1):
            annotations = {
                'comment': r'Duality identifies this value with HREF{Values_of_the_Riemann_zeta_function_at_rational_numbers#5}[$\zeta(5)$].',
                'equals': 'HREF{Values_of_the_Riemann_zeta_function_at_rational_numbers#5}'}
        elif index == (2, 2, 2, 2):
            annotations = {'comment': r'$\zeta(2,2,2,2)=\pi^8/9!$.'}
        return {'number': value, **annotations}


if __name__ == '__main__':
    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('\'"')
        numberdb.configure(api_key=key)
    generator = LengthFourMZV()
    if '--fill' in sys.argv:
        print(generator.publish(message='Fill 330 independently checked length-four multiple zeta values'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)
