ParticleCollector.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | Copyright (C) 2012-2014 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
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.
15 
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
19  for more details.
20 
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/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "ParticleCollector.H"
27 #include "Pstream.H"
28 #include "surfaceWriter.H"
29 #include "unitConversion.H"
30 #include "Random.H"
31 #include "triangle.H"
32 #include "cloud.H"
33 
34 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
35 
36 template<class CloudType>
38 (
39  const faceList& faces,
40  const Field<point>& points,
41  const Field<scalar>& area
42 )
43 {
44  // Create the output file if not already created
45  if (log_)
46  {
47  if (debug)
48  {
49  Info<< "Creating output file" << endl;
50  }
51 
52  if (Pstream::master())
53  {
54  // Create directory if does not exist
55  mkDir(this->outputTimeDir());
56 
57  // Open new file at start up
58  outputFilePtr_.reset
59  (
60  new OFstream(this->outputTimeDir()/(type() + ".dat"))
61  );
62 
63  outputFilePtr_()
64  << "# Source : " << type() << nl
65  << "# Bins : " << faces.size() << nl
66  << "# Total area : " << sum(area) << nl;
67 
68  outputFilePtr_()
69  << "# Geometry :" << nl
70  << '#'
71  << tab << "Bin"
72  << tab << "(Centre_x Centre_y Centre_z)"
73  << tab << "Area"
74  << nl;
75 
76  forAll(faces, i)
77  {
78  outputFilePtr_()
79  << '#'
80  << tab << i
81  << tab << faces[i].centre(points)
82  << tab << area[i]
83  << nl;
84  }
85 
86  outputFilePtr_()
87  << '#' << nl
88  << "# Output format:" << nl;
89 
90  forAll(faces, i)
91  {
92  word id = Foam::name(i);
93  word binId = "bin_" + id;
94 
95  outputFilePtr_()
96  << '#'
97  << tab << "Time"
98  << tab << binId
99  << tab << "mass[" << id << "]"
100  << tab << "massFlowRate[" << id << "]"
101  << endl;
102  }
103  }
104  }
105 }
106 
107 
108 template<class CloudType>
110 (
111  const List<Field<point> >& polygons
112 )
113 {
114  mode_ = mtPolygon;
115 
116  label nPoints = 0;
117  forAll(polygons, polyI)
118  {
119  label np = polygons[polyI].size();
120  if (np < 3)
121  {
123  (
124  "Foam::ParticleCollector<CloudType>::initPolygons()",
125  this->coeffDict()
126  )
127  << "polygons must consist of at least 3 points"
128  << exit(FatalIOError);
129  }
130 
131  nPoints += np;
132  }
133 
134  label pointOffset = 0;
135  points_.setSize(nPoints);
136  faces_.setSize(polygons.size());
137  faceTris_.setSize(polygons.size());
138  area_.setSize(polygons.size());
139  forAll(faces_, faceI)
140  {
141  const Field<point>& polyPoints = polygons[faceI];
142  face f(identity(polyPoints.size()) + pointOffset);
143  UIndirectList<point>(points_, f) = polyPoints;
144  area_[faceI] = f.mag(points_);
145 
146  DynamicList<face> tris;
147  f.triangles(points_, tris);
148  faceTris_[faceI].transfer(tris);
149 
150  faces_[faceI].transfer(f);
151 
152  pointOffset += polyPoints.size();
153  }
154 }
155 
156 
157 template<class CloudType>
159 {
160  mode_ = mtConcentricCircle;
161 
162  vector origin(this->coeffDict().lookup("origin"));
163 
164  radius_ = this->coeffDict().lookup("radius");
165  nSector_ = readLabel(this->coeffDict().lookup("nSector"));
166 
167  label nS = nSector_;
168 
169  vector refDir;
170  if (nSector_ > 1)
171  {
172  refDir = this->coeffDict().lookup("refDir");
173  refDir -= normal_[0]*(normal_[0] & refDir);
174  refDir /= mag(refDir);
175  }
176  else
177  {
178  // set 4 quadrants for single sector cases
179  nS = 4;
180 
181  vector tangent = vector::zero;
182  scalar magTangent = 0.0;
183 
184  Random rnd(1234);
185  while (magTangent < SMALL)
186  {
187  vector v = rnd.vector01();
188 
189  tangent = v - (v & normal_[0])*normal_[0];
190  magTangent = mag(tangent);
191  }
192 
193  refDir = tangent/magTangent;
194  }
195 
196  scalar dTheta = 5.0;
197  scalar dThetaSector = 360.0/scalar(nS);
198  label intervalPerSector = max(1, ceil(dThetaSector/dTheta));
199  dTheta = dThetaSector/scalar(intervalPerSector);
200 
201  label nPointPerSector = intervalPerSector + 1;
202 
203  label nPointPerRadius = nS*(nPointPerSector - 1);
204  label nPoint = radius_.size()*nPointPerRadius;
205  label nFace = radius_.size()*nS;
206 
207  // add origin
208  nPoint++;
209 
210  points_.setSize(nPoint);
211  faces_.setSize(nFace);
212  area_.setSize(nFace);
213 
214  coordSys_ = cylindricalCS("coordSys", origin, normal_[0], refDir, false);
215 
216  List<label> ptIDs(identity(nPointPerRadius));
217 
218  points_[0] = origin;
219 
220  // points
221  forAll(radius_, radI)
222  {
223  label pointOffset = radI*nPointPerRadius + 1;
224 
225  for (label i = 0; i < nPointPerRadius; i++)
226  {
227  label pI = i + pointOffset;
228  point pCyl(radius_[radI], degToRad(i*dTheta), 0.0);
229  points_[pI] = coordSys_.globalPosition(pCyl);
230  }
231  }
232 
233  // faces
234  DynamicList<label> facePts(2*nPointPerSector);
235  forAll(radius_, radI)
236  {
237  if (radI == 0)
238  {
239  for (label secI = 0; secI < nS; secI++)
240  {
241  facePts.clear();
242 
243  // append origin point
244  facePts.append(0);
245 
246  for (label ptI = 0; ptI < nPointPerSector; ptI++)
247  {
248  label i = ptI + secI*(nPointPerSector - 1);
249  label id = ptIDs.fcIndex(i - 1) + 1;
250  facePts.append(id);
251  }
252 
253  label faceI = secI + radI*nS;
254 
255  faces_[faceI] = face(facePts);
256  area_[faceI] = faces_[faceI].mag(points_);
257  }
258  }
259  else
260  {
261  for (label secI = 0; secI < nS; secI++)
262  {
263  facePts.clear();
264 
265  label offset = (radI - 1)*nPointPerRadius + 1;
266 
267  for (label ptI = 0; ptI < nPointPerSector; ptI++)
268  {
269  label i = ptI + secI*(nPointPerSector - 1);
270  label id = offset + ptIDs.fcIndex(i - 1);
271  facePts.append(id);
272  }
273  for (label ptI = nPointPerSector-1; ptI >= 0; ptI--)
274  {
275  label i = ptI + secI*(nPointPerSector - 1);
276  label id = offset + nPointPerRadius + ptIDs.fcIndex(i - 1);
277  facePts.append(id);
278  }
279 
280  label faceI = secI + radI*nS;
281 
282  faces_[faceI] = face(facePts);
283  area_[faceI] = faces_[faceI].mag(points_);
284  }
285  }
286  }
287 }
288 
289 
290 template<class CloudType>
292 (
293  const point& p1,
294  const point& p2
295 ) const
296 {
297  label dummyNearType = -1;
298  label dummyNearLabel = -1;
299 
300  forAll(faces_, faceI)
301  {
302  const label facePoint0 = faces_[faceI][0];
303 
304  const point& pf = points_[facePoint0];
305 
306  const scalar d1 = normal_[faceI] & (p1 - pf);
307  const scalar d2 = normal_[faceI] & (p2 - pf);
308 
309  if (sign(d1) == sign(d2))
310  {
311  // did not cross polygon plane
312  continue;
313  }
314 
315  // intersection point
316  const point pIntersect = p1 + (d1/(d1 - d2))*(p2 - p1);
317 
318  const List<face>& tris = faceTris_[faceI];
319 
320  // identify if point is within poly bounds
321  forAll(tris, triI)
322  {
323  const face& tri = tris[triI];
324  triPointRef t
325  (
326  points_[tri[0]],
327  points_[tri[1]],
328  points_[tri[2]]
329  );
330 
331  if (t.classify(pIntersect, dummyNearType, dummyNearLabel))
332  {
333  hitFaceIDs_.append(faceI);
334  }
335  }
336  }
337 }
338 
339 
340 template<class CloudType>
342 (
343  const point& p1,
344  const point& p2
345 ) const
346 {
347  label secI = -1;
348 
349  const scalar d1 = normal_[0] & (p1 - coordSys_.origin());
350  const scalar d2 = normal_[0] & (p2 - coordSys_.origin());
351 
352  if (sign(d1) == sign(d2))
353  {
354  // did not cross plane
355  return;
356  }
357 
358  // intersection point in cylindrical co-ordinate system
359  const point pCyl = coordSys_.localPosition(p1 + (d1/(d1 - d2))*(p2 - p1));
360 
361  scalar r = pCyl[0];
362 
363  if (r < radius_.last())
364  {
365  label radI = 0;
366  while (r > radius_[radI])
367  {
368  radI++;
369  }
370 
371  if (nSector_ == 1)
372  {
373  secI = 4*radI;
374  }
375  else
376  {
377  scalar theta = pCyl[1] + constant::mathematical::pi;
378 
379  secI =
380  nSector_*radI
381  + floor
382  (
383  scalar(nSector_)*theta/constant::mathematical::twoPi
384  );
385  }
386  }
387 
388  hitFaceIDs_.append(secI);
389 }
390 
391 
392 template<class CloudType>
394 {
395  const fvMesh& mesh = this->owner().mesh();
396  const Time& time = mesh.time();
397  scalar timeNew = time.value();
398  scalar timeElapsed = timeNew - timeOld_;
399 
400  totalTime_ += timeElapsed;
401 
402  const scalar alpha = (totalTime_ - timeElapsed)/totalTime_;
403  const scalar beta = timeElapsed/totalTime_;
404 
405  forAll(faces_, faceI)
406  {
407  massFlowRate_[faceI] =
408  alpha*massFlowRate_[faceI] + beta*mass_[faceI]/timeElapsed;
409  massTotal_[faceI] += mass_[faceI];
410  }
411 
412  const label procI = Pstream::myProcNo();
413 
414  Info<< type() << " output:" << nl;
415 
416  Field<scalar> faceMassTotal(mass_.size(), 0.0);
417  this->getModelProperty("massTotal", faceMassTotal);
418 
419  Field<scalar> faceMassFlowRate(massFlowRate_.size(), 0.0);
420  this->getModelProperty("massFlowRate", faceMassFlowRate);
421 
422 
423  scalar sumTotalMass = 0.0;
424  scalar sumAverageMFR = 0.0;
425  forAll(faces_, faceI)
426  {
427  scalarList allProcMass(Pstream::nProcs());
428  allProcMass[procI] = massTotal_[faceI];
429  Pstream::gatherList(allProcMass);
430  faceMassTotal[faceI] += sum(allProcMass);
431 
432  scalarList allProcMassFlowRate(Pstream::nProcs());
433  allProcMassFlowRate[procI] = massFlowRate_[faceI];
434  Pstream::gatherList(allProcMassFlowRate);
435  faceMassFlowRate[faceI] += sum(allProcMassFlowRate);
436 
437  sumTotalMass += faceMassTotal[faceI];
438  sumAverageMFR += faceMassFlowRate[faceI];
439 
440  if (outputFilePtr_.valid())
441  {
442  outputFilePtr_()
443  << time.timeName()
444  << tab << faceI
445  << tab << faceMassTotal[faceI]
446  << tab << faceMassFlowRate[faceI]
447  << endl;
448  }
449  }
450 
451  Info<< " sum(total mass) = " << sumTotalMass << nl
452  << " sum(average mass flow rate) = " << sumAverageMFR << nl
453  << endl;
454 
455 
456  if (surfaceFormat_ != "none")
457  {
458  if (Pstream::master())
459  {
461 
462  writer->write
463  (
464  this->outputTimeDir(),
465  "collector",
466  points_,
467  faces_,
468  "massTotal",
469  faceMassTotal,
470  false
471  );
472 
473  writer->write
474  (
475  this->outputTimeDir(),
476  "collector",
477  points_,
478  faces_,
479  "massFlowRate",
480  faceMassFlowRate,
481  false
482  );
483  }
484  }
485 
486 
487  if (resetOnWrite_)
488  {
489  Field<scalar> dummy(faceMassTotal.size(), 0.0);
490  this->setModelProperty("massTotal", dummy);
491  this->setModelProperty("massFlowRate", dummy);
492 
493  timeOld_ = timeNew;
494  totalTime_ = 0.0;
495  }
496  else
497  {
498  this->setModelProperty("massTotal", faceMassTotal);
499  this->setModelProperty("massFlowRate", faceMassFlowRate);
500  }
501 
502  forAll(faces_, faceI)
503  {
504  mass_[faceI] = 0.0;
505  massTotal_[faceI] = 0.0;
506  massFlowRate_[faceI] = 0.0;
507  }
508 }
509 
510 
511 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
512 
513 template<class CloudType>
515 (
516  const dictionary& dict,
517  CloudType& owner,
518  const word& modelName
519 )
520 :
521  CloudFunctionObject<CloudType>(dict, owner, modelName, typeName),
522  mode_(mtUnknown),
523  parcelType_(this->coeffDict().lookupOrDefault("parcelType", -1)),
524  removeCollected_(this->coeffDict().lookup("removeCollected")),
525  points_(),
526  faces_(),
527  faceTris_(),
528  nSector_(0),
529  radius_(),
530  coordSys_(false),
531  normal_(),
532  negateParcelsOppositeNormal_
533  (
534  readBool(this->coeffDict().lookup("negateParcelsOppositeNormal"))
535  ),
536  surfaceFormat_(this->coeffDict().lookup("surfaceFormat")),
537  resetOnWrite_(this->coeffDict().lookup("resetOnWrite")),
538  totalTime_(0.0),
539  mass_(),
540  massTotal_(),
541  massFlowRate_(),
542  log_(this->coeffDict().lookup("log")),
543  outputFilePtr_(),
544  timeOld_(owner.mesh().time().value()),
545  hitFaceIDs_()
546 {
547  normal_ /= mag(normal_);
548 
549  word mode(this->coeffDict().lookup("mode"));
550  if (mode == "polygon")
551  {
552  List<Field<point> > polygons(this->coeffDict().lookup("polygons"));
553 
554  initPolygons(polygons);
555 
556  vector n0(this->coeffDict().lookup("normal"));
557  normal_ = vectorField(faces_.size(), n0);
558  }
559  else if (mode == "polygonWithNormal")
560  {
561  List<Tuple2<Field<point>, vector> > polygonAndNormal
562  (
563  this->coeffDict().lookup("polygons")
564  );
565 
566  List<Field<point> > polygons(polygonAndNormal.size());
567  normal_.setSize(polygonAndNormal.size());
568 
569  forAll(polygons, polyI)
570  {
571  polygons[polyI] = polygonAndNormal[polyI].first();
572  normal_[polyI] = polygonAndNormal[polyI].second();
573  normal_[polyI] /= mag(normal_[polyI]) + ROOTVSMALL;
574  }
575 
576  initPolygons(polygons);
577  }
578  else if (mode == "concentricCircle")
579  {
580  vector n0(this->coeffDict().lookup("normal"));
581  normal_ = vectorField(1, n0);
582 
583  initConcentricCircles();
584  }
585  else
586  {
588  (
589  "Foam::ParticleCollector<CloudType>::ParticleCollector"
590  "("
591  "const dictionary&,"
592  "CloudType&, "
593  "const word&"
594  ")",
595  this->coeffDict()
596  )
597  << "Unknown mode " << mode << ". Available options are "
598  << "polygon, polygonWithNormal and concentricCircle"
599  << exit(FatalIOError);
600  }
601 
602  mass_.setSize(faces_.size(), 0.0);
603  massTotal_.setSize(faces_.size(), 0.0);
604  massFlowRate_.setSize(faces_.size(), 0.0);
605 
606  makeLogFile(faces_, points_, area_);
607 }
608 
609 
610 template<class CloudType>
612 (
614 )
615 :
617  mode_(pc.mode_),
618  parcelType_(pc.parcelType_),
619  removeCollected_(pc.removeCollected_),
620  points_(pc.points_),
621  faces_(pc.faces_),
622  faceTris_(pc.faceTris_),
623  nSector_(pc.nSector_),
624  radius_(pc.radius_),
625  coordSys_(pc.coordSys_),
626  normal_(pc.normal_),
627  negateParcelsOppositeNormal_(pc.negateParcelsOppositeNormal_),
628  surfaceFormat_(pc.surfaceFormat_),
629  resetOnWrite_(pc.resetOnWrite_),
630  totalTime_(pc.totalTime_),
631  mass_(pc.mass_),
632  massTotal_(pc.massTotal_),
633  massFlowRate_(pc.massFlowRate_),
634  log_(pc.log_),
635  outputFilePtr_(),
636  timeOld_(0.0),
637  hitFaceIDs_()
638 {}
639 
640 
641 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
642 
643 template<class CloudType>
645 {}
646 
647 
648 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
649 
650 template<class CloudType>
652 (
653  parcelType& p,
654  const label cellI,
655  const scalar dt,
656  const point& position0,
657  bool& keepParticle
658 )
659 {
660  if ((parcelType_ != -1) && (parcelType_ != p.typeId()))
661  {
662  return;
663  }
664 
665  // slightly extend end position to avoid falling within tracking tolerances
666  const point position1 = position0 + 1.0001*(p.position() - position0);
667 
668  hitFaceIDs_.clear();
669 
670  switch (mode_)
671  {
672  case mtPolygon:
673  {
674  collectParcelPolygon(position0, position1);
675  break;
676  }
677  case mtConcentricCircle:
678  {
679  collectParcelConcentricCircles(position0, position1);
680  break;
681  }
682  default:
683  {
684  }
685  }
686 
687 
688  forAll(hitFaceIDs_, i)
689  {
690  label faceI = hitFaceIDs_[i];
691  scalar m = p.nParticle()*p.mass();
692 
693  if (negateParcelsOppositeNormal_)
694  {
695  vector Uhat = p.U();
696  Uhat /= mag(Uhat) + ROOTVSMALL;
697  if ((Uhat & normal_[faceI]) < 0)
698  {
699  m *= -1.0;
700  }
701  }
702 
703  // add mass contribution
704  mass_[faceI] += m;
705 
706  if (nSector_ == 1)
707  {
708  mass_[faceI + 1] += m;
709  mass_[faceI + 2] += m;
710  mass_[faceI + 3] += m;
711  }
712 
713  if (removeCollected_)
714  {
715  keepParticle = false;
716  }
717  }
718 }
719 
720 
721 // ************************************************************************* //
bool readBool(Istream &)
Definition: boolIO.C:60
Mesh data needed to do the Finite Volume discretisation.
Definition: fvMesh.H:78
vector point
Point is a vector.
Definition: point.H:41
mode_t mode(const fileName &)
Return the file mode.
Definition: POSIX.C:574
dimensioned< scalar > mag(const dimensioned< Type > &)
word name(const complex &)
Return a string representation of a complex.
Definition: complex.C:47
labelList f(nPoints)
virtual void postMove(parcelType &p, const label cellI, const scalar dt, const point &position0, bool &keepParticle)
Post-move hook.
dimensionedScalar beta("beta", dimless/dimTemperature, laminarTransport)
A class for handling words, derived from string.
Definition: word.H:59
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
static word timeName(const scalar, const int precision=precision_)
Return time name of given scalar time.
Definition: Time.C:741
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:76
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
dimensionedScalar sign(const dimensionedScalar &ds)
ParticleCollector(const dictionary &dict, CloudType &owner, const word &modelName)
Construct from dictionary.
messageStream Info
List< face > faceList
Definition: faceListFwd.H:43
dynamicFvMesh & mesh
A list of keyword definitions, which are a keyword followed by any number of values (e...
Definition: dictionary.H:137
Function object to collect the parcel mass- and mass flow rate over a set of polygons. The polygons can either be specified by sets of user- supplied points, or in a concentric circles arrangement. If a parcel is &#39;collected&#39;, it can be flagged to be removed from the domain using the removeCollected entry.
T & first()
Return the first element of the list.
Definition: UListI.H:117
autoPtr< BasicCompressibleTurbulenceModel > New(const volScalarField &rho, const volVectorField &U, const surfaceScalarField &phi, const typename BasicCompressibleTurbulenceModel::transportModel &transport, const word &propertiesName)
label readLabel(Istream &is)
Definition: label.H:64
triangle< point, const point & > triPointRef
Definition: triPointRef.H:44
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:243
dictionary dict
Class to control time during OpenFOAM simulations that is also the top-level objectRegistry.
Definition: Time.H:68
static const char nl
Definition: Ostream.H:260
void setSize(const label)
Reset size of List.
Definition: List.C:318
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:251
stressControl lookup("compactNormalStress") >> compactNormalStress
IOerror FatalIOError
volVectorField vectorField(fieldObject, mesh)
const dimensionedScalar alpha
Fine-structure constant: default SI units: [].
dimensioned< Type > max(const dimensioned< Type > &, const dimensioned< Type > &)
#define forAll(list, i)
Definition: UList.H:421
Unit conversion functions.
Base class for graphics format writing. Entry points are.
Definition: writer.H:78
labelList identity(const label len)
Create identity map (map[i] == i) of given length.
Definition: ListOps.C:104
dimensioned< Type > sum(const DimensionedField< Type, GeoMesh > &df)
fileName::Type type(const fileName &)
Return the file type: DIRECTORY or FILE.
Definition: POSIX.C:589
void write()
Write post-processing info.
static const char tab
Definition: Ostream.H:259
scalar degToRad(const scalar deg)
Conversion from degrees to radians.
Vector< scalar > vector
A scalar version of the templated Vector.
Definition: vector.H:49
Templated base class for dsmc cloud.
Definition: DSMCCloud.H:68
virtual ~ParticleCollector()
Destructor.
bool mkDir(const fileName &, mode_t=0777)
Make a directory and return an error if it could not be created.
Definition: POSIX.C:420
Templated cloud function object base class.
#define FatalIOErrorIn(functionName, ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:325
const scalar twoPi(2 *pi)
An auto-pointer similar to the STL auto_ptr but with automatic casting to a reference to the type and...
Definition: PtrList.H:117
const Type & value() const
Return const reference to value.