checks.py

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

6948 bytes, as of the version from 2026-10-26 21:14. Recorded here, not run.

"""Independent checks for T528; run `sage -python checks.py` beside generate.py.

Checks do not fill, offer, review or publish a table. Set NUMBERDB_API_KEY
(or pipe a key with NUMBERDB_KEY_FROM_STDIN=1) to also check the private draft.
The source of the independent coefficient calculation is Janson (2007),
equations (62)--(65), from the generating function for graph enumeration.
"""
import os
import sys
import json
from math import factorial, comb
from fractions import Fraction
from itertools import combinations
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from numberdb._write import to_text
from numberdb._generate import _stored_entries
from check import exactness, measure, agrees_with, names_its_rings
from generate import WrightConstants, rational_factor, k_coefficients


def graph_coefficients(limit):
    # E(z)=sum e_r z^r; C(z)=log E(z). Implement E*C'=E' exactly.
    e = [QQ(factorial(6*r)) / (QQ(2)**(5*r) * QQ(3)**(2*r)
         * QQ(factorial(3*r)) * QQ(factorial(2*r))) for r in range(limit+1)]
    c = [QQ(0)]
    for r in range(1, limit+1):
        c.append(e[r] - sum((j*c[j]*e[r-j] for j in range(1,r)), QQ(0))/QQ(r))
    return e,c


def independent_factor(k,c):
    # Equation (34), simplified independently using Gamma(3*k/2).
    if k == 0:
        return QQ(1)/4
    if k % 2 == 0:
        m=k//2
        return c[k] / (QQ(2)**(3*m) * QQ(factorial(3*m-1)))
    m=(k-1)//2
    return c[k] * QQ(2)**(3*m+1)*QQ(factorial(3*m+1))/QQ(factorial(6*m+2))


def decimal_interval(text):
    # Exact endpoints of NumberDB's one-unit-in-the-last-place convention.
    mantissa,_,exponent=text.lower().partition('e')
    exponent=int(exponent or 0)
    places=len(mantissa.partition('.')[2])
    ulp=QQ(10)**(exponent-places)
    center=QQ(Fraction(text).numerator)/QQ(Fraction(text).denominator)
    return center-ulp,center+ulp


def contained(text,ball):
    lo,hi=decimal_interval(text)
    R=ball.parent()
    assert ball.is_finite() and ball.accuracy() >= 450
    assert R(lo) < ball and ball < R(hi), (text,str(ball))


def small_graph_controls():
    # Independently count labelled simple graphs by all edge subsets through n=5.
    counts={}
    for n in range(2,6):
        edges=list(combinations(range(n),2)); by_edges={}
        for mask in range(1 << len(edges)):
            neighbours=[set() for _ in range(n)]
            m=0
            for j,(a,b) in enumerate(edges):
                if mask & (1<<j):
                    neighbours[a].add(b);neighbours[b].add(a);m+=1
            reached={0}; frontier=[0]
            while frontier:
                for v in neighbours[frontier.pop()]-reached:
                    reached.add(v);frontier.append(v)
            if len(reached)==n:by_edges[m]=by_edges.get(m,0)+1
        assert by_edges.get(n-1,0)==n**(n-2)  # Cayley control first.
        if n>=3:
            unicyclic=QQ(factorial(n-1))/2*sum((QQ(n)**j/QQ(factorial(j)) for j in range(n-2)),QQ(0))
            assert by_edges[n]==unicyclic
        counts[str(n)]=by_edges
    return counts


def main():
    if os.environ.get('NUMBERDB_KEY_FROM_STDIN')=='1':
        key=sys.stdin.read().strip()
        if '=' in key and key.split('=',1)[0].isupper():key=key.split('=',1)[1].strip().strip('\'"')
        os.environ['NUMBERDB_API_KEY']=key
    controls=small_graph_controls()
    e,c=graph_coefficients(11)
    assert e[1]==QQ(5)/24 and e[2]==QQ(385)/1152 and c[1]==QQ(5)/24
    assert independent_factor(0,c)==QQ(1)/4
    assert independent_factor(1,c)==QQ(5)/24
    assert independent_factor(2,c)==QQ(5)/256
    assert independent_factor(3,c)==QQ(221)/24192
    factors={k:rational_factor(k) for k in range(12)}
    assert not agrees_with(factors,lambda k:independent_factor(k,c))
    # Check the displayed sigma recurrence across every required coefficient.
    K=k_coefficients(12);sigma={k:QQ(2)**(k+1)*K[k+1] for k in range(-1,12)}
    for k in range(-1,11):
        assert sigma[k+1]==QQ(3*k+2)/2*sigma[k]+sum((sigma[j]*sigma[k-j] for j in range(k+1)),QQ(0))
    gen=WrightConstants();values={int(p['k']):gen.value(p,100) for p in gen.enumerate()}
    assert not exactness(values) and not names_its_rings(__file__.replace('checks.py','generate.py'))
    R=RealBallField(640);sqrt2pi=(2*R.pi()).sqrt()
    rows=[]
    for k,value in values.items():
        text=to_text(value,digits=100)
        independent=R(independent_factor(k,c))*(sqrt2pi if k%2==0 else 1)
        if k%2:
            assert text==str(independent_factor(k,c)) and value in QQ
        else:
            contained(text,value);contained(text,independent)
            # Direct Gamma form avoids both parity simplifications.
            general=R(2)**(QQ(1-3*k)/2)*R.pi().sqrt()*R(sigma[k])/R(QQ(3*k)/2+1).gamma()
            assert general.is_finite() and general.accuracy()>600
            contained(text,general)
        # Check the public Programs formula at every index, including rational rows.
        program_value=R(2)**(QQ(3-k)/2)*R.pi().sqrt()*R(K[k+1])/R(QQ(3*k+2)/2).gamma()
        assert program_value.is_finite() and program_value.accuracy()>600
        assert program_value.overlaps(independent)
        rows.append({'k':k,'factor':str(factors[k]),'number':text,
                     'accuracy_bits':None if k%2 else int(value.accuracy()),
                     'radius':None if k%2 else str(value.rad())})
    result={'controls':controls,'rows':rows,'measure_written':measure({r['k']:r['number'] for r in rows}),
            'independent_coefficients':'12/12 exact matches','sigma_recurrence':'all required indices',
            'manifest':'agents/runs/20261026T210447Z-build-skills/manifest.json',
            'skill_sha256':'b4d51ded0d52cfe3dcc0859de0845c3879b0c2363273ba7e809a7e06ded40b18'}
    if os.environ.get('NUMBERDB_API_KEY'):
        doc=numberdb.table('T528')
        records=_stored_entries(doc)
        assert len(records)==12 and list(records)==[str(k) for k in range(12)]
        for identity,text in records.items():
            k=int(identity)
            if k%2:assert QQ(text)==independent_factor(k,c)
            else:contained(text,R(independent_factor(k,c))*sqrt2pi)
        verification=gen.verify(sample=None)
        assert verification.ok
        result['stored_independent_checks']='12/12';result['verify']=str(verification)
        excursion=_stored_entries(numberdb.table('T523'))
        assert len(excursion)==12 and set(excursion)=={str(n) for n in range(1,13)}
        for identity,text in excursion.items():
            n=int(identity);k=n-1
            expected=R(independent_factor(k,c))*factorial(n)*(sqrt2pi if k%2==0 else 1)
            if k%2:assert QQ(text)==independent_factor(k,c)*factorial(n)
            else:contained(text,expected)
        result['stored_excursion_moment_identity']='12/12 at authenticated draft T523'
    print(json.dumps(result,indent=2))


if __name__=='__main__':main()