chtMultiRegionFoam求解器及算例分析

1. 算例分析

1.1. 算例结构

算例目录heatTransfer/chtMultiRegionFoam/heatedDuct

  • 0
    • fluid
      • p
      • p_rgh
      • T
      • U
    • heater
      • T
    • metal
      • T
  • constant
    • fluid
      • g
      • momentumTransport
      • physicalProperties
    • heater
      • fvModels
      • physicalProperties
    • metal
      • physicalProperties
    • geometry
      • fluidToMetal.stl
      • heatedDuct.stl
      • metalToHeater.stl
    • regionProperties
  • system
    • fluid
      • fvSchemes
      • fvSolution
    • heater
      • fvSchemes
      • fvSolution
    • metal
      • fvSchemes
      • fvSolution
    • blockMeshDict
    • controlDict
    • decomposeParDict
    • fvSchemes
    • fvSolution
    • meshQualityDict
    • snappyHexMeshDict

1.2. 算例运行

通过Allrun了解其执行步骤:

blockMesh
snappyHexMesh -overwrite
splitMeshRegions -cellZones -overwrite
decomposePar -allRegions
chtMultiRegionFoam
reconstructPar -allRegions

1.2.1. blockMeshDict

该字典文件代码段。注意到blocks段落中出现了fluid关键词,这是icoFoam算例中没有的。通过观察blockMesh的运行输出文件,该关键词的效果是生成了名为fluid的cellZone,其包含 20 ∗ 24 ∗ 60 20*24*60 202460个网格

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/,用于记录网格信息。

1.2.2. snappyHexMeshDict

该工具可以自动的从STL和OBJ文件生成六面体和多面体网格,网格依靠迭代将一个初始网格细化,并将细化后的网格变形以依附于表面。运行该工具需提供

  • STL文件格式的几何文件(binary或者ascii格式)存储在constant/triSurface子字典中
  • 一个背景六面体网格(必须是纯六面体),定义计算域范围和背景网格, 一般由 blockMesh 生成。stl文件必须和背景网格产生交叉,snappyHexMesh就是将这些交叉处的网格细化
  • snappyHexMeshDict 字典,它提供了生成网格的必要信息,位于 system 子文件夹下
#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
  • metalExternal、heaterExternal、fluidToMetal、metalToHeater的网格被细化

1.2.3. splitMeshRegions

该工具将网格划分为多个区域,可以将已有的多个cellZones转化为一个cellZone

splitMeshRegions -cellZones

Each region is defined as a domain whose cells can all be reached by cell-face-cell walking without crossing

  • boundary faces
  • additional faces from faceset (-blockedFaces faceSet).
  • any face between differing cellZones (-cellZones)

可见,上述命令将根据已有的cellZone将创建region,该region中的网格彼此相连,直到接触到另一个cellZone为止。

该工具运行时会先检验system/xx/fvScheme文件是否存在,如果存在就会创建名为xx的region,生成如下文件

  • 0
    • cellToRegion
    • heater
      • cellToRegion 该文件给出当前region的编号以及region的边界条件,还有region之间交界面的信息,如metal_to_fluid
    • metal
      • cellToRegion
    • fluid
      • cellToRegion
  • constant
    • cellToRegion
    • heater
      • polyMesh
    • metal
      • polyMesh
    • fluid
      • polyMesh

2. chtMultiRegionFoam求解器解析

该求解器采用了分离式的求解策略,即上一个方程的求解结果作为输入条件应用于下一个方程的求解。在共轭传热问题中,求解步骤为

  1. 将初始固体温度作为边界条件输入到流体温度方程中求解;
  2. 解出流体温度,作为边界条件输入到固体温度方程中;
  3. 求出新的固体温度,重复上面两步,直到收敛。

压力基求解器的释义: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.

2.1. 程序结构

2.1.1. chtMultiRegionFoam.C

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;
}

2.1.2. createMeshes.H

regionProperties rp(runTime);

#include "createFluidMeshes.H"
#include "createSolidMeshes.H"

2.1.2.1. createFluidMeshes.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
            )
        )
    );
}

2.1.2.2. createSolidMeshes.H

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();
}

2.1.3. createFields.H

#include "createFluidFields.H"
#include "createSolidFields.H"

createFluidFields.Hlog文件中可知创建以下场

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;
}

2.2. 固体求解

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(αxjh)

