SprayParcel.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2023 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
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.
15 
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
19  for more details.
20 
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/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "SprayParcel.H"
27 #include "forceSuSp.H"
28 #include "CompositionModel.H"
29 #include "AtomisationModel.H"
30 
31 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
32 
33 template<class ParcelType>
34 template<class TrackCloudType>
36 (
37  TrackCloudType& cloud,
38  trackingData& td
39 )
40 {
41  ParcelType::setCellValues(cloud, td);
42 }
43 
44 
45 template<class ParcelType>
46 template<class TrackCloudType>
48 (
49  TrackCloudType& cloud,
50  trackingData& td,
51  const scalar dt
52 )
53 {
54  ParcelType::cellValueSourceCorrection(cloud, td, dt);
55 }
56 
57 
58 template<class ParcelType>
59 template<class TrackCloudType>
61 (
62  TrackCloudType& cloud,
63  trackingData& td,
64  const scalar dt
65 )
66 {
67  typedef typename TrackCloudType::thermoCloudType thermoCloudType;
69  cloud.composition();
70 
71  // Check if parcel belongs to liquid core
72  if (liquidCore() > 0.5)
73  {
74  // Liquid core parcels should not experience coupled forces
75  cloud.forces().setCalcCoupled(false);
76  }
77 
78  // Get old mixture composition
79  scalarField X0(composition.liquids().X(this->Y()));
80 
81  // Check if we have critical or boiling conditions
82  scalar TMax = composition.liquids().Tc(X0);
83  const scalar T0 = this->T();
84  const scalar pc0 = td.pc();
85  if (composition.liquids().pv(pc0, T0, X0) >= pc0*0.999)
86  {
87  // Set TMax to boiling temperature
88  TMax = composition.liquids().pvInvert(pc0, X0);
89  }
90 
91  // Set the maximum temperature limit
92  cloud.constProps().setTMax(TMax);
93 
94  // Store the parcel properties
95  this->Cp() = composition.liquids().Cp(pc0, T0, X0);
96  sigma_ = composition.liquids().sigma(pc0, T0, X0);
97  const scalar rho0 = composition.liquids().rho(pc0, T0, X0);
98  this->rho() = rho0;
99  const scalar mass0 = this->mass();
100  mu_ = composition.liquids().mu(pc0, T0, X0);
101 
102  ParcelType::calc(cloud,td, dt);
103 
104  if (td.keepParticle)
105  {
106  // Reduce the stripped parcel mass due to evaporation
107  // assuming the number of particles remains unchanged
108  this->ms() -= this->ms()*(mass0 - this->mass())/mass0;
109 
110  // Update Cp, sigma, density and diameter due to change in temperature
111  // and/or composition
112  scalar T1 = this->T();
113  scalarField X1(composition.liquids().X(this->Y()));
114 
115  this->Cp() = composition.liquids().Cp(td.pc(), T1, X1);
116 
117  sigma_ = composition.liquids().sigma(td.pc(), T1, X1);
118 
119  scalar rho1 = composition.liquids().rho(td.pc(), T1, X1);
120  this->rho() = rho1;
121 
122  mu_ = composition.liquids().mu(td.pc(), T1, X1);
123 
124  scalar d1 = this->d()*cbrt(rho0/rho1);
125  this->d() = d1;
126 
127  if (liquidCore() > 0.5)
128  {
129  calcAtomisation(cloud, td, dt);
130 
131  // Preserve the total mass/volume by increasing the number of
132  // particles in parcels due to breakup
133  scalar d2 = this->d();
134  this->nParticle() *= pow3(d1/d2);
135  }
136  else
137  {
138  calcBreakup(cloud, td, dt);
139  }
140  }
141 
142  // Restore coupled forces
143  cloud.forces().setCalcCoupled(true);
144 }
145 
146 
147 template<class ParcelType>
148 template<class TrackCloudType>
150 (
151  TrackCloudType& cloud,
152  trackingData& td,
153  const scalar dt
154 )
155 {
156  if (injector_ == -1) return;
157 
158  typedef typename TrackCloudType::thermoCloudType thermoCloudType;
160  cloud.composition();
161 
162  typedef typename TrackCloudType::sprayCloudType sprayCloudType;
163  const AtomisationModel<sprayCloudType>& atomisation =
164  cloud.atomisation();
165 
166  // Average molecular weight of carrier mix - assumes perfect gas
167  scalar Wc = td.rhoc()*RR*td.Tc()/td.pc();
168  scalar R = RR/Wc;
169  scalar Tav = atomisation.Taverage(this->T(), td.Tc());
170 
171  // Calculate average gas density based on average temperature
172  scalar rhoAv = td.pc()/(R*Tav);
173 
174  scalar soi = cloud.injectors()[injector_].timeStart();
175  scalar currentTime = cloud.db().time().value();
176  const vector& pos = this->position(td.mesh);
177  const vector& injectionPos = this->position0();
178 
179  // Disregard the continuous phase when calculating the relative velocity
180  // (in line with the deactivated coupled assumption)
181  scalar Urel = mag(this->U());
182 
183  scalar t0 = max(0.0, currentTime - this->age() - soi);
184  scalar t1 = min(t0 + dt, cloud.injectors()[injector_].timeEnd() - soi);
185 
186  scalar rho0 = mass0_/this->volume(d0_);
187 
188  // This should be the vol flow rate from when the parcel was injected
189  scalar volFlowRate = cloud.injectors()[injector_].massToInject(t0, t1)/rho0;
190 
191  scalar chi = 0.0;
192  if (atomisation.calcChi())
193  {
194  chi = this->chi(cloud, td, composition.liquids().X(this->Y()));
195  }
196 
197  atomisation.update
198  (
199  dt,
200  this->d(),
201  this->liquidCore(),
202  this->tc(),
203  this->rho(),
204  mu_,
205  sigma_,
206  volFlowRate,
207  rhoAv,
208  Urel,
209  pos,
210  injectionPos,
211  cloud.pAmbient(),
212  chi,
213  cloud.rndGen()
214  );
215 }
216 
217 
218 template<class ParcelType>
219 template<class TrackCloudType>
221 (
222  TrackCloudType& cloud,
223  trackingData& td,
224  const scalar dt
225 )
226 {
227  const typename TrackCloudType::parcelType& p =
228  static_cast<const typename TrackCloudType::parcelType&>(*this);
229  typename TrackCloudType::parcelType::trackingData& ttd =
230  static_cast<typename TrackCloudType::parcelType::trackingData&>(td);
231 
232  const typename TrackCloudType::forceType& forces = cloud.forces();
233 
234  if (cloud.breakup().solveOscillationEq())
235  {
236  solveTABEq(cloud, td, dt);
237  }
238 
239  // Average molecular weight of carrier mix - assumes perfect gas
240  scalar Wc = td.rhoc()*RR*td.Tc()/td.pc();
241  scalar R = RR/Wc;
242  scalar Tav = cloud.atomisation().Taverage(this->T(), td.Tc());
243 
244  // Calculate average gas density based on average temperature
245  scalar rhoAv = td.pc()/(R*Tav);
246  scalar muAv = td.muc();
247  vector Urel = this->U() - td.Uc();
248  scalar Urmag = mag(Urel);
249  scalar Re = this->Re(rhoAv, this->U(), td.Uc(), this->d(), muAv);
250 
251  const scalar mass = p.mass();
252  const forceSuSp Fcp = forces.calcCoupled(p, ttd, dt, mass, Re, muAv);
253  const forceSuSp Fncp = forces.calcNonCoupled(p, ttd, dt, mass, Re, muAv);
254  this->tMom() = mass/(Fcp.Sp() + Fncp.Sp());
255 
256  const vector g = cloud.g().value();
257 
258  scalar parcelMassChild = 0.0;
259  scalar dChild = 0.0;
260  if
261  (
262  cloud.breakup().update
263  (
264  dt,
265  g,
266  this->d(),
267  this->tc(),
268  this->ms(),
269  this->nParticle(),
270  this->KHindex(),
271  this->y(),
272  this->yDot(),
273  this->d0(),
274  this->rho(),
275  mu_,
276  sigma_,
277  this->U(),
278  rhoAv,
279  muAv,
280  Urel,
281  Urmag,
282  this->tMom(),
283  injector_,
284  dChild,
285  parcelMassChild
286  )
287  )
288  {
289  scalar Re = rhoAv*Urmag*dChild/muAv;
290 
291  // Add child parcel as copy of parent
293  child->origId() = this->getNewParticleID();
294  child->d() = dChild;
295  child->d0() = dChild;
296  const scalar massChild = child->mass();
297  child->mass0() = massChild;
298  child->nParticle() = parcelMassChild/massChild;
299 
300  const forceSuSp Fcp =
301  forces.calcCoupled(*child, ttd, dt, massChild, Re, muAv);
302  const forceSuSp Fncp =
303  forces.calcNonCoupled(*child, ttd, dt, massChild, Re, muAv);
304 
305  child->age() = 0.0;
306  child->liquidCore() = 0.0;
307  child->KHindex() = 1.0;
308  child->y() = cloud.breakup().y0();
309  child->yDot() = cloud.breakup().yDot0();
310  child->tc() = 0.0;
311  child->ms() = -great;
312  child->injector() = this->injector();
313  child->tMom() = massChild/(Fcp.Sp() + Fncp.Sp());
314  child->calcDispersion(cloud, td, dt);
315 
316  cloud.addParticle(child);
317  }
318 }
319 
320 
321 template<class ParcelType>
322 template<class TrackCloudType>
324 (
325  TrackCloudType& cloud,
326  trackingData& td,
327  const scalarField& X
328 ) const
329 {
330  // Modifications to take account of the flash boiling on primary break-up
331 
332  typedef typename TrackCloudType::thermoCloudType thermoCloudType;
334  cloud.composition();
335 
336  scalar chi = 0.0;
337  scalar T0 = this->T();
338  scalar p0 = td.pc();
339  scalar pAmb = cloud.pAmbient();
340 
341  scalar pv = composition.liquids().pv(p0, T0, X);
342 
343  forAll(composition.liquids(), i)
344  {
345  if (pv >= 0.999*pAmb)
346  {
347  // Liquid is boiling - calc boiling temperature
348 
349  const liquidProperties& liq = composition.liquids().properties()[i];
350  scalar TBoil = liq.pvInvert(p0);
351 
352  scalar hl = liq.hl(pAmb, TBoil);
353  scalar iTp = liq.Ha(pAmb, T0) - pAmb/liq.rho(pAmb, T0);
354  scalar iTb = liq.Ha(pAmb, TBoil) - pAmb/liq.rho(pAmb, TBoil);
355 
356  chi += X[i]*(iTp - iTb)/hl;
357  }
358  }
359 
360  chi = min(1.0, max(chi, 0.0));
361 
362  return chi;
363 }
364 
365 
366 template<class ParcelType>
367 template<class TrackCloudType>
369 (
370  TrackCloudType& cloud,
371  trackingData& td,
372  const scalar dt
373 )
374 {
375  const scalar& TABCmu = cloud.breakup().TABCmu();
376  const scalar& TABtwoWeCrit = cloud.breakup().TABtwoWeCrit();
377  const scalar& TABComega = cloud.breakup().TABComega();
378 
379  scalar r = 0.5*this->d();
380  scalar r2 = r*r;
381  scalar r3 = r*r2;
382 
383  // Inverse of characteristic viscous damping time
384  scalar rtd = 0.5*TABCmu*mu_/(this->rho()*r2);
385 
386  // Oscillation frequency (squared)
387  scalar omega2 = TABComega*sigma_/(this->rho()*r3) - rtd*rtd;
388 
389  if (omega2 > 0)
390  {
391  scalar omega = sqrt(omega2);
392  scalar We =
393  this->We(td.rhoc(), this->U(), td.Uc(), r, sigma_)/TABtwoWeCrit;
394 
395  // Initial values for y and yDot
396  scalar y0 = this->y() - We;
397  scalar yDot0 = this->yDot() + y0*rtd;
398 
399  // Update distortion parameters
400  scalar c = cos(omega*dt);
401  scalar s = sin(omega*dt);
402  scalar e = exp(-rtd*dt);
403 
404  this->y() = We + e*(y0*c + (yDot0/omega)*s);
405  this->yDot() = (We - this->y())*rtd + e*(yDot0*c - omega*y0*s);
406  }
407  else
408  {
409  // Reset distortion parameters
410  this->y() = 0;
411  this->yDot() = 0;
412  }
413 }
414 
415 
416 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
417 
418 template<class ParcelType>
420 :
421  ParcelType(p),
422  d0_(p.d0_),
423  position0_(p.position0_),
424  sigma_(p.sigma_),
425  mu_(p.mu_),
426  liquidCore_(p.liquidCore_),
427  KHindex_(p.KHindex_),
428  y_(p.y_),
429  yDot_(p.yDot_),
430  tc_(p.tc_),
431  ms_(p.ms_),
432  injector_(p.injector_),
433  tMom_(p.tMom_)
434 {}
435 
436 
437 // * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
438 
439 #include "SprayParcelIO.C"
440 
441 
442 // ************************************************************************* //
scalar Cp(const scalar p, const scalar T) const
Definition: EtoHthermo.H:2
scalar y
#define R(A, B, C, D, E, F, K, M)
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
Templated atomisation model class.
virtual void update(const scalar dt, scalar &d, scalar &liquidCore, scalar &tc, const scalar rho, const scalar mu, const scalar sigma, const scalar volFlowRate, const scalar rhoAv, const scalar Urel, const vector &pos, const vector &injectionPos, const scalar pAmbient, const scalar chi, Random &rndGen) const =0
virtual bool calcChi() const =0
Flag to indicate if chi needs to be calculated.
scalar Taverage(const scalar &Tliq, const scalar &Tc) const
Average temperature calculation.
Templated reacting parcel composition model class Consists of carrier species (via thermo package),...
const liquidMixtureProperties & liquids() const
Return the global (additional) liquids.
const objectRegistry & db() const
Return the local objectRegistry.
Definition: IOobject.C:312
Reaching spray parcel, with added functionality for atomisation and breakup.
Definition: SprayParcel.H:69
scalar chi(TrackCloudType &cloud, trackingData &td, const scalarField &X) const
Calculate the chi-factor for flash-boiling for the.
SprayParcel(const polyMesh &mesh, const barycentric &coordinates, const label celli, const label tetFacei, const label tetPti, const label facei)
Construct from mesh, coordinates and topology.
Definition: SprayParcelI.H:110
scalar liquidCore() const
Return const access to liquid core.
Definition: SprayParcelI.H:217
label injector() const
Return const access to injector id.
Definition: SprayParcelI.H:259
scalar tMom() const
Return const access to momentum relaxation time.
Definition: SprayParcelI.H:266
void cellValueSourceCorrection(TrackCloudType &cloud, trackingData &td, const scalar dt)
Correct cell values using latest transfer information.
Definition: SprayParcel.C:48
void calcAtomisation(TrackCloudType &cloud, trackingData &td, const scalar dt)
Correct parcel properties according to atomisation model.
Definition: SprayParcel.C:150
scalar yDot() const
Return const access to rate of change of spherical deviation.
Definition: SprayParcelI.H:238
scalar d0() const
Return const access to initial droplet diameter.
Definition: SprayParcelI.H:182
ParcelType::trackingData trackingData
Use base tracking data.
Definition: SprayParcel.H:139
void solveTABEq(TrackCloudType &cloud, trackingData &td, const scalar dt)
Solve the TAB equation.
Definition: SprayParcel.C:369
scalar KHindex() const
Return const access to Kelvin-Helmholtz breakup index.
Definition: SprayParcelI.H:224
scalar tc() const
Return const access to atomisation characteristic time.
Definition: SprayParcelI.H:245
scalar mass0() const
Return const access to initial mass [kg].
Definition: SprayParcelI.H:189
void setCellValues(TrackCloudType &cloud, trackingData &td)
Set cell values.
Definition: SprayParcel.C:36
void calcBreakup(TrackCloudType &cloud, trackingData &td, const scalar dt)
Correct parcel properties according to breakup model.
Definition: SprayParcel.C:221
scalar ms() const
Return const access to stripped parcel mass.
Definition: SprayParcelI.H:252
scalar y() const
Return const access to spherical deviation.
Definition: SprayParcelI.H:231
void calc(TrackCloudType &cloud, trackingData &td, const scalar dt)
Update parcel properties over the time interval.
Definition: SprayParcel.C:61
A cloud is a collection of lagrangian particles.
Definition: cloud.H:55
const Type & value() const
Return const reference to value.
Helper container for force Su and Sp terms.
Definition: forceSuSp.H:64
scalar Sp() const
Return const access to the implicit coefficient [kg/s].
Definition: forceSuSpI.H:62
scalar Cp(const scalar p, const scalar T, const scalarField &X) const
Calculate the mixture heat capacity [J/kg/K].
The thermophysical properties of a liquid.
virtual scalar hl(scalar p, scalar T) const =0
Heat of vapourisation [J/kg].
virtual scalar pvInvert(scalar p) const
Invert the vapour pressure relationship to retrieve the.
virtual scalar Ha(scalar p, scalar T) const =0
Liquid absolute enthalpy [J/kg].
const Time & time() const
Return time.
virtual scalar rho(scalar p, scalar T) const =0
Liquid density [kg/m^3].
const scalar omega
gmvFile<< "tracers "<< particles.size()<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().x()<< " ";}gmvFile<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().y()<< " ";}gmvFile<< nl;forAllConstIter(Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.name(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
U
Definition: pEqn.H:72
const dimensionedScalar e
Elementary charge.
const dimensionedScalar RR
Universal gas constant: default SI units: [J/kmol/K].
const dimensionedScalar c
Speed of light in a vacuum.
dimensionedScalar pos(const dimensionedScalar &ds)
dimensionedScalar exp(const dimensionedScalar &ds)
dimensionedScalar pow3(const dimensionedScalar &ds)
dimensionedScalar y0(const dimensionedScalar &ds)
dimensionedScalar sin(const dimensionedScalar &ds)
layerAndWeight min(const layerAndWeight &a, const layerAndWeight &b)
dimensionedScalar sqrt(const dimensionedScalar &ds)
dimensioned< scalar > mag(const dimensioned< Type > &)
layerAndWeight max(const layerAndWeight &a, const layerAndWeight &b)
dimensionedScalar cbrt(const dimensionedScalar &ds)
void T(FieldField< Field, Type > &f1, const FieldField< Field, Type > &f2)
dimensionedScalar cos(const dimensionedScalar &ds)
scalarField Re(const UList< complex > &cf)
Definition: complexFields.C:97
scalar rho0
scalarList X0(nSpecie, 0.0)
basicSpecieMixture & composition
volScalarField & p
scalar T0
Definition: createFields.H:22