1 /*---------------------------------------------------------------------------*\
3 \\ / F ield | foam-extend: Open Source CFD
4 \\ / O peration | Version: 3.2
5 \\ / A nd | Web: http://www.foam-extend.org
6 \\/ M anipulation | For copyright notice see file Copyright
7 -------------------------------------------------------------------------------
9 This file is part of foam-extend.
11 foam-extend 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 3 of the License, or (at your
14 option) any later version.
16 foam-extend is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with foam-extend. If not, see <http://www.gnu.org/licenses/>.
24 \*---------------------------------------------------------------------------*/
26 #include "DsmcCloudTemplate.H"
27 #include "BinaryCollisionModel.H"
28 #include "WallInteractionModel.H"
29 #include "InflowBoundaryModel.H"
31 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
33 template<class ParcelType>
34 Foam::scalar Foam::DsmcCloud<ParcelType>::kb = 1.380650277e-23;
37 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
39 template<class ParcelType>
40 void Foam::DsmcCloud<ParcelType>::buildConstProps()
42 Info<< nl << "Constructing constant properties for" << endl;
43 constProps_.setSize(typeIdList_.size());
45 dictionary moleculeProperties
47 particleProperties_.subDict("moleculeProperties")
50 forAll(typeIdList_, i)
52 const word& id(typeIdList_[i]);
54 Info<< " " << id << endl;
56 const dictionary& molDict(moleculeProperties.subDict(id));
59 typename ParcelType::constantProperties::constantProperties(molDict);
64 template<class ParcelType>
65 void Foam::DsmcCloud<ParcelType>::buildCellOccupancy()
67 forAll(cellOccupancy_, cO)
69 cellOccupancy_[cO].clear();
72 forAllIter(typename DsmcCloud<ParcelType>, *this, iter)
74 cellOccupancy_[iter().cell()].append(&iter());
79 template<class ParcelType>
80 void Foam::DsmcCloud<ParcelType>::initialise
82 const IOdictionary& dsmcInitialiseDict
85 Info<< nl << "Initialising particles" << endl;
87 const scalar temperature
89 readScalar(dsmcInitialiseDict.lookup("temperature"))
92 const vector velocity(dsmcInitialiseDict.lookup("velocity"));
94 const dictionary& numberDensitiesDict
96 dsmcInitialiseDict.subDict("numberDensities")
99 List<word> molecules(numberDensitiesDict.toc());
101 Field<scalar> numberDensities(molecules.size());
105 numberDensities[i] = readScalar
107 numberDensitiesDict.lookup(molecules[i])
111 numberDensities /= nParticle_;
113 forAll(mesh_.cells(), cell)
115 const vector& cC = mesh_.cellCentres()[cell];
116 const labelList& cellFaces = mesh_.cells()[cell];
117 const scalar cV = mesh_.cellVolumes()[cell];
121 // Each face is split into nEdges (or nVertices) - 2 tets.
122 forAll(cellFaces, face)
124 nTets += mesh_.faces()[cellFaces[face]].size() - 2;
127 // Calculate the cumulative tet volumes circulating around the cell and
128 // record the vertex labels of each.
129 scalarList cTetVFracs(nTets, 0.0);
131 List<labelList> tetPtIs(nTets, labelList(3,-1));
133 // Keep track of which tet this is.
136 forAll(cellFaces, face)
138 const labelList& facePoints = mesh_.faces()[cellFaces[face]];
141 while ((pointI + 1) < facePoints.size())
144 const vector& pA = mesh_.points()[facePoints[0]];
145 const vector& pB = mesh_.points()[facePoints[pointI]];
146 const vector& pC = mesh_.points()[facePoints[pointI + 1]];
149 mag(((pA - cC) ^ (pB - cC)) & (pC - cC))/(cV*6.0)
150 + cTetVFracs[max((tet - 1),0)];
152 tetPtIs[tet][0] = facePoints[0];
153 tetPtIs[tet][1] = facePoints[pointI];
154 tetPtIs[tet][2] = facePoints[pointI + 1];
161 // Force the last volume fraction value to 1.0 to avoid any
162 // rounding/non-flat face errors giving a value < 1.0
163 cTetVFracs[nTets - 1] = 1.0;
167 const word& moleculeName(molecules[i]);
169 label typeId(findIndex(typeIdList_, moleculeName));
173 FatalErrorIn("Foam::DsmcCloud<ParcelType>::initialise")
174 << "typeId " << moleculeName << "not defined." << nl
175 << abort(FatalError);
178 const typename ParcelType::constantProperties& cP =
181 scalar numberDensity = numberDensities[i];
183 // Calculate the number of particles required
184 scalar particlesRequired = numberDensity*mesh_.cellVolumes()[cell];
186 // Only integer numbers of particles can be inserted
187 label nParticlesToInsert = label(particlesRequired);
189 // Add another particle with a probability proportional to the
190 // remainder of taking the integer part of particlesRequired
191 if ((particlesRequired - nParticlesToInsert) > rndGen_.scalar01())
193 nParticlesToInsert++;
196 for (label pI = 0; pI < nParticlesToInsert; pI++)
198 // Choose a random point in a generic tetrahedron
200 scalar s = rndGen_.scalar01();
201 scalar t = rndGen_.scalar01();
202 scalar u = rndGen_.scalar01();
216 else if (s + t + u > 1.0)
223 // Choose a tetrahedron to insert in, based on their relative
225 scalar tetSelection = rndGen_.scalar01();
227 // Selected tetrahedron
230 forAll(cTetVFracs, tet)
234 if (cTetVFracs[tet] >= tetSelection)
242 + s*mesh_.points()[tetPtIs[sTet][0]]
243 + t*mesh_.points()[tetPtIs[sTet][1]]
244 + u*mesh_.points()[tetPtIs[sTet][2]];
246 vector U = equipartitionLinearVelocity
252 scalar Ei = equipartitionInternalEnergy
255 cP.internalDegreesOfFreedom()
272 // Initialise the sigmaTcRMax_ field to the product of the cross section of
273 // the most abundant species and the most probable thermal speed (Bird,
276 label mostAbundantType(findMax(numberDensities));
278 const typename ParcelType::constantProperties& cP = constProps
283 sigmaTcRMax_.internalField() = cP.sigmaT()*maxwellianMostProbableSpeed
289 sigmaTcRMax_.correctBoundaryConditions();
293 template<class ParcelType>
294 void Foam::DsmcCloud<ParcelType>::collisions()
296 buildCellOccupancy();
298 // Temporary storage for subCells
299 List<dynamicLabelList > subCells(8);
301 scalar deltaT = mesh().time().deltaTValue();
303 label collisionCandidates = 0;
305 label collisions = 0;
307 forAll(cellOccupancy_, celli)
309 const DynamicList<ParcelType*>& cellParcels(cellOccupancy_[celli]);
311 label nC(cellParcels.size());
316 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
317 // Assign particles to one of 8 Cartesian subCells
319 // Clear temporary lists
325 // Inverse addressing specifying which subCell a parcel is in
326 List<label> whichSubCell(cellParcels.size());
328 const point& cC = mesh_.cellCentres()[celli];
330 forAll(cellParcels, i)
332 ParcelType* p = cellParcels[i];
334 vector relPos = p->position() - cC;
337 pos(relPos.x()) + 2*pos(relPos.y()) + 4*pos(relPos.z());
339 subCells[subCell].append(i);
341 whichSubCell[i] = subCell;
344 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
346 scalar sigmaTcRMax = sigmaTcRMax_[celli];
348 scalar selectedPairs =
349 collisionSelectionRemainder_[celli]
350 + 0.5*nC*(nC - 1)*nParticle_*sigmaTcRMax*deltaT
351 /mesh_.cellVolumes()[celli];
353 label nCandidates(selectedPairs);
355 collisionSelectionRemainder_[celli] = selectedPairs - nCandidates;
357 collisionCandidates += nCandidates;
359 for (label c = 0; c < nCandidates; c++)
361 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
362 // subCell candidate selection procedure
364 // Select the first collision candidate
365 label candidateP = rndGen_.integer(0, nC - 1);
367 // Declare the second collision candidate
368 label candidateQ = -1;
370 List<label> subCellPs = subCells[whichSubCell[candidateP]];
372 label nSC = subCellPs.size();
376 // If there are two or more particle in a subCell, choose
377 // another from the same cell. If the same candidate is
378 // chosen, choose again.
382 candidateQ = subCellPs[rndGen_.integer(0, nSC - 1)];
384 } while(candidateP == candidateQ);
388 // Select a possible second collision candidate from the
389 // whole cell. If the same candidate is chosen, choose
394 candidateQ = rndGen_.integer(0, nC - 1);
396 } while(candidateP == candidateQ);
399 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
400 // uniform candidate selection procedure
402 // // Select the first collision candidate
403 // label candidateP = rndGen_.integer(0, nC-1);
405 // // Select a possible second collision candidate
406 // label candidateQ = rndGen_.integer(0, nC-1);
408 // // If the same candidate is chosen, choose again
409 // while(candidateP == candidateQ)
411 // candidateQ = rndGen_.integer(0, nC-1);
414 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
416 ParcelType* parcelP = cellParcels[candidateP];
417 ParcelType* parcelQ = cellParcels[candidateQ];
419 label typeIdP = parcelP->typeId();
420 label typeIdQ = parcelQ->typeId();
422 scalar sigmaTcR = binaryCollision().sigmaTcR
430 // Update the maximum value of sigmaTcR stored, but use the
431 // initial value in the acceptance-rejection criteria because
432 // the number of collision candidates selected was based on this
434 if (sigmaTcR > sigmaTcRMax_[celli])
436 sigmaTcRMax_[celli] = sigmaTcR;
439 if ((sigmaTcR/sigmaTcRMax) > rndGen_.scalar01())
441 binaryCollision().collide
457 reduce(collisions, sumOp<label>());
459 reduce(collisionCandidates, sumOp<label>());
461 if (collisionCandidates)
463 Info<< " Collisions = "
465 << " Acceptance rate = "
466 << scalar(collisions)/scalar(collisionCandidates) << nl
471 Info<< " No collisions" << endl;
476 template<class ParcelType>
477 void Foam::DsmcCloud<ParcelType>::resetFields()
479 q_ = dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0);
481 fD_ = dimensionedVector
484 dimensionSet(1, -1, -2, 0, 0),
488 rhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
490 rhoM_ = dimensionedScalar("zero", dimensionSet(1, -3, 0, 0, 0), VSMALL);
492 dsmcRhoN_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), 0.0);
494 linearKE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
496 internalE_ = dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0);
498 iDof_ = dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL);
500 momentum_ = dimensionedVector
503 dimensionSet(1, -2, -1, 0, 0),
509 template<class ParcelType>
510 void Foam::DsmcCloud<ParcelType>::calculateFields()
512 scalarField& rhoN = rhoN_.internalField();
514 scalarField& rhoM = rhoM_.internalField();
516 scalarField& dsmcRhoN = dsmcRhoN_.internalField();
518 scalarField& linearKE = linearKE_.internalField();
520 scalarField& internalE = internalE_.internalField();
522 scalarField& iDof = iDof_.internalField();
524 vectorField& momentum = momentum_.internalField();
526 forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
528 const ParcelType& p = iter();
529 const label cellI = p.cell();
533 rhoM[cellI] += constProps(p.typeId()).mass();
537 linearKE[cellI] += 0.5*constProps(p.typeId()).mass()*(p.U() & p.U());
539 internalE[cellI] += p.Ei();
541 iDof[cellI] += constProps(p.typeId()).internalDegreesOfFreedom();
543 momentum[cellI] += constProps(p.typeId()).mass()*p.U();
546 rhoN *= nParticle_/mesh().cellVolumes();
547 rhoN_.correctBoundaryConditions();
549 rhoM *= nParticle_/mesh().cellVolumes();
550 rhoM_.correctBoundaryConditions();
552 linearKE *= nParticle_/mesh().cellVolumes();
553 linearKE_.correctBoundaryConditions();
555 internalE *= nParticle_/mesh().cellVolumes();
556 internalE_.correctBoundaryConditions();
558 iDof *= nParticle_/mesh().cellVolumes();
559 iDof_.correctBoundaryConditions();
561 momentum *= nParticle_/mesh().cellVolumes();
562 momentum_.correctBoundaryConditions();
566 // * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * * //
568 template<class ParcelType>
569 void Foam::DsmcCloud<ParcelType>::addNewParcel
571 const vector& position,
578 ParcelType* pPtr = new ParcelType
588 this->addParticle(pPtr);
592 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
594 template<class ParcelType>
595 Foam::DsmcCloud<ParcelType>::DsmcCloud
597 const word& cloudName,
602 Cloud<ParcelType>(mesh, cloudName, false),
604 cloudName_(cloudName),
610 cloudName + "Properties",
611 mesh_.time().constant(),
617 typeIdList_(particleProperties_.lookup("typeIdList")),
618 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
619 cellOccupancy_(mesh_.nCells()),
624 this->name() + "SigmaTcRMax",
625 mesh_.time().timeName(),
632 collisionSelectionRemainder_(mesh_.nCells(), 0),
638 mesh_.time().timeName(),
650 mesh_.time().timeName(),
662 mesh_.time().timeName(),
674 mesh_.time().timeName(),
686 mesh_.time().timeName(),
698 mesh_.time().timeName(),
710 mesh_.time().timeName(),
722 mesh_.time().timeName(),
734 mesh_.time().timeName(),
742 rndGen_(label(149382906) + 7183*Pstream::myProcNo()),
750 mesh_.time().timeName(),
765 mesh_.time().timeName(),
773 binaryCollisionModel_
775 BinaryCollisionModel<DsmcCloud<ParcelType> >::New
781 wallInteractionModel_
783 WallInteractionModel<DsmcCloud<ParcelType> >::New
791 InflowBoundaryModel<DsmcCloud<ParcelType> >::New
800 buildCellOccupancy();
802 // Initialise the collision selection remainder to a random value between 0
804 forAll(collisionSelectionRemainder_, i)
806 collisionSelectionRemainder_[i] = rndGen_.scalar01();
811 ParcelType::readFields(*this);
816 template<class ParcelType>
817 Foam::DsmcCloud<ParcelType>::DsmcCloud
819 const word& cloudName,
821 const IOdictionary& dsmcInitialiseDict
824 Cloud<ParcelType>(mesh, cloudName, false),
826 cloudName_(cloudName),
832 cloudName + "Properties",
833 mesh_.time().constant(),
839 typeIdList_(particleProperties_.lookup("typeIdList")),
840 nParticle_(readScalar(particleProperties_.lookup("nEquivalentParticles"))),
846 this->name() + "SigmaTcRMax",
847 mesh_.time().timeName(),
853 dimensionedScalar("zero", dimensionSet(0, 3, -1, 0, 0), 0.0)
855 collisionSelectionRemainder_(),
861 mesh_.time().timeName(),
867 dimensionedScalar("zero", dimensionSet(1, 0, -3, 0, 0), 0.0)
873 this->name() + "fD_",
874 mesh_.time().timeName(),
883 dimensionSet(1, -1, -2, 0, 0),
891 this->name() + "rhoN_",
892 mesh_.time().timeName(),
898 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL)
904 this->name() + "rhoM_",
905 mesh_.time().timeName(),
911 dimensionedScalar("zero", dimensionSet(1, -3, 0, 0, 0), VSMALL)
917 this->name() + "dsmcRhoN_",
918 mesh_.time().timeName(),
924 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), 0.0)
930 this->name() + "linearKE_",
931 mesh_.time().timeName(),
937 dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0)
943 this->name() + "internalE_",
944 mesh_.time().timeName(),
950 dimensionedScalar("zero", dimensionSet(1, -1, -2, 0, 0), 0.0)
956 this->name() + "iDof_",
957 mesh_.time().timeName(),
963 dimensionedScalar("zero", dimensionSet(0, -3, 0, 0, 0), VSMALL)
969 this->name() + "momentum_",
970 mesh_.time().timeName(),
979 dimensionSet(1, -2, -1, 0, 0),
984 rndGen_(label(971501) + 1526*Pstream::myProcNo()),
992 mesh_.time().timeName(),
998 dimensionedScalar("zero", dimensionSet(0, 0, 0, 1, 0), 0.0)
1008 mesh_.time().timeName(),
1017 dimensionSet(0, 1, -1, 0, 0),
1022 binaryCollisionModel_(),
1023 wallInteractionModel_(),
1024 inflowBoundaryModel_()
1030 initialise(dsmcInitialiseDict);
1034 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
1036 template<class ParcelType>
1037 Foam::DsmcCloud<ParcelType>::~DsmcCloud()
1041 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
1043 template<class ParcelType>
1044 void Foam::DsmcCloud<ParcelType>::evolve()
1046 typename ParcelType::trackData td(*this);
1048 // Reset the data collection fields
1053 this->dumpParticlePositions();
1056 // Insert new particles from the inflow boundary
1057 this->inflowBoundary().inflow();
1059 // Move the particles ballistically with their current velocities
1060 Cloud<ParcelType>::move(td);
1062 // Calculate new velocities via stochastic collisions
1065 // Calculate the volume field data
1070 template<class ParcelType>
1071 void Foam::DsmcCloud<ParcelType>::info() const
1073 label nDsmcParticles = this->size();
1074 reduce(nDsmcParticles, sumOp<label>());
1076 scalar nMol = nDsmcParticles*nParticle_;
1078 vector linearMomentum = linearMomentumOfSystem();
1079 reduce(linearMomentum, sumOp<vector>());
1081 scalar linearKineticEnergy = linearKineticEnergyOfSystem();
1082 reduce(linearKineticEnergy, sumOp<scalar>());
1084 scalar internalEnergy = internalEnergyOfSystem();
1085 reduce(internalEnergy, sumOp<scalar>());
1087 Info<< "Cloud name: " << this->name() << nl
1088 << " Number of dsmc particles = "
1094 Info<< " Number of molecules = "
1096 << " Mass in system = "
1097 << returnReduce(massInSystem(), sumOp<scalar>()) << nl
1098 << " Average linear momentum = "
1099 << linearMomentum/nMol << nl
1100 << " |Average linear momentum| = "
1101 << mag(linearMomentum)/nMol << nl
1102 << " Average linear kinetic energy = "
1103 << linearKineticEnergy/nMol << nl
1104 << " Average internal energy = "
1105 << internalEnergy/nMol << nl
1106 << " Average total energy = "
1107 << (internalEnergy + linearKineticEnergy)/nMol
1113 template<class ParcelType>
1114 Foam::vector Foam::DsmcCloud<ParcelType>::equipartitionLinearVelocity
1121 sqrt(kb*temperature/mass)
1124 rndGen_.GaussNormal(),
1125 rndGen_.GaussNormal(),
1126 rndGen_.GaussNormal()
1131 template<class ParcelType>
1132 Foam::scalar Foam::DsmcCloud<ParcelType>::equipartitionInternalEnergy
1144 else if (iDof < 2.0 + SMALL && iDof > 2.0 - SMALL)
1146 // Special case for iDof = 2, i.e. diatomics;
1147 Ei = -log(rndGen_.scalar01())*kb*temperature;
1151 scalar a = 0.5*iDof - 1;
1159 energyRatio = 10*rndGen_.scalar01();
1161 P = pow((energyRatio/a), a)*exp(a - energyRatio);
1163 } while (P < rndGen_.scalar01());
1165 Ei = energyRatio*kb*temperature;
1172 template<class ParcelType>
1173 void Foam::DsmcCloud<ParcelType>::dumpParticlePositions() const
1177 this->db().time().path()/"parcelPositions_"
1178 + this->name() + "_"
1179 + this->db().time().timeName() + ".obj"
1182 forAllConstIter(typename DsmcCloud<ParcelType>, *this, iter)
1184 const ParcelType& p = iter();
1186 pObj<< "v " << p.position().x()
1187 << " " << p.position().y()
1188 << " " << p.position().z()
1196 // ************************************************************************* //