1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright (C) 2011 OpenFOAM Foundation
7 -------------------------------------------------------------------------------
9 This file is part of OpenFOAM.
11 OpenFOAM is free software: you can redistribute it and/or modify it
12 under the terms of the GNU General Public License as published by
13 the Free Software Foundation, either version 3 of the License, or
14 (at your option) any later version.
16 OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 You should have received a copy of the GNU General Public License
22 along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
24 \*---------------------------------------------------------------------------*/
26 #include "multiphaseMixture.H"
27 #include "alphaContactAngleFvPatchScalarField.H"
32 // * * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * //
34 const scalar Foam::multiphaseMixture::convertToRad =
35 Foam::constant::mathematical::pi/180.0;
38 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
40 void Foam::multiphaseMixture::calcAlphas()
45 forAllIter(PtrDictionary<phase>, phases_, iter)
47 alphas_ += level*iter();
51 alphas_.correctBoundaryConditions();
55 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
57 Foam::multiphaseMixture::multiphaseMixture
59 const volVectorField& U,
60 const surfaceScalarField& phi
63 transportModel(U, phi),
64 phases_(lookup("phases"), phase::iNew(U, phi)),
65 refPhase_(*phases_.lookup(word(lookup("refPhase")))),
76 mesh_.time().timeName(),
82 dimensionedScalar("rho*phi", dimMass/dimTime, 0.0)
90 mesh_.time().timeName(),
96 dimensionedScalar("alphas", dimless, 0.0),
97 zeroGradientFvPatchScalarField::typeName
100 sigmas_(lookup("sigmas")),
101 dimSigma_(1, 0, -2, 0, 0),
105 1e-8/pow(average(mesh_.V()), 1.0/3.0)
111 forAllIter(PtrDictionary<phase>, phases_, iter)
113 alphaTable_.add(iter());
118 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
120 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::rho() const
122 PtrDictionary<phase>::const_iterator iter = phases_.begin();
124 tmp<volScalarField> trho = iter()*iter().rho();
126 for (++iter; iter != phases_.end(); ++iter)
128 trho() += iter()*iter().rho();
135 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::mu() const
137 PtrDictionary<phase>::const_iterator iter = phases_.begin();
139 tmp<volScalarField> tmu = iter()*iter().rho()*iter().nu();
141 for (++iter; iter != phases_.end(); ++iter)
143 tmu() += iter()*iter().rho()*iter().nu();
150 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::muf() const
152 PtrDictionary<phase>::const_iterator iter = phases_.begin();
154 tmp<surfaceScalarField> tmuf =
155 fvc::interpolate(iter())*iter().rho()*fvc::interpolate(iter().nu());
157 for (++iter; iter != phases_.end(); ++iter)
160 fvc::interpolate(iter())*iter().rho()*fvc::interpolate(iter().nu());
167 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::nu() const
173 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nuf() const
175 return muf()/fvc::interpolate(rho());
179 Foam::tmp<Foam::surfaceScalarField>
180 Foam::multiphaseMixture::surfaceTensionForce() const
182 tmp<surfaceScalarField> tstf
184 new surfaceScalarField
188 "surfaceTensionForce",
189 mesh_.time().timeName(),
195 "surfaceTensionForce",
196 dimensionSet(1, -2, -2, 0, 0),
202 surfaceScalarField& stf = tstf();
204 forAllConstIter(PtrDictionary<phase>, phases_, iter1)
206 const phase& alpha1 = iter1();
208 PtrDictionary<phase>::const_iterator iter2 = iter1;
211 for (; iter2 != phases_.end(); ++iter2)
213 const phase& alpha2 = iter2();
215 sigmaTable::const_iterator sigma =
216 sigmas_.find(interfacePair(alpha1, alpha2));
218 if (sigma == sigmas_.end())
220 FatalErrorIn("multiphaseMixture::surfaceTensionForce() const")
221 << "Cannot find interface " << interfacePair(alpha1, alpha2)
222 << " in list of sigma values"
226 stf += dimensionedScalar("sigma", dimSigma_, sigma())
227 *fvc::interpolate(K(alpha1, alpha2))*
229 fvc::interpolate(alpha2)*fvc::snGrad(alpha1)
230 - fvc::interpolate(alpha1)*fvc::snGrad(alpha2)
239 void Foam::multiphaseMixture::solve()
241 forAllIter(PtrDictionary<phase>, phases_, iter)
246 const Time& runTime = mesh_.time();
248 const dictionary& pimpleDict = mesh_.solutionDict().subDict("PIMPLE");
250 label nAlphaSubCycles(readLabel(pimpleDict.lookup("nAlphaSubCycles")));
252 label nAlphaCorr(readLabel(pimpleDict.lookup("nAlphaCorr")));
254 bool cycleAlpha(Switch(pimpleDict.lookup("cycleAlpha")));
256 scalar cAlpha(readScalar(pimpleDict.lookup("cAlpha")));
259 volScalarField& alpha = phases_.first();
261 if (nAlphaSubCycles > 1)
263 surfaceScalarField rhoPhiSum(0.0*rhoPhi_);
264 dimensionedScalar totalDeltaT = runTime.deltaT();
268 subCycle<volScalarField> alphaSubCycle(alpha, nAlphaSubCycles);
269 !(++alphaSubCycle).end();
272 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
273 rhoPhiSum += (runTime.deltaT()/totalDeltaT)*rhoPhi_;
280 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
285 void Foam::multiphaseMixture::correct()
289 Foam::tmp<Foam::surfaceVectorField> Foam::multiphaseMixture::nHatfv
291 const volScalarField& alpha1,
292 const volScalarField& alpha2
296 // Cell gradient of alpha
297 volVectorField gradAlpha =
298 alpha2*fvc::grad(alpha1) - alpha1*fvc::grad(alpha2);
300 // Interpolated face-gradient of alpha
301 surfaceVectorField gradAlphaf = fvc::interpolate(gradAlpha);
304 surfaceVectorField gradAlphaf
306 fvc::interpolate(alpha2)*fvc::interpolate(fvc::grad(alpha1))
307 - fvc::interpolate(alpha1)*fvc::interpolate(fvc::grad(alpha2))
310 // Face unit interface normal
311 return gradAlphaf/(mag(gradAlphaf) + deltaN_);
315 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nHatf
317 const volScalarField& alpha1,
318 const volScalarField& alpha2
321 // Face unit interface normal flux
322 return nHatfv(alpha1, alpha2) & mesh_.Sf();
326 // Correction for the boundary condition on the unit normal nHat on
327 // walls to produce the correct contact angle.
329 // The dynamic contact angle is calculated from the component of the
330 // velocity on the direction of the interface, parallel to the wall.
332 void Foam::multiphaseMixture::correctContactAngle
336 surfaceVectorField::GeometricBoundaryField& nHatb
339 const volScalarField::GeometricBoundaryField& gbf
340 = refPhase_.boundaryField();
342 const fvBoundaryMesh& boundary = mesh_.boundary();
344 forAll(boundary, patchi)
346 if (isA<alphaContactAngleFvPatchScalarField>(gbf[patchi]))
348 const alphaContactAngleFvPatchScalarField& acap =
349 refCast<const alphaContactAngleFvPatchScalarField>(gbf[patchi]);
351 vectorField& nHatPatch = nHatb[patchi];
353 vectorField AfHatPatch
355 mesh_.Sf().boundaryField()[patchi]
356 /mesh_.magSf().boundaryField()[patchi]
359 alphaContactAngleFvPatchScalarField::thetaPropsTable::
361 acap.thetaProps().find(interfacePair(alpha1, alpha2));
363 if (tp == acap.thetaProps().end())
367 "multiphaseMixture::correctContactAngle"
368 "(const phase& alpha1, const phase& alpha2, "
369 "fvPatchVectorFieldField& nHatb) const"
370 ) << "Cannot find interface " << interfacePair(alpha1, alpha2)
371 << "\n in table of theta properties for patch "
372 << acap.patch().name()
376 bool matched = (tp.key().first() == alpha1.name());
378 scalar theta0 = convertToRad*tp().theta0(matched);
379 scalarField theta(boundary[patchi].size(), theta0);
381 scalar uTheta = tp().uTheta();
383 // Calculate the dynamic contact angle if required
386 scalar thetaA = convertToRad*tp().thetaA(matched);
387 scalar thetaR = convertToRad*tp().thetaR(matched);
389 // Calculated the component of the velocity parallel to the wall
392 U_.boundaryField()[patchi].patchInternalField()
393 - U_.boundaryField()[patchi]
395 Uwall -= (AfHatPatch & Uwall)*AfHatPatch;
397 // Find the direction of the interface parallel to the wall
400 nHatPatch - (AfHatPatch & nHatPatch)*AfHatPatch
404 nWall /= (mag(nWall) + SMALL);
406 // Calculate Uwall resolved normal to the interface parallel to
408 scalarField uwall(nWall & Uwall);
410 theta += (thetaA - thetaR)*tanh(uwall/uTheta);
414 // Reset nHatPatch to correspond to the contact angle
416 scalarField a12(nHatPatch & AfHatPatch);
418 scalarField b1(cos(theta));
420 scalarField b2(nHatPatch.size());
424 b2[facei] = cos(acos(a12[facei]) - theta[facei]);
427 scalarField det(1.0 - a12*a12);
429 scalarField a((b1 - a12*b2)/det);
430 scalarField b((b2 - a12*b1)/det);
432 nHatPatch = a*AfHatPatch + b*nHatPatch;
434 nHatPatch /= (mag(nHatPatch) + deltaN_.value());
440 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::K
446 tmp<surfaceVectorField> tnHatfv = nHatfv(alpha1, alpha2);
448 correctContactAngle(alpha1, alpha2, tnHatfv().boundaryField());
450 // Simple expression for curvature
451 return -fvc::div(tnHatfv & mesh_.Sf());
455 Foam::tmp<Foam::volScalarField>
456 Foam::multiphaseMixture::nearInterface() const
458 tmp<volScalarField> tnearInt
465 mesh_.time().timeName(),
469 dimensionedScalar("nearInterface", dimless, 0.0)
473 forAllConstIter(PtrDictionary<phase>, phases_, iter)
475 tnearInt() = max(tnearInt(), pos(iter() - 0.01)*pos(0.99 - iter()));
482 void Foam::multiphaseMixture::solveAlphas
484 const label nAlphaCorr,
485 const bool cycleAlpha,
489 static label nSolves=-1;
492 word alphaScheme("div(phi,alpha)");
493 word alphacScheme("div(phirb,alpha)");
495 tmp<fv::convectionScheme<scalar> > mvConvection
497 fv::convectionScheme<scalar>::New
502 mesh_.divScheme(alphaScheme)
506 surfaceScalarField phic(mag(phi_/mesh_.magSf()));
507 phic = min(cAlpha*phic, max(phic));
509 for (int gCorr=0; gCorr<nAlphaCorr; gCorr++)
511 phase* refPhasePtr = &refPhase_;
515 PtrDictionary<phase>::iterator refPhaseIter = phases_.begin();
516 for (label i=0; i<nSolves%phases_.size(); i++)
520 refPhasePtr = &refPhaseIter();
523 phase& refPhase = *refPhasePtr;
525 volScalarField refPhaseNew(refPhase);
528 rhoPhi_ = phi_*refPhase.rho();
530 forAllIter(PtrDictionary<phase>, phases_, iter)
532 phase& alpha = iter();
534 if (&alpha == &refPhase) continue;
536 fvScalarMatrix alphaEqn
539 + mvConvection->fvmDiv(phi_, alpha)
542 forAllIter(PtrDictionary<phase>, phases_, iter2)
544 phase& alpha2 = iter2();
546 if (&alpha2 == &alpha) continue;
548 surfaceScalarField phir(phic*nHatf(alpha, alpha2));
549 surfaceScalarField phirb12
551 -fvc::flux(-phir, alpha2, alphacScheme)
554 alphaEqn += fvm::div(phirb12, alpha, alphacScheme);
557 alphaEqn.solve(mesh_.solver("alpha"));
559 rhoPhi_ += alphaEqn.flux()*(alpha.rho() - refPhase.rho());
561 Info<< alpha.name() << " volume fraction, min, max = "
562 << alpha.weightedAverage(mesh_.V()).value()
563 << ' ' << min(alpha).value()
564 << ' ' << max(alpha).value()
567 refPhaseNew == refPhaseNew - alpha;
570 refPhase == refPhaseNew;
572 Info<< refPhase.name() << " volume fraction, min, max = "
573 << refPhase.weightedAverage(mesh_.V()).value()
574 << ' ' << min(refPhase).value()
575 << ' ' << max(refPhase).value()
583 bool Foam::multiphaseMixture::read()
585 if (transportModel::read())
589 PtrList<entry> phaseData(lookup("phases"));
592 forAllIter(PtrDictionary<phase>, phases_, iter)
594 readOK &= iter().read(phaseData[phasei++].dict());
597 lookup("sigmas") >> sigmas_;
608 // ************************************************************************* //