constantFluxCarrierTransfer.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) 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 "LagrangianSubFieldsFwd.H"
30 #include "massive.H"
31 #include "shaped.H"
32 
33 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 
35 namespace Foam
36 {
37 namespace Lagrangian
38 {
41  (
45  );
46 }
47 }
48 
49 
50 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
51 
52 void Foam::Lagrangian::constantFluxCarrierTransfer::readCoeffs
53 (
54  const dictionary& modelDict
55 )
56 {
57  const bool haveVolumeFlux = modelDict.found("volumeFlux");
58  const bool haveMassFlux = modelDict.found("massFlux");
59 
60  if (haveVolumeFlux == haveMassFlux)
61  {
62  FatalIOErrorInFunction(modelDict)
63  << "keywords volumeFlux and massFlux are both "
64  << (haveVolumeFlux ? "" : "un" ) << "defined in "
65  << "dictionary " << modelDict.name()
66  << exit(FatalIOError);
67  }
68 
69  flux_.read(modelDict);
70 
71  if (flux_.value() < 0)
72  {
73  FatalIOErrorInFunction(modelDict)
74  << flux_.name() << " must be positive"
75  << exit(FatalError);
76  }
77 }
78 
79 
81 Foam::Lagrangian::constantFluxCarrierTransfer::Sp
82 (
83  const LagrangianSubMesh& subMesh
84 ) const
85 {
86  tmp<LagrangianSubScalarField> tSByV =
87  cloud<clouds::shaped>().aByV(subMesh)*flux_;
88 
89  if (flux_.dimensions() == dimMass/dimArea/dimTime)
90  {
91  assertCloud
92  <
93  clouds::coupledToConstantDensityFluid,
94  clouds::massive
95  >();
96 
97  if (isCloud<clouds::coupledToConstantDensityFluid>())
98  {
99  return
100  tSByV
101  /cloud<clouds::coupledToConstantDensityFluid>().rho();
102  }
103  else
104  {
105  return
106  tSByV
107  /cloud<clouds::massive>().rho(subMesh);
108  }
109  }
110  else
111  {
112  return tSByV;
113  }
114 }
115 
116 
117 void Foam::Lagrangian::constantFluxCarrierTransfer::addSupType
118 (
119  const LagrangianSubScalarField& deltaT,
120  const LagrangianSubScalarSubField& vOrM,
121  LagrangianEqn<scalar>& eqn
122 ) const
123 {
124  const LagrangianSubMesh& subMesh = deltaT.mesh();
125  const SubList<LagrangianState> subStates = subMesh.sub(mesh().states());
126 
127  const LagrangianSubScalarField Sp(this->Sp(subMesh));
128 
129  if (eqn.isPsi(vOrM))
130  {
131  // Add the implicit coefficient to the models' total
132  if (sumDeltaTSp_.valid()) sumDeltaTSp_.ref() += deltaT*Sp;
133 
134  // Add an implicit source to the cloud
135  eqn.Sp -= Sp;
136  }
137  else
138  {
139  // Calculate the rate of consumption
140  tmp<LagrangianSubScalarField> Su = Sp*vOrM;
141 
142  // Modify the rate of consumption to fully consume removed particles
143  forAll(subMesh, subi)
144  {
145  if (subStates[subi] == LagrangianState::toBeRemoved)
146  {
147  Su.ref()[subi] *= 1 + 1/max(sumDeltaTSp_()[subi], small);
148  }
149  }
150 
151  // Add an explicit source to the carrier
152  eqn.Su -= Su;
153  }
154 }
155 
156 
157 template<class Type>
158 void Foam::Lagrangian::constantFluxCarrierTransfer::addSupType
159 (
160  const LagrangianSubScalarField& deltaT,
161  const LagrangianSubSubField<Type>& field,
162  LagrangianEqn<Type>& eqn
163 ) const
164 {
166  << "The Lagrangian model '" << name() << "' of cloud '"
167  << mesh().name() << "' cannot add a transfer for the field '"
168  << field.name() << "' because the equation is not volume or "
169  << "mass-weighted" << exit(FatalError);
170 }
171 
172 
173 template<class Type>
174 void Foam::Lagrangian::constantFluxCarrierTransfer::addSupType
175 (
176  const LagrangianSubScalarField& deltaT,
177  const LagrangianSubScalarSubField& vOrM,
178  const LagrangianSubSubField<Type>& field,
179  LagrangianEqn<Type>& eqn
180 ) const
181 {
182  const LagrangianSubMesh& subMesh = deltaT.mesh();
183  const SubList<LagrangianState> subStates = subMesh.sub(mesh().states());
184 
185  const LagrangianSubScalarField Sp(this->Sp(subMesh));
186 
187  if (eqn.isPsi(field))
188  {
189  // Add an implicit source to the cloud
190  eqn.Sp -= Sp*vOrM;
191  }
192  else
193  {
194  // Calculate the rate of consumption
195  tmp<LagrangianSubField<Type>> Su = Sp*vOrM*field;
196 
197  // Modify the rate of consumption to fully consume removed particles
198  forAll(subMesh, subi)
199  {
200  if (subStates[subi] == LagrangianState::toBeRemoved)
201  {
202  Su.ref()[subi] *= 1 + 1/max(sumDeltaTSp_()[subi], small);
203  }
204  }
205 
206  // Add an explicit source to the carrier
207  eqn.Su -= Su;
208  }
209 }
210 
211 
212 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
213 
215 (
216  const word& name,
217  const LagrangianMesh& mesh,
218  const dictionary& modelDict,
219  const dictionary& stateDict
220 )
221 :
223  cloudLagrangianModel(static_cast<const LagrangianModel&>(*this)),
224  flux_
225  (
226  modelDict.found("volumeFlux")
227  ? dimensionedScalar("volumeFlux", dimVolume/dimArea/dimTime, modelDict)
228  : modelDict.found("massFlux")
229  ? dimensionedScalar("massFlux", dimMass/dimArea/dimTime, modelDict)
231  ),
232  sumDeltaTSp_()
233 {
234  readCoeffs(modelDict);
235 }
236 
237 
238 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
239 
242 {
243  return
244  isCloud<clouds::massive>()
247 }
248 
249 
251 (
252  const word& fieldName,
253  const word& eqnFieldName
254 ) const
255 {
256  const word eqnPhaseName = IOobject::group(eqnFieldName);
257  const word eqnMember = IOobject::member(eqnFieldName);
258 
259  const bool isCarrierEqn = eqnMember[eqnMember.size() - 1] == 'c';
260 
261  return
262  !isCarrierEqn
263  || eqnPhaseName == word::null
264  || eqnPhaseName == cloud<clouds::carried>().carrierPhaseName();
265 }
266 
267 
269 (
270  const LagrangianSubScalarField& deltaT,
271  const bool final
272 )
273 {
274  if (!final) return;
275 
276  const LagrangianSubMesh& subMesh = deltaT.mesh();
277  SubList<LagrangianState> subStates = subMesh.sub(mesh().states());
278 
279  // Surface area divided by volume to the power of two-thirds. Could/should
280  // be made a virtual function of the shaped cloud if non-spherical
281  // representations are added.
282  static const scalar aByVPowTwoThirds = cbrt(36*constant::mathematical::pi);
283 
284  // Calculate the particle's remaining lifetime
286  3
287  *cbrt(cloud<clouds::shaped>().v(subMesh))
288  /aByVPowTwoThirds
289  /flux_;
290  if (flux_.dimensions() == dimMass/dimArea/dimTime)
291  {
292  assertCloud
293  <
296  >();
297 
298  if (isCloud<clouds::coupledToConstantDensityFluid>())
299  {
300  tlifetime =
302  }
303  else
304  {
305  tlifetime =
306  tlifetime/cloud<clouds::massive>().rho(subMesh);
307  }
308  }
309 
310  // Remove any particles with a lifetime shorter than the time-step
311  forAll(subMesh, subi)
312  {
313  if (tlifetime()[subi] < deltaT[subi])
314  {
315  subStates[subi] = LagrangianState::toBeRemoved;
316  }
317  }
318 }
319 
320 
323 (
324  const word& fieldName,
325  const LagrangianSubMesh& subMesh
326 )
327 const
328 {
329  return
330  - this->Sp(subMesh)
331  *(
332  isCloud<clouds::massive>()
333  ? cloud<clouds::massive>().m(subMesh)
334  : cloud<clouds::shaped>().v(subMesh)
335  );
336 }
337 
338 
340 (
341  const LagrangianSubScalarField& deltaT,
342  const bool final
343 )
344 {
345  if (!final) return;
346 
347  const LagrangianSubMesh& subMesh = deltaT.mesh();
348  const SubList<LagrangianState> subStates = subMesh.sub(mesh().states());
349 
350  // If particles are being removed then create/lookup the sum of the
351  // implicit coefficients
352  if (findIndex(subStates, LagrangianState::toBeRemoved) != -1)
353  {
354  sumDeltaTSp_.set
355  (
356  IOobject
357  (
358  "sumDeltaTSpvOrM",
359  mesh().time().name(),
360  mesh()
361  ),
362  subMesh,
363  dimensionedScalar(dimless, scalar(0))
364  );
365  }
366 }
367 
368 
370 (
372  Lagrangian::constantFluxCarrierTransfer
373 )
374 
375 
377 (
379  Lagrangian::constantFluxCarrierTransfer
380 )
381 
382 
384 (
385  const LagrangianSubScalarField& deltaT,
386  const bool final
387 )
388 {
389  if (final) sumDeltaTSp_.clear();
390 }
391 
392 
394 (
395  const dictionary& modelDict
396 )
397 {
398  if (LagrangianModel::read(modelDict))
399  {
400  readCoeffs(modelDict);
401  return true;
402  }
403  else
404  {
405  return false;
406  }
407 }
408 
409 
410 // ************************************************************************* //
#define IMPLEMENT_LAGRANGIAN_MODEL_ADD_FIELD_SUP(Type, modelType)
#define IMPLEMENT_LAGRANGIAN_MODEL_ADD_V_OR_M_FIELD_SUP(Type, modelType)
bool found
#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...
const GeoMesh & mesh() const
Return mesh.
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
word group() const
Return group (extension part of name)
Definition: IOobject.C:321
word member() const
Return member (name without the extension)
Definition: IOobject.C:327
Class containing Lagrangian geometry and topology.
Base class for Lagrangian models.
virtual bool read(const dictionary &modelDict)
Read dictionary.
Base class for Lagrangian sources. Minimal wrapper over LagrangianModel that provides an interface to...
Mesh that relates to a sub-section of a Lagrangian mesh. This is used to construct fields that relate...
word sub(const word &fieldName) const
Return the name of a field corresponding to this sub-mesh.
Model to represent mass transfer into the carrier phase with a constant surface volume or mass flux....
virtual wordList addSupFields() const
Return the name of the volume or mass field.
virtual bool addsSupToField(const word &fieldName, const word &eqnFieldName) const
Return true if this is a Lagrangian field or a field of the.
virtual tmp< LagrangianSubScalarField > source(const word &fieldName, const LagrangianSubMesh &subMesh) const
Return the source value.
virtual bool read(const dictionary &modelDict)
Read dictionary.
virtual void preAddSup(const LagrangianSubScalarField &deltaT, const bool final)
Hook before source evaluation.
virtual void postAddSup(const LagrangianSubScalarField &deltaT, const bool final)
Add a source term to an equation.
virtual void calculate(const LagrangianSubScalarField &deltaT, const bool final)
Update the transfer rate and remove any consumed particles.
constantFluxCarrierTransfer(const word &name, const LagrangianMesh &mesh, const dictionary &modelDict, const dictionary &stateDict)
Construct from components.
A List obtained as a section of another List.
Definition: SubList.H:56
Mix-in for Lagrangian models that refer to a cloud.
Base class for clouds. Provides a basic evolution algorithm, models, and a database for caching deriv...
Definition: cloud.H:61
Base class for clouds which are coupled to a constant density fluid.
Base class for clouds with particles with mass.
Definition: massive.H:51
static const word mName
Name of mass fields.
Definition: massive.H:69
static const word vName
Name of volume fields.
Definition: shaped.H:84
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
void read(const dictionary &, const unitSet &defaultUnits=NullObjectRef< unitSet >())
Update the value of dimensioned<Type>
const Type & value() const
Return const reference to value.
const word & name() const
Return const reference to name.
const word & name() const
Return reference to name.
Definition: fvMesh.H:447
A class for managing temporary objects.
Definition: tmp.H:55
A class for handling words, derived from string.
Definition: word.H:63
static const word null
An empty word.
Definition: word.H:78
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
defineTypeNameAndDebug(collisionPhaseTransfer, 0)
addToRunTimeSelectionTable(LagrangianModel, collisionPhaseTransfer, dictionary)
const dimensionSet time
tmp< VolField< Type > > Su(const VolField< Type > &su, const VolField< Type > &vf)
Definition: fvcSup.C:44
tmp< VolField< Type > > Sp(const volScalarField &sp, const VolField< Type > &vf)
Definition: fvcSup.C:67
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
List< word > wordList
A List of words.
Definition: fileName.H:54
const dimensionSet & dimless
Definition: dimensions.C:138
const dimensionSet & dimMass
Definition: dimensions.C:140
FOR_ALL_FIELD_TYPES(makeDimensionedPointFieldFunctions)
const dimensionSet & dimVolume
Definition: dimensions.C:150
const dimensionSet & dimTime
Definition: dimensions.C:142
IOerror FatalIOError
void cbrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
LagrangianSubSubField< scalar > LagrangianSubScalarSubField
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
label findIndex(const ListType &, typename ListType::const_reference, const label start=0)
Find first occurrence of given element and return index,.
error FatalError
LagrangianSubField< scalar > LagrangianSubScalarField
const dimensionSet & dimArea
Definition: dimensions.C:149
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.