1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright (C) 1991-2009 OpenCFD Ltd.
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 the
13 Free Software Foundation; either version 2 of the License, or (at your
14 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, write to the Free Software Foundation,
23 Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 \*---------------------------------------------------------------------------*/
27 #include "multiphaseMixture.H"
28 #include "alphaContactAngleFvPatchScalarField.H"
33 // * * * * * * * * * * * * * * * Static Member Data * * * * * * * * * * * * //
35 const scalar Foam::multiphaseMixture::convertToRad =
36 Foam::mathematicalConstant::pi/180.0;
39 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
41 void Foam::multiphaseMixture::calcAlphas()
46 forAllIter(PtrDictionary<phase>, phases_, iter)
48 alphas_ += level*iter();
52 alphas_.correctBoundaryConditions();
56 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
58 Foam::multiphaseMixture::multiphaseMixture
60 const volVectorField& U,
61 const surfaceScalarField& phi
64 transportModel(U, phi),
65 phases_(lookup("phases"), phase::iNew(U, phi)),
66 refPhase_(*phases_.lookup(word(lookup("refPhase")))),
77 mesh_.time().timeName(),
83 dimensionedScalar("rho*phi", dimMass/dimTime, 0.0)
91 mesh_.time().timeName(),
97 dimensionedScalar("alphas", dimless, 0.0),
98 zeroGradientFvPatchScalarField::typeName
101 sigmas_(lookup("sigmas")),
102 dimSigma_(1, 0, -2, 0, 0),
106 1e-8/pow(average(mesh_.V()), 1.0/3.0)
112 forAllIter(PtrDictionary<phase>, phases_, iter)
114 alphaTable_.add(iter());
119 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
121 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::rho() const
123 PtrDictionary<phase>::const_iterator iter = phases_.begin();
125 tmp<volScalarField> trho = iter()*iter().rho();
127 for(++iter; iter != phases_.end(); ++iter)
129 trho() += iter()*iter().rho();
136 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::mu() const
138 PtrDictionary<phase>::const_iterator iter = phases_.begin();
140 tmp<volScalarField> tmu = iter()*iter().rho()*iter().nu();
142 for(++iter; iter != phases_.end(); ++iter)
144 tmu() += iter()*iter().rho()*iter().nu();
151 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::muf() const
153 PtrDictionary<phase>::const_iterator iter = phases_.begin();
155 tmp<surfaceScalarField> tmuf =
156 fvc::interpolate(iter())*iter().rho()*fvc::interpolate(iter().nu());
158 for(++iter; iter != phases_.end(); ++iter)
161 fvc::interpolate(iter())*iter().rho()*fvc::interpolate(iter().nu());
168 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::nu() const
174 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nuf() const
176 return muf()/fvc::interpolate(rho());
180 Foam::tmp<Foam::surfaceScalarField>
181 Foam::multiphaseMixture::surfaceTensionForce() const
183 tmp<surfaceScalarField> tstf
185 new surfaceScalarField
189 "surfaceTensionForce",
190 mesh_.time().timeName(),
196 "surfaceTensionForce",
197 dimensionSet(1, -2, -2, 0, 0),
203 surfaceScalarField& stf = tstf();
205 forAllConstIter(PtrDictionary<phase>, phases_, iter1)
207 const phase& alpha1 = iter1();
209 PtrDictionary<phase>::const_iterator iter2 = iter1;
212 for(; iter2 != phases_.end(); ++iter2)
214 const phase& alpha2 = iter2();
216 sigmaTable::const_iterator sigma =
217 sigmas_.find(interfacePair(alpha1, alpha2));
219 if (sigma == sigmas_.end())
221 FatalErrorIn("multiphaseMixture::surfaceTensionForce() const")
222 << "Cannot find interface " << interfacePair(alpha1, alpha2)
223 << " in list of sigma values"
227 stf += dimensionedScalar("sigma", dimSigma_, sigma())
228 *fvc::interpolate(K(alpha1, alpha2))*
230 fvc::interpolate(alpha2)*fvc::snGrad(alpha1)
231 - fvc::interpolate(alpha1)*fvc::snGrad(alpha2)
240 void Foam::multiphaseMixture::correct()
242 forAllIter(PtrDictionary<phase>, phases_, iter)
247 const Time& runTime = mesh_.time();
249 label nAlphaSubCycles
253 mesh_.solutionDict().subDict("PISO").lookup("nAlphaSubCycles")
259 readLabel(mesh_.solutionDict().subDict("PISO").lookup("nAlphaCorr"))
264 Switch(mesh_.solutionDict().subDict("PISO").lookup("cycleAlpha"))
269 readScalar(mesh_.solutionDict().subDict("PISO").lookup("cAlpha"))
273 volScalarField& alpha = phases_.first();
275 if (nAlphaSubCycles > 1)
277 surfaceScalarField rhoPhiSum = 0.0*rhoPhi_;
278 dimensionedScalar totalDeltaT = runTime.deltaT();
282 subCycle<volScalarField> alphaSubCycle(alpha, nAlphaSubCycles);
283 !(++alphaSubCycle).end();
286 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
287 rhoPhiSum += (runTime.deltaT()/totalDeltaT)*rhoPhi_;
294 solveAlphas(nAlphaCorr, cycleAlpha, cAlpha);
299 Foam::tmp<Foam::surfaceVectorField> Foam::multiphaseMixture::nHatfv
301 const volScalarField& alpha1,
302 const volScalarField& alpha2
306 // Cell gradient of alpha
307 volVectorField gradAlpha =
308 alpha2*fvc::grad(alpha1) - alpha1*fvc::grad(alpha2);
310 // Interpolated face-gradient of alpha
311 surfaceVectorField gradAlphaf = fvc::interpolate(gradAlpha);
314 surfaceVectorField gradAlphaf =
315 fvc::interpolate(alpha2)*fvc::interpolate(fvc::grad(alpha1))
316 - fvc::interpolate(alpha1)*fvc::interpolate(fvc::grad(alpha2));
318 // Face unit interface normal
319 return gradAlphaf/(mag(gradAlphaf) + deltaN_);
323 Foam::tmp<Foam::surfaceScalarField> Foam::multiphaseMixture::nHatf
325 const volScalarField& alpha1,
326 const volScalarField& alpha2
329 // Face unit interface normal flux
330 return nHatfv(alpha1, alpha2) & mesh_.Sf();
334 // Correction for the boundary condition on the unit normal nHat on
335 // walls to produce the correct contact angle.
337 // The dynamic contact angle is calculated from the component of the
338 // velocity on the direction of the interface, parallel to the wall.
340 void Foam::multiphaseMixture::correctContactAngle
344 surfaceVectorField::GeometricBoundaryField& nHatb
347 const volScalarField::GeometricBoundaryField& gbf
348 = refPhase_.boundaryField();
350 const fvBoundaryMesh& boundary = mesh_.boundary();
352 forAll(boundary, patchi)
354 if (typeid(gbf[patchi]) == typeid(alphaContactAngleFvPatchScalarField))
356 const alphaContactAngleFvPatchScalarField& acap =
357 refCast<const alphaContactAngleFvPatchScalarField>(gbf[patchi]);
359 vectorField& nHatPatch = nHatb[patchi];
361 vectorField AfHatPatch =
362 mesh_.Sf().boundaryField()[patchi]
363 /mesh_.magSf().boundaryField()[patchi];
365 alphaContactAngleFvPatchScalarField::thetaPropsTable::
367 acap.thetaProps().find(interfacePair(alpha1, alpha2));
369 if (tp == acap.thetaProps().end())
373 "multiphaseMixture::correctContactAngle"
374 "(const phase& alpha1, const phase& alpha2, "
375 "fvPatchVectorFieldField& nHatb) const"
376 ) << "Cannot find interface " << interfacePair(alpha1, alpha2)
377 << "\n in table of theta properties for patch "
378 << acap.patch().name()
382 bool matched = (tp.key().first() == alpha1.name());
384 scalar theta0 = convertToRad*tp().theta0(matched);
385 scalarField theta(boundary[patchi].size(), theta0);
387 scalar uTheta = tp().uTheta();
389 // Calculate the dynamic contact angle if required
392 scalar thetaA = convertToRad*tp().thetaA(matched);
393 scalar thetaR = convertToRad*tp().thetaR(matched);
395 // Calculated the component of the velocity parallel to the wall
397 U_.boundaryField()[patchi].patchInternalField()
398 - U_.boundaryField()[patchi];
399 Uwall -= (AfHatPatch & Uwall)*AfHatPatch;
401 // Find the direction of the interface parallel to the wall
403 nHatPatch - (AfHatPatch & nHatPatch)*AfHatPatch;
406 nWall /= (mag(nWall) + SMALL);
408 // Calculate Uwall resolved normal to the interface parallel to
410 scalarField uwall = nWall & Uwall;
412 theta += (thetaA - thetaR)*tanh(uwall/uTheta);
416 // Reset nHatPatch to correspond to the contact angle
418 scalarField a12 = nHatPatch & AfHatPatch;
420 scalarField b1 = cos(theta);
422 scalarField b2(nHatPatch.size());
426 b2[facei] = cos(acos(a12[facei]) - theta[facei]);
429 scalarField det = 1.0 - a12*a12;
431 scalarField a = (b1 - a12*b2)/det;
432 scalarField b = (b2 - a12*b1)/det;
434 nHatPatch = a*AfHatPatch + b*nHatPatch;
436 nHatPatch /= (mag(nHatPatch) + deltaN_.value());
442 Foam::tmp<Foam::volScalarField> Foam::multiphaseMixture::K
448 tmp<surfaceVectorField> tnHatfv = nHatfv(alpha1, alpha2);
450 correctContactAngle(alpha1, alpha2, tnHatfv().boundaryField());
452 // Simple expression for curvature
453 return -fvc::div(tnHatfv & mesh_.Sf());
457 void Foam::multiphaseMixture::solveAlphas
459 const label nAlphaCorr,
460 const bool cycleAlpha,
464 static label nSolves=-1;
467 word alphaScheme("div(phi,alpha)");
468 word alphacScheme("div(phic,alpha)");
470 tmp<fv::convectionScheme<scalar> > mvConvection
472 fv::convectionScheme<scalar>::New
477 mesh_.divScheme(alphaScheme)
481 surfaceScalarField phic = mag(phi_/mesh_.magSf());
482 phic = min(cAlpha*phic, max(phic));
484 for (int gCorr=0; gCorr<nAlphaCorr; gCorr++)
486 phase* refPhasePtr = &refPhase_;
490 PtrDictionary<phase>::iterator refPhaseIter = phases_.begin();
491 for(label i=0; i<nSolves%phases_.size(); i++)
495 refPhasePtr = &refPhaseIter();
498 phase& refPhase = *refPhasePtr;
500 volScalarField refPhaseNew = refPhase;
503 rhoPhi_ = phi_*refPhase.rho();
505 forAllIter(PtrDictionary<phase>, phases_, iter)
507 phase& alpha = iter();
509 if (&alpha == &refPhase) continue;
511 fvScalarMatrix alphaEqn
514 + mvConvection->fvmDiv(phi_, alpha)
517 forAllIter(PtrDictionary<phase>, phases_, iter2)
519 phase& alpha2 = iter2();
521 if (&alpha2 == &alpha) continue;
523 surfaceScalarField phir = phic*nHatf(alpha, alpha2);
524 surfaceScalarField phirb12 =
525 -fvc::flux(-phir, alpha2, alphacScheme);
527 alphaEqn += fvm::div(phirb12, alpha, alphacScheme);
530 alphaEqn.solve(mesh_.solver("alpha"));
532 rhoPhi_ += alphaEqn.flux()*(alpha.rho() - refPhase.rho());
534 Info<< alpha.name() << " volume fraction, min, max = "
535 << alpha.weightedAverage(mesh_.V()).value()
536 << ' ' << min(alpha).value()
537 << ' ' << max(alpha).value()
540 refPhaseNew == refPhaseNew - alpha;
543 refPhase == refPhaseNew;
545 Info<< refPhase.name() << " volume fraction, min, max = "
546 << refPhase.weightedAverage(mesh_.V()).value()
547 << ' ' << min(refPhase).value()
548 << ' ' << max(refPhase).value()
556 bool Foam::multiphaseMixture::read()
558 if (transportModel::read())
562 PtrList<entry> phaseData(lookup("phases"));
565 forAllIter(PtrDictionary<phase>, phases_, iter)
567 readOK &= iter().read(phaseData[phasei++].dict());
570 lookup("sigmas") >> sigmas_;
581 // ************************************************************************* //