1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright held by original author
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 \*---------------------------------------------------------------------------*/
30 #include "dragModel.H"
31 #include "evaporationModel.H"
32 #include "heatTransferModel.H"
33 #include "wallModel.H"
34 #include "wallPolyPatch.H"
35 #include "wedgePolyPatch.H"
36 #include "processorPolyPatch.H"
37 #include "basicMultiComponentMixture.H"
39 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
43 defineParticleTypeNameAndDebug(parcel, 0);
44 defineTemplateTypeNameAndDebug(Cloud<parcel>, 0);
47 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
51 const Cloud<parcel>& cloud,
52 const vector& position,
63 const scalar liquidCore,
64 const scalar injector,
68 const List<word>& liquidNames
71 Particle<parcel>(cloud, position, cellI),
84 liquidCore_(liquidCore),
94 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
96 bool Foam::parcel::move(spray& sDB)
98 const polyMesh& mesh = cloud().pMesh();
99 const polyBoundaryMesh& pbMesh = mesh.boundaryMesh();
101 const liquidMixture& fuels = sDB.fuels();
103 scalar deltaT = sDB.runTime().deltaT().value();
104 label Nf = fuels.components().size();
105 label Ns = sDB.composition().Y().size();
107 // Calculate the interpolated gas properties at the position of the parcel
108 vector Up = sDB.UInterpolator().interpolate(position(), cell())
110 scalar rhog = sDB.rhoInterpolator().interpolate(position(), cell());
111 scalar pg = sDB.pInterpolator().interpolate(position(), cell());
112 scalar Tg = sDB.TInterpolator().interpolate(position(), cell());
114 scalarField Yfg(Nf, 0.0);
116 scalar cpMixture = 0.0;
117 for(label i=0; i<Ns; i++)
119 const volScalarField& Yi = sDB.composition().Y()[i];
120 if (sDB.isLiquidFuel()[i])
122 label j = sDB.gasToLiquidIndex()[i];
123 scalar Yicelli = Yi[cell()];
126 cpMixture += Yi[cell()]*sDB.gasProperties()[i].Cp(Tg);
129 // Correct the gaseous temperature for evaporated fuel
131 scalar cellV = sDB.mesh().V()[cell()];
132 scalar cellMass = rhog*cellV;
133 Tg += sDB.shs()[cell()]/(cpMixture*cellMass);
135 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
138 scalar tauMomentum = GREAT;
139 scalar tauHeatTransfer = GREAT;
140 scalarField tauEvaporation(Nf, GREAT);
141 scalarField tauBoiling(Nf, GREAT);
143 bool keepParcel = true;
163 // set the end-time for the track
164 scalar tEnd = (1.0 - stepFraction())*deltaT;
166 // Set the maximum time step for this parcel
168 // FPK changes: avoid temperature-out-of-range errors
169 // in spray tracking. HJ, 13/Oct/2007
170 // tauEvaporation no longer multiplied by 1e20,
171 // to account for the evaporation timescale
181 mag(min(tauEvaporation)),
184 mag(tauHeatTransfer),
191 // prevent the number of subcycles from being too many
192 // (10 000 seems high enough)
193 dtMax = max(dtMax, 1.0e-4*tEnd);
195 bool switchProcessor = false;
196 vector planeNormal = vector::zero;
199 planeNormal = n() ^ sDB.axisOfSymmetry();
200 planeNormal /= mag(planeNormal);
203 // move the parcel until there is no 'timeLeft'
204 while (keepParcel && tEnd > SMALL && !switchProcessor)
206 // set the lagrangian time-step
207 scalar dt = min(dtMax, tEnd);
209 // remember which cell the parcel is in
210 // since this will change if a face is hit
211 label celli = cell();
212 scalar p = sDB.p()[celli];
214 // track parcel to face, or end of trajectory
217 // Track and adjust the time step if the trajectory
219 dt *= trackToFace(position() + dt*U_, sDB);
221 // Decrement the end-time acording to how much time the track took
224 // Set the current time-step fraction.
225 stepFraction() = 1.0 - tEnd/deltaT;
227 if (onBoundary()) // hit face
229 # include "boundaryTreatment.H"
233 if (keepParcel && sDB.twoD())
235 scalar z = position() & sDB.axisOfSymmetry();
236 vector r = position() - z*sDB.axisOfSymmetry();
240 correctNormal(sDB.axisOfSymmetry());
244 // **** calculate the lagrangian source terms ****
245 // First we get the 'old' properties.
246 // and then 'update' them to get the 'new'
248 // The difference is then added to the source terms.
250 scalar oRho = fuels.rho(p, T(), X());
251 scalarField oMass(Nf, 0.0);
253 scalar oTotMass = m();
254 scalarField oYf(fuels.Y(X()));
258 oMass[i] = m()*oYf[i];
259 label j = sDB.liquidToGasIndex()[i];
260 oHg += oYf[i]*sDB.gasProperties()[j].Hs(T());
263 vector oMom = m()*U();
264 scalar oHv = fuels.hl(p, T(), X());
265 scalar oH = oHg - oHv;
266 scalar oPE = (p - fuels.pv(p, T(), X()))/oRho;
268 // update the parcel properties (U, T, D)
269 updateParcelProperties
277 scalar nRho = fuels.rho(p, T(), X());
279 scalarField nMass(Nf, 0.0);
280 scalarField nYf(fuels.Y(X()));
284 nMass[i] = m()*nYf[i];
285 label j = sDB.liquidToGasIndex()[i];
286 nHg += nYf[i]*sDB.gasProperties()[j].Hs(T());
289 vector nMom = m()*U();
290 scalar nHv = fuels.hl(p, T(), X());
291 scalar nH = nHg - nHv;
292 scalar nPE = (p - fuels.pv(p, T(), X()))/nRho;
293 scalar nKE = 0.5*pow(mag(U()), 2);
295 // Update the Spray Source Terms
298 sDB.srhos()[i][celli] += oMass[i] - nMass[i];
301 sDB.sms()[celli] += oMom - nMom;
303 sDB.shs()[celli] += oTotMass*(oH + oPE) - m()*(nH + nPE + nKE);
305 // Remove evaporated mass from stripped mass
306 ms() -= ms()*(oTotMass - m())/oTotMass;
308 // Remove parcel if it is 'small'
310 // FPK changes: avoid temperature-out-of-range errors
311 // in spray tracking. HJ, 13/Oct/2007
312 if (m() < sDB.minimumParcelMass())
316 // ... and add the removed 'stuff' to the gas
319 sDB.srhos()[i][celli] += nMass[i];
322 sDB.sms()[celli] += nMom;
323 sDB.shs()[celli] += m()*(nH + nPE + nKE);
326 if (onBoundary() && keepParcel)
330 if (isA<processorPolyPatch>(pbMesh[patch(face())]))
332 switchProcessor = true;
342 void Foam::parcel::updateParcelProperties
350 const liquidMixture& fuels = sDB.fuels();
352 label Nf = sDB.fuels().components().size();
353 label Ns = sDB.composition().Y().size();
355 // calculate mean molecular weight
357 for(label i = 0; i < Ns; i++)
359 W += sDB.composition().Y()[i][celli]/sDB.gasProperties()[i].W();
364 // Calculate the interpolated gas properties at the position of the parcel
365 vector Up = sDB.UInterpolator().interpolate(position(), celli, facei)
367 scalar rhog = sDB.rhoInterpolator().interpolate(position(), celli, facei);
368 scalar pg = sDB.pInterpolator().interpolate(position(), celli, facei);
369 scalar Tg0 = sDB.TInterpolator().interpolate(position(), celli, facei);
371 // correct the gaseous temperature for evaporated fuel
373 for (label i = 0; i < Ns; i++)
375 cpMix += sDB.composition().Y()[i][celli]
376 *sDB.gasProperties()[i].Cp(T());
378 scalar cellV = sDB.mesh().V()[celli];
379 scalar rho = sDB.rho()[celli];
380 scalar cellMass = rho*cellV;
381 scalar dh = sDB.shs()[celli];
382 scalarField addedMass(Nf, 0.0);
386 addedMass[i] += sDB.srhos()[i][celli]*cellV;
389 scalar Tg = Tg0 + dh/(cpMix*cellMass);
391 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
394 scalarField Yfg(Nf, 0.0);
397 label j = sDB.liquidToGasIndex()[i];
398 const volScalarField& Yj = sDB.composition().Y()[j];
399 scalar Yfg0 = Yj[celli];
400 Yfg[i] = (Yfg0*cellMass + addedMass[i])/(addedMass[i] + cellMass);
403 scalar tauMomentum = GREAT;
404 scalar tauHeatTransfer = GREAT;
405 scalarField tauEvaporation(Nf, GREAT);
406 scalarField tauBoiling(Nf, GREAT);
425 scalar timeRatio = dt/tauMomentum;
428 vector gcorr = sDB.g();
432 // remove the tangential velocity component
433 scalar v1 = Up & sDB.axisOfSymmetry();
434 scalar v2 = Up & n();
435 Ucorr = v1*sDB.axisOfSymmetry() + v2*n();
437 // Remove the tangential gravity component
438 scalar g1 = gcorr & sDB.axisOfSymmetry();
439 scalar g2 = gcorr & n();
440 gcorr = g1*sDB.axisOfSymmetry() + g2*n();
443 U() = (U() + timeRatio*Ucorr + gcorr*dt)/(1.0 + timeRatio);
447 vector normal = n() ^ sDB.axisOfSymmetry();
448 normal /= mag(normal);
449 scalar dU = U() & normal;
453 scalar TDroplet = T();
454 scalar oldDensity = fuels.rho(pg, T(), X());
455 scalar oldMass = m();
456 scalarField Yf0(fuels.Y(X()));
457 scalarField mi0(m()*Yf0);
461 for (label i=0; i<Nf; i++)
463 label j = sDB.liquidToGasIndex()[i];
464 oldhg += Yf0[i]*sDB.gasProperties()[j].Hs(T());
467 scalar oldhv = fuels.hl(pg, T(), X());
468 scalar Np = N(oldDensity);
470 scalar newDensity = oldDensity;
471 scalar newMass = oldMass;
472 scalar newhg = oldhg;
473 scalar newhv = oldhv;
478 // first calculate the new temperature and droplet mass,
479 // then calculate the energy source and correct the
480 // gaseous temperature, Tg, and mass fraction, Yfg,
481 // to calculate the new properties for the parcel
482 // This procedure seems to be more stable
484 // Iterate the temperature until convergedT and parcel mass
485 // is above minimumParcelMass
488 bool convergedT = false;
490 // FPK changes: avoid temperature-out-of-range errors
491 // in spray tracking. HJ, 13/Oct/2007
492 while ((!convergedT) && (m() > sDB.minimumParcelMass()))
495 // new characteristic times does not need to be calculated
499 newDensity = fuels.rho(pg, Tnew, X());
502 scalarField Ynew(fuels.Y(X()));
504 for(label i = 0; i < Nf; i++)
506 label j = sDB.liquidToGasIndex()[i];
508 newhg += Ynew[i]*sDB.gasProperties()[j].Hs(Tnew);
511 newhv = fuels.hl(pg, Tnew, X());
513 scalar dm = oldMass - newMass;
514 scalar dhNew = oldMass*(oldhg - oldhv) - newMass*(newhg - newhv);
516 // Prediction of new gaseous temperature and fuel mass fraction
517 Tg = Tg0 + (dh + dhNew)/(cpMix*cellMass);
519 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
524 label j = sDB.liquidToGasIndex()[i];
525 const volScalarField& Yj = sDB.composition().Y()[j];
526 scalar Yfg0 = Yj[celli];
529 (Yfg0*cellMass + addedMass[i] + dm)/
530 (addedMass[i] + cellMass + dm);
552 scalar Taverage = TDroplet + (Tg - TDroplet)/3.0;
553 // for a liquid Cl \approx Cp
554 scalar liquidcL = sDB.fuels().cp(pg, TDroplet, X());
557 for (label i = 0; i < Ns; i++)
559 if (sDB.isLiquidFuel()[i])
561 label j = sDB.gasToLiquidIndex()[i];
562 cpMix += Yfg[j]*sDB.gasProperties()[i].Cp(Taverage);
566 scalar Y = sDB.composition().Y()[i][celli];
568 cpMix += Y*sDB.gasProperties()[i].Cp(Taverage);
572 scalar evaporationSource = 0.0;
574 scalar tauEvap = 0.0;
576 for (label i =0 ; i < Nf; i++)
578 tauEvap += X()[i]*fuels.properties()[i].W()/tauEvaporation[i];
580 tauEvap = fuels.W(X())/tauEvap;
583 if (sDB.evaporation().evaporation())
585 scalar hv = fuels.hl(pg, TDroplet, X());
586 evaporationSource = hv/liquidcL/tauEvap;
588 z = cpMix*tauHeatTransfer/liquidcL/tauEvap;
591 if (sDB.heatTransfer().heatTransfer())
594 sDB.heatTransfer().fCorrection(z)/tauHeatTransfer;
596 Tnew = (TDroplet + dt*(fCorrect*Tg - evaporationSource))
597 /(1.0 + dt*fCorrect);
599 // Prevent droplet temperature to go above critial value
600 Tnew = min(Tnew, fuels.Tc(X()));
602 // Prevent droplet temperature to go too low
603 // Mainly a numerical stability issue
605 // Changed cut-off temperature for evaporation. HJ, 27/Apr/2011
606 Tnew = max(273.0, Tnew);
609 scalar pAtSurface = fuels.pv(pg, Td, X());
610 scalar pCompare = 0.999*pg;
611 scalar boiling = pAtSurface >= pCompare;
615 // can not go above boiling temperature
616 scalar Terr = 1.0e-3;
619 scalar pOld = pAtSurface;
624 pAtSurface = fuels.pv(pg, Td, X());
625 if ((pAtSurface < pCompare) && (pOld < pCompare))
631 if ((pAtSurface > pCompare) && (pOld > pCompare))
638 if ((pAtSurface > pCompare) && (pOld < pCompare))
655 Info << "n = " << n << ", T = " << Td << ", pv = " << pAtSurface << endl;
663 // Evaporate droplet!
664 // if the droplet is NOT boiling use implicit scheme.
665 if (sDB.evaporation().evaporation())
667 for (label i = 0; i < Nf; i++)
669 // Immediately evaporate mass that has reached
670 // critical condition
671 // Bug fix for 32- and 64-bit consistency: Tommaso Lucchini
672 if (mag(Tnew - fuels.Tc(X())) < 1e-10)
678 scalar Td = min(Tnew, 0.999*fuels.properties()[i].Tc());
680 scalar pAtSurface = fuels.properties()[i].pv(pg, Td);
681 scalar boiling = pAtSurface >= 0.999*pg;
685 scalar fr = dt/tauEvaporation[i];
686 mi[i] = mi0[i]/(1.0 + fr);
690 scalar fr = dt/tauBoiling[i];
691 mi[i] = mi0[i]/(1.0 + fr);
696 scalar mTot = sum(mi);
700 scalarField Ynew(mi/mTot);
701 scalarField Xnew(sDB.fuels().X(Ynew));
714 // FPK changes: avoid temperature-out-of-range errors
715 // in spray tracking. HJ, 13/Oct/2007
716 const scalar relaxfac = sDB.sprayRelaxFactor();
718 convergedT = mag(Tnew - T()) < 0.1 || n > sDB.sprayIterate();
720 // FPK version, 13/Oct/2007
721 T() = T()*(1 - relaxfac) + Tnew*relaxfac;
723 // T() += 0.5*(Tnew - T());
724 // OF-vanilla version
728 scalar rhod = fuels.rho(pg, T(), X());
729 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
733 scalar rhod = fuels.rho(pg, T(), X());
735 d() = cbrt(6.0*m()/(Np*rhod*M_PI));
739 void Foam::parcel::transformProperties(const tensor& T)
741 U_ = transform(T, U_);
745 void Foam::parcel::transformProperties(const vector&)
749 // ************************************************************************* //