Code of the Week #36: div(phi,U) Gauss linear

#201923

接下来的一段时间我们将重点讲 div 离散格式。对于离散格式的指定,在 Code of the Week #5: div(phi,U) Gauss linear 中已经进行了说明,此处对相应离散格式进行分析。OpenFOAM 中, \nabla\cdot(UU) 表示为

\text{fvm::div(phi,U)}

phi 指的是面上的通量,可以是体积通量,也可以是质量通量,对于体积通量,

\text{phi}=U_f\cdot S_f

对于质量通量,是

\text{phi} = \rho_f U_f\cdot S_f

div(phi,U) 的向量表达式为 \nabla\cdot(U\otimes U) ,经高斯定理,

\iiint_V\nabla\cdot(U\otimes U)=\oint_{\partial V} U\otimes U dS

因此,div(phi,U) 中的 phi 表示 UdS 网格面的通量,在 OpenFOAM 中是已知量, div(phi,U) 离散格式处理的是 U,从而形成以 U_x (或 U_y, U_z) 为未知量的矩阵。

Code of the Week #1: div(phi,U) 中对 linear 离散格式进行了说明。但没有进行详细的解析,现在根据数值计算方面的要求,进行必要的考量和分析。

首先,我们看一下 OpenFOAM 对这一量是如何声明的?

fvVectorMatrix UEqn
(
    fvm::ddt(U)
  + fvm::div(phi,U)
  - fvm::laplacian(nu,U)
);

每一项都返回 fvVectorMatrix 类型。因此,可以对 fvm::div(phi,U) 进行单独分析。fvm::div() 对应的类定义 (声明/构造) 为 (位于src/finiteVolume/fvMatrices/fvMatrix/fvMatrix.H)

    // Constructors

        //- Construct given a field to solve for
        fvMatrix
        (
            GeometricField<Type, fvPatchField, volMesh>&,
            const dimensionSet&
        );

        //- Construct as copy
        fvMatrix(const fvMatrix<Type>&);

        //- Construct as copy of tmp<fvMatrix<Type> > deleting argument
#       ifdef ConstructFromTmp
        fvMatrix(const tmp<fvMatrix<Type> >&);
#       endif

        //- Construct from Istream given field to solve for
        fvMatrix(GeometricField<Type, fvPatchField, volMesh>&, Istream&);


        //- Clone
        tmp<fvMatrix<Type> > clone() const
        {
            return tmp<fvMatrix<Type> >
            (
                new fvMatrix<Type>(*this)
            );
        }

可以看出, fvMatrix 可以由4种方式构造:

  1. GeometryField 和 dimensionedSet
  2. fvMatrix<Type>
  3. 临时的 fmMatrix<Type>
  4. GeometryField 和 Istream 类
    fvm::div() 则用第二种和第三种构造函数。fvm::div() 函数,fvm 表示的是名称空间,:: 代表的是作用域,意思是 fvm::div 中的 div 来自于 fvm 空间。

fvm::div(phi,U) 中有两个参数,其中一个是 phi (surfaceScalarField 类),另一个是 U (fvVectorField 类),因此,该函数则调用

div(surfaceScalarField& phi, fvVectorField& vf)

因此,查找 fvm 名称空间,有

template<class Type >
tmp< fvMatrix< Type > > 	div (const surfaceScalarField &flux, const GeometricField< Type, fvPatchField, volMesh > &vf, const word &name)
 
template<class Type >
tmp< fvMatrix< Type > > 	div (const tmp< surfaceScalarField > &tflux, const GeometricField< Type, fvPatchField, volMesh > &vf, const word &name)
 
template<class Type >
tmp< fvMatrix< Type > > 	div (const surfaceScalarField &flux, const GeometricField< Type, fvPatchField, volMesh > &vf)

可以找到三个函数,其中有两个参数的是

template<class Type >
tmp< fvMatrix< Type > > 	div (const surfaceScalarField &flux, const GeometricField< Type, fvPatchField, volMesh > &vf)

构造函数为 (fvmDiv.C)

template<class Type>
tmp<fvMatrix<Type> >
div
(
    const surfaceScalarField& flux,
    GeometricField<Type, fvPatchField, volMesh>& vf
)
{
    return fvm::div(flux, vf, "div("+flux.name()+','+vf.name()+')');
}

可以看到,这个构造函数也调用了另一个函数:

template<class Type>
tmp<fvMatrix<Type> >
div
(
    const surfaceScalarField& flux,
    GeometricField<Type, fvPatchField, volMesh>& vf,
    const word& name
)
{
    return fv::convectionScheme<Type>::New
    (
        vf.mesh(),
        flux,
        vf.mesh().schemesDict().divScheme(name) // 第 3 参数,指定格式
    )().fvmDiv(flux, vf);
}

