phaseChange.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) 2021-2025 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 "phaseChange.H"
29 
30 // * * * * * * * * * * * * * Static Member Functions * * * * * * * * * * * * //
31 
32 namespace Foam
33 {
34 namespace fv
35 {
37 }
38 }
39 
40 
41 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
42 
43 void Foam::fv::phaseChange::readCoeffs(const dictionary& dict)
44 {
45  energySemiImplicit_ = dict.lookup<bool>("energySemiImplicit");
46 }
47 
48 
49 // * * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * //
50 
52 (
53  const dictionary& dict,
54  const bool required
55 ) const
56 {
57  const bool haveSpecie = dict.found("specie");
58 
59  if (required && !haveSpecie)
60  {
61  dict.lookup<word>("specie");
62  }
63 
64  const wordList result =
65  haveSpecie
66  ? wordList(1, dict.lookup<word>("specie"))
67  : wordList();
68 
69  return result;
70 }
71 
72 
74 (
75  const dictionary& dict,
76  const bool required
77 ) const
78 {
79  const bool haveSpecie = dict.found("specie");
80  const bool haveSpecies = dict.found("species");
81 
82  if (haveSpecie && haveSpecies)
83  {
85  << "Both keywords specie and species "
86  << " are defined in dictionary " << dict.name()
87  << exit(FatalError);
88  }
89 
90  if (required && !haveSpecie && !haveSpecies)
91  {
93  << "Neither keywords specie nor species "
94  << " are defined in dictionary " << dict.name()
95  << exit(FatalError);
96  }
97 
98  const wordList result =
99  haveSpecie ? wordList(1, dict.lookup<word>("specie"))
100  : haveSpecies ? dict.lookup<wordList>("species")
101  : wordList();
102 
103  return result;
104 }
105 
106 
108 {
109  if (species() != readSpecie(dict, false))
110  {
112  << "Cannot change the specie of a " << type() << " model "
113  << "at run time" << exit(FatalIOError);
114  }
115 }
116 
117 
119 {
120  if (species() != readSpecies(dict, false))
121  {
123  << "Cannot change the species of a " << type() << " model "
124  << "at run time" << exit(FatalIOError);
125  }
126 }
127 
128 
130 (
131  const word& name,
132  const word& modelType,
133  const wordList& species
134 )
135 {
136  const ThermoRefPair<multicomponentThermo>& mcThermos =
137  thermos().thermos<multicomponentThermo>();
138 
139  // Set the names
140  species_ = species;
141 
142  // Set the indices
143  specieis_ = List<labelPair>(species.size(), labelPair(-1, -1));
144  forAll(phaseNames(), i)
145  {
146  if (mcThermos.valid()[i])
147  {
148  forAll(species, mDoti)
149  {
150  specieis_[mDoti][i] = mcThermos[i].species()[species[mDoti]];
151  }
152  }
153  }
154 
155  // Checks ...
156 
157  // If either phase is multicomponent then species should
158  // have been specified
159  if (mcThermos.either() && species_.empty())
160  {
162  << "Mixture transfer specified by model " << name << " of type "
163  << modelType << " for two phases " << phaseNames().first()
164  << " and " << phaseNames().second() << " but ";
165 
166  mcThermos.both()
168  << "both phases have"
170  << "phase " << phaseNames()[mcThermos.valid().second()] << " has";
171 
173  << " multiple species" << exit(FatalError);
174  }
175 
176  // If neither phase is multicomponent then species should
177  // not have been specified
178  if (!mcThermos.either() && species_.size())
179  {
181  << "Specie transfer specified by model " << name
182  << " of type " << modelType << " for two pure phases "
183  << phaseNames().first() << " and " << phaseNames().second()
184  << exit(FatalError);
185  }
186 
187  // If either phase is pure then there can be at most one specie
188  if (!mcThermos.both() && species_.size() > 1)
189  {
191  << "Multi-specie transfer specified by model " << name
192  << " of type " << modelType << " for phases "
193  << phaseNames().first() << " and " << phaseNames().second()
194  << " but phase " << phaseNames()[mcThermos.valid().first()]
195  << " is pure " << exit(FatalError);
196  }
197 }
198 
199 
201 {
202  setSpecies(name(), type(), species);
203 }
204 
205 
207 {
208  if (this->species() != species)
209  {
211  << "Cannot change the species of a " << type() << " model "
212  << "at run time" << exit(FatalError);
213  }
214 }
215 
216 
218 {
219  for (label i = 0; i < 2; ++ i)
220  {
221  if (isA<fluidThermo>(thermos()[i]))
222  {
223  return refCast<const fluidThermo>(thermos()[i]).p();
224  }
225  }
226 
227  return mesh().lookupObject<volScalarField>("p");
228 }
229 
230 
232 (
234 )
235 {
238  (
239  tvif().name(),
240  tvif().mesh(),
241  tvif().dimensions(),
243  );
244 
245  tvf->internalFieldRef() = tvif();
246  tvf->correctBoundaryConditions();
247 
248  tvif.clear();
249 
250  return tvf;
251 }
252 
253 
255 (
256  const tmp<volScalarField>& tvf
257 )
258 {
260 
261  tvf.clear();
262 
263  return tvif;
264 }
265 
266 
267 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
268 
270 (
271  const word& name,
272  const word& modelType,
273  const fvMesh& mesh,
274  const dictionary& dict,
275  const wordList& species
276 )
277 :
278  massTransfer(name, modelType, mesh, dict),
279  thermos_(mesh, phaseNames()),
280  heNames_(thermos_.first().he().name(), thermos_.second().he().name()),
281  species_(),
282  specieis_(),
283  energySemiImplicit_(false)
284 {
285  readCoeffs(coeffs(dict));
286 
287  if (notNull(species)) setSpecies(name, modelType, species);
288 }
289 
290 
291 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
292 
295 (
296  const bool firstRequired,
297  const bool secondRequired
298 ) const
299 {
300  return
301  thermos_.thermos<fluidThermo>
302  (
303  {firstRequired, secondRequired},
304  *this,
305  "fluid"
306  );
307 }
308 
309 
312 (
313  const bool firstRequired,
314  const bool secondRequired
315 ) const
316 {
317  return
318  thermos_.thermos<multicomponentThermo>
319  (
320  {firstRequired, secondRequired},
321  *this,
322  "multicomponent"
323  );
324 }
325 
326 
329 (
330  const bool firstRequired,
331  const bool secondRequired
332 ) const
333 {
334  return
335  thermos_.thermos<fluidMulticomponentThermo>
336  (
337  {firstRequired, secondRequired},
338  *this,
339  "fluid-multicomponent"
340  );
341 }
342 
343 
345 {
346  static const labelPair noSpecieis(-1, -1);
347 
348  if (mDoti == -1)
349  {
350  if (species().empty())
351  {
352  return noSpecieis;
353  }
354  else if (species().size() == 1)
355  {
356  return specieis_[0];
357  }
358  else
359  {
361  << "Requested mixture/single-specie indices from multi-specie "
362  << "model of type" << type() << exit(FatalError);
363  }
364  }
365 
366  return specieis_[mDoti];
367 }
368 
369 
372 {
375  (
376  name() + ":Tchange",
377  mesh(),
379  );
380  volScalarField::Internal& Tchange = tTchange.ref();
381 
382  tmp<volScalarField::Internal> tmDot = this->mDot();
383  const volScalarField::Internal& mDot = tmDot();
384 
385  const volScalarField::Internal& T1 = thermos().first().T();
386  const volScalarField::Internal& T2 = thermos().second().T();
387 
388  forAll(Tchange, i)
389  {
390  Tchange[i] =
391  mDot[i] > 0 ? T1[i]
392  : mDot[i] < 0 ? T2[i]
393  : (T1[i] + T2[i])/2;
394  }
395 
396  return tTchange;
397 }
398 
399 
402 {
403  const volScalarField::Internal& kappa1 = thermos().first().kappa();
404  const volScalarField::Internal& kappa2 = thermos().second().kappa();
405 
406  return kappa2/(kappa1 + kappa2);
407 }
408 
409 
411 (
412  const label mDoti
413 ) const
414 {
415  return L(this->Tchange(), mDoti);
416 }
417 
418 
420 (
421  const volScalarField::Internal& Tchange,
422  const label mDoti
423 ) const
424 {
425  const ThermoRefPair<multicomponentThermo>& mcThermos =
426  thermos().thermos<multicomponentThermo>();
427 
428  const labelPair specieis = this->specieis(mDoti);
429 
430  const volScalarField::Internal& p = this->p();
431 
432  // Absolute enthalpies at the interface
434  for (label j = 0; j < 2; ++ j)
435  {
436  has[j] =
437  specieis[j] == -1
438  ? thermos()[j].ha(p, Tchange)
439  : mcThermos[j].hai(specieis[j], p, Tchange);
440  }
441 
442  // Latent heat of phase change
443  return has.second() - has.first();
444 }
445 
446 
448 (
449  const label patchi,
450  const scalarField& Tchange,
451  const label mDoti
452 ) const
453 {
454  const ThermoRefPair<multicomponentThermo>& mcThermos =
455  thermos().thermos<multicomponentThermo>();
456 
457  const labelPair specieis = this->specieis(mDoti);
458 
459  const fvPatchScalarField& p = this->p().boundaryField()[patchi];
460 
461  // Absolute enthalpies at the interface
463  for (label j = 0; j < 2; ++ j)
464  {
465  has[j] =
466  specieis[j] == -1
467  ? thermos()[j].ha(Tchange, patchi)
468  : mcThermos[j].hai(specieis[j], p, Tchange);
469  }
470 
471  // Latent heat of phase change
472  return has.second() - has.first();
473 }
474 
475 
477 {
478  if (species().empty())
479  {
481  << "Mixture phase change rate not defined by model of type "
482  << type() << exit(FatalError);
483  }
484 
487  (
488  "mDot",
489  mesh(),
491  );
492 
493  forAll(species(), mDoti)
494  {
495  tmDot.ref() += mDot(mDoti);
496  }
497 
498  return tmDot;
499 }
500 
501 
503 (
504  const label mDoti
505 ) const
506 {
507  if (mDoti == -1)
508  {
509  return mDot();
510  }
511 
512  if (mDoti == 0 && species().size() == 1)
513  {
514  return mDot();
515  }
516 
517  if (species().size() > 1)
518  {
520  << "Specie phase change rate not defined by model of type "
521  << type() << exit(FatalError);
522  }
523 
524  return tmp<volScalarField::Internal>(nullptr);
525 }
526 
527 
529 (
530  const volScalarField& alpha,
531  const volScalarField& rho,
532  const volScalarField& heOrYi,
533  fvMatrix<scalar>& eqn
534 ) const
535 {
536  const label i = index(phaseNames(), alpha.group());
537  const label s = sign(phaseNames(), alpha.group());
538 
539  const ThermoRefPair<multicomponentThermo>& mcThermos =
540  thermos().thermos<multicomponentThermo>();
541 
542  // Energy equation
543  if (index(heNames(), heOrYi.name()) != -1)
544  {
545  const volScalarField::Internal& p = this->p();
546  tmp<volScalarField::Internal> tTchange = this->Tchange();
547 
548  for
549  (
550  label mDoti = species().empty() ? -1 : 0;
551  mDoti < species().size();
552  mDoti ++
553  )
554  {
555  const labelPair specieis = this->specieis(mDoti);
556  tmp<volScalarField::Internal> tmDot = this->mDot(mDoti);
557 
558  // Direct transfer of energy due to mass transfer
559  eqn +=
560  s
561  *tmDot()
562  *(
563  specieis[i] == -1
564  ? thermos()[i].hs(p, tTchange())
565  : mcThermos[i].hsi(specieis[i], p, tTchange())
566  );
567 
568  // Optional linearisation
569  if (energySemiImplicit_)
570  {
571  eqn += -fvm::SuSp(-s*tmDot(), heOrYi) - s*tmDot()*heOrYi;
572  }
573 
574  // Latent heat of phase change
575  eqn -=
576  (i == 0 ? 1 - Lfraction() : Lfraction())
577  *tmDot
578  *L(tTchange(), mDoti);
579  }
580 
581  return;
582  }
583 
584  // Mass fraction equation
585  const word specieName = heOrYi.member();
586  if (mcThermos.valid()[i] && mcThermos[i].containsSpecie(specieName))
587  {
588  // A non-transferring specie. Do not add a source.
589  if (!species().found(specieName)) return;
590 
591  // A transferring specie. Add a source.
592  eqn += s*mDot(species()[specieName]);
593 
594  return;
595  }
596 
597  // Something else. Fall back.
598  massTransfer::addSup(alpha, rho, heOrYi, eqn);
599 }
600 
601 
603 {
605  {
606  readCoeffs(coeffs(dict));
607  return true;
608  }
609  else
610  {
611  return false;
612  }
613 }
614 
615 
616 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Macros for easy insertion into run-time selection tables.
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
static tmp< DimensionedField< Type, GeoMesh, PrimitiveField > > New(const word &name, const GeoMesh &mesh, const dimensionSet &, const PrimitiveField< Type > &)
Return a temporary field constructed from name, mesh,.
tmp< DimensionedField< Type, GeoMesh, Field > > T() const
Return the field transpose (only defined for second rank tensors)
Generic GeometricField class.
static tmp< GeometricField< Type, GeoMesh, PrimitiveField > > New(const word &name, const Internal &, const PtrList< Patch > &, const HashPtrTable< Source > &=HashPtrTable< Source >())
Return a temporary field constructed from name,.
static word member(const word &name)
Return member (name without the extension)
Definition: IOobject.C:146
const word & name() const
Return name.
Definition: IOobject.H:307
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
const Type & second() const
Return second.
Definition: PairI.H:121
const Type & first() const
Return first.
Definition: PairI.H:107
Class containing a pair of thermo references. Handles down-casting to more specific thermo types by c...
Definition: ThermoRefPair.H:51
const Pair< bool > & valid() const
Access the validity flags.
bool either() const
Return if either validity flag is set.
ThermoRefPair< OtherThermoType > thermos() const
Cast to a different thermo type, with error checking.
bool both() const
Return if both validity flags are set.
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
This boundary condition applies a zero-gradient condition from the patch internal field onto the patc...
Base-class for multi-component fluid thermodynamic properties.
Base-class for fluid thermodynamic properties.
Definition: fluidThermo.H:56
A special matrix type and solver, designed for finite volume solutions of scalar equations....
Definition: fvMatrix.H:118
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:98
static const dictionary & coeffs(const word &modelType, const dictionary &)
Return the coefficients sub-dictionary for a given model type.
Definition: fvModelI.H:31
const word & name() const
Return const access to the source name.
Definition: fvModelI.H:57
Abstract base class with a fat-interface to all derived classes covering all possible ways in which t...
Definition: fvPatchField.H:90
Base class for mass transfers between phases.
Definition: massTransfer.H:55
virtual bool read(const dictionary &dict)
Read source dictionary.
Definition: massTransfer.C:247
virtual void addSup(fvMatrix< scalar > &eqn) const
Add a source term to a field-less proxy equation.
Definition: massTransfer.C:223
Base class for phase change models.
Definition: phaseChange.H:61
void reReadSpecies(const dictionary &dict) const
Re-read the names of the transferring species.
Definition: phaseChange.C:118
const labelPair & specieis(const label mDoti=-1) const
Return the indices of the transferring specie in the two.
Definition: phaseChange.C:344
wordList readSpecie(const dictionary &dict, const bool required) const
Read the names of the transferring specie.
Definition: phaseChange.C:52
const ThermoRefPair< multicomponentThermo > multicomponentThermos(const bool, const bool) const
Return the multicomponent thermo references.
Definition: phaseChange.C:312
virtual tmp< DimensionedField< scalar, fvMesh > > Lfraction() const
Return the fraction of the latent heat that is transferred into.
Definition: phaseChange.C:401
const ThermoRefPair< fluidThermo > fluidThermos(const bool, const bool) const
Return the fluid thermo references.
Definition: phaseChange.C:295
void reReadSpecie(const dictionary &dict) const
Re-read the names of the transferring specie.
Definition: phaseChange.C:107
wordList readSpecies(const dictionary &dict, const bool required) const
Read the names of the transferring species.
Definition: phaseChange.C:74
static tmp< DimensionedField< scalar, fvMesh > > vfToVif(const tmp< volScalarField > &tvf)
Remove the boundary field from the given geometric field.
Definition: phaseChange.C:255
static tmp< volScalarField > vifToVf(const tmp< DimensionedField< scalar, fvMesh >> &tvif)
Add a boundary field to the given internal field.
Definition: phaseChange.C:232
virtual tmp< DimensionedField< scalar, fvMesh > > mDot() const
Return the total phase change rate.
Definition: phaseChange.C:476
virtual bool read(const dictionary &dict)
Read source dictionary.
Definition: phaseChange.C:602
void addSup(const volScalarField &alpha, const volScalarField &rho, const volScalarField &heOrYi, fvMatrix< scalar > &eqn) const
Override the energy equation to add the phase change heat, or.
Definition: phaseChange.C:529
void setSpecies(const word &name, const word &modelType, const wordList &species)
Set the names of the transferring species.
Definition: phaseChange.C:130
const ThermoRefPair< fluidMulticomponentThermo > fluidMulticomponentThermos(const bool, const bool) const
Return the fluid multicomponent thermo references.
Definition: phaseChange.C:329
void reSetSpecies(const wordList &species)
Re-set the names of the transferring species.
Definition: phaseChange.C:206
tmp< DimensionedField< scalar, fvMesh > > L(const label mDoti=-1) const
Return the latent heat.
Definition: phaseChange.C:411
const hashedWordList & species() const
Return the names of the transferring species. Empty if neither.
Definition: phaseChangeI.H:44
const volScalarField & p() const
Access the pressure field.
Definition: phaseChange.C:217
virtual tmp< DimensionedField< scalar, fvMesh > > Tchange() const
Return the temperature at which the phases are considered to be.
Definition: phaseChange.C:371
phaseChange(const word &name, const word &modelType, const fvMesh &mesh, const dictionary &dict, const wordList &species)
Construct from explicit source name and mesh.
Definition: phaseChange.C:270
Base-class for multi-component thermodynamic properties.
const Type & lookupObject(const word &name) const
Lookup and return the object of the given Type and name.
A class for managing temporary objects.
Definition: tmp.H:55
T * ptr() const
Return tmp pointer for reuse.
Definition: tmpI.H:221
void clear() const
If object pointer points to valid object:
Definition: tmpI.H:253
T & ref() const
Return non-const reference or generate a fatal error.
Definition: tmpI.H:197
A class for handling words, derived from string.
Definition: word.H:63
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:346
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
label patchi
gmvFile<< "tracers "<< particles.size()<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().x()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().y()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.name(), lagrangian::cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
rho
Definition: pEqn.H:1
volScalarField alpha(IOobject("alpha", runTime.name(), mesh, IOobject::READ_IF_PRESENT, IOobject::AUTO_WRITE), lambda *max(Ua &U, zeroSensitivity))
defineTypeNameAndDebug(bound, 0)
tmp< fvMatrix< Type > > SuSp(const volScalarField::Internal &, const VolField< Type > &)
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
static const zero Zero
Definition: zero.H:97
List< word > wordList
A List of words.
Definition: fileName.H:54
dimensionedScalar sign(const dimensionedScalar &ds)
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
labelList second(const UList< labelPair > &p)
Definition: patchToPatch.C:49
labelList first(const UList< labelPair > &p)
Definition: patchToPatch.C:39
bool notNull(const T &t)
Return true if t is not a reference to the nullObject of type T.
Definition: nullObjectI.H:64
const dimensionSet & dimTime
Definition: dimensions.C:142
const dimensionSet & dimDensity
Definition: dimensions.C:158
IOerror FatalIOError
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
error FatalError
const dimensionSet & dimTemperature
Definition: dimensions.C:143
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
fileType type(const fileName &, const bool checkVariants=true, const bool followLink=true)
Return the file type: directory or file.
Definition: POSIX.C:488
labelList fv(nPoints)
dictionary dict
volScalarField & p