/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2018 OpenFOAM Foundation
Copyright (C) 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 .
\*---------------------------------------------------------------------------*/
#include "multiphaseSystem.H"
#include "alphaContactAngleFvPatchScalarField.H"
#include "fixedValueFvsPatchFields.H"
#include "Time.H"
#include "subCycle.H"
#include "MULES.H"
#include "surfaceInterpolate.H"
#include "fvcGrad.H"
#include "fvcSnGrad.H"
#include "fvcDiv.H"
#include "fvcFlux.H"
#include "fvcAverage.H"
#include "unitConversion.H"
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
void Foam::multiphaseSystem::calcAlphas()
{
scalar level = 0.0;
alphas_ == 0.0;
for (const phaseModel& phase : phases_)
{
alphas_ += level * phase;
level += 1.0;
}
}
void Foam::multiphaseSystem::solveAlphas()
{
PtrList alphaPhiCorrs(phases_.size());
int phasei = 0;
for (phaseModel& phase : phases_)
{
volScalarField& alpha1 = phase;
alphaPhiCorrs.set
(
phasei,
new surfaceScalarField
(
"phi" + alpha1.name() + "Corr",
fvc::flux
(
phi_,
phase,
"div(phi," + alpha1.name() + ')'
)
)
);
surfaceScalarField& alphaPhiCorr = alphaPhiCorrs[phasei];
for (phaseModel& phase2 : phases_)
{
volScalarField& alpha2 = phase2;
if (&phase2 == &phase) continue;
surfaceScalarField phir(phase.phi() - phase2.phi());
const auto cAlpha = cAlphas_.cfind(interfacePair(phase, phase2));
if (cAlpha.found())
{
surfaceScalarField phic
(
(mag(phi_) + mag(phir))/mesh_.magSf()
);
phir += min(cAlpha()*phic, max(phic))*nHatf(phase, phase2);
}
const word phirScheme
(
"div(phir," + alpha2.name() + ',' + alpha1.name() + ')'
);
alphaPhiCorr += fvc::flux
(
-fvc::flux(-phir, phase2, phirScheme),
phase,
phirScheme
);
}
phase.correctInflowOutflow(alphaPhiCorr);
MULES::limit
(
1.0/mesh_.time().deltaT().value(),
geometricOneField(),
phase,
phi_,
alphaPhiCorr,
zeroField(),
zeroField(),
oneField(),
zeroField(),
true
);
++phasei;
}
MULES::limitSum(alphaPhiCorrs);
volScalarField sumAlpha
(
IOobject
(
"sumAlpha",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedScalar("sumAlpha", dimless, 0)
);
phasei = 0;
for (phaseModel& phase : phases_)
{
surfaceScalarField& alphaPhi = alphaPhiCorrs[phasei];
alphaPhi += upwind(mesh_, phi_).flux(phase);
phase.correctInflowOutflow(alphaPhi);
MULES::explicitSolve
(
geometricOneField(),
phase,
alphaPhi
);
phase.alphaPhi() = alphaPhi;
Info<< phase.name() << " volume fraction, min, max = "
<< phase.weightedAverage(mesh_.V()).value()
<< ' ' << min(phase).value()
<< ' ' << max(phase).value()
<< endl;
sumAlpha += phase;
++phasei;
}
Info<< "Phase-sum volume fraction, min, max = "
<< sumAlpha.weightedAverage(mesh_.V()).value()
<< ' ' << min(sumAlpha).value()
<< ' ' << max(sumAlpha).value()
<< endl;
// Correct the sum of the phase-fractions to avoid 'drift'
volScalarField sumCorr(1.0 - sumAlpha);
for (phaseModel& phase : phases_)
{
volScalarField& alpha = phase;
alpha += alpha*sumCorr;
}
calcAlphas();
}
Foam::tmp Foam::multiphaseSystem::nHatfv
(
const volScalarField& alpha1,
const volScalarField& alpha2
) const
{
/*
// Cell gradient of alpha
volVectorField gradAlpha =
alpha2*fvc::grad(alpha1) - alpha1*fvc::grad(alpha2);
// Interpolated face-gradient of alpha
surfaceVectorField gradAlphaf = fvc::interpolate(gradAlpha);
*/
surfaceVectorField gradAlphaf
(
fvc::interpolate(alpha2)*fvc::interpolate(fvc::grad(alpha1))
- fvc::interpolate(alpha1)*fvc::interpolate(fvc::grad(alpha2))
);
// Face unit interface normal
return gradAlphaf/(mag(gradAlphaf) + deltaN_);
}
Foam::tmp Foam::multiphaseSystem::nHatf
(
const volScalarField& alpha1,
const volScalarField& alpha2
) const
{
// Face unit interface normal flux
return nHatfv(alpha1, alpha2) & mesh_.Sf();
}
// Correction for the boundary condition on the unit normal nHat on
// walls to produce the correct contact angle.
// The dynamic contact angle is calculated from the component of the
// velocity on the direction of the interface, parallel to the wall.
void Foam::multiphaseSystem::correctContactAngle
(
const phaseModel& phase1,
const phaseModel& phase2,
surfaceVectorField::Boundary& nHatb
) const
{
const volScalarField::Boundary& gbf
= phase1.boundaryField();
const fvBoundaryMesh& boundary = mesh_.boundary();
forAll(boundary, patchi)
{
if (isA(gbf[patchi]))
{
const alphaContactAngleFvPatchScalarField& acap =
refCast(gbf[patchi]);
vectorField& nHatPatch = nHatb[patchi];
vectorField AfHatPatch
(
mesh_.Sf().boundaryField()[patchi]
/mesh_.magSf().boundaryField()[patchi]
);
const auto tp =
acap.thetaProps().cfind(interfacePair(phase1, phase2));
if (!tp.found())
{
FatalErrorInFunction
<< "Cannot find interface " << interfacePair(phase1, phase2)
<< "\n in table of theta properties for patch "
<< acap.patch().name()
<< exit(FatalError);
}
bool matched = (tp.key().first() == phase1.name());
const scalar theta0 = degToRad(tp().theta0(matched));
scalarField theta(boundary[patchi].size(), theta0);
scalar uTheta = tp().uTheta();
// Calculate the dynamic contact angle if required
if (uTheta > SMALL)
{
const scalar thetaA = degToRad(tp().thetaA(matched));
const scalar thetaR = degToRad(tp().thetaR(matched));
// Calculated the component of the velocity parallel to the wall
vectorField Uwall
(
phase1.U().boundaryField()[patchi].patchInternalField()
- phase1.U().boundaryField()[patchi]
);
Uwall -= (AfHatPatch & Uwall)*AfHatPatch;
// Find the direction of the interface parallel to the wall
vectorField nWall
(
nHatPatch - (AfHatPatch & nHatPatch)*AfHatPatch
);
// Normalise nWall
nWall /= (mag(nWall) + SMALL);
// Calculate Uwall resolved normal to the interface parallel to
// the interface
scalarField uwall(nWall & Uwall);
theta += (thetaA - thetaR)*tanh(uwall/uTheta);
}
// Reset nHatPatch to correspond to the contact angle
scalarField a12(nHatPatch & AfHatPatch);
scalarField b1(cos(theta));
scalarField b2(nHatPatch.size());
forAll(b2, facei)
{
b2[facei] = cos(acos(a12[facei]) - theta[facei]);
}
scalarField det(1.0 - a12*a12);
scalarField a((b1 - a12*b2)/det);
scalarField b((b2 - a12*b1)/det);
nHatPatch = a*AfHatPatch + b*nHatPatch;
nHatPatch /= (mag(nHatPatch) + deltaN_.value());
}
}
}
Foam::tmp Foam::multiphaseSystem::K
(
const phaseModel& phase1,
const phaseModel& phase2
) const
{
tmp tnHatfv = nHatfv(phase1, phase2);
correctContactAngle(phase1, phase2, tnHatfv.ref().boundaryFieldRef());
// Simple expression for curvature
return -fvc::div(tnHatfv & mesh_.Sf());
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::multiphaseSystem::multiphaseSystem
(
const volVectorField& U,
const surfaceScalarField& phi
)
:
IOdictionary
(
IOobject
(
"transportProperties",
U.time().constant(),
U.db(),
IOobject::MUST_READ_IF_MODIFIED,
IOobject::NO_WRITE
)
),
phases_(lookup("phases"), phaseModel::iNew(U.mesh())),
mesh_(U.mesh()),
phi_(phi),
alphas_
(
IOobject
(
"alphas",
mesh_.time().timeName(),
mesh_,
IOobject::NO_READ,
IOobject::AUTO_WRITE
),
mesh_,
dimensionedScalar("alphas", dimless, 0.0)
),
sigmas_(lookup("sigmas")),
dimSigma_(1, 0, -2, 0, 0),
cAlphas_(lookup("interfaceCompression")),
Cvms_(lookup("virtualMass")),
deltaN_
(
"deltaN",
1e-8/cbrt(average(mesh_.V()))
)
{
calcAlphas();
alphas_.write();
interfaceDictTable dragModelsDict(lookup("drag"));
forAllConstIters(dragModelsDict, iter)
{
dragModels_.set
(
iter.key(),
dragModel::New
(
iter(),
*phases_.lookup(iter.key().first()),
*phases_.lookup(iter.key().second())
).ptr()
);
}
for (const phaseModel& phase1 : phases_)
{
for (const phaseModel& phase2 : phases_)
{
if (&phase2 == &phase1)
{
continue;
}
const interfacePair key(phase1, phase2);
if (sigmas_.found(key) && !cAlphas_.found(key))
{
WarningInFunction
<< "Compression coefficient not specified for phase pair ("
<< phase1.name() << ' ' << phase2.name()
<< ") for which a surface tension coefficient is specified"
<< endl;
}
}
}
}
// * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
Foam::tmp Foam::multiphaseSystem::rho() const
{
auto iter = phases_.cbegin();
tmp trho = iter()*iter().rho();
volScalarField& rho = trho.ref();
for (++iter; iter != phases_.cend(); ++iter)
{
rho += iter()*iter().rho();
}
return trho;
}
Foam::tmp
Foam::multiphaseSystem::rho(const label patchi) const
{
auto iter = phases_.cbegin();
tmp trho = iter().boundaryField()[patchi]*iter().rho().value();
scalarField& rho = trho.ref();
for (++iter; iter != phases_.cend(); ++iter)
{
rho += iter().boundaryField()[patchi]*iter().rho().value();
}
return trho;
}
Foam::tmp Foam::multiphaseSystem::nu() const
{
auto iter = phases_.cbegin();
tmp tmu = iter()*(iter().rho()*iter().nu());
volScalarField& mu = tmu.ref();
for (++iter; iter != phases_.cend(); ++iter)
{
mu += iter()*(iter().rho()*iter().nu());
}
return tmu/rho();
}
Foam::tmp
Foam::multiphaseSystem::nu(const label patchi) const
{
auto iter = phases_.cbegin();
tmp tmu =
iter().boundaryField()[patchi]
*(iter().rho().value()*iter().nu().value());
scalarField& mu = tmu.ref();
for (++iter; iter != phases_.cend(); ++iter)
{
mu +=
iter().boundaryField()[patchi]
*(iter().rho().value()*iter().nu().value());
}
return tmu/rho(patchi);
}
Foam::tmp Foam::multiphaseSystem::Cvm
(
const phaseModel& phase
) const
{
tmp tCvm
(
new volScalarField
(
IOobject
(
"Cvm",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedScalar(dimDensity, Zero)
)
);
for (const phaseModel& phase2 : phases_)
{
if (&phase2 == &phase)
{
continue;
}
auto iterCvm = Cvms_.cfind(interfacePair(phase, phase2));
if (iterCvm.found())
{
tCvm.ref() += iterCvm()*phase2.rho()*phase2;
}
else
{
iterCvm = Cvms_.cfind(interfacePair(phase2, phase));
if (iterCvm.found())
{
tCvm.ref() += iterCvm()*phase.rho()*phase2;
}
}
}
return tCvm;
}
Foam::tmp Foam::multiphaseSystem::Svm
(
const phaseModel& phase
) const
{
tmp tSvm
(
new volVectorField
(
IOobject
(
"Svm",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedVector
(
"Svm",
dimensionSet(1, -2, -2, 0, 0),
Zero
)
)
);
for (const phaseModel& phase2 : phases_)
{
if (&phase2 == &phase)
{
continue;
}
auto Cvm = Cvms_.cfind(interfacePair(phase, phase2));
if (Cvm.found())
{
tSvm.ref() += Cvm()*phase2.rho()*phase2*phase2.DDtU();
}
else
{
Cvm = Cvms_.cfind(interfacePair(phase2, phase));
if (Cvm.found())
{
tSvm.ref() += Cvm()*phase.rho()*phase2*phase2.DDtU();
}
}
}
volVectorField::Boundary& SvmBf =
tSvm.ref().boundaryFieldRef();
// Remove virtual mass at fixed-flux boundaries
forAll(phase.phi().boundaryField(), patchi)
{
if
(
isA
(
phase.phi().boundaryField()[patchi]
)
)
{
SvmBf[patchi] = Zero;
}
}
return tSvm;
}
Foam::autoPtr
Foam::multiphaseSystem::dragCoeffs() const
{
autoPtr dragCoeffsPtr(new dragCoeffFields);
forAllConstIters(dragModels_, iter)
{
const dragModel& dm = *iter();
volScalarField* Kptr =
(
max
(
// fvc::average(dm.phase1()*dm.phase2()),
// fvc::average(dm.phase1())*fvc::average(dm.phase2()),
dm.phase1()*dm.phase2(),
dm.residualPhaseFraction()
)
*dm.K
(
max
(
mag(dm.phase1().U() - dm.phase2().U()),
dm.residualSlip()
)
)
).ptr();
volScalarField::Boundary& Kbf = Kptr->boundaryFieldRef();
// Remove drag at fixed-flux boundaries
forAll(dm.phase1().phi().boundaryField(), patchi)
{
if
(
isA
(
dm.phase1().phi().boundaryField()[patchi]
)
)
{
Kbf[patchi] = 0.0;
}
}
dragCoeffsPtr().set(iter.key(), Kptr);
}
return dragCoeffsPtr;
}
Foam::tmp Foam::multiphaseSystem::dragCoeff
(
const phaseModel& phase,
const dragCoeffFields& dragCoeffs
) const
{
tmp tdragCoeff
(
new volScalarField
(
IOobject
(
"dragCoeff",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedScalar
(
"dragCoeff",
dimensionSet(1, -3, -1, 0, 0),
0
)
)
);
dragModelTable::const_iterator dmIter = dragModels_.begin();
dragCoeffFields::const_iterator dcIter = dragCoeffs.begin();
for
(
;
dmIter.good() && dcIter.good();
++dmIter, ++dcIter
)
{
if
(
&phase == &dmIter()->phase1()
|| &phase == &dmIter()->phase2()
)
{
tdragCoeff.ref() += *dcIter();
}
}
return tdragCoeff;
}
Foam::tmp Foam::multiphaseSystem::surfaceTension
(
const phaseModel& phase1
) const
{
tmp tSurfaceTension
(
new surfaceScalarField
(
IOobject
(
"surfaceTension",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedScalar
(
"surfaceTension",
dimensionSet(1, -2, -2, 0, 0),
0
)
)
);
tSurfaceTension.ref().setOriented();
for (const phaseModel& phase2 : phases_)
{
if (&phase2 == &phase1)
{
continue;
}
const auto sigma = sigmas_.cfind(interfacePair(phase1, phase2));
if (sigma.found())
{
tSurfaceTension.ref() +=
dimensionedScalar("sigma", dimSigma_, *sigma)
*fvc::interpolate(K(phase1, phase2))*
(
fvc::interpolate(phase2)*fvc::snGrad(phase1)
- fvc::interpolate(phase1)*fvc::snGrad(phase2)
);
}
}
return tSurfaceTension;
}
Foam::tmp
Foam::multiphaseSystem::nearInterface() const
{
tmp tnearInt
(
new volScalarField
(
IOobject
(
"nearInterface",
mesh_.time().timeName(),
mesh_
),
mesh_,
dimensionedScalar("nearInterface", dimless, 0.0)
)
);
for (const phaseModel& phase : phases_)
{
tnearInt.ref() =
max(tnearInt(), pos0(phase - 0.01)*pos0(0.99 - phase));
}
return tnearInt;
}
void Foam::multiphaseSystem::solve()
{
for (phaseModel& phase : phases_)
{
phase.correct();
}
const Time& runTime = mesh_.time();
const dictionary& alphaControls = mesh_.solverDict("alpha");
label nAlphaSubCycles(alphaControls.get