CFD: 可压 + 瞬态
可压缩求解器相对于不可压缩求解器还可以继续进行区分:
是否需要考虑体积力(比如温度变化引起的浮力);
弱可压缩还是强可压缩(前者典型的为人居环境的温度变化,后者主要是超音速流动);
对于超音速流动,也即可压缩性特别强的情况,得益于双曲方程的数学特征,这些方法通常采用瞬态计算且使用密度作为主要变量,然后通过状态方程求解压力,即密度基求解器。但密度基求解器在应用于弱可压缩流的情况下效率低下。一方面的解释是在不可压缩领域,压力与密度的耦合非常弱,另一方面的解释是不可压缩假定下音速趋向于无穷大,导致时间步长过小。
针对弱可压缩领域,fluid模块为一个压力基、可压缩、适用于全流速、也包含体积力的求解器,主要用于求解普适性可压缩流动,也可用于求解低音速/超音速流动。其可以处理瞬态问题,也可以处理稳态问题。本文讨论瞬态算法。
控制方程
fluid模块(buoyantFoam,甚至更老的buoyantPimpleFoam)中求解的为下述瞬态连续性方程、动量方程、以及状态方程:
(1)\[
\frac{\p\rho}{\p t}+\nabla\cdot\rho\bfU=0,
\]
(2)\[
\frac{\p\rho\bfU}{\p t}+\nabla \cdot (\rho\mathbf{U} \mathbf{U})=-\nabla p+\nabla \cdot\tau +\rho \bfg,
\]
(3)\[
p=\rho RT.
\]
其实还需要附加能量方程。但能量方程不涉及到速度-压力-密度耦合问题,此处略。上述三个方程,可用于迭代求解三个未知量:速度、压力、密度。首先,方程(2)中的重力项以及压力项可以进行数值处理。定义
(4)\[
p_\mathrm{rgh}=p-\rho \bfg \cdot\bfh
\]
其中\(\bfh\)表示网格单元体心的位置矢量。对方程(4)进行梯度操作:
(5)\[
\nabla p_\mathrm{rgh}=\nabla p-\bfg\cdot \bfh \nabla \rho - \rho \bfg
\]
即为
(6)\[
-\nabla p+\rho \bfg=-\bfg\cdot \bfh \nabla \rho -\nabla p_\mathrm{rgh}
\]
将方程(4)代入到方程(2)中有:
(7)\[
\frac{\p\rho\bfU}{\p t}+\nabla \cdot (\rho\mathbf{U} \mathbf{U})=-\bfg\cdot \bfh \nabla \rho -\nabla p_\mathrm{rgh}+\nabla\cdot\tau.
\label{mom}
\]
方程(7)为求解的动量方程。
密度方程
不同与稳态连续性方程,方程(1)可以用于更新密度。因此方程(1)可以离散为:
(8)\[
\int\int\left(\frac{\p\rho}{\p t}+\nabla\cdot\rho\bfU\right)\rd V\rd t=0,
\]
如果对时间项采用Euler格式进行离散,空间项隐性格式有:
(9)\[
\frac{\rho^{t+\dt}-\rho^t}{\Delta t}+\frac{1}{V_\rP}\sum_f \rho^{t+\dt}_f\bfU_f^{t+\dt}\cdot\bfS_f=0,
\]
方程(9)存在两个未知量:密度\(\rho^{t+\dt}\)与速度\(\bfU^{t+\dt}\)。不可解。因此,在对对流项离散的过程中,需要使用当前时间步的速度\(\bfU^{t}\)。在这种情况下,离散方程可以写为:
(10)\[
\frac{\rho^{*}-\rho^t}{\Delta t}+\frac{1}{V_\rP}\sum_f \rho^t_f\bfU_f^t\cdot\bfS_f=0,
\]
因为对流项的离散并没有采用\(\bfU^{t+\dt}\)。
方程(10)可以用于求解密度。但求解的密度并非当前时间步最终收敛的密度\(\rho^{t+\dt}\)。
注意:
方程(1)若需要去掉时间项,就变成了稳态的连续型方程。稳态的连续性方程并无明显的变量,是一种限定性条件。因此稳态求解器不存在密度方程。
速度方程
对方程(7)通过高斯定理进行对速度\(\bfU\)的离散,组建速度方程有:
(11)\[
\int\int \frac{\p\rho\bfU}{\p t} \rd V \rd t =
V_\rP \rho_\rP^{*}(\bfU^{*}_\rP - \bfU^t_\rP),
\]
Warning
方程(11)右侧之所以\(\rho\)带星号,是因为密度方程已经求结果。在这里是一个已知量。
(12)\[
\int\int {\nabla \cdot \left( {\rho\mathbf{U}\mathbf{U}} \right)\mathrm{d}V\rd t = } \Delta t\sum_f {{{\left( {\rho^{*}{\mathbf{U}^t}{\mathbf{U}^{*}}} \right)}_f}} \cdot\bfS_f = \Delta t\sum_f F_f^{t} \mathbf{U}_f^{*} ,
\]
Warning
方程(12)\(\rho\)带星号与之前一样的结果。第一次迭代的时候\(\bfU\)采用当前\(t\)的值。因此\(F_f^t=\rho_f^*\bfU_f^t\cdot\bfS_f\)。
(13)\[
\int\int \nabla p_\mathrm{rgh} \mathrm{d}V\rd t
=\Delta t\int p_\mathrm{rgh} \mathrm{d}\bfS
=\Delta t\sum_f p_{\mathrm{rgh},f}^t\bfS_f,
\]
(14)\[
\int\int \bfg\cdot \bfh \nabla \rho \mathrm{d}V\rd t
=\Delta t\int \bfg_f\cdot \bfh_f \rho_f \mathrm{d}\bfS
=\Delta t\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\bfS_f,
\]
其中上标\(^t\)表示为当前时间步(已知),出来密度外的上标\(^{*}\)表示当前时间步下的迭代步(待求),下标\(_f\)表示网格单元面上的值,\(\bfS_f\)表示网格单元的各个面的面矢量,\(F_f\)为质量通量。此处略去粘性项\(\tau\)的离散因为其不影响压力速度耦合。
为了防止方程(13)与方程(14)的离散产生数值振荡。其一般采取面法向梯度的形式来进行:
(15)\[
\Delta t\sum_f p_{\mathrm{rgh},f}^t\bfS_f
=\Delta t\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|,
\]
(16)\[
\Delta t\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\bfS_f
=\Delta t\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|,
\]
整理方程(15)、(16)、(12)、(11)有:
(17)\[\begin{split}
\frac{\rho_\rP^{*}V_\mathrm{P}}{\Delta t} (\bfU^{*}_\rP - \bfU^t_\rP)+\sum_f F_f^{t} \mathbf{U}_f^{*}
\hspace{20em}
\\
= -\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
-\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|.
\end{split}\]
需要注意的是,方程(17)左侧的对流项采用了线性化处理。方程(17)中的\(\bfU_f\)需要从体心速度进行插值来获得,在此步可以引入各种插值格式。假设使用中心线性格式(均一网格):
(18)\[
\mathbf{U}_f^{*} = \frac{{\mathbf{U}_\mathrm{P}^{*} + \mathbf{U}_\mathrm{N}^{*}}}{2}.
\]
将方程(18)代入到方程(17)有
(19)\[\begin{split}
\left( \sum_f \left(\frac{F_f^{t}}{2V_\rP} \right)+ \frac{\rho_\rP^{*} }{\Delta t} \right) \mathbf{U}_\mathrm{P}^{*}
+
\sum_N { { {\frac{{F_f^{t}}}{2V_
\rP} } \mathbf{U}_\mathrm{N}^{*}} }
\hspace{25em}\\
=
-\frac{1}{V_
P}\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
-\frac{1}{V_
P}\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\frac{\rho_\rP^{*} }{\Delta t} \bfU^t_\rP.
\hspace{7em}
\end{split}\]
其可简写为
(20)\[\begin{split}
{A_\mathrm{P}^{t}}\mathbf{U}_\mathrm{P}^{*}{\rm{ + }}\sum_N {A_\mathrm{N}^{t}\mathbf{U}_\mathrm{N}^{*}}
\hspace{27em}
\\
=
-\frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right)+\frac{1}{\Delta t} \bfU^t_\rP,
\hspace{2em}
\end{split}\]
其中\(A_\rP^{t}\),\(A_\rN^{t}\)分别表示当前网格点与相邻网格点的离散系数:
(21)\[
A_\mathrm{P}^{t}= \frac{\rho_\rP^{*} }{\Delta t}+\sum_f \frac{F_f^{t}}{2V_
P} ,
\]
(22)\[
A_\mathrm{N}^{t}= \frac{1}{V_\rP}{\frac{{F_f^{t}}}{2}} ,
\]
可见,\(A_\mathrm{P}\)与\(A_\mathrm{N}\)为关于\(\rho^{*}\)的代数式(且当前为已知量)。方程(20)构成一个稀疏线性系统。求解方程(20)即可获得速度\(\mathbf{U}^{*}\)。
对方程(20)进行转换有:
(23)\[
\mathbf{U}_\mathrm{P}^{*} = \mathbf{HbyA}^{*}_\mathrm{P} - \frac{1}{{{A^{t}_\mathrm{P}}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right).
\]
其中\(\bfHbyA\)定义为:
(24)\[
\bfHbyA_\rP^{*}=\frac{1}{A_\rP^{t}}\left(-\sum_NA_\rN^{t}\bfU_\rN^{*}+\frac{1}{\Delta t} \bfU^t_\rP\right).
\]
同时,面插值有:
(25)\[
\mathbf{U}_f^{*} = \mathbf{HbyA}^{*}_f - \frac{1}{{{A_f^{t}}}}
\left(
\frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^t\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \rho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right)
\right)_f,
\]
(26)\[
\bfHbyA_f^{*}=\frac{1}{A_f^{t}}\left(-\sum_NA_\rN^{t}\bfU_\rN^{*}+\frac{1}{\Delta t} \bfU^t_\rP\right)_f.
\]
能量方程
求解完动量方程之后,需要求解能量方程获得\(T^*\)。能量方程的离散与速度方程的离散思路相同,在这里不做介绍。在求解能量后,可以更新流体的可压缩性\(\psi^{*}\):
(27)\[
\psi^{*}=\frac{1}{RT^*}
\]
注意:
thermo.correct()
函数可以对\(\psi=1/RT\)进行更新。
同时再次更新热物理模型库中的密度:
(28)\[
\varrho ^{*}=p^t \psi^{*}
\]
注意,这里的密度\( \varrho \)存储在thermo
中,而不是求解器的rho
\(\rho\)。注意在迭代过程中,rho
不同于thermo.rho()
,\(\rho \neq \varrho\)。
压力方程
方程(23)在收敛的情况下可以写为:
(29)\[\begin{split}
\mathbf{U}_\mathrm{P}^{t+\dt}
\hspace{40em}
\\
= \mathbf{HbyA}^{t+\dt}_\mathrm{P} - \frac{1}{{{A^{t+\dt}_\mathrm{P}}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^{t+\dt}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \rho_f^{t+\dt}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right).
\hspace{8em}
\end{split}\]
在这里需要做一系列的假定:
略去临点的影响;
\(A^{t+\dt}_\mathrm{P}=A^{t}_\mathrm{P}\),否则其为一个非线性方程组不可解,也可以认为\(\dt\)间隔比较小的时候忽略其影响;
方程(29)与方程(23)相减有:
(30)\[
\mathbf{U}_\mathrm{P}^{'} = - \frac{1}{{{A^{t}_\mathrm{P}}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^{'}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \rho_f^{'}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right).
\]
方程(30)需要提供了速度修正与压力修正的一对一关系。但发现针对浮力驱动流,其中还存在密度修正。如果顺着这个思路进行,压力方程将进一步出现未知变量密度导致无法封闭。
Warning
这也是可压缩求解器与不可压缩求解器的主要区别。可压缩求解器的速度压力耦合还需要考虑密度。
为了处理这个问题,我们可以将方程(30)中的密度修正忽略掉。这样,即存在\(\mathbf{U}_f^{**}\)与\(p^{*}\)的一对一关系,结合连续性方程,可以用来求解压力。这是一种方法,但处理起来比较激进,可能会导致变量增量过大,引起发散。
如果想考虑密度修正的话,则在压力方程中,会存在一个未知的密度变量导致不封闭。但同时需要注意的是,密度变量之前从状态方程中已经得出一个更新值\(\varrho^*\),目前并没有使用。因此在这里,可以认为\(\varrho^*\)与密度修正量\(\rho^{'}\)的关系满足:
(31)\[
\varrho^*=\rho^{*}+\rho^{'}
\]
由于\(\varrho^*\)已求出,因此压力方程仅仅存在未知量压力。依据此思想,将方程(30)与方程(23)加和有:
(32)\[
\mathbf{U}_\mathrm{P}^{**} = \mathbf{HbyA}^{*}_\mathrm{P} - \frac{1}{{{A^t_\mathrm{P}}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \varrho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right).
\]
(33)\[
\mathbf{U}_f^{**} = \mathbf{HbyA}^{*}_f - \frac{1}{{{A^t_f}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
+\sum_f \bfg_f\cdot \bfh_f \varrho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right)_f.
\]
注意:
这里\(\mathbf{U}^{**} =\mathbf{U}^{*}+\bfU^{'}\),而不是\(\mathbf{U}^{t+\dt} =\mathbf{U}^{*}+\bfU^{'}\),这是因为已经做了一系列的假定。同时,方程(33)中的密度已经被求出,因此方程(33)可以在后续中用来组建压力方程。
压力方程
首先考虑低速流的情况,低速流认为\(\mathbf{U}_f^{**}\)需要满足连续性方程:
(34)\[
\frac{\rho^{t+\dt}-\rho^t}{\dt}+\frac{1}{V_\rP}\sum_f \rho_f^{t+\dt}\mathbf{U}_f^{t+\dt} \cdot \bfS_f=0.
\]
现在我们通过方程(34)来构造压力柏松方程。首先,方程(34)要对当先的速度\(\bfU^{**}\)进行实施,来构成一种限定性条件,即:
(35)\[
\frac{\rho^{t+\dt}-\rho^t}{\dt}+\frac{1}{V_\rP}\sum_f \rho_f^{t+\dt}\mathbf{U}_f^{**} \cdot \bfS_f=0.
\]
再次,方程(35)中的对流项存在未知变量密度\(\rho^{t+\dt}\),因此要做线性化:
(36)\[
\frac{\rho^{t+\dt}-\rho^t}{\dt}+\frac{1}{V_\rP}\sum_f \varrho_f^{*}\mathbf{U}_f^{**} \cdot \bfS_f=0.
\]
同样,方程(36)的时间项存在未知的变量\(\rho^{t+\dt}\)。OpenFOAM采用了一种巧妙的思想来进行设计,在这里假定读者是熟悉PISO算法需要若干次迭代的:
(37)\[
\rho^{t+\dt}-\rho^t = \rho^{t+\dt}-\varrho^{*}+(\varrho^{*}-\rho^t)
\]
其中的\(\varrho^{*}\)就是需要进行多次修正,然后趋向于\(\rho^{t+\dt}\)。假定PISO算法做了2次迭代认为\(\rho^{**}\)趋向于\(\rho^{t+\dt}\)。则有\(\underset{=0}{\underbrace{ (\rho^{t+\Delta t}-\varrho^{**} ) }} \)。方程方程(38)就变为:
(38)\[
\underset{=0}{\underbrace{ (\rho^{t+\Delta t}-\varrho^{**} ) }} +(\varrho^{**}-\rho^t)=\rho^{t+\dt}-\rho^t
\]
参考这个思想,结合状态方程,将方程(38)代入到方程(36)中有:
(39)\[\begin{split}
\frac{\psi^{*} p^{*}_\mathrm{rgh}-\psi^{*} p^t_\mathrm{rgh}}{\dt}
+\frac{\varrho^{*}-\rho^t}{\dt}
+\sum_f \varrho_f^{*} \mathbf{HbyA}^{*}_f \cdot \bfS_f
\hspace{15em}
\\
-\sum_f
\frac{\varrho^{*}_f}{{{A^t_f}}} \frac{1}{V_\rP}
\left(
\sum_f \bfg_f\cdot \bfh_f \varrho_f^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right)_f
\cdot \bfS_f
\hspace{7em}
\\
= \sum_f\frac{\varrho^{*}_f}{{{A^t_f}}} \frac{1}{V_\rP}
\left(
\sum_f p_{\mathrm{rgh},f}^{*}\frac{\bfS_f}{|\bfS_f|}|\bfS_f|
\right)_f
\cdot \bfS_f.
\hspace{3em}
\end{split}\]
注意:
这里\(\frac{p^{*}_\mathrm{rgh}-p^t_\mathrm{rgh}}{\dt}\)对应correction(fvc::ddt(p_rgh))
函数。correction()
函数类似于fvm::ddt()
函数,不同的是fvm::ddt()
在同一个时间步之内,一直表示的为\((p^{*}-p^t)/\dt\),其中\(p^{*}\)表示待求变量。若进行一次压力求解,fvm::ddt()
变为\((p^{**}-p^t)/\dt\)。继续进行压力求解,则变为\((p^{***}-p^t)/\dt\)。correction(fvm::ddt())
则在最初表示的为\((p^{*}-p^t)/\dt\)。进行一次压力求解,变成\((p^{**}-p^{*})/\dt\)。再进行一次压力求解,变成\((p^{***}-p^{**})/\dt\)。同时也可以看出,在最终一个时间步收敛的情况下,\(p^{***}\)趋向于\(p^{**}\),因此correction(fvm::ddt())
作用趋向于0。有关correction()
函数的详细内容请参考无痛苦N-S方程笔记。
可见,方程(39)可用于求解获得压力\(p_\mathrm{rgh}^*\)。将\(p_\mathrm{rgh}^*\)代入到方程(33)有\(\mathbf{U}_\mathrm{P}^{**}\)。
连续性误差与迭代
在前文中已经讨论过,因为是瞬态,因此存在密度的传输方程,因此有两种方法来更新密度:
上述两种更新密度的方法,在一个时间步下收敛后,必然一致。因此,可以通过\(\varrho^{**}\)与\(\rho^{**}\)的差来判断当前时间步下是否收敛:
(40)\[
\sum (\varrho^{**}_\rP-\rho^{**}_\rP)V_\rP =? 0
\]
至此,我们有了满足连续性方程的速度场\(\mathbf{U}_\mathrm{P}^{**}\)。但由于在上述推导过程中,引入了大量的假设,\(\mathbf{U}_\mathrm{P}^{**}\)与\(p^{*}\)并不是严格满足动量方程。同时,方程(40)也未必满足。在这里可以把求得的这些场当作初始场,在一个时间步内再进行迭代求解。直到方程(40)满足为止。
总之,可压缩瞬态的迭代过程可以表示为下面几个步骤:
通过方程(10)求解获得\(\rho^*\);
通过\(\rho^*\),组建速度方程,获得\(\bfU^*\);
组建能量方程,更新温度等变量,更新可压缩性\(\psi^{*}\),通过状态方程更新\(\varrho^{*}\);
通过第二步的\(\bfU^*\)组建\(\bfHbyA^*\),构建压力柏松方程(39),求解获得\(p_{\mathrm{rgh}}^*\);
通过\(p_{\mathrm{rgh}}^*\)更新速度\(\bfU^{**}\),求解方程(10),获得\(\rho^{**}\);
通过方程(40)判断连续性误差;
回到第二步进行迭代,直至通过密度方程求解的\(\rho\)与状态方程更新的密度\(\varrho\)趋向于一致为止;
关键代码
针对上述求解流程,流程第一步,对应于代码中最开始的密度方程求解,获得\(\rho^*\):
流程第二步,需要组建速度方程。b方程右侧,采用reconstruct()
函数保证无振荡:
UEqn
==
fvc::reconstruct
(
(
- ghf*fvc::snGrad(rho)
- fvc::snGrad(p_rgh)
)*mesh.magSf()
)
上述代码的源项对应方程(15)、(16)。在速度方程更性之后,求解能量方程,进入流程第三步,并通过下述代码进行可压缩性的更新,获得\(\psi^*\):
以及密度的更新\(\varrho^*\):
流程第四步,需要组建压力柏松方程:
fvScalarMatrix p_rghDDtEqn
(
psi*correction(fvm::ddt(p_rgh))
+ fvc::ddt(rho)
+ fvc::div(phi)
- fvm::laplacian(rhorAUf, p_rgh)
);
上述代码对应方程(39)。流程第五步,在求解压力之后,更新速度,更新密度:
phi += p_rghEqn.flux();
...
#include "rhoEqn.H"
流程第六步,更新\(\varrho^{**}\):
const volScalarField psip0(psi*p);
...
thermo.correctRho(psi*p - psip0);
流程第七步,判断连续性误差:
#include "compressibleContinuityErrs.H"
流程第八步,继续进行迭代。流程第九步,令通过密度方程求解的\(\rho\)与状态方程更新的密度\(\varrho\)完全相等: