本文译自作者的韩语原文。 阅读原文(한국어)
5-Equation Model 实现指南:用 Python 编写多相流求解器
收紧时间步的不是这 24 m/s,而是激波后水里大约 2530 m/s 的声速。
手动实现 5-Equation Model#
激波后的水压是 ,波前的水是 。中间夹着一个气泡,体积分数 在接近 0 和接近 1 之间跳。一维 5-equation diffuse interface model 用五个方程搬运这个跳跃。两种流体共用一个速度、一个压力,只有体积分数单独走。
把人工扩散滑块拨到 0,体积分数的界面会在一个单元里断开。
5-방정식 모델의 핵심: 부피분율 α의 이류. upwind 만으로는 계면이 점차 번지므로 실제 코드는 THINC / interface sharpening을 추가한다.
把滑块抬高,界面会铺开好几个单元。那些单元里的 直接进入混合压力的分母。
控制方程整理
一维的守恒变量和通量是这样。
第五个通量分量是 0。 是非守恒型,要单独推进。
收成一个速度、一个压力之后,就是这五个方程。把方程交还给每一相,是 Baer–Nunziato 七方程。
状态方程:Stiffened Gas EOS#
两种流体都用 stiffened gas。
混合压力用这条规则。
是流体 的压力, 是它的内能, 和 是状态方程常数。分母是 。
Python 实现#
初始设置与 EOS#
import numpy as np
import matplotlib.pyplot as plt
# Domain
N = 1000
x = np.linspace(0, 1, N)
dx = x[1] - x[0]
# EOS parameters
# Fluid 1: Air (ideal gas)
gamma1, pinf1 = 1.4, 0.0
# Fluid 2: Water (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时间积分 (一阶)
时间推进是前向欧拉。步长是 ,默认 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 不在这段代码里。由压力回到能量的式子在状态方程这边:。
注意事项
1. 的范围限制#
正好是 0 或 1 时,守恒量 没法再除以 。代码把 夹在 和 之间。
2. 混合声速:Wood 公式#
混合单元的声速可以比两种纯流体都低。这和直觉相反,但公式就是这样写的。CFL 也读这个声速。
把初始条件里的纯状态代进公式,数就出来了。气泡里的空气是 、、、。按 ,得到 。环境水是 、、、,所以 。激波后的水是 、,。 大约是环境水的 2.67 倍,密度却只是 倍。 的动能是 。同一单元的内能密度 ,动能大约只占 0.13%。这次冲击的能量在 里,不在这 50 m/s 里。
用 、共同压力 把气泡空气和环境水掺在一起,Wood 公式里的 几乎全是空气那一项。,而 。混合密度是 ,。既不是 374,也不是 1625。
压力分母在同一个地方很敏感。纯水的 是 。水里只要漏进 ,分母就变成 ,大约大 7.5%。压力要除以这个分母,所以抹开的界面上 的误差会变成压力误差。
CFL 的 , 是 linspace(0, 1, 1000) 的间距 。限制步长的不是混合单元。激波后面 ,所以 。只看声速 24 m/s 的那个单元,步长大约会大 110 倍。混合声速变低,并不会由那个单元把时间步收紧。它的影响是:在同一个 里,界面上的声波几乎是停着的。
3. 向高阶精确度扩展#
上面的时间积分是一阶,空间也直接用单元值。要二阶,就用 MUSCL 加 slope limiter;再往上是 WENO。时间用 Runge–Kutta 抬阶。阶数上去之后, 的非守恒更新和 Wood 声速还在。
结果解释
激波进入气泡之后,下游走出透射激波(transmitted shock),左侧返回反射稀疏波(reflected rarefaction)。气泡被迅速压扁,内部压力上升。射流(jet)要到二维或三维才会出现在下游壁上。水下爆炸和冲击波碎石用的就是这张图。
只迎风推进 α,接触面上的压力会先晃
第五个通量是 0, 按界面速度的符号做迎风。水里漏进 ,压力分母就动 7.5%。这个账的意思是,界面上一个单元的误差就会碰到平衡压力。这一阶更新并不另行强制那个平衡。非守恒项的离散是否把压力留住,是先要分开的地方。
检验平衡的题目,是一个什么都不该发生的接触。压力从那里漏到哪里,是 Abgrall 准则与非守恒项 要问的。
相关文章
如果对您有帮助,请分享。