/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2017-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see .
\*---------------------------------------------------------------------------*/
#include "MultiComponentPhaseModel.H"
#include "multiphaseInterSystem.H"
#include "fvmDdt.H"
#include "fvmDiv.H"
#include "fvmSup.H"
#include "fvmLaplacian.H"
#include "fvcDdt.H"
#include "fvcDiv.H"
#include "fvcDDt.H"
#include "fvMatrix.H"
#include "fvcFlux.H"
#include "CMULES.H"
#include "subCycle.H"
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template
Foam::MultiComponentPhaseModel::
MultiComponentPhaseModel
(
const multiphaseInterSystem& fluid,
const word& phaseName
)
:
BasePhaseModel(fluid, phaseName),
species_(),
inertIndex_(-1),
addDiffusion_(false)
{
thermoPtr_.reset
(
phaseThermo::New
(
fluid.mesh(),
phaseName,
basicThermo::phasePropertyName(basicThermo::dictName, phaseName)
)
);
if (thermoPtr_->composition().species().empty())
{
FatalErrorInFunction
<< " The selected thermo is pure. Use a multicomponent thermo."
<< exit(FatalError);
}
species_ = thermoPtr_->composition().species();
inertIndex_ = species_.find(thermoPtr_().template get("inertSpecie"));
addDiffusion_ =
thermoPtr_().template getOrDefault("addDiffusion", false);
Sct_ = thermoPtr_().template getOrDefault("Sct", 1.0);
X_.setSize(thermoPtr_->composition().species().size());
// Initiate X's using Y's to set BC's
forAll(species_, i)
{
X_.set
(
i,
new volScalarField
(
IOobject
(
IOobject::groupName("X" + species_[i], phaseName),
fluid.mesh().time().timeName(),
fluid.mesh(),
IOobject::NO_READ,
IOobject::NO_WRITE
),
Y()[i]
)
);
}
// Init vol fractions from mass fractions
calculateVolumeFractions();
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
template
void Foam::MultiComponentPhaseModel
::calculateVolumeFractions()
{
volScalarField Xtotal(0.0*X_[0]);
const volScalarField W(thermo().W());
forAll(X_, i)
{
const dimensionedScalar Wi
(
"Wi",
dimMass/dimMoles,
thermo().composition().W(i)
);
if (i != inertIndex_)
{
X_[i] = W*Y()[i]/Wi;
Xtotal += X_[i];
X_[i].correctBoundaryConditions();
const volScalarField::Boundary& YBf = Y()[i].boundaryField();
forAll(YBf, patchi)
{
const fvPatchScalarField& YPf = YBf[patchi];
if (YPf.fixesValue())
{
scalarField& xbf = X_[i].boundaryFieldRef()[patchi];
const scalarField& ybf = Y()[i].boundaryField()[patchi];
const scalarField& Wbf = W.boundaryField()[patchi];
forAll(xbf, facei)
{
xbf[facei] = Wbf[facei]*ybf[facei]/Wi.value();
}
}
}
}
}
X_[inertIndex_] = 1.0 - Xtotal;
X_[inertIndex_].correctBoundaryConditions();
}
template
void Foam::MultiComponentPhaseModel::
calculateMassFractions()
{
volScalarField W(X_[0]*thermo().composition().W(0));
for(label i=1; i< species_.size(); i++)
{
W += X_[i]*thermo().composition().W(i);
}
forAll(Y(), i)
{
Y()[i] = X_[i]*thermo().composition().W(i)/W;
Info<< Y()[i].name() << " mass fraction = "
<< " Min(Y) = " << min(Y()[i]).value()
<< " Max(Y) = " << max(Y()[i]).value()
<< endl;
}
}
template
const phaseThermo&
Foam::MultiComponentPhaseModel::thermo() const
{
return thermoPtr_();
}
template
phaseThermo&
Foam::MultiComponentPhaseModel::thermo()
{
return thermoPtr_();
}
template
void Foam::MultiComponentPhaseModel::correct()
{
return thermo().correct();
}
template
void Foam::MultiComponentPhaseModel::solveYi
(
PtrList& Su,
PtrList& Sp
)
{
const volScalarField& alpha1 = *this;
const fvMesh& mesh = alpha1.mesh();
const dictionary& MULEScontrols = mesh.solverDict(alpha1.name());
scalar cAlpha(MULEScontrols.get("cYi"));
PtrList phiYiCorrs(species_.size());
const surfaceScalarField& phi = this->fluid().phi();
surfaceScalarField phic(mag((phi)/mesh.magSf()));
phic = min(cAlpha*phic, max(phic));
surfaceScalarField phir(0.0*phi);
forAllConstIter
(
multiphaseInterSystem::phaseModelTable,this->fluid().phases(),iter2
)
{
const volScalarField& alpha2 = iter2()();
if (&alpha2 == &alpha1)
{
continue;
}
phir += phic*this->fluid().nHatf(alpha1, alpha2);
}
// Do not compress interface at non-coupled boundary faces
// (inlets, outlets etc.)
surfaceScalarField::Boundary& phirBf = phir.boundaryFieldRef();
forAll(phir.boundaryField(), patchi)
{
fvsPatchScalarField& phirp = phirBf[patchi];
if (!phirp.coupled())
{
phirp == 0;
}
}
word phirScheme("div(Yiphir," + alpha1.name() + ')');
forAll(X_, i)
{
if (inertIndex_ != i)
{
volScalarField& Yi = X_[i];
phiYiCorrs.set
(
i,
new surfaceScalarField
(
"phi" + Yi.name() + "Corr",
fvc::flux
(
phi,
Yi,
"div(phi," + Yi.name() + ')'
)
)
);
surfaceScalarField& phiYiCorr = phiYiCorrs[i];
forAllConstIter
(
multiphaseInterSystem::phaseModelTable,
this->fluid().phases(),
iter2
)
{
//const volScalarField& alpha2 = iter2()().oldTime();
const volScalarField& alpha2 = iter2()();
if (&alpha2 == &alpha1)
{
continue;
}
phiYiCorr += fvc::flux
(
-fvc::flux(-phir, alpha2, phirScheme),
Yi,
phirScheme
);
}
// Ensure that the flux at inflow BCs is preserved
forAll(phiYiCorr.boundaryField(), patchi)
{
fvsPatchScalarField& phiYiCorrp =
phiYiCorr.boundaryFieldRef()[patchi];
if (!phiYiCorrp.coupled())
{
const scalarField& phi1p = phi.boundaryField()[patchi];
const scalarField& Yip = Yi.boundaryField()[patchi];
forAll(phiYiCorrp, facei)
{
if (phi1p[facei] < 0)
{
phiYiCorrp[facei] = Yip[facei]*phi1p[facei];
}
}
}
}
MULES::limit
(
1.0/mesh.time().deltaTValue(),
geometricOneField(),
Yi,
phi,
phiYiCorr,
Sp[i],
Su[i],
oneField(),
zeroField(),
true
);
}
}
volScalarField Yt(0.0*X_[0]);
scalar nYiSubCycles
(
MULEScontrols.getOrDefault("nYiSubCycles", 1)
);
forAll(X_, i)
{
if (inertIndex_ != i)
{
volScalarField& Yi = X_[i];
fvScalarMatrix YiEqn
(
fv::EulerDdtScheme(mesh).fvmDdt(Yi)
+ fv::gaussConvectionScheme
(
mesh,
phi,
upwind(mesh, phi)
).fvmDiv(phi, Yi)
==
Su[i] + fvm::Sp(Sp[i], Yi)
);
YiEqn.solve();
surfaceScalarField& phiYiCorr = phiYiCorrs[i];
// Add a bounded upwind U-mean flux
phiYiCorr += YiEqn.flux();
if (nYiSubCycles > 1)
{
for
(
subCycle YiSubCycle(Yi, nYiSubCycles);
!(++YiSubCycle).end();
)
{
MULES::explicitSolve
(
geometricOneField(),
Yi,
phi,
phiYiCorr,
Sp[i],
Su[i],
oneField(),
zeroField()
);
}
}
else
{
MULES::explicitSolve
(
geometricOneField(),
Yi,
phi,
phiYiCorr,
Sp[i],
Su[i],
oneField(),
zeroField()
);
}
if (addDiffusion_)
{
const volScalarField& alpha = *this;
fvScalarMatrix YiDiffEqn
(
fvm::ddt(Yi) - fvc::ddt(Yi)
- fvm::laplacian
(
alpha*this->fluid().turbulence()->nut()/Sct_,
Yi
)
);
YiDiffEqn.solve("diffusion" + Yi.name());
}
Yt += Yi;
}
}
X_[inertIndex_] = scalar(1) - Yt;
X_[inertIndex_].clamp_min(0);
calculateMassFractions();
}
template
const Foam::PtrList&
Foam::MultiComponentPhaseModel::Y() const
{
return thermoPtr_->composition().Y();
}
template
Foam::PtrList&
Foam::MultiComponentPhaseModel::Y()
{
return thermoPtr_->composition().Y();
}
template
Foam::label
Foam::MultiComponentPhaseModel::inertIndex() const
{
return inertIndex_;
}
// ************************************************************************* //