163 lines
10 KiB
Markdown
163 lines
10 KiB
Markdown
# HHT 독립 시간정확도 검산
|
||
|
||
[검토 보고서](numerical-review.md)의 상세 evidence다. 2026-09-22 Python 표준 라이브러리만 사용했다. FESA solver, Abaqus 또는 reference 비교 실행이 아니다. 아래 오차는 승인 tolerance를 새로 정하는 값이 아니라 정확해와의 수렴 관찰값이다.
|
||
|
||
## 모델과 결과
|
||
|
||
1. Smooth SDOF: `T=.5(1+q²)v²`, `c=qv²`, `q_exact=.4+sin(t)`, `f=(1+q_exact²)a_exact+q_exact v_exact²`, `0≤t≤1`. 초기 `q=.4,v=1,a=0`. 입력은 일관된 무차원 검산 단위다. `alpha=-.05,beta=.275625,gamma=.55`.
|
||
2. Moving-null: `x(q)=q/|q|`, `T=.5|Dx(q)v|²`, `U=.5(|q|-1)²`, `q∈R²\{0}`. `r=|q|`, `d=q/r`, `M=(I-ddᵀ)/r²`, `c=-2(q·v)Mv/r²`, `f_int=(r-1)d`. `ker M=span(q)`가 회전한다. 정확해 `q=(cos t,sin t)`, `v=(-sin t,cos t)`, `a=-q`; 초기 위치·속도·가속도 대수조건은 일관된다. Endpoint 반경 대수조건 `r=1`은 각도 parameterization으로 정확하게 강제하며 tangent 평형만 Newton으로 푼다. 이 모델은 shell/drilling 구현이나 일반 singular-DAE 정리의 대체물이 아니다.
|
||
|
||
| SDOF 방법 | `h=1/512` U 오차 | `h=1/1024` U 오차 | 관측 차수 |
|
||
| --- | ---: | ---: | ---: |
|
||
| Naive `M1 A1+(1+alpha)g1-alpha g0=0` | 1.0272820368e-5 | 5.2200068232e-6 | 0.976709 |
|
||
| Weighted mass `((1+alpha)M1-alpha M0)A1+(1+alpha)g1-alpha g0=0` | 3.2914014847e-7 | 8.2299941973e-8 | 1.999739 |
|
||
| Endpoint physical `M1 a1+g1=0`, `A1=(1+alpha)a1-alpha a0` | 2.7561411953e-7 | 6.8902116013e-8 | 2.000030 |
|
||
|
||
`g=c-f`. 마지막 방법의 physical acceleration 오차는 `2.3176116737e-7 → 5.7545108678e-8`로 줄지만 algorithmic `A` 오차는 `5.2612378749e-5 → 2.6344436092e-5`다. 따라서 `A`를 endpoint physical acceleration으로 보고하면 안 된다.
|
||
|
||
Moving-null 마지막 방법은 같은 두 h에서 U 오차 `4.0606413493e-7 → 1.0152120526e-7`, V 오차 `4.1505360817e-7 → 1.0376522277e-7`, physical a 오차 `4.1511310820e-7 → 1.0262747089e-7`를 보였다. `max|q·v|`는 `8.4282e-11 → 1.0594e-11`, `max|q·a+v²|`는 `7.9368e-7 → 1.9445e-7`다. Endpoint 위치 대수조건을 지켜도 미분된 속도·가속도 조건이 매 frame 정확히 0이 되는 것은 아니다. 이 결함의 시간수렴과 초기 일관성을 별도로 검증해야 한다.
|
||
|
||
## 독립 수학 대조
|
||
|
||
Smooth 물리식 `M(q)a+g(q,v,t)=0`에서 Newmark 내부 가속도의 정확해 대응은 `A=a+alpha*h*adot+O(h²)`다. Naive 식의 일차 결함은
|
||
|
||
`alpha*h*(M*adot+gdot)=-alpha*h*Mdot*a`.
|
||
|
||
따라서 `Mdot*a≠0`인 일반 문제에서 상수 M 증명을 그대로 사용할 수 없다. Weighted mass는 이 항을 상쇄하지만 `alpha∈(-1,0)`이면 서로 다른 두 시점의 양의 준정부호 질량의 양의 결합이다. 그 영공간은 두 영공간의 교집합이므로, 영공간이 회전하면 rank가 늘어날 수 있다. 단순 SDOF의 2차 결과가 이를 정당화하지 않는다.
|
||
|
||
Physical/algorithmic 가속도 분리는 [Brüls–Arnold 원문](https://orbi.uliege.be/bitstream/2268/27088/1/pa_BrulsArnold2008_TheGeneralizedAlphaSchemeAsALinearMultistepIntegrator.pdf)의 Eq. (5), (7), (8)에 `alpha_m=0,alpha_f=-alpha`를 대입한 것이다. 원문의 p.6 equivalent-model/index-3 가정은 비특이 mass 및 제약 정칙성을 사용한다. Singular drilling mass에 그 증명을 직접 적용하지 않는다. 이 문서의 제조해와 결함 계산은 독립 검산이다.
|
||
|
||
## 재현 코드와 명령
|
||
|
||
Workspace root에서 다음 명령으로 아래 Python block만 실행한다. 외부 package, 파일 생성 또는 solver 실행은 없다.
|
||
|
||
```powershell
|
||
python -c 'from pathlib import Path; s=Path("docs/hht-implicit-dynamics/numerical-review-probes.md").read_text(encoding="utf-8"); exec(s.split(chr(96)*3+"python\n",1)[1].split(chr(96)*3,1)[0])'
|
||
```
|
||
|
||
```python
|
||
import math
|
||
|
||
alpha = -.05
|
||
beta = (1-alpha)**2/4
|
||
gamma = .5-alpha
|
||
|
||
def exact(t):
|
||
return .4+math.sin(t), math.cos(t), -math.sin(t)
|
||
|
||
def force(t):
|
||
q, v, a = exact(t)
|
||
return (1+q*q)*a+q*v*v
|
||
|
||
def scalar(h, variant):
|
||
q, v, ap = exact(0)
|
||
A = ap
|
||
for n in range(round(1/h)):
|
||
t = (n+1)*h
|
||
m0 = 1+q*q
|
||
g0 = q*v*v-force(n*h)
|
||
oldap = ap
|
||
pred = q+h*v+h*h*(.5-beta)*A
|
||
vpred = v+h*(1-gamma)*A
|
||
def calc(x):
|
||
an = (x-pred)/(beta*h*h)
|
||
vn = vpred+gamma*h*an
|
||
m = 1+x*x
|
||
if variant == 'physical':
|
||
pn = (an+alpha*oldap)/(1+alpha)
|
||
r = m*pn+x*vn*vn-force(t)
|
||
dr = 2*x*pn+m/((1+alpha)*beta*h*h)+vn*vn+2*x*vn*gamma/(beta*h)
|
||
else:
|
||
me = m if variant == 'naive' else (1+alpha)*m-alpha*m0
|
||
dm = 2*x if variant == 'naive' else (1+alpha)*2*x
|
||
r = me*an+(1+alpha)*(x*vn*vn-force(t))-alpha*g0
|
||
dr = dm*an+me/(beta*h*h)+(1+alpha)*(vn*vn+2*x*vn*gamma/(beta*h))
|
||
pn = an
|
||
return r, dr, vn, an, pn
|
||
x = pred+beta*h*h*A
|
||
for _ in range(20):
|
||
r, dr, *_ = calc(x)
|
||
dx = -r/dr
|
||
x += dx
|
||
if abs(dx) < 1e-14:
|
||
break
|
||
else:
|
||
raise RuntimeError('scalar Newton failed')
|
||
q = x
|
||
_, _, v, A, ap = calc(q)
|
||
qe, ve, ae = exact(1)
|
||
return abs(q-qe), abs(v-ve), abs(ap-ae), abs(A-ae)
|
||
|
||
def moving_null(h):
|
||
q, v, ap = [1.,0.], [0.,1.], [-1.,0.]
|
||
A = ap[:]
|
||
theta = 0.
|
||
max_cv = max_ca = 0.
|
||
dot = lambda x,y: sum(x[i]*y[i] for i in range(2))
|
||
for _ in range(round(1/h)):
|
||
pred = [q[i]+h*v[i]+h*h*(.5-beta)*A[i] for i in range(2)]
|
||
vp = [v[i]+h*(1-gamma)*A[i] for i in range(2)]
|
||
old = ap[:]
|
||
theta += h
|
||
def calc(angle):
|
||
d = [math.cos(angle),math.sin(angle)]
|
||
tangent = [-d[1],d[0]]
|
||
an = [(d[i]-pred[i])/(beta*h*h) for i in range(2)]
|
||
vn = [vp[i]+gamma*h*an[i] for i in range(2)]
|
||
pn = [(an[i]+alpha*old[i])/(1+alpha) for i in range(2)]
|
||
dv, tv = dot(d,vn), dot(tangent,vn)
|
||
residual = dot(pn,tangent)-2*dv*tv
|
||
derivative = 1/((1+alpha)*beta*h*h)-dot(pn,d)-2*(tv*tv+dv*(-dv+gamma/(beta*h)))
|
||
return residual, derivative, d, vn, pn, an
|
||
for _ in range(15):
|
||
r, dr, *_ = calc(theta)
|
||
dx = -r/dr
|
||
theta += dx
|
||
if abs(dx) < 1e-14:
|
||
break
|
||
else:
|
||
raise RuntimeError('moving-null Newton failed')
|
||
_, _, q, v, ap, A = calc(theta)
|
||
max_cv = max(max_cv,abs(dot(q,v)))
|
||
max_ca = max(max_ca,abs(dot(q,ap)+dot(v,v)))
|
||
qe = [math.cos(1),math.sin(1)]
|
||
ve = [-math.sin(1),math.cos(1)]
|
||
ae = [-x for x in qe]
|
||
errors = [math.hypot(*[x[i]-y[i] for i in range(2)]) for x,y in ((q,qe),(v,ve),(ap,ae))]
|
||
return *errors, max_cv, max_ca
|
||
|
||
for variant in ('naive','weighted_mass','physical','moving_null'):
|
||
print(variant)
|
||
previous = None
|
||
for k in range(5,11):
|
||
h = 2**(-k)
|
||
errors = moving_null(h) if variant == 'moving_null' else scalar(h,variant)
|
||
order = math.log(previous/errors[0],2) if previous else float('nan')
|
||
print(f'h={h:.9g} errors='+','.join(f'{x:.10e}' for x in errors)+f' pU={order:.6f}')
|
||
previous = errors[0]
|
||
```
|
||
|
||
Newton correction cutoff `1e-14`는 이 독립 double-precision probe의 반복 종료값이다. Solver convergence 또는 reference acceptance tolerance로 채택하지 않는다. 미세 h의 acceleration 오차에는 displacement 차분의 roundoff가 포함되므로 표의 관측값을 정확한 수학 상수로 해석하지 않는다.
|
||
|
||
## 후속 full Cartesian moving-null 검산
|
||
|
||
추가 Numerical 작업에서 메인이 [full_coordinate_probe.py](../../.harness/build/hht-numerical/full_coordinate_probe.py)를 작성했고 수치 검토자가 residual/Jacobian 식을 읽고 아래 명령을 재실행했다. Exit `0`, 2026-09-22. 이 script는 ignored build evidence이며 FESA source가 아니다. 위 angle-reduced probe와 달리 q의 두 Cartesian 성분을 직접 Newton으로 풀며 radial projection·normalization을 하지 않는다. 같은 `x=q/|q|` 모델의 exact 2×2 Jacobian을 사용한다.
|
||
|
||
```powershell
|
||
python .harness/build/hht-numerical/full_coordinate_probe.py
|
||
```
|
||
|
||
오차는 각 h의 **모든 시점**에서 q/v/physical a의 2-norm 최대다. 앞 절의 종단 오차와 다른 측정량이다.
|
||
|
||
| h | max q error | max v error | max physical a error | 직전 h 대비 q/v/a 차수 |
|
||
| --- | ---: | ---: | ---: | --- |
|
||
| 1/128 | 6.4948801161e-6 | 6.6395874231e-6 | 1.2690547915e-5 | 1.99932 / 1.99962 / 2.01024 |
|
||
| 1/256 | 1.6240799623e-6 | 1.6601117645e-6 | 3.1667175260e-6 | 1.99968 / 1.99981 / 2.00269 |
|
||
| 1/512 | 4.0606299832e-7 | 4.1505196280e-7 | 7.9259171465e-7 | 1.99985 / 1.99992 / 1.99834 |
|
||
|
||
마지막 grid의 최대 `||q|-1|=2.22045e-16`, `|q·v|=8.39836e-11`, `|q·a+v²|=7.91118e-7`. 상쇄 전 `Ma`의 entry products·c·f_int를 사용한 최대 normwise residual은 `7.94824e-11`, Newton 최대 3 corrections, 마지막 correction 최대 `4.00220e-16`이었다. 이 probe의 종료는 correction `<=1e-13`이며 N2의 모든 AND gate를 구현한 solver가 아니다. 위 관찰을 N2–N7 전체 통과나 실제 shell 통과로 확대하지 않는다.
|
||
|
||
별도 trial `q=(.85,.52)`, predictor `(.83,.50)`, velocity predictor `(-.35,.71)`, old physical a `(-.7,-.3)`, h=.1, direction `(.6,-.8)`의 exact effective Jacobian을 중앙차분했다. N5의 j=8,10,12,14,16,18,20 순서에서 상대 오차는 `[3.13656e-5,1.96035e-6,1.22522e-7,7.65779e-9,4.77929e-10,3.25134e-11,1.04250e-11]`이다. 연속한 내부 sweep 점 j=12/14 등이 `1e-6`을 만족한다. 이는 이 2D trial/direction에 대한 미분 evidence이며 shell D3 oracle를 대체하지 않는다.
|
||
|
||
Full-coordinate 결과는 새로운 [C04b](verification-cases.md)의 moving-null 구조와 max-time 측정을 지지한다. C04b의 전체 prescribed grids, 초기화 실패 mutation, regularity·scale·반복오차 분리 등 모든 판정을 실행한 것은 아니다. 더 미세한 h에서 residual이 증가할 수 있으므로 h=1/1024까지 같은 residual 기준으로 통과했다는 주장을 하지 않는다.
|