Files
openfoam/src/lagrangian/intermediate/parcels/Templates/ReactingMultiphaseParcel/ReactingMultiphaseParcel.C

661 lines
16 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2008-2011 OpenCFD Ltd.
\\/ M anipulation |
-------------------------------------------------------------------------------
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 <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "ReactingMultiphaseParcel.H"
#include "mathematicalConstants.H"
using namespace Foam::constant::mathematical;
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
template<class ParcelType>
const Foam::label Foam::ReactingMultiphaseParcel<ParcelType>::GAS(0);
template<class ParcelType>
const Foam::label Foam::ReactingMultiphaseParcel<ParcelType>::LIQ(1);
template<class ParcelType>
const Foam::label Foam::ReactingMultiphaseParcel<ParcelType>::SLD(2);
// * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * //
template<class ParcelType>
template<class TrackData>
Foam::scalar Foam::ReactingMultiphaseParcel<ParcelType>::CpEff
(
TrackData& td,
const scalar p,
const scalar T,
const label idG,
const label idL,
const label idS
) const
{
return
this->Y_[GAS]*td.cloud().composition().Cp(idG, YGas_, p, T)
+ this->Y_[LIQ]*td.cloud().composition().Cp(idL, YLiquid_, p, T)
+ this->Y_[SLD]*td.cloud().composition().Cp(idS, YSolid_, p, T);
}
template<class ParcelType>
template<class TrackData>
Foam::scalar Foam::ReactingMultiphaseParcel<ParcelType>::HEff
(
TrackData& td,
const scalar p,
const scalar T,
const label idG,
const label idL,
const label idS
) const
{
return
this->Y_[GAS]*td.cloud().composition().H(idG, YGas_, p, T)
+ this->Y_[LIQ]*td.cloud().composition().H(idL, YLiquid_, p, T)
+ this->Y_[SLD]*td.cloud().composition().H(idS, YSolid_, p, T);
}
template<class ParcelType>
template<class TrackData>
Foam::scalar Foam::ReactingMultiphaseParcel<ParcelType>::LEff
(
TrackData& td,
const scalar p,
const scalar T,
const label idG,
const label idL,
const label idS
) const
{
return
this->Y_[GAS]*td.cloud().composition().L(idG, YGas_, p, T)
+ this->Y_[LIQ]*td.cloud().composition().L(idL, YLiquid_, p, T)
+ this->Y_[SLD]*td.cloud().composition().L(idS, YSolid_, p, T);
}
template<class ParcelType>
Foam::scalar Foam::ReactingMultiphaseParcel<ParcelType>::updateMassFractions
(
const scalar mass0,
const scalarField& dMassGas,
const scalarField& dMassLiquid,
const scalarField& dMassSolid
)
{
scalarField& YMix = this->Y_;
scalar massGas =
this->updateMassFraction(mass0*YMix[GAS], dMassGas, YGas_);
scalar massLiquid =
this->updateMassFraction(mass0*YMix[LIQ], dMassLiquid, YLiquid_);
scalar massSolid =
this->updateMassFraction(mass0*YMix[SLD], dMassSolid, YSolid_);
scalar massNew = max(massGas + massLiquid + massSolid, ROOTVSMALL);
YMix[GAS] = massGas/massNew;
YMix[LIQ] = massLiquid/massNew;
YMix[SLD] = 1.0 - YMix[GAS] - YMix[LIQ];
return massNew;
}
// * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
template<class ParcelType>
template<class TrackData>
void Foam::ReactingMultiphaseParcel<ParcelType>::setCellValues
(
TrackData& td,
const scalar dt,
const label cellI
)
{
ParcelType::setCellValues(td, dt, cellI);
}
template<class ParcelType>
template<class TrackData>
void Foam::ReactingMultiphaseParcel<ParcelType>::cellValueSourceCorrection
(
TrackData& td,
const scalar dt,
const label cellI
)
{
// Re-use correction from reacting parcel
ParcelType::cellValueSourceCorrection(td, dt, cellI);
}
template<class ParcelType>
template<class TrackData>
void Foam::ReactingMultiphaseParcel<ParcelType>::calc
(
TrackData& td,
const scalar dt,
const label cellI
)
{
typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
const CompositionModel<reactingCloudType>& composition =
td.cloud().composition();
// Define local properties at beginning of timestep
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const scalar np0 = this->nParticle_;
const scalar d0 = this->d_;
const vector& U0 = this->U_;
const scalar rho0 = this->rho_;
const scalar T0 = this->T_;
const scalar Cp0 = this->Cp_;
const scalar mass0 = this->mass();
const scalar pc = this->pc_;
const scalarField& YMix = this->Y_;
const label idG = composition.idGas();
const label idL = composition.idLiquid();
const label idS = composition.idSolid();
// Calc surface values
// ~~~~~~~~~~~~~~~~~~~
scalar Ts, rhos, mus, Prs, kappas;
this->calcSurfaceValues(td, cellI, T0, Ts, rhos, mus, Prs, kappas);
// Reynolds number
scalar Res = this->Re(U0, d0, rhos, mus);
// Sources
//~~~~~~~~
// Explicit momentum source for particle
vector Su = vector::zero;
// Momentum transfer from the particle to the carrier phase
vector dUTrans = vector::zero;
// Explicit enthalpy source for particle
scalar Sh = 0.0;
// Sensible enthalpy transfer from the particle to the carrier phase
scalar dhsTrans = 0.0;
// Phase change in liquid phase
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Mass transfer due to phase change
scalarField dMassPC(YLiquid_.size(), 0.0);
// Molar flux of species emitted from the particle (kmol/m^2/s)
scalar Ne = 0.0;
// Sum Ni*Cpi*Wi of emission species
scalar NCpW = 0.0;
// Surface concentrations of emitted species
scalarField Cs(composition.carrier().species().size(), 0.0);
// Calc mass and enthalpy transfer due to phase change
this->calcPhaseChange
(
td,
dt,
cellI,
Res,
Ts,
mus/rhos,
d0,
T0,
mass0,
idL,
YMix[LIQ],
YLiquid_,
dMassPC,
Sh,
Ne,
NCpW,
Cs
);
// Devolatilisation
// ~~~~~~~~~~~~~~~~
// Mass transfer due to devolatilisation
scalarField dMassDV(YGas_.size(), 0.0);
// Calc mass and enthalpy transfer due to devolatilisation
calcDevolatilisation
(
td,
dt,
Ts,
d0,
T0,
mass0,
this->mass0_,
YMix[GAS]*YGas_,
canCombust_,
dMassDV,
Sh,
Ne,
NCpW,
Cs
);
// Surface reactions
// ~~~~~~~~~~~~~~~~~
// Change in carrier phase composition due to surface reactions
scalarField dMassSRGas(YGas_.size(), 0.0);
scalarField dMassSRLiquid(YLiquid_.size(), 0.0);
scalarField dMassSRSolid(YSolid_.size(), 0.0);
scalarField dMassSRCarrier(composition.carrier().species().size(), 0.0);
// Calc mass and enthalpy transfer due to surface reactions
calcSurfaceReactions
(
td,
dt,
cellI,
d0,
T0,
mass0,
canCombust_,
Ne,
YMix,
YGas_,
YLiquid_,
YSolid_,
dMassSRGas,
dMassSRLiquid,
dMassSRSolid,
dMassSRCarrier,
Sh,
dhsTrans
);
// Correct surface values due to emitted species
this->correctSurfaceValues(td, cellI, Ts, Cs, rhos, mus, Prs, kappas);
Res = this->Re(U0, d0, rhos, mus);
// Update component mass fractions
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
scalarField dMassGas(dMassDV + dMassSRGas);
scalarField dMassLiquid(dMassPC + dMassSRLiquid);
scalarField dMassSolid(dMassSRSolid);
scalar mass1 =
updateMassFractions(mass0, dMassGas, dMassLiquid, dMassSolid);
// Heat transfer
// ~~~~~~~~~~~~~
// Calculate new particle temperature
scalar Sph = 0.0;
scalar T1 =
this->calcHeatTransfer
(
td,
dt,
cellI,
Res,
Prs,
kappas,
d0,
rho0,
T0,
Cp0,
NCpW,
Sh,
dhsTrans,
Sph
);
// Motion
// ~~~~~~
// Calculate new particle velocity
scalar Spu = 0;
vector U1 =
this->calcVelocity
(
td,
dt,
cellI,
Res,
mus,
d0,
U0,
rho0,
0.5*(mass0 + mass1),
Su,
dUTrans,
Spu
);
// Accumulate carrier phase source terms
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (td.cloud().solution().coupled())
{
// Transfer mass lost from particle to carrier mass source
forAll(YGas_, i)
{
label gid = composition.localToGlobalCarrierId(GAS, i);
td.cloud().rhoTrans(gid)[cellI] += np0*dMassGas[i];
}
forAll(YLiquid_, i)
{
label gid = composition.localToGlobalCarrierId(LIQ, i);
td.cloud().rhoTrans(gid)[cellI] += np0*dMassLiquid[i];
}
/*
// No mapping between solid components and carrier phase
forAll(YSolid_, i)
{
label gid = composition.localToGlobalCarrierId(SLD, i);
td.cloud().rhoTrans(gid)[cellI] += np0*dMassSolid[i];
}
*/
forAll(dMassSRCarrier, i)
{
td.cloud().rhoTrans(i)[cellI] += np0*dMassSRCarrier[i];
}
// Update momentum transfer
td.cloud().UTrans()[cellI] += np0*dUTrans;
// Update momentum transfer coefficient
td.cloud().UCoeff()[cellI] += np0*Spu;
// Update sensible enthalpy transfer
td.cloud().hsTrans()[cellI] += np0*dhsTrans;
// Update sensible enthalpy coefficient
td.cloud().hsCoeff()[cellI] += np0*Sph;
}
// Remove the particle when mass falls below minimum threshold
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if (mass1 < td.cloud().constProps().minParticleMass())
{
td.keepParticle = false;
if (td.cloud().solution().coupled())
{
// Absorb parcel into carrier phase
forAll(YGas_, i)
{
label gid = composition.localToGlobalCarrierId(GAS, i);
td.cloud().rhoTrans(gid)[cellI] += np0*mass1*YMix[GAS]*YGas_[i];
}
forAll(YLiquid_, i)
{
label gid = composition.localToGlobalCarrierId(LIQ, i);
td.cloud().rhoTrans(gid)[cellI] +=
np0*mass1*YMix[LIQ]*YLiquid_[i];
}
/*
// No mapping between solid components and carrier phase
forAll(YSolid_, i)
{
label gid = composition.localToGlobalCarrierId(SLD, i);
td.cloud().rhoTrans(gid)[cellI] +=
np0*mass1*YMix[SLD]*YSolid_[i];
}
*/
td.cloud().UTrans()[cellI] += np0*mass1*U1;
// enthalpy transfer accounted for via change in mass fractions
}
}
// Set new particle properties
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~
else
{
this->Cp_ = CpEff(td, pc, T1, idG, idL, idS);
this->T_ = T1;
this->U_ = U1;
// Update particle density or diameter
if (td.cloud().constProps().constantVolume())
{
this->rho_ = mass1/this->volume();
}
else
{
this->d_ = cbrt(mass1/this->rho_*6.0/pi);
}
}
}
template<class ParcelType>
template<class TrackData>
void Foam::ReactingMultiphaseParcel<ParcelType>::calcDevolatilisation
(
TrackData& td,
const scalar dt,
const scalar Ts,
const scalar d,
const scalar T,
const scalar mass,
const scalar mass0,
const scalarField& YGasEff,
bool& canCombust,
scalarField& dMassDV,
scalar& Sh,
scalar& N,
scalar& NCpW,
scalarField& Cs
) const
{
// Check that model is active, and that the parcel temperature is
// within necessary limits for devolatilisation to occur
if
(
!td.cloud().devolatilisation().active()
|| T < td.cloud().constProps().Tvap()
)
{
return;
}
typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
const CompositionModel<reactingCloudType>& composition =
td.cloud().composition();
// Total mass of volatiles evolved
td.cloud().devolatilisation().calculate
(
dt,
mass0,
mass,
T,
YGasEff,
canCombust,
dMassDV
);
scalar dMassTot = sum(dMassDV);
td.cloud().addToMassDevolatilisation(this->nParticle_*dMassTot);
Sh -= dMassTot*td.cloud().constProps().LDevol()/dt;
// Molar average molecular weight of carrier mix
const scalar Wc = this->rhoc_*specie::RR*this->Tc_/this->pc_;
// Update molar emissions
forAll(dMassDV, i)
{
// Note: hardcoded gaseous diffusivities for now
// TODO: add to carrier thermo
const scalar beta = sqr(cbrt(15.0) + cbrt(15.0));
const label id = composition.localToGlobalCarrierId(GAS, i);
const scalar Cp = composition.carrier().Cp(id, Ts);
const scalar W = composition.carrier().W(id);
const scalar Ni = dMassDV[i]/(this->areaS(d)*dt*W);
// Dab calc'd using API vapour mass diffusivity function
const scalar Dab =
3.6059e-3*(pow(1.8*Ts, 1.75))*sqrt(1.0/W + 1.0/Wc)/(this->pc_*beta);
N += Ni;
NCpW += Ni*Cp*W;
Cs[id] += Ni*d/(2.0*Dab);
}
}
template<class ParcelType>
template<class TrackData>
void Foam::ReactingMultiphaseParcel<ParcelType>::calcSurfaceReactions
(
TrackData& td,
const scalar dt,
const label cellI,
const scalar d,
const scalar T,
const scalar mass,
const bool canCombust,
const scalar N,
const scalarField& YMix,
const scalarField& YGas,
const scalarField& YLiquid,
const scalarField& YSolid,
scalarField& dMassSRGas,
scalarField& dMassSRLiquid,
scalarField& dMassSRSolid,
scalarField& dMassSRCarrier,
scalar& Sh,
scalar& dhsTrans
) const
{
// Check that model is active
if (!td.cloud().surfaceReaction().active() || !canCombust)
{
return;
}
// Update surface reactions
const scalar hReaction = td.cloud().surfaceReaction().calculate
(
dt,
cellI,
d,
T,
this->Tc_,
this->pc_,
this->rhoc_,
mass,
YGas,
YLiquid,
YSolid,
YMix,
N,
dMassSRGas,
dMassSRLiquid,
dMassSRSolid,
dMassSRCarrier
);
td.cloud().addToMassSurfaceReaction
(
this->nParticle_
*(sum(dMassSRGas) + sum(dMassSRLiquid) + sum(dMassSRSolid))
);
const scalar xsi = min(T/5000.0, 1.0);
const scalar coeff =
(1.0 - xsi*xsi)*td.cloud().constProps().hRetentionCoeff();
Sh += coeff*hReaction/dt;
dhsTrans += (1.0 - coeff)*hReaction;
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template<class ParcelType>
Foam::ReactingMultiphaseParcel<ParcelType>::ReactingMultiphaseParcel
(
const ReactingMultiphaseParcel<ParcelType>& p
)
:
ParcelType(p),
YGas_(p.YGas_),
YLiquid_(p.YLiquid_),
YSolid_(p.YSolid_)
{}
template<class ParcelType>
Foam::ReactingMultiphaseParcel<ParcelType>::ReactingMultiphaseParcel
(
const ReactingMultiphaseParcel<ParcelType>& p,
const polyMesh& mesh
)
:
ParcelType(p, mesh),
YGas_(p.YGas_),
YLiquid_(p.YLiquid_),
YSolid_(p.YSolid_)
{}
// * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
#include "ReactingMultiphaseParcelIO.C"
// ************************************************************************* //