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 "DsmcCloud.H"
28 #include "BinaryCollisionModel.H"
29 #include "WallInteractionModel.H"
30 #include "InflowBoundaryModel.H"
32 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
34 template<class ParcelType>
35 Foam::scalar Foam::DsmcCloud<ParcelType>::kb = 1.380650277e-23;
38 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
40 template<class ParcelType>
41 void Foam::DsmcCloud<ParcelType>::buildConstProps()
43 Info<< nl << "Constructing constant properties for" << endl;
44 constProps_.setSize(typeIdList_.size());
46 dictionary moleculeProperties
48 particleProperties_.subDict("moleculeProperties")
51 forAll(typeIdList_, i)
53 const word& id(typeIdList_[i]);
55 Info<< " " << id << endl;
57 const dictionary& molDict(moleculeProperties.subDict(id));
60 typename ParcelType::constantProperties::constantProperties(molDict);
65 template<class ParcelType>
66 void Foam::DsmcCloud<ParcelType>::buildCellOccupancy()
68 forAll(cellOccupancy_, cO)
70 cellOccupancy_[cO].clear();
73 forAllIter(typename DsmcCloud<ParcelType>, *this, iter)
75 cellOccupancy_[iter().cell()].append(&iter());
80 template<class ParcelType>
81 void Foam::DsmcCloud<ParcelType>::initialise
83 const IOdictionary& dsmcInitialiseDict
86 Info<< nl << "Initialising particles" << endl;
88 const scalar temperature
90 readScalar(dsmcInitialiseDict.lookup("temperature"))
93 const vector velocity(dsmcInitialiseDict.lookup("velocity"));
95 const dictionary& numberDensitiesDict
97 dsmcInitialiseDict.subDict("numberDensities")
100 List<word> molecules(numberDensitiesDict.toc());
102 Field<scalar> numberDensities(molecules.size());
106 numberDensities[i] = readScalar
108 numberDensitiesDict.lookup(molecules[i])
112 numberDensities /= nParticle_;
114 forAll(mesh_.cells(), cell)
116 const vector& cC = mesh_.cellCentres()[cell];
117 const labelList& cellFaces = mesh_.cells()[cell];
118 const scalar cV = mesh_.cellVolumes()[cell];
122 // Each face is split into nEdges (or nVertices) - 2 tets.
123 forAll(cellFaces, face)
125 nTets += mesh_.faces()[cellFaces[face]].size() - 2;
128 // Calculate the cumulative tet volumes circulating around the cell and
129 // record the vertex labels of each.
130 scalarList cTetVFracs(nTets, 0.0);
132 List<labelList> tetPtIs(nTets, labelList(3,-1));
134 // Keep track of which tet this is.
137 forAll(cellFaces, face)
139 const labelList& facePoints = mesh_.faces()[cellFaces[face]];
142 while ((pointI + 1) < facePoints.size())
145 const vector& pA = mesh_.points()[facePoints[0]];
146 const vector& pB = mesh_.points()[facePoints[pointI]];
147 const vector& pC = mesh_.points()[facePoints[pointI + 1]];
150 mag(((pA - cC) ^ (pB - cC)) & (pC - cC))/(cV*6.0)
151 + cTetVFracs[max((tet - 1),0)];
153 tetPtIs[tet][0] = facePoints[0];
154 tetPtIs[tet][1] = facePoints[pointI];
155 tetPtIs[tet][2] = facePoints[pointI + 1];
162 // Force the last volume fraction value to 1.0 to avoid any
163 // rounding/non-flat face errors giving a value < 1.0
164 cTetVFracs[nTets - 1] = 1.0;
168 const word& moleculeName(molecules[i]);
170 label typeId(findIndex(typeIdList_, moleculeName));
174 FatalErrorIn("Foam::DsmcCloud<ParcelType>::initialise")
175 << "typeId " << moleculeName << "not defined." << nl
176 << abort(FatalError);
179 const typename ParcelType::constantProperties& cP =
182 scalar numberDensity = numberDensities[i];
184 // Calculate the number of particles required
185 scalar particlesRequired = numberDensity*mesh_.cellVolumes()[cell];
187 // Only integer numbers of particles can be inserted
188 label nParticlesToInsert = label(particlesRequired);
190 // Add another particle with a probability proportional to the
191 // remainder of taking the integer part of particlesRequired
192 if ((particlesRequired - nParticlesToInsert) > rndGen_.scalar01())
194 nParticlesToInsert++;
197 for (label pI = 0; pI < nParticlesToInsert; pI++)
199 // Choose a random point in a generic tetrahedron
201 scalar s = rndGen_.scalar01();
202 scalar t = rndGen_.scalar01();
203 scalar u = rndGen_.scalar01();
217 else if (s + t + u > 1.0)
224 // Choose a tetrahedron to insert in, based on their relative
226 scalar tetSelection = rndGen_.scalar01();
228 // Selected tetrahedron
231 forAll(cTetVFracs, tet)
235 if (cTetVFracs[tet] >= tetSelection)
243 + s*mesh_.points()[tetPtIs[sTet][0]]
244 + t*mesh_.points()[tetPtIs[sTet][1]]
245 + u*mesh_.points()[tetPtIs[sTet][2]];
247 vector U = equipartitionLinearVelocity
253 scalar Ei = equipartitionInternalEnergy
256 cP.internalDegreesOfFreedom()
273 // Initialise the sigmaTcRMax_ field to the product of the cross section of
274 // the most abundant species and the most probable thermal speed (Bird,
277 label mostAbundantType(findMax(numberDensities));
279 const typename ParcelType::constantProperties& cP = constProps
284 sigmaTcRMax_.internalField() = cP.sigmaT()*maxwellianMostProbableSpeed
290 sigmaTcRMax_.correctBoundaryConditions();
294 template<class ParcelType>
295 void Foam::DsmcCloud<ParcelType>::collisions()
297 buildCellOccupancy();
299 // Temporary storage for subCells
300 List<DynamicList<label> > subCells(8);
302 scalar deltaT = cachedDeltaT();
304 label collisionCandidates = 0;
306 label collisions = 0;
308 forAll(cellOccupancy_, celli)
310 const DynamicList<ParcelType*>& cellParcels(cellOccupancy_[celli]);
312 label nC(cellParcels.size());
317 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
318 // Assign particles to one of 8 Cartesian subCells
320 // Clear temporary lists
326 // Inverse addressing specifying which subCell a parcel is in
327 List<label> whichSubCell(cellParcels.size());
329 const point& cC = mesh_.cellCentres()[celli];
331 forAll(cellParcels, i)
333 ParcelType* p = cellParcels[i];
335 vector relPos = p->position() - cC;
338 pos(relPos.x()) + 2*pos(relPos.y()) + 4*pos(relPos.z());
340 subCells[subCell].append(i);
342 whichSubCell[i] = subCell;
345 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
347 scalar sigmaTcRMax = sigmaTcRMax_[celli];
349 scalar selectedPairs =
350 collisionSelectionRemainder_[celli]
351 + 0.5*nC*(nC - 1)*nParticle_*sigmaTcRMax*deltaT
352 /mesh_.cellVolumes()[celli];
354 label nCandidates(selectedPairs);
356 collisionSelectionRemainder_[celli] = selectedPairs - nCandidates;
358 collisionCandidates += nCandidates;
360 for (label c = 0; c < nCandidates; c++)
362 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
363 // subCell candidate selection procedure
365 // Select the first collision candidate
366 label candidateP = rndGen_.integer(0, nC - 1);
368 // Declare the second collision candidate
369 label candidateQ = -1;
371 List<label> subCellPs = subCells[whichSubCell[candidateP]];
373 label nSC = subCellPs.size();
377 // If there are two or more particle in a subCell, choose
378 // another from the same cell. If the same candidate is
379 // chosen, choose again.
383 candidateQ = subCellPs[rndGen_.integer(0, nSC - 1)];
385 } while(candidateP == candidateQ);
389 // Select a possible second collision candidate from the
390 // whole cell. If the same candidate is chosen, choose
395 candidateQ = rndGen_.integer(0, nC - 1);
397 } while(candidateP == candidateQ);
400 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
401 // uniform candidate selection procedure
403 // // Select the first collision candidate
404 // label candidateP = rndGen_.integer(0, nC-1);
406 // // Select a possible second collision candidate
407 // label candidateQ = rndGen_.integer(0, nC-1);
409 // // If the same candidate is chosen, choose again
410 // while(candidateP == candidateQ)
412 // candidateQ = rndGen_.integer(0, nC-1);
415 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
417 ParcelType* parcelP = cellParcels[candidateP];
418 ParcelType* parcelQ = cellParcels[candidateQ];
420 label typeIdP = parcelP->typeId();
421 label typeIdQ = parcelQ->typeId();
423 scalar sigmaTcR = binaryCollision().sigmaTcR
431 // Update the maximum value of sigmaTcR stored, but use the
432 // initial value in the acceptance-rejection criteria because
433 // the number of collision candidates selected was based on this
435 if (sigmaTcR > sigmaTcRMax_[celli])
437 sigmaTcRMax_[celli] = sigmaTcR;
440 if ((sigmaTcR/sigmaTcRMax) > rndGen_.scalar01())
442 binaryCollision().collide
458 reduce(collisions, sumOp<label>());
460 reduce(collisionCandidates, sumOp<label>());
462 if (collisionCandidates)
464 Info<< " Collisions = "
466 << " Acceptance rate = "
467 << scalar(collisions)/scalar(collisionCandidates) << nl
472 Info<< " No collisions" << endl;
477 template<class ParcelType>
478 void Foam::DsmcCloud<ParcelType>::resetSurfaceDataFields()
480 volScalarField::GeometricBoundaryField& qBF(q_.boundaryField());
487 volVectorField::GeometricBoundaryField& fDBF(fD_.boundaryField());
491 fDBF[p] = vector::zero;
496 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
498 template<class ParcelType>
499 void Foam::DsmcCloud<ParcelType>::addNewParcel
501 const vector& position,
508 ParcelType* pPtr = new ParcelType
522 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
524 template<class ParcelType>
525 Foam::DsmcCloud<ParcelType>::DsmcCloud
527 const word& cloudName,
528 const volScalarField& T,
529 const volVectorField& U
532 Cloud<ParcelType>(T.mesh(), cloudName, false),
534 cloudName_(cloudName),
540 cloudName + "Properties",
541 mesh_.time().constant(),
547 typeIdList_(particleProperties_.lookup("typeIdList")),
548 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
549 cellOccupancy_(mesh_.nCells()),
554 this->name() + "SigmaTcRMax",
555 mesh_.time().timeName(),
562 collisionSelectionRemainder_(mesh_.nCells(), 0),
568 mesh_.time().timeName(),
574 dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0)
580 this->name() + "fD_",
581 mesh_.time().timeName(),
590 dimensionSet(1, -1, -2, 0, 0),
595 rndGen_(label(149382906) + 7183*Pstream::myProcNo()),
598 binaryCollisionModel_
600 BinaryCollisionModel<DsmcCloud<ParcelType> >::New
606 wallInteractionModel_
608 WallInteractionModel<DsmcCloud<ParcelType> >::New
616 InflowBoundaryModel<DsmcCloud<ParcelType> >::New
625 buildCellOccupancy();
627 // Initialise the collision selection remainder to a random value between 0
629 forAll(collisionSelectionRemainder_, i)
631 collisionSelectionRemainder_[i] = rndGen_.scalar01();
636 template<class ParcelType>
637 Foam::DsmcCloud<ParcelType>::DsmcCloud
639 const word& cloudName,
643 Cloud<ParcelType>(mesh, cloudName, false),
645 cloudName_(cloudName),
651 cloudName + "Properties",
652 mesh_.time().constant(),
658 typeIdList_(particleProperties_.lookup("typeIdList")),
659 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
665 this->name() + "SigmaTcRMax",
666 mesh_.time().timeName(),
672 dimensionedScalar("zero", dimensionSet(0, 3, -1, 0, 0), 0.0)
674 collisionSelectionRemainder_(),
680 mesh_.time().timeName(),
686 dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0)
692 this->name() + "fD_",
693 mesh_.time().timeName(),
702 dimensionSet(1, -1, -2, 0, 0),
707 rndGen_(label(971501) + 1526*Pstream::myProcNo()),
715 mesh_.time().timeName(),
721 dimensionedScalar("zero", dimensionSet(0, 0, 0, 1, 0), 0.0)
731 mesh_.time().timeName(),
740 dimensionSet(0, 1, -1, 0, 0),
745 binaryCollisionModel_(),
746 wallInteractionModel_(),
747 inflowBoundaryModel_()
753 IOdictionary dsmcInitialiseDict
757 "dsmcInitialiseDict",
758 mesh_.time().system(),
765 initialise(dsmcInitialiseDict);
769 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
771 template<class ParcelType>
772 Foam::DsmcCloud<ParcelType>::~DsmcCloud()
776 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
778 template<class ParcelType>
779 void Foam::DsmcCloud<ParcelType>::evolve()
781 // cache the value of deltaT for this timestep
784 typename ParcelType::trackData td(*this);
786 // Reset the surface data collection fields
787 resetSurfaceDataFields();
791 this->dumpParticlePositions();
794 // Insert new particles from the inflow boundary
795 this->inflowBoundary().inflow();
797 // Move the particles ballistically with their current velocities
798 Cloud<ParcelType>::move(td);
800 // Calculate new velocities via stochastic collisions
805 template<class ParcelType>
806 void Foam::DsmcCloud<ParcelType>::info() const
808 label nDsmcParticles = this->size();
809 reduce(nDsmcParticles, sumOp<label>());
811 scalar nMol = nDsmcParticles*nParticle_;
813 vector linearMomentum = linearMomentumOfSystem();
814 reduce(linearMomentum, sumOp<vector>());
816 scalar linearKineticEnergy = linearKineticEnergyOfSystem();
817 reduce(linearKineticEnergy, sumOp<scalar>());
819 scalar internalEnergy = internalEnergyOfSystem();
820 reduce(internalEnergy, sumOp<scalar>());
822 Info<< "Cloud name: " << this->name() << nl
823 << " Number of dsmc particles = "
829 Info<< " Number of molecules = "
831 << " Mass in system = "
832 << returnReduce(massInSystem(), sumOp<scalar>()) << nl
833 << " Average linear momentum = "
834 << linearMomentum/nMol << nl
835 << " |Average linear momentum| = "
836 << mag(linearMomentum)/nMol << nl
837 << " Average linear kinetic energy = "
838 << linearKineticEnergy/nMol << nl
839 << " Average internal energy = "
840 << internalEnergy/nMol << nl
841 << " Average total energy = "
842 << (internalEnergy + linearKineticEnergy)/nMol
848 template<class ParcelType>
849 Foam::vector Foam::DsmcCloud<ParcelType>::equipartitionLinearVelocity
856 sqrt(kb*temperature/mass)
859 rndGen_.GaussNormal(),
860 rndGen_.GaussNormal(),
861 rndGen_.GaussNormal()
866 template<class ParcelType>
867 Foam::scalar Foam::DsmcCloud<ParcelType>::equipartitionInternalEnergy
879 else if (iDof < 2.0 + SMALL && iDof > 2.0 - SMALL)
881 // Special case for iDof = 2, i.e. diatomics;
882 Ei = -log(rndGen_.scalar01())*kb*temperature;
886 scalar a = 0.5*iDof - 1;
894 energyRatio = 10*rndGen_.scalar01();
896 P = pow((energyRatio/a), a)*exp(a - energyRatio);
898 } while (P < rndGen_.scalar01());
900 Ei = energyRatio*kb*temperature;
907 template<class ParcelType>
908 void Foam::DsmcCloud<ParcelType>::dumpParticlePositions() const
912 this->db().time().path()/"parcelPositions_"
914 + this->db().time().timeName() + ".obj"
917 forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
919 const ParcelType& p = iter();
921 pObj<< "v " << p.position().x()
922 << " " << p.position().y()
923 << " " << p.position().z()
931 // ************************************************************************* //