1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
5 \\ / A nd | Copyright (C) 2009-2009 OpenCFD Ltd.
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 the
13 Free Software Foundation; either version 2 of the License, or (at your
14 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, write to the Free Software Foundation,
23 Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 \*---------------------------------------------------------------------------*/
27 #include "FreeStream.H"
29 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
31 template <class CloudType>
32 Foam::FreeStream<CloudType>::FreeStream
34 const dictionary& dict,
38 InflowBoundaryModel<CloudType>(dict, cloud, typeName),
42 particleFluxAccumulators_()
44 // Identify which patches to use
46 DynamicList<label> patches;
48 forAll(cloud.mesh().boundaryMesh(), p)
50 const polyPatch& patch = cloud.mesh().boundaryMesh()[p];
52 if (patch.type() == polyPatch::typeName)
58 patches_.transfer(patches);
60 const dictionary& numberDensitiesDict
62 this->coeffDict().subDict("numberDensities")
65 List<word> molecules(numberDensitiesDict.toc());
67 // Initialise the particleFluxAccumulators_
68 particleFluxAccumulators_.setSize(patches_.size());
72 const polyPatch& patch = cloud.mesh().boundaryMesh()[patches_[p]];
74 particleFluxAccumulators_[p] = List<Field<scalar> >
77 Field<scalar>(patch.size(), 0.0)
81 moleculeTypeIds_.setSize(molecules.size());
83 numberDensities_.setSize(molecules.size());
87 numberDensities_[i] = readScalar
89 numberDensitiesDict.lookup(molecules[i])
92 moleculeTypeIds_[i] = findIndex(cloud.typeIdList(), molecules[i]);
94 if (moleculeTypeIds_[i] == -1)
98 "Foam::FreeStream<CloudType>::FreeStream"
100 "const dictionary&, "
103 ) << "typeId " << molecules[i] << "not defined in cloud." << nl
104 << abort(FatalError);
108 numberDensities_ /= cloud.nParticle();
113 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
115 template <class CloudType>
116 Foam::FreeStream<CloudType>::~FreeStream()
120 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
122 template <class CloudType>
123 void Foam::FreeStream<CloudType>::inflow()
125 CloudType& cloud(this->owner());
127 const polyMesh& mesh(cloud.mesh());
129 const scalar deltaT = cloud.cachedDeltaT();
131 Random& rndGen(cloud.rndGen());
133 scalar sqrtPi = sqrt(mathematicalConstant::pi);
135 label particlesInserted = 0;
137 const volScalarField::GeometricBoundaryField& boundaryT
139 cloud.T().boundaryField()
142 const volVectorField::GeometricBoundaryField& boundaryU
144 cloud.U().boundaryField()
149 label patchI = patches_[p];
151 const polyPatch& patch = mesh.boundaryMesh()[patchI];
153 // Add mass to the accumulators. negative face area dotted with the
154 // velocity to point flux into the domain.
156 // Take a reference to the particleFluxAccumulator for this patch
157 List<Field<scalar> >& pFA = particleFluxAccumulators_[p];
161 label typeId = moleculeTypeIds_[i];
163 scalar mass = cloud.constProps(typeId).mass();
165 if (min(boundaryT[patchI]) < SMALL)
167 FatalErrorIn ("Foam::FreeStream<CloudType>::inflow()")
168 << "Zero boundary temperature detected, check boundaryT condition." << nl
169 << nl << abort(FatalError);
172 scalarField mostProbableSpeed
174 cloud.maxwellianMostProbableSpeed
181 // Dotting boundary velocity with the face unit normal (which points
182 // out of the domain, so it must be negated), dividing by the most
183 // probable speed to form molecularSpeedRatio * cosTheta
185 scalarField sCosTheta =
187 & -patch.faceAreas()/mag(patch.faceAreas())
190 // From Bird eqn 4.22
193 mag(patch.faceAreas())*numberDensities_[i]*deltaT
196 exp(-sqr(sCosTheta)) + sqrtPi*sCosTheta*(1 + erf(sCosTheta))
203 // Loop over all faces as the outer loop to avoid calculating
204 // geometrical properties multiple times.
206 labelList faceVertices = patch[f];
208 label nVertices = faceVertices.size();
210 label globalFaceIndex = f + patch.start();
212 label cell = mesh.faceOwner()[globalFaceIndex];
214 const vector& fC = patch.faceCentres()[f];
216 scalar fA = mag(patch.faceAreas()[f]);
218 // Cumulative triangle area fractions
219 List<scalar> cTriAFracs(nVertices);
221 for (label v = 0; v < nVertices - 1; v++)
223 const point& vA = mesh.points()[faceVertices[v]];
225 const point& vB = mesh.points()[faceVertices[(v + 1)]];
228 0.5*mag((vA - fC)^(vB - fC))/fA
229 + cTriAFracs[max((v - 1), 0)];
232 // Force the last area fraction value to 1.0 to avoid any
233 // rounding/non-flat face errors giving a value < 1.0
234 cTriAFracs[nVertices - 1] = 1.0;
236 // Normal unit vector *negative* so normal is pointing into the
238 vector n = patch.faceAreas()[f];
241 // Wall tangential unit vector. Use the direction between the
242 // face centre and the first vertex in the list
243 vector t1 = fC - (mesh.points()[faceVertices[0]]);
246 // Other tangential unit vector. Rescaling in case face is not
247 // flat and n and t1 aren't perfectly orthogonal
251 scalar faceTemperature = boundaryT[patchI][f];
253 const vector& faceVelocity = boundaryU[patchI][f];
257 scalar& faceAccumulator = pFA[i][f];
259 // Number of whole particles to insert
260 label nI = max(label(faceAccumulator), 0);
262 // Add another particle with a probability proportional to the
263 // remainder of taking the integer part of faceAccumulator
264 if ((faceAccumulator - nI) > rndGen.scalar01())
269 faceAccumulator -= nI;
271 label typeId = moleculeTypeIds_[i];
273 scalar mass = cloud.constProps(typeId).mass();
275 for (label i = 0; i < nI; i++)
277 // Choose a triangle to insert on, based on their relative
280 scalar triSelection = rndGen.scalar01();
285 forAll(cTriAFracs, tri)
289 if (cTriAFracs[tri] >= triSelection)
295 // Randomly distribute the points on the triangle, using the
297 // Generating Random Points in Triangles
299 // from "Graphics Gems", Academic Press, 1990
300 // http://tog.acm.org/GraphicsGems/gems/TriPoints.c
303 const point& B = mesh.points()[faceVertices[sTri]];
305 mesh.points()[faceVertices[(sTri + 1) % nVertices]];
307 scalar s = rndGen.scalar01();
308 scalar t = sqrt(rndGen.scalar01());
310 point p = (1 - t)*A + (1 - s)*t*B + s*t*C;
312 // Velocity generation
314 scalar mostProbableSpeed
316 cloud.maxwellianMostProbableSpeed
323 scalar sCosTheta = (faceVelocity & n)/mostProbableSpeed;
325 // Coefficients required for Bird eqn 12.5
326 scalar uNormProbCoeffA =
327 sCosTheta + sqrt(sqr(sCosTheta) + 2.0);
329 scalar uNormProbCoeffB =
333 + sCosTheta*(sCosTheta - sqrt(sqr(sCosTheta) + 2.0))
336 // Equivalent to the QA value in Bird's DSMC3.FOR
337 scalar randomScaling = 3.0;
341 randomScaling = mag(sCosTheta) + 1;
346 // Normalised candidates for the normal direction velocity
349 scalar uNormalThermal;
351 // Select a velocity using Bird eqn 12.5
355 randomScaling*(2.0*rndGen.scalar01() - 1);
357 uNormal = uNormalThermal + sCosTheta;
365 P = 2.0*uNormal/uNormProbCoeffA
366 *exp(uNormProbCoeffB - sqr(uNormalThermal));
369 } while (P < rndGen.scalar01());
372 sqrt(CloudType::kb*faceTemperature/mass)
374 rndGen.GaussNormal()*t1
375 + rndGen.GaussNormal()*t2
377 + (t1 & faceVelocity)*t1
378 + (t2 & faceVelocity)*t2
379 + mostProbableSpeed*uNormal*n;
381 scalar Ei = cloud.equipartitionInternalEnergy
384 cloud.constProps(typeId).internalDegreesOfFreedom()
402 reduce(particlesInserted, sumOp<label>());
404 Info<< " Particles inserted = "
405 << particlesInserted << endl;
410 // ************************************************************************* //