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 \*---------------------------------------------------------------------------*/
29 #include "dragModel.H"
30 #include "evaporationModel.H"
31 #include "heatTransferModel.H"
32 #include "wallModel.H"
33 #include "wallPolyPatch.H"
34 #include "wedgePolyPatch.H"
35 #include "processorPolyPatch.H"
36 #include "basicMultiComponentMixture.H"
38 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
43 const vector& position,
56 const scalar liquidCore,
57 const scalar injector,
61 const List<word>& liquidNames
64 particle(mesh, position, cellI, tetFaceI, tetPtI),
65 liquidComponents_(liquidNames),
74 liquidCore_(liquidCore),
84 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
86 bool Foam::parcel::move(trackingData& td, const scalar trackTime)
88 td.switchProcessor = false;
89 td.keepParticle = true;
91 const polyBoundaryMesh& pbMesh = mesh_.boundaryMesh();
93 spray& sDB = td.cloud();
94 const liquidMixtureProperties& fuels = sDB.fuels();
96 label Nf = fuels.components().size();
97 label Ns = sDB.composition().Y().size();
99 tetIndices tetIs = this->currentTetIndices();
101 // Calculate the interpolated gas properties at the position of the parcel
102 vector Up = sDB.UInterpolator().interpolate(position(), tetIs) + Uturb();
103 scalar rhog = sDB.rhoInterpolator().interpolate(position(), tetIs);
104 scalar pg = sDB.pInterpolator().interpolate(position(), tetIs);
105 scalar Tg = sDB.TInterpolator().interpolate(position(), tetIs);
107 scalarField Yfg(Nf, 0.0);
109 scalar cpMixture = 0.0;
110 for (label i=0; i<Ns; i++)
112 const volScalarField& Yi = sDB.composition().Y()[i];
113 if (sDB.isLiquidFuel()[i])
115 label j = sDB.gasToLiquidIndex()[i];
116 scalar YicellI = Yi[cell()];
119 cpMixture += Yi[cell()]*sDB.gasProperties()[i].Cp(Tg);
122 // correct the gaseous temperature for evaporated fuel
124 scalar cellV = sDB.mesh().V()[cell()];
125 scalar cellMass = rhog*cellV;
126 Tg += sDB.shs()[cell()]/(cpMixture*cellMass);
129 scalar tauMomentum = GREAT;
130 scalar tauHeatTransfer = GREAT;
131 scalarField tauEvaporation(Nf, GREAT);
132 scalarField tauBoiling(Nf, GREAT);
152 // set the end-time for the track
153 scalar tEnd = (1.0 - stepFraction())*trackTime;
155 // set the maximum time step for this parcel
164 1.0e+10*mag(min(tauEvaporation)), // evaporation is not an issue
167 mag(tauHeatTransfer),
174 // prevent the number of subcycles from being too many
175 // (10 000 seems high enough)
176 dtMax = max(dtMax, 1.0e-4*tEnd);
178 vector planeNormal = vector::zero;
181 planeNormal = n() ^ sDB.axisOfSymmetry();
182 planeNormal /= mag(planeNormal);
185 // move the parcel until there is no 'timeLeft'
186 while (td.keepParticle && !td.switchProcessor && tEnd > SMALL)
188 // set the lagrangian time-step
189 scalar dt = min(dtMax, tEnd);
191 // remember which cell the parcel is in
192 // since this will change if a face is hit
193 label cellI = cell();
194 scalar p = sDB.p()[cellI];
196 // track parcel to face, or end of trajectory
199 // Track and adjust the time step if the trajectory is not completed
200 dt *= trackToFace(position() + dt*U_, td);
202 // Decrement the end-time acording to how much time the track took
205 // Set the current time-step fraction.
206 stepFraction() = 1.0 - tEnd/trackTime;
208 if (onBoundary()) // hit face
210 #include "boundaryTreatment.H"
214 if (td.keepParticle && sDB.twoD())
216 scalar z = position() & sDB.axisOfSymmetry();
217 vector r = position() - z*sDB.axisOfSymmetry();
220 correctNormal(sDB.axisOfSymmetry());
224 // **** calculate the lagrangian source terms ****
225 // First we get the 'old' properties.
226 // and then 'update' them to get the 'new'
228 // The difference is then added to the source terms.
230 scalar oRho = fuels.rho(p, T(), X());
231 scalarField oMass(Nf, 0.0);
233 scalar oTotMass = m();
234 scalarField oYf(fuels.Y(X()));
238 oMass[i] = m()*oYf[i];
239 label j = sDB.liquidToGasIndex()[i];
240 oHg += oYf[i]*sDB.gasProperties()[j].Hs(T());
243 vector oMom = m()*U();
244 scalar oHv = fuels.hl(p, T(), X());
245 scalar oH = oHg - oHv;
246 scalar oPE = (p - fuels.pv(p, T(), X()))/oRho;
248 // update the parcel properties (U, T, D)
249 updateParcelProperties
257 scalar nRho = fuels.rho(p, T(), X());
259 scalarField nMass(Nf, 0.0);
260 scalarField nYf(fuels.Y(X()));
264 nMass[i] = m()*nYf[i];
265 label j = sDB.liquidToGasIndex()[i];
266 nHg += nYf[i]*sDB.gasProperties()[j].Hs(T());
269 vector nMom = m()*U();
270 scalar nHv = fuels.hl(p, T(), X());
271 scalar nH = nHg - nHv;
272 scalar nPE = (p - fuels.pv(p, T(), X()))/nRho;
274 // Update the Spray Source Terms
277 sDB.srhos()[i][cellI] += oMass[i] - nMass[i];
279 sDB.sms()[cellI] += oMom - nMom;
281 sDB.shs()[cellI] += oTotMass*(oH + oPE) - m()*(nH + nPE);
283 // Remove evaporated mass from stripped mass
284 ms() -= ms()*(oTotMass - m())/oTotMass;
286 // remove parcel if it is 'small'
289 td.keepParticle = false;
291 // ... and add the removed 'stuff' to the gas
294 sDB.srhos()[i][cellI] += nMass[i];
297 sDB.sms()[cellI] += nMom;
298 sDB.shs()[cellI] += m()*(nH + nPE);
301 if (onBoundary() && td.keepParticle)
305 if (isA<processorPolyPatch>(pbMesh[patch(face())]))
307 td.switchProcessor = true;
313 return td.keepParticle;
317 void Foam::parcel::updateParcelProperties
325 const liquidMixtureProperties& fuels = sDB.fuels();
327 label Nf = sDB.fuels().components().size();
328 label Ns = sDB.composition().Y().size();
330 // calculate mean molecular weight
332 for (label i=0; i<Ns; i++)
334 W += sDB.composition().Y()[i][cellI]/sDB.gasProperties()[i].W();
339 // Calculate the interpolated gas properties at the position of the parcel
340 vector Up = sDB.UInterpolator().interpolate(position(), cellI, faceI)
342 scalar rhog = sDB.rhoInterpolator().interpolate(position(), cellI, faceI);
343 scalar pg = sDB.pInterpolator().interpolate(position(), cellI, faceI);
344 scalar Tg0 = sDB.TInterpolator().interpolate(position(), cellI, faceI);
346 // correct the gaseous temperature for evaporated fuel
348 for (label i=0; i<Ns; i++)
350 cpMix += sDB.composition().Y()[i][cellI]
351 *sDB.gasProperties()[i].Cp(T());
353 scalar cellV = sDB.mesh().V()[cellI];
354 scalar rho = sDB.rho()[cellI];
355 scalar cellMass = rho*cellV;
356 scalar dh = sDB.shs()[cellI];
357 scalarField addedMass(Nf, 0.0);
361 addedMass[i] += sDB.srhos()[i][cellI]*cellV;
364 scalar Tg = Tg0 + dh/(cpMix*cellMass);
367 scalarField Yfg(Nf, 0.0);
370 label j = sDB.liquidToGasIndex()[i];
371 const volScalarField& Yj = sDB.composition().Y()[j];
372 scalar Yfg0 = Yj[cellI];
373 Yfg[i] = (Yfg0*cellMass + addedMass[i])/(addedMass[i] + cellMass);
376 scalar tauMomentum = GREAT;
377 scalar tauHeatTransfer = GREAT;
378 scalarField tauEvaporation(Nf, GREAT);
379 scalarField tauBoiling(Nf, GREAT);
398 scalar timeRatio = dt/tauMomentum;
401 vector gcorr = sDB.g();
405 // remove the tangential velocity component
406 scalar v1 = Up & sDB.axisOfSymmetry();
407 scalar v2 = Up & n();
408 Ucorr = v1*sDB.axisOfSymmetry() + v2*n();
410 // Remove the tangential gravity component
411 scalar g1 = gcorr & sDB.axisOfSymmetry();
412 scalar g2 = gcorr & n();
413 gcorr = g1*sDB.axisOfSymmetry() + g2*n();
416 U() = (U() + timeRatio*Ucorr + gcorr*dt)/(1.0 + timeRatio);
420 vector normal = n() ^ sDB.axisOfSymmetry();
421 normal /= mag(normal);
422 scalar dU = U() & normal;
426 scalar TDroplet = T();
427 scalar oldDensity = fuels.rho(pg, T(), X());
428 scalar oldMass = m();
429 scalarField Yf0(fuels.Y(X()));
430 scalarField mi0(m()*Yf0);
434 for (label i=0; i<Nf; i++)
436 label j = sDB.liquidToGasIndex()[i];
437 oldhg += Yf0[i]*sDB.gasProperties()[j].Hs(T());
440 scalar oldhv = fuels.hl(pg, T(), X());
441 scalar Np = N(oldDensity);
443 scalar newMass = oldMass;
444 scalar newhg = oldhg;
445 scalar newhv = oldhv;
449 // first calculate the new temperature and droplet mass,
450 // then calculate the energy source and correct the
451 // gaseous temperature, Tg, and mass fraction, Yfg,
452 // to calculate the new properties for the parcel
453 // This procedure seems to be more stable
455 while ((n < sDB.evaporation().nEvapIter()) && (m() > VSMALL))
458 // new characteristic times does not need to be calculated the
464 scalarField Ynew(fuels.Y(X()));
465 for (label i=0; i<Nf; i++)
467 label j = sDB.liquidToGasIndex()[i];
468 newhg += Ynew[i]*sDB.gasProperties()[j].Hs(Tnew);
471 newhv = fuels.hl(pg, Tnew, X());
473 scalar dm = oldMass - newMass;
474 scalar dhNew = oldMass*(oldhg-oldhv) - newMass*(newhg-newhv);
476 // Prediction of new gaseous temperature and fuel mass fraction
477 Tg = Tg0 + (dh+dhNew)/(cpMix*cellMass);
482 label j = sDB.liquidToGasIndex()[i];
483 const volScalarField& Yj = sDB.composition().Y()[j];
484 scalar Yfg0 = Yj[cellI];
485 Yfg[i] = (Yfg0*cellMass + addedMass[i] + dm)
486 /(addedMass[i] + cellMass + dm);
508 scalar Taverage = TDroplet + (Tg - TDroplet)/3.0;
509 // for a liquid Cl \approx Cp
510 scalar liquidcL = sDB.fuels().Cp(pg, TDroplet, X());
513 for (label i=0; i<Ns; i++)
515 if (sDB.isLiquidFuel()[i])
517 label j = sDB.gasToLiquidIndex()[i];
518 cpMix += Yfg[j]*sDB.gasProperties()[i].Cp(Taverage);
522 scalar Y = sDB.composition().Y()[i][cellI];
523 cpMix += Y*sDB.gasProperties()[i].Cp(Taverage);
527 scalar evaporationSource = 0.0;
529 scalar tauEvap = 0.0;
531 for (label i=0; i<Nf; i++)
533 tauEvap += X()[i]*fuels.properties()[i].W()/tauEvaporation[i];
535 tauEvap = fuels.W(X())/tauEvap;
538 if (sDB.evaporation().evaporation())
540 scalar hv = fuels.hl(pg, TDroplet, X());
544 z = cpMix*tauHeatTransfer/liquidcL/tauEvap;
547 if (sDB.heatTransfer().heatTransfer())
550 sDB.heatTransfer().fCorrection(z)/tauHeatTransfer;
553 (TDroplet + dt*(fCorrect * Tg - evaporationSource))
554 /(1.0 + dt*fCorrect);
556 // Prevent droplet temperature to go above critial value
557 Tnew = min(Tnew, fuels.Tc(X()));
559 // Prevent droplet temperature to go too low
560 // Mainly a numerical stability issue
561 Tnew = max(200.0, Tnew);
564 scalar pAtSurface = fuels.pv(pg, Td, X());
565 scalar pCompare = 0.999*pg;
566 scalar boiling = pAtSurface >= pCompare;
569 // can not go above boiling temperature
570 scalar Terr = 1.0e-3;
573 scalar pOld = pAtSurface;
577 pAtSurface = fuels.pv(pg, Td, X());
578 if ((pAtSurface < pCompare) && (pOld < pCompare))
584 if ((pAtSurface > pCompare) && (pOld > pCompare))
591 if ((pAtSurface > pCompare) && (pOld < pCompare))
606 Info<< "n = " << n << ", T = " << Td << ", pv = "
607 << pAtSurface << endl;
615 // Evaporate droplet!
616 // if the droplet is NOT boiling use implicit scheme.
617 if (sDB.evaporation().evaporation())
619 for (label i=0; i<Nf; i++)
621 // immediately evaporate mass that has reached critical
623 if (mag(Tnew - fuels.Tc(X())) < SMALL)
629 scalar Td = min(Tnew, 0.999*fuels.properties()[i].Tc());
631 scalar pAtSurface = fuels.properties()[i].pv(pg, Td);
632 scalar boiling = pAtSurface >= 0.999*pg;
636 scalar fr = dt/tauEvaporation[i];
637 mi[i] = mi0[i]/(1.0 + fr);
641 scalar fr = dt/tauBoiling[i];
642 mi[i] = mi0[i]/(1.0 + fr);
647 scalar mTot = sum(mi);
650 scalarField Ynew(mi/mTot);
651 scalarField Xnew(sDB.fuels().X(Ynew));
664 scalar rhod = fuels.rho(pg, T(), X());
665 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
669 scalar rhod = fuels.rho(pg, T(), X());
671 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
675 void Foam::parcel::transformProperties(const tensor& T)
677 U_ = transform(T, U_);
681 void Foam::parcel::transformProperties(const vector&)
685 // ************************************************************************* //