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