Skip to content
cfd-lab:~/zh/posts/2026-10-10-toro-vazquez-…● online

本文译自作者的韩语原文。 阅读原文(한국어)

NOTE #180DAY SAT 논문리뷰DATE 2026.10.10UPDATED 2026.10.19READ 6 min read#Flux-Splitting#IMEX#Low-Mach#Compressible-Multiphase#Paper-Review

声速1625 m/s的水在压力子系统里只有18 m/s — Toro–Vázquez分裂把刚性移到了哪里

分裂后压力子系统的波速A不是声速。水的A/c只有0.011,消失的刚性转移到了两步之间的状态方程重算中。

声速1625 m/s的水被算成了18 m/s#

水中声速约为1625 m/s。但在这篇论文的压力子系统里,1 bar水的波速是18.44 m/s。这不是算错了。它直接来自一个公式。

论文是Schropff、Petitpas和Daniel于2026年发表在JCP上的"Semi-implicit method for two-phase flows in pressure disequilibrium"。这是一种IMEX(隐式–显式混合)方法,用一个速度求解压力不同的两相。把材料切到Water或Spinel,橙线几乎竖直;压力和马赫数滑块会同时推动 AA 和时间步长比。

c = 1624.9 m/s  | A = 18.44 m/s (A/c = 0.0113)  | u = 162.49 m/s  | dt_IMEX / dt_explicit = 11.0

灰色虚线是原系统的特征线 u±cu\pm c。橙色实线是压力子系统的 ±A\pm A,蓝色实线是输运速度 uu。右侧柱状图用对数刻度显示低马赫下时间步长增大了多少倍。水里橙线越接近竖直,A/cA/c 就越落到0.01以下。

1993年:把压力从对流中拆出来#

起点是Zha–Bilgen分裂(flux splitting,按物理作用拆分通量并分别处理的方法)。它把欧拉方程的通量分成两块。一块是随流速输运的对流部分。另一块是由压力推动的部分。

Toro和Vázquez在2012年把这种分裂改写成Godunov形式。这样每个子系统都能单独提出黎曼问题。Schropff等人把这一思路搬到压力非平衡的两相模型上。与压力效应有关的通量和非守恒项归入第一个子系统。剩下的输运项归入第二个子系统。

2026年:两相拆成两个子系统#

原始变量为 W=(α1,α2,ρ1,ρ2,p1,p2,u)TW=(\alpha_1,\alpha_2,\rho_1,\rho_2,p_1,p_2,u)^T。压力子系统如下。

∂tαk=0,∂t(αkρk)=0,∂t(αkρkek)+αkpk ∇⋅u=0,∂t(ρu)+∇p=0.\begin{aligned} &\partial_t \alpha_k = 0,\qquad \partial_t(\alpha_k\rho_k) = 0,\\ &\partial_t(\alpha_k\rho_k e_k) + \alpha_k p_k\,\nabla\cdot u = 0,\\ &\partial_t(\rho u) + \nabla p = 0. \end{aligned}

αk\alpha_k 是相 kk 的体积分数,ρk\rho_k 是相密度,eke_k 是比内能。p=∑kαkpkp=\sum_k\alpha_k p_k 是混合压力。

输运子系统里没有压力。体积分数、质量、内能和动量都由 uu 输运。因此7个特征值全部等于 uu。两个子系统相加即恢复原模型。分析中略去了松弛项。

压力步中密度是冻结的

再看压力子系统。αk\alpha_k 和 αkρk\alpha_k\rho_k 都不随时间变化,所以 ρk\rho_k 也不变。改变压力的唯一途径是内能。写成原始变量形式:

∂tpk+pkΓk ∇⋅u=0,Γk=(ρkep,k)−1\partial_t p_k + p_k\Gamma_k\,\nabla\cdot u = 0,\qquad \Gamma_k = (\rho_k e_{p,k})^{-1}

