505 lines
20 KiB
Markdown
505 lines
20 KiB
Markdown
# 독립 numerical oracle scratch
|
|
|
|
이 파일은 numerical-review.md의 사전 계산을 재현하는 문서 부록이다. Solver 또는
|
|
production test가 아니며 외부 package·reference·출력 파일을 사용하지 않는다.
|
|
Python 표준 라이브러리만 사용한다. Fraction은 oracle에서 입력 double의 exact 산술을
|
|
확인하는 용도이며 production arbitrary-precision dependency를 요구하지 않는다.
|
|
|
|
T1 root에서 다음 명령은 각 Python block을 순서대로 메모리에서 실행한다.
|
|
|
|
```powershell
|
|
@'
|
|
from pathlib import Path
|
|
import re
|
|
p = Path('docs/shell-transient-heat-transfer/numerical-oracle-scratch.md')
|
|
blocks = re.findall(r'```python\n(.*?)```', p.read_text(encoding='utf-8-sig'), re.S)
|
|
assert len(blocks) == 5
|
|
scope = {}
|
|
for block in blocks:
|
|
exec(compile(block, str(p), 'exec'), scope)
|
|
print('ALL SCRATCH BLOCKS COMPLETED')
|
|
'@ | python -
|
|
```
|
|
|
|
실행 exit는 0이어야 한다. Assertion 범위는 exact algebra와 독립 oracle 자체에 한정된다.
|
|
수렴/이산 오차의 출력 숫자 자체를 production tolerance로 사용하지 않고 본 보고서의
|
|
roundoff·conditioning·오차 전파 기준을 적용한다. NumPy 부재 probe exit1은 실행 실패
|
|
이력으로 보고서에 남겼으며 아래 코드에는 NumPy를 쓰지 않는다.
|
|
|
|
## A. Slab spectral·quadrature·공간/시간 oracle
|
|
|
|
```python
|
|
import math,json
|
|
def zeros(n,m=None): return [[0.0]*(n if m is None else m) for _ in range(n)]
|
|
def tr(A):return list(map(list,zip(*A)))
|
|
def mm(A,B):return [[sum(a*b for a,b in zip(row,col)) for col in zip(*B)]for row in A]
|
|
def mv(A,x):return [sum(a*b for a,b in zip(row,x)) for row in A]
|
|
def solve(A,b):
|
|
a=[list(r)+[v]for r,v in zip(A,b)];n=len(b)
|
|
for k in range(n):
|
|
p=max(range(k,n),key=lambda i:abs(a[i][k]));a[k],a[p]=a[p],a[k]
|
|
d=a[k][k]
|
|
for j in range(k,n+1):a[k][j]/=d
|
|
for i in range(n):
|
|
if i!=k:
|
|
d=a[i][k]
|
|
for j in range(k,n+1):a[i][j]-=d*a[k][j]
|
|
return [r[-1]for r in a]
|
|
def chol(A):
|
|
n=len(A);L=zeros(n)
|
|
for i in range(n):
|
|
for j in range(i+1):
|
|
v=A[i][j]-sum(L[i][k]*L[j][k]for k in range(j))
|
|
L[i][j]=math.sqrt(v)if i==j else v/L[j][j]
|
|
return L
|
|
def eig(A):
|
|
a=[r[:]for r in A];n=len(a);v=[[float(i==j)for j in range(n)]for i in range(n)]
|
|
for z in range(100*n*n):
|
|
p,q=max(((i,j)for i in range(n)for j in range(i+1,n)),key=lambda ij:abs(a[ij[0]][ij[1]]))
|
|
if abs(a[p][q])<1e-15*sum(abs(a[i][i])for i in range(n)):break
|
|
phi=.5*math.atan2(2*a[p][q],a[q][q]-a[p][p]);c=math.cos(phi);s=math.sin(phi)
|
|
J=[[float(i==j)for j in range(n)]for i in range(n)]
|
|
J[p][p]=J[q][q]=c;J[p][q]=s;J[q][p]=-s
|
|
a=mm(mm(tr(J),a),J);v=mm(v,J)
|
|
ix=sorted(range(n),key=lambda i:a[i][i])
|
|
return [a[i][i]for i in ix],[[v[j][i]for i in ix]for j in range(n)]
|
|
def slabmat(ne):
|
|
n=2*ne+1;C=zeros(n);K=zeros(n);l=1/ne
|
|
for e in range(ne):
|
|
for i in range(3):
|
|
for j in range(3):
|
|
C[2*e+i][2*e+j]+=l/30*[[4,2,-1],[2,16,2],[-1,2,4]][i][j]
|
|
K[2*e+i][2*e+j]+=[[7,-8,1],[-8,16,-8],[1,-8,7]][i][j]/(3*l)
|
|
return C,K
|
|
C,K=slabmat(2);Cf=[r[1:]for r in C[1:]];Kf=[r[1:]for r in K[1:]]
|
|
L=chol(Cf);Li=tr([solve(L,[float(i==j)for i in range(4)])for j in range(4)])
|
|
lam,V=eig(mm(mm(Li,Kf),tr(Li)));W=mm(tr(Li),V)
|
|
yp=[-1-x for x in solve(Cf,[r[0]for r in C[1:]])]
|
|
coef=mv(tr(W),mv(Cf,yp))
|
|
def spectral(t,n=None):
|
|
amp=[a*(math.exp(-l*t)if n is None else math.exp(-n*math.log1p(l*t/n)))for a,l in zip(coef,lam)]
|
|
T=[1]+[1+x for x in mv(W,amp)]
|
|
if n is None:dT=[0]+mv(W,[-l*x for l,x in zip(lam,amp)])
|
|
else:
|
|
oldamp=[x*(1+l*t/n)for l,x in zip(lam,amp)]
|
|
old=[1]+[1+x for x in mv(W,oldamp)]
|
|
dT=[(a-b)/(t/n)for a,b in zip(T,old)]
|
|
E=sum(mv(C,T));R=mv(C,dT)[0]+mv(K,T)[0]
|
|
q=[]
|
|
for e,ss in [(0,-1),(0,0),(0,1),(1,-1),(1,0),(1,1)]:
|
|
q.append(-4*sum(a*b for a,b in zip([ss-.5,-2*ss,ss+.5],T[2*e:2*e+3])))
|
|
return T,q,E,R
|
|
def exact(t):
|
|
M=80
|
|
T=[1-sum(4/((2*m+1)*math.pi)*math.sin((2*m+1)*math.pi*x/2)*math.exp(-((2*m+1)*math.pi/2)**2*t)for m in range(M))for x in [0,.25,.5,.75,1]]
|
|
q=[sum(2*math.cos((2*m+1)*math.pi*x/2)*math.exp(-((2*m+1)*math.pi/2)**2*t)for m in range(M))for x in [0,.25,.5,.5,.75,1]]
|
|
E=1-sum(8/((2*m+1)*math.pi)**2*math.exp(-((2*m+1)*math.pi/2)**2*t)for m in range(M))
|
|
return T,q,E,q[0]
|
|
def errors(a,b):return [max(abs(x-y)for x,y in zip(a[0],b[0])),max(abs(x-y)for x,y in zip(a[1],b[1])),abs(a[2]-b[2]),abs(a[3]-b[3])]
|
|
out={"lambda":lam,"projected_initial_free":[1+x for x in yp],"slab":[]}
|
|
for t in [.001,.01,.05,.1,.5,1]:
|
|
s=spectral(t);x=exact(t)
|
|
out["slab"].append({"t":t,"penetration":math.sqrt(t),"spatial_error":errors(s,x),"BE4096_temporal":errors(spectral(t,4096),s),"BE8192_temporal":errors(spectral(t,8192),s),"spectral":s})
|
|
print(json.dumps(out,indent=2))
|
|
|
|
def gauss(n):
|
|
x=[];w=[]
|
|
for i in range(1,n+1):
|
|
z=math.cos(math.pi*(i-.25)/(n+.5))
|
|
for _ in range(30):
|
|
p0,p1=1.,z
|
|
for j in range(2,n+1):p0,p1=p1,((2*j-1)*z*p1-(j-1)*p0)/j
|
|
dp=n*(z*p1-p0)/(z*z-1);nz=z-p1/dp
|
|
if abs(nz-z)<2e-16: z=nz;break
|
|
z=nz
|
|
x.append(z);w.append(2/((1-z*z)*dp*dp))
|
|
return list(zip(x,w))
|
|
def dot(x,y):return sum(a*b for a,b in zip(x,y))
|
|
def cross(a,b):return [a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]
|
|
def geometry(X,xi,eta):
|
|
signs=[(-1,-1),(1,-1),(1,1),(-1,1)]
|
|
N=[(1+s*xi)*(1+t*eta)/4 for s,t in signs]
|
|
D=[[s*(1+t*eta)/4,t*(1+s*xi)/4]for s,t in signs]
|
|
a=[sum(D[i][0]*X[i][j]for i in range(4))for j in range(3)]
|
|
b=[sum(D[i][1]*X[i][j]for i in range(4))for j in range(3)]
|
|
W=cross(a,b);J=math.sqrt(dot(W,W));norm=[z/J for z in W]
|
|
aa,ab,bb=dot(a,a),dot(a,b),dot(b,b);det=J*J
|
|
ra=[(bb*x-ab*y)/det for x,y in zip(a,b)];rb=[(aa*y-ab*x)/det for x,y in zip(a,b)]
|
|
G=[[D[i][0]*ra[j]+D[i][1]*rb[j]for j in range(3)]for i in range(4)]
|
|
return N,G,J,norm
|
|
def surface(X,n):
|
|
M=zeros(4);S=zeros(4)
|
|
for xi,wx in gauss(n):
|
|
for eta,wy in gauss(n):
|
|
N,G,J,no=geometry(X,xi,eta)
|
|
for i in range(4):
|
|
for j in range(4):
|
|
M[i][j]+=N[i]*N[j]*J*wx*wy;S[i][j]+=dot(G[i],G[j])*J*wx*wy
|
|
return M,S
|
|
Z0,Z1=slabmat(2)
|
|
# h=1: Z0 physical mass already; Z1 physical normal stiffness already.
|
|
def mats(X,n):
|
|
M,S=surface(X,n);C=zeros(20);K=zeros(20)
|
|
for i in range(4):
|
|
for j in range(4):
|
|
for a in range(5):
|
|
for b in range(5):
|
|
C[5*i+a][5*j+b]=M[i][j]*Z0[a][b]
|
|
K[5*i+a][5*j+b]=S[i][j]*Z0[a][b]+M[i][j]*Z1[a][b]
|
|
return C,K
|
|
def fn(A):return math.sqrt(sum(x*x for r in A for x in r))
|
|
def rel(A,B):return fn([[a-b for a,b in zip(r,s)]for r,s in zip(A,B)])/fn(B)
|
|
def response(X,C,K):
|
|
A=[[c/.1+k for c,k in zip(cr,kr)]for cr,kr in zip(C,K)]
|
|
T=solve(A,[float(i==0)for i in range(20)])
|
|
flux=[]
|
|
for xi,eta in [(-1/math.sqrt(3),-1/math.sqrt(3)),(1/math.sqrt(3),-1/math.sqrt(3)),(1/math.sqrt(3),1/math.sqrt(3)),(-1/math.sqrt(3),1/math.sqrt(3))]:
|
|
N,G,J,no=geometry(X,xi,eta)
|
|
for off,s in [(0,-1),(0,0),(0,1),(2,-1),(2,0),(2,1)]:
|
|
Ls=[s*(s-1)/2,1-s*s,s*(s+1)/2];Ds=[4*(s-.5),-8*s,4*(s+.5)]
|
|
flux.append([-sum((G[i][j]*Ls[a]+no[j]*N[i]*Ds[a])*T[5*i+off+a]for i in range(4)for a in range(3))for j in range(3)])
|
|
return T,flux
|
|
def rv(a,b):return math.sqrt(sum((x-y)**2 for x,y in zip(a,b)))/math.sqrt(sum(x*x for x in b))
|
|
cases={"affine":[[0,0,0],[1,0,0],[1,1,0],[0,1,0]],"distorted":[[0,0,0],[2,0,0],[1.5,1,0],[0,1,0]]}
|
|
for w in [.1,.5,2]:cases["saddle"+str(w)]=[[x,y,w*x*y]for x,y in [(-1,-1),(1,-1),(1,1),(-1,1)]]
|
|
ans={}
|
|
for name,X in cases.items():
|
|
ds=[]
|
|
for n in [3,5,7]:
|
|
C,K=mats(X,n);T,q=response(X,C,K);ds.append((C,K,T,q))
|
|
rows=[]
|
|
for a,b in [(ds[0],ds[1]),(ds[1],ds[2])]:
|
|
rows.append([rel(a[0],b[0]),rel(a[1],b[1]),rv(a[2],b[2]),max(math.sqrt(dot([x-y for x,y in zip(q,r)],[x-y for x,y in zip(q,r)]))for q,r in zip(a[3],b[3]))/max(math.sqrt(dot(r,r))for r in b[3])])
|
|
ans[name]=rows
|
|
print(json.dumps({"quadrature":ans},indent=2))
|
|
def direct54(X):
|
|
C=zeros(20);K=zeros(20)
|
|
for xi,wx in gauss(3):
|
|
for eta,wy in gauss(3):
|
|
N,G,J,no=geometry(X,xi,eta)
|
|
for off in [0,2]:
|
|
for s,ws in gauss(3):
|
|
ls=[s*(s-1)/2,1-s*s,s*(s+1)/2]
|
|
ds=[4*(s-.5),-8*s,4*(s+.5)]
|
|
H=[0.]*20;B=zeros(20,3)
|
|
for i in range(4):
|
|
for a in range(3):
|
|
p=5*i+off+a;H[p]=N[i]*ls[a]
|
|
B[p]=[G[i][j]*ls[a]+no[j]*N[i]*ds[a]for j in range(3)]
|
|
weight=wx*wy*ws*J/4
|
|
for i in range(20):
|
|
for j in range(20):
|
|
C[i][j]+=weight*H[i]*H[j];K[i][j]+=weight*dot(B[i],B[j])
|
|
return C,K
|
|
for name,X in cases.items():
|
|
C,K=mats(X,3);Cd,Kd=direct54(X)
|
|
print("54point",name,rel(C,Cd),rel(K,Kd))
|
|
assert rel(C,Cd)<1e-12 and rel(K,Kd)<1e-12
|
|
# Generalized eigen residual and C orthogonality, independently reassemble.
|
|
C,K=slabmat(2);Cf=[r[1:]for r in C[1:]];Kf=[r[1:]for r in K[1:]]
|
|
res=fn([[a-b*l for a,b,l in zip(kr,cr,lam)]for kr,cr in zip(mm(Kf,W),mm(Cf,W))])/fn(mm(Kf,W))
|
|
orth=fn([[v-float(i==j)for j,v in enumerate(row)]for i,row in enumerate(mm(mm(tr(W),Cf),W))])
|
|
print("slab eigen residual",res,"C orthogonality",orth)
|
|
assert res<1e-12 and orth<1e-12
|
|
|
|
|
|
def spatial_bounded(n,steps=32,t=.001,ng=12):
|
|
h=1/n;l=6*(1-math.cos(math.pi/n))/(h*h*(2+math.cos(math.pi/n)))
|
|
af=math.exp(-steps*math.log1p(l*t/steps));ase=math.exp(-l*t);ae=math.exp(-math.pi**2*t)
|
|
et=eg=etime=ese=0.
|
|
for i in range(n):
|
|
for s,w in gauss(ng):
|
|
x=(i+(s+1)/2)*h;measure=w*h/2
|
|
v=((1-s)*math.cos(math.pi*i/n)+(1+s)*math.cos(math.pi*(i+1)/n))/2
|
|
g=(math.cos(math.pi*(i+1)/n)-math.cos(math.pi*i/n))/h
|
|
et+=(af*v-ae*math.cos(math.pi*x))**2*measure
|
|
eg+=(af*g+math.pi*ae*math.sin(math.pi*x))**2*measure
|
|
etime+=((af-ase)*v)**2*measure
|
|
ese+=(ase*v-ae*math.cos(math.pi*x))**2*measure
|
|
return math.sqrt(et),math.sqrt(eg),math.sqrt(etime),math.sqrt(ese)
|
|
rows=[]
|
|
for n in [8,16,32,64]:
|
|
a=spatial_bounded(n);b=spatial_bounded(n,ng=16)
|
|
ratio=a[2]/a[3];quad=max(abs(x-y)/abs(x)for x,y in zip(a,b))
|
|
assert ratio<1/32 and quad<1e-8
|
|
rows.append(a)
|
|
fluxbytes=n*n*24*3*32*8
|
|
tempbytes=(n+1)**2*5*33*8
|
|
print("bounded space",n,"T,grad,time,semi",a,"time/space",ratio,"quad",quad,"historybytes",fluxbytes+tempbytes)
|
|
for a,b in zip(rows,rows[1:]):
|
|
pt=math.log(a[0]/b[0],2);pg=math.log(a[1]/b[1],2)
|
|
assert 1.95<=pt<=2.05 and .98<=pg<=1.03
|
|
print("bounded order",pt,pg)
|
|
|
|
time_orders=[]
|
|
for n in [16,32,64,128]:
|
|
e1=abs(math.exp(-n*math.log1p(1/n))-math.exp(-1))
|
|
e2=abs(math.exp(-2*n*math.log1p(1/(2*n)))-math.exp(-1))
|
|
order=math.log(e1/e2,2)
|
|
assert .97<=order<=1.02
|
|
time_orders.append(order)
|
|
print("fixed spatial time orders",time_orders)
|
|
|
|
```
|
|
|
|
## B. Exact Fraction integration·rank·jump
|
|
|
|
```python
|
|
|
|
from fractions import Fraction as F
|
|
def fmat(A,d):return [[F(v,d)for v in r]for r in A]
|
|
def mul(a,b):
|
|
c=[F(0)]*(len(a)+len(b)-1)
|
|
for i,x in enumerate(a):
|
|
for j,y in enumerate(b):c[i+j]+=x*y
|
|
return c
|
|
def der(a):return [i*a[i] for i in range(1,len(a))]
|
|
def integ(a):return sum(F(x,i+1)for i,x in enumerate(a))
|
|
sh=[[1,-3,2],[0,4,-4],[0,-1,2]]
|
|
assert [[integ(mul(a,b))for b in sh]for a in sh]==fmat([[4,2,-1],[2,16,2],[-1,2,4]],30)
|
|
assert [[integ(mul(der(a),der(b)))for b in sh]for a in sh]==fmat([[7,-8,1],[-8,16,-8],[1,-8,7]],3)
|
|
M=fmat([[4,2,1,2],[2,4,2,1],[1,2,4,2],[2,1,2,4]],36)
|
|
S=fmat([[4,-1,-2,-1],[-1,4,-1,-2],[-2,-1,4,-1],[-1,-2,-1,4]],6)
|
|
fac=[([1,-1],[1,-1]),([0,1],[1,-1]),([0,1],[0,1]),([1,-1],[0,1])]
|
|
assert [[integ(mul(a,c))*integ(mul(b,d))for c,d in fac]for a,b in fac]==M
|
|
assert [[integ(mul(der(a),der(c)))*integ(mul(b,d))+integ(mul(a,c))*integ(mul(der(b),der(d)))for c,d in fac]for a,b in fac]==S
|
|
Z0=[[F(0)for _ in range(5)]for _ in range(5)]
|
|
Z1=[[F(0)for _ in range(5)]for _ in range(5)]
|
|
for off in [0,2]:
|
|
for i in range(3):
|
|
for j in range(3):
|
|
Z0[i+off][j+off]+=F([[4,2,-1],[2,16,2],[-1,2,4]][i][j],30)
|
|
Z1[i+off][j+off]+=F([[7,-8,1],[-8,16,-8],[1,-8,7]][i][j],3)
|
|
C=[[M[i//5][j//5]*Z0[i%5][j%5]for j in range(20)]for i in range(20)]
|
|
K=[[S[i//5][j//5]*Z0[i%5][j%5]+M[i//5][j//5]*Z1[i%5][j%5]for j in range(20)]for i in range(20)]
|
|
def pivots(A):
|
|
a=[r[:]for r in A];p=[]
|
|
for k in range(len(a)):
|
|
d=a[k][k];p.append(d)
|
|
for i in range(k+1,len(a)):
|
|
for j in range(k+1,len(a)):a[i][j]-=a[i][k]*a[k][j]/d
|
|
return p
|
|
assert all(x>0 for x in pivots(C))
|
|
assert all(x>0 for x in pivots([r[:19]for r in K[:19]]))
|
|
assert all(sum(r)==0 for r in K)
|
|
assert sum(map(sum,C))==2
|
|
cc=[[F(2),F(1)],[F(1),F(2)]];kk=[[F(1),F(-1)],[F(-1),F(1)]]
|
|
old=[F(0),F(0)]
|
|
for expected,react in [(F(-1,5),F(24,5)),(F(1,25),F(36,25))]:
|
|
new=[(2*(2*old[0]+old[1])-1)/5,F(1)]
|
|
r=[2*sum(cc[i][j]*(new[j]-old[j])for j in range(2))+sum(kk[i][j]*new[j]for j in range(2))for i in range(2)]
|
|
assert new[0]==expected and r==[0,react]
|
|
assert 3*sum(new[j]-old[j]for j in range(2))==react/2
|
|
old=new
|
|
print("exact: C SPD 20/20; K rank19; K1=0; 1TC1=2; jump -1/5,1/25; Rc 24/5,36/25")
|
|
|
|
```
|
|
|
|
## C. Time predicate와 경계
|
|
|
|
```python
|
|
|
|
import math,random,struct
|
|
from fractions import Fraction as F
|
|
def input_cell(x):
|
|
v=F(x);prev=F(math.nextafter(x,-math.inf))
|
|
nxt=math.nextafter(x,math.inf)
|
|
hi=(v+F(nxt))/2 if math.isfinite(nxt) else v+(v-prev)/2
|
|
even=not(struct.unpack('>Q',struct.pack('>d',x))[0]&1)
|
|
return (prev+v)/2,hi,even
|
|
def cell_overlap(t,d,m):
|
|
a,b,ca=input_cell(t);c,e,cb=input_cell(d);c*=m;e*=m
|
|
lo=max(a,c);hi=min(b,e)
|
|
return lo<hi or (lo==hi and (ca or (a<lo<b)) and (cb or (c<lo<e)))
|
|
def dyadic_overlap(t,d,m):
|
|
_,ex=math.frexp(t)
|
|
factor=F(2)**(-ex)
|
|
aa,bb,ca=input_cell(t);cc,ee,cb=input_cell(d)
|
|
ints=[]
|
|
for x in [aa,bb,cc,ee]:
|
|
z=x*factor*2**88
|
|
assert z.denominator==1
|
|
ints.append(z.numerator)
|
|
a,b,c,e=ints;c*=m;e*=m
|
|
assert max(z.bit_length()for z in [a,b,c,e])<=122
|
|
lo=max(a,c);hi=min(b,e)
|
|
return lo<hi or (lo==hi and (ca or (a<lo<b)) and (cb or(c<lo<e)))
|
|
def fm(a,b,c):return float(F(a)*F(b)+F(c))
|
|
def grid_cell(t,d,inc=100):
|
|
if not(math.isfinite(t)and math.isfinite(d)and t>0 and d>0 and type(inc)is int and 1<=inc<=2**32-1):return "input-error"
|
|
if d>=t:return 1,False,t
|
|
if F(t)/F(d)>inc+1:return "INC"
|
|
_,e=math.frexp(t);q=math.ldexp(t,-e)/math.ldexp(d,-e);m=round(q)
|
|
close=cell_overlap(t,d,m)
|
|
assert close==dyadic_overlap(t,d,m)
|
|
if close:n=m
|
|
else:
|
|
factor=F(2)**(-e+88);X=F(t)*factor;Y=F(d)*factor
|
|
assert X.denominator==Y.denominator==1
|
|
X=int(X);Y=int(Y);f=math.floor(q)
|
|
if f*Y>X:f-=1
|
|
elif (f+1)*Y<=X:f+=1
|
|
assert f*Y<=X<(f+1)*Y
|
|
n=f if X==f*Y else f+1
|
|
if n>inc:return "INC"
|
|
if not close:assert n==-(-F(t)//F(d))
|
|
delta=d if close else fm(-(n-1),d,t)
|
|
assert 0<delta<=d and math.isfinite(delta)
|
|
assert (n-1)*d<t
|
|
return n,close,delta
|
|
pairs=[(10.,3.),(2.,3.),(.9,.3),(math.nextafter(.9,math.inf),.3),
|
|
(5.351455173733616,.17262758624947144),
|
|
(20*math.ulp(0.),6*math.ulp(0.)),(17*math.ulp(0.),5*math.ulp(0.)),
|
|
(math.nextafter(math.inf,0.),math.nextafter(math.inf,0.)/3)]
|
|
for t,d in pairs:print("input-cell grid",repr(t),repr(d),grid_cell(t,d))
|
|
rng=random.Random(901)
|
|
count=0
|
|
for i in range(10000):
|
|
m=rng.randint(2,2**32-1);d=math.ldexp(rng.uniform(.5,1),rng.randint(-1073,990))
|
|
t=m*d
|
|
if not(math.isfinite(t)and d>0):continue
|
|
t=math.nextafter(t,math.inf if i%2 else -math.inf)
|
|
if not math.isfinite(t):continue
|
|
q=t/d
|
|
if not math.isfinite(q)or q>2**32:continue
|
|
mm=round(q)
|
|
assert cell_overlap(t,d,mm)==dyadic_overlap(t,d,mm)
|
|
count+=1
|
|
print("input-cell/fixed-dyadic independent comparisons",count)
|
|
assert grid_cell(5.351455173733616,.17262758624947144)[0]==31
|
|
assert grid_cell(math.nextafter(.9,math.inf),.3)[0]==4
|
|
assert grid_cell(20*math.ulp(0.),6*math.ulp(0.))[0]==3
|
|
assert grid_cell(17*math.ulp(0.),5*math.ulp(0.))[0]==4
|
|
print("INPUT-CELL ASSERTIONS PASSED")
|
|
|
|
for t,d in [(1.,.1),(math.ldexp(.9,-1000),math.ldexp(.3,-1000)),(math.ldexp(.9,1000),math.ldexp(.3,1000)),(1e-323,5e-324)]:
|
|
print("input-cell scale",repr(t),repr(d),grid_cell(t,d))
|
|
assert grid_cell(10.,3.,3)=="INC" and grid_cell(10.,3.,4)[0]==4
|
|
assert grid_cell(float(2**32),1.,2**32-1)=="INC"
|
|
for bad in [0,-1,1.5,2**32]:assert grid_cell(1.,.1,bad)=="input-error"
|
|
for bad in [0.,-1.,math.inf,math.nan]:assert grid_cell(bad,.1)=="input-error"
|
|
print("CURRENT TIME GRID ASSERTIONS PASSED")
|
|
|
|
```
|
|
|
|
## D. 입력 rounding-cell의 독립 반증 검색
|
|
|
|
고정 seed의 검색은 폐기된 arithmetic-only predicate와 입력 rounding-cell의 차이를
|
|
검출한다. 이 출력은 predicate 실행 성공과 승인 시간 의미의 정합을 구별하기 위한
|
|
반증 evidence다. 상세 판정은 numerical-review.md §8을 따른다.
|
|
|
|
```python
|
|
|
|
import math,random
|
|
from fractions import Fraction as F
|
|
def rad(x):return max(x-math.nextafter(x,-math.inf),math.nextafter(x,math.inf)-x)/2
|
|
def pred(t,d,m):
|
|
q=t/d;p=m*d
|
|
B=math.nextafter(d*rad(q)+rad(p),math.inf)
|
|
return abs(float(F(t)-m*F(d)))<=B,B
|
|
rng=random.Random(41);found=[]
|
|
for z in range(20000):
|
|
d=rng.uniform(.125,.25);m=rng.randint(2,100);t=m*d
|
|
for dr in [-1,1]:
|
|
tt=math.nextafter(t,math.inf if dr>0 else -math.inf)
|
|
loT=(F(tt)+F(math.nextafter(tt,-math.inf)))/2
|
|
hiT=(F(tt)+F(math.nextafter(tt,math.inf)))/2
|
|
loD=(F(d)+F(math.nextafter(d,-math.inf)))/2
|
|
hiD=(F(d)+F(math.nextafter(d,math.inf)))/2
|
|
overlap=max(loT,m*loD)<=min(hiT,m*hiD)
|
|
ok,B=pred(tt,d,m)
|
|
if overlap and not ok:
|
|
found.append((tt,d,m,float(abs(F(tt)-m*F(d))),B));break
|
|
if found:break
|
|
print("input-cell overlap rejected by retired arithmetic predicate:",found)
|
|
t=math.nextafter(.9,math.inf);d=.3;m=3
|
|
print("specific .9 upper:",float(F(t)-m*F(d)),rad(t)+m*rad(d),"input-cell overlap",abs(F(t)-m*F(d))<=F(rad(t))+m*F(rad(d)))
|
|
|
|
```
|
|
|
|
## E. Library ULP 가정 없는 Fourier interval oracle
|
|
|
|
고정 dyadic 320-bit outward enclosure, Machin π, Taylor sin/cos/exp와 analytic
|
|
remainder를 사용한다. Python Fraction은 검증 generator 전용이다. 출력 interval의
|
|
outward binary64 hex endpoints를 C++ fixture에 고정할 수 있다. FESA solver와
|
|
C++ std::sin/std::exp의 ULP 보장을 가정하지 않는다.
|
|
|
|
```python
|
|
|
|
from fractions import Fraction as F
|
|
import math
|
|
BITS=320;SCALE=1<<BITS
|
|
def down(x):return F((x*SCALE).numerator//(x*SCALE).denominator,SCALE)
|
|
def up(x):return -down(-x)
|
|
def interval(a,b=None):return (down(F(a)),up(F(a if b is None else b)))
|
|
def add(a,b):return down(a[0]+b[0]),up(a[1]+b[1])
|
|
def neg(a):return -a[1],-a[0]
|
|
def sub(a,b):return add(a,neg(b))
|
|
def mul(a,b):
|
|
v=[x*y for x in a for y in b];return down(min(v)),up(max(v))
|
|
def div(a,b):
|
|
assert b[0]>0 or b[1]<0
|
|
return mul(a,(F(1)/b[1],F(1)/b[0]))
|
|
def scale(a,c):return mul(a,interval(c))
|
|
def atan_bounds(q):
|
|
a=sum((F(-1)**j)*q**(2*j+1)/(2*j+1) for j in range(64))
|
|
tail=q**129/129
|
|
return a,a+tail
|
|
a,b=atan_bounds(F(1,5)),atan_bounds(F(1,239))
|
|
PI=interval(16*a[0]-4*b[1],16*a[1]-4*b[0])
|
|
assert PI[1]-PI[0]<F(2)**-295
|
|
def exneg(x):
|
|
assert x[0]>=0
|
|
if x[0]>=512:return interval(0,F(2)**-512)
|
|
assert x[1]<=512
|
|
y=scale(x,F(1,1024));term=interval(1);s=term
|
|
for j in range(1,65):
|
|
term=scale(mul(term,neg(y)),F(1,j));s=add(s,term)
|
|
tail=F(1,2)**65/math.factorial(65)
|
|
s=add(s,interval(-tail,tail))
|
|
for _ in range(10):s=mul(s,s)
|
|
return s
|
|
def sincos_pi8(k):
|
|
k%=16;quad=(k+2)//4;rem=k-4*quad
|
|
x=scale(PI,F(rem,8));x2=mul(x,x)
|
|
st=x;ss=x;ct=interval(1);cs=ct
|
|
for j in range(1,32):
|
|
st=scale(mul(st,neg(x2)),F(1,2*j*(2*j+1)));ss=add(ss,st)
|
|
ct=scale(mul(ct,neg(x2)),F(1,(2*j-1)*2*j));cs=add(cs,ct)
|
|
ss=add(ss,interval(-F(1,math.factorial(65)),F(1,math.factorial(65))))
|
|
cs=add(cs,interval(-F(1,math.factorial(64)),F(1,math.factorial(64))))
|
|
q=quad%4
|
|
return [(ss,cs),(cs,neg(ss)),(neg(ss),neg(cs)),(neg(cs),ss)][q]
|
|
def series_interval(t):
|
|
Ts=[interval(1)for _ in range(5)];qs=[interval(0)for _ in range(5)];E=interval(1)
|
|
for m in range(80):
|
|
odd=2*m+1
|
|
x=scale(mul(PI,PI),F(t)*odd*odd/4);dec=exneg(x)
|
|
for j in range(5):
|
|
si,co=sincos_pi8(odd*j)
|
|
Ts[j]=sub(Ts[j],scale(div(mul(si,dec),PI),F(4,odd)))
|
|
qs[j]=add(qs[j],scale(mul(co,dec),2))
|
|
E=sub(E,scale(div(dec,mul(PI,PI)),F(8,odd*odd)))
|
|
assert max(hi-lo for lo,hi in Ts+qs+[E])<F(2)**-270
|
|
# Uniform analytic tail bound, using safe r upper at minimum time.
|
|
beta=scale(mul(PI,PI),F(t)/4)
|
|
first=exneg(scale(beta,161*161))
|
|
rr=exneg(scale(beta,8*81))
|
|
tailq=div(scale(first,2),sub(interval(1),rr))[1]
|
|
tailt=div(scale(first,4),scale(mul(PI,sub(interval(1),rr)),161))[1]
|
|
Ts=[add(v,interval(-tailt,tailt))for v in Ts]
|
|
qs=[add(v,interval(-tailq,tailq))for v in qs]
|
|
E=add(E,interval(-tailt,tailt))
|
|
return Ts,qs,E,tailt,tailq
|
|
for t in [.001,.01,.05,.1,.5,1.]:
|
|
Ts,qs,ee,tt,tq=series_interval(t)
|
|
assert tt<F(1,10**27) and tq<F(1,10**27)
|
|
# Certified brackets can be exported as outward binary64 hex endpoints for C++ fixtures.
|
|
width=max(float(hi-lo)for lo,hi in Ts+qs+[ee])
|
|
assert width<1e-26
|
|
print("Fourier certified",t,"max-width",width,"tailT",float(tt),"tailq",float(tq),
|
|
"E bracket",math.nextafter(float(ee[0]),-math.inf).hex(),math.nextafter(float(ee[1]),math.inf).hex())
|
|
print("FOURIER INTERVAL ASSERTIONS PASSED")
|
|
|
|
```
|