/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2017 OpenFOAM Foundation
-------------------------------------------------------------------------------
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 "SprayParcel.H"
#include "BreakupModel.H"
#include "CompositionModel.H"
#include "AtomizationModel.H"
// * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
template
template
void Foam::SprayParcel::setCellValues
(
TrackCloudType& cloud,
trackingData& td
)
{
ParcelType::setCellValues(cloud, td);
}
template
template
void Foam::SprayParcel::cellValueSourceCorrection
(
TrackCloudType& cloud,
trackingData& td,
const scalar dt
)
{
ParcelType::cellValueSourceCorrection(cloud, td, dt);
}
template
template
void Foam::SprayParcel::calc
(
TrackCloudType& cloud,
trackingData& td,
const scalar dt
)
{
const auto& composition = cloud.composition();
const auto& liquids = composition.liquids();
// Check if parcel belongs to liquid core
if (liquidCore() > 0.5)
{
// Liquid core parcels should not experience coupled forces
cloud.forces().setCalcCoupled(false);
}
// Get old mixture composition
scalarField X0(liquids.X(this->Y()));
// Check if we have critical or boiling conditions
scalar TMax = liquids.Tc(X0);
const scalar T0 = this->T();
const scalar pc0 = td.pc();
if (liquids.pv(pc0, T0, X0) >= pc0*0.999)
{
// Set TMax to boiling temperature
TMax = liquids.pvInvert(pc0, X0);
}
// Set the maximum temperature limit
cloud.constProps().setTMax(TMax);
// Store the parcel properties
this->Cp() = liquids.Cp(pc0, T0, X0);
sigma_ = liquids.sigma(pc0, T0, X0);
const scalar rho0 = liquids.rho(pc0, T0, X0);
this->rho() = rho0;
const scalar mass0 = this->mass();
mu_ = liquids.mu(pc0, T0, X0);
ParcelType::calc(cloud, td, dt);
if (td.keepParticle)
{
// Reduce the stripped parcel mass due to evaporation
// assuming the number of particles remains unchanged
this->ms() -= this->ms()*(mass0 - this->mass())/mass0;
// Update Cp, sigma, density and diameter due to change in temperature
// and/or composition
scalar T1 = this->T();
scalarField X1(liquids.X(this->Y()));
this->Cp() = liquids.Cp(td.pc(), T1, X1);
sigma_ = liquids.sigma(td.pc(), T1, X1);
scalar rho1 = liquids.rho(td.pc(), T1, X1);
this->rho() = rho1;
mu_ = liquids.mu(td.pc(), T1, X1);
scalar d1 = this->d()*cbrt(rho0/rho1);
this->d() = d1;
if (liquidCore() > 0.5)
{
calcAtomization(cloud, td, dt);
// Preserve the total mass/volume by increasing the number of
// particles in parcels due to breakup
scalar d2 = this->d();
this->nParticle() *= pow3(d1/d2);
}
else
{
calcBreakup(cloud, td, dt);
}
}
// Restore coupled forces
cloud.forces().setCalcCoupled(true);
}
template
template
void Foam::SprayParcel::calcAtomization
(
TrackCloudType& cloud,
trackingData& td,
const scalar dt
)
{
const auto& atomization = cloud.atomization();
if (!atomization.active())
{
return;
}
const auto& composition = cloud.composition();
const auto& liquids = composition.liquids();
// Average molecular weight of carrier mix - assumes perfect gas
scalar Wc = td.rhoc()*RR*td.Tc()/td.pc();
scalar R = RR/Wc;
scalar Tav = atomization.Taverage(this->T(), td.Tc());
// Calculate average gas density based on average temperature
scalar rhoAv = td.pc()/(R*Tav);
scalar soi = cloud.injectors().timeStart();
scalar currentTime = cloud.db().time().value();
const vector pos(this->position());
const vector& injectionPos = this->position0();
// Disregard the continuous phase when calculating the relative velocity
// (in line with the deactivated coupled assumption)
scalar Urel = mag(this->U());
scalar t0 = max(0.0, currentTime - this->age() - soi);
scalar t1 = min(t0 + dt, cloud.injectors().timeEnd() - soi);
// This should be the vol flow rate from when the parcel was injected
scalar volFlowRate = cloud.injectors().volumeToInject(t0, t1)/dt;
scalar chi = 0.0;
if (atomization.calcChi())
{
chi = this->chi(cloud, td, liquids.X(this->Y()));
}
atomization.update
(
dt,
this->d(),
this->liquidCore(),
this->tc(),
this->rho(),
mu_,
sigma_,
volFlowRate,
rhoAv,
Urel,
pos,
injectionPos,
cloud.pAmbient(),
chi,
cloud.rndGen()
);
}
template
template
void Foam::SprayParcel::calcBreakup
(
TrackCloudType& cloud,
trackingData& td,
const scalar dt
)
{
auto& breakup = cloud.breakup();
if (!breakup.active())
{
return;
}
if (breakup.solveOscillationEq())
{
solveTABEq(cloud, td, dt);
}
// Average molecular weight of carrier mix - assumes perfect gas
scalar Wc = td.rhoc()*RR*td.Tc()/td.pc();
scalar R = RR/Wc;
scalar Tav = cloud.atomization().Taverage(this->T(), td.Tc());
// Calculate average gas density based on average temperature
scalar rhoAv = td.pc()/(R*Tav);
scalar muAv = td.muc();
vector Urel = this->U() - td.Uc();
scalar Urmag = mag(Urel);
scalar Re = this->Re(rhoAv, this->U(), td.Uc(), this->d(), muAv);
const typename TrackCloudType::parcelType& p =
static_cast(*this);
typename TrackCloudType::parcelType::trackingData& ttd =
static_cast(td);
const scalar mass = p.mass();
const typename TrackCloudType::forceType& forces = cloud.forces();
const forceSuSp Fcp = forces.calcCoupled(p, ttd, dt, mass, Re, muAv);
const forceSuSp Fncp = forces.calcNonCoupled(p, ttd, dt, mass, Re, muAv);
this->tMom() = mass/(Fcp.Sp() + Fncp.Sp() + ROOTVSMALL);
const vector g = cloud.g().value();
scalar parcelMassChild = 0.0;
scalar dChild = 0.0;
if
(
breakup.update
(
dt,
g,
this->d(),
this->tc(),
this->ms(),
this->nParticle(),
this->KHindex(),
this->y(),
this->yDot(),
this->d0(),
this->rho(),
mu_,
sigma_,
this->U(),
rhoAv,
muAv,
Urel,
Urmag,
this->tMom(),
dChild,
parcelMassChild
)
)
{
scalar Re = rhoAv*Urmag*dChild/muAv;
// Add child parcel as copy of parent
SprayParcel* child = new SprayParcel(*this);
child->origId() = this->getNewParticleID();
child->origProc() = Pstream::myProcNo();
child->d() = dChild;
child->d0() = dChild;
const scalar massChild = child->mass();
child->mass0() = massChild;
child->nParticle() = parcelMassChild/massChild;
const forceSuSp Fcp =
forces.calcCoupled(*child, ttd, dt, massChild, Re, muAv);
const forceSuSp Fncp =
forces.calcNonCoupled(*child, ttd, dt, massChild, Re, muAv);
child->age() = 0.0;
child->liquidCore() = 0.0;
child->KHindex() = 1.0;
child->y() = cloud.breakup().y0();
child->yDot() = cloud.breakup().yDot0();
child->tc() = 0.0;
child->ms() = -GREAT;
child->injector() = this->injector();
child->tMom() = massChild/(Fcp.Sp() + Fncp.Sp());
child->user() = 0.0;
child->calcDispersion(cloud, td, dt);
cloud.addParticle(child);
}
}
template
template
Foam::scalar Foam::SprayParcel::chi
(
TrackCloudType& cloud,
trackingData& td,
const scalarField& X
) const
{
// Modifications to take account of the flash boiling on primary break-up
const auto& composition = cloud.composition();
const auto& liquids = composition.liquids();
scalar chi = 0.0;
scalar T0 = this->T();
scalar p0 = td.pc();
scalar pAmb = cloud.pAmbient();
scalar pv = liquids.pv(p0, T0, X);
forAll(liquids, i)
{
if (pv >= 0.999*pAmb)
{
// Liquid is boiling - calc boiling temperature
const liquidProperties& liq = liquids.properties()[i];
scalar TBoil = liq.pvInvert(p0);
scalar hl = liq.hl(pAmb, TBoil);
scalar iTp = liq.h(pAmb, T0) - pAmb/liq.rho(pAmb, T0);
scalar iTb = liq.h(pAmb, TBoil) - pAmb/liq.rho(pAmb, TBoil);
chi += X[i]*(iTp - iTb)/hl;
}
}
return clamp(chi, zero_one{});
}
template
template
void Foam::SprayParcel::solveTABEq
(
TrackCloudType& cloud,
trackingData& td,
const scalar dt
)
{
const scalar& TABCmu = cloud.breakup().TABCmu();
const scalar& TABtwoWeCrit = cloud.breakup().TABtwoWeCrit();
const scalar& TABComega = cloud.breakup().TABComega();
scalar r = 0.5*this->d();
scalar r2 = r*r;
scalar r3 = r*r2;
// Inverse of characteristic viscous damping time
scalar rtd = 0.5*TABCmu*mu_/(this->rho()*r2);
// Oscillation frequency (squared)
scalar omega2 = TABComega*sigma_/(this->rho()*r3) - rtd*rtd;
if (omega2 > 0)
{
scalar omega = sqrt(omega2);
scalar We =
this->We(td.rhoc(), this->U(), td.Uc(), r, sigma_)/TABtwoWeCrit;
// Initial values for y and yDot
scalar y0 = this->y() - We;
scalar yDot0 = this->yDot() + y0*rtd;
// Update distortion parameters
scalar c = cos(omega*dt);
scalar s = sin(omega*dt);
scalar e = exp(-rtd*dt);
this->y() = We + e*(y0*c + (yDot0/omega)*s);
this->yDot() = (We - this->y())*rtd + e*(yDot0*c - omega*y0*s);
}
else
{
// Reset distortion parameters
this->y() = 0;
this->yDot() = 0;
}
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template
Foam::SprayParcel::SprayParcel(const SprayParcel& p)
:
ParcelType(p),
d0_(p.d0_),
position0_(p.position0_),
sigma_(p.sigma_),
mu_(p.mu_),
liquidCore_(p.liquidCore_),
KHindex_(p.KHindex_),
y_(p.y_),
yDot_(p.yDot_),
tc_(p.tc_),
ms_(p.ms_),
injector_(p.injector_),
tMom_(p.tMom_),
user_(p.user_)
{}
template
Foam::SprayParcel::SprayParcel
(
const SprayParcel& p,
const polyMesh& mesh
)
:
ParcelType(p, mesh),
d0_(p.d0_),
position0_(p.position0_),
sigma_(p.sigma_),
mu_(p.mu_),
liquidCore_(p.liquidCore_),
KHindex_(p.KHindex_),
y_(p.y_),
yDot_(p.yDot_),
tc_(p.tc_),
ms_(p.ms_),
injector_(p.injector_),
tMom_(p.tMom_),
user_(p.user_)
{}
// * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
#include "SprayParcelIO.C"
// ************************************************************************* //