Newer
Older
/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2019-2020 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 <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "ThermoSurfaceFilm.H"
#include "surfaceFilmRegionModel.H"
#include "liquidFilmBase.H"
#include "addToRunTimeSelectionTable.H"
#include "Pstream.H"
using namespace Foam::constant::mathematical;
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
template<class CloudType>
Foam::wordList Foam::ThermoSurfaceFilm<CloudType>::interactionTypeNames_
{
"absorb", "bounce", "splashBai"
};
// * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * //
template<class CloudType>
typename Foam::ThermoSurfaceFilm<CloudType>::interactionType
Foam::ThermoSurfaceFilm<CloudType>::interactionTypeEnum(const word& it) const
{
forAll(interactionTypeNames_, i)
{
if (interactionTypeNames_[i] == it)
{
return interactionType(i);
}
}
FatalErrorInFunction
<< "Unknown interaction type " << it
<< ". Valid interaction types include: " << interactionTypeNames_
<< abort(FatalError);
return interactionType(0);
}
template<class CloudType>
Foam::word Foam::ThermoSurfaceFilm<CloudType>::interactionTypeStr
(
const interactionType& it
) const
{
if (it >= interactionTypeNames_.size())
{
FatalErrorInFunction
<< "Unknown interaction type enumeration" << abort(FatalError);
}
return interactionTypeNames_[it];
}
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
template<class CloudType>
Foam::vector Foam::ThermoSurfaceFilm<CloudType>::tangentVector
(
const vector& v
) const
{
scalar magTangent = 0.0;
while (magTangent < SMALL)
{
vector vTest = rndGen_.sample01<vector>();
tangent = vTest - (vTest & v)*v;
magTangent = mag(tangent);
}
return tangent/magTangent;
}
template<class CloudType>
Foam::vector Foam::ThermoSurfaceFilm<CloudType>::splashDirection
(
const vector& tanVec1,
const vector& tanVec2,
const vector& nf
) const
{
Henry Weller
committed
// Azimuthal angle [rad]
const scalar phiSi = twoPi*rndGen_.sample01<scalar>();
Henry Weller
committed
// Ejection angle [rad]
const scalar thetaSi = degToRad(rndGen_.sample01<scalar>()*(50 - 5) + 5);
Henry Weller
committed
// Direction vector of new parcel
const scalar alpha = sin(thetaSi);
const scalar dcorr = cos(thetaSi);
const vector normal = alpha*(tanVec1*cos(phiSi) + tanVec2*sin(phiSi));
vector dirVec = dcorr*nf;
dirVec += normal;
return dirVec/mag(dirVec);
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::init()
const fvMesh& mesh = this->owner().mesh();
// set up filmModel pointer
filmModel_ =
const_cast<regionFilm*>
mesh.time().objectRegistry::template findObject
<
regionFilm
>
(
"surfaceFilmProperties"
)
// set up areaFilms
const wordList names =
mesh.time().objectRegistry::template
sortedNames<regionModels::regionFaModel>();
forAll(names, i)
{
const regionModels::regionFaModel* regionFa =
mesh.time().objectRegistry::template findObject
<
regionModels::regionFaModel
>(names[i]);
if (regionFa && isA<areaFilm>(*regionFa))
{
areaFilm& film =
const_cast<areaFilm&>(refCast<const areaFilm>(*regionFa));
areaFilms_.append(&film);
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::absorbInteraction
(
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
const parcelType& p,
const polyPatch& pp,
const label facei,
const scalar mass,
bool& keepParticle
)
{
if (debug)
{
Info<< "Parcel " << p.origId() << " absorbInteraction" << endl;
}
// Patch face normal
const vector& nf = pp.faceNormals()[facei];
// Patch velocity
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
// Relative parcel velocity
const vector Urel = p.U() - Up;
// Parcel normal velocity
const vector Un = nf*(Urel & nf);
// Parcel tangential velocity
const vector Ut = Urel - Un;
mass, // mass
mass*Ut, // tangential momentum
mass*mag(Un), // impingement pressure
mass*p.hs() // energy
);
this->nParcelsTransferred()++;
this->totalMassTransferred() += mass;
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::bounceInteraction
(
parcelType& p,
const polyPatch& pp,
bool& keepParticle
) const
{
if (debug)
{
Info<< "Parcel " << p.origId() << " bounceInteraction" << endl;
}
// Patch face normal
const vector& nf = pp.faceNormals()[facei];
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
// Relative parcel velocity
const vector Urel = p.U() - Up;
// Flip parcel normal velocity component
p.U() -= 2.0*nf*(Urel & nf);
keepParticle = true;
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::drySplashInteraction
(
const parcelType& p,
const polyPatch& pp,
bool& keepParticle
)
{
if (debug)
{
Info<< "Parcel " << p.origId() << " drySplashInteraction" << endl;
}
const liquidProperties& liq = thermo_.liquids().properties()[0];
// Patch face velocity and normal
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
const vector& nf = pp.faceNormals()[facei];
Henry Weller
committed
// Local pressure
const scalar pc = thermo_.thermo().p()[p.cell()];
// Retrieve parcel properties
const scalar m = p.mass()*p.nParticle();
const scalar rho = p.rho();
const scalar d = p.d();
const scalar sigma = liq.sigma(pc, p.T());
const scalar mu = liq.mu(pc, p.T());
const vector Urel = p.U() - Up;
const vector Un = nf*(Urel & nf);
// Laplace number
const scalar La = rho*sigma*d/sqr(mu);
// Weber number
const scalar We = rho*magSqr(Un)*d/sigma;
// Critical Weber number
const scalar Wec = Adry_*pow(La, -0.183);
Henry Weller
committed
if (We < Wec) // Adhesion - assume absorb
absorbInteraction<filmType>
(filmModel, p, pp, facei, m, keepParticle);
Henry Weller
committed
else // Splash
Henry Weller
committed
// Ratio of incident mass to splashing mass
const scalar mRatio = 0.2 + 0.6*rndGen_.sample01<scalar>();
splashInteraction<filmType>
(filmModel, p, pp, facei, mRatio, We, Wec, sigma, keepParticle);
}
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::wetSplashInteraction
(
parcelType& p,
const polyPatch& pp,
bool& keepParticle
)
{
if (debug)
{
Info<< "Parcel " << p.origId() << " wetSplashInteraction" << endl;
}
const liquidProperties& liq = thermo_.liquids().properties()[0];
// Patch face velocity and normal
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
const vector& nf = pp.faceNormals()[facei];
Henry Weller
committed
// Local pressure
const scalar pc = thermo_.thermo().p()[p.cell()];
// Retrieve parcel properties
const scalar m = p.mass()*p.nParticle();
const scalar rho = p.rho();
const scalar d = p.d();
vector& U = p.U();
const scalar sigma = liq.sigma(pc, p.T());
const scalar mu = liq.mu(pc, p.T());
const vector Urel = p.U() - Up;
const vector Un = nf*(Urel & nf);
const vector Ut = Urel - Un;
// Laplace number
const scalar La = rho*sigma*d/sqr(mu);
// Weber number
const scalar We = rho*magSqr(Un)*d/sigma;
// Critical Weber number
const scalar Wec = Awet_*pow(La, -0.183);
Henry Weller
committed
if (We < 2) // Adhesion - assume absorb
absorbInteraction<filmType>
(filmModel, p, pp, facei, m, keepParticle);
Henry Weller
committed
else if ((We >= 2) && (We < 20)) // Bounce
Henry Weller
committed
// Incident angle of impingement
const scalar theta = piByTwo - acos(U/mag(U) & nf);
Henry Weller
committed
// Restitution coefficient
const scalar epsilon = 0.993 - theta*(1.76 - theta*(1.56 - theta*0.49));
Henry Weller
committed
// Update parcel velocity
U = -epsilon*(Un) + 5.0/7.0*(Ut);
Henry Weller
committed
else if ((We >= 20) && (We < Wec)) // Spread - assume absorb
absorbInteraction<filmType>
(filmModel, p, pp, facei, m, keepParticle);
Henry Weller
committed
else // Splash
Henry Weller
committed
// Ratio of incident mass to splashing mass
// splash mass can be > incident mass due to film entrainment
const scalar mRatio = 0.2 + 0.9*rndGen_.sample01<scalar>();
splashInteraction<filmType>
(filmModel, p, pp, facei, mRatio, We, Wec, sigma, keepParticle);
}
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::splashInteraction
(
const parcelType& p,
const polyPatch& pp,
const scalar mRatio,
const scalar We,
const scalar Wec,
const scalar sigma,
bool& keepParticle
)
{
// Patch face velocity and normal
const fvMesh& mesh = this->owner().mesh();
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
const vector& nf = pp.faceNormals()[facei];
// Determine direction vectors tangential to patch normal
const vector tanVec1 = tangentVector(nf);
const vector tanVec2 = nf^tanVec1;
// Retrieve parcel properties
const scalar np = p.nParticle();
const scalar m = p.mass()*np;
const scalar d = p.d();
const vector Urel = p.U() - Up;
const vector Un = nf*(Urel & nf);
const vector Ut = Urel - Un;
const vector& posC = mesh.C()[p.cell()];
const vector& posCf = mesh.Cf().boundaryField()[pp.index()][facei];
Henry Weller
committed
// Total mass of (all) splashed parcels
const scalar mSplash = m*mRatio;
Henry Weller
committed
// Number of splashed particles per incoming particle
const scalar Ns = 5.0*(We/Wec - 1.0);
Henry Weller
committed
// Average diameter of splashed particles
const scalar dBarSplash = 1/cbrt(6.0)*cbrt(mRatio/Ns)*d + ROOTVSMALL;
Henry Weller
committed
// Cumulative diameter splash distribution
const scalar dMax = 0.9*cbrt(mRatio)*d;
const scalar dMin = 0.1*dMax;
const scalar K = exp(-dMin/dBarSplash) - exp(-dMax/dBarSplash);
Henry Weller
committed
// Surface energy of secondary parcels [J]
scalar ESigmaSec = 0;
Henry Weller
committed
// Sample splash distribution to determine secondary parcel diameters
scalarList dNew(parcelsPerSplash_);
scalarList npNew(parcelsPerSplash_);
forAll(dNew, i)
{
const scalar y = rndGen_.sample01<scalar>();
dNew[i] = -dBarSplash*log(exp(-dMin/dBarSplash) - y*K);
npNew[i] = mRatio*np*pow3(d)/pow3(dNew[i])/parcelsPerSplash_;
ESigmaSec += npNew[i]*sigma*p.areaS(dNew[i]);
Henry Weller
committed
// Incident kinetic energy [J]
const scalar EKIn = 0.5*m*magSqr(Un);
Henry Weller
committed
// Incident surface energy [J]
const scalar ESigmaIn = np*sigma*p.areaS(d);
Henry Weller
committed
// Dissipative energy
const scalar Ed = max(0.8*EKIn, np*Wec/12*pi*sigma*sqr(d));
Henry Weller
committed
// Total energy [J]
const scalar EKs = EKIn + ESigmaIn - ESigmaSec - Ed;
Henry Weller
committed
// Switch to absorb if insufficient energy for splash
absorbInteraction<filmType>
(filmModel, p, pp, facei, m, keepParticle);
Henry Weller
committed
// Helper variables to calculate magUns0
const scalar logD = log(d);
const scalar coeff2 = log(dNew[0]) - logD + ROOTVSMALL;
scalar coeff1 = 0.0;
forAll(dNew, i)
{
coeff1 += sqr(log(dNew[i]) - logD);
}
Henry Weller
committed
// Magnitude of the normal velocity of the first splashed parcel
sqrt(2.0*parcelsPerSplash_*EKs/mSplash/(1.0 + coeff1/sqr(coeff2)));
// Set splashed parcel properties
forAll(dNew, i)
{
const vector dirVec = splashDirection(tanVec1, tanVec2, -nf);
// Create a new parcel by copying source parcel
parcelType* pPtr = new parcelType(p);
pPtr->origId() = pPtr->getNewParticleID();
pPtr->origProc() = Pstream::myProcNo();
if (splashParcelType_ >= 0)
{
pPtr->typeId() = splashParcelType_;
}
Henry Weller
committed
// Perturb new parcels towards the owner cell centre
pPtr->track(0.5*rndGen_.sample01<scalar>()*(posC - posCf), 0);
pPtr->nParticle() = npNew[i];
pPtr->d() = dNew[i];
pPtr->U() = dirVec*(mag(Cf_*Ut) + magUns0*(log(dNew[i]) - logD)/coeff2);
// Apply correction to velocity for 2-D cases
meshTools::constrainDirection(mesh, mesh.solutionD(), pPtr->U());
// Add the new parcel
this->owner().addParticle(pPtr);
nParcelsSplashed_++;
}
Henry Weller
committed
// Transfer remaining part of parcel to film 0 - splashMass can be -ve
// if entraining from the film
const scalar mDash = m - mSplash;
absorbInteraction<filmType>
(filmModel, p, pp, facei, mDash, keepParticle);
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template<class CloudType>
Foam::ThermoSurfaceFilm<CloudType>::ThermoSurfaceFilm
(
const dictionary& dict,
SurfaceFilmModel<CloudType>(dict, owner, typeName),
thermo_
(
owner.db().objectRegistry::template lookupObject<SLGThermo>("SLGThermo")
),
TFilmPatch_(0),
CpFilmPatch_(0),
interactionType_
(
interactionTypeEnum(this->coeffDict().getWord("interactionType"))
),
deltaWet_(0.0),
splashParcelType_(0),
parcelsPerSplash_(0),
Adry_(0.0),
Awet_(0.0),
Cf_(0.0),
nParcelsSplashed_(0)
{
Info<< " Applying " << interactionTypeStr(interactionType_)
<< " interaction model" << endl;
if (interactionType_ == itSplashBai)
{
this->coeffDict().readEntry("deltaWet", deltaWet_);
this->coeffDict().getOrDefault("splashParcelType", -1);
this->coeffDict().getOrDefault("parcelsPerSplash", 2);
this->coeffDict().readEntry("Adry", Adry_);
this->coeffDict().readEntry("Awet", Awet_);
this->coeffDict().readEntry("Cf", Cf_);
template<class CloudType>
Foam::ThermoSurfaceFilm<CloudType>::ThermoSurfaceFilm
(
const ThermoSurfaceFilm<CloudType>& sfm
)
:
SurfaceFilmModel<CloudType>(sfm),
rndGen_(sfm.rndGen_),
thermo_(sfm.thermo_),
TFilmPatch_(sfm.TFilmPatch_),
CpFilmPatch_(sfm.CpFilmPatch_),
interactionType_(sfm.interactionType_),
deltaWet_(sfm.deltaWet_),
splashParcelType_(sfm.splashParcelType_),
parcelsPerSplash_(sfm.parcelsPerSplash_),
Adry_(sfm.Adry_),
Awet_(sfm.Awet_),
Cf_(sfm.Cf_),
nParcelsSplashed_(sfm.nParcelsSplashed_)
// * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
template<class CloudType>
Foam::ThermoSurfaceFilm<CloudType>::~ThermoSurfaceFilm()
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
template<class CloudType>
bool Foam::ThermoSurfaceFilm<CloudType>::transferParcel
(
parcelType& p,
const polyPatch& pp,
bool& keepParticle
const label patchi = pp.index();
// Check the singleLayer film models
if (filmModel_ && filmModel_->isRegionPatch(patchi))
const label facei = pp.whichFace(p.face());
switch (interactionType_)
bounceInteraction(p, pp, facei, keepParticle);
break;
}
case itAbsorb:
{
const scalar m = p.nParticle()*p.mass();
absorbInteraction<regionFilm>
(*filmModel_, p, pp, facei, m, keepParticle);
break;
}
case itSplashBai:
{
bool dry = this->deltaFilmPatch_[patchi][facei] < deltaWet_;
drySplashInteraction<regionFilm>
(*filmModel_, p, pp, facei, keepParticle);
wetSplashInteraction<regionFilm>
(*filmModel_, p, pp, facei, keepParticle);
}
break;
}
default:
{
FatalErrorInFunction
<< "Unknown interaction type enumeration"
<< abort(FatalError);
}
Henry Weller
committed
// Transfer parcel/parcel interactions complete
for (areaFilm& film : areaFilms_)
{
if (patchi == film.patchID())
const label facei = pp.whichFace(p.face());
switch (interactionType_)
// It only supports absorp model
case itAbsorb:
{
const scalar m = p.nParticle()*p.mass();
absorbInteraction<areaFilm>
(
film, p, pp, facei, m, keepParticle
);
break;
}
case itBounce:
{
bounceInteraction(p, pp, facei, keepParticle);
break;
}
case itSplashBai:
bool dry = film.h()[facei] < deltaWet_;
if (dry)
drySplashInteraction<areaFilm>
(film, p, pp, facei, keepParticle);
wetSplashInteraction<areaFilm>
(film, p, pp, facei, keepParticle);
break;
}
default:
{
FatalErrorInFunction
<< "Unknown interaction type enumeration"
<< abort(FatalError);
// Transfer parcel/parcel interactions complete
bInteraction = true;
Henry Weller
committed
// Parcel not interacting with film
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::cacheFilmFields
(
const label filmPatchi,
const label primaryPatchi,
const regionModels::surfaceFilmModels::surfaceFilmRegionModel& filmModel
)
{
SurfaceFilmModel<CloudType>::cacheFilmFields
(
filmPatchi,
primaryPatchi,
filmModel
);
TFilmPatch_ = filmModel.Ts().boundaryField()[filmPatchi];
filmModel.toPrimary(filmPatchi, TFilmPatch_);
CpFilmPatch_ = filmModel.Cp().boundaryField()[filmPatchi];
filmModel.toPrimary(filmPatchi, CpFilmPatch_);
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::cacheFilmFields
(
const label filmPatchi,
const areaFilm& filmModel
)
{
SurfaceFilmModel<CloudType>::cacheFilmFields
(
filmPatchi,
filmModel
);
const volSurfaceMapping& map = filmModel.region().vsm();
TFilmPatch_.setSize(filmModel.Tf().size(), Zero);
map.mapToField(filmModel.Tf(), TFilmPatch_);
CpFilmPatch_.setSize(filmModel.Tf().size(), Zero);
map.mapToField(filmModel.Cp(), CpFilmPatch_);
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::setParcelProperties
(
parcelType& p,
const label filmFacei
SurfaceFilmModel<CloudType>::setParcelProperties(p, filmFacei);
// Set parcel properties
p.T() = TFilmPatch_[filmFacei];
p.Cp() = CpFilmPatch_[filmFacei];
}
template<class CloudType>
void Foam::ThermoSurfaceFilm<CloudType>::info(Ostream& os)
SurfaceFilmModel<CloudType>::info(os);
label nSplash0 = this->template getModelProperty<label>("nParcelsSplashed");
label nSplashTotal =
nSplash0 + returnReduce(nParcelsSplashed_, sumOp<label>());
os << " - new splash parcels = " << nSplashTotal << endl;
Henry Weller
committed
if (this->writeTime())
{
this->setModelProperty("nParcelsSplashed", nSplashTotal);
nParcelsSplashed_ = 0;
}