Skip to content
cfd-lab:~/ja/posts/2026-05-13-von-neumann-a…● online

この記事は著者が韓国語で書いた原文の翻訳です。 原文(한국어)を読む

NOTE #040DAY WED CFD기법DATE 2026.05.13UPDATED 2026.10.19READ 5 min read#CFD#Shock-Capturing#Artificial-Viscosity#Staggered-Grid#Historical

1950年、von Neumannは衝撃波をわざとぼかした — 人工粘性とodd-evenデカップリング

密度が1と2の鋸歯でも二セル差分の圧力は0です。collocated格子はその力を見ません。

ロス・アラモス、1950年。John von Neumann と Robert Richtmyer が Journal of Applied Physics に短い論文を載せました。タイトルは "A Method for the Numerical Calculation of Hydrodynamic Shocks"。衝撃が格子の一セルに乗らないなら、先にぼかしてしまう。その一行です。

左が ρ=1,p=1\rho = 1, p = 1、右が ρ=0.125,p=0.1\rho = 0.125, p = 0.1 で、t=0t = 0 に膜を抜いた Sod 管です。ξ スライダーを 0 から 5 へ上げると、衝撃の後ろの sawtooth が静まり、不連続は数セルに広がります。

Sod shock tube. ξ=0: post-shock oscillations. ξ≈2-3: shock smeared over a few cells, wiggles damped.

ξ = 0 では衝撃の後ろが鋸歯で、ξ = 2 付近では振動が消えて衝撃は 2〜3 セルに残り、ξ = 5 ではプラトーまで波打ちます。von Neumann が推した ξ≈2\xi \approx 2〜33 がその間の位置です。

人工粘性がなぜ効くか、厚さをどれだけ残すか、collocated 格子の odd-even までがこのノートの範囲です。最後に NumPy で Mach 2 の管を一度つかまえます。

格子上の衝撃波は振動する

理想化した Euler 方程式に粘性はありません。だから本物の衝撃波は分子平均自由行程程度の幅に圧縮されます。CFD のセルはその約 100 万倍幅広い。1 セルの中で密度と圧力が二倍に跳ねろという話です。

この不連続を保存型差分スキームに通すと、ほぼ必ず現れる現象があります。衝撃面の後ろに立ち上がる sawtooth 状の振動、いわゆる wiggle です。von Neumann は 1944 年のマンハッタン計画時代にすでにこの問題に出会っていました。爆発シミュレーションのポストショック領域が荒れると、TNT 当量の計算が丸ごとずれます。

原因は単純です。1 次 upwind のように強い数値粘性は衝撃を捕らえる代わりに他のすべてを鈍らせる。2 次以上のスキームは精度こそ高いものの不連続点では Gibbs 現象のように振動する。自然は分子粘性で衝撃内部にエントロピーを生成しますが、格子にはその通路がないのです。セル一つの衝撃管をリーマン問題として解く道は Godunov がセルごとに衝撃管を置く方法です。人工粘性はその不連続を三セルほどに伸ばして振動を消します。不連続の扱いが違います。

1950年の一行#

von Neumann と Richtmyer の答えは自然を真似ることでした。分子粘性を直接モデル化できないなら、セル幅程度に膨らませた偽の粘性項を圧力に足す。