( ρ 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=αxjhdS(α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

2.3. 流体求解

求解流程:

  1. 由连续性方程更新密度;
  2. 解动量方程,解出的速度不满足连续性方程;
  3. 解浓度输运方程,求出各物质的浓度以及化学反应;
  4. 解能量方程,求出温度;
  5. 解压力泊松方程使得速度满足连续性方程;
  6. 使用新的压力场,结合状态方程求出流体密度。

2.3.1. solveFluid.H

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();
        }
    }
}

2.3.2. src/finiteVolume/cfdTools/compressible/rhoEqn.H

连续性方程
∂ ρ ∂ 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);
}

2.3.3. UEqn.H

可压缩流体动量方程
∂ ρ 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)τ=prghghρ+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()都是由此建立的。

2.3.3.1. fvc::reconstruct

该函数的功能是对已知的场量进行重构,由面通量 ϕ = u f ⋅ S f \phi=u_{f}\cdot S_{f} ϕ=ufSf重构出面体心值 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=SfSfSfSfSfϕ

( ∑ 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) (SfSfSf)uP=SfSf(SfuP)
因此,问题转化为
∑ S f ⋅ u P = ? ∑ ϕ \sum S_f \cdot u_P \overset{\text{?}}{=} \sum \phi SfuP=?ϕ

即由已知的方程右端重构出方程左端。
⊗ \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)=fSf1(ϕfuPSf)2
该重构有一阶精度,能够减弱大梯度效应从而抑制震荡。以 ∇ p \nabla p p的求解为例,

  • fvc::grad(p)
    1 Δ V ∫ ∇ p d V = 1 Δ V ∫ p d S = 1 Δ V ∑ p f S f \frac{1}{\Delta V}\int \nabla p dV=\frac{1}{\Delta V}\int p dS = \frac{1}{\Delta V}\sum p_f S_f ΔV1pdV=ΔV1pdS=ΔV1pfSf
  • fvc::reconstruct(fvc::snGrad(p)*mesh.magSf())
    ∑ ∇ p P ⋅ S f = ∑ [ ( ∇ p ) f ⋅ S f ∣ S f ∣ ] ∣ S f ∣ \sum \nabla p_P \cdot S_f = \sum \left[ (\nabla p)_f \cdot \frac{S_f}{|S_f|} \right] |S_f| pPSf=[(p)fSfSf]Sf

2.3.3.2. p r g h p_{rgh} prgh

p = p r g h + ρ g ⋅ h p=p_{rgh}+\rho g\cdot h p=prgh+ρgh
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 (ρgh)=ρgh+hρg=ρgh+h(gρ+ρg)p=prgh+ρg+ghρ

2.3.4. EEqn.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)=xipujρ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)+tp+ρr+Radρgjuj+xj(τijui)
其中 q i = − k ∇ T q_i=-k\nabla T qi=kT

在OF中,热通量通常不与温度联系起来,而是与比内能联系,即 q = − α e f f ∇ e q=-\alpha_{eff} \nabla e q=αeffe α 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(αeffe)tp=ρ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;
}

2.3.5. YEqn.H

物种守恒。由于化学反应的发生,物质的量会发生变化,使用标量传输方程来刻画此种变化,其中 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μeffxjYk+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();

2.3.6. pEqn.H

压力修正的目的仍然是使得速度场和密度场满足连续性方程。上述求解过程的求解顺序为 ρ → U → Y → e \rho\rightarrow U\rightarrow Y\rightarrow e ρUYe,接下来求解压力,其求解文件位于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=Hpn+ρg=Hprghnghρ

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=HbyAA1prghnA1ghρ(!)

注意:在可压缩流体的动量方程中, 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+1An+11VP1prghn+1SfA1ghρ

两式相减可得压力修正值 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=HbyAAn+11VP1prghn+1Sf+A1VP1prghnSf

忽略邻点假设,即不考虑 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 UPA1VP1prghSf

将上式的速度修正量加到 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∗∗=HbyAA1VP1prghSfA1ghρ

上式中 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+ρUSf=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+ρ(UU)+ρ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+ψpUSf+ρ(HbyAA1prghA1ghρ)SfρUSf=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)USf+ρHbyASfAρprghSfAρghρSfρUSf=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+ψ(prghprgh)USf+ρHbyASfAρprghSfAρghρ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ρ+ψ(tprgh+tρgh)tρ+ψtprgh

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);
    }
}

压力求解流程还存在疑点!

3. 参考文献

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

你可能感兴趣的:(理论基础,算法)