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 const Foam::List<Foam::labelPair> Foam::fv::phaseChange::initSpecieis() const
50 {
51  const ThermoRefPair<multicomponentThermo>& mcThermos =
52  thermos().thermos<multicomponentThermo>();
53 
54  List<labelPair> result(species().size(), labelPair(-1, -1));
55 
56  forAll(phaseNames(), i)
57  {
58  if (mcThermos.valid()[i])
59  {
60  forAll(species(), mDoti)
61  {
62  result[mDoti][i] = mcThermos[i].species()[species()[mDoti]];
63  }
64  }
65  }
66 
67  return result;
68 }
69 
70 
71 // * * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * //
72 
74 (
75  const dictionary& dict,
76  const bool required
77 ) const
78 {
79  const bool haveSpecie = dict.found("specie");
80 
81  if (required && !haveSpecie)
82  {
83  dict.lookup<word>("specie");
84  }
85 
86  const wordList result =
87  haveSpecie
88  ? wordList(1, dict.lookup<word>("specie"))
89  : wordList();
90 
91  return result;
92 }
93 
94 
96 (
97  const dictionary& dict,
98  const bool required
99 ) const
100 {
101  const bool haveSpecie = dict.found("specie");
102  const bool haveSpecies = dict.found("species");
103 
104  if (haveSpecie && haveSpecies)
105  {
107  << "Both keywords specie and species "
108  << " are defined in dictionary " << dict.name()
109  << exit(FatalError);
110  }
111 
112  if (required && !haveSpecie && !haveSpecies)
113  {
115  << "Neither keywords specie nor species "
116  << " are defined in dictionary " << dict.name()
117  << exit(FatalError);
118  }
119 
120  const wordList result =
121  haveSpecie ? wordList(1, dict.lookup<word>("specie"))
122  : haveSpecies ? dict.lookup<wordList>("species")
123  : wordList();
124 
125  return result;
126 }
127 
128 
130 {
131  if (species() != readSpecie(dict, false))
132  {
134  << "Cannot change the specie of a " << type() << " model "
135  << "at run time" << exit(FatalIOError);
136  }
137 }
138 
139 
141 {
142  if (species() != readSpecies(dict, false))
143  {
145  << "Cannot change the species of a " << type() << " model "
146  << "at run time" << exit(FatalIOError);
147  }
148 }
149 
150 
152 (
153  const word& name,
154  const word& modelType,
155  const wordList& species
156 )
157 {
158  const ThermoRefPair<multicomponentThermo>& mcThermos =
159  thermos().thermos<multicomponentThermo>();
160 
161  // Set the names
162  species_ = species;
163 
164  // Set the indices
165  specieis_ = List<labelPair>(species.size(), labelPair(-1, -1));
166  forAll(phaseNames(), i)
167  {
168  if (mcThermos.valid()[i])
169  {
170  forAll(species, mDoti)
171  {
172  specieis_[mDoti][i] = mcThermos[i].species()[species[mDoti]];
173  }
174  }
175  }
176 
177  // Checks ...
178 
179  // If either phase is multicomponent then species should
180  // have been specified
181  if (mcThermos.either() && species_.empty())
182  {
184  << "Mixture transfer specified by model " << name << " of type "
185  << modelType << " for two phases " << phaseNames().first()
186  << " and " << phaseNames().second() << " but ";
187 
188  mcThermos.both()
190  << "both phases have"
192  << "phase " << phaseNames()[mcThermos.valid().second()] << " has";
193 
195  << " multiple species" << exit(FatalError);
196  }
197 
198  // If neither phase is multicomponent then species should
199  // not have been specified
200  if (!mcThermos.either() && species_.size())
201  {
203  << "Specie transfer specified by model " << name
204  << " of type " << modelType << " for two pure phases "
205  << phaseNames().first() << " and " << phaseNames().second()
206  << exit(FatalError);
207  }
208 
209  // If either phase is pure then there can be at most one specie
210  if (!mcThermos.both() && species_.size() > 1)
211  {
213  << "Multi-specie transfer specified by model " << name
214  << " of type " << modelType << " for phases "
215  << phaseNames().first() << " and " << phaseNames().second()
216  << " but phase " << phaseNames()[mcThermos.valid().first()]
217  << " is pure " << exit(FatalError);
218  }
219 }
220 
221 
223 {
224  setSpecies(name(), type(), species);
225 }
226 
227 
229 {
230  if (this->species() != species)
231  {
233  << "Cannot change the species of a " << type() << " model "
234  << "at run time" << exit(FatalError);
235  }
236 }
237 
238 
240 {
241  for (label i = 0; i < 2; ++ i)
242  {
243  if (isA<fluidThermo>(thermos()[i]))
244  {
245  return refCast<const fluidThermo>(thermos()[i]).p();
246  }
247  }
248 
249  return mesh().lookupObject<volScalarField>("p");
250 }
251 
252 
254 (
256 )
257 {
258  tmp<volScalarField> tvf =
260  (
261  tvif().name(),
262  tvif().mesh(),
263  tvif().dimensions(),
265  );
266 
267  tvf->internalFieldRef() = tvif();
268  tvf->correctBoundaryConditions();
269 
270  tvif.clear();
271 
272  return tvf;
273 }
274 
275 
277 (
278  const tmp<volScalarField>& tvf
279 )
280 {
282 
283  tvf.clear();
284 
285  return tvif;
286 }
287 
288 
289 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
290 
292 (
293  const word& name,
294  const word& modelType,
295  const fvMesh& mesh,
296  const dictionary& dict,
297  const wordList& species
298 )
299 :
300  massTransfer(name, modelType, mesh, dict),
301  thermos_(mesh, phaseNames()),
302  heNames_(thermos_.first().he().name(), thermos_.second().he().name()),
303  species_(),
304  specieis_(),
305  energySemiImplicit_(false)
306 {
307  readCoeffs(coeffs(dict));
308 
309  if (notNull(species)) setSpecies(name, modelType, species);
310 }
311 
312 
313 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
314 
317 (
318  const bool firstRequired,
319  const bool secondRequired
320 ) const
321 {
322  return
323  thermos_.thermos<fluidThermo>
324  (
325  {firstRequired, secondRequired},
326  *this,
327  "fluid"
328  );
329 }
330 
331 
334 (
335  const bool firstRequired,
336  const bool secondRequired
337 ) const
338 {
339  return
340  thermos_.thermos<multicomponentThermo>
341  (
342  {firstRequired, secondRequired},
343  *this,
344  "multicomponent"
345  );
346 }
347 
348 
351 (
352  const bool firstRequired,
353  const bool secondRequired
354 ) const
355 {
356  return
357  thermos_.thermos<fluidMulticomponentThermo>
358  (
359  {firstRequired, secondRequired},
360  *this,
361  "fluid-multicomponent"
362  );
363 }
364 
365 
367 {
368  static const labelPair noSpecieis(-1, -1);
369 
370  if (mDoti == -1)
371  {
372  if (species().empty())
373  {
374  return noSpecieis;
375  }
376  else if (species().size() == 1)
377  {
378  return specieis_[0];
379  }
380  else
381  {
383  << "Requested mixture/single-specie indices from multi-specie "
384  << "model of type" << type() << exit(FatalError);
385  }
386  }
387 
388  return specieis_[mDoti];
389 }
390 
391 
394 {
397  (
398  name() + ":Tchange",
399  mesh(),
401  );
402  volScalarField::Internal& Tchange = tTchange.ref();
403 
404  tmp<volScalarField::Internal> tmDot = this->mDot();
405  const volScalarField::Internal& mDot = tmDot();
406 
407  const volScalarField::Internal& T1 = thermos().first().T();
408  const volScalarField::Internal& T2 = thermos().second().T();
409 
410  forAll(Tchange, i)
411  {
412  Tchange[i] =
413  mDot[i] > 0 ? T1[i]
414  : mDot[i] < 0 ? T2[i]
415  : (T1[i] + T2[i])/2;
416  }
417 
418  return tTchange;
419 }
420 
421 
424 {
425  const volScalarField::Internal& kappa1 = thermos().first().kappa();
426  const volScalarField::Internal& kappa2 = thermos().second().kappa();
427 
428  return kappa2/(kappa1 + kappa2);
429 }
430 
431 
433 (
434  const label mDoti
435 ) const
436 {
437  return L(this->Tchange(), mDoti);
438 }
439 
440 
442 (
443  const volScalarField::Internal& Tchange,
444  const label mDoti
445 ) const
446 {
447  const ThermoRefPair<multicomponentThermo>& mcThermos =
448  thermos().thermos<multicomponentThermo>();
449 
450  const labelPair specieis = this->specieis(mDoti);
451 
452  const volScalarField::Internal& p = this->p();
453 
454  // Absolute enthalpies at the interface
456  for (label j = 0; j < 2; ++ j)
457  {
458  has[j] =
459  specieis[j] == -1
460  ? thermos()[j].ha(p, Tchange)
461  : mcThermos[j].hai(specieis[j], p, Tchange);
462  }
463 
464  // Latent heat of phase change
465  return has.second() - has.first();
466 }
467 
468 
470 (
471  const label patchi,
472  const scalarField& Tchange,
473  const label mDoti
474 ) const
475 {
476  const ThermoRefPair<multicomponentThermo>& mcThermos =
477  thermos().thermos<multicomponentThermo>();
478 
479  const labelPair specieis = this->specieis(mDoti);
480 
481  const fvPatchScalarField& p = this->p().boundaryField()[patchi];
482 
483  // Absolute enthalpies at the interface
485  for (label j = 0; j < 2; ++ j)
486  {
487  has[j] =
488  specieis[j] == -1
489  ? thermos()[j].ha(Tchange, patchi)
490  : mcThermos[j].hai(specieis[j], p, Tchange);
491  }
492 
493  // Latent heat of phase change
494  return has.second() - has.first();
495 }
496 
497 
499 {
500  if (species().empty())
501  {
503  << "Mixture phase change rate not defined by model of type "
504  << type() << exit(FatalError);
505  }
506 
509  (
510  "mDot",
511  mesh(),
513  );
514 
515  forAll(species(), mDoti)
516  {
517  tmDot.ref() += mDot(mDoti);
518  }
519 
520  return tmDot;
521 }
522 
523 
525 (
526  const label mDoti
527 ) const
528 {
529  if (mDoti == -1)
530  {
531  return mDot();
532  }
533 
534  if (mDoti == 0 && species().size() == 1)
535  {
536  return mDot();
537  }
538 
539  if (species().size() > 1)
540  {
542  << "Specie phase change rate not defined by model of type "
543  << type() << exit(FatalError);
544  }
545 
546  return tmp<volScalarField::Internal>(nullptr);
547 }
548 
549 
551 (
552  const volScalarField& alpha,
553  const volScalarField& rho,
554  const volScalarField& heOrYi,
555  fvMatrix<scalar>& eqn
556 ) const
557 {
558  const label i = index(phaseNames(), alpha.group());
559  const label s = sign(phaseNames(), alpha.group());
560 
561  const ThermoRefPair<multicomponentThermo>& mcThermos =
562  thermos().thermos<multicomponentThermo>();
563 
564  // Energy equation
565  if (index(heNames(), heOrYi.name()) != -1)
566  {
567  const volScalarField::Internal& p = this->p();
568  tmp<volScalarField::Internal> tTchange = this->Tchange();
569 
570  for
571  (
572  label mDoti = species().empty() ? -1 : 0;
573  mDoti < species().size();
574  mDoti ++
575  )
576  {
577  const labelPair specieis = this->specieis(mDoti);
578  tmp<volScalarField::Internal> tmDot = this->mDot(mDoti);
579 
580  // Direct transfer of energy due to mass transfer
581  eqn +=
582  s
583  *tmDot()
584  *(
585  specieis[i] == -1
586  ? thermos()[i].hs(p, tTchange())
587  : mcThermos[i].hsi(specieis[i], p, tTchange())
588  );
589 
590  // Optional linearisation
591  if (energySemiImplicit_)
592  {
593  eqn += -fvm::SuSp(-s*tmDot(), heOrYi) - s*tmDot()*heOrYi;
594  }
595 
596  // Latent heat of phase change
597  eqn -=
598  (i == 0 ? 1 - Lfraction() : Lfraction())
599  *tmDot
600  *L(tTchange(), mDoti);
601  }
602 
603  return;
604  }
605 
606  // Mass fraction equation
607  const word specieName = heOrYi.member();
608  if (mcThermos.valid()[i] && mcThermos[i].containsSpecie(specieName))
609  {
610  // A non-transferring specie. Do not add a source.
611  if (!species().found(specieName)) return;
612 
613  // A transferring specie. Add a source.
614  eqn += s*mDot(species()[specieName]);
615 
616  return;
617  }
618 
619  // Something else. Fall back.
620  massTransfer::addSup(alpha, rho, heOrYi, eqn);
621 }
622 
623 
625 {
627  {
628  readCoeffs(coeffs(dict));
629  return true;
630  }
631  else
632  {
633  return false;
634  }
635 }
636 
637 
638 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
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 Mesh &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
A 1D array of objects of type <T>, where the size of the vector is known and used for subscript bound...
Definition: List.H:91
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:96
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:91
Base class for mass transfers between phases.
Definition: massTransfer.H:55
virtual bool read(const dictionary &dict)
Read source dictionary.
Definition: massTransfer.C:249
virtual void addSup(fvMatrix< scalar > &eqn) const
Add a source term to a field-less proxy equation.
Definition: massTransfer.C:225
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:140
const labelPair & specieis(const label mDoti=-1) const
Return the indices of the transferring specie in the two.
Definition: phaseChange.C:366
wordList readSpecie(const dictionary &dict, const bool required) const
Read the names of the transferring specie.
Definition: phaseChange.C:74
const ThermoRefPair< multicomponentThermo > multicomponentThermos(const bool, const bool) const
Return the multicomponent thermo references.
Definition: phaseChange.C:334
virtual tmp< DimensionedField< scalar, volMesh > > Lfraction() const
Return the fraction of the latent heat that is transferred into.
Definition: phaseChange.C:423
const ThermoRefPair< fluidThermo > fluidThermos(const bool, const bool) const
Return the fluid thermo references.
Definition: phaseChange.C:317
void reReadSpecie(const dictionary &dict) const
Re-read the names of the transferring specie.
Definition: phaseChange.C:129
wordList readSpecies(const dictionary &dict, const bool required) const
Read the names of the transferring species.
Definition: phaseChange.C:96
static tmp< DimensionedField< scalar, volMesh > > vfToVif(const tmp< volScalarField > &tvf)
Remove the boundary field from the given geometric field.
Definition: phaseChange.C:277
virtual tmp< DimensionedField< scalar, volMesh > > mDot() const
Return the total phase change rate.
Definition: phaseChange.C:498
virtual bool read(const dictionary &dict)
Read source dictionary.
Definition: phaseChange.C:624
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:551
void setSpecies(const word &name, const word &modelType, const wordList &species)
Set the names of the transferring species.
Definition: phaseChange.C:152
const ThermoRefPair< fluidMulticomponentThermo > fluidMulticomponentThermos(const bool, const bool) const
Return the fluid multicomponent thermo references.
Definition: phaseChange.C:351
void reSetSpecies(const wordList &species)
Re-set the names of the transferring species.
Definition: phaseChange.C:228
tmp< DimensionedField< scalar, volMesh > > L(const label mDoti=-1) const
Return the latent heat.
Definition: phaseChange.C:433
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:239
virtual tmp< DimensionedField< scalar, volMesh > > Tchange() const
Return the temperature at which the phases are considered to be.
Definition: phaseChange.C:393
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:292
static tmp< volScalarField > vifToVf(const tmp< DimensionedField< scalar, volMesh >> &tvif)
Add a boundary field to the given internal field.
Definition: phaseChange.C:254
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:62
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))
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
Pair< label > labelPair
Label pair.
Definition: labelPair.H:48
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
const HashTable< dimensionSet > & dimensions()
Get the table of dimension sets.
Definition: dimensionSets.C:96
labelList second(const UList< labelPair > &p)
Definition: patchToPatch.C:49
const dimensionSet dimTemperature
labelList first(const UList< labelPair > &p)
Definition: patchToPatch.C:39
const dimensionSet dimTime
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 dimDensity
IOerror FatalIOError
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
error FatalError
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
thermo he()
labelList fv(nPoints)
dictionary dict
volScalarField & p