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