1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | foam-extend: Open Source CFD
4 \\ / O peration | Version: 3.2
5 \\ / A nd | Web: http://www.foam-extend.org
6 \\/ M anipulation | For copyright notice see file Copyright
7 -------------------------------------------------------------------------------
9 This file is part of foam-extend.
11 foam-extend is free software: you can redistribute it and/or modify it
12 under the terms of the GNU General Public License as published by the
13 Free Software Foundation, either version 3 of the License, or (at your
14 option) any later version.
16 foam-extend is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with foam-extend. If not, see <http://www.gnu.org/licenses/>.
24 \*---------------------------------------------------------------------------*/
26 #include "multiphaseMixture.H"
27 #include "multiphaseAlphaContactAngleFvPatchScalarField.H"
32 // * * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * //
34 const scalar Foam::multiphaseMixture::convertToRad =
35 Foam::mathematicalConstant::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("nu", sqr(dimLength)/dimTime, 0.0)
90 mesh_.time().timeName(),
96 dimensionedScalar("rho*phi", dimMass/dimTime, 0.0)
104 mesh_.time().timeName(),
110 dimensionedScalar("alphas", dimless, 0.0),
111 zeroGradientFvPatchScalarField::typeName
114 sigmas_(lookup("sigmas")),
115 dimSigma_(1, 0, -2, 0, 0),
119 1e-8/pow(average(mesh_.V()), 1.0/3.0)
125 forAllIter(PtrDictionary<phase>, phases_, iter)
127 alphaTable_.add(iter());
132 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
134 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::rho() const
136 PtrDictionary<phase>::const_iterator iter = phases_.begin();
138 tmp<volScalarField> trho = iter().limitedAlpha()*iter().rho();
140 for(++iter; iter != phases_.end(); ++iter)
142 trho() += iter().limitedAlpha()*iter().rho();
149 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::mu() const
151 PtrDictionary<phase>::const_iterator iter = phases_.begin();
153 tmp<volScalarField> tmu = iter().limitedAlpha()*iter().rho()*iter().nu();
155 for(++iter; iter != phases_.end(); ++iter)
157 tmu() += iter().limitedAlpha()*iter().rho()*iter().nu();
164 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::muf() const
166 PtrDictionary<phase>::const_iterator iter = phases_.begin();
168 tmp<surfaceScalarField> tmuf =
169 fvc::interpolate(iter().limitedAlpha())*iter().rho()*
170 fvc::interpolate(iter().nu());
172 for(++iter; iter != phases_.end(); ++iter)
175 fvc::interpolate(iter().limitedAlpha())*iter().rho()*
176 fvc::interpolate(iter().nu());
183 const Foam::volScalarField& Foam::multiphaseMixture::nu() const
189 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nuf() const
191 return muf()/fvc::interpolate(rho());
195 Foam::tmp<Foam::surfaceScalarField>
196 Foam::multiphaseMixture::surfaceTensionForce() const
198 tmp<surfaceScalarField> tstf
200 new surfaceScalarField
204 "surfaceTensionForce",
205 mesh_.time().timeName(),
211 "surfaceTensionForce",
212 dimensionSet(1, -2, -2, 0, 0),
218 surfaceScalarField& stf = tstf();
220 forAllConstIter(PtrDictionary<phase>, phases_, iter1)
222 const phase& alpha1 = iter1();
224 PtrDictionary<phase>::const_iterator iter2 = iter1;
227 for(; iter2 != phases_.end(); ++iter2)
229 const phase& alpha2 = iter2();
231 sigmaTable::const_iterator sigma =
232 sigmas_.find(interfacePair(alpha1, alpha2));
234 if (sigma == sigmas_.end())
236 FatalErrorIn("multiphaseMixture::surfaceTensionForce() const")
237 << "Cannot find interface " << interfacePair(alpha1, alpha2)
238 << " in list of sigma values"
242 stf += dimensionedScalar("sigma", dimSigma_, sigma())
243 *fvc::interpolate(K(alpha1, alpha2))*
245 fvc::interpolate(alpha2)*fvc::snGrad(alpha1)
246 - fvc::interpolate(alpha1)*fvc::snGrad(alpha2)
255 void Foam::multiphaseMixture::correct()
257 forAllIter(PtrDictionary<phase>, phases_, iter)
262 const Time& runTime = mesh_.time();
264 label nAlphaSubCycles
268 mesh_.solutionDict().subDict("PISO").lookup("nAlphaSubCycles")
274 readLabel(mesh_.solutionDict().subDict("PISO").lookup("nAlphaCorr"))
279 Switch(mesh_.solutionDict().subDict("PISO").lookup("cycleAlpha"))
284 readScalar(mesh_.solutionDict().subDict("PISO").lookup("cAlpha"))
288 volScalarField& alpha = phases_.first();
290 if (nAlphaSubCycles > 1)
292 surfaceScalarField rhoPhiSum = 0.0*rhoPhi_;
293 dimensionedScalar totalDeltaT = runTime.deltaT();
297 subCycle<volScalarField> alphaSubCycle(alpha, nAlphaSubCycles);
298 !(++alphaSubCycle).end();
301 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
302 rhoPhiSum += (runTime.deltaT()/totalDeltaT)*rhoPhi_;
309 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
316 Foam::tmp<Foam::surfaceVectorField> Foam::multiphaseMixture::nHatfv
318 const volScalarField& alpha1,
319 const volScalarField& alpha2
323 // Cell gradient of alpha
324 volVectorField gradAlpha =
325 alpha2*fvc::grad(alpha1) - alpha1*fvc::grad(alpha2);
327 // Interpolated face-gradient of alpha
328 surfaceVectorField gradAlphaf = fvc::interpolate(gradAlpha);
331 surfaceVectorField gradAlphaf =
332 fvc::interpolate(alpha2)*fvc::interpolate(fvc::grad(alpha1))
333 - fvc::interpolate(alpha1)*fvc::interpolate(fvc::grad(alpha2));
335 // Face unit interface normal
336 return gradAlphaf/(mag(gradAlphaf) + deltaN_);
340 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nHatf
342 const volScalarField& alpha1,
343 const volScalarField& alpha2
346 // Face unit interface normal flux
347 return nHatfv(alpha1, alpha2) & mesh_.Sf();
351 // Correction for the boundary condition on the unit normal nHat on
352 // walls to produce the correct contact angle.
354 // The dynamic contact angle is calculated from the component of the
355 // velocity on the direction of the interface, parallel to the wall.
357 void Foam::multiphaseMixture::correctContactAngle
361 surfaceVectorField::GeometricBoundaryField& nHatb
364 const volScalarField::GeometricBoundaryField& gbf =
365 refPhase_.boundaryField();
367 const fvBoundaryMesh& boundary = mesh_.boundary();
369 forAll(boundary, patchi)
371 if (isA<multiphaseAlphaContactAngleFvPatchScalarField>(gbf[patchi]))
373 const multiphaseAlphaContactAngleFvPatchScalarField& acap =
374 refCast<const multiphaseAlphaContactAngleFvPatchScalarField>
379 vectorField& nHatPatch = nHatb[patchi];
381 vectorField AfHatPatch =
382 mesh_.Sf().boundaryField()[patchi]
383 /mesh_.magSf().boundaryField()[patchi];
385 multiphaseAlphaContactAngleFvPatchScalarField::thetaPropsTable::
387 acap.thetaProps().find(interfacePair(alpha1, alpha2));
389 if (tp == acap.thetaProps().end())
393 "multiphaseMixture::correctContactAngle"
394 "(const phase& alpha1, const phase& alpha2, "
395 "fvPatchVectorFieldField& nHatb) const"
396 ) << "Cannot find interface "
397 << interfacePair(alpha1, alpha2)
398 << "\n in table of theta properties for patch "
399 << acap.patch().name()
403 bool matched = (tp.key().first() == alpha1.name());
405 scalar theta0 = convertToRad*tp().theta0(matched);
406 scalarField theta(boundary[patchi].size(), theta0);
408 scalar uTheta = tp().uTheta();
410 // Calculate the dynamic contact angle if required
413 scalar thetaA = convertToRad*tp().thetaA(matched);
414 scalar thetaR = convertToRad*tp().thetaR(matched);
416 // Calculated the component of the velocity parallel
419 U_.boundaryField()[patchi].patchInternalField()
420 - U_.boundaryField()[patchi];
421 Uwall -= (AfHatPatch & Uwall)*AfHatPatch;
423 // Find the direction of the interface parallel to the wall
425 nHatPatch - (AfHatPatch & nHatPatch)*AfHatPatch;
428 nWall /= (mag(nWall) + SMALL);
430 // Calculate Uwall resolved normal to the interface parallel to
432 scalarField uwall = nWall & Uwall;
434 theta += (thetaA - thetaR)*tanh(uwall/uTheta);
438 // Reset nHatPatch to correspond to the contact angle
440 scalarField a12 = nHatPatch & AfHatPatch;
442 scalarField b1 = cos(theta);
444 scalarField b2(nHatPatch.size());
448 b2[facei] = cos(acos(a12[facei]) - theta[facei]);
451 scalarField det = 1.0 - a12*a12;
453 scalarField a = (b1 - a12*b2)/det;
454 scalarField b = (b2 - a12*b1)/det;
456 nHatPatch = a*AfHatPatch + b*nHatPatch;
458 nHatPatch /= (mag(nHatPatch) + deltaN_.value());
464 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::K
470 tmp<surfaceVectorField> tnHatfv = nHatfv(alpha1, alpha2);
472 correctContactAngle(alpha1, alpha2, tnHatfv().boundaryField());
474 // Simple expression for curvature
475 return -fvc::div(tnHatfv & mesh_.Sf());
479 void Foam::multiphaseMixture::solveAlphas
481 const label nAlphaCorr,
482 const bool cycleAlpha,
486 static label nSolves=-1;
489 word alphaScheme("div(phi,alpha)");
490 word alphacScheme("div(phic,alpha)");
492 tmp<fv::convectionScheme<scalar> > mvConvection
494 fv::convectionScheme<scalar>::New
499 mesh_.schemesDict().divScheme(alphaScheme)
503 surfaceScalarField phic = mag(phi_/mesh_.magSf());
504 phic = min(cAlpha*phic, max(phic));
506 for (int gCorr=0; gCorr<nAlphaCorr; gCorr++)
508 phase* refPhasePtr = &refPhase_;
512 PtrDictionary<phase>::iterator refPhaseIter = phases_.begin();
513 for(label i=0; i<nSolves%phases_.size(); i++)
517 refPhasePtr = &refPhaseIter();
520 phase& refPhase = *refPhasePtr;
522 volScalarField refPhaseNew = refPhase;
525 rhoPhi_ = phi_*refPhase.rho();
527 forAllIter(PtrDictionary<phase>, phases_, iter)
529 phase& alpha = iter();
531 if (&alpha == &refPhase) continue;
533 fvScalarMatrix alphaEqn
536 + mvConvection->fvmDiv(phi_, alpha)
539 forAllIter(PtrDictionary<phase>, phases_, iter2)
541 phase& alpha2 = iter2();
543 if (&alpha2 == &alpha) continue;
545 surfaceScalarField phir = phic*nHatf(alpha, alpha2);
546 surfaceScalarField phirb12 =
547 -fvc::flux(-phir, alpha2, alphacScheme);
549 alphaEqn += fvm::div(phirb12, alpha, alphacScheme);
552 alphaEqn.solve(mesh_.solutionDict().solver("alpha"));
554 rhoPhi_ += alphaEqn.flux()*(alpha.rho() - refPhase.rho());
556 Info<< alpha.name() << " volume fraction, min, max = "
557 << alpha.weightedAverage(mesh_.V()).value()
558 << ' ' << min(alpha).value()
559 << ' ' << max(alpha).value()
562 refPhaseNew == refPhaseNew - alpha;
565 refPhase == refPhaseNew;
567 Info<< refPhase.name() << " volume fraction, min, max = "
568 << refPhase.weightedAverage(mesh_.V()).value()
569 << ' ' << min(refPhase).value()
570 << ' ' << max(refPhase).value()
578 bool Foam::multiphaseMixture::read()
580 if (transportModel::read())
584 PtrList<entry> phaseData(lookup("phases"));
587 forAllIter(PtrDictionary<phase>, phases_, iter)
589 readOK &= iter().read(phaseData[phasei++].dict());
592 lookup("sigmas") >> sigmas_;
603 // ************************************************************************* //