Files
FESADev/docs/mitc4-shell-feast-eigen2/numerical-review.md
T
2026-10-04 03:32:59 +09:00

272 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MITC4 eigen 독립 검산과 판정
<a id="numerical-review"></a>
<a id="numerical-review--독립-numerical-review"></a>
## 독립 Numerical Review
- Feature ID: `mitc4-shell-feast-eigen2`; Owner: `numerical-review-agent`; revision `r1`.
- 입력: [requirements r1](requirements.md#requirements), [evidence](requirements.md#requirements-evidence),
[새 research](research.md#research), [새 formulation r1](formulation.md), [TOLERANCE](../TOLERANCE.md).
- Verdict: **`pass-for-io-definition`**. 새 수학 계약의 확인된 blocking defect 없음.
- 범위: 문서·독립 수학 검산. 구현, FEAST runtime, build/test, reference comparison, Physics/release 통과 판정이 아니다.
<a id="numerical-review--판단과-독립-근거"></a>
### 판단과 독립 근거
새 질량·stabilization·constraint/FEAST 문서를 실제 작성 후 모두 읽었다.
기존 pass를 전제로 삼지 않고 새 요구 27개와 식·실패 경계를 대조했다.
기준 문서는 [프로젝트 지침](../../AGENTS.md), [PRD](../PRD.md),
[ARCHITECTURE](../ARCHITECTURE.md), [ADR-028](../ADR.md#adr-028)이다.
| 검토 | 판단 |
| --- | --- |
| 운동학·부호·DOF·단위 | `[uX,uY,uZ,thetaX,thetaY,thetaZ]`, `S=-[d]cross`, `S theta=theta cross d`의 signs와 mass cross-block transpose 일치. C0/C1/C2 차원 M/ML/ML²; 길이 congruence 후 eigen/residual norm 일관 |
| 적분·consistent mass | `NiNj z^k J` 최대 성분별 차수4로 27점의 degree5 정확성 충분. Positive weights/J에서 Hq=0인 다항식은 모든 nodal u=0, Si theta=0이므로 physical20 positive, drill null4. 독립 rational 적분으로 C1 비영·행합·first moment 재현 |
| Derived lumped | Partition of unity로 scalar moment row-sum과 `integral rho Ni Gi^TGi` 일치. Strict Cauchy–Schwarz로 rank5/node·central tangent inertia 양수. Jensen 차1/2와 kinetic energy 추가1/2 구분. Orbital inertia/모든 spectrum 동일성을 주장하지 않음 |
| 공변 stabilization | Rotary block trace는 independent tangent orthogonal change에 불변. 여덟 항을 모두 평균하며 양의 초기 scale. Geometry/frame부터 재적분한 complete K/ML와 partial-BC static reaction이 공변; NR-MFE-001 새 식에서 해소 |
| 실제 BC·finite spectrum | Free injection을 먼저 적용. Constrained director exact-zero 교집합만 mass null이고 tiny positive는 유지. Shared director와 positive kd로 KZ=Zdiag(kappa), Kpz=0,Kzz>0; partial BC에 남은 stabilization 보존. 일반 Schur 반례5/2 대 삭제3도 정합 |
| Whitening·안정성 | Mp nodal SPD Cholesky와 `A=L^-1KpL^-T`·`phi=DCPL^-T x`는 exact congruence. Null rank와 numerical conditioning 분리, inverse/floor/pseudoinverse 우회 없음 |
| Lowest N·multiplicity | Fixed a0의 outward enclosure가 낮은 spectrum 부재 근거. 성공 total count=n과 guarded upper search·bounded enclosing fallback, final c=count=m와 c 전체 Gram/residual을 조합. Count empty 성공의 비보장성은 필수 수학 전제에서 제거 |
| Recovery·state | 최종 normalized phi에서 gi/yf를 다시 계산한 full original-free residual·Gram·Rayleigh/energy를 검사. Constrained components exact zero; modal reaction/stress를 새 output으로 만들지 않음. 검증 candidate·writer finalize 후 state commit 경계 유지 |
| 비선형 영향 | Initial kd의 Dkd=D²kd=0으로 기존 objective potential의 residual/Hessian 미분을 유지. Nonlinear Hessian SPD나 초기 Dgamma=linear Td를 주장하지 않음. 선형극한·objectivity·회귀·rollback은 후속 실행 의무 |
Fresh 독립 oracle의 재현 코드와 실제 값은 [numerical-review-evidence](numerical-review.md#numerical-review-evidence)에 있다.
NR-MFE-001은 `kd=19/4800`, `Mff=3/50`, `Kff=10369/7500`,
`lambda=10369/450`, 단위 moment `thetaZ=7500/10369`을 확인했다.
Complete K/ML/response/full reaction 공변성 ratio는 모두 `1.8e-16` 미만이다.
곡면 mass rational oracle와 27점 적분 최대 entry 차는 `2.221e-16` 미만이었다.
<a id="numerical-review--고정한-기준과-후속-인계"></a>
### 고정한 기준과 후속 인계
- [내부 controls](numerical-review-controls.md): 검토 후 채택한 기존 C1–C4/C6/C7 수치 정의,
새 fixed-lower C5·finite budgets·seed·conditioning·raw classification. 기존 workflow/제외 portfolio/과거 pass는 미채택.
- [27개 verification cases](numerical-review-cases.md): requirement 27개별 analytical/invariant,
actual FEAST·실패 주입·남는 static coverage. Expected value는 production 재호출로 생성하지 않는다.
- [reference-model](reference-model.md): 원본 TXT의 literal `Frame: Mode i`와 유일한 source
`Step-2`에서 per-modal-frame 10 singleton Value families를 새로 판정했다. 공통 정책 그대로
행5%·RMS1%, 이번 singleton의 실효 각 Value1%; 모두 blocking. 외부 mode shape gate 없음.
I/O는 정확한 입력 allowlist·diagnostic·영속 HDF5 경로 및 source/modal identity를 정의한다.
최소한 used lumped mass/backend, N개 public pair와 전체 c개 검증의 재현 evidence,
raw negative/nearzero와 frequency validity, 실패한 optional optimization·fallback까지의
count/solve history를 의미 손실 없이 연결해야 한다. 데이터 layout은 NR가 선점하지 않는다.
<a id="numerical-review--잔여-위험미실행"></a>
### 잔여 위험·미실행
Research G01/G02의 installed count-empty/endpoint/workspace·ABI probes, n1/N=n/allzero/
repeated/clustered actual FEAST와 shifted sparse factorization memory는 **NOT RUN**이다.
현재 수학 blocker는 아니지만 구현 완료 시 필수다. 넓은 enclosing fallback도 runtime 실패나
O(n²) 메모리 비용을 없애지 않으며 bounded failure와 기존 결과 보존이 필요하다.
얇은 shell·거의 정렬된 partial BC의 positive mass conditioning, 왜곡/곡면 MITC4의
locking·accuracy, 27점 밖 geometry injectivity 및 floating count의 정확성 한계가 남는다.
이 한계를 arbitrary rank cutoff·reference fitting·tolerance 완화로 숨기지 않는다.
남는 shell/shellNL/linear-limit fresh solves와 nonlinear tangent/objectivity·balance는 미실행이다.
제외된 네 예제의 원본·복사·cache를 읽거나 사용하지 않았다. Pressure fresh 외부 coverage 부재를 명시한다.
확인된 수학 correction request는 없다. 구현 owner는 명시 controls/cases를 실제 runtime과
연결하고 I/O owner는 reference/validation projection을 보존해야 한다. Source/tests,
upstream 계약·공통 tolerance·reference artifacts·Git은 수정하지 않았다.
<a id="numerical-review-evidence"></a>
<a id="numerical-review-evidence--독립-수학-검산-evidence"></a>
## 독립 수학 검산 evidence
- Feature ID: `mitc4-shell-feast-eigen2`
- 검토일: `2026-09-08`; 기본 보고서: [numerical-review](numerical-review.md#numerical-review).
- 대상: 새 requirement r1. 아래는 Python 표준 라이브러리만 사용하는 수학 검산이며 C++/FEAST 실행이 아니다.
<a id="numerical-review-evidence--nr-mfe-001의-geometry부터-재계산"></a>
### 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를 유지한다. 아래는 [이전 독립 적분](../mitc4-shell-feast-eigen/numerical-review.md#numerical-review-evidence)의
평판 membrane/curvature/MITC edge-work를 검토한 뒤 geometry edge에서 길이·axes·director를
다시 얻도록 구성한 새 실행이다. 기존 수치 출력이나 저장 K를 입력으로 쓰지 않는다.
전역 24×24 physical K를 직접 적분하고 새 director의 least-aligned tangent를 재생성해
여덟 rotary diagonal의 trace mean을 계산한다. 비교 대상 congruence에만 원 K를 회전한다.
Norm에는 ell=4, D=diag(ell I3,I3)를 사용하여 matrix는 D^T A D,
response는 D^-1 q, force/moment residual은 D^T r로 같은 단위를 만든다.
```python
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)
```
<a id="numerical-review-evidence--독립-rational-mass-oracle"></a>
### 독립 rational mass oracle
다음 coefficient-dictionary 적분은 production shape/J/질량 함수를 사용하지 않는다.
이전 문서의 rational fixture 자체는 재사용하지만 모든 계수·행합·first moment를 이번에
다시 적분한다. Kernel-level director fixture이며 input explicit-normal 지원을 추가하지 않는다.
```python
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)))
```
<a id="numerical-review-evidence--이번-실행-결과"></a>
### 이번 실행 결과
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.658e-16`, `7.851e-17`, `1.535e-16`, `1.706e-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 통과가 아니다.
최종 정적 Python audit는 owned Markdown 5개, 상대 파일 링크 38개, requirement 27개와
case 27개의 유일·연속 identity, UTF-8/whitespace/final newline을 검사했고 exit `0`이었다.
두 oracle 모두 exit `0`; 이후 norm의 length congruence 명시를 코드에 반영하고 영향받은
첫 oracle를 재실행하여 exit `0` 및 위 최종 scaled ratio를 확인했다. 식·판정 한계는 불변이다.