本文译自作者的韩语原文。 阅读原文(한국어)
去掉K之后水被压缩了1.86倍 — 加密网格也不会减小的界面误差
界面单元里ψ+K几乎等于零。K正是用来抹去ψ∇·u的系数,去掉这一项,液体就会按气体的压缩率被压。
可压缩VOF的颜色函数输运方程多了一项:。我把这个 写进代码并打印了它的数值。水与空气的界面单元在 时得到 ,于是 。为了让这一项正确而加进去的系数,几乎把这一项本身抹掉了。
本文从Denner的压力基全耦合可压缩多相有限体积法论文出发,用等压封闭直接推导 。随后复现把 置零或把符号反过来时,一维压缩问题中液体体积分别多减1.86倍和4.23倍的结果。最后看看为什么把网格加密8倍,这个误差只减少7%。
可压缩VOF为什么带着 #
不可压缩VOF中颜色函数只是随流而动。因为 ,只做输运就够了。一旦考虑可压缩性,流体会膨胀和收缩。颜色函数是体积分数,必须跟上这种膨胀与收缩。
论文中的式子是这样:
其中 是液体体积分数, 是速度, 是随物性变化的压缩率因子(compressibility factor)。把第三项移过去就得到物质导数形式。
若 ,体积分数沿物质路径就不变。看起来合理,其实是错的。界面单元看到的 是混合物的整体值,而混合物的压缩率由软的一侧主导。
用数字说话。1个大气压下水的体积模量为 Pa,空气为 Pa,比值18,857。混合物压缩率是按体积分数加权的和,所以即使 ,空气那一项也几乎占满全部。结果混合物声速跌到23.65 m/s,比水慢69倍,比空气慢14倍。
在下面的模拟中直接拖动体积分数试试。
crossing 2.0 m: liquid 1.23 ms | gas 5.85 ms | mixture 84.57 ms
把 滑块从零往上抬一点,声速就从341 m/s垂直跌到100 m/s以下。下方的跑道显示声波脉冲穿过同一段2 m介质所需的时间。液体只占0.1%体积,混合物那条跑道就已明显落后于纯气体。
是从哪里来的数#
来自一个假设:等压封闭(isobaric closure)——界面单元内两相共享同一压力,各自以自己的体积模量响应。这与论文在ACID中使用的局部单相假设是同一回事。
跟随一个拉格朗日单元,要求相 的质量守恒,可以写出
是该相占据的体积, 是它的体积模量。两相相加就得到混合物的散度。
方括号里就是混合物压缩率,Wood声速由此自动浮现。接着对 求导并代入上面两式,剩下
乘在前面的是两个压缩率之差。两相体积模量相同时,体积分数根本不动。与 对比并消去 , 就掉出来了。
分母里有 和 ,在纯相单元会发散,所以代码里应该写约分后的形式。
这个形式在 和 处直接为零。分子的符号完全取决于 指的是哪一相。照抄论文公式时标号一换,符号就可能反掉。后面会测出这个代价。
如果 是 ,两项能一起删掉吗#
取 的极限。分子趋于 ,分母趋于 ,于是
物理上这完全正确。水几乎不可压缩,所以水的体积应该只被输运。把 压到零,正是这种行为。
那么干脆两项都不要?纯液体单元是另一回事。 且 ,于是 。液体也应该按自己的压缩率被压缩,而这一项承担的就是这件事。抵消是否彻底也取决于体积模量之比。比值在 附近时(超临界流体之间,或气体对气体), 处 ,并不是零。
从1 bar压到10 bar,究竟差多少#
在欧拉网格上直接测。把区间 的左半填水,右半填空气。左端是壁面(),右端是允许纯空气流入的开边界。压力在空间上保持均匀,从1 bar线性升到10 bar。采用stiffened-gas状态方程(, Pa),面速度由上面推导的混合物压缩率积分得到。
水不会到达右边界,所以液体质量守恒。于是精确液体体积直接就是 ,参考解白送。
import math
P0, PEND = 1.0e5, 1.0e6 # 1 bar -> 10 bar
GAM_L, PI_L, RHO_L0 = 4.4, 6.0e8, 1000.0 # 水 (stiffened gas)
GAM_G, PI_G, RHO_G0 = 1.4, 0.0, 1.2 # 空气
def stiffened_rho(p, gam, pi, rho0):
return rho0 * ((p + pi) / (P0 + pi)) ** (1.0 / gam)
def stiffened_bulk(p, gam, pi):
return gam * (p + pi) # rho*a^2
def wood_speed(psi, p):
rho_m = psi * stiffened_rho(p, GAM_L, PI_L, RHO_L0) \
+ (1 - psi) * stiffened_rho(p, GAM_G, PI_G, RHO_G0)
comp = psi / stiffened_bulk(p, GAM_L, PI_L) \
+ (1 - psi) / stiffened_bulk(p, GAM_G, PI_G)
return math.sqrt(1.0 / (rho_m * comp)), rho_m
def compressibility_factor(psi, p, mode):
bl = stiffened_bulk(p, GAM_L, PI_L)
bg = stiffened_bulk(p, GAM_G, PI_G)
k = (bg - bl) * psi * (1 - psi) / (bg * psi + bl * (1 - psi))
return 0.0 if mode == 'zero' else (-k if mode == 'flip' else k)
def squeeze_column(mode, ncell, nstep, psi0=0.5):
"""左壁 u=0,右侧纯气体流入,p(t) 从 1 到 10 bar 线性上升"""
dx, dt = 1.0 / ncell, 1.0 / nstep
psi = [1.0 if (i + 0.5) * dx < psi0 else 0.0 for i in range(ncell)]
for n in range(nstep):
p = P0 + (PEND - P0) * (n * dt)
pdot = PEND - P0
bl = stiffened_bulk(p, GAM_L, PI_L)
bg = stiffened_bulk(p, GAM_G, PI_G)
u = [0.0] * (ncell + 1) # 混合物压缩率 -> du/dx
for i in range(ncell):
u[i + 1] = u[i] - dx * pdot * (psi[i] / bl + (1 - psi[i]) / bg)
nxt = psi[:]
for i in range(ncell):
ur, ul = u[i + 1], u[i]
right = psi[i + 1] if i + 1 < ncell else 0.0
fr = ur * (psi[i] if ur > 0 else right)
fl = (ul * (psi[i - 1] if ul > 0 else psi[i])) if i > 0 else 0.0
src = (psi[i] + compressibility_factor(psi[i], p, mode)) * (ur - ul) / dx
nxt[i] = min(1.0, max(0.0, psi[i] - dt * (fr - fl) / dx + dt * src))
psi = nxt
exact = psi0 * stiffened_rho(P0, GAM_L, PI_L, RHO_L0) \
/ stiffened_rho(PEND, GAM_L, PI_L, RHO_L0)
return sum(psi) * dx, exact
print("[1] stiffened-gas properties")
for p in (P0, PEND):
rl = stiffened_rho(p, GAM_L, PI_L, RHO_L0)
rg = stiffened_rho(p, GAM_G, PI_G, RHO_G0)
print(f" p={p/1e5:4.1f} bar rho_l={rl:8.3f} rho_g={rg:6.3f}"
f" rho_l*a_l^2={stiffened_bulk(p,GAM_L,PI_L):.3e}"
f" rho_g*a_g^2={stiffened_bulk(p,GAM_G,PI_G):.3e}")
print("\n[2] K and (psi+K) at 1 bar")
for psi in (0.10, 0.25, 0.50, 0.75, 0.90):
k = compressibility_factor(psi, P0, 'acid')
a, _ = wood_speed(psi, P0)
print(f" psi={psi:4.2f} K={k:+9.6f} psi+K={psi+k:+.3e} a_wood={a:7.2f} m/s")
print("\n[3] liquid-volume change after 1->10 bar compression")
print(" N exact ACID K=0 K flipped | x exact")
for ncell in (50, 100, 200, 400):
out = {}
for mode in ('acid', 'zero', 'flip'):
v, exact = squeeze_column(mode, ncell, 10 * ncell)
out[mode] = v - 0.5
d = exact - 0.5
print(f" {ncell:4d} {d:.4e} {out['acid']:.4e} {out['zero']:.4e} "
f"{out['flip']:.4e} | {out['zero']/d:5.3f} {out['flip']/d:5.3f}")[1] stiffened-gas properties
p= 1.0 bar rho_l=1000.000 rho_g= 1.200 rho_l*a_l^2=2.640e+09 rho_g*a_g^2=1.400e+05
p=10.0 bar rho_l=1000.341 rho_g= 6.215 rho_l*a_l^2=2.644e+09 rho_g*a_g^2=1.400e+06
[2] K and (psi+K) at 1 bar
psi=0.10 K=-0.099994 psi+K=+5.891e-06 a_wood= 39.23 m/s
psi=0.25 K=-0.249982 psi+K=+1.767e-05 a_wood= 27.28 m/s
psi=0.50 K=-0.499947 psi+K=+5.302e-05 a_wood= 23.65 m/s
psi=0.75 K=-0.749841 psi+K=+1.590e-04 a_wood= 27.32 m/s
psi=0.90 K=-0.899523 psi+K=+4.770e-04 a_wood= 39.43 m/s
[3] liquid-volume change after 1->10 bar compression
N exact ACID K=0 K flipped | x exact
50 -1.7027e-04 -1.7027e-04 -3.1695e-04 -7.1967e-04 | 1.861 4.227
100 -1.7027e-04 -1.7027e-04 -3.1559e-04 -7.0606e-04 | 1.853 4.147
200 -1.7027e-04 -1.7027e-04 -3.1256e-04 -6.7737e-04 | 1.836 3.978
400 -1.7027e-04 -1.7027e-04 -3.0670e-04 -6.2727e-04 | 1.801 3.684K = 0、反号、体积模量比滑块会改变液柱缩短的速度。
V_liquid: scheme 0.50000 vs exact 0.50000 → +0.00%
打开 K = 0 再播放,蓝色块比精确解缩得更快。把比值降到 ,三根柱子叠到一起,而 的读数离开 。
压力升高十倍的过程中,水实际减少的体积是 ,占初始体积的0.034%。ACID封闭把这个值对到小数第五位。 削掉的却是 ,是物理压缩量的1.84倍。
0.034%和0.062%之差听起来很小。但对照的基准不是总体积,而是物理压缩量。格式凭空添加了这个量的84%。在气泡崩溃、空化这类体积变化本身就是答案的问题里,这个比例直接决定结论。
表[2]接近对称。时,,声速 m/s。时,,声速 m/s。两端声速只差 m/s,却拉开约倍。谷底是的 m/s,比慢 m/s,接近。体积模量比等于。这个比把的贴到旁边,也就是表里的,为。表[3]里的倍率从的降到的,少了。反号的倍率从降到,少了。同样加密倍,符号误差的降幅大约是的倍。时和仍然远离。上与之差是,四舍五入就是上文的。是减去,得。 bar时液体密度从升到,增加,气体从跳到。丢掉的向界面索取的,是这份很小的液体压缩的倍。
网格加密8倍,误差会消失吗#
表[3]最后两列给出答案。 从50加到400,加密8倍, 的比值只从1.861变到1.801。按误差绝对值算,从 到 ,减少7%。这谈不上收敛。
原因在于 不是离散化误差。把 置零,所求解的方程本身就变成了 。格式收敛到的是另一个方程的解,没有理由在 时让差距消失。
剩下那7%来自抹开的界面带随网格略微改变形状。误差大小等于在该带内对 的积分,而一阶上风格式的数值扩散在加密时会按单元数把带拉宽。带的物理厚度几乎不变,积分值也就几乎不变。这条带一步能推多远,由VOF界面输运的CFL上限另行规定。
这个性质既是坏消息也是好消息。网格收敛性测试抓不到这个错误。反过来说,跑两套网格而比值维持不变,这份平坦本身就是封闭写错了的信号。
符号反过来会错成什么样
第三列是反号的结果。 时系数变成 ,压缩量跳到物理值的3.68~4.23倍。方向也翻了:明明在压缩,液体体积分数却在下降,画面上看就像气泡在长大。
麻烦在于这一侧同样给出一幅像样的图。界面依旧光滑,体积分数依旧落在 内,也不发散。检查单相极限同样抓不到,因为 和 时两种符号下 都为零。
所以把论文公式搬进代码时,用注释钉死 代表哪一相的体积分数,再用上面那个一维压缩算例锁定符号,这样最快。50个单元大约一秒就跑完。
同一个假设在焓里再用一次的地方
论文中的ACID(Acoustically-Conservative Interface Discretisation)把这个等压封闭分别用在密度和焓上。密度是容易的一半。对分密度做线性插值得到
它与等压封闭在数学上完全等价,质量和动量守恒随之成立。
焓不一样。 是线性系统中直接求解的一次变量,不能像密度那样换掉插值式。于是方法另外计算一个带有ACID假设的目标面焓。
和 是上游与下游单元, 是插值权重, 是限制器。再把它与当前值之差作为延迟修正项加到能量方程右端。
与 是同一个假设的两张面孔。在体积分数方程里它代数地闭合,坍缩成一个系数;在焓这个一次变量上它闭合不了,只能延迟成显式源项。延迟的那一半必须在迭代内收敛,所以会反映到非线性迭代次数上。用等压收到同一个压力上,是先用两个压力求解再松弛成一个压力那条路的终点。差别在于这边把那次松弛折进了一个。
Denner能丢掉黎曼求解器的原因#
密度基解法把界面当黎曼问题求解,从而保持接触间断。压力基全耦合解法把 从求解变量中剔除,把 、、 绑进同一个线性系统。同位网格上出现的压力-速度解耦靠MWI压住。在极端密度比下,面密度用调和平均加权。用重构保住接触间断的另一条路,是挑选更小振幅的MUSCL-THINC-BVD。
丢掉黎曼求解器,同时也丢掉了自动照料界面物理的那套机制。这个缺口只能用封闭假设手工填上。 与 就是填料。
要隐式跨过23.65 m/s的混合物声速又不把接触间断抹开,封闭就必须准确。声学一旦隐式,CFL的账单就转到输运一侧。把声学和输运拆开再解CFL的分离就是那个位置。1.86倍,就是对这个封闭所做工作量的一次测量。
相关文章
如果对您有帮助,请分享。