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))