Qi={ξ2 (ui+1−ui−1)2 ρiif ui+1≤ui−10otherwiseQ_i = \begin{cases} \xi^2 \, (u_{i+1} - u_{i-1})^2 \, \rho_i & \text{if } u_{i+1} \le u_{i-1} \\ 0 & \text{otherwise} \end{cases}

QiQ_i はセル ii での人工バルク圧力、ξ\xi は衝撃を何セル分に広げるかを決めるチューニングパラメータ(通常 2〜3)、ui+1−ui−1u_{i+1} - u_{i-1} は速度の 1 次差分、ρi\rho_i はセル密度です。運動量・エネルギー式中の PP を P+QP + Q に置き換えるだけで終わります。

QQ は (Δu)2(\Delta u)^2 に比例します。滑らかな流れではほぼ 0 で、衝撃の近くだけで強くなります。点火条件は ui+1≤ui−1u_{i+1} \le u_{i-1}、速度が収束するセルだけです。膨張波や音波には触れません。ρi\rho_i を掛けると重い媒質により大きな圧力が乗ります。

本文の ξ とコードの 0.25 は同じ厚さにならない#

短く書くと Q=ξ2(Δu)2ρQ = \xi^2 (\Delta u)^2 \rho です。点火するのは Δu<0\Delta u < 0 のときだけです。下のリストは 0.25 * xi**2 * du**2 * rho を掛けます。du は一セル飛ばしの差分です。一セルの傾きに直すと分母に 2Δx2\Delta x が入り、二乗で 1/41/4 が付きます。コードの 0.25 はその中心差分です。上の式はその係数を落として ξ2\xi^2 だけを書いています。

ξ=2.5\xi = 2.5 なら ξ2=6.25\xi^2 = 6.25 です。コードが実際に掛ける数は 0.25×6.25=1.56250.25 \times 6.25 = 1.5625 です。0.25 なしで同じ ξ\xi を式に入れると QQ は 4 倍です。QQ は ξ2\xi^2 に比例するので、4 倍は ξ\xi を 2 倍にしたのと同じです。スライダーで ξ=2\xi = 2 に静まった衝撃は、式をそのまま写した実装では ξ=4\xi = 4 に相当します。ξ=5\xi = 5 ではプラトーまで鈍ります。ξ=2.5\xi = 2.5 を 0.25 なしで入れると、その鈍い側に寄ります。

格子 200、時間 0.2、CFL 0.45 はリストに書いたままです。出力の文は、ピーク密度が Mach 2 の Rankine–Hugoniot 値 2.667 の数 % 以内だと言うだけです。0.25 を外した式をその文で確認すると、測っている衝撃の厚さが別物になります。

NumPyで追う人工粘性#

50 行に核を収めます。donor-cell 移流と人工粘性項だけを抜き出した最小実装です。

import numpy as np
 
def vnr_shock_solver(N=200, T=0.2, xi=2.5, gamma=1.4):
    # 初期条件: Sod衝撃管
    dx = 1.0 / N
    rho = np.where(np.arange(N) < N // 2, 1.0, 0.125)
    mom = np.zeros(N)
    p   = np.where(np.arange(N) < N // 2, 1.0, 0.1)
    E   = p / (gamma - 1)
    t = 0.0
    while t < T:
        u = mom / rho
        c = np.sqrt(gamma * p / np.maximum(rho, 1e-9))
        dt = 0.45 * dx / np.max(np.abs(u) + c)
        # von Neumann-Richtmyer の Q
        du = np.zeros(N)
        du[1:-1] = u[2:] - u[:-2]
        Q = np.where(du < 0, 0.25 * xi**2 * du**2 * rho, 0.0)
        # rho, mom, E を donor-cell で移流
        uf = 0.5 * (u[:-1] + u[1:])
        for q_arr, q_new in [(rho, 'rho'), (mom, 'mom'), (E, 'E')]:
            flux = np.where(uf > 0, q_arr[:-1] * uf, q_arr[1:] * uf)
            q_arr[1:-1] -= dt / dx * (flux[1:] - flux[:-1])
        # 圧力更新と P+Q の体積力
        p = (gamma - 1) * (E - 0.5 * rho * (mom / rho) ** 2)
        Ptot = p + Q
        mom[1:-1] -= dt / (2 * dx) * (Ptot[2:] - Ptot[:-2])
        E[1:-1]   -= dt / (2 * dx) * (Ptot[2:] * u[2:] - Ptot[:-2] * u[:-2])
        t += dt
    return rho, mom, E
 
rho, mom, E = vnr_shock_solver(xi=2.5)
print(f"shock peak rho ≈ {rho.max():.3f}   (Rankine-Hugoniot exact ≈ 2.667 for Mach 2)")

実行するとピーク密度が Rankine-Hugoniot 解の数 % 以内に着地します。同じコードを ξ = 0 で回すと、振動のせいで max・min が真のプラトーから非対称に振れます。

odd-evenデカップリングというもう一つの罠#

衝撃を捕らえたら終わりではありません。同じ collocated 格子(全変数をセル中心に置く配置)には von Neumann が気づいたもう一つの病があります。次は静止状態です。速度 0、比熱エネルギー 1 で一定、しかし密度が sawtooth に揺れています。

ρi={1i=1,3,5,…2i=2,4,6,…\rho_i = \begin{cases} 1 & i = 1, 3, 5, \dots \\ 2 & i = 2, 4, 6, \dots \end{cases}

圧力 pi=ρip_i = \rho_i(等温仮定)なら、セル i=2i = 2 は隣接の二倍の圧力です。直感的にはすぐ崩れるはずです。ところがセル ii の運動量更新は

∂(ρu)i∂t=−Fi+1/2−Fi−1/2Δx=−pi+1−pi−12Δx=0\frac{\partial (\rho u)_i}{\partial t} = -\frac{F_{i+1/2} - F_{i-1/2}}{\Delta x} = -\frac{p_{i+1} - p_{i-1}}{2\Delta x} = 0

となります。pi+1p_{i+1} と pi−1p_{i-1} が同じパリティの部分格子から来るため、差が正確に 0 になるのです。格子が二つの独立な副格子(偶セル、奇セル)に割れて互いを見ない状態です。この sawtooth モードは育ちも消えもしません。

Initial: ρ alternates 1↔2 (isothermal, p=ρ). Collocated: ∂p/∂x at cell center cancels — sawtooth survives. Staggered: face gradient sees the jump and damps it.

モードを collocated から staggered に切り替えます。staggered 格子は運動量をセル面に置きます。面 i+1/2i+1/2 の圧力差は pi+1−pip_{i+1} - p_i です。偶・奇のパリティが 1 セル隣で出会います。sawtooth はたちまち 1 次差分の射程に入り減衰します。

何を捨て、何を得たか

von Neumann の 1950 年のトリックは二つを同時に示しました。格子の限界を模倣で埋める道があること。そしてその模倣が別の格子病を生むこと。両方とも現代 CFD に生きています。

今日の ZEUS、Athena、PLUTO といった astrophysics コードは staggered 格子と人工粘性をそのまま使います。一方 OpenFOAM や ANSYS Fluent は collocated 格子に Rhie-Chow 補間を載せて同じデカップリングを回避します。同じ問題を別角度から解いた結果です。

衝撃の後ろの sawtooth はバグとは限りません。1944 年から知られている格子病のもう一つの顔です。

0.25 を外した ξ=2.5 はスライダーの ξ=5 側に寄る#

0.25 を外して ξ=2.5\xi = 2.5 を本文の式に入れると、QQ の大きさはコードが ξ=5\xi = 5 で出した値と同じになります。スライダーのその位置は、すでに衝撃が鈍い側です。ξ≈2\xi \approx 2〜33 は、コードに 0.25 が付いているときの場所です。係数を外したまま数字だけ移すと、厚さの話が変わります。

偶セルと奇セルが互いを見ない問題は、人工粘性とは別の病です。速度をセル面に置くと、一セル隣の圧力を見ます。その配置が衝撃背後の sawtooth とは別の誤差で崩れた記録は カール拘束がスタッガード格子でずれた場合にあります。

役に立ったらシェアしてください。