checks.py

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

8618 bytes, as of the version from 2026-10-25 05:25 (current). Recorded here, not run.

"""Numerical checks for Scaling dimensions of the 3D Ising CFT -- numberdb.org/T473.

    sage -python checks.py             # local generator, no network/key needed
    sage -python checks.py --stored    # database, with a draft-readable key

Uses exact rational interval arithmetic, known 2D Ising controls, an intentionally
wrong control, source transcriptions, and independent published computations.
This does not rerun either conformal-bootstrap optimisation or Monte Carlo.
"""
import json
import re
import sys
from fractions import Fraction as F
from pathlib import Path

import numberdb.sage as numberdb
from sage.rings.integer_ring import ZZ

from generate import IsingScalingDimensions, configure_key
from check import exactness, measure, names_its_rings


class Interval:
    def __init__(self, lo, hi=None):
        self.lo = F(str(lo))
        self.hi = self.lo if hi is None else F(str(hi))
        assert self.lo <= self.hi

    def __add__(self, other):
        other = interval(other)
        return Interval(self.lo + other.lo, self.hi + other.hi)

    __radd__ = __add__

    def __neg__(self):
        return Interval(-self.hi, -self.lo)

    def __sub__(self, other):
        return self + -interval(other)

    def __rsub__(self, other):
        return interval(other) + -self

    def __mul__(self, other):
        other = interval(other)
        corners = [a*b for a in (self.lo, self.hi)
                   for b in (other.lo, other.hi)]
        return Interval(min(corners), max(corners))

    __rmul__ = __mul__

    def __truediv__(self, other):
        other = interval(other)
        assert not other.contains(0), 'division interval includes zero'
        return self * Interval(1/other.hi, 1/other.lo)

    def __rtruediv__(self, other):
        return interval(other) / self

    def contains(self, value):
        return self.lo <= value <= self.hi

    def overlaps(self, other):
        other = interval(other)
        return max(self.lo, other.lo) <= min(self.hi, other.hi)

    def endpoints(self):
        return [str(self.lo), str(self.hi)]


def interval(value):
    return value if isinstance(value, Interval) else Interval(value)


def published(text):
    """Parse the source's parentheses, independently of the generator strings."""
    m = re.fullmatch(r'(\d+\.\d+)\((\d+)\)', text)
    assert m, text
    c = F(m[1]); r = F(int(m[2]), 10 ** len(m[1].split('.')[1]))
    return Interval(c-r, c+r)


def written(value):
    text = str(value)
    if ' +/- ' in text:
        c, r = map(F, text.split(' +/- '))
        assert r > 0
        return Interval(c-r, c+r)
    assert '.' not in text and 'e' not in text.lower(), text
    return Interval(F(text))


def exponents(sigma, epsilon, d=3):
    nu = 1/(d-epsilon)
    return {'nu': nu, 'eta': 2*sigma-d+2,
            'alpha': 2-d*nu, 'beta': sigma*nu,
            'gamma': (d-2*sigma)*nu, 'delta': (d-sigma)/sigma}


def residuals(e, d=3):
    a,b,g,de,n,eta = (e[k] for k in ('alpha','beta','gamma','delta','nu','eta'))
    return {'Rushbrooke': a+2*b+g-2,
            'Widom': g-b*(de-1),
            'Fisher': g-n*(2-eta),
            'Josephson': 2-a-d*n,
            'Griffiths': a+b*(de+1)-2}


def controls():
    assert (Interval(-2,3)*Interval(-4,5)).endpoints() == ['-12','15']
    assert (Interval(1,2)/Interval(2,4)).endpoints() == ['1/4','1']
    assert published('0.518148806(24)').endpoints() == ['259074391/500000000','51814883/100000000']
    control = exponents(F(1,8), F(1), d=2)
    known = {'alpha':F(0),'beta':F(1,8),'gamma':F(7,4),
             'delta':F(15),'nu':F(1),'eta':F(1,4)}
    assert control == known
    assert all(x == 0 for x in residuals(control, d=2).values())
    bad = {k: Interval(v) for k,v in known.items()}
    bad['gamma'] = Interval(2)
    assert not residuals(bad, d=2)['Fisher'].contains(0)
    assert exactness({'bad':1.0}), 'shared check must reject float control'
    assert exactness({'good':ZZ(3)}) == []


