この記事は著者が韓国語で書いた原文の翻訳です。 原文(한국어)を読む
5-Equation Model 実装ガイド:Python で作る多相流ソルバー
時間刻みを決めるのはその24 m/sではなく、衝撃波背後の水の約2530 m/sです。
5-Equation Model を直接実装する#
衝撃波の背後の水は圧力 、その手前の水は です。あいだに空気の気泡があり、体積分率 はほぼ 0 とほぼ 1 を行き来します。1D の 5-equation diffuse interface model は、この跳びを式五つで運びます。二つの流体は速度を一つ、圧力を一つ分け合い、体積分率だけが別に流れます。
人工拡散のスライダーを 0 まで下げると、体積分率の界面は一セルで切れます。
5-방정식 모델의 핵심: 부피분율 α의 이류. upwind 만으로는 계면이 점차 번지므로 실제 코드는 THINC / interface sharpening을 추가한다.
スライダーを上げると界面は数セルに広がります。そのセルの が、混合圧力の分母にそのまま入ります。
支配方程式の整理
1D の保存変数とフラックスは次のとおりです。
五行目のフラックスは 0 です。 は非保存型なので、別に進めます。
速度一つ、圧力一つへ縮めたのがこの五つの式です。各相に方程式を戻す側は Baer–Nunziato の 7 方程式です。
状態方程式:Stiffened Gas EOS#
両方の流体が stiffened gas です。
混合圧力はこの規則で求めます。
は流体 の圧力、 はその内部エネルギー、 と は EOS の定数です。分母は です。
Python による実装#
初期設定と EOS#
import numpy as np
import matplotlib.pyplot as plt
# ドメイン設定
N = 1000
x = np.linspace(0, 1, N)
dx = x[1] - x[0]
# EOS パラメータ
# 流体 1: 空気 (理想気体)
gamma1, pinf1 = 1.4, 0.0
# 流体 2: 水 (stiffened gas)
gamma2, pinf2 = 4.4, 6e8
def pressure(alpha1, arho1, arho2, E, u):
"""保存変数から混合圧力を計算します。"""
alpha2 = 1.0 - alpha1
rho = arho1 + arho2
ke = 0.5 * rho * u**2
rhoe = E - ke
num = rhoe - alpha1*gamma1*pinf1/(gamma1-1) - alpha2*gamma2*pinf2/(gamma2-1)
den = alpha1/(gamma1-1) + alpha2/(gamma2-1)
return num / den
def sound_speed(alpha1, arho1, arho2, p):
"""混合音速を計算します (Wood の公式)。"""
alpha2 = 1.0 - alpha1
rho = arho1 + arho2
rho1 = arho1 / np.maximum(alpha1, 1e-12)
rho2 = arho2 / np.maximum(alpha2, 1e-12)
c1sq = gamma1 * (p + pinf1) / rho1
c2sq = gamma2 * (p + pinf2) / rho2
# Wood の公式
inv_rhoc2 = alpha1/(rho1*c1sq) + alpha2/(rho2*c2sq)
return np.sqrt(1.0 / (rho * inv_rhoc2))pressure は上の混合則を保存変数で書き直したものです。sound_speed の が、純粋流体の音速です。
HLLC フラックスの計算#
保存式四つだけが HLLC です。 を運ぶ接触速度 も一緒に返します。
def hllc_flux(UL, UR, alpha1L, alpha1R):
"""4つの保存方程式に対する HLLC 数値フラックスを計算します。"""
arho1L, arho2L, momL, EL = UL
arho1R, arho2R, momR, ER = UR
rhoL = arho1L + arho2L
rhoR = arho1R + arho2R
uL = momL / rhoL
uR = momR / rhoR
pL = pressure(alpha1L, arho1L, arho2L, EL, uL)
pR = pressure(alpha1R, arho1R, arho2R, ER, uR)
cL = sound_speed(alpha1L, arho1L, arho2L, pL)
cR = sound_speed(alpha1R, arho1R, arho2R, pR)
# 波速の推定
SL = min(uL - cL, uR - cR)
SR = max(uL + cL, uR + cR)
Sstar = (pR - pL + rhoL*uL*(SL-uL) - rhoR*uR*(SR-uR)) \
/ (rhoL*(SL-uL) - rhoR*(SR-uR))
FL = np.array([arho1L*uL, arho2L*uL, momL*uL + pL, (EL+pL)*uL])
FR = np.array([arho1R*uR, arho2R*uR, momR*uR + pR, (ER+pR)*uR])
if SL >= 0:
return FL, Sstar
elif Sstar >= 0:
coeff = rhoL * (SL - uL) / (SL - Sstar)
UstarL = coeff * np.array([
arho1L/rhoL,
arho2L/rhoL,
Sstar,
EL/rhoL + (Sstar - uL)*(Sstar + pL/(rhoL*(SL-uL)))
])
return FL + SL*(UstarL - UL), Sstar
elif SR > 0:
coeff = rhoR * (SR - uR) / (SR - Sstar)
UstarR = coeff * np.array([
arho1R/rhoR,
arho2R/rhoR,
Sstar,
ER/rhoR + (Sstar - uR)*(Sstar + pR/(rhoR*(SR-uR)))
])
return FR + SR*(UstarR - UR), Sstar
else:
return FR, Sstar時間積分(1次精度)#
時間前進は前進オイラーです。 は で、CFL の既定値は 0.5 です。
def solve(U, alpha1, dx, t_end, cfl=0.5):
"""前進オイラー法による時間積分を行います。"""
t = 0.0
while t < t_end:
rho = U[0] + U[1]
u = U[2] / rho
p = pressure(alpha1, U[0], U[1], U[3], u)
c = sound_speed(alpha1, U[0], U[1], p)
dt = cfl * dx / np.max(np.abs(u) + c)
if t + dt > t_end:
dt = t_end - t
# セル界面でのフラックスを計算
flux = np.zeros((4, N+1))
uface = np.zeros(N+1)
for i in range(N+1):
iL = max(i-1, 0)
iR = min(i, N-1)
UL_local = U[:4, iL]
UR_local = U[:4, iR]
flux[:, i], uface[i] = hllc_flux(
UL_local, UR_local, alpha1[iL], alpha1[iR]
)
# 保存変数の更新
for k in range(4):
U[k, 1:-1] -= dt/dx * (flux[k, 2:-1] - flux[k, 1:-2])
# alpha1 の更新 (非保存型, アップウィンド)
for i in range(1, N-1):
if uface[i] >= 0:
alpha1[i] -= dt/dx * uface[i] * (alpha1[i] - alpha1[i-1])
else:
alpha1[i] -= dt/dx * uface[i] * (alpha1[i+1] - alpha1[i])
alpha1 = np.clip(alpha1, 1e-8, 1-1e-8)
t += dt
return U, alpha1 の更新は保存フラックスではありません。面速度 uface の符号で風上を選びます。
初期条件:衝撃波と気泡
位置 0.25 より手前は、衝撃波を通過した水です。、、。0.4 と 0.6 のあいだは空気の気泡で、、、。残りは周囲の水、、、 です。
# 初期条件:水中の空気の気泡に衝撃波が当たる問題
U = np.zeros((4, N))
alpha1 = np.zeros(N)
for i in range(N):
if x[i] < 0.25:
# 衝撃波背後の水 (高圧)
rho2 = 1100.0; u_val = 50.0; p_val = 1e9
alpha1[i] = 1e-8
arho1 = alpha1[i] * 1.0 # 無視できる程度の空気
arho2 = (1 - alpha1[i]) * rho2
elif 0.4 < x[i] < 0.6:
# 空気の気泡
rho1 = 1.0; u_val = 0.0; p_val = 1e5
alpha1[i] = 1 - 1e-8
arho1 = alpha1[i] * rho1
arho2 = (1 - alpha1[i]) * 1000.0
else:
# 周囲の水
rho2 = 1000.0; u_val = 0.0; p_val = 1e5
alpha1[i] = 1e-8
arho1 = alpha1[i] * 1.0
arho2 = (1 - alpha1[i]) * rho2
rho = arho1 + arho2
E = compute_total_energy(alpha1[i], arho1, arho2, p_val, u_val)
U[0, i] = arho1
U[1, i] = arho2
U[2, i] = rho * u_val
U[3, i] = Ecompute_total_energy はこの断片にありません。圧力からエネルギーへ戻す式は EOS 側にあります。 です。
注意事項
1. の範囲制限#
がちょうど 0 または 1 だと、保存量 を で割れません。コードは を と のあいだに切ります。
2. 混合音速:Wood の公式#
混合セルの音速は、どちらの純粋流体よりも低くなり得ます。直感とは逆ですが、式はそう言っています。CFL もこの音速を見ます。
初期条件の純粋状態をこの式に入れると、数が出ます。気泡の空気は 、、、 です。 なら です。周囲の水は 、、、 なので です。衝撃波背後の水は 、 で です。 は周囲の水の約 2.67 倍なのに、密度は 倍だけです。 の運動エネルギーは です。同じセルの内部エネルギー密度 の約 0.13% です。この衝撃のエネルギーは速度より 側にあります。
で気泡の空気と周囲の水を、圧力 のまま混ぜると、Wood の式の はほとんど空気の項です。、 です。混合密度は で、 です。374 でも 1625 でもありません。
圧力の分母も同じ場所で敏感です。純粋な水の は です。水に だけ漏れても、分母は となり、約 7.5% 増えます。圧力はこの分母で割るので、広がった界面の の誤差は圧力の誤差になります。
CFL の で、 は linspace(0, 1, 1000) の間隔 です。制限するのは混合セルではありません。衝撃波の背後は なので です。音速 24 m/s のセルだけを見ると、間隔は約 110 倍大きくなります。混合音速が低くても、時間刻みはそのセルが締めません。同じ のあいだ、界面の音響がほとんど止まって見える方が影響です。
3. 高次精度への拡張#
上の時間積分は 1 次です。空間もセル値をそのまま使います。2 次が要るなら MUSCL と slope limiter、その上は WENO です。時間は Runge–Kutta で上げます。次数を上げても、 の非保存更新と Wood の音速は残ります。
結果の解釈
衝撃が気泡に入ると、下流側へ透過衝撃波(transmitted shock)が出て、左へ反射膨張波(reflected rarefaction)が戻ります。気泡は急に押しつぶされ、内部の圧力が上がります。ジェット(jet)は 2D か 3D で、下流側の壁にできます。水中爆発と衝撃波結石破砕は、この絵を使います。
風上の α だけでは接触面の圧力が先に揺れます
五番目のフラックスは 0 で、 は面速度の符号で風上になります。水に が漏れただけで圧力の分母が 7.5% 動くという計算は、界面一セルの誤差が平衡圧力に触れるという意味です。この 1 次の更新は、その平衡を別に強制しません。非保存項の離散化が圧力を保つかどうかが、先に分かれます。
平衡を測る問題は、何も起きてはいけない接触です。そこで圧力がどこへ漏れるかは、Abgrall 基準と非保存項が問います。
関連記事
役に立ったらシェアしてください。