/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2021-2023 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 "KinematicSurfaceFilm.H"
#include "surfaceFilmRegionModel.H"
#include "liquidFilmModel.H"
#include "addToRunTimeSelectionTable.H"
#include "unitConversion.H"
#include "Pstream.H"
using namespace Foam::constant::mathematical;
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
template
const Foam::Enum
<
typename Foam::KinematicSurfaceFilm::interactionType
>
Foam::KinematicSurfaceFilm::interactionTypeNames
({
{ interactionType::absorb, "absorb" },
{ interactionType::bounce, "bounce" },
{ interactionType::splashBai, "splashBai" },
});
// * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
template
Foam::vector Foam::KinematicSurfaceFilm::tangentVector
(
const vector& v
) const
{
vector tangent(Zero);
scalar magTangent = 0.0;
while (magTangent < SMALL)
{
const vector vTest(rndGen_.sample01());
tangent = vTest - (vTest & v)*v;
magTangent = mag(tangent);
}
return tangent/magTangent;
}
template
Foam::vector Foam::KinematicSurfaceFilm::splashDirection
(
const vector& tanVec1,
const vector& tanVec2,
const vector& nf
) const
{
// Azimuthal angle [rad]
const scalar phiSi = twoPi*rndGen_.sample01();
// Ejection angle [rad]
const scalar thetaSi = degToRad(rndGen_.sample01()*(50 - 5) + 5);
// 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
void Foam::KinematicSurfaceFilm::initFilmModels()
{
const fvMesh& mesh = this->owner().mesh();
// Set up region film
if (!filmModel_)
{
filmModel_ =
mesh.time().objectRegistry::template getObjectPtr
(
"surfaceFilmProperties"
);
}
// Set up area films
if (areaFilms_.empty())
{
for
(
const areaFilm& regionFa
: mesh.time().objectRegistry::template csorted()
)
{
areaFilms_.push_back(const_cast(®ionFa));
}
}
}
template
void Foam::KinematicSurfaceFilm::init(bool binitThermo)
{
if (binitThermo)
{
this->coeffDict().readEntry("pRef", pRef_);
this->coeffDict().readEntry("TRef", TRef_);
thermo_ = new liquidMixtureProperties(this->coeffDict().subDict("thermo"));
}
}
template
template
void Foam::KinematicSurfaceFilm::absorbInteraction
(
filmType& film,
const parcelType& p,
const polyPatch& pp,
const label facei,
const scalar mass,
bool& keepParticle
)
{
DebugInfo<< "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);
film.addSources
(
pp.index(),
facei,
mass, // mass
mass*Ut, // tangential momentum
mass*mag(Un), // impingement pressure
0 // energy
);
this->nParcelsTransferred()++;
this->totalMassTransferred() += mass;
keepParticle = false;
}
template
void Foam::KinematicSurfaceFilm::bounceInteraction
(
parcelType& p,
const polyPatch& pp,
const label facei,
bool& keepParticle
) const
{
DebugInfo<< "Parcel " << p.origId() << " bounceInteraction" << 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);
// Flip parcel normal velocity component
p.U() -= 2.0*nf*(Urel & nf);
keepParticle = true;
}
template
template
void Foam::KinematicSurfaceFilm::drySplashInteraction
(
filmType& filmModel,
const scalar sigma,
const scalar mu,
const parcelType& p,
const polyPatch& pp,
const label facei,
bool& keepParticle
)
{
DebugInfo<< "Parcel " << p.origId() << " drySplashInteraction" << endl;
// Patch face velocity and normal
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
const vector& nf = pp.faceNormals()[facei];
// 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 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);
if (We < Wec) // Adhesion - assume absorb
{
absorbInteraction
(filmModel, p, pp, facei, m, keepParticle);
}
else // Splash
{
// Ratio of incident mass to splashing mass
const scalar mRatio = 0.2 + 0.6*rndGen_.sample01();
splashInteraction
(filmModel, p, pp, facei, mRatio, We, Wec, sigma, keepParticle);
}
}
template
template
void Foam::KinematicSurfaceFilm::wetSplashInteraction
(
filmType& filmModel,
const scalar sigma,
const scalar mu,
parcelType& p,
const polyPatch& pp,
const label facei,
bool& keepParticle
)
{
DebugInfo<< "Parcel " << p.origId() << " wetSplashInteraction" << endl;
// Patch face velocity and normal
const vector& Up = this->owner().U().boundaryField()[pp.index()][facei];
const vector& nf = pp.faceNormals()[facei];
// 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 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);
if (We < 2) // Adhesion - assume absorb
{
absorbInteraction
(filmModel, p, pp, facei, m, keepParticle);
}
else if ((We >= 2) && (We < 20)) // Bounce
{
// Incident angle of impingement
const scalar theta = piByTwo - acos(U/mag(U) & nf);
// Restitution coefficient
const scalar epsilon = 0.993 - theta*(1.76 - theta*(1.56 - theta*0.49));
// Update parcel velocity
U = -epsilon*(Un) + 5.0/7.0*(Ut);
keepParticle = true;
return;
}
else if ((We >= 20) && (We < Wec)) // Spread - assume absorb
{
absorbInteraction
(filmModel, p, pp, facei, m, keepParticle);
}
else // Splash
{
// 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();
splashInteraction
(filmModel, p, pp, facei, mRatio, We, Wec, sigma, keepParticle);
}
}
template
template
void Foam::KinematicSurfaceFilm::splashInteraction
(
filmType& filmModel,
const parcelType& p,
const polyPatch& pp,
const label facei,
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];
// Total mass of (all) splashed parcels
const scalar mSplash = m*mRatio;
// Number of splashed particles per incoming particle
const scalar Ns = 5.0*(We/Wec - 1.0);
// Average diameter of splashed particles
const scalar dBarSplash = 1/cbrt(6.0)*cbrt(mRatio/Ns)*d + ROOTVSMALL;
// Cumulative diameter splash distribution
const scalar dMax = dMaxSplash_ > 0 ? dMaxSplash_ : 0.9*cbrt(mRatio)*d;
const scalar dMin = dMinSplash_ > 0 ? dMinSplash_ : 0.1*dMax;
const scalar K = exp(-dMin/dBarSplash) - exp(-dMax/dBarSplash);
// Surface energy of secondary parcels [J]
scalar ESigmaSec = 0;
// Sample splash distribution to determine secondary parcel diameters
scalarList dNew(parcelsPerSplash_);
scalarList npNew(parcelsPerSplash_);
forAll(dNew, i)
{
const scalar y = rndGen_.sample01();
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]);
}
// Incident kinetic energy [J]
const scalar EKIn = 0.5*m*magSqr(Un);
// Incident surface energy [J]
const scalar ESigmaIn = np*sigma*p.areaS(d);
// Dissipative energy
const scalar Ed = max(0.8*EKIn, np*Wec/12*pi*sigma*sqr(d));
// Total energy [J]
const scalar EKs = EKIn + ESigmaIn - ESigmaSec - Ed;
// Switch to absorb if insufficient energy for splash
if (EKs <= 0)
{
absorbInteraction
(filmModel, p, pp, facei, m, keepParticle);
return;
}
// Helper variables to calculate magUns0
const scalar logD = log(d);
const scalar coeff2 = log(dNew[0]) - logD + ROOTVSMALL;
scalar coeff1 = 0.0;
// Note: loop from i = 1 to (p-1)
for (int i = 1; i < parcelsPerSplash_; ++i)
{
coeff1 += sqr(log(dNew[i]) - logD);
}
// Magnitude of the normal velocity of the first splashed parcel
const scalar magUns0 =
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_;
}
// Perturb new parcels towards the owner cell centre
pPtr->track(0.5*rndGen_.sample01()*(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_++;
}
// Transfer remaining part of parcel to film 0 - splashMass can be -ve
// if entraining from the film
const scalar mDash = m - mSplash;
absorbInteraction
(filmModel, p, pp, facei, mDash, keepParticle);
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
template
Foam::KinematicSurfaceFilm::KinematicSurfaceFilm
(
const dictionary& dict,
CloudType& owner,
const word& type,
bool initThermo
)
:
SurfaceFilmModel(dict, owner, type),
rndGen_(owner.rndGen()),
thermo_(nullptr),
filmModel_(nullptr),
areaFilms_(),
interactionType_
(
interactionTypeNames.get("interactionType", this->coeffDict())
),
parcelTypes_(this->coeffDict().getOrDefault("parcelTypes", labelList())),
deltaWet_(Zero),
splashParcelType_(0),
parcelsPerSplash_(0),
dMaxSplash_(-1),
dMinSplash_(-1),
Adry_(Zero),
Awet_(Zero),
Cf_(Zero),
nParcelsSplashed_(0)
{
Info<< " Applying " << interactionTypeNames[interactionType_]
<< " interaction model" << endl;
if (interactionType_ == interactionType::splashBai)
{
this->coeffDict().readEntry("deltaWet", deltaWet_);
splashParcelType_ =
this->coeffDict().getOrDefault("splashParcelType", -1);
parcelsPerSplash_ =
this->coeffDict().getOrDefault("parcelsPerSplash", 2);
dMinSplash_ = this->coeffDict().getOrDefault("dMinSplash", -1);
dMaxSplash_ = this->coeffDict().getOrDefault("dMaxSplash", -1);
this->coeffDict().readEntry("Adry", Adry_);
this->coeffDict().readEntry("Awet", Awet_);
this->coeffDict().readEntry("Cf", Cf_);
init(initThermo);
}
}
template
Foam::KinematicSurfaceFilm::KinematicSurfaceFilm
(
const KinematicSurfaceFilm& sfm,
bool initThermo
)
:
SurfaceFilmModel(sfm),
rndGen_(sfm.rndGen_),
thermo_(nullptr),
filmModel_(nullptr),
areaFilms_(),
interactionType_(sfm.interactionType_),
parcelTypes_(sfm.parcelTypes_),
deltaWet_(sfm.deltaWet_),
splashParcelType_(sfm.splashParcelType_),
parcelsPerSplash_(sfm.parcelsPerSplash_),
dMaxSplash_(sfm.dMaxSplash_),
dMinSplash_(sfm.dMinSplash_),
Adry_(sfm.Adry_),
Awet_(sfm.Awet_),
Cf_(sfm.Cf_),
nParcelsSplashed_(sfm.nParcelsSplashed_)
{
if (interactionType_ == interactionType::splashBai)
{
init(initThermo);
}
}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
template
bool Foam::KinematicSurfaceFilm::transferParcel
(
parcelType& p,
const polyPatch& pp,
bool& keepParticle
)
{
if (parcelTypes_.size() && parcelTypes_.find(p.typeId()) == -1)
{
if (debug)
{
Pout<< "transferParcel: ignoring particle with typeId="
<< p.typeId()
<< endl;
}
return false;
}
const label patchi = pp.index();
const label meshFacei = p.face();
const label facei = pp.whichFace(meshFacei);
initFilmModels();
// Check the singleLayer film models
if (this->filmModel_ && this->filmModel_->isRegionPatch(patchi))
{
auto& film = *filmModel_;
switch (interactionType_)
{
case interactionType::bounce:
{
bounceInteraction(p, pp, facei, keepParticle);
break;
}
case interactionType::absorb:
{
const scalar m = p.nParticle()*p.mass();
absorbInteraction
(film, p, pp, facei, m, keepParticle);
break;
}
case interactionType::splashBai:
{
const scalarField X(thermo_->size(), 1);
const scalar mu = thermo_->mu(pRef_, TRef_, X);
const scalar sigma = thermo_->sigma(pRef_, TRef_, X);
const bool dry
(
this->deltaFilmPatch_[patchi][facei] < deltaWet_
);
if (dry)
{
drySplashInteraction
(film, sigma, mu, p, pp, facei, keepParticle);
}
else
{
wetSplashInteraction
(film, sigma, mu, p, pp, facei, keepParticle);
}
break;
}
default:
{
FatalErrorInFunction
<< "Unknown interaction type enumeration"
<< abort(FatalError);
}
}
// Transfer parcel/parcel interactions complete
return true;
}
// Check the area film models
for (areaFilm& film : this->areaFilms_)
{
const label filmFacei
(
film.isRegionPatch(patchi)
? film.regionMesh().whichFace(meshFacei)
: -1
);
if (filmFacei < 0)
{
// Film model does not include this patch face
continue;
}
switch (interactionType_)
{
case interactionType::bounce:
{
bounceInteraction(p, pp, facei, keepParticle);
break;
}
case interactionType::absorb:
{
const scalar m = p.nParticle()*p.mass();
absorbInteraction
(
film, p, pp, facei, m, keepParticle
);
break;
}
case interactionType::splashBai:
{
auto& liqFilm =
refCast
(
film
);
const scalarField X(liqFilm.thermo().size(), 1);
const scalar pRef = film.pRef();
const scalar TRef = liqFilm.Tref();
const scalar mu = liqFilm.thermo().mu(pRef, TRef, X);
const scalar sigma = liqFilm.thermo().sigma(pRef, TRef, X);
const bool dry = film.h()[filmFacei] < this->deltaWet_;
if (dry)
{
drySplashInteraction
(film, sigma, mu, p, pp, facei, keepParticle);
}
else
{
wetSplashInteraction
(film, sigma, mu, p, pp, facei, keepParticle);
}
break;
}
default:
{
FatalErrorInFunction
<< "Unknown interaction type enumeration"
<< abort(FatalError);
}
}
// Transfer parcel/parcel interactions complete
return true;
}
// Parcel not interacting with film
return false;
}
template
void Foam::KinematicSurfaceFilm::cacheFilmFields
(
const label filmPatchi,
const label primaryPatchi,
const regionModels::surfaceFilmModels::surfaceFilmRegionModel& filmModel
)
{
SurfaceFilmModel::cacheFilmFields
(
filmPatchi,
primaryPatchi,
filmModel
);
}
template
void Foam::KinematicSurfaceFilm::cacheFilmFields
(
const areaFilm& film
)
{
SurfaceFilmModel::cacheFilmFields(film);
}
template
void Foam::KinematicSurfaceFilm::setParcelProperties
(
parcelType& p,
const label filmFacei
) const
{
SurfaceFilmModel::setParcelProperties(p, filmFacei);
}
template
void Foam::KinematicSurfaceFilm::info()
{
SurfaceFilmModel::info();
label nSplash0 = this->template getModelProperty