Simple系列算法是求解不可压缩流的主要工具。OpenFoam中提供了SimpleFoam求解器、PisoFoam求解器以完成不可压缩流的求解。
但是OpenFoam中的代码与流体力学教材中的讲述并不一致,导致初学者难以理解代码的含义。笔者推荐一篇Martínez撰写的文章《Influence of momentum interpolation methods on the accuracy and convergence of pressure–velocity coupling algorithms in OpenFOAM》,该文章详细地推导了Simple算法、SimpleC算法和Piso算法,更重要的是其最终形式就是OpenFoam所采用的形式。
本文将直接使用Martínez的推导结果,将结果与OpenFoam中的代码对应,方便初学者理解代码含义。
文中公式(40)表示使用Rhie-Chow插值得到的面心速度
u e m = α u ( ∑ N B A N B u N B ∗ + B P ) e ( A P ) e − α u Δ y ( p E m − p P m ) ( A P ) e u_{e}^{m}=\frac{\alpha_{u}\left(\sum_{N B} A_{N B} u_{N B}^{*}+B_{P}\right)_{e}}{\left(A_{P}\right)_{e}}-\frac{\alpha_{u} \Delta y\left(p_{E}^{m}-p_{P}^{m}\right)}{\left(A_{P}\right)_{e}} uem=(AP)eαu(∑NBANBuNB∗+BP)e−(AP)eαuΔy(pEm−pPm)面心速度是用来构造连续方程的。考虑不可压缩流,对于任意一个单元,进出各面心的流量之和必然为0。所以针对一个单元,对上式求和(面积加权),即可得到压力方程
∑ S f α u Δ y ( p E m − p P m ) ( A P ) f = ∑ S f α u ( ∑ N B A N B u N B ∗ + B P ) f ( A P ) f \sum {{S_f}{{{\alpha _u}\Delta y\left( {p_E^m - p_P^m} \right)} \over {{{\left( {{A_P}} \right)}_f}}}} = \sum {{S_f}{{{\alpha _u}{{\left( {\sum\limits_{NB} {{A_{NB}}} u_{NB}^* + {B_P}} \right)}_f}} \over {{{\left( {{A_P}} \right)}_f}}}} ∑Sf(AP)fαuΔy(pEm−pPm)=∑Sf(AP)fαu(NB∑ANBuNB∗+BP)f
现在看看SimpleFoam求解器中的代码
fvm::laplacian(rAtU(), p) == fvc::div(phiHbyA)
代码与公式是严格对应的,rAtU是动量方程对角矩阵Ap的倒数。phiHbyA是H矩阵与Ap之商在面心插值后的结果。总之,代码与公式是严格对应的。
文中公式(48)是使用SimpleC算法和Rhie-Chow插值得到的面心速度
u e m = α u ( ∑ N B A N B u N B ∗ + B P ) e ( A P ) e − α u ( 1 ( A P ) e − 1 ( A P − ∑ N B A N B ) e ) Δ y ( p E m − 1 − p P m − 1 ) − α u Δ y ( p E m − p P m ) ( A P − ∑ N B A N B ) e u_{e}^{m}=\frac{\alpha_{u}\left(\sum_{N B} A_{N B} u_{N B}^{*}+B_{P}\right)_{e}}{\left(A_{P}\right)_{e}}-\alpha_{u}\left(\frac{1}{\left(A_{P}\right)_{e}}-\frac{1}{\left(A_{P}-\sum_{N B} A_{N B}\right)_{e}}\right) \Delta y\left(p_{E}^{m-1}-p_{P}^{m-1}\right)-\frac{\alpha_{u} \Delta y\left(p_{E}^{m}-p_{P}^{m}\right)}{\left(A_{P}-\sum_{N B} A_{N B}\right)_{e}} uem=(AP)eαu(∑NBANBuNB∗+BP)e−αu((AP)e1−(AP−∑NBANB)e1)Δy(pEm−1−pPm−1)−(AP−∑NBANB)eαuΔy(pEm−pPm)
同样对面求和之后有
∑ S f α u Δ y ( p F m − p P m ) ( A P − ∑ N B A N B ) f = ∑ S f ( α u ( ∑ N B A N B u N B ∗ + B P ) f ( A P ) f − α u ( 1 ( A P ) f − 1 ( A P − ∑ N B A N B ) f ) Δ y ( p F m − 1 − p P m − 1 ) ) \sum {{S_f}{{{\alpha _u}\Delta y\left( {p_F^m - p_P^m} \right)} \over {{{\left( {{A_P} - \sum\limits_{NB} {{A_{NB}}} } \right)}_f}}}} = \sum {{S_f}\left( {{{{\alpha _u}{{\left( {\sum\limits_{NB} {{A_{NB}}} u_{NB}^* + {B_P}} \right)}_f}} \over {{{\left( {{A_P}} \right)}_f}}} - {\alpha _u}\left( {{1 \over {{{\left( {{A_P}} \right)}_f}}} - {1 \over {{{\left( {{A_P} - \sum\limits_{NB} {{A_{NB}}} } \right)}_f}}}} \right)\Delta y\left( {p_F^{m - 1} - p_P^{m - 1}} \right)} \right)} ∑Sf(AP−NB∑ANB)fαuΔy(pFm−pPm)=∑Sf (AP)fαu(NB∑ANBuNB∗+BP)f−αu (AP)f1−(AP−NB∑ANB)f1 Δy(pFm−1−pPm−1)
现在看看SimpleFoam求解器中的代码
SimpleC在Simple算法的基础上对一些系数做了修改,如下
if (simple.consistent())
{
rAtU = 1.0/(1.0/rAU - UEqn.H1());
phiHbyA +=
fvc::interpolate(rAtU() - rAU)*fvc::snGrad(p)*mesh.magSf();
HbyA -= (rAU - rAtU())*fvc::grad(p);
}
rAtU 变成了 1 ( A P − ∑ N B A N B ) f {1 \over {{{\left( {{A_P} - \sum\limits_{NB} {{A_{NB}}} } \right)}_f}}} (AP−NB∑ANB)f1,phiHbyA变成了更复杂的形式。
压力泊松方程依然没有变化
fvm::laplacian(rAtU(), p) == fvc::div(phiHbyA)
代码与公式是严格对应的。
文中公式(55)是使用Piso算法和Rhie-Chow插值得到的面心速度
u e m = α u ( ∑ N B A N B u N B ∗ ∗ + B P ) e ( A P ) e − α u Δ y ( p E m − p P m ) ( A P ) e u_{e}^{m}=\frac{\alpha_{u}\left(\sum_{N B} A_{N B} u_{N B}^{* *}+B_{P}\right)_{e}}{\left(A_{P}\right)_{e}}-\frac{\alpha_{u} \Delta y\left(p_{E}^{m}-p_{P}^{m}\right)}{\left(A_{P}\right)_{e}} uem=(AP)eαu(∑NBANBuNB∗∗+BP)e−(AP)eαuΔy(pEm−pPm)
代码与公式之间是严格对应的。
事实上,Piso算法与Simple算法的压力修正方程并无区别。在OpenFoam中,Piso算法是为了算瞬态,而Simple算法只能算稳态。二者之间的区别主要在压力修正的计算次序上
Piso算法的流程如下:
while (runTime.loop())
{
Info<< "Time = " << runTime.userTimeName() << nl << endl;
#include "CourantNo.H"
// Pressure-velocity PISO corrector
{
fvModels.correct();
#include "UEqn.H"
// --- PISO loop
while (piso.correct())
{
#include "pEqn.H"
}
}
viscosity->correct();
turbulence->correct();
runTime.write();
Info<< "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
<< nl << endl;
}
代码中的runTime是物理时间,在每个物理时间步下完成速度预测与若干次压力修正。收敛之后,迈向下一个时间步。
Simple算法的流程如下:
while (simple.loop(runTime))
{
Info<< "Time = " << runTime.userTimeName() << nl << endl;
fvModels.correct();
// --- Pressure-velocity SIMPLE corrector
{
#include "UEqn.H"
#include "pEqn.H"
}
viscosity->correct();
turbulence->correct();
runTime.write();
Info<< "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
<< nl << endl;
}
代码中的runTime不是物理时间,而是一个递增的整数,代表迭代次数。每一步迭代都需要求解速度预测方程与压力修正方程。收敛后得到的是稳态解。