1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright (C) 2011 OpenFOAM Foundation
7 -------------------------------------------------------------------------------
9 This file is part of OpenFOAM.
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.
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
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/>.
24 \*---------------------------------------------------------------------------*/
26 #include "FreeStream.H"
27 #include "constants.H"
28 #include "triPointRef.H"
29 #include "tetIndices.H"
31 using namespace Foam::constant::mathematical;
33 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
35 template <class CloudType>
36 Foam::FreeStream<CloudType>::FreeStream
38 const dictionary& dict,
42 InflowBoundaryModel<CloudType>(dict, cloud, typeName),
46 particleFluxAccumulators_()
48 // Identify which patches to use
50 DynamicList<label> patches;
52 forAll(cloud.mesh().boundaryMesh(), p)
54 const polyPatch& patch = cloud.mesh().boundaryMesh()[p];
56 if (isType<polyPatch>(patch))
62 patches_.transfer(patches);
64 const dictionary& numberDensitiesDict
66 this->coeffDict().subDict("numberDensities")
69 List<word> molecules(numberDensitiesDict.toc());
71 // Initialise the particleFluxAccumulators_
72 particleFluxAccumulators_.setSize(patches_.size());
76 const polyPatch& patch = cloud.mesh().boundaryMesh()[patches_[p]];
78 particleFluxAccumulators_[p] = List<Field<scalar> >
81 Field<scalar>(patch.size(), 0.0)
85 moleculeTypeIds_.setSize(molecules.size());
87 numberDensities_.setSize(molecules.size());
91 numberDensities_[i] = readScalar
93 numberDensitiesDict.lookup(molecules[i])
96 moleculeTypeIds_[i] = findIndex(cloud.typeIdList(), molecules[i]);
98 if (moleculeTypeIds_[i] == -1)
102 "Foam::FreeStream<CloudType>::FreeStream"
104 "const dictionary&, "
107 ) << "typeId " << molecules[i] << "not defined in cloud." << nl
108 << abort(FatalError);
112 numberDensities_ /= cloud.nParticle();
117 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
119 template <class CloudType>
120 Foam::FreeStream<CloudType>::~FreeStream()
124 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
126 template <class CloudType>
127 void Foam::FreeStream<CloudType>::inflow()
129 CloudType& cloud(this->owner());
131 const polyMesh& mesh(cloud.mesh());
133 const scalar deltaT = mesh.time().deltaTValue();
135 Random& rndGen(cloud.rndGen());
137 scalar sqrtPi = sqrt(pi);
139 label particlesInserted = 0;
141 const volScalarField::GeometricBoundaryField& boundaryT
143 cloud.boundaryT().boundaryField()
146 const volVectorField::GeometricBoundaryField& boundaryU
148 cloud.boundaryU().boundaryField()
153 label patchI = patches_[p];
155 const polyPatch& patch = mesh.boundaryMesh()[patchI];
157 // Add mass to the accumulators. negative face area dotted with the
158 // velocity to point flux into the domain.
160 // Take a reference to the particleFluxAccumulator for this patch
161 List<Field<scalar> >& pFA = particleFluxAccumulators_[p];
165 label typeId = moleculeTypeIds_[i];
167 scalar mass = cloud.constProps(typeId).mass();
169 if (min(boundaryT[patchI]) < SMALL)
171 FatalErrorIn ("Foam::FreeStream<CloudType>::inflow()")
172 << "Zero boundary temperature detected, check boundaryT "
173 << "condition." << nl
174 << nl << abort(FatalError);
177 scalarField mostProbableSpeed
179 cloud.maxwellianMostProbableSpeed
186 // Dotting boundary velocity with the face unit normal
187 // (which points out of the domain, so it must be
188 // negated), dividing by the most probable speed to form
189 // molecularSpeedRatio * cosTheta
191 scalarField sCosTheta
193 (boundaryU[patchI] & -patch.faceAreas()/mag(patch.faceAreas()))
197 // From Bird eqn 4.22
200 mag(patch.faceAreas())*numberDensities_[i]*deltaT
203 exp(-sqr(sCosTheta)) + sqrtPi*sCosTheta*(1 + erf(sCosTheta))
210 // Loop over all faces as the outer loop to avoid calculating
211 // geometrical properties multiple times.
213 const face& f = patch[pFI];
215 label globalFaceIndex = pFI + patch.start();
217 label cellI = mesh.faceOwner()[globalFaceIndex];
219 const vector& fC = patch.faceCentres()[pFI];
221 scalar fA = mag(patch.faceAreas()[pFI]);
223 List<tetIndices> faceTets = polyMeshTetDecomposition::faceTetIndices
230 // Cumulative triangle area fractions
231 List<scalar> cTriAFracs(faceTets.size(), 0.0);
233 scalar previousCummulativeSum = 0.0;
235 forAll(faceTets, triI)
237 const tetIndices& faceTetIs = faceTets[triI];
240 faceTetIs.faceTri(mesh).mag()/fA
241 + previousCummulativeSum;
243 previousCummulativeSum = cTriAFracs[triI];
246 // Force the last area fraction value to 1.0 to avoid any
247 // rounding/non-flat face errors giving a value < 1.0
248 cTriAFracs.last() = 1.0;
250 // Normal unit vector *negative* so normal is pointing into the
252 vector n = patch.faceAreas()[pFI];
255 // Wall tangential unit vector. Use the direction between the
256 // face centre and the first vertex in the list
257 vector t1 = fC - (mesh.points()[f[0]]);
260 // Other tangential unit vector. Rescaling in case face is not
261 // flat and n and t1 aren't perfectly orthogonal
265 scalar faceTemperature = boundaryT[patchI][pFI];
267 const vector& faceVelocity = boundaryU[patchI][pFI];
271 scalar& faceAccumulator = pFA[i][pFI];
273 // Number of whole particles to insert
274 label nI = max(label(faceAccumulator), 0);
276 // Add another particle with a probability proportional to the
277 // remainder of taking the integer part of faceAccumulator
278 if ((faceAccumulator - nI) > rndGen.scalar01())
283 faceAccumulator -= nI;
285 label typeId = moleculeTypeIds_[i];
287 scalar mass = cloud.constProps(typeId).mass();
289 for (label i = 0; i < nI; i++)
291 // Choose a triangle to insert on, based on their relative
294 scalar triSelection = rndGen.scalar01();
297 label selectedTriI = -1;
299 forAll(cTriAFracs, triI)
303 if (cTriAFracs[triI] >= triSelection)
309 // Randomly distribute the points on the triangle.
311 const tetIndices& faceTetIs = faceTets[selectedTriI];
313 point p = faceTetIs.faceTri(mesh).randomPoint(rndGen);
315 // Velocity generation
317 scalar mostProbableSpeed
319 cloud.maxwellianMostProbableSpeed
326 scalar sCosTheta = (faceVelocity & n)/mostProbableSpeed;
328 // Coefficients required for Bird eqn 12.5
329 scalar uNormProbCoeffA =
330 sCosTheta + sqrt(sqr(sCosTheta) + 2.0);
332 scalar uNormProbCoeffB =
336 + sCosTheta*(sCosTheta - sqrt(sqr(sCosTheta) + 2.0))
339 // Equivalent to the QA value in Bird's DSMC3.FOR
340 scalar randomScaling = 3.0;
344 randomScaling = mag(sCosTheta) + 1;
349 // Normalised candidates for the normal direction velocity
352 scalar uNormalThermal;
354 // Select a velocity using Bird eqn 12.5
358 randomScaling*(2.0*rndGen.scalar01() - 1);
360 uNormal = uNormalThermal + sCosTheta;
368 P = 2.0*uNormal/uNormProbCoeffA
369 *exp(uNormProbCoeffB - sqr(uNormalThermal));
372 } while (P < rndGen.scalar01());
375 sqrt(physicoChemical::k.value()*faceTemperature/mass)
377 rndGen.GaussNormal()*t1
378 + rndGen.GaussNormal()*t2
380 + (t1 & faceVelocity)*t1
381 + (t2 & faceVelocity)*t2
382 + mostProbableSpeed*uNormal*n;
384 scalar Ei = cloud.equipartitionInternalEnergy
387 cloud.constProps(typeId).internalDegreesOfFreedom()
407 reduce(particlesInserted, sumOp<label>());
409 Info<< " Particles inserted = "
410 << particlesInserted << endl;
415 // ************************************************************************* //