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 \*---------------------------------------------------------------------------*/
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 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
42 defineParticleTypeNameAndDebug(parcel, 0);
43 defineTemplateTypeNameAndDebug(Cloud<parcel>, 0);
46 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
50 const Cloud<parcel>& cloud,
51 const vector& position,
62 const scalar liquidCore,
63 const scalar injector,
67 const List<word>& liquidNames
70 Particle<parcel>(cloud, position, cellI),
83 liquidCore_(liquidCore),
93 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
95 bool Foam::parcel::move(spray& sDB)
97 const polyMesh& mesh = cloud().pMesh();
98 const polyBoundaryMesh& pbMesh = mesh.boundaryMesh();
100 const liquidMixture& fuels = sDB.fuels();
102 scalar deltaT = sDB.runTime().deltaT().value();
103 label Nf = fuels.components().size();
104 label Ns = sDB.composition().Y().size();
106 // Calculate the interpolated gas properties at the position of the parcel
107 vector Up = sDB.UInterpolator().interpolate(position(), cell())
109 scalar rhog = sDB.rhoInterpolator().interpolate(position(), cell());
110 scalar pg = sDB.pInterpolator().interpolate(position(), cell());
111 scalar Tg = sDB.TInterpolator().interpolate(position(), cell());
113 scalarField Yfg(Nf, 0.0);
115 scalar cpMixture = 0.0;
116 for(label i=0; i<Ns; i++)
118 const volScalarField& Yi = sDB.composition().Y()[i];
119 if (sDB.isLiquidFuel()[i])
121 label j = sDB.gasToLiquidIndex()[i];
122 scalar Yicelli = Yi[cell()];
125 cpMixture += Yi[cell()]*sDB.gasProperties()[i].Cp(Tg);
128 // Correct the gaseous temperature for evaporated fuel
130 scalar cellV = sDB.mesh().V()[cell()];
131 scalar cellMass = rhog*cellV;
132 Tg += sDB.shs()[cell()]/(cpMixture*cellMass);
134 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
137 scalar tauMomentum = GREAT;
138 scalar tauHeatTransfer = GREAT;
139 scalarField tauEvaporation(Nf, GREAT);
140 scalarField tauBoiling(Nf, GREAT);
142 bool keepParcel = true;
162 // set the end-time for the track
163 scalar tEnd = (1.0 - stepFraction())*deltaT;
165 // Set the maximum time step for this parcel
167 // FPK changes: avoid temperature-out-of-range errors
168 // in spray tracking. HJ, 13/Oct/2007
169 // tauEvaporation no longer multiplied by 1e20,
170 // to account for the evaporation timescale
180 mag(min(tauEvaporation)),
183 mag(tauHeatTransfer),
190 // prevent the number of subcycles from being too many
191 // (10 000 seems high enough)
192 dtMax = max(dtMax, 1.0e-4*tEnd);
194 bool switchProcessor = false;
195 vector planeNormal = vector::zero;
198 planeNormal = n() ^ sDB.axisOfSymmetry();
199 planeNormal /= mag(planeNormal);
202 // move the parcel until there is no 'timeLeft'
203 while (keepParcel && tEnd > SMALL && !switchProcessor)
205 // set the lagrangian time-step
206 scalar dt = min(dtMax, tEnd);
208 // remember which cell the parcel is in
209 // since this will change if a face is hit
210 label celli = cell();
211 scalar p = sDB.p()[celli];
213 // track parcel to face, or end of trajectory
216 // Track and adjust the time step if the trajectory
218 dt *= trackToFace(position() + dt*U_, sDB);
220 // Decrement the end-time acording to how much time the track took
223 // Set the current time-step fraction.
224 stepFraction() = 1.0 - tEnd/deltaT;
226 if (onBoundary()) // hit face
228 # include "boundaryTreatment.H"
232 if (keepParcel && sDB.twoD())
234 scalar z = position() & sDB.axisOfSymmetry();
235 vector r = position() - z*sDB.axisOfSymmetry();
239 correctNormal(sDB.axisOfSymmetry());
243 // **** calculate the lagrangian source terms ****
244 // First we get the 'old' properties.
245 // and then 'update' them to get the 'new'
247 // The difference is then added to the source terms.
249 scalar oRho = fuels.rho(p, T(), X());
250 scalarField oMass(Nf, 0.0);
252 scalar oTotMass = m();
253 scalarField oYf(fuels.Y(X()));
257 oMass[i] = m()*oYf[i];
258 label j = sDB.liquidToGasIndex()[i];
259 oHg += oYf[i]*sDB.gasProperties()[j].Hs(T());
262 vector oMom = m()*U();
263 scalar oHv = fuels.hl(p, T(), X());
264 scalar oH = oHg - oHv;
265 scalar oPE = (p - fuels.pv(p, T(), X()))/oRho;
267 // update the parcel properties (U, T, D)
268 updateParcelProperties
276 scalar nRho = fuels.rho(p, T(), X());
278 scalarField nMass(Nf, 0.0);
279 scalarField nYf(fuels.Y(X()));
283 nMass[i] = m()*nYf[i];
284 label j = sDB.liquidToGasIndex()[i];
285 nHg += nYf[i]*sDB.gasProperties()[j].Hs(T());
288 vector nMom = m()*U();
289 scalar nHv = fuels.hl(p, T(), X());
290 scalar nH = nHg - nHv;
291 scalar nPE = (p - fuels.pv(p, T(), X()))/nRho;
292 scalar nKE = 0.5*pow(mag(U()), 2);
294 // Update the Spray Source Terms
297 sDB.srhos()[i][celli] += oMass[i] - nMass[i];
300 sDB.sms()[celli] += oMom - nMom;
302 sDB.shs()[celli] += oTotMass*(oH + oPE) - m()*(nH + nPE + nKE);
304 // Remove evaporated mass from stripped mass
305 ms() -= ms()*(oTotMass - m())/oTotMass;
307 // Remove parcel if it is 'small'
309 // FPK changes: avoid temperature-out-of-range errors
310 // in spray tracking. HJ, 13/Oct/2007
311 if (m() < sDB.minimumParcelMass())
315 // ... and add the removed 'stuff' to the gas
318 sDB.srhos()[i][celli] += nMass[i];
321 sDB.sms()[celli] += nMom;
322 sDB.shs()[celli] += m()*(nH + nPE + nKE);
325 if (onBoundary() && keepParcel)
329 if (isA<processorPolyPatch>(pbMesh[patch(face())]))
331 switchProcessor = true;
341 void Foam::parcel::updateParcelProperties
349 const liquidMixture& fuels = sDB.fuels();
351 label Nf = sDB.fuels().components().size();
352 label Ns = sDB.composition().Y().size();
354 // calculate mean molecular weight
356 for(label i = 0; i < Ns; i++)
358 W += sDB.composition().Y()[i][celli]/sDB.gasProperties()[i].W();
363 // Calculate the interpolated gas properties at the position of the parcel
364 vector Up = sDB.UInterpolator().interpolate(position(), celli, facei)
366 scalar rhog = sDB.rhoInterpolator().interpolate(position(), celli, facei);
367 scalar pg = sDB.pInterpolator().interpolate(position(), celli, facei);
368 scalar Tg0 = sDB.TInterpolator().interpolate(position(), celli, facei);
370 // correct the gaseous temperature for evaporated fuel
372 for (label i = 0; i < Ns; i++)
374 cpMix += sDB.composition().Y()[i][celli]
375 *sDB.gasProperties()[i].Cp(T());
377 scalar cellV = sDB.mesh().V()[celli];
378 scalar rho = sDB.rho()[celli];
379 scalar cellMass = rho*cellV;
380 scalar dh = sDB.shs()[celli];
381 scalarField addedMass(Nf, 0.0);
385 addedMass[i] += sDB.srhos()[i][celli]*cellV;
388 scalar Tg = Tg0 + dh/(cpMix*cellMass);
390 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
393 scalarField Yfg(Nf, 0.0);
396 label j = sDB.liquidToGasIndex()[i];
397 const volScalarField& Yj = sDB.composition().Y()[j];
398 scalar Yfg0 = Yj[celli];
399 Yfg[i] = (Yfg0*cellMass + addedMass[i])/(addedMass[i] + cellMass);
402 scalar tauMomentum = GREAT;
403 scalar tauHeatTransfer = GREAT;
404 scalarField tauEvaporation(Nf, GREAT);
405 scalarField tauBoiling(Nf, GREAT);
424 scalar timeRatio = dt/tauMomentum;
427 vector gcorr = sDB.g();
431 // remove the tangential velocity component
432 scalar v1 = Up & sDB.axisOfSymmetry();
433 scalar v2 = Up & n();
434 Ucorr = v1*sDB.axisOfSymmetry() + v2*n();
436 // Remove the tangential gravity component
437 scalar g1 = gcorr & sDB.axisOfSymmetry();
438 scalar g2 = gcorr & n();
439 gcorr = g1*sDB.axisOfSymmetry() + g2*n();
442 U() = (U() + timeRatio*Ucorr + gcorr*dt)/(1.0 + timeRatio);
446 vector normal = n() ^ sDB.axisOfSymmetry();
447 normal /= mag(normal);
448 scalar dU = U() & normal;
452 scalar TDroplet = T();
453 scalar oldDensity = fuels.rho(pg, T(), X());
454 scalar oldMass = m();
455 scalarField Yf0(fuels.Y(X()));
456 scalarField mi0(m()*Yf0);
460 for (label i=0; i<Nf; i++)
462 label j = sDB.liquidToGasIndex()[i];
463 oldhg += Yf0[i]*sDB.gasProperties()[j].Hs(T());
466 scalar oldhv = fuels.hl(pg, T(), X());
467 scalar Np = N(oldDensity);
469 scalar newMass = oldMass;
470 scalar newhg = oldhg;
471 scalar newhv = oldhv;
476 // first calculate the new temperature and droplet mass,
477 // then calculate the energy source and correct the
478 // gaseous temperature, Tg, and mass fraction, Yfg,
479 // to calculate the new properties for the parcel
480 // This procedure seems to be more stable
482 // Iterate the temperature until convergedT and parcel mass
483 // is above minimumParcelMass
486 bool convergedT = false;
488 // FPK changes: avoid temperature-out-of-range errors
489 // in spray tracking. HJ, 13/Oct/2007
490 while ((!convergedT) && (m() > sDB.minimumParcelMass()))
493 // new characteristic times does not need to be calculated
499 scalarField Ynew(fuels.Y(X()));
501 for(label i = 0; i < Nf; i++)
503 label j = sDB.liquidToGasIndex()[i];
505 newhg += Ynew[i]*sDB.gasProperties()[j].Hs(Tnew);
508 newhv = fuels.hl(pg, Tnew, X());
510 scalar dm = oldMass - newMass;
511 scalar dhNew = oldMass*(oldhg - oldhv) - newMass*(newhg - newhv);
513 // Prediction of new gaseous temperature and fuel mass fraction
514 Tg = Tg0 + (dh + dhNew)/(cpMix*cellMass);
516 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
521 label j = sDB.liquidToGasIndex()[i];
522 const volScalarField& Yj = sDB.composition().Y()[j];
523 scalar Yfg0 = Yj[celli];
526 (Yfg0*cellMass + addedMass[i] + dm)/
527 (addedMass[i] + cellMass + dm);
549 scalar Taverage = TDroplet + (Tg - TDroplet)/3.0;
550 // for a liquid Cl \approx Cp
551 scalar liquidcL = sDB.fuels().cp(pg, TDroplet, X());
554 for (label i = 0; i < Ns; i++)
556 if (sDB.isLiquidFuel()[i])
558 label j = sDB.gasToLiquidIndex()[i];
559 cpMix += Yfg[j]*sDB.gasProperties()[i].Cp(Taverage);
563 scalar Y = sDB.composition().Y()[i][celli];
565 cpMix += Y*sDB.gasProperties()[i].Cp(Taverage);
569 scalar evaporationSource = 0.0;
571 scalar tauEvap = 0.0;
573 for (label i =0 ; i < Nf; i++)
575 tauEvap += X()[i]*fuels.properties()[i].W()/tauEvaporation[i];
577 tauEvap = fuels.W(X())/tauEvap;
580 if (sDB.evaporation().evaporation())
582 scalar hv = fuels.hl(pg, TDroplet, X());
583 evaporationSource = hv/liquidcL/tauEvap;
585 z = cpMix*tauHeatTransfer/liquidcL/tauEvap;
588 if (sDB.heatTransfer().heatTransfer())
591 sDB.heatTransfer().fCorrection(z)/tauHeatTransfer;
593 Tnew = (TDroplet + dt*(fCorrect*Tg - evaporationSource))
594 /(1.0 + dt*fCorrect);
596 // Prevent droplet temperature to go above critial value
597 Tnew = min(Tnew, fuels.Tc(X()));
599 // Prevent droplet temperature to go too low
600 // Mainly a numerical stability issue
602 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
603 Tnew = max(273.0, Tnew);
606 scalar pAtSurface = fuels.pv(pg, Td, X());
607 scalar pCompare = 0.999*pg;
608 scalar boiling = pAtSurface >= pCompare;
612 // can not go above boiling temperature
613 scalar Terr = 1.0e-3;
616 scalar pOld = pAtSurface;
621 pAtSurface = fuels.pv(pg, Td, X());
622 if ((pAtSurface < pCompare) && (pOld < pCompare))
628 if ((pAtSurface > pCompare) && (pOld > pCompare))
635 if ((pAtSurface > pCompare) && (pOld < pCompare))
652 Info << "n = " << n << ", T = " << Td << ", pv = " << pAtSurface << endl;
660 // Evaporate droplet!
661 // if the droplet is NOT boiling use implicit scheme.
662 if (sDB.evaporation().evaporation())
664 for (label i = 0; i < Nf; i++)
666 // Immediately evaporate mass that has reached
667 // critical condition
668 // Bug fix for 32- and 64-bit consistency: Tommaso Lucchini
669 if (mag(Tnew - fuels.Tc(X())) < 1e-10)
675 scalar Td = min(Tnew, 0.999*fuels.properties()[i].Tc());
677 scalar pAtSurface = fuels.properties()[i].pv(pg, Td);
678 scalar boiling = pAtSurface >= 0.999*pg;
682 scalar fr = dt/tauEvaporation[i];
683 mi[i] = mi0[i]/(1.0 + fr);
687 scalar fr = dt/tauBoiling[i];
688 mi[i] = mi0[i]/(1.0 + fr);
693 scalar mTot = sum(mi);
697 scalarField Ynew(mi/mTot);
698 scalarField Xnew(sDB.fuels().X(Ynew));
711 // FPK changes: avoid temperature-out-of-range errors
712 // in spray tracking. HJ, 13/Oct/2007
713 const scalar relaxfac = sDB.sprayRelaxFactor();
715 convergedT = mag(Tnew - T()) < 0.1 || n > sDB.sprayIterate();
717 // FPK version, 13/Oct/2007
718 T() = T()*(1 - relaxfac) + Tnew*relaxfac;
720 // T() += 0.5*(Tnew - T());
721 // OF-vanilla version
725 scalar rhod = fuels.rho(pg, T(), X());
726 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
730 scalar rhod = fuels.rho(pg, T(), X());
732 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
736 void Foam::parcel::transformProperties(const tensor& T)
738 U_ = transform(T, U_);
742 void Foam::parcel::transformProperties(const vector&)
746 // ************************************************************************* //