ReactingParcel.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 "ReactingParcel.H"
27 #include "specie.H"
28 #include "CompositionModel.H"
29 #include "PhaseChangeModel.H"
30 #include "mathematicalConstants.H"
31 
32 using namespace Foam::constant::mathematical;
33 
34 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
35 
36 template<class ParcelType>
37 template<class TrackCloudType>
39 (
40  TrackCloudType& cloud,
41  trackingData& td,
42  const scalar dt,
43  const scalar Re,
44  const scalar Pr,
45  const scalar Ts,
46  const scalar nus,
47  const scalar d,
48  const scalar T,
49  const scalar mass,
50  const label idPhase,
51  const scalar YPhase,
52  const scalarField& Y,
53  scalarField& dMassPC,
54  scalar& Sh,
55  scalar& N,
56  scalar& NCpW,
57  scalarField& Cs
58 )
59 {
60  typedef typename TrackCloudType::thermoCloudType thermoCloudType;
62  cloud.composition();
63 
64  typedef typename TrackCloudType::reactingCloudType reactingCloudType;
65  PhaseChangeModel<reactingCloudType>& phaseChange = cloud.phaseChange();
66 
67  if (YPhase < small)
68  {
69  return;
70  }
71 
72  scalarField X(composition.liquids().X(Y));
73 
74  scalar Tvap = phaseChange.Tvap(X);
75 
76  if (T < Tvap)
77  {
78  return;
79  }
80 
81  const scalar TMax = phaseChange.TMax(td.pc(), X);
82  const scalar Tdash = min(T, TMax);
83  const scalar Tsdash = min(Ts, TMax);
84 
85  const typename TrackCloudType::parcelType& p =
86  static_cast<const typename TrackCloudType::parcelType&>(*this);
87  typename TrackCloudType::parcelType::trackingData& ttd =
88  static_cast<typename TrackCloudType::parcelType::trackingData&>(td);
89 
90  // Calculate mass transfer due to phase change
91  phaseChange.calculate
92  (
93  p,
94  ttd,
95  dt,
96  Re,
97  Pr,
98  d,
99  nus,
100  Tdash,
101  Tsdash,
102  td.pc(),
103  td.Tc(),
104  X,
105  dMassPC
106  );
107 
108  // Limit phase change mass by availability of each specie
109  dMassPC = min(mass*YPhase*Y, dMassPC);
110 
111  const scalar dMassTot = sum(dMassPC);
112 
113  // Add to cumulative phase change mass
114  phaseChange.addToPhaseChangeMass(this->nParticle_*dMassTot);
115 
116  forAll(dMassPC, i)
117  {
118  const label cid = composition.localToCarrierId(idPhase, i);
119 
120  const scalar dh = phaseChange.dh(cid, i, td.pc(), Tdash);
121  Sh -= dMassPC[i]*dh/dt;
122  }
123 
124 
125  // Update molar emissions
126  if (cloud.heatTransfer().BirdCorrection())
127  {
128  // Average molecular weight of carrier mix - assumes perfect gas
129  const scalar Wc = td.rhoc()*RR*td.Tc()/td.pc();
130 
131  forAll(dMassPC, i)
132  {
133  const label cid = composition.localToCarrierId(idPhase, i);
134 
135  const scalar Cp = composition.carrier().Cp(cid, td.pc(), Tsdash);
136  const scalar W = composition.carrier().Wi(cid);
137  const scalar Ni = dMassPC[i]/(this->areaS(d)*dt*W);
138 
139  const scalar Dab =
140  composition.liquids().properties()[i].D(td.pc(), Tsdash, Wc);
141 
142  // Molar flux of species coming from the particle (kmol/m^2/s)
143  N += Ni;
144 
145  // Sum of Ni*Cpi*Wi of emission species
146  NCpW += Ni*Cp*W;
147 
148  // Concentrations of emission species
149  Cs[cid] += Ni*d/(2.0*Dab);
150  }
151  }
152 }
153 
154 
155 template<class ParcelType>
157 (
158  const scalar mass0,
159  const scalarField& dMass,
160  scalarField& Y
161 ) const
162 {
163  scalar mass1 = mass0 - sum(dMass);
164 
165  // only update the mass fractions if the new particle mass is finite
166  if (mass1 > rootVSmall)
167  {
168  forAll(Y, i)
169  {
170  Y[i] = (Y[i]*mass0 - dMass[i])/mass1;
171  }
172  }
173 
174  return mass1;
175 }
176 
177 
178 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
179 
180 template<class ParcelType>
182 (
184 )
185 :
186  ParcelType(p),
187  Y_(p.Y_)
188 {}
189 
190 
191 // * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * * //
192 
193 template<class ParcelType>
194 template<class TrackCloudType>
196 (
197  TrackCloudType& cloud,
198  trackingData& td
199 )
200 {
201  ParcelType::setCellValues(cloud, td);
202 
203  td.pc() = td.pInterp().interpolate
204  (
205  this->coordinates(),
206  this->currentTetIndices(td.mesh)
207  );
208 
209  if (td.pc() < cloud.constProps().pMin())
210  {
211  if (debug)
212  {
214  << "Limiting observed pressure in cell " << this->cell()
215  << " to " << cloud.constProps().pMin() << nl << endl;
216  }
217 
218  td.pc() = cloud.constProps().pMin();
219  }
220 }
221 
222 
223 template<class ParcelType>
224 template<class TrackCloudType>
226 (
227  TrackCloudType& cloud,
228  trackingData& td,
229  const scalar dt
230 )
231 {
232  scalar addedMass = 0.0;
233  scalar maxMassI = 0.0;
234  forAll(cloud.rhoTrans(), i)
235  {
236  scalar dm = cloud.rhoTrans(i)[this->cell()];
237  maxMassI = max(maxMassI, mag(dm));
238  addedMass += dm;
239  }
240 
241  if (maxMassI < rootVSmall)
242  {
243  return;
244  }
245 
246  const scalar massCell = this->massCell(td);
247 
248  td.rhoc() += addedMass/cloud.pMesh().cellVolumes()[this->cell()];
249 
250  const scalar massCellNew = massCell + addedMass;
251  td.Uc() = (td.Uc()*massCell + cloud.UTransRef()[this->cell()])/massCellNew;
252 
253  scalar CpEff = 0.0;
254  forAll(cloud.rhoTrans(), i)
255  {
256  scalar Y = cloud.rhoTrans(i)[this->cell()]/addedMass;
257  CpEff += Y*cloud.composition().carrier().Cp(i, td.pc(), td.Tc());
258  }
259 
260  const scalar Cpc = td.CpInterp().psi()[this->cell()];
261  td.Cpc() = (massCell*Cpc + addedMass*CpEff)/massCellNew;
262 
263  td.Tc() += cloud.hsTransRef()[this->cell()]/(td.Cpc()*massCellNew);
264 
265  if (td.Tc() < cloud.constProps().TMin())
266  {
267  if (debug)
268  {
270  << "Limiting observed temperature in cell " << this->cell()
271  << " to " << cloud.constProps().TMin() << nl << endl;
272  }
273 
274  td.Tc() = cloud.constProps().TMin();
275  }
276 }
277 
278 
279 template<class ParcelType>
280 template<class TrackCloudType>
282 (
283  TrackCloudType& cloud,
284  trackingData& td,
285  const scalar T,
286  const scalarField& Cs,
287  scalar& rhos,
288  scalar& mus,
289  scalar& Prs,
290  scalar& kappas
291 )
292 {
293  // No correction if total concentration of emitted species is small
294  if (!cloud.heatTransfer().BirdCorrection() || (sum(Cs) < small))
295  {
296  return;
297  }
298 
299  const basicSpecieMixture& carrier = cloud.composition().carrier();
300 
301  // Far field carrier molar fractions
302  scalarField Xinf(carrier.species().size());
303 
304  forAll(Xinf, i)
305  {
306  Xinf[i] = carrier.Y(i)[this->cell()]/carrier.Wi(i);
307  }
308  Xinf /= sum(Xinf);
309 
310  // Molar fraction of far field species at particle surface
311  const scalar Xsff = 1.0 - min(sum(Cs)*RR*this->T_/td.pc(), 1.0);
312 
313  // Surface carrier total molar concentration
314  const scalar CsTot = td.pc()/(RR*this->T_);
315 
316  // Surface carrier composition (molar fraction)
317  scalarField Xs(Xinf.size());
318 
319  // Surface carrier composition (mass fraction)
320  scalarField Ys(Xinf.size());
321 
322  forAll(Xs, i)
323  {
324  // Molar concentration of species at particle surface
325  const scalar Csi = Cs[i] + Xsff*Xinf[i]*CsTot;
326 
327  Xs[i] = (2.0*Csi + Xinf[i]*CsTot)/3.0;
328  Ys[i] = Xs[i]*carrier.Wi(i);
329  }
330  Xs /= sum(Xs);
331  Ys /= sum(Ys);
332 
333 
334  rhos = 0;
335  mus = 0;
336  kappas = 0;
337  scalar Cps = 0;
338  scalar sumYiSqrtW = 0;
339  scalar sumYiCbrtW = 0;
340 
341  forAll(Ys, i)
342  {
343  const scalar W = carrier.Wi(i);
344  const scalar sqrtW = sqrt(W);
345  const scalar cbrtW = cbrt(W);
346 
347  rhos += Xs[i]*W;
348  mus += Ys[i]*sqrtW*carrier.mu(i, td.pc(), T);
349  kappas += Ys[i]*cbrtW*carrier.kappa(i, td.pc(), T);
350  Cps += Xs[i]*carrier.Cp(i, td.pc(), T);
351 
352  sumYiSqrtW += Ys[i]*sqrtW;
353  sumYiCbrtW += Ys[i]*cbrtW;
354  }
355 
356  Cps = max(Cps, rootVSmall);
357 
358  rhos *= td.pc()/(RR*T);
359  rhos = max(rhos, rootVSmall);
360 
361  mus /= sumYiSqrtW;
362  mus = max(mus, rootVSmall);
363 
364  kappas /= sumYiCbrtW;
365  kappas = max(kappas, rootVSmall);
366 
367  Prs = Cps*mus/kappas;
368 }
369 
370 
371 template<class ParcelType>
372 template<class TrackCloudType>
374 (
375  TrackCloudType& cloud,
376  trackingData& td,
377  const scalar dt
378 )
379 {
380  typedef typename TrackCloudType::thermoCloudType thermoCloudType;
382  cloud.composition();
383 
384 
385  // Define local properties at beginning of time step
386  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
387 
388  const scalar np0 = this->nParticle_;
389  const scalar d0 = this->d_;
390  const vector& U0 = this->U_;
391  const scalar T0 = this->T_;
392  const scalar mass0 = this->mass();
393 
394 
395  // Calc surface values
396  scalar Ts, rhos, mus, Prs, kappas;
397  this->calcSurfaceValues(cloud, td, T0, Ts, rhos, mus, Prs, kappas);
398  scalar Res = this->Re(rhos, U0, td.Uc(), d0, mus);
399 
400 
401  // Sources
402  // ~~~~~~~
403 
404  // Explicit momentum source for particle
405  vector Su = Zero;
406 
407  // Linearised momentum source coefficient
408  scalar Spu = 0.0;
409 
410  // Momentum transfer from the particle to the carrier phase
411  vector dUTrans = Zero;
412 
413  // Explicit enthalpy source for particle
414  scalar Sh = 0.0;
415 
416  // Linearised enthalpy source coefficient
417  scalar Sph = 0.0;
418 
419  // Sensible enthalpy transfer from the particle to the carrier phase
420  scalar dhsTrans = 0.0;
421 
422 
423  // 1. Compute models that contribute to mass transfer - U, T held constant
424  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
425 
426  // Phase change
427  // ~~~~~~~~~~~~
428 
429  // Mass transfer due to phase change
430  scalarField dMassPC(Y_.size(), 0.0);
431 
432  // Molar flux of species emitted from the particle (kmol/m^2/s)
433  scalar Ne = 0.0;
434 
435  // Sum Ni*Cpi*Wi of emission species
436  scalar NCpW = 0.0;
437 
438  // Surface concentrations of emitted species
439  scalarField Cs(composition.carrier().species().size(), 0.0);
440 
441  // Calc mass and enthalpy transfer due to phase change
442  calcPhaseChange
443  (
444  cloud,
445  td,
446  dt,
447  Res,
448  Prs,
449  Ts,
450  mus/rhos,
451  d0,
452  T0,
453  mass0,
454  0,
455  1.0,
456  Y_,
457  dMassPC,
458  Sh,
459  Ne,
460  NCpW,
461  Cs
462  );
463 
464 
465  // 2. Update the parcel properties due to change in mass
466  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
467 
468  scalarField dMass(dMassPC);
469  scalar mass1 = updateMassFraction(mass0, dMass, Y_);
470 
471  this->Cp_ = composition.Cp(0, Y_, td.pc(), T0);
472 
473  // Update particle density or diameter
474  if (cloud.constProps().constantVolume())
475  {
476  this->rho_ = mass1/this->volume();
477  }
478  else
479  {
480  this->d_ = cbrt(mass1/this->rho_*6.0/pi);
481  }
482 
483  // Remove the particle when mass falls below minimum threshold
484  if (np0*mass1 < cloud.constProps().minParcelMass())
485  {
486  td.keepParticle = false;
487 
488  if (cloud.solution().coupled())
489  {
490  scalar dm = np0*mass0;
491 
492  // Absorb parcel into carrier phase
493  forAll(Y_, i)
494  {
495  scalar dmi = dm*Y_[i];
496  label gid = composition.localToCarrierId(0, i);
497  scalar hs = composition.carrier().Hs(gid, td.pc(), T0);
498 
499  cloud.rhoTrans(gid)[this->cell()] += dmi;
500  cloud.hsTransRef()[this->cell()] += dmi*hs;
501  }
502  cloud.UTransRef()[this->cell()] += dm*U0;
503 
504  cloud.phaseChange().addToPhaseChangeMass(np0*mass1);
505  }
506 
507  return;
508  }
509 
510  // Correct surface values due to emitted species
511  correctSurfaceValues(cloud, td, Ts, Cs, rhos, mus, Prs, kappas);
512  Res = this->Re(rhos, U0, td.Uc(), this->d(), mus);
513 
514 
515  // 3. Compute heat- and momentum transfers
516  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
517 
518  // Heat transfer
519  // ~~~~~~~~~~~~~
520 
521  // Calculate new particle temperature
522  this->T_ =
523  this->calcHeatTransfer
524  (
525  cloud,
526  td,
527  dt,
528  Res,
529  Prs,
530  kappas,
531  NCpW,
532  Sh,
533  dhsTrans,
534  Sph
535  );
536 
537  this->Cp_ = composition.Cp(0, Y_, td.pc(), T0);
538 
539 
540  // Motion
541  // ~~~~~~
542 
543  // Calculate new particle velocity
544  this->U_ =
545  this->calcVelocity(cloud, td, dt, Res, mus, mass1, Su, dUTrans, Spu);
546 
547 
548  // 4. Accumulate carrier phase source terms
549  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
550 
551  if (cloud.solution().coupled())
552  {
553  // Transfer mass lost to carrier mass, momentum and enthalpy sources
554  forAll(dMass, i)
555  {
556  scalar dm = np0*dMass[i];
557  label gid = composition.localToCarrierId(0, i);
558  scalar hs = composition.carrier().Hs(gid, td.pc(), T0);
559 
560  cloud.rhoTrans(gid)[this->cell()] += dm;
561  cloud.UTransRef()[this->cell()] += dm*U0;
562  cloud.hsTransRef()[this->cell()] += dm*hs;
563  }
564 
565  // Update momentum transfer
566  cloud.UTransRef()[this->cell()] += np0*dUTrans;
567  cloud.UCoeffRef()[this->cell()] += np0*Spu;
568 
569  // Update sensible enthalpy transfer
570  cloud.hsTransRef()[this->cell()] += np0*dhsTrans;
571  cloud.hsCoeffRef()[this->cell()] += np0*Sph;
572 
573  // Update radiation fields
574  if (cloud.radiation())
575  {
576  const scalar ap = this->areaP();
577  const scalar T4 = pow4(T0);
578  cloud.radAreaP()[this->cell()] += dt*np0*ap;
579  cloud.radT4()[this->cell()] += dt*np0*T4;
580  cloud.radAreaPT4()[this->cell()] += dt*np0*ap*T4;
581  }
582  }
583 }
584 
585 
586 // * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
587 
588 #include "ReactingParcelIO.C"
589 
590 // ************************************************************************* //
scalar Cp(const scalar p, const scalar T) const
Definition: EtoHthermo.H:2
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:434
Templated reacting parcel composition model class Consists of carrier species (via thermo package),...
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
Templated phase change model class.
virtual scalar dh(const label idc, const label idl, const scalar p, const scalar T) const
Return the enthalpy per unit mass.
virtual void calculate(const typename CloudType::parcelType &p, const typename CloudType::parcelType::trackingData &td, const scalar dt, const scalar Re, const scalar Pr, const scalar d, const scalar nu, const scalar T, const scalar Ts, const scalar pc, const scalar Tc, const scalarField &X, scalarField &dMassPC) const =0
Update model.
void addToPhaseChangeMass(const scalar dMass)
Add to phase change mass.
virtual scalar Tvap(const scalarField &X) const
Return vapourisation temperature.
virtual scalar TMax(const scalar p, const scalarField &X) const
Return maximum/limiting temperature.
Reacting parcel class with one/two-way coupling with the continuous phase.
void cellValueSourceCorrection(TrackCloudType &cloud, trackingData &td, const scalar dt)
Correct cell values using latest transfer information.
void calcPhaseChange(TrackCloudType &cloud, trackingData &td, const scalar dt, const scalar Re, const scalar Pr, const scalar Ts, const scalar nus, const scalar d, const scalar T, const scalar mass, const label idPhase, const scalar YPhase, const scalarField &YComponents, scalarField &dMassPC, scalar &Sh, scalar &N, scalar &NCpW, scalarField &Cs)
Calculate Phase change.
void correctSurfaceValues(TrackCloudType &cloud, trackingData &td, const scalar T, const scalarField &Cs, scalar &rhos, scalar &mus, scalar &Prs, scalar &kappas)
Correct surface values due to emitted species.
ParcelType::trackingData trackingData
Use base tracking data.
scalar updateMassFraction(const scalar mass0, const scalarField &dMass, scalarField &Y) const
Update mass fraction.
void setCellValues(TrackCloudType &cloud, trackingData &td)
Set cell values.
ReactingParcel(const polyMesh &mesh, const barycentric &coordinates, const label celli, const label tetFacei, const label tetPti, const label facei)
Construct from mesh, coordinates and topology.
void calc(TrackCloudType &cloud, trackingData &td, const scalar dt)
Update parcel properties over the time interval.
Specialisation of basicMixture for a mixture consisting of a number for molecular species.
virtual scalar Hs(const label speciei, const scalar p, const scalar T) const =0
Sensible enthalpy [J/kg].
virtual scalar Wi(const label speciei) const =0
Molecular weight of the given specie [kg/kmol].
const speciesTable & species() const
Return the table of species.
virtual scalar Cp(const label speciei, const scalar p, const scalar T) const =0
Heat capacity at constant pressure [J/kg/K].
PtrList< volScalarField > & Y()
Return the mass-fraction fields.
virtual scalar mu(const label speciei, const scalar p, const scalar T) const =0
Dynamic viscosity [kg/m/s].
virtual scalar kappa(const label speciei, const scalar p, const scalar T) const =0
Thermal conductivity [W/m/K].
A cell is defined as a list of faces with extra functionality.
Definition: cell.H:60
A cloud is a collection of lagrangian particles.
Definition: cloud.H:55
#define WarningInFunction
Report a warning using Foam::Warning.
mathematical constants.
const dimensionedScalar RR
Universal gas constant: default SI units: [J/kmol/K].
tmp< VolField< Type > > Su(const VolField< Type > &su, const VolField< Type > &vf)
Definition: fvcSup.C:44
static const zero Zero
Definition: zero.H:97
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:251
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
layerAndWeight min(const layerAndWeight &a, const layerAndWeight &b)
scalarList W(const fluidMulticomponentThermo &thermo)
dimensionedScalar sqrt(const dimensionedScalar &ds)
dimensioned< scalar > mag(const dimensioned< Type > &)
dimensionedScalar pow4(const dimensionedScalar &ds)
layerAndWeight max(const layerAndWeight &a, const layerAndWeight &b)
dimensionedScalar cbrt(const dimensionedScalar &ds)
static const char nl
Definition: Ostream.H:260
void T(FieldField< Field, Type > &f1, const FieldField< Field, Type > &f2)
scalarField Re(const UList< complex > &cf)
Definition: complexFields.C:97
basicSpecieMixture & composition
volScalarField & p
PtrList< volScalarField > & Y
scalar T0
Definition: createFields.H:22