Merge /u/wyldckat/foam-extend32/ branch master into master
[foam-extend-3.2.git] / src / lagrangian / dieselSpray / parcel / parcel.C
bloba95f7dc8263304a44c46801ee670a6be7b592dd3
1 /*---------------------------------------------------------------------------*\
2   =========                 |
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 -------------------------------------------------------------------------------
8 License
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 "parcel.H"
28 #include "spray.H"
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 * * * * * * * * * * * * * //
40 namespace Foam
42     defineParticleTypeNameAndDebug(parcel, 0);
43     defineTemplateTypeNameAndDebug(Cloud<parcel>, 0);
46 // * * * * * * * * * * * * * * * * Constructors  * * * * * * * * * * * * * * //
48 Foam::parcel::parcel
50     const Cloud<parcel>& cloud,
51     const vector& position,
52     const label cellI,
53     const vector& n,
54     const scalar d,
55     const scalar T,
56     const scalar m,
57     const scalar y,
58     const scalar yDot,
59     const scalar ct,
60     const scalar ms,
61     const scalar tTurb,
62     const scalar liquidCore,
63     const scalar injector,
64     const vector& U,
65     const vector& Uturb,
66     const scalarField& X,
67     const List<word>& liquidNames
70     Particle<parcel>(cloud, position, cellI),
71     liquidComponents_
72     (
73         liquidNames
74     ),
75     d_(d),
76     T_(T),
77     m_(m),
78     y_(y),
79     yDot_(yDot),
80     ct_(ct),
81     ms_(ms),
82     tTurb_(tTurb),
83     liquidCore_(liquidCore),
84     injector_(injector),
85     U_(U),
86     Uturb_(Uturb),
87     n_(n),
88     X_(X),
89     tMom_(GREAT)
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())
108         + Uturb();
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++)
117     {
118         const volScalarField& Yi = sDB.composition().Y()[i];
119         if (sDB.isLiquidFuel()[i])
120         {
121             label j = sDB.gasToLiquidIndex()[i];
122             scalar Yicelli = Yi[cell()];
123             Yfg[j] = Yicelli;
124         }
125         cpMixture += Yi[cell()]*sDB.gasProperties()[i].Cp(Tg);
126     }
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
135     Tg = max(273, Tg);
137     scalar tauMomentum = GREAT;
138     scalar tauHeatTransfer = GREAT;
139     scalarField tauEvaporation(Nf, GREAT);
140     scalarField tauBoiling(Nf, GREAT);
142     bool keepParcel = true;
144     setRelaxationTimes
145     (
146         cell(),
147         tauMomentum,
148         tauEvaporation,
149         tauHeatTransfer,
150         tauBoiling,
151         sDB,
152         rhog,
153         Up,
154         Tg,
155         pg,
156         Yfg,
157         m()*fuels.Y(X()),
158         deltaT
159     );
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
171     // FPK, 13/Oct/2007
172     scalar dtMax = min
173     (
174         tEnd,
175         min
176         (
177             tauMomentum,
178             min
179             (
180                 mag(min(tauEvaporation)),
181                 min
182                 (
183                     mag(tauHeatTransfer),
184                     mag(min(tauBoiling))
185                 )
186             )
187         )
188     )/sDB.subCycles();
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;
196     if (sDB.twoD())
197     {
198         planeNormal = n() ^ sDB.axisOfSymmetry();
199         planeNormal /= mag(planeNormal);
200     }
202     // move the parcel until there is no 'timeLeft'
203     while (keepParcel && tEnd > SMALL && !switchProcessor)
204     {
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
214         if (keepParcel)
215         {
216             // Track and adjust the time step if the trajectory
217             // is not completed
218             dt *= trackToFace(position() + dt*U_, sDB);
220             // Decrement the end-time acording to how much time the track took
221             tEnd -= dt;
223             // Set the current time-step fraction.
224             stepFraction() = 1.0 - tEnd/deltaT;
226             if (onBoundary()) // hit face
227             {
228 #               include "boundaryTreatment.H"
229             }
230         }
232         if (keepParcel && sDB.twoD())
233         {
234             scalar z = position() & sDB.axisOfSymmetry();
235             vector r = position() - z*sDB.axisOfSymmetry();
237             if (mag(r) > SMALL)
238             {
239                 correctNormal(sDB.axisOfSymmetry());
240             }
241         }
243         // **** calculate the lagrangian source terms ****
244         // First we get the 'old' properties.
245         // and then 'update' them to get the 'new'
246         // properties.
247         // The difference is then added to the source terms.
249         scalar oRho = fuels.rho(p, T(), X());
250         scalarField oMass(Nf, 0.0);
251         scalar oHg = 0.0;
252         scalar oTotMass = m();
253         scalarField oYf(fuels.Y(X()));
255         forAll(oMass, i)
256         {
257             oMass[i] = m()*oYf[i];
258             label j = sDB.liquidToGasIndex()[i];
259             oHg += oYf[i]*sDB.gasProperties()[j].Hs(T());
260         }
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
269         (
270             dt,
271             sDB,
272             celli,
273             face()
274         );
276         scalar nRho = fuels.rho(p, T(), X());
277         scalar nHg = 0.0;
278         scalarField nMass(Nf, 0.0);
279         scalarField nYf(fuels.Y(X()));
281         forAll(nMass, i)
282         {
283             nMass[i] = m()*nYf[i];
284             label j = sDB.liquidToGasIndex()[i];
285             nHg += nYf[i]*sDB.gasProperties()[j].Hs(T());
286         }
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
295         forAll(nMass, i)
296         {
297             sDB.srhos()[i][celli] += oMass[i] - nMass[i];
298         }
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())
312         {
313             keepParcel = false;
315             // ... and add the removed 'stuff' to the gas
316             forAll(nMass, i)
317             {
318                 sDB.srhos()[i][celli] += nMass[i];
319             }
321             sDB.sms()[celli] += nMom;
322             sDB.shs()[celli] += m()*(nH + nPE + nKE);
323         }
325         if (onBoundary() && keepParcel)
326         {
327             if (face() > -1)
328             {
329                 if (isA<processorPolyPatch>(pbMesh[patch(face())]))
330                 {
331                     switchProcessor = true;
332                 }
333             }
334         }
335     }
337     return keepParcel;
341 void Foam::parcel::updateParcelProperties
343     const scalar dt,
344     spray& sDB,
345     const label celli,
346     const label facei
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
355     scalar W = 0.0;
356     for(label i = 0; i < Ns; i++)
357     {
358         W += sDB.composition().Y()[i][celli]/sDB.gasProperties()[i].W();
360     }
361     W = 1.0/W;
363     // Calculate the interpolated gas properties at the position of the parcel
364     vector Up = sDB.UInterpolator().interpolate(position(), celli, facei)
365         + Uturb();
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
371     scalar cpMix = 0.0;
372     for (label i = 0; i < Ns; i++)
373     {
374         cpMix += sDB.composition().Y()[i][celli]
375             *sDB.gasProperties()[i].Cp(T());
376     }
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);
383     forAll(addedMass, i)
384     {
385         addedMass[i] += sDB.srhos()[i][celli]*cellV;
386     }
388     scalar Tg  = Tg0 + dh/(cpMix*cellMass);
390     // Changed cut-off temperature for evaporation.  HJ, 27/Apr/2011
391     Tg = max(273.0, Tg);
393     scalarField Yfg(Nf, 0.0);
394     forAll(Yfg, i)
395     {
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);
400     }
402     scalar tauMomentum     = GREAT;
403     scalar tauHeatTransfer = GREAT;
404     scalarField tauEvaporation(Nf, GREAT);
405     scalarField tauBoiling(Nf, GREAT);
407     setRelaxationTimes
408     (
409         celli,
410         tauMomentum,
411         tauEvaporation,
412         tauHeatTransfer,
413         tauBoiling,
414         sDB,
415         rhog,
416         Up,
417         Tg,
418         pg,
419         Yfg,
420         m()*fuels.Y(X()),
421         dt
422     );
424     scalar timeRatio = dt/tauMomentum;
426     vector Ucorr = Up;
427     vector gcorr = sDB.g();
429     if (sDB.twoD())
430     {
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();
440     }
442     U() = (U() + timeRatio*Ucorr + gcorr*dt)/(1.0 + timeRatio);
444     if (sDB.twoD())
445     {
446         vector normal = n() ^ sDB.axisOfSymmetry();
447         normal /= mag(normal);
448         scalar dU = U() & normal;
449         U() -= dU*normal;
450     }
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);
457     scalarField mi(mi0);
459     scalar oldhg = 0.0;
460     for (label i=0; i<Nf; i++)
461     {
462         label j = sDB.liquidToGasIndex()[i];
463         oldhg += Yf0[i]*sDB.gasProperties()[j].Hs(T());
464     }
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;
473     scalar Tnew = T();
475     // NN.
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
481     // FPK.
482     // Iterate the temperature until convergedT and parcel mass
483     // is above minimumParcelMass
485     label n = 0;
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()))
491     {
492         n++;
493         // new characteristic times does not need to be calculated
494         // the first time
495         if (n > 1)
496         {
497             newMass = m();
498             newhg = 0.0;
499             scalarField Ynew(fuels.Y(X()));
501             for(label i = 0; i < Nf; i++)
502             {
503                 label j = sDB.liquidToGasIndex()[i];
505                 newhg += Ynew[i]*sDB.gasProperties()[j].Hs(Tnew);
506             }
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
517             Tg = max(273.0, Tg);
519             forAll (Yfg, i)
520             {
521                 label j = sDB.liquidToGasIndex()[i];
522                 const volScalarField& Yj = sDB.composition().Y()[j];
523                 scalar Yfg0 = Yj[celli];
525                 Yfg[i] =
526                     (Yfg0*cellMass + addedMass[i] + dm)/
527                     (addedMass[i] + cellMass + dm);
528             }
530             setRelaxationTimes
531             (
532                 celli,
533                 tauMomentum,
534                 tauEvaporation,
535                 tauHeatTransfer,
536                 tauBoiling,
537                 sDB,
538                 rhog,
539                 Up,
540                 Tg,
541                 pg,
542                 Yfg,
543                 m()*fuels.Y(X()),
544                 dt
545             );
547         }
549         scalar Taverage = TDroplet + (Tg - TDroplet)/3.0;
550         // for a liquid Cl \approx Cp
551         scalar liquidcL = sDB.fuels().cp(pg, TDroplet, X());
553         cpMix = 0.0;
554         for (label i = 0; i < Ns; i++)
555         {
556             if (sDB.isLiquidFuel()[i])
557             {
558                 label j = sDB.gasToLiquidIndex()[i];
559                 cpMix += Yfg[j]*sDB.gasProperties()[i].Cp(Taverage);
560             }
561             else
562             {
563                 scalar Y = sDB.composition().Y()[i][celli];
565                 cpMix += Y*sDB.gasProperties()[i].Cp(Taverage);
566             }
567         }
569         scalar evaporationSource = 0.0;
570         scalar z = 0.0;
571         scalar tauEvap = 0.0;
573         for (label i =0 ; i < Nf; i++)
574         {
575             tauEvap += X()[i]*fuels.properties()[i].W()/tauEvaporation[i];
576         }
577         tauEvap = fuels.W(X())/tauEvap;
580         if (sDB.evaporation().evaporation())
581         {
582             scalar hv = fuels.hl(pg, TDroplet, X());
583             evaporationSource = hv/liquidcL/tauEvap;
585             z = cpMix*tauHeatTransfer/liquidcL/tauEvap;
586         }
588         if (sDB.heatTransfer().heatTransfer())
589         {
590             scalar fCorrect =
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);
604             scalar Td = Tnew;
606             scalar pAtSurface = fuels.pv(pg, Td, X());
607             scalar pCompare = 0.999*pg;
608             scalar boiling = pAtSurface >= pCompare;
610             if (boiling)
611             {
612                 // can not go above boiling temperature
613                 scalar Terr = 1.0e-3;
614                 label n = 0;
615                 scalar dT = 1.0;
616                 scalar pOld = pAtSurface;
618                 while (dT > Terr)
619                 {
620                     n++;
621                     pAtSurface = fuels.pv(pg, Td, X());
622                     if ((pAtSurface < pCompare) && (pOld < pCompare))
623                     {
624                         Td += dT;
625                     }
626                     else
627                     {
628                         if ((pAtSurface > pCompare) && (pOld > pCompare))
629                         {
630                             Td -= dT;
631                         }
632                         else
633                         {
634                             dT *= 0.5;
635                             if ((pAtSurface > pCompare) && (pOld < pCompare))
636                             {
637                                 Td -= dT;
638                             }
639                             else
640                             {
641                                 Td += dT;
642                             }
643                         }
644                     }
646                     pOld = pAtSurface;
648 //                     if (debug)
649                     {
650                         if (n > 100)
651                         {
652                             Info << "n = " << n << ", T = " << Td << ", pv = " << pAtSurface << endl;
653                         }
654                     }
655                 }
656                 Tnew = Td;
657             }
658         }
660         // Evaporate droplet!
661         // if the droplet is NOT boiling use implicit scheme.
662         if (sDB.evaporation().evaporation())
663         {
664             for (label i = 0; i < Nf; i++)
665             {
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)
670                 {
671                     mi[i] = 0.0;
672                 }
673                 else
674                 {
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;
680                     if (!boiling)
681                     {
682                         scalar fr = dt/tauEvaporation[i];
683                         mi[i] = mi0[i]/(1.0 + fr);
684                     }
685                     else
686                     {
687                         scalar fr = dt/tauBoiling[i];
688                         mi[i] = mi0[i]/(1.0 + fr);
689                     }
690                 }
691             }
693             scalar mTot = sum(mi);
695             if (mTot > VSMALL)
696             {
697                 scalarField Ynew(mi/mTot);
698                 scalarField Xnew(sDB.fuels().X(Ynew));
699                 forAll(Xnew, i)
700                 {
701                     X()[i] = Xnew[i];
702                 }
703                 m() = mTot;
704             }
705             else
706             {
707                 m() = 0.0;
708             }
709         }
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;
719         // NN version
720 //         T() += 0.5*(Tnew - T());
721         // OF-vanilla version
722 //         T() = Tnew;
723         Tnew = T();
725         scalar rhod = fuels.rho(pg, T(), X());
726         d() = cbrt(6.0*m()/(Np*rhod*M_PI));
727     }
729     T() = Tnew;
730     scalar rhod = fuels.rho(pg, T(), X());
731     m() = sum(mi);
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 // ************************************************************************* //