FreeStream.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) 2011-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 "FreeStream.H"
27 #include "constants.H"
28 #include "triPointRef.H"
29 #include "tetIndices.H"
30 #include "standardNormal.H"
31 #include "meshSearch.H"
32 
33 using namespace Foam::constant::mathematical;
34 
35 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
36 
37 template<class CloudType>
39 (
40  const dictionary& dict,
42 )
43 :
45  patches_(),
46  moleculeTypeIds_(),
47  numberDensities_(),
48  particleFluxAccumulators_()
49 {
50  // Identify which patches to use
51 
53 
54  forAll(cloud.mesh().poly().boundary(), p)
55  {
56  const polyPatch& patch = cloud.mesh().poly().boundary()[p];
57 
58  if (isType<polyPatch>(patch))
59  {
60  patches.append(p);
61  }
62  }
63 
64  patches_.transfer(patches);
65 
66  const dictionary& numberDensitiesDict
67  (
68  this->typeDict().subDict("numberDensities")
69  );
70 
71  List<word> molecules(numberDensitiesDict.toc());
72 
73  // Initialise the particleFluxAccumulators_
74  particleFluxAccumulators_.setSize(patches_.size());
75 
76  forAll(patches_, p)
77  {
78  const polyPatch& patch = cloud.mesh().poly().boundary()[patches_[p]];
79 
80  particleFluxAccumulators_[p] = List<Field<scalar>>
81  (
82  molecules.size(),
83  Field<scalar>(patch.size(), 0.0)
84  );
85  }
86 
87  moleculeTypeIds_.setSize(molecules.size());
88 
89  numberDensities_.setSize(molecules.size());
90 
91  forAll(molecules, i)
92  {
93  numberDensities_[i] =
94  numberDensitiesDict.lookup<scalar>(molecules[i]);
95 
96  moleculeTypeIds_[i] = findIndex(cloud.typeIdList(), molecules[i]);
97 
98  if (moleculeTypeIds_[i] == -1)
99  {
101  << "typeId " << molecules[i] << "not defined in cloud." << nl
102  << abort(FatalError);
103  }
104  }
105 
106  numberDensities_ /= cloud.nParticle();
107 }
108 
109 
110 
111 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
112 
113 template<class CloudType>
115 {}
116 
117 
118 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
119 
120 template<class CloudType>
122 {
123  CloudType& cloud(this->owner());
124  const polyMesh& mesh(cloud.mesh());
125 
126  forAll(patches_, p)
127  {
128  label patchi = patches_[p];
129 
130  const polyPatch& patch = mesh.boundary()[patchi];
131  List<Field<scalar>>& pFA = particleFluxAccumulators_[p];
132 
133  forAll(pFA, facei)
134  {
135  pFA[facei].setSize(patch.size(), 0);
136  }
137  }
138 }
139 
140 
141 template<class CloudType>
143 {
144  CloudType& cloud(this->owner());
145 
146  const polyMesh& mesh = cloud.mesh();
147 
148  const meshSearch& searchEngine = meshSearch::New(mesh);
149 
150  const scalar deltaT = mesh.time().deltaTValue();
151 
152  randomGenerator& rndGen = cloud.rndGen();
153  distributions::standardNormal& stdNormal = cloud.stdNormal();
154 
155  scalar sqrtPi = sqrt(pi);
156 
157  label particlesInserted = 0;
158 
159  const volScalarField::Boundary& boundaryT
160  (
161  cloud.boundaryT().boundaryField()
162  );
163 
164  const volVectorField::Boundary& boundaryU
165  (
166  cloud.boundaryU().boundaryField()
167  );
168 
169  label nLocateBoundaryHits = 0;
170 
171  forAll(patches_, p)
172  {
173  label patchi = patches_[p];
174 
175  const polyPatch& patch = mesh.boundary()[patchi];
176 
177  // Add mass to the accumulators. negative face area dotted with the
178  // velocity to point flux into the domain.
179 
180  // Take a reference to the particleFluxAccumulator for this patch
181  List<Field<scalar>>& pFA = particleFluxAccumulators_[p];
182 
183  forAll(pFA, i)
184  {
185  label typeId = moleculeTypeIds_[i];
186 
187  scalar mass = cloud.constProps(typeId).mass();
188 
189  if (min(boundaryT[patchi]) < small)
190  {
192  << "Zero boundary temperature detected, check boundaryT "
193  << "condition." << nl
194  << nl << abort(FatalError);
195  }
196 
197  scalarField mostProbableSpeed
198  (
199  cloud.maxwellianMostProbableSpeed
200  (
201  boundaryT[patchi],
202  mass
203  )
204  );
205 
206  // Dotting boundary velocity with the face unit normal
207  // (which points out of the domain, so it must be
208  // negated), dividing by the most probable speed to form
209  // molecularSpeedRatio * cosTheta
210 
211  scalarField sCosTheta
212  (
213  (boundaryU[patchi] & -patch.faceAreas()/patch.magFaceAreas())
214  / mostProbableSpeed
215  );
216 
217  // From Bird eqn 4.22
218 
219  pFA[i] +=
220  patch.magFaceAreas()*numberDensities_[i]*deltaT
221  *mostProbableSpeed
222  *(
223  exp(-sqr(sCosTheta)) + sqrtPi*sCosTheta*(1 + erf(sCosTheta))
224  )
225  /(2.0*sqrtPi);
226  }
227 
228  const pointField::subField patchFaceCentres = patch.faceCentres();
229  const scalarField::subField patchMagFaceAreas = patch.magFaceAreas();
230 
231  forAll(patch, pFI)
232  {
233  // Loop over all faces as the outer loop to avoid calculating
234  // geometrical properties multiple times.
235 
236  const face& f = patch[pFI];
237 
238  label globalFaceIndex = pFI + patch.start();
239 
240  label celli = mesh.faceOwner()[globalFaceIndex];
241 
242  const vector& fC = patchFaceCentres[pFI];
243 
244  scalar fA = patchMagFaceAreas[pFI];
245 
247  (
248  mesh,
249  globalFaceIndex,
250  celli
251  );
252 
253  // Cumulative triangle area fractions
254  List<scalar> cTriAFracs(faceTets.size(), 0.0);
255 
256  scalar previousCumulativeSum = 0.0;
257 
258  forAll(faceTets, triI)
259  {
260  const tetIndices& faceTetIs = faceTets[triI];
261 
262  cTriAFracs[triI] =
263  faceTetIs.faceTri(mesh).mag()/fA
264  + previousCumulativeSum;
265 
266  previousCumulativeSum = cTriAFracs[triI];
267  }
268 
269  // Force the last area fraction value to 1.0 to avoid any
270  // rounding/non-flat face errors giving a value < 1.0
271  cTriAFracs.last() = 1.0;
272 
273  // Normal unit vector *negative* so normal is pointing into the
274  // domain
275  vector n = patch.faceAreas()[pFI];
276  n /= -mag(n);
277 
278  // Wall tangential unit vector. Use the direction between the
279  // face centre and the first vertex in the list
280  vector t1 = fC - (mesh.points()[f[0]]);
281  t1 /= mag(t1);
282 
283  // Other tangential unit vector. Rescaling in case face is not
284  // flat and n and t1 aren't perfectly orthogonal
285  vector t2 = n^t1;
286  t2 /= mag(t2);
287 
288  scalar faceTemperature = boundaryT[patchi][pFI];
289 
290  const vector& faceVelocity = boundaryU[patchi][pFI];
291 
292  forAll(pFA, i)
293  {
294  scalar& faceAccumulator = pFA[i][pFI];
295 
296  // Number of whole particles to insert
297  label nI = max(label(faceAccumulator), 0);
298 
299  // Add another particle with a probability proportional to the
300  // remainder of taking the integer part of faceAccumulator
301  if ((faceAccumulator - nI) > rndGen.scalar01())
302  {
303  nI++;
304  }
305 
306  faceAccumulator -= nI;
307 
308  label typeId = moleculeTypeIds_[i];
309 
310  scalar mass = cloud.constProps(typeId).mass();
311 
312  for (label i = 0; i < nI; i++)
313  {
314  // Choose a triangle to insert on, based on their relative
315  // area
316 
317  scalar triSelection = rndGen.scalar01();
318 
319  // Selected triangle
320  label selectedTriI = -1;
321 
322  forAll(cTriAFracs, triI)
323  {
324  selectedTriI = triI;
325 
326  if (cTriAFracs[triI] >= triSelection)
327  {
328  break;
329  }
330  }
331 
332  // Randomly distribute the points on the triangle.
333 
334  const tetIndices& faceTetIs = faceTets[selectedTriI];
335 
336  point p = faceTetIs.faceTri(mesh).randomPoint(rndGen);
337 
338  // Velocity generation
339 
340  scalar mostProbableSpeed
341  (
342  cloud.maxwellianMostProbableSpeed
343  (
344  faceTemperature,
345  mass
346  )
347  );
348 
349  scalar sCosTheta = (faceVelocity & n)/mostProbableSpeed;
350 
351  // Coefficients required for Bird eqn 12.5
352  scalar uNormProbCoeffA =
353  sCosTheta + sqrt(sqr(sCosTheta) + 2.0);
354 
355  scalar uNormProbCoeffB =
356  0.5*
357  (
358  1.0
359  + sCosTheta*(sCosTheta - sqrt(sqr(sCosTheta) + 2.0))
360  );
361 
362  // Equivalent to the QA value in Bird's DSMC3.FOR
363  scalar randomScaling = 3.0;
364 
365  if (sCosTheta < -3)
366  {
367  randomScaling = mag(sCosTheta) + 1;
368  }
369 
370  scalar P = -1;
371 
372  // Normalised candidates for the normal direction velocity
373  // component
374  scalar uNormal;
375  scalar uNormalThermal;
376 
377  // Select a velocity using Bird eqn 12.5
378  do
379  {
380  uNormalThermal =
381  randomScaling*(2.0*rndGen.scalar01() - 1);
382 
383  uNormal = uNormalThermal + sCosTheta;
384 
385  if (uNormal < 0.0)
386  {
387  P = -1;
388  }
389  else
390  {
391  P = 2.0*uNormal/uNormProbCoeffA
392  *exp(uNormProbCoeffB - sqr(uNormalThermal));
393  }
394 
395  } while (P < rndGen.scalar01());
396 
397  vector U =
398  sqrt(physicoChemical::k.value()*faceTemperature/mass)
399  *(stdNormal.sample()*t1 + stdNormal.sample()*t2)
400  + (t1 & faceVelocity)*t1
401  + (t2 & faceVelocity)*t2
402  + mostProbableSpeed*uNormal*n;
403 
404  scalar Ei = cloud.equipartitionInternalEnergy
405  (
406  faceTemperature,
407  cloud.constProps(typeId).internalDegreesOfFreedom()
408  );
409 
410  cloud.addNewParcel
411  (
412  searchEngine,
413  p,
414  celli,
415  nLocateBoundaryHits,
416  U,
417  Ei,
418  typeId
419  );
420 
421  particlesInserted++;
422  }
423  }
424  }
425  }
426 
427  reduce(nLocateBoundaryHits, sumOp<label>());
428  if (nLocateBoundaryHits != 0)
429  {
431  << "Freestream inflow model for cloud " << this->owner().name()
432  << " did not accurately locate " << nLocateBoundaryHits
433  << " particles" << endl;
434  }
435 
436  reduce(particlesInserted, sumOp<label>());
437 
438  Info<< " Particles inserted = "
439  << particlesInserted << endl;
440 }
441 
442 
443 // ************************************************************************* //
label k
label n
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
Templated base class for dsmc cloud.
Definition: DSMCCloud.H:80
virtual void topoChange()
Update following mesh change.
Definition: FreeStream.C:121
FreeStream(const dictionary &dict, CloudType &cloud)
Construct from dictionary.
Definition: FreeStream.C:39
virtual void inflow()
Introduce particles.
Definition: FreeStream.C:142
virtual ~FreeStream()
Destructor.
Definition: FreeStream.C:114
Generic GeometricBoundaryField class.
Templated inflow boundary model class.
const dictionary & typeDict() const
Return the coefficients dictionary.
const polyMesh & poly() const
Access the poly mesh.
void transfer(List< T > &)
Transfer the contents of the argument List into this list.
Definition: List.C:342
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
void setSize(const label)
Reset size of List.
Definition: List.C:281
Pre-declare related SubField type.
Definition: SubField.H:63
scalar deltaTValue() const
Return time step value.
Definition: TimeStateI.H:34
label size() const
Return the number of elements in the UList.
Definition: UListI.H:311
T & last()
Return the last element of the list.
Definition: UListI.H:128
Base class for clouds. Provides a basic evolution algorithm, models, and a database for caching deriv...
Definition: cloud.H:61
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:669
wordList toc() const
Return the table of contents.
Definition: dictionary.C:981
Standard normal distribution. Not selectable.
virtual scalar sample() const
Sample the distribution.
A face is a list of labels corresponding to mesh vertices.
Definition: face.H:76
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:433
const fvBoundaryMesh & boundary() const
Return reference to boundary mesh.
Definition: fvMesh.C:932
Mesh object that implements searches within the local cells and faces.
Definition: meshSearch.H:59
static const meshSearch & New(const polyMesh &mesh, const pointInCellShapes=pointInCellShapes::tets)
Lookup or construct from mesh and cell decomposition option.
Definition: meshSearch.C:61
static List< tetIndices > faceTetIndices(const polyMesh &mesh, label fI, label cI)
Return the tet decomposition of the given face, with.
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:78
const polyBoundaryMesh & boundary() const
Return boundary mesh.
Definition: polyMesh.H:393
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1321
virtual const pointField & points() const
Return raw points.
Definition: polyMesh.C:1295
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:71
const vectorField::subField faceAreas() const
Return face areas.
Definition: polyPatch.C:233
label start() const
Return start label of this patch in the polyMesh face list.
Definition: polyPatch.H:277
const vectorField::subField faceCentres() const
Return face centres.
Definition: polyPatch.C:227
const scalarField::subField magFaceAreas() const
Return face area magnitudes.
Definition: polyPatch.C:239
Random number generator.
scalar scalar01()
Return a scalar uniformly distributed between zero and one.
Storage and named access for the indices of a tet which is part of the decomposition of a cell.
Definition: tetIndices.H:82
triPointRef faceTri(const polyMesh &mesh, const pointField &meshPoints) const
Return the geometry corresponding to the tri on the face for.
Definition: tetIndicesI.H:134
scalar mag() const
Return scalar magnitude.
Definition: triangleI.H:103
Point randomPoint(randomGenerator &rndGen) const
Return a random point on the triangle from a uniform.
Definition: triangleI.H:232
Template function which returns the un-mangled name of a given type. Useful for types which do not ha...
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
const fvPatchList & patches
U
Definition: pEqn.H:72
#define WarningInFunction
Report a warning using Foam::Warning.
mathematical constants.
const dimensionSet mass
dimensionedScalar exp(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
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
errorManip< error > abort(error &err)
Definition: errorManip.H:131
dimensionedScalar erf(const dimensionedScalar &ds)
messageStream Info
tmp< DimensionedField< typename outerProduct< Type, Type >::type, GeoMesh, Field >> sqr(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
void reduce(const List< UPstream::commsStruct > &comms, T &Value, const BinaryOp &bop, const int tag, const label comm)
dimensioned< Type > min(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
label findIndex(const ListType &, typename ListType::const_reference, const label start=0)
Find first occurrence of given element and return index,.
error FatalError
void sqrt(LagrangianPatchField< scalar > &f, const LagrangianPatchField< scalar > &f1)
static const char nl
Definition: Ostream.H:297
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
labelList f(nPoints)
dictionary dict
randomGenerator rndGen(653213)
volScalarField & p