generate.py

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

4939 bytes, as of the version from 2026-11-09 11:44. Recorded here, not run.

"""Genus-zero Gopakumar–Vafa invariants of projective complete intersections -- numberdb.org/T689.

Run with SageMath:
    sage -pip install numberdb               # once
    sage -python generate.py                 # verify all stored entries
    sage -python generate.py --fill          # fill this draft, with NUMBERDB_API_KEY
    sage -python checks.py                   # independent checks; no network

All arithmetic is exact in QQ; final invariants are returned in ZZ. The
standalone generator uses the geometric Yukawa coupling, formal inverse of
q=x exp(G/F), and triangular Lambert-series extraction. No live table is an
input. Default coverage is all five ordinary-projective Calabi–Yau threefold
complete-intersection types and degrees 1 through 20. See argument-selection.md.
The extra four series orders are checked against eight extra orders in checks.py.
Filling entries neither offers nor publishes a draft.
"""
import os
import sys
from functools import lru_cache
from math import factorial, prod

import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.integer_ring import ZZ

TARGETS = ((5,), (2, 4), (3, 3), (2, 2, 3), (2, 2, 2, 2))
MAX_DEGREE = 20
EXTRA_ORDERS = 4


def multiply(a, b):
    n = len(a) - 1
    return [sum((a[j] * b[i-j] for j in range(i+1)), QQ(0))
            for i in range(n+1)]


def reciprocal(a):
    n = len(a) - 1
    b = [QQ(1) / a[0]]
    for i in range(1, n+1):
        b.append(-sum((a[j] * b[i-j] for j in range(1, i+1)), QQ(0)) / a[0])
    return b


def exponential(a):
    assert a[0] == 0
    b = [QQ(1)]
    for i in range(1, len(a)):
        b.append(sum((QQ(j) * a[j] * b[i-j] for j in range(1, i+1)), QQ(0)) / QQ(i))
    return b


def compose(a, b):
    assert b[0] == 0
    out = [QQ(0)] * len(a)
    for coefficient in reversed(a):
        out = multiply(out, b)
        out[0] += coefficient
    return out


def periods(target, order):
    r = len(target)
    harmonics = [QQ(0)]
    for j in range(1, max(target)*order+1):
        harmonics.append(harmonics[-1] + QQ(1)/QQ(j))
    f, g = [], []
    for m in range(order+1):
        a = QQ(prod(factorial(l*m) for l in target)) / QQ(factorial(m)**(r+4))
        f.append(a)
        g.append(a * (sum(l*harmonics[l*m] for l in target) - (r+4)*harmonics[m]))
    return f, g


@lru_cache(maxsize=None)
def coefficients(target, order):
    """Return geometric Yukawa, GW and GV coefficients through order inclusive."""
    assert target in TARGETS
    f, g = periods(target, order)
    h = multiply(g, reciprocal(f))
    jacobian = [QQ(1)] + [QQ(i)*h[i] for i in range(1, order+1)]
    inv_jacobian = reciprocal(jacobian)
    inv_f = reciprocal(f)
    c = prod(l**l for l in target)
    kappa = prod(target)
    coupling = [QQ(kappa) * QQ(c)**i for i in range(order+1)]
    for factor in (inv_f, inv_f, inv_jacobian, inv_jacobian, inv_jacobian):
        coupling = multiply(coupling, factor)
    # Fixed-point inversion x(q)=q exp(-h(x(q))). Each iteration fixes
    # at least one additional coefficient, since h(0)=0.
    x = [QQ(0), QQ(1)] + [QQ(0)]*(order-1)
    for _ in range(order):
        e = exponential([-v for v in compose(h, x)])
        x = [QQ(0)] + e[:-1]
    q = [QQ(0)] + exponential(h)[:-1]
    assert compose(q, x) == [QQ(0), QQ(1)] + [QQ(0)]*(order-1)
    k = compose(coupling, x)
    gw = [QQ(0)] + [k[d]/QQ(d**3) for d in range(1, order+1)]
    gv = [ZZ(0)]
    for d in range(1, order+1):
        proper = sum((QQ(e**3)*gv[e] for e in range(1, d) if d % e == 0), QQ(0))
        value = (k[d] - proper) / QQ(d**3)
        assert value.denominator() == 1, (target, d, value)
        gv.append(ZZ(value))
    return k, gw, gv


class GenusZeroGV(numberdb.Generator):
    table = 'T689'
    parameters = ('multidegree', 'd')
    type = 'Z'
    rigour = 'exact'
    digits = 100

    def enumerate(self, maximum_degree=MAX_DEGREE):
        for target in TARGETS:
            for d in range(1, maximum_degree+1):
                yield {'multidegree': ','.join(map(str, target)), 'd': str(d)}

    def value(self, params, digits):
        target = tuple(int(v) for v in params['multidegree'].split(','))
        d = int(params['d'])
        assert d >= 1
        return coefficients(target, max(MAX_DEGREE, d)+EXTRA_ORDERS)[2][d]


if __name__ == '__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
        token = sys.stdin.read().strip()
        if '=' in token and token.split('=', 1)[0].replace('_', '').isalnum():
            token = token.split('=', 1)[1].strip().strip('\"\'')
        numberdb.configure(api_key=token)
    generator = GenusZeroGV()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
        print(generator.publish(message='Fill exact genus-zero GV invariants after independent checks'))
    else:
        report = generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)