这个函数则调用了 New(),而它属于 fv::convectionScheme<Type> 类。而生成的对象调用子函数 fvmDiv()

首先,看一下 New() 函数 (src/finiteVolume/finiteVolume/convectionSchemes/convectionScheme /convectionScheme.H)

        //- Return a pointer to a new convectionScheme created on freestore
        static tmp<convectionScheme<Type> > New
        (
            const fvMesh& mesh,
            const surfaceScalarField& faceFlux,
            Istream& schemeData
        );

类本身还有虚函数

        virtual tmp<fvMatrix<Type> > fvmDiv
        (
            const surfaceScalarField&,
            GeometricField<Type, fvPatchField, volMesh>&
        ) const = 0;

        virtual tmp<GeometricField<Type, fvPatchField, volMesh> > fvcDiv
        (
            const surfaceScalarField&,
            const GeometricField<Type, fvPatchField, volMesh>&
        ) const = 0;

而使用的 New() 函数的代码为

template<class Type>
tmp<convectionScheme<Type> > convectionScheme<Type>::New
(
    const fvMesh& mesh,
    const surfaceScalarField& faceFlux,
    Istream& schemeData
)
{
    if (fv::debug)
    {
        Info<< "convectionScheme<Type>::New"
               "(const fvMesh&, const surfaceScalarField&, Istream&) : "
               "constructing convectionScheme<Type>"
            << endl;
    }

    if (schemeData.eof())
    {
        FatalIOErrorIn
        (
            "convectionScheme<Type>::New"
            "(const fvMesh&, const surfaceScalarField&, Istream&)",
            schemeData
        )   << "Convection scheme not specified" << endl << endl
            << "Valid convection schemes are :" << endl
            << IstreamConstructorTablePtr_->sortedToc()
            << exit(FatalIOError);
    }

    word schemeName(schemeData);

    typename IstreamConstructorTable::iterator cstrIter =
        IstreamConstructorTablePtr_->find(schemeName);

    if (cstrIter == IstreamConstructorTablePtr_->end())
    {
        FatalIOErrorIn
        (
            "convectionScheme<Type>::New"
            "(const fvMesh&, const surfaceScalarField&, Istream&)",
            schemeData
        )   << "unknown convection scheme " << schemeName << endl << endl
            << "Valid convection schemes are :" << endl
            << IstreamConstructorTablePtr_->sortedToc()
            << exit(FatalIOError);
    }

    return cstrIter()(mesh, faceFlux, schemeData);
}

其中的第三个参数 schemeData 来自 vf.mesh().divScheme(name),实际上, mesh (fvMesh 类) 对象并没有直接的 divScheme 函数,而是继承了 fvSchemes 类,其中包括了 divScheme() (src/finiteVolume/finiteVolume/fvSchemes/fvSchemes.C):

Foam::ITstream& Foam::fvSchemes::divScheme(const word& name) const
{
    if (debug)
    {
        Info<< "Lookup divScheme for " << name << endl;
    }

    if (divSchemes_.found(name) || defaultDivScheme_.empty())
    {
        return divSchemes_.lookup(name);
    }
    else
    {
        const_cast<ITstream&>(defaultDivScheme_).rewind();
        return const_cast<ITstream&>(defaultDivScheme_);
    }
}

其意思是:如果在 system/fvSchemes 文件中找到 div(phi,U) 或 default 项为 none,则返回 div(phi,U) 指定的格式,否则按 default 指定的格式。可以进一步找 lookup() 函数。此处就不展开了。

返回一个 convectionScheme 对象 cstrIter,可能的方式有

  • boundedConvectionScheme
  • gaussConvectionScheme
  • multivariateGaussConvectionScheme

当字典文件写的是 Gauss 时,返回的对象是 gaussConvectionScheme,以 fvmDiv 为例,代码为

