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 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 {
64  cloud().stateField<Type>
65  (
66  IOobject
67  (
68  name,
69  mesh().time().name(),
70  mesh(),
73  ),
74  mesh(),
75  dimensioned<Type>(name, dims, value)
76  );
77 
78  return Tuple2<bool, CloudStateField<Type>&>(ref.headerOk(), ref);
79 }
80 
81 
82 template<class InjectionFieldSourceType, class Type>
83 void Foam::Lagrangian::turbulentDispersion::completeTurbField
84 (
85  Tuple2<bool, CloudStateField<Type>&>& turbField
86 )
87 {
88  if (turbField.first()) return;
89 
90  turbField.first() = true;
91 
92  LagrangianModels& modelList = cloud().LagrangianModels();
93 
94  turbField.second().sourcesRef().table().transfer
95  (
97  (
98  turbField.second(),
100  <
102  InjectionFieldSourceType,
105  >()
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.mesh().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::coupled>().carrierField<scalar>
133  (
134  clouds::coupled::carrierName
135  (
136  momentumTransportModel_.k()().name()
137  ),
138  [&]()
139  {
140  return momentumTransportModel_.k();
141  }
142  )
143  ),
144  epsilonc_
145  (
146  cloud<clouds::coupled>().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(1, cloud().U.name());
171 }
172 
173 
175 {
176  LagrangianModels& modelList = cloud().LagrangianModels();
177 
178  dragPtr_ = nullptr;
179 
180  forAll(modelList, i)
181  {
182  if (!isA<drag>(modelList[i])) continue;
183 
184  if (dragPtr_ != nullptr)
185  {
187  << "Multiple drag models found. Turbulent dispersion "
188  << "requires exactly one drag model."
189  << exit(FatalError);
190  }
191 
192  dragPtr_ = &refCast<const drag>(modelList[i]);
193  }
194 
195  if (dragPtr_ == nullptr)
196  {
198  << "No drag models found. Turbulent dispersion "
199  << "requires exactly one drag model."
200  << exit(FatalError);
201  }
202 
203  completeTurbField<maxLagrangianScalarFieldSource>(fractionTurb_);
204  completeTurbField<NaNLagrangianScalarFieldSource>(tTurb_);
205  completeTurbField<NaNLagrangianVectorFieldSource>(Uturb_);
206 }
207 
208 
210 (
211  const LagrangianSubScalarField& deltaT,
212  const bool final
213 )
214 {
215  const LagrangianSubMesh& subMesh = deltaT.mesh();
216 
217  // References to the evolving fields
218  LagrangianSubScalarSubField& fractionTurb =
219  fractionTurb_.second().ref(subMesh);
220  LagrangianSubScalarSubField& tTurb = tTurb_.second().ref(subMesh);
221  LagrangianSubVectorSubField& Uturb = Uturb_.second().ref(subMesh);
222  const LagrangianSubScalarField& fractionTurb0 = fractionTurb.oldTime();
223  const LagrangianSubVectorField& Uturb0 = Uturb.oldTime();
224 
225  // Update the eddy time-scale
226  const LagrangianSubScalarField magUrel
227  (
228  mag(cloud().U(subMesh) - cloud<clouds::coupled>().Uc(subMesh))
229  );
230  static const dimensionedScalar rootVSmallEpsilon
231  (
233  rootVSmall
234  );
235  static const dimensionedScalar rootVSmallEpsilonU
236  (
238  rootVSmall
239  );
240  tTurb =
241  min
242  (
243  kc_(subMesh)/max(epsilonc_(subMesh), rootVSmallEpsilon),
244  Cmu75_*kc_(subMesh)*sqrt(kc_(subMesh))
245  /max(epsilonc_(subMesh)*magUrel, rootVSmallEpsilonU)
246  );
247 
248  // Velocity fluctuation magnitude
249  LagrangianSubScalarField magUturb(sqrt(2.0/3.0*kc_(subMesh)));
250 
251  // Modify particles on the walls to account for the turbulent kinetic
252  // energy being constrained to zero. This prevents a turbulent velocity
253  // fluctuation from pointing through a wall and causing a particle to
254  // vibrate as the drag model and the rebound model fight each other.
255  const PackedBoolList patchIsWall
256  (
257  mesh().mesh().boundaryMesh().findIndices<wallPolyPatch>().toc()
258  );
259  forAll(subMesh, subi)
260  {
261  const label i = subi + subMesh.start();
262 
263  if (mesh().state(i) < LagrangianState::onPatchZero) continue;
264 
265  const label patchi =
266  static_cast<label>(mesh().state(i))
267  - static_cast<label>(LagrangianState::onPatchZero);
268 
269  if (!patchIsWall[patchi]) continue;
270 
271  tTurb[subi] = rootVSmall;
272 
273  magUturb[subi] = 0;
274  }
275 
276  // Initialise the average turbulent velocities
277  avgUturbPtr_.reset
278  (
280  (
281  "avgUturb",
282  subMesh,
284  ).ptr()
285  );
286 
287  // Consider each particle in turn
288  forAll(subMesh, subi)
289  {
290  // Create independent generators for each particle. That way if the
291  // sequence grows or shrinks across iterations, that effect doesn't
292  // propagate and affect other particles' sequences
294  (
295  rndGen_.sampleAB(labelMin, labelMax),
296  false
297  );
299  (
300  rndGen_.sampleAB(labelMin, labelMax),
301  false
302  );
303 
304  // Generate a random direction with a normally distributed magnitude
305  auto rndDir = [&rndGen, &stdNormal]()
306  {
307  const scalar theta =
309  const scalar z = 2*rndGen.scalar01() - 1;
310  const scalar r = sqrt(1 - sqr(z));
311  return stdNormal.sample()*vector(r*cos(theta), r*sin(theta), z);
312  };
313 
314  // Set up sub-stepping
315  const scalar Dt = max(deltaT[subi], rootVSmall);
316  scalar dt = 0;
317 
318  // Continue/complete the previous eddy
319  if (fractionTurb0[subi] < 1)
320  {
321  dt += tTurb[subi]*(1 - fractionTurb0[subi]);
322 
323  avgUturbPtr_()[subi] += min(dt/Dt, 1)*Uturb0[subi];
324  }
325 
326  // Add new eddies across the time-step
327  const scalar nEddies = Dt/tTurb[subi];
328  if (nEddies < maxDiscreteEddies_)
329  {
330  while (dt < Dt)
331  {
332  // Create a turbulent velocity fluctuation for the new eddy
333  Uturb[subi] = rndDir()*magUturb[subi];
334 
335  // Add this eddy to the average
336  avgUturbPtr_()[subi] +=
337  (min(dt + tTurb[subi], Dt) - dt)/Dt*Uturb[subi];
338 
339  // Increment the time
340  dt += tTurb[subi];
341  }
342 
343  // Set the fraction to where we got to in the current eddy
344  fractionTurb[subi] = 1 - (dt - Dt)/tTurb[subi];
345  }
346  else
347  {
348  // Create a turbulent velocity fluctuation for the new eddy
349  Uturb[subi] = rndDir()*magUturb[subi];
350 
351  // Add the combined effect of all the eddies to the average
352  avgUturbPtr_()[subi] +=
353  (Dt - dt)/Dt
354  *sqrt(nEddies/3)
355  *stdNormal.sample<vector>()
356  *magUturb[subi];
357 
358  // Set the fraction to indicate that the eddies are finished
359  fractionTurb[subi] = 1;
360  }
361  }
362 
363  // If this is not the final iteration then rewind the generator so that the
364  // same numbers are generated in the next iteration
365  rndGen_.start(!final);
366 }
367 
368 
370 (
371  const LagrangianSubScalarField& deltaT,
374 ) const
375 {
376  assertCloud<clouds::coupledToIncompressibleFluid>();
377 
378  eqn.Su += dragPtr_->D(deltaT.mesh())*avgUturbPtr_();
379 }
380 
381 
383 (
384  const LagrangianSubScalarField& deltaT,
385  const LagrangianSubScalarSubField& vOrM,
388 ) const
389 {
390  assertCloud<clouds::coupledToIncompressibleFluid, clouds::coupledToFluid>();
391 
392  eqn.Su += dragPtr_->D(deltaT.mesh())*avgUturbPtr_();
393 }
394 
395 
397 (
398  Ostream& os
399 ) const
400 {
402 
403  writeEntry(os, "rndGen", rndGen_);
404 }
405 
406 
407 // ************************************************************************* //
label k
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
Macros for easy insertion into run-time selection tables.
A field which is stored as part of the state of the cloud. This is a light wrapper around a dynamic L...
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
const Mesh & mesh() const
Return mesh.
void reset(const DimensionedField< Type, GeoMesh, PrimitiveField2 > &)
Reset the field values to the given field.
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,.
Part of a geometric field used for setting the values associated with optional sources.
const HashPtrTable< Source > & table() const
Access the underlying field table.
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
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 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.
virtual wordList addSupFields() const
Return the name of the 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.
turbulentDispersion(const word &name, const LagrangianMesh &mesh, const dictionary &modelDict, const dictionary &stateDict)
Construct from components.
virtual void calculate(const LagrangianSubScalarField &deltaT, const bool final)
Solve equations and/or update continually changing properties.
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.
A 2-tuple for storing two objects of different types.
Definition: Tuple2.H:66
static Form uniform(const Cmpt &s)
Return a VectorSpace with all elements = s.
Definition: VectorSpaceI.H:168
Base class for Lagrangian models that refer to a cloud. Not a Lagrangian model in itself....
const Cloud & cloud() const
Get a reference to the cloud.
Base class for clouds. Provides a basic evolution algorithm, models, and a database for caching deriv...
Definition: cloud.H:63
Foam::LagrangianModels & LagrangianModels() const
Access the models.
Definition: cloud.C:623
static word carrierName(const word &name)
Modify a name to disambiguate it as relating to the carrier.
Definition: coupled.C:246
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
void transfer(dictionary &)
Transfer the contents of the argument and annul the argument.
Definition: dictionary.C:1368
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 turbulence models (RAS, LES and laminar).
virtual tmp< volScalarField > k() const =0
Return the turbulence kinetic energy.
Random number generator.
virtual void writeProcessorState(Ostream &os) const
Write processor state.
Definition: stateModel.C:132
A class for handling words, derived from string.
Definition: word.H:62
trAU ref().rename("rAU")
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
addToRunTimeSelectionTable(LagrangianModel, constantCoefficientVirtualMass, dictionary)
defineTypeNameAndDebug(constantCoefficientVirtualMass, 0)
const scalar twoPi(2 *pi)
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
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 dimEnergy
const dimensionSet dimless
dimensionedScalar sin(const dimensionedScalar &ds)
void mag(LagrangianPatchField< scalar > &f, const LagrangianPatchField< Type > &f1)
layerAndWeight min(const layerAndWeight &a, const layerAndWeight &b)
const dimensionSet dimTime
void writeEntry(Ostream &os, const HashTable< T, Key, Hash > &ht)
Definition: HashTableIO.C:96
Vector< scalar > vector
A scalar version of the templated Vector.
Definition: vector.H:49
void pow(LagrangianPatchField< typename powProduct< Type, r >::type > &f, const LagrangianPatchField< Type > &f1)
layerAndWeight max(const layerAndWeight &a, const layerAndWeight &b)
dimensioned< vector > dimensionedVector
Dimensioned vector obtained from generic dimensioned type.
void sqr(LagrangianPatchField< typename outerProduct< Type, Type >::type > &f, const LagrangianPatchField< Type > &f1)
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
const dimensionSet dimMass
const dimensionSet dimVelocity
error FatalError
static const label labelMax
Definition: label.H:62
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
dimensionedScalar cos(const dimensionedScalar &ds)
static const label labelMin
Definition: label.H:61
randomGenerator rndGen(653213)