back to table · edit · history · where entries came from · files · download
7249 bytes, as of the version from 2026-11-01 11:13. Recorded here, not run.
"""Mahler measures of the Hessian family -- numberdb.org/T649.
Run with SageMath and the numberdb client installed:
sage -pip install numberdb
sage -python generate.py # verify every stored entry
sage -python generate.py --fill # fill the existing draft
The numerical method is Jensen's formula for y^2+(x^2-k*x)*y+x,
where k is the real cube root of t. The roots have product x, of modulus
one; the logarithm of the larger modulus equals |Re acosh(z)| with
z=(k*exp(i*theta/2)-exp(3*i*theta/2))/2. Tanh-sinh quadrature is split
at every torus crossing. Two precisions, digits+40 and digits+90, must
agree with a margin of 15 digits. This is an agreement check, not a
certified integration bound. No floating result is disguised as a ball.
The only parameter is t. The exact catalogue and selection rationale are
in argument-selection.md. Selection-relevant source dependency: this file
alone. checks.py supplies independent cubic Jensen and hypergeometric
checks and exact CM checks. The source catalogue is Samart--Tao v3,
arXiv:2409.14714, Table 1, rows 13--27.
"""
import os
import sys
import json
from functools import lru_cache
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.qqbar import AA
import mpmath as mp
# row, rational part, radical coefficient, radicand, CM discriminant.
CATALOGUE = (
(13, '108', '54', 2, -24),
(14, '18', '6', 3, -36),
(15, '270', '162', 3, -4),
(16, '729', '405', 3, -48),
(17, '-27/2', '-27/2', 5, -15),
(18, '3375/2', '1485/2', 5, -60),
(19, '-4320', '-1944', 5, -75),
(20, '32', '2', 6, -72),
(21, '3672', '1458', 6, -8),
(22, '-864', '-216', 17, -51),
(23, '-163296', '-35640', 21, -147),
(24, '4', '-4', 33, -99),
(25, '-16740', '-2916', 33, -11),
(26, '-55296', '-8640', 41, -123),
(27, '-13500000', '-1431000', 89, -267),
)
def catalogue():
result = []
for text in ('-1', '0', '27', '-216', '24', '54'):
result.append(dict(key=text, a=text, b='0', d=1,
role='intrinsic' if text in ('-1','0','27') else 'arithmetic_structure',
D={'-216':-3,'24':-27,'54':-12}.get(text)))
for row, a, b, d, D in CATALOGUE:
for sign in (1, -1):
coefficient = sign*QQ(b)
key = a+('+' if coefficient>0 else '-')+str(abs(coefficient))+'*sqrt('+str(d)+')'
result.append(dict(key=key, a=a, b=str(coefficient), d=d,
row=row, D=D, role='arithmetic_structure'))
return result
ITEMS = catalogue()
BY_KEY = {item['key']: item for item in ITEMS}
def exact_argument(item):
return AA(QQ(item['a'])) + AA(QQ(item['b']))*AA(item['d']).sqrt()
def mp_rational(s):
parts=s.split('/')
return mp.mpf(parts[0])/mp.mpf(parts[1]) if len(parts)==2 else mp.mpf(s)
def mp_argument(item):
return mp_rational(item['a'])+mp_rational(item['b'])*mp.sqrt(item['d'])
def compute(item, working):
# mpmath retains transformed nodes for every interval and precision.
# Bound that cache across values; no numerical result is reused here.
mp.mp._tanh_sinh.clear()
with mp.workdps(working):
t=mp_argument(item)
k=mp.sign(t)*mp.root(abs(t),3)
def integrand(theta):
z=(k*mp.exp(mp.j*theta/2)-mp.exp(3*mp.j*theta/2))/2
return abs(mp.re(mp.acosh(z)))
points=[mp.mpf(0),mp.pi]
if -1<t<27:
points.insert(1,mp.acos((k-1)/2))
# Midpoints make each interval's independent convergence less sensitive
# to a distant complex singularity. They do not select table arguments.
split=[points[0]]
for a,b in zip(points,points[1:]):
split.extend([(a+b)/2,b])
tolerance=mp.power(10,-working+20)
def integrate(a,b,depth=0):
value,error=mp.quad(integrand,[a,b],error=True,maxdegree=8)
if error <= tolerance*(b-a)/mp.pi:
return value,error
if depth>=24:
raise ArithmeticError('Adaptive quadrature depth exhausted for '+item['key'])
middle=(a+b)/2
left,el=integrate(a,middle,depth+1)
right,er=integrate(middle,b,depth+1)
return left+right,el+er
pieces=[integrate(a,b) for a,b in zip(split,split[1:])]
value=sum(v for v,e in pieces)/mp.pi
error=sum(e for v,e in pieces)/mp.pi
if not mp.isfinite(value) or error>mp.power(10,-working+20):
raise ArithmeticError('Quadrature failed convergence for '+item['key']+' at '+str(working)+' digits: estimate='+str(error))
return mp.nstr(value,working),mp.nstr(error,8)
@lru_cache(maxsize=None)
def evaluated(key,digits):
item=BY_KEY[key]
low,err_low=compute(item,digits+40)
high,err_high=compute(item,digits+90)
with mp.workdps(digits+100):
difference=abs(mp.mpf(low)-mp.mpf(high))
scale=max(mp.mpf(1),abs(mp.mpf(high)))
agreement=-mp.log10(difference/scale) if difference else mp.inf
if agreement<digits+15:
raise ArithmeticError('Insufficient precision agreement for '+key)
return dict(number=mp.nstr(mp.mpf(high),digits,strip_zeros=False),
low=low, high=high, agreement=str(agreement),
quadrature_estimates=[err_low,err_high])
def radical_label(item):
if item['b']=='0':
return None
def rational_latex(s):
q=QQ(s)
return str(q.numerator()) if q.denominator()==1 else r'\frac{'+str(q.numerator())+'}{'+str(q.denominator())+'}'
a=rational_latex(item['a'])
b=QQ(item['b'])
return '$'+a+('+' if b>0 else '-')+rational_latex(str(abs(b)))+r'\sqrt{'+str(item['d'])+'}$'
class HessianMahler(numberdb.Generator):
table='T649'
parameters=('t',)
type='R'
digits=100
rigour='heuristic (agreement-checked)'
def environment(self):
return dict(super().environment(), mpmath=mp.__version__)
def enumerate(self):
for item in ITEMS:
yield {'t':item['key']}
def value(self,params,digits):
item=BY_KEY[str(params['t'])]
out={'number':evaluated(item['key'],digits)['number']}
label=radical_label(item)
if label:
out['param-latex']=label
comments={
'-1':r'The negative real boundary of the torus-zero region, with $k=-1$.',
'0':r'The Fermat specialization gives $N(0)=\mu_3$, the three-step value in HREF{Mahler_measures_of_(short_random_walks)#3}.',
'27':r'The cubic factors into three linear factors, each of Mahler measure $\mu_3$, so $N(27)=3\mu_3$.'}
if item['key'] in comments:
out['comment']=comments[item['key']]
return out
if __name__=='__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
numberdb.configure(api_key=sys.stdin.read().strip())
g=HessianMahler()
if '--fill' in sys.argv or os.environ.get('NUMBERDB_PUBLISH')=='1':
print(g.publish(message='Fill 36 ordinary Hessian Mahler measures with 100 agreement-checked digits'))
else:
report=g.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)