speciesSorptionFvPatchScalarField.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 | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2022 OpenCFD Ltd.
9  Copyright (C) 2026 Keysight Technologies
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
31 #include "fvPatchFieldMapper.H"
32 #include "volFields.H"
33 #include "rhoReactionThermo.H"
34 
35 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
36 
37 const Foam::Enum
38 <
40 >
42 ({
43  { equilibriumModelType::LANGMUIR, "Langmuir" }
44 });
45 
46 
47 const Foam::Enum
48 <
50 >
52 ({
53  { kineticModelType::PseudoFirstOrder, "PseudoFirstOrder" }
54 });
55 
56 
57 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
58 
60 Foam::speciesSorptionFvPatchScalarField::calcMoleFractions() const
61 {
62  auto tMole = tmp<scalarField>::New(patch().size(), Zero);
63  auto& Mole = tMole.ref();
64 
65  if (db().foundObject<rhoReactionThermo>(basicThermo::dictName))
66  {
67  const auto& thermo = db().lookupObject<rhoReactionThermo>
68  (
70  );
71 
72  const PtrList<volScalarField>& Y = thermo.composition().Y();
73 
74  const volScalarField W(thermo.W());
75 
76  const labelUList& faceCells = patch().faceCells();
77 
78  const label speciesId =
79  thermo.composition().species()[this->internalField().name()];
80 
81  const dimensionedScalar Wi
82  (
84  thermo.composition().W(speciesId)
85  );
86 
87  const volScalarField X(W*Y[speciesId]/Wi);
88 
89  forAll(faceCells, i)
90  {
91  const label cellId = faceCells[i];
92  Mole[i] = X[cellId];
93  }
94  }
95  else
96  {
98  << "Thermo type is not 'rhoReactionThermo'. " << nl
99  << "This BC is designed to operate with a rho based thermo."
100  << exit(FatalError);
101  }
102 
103  return tMole;
104 }
105 
106 
108 Foam::speciesSorptionFvPatchScalarField::field
109 (
110  const word& fieldName,
111  const dimensionSet& dim
112 ) const
113 {
114  const fvMesh& mesh = this->internalField().mesh();
115  auto* ptr = mesh.getObjectPtr<volScalarField>(fieldName);
116 
117  if (!ptr)
118  {
119  ptr = new volScalarField
120  (
121  IOobject
122  (
123  fieldName,
124  mesh.time().timeName(),
125  mesh.thisDb(),
129  ),
130  mesh,
131  dimensionedScalar(dim, Zero)
132  );
133 
134  ptr->store();
135  }
136 
137  return *ptr;
138 }
139 
140 
141 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
142 
144 (
145  const fvPatch& p,
147 )
148 :
149  parent_bctype(p, iF),
150  equilibriumModel_(equilibriumModelType::LANGMUIR),
151  kinematicModel_(kineticModelType::PseudoFirstOrder),
152  allowDesorption_(true),
153  thicknessPtr_(nullptr),
154  kabs_(1),
155  kl_(0),
156  max_(1),
157  rhoS_(0),
158  pName_("p"),
159  dfldp_(p.size(), 0),
160  mass_(p.size(), 0)
161 {}
162 
163 
165 (
166  const fvPatch& p,
168  const dictionary& dict
169 )
170 :
171  parent_bctype(p, iF, dict),
172  equilibriumModel_(equilibriumModelTypeNames.get("equilibriumModel", dict)),
173  kinematicModel_(kinematicModelTypeNames.get("kinematicModel", dict)),
174  allowDesorption_(dict.getOrDefault<bool>("allowDesorption", true)),
175  thicknessPtr_(PatchFunction1<scalar>::New(p.patch(), "thickness", dict)),
176  kabs_(dict.getCheck<scalar>("kabs", scalarMinMax::ge(0))),
177  kl_(dict.getCheck<scalar>("kl", scalarMinMax::ge(0))),
178  max_(dict.getCheck<scalar>("max", scalarMinMax::ge(0))),
179  rhoS_(dict.get<scalar>("rhoS")),
180  pName_(dict.getOrDefault<word>("p", "p")),
181  dfldp_("dfldp", dict, p.size(), IOobjectOption::LAZY_READ),
182  mass_("mass", dict, p.size(), IOobjectOption::LAZY_READ)
183 {
184  if (!this->readValueEntry(dict))
185  {
187  }
188 }
189 
190 
192 (
193  const this_bctype& ptf,
194  const fvPatch& p,
195  const DimensionedField<scalar, volMesh>& iF,
196  const fvPatchFieldMapper& mapper
197 )
198 :
199  parent_bctype(ptf, p, iF, mapper),
200  equilibriumModel_(ptf.equilibriumModel_),
201  kinematicModel_(ptf.kinematicModel_),
202  allowDesorption_(ptf.allowDesorption_),
203  thicknessPtr_(ptf.thicknessPtr_.clone(patch().patch())),
204  kabs_(ptf.kabs_),
205  kl_(ptf.kl_),
206  max_(ptf.max_),
207  rhoS_(ptf.rhoS_),
208  pName_(ptf.pName_),
209  dfldp_(ptf.dfldp_, mapper),
210  mass_(ptf.mass_, mapper)
211 {}
212 
213 
215 (
216  const this_bctype& ptf,
218 )
219 :
220  parent_bctype(ptf, iF),
221  equilibriumModel_(ptf.equilibriumModel_),
222  kinematicModel_(ptf.kinematicModel_),
223  allowDesorption_(ptf.allowDesorption_),
224  thicknessPtr_(ptf.thicknessPtr_.clone(patch().patch())),
225  kabs_(ptf.kabs_),
226  kl_(ptf.kl_),
227  max_(ptf.max_),
228  rhoS_(ptf.rhoS_),
229  pName_(ptf.pName_),
230  dfldp_(ptf.dfldp_),
231  mass_(ptf.mass_)
232 {}
233 
234 
235 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
236 
238 (
239  const fvPatchFieldMapper& m
240 )
241 {
242  this->parent_bctype::autoMap(m);
243 
244  dfldp_.autoMap(m);
245  mass_.autoMap(m);
246 
247  if (thicknessPtr_)
248  {
249  thicknessPtr_->autoMap(m);
250  }
251 }
252 
253 
255 (
256  const fvPatchScalarField& ptf,
257  const labelList& addr
258 )
259 {
260  this->parent_bctype::rmap(ptf, addr);
261 
262  const auto& tiptf = refCast<const this_bctype>(ptf);
263 
264  dfldp_.rmap(tiptf.dfldp_, addr);
265  mass_.rmap(tiptf.mass_, addr);
266 
267  if (thicknessPtr_ && tiptf.thicknessPtr_)
268  {
269  thicknessPtr_->rmap(tiptf.thicknessPtr_(), addr);
270  }
271 }
272 
273 
275 patchSource() const
276 {
277  const auto& thermo = db().lookupObject<rhoReactionThermo>
278  (
280  );
281 
282  const label speciesId =
283  thermo.composition().species()[this->internalField().name()];
284 
285  const scalar Wi(thermo.composition().W(speciesId));
286 
287  const scalar t = db().time().timeOutputValue();
288 
289  const scalarField h(thicknessPtr_->value(t));
290 
291  const scalarField AbyV(this->patch().magSf());
292 
293  // Solid mass [kg]
294  const scalarField mass(h*AbyV*rhoS_);
295 
296  scalarField Vol(this->patch().size());
297 
298  forAll(AbyV, facei)
299  {
300  const label faceCelli = this->patch().faceCells()[facei];
301  Vol[facei] = this->internalField().mesh().V()[faceCelli];
302  }
303 
304  // The moles absorbed by the solid
305  // dfldp[mol/kg/sec]* mass[kg]* Wi[kg/mol] / Vol[m3]= [kg/sec/m3]
306  const scalarField dfldp(-dfldp_*mass*Wi*1e-3/Vol);
307 
308  if (debug)
309  {
310  auto limits = gMinMax(dfldp);
311 
312  Info<< " Patch mass rate min/max [kg/m3/sec]: "
313  << limits.min() << " - " << limits.max() << endl;
314  }
315 
316  return tmp<scalarField>::New(dfldp);
317 }
318 
319 
321 mass() const
322 {
323  return tmp<scalarField>::New(mass_);
324 }
325 
326 
328 {
329  if (updated())
330  {
331  return;
332  }
333 
334  // equilibrium in mol/kg
335  scalarField cEq(patch().size(), 0);
336 
337  switch (equilibriumModel_)
338  {
339  case equilibriumModelType::LANGMUIR:
340  {
341  // mole fraction
342  tmp<scalarField> tco = calcMoleFractions();
343 
344  const auto& pp = patch().lookupPatchField<volScalarField>(pName_);
345 
346  cEq = max_*(kl_*tco()*pp/(1 + kl_*tco()*pp));
347  break;
348  }
349  default:
350  break;
351  }
352 
353  // source [mol/kg/sec]
354  dfldp_ = Zero;
355 
356  switch (kinematicModel_)
357  {
358  case kineticModelType::PseudoFirstOrder:
359  {
360  dfldp_ = kabs_*(cEq - mass_);
361  }
362  default:
363  break;
364  }
365 
366  if (!allowDesorption_)
367  {
368  dfldp_.clamp_min(scalar(0));
369  }
370 
371  // mass [mol/kg]
372  const scalar dt = db().time().deltaTValue();
373  mass_ += dfldp_*dt;
374  mass_ = max(mass_, scalar(0));
375 
376  scalarField& pMass =
377  field
378  (
379  "adsorbedMass" + this->internalField().name(),
380  dimensionSet(dimMoles/dimMass)
381  ).boundaryFieldRef()[patch().index()];
382 
383  pMass = mass_;
384 
385  if (debug)
386  {
387  auto limits = gMinMax(dfldp_);
388 
389  Info<< " Absorption rate min/max [mol/kg/sec]: "
390  << limits.min() << " - " << limits.max() << endl;
391  }
392 
393  this->parent_bctype::updateCoeffs();
394 }
395 
396 
398 {
400 
401  os.writeEntry
402  (
403  "equilibriumModel", equilibriumModelTypeNames[equilibriumModel_]
404  );
405  os.writeEntry
406  (
407  "kinematicModel", kinematicModelTypeNames[kinematicModel_]
408  );
409  if (thicknessPtr_)
410  {
411  thicknessPtr_->writeData(os);
412  }
413  os.writeEntry("kabs", kabs_);
414  os.writeEntry("kl", kl_);
415  os.writeEntry("max", max_);
416  os.writeEntry("rhoS", rhoS_);
417  os.writeEntryIfDifferent<bool>
418  (
419  "allowDesorption",
420  true,
421  allowDesorption_
422  );
423 
424  dfldp_.writeEntry("dfldp", os);
425  mass_.writeEntry("mass", os);
426  os.writeEntryIfDifferent<word>("p", "p", pName_);
427 
429 }
430 
431 
432 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
433 
434 namespace Foam
435 {
437  (
439  speciesSorptionFvPatchScalarField
440  );
441 }
442 
443 // ************************************************************************* //
bool readValueEntry(const dictionary &dict, IOobjectOption::readOption readOpt=IOobjectOption::LAZY_READ)
Read the "value" entry into *this.
word dictName() const
The local dictionary name (final part of scoped name)
Definition: dictionaryI.H:53
dictionary dict
const Type & lookupObject(const word &name, const bool recursive=false) const
Lookup and return const reference to the object of the given Type. Fatal if not found or the wrong ty...
virtual void rmap(const fvPatchScalarField &, const labelList &)
Reverse map the given fvPatchField onto this fvPatchField.
rDeltaTY field()
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
const objectRegistry & db() const
The associated objectRegistry.
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:130
const fvPatch & patch() const noexcept
Return the patch.
Definition: fvPatchField.H:262
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:652
virtual void updateCoeffs()
Update the coefficients associated with the patch field.
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:40
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:50
virtual void write(Ostream &) const
Write.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:518
A finiteVolume patch using a polyPatch and a fvBoundaryMesh.
Definition: fvPatch.H:70
tmp< DimensionedField< TypeR, GeoMesh > > New(const tmp< DimensionedField< TypeR, GeoMesh >> &tf1, const word &name, const dimensionSet &dimensions, const bool initCopy=false)
Global function forwards to reuseTmpDimensionedField::New.
const speciesTable & species() const
Return the table of species.
bool store()
Register object with its registry and transfer ownership to the registry.
Definition: regIOobjectI.H:36
void writeValueEntry(Ostream &os) const
Write *this field as a "value" entry.
Definition: fvPatchField.H:425
speciesSorptionFvPatchScalarField(const fvPatch &, const DimensionedField< scalar, volMesh > &)
Construct from patch and internal field.
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:360
Macros for easy insertion into run-time selection tables.
UList< label > labelUList
A UList of labels.
Definition: UList.H:76
virtual const objectRegistry & thisDb() const
Return the object registry - resolve conflict polyMesh/lduMesh.
Definition: fvMesh.H:376
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:400
GeometricField< scalar, fvPatchField, volMesh > volScalarField
Definition: volFieldsFwd.H:72
psiReactionThermo & thermo
Definition: createFields.H:28
auto limits
Definition: setRDeltaT.H:186
const dimensionedScalar e
Elementary charge.
Definition: createFields.H:11
dynamicFvMesh & mesh
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for expressions::valueTypeCode::INVALID.
Definition: exprTraits.C:127
fvPatchField< scalar > fvPatchScalarField
constexpr T & get(FixedList< T, N > &list) noexcept
Definition: FixedList.H:887
A class for handling words, derived from Foam::string.
Definition: word.H:63
Type * getObjectPtr(const word &name, const bool recursive=false) const
Return non-const pointer to the object of the given Type, using a const-cast to have it behave like a...
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
virtual const labelUList & faceCells() const
Return faceCells.
Definition: fvPatch.C:107
A FieldMapper for finite-volume patch fields.
static tmp< T > New(Args &&... args)
Construct tmp with forwarding arguments.
Definition: tmp.H:217
MinMax< Type > gMinMax(const FieldField< Field, Type > &f)
virtual void autoMap(const fvPatchFieldMapper &)
Map (and resize as needed) from self given a mapping object.
Top level data entry class for use in dictionaries. Provides a mechanism to specify a variable as a c...
static word timeName(const scalar t, const int precision=precision_)
Return a time name for the given scalar time value formatted with the given precision.
Definition: Time.C:713
label size() const noexcept
The number of elements in the container.
Definition: UList.H:707
int debug
Static debugging option.
const dimensionSet dimMoles(0, 0, 0, 0, 1, 0, 0)
Definition: dimensionSets.H:53
static const Enum< equilibriumModelType > equilibriumModelTypeNames
Names for equilibriumModelType.
tmp< scalarField > mass() const
Access to mass.
const dimensionedScalar h
Planck constant.
static const Enum< kineticModelType > kinematicModelTypeNames
Names for kineticModelType.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
Definition: error.H:68
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
PtrList< volScalarField > & Y
label cellId
A simple container of IOobject preferences. Can also be used for general handling of read/no-read/rea...
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
Nothing to be read.
Automatically write from objectRegistry::writeObject()
const DimensionedField< scalar, volMesh > & internalField() const noexcept
Return const-reference to the dimensioned internal field.
Definition: fvPatchField.H:734
const std::string patch
OpenFOAM patch number as a std::string.
messageStream Info
Information stream (stdout output on master, null elsewhere)
virtual basicSpecieMixture & composition()=0
Return the composition of the multi-component mixture.
virtual void operator=(const UList< Type > &)
const dimensionSet dimMass(1, 0, 0, 0, 0, 0, 0)
Definition: dimensionSets.H:49
List< label > labelList
A List of labels.
Definition: List.H:61
volScalarField & p
A class for managing temporary objects.
Definition: HashPtrTable.H:50
virtual scalar W(const label speciei) const =0
Molecular weight of the given specie [kg/kmol].
virtual tmp< scalarField > patchSource() const
Source of cells next to the patch.
makePatchTypeField(fvPatchScalarField, atmBoundaryLayerInletEpsilonFvPatchScalarField)
Request registration (bool: true)
uindirectPrimitivePatch pp(UIndirectList< face >(mesh.faces(), faceLabels), mesh.points())
Namespace for OpenFOAM.
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:127