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 = mesh().time().deltaTValue();
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>::resetFields()
480 q_ = dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0);
482 fD_ = dimensionedVector
485 dimensionSet(1, -1, -2, 0, 0),
489 rhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
491 rhoM_ = dimensionedScalar("zero", dimensionSet(1, -3, 0, 0, 0), VSMALL);
493 dsmcRhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), 0.0);
495 linearKE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
497 internalE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
499 iDof_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
501 momentum_ = dimensionedVector
504 dimensionSet(1, -2, -1, 0, 0),
510 template<class ParcelType>
511 void Foam::DsmcCloud<ParcelType>::calculateFields()
513 scalarField& rhoN = rhoN_.internalField();
515 scalarField& rhoM = rhoM_.internalField();
517 scalarField& dsmcRhoN = dsmcRhoN_.internalField();
519 scalarField& linearKE = linearKE_.internalField();
521 scalarField& internalE = internalE_.internalField();
523 scalarField& iDof = iDof_.internalField();
525 vectorField& momentum = momentum_.internalField();
527 forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
529 const ParcelType& p = iter();
530 const label cellI = p.cell();
534 rhoM[cellI] += constProps(p.typeId()).mass();
538 linearKE[cellI] += 0.5*constProps(p.typeId()).mass()*(p.U() & p.U());
540 internalE[cellI] += p.Ei();
542 iDof[cellI] += constProps(p.typeId()).internalDegreesOfFreedom();
544 momentum[cellI] += constProps(p.typeId()).mass()*p.U();
547 rhoN *= nParticle_/mesh().cellVolumes();
548 rhoN_.correctBoundaryConditions();
550 rhoM *= nParticle_/mesh().cellVolumes();
551 rhoM_.correctBoundaryConditions();
553 linearKE *= nParticle_/mesh().cellVolumes();
554 linearKE_.correctBoundaryConditions();
556 internalE *= nParticle_/mesh().cellVolumes();
557 internalE_.correctBoundaryConditions();
559 iDof *= nParticle_/mesh().cellVolumes();
560 iDof_.correctBoundaryConditions();
562 momentum *= nParticle_/mesh().cellVolumes();
563 momentum_.correctBoundaryConditions();
567 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
569 template<class ParcelType>
570 void Foam::DsmcCloud<ParcelType>::addNewParcel
572 const vector& position,
579 ParcelType* pPtr = new ParcelType
593 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
595 template<class ParcelType>
596 Foam::DsmcCloud<ParcelType>::DsmcCloud
598 const word& cloudName,
603 Cloud<ParcelType>(mesh, cloudName, false),
605 cloudName_(cloudName),
611 cloudName + "Properties",
612 mesh_.time().constant(),
618 typeIdList_(particleProperties_.lookup("typeIdList")),
619 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
620 cellOccupancy_(mesh_.nCells()),
625 this->name() + "SigmaTcRMax",
626 mesh_.time().timeName(),
633 collisionSelectionRemainder_(mesh_.nCells(), 0),
639 mesh_.time().timeName(),
651 mesh_.time().timeName(),
663 mesh_.time().timeName(),
675 mesh_.time().timeName(),
687 mesh_.time().timeName(),
699 mesh_.time().timeName(),
711 mesh_.time().timeName(),
723 mesh_.time().timeName(),
735 mesh_.time().timeName(),
743 rndGen_(label(149382906) + 7183*Pstream::myProcNo()),
751 mesh_.time().timeName(),
766 mesh_.time().timeName(),
774 binaryCollisionModel_
776 BinaryCollisionModel<DsmcCloud<ParcelType> >::New
782 wallInteractionModel_
784 WallInteractionModel<DsmcCloud<ParcelType> >::New
792 InflowBoundaryModel<DsmcCloud<ParcelType> >::New
801 buildCellOccupancy();
803 // Initialise the collision selection remainder to a random value between 0
805 forAll(collisionSelectionRemainder_, i)
807 collisionSelectionRemainder_[i] = rndGen_.scalar01();
812 ParcelType::readFields(*this);
817 template<class ParcelType>
818 Foam::DsmcCloud<ParcelType>::DsmcCloud
820 const word& cloudName,
822 const IOdictionary& dsmcInitialiseDict
825 Cloud<ParcelType>(mesh, cloudName, false),
827 cloudName_(cloudName),
833 cloudName + "Properties",
834 mesh_.time().constant(),
840 typeIdList_(particleProperties_.lookup("typeIdList")),
841 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
847 this->name() + "SigmaTcRMax",
848 mesh_.time().timeName(),
854 dimensionedScalar("zero", dimensionSet(0, 3, -1, 0, 0), 0.0)
856 collisionSelectionRemainder_(),
862 mesh_.time().timeName(),
868 dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0)
874 this->name() + "fD_",
875 mesh_.time().timeName(),
884 dimensionSet(1, -1, -2, 0, 0),
892 this->name() + "rhoN_",
893 mesh_.time().timeName(),
899 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL)
905 this->name() + "rhoM_",
906 mesh_.time().timeName(),
912 dimensionedScalar("zero", dimensionSet(1, -3, 0, 0, 0), VSMALL)
918 this->name() + "dsmcRhoN_",
919 mesh_.time().timeName(),
925 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), 0.0)
931 this->name() + "linearKE_",
932 mesh_.time().timeName(),
938 dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0)
944 this->name() + "internalE_",
945 mesh_.time().timeName(),
951 dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0)
957 this->name() + "iDof_",
958 mesh_.time().timeName(),
964 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL)
970 this->name() + "momentum_",
971 mesh_.time().timeName(),
980 dimensionSet(1, -2, -1, 0, 0),
985 rndGen_(label(971501) + 1526*Pstream::myProcNo()),
993 mesh_.time().timeName(),
999 dimensionedScalar("zero", dimensionSet(0, 0, 0, 1, 0), 0.0)
1009 mesh_.time().timeName(),
1018 dimensionSet(0, 1, -1, 0, 0),
1023 binaryCollisionModel_(),
1024 wallInteractionModel_(),
1025 inflowBoundaryModel_()
1031 initialise(dsmcInitialiseDict);
1035 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
1037 template<class ParcelType>
1038 Foam::DsmcCloud<ParcelType>::~DsmcCloud()
1042 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1044 template<class ParcelType>
1045 void Foam::DsmcCloud<ParcelType>::evolve()
1047 typename ParcelType::trackData td(*this);
1049 // Reset the data collection fields
1054 this->dumpParticlePositions();
1057 // Insert new particles from the inflow boundary
1058 this->inflowBoundary().inflow();
1060 // Move the particles ballistically with their current velocities
1061 Cloud<ParcelType>::move(td);
1063 // Calculate new velocities via stochastic collisions
1066 // Calculate the volume field data
1071 template<class ParcelType>
1072 void Foam::DsmcCloud<ParcelType>::info() const
1074 label nDsmcParticles = this->size();
1075 reduce(nDsmcParticles, sumOp<label>());
1077 scalar nMol = nDsmcParticles*nParticle_;
1079 vector linearMomentum = linearMomentumOfSystem();
1080 reduce(linearMomentum, sumOp<vector>());
1082 scalar linearKineticEnergy = linearKineticEnergyOfSystem();
1083 reduce(linearKineticEnergy, sumOp<scalar>());
1085 scalar internalEnergy = internalEnergyOfSystem();
1086 reduce(internalEnergy, sumOp<scalar>());
1088 Info<< "Cloud name: " << this->name() << nl
1089 << " Number of dsmc particles = "
1095 Info<< " Number of molecules = "
1097 << " Mass in system = "
1098 << returnReduce(massInSystem(), sumOp<scalar>()) << nl
1099 << " Average linear momentum = "
1100 << linearMomentum/nMol << nl
1101 << " |Average linear momentum| = "
1102 << mag(linearMomentum)/nMol << nl
1103 << " Average linear kinetic energy = "
1104 << linearKineticEnergy/nMol << nl
1105 << " Average internal energy = "
1106 << internalEnergy/nMol << nl
1107 << " Average total energy = "
1108 << (internalEnergy + linearKineticEnergy)/nMol
1114 template<class ParcelType>
1115 Foam::vector Foam::DsmcCloud<ParcelType>::equipartitionLinearVelocity
1122 sqrt(kb*temperature/mass)
1125 rndGen_.GaussNormal(),
1126 rndGen_.GaussNormal(),
1127 rndGen_.GaussNormal()
1132 template<class ParcelType>
1133 Foam::scalar Foam::DsmcCloud<ParcelType>::equipartitionInternalEnergy
1145 else if (iDof < 2.0 + SMALL && iDof > 2.0 - SMALL)
1147 // Special case for iDof = 2, i.e. diatomics;
1148 Ei = -log(rndGen_.scalar01())*kb*temperature;
1152 scalar a = 0.5*iDof - 1;
1160 energyRatio = 10*rndGen_.scalar01();
1162 P = pow((energyRatio/a), a)*exp(a - energyRatio);
1164 } while (P < rndGen_.scalar01());
1166 Ei = energyRatio*kb*temperature;
1173 template<class ParcelType>
1174 void Foam::DsmcCloud<ParcelType>::dumpParticlePositions() const
1178 this->db().time().path()/"parcelPositions_"
1179 + this->name() + "_"
1180 + this->db().time().timeName() + ".obj"
1183 forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
1185 const ParcelType& p = iter();
1187 pObj<< "v " << p.position().x()
1188 << " " << p.position().y()
1189 << " " << p.position().z()
1197 // ************************************************************************* //