ReactingMultiphaseParcel.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-2016 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 
27 #include "mathematicalConstants.H"
28 
29 using namespace Foam::constant::mathematical;
30 
31 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
32 
33 template<class ParcelType>
35 
36 template<class ParcelType>
38 
39 template<class ParcelType>
41 
42 
43 // * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * * //
44 
45 template<class ParcelType>
46 template<class TrackData>
48 (
49  TrackData& td,
50  const scalar p,
51  const scalar T,
52  const label idG,
53  const label idL,
54  const label idS
55 ) const
56 {
57  return
58  this->Y_[GAS]*td.cloud().composition().Cp(idG, YGas_, p, T)
59  + this->Y_[LIQ]*td.cloud().composition().Cp(idL, YLiquid_, p, T)
60  + this->Y_[SLD]*td.cloud().composition().Cp(idS, YSolid_, p, T);
61 }
62 
63 
64 template<class ParcelType>
65 template<class TrackData>
67 (
68  TrackData& td,
69  const scalar p,
70  const scalar T,
71  const label idG,
72  const label idL,
73  const label idS
74 ) const
75 {
76  return
77  this->Y_[GAS]*td.cloud().composition().Hs(idG, YGas_, p, T)
78  + this->Y_[LIQ]*td.cloud().composition().Hs(idL, YLiquid_, p, T)
79  + this->Y_[SLD]*td.cloud().composition().Hs(idS, YSolid_, p, T);
80 }
81 
82 
83 template<class ParcelType>
84 template<class TrackData>
86 (
87  TrackData& td,
88  const scalar p,
89  const scalar T,
90  const label idG,
91  const label idL,
92  const label idS
93 ) const
94 {
95  return
96  this->Y_[GAS]*td.cloud().composition().L(idG, YGas_, p, T)
97  + this->Y_[LIQ]*td.cloud().composition().L(idL, YLiquid_, p, T)
98  + this->Y_[SLD]*td.cloud().composition().L(idS, YSolid_, p, T);
99 }
100 
101 
102 template<class ParcelType>
104 (
105  const scalar mass0,
106  const scalarField& dMassGas,
107  const scalarField& dMassLiquid,
108  const scalarField& dMassSolid
109 )
110 {
111  scalarField& YMix = this->Y_;
112 
113  scalar massGas =
114  this->updateMassFraction(mass0*YMix[GAS], dMassGas, YGas_);
115  scalar massLiquid =
116  this->updateMassFraction(mass0*YMix[LIQ], dMassLiquid, YLiquid_);
117  scalar massSolid =
118  this->updateMassFraction(mass0*YMix[SLD], dMassSolid, YSolid_);
119 
120  scalar massNew = max(massGas + massLiquid + massSolid, ROOTVSMALL);
121 
122  YMix[GAS] = massGas/massNew;
123  YMix[LIQ] = massLiquid/massNew;
124  YMix[SLD] = 1.0 - YMix[GAS] - YMix[LIQ];
125 
126  return massNew;
127 }
128 
129 
130 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
131 
132 template<class ParcelType>
133 template<class TrackData>
135 (
136  TrackData& td,
137  const scalar dt,
138  const label celli
139 )
140 {
141  ParcelType::setCellValues(td, dt, celli);
142 }
143 
144 
145 template<class ParcelType>
146 template<class TrackData>
148 (
149  TrackData& td,
150  const scalar dt,
151  const label celli
152 )
153 {
154  // Re-use correction from reacting parcel
155  ParcelType::cellValueSourceCorrection(td, dt, celli);
156 }
157 
158 
159 template<class ParcelType>
160 template<class TrackData>
162 (
163  TrackData& td,
164  const scalar dt,
165  const label celli
166 )
167 {
168  typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
170  td.cloud().composition();
171 
172 
173  // Define local properties at beginning of timestep
174  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
175 
176  const scalar np0 = this->nParticle_;
177  const scalar d0 = this->d_;
178  const vector& U0 = this->U_;
179  const scalar T0 = this->T_;
180  const scalar mass0 = this->mass();
181 
182  const scalar pc = this->pc_;
183 
184  const scalarField& YMix = this->Y_;
185  const label idG = composition.idGas();
186  const label idL = composition.idLiquid();
187  const label idS = composition.idSolid();
188 
189 
190  // Calc surface values
191  scalar Ts, rhos, mus, Prs, kappas;
192  this->calcSurfaceValues(td, celli, T0, Ts, rhos, mus, Prs, kappas);
193  scalar Res = this->Re(U0, d0, rhos, mus);
194 
195 
196  // Sources
197  //~~~~~~~~
198 
199  // Explicit momentum source for particle
200  vector Su = Zero;
201 
202  // Linearised momentum source coefficient
203  scalar Spu = 0.0;
204 
205  // Momentum transfer from the particle to the carrier phase
206  vector dUTrans = Zero;
207 
208  // Explicit enthalpy source for particle
209  scalar Sh = 0.0;
210 
211  // Linearised enthalpy source coefficient
212  scalar Sph = 0.0;
213 
214  // Sensible enthalpy transfer from the particle to the carrier phase
215  scalar dhsTrans = 0.0;
216 
217 
218  // 1. Compute models that contribute to mass transfer - U, T held constant
219  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
220 
221  // Phase change in liquid phase
222  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
223 
224  // Mass transfer due to phase change
225  scalarField dMassPC(YLiquid_.size(), 0.0);
226 
227  // Molar flux of species emitted from the particle (kmol/m^2/s)
228  scalar Ne = 0.0;
229 
230  // Sum Ni*Cpi*Wi of emission species
231  scalar NCpW = 0.0;
232 
233  // Surface concentrations of emitted species
234  scalarField Cs(composition.carrier().species().size(), 0.0);
235 
236  // Calc mass and enthalpy transfer due to phase change
237  this->calcPhaseChange
238  (
239  td,
240  dt,
241  celli,
242  Res,
243  Prs,
244  Ts,
245  mus/rhos,
246  d0,
247  T0,
248  mass0,
249  idL,
250  YMix[LIQ],
251  YLiquid_,
252  dMassPC,
253  Sh,
254  Ne,
255  NCpW,
256  Cs
257  );
258 
259 
260  // Devolatilisation
261  // ~~~~~~~~~~~~~~~~
262 
263  // Mass transfer due to devolatilisation
264  scalarField dMassDV(YGas_.size(), 0.0);
265 
266  // Calc mass and enthalpy transfer due to devolatilisation
267  calcDevolatilisation
268  (
269  td,
270  dt,
271  this->age_,
272  Ts,
273  d0,
274  T0,
275  mass0,
276  this->mass0_,
277  YMix[GAS]*YGas_,
278  YMix[LIQ]*YLiquid_,
279  YMix[SLD]*YSolid_,
280  canCombust_,
281  dMassDV,
282  Sh,
283  Ne,
284  NCpW,
285  Cs
286  );
287 
288 
289  // Surface reactions
290  // ~~~~~~~~~~~~~~~~~
291 
292  // Change in carrier phase composition due to surface reactions
293  scalarField dMassSRGas(YGas_.size(), 0.0);
294  scalarField dMassSRLiquid(YLiquid_.size(), 0.0);
295  scalarField dMassSRSolid(YSolid_.size(), 0.0);
296  scalarField dMassSRCarrier(composition.carrier().species().size(), 0.0);
297 
298  // Calc mass and enthalpy transfer due to surface reactions
299  calcSurfaceReactions
300  (
301  td,
302  dt,
303  celli,
304  d0,
305  T0,
306  mass0,
307  canCombust_,
308  Ne,
309  YMix,
310  YGas_,
311  YLiquid_,
312  YSolid_,
313  dMassSRGas,
314  dMassSRLiquid,
315  dMassSRSolid,
316  dMassSRCarrier,
317  Sh,
318  dhsTrans
319  );
320 
321 
322  // 2. Update the parcel properties due to change in mass
323  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
324 
325  scalarField dMassGas(dMassDV + dMassSRGas);
326  scalarField dMassLiquid(dMassPC + dMassSRLiquid);
327  scalarField dMassSolid(dMassSRSolid);
328  scalar mass1 =
329  updateMassFractions(mass0, dMassGas, dMassLiquid, dMassSolid);
330 
331  this->Cp_ = CpEff(td, pc, T0, idG, idL, idS);
332 
333  // Update particle density or diameter
334  if (td.cloud().constProps().constantVolume())
335  {
336  this->rho_ = mass1/this->volume();
337  }
338  else
339  {
340  this->d_ = cbrt(mass1/this->rho_*6.0/pi);
341  }
342 
343  // Remove the particle when mass falls below minimum threshold
344  if (np0*mass1 < td.cloud().constProps().minParcelMass())
345  {
346  td.keepParticle = false;
347 
348  if (td.cloud().solution().coupled())
349  {
350  scalar dm = np0*mass0;
351 
352  // Absorb parcel into carrier phase
353  forAll(YGas_, i)
354  {
355  label gid = composition.localToCarrierId(GAS, i);
356  td.cloud().rhoTrans(gid)[celli] += dm*YMix[GAS]*YGas_[i];
357  }
358  forAll(YLiquid_, i)
359  {
360  label gid = composition.localToCarrierId(LIQ, i);
361  td.cloud().rhoTrans(gid)[celli] += dm*YMix[LIQ]*YLiquid_[i];
362  }
363 
364  // No mapping between solid components and carrier phase
365  /*
366  forAll(YSolid_, i)
367  {
368  label gid = composition.localToCarrierId(SLD, i);
369  td.cloud().rhoTrans(gid)[celli] += dm*YMix[SLD]*YSolid_[i];
370  }
371  */
372 
373  td.cloud().UTrans()[celli] += dm*U0;
374 
375  td.cloud().hsTrans()[celli] += dm*HsEff(td, pc, T0, idG, idL, idS);
376 
377  td.cloud().phaseChange().addToPhaseChangeMass(np0*mass1);
378  }
379 
380  return;
381  }
382 
383  // Correct surface values due to emitted species
384  this->correctSurfaceValues(td, celli, Ts, Cs, rhos, mus, Prs, kappas);
385  Res = this->Re(U0, this->d_, rhos, mus);
386 
387 
388  // 3. Compute heat- and momentum transfers
389  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
390 
391  // Heat transfer
392  // ~~~~~~~~~~~~~
393 
394  // Calculate new particle temperature
395  this->T_ =
396  this->calcHeatTransfer
397  (
398  td,
399  dt,
400  celli,
401  Res,
402  Prs,
403  kappas,
404  NCpW,
405  Sh,
406  dhsTrans,
407  Sph
408  );
409 
410 
411  this->Cp_ = CpEff(td, pc, this->T_, idG, idL, idS);
412 
413 
414  // Motion
415  // ~~~~~~
416 
417  // Calculate new particle velocity
418  this->U_ =
419  this->calcVelocity(td, dt, celli, Res, mus, mass1, Su, dUTrans, Spu);
420 
421 
422  // 4. Accumulate carrier phase source terms
423  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
424 
425  if (td.cloud().solution().coupled())
426  {
427  // Transfer mass lost to carrier mass, momentum and enthalpy sources
428  forAll(YGas_, i)
429  {
430  scalar dm = np0*dMassGas[i];
431  label gid = composition.localToCarrierId(GAS, i);
432  scalar hs = composition.carrier().Hs(gid, pc, T0);
433  td.cloud().rhoTrans(gid)[celli] += dm;
434  td.cloud().UTrans()[celli] += dm*U0;
435  td.cloud().hsTrans()[celli] += dm*hs;
436  }
437  forAll(YLiquid_, i)
438  {
439  scalar dm = np0*dMassLiquid[i];
440  label gid = composition.localToCarrierId(LIQ, i);
441  scalar hs = composition.carrier().Hs(gid, pc, T0);
442  td.cloud().rhoTrans(gid)[celli] += dm;
443  td.cloud().UTrans()[celli] += dm*U0;
444  td.cloud().hsTrans()[celli] += dm*hs;
445  }
446 
447  // No mapping between solid components and carrier phase
448  /*
449  forAll(YSolid_, i)
450  {
451  scalar dm = np0*dMassSolid[i];
452  label gid = composition.localToCarrierId(SLD, i);
453  scalar hs = composition.carrier().Hs(gid, pc, T0);
454  td.cloud().rhoTrans(gid)[celli] += dm;
455  td.cloud().UTrans()[celli] += dm*U0;
456  td.cloud().hsTrans()[celli] += dm*hs;
457  }
458  */
459 
460  forAll(dMassSRCarrier, i)
461  {
462  scalar dm = np0*dMassSRCarrier[i];
463  scalar hs = composition.carrier().Hs(i, pc, T0);
464  td.cloud().rhoTrans(i)[celli] += dm;
465  td.cloud().UTrans()[celli] += dm*U0;
466  td.cloud().hsTrans()[celli] += dm*hs;
467  }
468 
469  // Update momentum transfer
470  td.cloud().UTrans()[celli] += np0*dUTrans;
471  td.cloud().UCoeff()[celli] += np0*Spu;
472 
473  // Update sensible enthalpy transfer
474  td.cloud().hsTrans()[celli] += np0*dhsTrans;
475  td.cloud().hsCoeff()[celli] += np0*Sph;
476 
477  // Update radiation fields
478  if (td.cloud().radiation())
479  {
480  const scalar ap = this->areaP();
481  const scalar T4 = pow4(T0);
482  td.cloud().radAreaP()[celli] += dt*np0*ap;
483  td.cloud().radT4()[celli] += dt*np0*T4;
484  td.cloud().radAreaPT4()[celli] += dt*np0*ap*T4;
485  }
486  }
487 }
488 
489 
490 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
491 
492 template<class ParcelType>
493 template<class TrackData>
495 (
496  TrackData& td,
497  const scalar dt,
498  const scalar age,
499  const scalar Ts,
500  const scalar d,
501  const scalar T,
502  const scalar mass,
503  const scalar mass0,
504  const scalarField& YGasEff,
505  const scalarField& YLiquidEff,
506  const scalarField& YSolidEff,
507  label& canCombust,
508  scalarField& dMassDV,
509  scalar& Sh,
510  scalar& N,
511  scalar& NCpW,
512  scalarField& Cs
513 ) const
514 {
515  // Check that model is active
516  if (!td.cloud().devolatilisation().active())
517  {
518  return;
519  }
520 
521  // Initialise demand-driven constants
522  (void)td.cloud().constProps().TDevol();
523  (void)td.cloud().constProps().LDevol();
524 
525  // Check that the parcel temperature is within necessary limits for
526  // devolatilisation to occur
527  if (T < td.cloud().constProps().TDevol() || canCombust == -1)
528  {
529  return;
530  }
531 
532  typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
534  td.cloud().composition();
535 
536 
537  // Total mass of volatiles evolved
538  td.cloud().devolatilisation().calculate
539  (
540  dt,
541  age,
542  mass0,
543  mass,
544  T,
545  YGasEff,
546  YLiquidEff,
547  YSolidEff,
548  canCombust,
549  dMassDV
550  );
551 
552  scalar dMassTot = sum(dMassDV);
553 
554  td.cloud().devolatilisation().addToDevolatilisationMass
555  (
556  this->nParticle_*dMassTot
557  );
558 
559  Sh -= dMassTot*td.cloud().constProps().LDevol()/dt;
560 
561  // Update molar emissions
562  if (td.cloud().heatTransfer().BirdCorrection())
563  {
564  // Molar average molecular weight of carrier mix
565  const scalar Wc =
566  max(SMALL, this->rhoc_*RR*this->Tc_/this->pc_);
567 
568  // Note: hardcoded gaseous diffusivities for now
569  // TODO: add to carrier thermo
570  const scalar beta = sqr(cbrt(15.0) + cbrt(15.0));
571 
572  forAll(dMassDV, i)
573  {
574  const label id = composition.localToCarrierId(GAS, i);
575  const scalar Cp = composition.carrier().Cp(id, this->pc_, Ts);
576  const scalar W = composition.carrier().W(id);
577  const scalar Ni = dMassDV[i]/(this->areaS(d)*dt*W);
578 
579  // Dab calc'd using API vapour mass diffusivity function
580  const scalar Dab =
581  3.6059e-3*(pow(1.8*Ts, 1.75))
582  *sqrt(1.0/W + 1.0/Wc)
583  /(this->pc_*beta);
584 
585  N += Ni;
586  NCpW += Ni*Cp*W;
587  Cs[id] += Ni*d/(2.0*Dab);
588  }
589  }
590 }
591 
592 
593 template<class ParcelType>
594 template<class TrackData>
596 (
597  TrackData& td,
598  const scalar dt,
599  const label celli,
600  const scalar d,
601  const scalar T,
602  const scalar mass,
603  const label canCombust,
604  const scalar N,
605  const scalarField& YMix,
606  const scalarField& YGas,
607  const scalarField& YLiquid,
608  const scalarField& YSolid,
609  scalarField& dMassSRGas,
610  scalarField& dMassSRLiquid,
611  scalarField& dMassSRSolid,
612  scalarField& dMassSRCarrier,
613  scalar& Sh,
614  scalar& dhsTrans
615 ) const
616 {
617  // Check that model is active
618  if (!td.cloud().surfaceReaction().active())
619  {
620  return;
621  }
622 
623  // Initialise demand-driven constants
624  (void)td.cloud().constProps().hRetentionCoeff();
625  (void)td.cloud().constProps().TMax();
626 
627  // Check that model is active
628  if (canCombust != 1)
629  {
630  return;
631  }
632 
633 
634  // Update surface reactions
635  const scalar hReaction = td.cloud().surfaceReaction().calculate
636  (
637  dt,
638  celli,
639  d,
640  T,
641  this->Tc_,
642  this->pc_,
643  this->rhoc_,
644  mass,
645  YGas,
646  YLiquid,
647  YSolid,
648  YMix,
649  N,
650  dMassSRGas,
651  dMassSRLiquid,
652  dMassSRSolid,
653  dMassSRCarrier
654  );
655 
656  td.cloud().surfaceReaction().addToSurfaceReactionMass
657  (
658  this->nParticle_
659  *(sum(dMassSRGas) + sum(dMassSRLiquid) + sum(dMassSRSolid))
660  );
661 
662  const scalar xsi = min(T/td.cloud().constProps().TMax(), 1.0);
663  const scalar coeff =
664  (1.0 - xsi*xsi)*td.cloud().constProps().hRetentionCoeff();
665 
666  Sh += coeff*hReaction/dt;
667 
668  dhsTrans += (1.0 - coeff)*hReaction;
669 }
670 
671 
672 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
673 
674 template<class ParcelType>
676 (
678 )
679 :
680  ParcelType(p),
681  YGas_(p.YGas_),
682  YLiquid_(p.YLiquid_),
683  YSolid_(p.YSolid_),
684  canCombust_(p.canCombust_)
685 {}
686 
687 
688 template<class ParcelType>
690 (
692  const polyMesh& mesh
693 )
694 :
695  ParcelType(p, mesh),
696  YGas_(p.YGas_),
697  YLiquid_(p.YLiquid_),
698  YSolid_(p.YSolid_),
699  canCombust_(p.canCombust_)
700 {}
701 
702 
703 // * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
704 
706 
707 // ************************************************************************* //
#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
zeroField Su
Definition: alphaSuSp.H:1
dimensioned< Type > max(const dimensioned< Type > &, const dimensioned< Type > &)
label canCombust_
Flag to identify if the particle can devolatilise and combust.
basicMultiComponentMixture & composition
dimensionedSymmTensor sqr(const dimensionedVector &dv)
virtual label idSolid() const =0
Solid id.
void setCellValues(TrackData &td, const scalar dt, const label celli)
Set cell values.
dimensionedScalar sqrt(const dimensionedScalar &ds)
void cellValueSourceCorrection(TrackData &td, const scalar dt, const label celli)
Correct cell values using latest transfer information.
label localToCarrierId(const label phaseI, const label id, const bool allowNotFound=false) const
Return carrier id of component given local id.
void calc(TrackData &td, const scalar dt, const label celli)
Update parcel properties over the time interval.
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
ReactingMultiphaseParcel(const polyMesh &mesh, const barycentric &coordinates, const label celli, const label tetFacei, const label tetPti)
Construct from mesh, position and topology.
scalarField YGas_
Mass fractions of gases [].
mathematical constants.
dimensionedScalar cbrt(const dimensionedScalar &ds)
static const zero Zero
Definition: zero.H:91
virtual label idLiquid() const =0
Liquid id.
scalarField YSolid_
Mass fractions of solids [].
dimensioned< Type > min(const dimensioned< Type > &, const dimensioned< Type > &)
scalarField YLiquid_
Mass fractions of liquids [].
dimensionedScalar pow(const dimensionedScalar &ds, const dimensionedScalar &expt)
void calcDevolatilisation(TrackData &td, const scalar dt, const scalar age, const scalar Ts, const scalar d, const scalar T, const scalar mass, const scalar mass0, const scalarField &YGasEff, const scalarField &YLiquidEff, const scalarField &YSolidEff, label &canCombust, scalarField &dMassDV, scalar &Sh, scalar &N, scalar &NCpW, scalarField &Cs) const
Calculate Devolatilisation.
dimensionedScalar pow4(const dimensionedScalar &ds)
Multiphase variant of the reacting parcel class with one/two-way coupling with the continuous phase...
dimensionedScalar beta("beta", dimless/dimTemperature, laminarTransport)
const scalar RR
Universal gas constant (default in [J/(kmol K)])
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:74
virtual label idGas() const =0
Gas id.
Templated reacting parcel composition model class Consists of carrier species (via thermo package)...
Definition: ReactingCloud.H:52
void calcSurfaceReactions(TrackData &td, const scalar dt, const label celli, const scalar d, const scalar T, const scalar mass, const label canCombust, const scalar N, const scalarField &YMix, const scalarField &YGas, const scalarField &YLiquid, const scalarField &YSolid, scalarField &dMassSRGas, scalarField &dMassSRLiquid, scalarField &dMassSRSolid, scalarField &dMassSRCarrier, scalar &Sh, scalar &dhsTrans) const
Calculate surface reactions.
scalarField Re(const UList< complex > &cf)
Definition: complexFields.C:97
const basicSpecieMixture & carrier() const
Return the carrier components (wrapper function)