def normalize_numbers(numbers):
    if isinstance(numbers, dict):
        out = {k: dict(v) if isinstance(v,dict) else {'number':v}
               for k,v in numbers.items()}
    else:
        out = {v['params']['operator']:dict(v) for v in numbers}
    # The document API returns wire strings even for exact integers. Decode
    # that exact spelling before invoking the shared Sage-object type check.
    # A decimal such as '3.0' deliberately does not qualify.
    for entry in out.values():
        text = entry['number']
        if isinstance(text,str) and re.fullmatch(r'-?\d+',text):
            entry['number'] = ZZ(text)
    return out


def run(values, companion=None):
    controls()
    assert names_its_rings(str(Path(__file__).with_name('generate.py'))) == []
    assert exactness(values) == []
    assert list(values) == ['sigma','epsilon','T','epsilon-prime','sigma-prime']
    # Independently read from the two cited primary papers.
    expected = {'sigma':'0.518148806(24)','epsilon':'1.41262528(29)',
                'epsilon-prime':'3.82951(61)','sigma-prime':'5.262(89)'}
    got = {k:written(v['number']) for k,v in values.items()}
    for key, text in expected.items():
        assert got[key].endpoints() == published(text).endpoints(), key
        assert 0 < got[key].hi-got[key].lo <= F(1,5), key
        assert got[key].lo >= F(1,2), '3D scalar unitarity bound'
    assert str(values['T']['number']) == '3'
    assert got['T'].endpoints() == ['3','3']
    assert got['sigma'].hi < got['epsilon'].lo < 3
    assert 3 < got['epsilon-prime'].lo < got['sigma-prime'].lo
    assert got['sigma-prime'].hi < 6

    # Check source consistency: Chang2025 Table 1 versus Table 2.
    exps = exponents(got['sigma'],got['epsilon'])
    exps['omega'] = got['epsilon-prime']-3
    for key,text in {'eta':'0.036297612(48)','nu':'0.62997097(12)'}.items():
        assert exps[key].overlaps(published(text)), key
    for key,res in residuals(exps).items():
        assert res.contains(0), key
        assert res.hi-res.lo < F(1,1000), 'uninformative residual interval'
    # All source error-box corners satisfy the algebraic identities exactly.
    for s in (got['sigma'].lo,got['sigma'].hi):
        for e in (got['epsilon'].lo,got['epsilon'].hi):
            assert all(r == 0 for r in residuals(exponents(s,e)).values())

    # Independent finite-size Monte Carlo, Hasenbusch2010 abstract.
    mc = {'nu':'0.63002(10)','eta':'0.03627(10)','omega':'0.832(6)'}
    for key,text in mc.items():
        assert exps[key].overlaps(published(text)), ('Monte Carlo',key)
    # A distinct bootstrap extraction, not a rigorous second error bound.
    efm = {'epsilon-prime':'3.82968(23)','sigma-prime':'5.2906(11)'}
    for key,text in efm.items():
        assert got[key].overlaps(published(text)), ('EFM',key)

    companion_checks = []
    if companion is not None:
        # T472 shares some input literature, so this is consistency, not independence.
        ising = companion['Numbers']['ising']
        for key,x in exps.items():
            assert x.overlaps(written(ising[key]['number'])), ('T472',key)
            companion_checks.append(key)
        stored_exps = {k:written(ising[k]['number']) for k in exps}
        assert all(r.contains(0) for r in residuals(stored_exps).values())
        centers = {k:(x.lo+x.hi)/2 for k,x in got.items()}
        center_exps = exponents(centers['sigma'],centers['epsilon'])
        center_exps['omega'] = centers['epsilon-prime']-3
        assert all(stored_exps[k].contains(x) for k,x in center_exps.items())
    return {'ok':True,'entries':len(values),'controls':'exact 2D Ising and inconsistent Fisher control passed',
            'source_transcriptions':4,'exact_stress_tensor':True,
            'scalar_unitarity':4,'scaling_relations':list(residuals(exps)),
            'source_exponents':['eta','nu'], 'independent_Monte_Carlo':list(mc),
            'independent_EFM':list(efm),'companion_T472':companion_checks,
            'size':measure(values),'stored_intervals':{k:x.endpoints() for k,x in got.items()},
            'derived_exponent_intervals':{k:x.endpoints() for k,x in exps.items()}}


if __name__ == '__main__':
    configure_key()
    if '--stored' in sys.argv:
        values = normalize_numbers(numberdb.table('T473')['Numbers'])
        companion = numberdb.table('T472')
    else:
        gen = IsingScalingDimensions()
        values = {p['operator']:gen.value(p,gen.digits) for p in gen.enumerate()}
        path = Path(__file__).with_name('T472.json')
        companion = json.loads(path.read_text()) if path.exists() else None
    print(json.dumps(run(values,companion),indent=2))