turbulentDispersion.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-2026 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 "turbulentDispersion.H"
29 #include "coupledToFluid.H"
30 #include "standardNormal.H"
31 #include "wallPolyPatch.H"
35 
36 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
37 
38 namespace Foam
39 {
40 namespace Lagrangian
41 {
44  (
48  );
49 }
50 }
51 
52 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
53 
54 template<class Type>
56 Foam::Lagrangian::turbulentDispersion::initialiseTurbField
57 (
58  const word& name,
59  const dimensionSet& dims,
60  const Type& value
61 )
62 {
63  return
65  (
67  (
68  IOobject
69  (
70  name,
71  mesh().time().name(),
72  mesh(),
75  ),
76  mesh(),
77  dimensioned<Type>(name, dims, value)
78  )
79  );
80 }
81 
82 
83 template<class InjectionFieldSourceType, class Type>
84 void Foam::Lagrangian::turbulentDispersion::completeTurbField
85 (
87 )
88 {
89  if (turbField.headerOk()) return;
90 
91  LagrangianModels& modelList = cloud().LagrangianModels();
92 
93  turbField.sourcesRef().table().transfer
94  (
96  (
97  turbField,
99  <
101  InjectionFieldSourceType,
104  >(),
106  ).table()
107  );
108 }
109 
110 
111 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
112 
114 (
115  const word& name,
116  const LagrangianMesh& mesh,
117  const dictionary& modelDict,
118  const dictionary& stateDict
119 )
120 :
122  cloudLagrangianModel(static_cast<const LagrangianModel&>(*this)),
123  dragPtr_(nullptr),
124  momentumTransportModel_(mesh.poly().lookupType<momentumTransportModel>()),
125  Cmu75_(pow(dimensionedScalar("Cmu", dimless, modelDict, 0.09), 0.75)),
126  maxDiscreteEddies_
127  (
128  modelDict.lookupOrDefault<label>("maxDiscreteEddies", 32)
129  ),
130  kc_
131  (
132  cloud<clouds::carried>().carrierField<scalar>
133  (
134  clouds::carried::nameToCarrierName
135  (
136  momentumTransportModel_.k()().name()
137  ),
138  [&]()
139  {
140  return momentumTransportModel_.k();
141  }
142  )
143  ),
144  epsilonc_
145  (
146  cloud<clouds::carried>().carrierField<scalar>
147  (
149  (
150  momentumTransportModel_.epsilon()().name()
151  ),
152  [&]()
153  {
154  return momentumTransportModel_.epsilon();
155  }
156  )
157  ),
158  fractionTurb_(initialiseTurbField("fractionTurb", dimless, vGreat)),
159  tTurb_(initialiseTurbField("tTurb", dimTime, NaN)),
160  Uturb_(initialiseTurbField("Uturb", dimVelocity, vector::uniform(NaN))),
161  rndGen_("rndGen", stateDict, name, false),
162  avgUturbPtr_(nullptr)
163 {}
164 
165 
166 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
167 
169 {
170  return wordList({cloud().U.name()});
171 }
172 
173 
175 (
176  const word& fieldName,
177  const word& eqnFieldName
178 ) const
179 {
180  return
181  fieldName == cloud().U.name()
182  && (
183  eqnFieldName == cloud().U.name()
184  || eqnFieldName == cloud<clouds::carried>().Uc.name()
185  );
186 }
187 
188 
190 {
191  LagrangianModels& modelList = cloud().LagrangianModels();
192 
193  dragPtr_ = nullptr;
194 
195  forAll(modelList, i)
196  {
197  if (!isA<drag>(modelList[i])) continue;
198 
199  if (dragPtr_ != nullptr)
200  {
202  << "Multiple drag models found. Turbulent dispersion "
203  << "requires exactly one drag model."
204  << exit(FatalError);
205  }
206 
207  dragPtr_ = &refCast<const drag>(modelList[i]);
208  }
209 
210  if (dragPtr_ == nullptr)
211  {
213  << "No drag models found. Turbulent dispersion "
214  << "requires exactly one drag model."
215  << exit(FatalError);
216  }
217 
218  completeTurbField<maxLagrangianScalarFieldSource>(fractionTurb_);
219  completeTurbField<NaNLagrangianScalarFieldSource>(tTurb_);
220  completeTurbField<NaNLagrangianVectorFieldSource>(Uturb_);
221 }
222 
223 
225 (
226  const LagrangianSubScalarField& deltaT,
227  const bool final
228 )
229 {
230  const LagrangianSubMesh& subMesh = deltaT.mesh();
231 
232  // References to the evolving fields
233  LagrangianSubScalarSubField& fractionTurb = fractionTurb_.ref(subMesh);
234  LagrangianSubScalarSubField& tTurb = tTurb_.ref(subMesh);
235  LagrangianSubVectorSubField& Uturb = Uturb_.ref(subMesh);
236  const LagrangianSubScalarField& fractionTurb0 = fractionTurb.oldTime();
237  const LagrangianSubVectorField& Uturb0 = Uturb.oldTime();
238 
239  // Update the eddy time-scale
240  const LagrangianSubScalarField magUrel
241  (
242  mag(cloud().U(subMesh) - cloud<clouds::carried>().Uc(subMesh))
243  );
244  static const dimensionedScalar rootVSmallEpsilon
245  (
247  rootVSmall
248  );
249  static const dimensionedScalar rootVSmallEpsilonU
250  (
252  rootVSmall
253  );
254  tTurb =
255  min
256  (
257  kc_(subMesh)/max(epsilonc_(subMesh), rootVSmallEpsilon),
258  Cmu75_*kc_(subMesh)*sqrt(kc_(subMesh))
259  /max(epsilonc_(subMesh)*magUrel, rootVSmallEpsilonU)
260  );
261 
262  // Velocity fluctuation magnitude
263  LagrangianSubScalarField magUturb(sqrt(2.0/3.0*kc_(subMesh)));
264 
265  // Modify particles on the walls to account for the turbulent kinetic
266  // energy being constrained to zero. This prevents a turbulent velocity
267  // fluctuation from pointing through a wall and causing a particle to
268  // vibrate as the drag model and the rebound model fight each other.
269  const PackedBoolList patchIsWall
270  (
271  mesh().poly().boundary().findIndices<wallPolyPatch>().toc()
272  );
273  forAll(subMesh, subi)
274  {
275  const label i = subi + subMesh.start();
276 
277  if (mesh().state(i) < LagrangianState::onPatchZero) continue;
278 
279  const label patchi =
280  static_cast<label>(mesh().state(i))
281  - static_cast<label>(LagrangianState::onPatchZero);
282 
283  if (!patchIsWall[patchi]) continue;
284 
285  tTurb[subi] = rootVSmall;
286 
287  magUturb[subi] = 0;
288  }
289 
290  // Initialise the average turbulent velocities
291  avgUturbPtr_.set
292  (
294  (
295  "avgUturb",
296  subMesh,
298  ).ptr()
299  );
300 
301  // Consider each particle in turn
302  forAll(subMesh, subi)
303  {
304  // Create independent generators for each particle. That way if the
305  // sequence grows or shrinks across iterations, that effect doesn't
306  // propagate and affect other particles' sequences
308  (
309  rndGen_.sampleAB(labelMin, labelMax),
310  false
311  );
313  (
314  rndGen_.sampleAB(labelMin, labelMax),
315  false
316  );
317 
318  // Generate a random direction with a normally distributed magnitude
319  auto rndDir = [&rndGen, &stdNormal]()
320  {
321  const scalar theta =
323  const scalar z = 2*rndGen.scalar01() - 1;
324  const scalar r = sqrt(1 - sqr(z));
325  return stdNormal.sample()*vector(r*cos(theta), r*sin(theta), z);
326  };
327 
328  // Set up sub-stepping
329  const scalar Dt = max(deltaT[subi], rootVSmall);
330  scalar dt = 0;
331 
332  // Continue/complete the previous eddy
333  if (fractionTurb0[subi] < 1)
334  {
335  dt += tTurb[subi]*(1 - fractionTurb0[subi]);
336 
337  avgUturbPtr_()[subi] += min(dt/Dt, 1)*Uturb0[subi];
338  }
339 
340  // Add new eddies across the time-step
341  const scalar nEddies = Dt/tTurb[subi];
342  if (nEddies < maxDiscreteEddies_)
343  {
344  while (dt < Dt)
345  {
346  // Create a turbulent velocity fluctuation for the new eddy
347  Uturb[subi] = rndDir()*magUturb[subi];
348 
349  // Add this eddy to the average
350  avgUturbPtr_()[subi] +=
351  (min(dt + tTurb[subi], Dt) - dt)/Dt*Uturb[subi];
352 
353  // Increment the time
354  dt += tTurb[subi];
355  }
356 
357  // Set the fraction to where we got to in the current eddy
358  fractionTurb[subi] = 1 - (dt - Dt)/tTurb[subi];
359  }
360  else
361  {
362  // Create a turbulent velocity fluctuation for the new eddy
363  Uturb[subi] = rndDir()*magUturb[subi];
364 
365  // Add the combined effect of all the eddies to the average
366  avgUturbPtr_()[subi] +=
367  (Dt - dt)/Dt
368  *sqrt(nEddies/3)
369  *stdNormal.sample<vector>()
370  *magUturb[subi];
371 
372  // Set the fraction to indicate that the eddies are finished
373  fractionTurb[subi] = 1;
374  }
375  }
376 
377  // If this is not the final iteration then rewind the generator so that the
378  // same numbers are generated in the next iteration
379  rndGen_.start(!final);
380 }
381 
382 
384 (
385  const LagrangianSubScalarField& deltaT,
388 ) const
389 {
390  assertCloud<clouds::coupledToConstantDensityFluid>();
391 
392  eqn.Su += dragPtr_->D(deltaT.mesh())*avgUturbPtr_();
393 }
394 
395 
397 (
398  const LagrangianSubScalarField& deltaT,
399  const LagrangianSubScalarSubField& vOrM,
402 ) const
403 {
404  assertCloud
405  <
408  >();
409 
410  eqn.Su += dragPtr_->D(deltaT.mesh())*avgUturbPtr_();
411 }
412 
413 
415 (
416  const LagrangianSubScalarField& deltaT,
417  const bool final
418 )
419 {
420  avgUturbPtr_.clear();
421 }
422 
423 
425 (
426  Ostream& os
427 ) const
428 {
430 
431  writeEntry(os, "rndGen", rndGen_);
432 }
433 
434 
435 // ************************************************************************* //
label k
#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,.
const GeoMesh & mesh() const
Return mesh.
Part of a geometric field used for setting the values associated with optional sources.
const HashPtrTable< Source > & table() const
Access the underlying field table.
Generic GeometricField class.
Sources & sourcesRef()
Return a reference to the sources.
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
const word & name() const
Return name.
Definition: IOobject.H:307
This class stores the coefficients of a Lagrangian equation, and facilitates solving that equation an...
Definition: LagrangianEqn.H:56
LagrangianCoeff< Type, false > Su
Explicit coefficient.
Base class for Lagrangian injections. Minimal wrapper over LagrangianSource. Implements some utility ...
Class containing Lagrangian geometry and topology.
Base class for Lagrangian models.
const Time & time() const
Return time.
const LagrangianMesh & mesh() const
The mesh.
const word & name() const
The source name.
List of Lagrangian models, constructed as a (Lagrangian) mesh object. Provides similar functions to t...
HashTable< word > modelTypeFieldSourceTypes() const
Return a table of field source types that are chosen to match given.
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...
label start() const
Return start.
Model for turbulent dispersion. This model creates a random turbulent velocity fluctuation based on t...
virtual wordList addSupFields() const
Return the name of the velocity field.
virtual bool addsSupToField(const word &fieldName, const word &eqnFieldName) const
Return true for the velocity or carrier velocity field.
virtual void postConstruct()
Do post construction steps which require access to other models.
virtual void addSup(const LagrangianSubScalarField &deltaT, const LagrangianSubVectorSubField &U, LagrangianEqn< vector > &eqn) const
Add a source term to the velocity equation.
virtual void writeProcessorState(Ostream &os) const
Write state.
virtual void preAddSup(const LagrangianSubScalarField &deltaT, const bool final)
Hook before source evaluation.
turbulentDispersion(const word &name, const LagrangianMesh &mesh, const dictionary &modelDict, const dictionary &stateDict)
Construct from components.
virtual void postAddSup(const LagrangianSubScalarField &deltaT, const bool final)
Hook after source evaluation.
const Field0Type & oldTime() const
Return the old-time field.
Definition: OldTimeField.C:322
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
A bit-packed bool list.
static Form uniform(const Cmpt &s)
Return a VectorSpace with all elements = s.
Definition: VectorSpaceI.H:168
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
Foam::LagrangianModels & LagrangianModels() const
Access the models.
Definition: cloud.C:597
CloudStateField< vector > U
Velocity.
Definition: cloud.H:209
static word nameToCarrierName(const word &name)
Convert a name to its disambiguated carrier equivalent name. I.e.,.
Definition: carried.C:273
Base class for clouds which are coupled to a constant density fluid.
Base class for clouds which are coupled to a variable density fluid.
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
Dimension set for the base types.
Definition: dimensionSet.H:125
Generic dimensioned Type class.
Standard normal distribution. Not selectable.
virtual scalar sample() const
Sample the distribution.
This source condition retains the internal value.
Abstract base class for momentum transport models (RAS, LES and laminar).
virtual tmp< volScalarField > k() const =0
Return the turbulence kinetic energy.
Random number generator.
bool headerOk()
Read and check header info.
Definition: regIOobject.C:429
virtual void writeProcessorState(Ostream &os) const
Write processor state.
Definition: stateModel.C:132
A class for managing temporary objects.
Definition: tmp.H:55
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 FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
label patchi
U
Definition: pEqn.H:72
defineTypeNameAndDebug(collisionPhaseTransfer, 0)
addToRunTimeSelectionTable(LagrangianModel, collisionPhaseTransfer, dictionary)
const scalar twoPi(2 *pi)
const HashTable< dimensionSet > table
Table of dimensions.
Definition: dimensions.C:74
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
const dimensionSet & dimless
Definition: dimensions.C:138
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 dimensionSet & dimMass
Definition: dimensions.C:140
dimensionedScalar sin(const dimensionedScalar &ds)
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
Vector< scalar > vector
A scalar version of the templated Vector.
Definition: vector.H:49
const dimensionSet & dimVelocity
Definition: dimensions.C:154
const dimensionSet & dimTime
Definition: dimensions.C:142
dimensioned< Type > min(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< vector > dimensionedVector
Dimensioned vector obtained from generic dimensioned type.
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
tmp< DimensionedField< typename powProduct< Type, r >::type, GeoMesh, Field > > pow(const DimensionedField< Type, GeoMesh, PrimitiveField > &df, typename powProduct< Type, r >::type)
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
const dimensionSet & dimEnergy
Definition: dimensions.C:160
error FatalError
static const label labelMax
Definition: label.H:62
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
void writeEntry(Ostream &os, const word &key, const DimensionedFieldFunction< DimensionedFieldType > &f)
dimensionedScalar cos(const dimensionedScalar &ds)
static const label labelMin
Definition: label.H:61
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
faceListList boundary(nPatches)
randomGenerator rndGen(653213)