Γk\Gamma_k 是Grüneisen系数(密度固定时加入能量引起的压力升高率)。ep,ke_{p,k} 是密度固定时的 ∂ek/∂pk\partial e_k/\partial p_k。将此式与动量方程联立,得到波速 ±A\pm A。

A2=1ρ∑kαkpkΓkA^2 = \frac{1}{\rho}\sum_k \alpha_k p_k \Gamma_k

其中 ρ=∑kαkρk\rho=\sum_k\alpha_k\rho_k 是混合密度。AA 中不含 uu。因此论文说这种波系总是亚声速的。

对刚性气体(stiffened gas)状态方程,Γk=γk−1\Gamma_k=\gamma_k-1。于是得到论文式(14)。

A2=cf2−p+∑kαkγkp∞,kρA^2 = c_f^2 - \frac{p + \sum_k \alpha_k\gamma_k p_{\infty,k}}{\rho}

cfc_f 是冻结声速(frozen sound speed,无相间松弛时的混合声速),p∞,kp_{\infty,k} 是刚性气体的刚性压力。理想气体有 A2=c2−p/ρA^2=c^2-p/\rho,空气的 A/c=(γ−1)/γ=0.535A/c=\sqrt{(\gamma-1)/\gamma}=0.535。水的情况不同。γp∞\gamma p_\infty 项几乎占了 c2c^2 的全部,这一部分被整个去掉了。

把四种材料的A和c放进一张表#

用论文表1的刚性气体参数(空气、水、环氧树脂、尖晶石)计算 AA 和 cc。同一段代码还单独取出空气的压力子系统做脉冲实验。在相同Courant数下比较显式Lax–Friedrichs和隐式后向欧拉。最后一块是低马赫下的时间步长比。

import math
 
# Stiffened-gas parameters (gamma, p_inf [Pa]) from the paper's Table 1
MATS = {"Air": (1.4, 0.0), "Water": (4.4, 6e8), "Epoxy": (2.43, 5.3e9), "Spinel": (1.62, 141e9)}
RHO = {"Air": 1.2, "Water": 1000.0, "Epoxy": 1185.0, "Spinel": 3622.0}
 
def split_speeds(gam, pinf, p, rho):
    c = math.sqrt(gam * (p + pinf) / rho)     # full sound speed
    A = math.sqrt((gam - 1.0) * p / rho)       # pressure-subsystem speed, Eq. (14)
    return c, A
 
def thomas_solve(rhs, beta):
    # (I - beta*L) x = rhs, compact Laplacian, zero-gradient ends
    n = len(rhs)
    cp, dp = [0.0] * n, [0.0] * n
    b0 = 1 + beta
    cp[0], dp[0] = -beta / b0, rhs[0] / b0
    for i in range(1, n):
        b = 1 + beta if i == n - 1 else 1 + 2 * beta
        m = b + beta * cp[i - 1]
        cp[i] = -beta / m
        dp[i] = (rhs[i] + beta * dp[i - 1]) / m
    x = [0.0] * n
    x[-1] = dp[-1]
    for i in range(n - 2, -1, -1):
        x[i] = dp[i] - cp[i] * x[i + 1]
    return x
 
def ddx(f, dx):
    n = len(f)
    return [(f[min(i + 1, n - 1)] - f[max(i - 1, 0)]) / (2 * dx) for i in range(n)]
 
