back to table · edit · history · where entries came from · files · download
5724 bytes, as of the version from 2026-10-27 21:27 (current). Recorded here, not run.
"""Area Siegel–Veech constants of Abelian strata -- numberdb.org/T561.
Run with SageMath (or a modular Sage installation):
sage -pip install numberdb
sage -python generate.py # verify every stored entry
sage -python checks.py # independent checks, no writes
To fill this same draft, an authorized contributor can use
AreaSiegelVeech().publish(client=numberdb.Client(api_key=...))
The method writes entries; it does not publish or offer the draft.
The exact rightmost column of Eskin–Kontsevich–Zorich, arXiv:1112.5872v6,
Appendix A, pp. 100–101, supplies the genus-three and genus-four Lyapunov
sums. Genus two uses Corollary 2, p. 15. Theorem 1, equation (2.1), gives
c_area = 3*(L - sum(m*(m+2)/(m+1))/12)/pi**2. Only the exact sums are used,
never the experimental individual Lyapunov exponents in adjacent columns.
All 25 components of all positive zero partitions in genera 2–4 are included.
The default enumeration generates partitions and applies the KZ classification;
it is not a Cartesian product of profiles and component labels. See the attached
argument-selection.md. Every arithmetic division is between exact Sage values
or certified balls. A 64-bit guard is measured by checks.py at 100 digits.
"""
import os
import sys
import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
WORKING_GUARD = 64
# (genus, positive zero profile, component, exact sum of nonnegative exponents)
SOURCE_ROWS = (
(2, (2,), 'hyp', '4/3'),
(2, (1, 1), 'hyp', '3/2'),
(3, (4,), 'hyp', '9/5'),
(3, (4,), 'odd', '8/5'),
(3, (3, 1), 'connected', '7/4'),
(3, (2, 2), 'hyp', '2'),
(3, (2, 2), 'odd', '5/3'),
(3, (2, 1, 1), 'connected', '11/6'),
(3, (1, 1, 1, 1), 'connected', '53/28'),
(4, (6,), 'hyp', '16/7'),
(4, (6,), 'even', '2'),
(4, (6,), 'odd', '13/7'),
(4, (5, 1), 'connected', '2'),
(4, (4, 2), 'even', '32/15'),
(4, (4, 2), 'odd', '29/15'),
(4, (4, 1, 1), 'connected', '1137/550'),
(4, (3, 3), 'hyp', '5/2'),
(4, (3, 3), 'nonhyp', '2'),
(4, (3, 2, 1), 'connected', '25/12'),
(4, (3, 1, 1, 1), 'connected', '66/31'),
(4, (2, 2, 2), 'even', '737/333'),
(4, (2, 2, 2), 'odd', '2'),
(4, (2, 2, 1, 1), 'connected', '5045/2358'),
(4, (2, 1, 1, 1, 1), 'connected', '131/60'),
(4, (1, 1, 1, 1, 1, 1), 'connected', '839/377'),
)
SUMS = {(g, mu, c): QQ(total) for g, mu, c, total in SOURCE_ROWS}
def partitions(total, largest=None):
"""Positive partitions, in decreasing lexicographic order."""
if total == 0:
yield ()
return
largest = total if largest is None else min(total, largest)
for first in range(largest, 0, -1):
for tail in partitions(total - first, first):
yield (first,) + tail
def components(g, mu):
"""KZ component classification for positive profiles and g >= 2."""
if g < 2 or not mu or tuple(sorted(mu, reverse=True)) != mu \
or min(mu) < 1 or sum(mu) != 2*g-2:
raise ValueError('Use a positive, non-increasing partition of 2*g-2.')
if g == 2:
return ('hyp',)
special = mu in ((2*g-2,), (g-1, g-1))
if g == 3 and special:
return ('hyp', 'odd')
if all(m % 2 == 0 for m in mu):
return ('hyp', 'even', 'odd') if special else ('even', 'odd')
if mu == (g-1, g-1):
return ('hyp', 'nonhyp')
return ('connected',)
def coefficient(g, mu, component):
"""Return the exact rational pi**2*c_area from the EKZ identity."""
g, mu = int(g), tuple(int(m) for m in mu)
if component not in components(g, mu):
raise ValueError('This profile has no component with that label.')
total = SUMS[(g, mu, component)] # Source coverage is deliberately finite.
kappa = sum((QQ(m*(m+2)) / QQ(m+1) for m in mu), QQ(0)) / QQ(12)
return QQ(3) * (total - kappa)
def area_constant(g, mu, component, digits=100):
field = RealBallField(numberdb.bits(digits, losing=WORKING_GUARD))
return field(coefficient(g, mu, component)) / field.pi()**2
class AreaSiegelVeech(numberdb.Generator):
table = 'T561'
parameters = ('g', 'mu', 'component')
type = 'R'
digits = 100
rigour = 'proven'
files = ('generate.py', 'checks.py', 'check.py', 'table_records.py',
'argument-selection.md',
'source-evidence.md', 'numerical-evidence.json')
def enumerate(self):
for g in range(2, 5):
for mu in partitions(2*g-2):
for component in components(g, mu):
yield {'g': g, 'mu': ','.join(map(str, mu)), 'component': component}
def value(self, params, digits):
mu = tuple(int(m) for m in str(params['mu']).split(','))
value = area_constant(params['g'], mu, params['component'], digits)
if (int(params['g']), mu, params['component']) == (2, (2,), 'hyp'):
return {'number': value, 'comment': r'The value is $10/(3\pi^2)$ CITE{EKZ}, Corollary 2.'}
if (int(params['g']), mu, params['component']) == (2, (1, 1), 'hyp'):
return {'number': value, 'comment': r'The value is $15/(4\pi^2)$ CITE{EKZ}, Corollary 2.'}
return value
if __name__ == '__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
key = sys.stdin.read().strip()
if key.startswith('NUMBERDB_'):
key = key.split('=', 1)[1].strip().strip('"').strip("'")
client = numberdb.Client(api_key=key)
else:
client = None
result = AreaSiegelVeech().verify(sample=None, client=client)
print(result)
raise SystemExit(0 if result.ok else 1)