generate.py

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

7183 bytes, as of the version from 2026-10-24 20:50 (current). Recorded here, not run.

"""Feynman periods of primitive log-divergent non-phi4 graphs -- numberdb.org/T466.

Run with SageMath after downloading the attached files into one directory:
    sage -pip install numberdb
    sage -python generate.py              # verify every stored entry
    sage -python checks.py                # independent controls and graph checks

To fill this draft (this does not publish the table):
    NUMBERDB_API_KEY=... sage -python generate.py --fill

The pinned Panzer--Schnetz v2 census is parsed without evaluating Maple code.
Only its 511 known periods are included, with every label retained. Rational
coefficients are Sage rationals; PARI zetamult uses the descending-index
convention directly (Sage Multizeta reverses it). Alternating terms use
polylogmult(s, [-1,1,...,1]). Values are recomputed at 150 and 200 decimal
working digits. Agreement is heuristic, never a certified graph integral.
The source's 100-digit values are independent numerical comparison targets.
"""
import numberdb.sage as numberdb
from sage.libs.pari import pari
from sage.rings.real_mpfr import RealField
from sage.rings.rational_field import QQ
import ast
import hashlib
import json
import os
import re
import sys
from functools import lru_cache
from pathlib import Path
from urllib.request import urlopen
from parse_census import parse, canonical

SOURCE_URL='https://arxiv.org/src/1603.04289v2/anc/PeriodsNonPhi4'
SOURCE_SHA256='f735c62370e0e1561e0cdffc28d86226d6d0fb0be138729abd44f74191d1966a'
WORKING_DIGITS=(150,200)
ROOT=Path(__file__).resolve().parent

@lru_cache(None)
def records():
    local=ROOT/'PeriodsNonPhi4'
    raw=local.read_bytes() if local.exists() else urlopen(SOURCE_URL,timeout=60).read()
    assert hashlib.sha256(raw).hexdigest()==SOURCE_SHA256,'Source checksum changed'
    return parse(raw.decode('utf-8'))

@lru_cache(None)
def field(digits):
    return RealField(numberdb.bits(digits,losing=32))

@lru_cache(None)
def zeta(s, alternating, digits):
    R=field(digits)
    assert all(isinstance(n,int) and n>0 for n in s)
    if alternating:
        return R(pari.polylogmult(list(s),[-1]+[1]*(len(s)-1),precision=R.precision()))
    assert s[0]>=2
    return R(pari.zetamult(list(s),precision=R.precision()))

@lru_cache(None)
def expression_tree(expression):
    return ast.parse(expression.replace('^','**'), mode='eval').body

def evaluate_node(node,digits):
    if isinstance(node,ast.Constant) and type(node.value) is int:
        return QQ(node.value)
    if isinstance(node,ast.Name) and node.id=='Pi':
        return field(digits).pi()
    if isinstance(node,ast.UnaryOp) and isinstance(node.op,(ast.UAdd,ast.USub)):
        a=evaluate_node(node.operand,digits)
        return -a if isinstance(node.op,ast.USub) else a
    if isinstance(node,ast.BinOp):
        a,b=evaluate_node(node.left,digits),evaluate_node(node.right,digits)
        if isinstance(node.op,ast.Add): return a+b
        if isinstance(node.op,ast.Sub): return a-b
        if isinstance(node.op,ast.Mult): return a*b
        if isinstance(node.op,ast.Div): return a/b
        if isinstance(node.op,ast.Pow):
            assert b in QQ and b.denominator()==1
            return a**int(b)
    if isinstance(node,ast.Subscript) and isinstance(node.value,ast.Name) and node.value.id=='zeta':
        args=ast.literal_eval(node.slice)
        if not isinstance(args,tuple): args=(args,)
        alternating=isinstance(args[0],list)
        if alternating:
            assert args[0][0]==-1 and len(args[0])==2
            args=(args[0][1],)+args[1:]
        return zeta(tuple(args),alternating,digits)
    raise ValueError('Unexpected expression syntax: '+ast.dump(node))

@lru_cache(None)
def compute(expression,digits):
    value=field(digits)(evaluate_node(expression_tree(expression),digits))
    assert not value.is_NaN() and not value.is_infinity() and value>0
    return value

@lru_cache(None)
def comparison(expression):
    a,b=(compute(expression,d) for d in WORKING_DIGITS)
    R=field(200)
    relative=abs(R(a)-b)/abs(b)
    agreed=999 if relative==0 else float(-relative.log10())
    assert agreed>125,('insufficient agreement',agreed,expression)
    return agreed

@lru_cache(None)
def aliases():
    seen={}; out={}
    for key,row in records().items():
        ex=row['expression']
        if ex=='FAIL': continue
        ex=canonical(ex)
        if ex in seen: out[key]=seen[ex]
        else: seen[ex]=key
    return out

@lru_cache(None)
def wheel_labels():
    out={}
    for key,row in records().items():
        if row['expression']=='FAIL': continue
        infinity=max(v for e in row['edges'] for v in e['vertices'])
        es=[e['vertices'] for e in row['edges'] if infinity not in e['vertices']]
        degree={v:sum(v in e for e in es) for v in range(infinity)}
        n=row['loops']
        hubs=[v for v,d in degree.items() if d==n]
        if len(hubs)!=1 or any(d!=3 for v,d in degree.items() if v!=hubs[0]): continue
        hub=hubs[0]; rim=[e for e in es if hub not in e]
        visited={next(v for v in degree if v!=hub)}
        while True:
            new=visited|{v for e in rim if any(w in visited for w in e) for v in e}
            if new==visited: break
            visited=new
        if len(visited)==n: out[key]=n
    return out

class NonPhi4Periods(numberdb.Generator):
    table='T466'
    parameters=('loops','index')
    type='R'
    digits=100
    rigour='heuristic (agreement-checked)'
    files=('generate.py','parse_census.py','checks.py','check.py','PeriodsNonPhi4','cross-equalities.json','cross-equality-proofs.json','cross-source-values.json')

    def enumerate(self):
        for row in records().values():
            if row['expression']!='FAIL':
                yield {'loops':row['loops'],'index':row['index']}

    def value(self,params,digits):
        key=f"{params['loops']},{params['index']}"
        row=records()[key]; ex=row['expression']
        comparison(ex)
        value=numberdb.agreeing(lambda d:compute(ex,d),at=WORKING_DIGITS)
        result={'number':value}
        if key in aliases(): result['equals']=f'HREF{{T466#{aliases()[key]}}}'
        # Cross-table equalities are supported by exact source expressions.
        cross_path=ROOT/'cross-equalities.json'
        if cross_path.exists():
            cross=json.loads(cross_path.read_text())
            if key in cross: result['equals']=cross[key]
        if key in wheel_labels():
            n=wheel_labels()[key]
            result['comment']=f'The wheel with {n} spokes has period $\\binom{{{2*n-2}}}{{{n-1}}}\\zeta({2*n-3})$.'
        if key=='8,39':
            result['comment']=r'This period contains alternating multiple polylogarithms CITE{PS}, equation (5.6).'
        return result

if __name__=='__main__':
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN'):
        numberdb.configure(api_key=sys.stdin.read().strip())
    generator=NonPhi4Periods()
    if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH')=='1':
        print(generator.publish(message='Recompute 511 non-phi4 census periods at two precisions'))
    else:
        report=generator.verify(sample=None)
        print(report)
        sys.exit(0 if report.ok else 1)