1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright (C) 2011 OpenFOAM Foundation
7 -------------------------------------------------------------------------------
9 This file is part of OpenFOAM.
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.
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
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/>.
24 \*---------------------------------------------------------------------------*/
26 #include "ReactingParcel.H"
28 #include "CompositionModel.H"
29 #include "mathematicalConstants.H"
31 using namespace Foam::constant::mathematical;
33 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
35 template<class ParcelType>
36 template<class TrackData>
37 void Foam::ReactingParcel<ParcelType>::setCellValues
44 ParcelType::setCellValues(td, dt, cellI);
46 pc_ = td.pInterp().interpolate
49 this->currentTetIndices()
52 if (pc_ < td.cloud().constProps().pMin())
58 "void Foam::ReactingParcel<ParcelType>::setCellValues"
64 ) << "Limiting observed pressure in cell " << cellI << " to "
65 << td.cloud().constProps().pMin() << nl << endl;
68 pc_ = td.cloud().constProps().pMin();
73 template<class ParcelType>
74 template<class TrackData>
75 void Foam::ReactingParcel<ParcelType>::cellValueSourceCorrection
82 scalar addedMass = 0.0;
83 forAll(td.cloud().rhoTrans(), i)
85 addedMass += td.cloud().rhoTrans(i)[cellI];
88 if (addedMass < ROOTVSMALL)
93 const scalar massCell = this->massCell(cellI);
95 this->rhoc_ += addedMass/td.cloud().pMesh().cellVolumes()[cellI];
97 const scalar massCellNew = massCell + addedMass;
98 this->Uc_ += td.cloud().UTrans()[cellI]/massCellNew;
101 if (addedMass > ROOTVSMALL)
103 forAll(td.cloud().rhoTrans(), i)
105 scalar Y = td.cloud().rhoTrans(i)[cellI]/addedMass;
106 CpEff += Y*td.cloud().composition().carrier().Cp(i, this->Tc_);
110 const scalar Cpc = td.CpInterp().psi()[cellI];
111 this->Cpc_ = (massCell*Cpc + addedMass*CpEff)/massCellNew;
113 this->Tc_ += td.cloud().hsTrans()[cellI]/(this->Cpc_*massCellNew);
115 if (debug && (this->Tc_ < td.cloud().constProps().TMin()))
121 "void Foam::ReactingParcel<ParcelType>::"
122 "cellValueSourceCorrection"
128 ) << "Limiting observed temperature in cell " << cellI << " to "
129 << td.cloud().constProps().TMin() << nl << endl;
132 this->Tc_ = td.cloud().constProps().TMin();
136 // this->pc_ = this->pc_;
140 template<class ParcelType>
141 template<class TrackData>
142 void Foam::ReactingParcel<ParcelType>::correctSurfaceValues
147 const scalarField& Cs,
154 // No correction if total concentration of emitted species is small
160 const SLGThermo& thermo = td.cloud().thermo();
162 // Far field carrier molar fractions
163 scalarField Xinf(td.cloud().thermo().carrier().species().size());
167 Xinf[i] = thermo.carrier().Y(i)[cellI]/thermo.carrier().W(i);
171 // Molar fraction of far field species at particle surface
172 const scalar Xsff = 1.0 - min(sum(Cs)*specie::RR*this->T_/pc_, 1.0);
174 // Surface carrier total molar concentration
175 const scalar CsTot = pc_/(specie::RR*this->T_);
177 // Surface carrier composition (molar fraction)
178 scalarField Xs(Xinf.size());
180 // Surface carrier composition (mass fraction)
181 scalarField Ys(Xinf.size());
185 // Molar concentration of species at particle surface
186 const scalar Csi = Cs[i] + Xsff*Xinf[i]*CsTot;
188 Xs[i] = (2.0*Csi + Xinf[i]*CsTot)/3.0;
189 Ys[i] = Xs[i]*thermo.carrier().W(i);
199 scalar sumYiSqrtW = 0;
200 scalar sumYiCbrtW = 0;
204 const scalar W = thermo.carrier().W(i);
205 const scalar sqrtW = sqrt(W);
206 const scalar cbrtW = cbrt(W);
209 mus += Ys[i]*sqrtW*thermo.carrier().mu(i, T);
210 kappas += Ys[i]*cbrtW*thermo.carrier().kappa(i, T);
211 Cps += Xs[i]*thermo.carrier().Cp(i, T);
213 sumYiSqrtW += Ys[i]*sqrtW;
214 sumYiCbrtW += Ys[i]*cbrtW;
217 Cps = max(Cps, ROOTVSMALL);
219 rhos *= pc_/(specie::RR*T);
220 rhos = max(rhos, ROOTVSMALL);
223 mus = max(mus, ROOTVSMALL);
225 kappas /= sumYiCbrtW;
226 kappas = max(kappas, ROOTVSMALL);
228 Prs = Cps*mus/kappas;
232 template<class ParcelType>
233 Foam::scalar Foam::ReactingParcel<ParcelType>::updateMassFraction
236 const scalarField& dMass,
240 scalar mass1 = mass0 - sum(dMass);
242 // only update the mass fractions if the new particle mass is finite
243 if (mass1 > ROOTVSMALL)
247 Y[i] = (Y[i]*mass0 - dMass[i])/mass1;
255 template<class ParcelType>
256 template<class TrackData>
257 void Foam::ReactingParcel<ParcelType>::calc
264 typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
265 const CompositionModel<reactingCloudType>& composition =
266 td.cloud().composition();
269 // Define local properties at beginning of time step
270 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
272 const scalar np0 = this->nParticle_;
273 const scalar d0 = this->d_;
274 const vector& U0 = this->U_;
275 const scalar T0 = this->T_;
276 const scalar mass0 = this->mass();
279 // Calc surface values
280 scalar Ts, rhos, mus, Prs, kappas;
281 this->calcSurfaceValues(td, cellI, T0, Ts, rhos, mus, Prs, kappas);
282 scalar Res = this->Re(U0, d0, rhos, mus);
288 // Explicit momentum source for particle
289 vector Su = vector::zero;
291 // Linearised momentum source coefficient
294 // Momentum transfer from the particle to the carrier phase
295 vector dUTrans = vector::zero;
297 // Explicit enthalpy source for particle
300 // Linearised enthalpy source coefficient
303 // Sensible enthalpy transfer from the particle to the carrier phase
304 scalar dhsTrans = 0.0;
307 // 1. Compute models that contribute to mass transfer - U, T held constant
308 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
313 // Mass transfer due to phase change
314 scalarField dMassPC(Y_.size(), 0.0);
316 // Molar flux of species emitted from the particle (kmol/m^2/s)
319 // Sum Ni*Cpi*Wi of emission species
322 // Surface concentrations of emitted species
323 scalarField Cs(composition.carrier().species().size(), 0.0);
325 // Calc mass and enthalpy transfer due to phase change
348 // 2. Update the parcel properties due to change in mass
349 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
351 scalarField dMass(dMassPC);
352 scalar mass1 = updateMassFraction(mass0, dMass, Y_);
354 this->Cp_ = composition.Cp(0, Y_, pc_, T0);
356 // Update particle density or diameter
357 if (td.cloud().constProps().constantVolume())
359 this->rho_ = mass1/this->volume();
363 this->d_ = cbrt(mass1/this->rho_*6.0/pi);
366 // Remove the particle when mass falls below minimum threshold
367 if (np0*mass1 < td.cloud().constProps().minParticleMass())
369 td.keepParticle = false;
371 if (td.cloud().solution().coupled())
373 scalar dm = np0*mass1;
375 // Absorb parcel into carrier phase
378 scalar dmi = dm*Y_[i];
379 label gid = composition.localToGlobalCarrierId(0, i);
380 scalar hs = composition.carrier().Hs(gid, T0);
382 td.cloud().rhoTrans(gid)[cellI] += dmi;
383 td.cloud().hsTrans()[cellI] += dmi*hs;
385 td.cloud().UTrans()[cellI] += dm*U0;
387 td.cloud().addToMassPhaseChange(dm);
393 // Correct surface values due to emitted species
394 correctSurfaceValues(td, cellI, Ts, Cs, rhos, mus, Prs, kappas);
395 Res = this->Re(U0, this->d_, rhos, mus);
398 // 3. Compute heat- and momentum transfers
399 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
404 // Calculate new particle temperature
406 this->calcHeatTransfer
420 this->Cp_ = composition.Cp(0, Y_, pc_, T0);
426 // Calculate new particle velocity
428 this->calcVelocity(td, dt, cellI, Res, mus, mass1, Su, dUTrans, Spu);
431 // 4. Accumulate carrier phase source terms
432 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434 if (td.cloud().solution().coupled())
436 // Transfer mass lost to carrier mass, momentum and enthalpy sources
439 scalar dm = np0*dMass[i];
440 label gid = composition.localToGlobalCarrierId(0, i);
441 scalar hs = composition.carrier().Hs(gid, T0);
443 td.cloud().rhoTrans(gid)[cellI] += dm;
444 td.cloud().UTrans()[cellI] += dm*U0;
445 td.cloud().hsTrans()[cellI] += dm*hs;
448 // Update momentum transfer
449 td.cloud().UTrans()[cellI] += np0*dUTrans;
450 td.cloud().UCoeff()[cellI] += np0*Spu;
452 // Update sensible enthalpy transfer
453 td.cloud().hsTrans()[cellI] += np0*dhsTrans;
454 td.cloud().hsCoeff()[cellI] += np0*Sph;
459 template<class ParcelType>
460 template<class TrackData>
461 void Foam::ReactingParcel<ParcelType>::calcPhaseChange
474 const scalarField& YComponents,
475 scalarField& dMassPC,
484 !td.cloud().phaseChange().active()
485 || T < td.cloud().constProps().Tvap()
492 typedef typename TrackData::cloudType::reactingCloudType reactingCloudType;
493 const CompositionModel<reactingCloudType>& composition =
494 td.cloud().composition();
497 // Calculate mass transfer due to phase change
498 td.cloud().phaseChange().calculate
511 // Limit phase change mass by availability of each specie
512 dMassPC = min(mass*YPhase*YComponents, dMassPC);
514 const scalar dMassTot = sum(dMassPC);
516 // Add to cumulative phase change mass
517 td.cloud().addToMassPhaseChange(this->nParticle_*dMassTot);
521 const label idc = composition.localToGlobalCarrierId(idPhase, i);
522 const label idl = composition.globalIds(idPhase)[i];
524 const scalar dh = td.cloud().phaseChange().dh(idc, idl, pc_, T);
525 Sh -= dMassPC[i]*dh/dt;
529 // Update molar emissions
530 if (td.cloud().heatTransfer().BirdCorrection())
532 // Average molecular weight of carrier mix - assumes perfect gas
533 const scalar Wc = this->rhoc_*specie::RR*this->Tc_/this->pc_;
538 const label idc = composition.localToGlobalCarrierId(idPhase, i);
539 const label idl = composition.globalIds(idPhase)[i];
541 const scalar Cp = composition.carrier().Cp(idc, Ts);
542 const scalar W = composition.carrier().W(idc);
543 const scalar Ni = dMassPC[i]/(this->areaS(d)*dt*W);
546 composition.liquids().properties()[idl].D(pc_, Ts, Wc);
548 // Molar flux of species coming from the particle (kmol/m^2/s)
551 // Sum of Ni*Cpi*Wi of emission species
554 // Concentrations of emission species
555 Cs[idc] += Ni*d/(2.0*Dab);
561 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
563 template<class ParcelType>
564 Foam::ReactingParcel<ParcelType>::ReactingParcel
566 const ReactingParcel<ParcelType>& p
576 template<class ParcelType>
577 Foam::ReactingParcel<ParcelType>::ReactingParcel
579 const ReactingParcel<ParcelType>& p,
590 // * * * * * * * * * * * * * * IOStream operators * * * * * * * * * * * * * //
592 #include "ReactingParcelIO.C"
594 // ************************************************************************* //