9.7 KiB
독립 수학 검산 evidence
- Feature ID:
mitc4-shell-feast-eigen2 - Owner:
numerical-review-agent - 검토일:
2026-09-08; 기본 보고서: numerical-review. - 대상: 새 requirement r1. 아래는 Python 표준 라이브러리만 사용하는 수학 검산이며 C++/FEAST 실행이 아니다.
NR-MFE-001의 geometry부터 재계산
a=(0,1,0), b=(-4/5,0,3/5), d=(3/5,0,4/5)이고
X=[-2a-b,2a-b,2a+b,-2a+b], E=12, nu=0, rho=t=1이다.
Node 1 URZ만 free이고 나머지 23 DOF는 fixed다. Z45° 회전은 같은 physical
free subspace를 유지한다. 아래는 이전 독립 적분의
평판 membrane/curvature/MITC edge-work를 검토한 뒤 geometry edge에서 길이·axes·director를
다시 얻도록 구성한 새 실행이다. 기존 수치 출력이나 저장 K를 입력으로 쓰지 않는다.
전역 24×24 physical K를 직접 적분하고 새 director의 least-aligned tangent를 재생성해
여덟 rotary diagonal의 trace mean을 계산한다. 비교 대상 congruence에만 원 K를 회전한다.
from math import sqrt, fsum
from fractions import Fraction as F
def dot(a,b): return fsum(x*y for x,y in zip(a,b))
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 norm(a): return sqrt(dot(a,a))
def unit(a): return tuple(x/norm(a) for x in a)
def sub(a,b): return tuple(x-y for x,y in zip(a,b))
def rz(v): return ((v[0]-v[1])/sqrt(2),(v[0]+v[1])/sqrt(2),v[2])
def action(A,x): return [dot(row,x) for row in A]
def rotate_vector(v): return [x for i in range(0,len(v),3) for x in rz(v[i:i+3])]
def rotate_matrix(A):
left=list(zip(*(rotate_vector(c) for c in zip(*A))))
return [rotate_vector(row) for row in left]
def err(a,b): return norm(sub(a,b))/(norm(a)+norm(b))
D=[4. if i%6<3 else 1. for i in range(24)]
def merr(A,B): return err([D[i]*A[i][j]*D[j] for i in range(24) for j in range(24)],[D[i]*B[i][j]*D[j] for i in range(24) for j in range(24)])
def qerr(a,b): return err([x/s for x,s in zip(a,D)],[x/s for x,s in zip(b,D)])
def rerr(a,b): return err([x*s for x,s in zip(a,D)],[x*s for x,s in zip(b,D)])
def build(X):
edge1=sub(X[1],X[0]); edge2=sub(X[3],X[0])
lx,ly=norm(edge1),norm(edge2); ex,ey=unit(edge1),unit(edge2)
d=unit(cross(ex,ey)); area=lx*ly
K=[[0.]*24 for _ in range(24)]
for xi in [-1/sqrt(3),1/sqrt(3)]:
for eta in [-1/sqrt(3),1/sqrt(3)]:
fields=[]
for sx,sy in [(-1,-1),(1,-1),(1,1),(-1,1)]:
nx=sx*(1+sy*eta)/(2*lx); ny=sy*(1+sx*xi)/(2*ly)
tx=(1+sy*eta)/4; ty=(1+sx*xi)/4
for c in range(3):
fields.append(([nx*ex[c],ny*ey[c],ny*ex[c]+nx*ey[c]],[0,0,0],[nx*d[c],ny*d[c]]))
for c in range(3):
fields.append(([0,0,0],[nx*ey[c],-ny*ex[c],ny*ey[c]-nx*ex[c]],[tx*ey[c],-ty*ex[c]]))
for i,(mi,bi,si) in enumerate(fields):
for j,(mj,bj,sj) in enumerate(fields):
K[i][j]+=area/4*(12*mi[0]*mj[0]+12*mi[1]*mj[1]+6*mi[2]*mj[2]+bi[0]*bj[0]+bi[1]*bj[1]+.5*bi[2]*bj[2]+5*dot(si,sj))
axis=min(range(3),key=lambda k:(abs(d[k]),k))
ta=unit(tuple(float(c==axis)-d[axis]*d[c] for c in range(3)))
tb=cross(d,ta); diag=[]
for i in range(4):
for tangent in [ta,tb]:
v=[0.]*24; v[6*i+3:6*i+6]=tangent
diag.append(dot(v,action(K,v)))
kd=fsum(diag)/8000
M=[[0.]*24 for _ in range(24)]
for i in range(4):
for a in range(3):
M[6*i+a][6*i+a]=area/4
for b in range(3):
K[6*i+3+a][6*i+3+b]+=kd*d[a]*d[b]
M[6*i+3+a][6*i+3+b]=area/48*(float(a==b)-d[a]*d[b])
return K,M,kd,d,ey,diag
a=(0.,1.,0.); b=(-.8,0.,.6)
X=[tuple(s*a[k]+t*b[k] for k in range(3)) for s,t in [(-2,-1),(2,-1),(2,1),(-2,1)]]
K,M,kd,d,ey,diags=build(X)
Kr,Mr,kdr,dr,eyr,diagsr=build([rz(x) for x in X])
load=[0.]*24; load[3:6]=[5*x/3 for x in ey]; lr=rotate_vector(load)
q=[0.]*24; qr=[0.]*24; q[5]=load[5]/K[5][5]; qr[5]=lr[5]/Kr[5][5]
r=sub(action(K,q),load); rr=sub(action(Kr,qr),lr)
targets=[float(F(19,4800)),float(F(10369,7500)),float(F(3,50)),float(F(10369,450)),float(F(7500,10369))]
actual=[kd,K[5][5],M[5][5],K[5][5]/M[5][5],q[5]]
for v,w in zip(actual,targets): assert abs(v-w)/(abs(v)+abs(w))<1e-12
cov=[merr(Kr,rotate_matrix(K)),merr(Mr,rotate_matrix(M)),qerr(qr,rotate_vector(q)),rerr(rr,rotate_vector(r))]
assert max(cov)<1e-12
assert abs(kd-kdr)/(abs(kd)+abs(kdr))<1e-12
assert abs(dot(d,load[3:6]))/norm(load[3:6])<1e-12
for A,v,f,residual in [(K,q,load,r),(Kr,qr,lr,rr)]:
assert abs(residual[5])/(abs(A[5][5]*v[5])+abs(f[5]))<=1e-10
print('partial_bc=',actual)
print('rotated_partial_bc=',[kdr,Kr[5][5],Mr[5][5],Kr[5][5]/Mr[5][5],qr[5]])
print('covariance_K_M_q_reaction=',cov)
print('fresh_rotary_diagonals=',diags,diagsr)
독립 rational mass oracle
다음 coefficient-dictionary 적분은 production shape/J/질량 함수를 사용하지 않는다. 이전 문서의 rational fixture 자체는 재사용하지만 모든 계수·행합·first moment를 이번에 다시 적분한다. Kernel-level director fixture이며 input explicit-normal 지원을 추가하지 않는다.
from fractions import Fraction as F
from itertools import permutations, product
from math import sqrt
def const(x): return {(0,0,0):F(x)}
def add(*args):
out={}
for a in args:
for p,c in a.items(): out[p]=out.get(p,F(0))+c
return {p:c for p,c in out.items() if c}
def scale(a,c): return {p:v*c for p,v in a.items() if v*c}
def mul(a,b):
out={}
for p,c in a.items():
for q,d in b.items():
key=tuple(x+y for x,y in zip(p,q));out[key]=out.get(key,F(0))+c*d
return {p:c for p,c in out.items() if c}
def diff(a,k):
out={}
for p,c in a.items():
if p[k]:
q=list(p);q[k]-=1;out[tuple(q)]=c*p[k]
return out
def integ(a): return sum((c*F(8,(p[0]+1)*(p[1]+1)*(p[2]+1)) for p,c in a.items() if all(v%2==0 for v in p)),F(0))
def value(a,x): return sum(float(c)*x[0]**p[0]*x[1]**p[1]*x[2]**p[2] for p,c in a.items())
v=[{tuple(int(k==i) for k in range(3)):F(1)} for i in range(3)]
N=[scale(mul(add(const(1),scale(v[0],sx)),add(const(1),scale(v[1],sy))),F(1,4)) for sx,sy in [(-1,-1),(1,-1),(1,1),(-1,1)]]
X=[(-1,-1,F(1,10)),(1,-1,-F(1,10)),(1,1,F(1,5)),(-1,1,-F(1,10))]
d=[(0,0,1),(F(3,5),0,F(4,5)),(F(2,3),F(1,3),F(2,3)),(0,F(3,5),F(4,5))]
z=scale(v[2],F(1,4));pos=[add(*(mul(N[i],add(const(X[i][c]),scale(z,d[i][c]))) for i in range(4))) for c in range(3)]
J={}
for perm in permutations(range(3)):
inversions=sum(perm[i]>perm[j] for i in range(3) for j in range(i+1,3))
term=const((-1)**inversions)
for c in range(3): term=mul(term,diff(pos[c],perm[c]))
J=add(J,term)
rhoJ=scale(J,F(3));zk=[const(1),z,mul(z,z)]
polys=[[[mul(mul(mul(rhoJ,N[i]),N[j]),zk[k]) for j in range(4)] for i in range(4)] for k in range(3)]
C=[[[integ(p) for p in row] for row in mat] for mat in polys]
rows=[[sum(row,F(0)) for row in mat] for mat in C]
assert [mat[0][0] for mat in C]==[F(17609,28800),F(11003,1440000),F(88177,6912000)]
assert [sum(row,F(0)) for row in rows]==[F(7743,1600),F(739,12000),F(517,5120)]
first=[sum((rows[0][i]*X[i][c]+rows[1][i]*d[i][c] for i in range(4)),F(0)) for c in range(3)]
assert first==[integ(mul(rhoJ,p)) for p in pos]
g=[(-sqrt(3/5),5/9),(0.,8/9),(sqrt(3/5),5/9)]
maxerr=0.;jvals=[]
for q in product(g,repeat=3): jvals.append(value(J,[s[0] for s in q]))
for k,i,j in product(range(3),range(4),range(4)):
approx=sum(value(polys[k][i][j],[s[0] for s in q])*q[0][1]*q[1][1]*q[2][1] for q in product(g,repeat=3))
maxerr=max(maxerr,abs(approx-float(C[k][i][j])))
assert min(jvals)>0 and maxerr<1e-12
central=[rows[2][i]-rows[1][i]**2/rows[0][i] for i in range(4)]
assert min(central)>0
print('mass_C11=',[str(mat[0][0]) for mat in C])
print('mass_rows=',[[str(x) for x in row] for row in rows])
print('mass_first_moment=',[str(x) for x in first])
print('mass_Jrange_Gauss_error_central=',min(jvals),max(jvals),maxerr,float(min(central)))
이번 실행 결과
2026-09-08 PowerShell here-string에서 이 문서의 두 Python block을 추출하고
subprocess.run([sys.executable,'-B','-'])으로 각각 실행했다. Oracle 1/2 exit 0이다.
| 항목 | 이번 직접 계산 |
|---|---|
kd, Kff, Mff |
0.003958333333333333, 1.3825333333333332, 0.05999999999999998 |
lambda 원/회전 |
23.04222222222223 / 23.042222222222208; exact 10369/450 [T^-2] |
단위 moment의 thetaZ 원/회전 |
0.7233098659465717 / 0.7233098659465714; exact 7500/10369 |
Complete K, ML, response, full reaction 공변성 ratio |
1.483e-16, 7.915e-17, 1.535e-16, 1.654e-16 |
| 원/회전 free residual raw | 0, -1.1102230246251565e-16; 기존 scaled 1e-10 기준 충족 |
Rational C0_11,C1_11,C2_11 |
17609/28800, 11003/1440000, 88177/6912000 |
sum m,sum h,sum j |
7743/1600, 739/12000, 517/5120 |
| Spatial first moment 두 독립식 | Exact equal: (-79747/360000,-685951/2880000,49651/320000) |
| 27점 J min/max | 0.1285991922161525 / 0.2701341411171808 |
| Gauss vs rational 최대 entry absolute error | 2.220446049250313e-16 |
| 최소 nodal central inertia | 0.022318161338791092 > 0 |
최초 oracle 1 exit 1은 회전 후 raw free residual에 잘못 추가한 exact-zero assertion이었다.
반력은 일반 floating arithmetic이며 원 계약은 scaled residual 1e-10이다.
이를 기존 기준으로 수정하고 raw 값을 보존했다. Exact constrained displacement zero 조건,
정식화 또는 승인 tolerance를 변경한 것이 아니다.
외부 solver, FEAST runtime, build/test, retained static fresh solve 및 HDF5–reference comparison은 NOT RUN이다. 위 항등식의 통과는 구현/reference 통과가 아니다.