算例目录heatTransfer/chtMultiRegionFoam/heatedDuct
通过Allrun
了解其执行步骤:
blockMesh
snappyHexMesh -overwrite
splitMeshRegions -cellZones -overwrite
decomposePar -allRegions
chtMultiRegionFoam
reconstructPar -allRegions
该字典文件代码段。注意到blocks
段落中出现了fluid
关键词,这是icoFoam
算例中没有的。通过观察blockMesh的运行输出文件,该关键词的效果是生成了名为fluid
的cellZone,其包含 20 ∗ 24 ∗ 60 20*24*60 20∗24∗60个网格
convertToMeters 0.001;
vertices
(
(-0.001 -10.001 -0.001) // (x0, y0, z0)
(50.001 -10.001 -0.001) // (x1, y0, z0)
(50.001 50.001 -0.001) // (x1, y1, z0)
(-0.001 50.001 -0.001) // (x0, y1, z0)
(-0.001 -10.001 150.001) // (x0, y0, z1)
(50.001 -10.001 150.001) // (x1, y0, z1)
(50.001 50.001 150.001) // (x1, y1, z1)
(-0.001 50.001 150.001) // (x0, y1, z1)
);
blocks
(
hex (0 1 2 3 4 5 6 7) fluid (20 24 60) simpleGrading (1 1 1)
);
defaultPatch
{
type patch; // 定义默认面的类型为patch
}
运行该文件会在constant/
下生成polyMesh/
,用于记录网格信息。
该工具可以自动的从STL和OBJ文件生成六面体和多面体网格,网格依靠迭代将一个初始网格细化,并将细化后的网格变形以依附于表面。运行该工具需提供
#includeEtc "caseDicts/mesh/generation/snappyHexMeshDict.cfg"
castellatedMesh true; // 是否切割网格
snap true; // 是否进行网格贴合或对齐
addLayers false; // 是否添加边界层
geometry // 所用的几何文件
{
heatedDuct // 整个构体几何外型
{
type triSurfaceMesh; // 类型,除此之外还有searchableBox
file "heatedDuct.stl"; // 文件名
regions // 当stl文件内包含多个solid时需指定
{
metalInlet { name metalInlet; } // stl文件内的域名
heaterInlet { name heaterInlet; }
fluidInlet { name fluidInlet; }
metalOutlet { name metalOutlet; }
heaterOutlet { name heaterOutlet; }
fluidOutlet { name fluidOutlet; }
metalExternal { name metalExternal; }
heaterExternal { name heaterExternal; }
}
}
metalToHeater // 金属套管和加热器的接触面
{
type triSurfaceMesh;
file "metalToHeater.stl";
}
fluidToMetal // 流体和金属套管接触面
{
type triSurfaceMesh;
file "fluidToMetal.stl";
}
};
castellatedMeshControls // 切割或细化网格的字典
{
refinementSurfaces // 指定需要细化的面,相应还有refinementRegions
{
heatedDuct
{
level (0 0); // 该面默认的细化程度:细化等级的最小、最大值
regions // 除了默认,指定以下region的细化方式以及面的类型
{
metalInlet { level (0 0); patchInfo { type patch; } }
heaterInlet { level (0 0); patchInfo { type patch; } }
fluidInlet { level (0 0); patchInfo { type patch; } }
metalOutlet { level (0 0); patchInfo { type patch; } }
heaterOutlet { level (0 0); patchInfo { type patch; } }
fluidOutlet { level (0 0); patchInfo { type patch; } }
metalExternal { level (1 1); patchInfo { type wall; } }
heaterExternal { level (1 1); patchInfo { type wall; } }
}
}
fluidToMetal
{
level (1 1);
faceZone fluidToMetal; // 生成faceZone的名称
cellZone metal; // 生成cellZone的名称
mode insidePoint; // 如何生成cellZone
insidePoint (0.025 0.0025 0.075);
}
metalToHeater
{
level (1 1);
faceZone metalToHeater;
cellZone heater;
mode insidePoint;
insidePoint (0.025 -0.005 0.075);
}
}
nCellsBetweenLevels 1;
insidePoint (0.025 0.025 0.075);
}
addLayersControls
{
relativeSizes true; // 相对边界层厚度
minThickness 1; // 边界层网格最小厚度
finalLayerThickness 1; // 壁面最近的边界层厚度
expansionRatio 1; // 膨胀因子
layers
{}
}
执行该命令后,
polyMesh/faceZones
中生成了fluidToMetal和metalToHeater,在polyMesh/cellZones
中生成了fluid,metal和heater该工具将网格划分为多个区域,可以将已有的多个cellZones
转化为一个cellZone
splitMeshRegions -cellZones
Each region is defined as a domain whose cells can all be reached by cell-face-cell walking without crossing
可见,上述命令将根据已有的cellZone将创建region,该region中的网格彼此相连,直到接触到另一个cellZone
为止。
该工具运行时会先检验system/xx/fvScheme
文件是否存在,如果存在就会创建名为xx
的region,生成如下文件
该求解器采用了分离式的求解策略,即上一个方程的求解结果作为输入条件应用于下一个方程的求解。在共轭传热问题中,求解步骤为
压力基求解器的释义:The solver used to solve the fluid equations is a pressure bases solver. That means that a pressure equation (similar to the pressure equation used in an incompressible solver) is used to establish the connection between the momentum and the continuity equation.
int main(int argc, char *argv[])
{
#define NO_CONTROL
#define CREATE_MESH createMeshesPostProcess.H
#include "postProcess.H"
#include "setRootCaseLists.H"
#include "createTime.H"
#include "createMeshes.H"
pimpleMultiRegionControl pimples(fluidRegions, solidRegions);
#include "createFields.H"
#include "initContinuityErrs.H"
#include "createFluidPressureControls.H"
#include "createTimeControls.H"
#include "readSolidTimeControls.H"
#include "compressibleMultiRegionCourantNo.H"
#include "solidRegionDiffusionNo.H"
#include "setInitialMultiRegionDeltaT.H"
while (pimples.run(runTime))
{
#include "readTimeControls.H"
#include "readSolidTimeControls.H"
#include "compressibleMultiRegionCourantNo.H"
#include "solidRegionDiffusionNo.H"
#include "setMultiRegionDeltaT.H"
runTime++;
Info<< "Time = " << runTime.userTimeName() << nl << endl;
// Optional number of energy correctors
// 若没有在fvSolution中找到该项,则设为1,本算例中没有该项
const int nEcorr = pimples.dict().lookupOrDefault<int>
(
"nEcorrectors",
1
);
// --- PIMPLE loop
while (pimples.loop())
{
List<tmp<fvVectorMatrix>> UEqns(fluidRegions.size()); // 建立一个列表,里面每个元素都是fvVectorMatrix
for(int Ecorr=0; Ecorr<nEcorr; Ecorr++) // nEcorr=1
{
// solidRegions & fluidRegions 存在于constant/regionProperties
forAll(solidRegions, i)
{
Info<< "\nSolving for solid region "
<< solidRegions[i].name() << endl;
#include "setRegionSolidFields.H" // volScalarField& e = thermo.he()
#include "solveSolid.H"
}
forAll(fluidRegions, i)
{
Info<< "\nSolving for fluid region "
<< fluidRegions[i].name() << endl;
#include "setRegionFluidFields.H"
#include "solveFluid.H"
}
}
}
runTime.write();
Info<< "ExecutionTime = " << runTime.elapsedCpuTime() << " s"
<< " ClockTime = " << runTime.elapsedClockTime() << " s"
<< nl << endl;
}
Info<< "End\n" << endl;
return 0;
}
regionProperties rp(runTime);
#include "createFluidMeshes.H"
#include "createSolidMeshes.H"
const wordList fluidNames
(
rp.found("fluid") ? rp["fluid"] : wordList(0)
);
PtrList<fvMesh> fluidRegions(fluidNames.size()); // 是一个指针列表,每一个指针所指向的元素都是fvMesh类型
forAll(fluidNames, i)
{
Info<< "Create fluid mesh for region " << fluidNames[i]
<< " for time = " << runTime.timeName() << nl << endl;
fluidRegions.set
(
i,
new fvMesh
(
IOobject
(
fluidNames[i],
runTime.timeName(),
runTime,
IOobject::MUST_READ
)
)
);
}
const wordList solidNames
(
rp.found("solid") ? rp["solid"] : wordList(0)
);
PtrList<fvMesh> solidRegions(solidNames.size());
forAll(solidNames, i)
{
Info<< "Create solid mesh for region " << solidNames[i]
<< " for time = " << runTime.timeName() << nl << endl;
solidRegions.set
(
i,
new fvMesh
(
IOobject
(
solidNames[i],
runTime.timeName(),
runTime,
IOobject::MUST_READ
)
)
);
// Force calculation of geometric properties to prevent it being done
// later in e.g. some boundary evaluation
//(void)solidRegions[i].weights();
//(void)solidRegions[i].deltaCoeffs();
}
#include "createFluidFields.H"
#include "createSolidFields.H"
从createFluidFields.H
和log
文件中可知创建以下场
Adding to thermoFluid // 读取自constant/fluid/physicalProperties
Adding to rhoFluid
Adding to UFluid
Adding to phiFluid // rhoFluid*UFluid
Adding to gFluid // 读取自constant/fluid/g
Adding to hRefFluid // 参考高度
Adding to pRefFluid // 未从constant中读取到
Adding to ghFluid // gFluid*cellCenter + gFluid*hRefFluid
Adding to ghfFluid // gFluid*faceCenter + gFluid*hRefFluid
Adding to turbulenceFluid // 读取自constant/fluid/momentumTransport
Adding to thermophysicalTransport // 同上
Adding to reactionFluid // 未读取到combustionProperties文件
Adding to KFluid // 0.5*(UFluid)^2
Adding to dpdtFluid
Adding to fieldsFluid
从createSolidFields.H
创建了thermoSolid
。对于heater
,读取到了constant/heater/fvModels
,定义了能量源项以实现加热的功能
energySource
{
type heatSource;
selectionMode all; // 选中heater cellZone
q 1e7;
}
solveSolid.H
求解
{
while (pimple.correctNonOrthogonal())
{
fvScalarMatrix eEqn
(
fvm::ddt(rho, e)
+ thermo.divq(e) // 热传导,拉普拉斯项
==
fvModels.source(rho, e) // 源项
);
eEqn.relax();
fvConstraints.constrain(eEqn);
eEqn.solve();
fvConstraints.constrain(e);
}
}
thermo.correct();
Info<< "Min/max T:" << min(thermo.T()).value() << ' '
<< max(thermo.T()).value() << endl;
固体能量方程本质上是热传导方程:
∂ ( ρ h ) ∂ t = ∂ ∂ x j ( α ∂ h ∂ x j ) \frac{\partial (\rho h)}{\partial t}=\frac{\partial}{\partial x_j}\left( \alpha \frac{\partial h}{\partial x_j} \right) ∂t∂(ρh)=∂xj∂(α∂xj∂h)
( ρ h ) V = ∫ α ∂ h ∂ x j d S ≈ ∑ ( α ∇ h ) ⋅ S (\rho h)V=\int \alpha \frac{\partial h}{\partial x_j} dS \approx \sum(\alpha \nabla h)\cdot S (ρh)V=∫α∂xj∂hdS≈∑(α∇h)⋅S
h h h is the specific enthalpy, kJ/kg, ρ \rho ρ the density and α = κ / c p \alpha = \kappa / c_p α=κ/cp is the thermal diffusivity which is defined as the ratio between the thermal conductivity κ \kappa κ and the specific heat capacity c p c_p cp. Note that κ \kappa κ can be also anisotropic
对于不可压缩流体, h h h仅是温度和压力的函数, h = h ( T , P ) = U + P V h=h(T,P)=U+PV h=h(T,P)=U+PV
求解流程:
if (!pimple.flow())
{
if (pimple.models())
{
fvModels.correct();
}
if (pimple.thermophysics())
{
tmp<fv::convectionScheme<scalar>> mvConvection(nullptr);
if (Ecorr == 0)
{
#include "YEqn.H" // 求解组分方程
}
#include "EEqn.H"
}
}
else
{
if (Ecorr == 0)
{
if (!mesh.schemes().steady() && pimples.firstPimpleIter())
{
#include "rhoEqn.H"
}
if (pimple.models())
{
fvModels.correct();
}
#include "UEqn.H"
}
if (pimple.thermophysics())
{
tmp<fv::convectionScheme<scalar>> mvConvection(nullptr);
if (Ecorr == 0)
{
#include "YEqn.H"
}
#include "EEqn.H"
}
if (Ecorr == nEcorr - 1)
{
tmp<fvVectorMatrix>& tUEqn = UEqns[i]; // 第 i个fluidRegion的U方程
fvVectorMatrix& UEqn = tUEqn.ref();
// --- PISO loop
while (pimple.correct())
{
#include "../../buoyantFoam/pEqn.H"
}
if (pimples.pimpleTurbCorr(i))
{
turbulence.correct();
thermophysicalTransport.correct();
}
if (!mesh.schemes().steady() && pimples.finalPimpleIter())
{
rho = thermo.rho();
}
}
}
连续性方程
∂ ρ ∂ t + ∂ ρ u j ∂ x j = ∂ ρ ∂ t + ∇ ⋅ ( ρ U ) = 0 \frac{\partial \rho}{\partial t} + \frac{\partial \rho u_j}{\partial x_j} = \frac{\partial \rho}{\partial t}+\nabla\cdot (\rho U)=0 ∂t∂ρ+∂xj∂ρuj=∂t∂ρ+∇⋅(ρU)=0
{
fvScalarMatrix rhoEqn
(
fvm::ddt(rho)
+ fvc::div(phi)
==
fvModels.source(rho)
);
fvConstraints.constrain(rhoEqn);
rhoEqn.solve();
fvConstraints.constrain(rho);
}
可压缩流体动量方程
∂ ρ U ∂ t + ∇ ⋅ ( ρ U U ) − ∇ ⋅ τ = − ∇ p r g h − g ⋅ h ∇ ρ + S \frac{\partial \rho U}{\partial t}+\nabla \cdot (\rho U U ) - \nabla \cdot \tau =-\nabla p_{rgh}- g\cdot h \nabla \rho+S ∂t∂ρU+∇⋅(ρUU)−∇⋅τ=−∇prgh−g⋅h∇ρ+S
上式与代码并不对应,下文的代码能够考虑旋转体系中的科里奥利力,施加了MRF,由于本算例并不涉及,所以使用上文的动量方程。关于turbulence.divDevTau(U)
的解析可参考文献
// Solve the Momentum equation
MRF.correctBoundaryVelocity(U);
UEqns[i] =
(
fvm::ddt(rho, U) + fvm::div(phi, U) // 左端前两项
+ MRF.DDt(rho, U)
+ turbulence.divDevTau(U) // 左端第三项
==
fvModels.source(rho, U) // 右端第三项
);
fvVectorMatrix& UEqn = UEqns[i].ref();
UEqn.relax();
fvConstraints.constrain(UEqn);
if (pimple.momentumPredictor())
{
solve
(
UEqn
==
fvc::reconstruct
(
(
- ghf*fvc::snGrad(rho) // 右端第二项
- fvc::snGrad(p_rgh) // 右端第一项
)*mesh.magSf()
)
);
fvConstraints.constrain(U);
K = 0.5*magSqr(U);
}
fvConstraints.constrain(U);
注意UEqn的构建,只囊括了动量方程左端,下文压力方程中用到的UEqn.A()和UEqn.H()都是由此建立的。
该函数的功能是对已知的场量进行重构,由面通量 ϕ = u f ⋅ S f \phi=u_{f}\cdot S_{f} ϕ=uf⋅Sf重构出面体心值 u P u_{P} uP
u P = ∑ S f ∣ S f ∣ ϕ ∑ S f ∣ S f ∣ ⊗ S f u_{P}= \frac{\sum \frac{S_f}{|S_f|} \phi}{\sum \frac{S_f}{|S_f|} \otimes S_f} uP=∑∣Sf∣Sf⊗Sf∑∣Sf∣Sfϕ
( ∑ S f ∣ S f ∣ ⊗ S f ) ⋅ u P = ∑ S f ∣ S f ∣ ( S f ⋅ u P ) \left(\sum \frac{S_f}{|S_f|} \otimes S_f \right) \cdot u_P = \sum \frac{S_f}{|S_f|} (S_f \cdot u_P) (∑∣Sf∣Sf⊗Sf)⋅uP=∑∣Sf∣Sf(Sf⋅uP)
因此,问题转化为
∑ S f ⋅ u P = ? ∑ ϕ \sum S_f \cdot u_P \overset{\text{?}}{=} \sum \phi ∑Sf⋅uP=?∑ϕ
即由已知的方程右端重构出方程左端。
⊗ \otimes ⊗为张量积,两个矢量做张量积结果为二阶矢量。
上式可等价为求下式的最小值问题
g ( u i ) = ∑ f 1 ∣ S f ∣ ( ϕ f − u P S f ) 2 g(u_i)=\sum_f \frac{1}{|S_{f}|} (\phi_f - u_{P}S_{f})^2 g(ui)=f∑∣Sf∣1(ϕf−uPSf)2
该重构有一阶精度,能够减弱大梯度效应从而抑制震荡。以 ∇ p \nabla p ∇p的求解为例,
fvc::grad(p)
fvc::reconstruct(fvc::snGrad(p)*mesh.magSf())
p = p r g h + ρ g ⋅ h p=p_{rgh}+\rho g\cdot h p=prgh+ρg⋅h
h h h表示网格体心的坐标即位置矢量。对 p p p求梯度可得
∇ ( ρ g ⋅ h ) = ρ g ⋅ ∇ h + h ⋅ ∇ ρ g = ρ g ⋅ ∇ h + h ⋅ ( g ∇ ρ + ρ ∇ g ) ∇ p = ∇ p r g h + ρ g + g ⋅ h ∇ ρ \nabla (\rho g\cdot h)=\rho g \cdot \nabla h + h\cdot \nabla \rho g = \rho g \cdot \nabla h + h \cdot (g\nabla \rho + \rho \nabla g) \\ \nabla p=\nabla p_{rgh} + \rho g + g\cdot h \nabla \rho ∇(ρg⋅h)=ρg⋅∇h+h⋅∇ρg=ρg⋅∇h+h⋅(g∇ρ+ρ∇g)∇p=∇prgh+ρg+g⋅h∇ρ
流体微团的能量,比能E,由比动能(kinetic energy, k)和比内能(internal energy, e)组成,单位 m 2 / s 2 m^2/s^2 m2/s2:
动能的变化来自流体受力,如表面力和体积力
∂ ( ρ k ) ∂ t + ∂ ∂ x j ( ρ u j k ) = − ∂ p u j ∂ x i − ρ g j u j + ∂ ∂ x j ( τ i j u i ) \frac{\partial(\rho k)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} k\right)=-\frac{\partial p u_{j}}{\partial x_{i}}-\rho g_{j} u_{j}+\frac{\partial}{\partial x_{j}}\left(\tau_{i j} u_{i}\right) ∂t∂(ρk)+∂xj∂(ρujk)=−∂xi∂puj−ρgjuj+∂xj∂(τijui)
内能的变化来自粘性耗散 q t i q_{ti} qti、热传导 q i q_i qi、热辐射Rad和热源r
∂ ( ρ e ) ∂ t + ∂ ∂ x j ( ρ u j e ) = − ∂ ( q i + q t i ) ∂ x i + ρ r + R a d \frac{\partial(\rho e)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} e\right)=-\frac{\partial (q_{i}+q_{ti})}{\partial x_{i}}+\rho r+R a d ∂t∂(ρe)+∂xj∂(ρuje)=−∂xi∂(qi+qti)+ρr+Rad
上面两式相加得到总能方程,将 h = e + p / ρ h=e+p/\rho h=e+p/ρ代入上式可得
∂ ( ρ h ) ∂ t + ∂ ∂ x j ( ρ u j h ) + ∂ ( ρ k ) ∂ t + ∂ ∂ x j ( ρ u j k ) = − ∂ ( q i + q t i ) ∂ x i + ∂ p ∂ t + ρ r + R a d − ρ g j u j + ∂ ∂ x j ( τ i j u i ) \frac{\partial(\rho h)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} h\right)+\frac{\partial(\rho k)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} k\right) =-\frac{\partial (q_{i}+q_{ti})}{\partial x_{i}} + \frac{\partial p}{\partial t} + \rho r+R a d-\rho g_{j} u_{j}+\frac{\partial}{\partial x_{j}}\left(\tau_{i j} u_{i}\right) ∂t∂(ρh)+∂xj∂(ρujh)+∂t∂(ρk)+∂xj∂(ρujk)=−∂xi∂(qi+qti)+∂t∂p+ρr+Rad−ρgjuj+∂xj∂(τijui)
其中 q i = − k ∇ T q_i=-k\nabla T qi=−k∇T
在OF中,热通量通常不与温度联系起来,而是与比内能联系,即 q = − α e f f ∇ e q=-\alpha_{eff} \nabla e q=−αeff∇e, α e f f \alpha_{eff} αeff表示层流热扩散系数与湍流热扩散系数之和
对于亚音速流动,粘性应力做功项 ∇ ⋅ ( τ u ) \nabla \cdot (\tau u) ∇⋅(τu)通常被忽略
经过以上两点假设,比焓方程可简化为
∂ ( ρ h ) ∂ t + ∂ ∂ x j ( ρ u j h ) + ∂ ( ρ k ) ∂ t + ∂ ∂ x j ( ρ u j k ) − ∂ ( α e f f ∇ e ) ∂ x j − ∂ p ∂ t = ρ r + R a d − ρ g j u j \frac{\partial(\rho h)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} h\right)+\frac{\partial(\rho k)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} k\right) - \frac{\partial\left(\alpha_{eff}\nabla e\right)}{\partial x_{j}} -\frac{\partial p}{\partial t} =\rho r+R a d-\rho g_{j} u_{j} ∂t∂(ρh)+∂xj∂(ρujh)+∂t∂(ρk)+∂xj∂(ρujk)−∂xj∂(αeff∇e)−∂t∂p=ρr+Rad−ρgjuj
{
volScalarField& he = thermo.he();
fvScalarMatrix EEqn
(
fvm::ddt(rho, he) // 左端第一项
+ (
mvConvection.valid()
? mvConvection->fvmDiv(phi, he)
: fvm::div(phi, he)
) // 左端第二项
+ fvc::ddt(rho, K) + fvc::div(phi, K) // 左端三四项
+ (
he.name() == "e" // 如果是比内能方程,
? mvConvection.valid()
? mvConvection->fvcDiv(fvc::absolute(phi, rho, U), p/rho)
: fvc::div(fvc::absolute(phi, rho, U), p/rho)
: -dpdt // 如果不是比内能方程,则是比焓方程,会包含左端第六项
)
+ thermophysicalTransport.divq(he) // 左端第五项
==
rho*(U&g) // 右端第三项
+ reaction.Qdot()
+ fvModels.source(rho, he) // 右端第一项
);
EEqn.relax();
fvConstraints.constrain(EEqn);
EEqn.solve();
fvConstraints.constrain(he);
thermo.correct();
Info<< "Min/max T:" << min(thermo.T()).value() << ' '
<< max(thermo.T()).value() << endl;
}
物种守恒。由于化学反应的发生,物质的量会发生变化,使用标量传输方程来刻画此种变化,其中 R k R_k Rk表示物质 k k k的化学反应速率
∂ ( ρ Y k ) ∂ t + ∂ ∂ x j ( ρ u j Y k ) = ∂ ∂ x j μ e f f ∂ Y k ∂ x j + R k \frac{\partial\left(\rho Y_{k}\right)}{\partial t}+\frac{\partial}{\partial x_{j}}\left(\rho u_{j} Y_{k}\right)=\frac{\partial}{\partial x_{j}} \mu_{e f f} \frac{\partial Y_{k}}{\partial x_{j}}+R_{k} ∂t∂(ρYk)+∂xj∂(ρujYk)=∂xj∂μeff∂xj∂Yk+Rk
if (Y.size()) // 如果不为0,说明有化学反应存在
{
mvConvection = tmp<fv::convectionScheme<scalar>>
(
fv::convectionScheme<scalar>::New
(
mesh,
fields,
phi,
mesh.schemes().div("div(phi,Yi_h)")
)
);
}
reaction.correct();
forAll(Y, i)
{
if (composition.solve(i))
{
volScalarField& Yi = Y[i]; // 第 i个物质的分率
fvScalarMatrix YiEqn
(
fvm::ddt(rho, Yi) + mvConvection->fvmDiv(phi, Yi) // 非稳态项和对流项
+ thermophysicalTransport.divj(Yi) // 扩散项
==
reaction.R(Yi) // 反应
+ fvModels.source(rho, Yi) // 源项
);
YiEqn.relax();
fvConstraints.constrain(YiEqn);
YiEqn.solve("Yi");
fvConstraints.constrain(Yi);
}
}
composition.normalise();
压力修正的目的仍然是使得速度场和密度场满足连续性方程。上述求解过程的求解顺序为 ρ → U → Y → e \rho\rightarrow U\rightarrow Y\rightarrow e ρ→U→Y→e,接下来求解压力,其求解文件位于OpenFOAM-10/applications/solvers/heatTransfer/buoyantFoam/pEqn.H
代码中的 ψ \psi ψ定义为
ψ = ( ∂ ρ ∂ p ) T = 1 R T \psi=\left( \frac{\partial \rho}{\partial p} \right)_T = \frac{1}{RT} ψ=(∂p∂ρ)T=RT1
动量方程离散后的形式为
A U P ∗ = H ∗ − ∇ p n + ρ g = H ∗ − ∇ p r g h n − g h ∇ ρ AU^{*}_P=H^*-\nabla p^n + \rho g = H^*-\nabla p^n_{rgh} - gh\nabla \rho AUP∗=H∗−∇pn+ρg=H∗−∇prghn−gh∇ρ
U ∗ = H b y A ∗ − 1 A ∇ p r g h n − 1 A g h ∇ ρ (!) U^{*}=HbyA^{*}-\frac{1}{A}\nabla p^n_{rgh} - \frac{1}{A}gh \nabla \rho \tag{!} U∗=HbyA∗−A1∇prghn−A1gh∇ρ(!)
注意:在可压缩流体的动量方程中, A P A_P AP中包含通量,通量包含密度。如果做完动量预测后,加入一句代码更新密度,那么 A P A_P AP也会随之更新
若收敛,则动量方程应满足
U P n + 1 = H b y A n + 1 − 1 A n + 1 1 V P ∑ p r g h n + 1 S f − 1 A g h ∇ ρ U^{n+1}_P=HbyA^{n+1}-\frac{1}{A^{n+1}}\frac{1}{V_P}\sum p^{n+1}_{rgh} S_f - \frac{1}{A}gh \nabla \rho UPn+1=HbyAn+1−An+11VP1∑prghn+1Sf−A1gh∇ρ
两式相减可得压力修正值 p ′ p^{\prime} p′方程
U P ′ = H b y A ′ − 1 A n + 1 1 V P ∑ p r g h n + 1 S f + 1 A 1 V P ∑ p r g h n S f U_P^{\prime}=HbyA^{\prime}-\frac{1}{A^{n+1}}\frac{1}{V_P}\sum p^{n+1}_{rgh} S_f + \frac{1}{A}\frac{1}{V_P}\sum p^{n}_{rgh} S_f UP′=HbyA′−An+11VP1∑prghn+1Sf+A1VP1∑prghnSf
忽略邻点假设,即不考虑 H b y A ′ HbyA^{\prime} HbyA′对压力的影响,并假设 A n + 1 = A A^{n+1}=A An+1=A
U P ′ ≈ − 1 A 1 V P ∑ p r g h ′ S f U_P^{\prime} \approx -\frac{1}{A}\frac{1}{V_P}\sum p^{\prime}_{rgh} S_f UP′≈−A1VP1∑prgh′Sf
将上式的速度修正量加到 U P ∗ U_P^* UP∗可得满足连续性方程的速度 U P ∗ ∗ U_P^{**} UP∗∗
U P ∗ ∗ = H b y A ∗ − 1 A 1 V P ∑ p r g h ∗ S f − 1 A g h ∇ ρ U^{**}_P=HbyA^*-\frac{1}{A}\frac{1}{V_P}\sum p^*_{rgh} S_f - \frac{1}{A}gh \nabla \rho UP∗∗=HbyA∗−A1VP1∑prgh∗Sf−A1gh∇ρ
上式中 p ∗ = p n + p ′ p^*=p^n+p^{\prime} p∗=pn+p′未知,为待求量。将 U ∗ ∗ U^{**} U∗∗代入连续性方程,下文中 ρ \rho ρ和 p p p均代表收敛时的值,不同于 p ∗ p^{*} p∗和 ρ ∗ \rho^* ρ∗
∂ ρ ∂ t V P + ∑ ρ U ⋅ S f = ∂ ρ ∂ t V P + ∑ ( ρ ∗ + ρ ′ ) ( U ∗ + U ′ ) ⋅ S f \frac{\partial \rho}{\partial t} V_P+\sum \rho U\cdot S_f=\frac{\partial \rho}{\partial t} V_P+\sum (\rho^*+\rho^{\prime})(U^*+U^{\prime}) \cdot S_f ∂t∂ρVP+∑ρU⋅Sf=∂t∂ρVP+∑(ρ∗+ρ′)(U∗+U′)⋅Sf
其中 ρ U \rho U ρU的乘积展开为如下,此处参考了文献
( ρ ∗ + ρ ′ ) ( U ∗ + U ′ ) = ρ ∗ U ∗ + ρ ′ U ∗ + ρ ∗ U ′ + ρ ′ U ′ = ρ ∗ U ∗ + ( ρ − ρ ∗ ) U ∗ + ρ ∗ ( U − U ∗ ) + ρ ′ U ′ = ρ U ∗ + ρ ∗ U − ρ ∗ U ∗ + ρ ′ U ′ ≈ ρ U ∗ + ρ ∗ U − ρ ∗ U ∗ (\rho^*+\rho^{\prime})(U^*+U^{\prime}) = \rho^*U^* + \rho^{\prime}U^* + \rho^*U^{\prime}+\rho^{\prime}U^{\prime} = \rho^*U^* + (\rho-\rho^{*})U^* + \rho^*(U-U^*)+\rho^{\prime}U^{\prime} \\= \rho U^* + \rho^* U - \rho^* U^* + \rho^{\prime}U^{\prime} \approx \rho U^* + \rho^* U - \rho^* U^* (ρ∗+ρ′)(U∗+U′)=ρ∗U∗+ρ′U∗+ρ∗U′+ρ′U′=ρ∗U∗+(ρ−ρ∗)U∗+ρ∗(U−U∗)+ρ′U′=ρU∗+ρ∗U−ρ∗U∗+ρ′U′≈ρU∗+ρ∗U−ρ∗U∗
将其继续代入连续性方程,其中 ρ = ψ p \rho =\psi p ρ=ψp, ρ \rho ρ和 p p p均为未知
∂ ρ ∂ t V P + ∑ ψ p U ∗ S f + ∑ ρ ∗ ( H b y A ∗ − 1 A ∇ p r g h − 1 A g h ∇ ρ ) S f − ∑ ρ ∗ U ∗ S f = 0 \frac{\partial \rho}{\partial t} V_P+ \sum \psi p U^* S_f + \sum \rho^* \left(HbyA^* - \frac{1}{A} \nabla p_{rgh} -\frac{1}{A}gh \nabla \rho \right) S_f - \sum \rho^* U^* S_f =0 ∂t∂ρVP+∑ψpU∗Sf+∑ρ∗(HbyA∗−A1∇prgh−A1gh∇ρ)Sf−∑ρ∗U∗Sf=0
openfoamwiki中关于此式的写法有误,在压力梯度项少乘了 ρ ∗ \rho^* ρ∗
将 p p p进一步写为 p = p r g h + ρ g h p=p_{rgh}+\rho gh p=prgh+ρgh, ρ = ψ p = ψ ( p r g h + ρ g h ) \rho=\psi p =\psi(p_{rgh}+ \rho gh) ρ=ψp=ψ(prgh+ρgh), ρ ∗ = ψ p ∗ = ψ ( p r g h ∗ + ρ g h ) \rho^*=\psi p^*=\psi (p^*_{rgh} + \rho gh) ρ∗=ψp∗=ψ(prgh∗+ρgh)
∂ ρ ∂ t V P + ∑ ψ ( p r g h + ρ g h ) U ∗ S f + ∑ ρ ∗ ⋅ H b y A ∗ ⋅ S f − ∑ ρ ∗ ∇ p r g h A ⋅ S f − ∑ ρ ∗ g ⋅ h ∇ ρ A S f − ∑ ρ ∗ U ∗ S f = 0 \frac{\partial \rho}{\partial t} V_P+ \sum \psi (p_{rgh}+\rho gh) U^* S_f + \sum \rho^* \cdot HbyA^* \cdot S_f - \sum \frac{\rho^* \nabla p_{rgh}}{A} \cdot S_f - \sum \frac{\rho^* g\cdot h \nabla \rho}{A} S_f - \sum \rho^* U^* S_f =0 ∂t∂ρVP+∑ψ(prgh+ρgh)U∗Sf+∑ρ∗⋅HbyA∗⋅Sf−∑Aρ∗∇prgh⋅Sf−∑Aρ∗g⋅h∇ρSf−∑ρ∗U∗Sf=0
上式第二项和最后一项相减可得
∂ ρ ∂ t V P + ∑ ψ ( p r g h − p r g h ∗ ) U ∗ S f + ∑ ρ ∗ ⋅ H b y A ∗ ⋅ S f − ∑ ρ ∗ ∇ p r g h A ⋅ S f − ∑ ρ ∗ g ⋅ h ∇ ρ A S f = 0 (*) \frac{\partial \rho}{\partial t} V_P+ \sum \psi (p_{rgh}-p_{rgh}^*) U^* S_f + \sum \rho^* \cdot HbyA^* \cdot S_f - \sum \frac{\rho^* \nabla p_{rgh}}{A} \cdot S_f - \sum \frac{\rho^* g\cdot h \nabla \rho}{A} S_f =0 \tag{*} ∂t∂ρVP+∑ψ(prgh−prgh∗)U∗Sf+∑ρ∗⋅HbyA∗⋅Sf−∑Aρ∗∇prgh⋅Sf−∑Aρ∗g⋅h∇ρSf=0(*)
对于非稳态项
∂ ρ ∂ t = ∂ ρ ∗ ∂ t + ∂ ρ ′ ∂ t = ∂ ρ ∗ ∂ t + ψ ( ∂ p r g h ′ ∂ t + ∂ ρ ′ ∂ t g h ) ≈ ∂ ρ ∗ ∂ t + ψ ∂ p r g h ′ ∂ t \frac{\partial \rho}{\partial t} = \frac{\partial \rho^*}{\partial t}+\frac{\partial \rho^{\prime}}{\partial t} = \frac{\partial \rho^*}{\partial t} + \psi \left( \frac{\partial p^{\prime}_{rgh}}{\partial t} + \frac{\partial \rho^{\prime}}{\partial t}gh \right) \approx \frac{\partial \rho^*}{\partial t} + \psi \frac{\partial p^{\prime}_{rgh}}{\partial t} ∂t∂ρ=∂t∂ρ∗+∂t∂ρ′=∂t∂ρ∗+ψ(∂t∂prgh′+∂t∂ρ′gh)≈∂t∂ρ∗+ψ∂t∂prgh′
rho = thermo.rho();
rho.relax();
// Thermodynamic density needs to be updated by psi*d(p) after the
// pressure solution
const volScalarField psip0(psi*p);
const volScalarField rAU("rAU", 1.0/UEqn.A());
const surfaceScalarField rhorAUf("rhorAUf", fvc::interpolate(rho*rAU)); // rho/A
volVectorField HbyA(constrainHbyA(rAU*UEqn.H(), U, p_rgh));
if (pimple.nCorrPiso() <= 1)
{
tUEqn.clear();
}
surfaceScalarField phiHbyA
(
"phiHbyA",
fvc::interpolate(rho)*fvc::flux(HbyA) // (*)式第三项
+ MRF.zeroFilter(rhorAUf*fvc::ddtCorr(rho, U, phi, rhoUf))
);
MRF.makeRelative(fvc::interpolate(rho), phiHbyA);
bool adjustMass = mesh.schemes().steady() && adjustPhi(phiHbyA, U, p_rgh);
const surfaceScalarField phig(-rhorAUf*ghf*fvc::snGrad(rho)*mesh.magSf()); // (*)式第五项
phiHbyA += phig;
// Update the pressure BCs to ensure flux consistency
constrainPressure(p_rgh, rho, U, phiHbyA, rhorAUf, MRF);
fvc::makeRelative(phiHbyA, rho, U);
fvScalarMatrix p_rghEqn(p_rgh, dimMass/dimTime);
if (pimple.transonic())
{
const surfaceScalarField phid
(
"phid",
(fvc::interpolate(psi)/fvc::interpolate(rho))*phiHbyA
);
const fvScalarMatrix divPhidp(fvm::div(phid, p));
phiHbyA -= divPhidp.flux();
fvScalarMatrix p_rghDDtEqn
(
fvc::ddt(rho) + psi*correction(fvm::ddt(p_rgh))
+ fvc::div(phiHbyA) + divPhidp
==
fvModels.source(psi, p_rgh, rho.name())
);
while (pimple.correctNonOrthogonal())
{
p_rghEqn = p_rghDDtEqn - fvm::laplacian(rhorAUf, p_rgh);
// Relax the pressure equation to ensure diagonal-dominance
p_rghEqn.relax();
p_rghEqn.setReference
(
pressureReference.refCell(),
pressureReference.refValue()
);
p_rghEqn.solve();
}
}
else
{
fvScalarMatrix p_rghDDtEqn
(
fvc::ddt(rho) + psi*correction(fvm::ddt(p_rgh)) // 非稳态项
+ fvc::div(phiHbyA) // 第三+五项
==
fvModels.source(psi, p_rgh, rho.name()) // 可能是第二项
);
while (pimple.correctNonOrthogonal())
{
p_rghEqn = p_rghDDtEqn - fvm::laplacian(rhorAUf, p_rgh); // 第四项
p_rghEqn.setReference
(
pressureReference.refCell(),
pressureReference.refValue()
);
p_rghEqn.solve();
}
}
phi = phiHbyA + p_rghEqn.flux();
if (mesh.schemes().steady())
{
#include "incompressible/continuityErrs.H"
}
else
{
p = p_rgh + rho*gh + pRef;
const bool constrained = fvConstraints.constrain(p);
// Thermodynamic density update
thermo.correctRho(psi*p - psip0);
if (constrained)
{
rho = thermo.rho();
}
#include "rhoEqn.H"
#include "compressibleContinuityErrs.H"
}
// Explicitly relax pressure for momentum corrector
p_rgh.relax();
p = p_rgh + rho*gh + pRef;
// Correct the momentum source with the pressure gradient flux
// calculated from the relaxed pressure
U = HbyA + rAU*fvc::reconstruct((phig + p_rghEqn.flux())/rhorAUf); // 对应上文(!)式,此处为何是'HbyA +...'
U.correctBoundaryConditions();
fvConstraints.constrain(U);
K = 0.5*magSqr(U);
if (mesh.schemes().steady())
{
if (fvConstraints.constrain(p))
{
p_rgh = p - rho*gh - pRef;
p_rgh.correctBoundaryConditions();
}
}
// For steady closed-volume compressible cases adjust the pressure level
// to obey overall mass continuity
if (adjustMass && !thermo.incompressible())
{
p += (initialMass - fvc::domainIntegrate(thermo.rho()))
/fvc::domainIntegrate(psi);
p_rgh = p - rho*gh - pRef;
p_rgh.correctBoundaryConditions();
}
if (mesh.schemes().steady() || pimple.simpleRho() || adjustMass)
{
rho = thermo.rho();
}
if (mesh.schemes().steady() || pimple.simpleRho())
{
rho.relax();
}
// Correct rhoUf if the mesh is moving
fvc::correctRhoUf(rhoUf, rho, U, phi, MRF);
if (thermo.dpdt())
{
dpdt = fvc::ddt(p);
if (mesh.moving())
{
dpdt -= fvc::div(fvc::meshPhi(rho, U), p);
}
}
压力求解流程还存在疑点!
openfoamwiki-chtMultiRegionFoam
Modelling turbulent combustion coupled with conjugate heat transfer in OpenFOAM
OpenFOAM中divDevRhoReff和divDevReff详解
NS方程笔记
A Unified Formulation of the Segregated Class of Algorithms for Multi-Fluid Flow at All-Speeds