back to table · edit · history · where entries came from · files · download
7562 bytes, as of the version from 2026-10-25 05:01 (current). Recorded here, not run.
"""Critical exponents of the three-dimensional universality classes -- numberdb.org/T472.
Run with SageMath (or passagemath with numberdb installed):
sage -pip install numberdb
sage -python generate.py # verify every stored entry
sage -python checks.py # offline numerical checks
sage -python generate.py --fill-draft # write entries to this draft
The authenticated operations need NUMBERDB_API_KEY. The campaign can supply
it on stdin with NUMBERDB_KEY_FROM_STDIN=1. --fill-draft writes numbers only;
it neither publishes the table nor offers it for review.
Published estimates, not fresh bootstrap or Monte Carlo computations, are
transcribed here. Increasing digits cannot increase their precision. Direct
inputs retain the source's uncertainty. Derived values enclose the image of
the rectangular input error box using exact rational arithmetic; the radius
is rounded upward to two significant figures. This does not turn empirical
error bars into rigorous bounds or statistical confidence intervals.
"""
import numberdb.sage as numberdb
from fractions import Fraction as Q
from itertools import product
import os
import sys
CLASSES = ('ising', 'xy', 'heisenberg', 'saw', 'percolation')
EXPONENTS = ('alpha', 'beta', 'gamma', 'delta', 'nu', 'eta', 'omega')
# Strings are centre +/- radius, with no binary floating-point conversion.
INPUTS = {
'ising': {'nu': '0.62997097 +/- 0.00000012',
'eta': '0.036297612 +/- 0.000000048',
'omega': '0.82951 +/- 0.00061'},
'xy': {'nu': '0.67169 +/- 0.00007', 'eta': '0.03810 +/- 0.00008',
'omega': '0.789 +/- 0.004'},
'heisenberg': {'nu': '0.71164 +/- 0.00010', 'eta': '0.03784 +/- 0.00005',
'omega': '0.759 +/- 0.002'},
'saw': {'nu': '0.58759700 +/- 0.00000040',
'gamma': '1.15695300 +/- 0.00000095', 'Delta1': '0.528 +/- 0.008'},
# Table 5 gives separate statistical and omega-fit errors. We add them
# conservatively: .0016+.0002 and .00027+.00007, without assuming independence.
'percolation': {'nu': '0.8765 +/- 0.0018', 'eta': '-0.04602 +/- 0.00034',
'omega': '1.62 +/- 0.13'},
}
SOURCES = {'ising': 'Chang2025', 'xy': 'Hasenbusch2019',
'heisenberg': 'Hasenbusch2020', 'percolation': 'Ballesteros1999'}
def parse(text):
c, r = (Q(s.strip()) for s in text.split('+/-'))
assert r > 0
return c-r, c+r
def exponent_at(nu, second, key, walk=False):
# Algebraically direct in (nu,gamma) for SAW, retaining their dependence
# when eta is introduced, instead of treating derived eta as independent.
gamma = second if walk else nu*(2-second)
if key == 'alpha': return 2-3*nu
if key == 'beta': return (3*nu-gamma)/2
if key == 'gamma': return gamma
if key == 'delta': return (3*nu+gamma)/(3*nu-gamma)
if key == 'nu': return nu
if key == 'eta': return 2-gamma/nu
raise KeyError(key)
def bounds(cls, key):
inputs = INPUTS[cls]
if key in inputs:
return parse(inputs[key])
if cls == 'saw' and key == 'omega':
values = [d/n for d,n in product(parse(inputs['Delta1']), parse(inputs['nu']))]
else:
second = 'gamma' if cls == 'saw' else 'eta'
values = [exponent_at(n,s,key,cls=='saw')
for n,s in product(parse(inputs['nu']),parse(inputs[second]))]
# Every transform is monotone in each input on the specific positive nu,
# gamma and 1+eta ranges here. checks.py verifies derivatives' signs and
# denominator positivity before relying on corner extrema.
return min(values), max(values)
def power10(n):
return Q(10**n) if n >= 0 else Q(1,10**(-n))
def decimal_order(x):
assert x > 0
k = 0
while x < power10(k): k -= 1
while x >= power10(k+1): k += 1
return k
def decimal_text(x, places):
scale = 10**places
integer = x*scale
assert integer.denominator == 1
integer = integer.numerator
sign = '-' if integer < 0 else ''
chars = str(abs(integer)).zfill(places+1)
return sign+chars[:-places]+'.'+chars[-places:] if places else sign+chars
def rounded_ball(low, high):
assert low < high
mid = (low+high)/2
step = power10(decimal_order((high-low)/2)-1)
center = ((mid/step+Q(1,2)).numerator // (mid/step+Q(1,2)).denominator)*step
needed = max(center-low, high-center)
ratio = needed/step
radius = (-(-ratio.numerator//ratio.denominator))*step
# If rounding crossed a decade, recompute at the two-digit radius place.
new_step = power10(decimal_order(radius)-1)
if new_step > step:
step = new_step
ratio = mid/step+Q(1,2)
center = (ratio.numerator//ratio.denominator)*step
ratio = max(center-low,high-center)/step
radius = (-(-ratio.numerator//ratio.denominator))*step
places = max(0,-decimal_order(step))
text = decimal_text(center,places)+' +/- '+decimal_text(radius,places)
l,h = parse(text)
assert l <= low < high <= h
return text
def entry(cls, key):
if key in INPUTS[cls]:
value = INPUTS[cls][key]
if cls == 'saw':
ref = 'Clisby2017' if key == 'gamma' else 'ClisbyDuenweg2016'
comment = 'Monte Carlo estimate, CITE{%s}.' % ref
elif cls == 'ising' and key == 'omega':
comment = r'$\omega=\Delta_{\epsilon^\prime}-3$, with $\Delta_{\epsilon^\prime}=3.82951(61)$ from the conditional bootstrap bounds in CITE{Reehorst2022}.'
else:
comment = 'Published estimate, CITE{%s}.' % SOURCES[cls]
if cls == 'percolation' and key != 'omega':
comment += ' The statistical and correction-fit uncertainties are added.'
if cls == 'percolation' and key == 'omega':
comment += r' This is the spatial correction exponent from that study; other determinations show substantial dispersion CITE{Borinsky2021}.'
else:
value = rounded_ball(*bounds(cls,key))
ref = 'ClisbyDuenweg2016} and CITE{Clisby2017' if cls == 'saw' else SOURCES[cls]
comment = 'Derived through the scaling relations from CITE{%s}; the radius encloses the input error box.' % ref
if cls == 'saw' and key == 'omega':
comment = r'Derived as $\omega=\Delta_1/\nu$ from $\Delta_1=0.528(8)$ and $\nu=0.58759700(40)$ in CITE{ClisbyDuenweg2016}.'
return {'number':value,'comment':comment}
class CriticalExponents3D(numberdb.Generator):
table = 'T472'
parameters = ('class','exponent')
type = 'R'
rigour = 'measured'
# The least precise entry (percolation omega) only supports one digit.
# All strings retain their full, explicitly stated source precision.
digits = 1
files = ('generate.py','checks.py','check.py','source-notes.md','preflight-evidence.json')
def enumerate(self):
for cls in CLASSES:
for exponent in EXPONENTS:
yield {'class':cls,'exponent':exponent}
def value(self, params, digits):
return entry(params['class'],params['exponent'])
if __name__ == '__main__':
if os.environ.get('NUMBERDB_KEY_FROM_STDIN') == '1':
os.environ['NUMBERDB_API_KEY'] = sys.stdin.read().strip()
gen = CriticalExponents3D()
if '--fill-draft' in sys.argv or os.environ.get('NUMBERDB_PUBLISH') == '1':
print(gen.publish(message='Fill 35 sourced estimates and scaling-derived bounds; attach reproducible checks.'))
else:
report = gen.verify(sample=None)
print(report)
sys.exit(0 if report.ok else 1)