back to table · edit · history · where entries came from · files · download
10629 bytes, as of the version from 2026-11-09 16:36 (current). Recorded here, not run.
"""Checks for Taylor coefficients of Bettin-Conrey reciprocity -- numberdb.org/T692.
Run `sage -python checks.py` beside generate.py and the repository's check.py.
This checks the whole prefix privately, including a rigorous contour integral
for every order. It writes JSON evidence to stdout and never writes to the API.
A caller may use quick_checks(document) to repeat the checks on database values.
The contour check uses AM (3.1)-(3.5), independently of its Bernoulli formula.
On s=3/2+it, F(s)=zeta(s)zeta(1-s)/sin(pi*s) obeys
|F(s)| <= 9/(2*pi)*sqrt(t^2+1/4)/cosh(pi*t),
using the zeta functional equation, |zeta(3/2+it)| <= zeta(3/2)<3,
and |Gamma(3/2+it) cos(pi*s/2)|=sqrt(pi/2)*sqrt(t^2+1/4).
AM's Cauchy bound gives |binom(s,n)| <= 2^(3/2)exp(pi*|t|/2).
Consequently the two omitted tails beyond |t|=T contribute less than
(12*T+14)*exp(-3*T/2)
to every coefficient (using pi>3, sqrt(8)<3, sqrt(t^2+1/4)<=t+1/2).
This bound is included in the enclosure, not estimated from mesh agreement.
At T=0 the bound is 14, so |g_n|<15 for n>=1. We use 16 in the
Taylor remainder 16*|z|^(N+1)/(1-|z|) to check finite cotangent sums.
"""
import json
import time
from decimal import Decimal
from fractions import Fraction
from math import comb
from pathlib import Path
import numberdb.sage as numberdb
from sage.rings.rational_field import QQ
from sage.rings.real_arb import RealBallField
from sage.rings.complex_arb import ComplexBallField
from numberdb._write import to_text
from generate import BettinConreyTaylor, coefficient, rational_coefficients, MAX_ORDER
from check import exactness, measure, names_its_rings, agrees_with
MANIFEST = '../20261109T150707Z-build-skills/manifest.json'
SKILL_SHA256 = '886390324dd1971e381a43cf3b46b822ab0bc67d62f31d8481e29ec79556746b'
def number_part(v):
return v['number'] if isinstance(v,dict) else v
def rational(s):
f=Fraction(str(s))
return QQ(f.numerator)/QQ(f.denominator)
def bounds(text):
text=str(text)
centre=rational(text)
if '.' not in text and 'e' not in text.lower():
return centre,centre
unit=QQ(10)**Decimal(text).as_tuple().exponent
return centre-unit,centre+unit
def enclosure(text,R):
lo,hi=bounds(text)
return R((lo+hi)/2).add_error(R((hi-lo)/2))
def strictly_contains(text, ball):
lo,hi=bounds(text)
if lo==hi:
return ball==lo
return bool(ball>lo and ball<hi)
def independent_values(N=MAX_ORDER, precision=1000):
"""Bernoulli recurrence and direct Arb zeta, with no generator arithmetic."""
R=RealBallField(precision)
B=[QQ(1)]
for m in range(1,N+1):
B.append(-sum((QQ(comb(m+1,j))*B[j] for j in range(m)),QQ(0))/(m+1))
assert B[:5]==[QQ(1),-QQ(1)/2,QQ(1)/6,QQ(0),-QQ(1)/30]
b={k:R(B[k])*R(k).zeta()/k for k in range(2,N+1,2)}
out=[R(-1),R(QQ(1)/2)]
for n in range(2,N+1):
v=R(QQ(1)/(n*(n+1)))+2*b.get(n,R(0))
for j in range(n-1):
v += 2*comb(n-1,j)*b.get(j+2,R(0))
assert v.is_finite() and v.accuracy()>600
out.append(v)
return out,B
def cotangent_sum(h,k,R):
if k==1:
return R(0)
return -sum((R(QQ(a)/k)*(R.pi()*R(QQ((a*h)%k)/k)).cot()
for a in range(1,k)),R(0))
def quick_checks(document=None):
gen=BettinConreyTaylor()
raw={n:gen.value({'n':n},100) for n in range(MAX_ORDER+1)}
assert not exactness(raw)
assert not names_its_rings(Path(__file__).with_name('generate.py'))
p={n:rational_coefficients(n) for n in range(MAX_ORDER+1)}
assert not exactness(p)
assert all(p[n][-1]!=0 for n in range(2,MAX_ORDER+1))
written={n:to_text(number_part(v),100) for n,v in raw.items()}
if document is not None:
nums=document['Numbers']
if isinstance(nums,dict):
stored={int(k):str(number_part(v)) for k,v in nums.items()}
else:
stored={int(row['params']['n']):str(row['number']) for row in nums}
assert list(stored)==list(range(MAX_ORDER+1))
written=stored
alt,B=independent_values()
# Exact recurrence coefficients are also checked against published T13.
fixtures=Path(__file__).with_name('fixtures.json')
fixture_report={}
if fixtures.exists():
f=json.loads(fixtures.read_text())
assert not agrees_with({int(k):rational(v) for k,v in f['bernoulli'].items()},lambda n:B[n])
fixture_report['bernoulli_T13']=len(f['bernoulli'])
for k,v in f['zeta_even'].items():
assert strictly_contains(v,RealBallField(1000)(int(k)).zeta())
fixture_report['even_zeta_T14']=len(f['zeta_even'])
for row in f['cotangent']:
h,k=int(row['h']),int(row['k'])
c=cotangent_sum(h,k,RealBallField(1000))
# Finite Arb cancellation at exact zero is not equality testing.
a,b=bounds(row['number'])
if a==b:
assert a in c and c.rad_as_ball()<RealBallField(1000)(10)**-250
else:
assert strictly_contains(row['number'],c)
fixture_report['cotangent_T639']=len(f['cotangent'])
R=RealBallField(1000)
for n in range(MAX_ORDER+1):
main=R(number_part(raw[n]))
assert main.is_finite() and alt[n].is_finite()
assert main.overlaps(alt[n])
assert strictly_contains(written[n],main)
assert strictly_contains(written[n],alt[n])
if n>=2:
# A control that would detect an error ten written last places away.
lo,hi=bounds(written[n]); unit=(hi-lo)/2
assert not strictly_contains(written[n],alt[n]+10*unit)
closed=[R(-1),R(QQ(1)/2),R(QQ(1)/6)+R.pi()**2/18,
R(QQ(1)/12)+R.pi()**2/36]
assert all(enclosure(written[n],R).overlaps(closed[n]) for n in range(4))
# Reciprocal symmetry: g_n=sum_{j=2}^n (-1)^j binom(n-2,j-2)g_j.
# Check exactly as rational polynomials before interval readback testing.
for n in range(2,MAX_ORDER+1):
rhs=[QQ(0) for _ in p[n]]
for j in range(2,n+1):
w=(-1)**j*comb(n-2,j-2)
for k,c in enumerate(p[j]): rhs[k]+=w*c
assert tuple(rhs)==p[n]
stored_balls=[enclosure(written[n],R) for n in range(MAX_ORDER+1)]
symmetry_width=[]
for n in range(2,MAX_ORDER+1):
residual=stored_balls[n]-sum(((-1)**j*comb(n-2,j-2)*stored_balls[j]
for j in range(2,n+1)),R(0))
assert 0 in residual and residual.rad_as_ball()<R(10)**-68
symmetry_width.append(residual.rad())
assert QQ(1)/2 in cotangent_sum(1,4,R)
assert cotangent_sum(1,3,R).overlaps(R(QQ(1)/3)/R(3).sqrt())
recon=[]
for x in [QQ(31)/32,QQ(33)/32,QQ(7)/8,QQ(9)/8,QQ(3)/4,QQ(5)/4]:
h,k=int(x.numerator()),int(x.denominator())
direct=R.pi()*(R(x)*cotangent_sum(h,k,R)+cotangent_sum(k,h,R))-R(QQ(1)/k)
z=R(x-1)
total=R(0)
for n in range(MAX_ORDER,-1,-1): total=total*(-z)+stored_balls[n]
remainder=R(16)*abs(z)**(MAX_ORDER+1)/(1-abs(z))
difference=(total-direct).add_error(remainder)
assert 0 in difference and difference.rad_as_ball()<R(10)**-59
recon.append({'x':str(x),'remainder_bound':str(remainder),'residual_radius':str(difference.rad())})
accuracies=[(int(number_part(raw[n]).accuracy()),n) for n in range(2,MAX_ORDER+1)]
losses=[]
for n in range(2,MAX_ORDER+1):
terms=[abs(R(c)*R.pi()**(2*k)) for k,c in enumerate(p[n])]
condition=sum(terms,R(0))/abs(R(number_part(raw[n])))
losses.append((float(condition.log()/R(10).log()),n))
report={'stage':'stored_quick_checks' if document else 'private_quick_checks',
'entries':MAX_ORDER+1,'exact_rational_orders':[0,1],
'working_bits':numberdb.bits(100,losing=384),
'minimum_generator_accuracy_bits':min(accuracies),
'minimum_independent_accuracy_bits':min(int(v.accuracy()) for v in alt[2:]),
'maximum_cancellation_decimal_digits':max(losses),
'strict_written_interval_containment':True,'perturbation_controls':99,
'reciprocal_identities_checked':99,'maximum_stored_symmetry_radius':str(max(symmetry_width)),
'measurement':measure(written),'cotangent_reconstructions':recon,
'published_ingredient_checks':fixture_report,
'skill_manifest':MANIFEST,'skill_sha256':SKILL_SHA256,
'all_passed':True}
print(json.dumps(report),flush=True)
return written,report
def contour_checks(written):
R=RealBallField(180); C=ComplexBallField(180)
control=C.integral(lambda z,_:z,0,1)
assert QQ(1)/2 in control.real() and 0 in control.imag()
assert control.real().rad_as_ball()<R(2)**-170
T=60; error=R(12*T+14)*(-R(3)*T/2).exp()
records=[]
for n in range(MAX_ORDER+1):
start=time.monotonic()
def integrand(t,_analytic):
s=C(QQ(3)/2)+C(0,1)*t
value=s.zeta()*(1-s).zeta()/(C.pi()*s).sin()
for k in range(n): value=value*(s-k)/(k+1)
return value
integral=2*C.integral(integrand,0,T,abs_tol=R(2)**-140,
rel_tol=R(2)**-140,eval_limit=100000).real()
if n==0:
answer=-(2*R.pi()).log()+R.euler_constant()+integral
else:
answer=R(QQ(1)/n)+(-1)**n*integral
answer=answer.add_error(error)
assert answer.is_finite() and answer.accuracy()>105
assert answer.overlaps(enclosure(written[n],R))
assert not (answer+R(10)**-30).overlaps(enclosure(written[n],R))
if n<2: assert rational(written[n]) in answer
row={'n':n,'value':to_text(answer,30),'radius':str(answer.rad()),
'accuracy_bits':int(answer.accuracy()),'seconds':time.monotonic()-start}
records.append(row)
print(json.dumps({'contour':row}),flush=True)
report={'stage':'contour_checks','entries':len(records),'all_passed':True,
'minimum_accuracy_bits':min(r['accuracy_bits'] for r in records),
'tail_bound':str(error),'working_bits':180,'truncation_T':T,
'control':'integral of t from 0 to 1 is 1/2; g0=-1 and g1=1/2',
'deliberate_error_controls':len(records),
'skill_manifest':MANIFEST,'skill_sha256':SKILL_SHA256}
print(json.dumps(report),flush=True)
return records,report
if __name__=='__main__':
from sage.env import SAGE_VERSION
print(json.dumps({'versions':{'sage':SAGE_VERSION,'numberdb':numberdb.__version__}}),flush=True)
written,_=quick_checks()
print(json.dumps({'written_values':written}),flush=True)
contour_checks(written)