back to table · edit · history · where entries came from · files · download
5446 bytes, as of the version from 2026-10-16 09:47 (current). Recorded here, not run.
"""Weil-Petersson volume polynomials -- numberdb.org/T460.
Run with SageMath (or numberdb plus passagemath):
sage -pip install numberdb
sage -python generate.py # verify the stored draft/table
sage -python generate.py --fill # fill this draft with your key
A draft requires NUMBERDB_API_KEY even to verify. --fill sends entries but
never offers, reviews, or publishes a table. The campaign wrapper may provide
NUMBERDB_KEY_FROM_STDIN=1 and NUMBERDB_PUBLISH=1 instead of command arguments.
The polynomial variables are L1,...,Ln,p, with p representing pi formally.
All coefficients are exact. Pure psi intersections use the DVV recurrence;
Lemma 2.2 of Liu-Xu (arXiv:0708.0565) converts kappa_1 powers to them.
Mirzakhani's coefficient formula then gives the volume, with the orbifold
normalization V11=(L1^2+4*p^2)/48. See checks.py for an independent computation
by the geometric Mirzakhani recursion and checks against published values.
"""
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from collections import Counter
from fractions import Fraction as F
from functools import lru_cache
from math import factorial, prod
import os
import sys
MAX_COMPLEXITY = 3
MAX_BOUNDARIES = 5
def df(n):
return prod(range(n, 0, -2))
def psi(g, d):
return _psi(int(g), tuple(sorted(d)))
@lru_cache(None)
def _psi(g, d):
n = len(d)
if g < 0 or 2*g-2+n <= 0 or any(x < 0 for x in d):
return F(0)
if sum(d) != 3*g-3+n:
return F(0)
if g == 0:
return F(factorial(n-3), prod(factorial(x) for x in d))
if d == (1,) and g == 1:
return F(1, 24)
if d[0] == 0:
tail = d[1:]
return sum((psi(g, tail[:j]+(x-1,)+tail[j+1:])
for j,x in enumerate(tail) if x), F(0))
if d[0] == 1:
return (2*g-2+n-1)*psi(g, d[1:])
k, tail = d[-1], d[:-1]
total = F(0)
for j,x in enumerate(tail):
total += F(df(2*k+2*x-1), df(2*x-1))*psi(
g, tail[:j]+(k+x-1,)+tail[j+1:])
for a in range(k-1):
b = k-2-a
split = psi(g-1, (a,b)+tail)
for mask in range(1 << len(tail)):
left = tuple(x for i,x in enumerate(tail) if mask & (1 << i))
right = tuple(x for i,x in enumerate(tail) if not mask & (1 << i))
for h in range(g+1):
split += psi(h,(a,)+left)*psi(g-h,(b,)+right)
total += F(df(2*a+1)*df(2*b+1),2)*split
return total / df(2*k+1)
def partitions(m, minimum=1):
if m == 0:
yield ()
for a in range(minimum,m+1):
for rest in partitions(m-a,a):
yield (a,)+rest
@lru_cache(None)
def mixed(g, d, m):
"""Integral of kappa_1^m times the given psi powers."""
answer = F(0)
for blocks in partitions(m):
multiplicities = Counter(blocks)
denominator = prod(factorial(b) for b in blocks)
denominator *= prod(factorial(c) for c in multiplicities.values())
multiplicity = factorial(m) // denominator
answer += (-1)**(m-len(blocks))*multiplicity*psi(
g, d+tuple(b+1 for b in blocks))
return answer
def compositions_at_most(total, n):
if n == 0:
yield ()
else:
for a in range(total+1):
for rest in compositions_at_most(total-a,n-1):
yield (a,)+rest
def volume(g, n):
"""Return V_{g,n} in QQ[L1,...,Ln,p], with orbifold weights."""
g,n = int(g),int(n)
if g < 0 or n < 0 or 2*g-2+n <= 0:
raise ValueError('g,n must be a stable nonnegative pair')
R = PolynomialRing(QQ, names=tuple('L%d'%i for i in range(1,n+1))+('p',))
dimension = 3*g-3+n
coefficients = {}
for d in compositions_at_most(dimension,n):
m = dimension-sum(d)
coefficient = mixed(g,tuple(sorted(d)),m)*F(
2**m, 2**sum(d)*factorial(m)*prod(factorial(a) for a in d))
assert coefficient > 0
exponents = tuple(2*a for a in d)+(2*m,)
# Sage's one-variable constructor takes integer rather than tuple keys.
key = exponents if n else exponents[0]
coefficients[key] = QQ(coefficient.numerator)/QQ(coefficient.denominator)
return R(coefficients)
class WeilPeterssonPolynomials(numberdb.Generator):
table = 'T460'
parameters = ('g','n')
type = 'Q[]'
rigour = 'exact'
def enumerate(self, complexity=MAX_COMPLEXITY, boundaries=MAX_BOUNDARIES):
for g in range((complexity+2)//2+1):
for n in range(boundaries+1):
if 0 < 2*g-2+n <= complexity:
yield {'g':str(g),'n':str(n)}
def value(self, params, digits=100):
return volume(int(params['g']),int(params['n']))
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].isupper():
token = token.split('=',1)[1].strip().strip('\'"')
numberdb.configure(api_key=token)
generator = WeilPeterssonPolynomials()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(generator.publish(message='Exact orbifold volume polynomials, checked by an independent geometric recursion.'))
else:
result = generator.verify(sample=None)
print(result)
sys.exit(0 if result.ok else 1)