def pulse_run(courant, implicit, n=200, travel=0.3):
    # linearised pressure subsystem (air, 1 bar): p_t + K u_x = 0, u_t + p_x / rho = 0
    rho, K = 1.2, 0.4 * 1e5
    A = math.sqrt(K / rho)
    dx = 1.0 / n
    x = [(i + 0.5) * dx for i in range(n)]
    p = [100.0 * math.exp(-((xi - 0.5) / 0.04) ** 2) for xi in x]
    u = [0.0] * n
    dt = courant * dx / A
    steps = math.ceil(travel / A / dt)
    for _ in range(steps):
        if implicit:
            ux = ddx(u, dx)
            p = thomas_solve([p[i] - dt * K * ux[i] for i in range(n)], dt * dt * K / rho / dx ** 2)
            px = ddx(p, dx)
            u = [u[i] - dt / rho * px[i] for i in range(n)]
        else:  # Lax-Friedrichs
            P = [p[0]] + p + [p[-1]]; U = [u[0]] + u + [u[-1]]
            p = [0.5 * (P[i] + P[i + 2]) - dt * K * (U[i + 2] - U[i]) / (2 * dx) for i in range(n)]
            u = [0.5 * (U[i] + U[i + 2]) - dt / rho * (P[i + 2] - P[i]) / (2 * dx) for i in range(n)]
        if max(abs(v) for v in p) > 1e9:
            return steps, float("inf"), float("nan")
    right = max(range(n // 2, n), key=lambda i: p[i])
    speed = (x[right] - 0.5) / (steps * dt)
    return steps, max(abs(v) for v in p[n // 2:]), speed
 
print("material  p[bar]     c[m/s]     A[m/s]      A/c")
for name, (g, pinf) in MATS.items():
    for pbar in (1.0, 100.0):
        rho = RHO[name] * (pbar if name == "Air" else 1.0)  # isothermal air
        c, A = split_speeds(g, pinf, pbar * 1e5, rho)
        print(f"{name:7s} {pbar:7.0f} {c:10.1f} {A:10.2f} {A / c:9.5f}")
 
print("\nwater/air mixture, 1 bar, Eq. (13)")
for aw in (0.01, 0.5, 0.99):
    rho = aw * 1000.0 + (1 - aw) * 1.2
    A = math.sqrt((aw * 1e5 * 3.4 + (1 - aw) * 1e5 * 0.4) / rho)
    print(f"alpha_w={aw:4.2f}  rho={rho:7.1f}  A={A:7.2f} m/s")
 
A_air = math.sqrt(0.4e5 / 1.2)
print(f"\npressure pulse, air 1 bar, A = {A_air:.1f} m/s, c = {math.sqrt(1.4e5 / 1.2):.1f} m/s")
print("Courant_A steps  exp_peak  exp_speed  imp_peak  imp_speed")
for cr in (0.5, 0.9, 1.5, 5.0, 20.0):
    s, pe, ve = pulse_run(cr, False)
    _, pi, vi = pulse_run(cr, True)
    print(f"{cr:7.1f} {s:6d} {pe:9.2f} {ve:9.1f} {pi:9.2f} {vi:9.1f}")
 
print("\nlow-Mach air: dt ratio at equal Courant number = (|u|+c)/|u|")
for M in (0.3, 0.1, 0.01, 0.001):
    print(f"M={M:6.3f}  ratio={(1 + M) / M:9.1f}")
material  p[bar]     c[m/s]     A[m/s]      A/c
Air           1      341.6     182.57   0.53452
Air         100      341.6     182.57   0.53452
Water         1     1624.9      18.44   0.01135
Water       100     1638.3     184.39   0.11255
Epoxy         1     3296.7      10.99   0.00333
Epoxy       100     3299.8     109.85   0.03329
Spinel        1     7941.3       4.14   0.00052
Spinel      100     7941.6      41.37   0.00521
 
water/air mixture, 1 bar, Eq. (13)
alpha_w=0.01  rho=   11.2  A=  62.00 m/s
alpha_w=0.50  rho=  500.6  A=  19.48 m/s
alpha_w=0.99  rho=  990.0  A=  18.45 m/s
 
pressure pulse, air 1 bar, A = 182.6 m/s, c = 341.6 m/s
Courant_A steps  exp_peak  exp_speed  imp_peak  imp_speed
    0.5    120     25.59     184.1     35.90     181.1
    0.9     67     42.37     183.2     30.52     180.2
    1.5     40    240.14     129.3     25.76     178.0
    5.0     12 102438.55      13.7     16.02     168.9
   20.0      3   3336.76      32.0      9.82     129.3
 
low-Mach air: dt ratio at equal Courant number = (|u|+c)/|u|
M= 0.300  ratio=      4.3
M= 0.100  ratio=     11.0
M= 0.010  ratio=    101.0
M= 0.001  ratio=   1001.0

把 Courant 滑块从 0.5 推到 20,并在显式和隐式之间切换,表里的 exp_peak 和 imp_peak 会在画面上重算。

A = 182.6 m/s  |  steps = 0  |  t = 0.000 ms  | right peak = 100.00 Pa (exact 50 Pa, gray dashed)

到 0.9 为止两种波速都在 180 m/s 附近。超过 1.5,显式峰值跳到 240 Pa。隐式在 20 也只是被压到 9.82 Pa,数字并不炸开。

1 bar水的 A/cA/c 为0.01135。环氧树脂降到0.00333,尖晶石降到0.00052。把压力提高到100 bar,水的 AA 正好变为10倍,因为 A2A^2 与 pp 成正比。而 cc 只从1624.9变到1638.3,变化0.8%。空气无论压力如何都固定在0.53452。

水–空气混合物也值得看一眼。水体积分数为0.5时,AA 为19.48 m/s。与0.99时的18.45 m/s几乎相同。因为混合密度和分子 ∑αkpkΓk\sum\alpha_k p_k\Gamma_k 都由水决定。只有空气占到99%时,AA 才跳到62.00 m/s。

压力从 1 bar 提到 100 bar,水的 AA 从 18.44 变成 184.39。正好 10 倍。A2A^2 与 pp 成正比,压力变为 100 倍,波速就是 10 倍。同一段里 cc 只从 1624.9 到 1638.3,也就是页上写的 0.8%。环氧树脂的 AA 同样从 10.99 到 109.85,是 10 倍,cc 停在 3296.7 和 3299.8。尖晶石的 AA 从 4.14 到 41.37,cc 是 7941.3 和 7941.6。A/cA/c 从 0.00052 增到 0.00521,仍低于 0.01。

空气在 1 bar 和 100 bar 是同一行。c=341.6c=341.6,A=182.57A=182.57,A/c=0.53452A/c=0.53452。理想气体把密度按压力成比例放大,温度固定,cc 也就固定。所以 (γ−1)/γ=0.4/1.4\sqrt{(\gamma-1)/\gamma}=\sqrt{0.4/1.4} 写成 0.535。

只要有一点水,AA 就贴到水的数值上。αw=0.01\alpha_w=0.01 时 ρ=11.2\rho=11.2,A=62.00A=62.00。αw=0.50\alpha_w=0.50 时 ρ=500.6\rho=500.6,A=19.48A=19.48。αw=0.99\alpha_w=0.99 时 ρ=990.0\rho=990.0,A=18.45A=18.45,和纯水的 18.44 只差 0.01 m/s。分母里的混合密度和分子里的 ∑αkpkΓk\sum \alpha_k p_k \Gamma_k 都由水带走。脉冲表里显式峰值在 Courant 0.9 是 42.37 Pa,1.5 是 240.14 Pa,5.0 是 102438.55 Pa。隐式在同样三格里反而下降:30.52、25.76、16.02 Pa。Courant 20 的隐式峰值 9.82 Pa 是右行波基准 50 Pa 的 1/5,测得速度掉到 129.3 m/s。

刚性没有消失,而是移到了两步之间

那么水的1625 m/s去哪了?两个子系统里都没有 u±cu\pm c。压力子系统只有 ±A\pm A,输运子系统只有 uu。

真实声速在两步交替推进时重新出现。输运步中 ρk\rho_k 发生变化。状态方程以 ρc2\rho c^2 的比率把这一变化转换为压力变化。这个压力成为下一个压力步的输入。水的刚性从子系统特征值中退出,转移到了步与步之间的状态方程重算中。把声学放进一步、输运放进下一步来搬走刚性,把声学和输运分成两步的IMEX也是这么做的。

以下是笔者的解读,并非论文原文。单看压力步,似乎可以按 Δt≤Δx/A\Delta t\le\Delta x/A 显式处理。但这个不等式并不保证组合格式的稳定性。从这个角度看,论文即使在 AA 很小的水中也一律隐式求解压力步,是顺理成章的。

隐式压力步让脉冲存活,但被压平

输出的第二块是脉冲实验。显式Lax–Friedrichs在Courant 0.9时还能撑住,到1.5就崩溃。1.5时最大压力达到240 Pa,超过了初始值。5.0时超过10万Pa。隐式格式在20时也不发散。代价是峰值被压到9.82 Pa,只有精确解50 Pa的1/5。

这种衰减正是IMEX的交换条件。低马赫问题中,声波振幅不是关注对象。只要压力场能立刻跟上速度场就够了。若问题需要精确追踪声波,这笔交易就不划算。

论文在隐式压力步中为每一相建立压力方程。由于 Γk\Gamma_k 是压力的函数,方程是非线性的。用Picard迭代(把部分系数固定为上一次迭代值以实现线性化的迭代法)求解。空间离散采用紧致模板,因此矩阵是窄带矩阵。上面代码中的 thomas_solve 就相当于在1D中求解一个这样的带状矩阵。

低马赫下Δt变成1001倍的算术#

显式格式的时间步长受 Δx/(∣u∣+c)\Delta x/(|u|+c) 限制。IMEX中只有输运步是显式的,因此放宽到 Δx/∣u∣\Delta x/|u|。论文使用 ΔtIMEX=0.5 Δx/∣σ∣\Delta t_{IMEX}=0.5\,\Delta x/|\sigma|。假设Courant数相同,比值为:

ΔtIMEXΔtexp=∣u∣+c∣u∣=1+1M\frac{\Delta t_{IMEX}}{\Delta t_{exp}} = \frac{|u|+c}{|u|} = 1 + \frac{1}{M}

MM 是马赫数。M=0.3M=0.3 时为4.3倍,M=0.001M=0.001 时为1001倍。不过这是把每步成本视为相同的上限。隐式步每次Picard迭代都要解一个线性方程组。论文结论也这样说。低马赫问题收益很大,激波管问题收益很小甚至没有。

收回非守恒项损失能量的最后一步

分裂还附带一项善后工作。各相内能方程中有 αkpk∇⋅u\alpha_k p_k\nabla\cdot u 这类非守恒项。离散后,这些项不能精确守恒总能量。

因此论文把修正算子作为第三步。质量、动量、混合总能量和体积分数直接采用预测值。只对各相内能加上 αkn δε\alpha_k^n\,\delta\varepsilon。这样把差值分配下去,使之与混合总能量一致。压力松弛在IMEX步完成后单独进行。

只看水的 AA 就把压力步做成显式,组合格式撑不住#

尖晶石的 A/cA/c 是 0.00052,环氧树脂是 0.00333,1 bar 的水是 0.01135。三个都在 0.01 附近或更低。只看压力步,Δt≤Δx/A\Delta t\le\Delta x/A 像是可以显式。这个不等式只讲子系统。组合格式的刚性在 cc,不在 AA。1 bar 的水里 18.44 和 1624.9 是两个数。M=0.3M=0.3 时时间步比是 4.3 倍,M=0.001M=0.001 时是 1001 倍。在 1D 里,只有 1+1/M1+1/M 超过 Picard 迭代的成本,这个比才算收益。

Courant 20 的后向欧拉把这份输出里的脉冲峰值削到 9.82 Pa,是 50 Pa 的 1/5。声波振幅进结果的话,这个数字得写下来。隐式压力步把 L∞L_\infty 峰值压平的情形,和隐式压力步把 L∞L_\infty 峰值压平是同一件事。振幅要拿来分析,时间步就按声学来定,或者离开后向欧拉,改用更高阶的隐式积分。

如果对您有帮助,请分享。