normalization-check.py

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

5526 bytes, as of the version from 2026-11-02 06:04 (current). Recorded here, not run.

"""T670 normalization repair checks, leaving the table and generator unchanged.

Run with SageMath and numberdb installed, beside generate.py:
    sage -python normalization-check.py document-before.json
The argument is an authenticated GET /api/table?id=T670 JSON export. The
campaign mounts it under that name and runs agents/sage.sh on this script.

Reproduces all 500 stored decimal enclosures with the existing generator.
Independently checks geometric heights for nine arithmetic edge cases using
Dedekind eta's pentagonal series, and controls the square case by AGM.
The formula being relocated is Barquero-Sanchez et al., Remark 1.4, (1.2),
https://doi.org/10.1007/s40993-017-0077-7. The eta series is DLMF 23.17.6, with q=e^(pi*i*tau); the code uses
its square Q=e^(2*pi*i*tau).
For |q|=r and terms n<=10, the omitted absolute tail is at most
2*r^176/(1-r^34); the first omitted smaller exponent is 176 and increments
are at least 34. Propagate this bound through abs, log and the class mean.

Governing manifest: agents/runs/20261102T055816Z-repair-skills/manifest.json.
Both archived skills have SHA-256:
a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278.
No assertion of an independent campaign argument-selection verdict is made.
"""
import sys,json,hashlib,math
from pathlib import Path
from decimal import Decimal
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.rings.real_mpfi import RealIntervalField
from sage.rings.complex_arb import ComplexBallField
from sage.env import SAGE_VERSION
import generate

work=Path(__file__).resolve().parent
path=Path(sys.argv[1]) if len(sys.argv)>1 else work/'document-before.json'
doc=json.loads(path.read_text())
R=RealBallField(512); I=RealIntervalField(512); C=ComplexBallField(512)

def decimal_interval(s):
    dec=Decimal(s); sign,digits,exponent=dec.as_tuple()
    integer=sum(v*10**(len(digits)-i-1) for i,v in enumerate(digits))
    center=QQ((-1)**sign*integer)*QQ(10)**exponent
    ulp=QQ(10)**exponent
    return center,ulp

def inside(ball,s):
    assert ball.is_finite() and ball.accuracy()>400
    center,ulp=decimal_interval(s)
    interval=I(ball)
    return interval.lower()>I(center-ulp).upper() and interval.upper()<I(center+ulp).lower()

def forms(delta):
    d=-delta
    for a in range(1,math.isqrt(d//3)+1):
        for b in range(-a,a+1):
            if (b*b+d)%(4*a): continue
            c=(b*b+d)//(4*a)
            if c<a or math.gcd(math.gcd(a,b),c)!=1: continue
            if (abs(b)==a or a==c) and b<0: continue
            yield a,b,c

def geometric(delta):
    total=R(0); count=0
    for a,b,c in forms(delta):
        y=R(-delta).sqrt()/(2*a)
        tau=C(R(QQ(-b)/(2*a)),y)
        q=(C(0,2*R.pi())*tau).exp(); r=abs(q)
        assert r.upper()<R(1).lower()
        series=C(1)
        for n in range(1,11):
            series+=(-1)**n*(q**(n*(3*n-1)//2)+q**(n*(3*n+1)//2))
        tail=2*r**176/(1-r**34)
        absolute=abs(series).add_error(tail)
        assert absolute.lower()>0
        total+=y.log()/2-R.pi()*y/6+2*absolute.log()
        count+=1
    assert count
    return -(2*R.pi().sqrt()).log()-total/count,count

# Known-answer control first: eta(i)^2=K(1/2)/pi, with K from AGM.
K=R.pi()/(2*R(1).agm(R(QQ(1)/2).sqrt()))
control=R.pi().log()/2-R(2).log()-K.log()
geom4,_=geometric(-4)
assert control.is_finite() and control.accuracy()>480
assert geom4.is_finite() and geom4.accuracy()>480
assert control.overlaps(geom4)
assert not (geom4+R(QQ(1)/10**90)).overlaps(control)

rows=doc['Numbers']
selected=[p['Delta'] for p in generate.StableCMHeights().enumerate()]
assert selected==list(rows) and len(selected)==500
minimum=10000;max_discrepancy=R(0)
for key,entry in rows.items():
    ball=generate.height(int(key),429)
    assert inside(ball,entry['number']),key
    minimum=min(minimum,int(ball.accuracy()))
    center,ulp=decimal_interval(entry['number'])
    discrepancy=abs((R(ball)-R(center))/R(ulp))
    if discrepancy.upper()>max_discrepancy.upper(): max_discrepancy=discrepancy

samples=[]
for delta in (-3,-4,-12,-23,-28,-144,-823,-972,-1000):
    geom,count=geometric(delta)
    stable=geom-R.pi().log()/2
    assert inside(stable,rows[str(delta)]['number']),delta
    h=generate.height(delta,512)
    assert geom.overlaps(h+R.pi().log()/2),delta
    assert not (stable+R(QQ(1)/10**90)).overlaps(h),delta
    samples.append({'Delta':delta,'forms':count,'geometric_height':str(geom),
                    'accuracy_bits':int(geom.accuracy()),'stored_interval_contains_enclosure':True})
result={'table':'T670','sage':SAGE_VERSION,'python':sys.version.split()[0],
 'skill_manifest':'agents/runs/20261102T055816Z-repair-skills/manifest.json',
 'skill_sha256':{'local':'a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278',
                 'live':'a2b28d9953264d7b0ca62663785c2952190c5be6d6d3597fd695d9a54d606278'},
 'document_sha256':hashlib.sha256(path.read_bytes()).hexdigest(),
 'generator_sha256':hashlib.sha256((work/'generate.py').read_bytes()).hexdigest(),
 'all_stored_values_reproduced':len(rows),'minimum_reproduction_accuracy_bits':minimum,
 'maximum_reproduction_discrepancy_ulps':str(max_discrepancy),
 'independent_normalization_samples':samples,'square_AGM_control_passed':True,
 'perturbation_controls_rejected':True,
 'source':'Barquero-Sanchez et al. (2017), Remark 1.4, equation (1.2)',
 'fresh_independent_critique_required_after_prose_move':True}
print(json.dumps(result,indent=2))