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-2015 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->position(),
217  this->currentTetIndices()
218  );
219 
220  if (pc_ < td.cloud().constProps().pMin())
221  {
222  if (debug)
223  {
224  WarningIn
225  (
226  "void Foam::ReactingParcel<ParcelType>::setCellValues"
227  "("
228  "TrackData&, "
229  "const scalar, "
230  "const label"
231  ")"
232  ) << "Limiting observed pressure in cell " << cellI << " to "
233  << td.cloud().constProps().pMin() << nl << endl;
234  }
235 
236  pc_ = td.cloud().constProps().pMin();
237  }
238 }
239 
240 
241 template<class ParcelType>
242 template<class TrackData>
244 (
245  TrackData& td,
246  const scalar dt,
247  const label cellI
248 )
249 {
250  scalar addedMass = 0.0;
251  scalar maxMassI = 0.0;
252  forAll(td.cloud().rhoTrans(), i)
253  {
254  scalar dm = td.cloud().rhoTrans(i)[cellI];
255  maxMassI = max(maxMassI, mag(dm));
256  addedMass += dm;
257  }
258 
259  if (maxMassI < ROOTVSMALL)
260  {
261  return;
262  }
263 
264  const scalar massCell = this->massCell(cellI);
265 
266  this->rhoc_ += addedMass/td.cloud().pMesh().cellVolumes()[cellI];
267 
268  const scalar massCellNew = massCell + addedMass;
269  this->Uc_ = (this->Uc_*massCell + td.cloud().UTrans()[cellI])/massCellNew;
270 
271  scalar CpEff = 0.0;
272  forAll(td.cloud().rhoTrans(), i)
273  {
274  scalar Y = td.cloud().rhoTrans(i)[cellI]/addedMass;
275  CpEff += Y*td.cloud().composition().carrier().Cp
276  (
277  i,
278  this->pc_,
279  this->Tc_
280  );
281  }
282 
283  const scalar Cpc = td.CpInterp().psi()[cellI];
284  this->Cpc_ = (massCell*Cpc + addedMass*CpEff)/massCellNew;
285 
286  this->Tc_ += td.cloud().hsTrans()[cellI]/(this->Cpc_*massCellNew);
287 
288  if (this->Tc_ < td.cloud().constProps().TMin())
289  {
290  if (debug)
291  {
292  WarningIn
293  (
294  "void Foam::ReactingParcel<ParcelType>::"
295  "cellValueSourceCorrection"
296  "("
297  "TrackData&, "
298  "const scalar, "
299  "const label"
300  ")"
301  ) << "Limiting observed temperature in cell " << cellI << " to "
302  << td.cloud().constProps().TMin() << nl << endl;
303  }
304 
305  this->Tc_ = td.cloud().constProps().TMin();
306  }
307 }
308 
309 
310 template<class ParcelType>
311 template<class TrackData>
313 (
314  TrackData& td,
315  const label cellI,
316  const scalar T,
317  const scalarField& Cs,
318  scalar& rhos,
319  scalar& mus,
320  scalar& Prs,
321  scalar& kappas
322 )
323 {
324  // No correction if total concentration of emitted species is small
325  if (!td.cloud().heatTransfer().BirdCorrection() || (sum(Cs) < SMALL))
326  {
327  return;
328  }
329 
330  const SLGThermo& thermo = td.cloud().thermo();
331 
332  // Far field carrier molar fractions
333  scalarField Xinf(thermo.carrier().species().size());
334 
335  forAll(Xinf, i)
336  {
337  Xinf[i] = thermo.carrier().Y(i)[cellI]/thermo.carrier().W(i);
338  }
339  Xinf /= sum(Xinf);
340 
341  // Molar fraction of far field species at particle surface
342  const scalar Xsff = 1.0 - min(sum(Cs)*RR*this->T_/pc_, 1.0);
343 
344  // Surface carrier total molar concentration
345  const scalar CsTot = pc_/(RR*this->T_);
346 
347  // Surface carrier composition (molar fraction)
348  scalarField Xs(Xinf.size());
349 
350  // Surface carrier composition (mass fraction)
351  scalarField Ys(Xinf.size());
352 
353  forAll(Xs, i)
354  {
355  // Molar concentration of species at particle surface
356  const scalar Csi = Cs[i] + Xsff*Xinf[i]*CsTot;
357 
358  Xs[i] = (2.0*Csi + Xinf[i]*CsTot)/3.0;
359  Ys[i] = Xs[i]*thermo.carrier().W(i);
360  }
361  Xs /= sum(Xs);
362  Ys /= sum(Ys);
363 
364 
365  rhos = 0;
366  mus = 0;
367  kappas = 0;
368  scalar Cps = 0;
369  scalar sumYiSqrtW = 0;
370  scalar sumYiCbrtW = 0;
371 
372  forAll(Ys, i)
373  {
374  const scalar W = thermo.carrier().W(i);
375  const scalar sqrtW = sqrt(W);
376  const scalar cbrtW = cbrt(W);
377 
378  rhos += Xs[i]*W;
379  mus += Ys[i]*sqrtW*thermo.carrier().mu(i, pc_, T);
380  kappas += Ys[i]*cbrtW*thermo.carrier().kappa(i, pc_, T);
381  Cps += Xs[i]*thermo.carrier().Cp(i, pc_, T);
382 
383  sumYiSqrtW += Ys[i]*sqrtW;
384  sumYiCbrtW += Ys[i]*cbrtW;
385  }
386 
387  Cps = max(Cps, ROOTVSMALL);
388 
389  rhos *= pc_/(RR*T);
390  rhos = max(rhos, ROOTVSMALL);
391 
392  mus /= sumYiSqrtW;
393  mus = max(mus, ROOTVSMALL);
394 
395  kappas /= sumYiCbrtW;
396  kappas = max(kappas, ROOTVSMALL);
397 
398  Prs = Cps*mus/kappas;
399 }
400 
401 
402 template<class ParcelType>
403 template<class TrackData>
405 (
406  TrackData& td,
407  const scalar dt,
408  const label cellI
409 )
410 {
411  typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
413  td.cloud().composition();
414 
415 
416  // Define local properties at beginning of time step
417  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
418 
419  const scalar np0 = this->nParticle_;
420  const scalar d0 = this->d_;
421  const vector& U0 = this->U_;
422  const scalar T0 = this->T_;
423  const scalar mass0 = this->mass();
424 
425 
426  // Calc surface values
427  scalar Ts, rhos, mus, Prs, kappas;
428  this->calcSurfaceValues(td, cellI, T0, Ts, rhos, mus, Prs, kappas);
429  scalar Res = this->Re(U0, d0, rhos, mus);
430 
431 
432  // Sources
433  // ~~~~~~~
434 
435  // Explicit momentum source for particle
437 
438  // Linearised momentum source coefficient
439  scalar Spu = 0.0;
440 
441  // Momentum transfer from the particle to the carrier phase
442  vector dUTrans = vector::zero;
443 
444  // Explicit enthalpy source for particle
445  scalar Sh = 0.0;
446 
447  // Linearised enthalpy source coefficient
448  scalar Sph = 0.0;
449 
450  // Sensible enthalpy transfer from the particle to the carrier phase
451  scalar dhsTrans = 0.0;
452 
453 
454  // 1. Compute models that contribute to mass transfer - U, T held constant
455  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
456 
457  // Phase change
458  // ~~~~~~~~~~~~
459 
460  // Mass transfer due to phase change
461  scalarField dMassPC(Y_.size(), 0.0);
462 
463  // Molar flux of species emitted from the particle (kmol/m^2/s)
464  scalar Ne = 0.0;
465 
466  // Sum Ni*Cpi*Wi of emission species
467  scalar NCpW = 0.0;
468 
469  // Surface concentrations of emitted species
470  scalarField Cs(composition.carrier().species().size(), 0.0);
471 
472  // Calc mass and enthalpy transfer due to phase change
473  calcPhaseChange
474  (
475  td,
476  dt,
477  cellI,
478  Res,
479  Prs,
480  Ts,
481  mus/rhos,
482  d0,
483  T0,
484  mass0,
485  0,
486  1.0,
487  Y_,
488  dMassPC,
489  Sh,
490  Ne,
491  NCpW,
492  Cs
493  );
494 
495 
496  // 2. Update the parcel properties due to change in mass
497  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
498 
499  scalarField dMass(dMassPC);
500  scalar mass1 = updateMassFraction(mass0, dMass, Y_);
501 
502  this->Cp_ = composition.Cp(0, Y_, pc_, T0);
503 
504  // Update particle density or diameter
505  if (td.cloud().constProps().constantVolume())
506  {
507  this->rho_ = mass1/this->volume();
508  }
509  else
510  {
511  this->d_ = cbrt(mass1/this->rho_*6.0/pi);
512  }
513 
514  // Remove the particle when mass falls below minimum threshold
515  if (np0*mass1 < td.cloud().constProps().minParcelMass())
516  {
517  td.keepParticle = false;
518 
519  if (td.cloud().solution().coupled())
520  {
521  scalar dm = np0*mass0;
522 
523  // Absorb parcel into carrier phase
524  forAll(Y_, i)
525  {
526  scalar dmi = dm*Y_[i];
527  label gid = composition.localToCarrierId(0, i);
528  scalar hs = composition.carrier().Hs(gid, pc_, T0);
529 
530  td.cloud().rhoTrans(gid)[cellI] += dmi;
531  td.cloud().hsTrans()[cellI] += dmi*hs;
532  }
533  td.cloud().UTrans()[cellI] += dm*U0;
534 
535  td.cloud().phaseChange().addToPhaseChangeMass(np0*mass1);
536  }
537 
538  return;
539  }
540 
541  // Correct surface values due to emitted species
542  correctSurfaceValues(td, cellI, Ts, Cs, rhos, mus, Prs, kappas);
543  Res = this->Re(U0, this->d_, rhos, mus);
544 
545 
546  // 3. Compute heat- and momentum transfers
547  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
548 
549  // Heat transfer
550  // ~~~~~~~~~~~~~
551 
552  // Calculate new particle temperature
553  this->T_ =
554  this->calcHeatTransfer
555  (
556  td,
557  dt,
558  cellI,
559  Res,
560  Prs,
561  kappas,
562  NCpW,
563  Sh,
564  dhsTrans,
565  Sph
566  );
567 
568  this->Cp_ = composition.Cp(0, Y_, pc_, T0);
569 
570 
571  // Motion
572  // ~~~~~~
573 
574  // Calculate new particle velocity
575  this->U_ =
576  this->calcVelocity(td, dt, cellI, Res, mus, mass1, Su, dUTrans, Spu);
577 
578 
579  // 4. Accumulate carrier phase source terms
580  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
581 
582  if (td.cloud().solution().coupled())
583  {
584  // Transfer mass lost to carrier mass, momentum and enthalpy sources
585  forAll(dMass, i)
586  {
587  scalar dm = np0*dMass[i];
588  label gid = composition.localToCarrierId(0, i);
589  scalar hs = composition.carrier().Hs(gid, pc_, T0);
590 
591  td.cloud().rhoTrans(gid)[cellI] += dm;
592  td.cloud().UTrans()[cellI] += dm*U0;
593  td.cloud().hsTrans()[cellI] += dm*hs;
594  }
595 
596  // Update momentum transfer
597  td.cloud().UTrans()[cellI] += np0*dUTrans;
598  td.cloud().UCoeff()[cellI] += np0*Spu;
599 
600  // Update sensible enthalpy transfer
601  td.cloud().hsTrans()[cellI] += np0*dhsTrans;
602  td.cloud().hsCoeff()[cellI] += np0*Sph;
603 
604  // Update radiation fields
605  if (td.cloud().radiation())
606  {
607  const scalar ap = this->areaP();
608  const scalar T4 = pow4(T0);
609  td.cloud().radAreaP()[cellI] += dt*np0*ap;
610  td.cloud().radT4()[cellI] += dt*np0*T4;
611  td.cloud().radAreaPT4()[cellI] += dt*np0*ap*T4;
612  }
613  }
614 }
615 
616 
617 // * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
618 
619 #include "ReactingParcelIO.C"
620 
621 // ************************************************************************* //
dimensionedScalar sqrt(const dimensionedScalar &ds)
const basicSpecieMixture & carrier() const
Return reference to the gaseous components.
Definition: SLGThermo.C:108
virtual scalar mu(const label speciei, const scalar p, const scalar T) const =0
Dynamic viscosity [kg/m/s].
virtual bool active() const
Return the model &#39;active&#39; status - default active = true.
Definition: subModelBase.C:154
dimensioned< scalar > mag(const dimensioned< Type > &)
void T(FieldField< Field, Type > &f1, const FieldField< Field, Type > &f2)
virtual scalar dh(const label idc, const label idl, const scalar p, const scalar T) const
Return the enthalpy per unit mass.
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
dimensionedScalar cbrt(const dimensionedScalar &ds)
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:76
virtual scalar W(const label speciei) const =0
Molecular weight of the given specie [kg/kmol].
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.
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.
scalar pc_
Pressure [Pa].
const liquidMixtureProperties & liquids() const
Return the global (additional) liquids.
Templated phase change model class.
Definition: ReactingCloud.H:55
Thermo package for (S)olids (L)iquids and (G)ases Takes reference to thermo package, and provides:
Definition: SLGThermo.H:62
Reacting parcel class with one/two-way coupling with the continuous phase.
tmp< GeometricField< Type, fvPatchField, volMesh > > Su(const GeometricField< Type, fvPatchField, volMesh > &su, const GeometricField< Type, fvPatchField, volMesh > &vf)
Definition: fvcSup.C:44
virtual scalar TMax(const scalar p, const scalarField &X) const
Return maximum/limiting temperature.
void setCellValues(TrackData &td, const scalar dt, const label cellI)
Set cell values.
static const char nl
Definition: Ostream.H:260
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:251
basicMultiComponentMixture & composition
Definition: createFields.H:35
#define WarningIn(functionName)
Report a warning using Foam::Warning.
label localToCarrierId(const label phaseI, const label id, const bool allowNotFound=false) const
Return carrier id of component given local id.
virtual scalar kappa(const label speciei, const scalar p, const scalar T) const =0
Thermal conductivity [W/m/K].
dimensioned< Type > max(const dimensioned< Type > &, const dimensioned< Type > &)
#define forAll(list, i)
Definition: UList.H:421
scalar updateMassFraction(const scalar mass0, const scalarField &dMass, scalarField &Y) const
Update mass fraction.
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
void addToPhaseChangeMass(const scalar dMass)
Add to phase change mass.
scalarField Y_
Mass fractions of mixture [].
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.
const scalar RR
Universal gas constant (default in [J/(kmol K)])
PtrList< volScalarField > & Y()
Return the mass-fraction fields.
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:74
void cellValueSourceCorrection(TrackData &td, const scalar dt, const label cellI)
Correct cell values using latest transfer information.
virtual scalar Tvap(const scalarField &X) const
Return vapourisation temperature.
virtual scalar Cp(const label speciei, const scalar p, const scalar T) const =0
Heat capacity at constant pressure [J/(kg K)].
static const Vector zero
Definition: Vector.H:80
dimensionedScalar pow4(const dimensionedScalar &ds)
Templated reacting parcel composition model class Consists of carrier species (via thermo package)...
Definition: ReactingCloud.H:52
void calc(TrackData &td, const scalar dt, const label cellI)
Update parcel properties over the time interval.
virtual scalar Cp(const label phaseI, const scalarField &Y, const scalar p, const scalar T) const
Return specific heat caoacity for the phase phaseI.
const speciesTable & species() const
Return the table of species.
dimensioned< Type > min(const dimensioned< Type > &, const dimensioned< Type > &)
mathematical constants.
const basicSpecieMixture & carrier() const
Return the carrier components (wrapper function)
Basic thermodynamics type based on the use of fitting functions for cp, h, s obtained from the templa...
scalar mass0_
Initial mass [kg].
ReactingParcel(const polyMesh &mesh, const vector &position, const label cellI, const label tetFaceI, const label tetPtI)
Construct from owner, position, and cloud owner.
scalarField Re(const UList< complex > &cf)
Definition: complexFields.C:97