tmp<fvMatrix<Type> >
gaussConvectionScheme<Type>::fvmDiv
(
    const surfaceScalarField& faceFlux,
    GeometricField<Type, fvPatchField, volMesh>& vf
) const
{
    tmp<surfaceScalarField> tweights = tinterpScheme_().weights(vf);
    const surfaceScalarField& weights = tweights();

    tmp<fvMatrix<Type> > tfvm
    (
        new fvMatrix<Type>
        (
            vf,
            faceFlux.dimensions()*vf.dimensions()
        )
    );
    fvMatrix<Type>& fvm = tfvm();

    fvm.lower() = -weights.internalField()*faceFlux.internalField();
    fvm.upper() = fvm.lower() + faceFlux.internalField();
    fvm.negSumDiag();

    forAll(fvm.psi().boundaryField(), patchI)
    {
        const fvPatchField<Type>& psf = fvm.psi().boundaryField()[patchI];
        const fvsPatchScalarField& patchFlux = faceFlux.boundaryField()[patchI];
        const fvsPatchScalarField& pw = weights.boundaryField()[patchI];

        fvm.internalCoeffs()[patchI] = patchFlux*psf.valueInternalCoeffs(pw);
        fvm.boundaryCoeffs()[patchI] = -patchFlux*psf.valueBoundaryCoeffs(pw);
    }

    if (tinterpScheme_().corrected())
    {
        fvm += fvc::surfaceIntegrate(faceFlux*tinterpScheme_().correction(vf));
    }

    return tfvm;
}

这个函数有一个类成员 tinterpScheme_ 指定了面上的量的插值方式 system/fvSchemes

假设此处使用的是 linear 函数,怎样去打捞这个函数呢?在这里 src/finiteVolume/interpolation/surfaceInterpolation,而 linear 则位于src/finiteVolume/interpolation/surfaceInterpolation/schemes/linear,从上一段可以看出,fvmDiv() 使用了以下几个参数

  • tinterpScheme_().weights(vf)
  • tinterpScheme_().corrected()
  • tinterpScheme_().correction()

weights() 使用一个参数的成员函数,在 linear.H 中其定义为

        //- Return the interpolation weighting factors
        tmp<surfaceScalarField> weights
        (
            const GeometricField<Type, fvPatchField, volMesh>&
        ) const
        {
            return this->mesh().surfaceInterpolation::weights();
        }

weights() 又指向了 makeWeights() (surfaceInterpolation.C):

const Foam::surfaceScalarField& Foam::surfaceInterpolation::weights() const
{
    if (!weightingFactors_)
    {
        makeWeights();
    }

    return (*weightingFactors_);
}

makeWeights() 在 surfaceInterpolation.C 定义:

void Foam::surfaceInterpolation::makeWeights() const
{
    if (debug)
    {
        Info<< "surfaceInterpolation::makeWeights() : "
            << "Constructing weighting factors for face interpolation"
            << endl;
    }


    weightingFactors_ = new surfaceScalarField
    (
        IOobject
        (
            "weightingFactors",
            mesh_.pointsInstance(),
            mesh_
        ),
        mesh_,
        dimless
    );
    surfaceScalarField& weightingFactors = *weightingFactors_;


    // Set local references to mesh data
    // (note that we should not use fvMesh sliced fields at this point yet
    //  since this causes a loop when generating weighting factors in
    //  coupledFvPatchField evaluation phase)
    const unallocLabelList& owner = mesh_.owner();
    const unallocLabelList& neighbour = mesh_.neighbour();

    const vectorField& Cf = mesh_.faceCentres(); // 面的中心
    const vectorField& C = mesh_.cellCentres();  // 网格中心
    const vectorField& Sf = mesh_.faceAreas();   // 面的面积

    // ... and reference to the internal field of the weighting factors
    scalarField& w = weightingFactors.internalField();

    forAll (owner, facei)                        // 关键部分
    {
        // Note: mag in the dot-product.
        // For all valid meshes, the non-orthogonality will be less than
        // 90 deg and the dot-product will be positive.  For invalid
        // meshes (d & s <= 0), this will stabilise the calculation
        // but the result will be poor.
        scalar SfdOwn = mag(Sf[facei] & (Cf[facei] - C[owner[facei]]));     // 本网格 (共享) 面到中心的距离 A
        scalar SfdNei = mag(Sf[facei] & (C[neighbour[facei]] - Cf[facei])); // 相邻网格中心到共享面的距离 B
        w[facei] = SfdNei/(SfdOwn + SfdNei);            // B/(A+B)
    }

    forAll (mesh_.boundary(), patchi) // 边界
    {
        mesh_.boundary()[patchi].makeWeights
        (
            weightingFactors.boundaryField()[patchi]
        );
    }


    if (debug)
    {
        Info<< "surfaceInterpolation::makeWeights() : "
            << "Finished constructing weighting factors for face interpolation"
            << endl;
    }
}

从上面的代码可见,内部和边界分别生成 weights 值。内部 weights 值跟网格的面到中心的距离有关,这个应该很好理解吧:

而边界上的值则单独处理,其源程序位于 fvPatch.C:

 void Foam::fvPatch::makeWeights(scalarField& w) const
 {
     w = 1.0;
 }

而 linear 插值方式没有 correction 方式。