checks.py

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

7318 bytes, as of the version from 2026-10-27 16:36. Recorded here, not run.

"""Independent numerical checks for numberdb.org/T553.

Run `sage -python checks.py`; add --stored to check the website's actual rows.
The unpublished draft needs NUMBERDB_API_KEY. Dependencies: generate.py and
check.py (the campaign's unmodified check helpers), SageMath and mpmath.

The generator evaluates a Gamma quotient with Arb. Independent paths:
(1) the area-normalized formula, Solynin--Zalgaller (2004), eq. (1.2),
    implemented directly with 600-bit Arb;
(2) Schwarz--Christoffel boundary length, using mpmath quadrature with no
    Gamma evaluations, at 130 and 170 decimal digits;
(3) the square via a manually iterated AGM, with a certified bracket.

For (2), F'(w)=c*(1-w**(-n))**(2/n), branch tending to c at infinity.
The side length 2*sin(pi/n) is c times the arc integral of |F'|/c, hence
c=n*sin(pi/n)/(2**(1+2/n)*int_0^(pi/2) sin(t)**(2/n) dt).
See Batista (2026), J. Engineering Mathematics 160, 8, sec. 3.2.1, eq. (46),
https://doi.org/10.1007/s10665-026-10553-z. Controls are the exact integrals
int_0^(pi/2) 1 dt=pi/2 and int_0^(pi/2) sin(t) dt=1, run first at each precision.
Quadrature is an independent agreement check, not the certified error bound.
The certified bound comes from Arb and exact containment of serialized values.

Policy manifest: agents/runs/20261027T162618Z-build-skills/manifest.json.
Both archived skills: e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c.
"""
import json
import sys
from decimal import Decimal
from fractions import Fraction
import importlib.metadata
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.env import SAGE_VERSION
from numberdb._write import to_text
import mpmath as mp
from generate import PolygonCapacity
from check import exactness, measure, names_its_rings


def rational_decimal(text):
    f = Fraction(text)
    return QQ(f.numerator) / QQ(f.denominator)


def decimal_interval(text):
    d = Decimal(text)
    centre = rational_decimal(text)
    ulp = QQ(10)**int(d.as_tuple().exponent)
    return centre-ulp, centre+ulp, ulp


def source_formula(n):
    R = RealBallField(600)
    a = QQ(1)/QQ(n)
    theta = R.pi()*R(a)
    area = R(n)*(2*theta).sin()/2
    ratio = R(n)*theta.tan()*R(1+a).gamma()**2
    ratio /= R.pi()*R(2)**R(4*a)*R(QQ(1)/2+a).gamma()**2
    return (area*ratio).sqrt()


def agm_square():
    R=RealBallField(600)
    # The AGM of (1,1) is exactly 1; this checks the recurrence's control.
    a=b=R(1)
    for _ in range(12):
        a,b=(a+b)/2,(a*b).sqrt()
    assert 1 in a and 1 in b
    a,b=R(1),1/R(2).sqrt()
    for _ in range(12):
        a,b=(a+b)/2,(a*b).sqrt()
    # The true AGM lies between each geometric and arithmetic iterate.
    lo,hi=QQ(b.lower()),QQ(a.upper())
    assert 0 < hi-lo < QQ(1)/10**170
    bracket=R((lo+hi)/2,(hi-lo)/2)
    answer=1/(R(2).sqrt()*bracket)
    assert answer.is_finite() and QQ(answer.rad()) < QQ(1)/10**170
    return answer


def quadratures():
    outputs={}
    controls={}
    for dps in (130,170):
        with mp.workdps(dps):
            c0=mp.quad(lambda t: mp.mpf(1), [0,mp.pi/2])
            c1=mp.quad(lambda t: mp.sin(t), [0,mp.pi/2])
            error=max(abs(c0-mp.pi/2),abs(c1-1))
            assert error < mp.mpf(10)**(-dps+10)
            controls[str(dps)]=mp.nstr(error,8)
            outputs[dps]={}
            for n in range(3,31):
                power=mp.mpf(2)/n
                integral=mp.quad(lambda t: mp.sin(t)**power,[0,mp.pi/4,mp.pi/2])
                value=n*mp.sin(mp.pi/n)/(mp.power(2,1+power)*integral)
                assert mp.isfinite(value) and value>0
                outputs[dps][n]=mp.nstr(value,dps-5)
    return outputs,controls


