面ごとに行列を掛けずに衝撃波を捉える — AUSM フラックス分離
Roe なしで衝撃波を捉える AUSM フラックス分離の原理と実装
1993年、NASA ルイス研究所の Meng-Sing Liou は Roe ソルバーの行列演算が気に入りませんでした。衝撃波はきれいに捉えられます。しかし面を一つ解くたびに、5×5 ヤコビアンの固有構造を丸ごと分解する必要があったのです。Liou は別の道を選びました。フラックスを「対流」と「圧力」に分け、それぞれをマッハ数の多項式で切り分けたのです。こうして生まれた AUSM(Advection Upstream Splitting Method)は、行列を一度も掛けません。本稿ではその分離が数式でどう働くかを追い、Python で Shu–Osher 問題を実際に解いて検証します。
1993年、Liou がフラックスを二つに割る#
オイラー方程式の面フラックスを見てみましょう。一次元では
ここで は密度、 は速度、 は圧力、 は全エネルギー、 は全エンタルピーです。
Liou の洞察は単純でした。対流項は質量フラックス がどちらの方向から来るかで決まります。圧力項は音波に乗って両方向へ伝わります。物理が違うのだから、分けて扱おうというわけです。Roe が二つの状態の差をヤコビアンで一度に解いた(flux-difference splitting)のに対し、AUSM は各状態が面に与える寄与を別々に分けます(flux-vector splitting)。
面(interface)での AUSM フラックスはこう組み立てられます。
は対流が運ぶ量、 は圧力の寄与です。鍵は面質量フラックス と面圧力 をどう決めるかにあります。
マッハ数を多項式で切り分ける#
面質量フラックスは面マッハ数 から出てきます。
、 は左右セルのマッハ数、 は面音速です。分離関数 が本稿の心臓部です。
最も単純な一次分離は単純な風上化です。
超音速()ではこの式をそのまま使います。片側が 0 になり完全な風上化となります。問題は遷音速付近です。一次関数は で折れ、微分が不連続になります。そこでソルバーの収束が止まります。
AUSM+ は遷音速域を 4 次多項式で滑らかに埋めます()。
圧力も同じように切り分けます()。
言葉では抽象的です。下のシミュレーションで自分でパラメータを動かしてみましょう。
Cyan = forward split (M+ / P+), pink = backward split (M- / P-). Outside the sonic band |M|>1 the curves collapse onto the fully upwind branch; inside, the polynomial blend keeps them smooth and differentiable.
の外では二つの曲線が完全風上の枝に張り付きます。内側では多項式が滑らかにつなぎ、微分可能性を保ちます。β を大きくすると遷音速域の曲率が増します。この滑らかさこそ、Roe ソルバーなしで収束を可能にした秘訣です。
圧力は圧力で、対流は対流で#
分離関数が用意できれば、面の値は一行に集まります。質量フラックスは
符号がそのまま風上方向です。 なら左セルの が運び出されます。圧力は左右の重み付き和です。
対流は片側からだけ来て、圧力は両側から来ます。この非対称性が、AUSM が接触不連続(密度は跳ぶが圧力は平らな面)をくっきり捉える理由です。Roe は接触面をヤコビアンの一つの固有値として扱いますが、AUSM は質量フラックスが 0 になる点で自然に分離します。圧力振動を生む余分な散逸が入り込みません。
AUSM+ そして AUSM+-up — 低マッハまで#
元の AUSM には弱点がありました。マッハ数が 0.01 のように非常に低い非圧縮極限で、圧力場が格子単位で振動したのです。音響項が運動量に比べて強く散逸されすぎるためです。2006 年、Liou は AUSM+-up でこの域を手直ししました。質量フラックスに圧力拡散項を、圧力に速度拡散項を加えたのです。
は圧力差を質量フラックスに引き込み、低マッハの振動を抑えます。 は局所マッハ数に応じて 0 と 1 の間を動くスケール関数です。マッハ数が高いと となって追加項が消え、本来の衝撃波捕捉性能がそのまま戻ります。一つの式が非圧縮極限も極超音速も担います。これが AUSM+-up を「all-speed」スキームと呼ぶ理由です。
Python — Shu–Osher で試す#
検証問題には Shu–Osher を選びます。マッハ 3 の衝撃波が正弦波の密度擾乱に突っ込む一次元オイラー問題です。衝撃波捕捉と高周波波動の保存を同時に試します。下のコードは一次精度の AUSM+ で解きます。
import numpy as np
GAMMA = 1.4
def mach_split(M, sign):
# AUSM+ 4 次分離マッハ多項式 (beta = 1/8)
beta = 1.0 / 8.0
if abs(M) >= 1.0:
return 0.5 * (M + abs(M)) if sign > 0 else 0.5 * (M - abs(M))
if sign > 0:
return 0.25 * (M + 1.0) ** 2 + beta * (M * M - 1.0) ** 2
return -0.25 * (M - 1.0) ** 2 - beta * (M * M - 1.0) ** 2
def pressure_split(M, sign):
# AUSM+ 5 次分離圧力多項式 (alpha = 3/16)
alpha = 3.0 / 16.0
if abs(M) >= 1.0:
return 0.5 * (1.0 + np.sign(M)) if sign > 0 else 0.5 * (1.0 - np.sign(M))
if sign > 0:
return 0.25 * (M + 1.0) ** 2 * (2.0 - M) + alpha * M * (M * M - 1.0) ** 2
return 0.25 * (M - 1.0) ** 2 * (2.0 + M) - alpha * M * (M * M - 1.0) ** 2
def ausm_plus(wl, wr):
rl, ul, pl = wl
rr, ur, pr = wr
al = np.sqrt(GAMMA * pl / rl)
ar = np.sqrt(GAMMA * pr / rr)
a12 = 0.5 * (al + ar) # 面音速 (単純平均)
Ml, Mr = ul / a12, ur / a12
M12 = mach_split(Ml, +1) + mach_split(Mr, -1)
p12 = pressure_split(Ml, +1) * pl + pressure_split(Mr, -1) * pr
Hl = (pl / (GAMMA - 1) + 0.5 * rl * ul * ul + pl) / rl
Hr = (pr / (GAMMA - 1) + 0.5 * rr * ur * ur + pr) / rr
mdot = a12 * M12 * (rl if M12 > 0 else rr)
psi = np.array([1.0, ul, Hl]) if M12 > 0 else np.array([1.0, ur, Hr])
return mdot * psi + np.array([0.0, p12, 0.0])
def euler_step(U, dx, cfl):
r = U[0]
u = U[1] / r
p = (GAMMA - 1.0) * (U[2] - 0.5 * r * u * u)
a = np.sqrt(GAMMA * p / r)
dt = cfl * dx / np.max(np.abs(u) + a)
n = U.shape[1]
F = np.zeros((3, n + 1))
for f in range(1, n): # 内部面フラックス
F[:, f] = ausm_plus((r[f-1], u[f-1], p[f-1]), (r[f], u[f], p[f]))
F[:, 0] = F[:, 1] # 透過境界
F[:, n] = F[:, n - 1]
return U - (dt / dx) * (F[:, 1:] - F[:, :-1]), dt
def shu_osher(n=400, t_end=1.8):
x = np.linspace(-5, 5, n, endpoint=False) + 5.0 / n
dx = 10.0 / n
r = np.where(x < -4, 3.857143, 1.0 + 0.2 * np.sin(5.0 * x))
u = np.where(x < -4, 2.629369, 0.0)
p = np.where(x < -4, 10.33333, 1.0)
U = np.zeros((3, n))
U[0], U[1], U[2] = r, r * u, p / (GAMMA - 1.0) + 0.5 * r * u * u
t = 0.0
while t < t_end:
U, dt = euler_step(U, dx, cfl=0.4)
t += dt
return x, U[0]
if __name__ == "__main__":
x, rho = shu_osher()
print(f"密度 min/max : {rho.min():.3f} / {rho.max():.3f}")
print(f"x=2.0 の密度 : {rho[np.argmin(np.abs(x - 2.0))]:.3f}")実行すると、衝撃波の後ろで正弦波が圧縮され生き残った密度場が得られます。マッハ 3 の衝撃が通過したあと、密度の最大値は 4 を超えます。分離関数だけで衝撃を安定に捉えつつ、圧力は振動なく滑らかです。
同じ AUSM+ フラックスを一次元 Sod 衝撃管に適用した結果を、下でリアルタイムに見られます。
Sod shock tube solved live with AUSM+. Watch three structures appear from one diaphragm: a rarefaction fan moving left, a contact discontinuity in the middle (sharp in density, flat in pressure), and a shock on the right. Push CFL toward 0.95 to see the first-order scheme stay stable but smear the contact further.
一枚の隔膜から三つの構造が分かれます。左へ広がる膨張波、中央の接触不連続(密度は折れ、圧力は平ら)、右の衝撃波です。CFL を 0.95 まで上げても一次スキームは発散しません。代わりに接触面がさらにぼやけます。
Roe と並べてみると#
AUSM はタダではありません。一次版は接触面を Roe より少しぼかします。上のコードのようにセル値をそのまま使うと一次精度に留まります。実務では MUSCL や WENO 再構成を左右の状態に先に適用して高次に引き上げます。分離関数はそのまま、入力だけを変えればよいのです。
| 項目 | Roe (FDS) | AUSM+ (FVS) |
|---|---|---|
| 面あたりのコスト | ヤコビアン固有分解 | 多項式評価 |
| 接触不連続 | 非常に鮮明 | 鮮明 (エントロピー修正不要) |
| カーバンクル現象 | 脆弱 | 頑健 |
| 低マッハ挙動 | 別途前処理が必要 | AUSM+-up に内蔵 |
極超音速の衝撃波に対して格子が垂直に揃うと、Roe はカーバンクル(carbuncle)という非物理的な突出を生みます。AUSM 系はこの病にはるかに頑健です。対流と圧力を分けた構造そのものが、衝撃面の横方向不安定をあまり育てないためです。
最後に残しておきたいこと#
- AUSM はフラックスを対流・圧力に割り、それぞれをマッハ数の多項式分離関数で面の値に集めます。ヤコビアン分解はありません。
- 遷音速を 4 次・5 次多項式で滑らかに埋めたおかげで微分可能性が保たれ、収束が安定します。AUSM+-up の拡散項は同じ式で低マッハまで伸ばします。
- 接触不連続に鮮明でカーバンクルに頑健です。高次が必要なら、分離関数は残して左右の入力だけを再構成で変えます。
役に立ったらシェアしてください。