hessian_generator.py

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

7043 bytes, as of the version from 2026-11-02 11:01 (current). Recorded here, not run.

"""Shared reproducible generators for Hessian periods and CM determinants.

Download this file, hessian_periods.py, hessian-catalogue.json and
entry-metadata.json beside the table's generate.py. The metadata file contains
labels and explanations only, never numerical answers. All values are
recomputed from the exact arguments in the catalogue.
"""

import argparse
from functools import lru_cache
import itertools
import json
from pathlib import Path

import numberdb.sage as numberdb
import mpmath as mp

from hessian_periods import evaluate, exact_argument, MINIMUM_AGREEMENT_MARGIN


ROOT = Path(__file__).resolve().parent


@lru_cache(maxsize=1)
def catalogue():
    return json.loads((ROOT / 'hessian-catalogue.json').read_text())


@lru_cache(maxsize=1)
def arguments():
    return {item['key']: item for item in catalogue()['arguments']}


@lru_cache(maxsize=512)
def scalar_result(key, digits):
    return evaluate(arguments()[key], digits)


def determinant_matrix(orbit, values):
    scaled = [mp.sqrt(coefficient) * value
              for coefficient, value in zip(orbit['coefficients'], values)]
    arrangement = ((0, 1, 2, 3), (1, 0, 3, 2), (2, 3, 0, 1), (3, 2, 1, 0))
    signs = catalogue()['sign_matrices'][str(orbit['variant'])]
    return mp.matrix([[signs[row][column] * scaled[arrangement[row][column]]
                       for column in range(4)] for row in range(4)])


def quartic_determinant(orbit, values):
    matrix = determinant_matrix(orbit, values)
    result = mp.det(matrix)
    absolute_terms = mp.fsum(mp.fprod(abs(matrix[row, permutation[row]]) for row in range(4))
                            for permutation in itertools.permutations(range(4)))
    if not mp.isfinite(result) or result == 0:
        raise ArithmeticError('Invalid quartic determinant')
    return result, absolute_terms / abs(result)


def determinant_result(kind, orbit, digits, scalar_results=None):
    results = scalar_results or {key: scalar_result(key, digits) for key in orbit['keys']}
    working_precisions = next(iter(results.values()))['working_precisions']
    measurements = []
    cancellations = []
    for position, working in enumerate(working_precisions):
        with mp.workdps(working):
            periods = []
            for key in orbit['keys']:
                result = results[key]
                assert result['working_precisions'] == working_precisions
                quantity = 'modified' if arguments()[key]['modified'] else 'ordinary'
                periods.append(mp.mpf(result['measurements'][position][quantity]))
            if kind == 'quadratic':
                terms = [coefficient * period**2
                         for coefficient, period in zip(orbit['coefficients'], periods)]
                value = mp.fsum(terms)
                if not mp.isfinite(value) or value == 0:
                    raise ArithmeticError('Invalid quadratic determinant')
                cancellation = mp.fsum(abs(term) for term in terms) / abs(value)
            else:
                value, cancellation = quartic_determinant(orbit, periods)
            measurements.append(mp.nstr(value, working - 8))
            cancellations.append(mp.nstr(cancellation, 16))
    with mp.workdps(working_precisions[-1] + 20):
        lower, higher = map(mp.mpf, measurements)
        discrepancy = abs((lower - higher) / higher)
        agreement = -mp.log10(discrepancy) if discrepancy else mp.inf
        if agreement < digits + MINIMUM_AGREEMENT_MARGIN:
            raise ArithmeticError('Insufficient determinant agreement: ' + str(agreement))
        return {
            'key': orbit['keys'][0], 'kind': kind, 'source_row': orbit['source_row'],
            'number': mp.nstr(higher, digits, strip_zeros=False),
            'working_precisions': working_precisions, 'measurements': measurements,
            'agreement_digits': str(agreement), 'cancellation_factors': cancellations,
        }


class HessianTable(numberdb.Generator):
    parameters = ('t',)
    type = 'R'
    digits = 100
    rigour = 'heuristic (agreement-checked)'
    kind = None
    files = ('generate.py', 'hessian_generator.py', 'hessian_periods.py',
             'hessian-catalogue.json', 'entry-metadata.json', 'argument-selection.md',
             'verification.json')

    def items(self):
        if self.kind in ('ordinary', 'modified'):
            return [item for item in catalogue()['arguments'] if item[self.kind]]
        return catalogue()[self.kind + '_orbits']

    def enumerate(self):
        for item in self.items():
            yield {'t': item['key'] if self.kind in ('ordinary', 'modified') else item['keys'][0]}

    def value(self, params, digits):
        key = str(params['t'])
        if key not in {entry['t'] for entry in self.enumerate()}:
            raise ValueError('The argument is outside this generator selection')
        if self.kind in ('ordinary', 'modified'):
            number = scalar_result(key, digits)['values'][self.kind]
        else:
            orbit = next(item for item in self.items() if item['keys'][0] == key)
            number = determinant_result(self.kind, orbit, digits)['number']
        metadata = json.loads((ROOT / 'entry-metadata.json').read_text()).get(key, {})
        if set(metadata) - {'comment', 'param-latex'}:
            raise ValueError('Unexpected value-bearing entry metadata')
        return dict(metadata, number=number)

    def environment(self):
        return dict(super().environment(), mpmath=mp.__version__)


class SnapshotClient:
    def __init__(self, table_id, document):
        self.table_id = table_id
        self.document = document

    def request(self, path, parameters):
        if path != 'api/table' or parameters != {'id': self.table_id}:
            raise RuntimeError('Only the specified offline table may be read')
        return self.document


def run(generator):
    parser = argparse.ArgumentParser(description=generator.__doc__)
    parser.add_argument('--snapshot', type=Path, help='verify an offline JSON table export')
    parser.add_argument('--digits', type=int, default=100)
    parser.add_argument('--publish', action='store_true', help='submit entries for review; requires an API key')
    options = parser.parse_args()
    generator.digits = options.digits
    if options.publish:
        if options.snapshot:
            parser.error('--publish cannot be combined with --snapshot')
        print(generator.publish(message='Extend the source-backed Hessian reference selection'))
        return 0
    client = None
    if options.snapshot:
        document = json.loads(options.snapshot.read_text())
        expected = [parameters['t'] for parameters in generator.enumerate()]
        if expected != list(document['Numbers']):
            raise ArithmeticError('Snapshot and default enumeration differ')
        client = SnapshotClient(generator.table, document)
    report = generator.verify(sample=None, digits=options.digits, client=client)
    print(report)
    return 0 if report.ok and not report.coarser else 1