def stored_numbers(client=None):
    # check.stored needs Django and a database; this build uses the public API.
    document=numberdb.table('T553',client=client)
    block=document['Numbers']
    if isinstance(block,list):
        return {int(r['params']['n']):r['number'] for r in block}
    return {int(n):v['number'] for n,v in block.items()}


def run(stored=False,client=None):
    gen=PolygonCapacity()
    assert not names_its_rings(__file__.replace('checks.py','generate.py'))
    params=list(gen.enumerate())
    assert params==[{'n':n} for n in range(3,31)]
    values={p['n']:gen.value(p,100) for p in params}
    assert not exactness(values)
    texts=stored_numbers(client) if stored else {n:to_text(v,digits=100) for n,v in values.items()}
    assert list(texts)==list(range(3,31))
    quadrature,controls=quadratures()
    square=agm_square()
    rows=[]
    worst_agreement=QQ(0)
    for n,v in values.items():
        lo,hi,ulp=decimal_interval(texts[n])
        assert hi-lo == 2*ulp
        assert lo <= QQ(v.lower()) and QQ(v.upper()) <= hi
        alt=source_formula(n)
        assert alt.is_finite() and QQ(alt.rad())<QQ(1)/10**170
        assert v.overlaps(alt)
        assert lo <= QQ(alt.lower()) and QQ(alt.upper()) <= hi
        R=v.parent()
        assert v > (R.pi()/n).cos() and v < 1
        q1=rational_decimal(quadrature[130][n]);q2=rational_decimal(quadrature[170][n])
        difference=abs(q1-q2)
        assert difference < QQ(1)/10**120
        # Independent heuristic approximation is far inside the written interval.
        assert lo < q1 < hi and lo < q2 < hi
        assert abs(q2-QQ(v.mid()))<QQ(1)/10**130
        if n==4:
            assert v.overlaps(square)
            assert lo <= QQ(square.lower()) and QQ(square.upper()) <= hi
        # Relative error bounded by 2**(-bits), using exact rational comparisons.
        bits=0
        radius=QQ(v.rad())
        lower=QQ(v.lower())
        while radius*2**(bits+1) < lower:
            bits+=1
        worst_agreement=max(worst_agreement,difference)
        rows.append({'n':n,'number':texts[n],'radius_upper':str(v.rad()),
                     'certified_relative_bits_at_least':bits,
                     'source_formula_and_serialization':'pass',
                     'quadrature_130':quadrature[130][n],
                     'quadrature_170':quadrature[170][n]})
    output={'table':'T553','stored_readback':stored,'entries':len(rows),
            'working_bits':numberdb.bits(100,losing=128),
            'minimum_relative_accuracy_bits':min(r['certified_relative_bits_at_least'] for r in rows),
            'serialization':'All Arb bounds and independent values lie inside decimal +/- one ulp.',
            'quadrature_controls_absolute_errors':controls,
            'quadrature_130_170_max_difference':str(RealBallField(80)(worst_agreement)),
            'square_agm':'pass; bracket width < 1e-170; (1,1) control passed',
            'geometric_disk_bounds':'pass on all 28 entries',
            'measure_serialized':measure(texts),
            'versions':{'sage':SAGE_VERSION,'numberdb':importlib.metadata.version('numberdb'),'mpmath':mp.__version__},
            'policy_manifest':'agents/runs/20261027T162618Z-build-skills/manifest.json',
            'skill_local_sha256':'e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c',
            'skill_live_sha256':'e37efb7e2ededf6c1d7134c673f152f648f26e0267c556aebdf3ec65765d8b4c',
            'rows':rows}
    return output

if __name__=='__main__':
    print(json.dumps(run(stored='--stored' in sys.argv),indent=2))