particle.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 | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2011-2017, 2020 OpenFOAM Foundation
9  Copyright (C) 2018-2021 OpenCFD Ltd.
10 -------------------------------------------------------------------------------
11 License
12  This file is part of OpenFOAM.
13 
14  OpenFOAM is free software: you can redistribute it and/or modify it
15  under the terms of the GNU General Public License as published by
16  the Free Software Foundation, either version 3 of the License, or
17  (at your option) any later version.
18 
19  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
20  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
22  for more details.
23 
24  You should have received a copy of the GNU General Public License
25  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
26 
27 \*---------------------------------------------------------------------------*/
28 
29 #include "particle.H"
30 #include "transform.H"
31 #include "treeDataCell.H"
32 #include "cubicEqn.H"
33 #include "registerSwitch.H"
34 #include "indexedOctree.H"
35 
36 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
37 
38 namespace Foam
39 {
40  defineTypeNameAndDebug(particle, 0);
41 }
42 
43 const Foam::label Foam::particle::maxNBehind_ = 10;
44 
45 Foam::label Foam::particle::particleCount_ = 0;
46 
48 
50 (
51  Foam::debug::infoSwitch("writeLagrangianPositions", 1)
52 );
53 
55 (
56  "writeLagrangianPositions",
57  bool,
59 );
60 
61 
62 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
63 
64 void Foam::particle::stationaryTetReverseTransform
65 (
66  vector& centre,
67  scalar& detA,
69 ) const
70 {
71  barycentricTensor A = stationaryTetTransform();
72 
73  const vector ab = A.b() - A.a();
74  const vector ac = A.c() - A.a();
75  const vector ad = A.d() - A.a();
76  const vector bc = A.c() - A.b();
77  const vector bd = A.d() - A.b();
78 
79  centre = A.a();
80 
81  detA = ab & (ac ^ ad);
82 
84  (
85  bd ^ bc,
86  ac ^ ad,
87  ad ^ ab,
88  ab ^ ac
89  );
90 }
91 
92 
93 void Foam::particle::movingTetReverseTransform
94 (
95  const scalar fraction,
96  Pair<vector>& centre,
97  FixedList<scalar, 4>& detA,
98  FixedList<barycentricTensor, 3>& T
99 ) const
100 {
101  Pair<barycentricTensor> A = movingTetTransform(fraction);
102 
103  const Pair<vector> ab(A[0].b() - A[0].a(), A[1].b() - A[1].a());
104  const Pair<vector> ac(A[0].c() - A[0].a(), A[1].c() - A[1].a());
105  const Pair<vector> ad(A[0].d() - A[0].a(), A[1].d() - A[1].a());
106  const Pair<vector> bc(A[0].c() - A[0].b(), A[1].c() - A[1].b());
107  const Pair<vector> bd(A[0].d() - A[0].b(), A[1].d() - A[1].b());
108 
109  centre[0] = A[0].a();
110  centre[1] = A[1].a();
111 
112  detA[0] = ab[0] & (ac[0] ^ ad[0]);
113  detA[1] =
114  (ab[1] & (ac[0] ^ ad[0]))
115  + (ab[0] & (ac[1] ^ ad[0]))
116  + (ab[0] & (ac[0] ^ ad[1]));
117  detA[2] =
118  (ab[0] & (ac[1] ^ ad[1]))
119  + (ab[1] & (ac[0] ^ ad[1]))
120  + (ab[1] & (ac[1] ^ ad[0]));
121  detA[3] = ab[1] & (ac[1] ^ ad[1]);
122 
123  T[0] = barycentricTensor
124  (
125  bd[0] ^ bc[0],
126  ac[0] ^ ad[0],
127  ad[0] ^ ab[0],
128  ab[0] ^ ac[0]
129  );
130  T[1] = barycentricTensor
131  (
132  (bd[0] ^ bc[1]) + (bd[1] ^ bc[0]),
133  (ac[0] ^ ad[1]) + (ac[1] ^ ad[0]),
134  (ad[0] ^ ab[1]) + (ad[1] ^ ab[0]),
135  (ab[0] ^ ac[1]) + (ab[1] ^ ac[0])
136  );
137  T[2] = barycentricTensor
138  (
139  bd[1] ^ bc[1],
140  ac[1] ^ ad[1],
141  ad[1] ^ ab[1],
142  ab[1] ^ ac[1]
143  );
144 }
145 
146 
147 void Foam::particle::reflect()
148 {
149  std::swap(coordinates_.c(), coordinates_.d());
150 }
151 
152 
153 void Foam::particle::rotate(const bool reverse)
154 {
155  if (!reverse)
156  {
157  scalar temp = coordinates_.b();
158  coordinates_.b() = coordinates_.c();
159  coordinates_.c() = coordinates_.d();
160  coordinates_.d() = temp;
161  }
162  else
163  {
164  scalar temp = coordinates_.d();
165  coordinates_.d() = coordinates_.c();
166  coordinates_.c() = coordinates_.b();
167  coordinates_.b() = temp;
168  }
169 }
170 
171 
172 void Foam::particle::changeTet(const label tetTriI)
173 {
174  const bool isOwner = mesh_.faceOwner()[tetFacei_] == celli_;
175 
176  const label firstTetPtI = 1;
177  const label lastTetPtI = mesh_.faces()[tetFacei_].size() - 2;
178 
179  if (tetTriI == 1)
180  {
181  changeFace(tetTriI);
182  }
183  else if (tetTriI == 2)
184  {
185  if (isOwner)
186  {
187  if (tetPti_ == lastTetPtI)
188  {
189  changeFace(tetTriI);
190  }
191  else
192  {
193  reflect();
194  tetPti_ += 1;
195  }
196  }
197  else
198  {
199  if (tetPti_ == firstTetPtI)
200  {
201  changeFace(tetTriI);
202  }
203  else
204  {
205  reflect();
206  tetPti_ -= 1;
207  }
208  }
209  }
210  else if (tetTriI == 3)
211  {
212  if (isOwner)
213  {
214  if (tetPti_ == firstTetPtI)
215  {
216  changeFace(tetTriI);
217  }
218  else
219  {
220  reflect();
221  tetPti_ -= 1;
222  }
223  }
224  else
225  {
226  if (tetPti_ == lastTetPtI)
227  {
228  changeFace(tetTriI);
229  }
230  else
231  {
232  reflect();
233  tetPti_ += 1;
234  }
235  }
236  }
237  else
238  {
240  << "Changing tet without changing cell should only happen when the "
241  << "track is on triangle 1, 2 or 3."
242  << exit(FatalError);
243  }
244 }
245 
246 
247 void Foam::particle::changeFace(const label tetTriI)
248 {
249  // Get the old topology
250  const triFace triOldIs(currentTetIndices().faceTriIs(mesh_));
251 
252  // Get the shared edge and the pre-rotation
253  edge sharedEdge;
254  if (tetTriI == 1)
255  {
256  sharedEdge = edge(triOldIs[1], triOldIs[2]);
257  }
258  else if (tetTriI == 2)
259  {
260  sharedEdge = edge(triOldIs[2], triOldIs[0]);
261  }
262  else if (tetTriI == 3)
263  {
264  sharedEdge = edge(triOldIs[0], triOldIs[1]);
265  }
266  else
267  {
269  << "Changing face without changing cell should only happen when the"
270  << " track is on triangle 1, 2 or 3."
271  << exit(FatalError);
272 
273  sharedEdge = edge(-1, -1);
274  }
275 
276  // Find the face in the same cell that shares the edge, and the
277  // corresponding tetrahedra point
278  tetPti_ = -1;
279  forAll(mesh_.cells()[celli_], cellFaceI)
280  {
281  const label newFaceI = mesh_.cells()[celli_][cellFaceI];
282  const class face& newFace = mesh_.faces()[newFaceI];
283  const label newOwner = mesh_.faceOwner()[newFaceI];
284 
285  // Exclude the current face
286  if (tetFacei_ == newFaceI)
287  {
288  continue;
289  }
290 
291  // Loop over the edges, looking for the shared one. Note that we have to
292  // match the direction of the edge as well as the end points in order to
293  // avoid false positives when dealing with coincident ACMI faces.
294  const label edgeComp = newOwner == celli_ ? -1 : +1;
295  label edgeI = 0;
296  for
297  (
298  ;
299  edgeI < newFace.size()
300  && edge::compare(sharedEdge, newFace.edge(edgeI)) != edgeComp;
301  ++ edgeI
302  );
303 
304  // If the face does not contain the edge, then move on to the next face
305  if (edgeI >= newFace.size())
306  {
307  continue;
308  }
309 
310  // Make the edge index relative to the base point
311  const label newBaseI = max(0, mesh_.tetBasePtIs()[newFaceI]);
312  edgeI = (edgeI - newBaseI + newFace.size()) % newFace.size();
313 
314  // If the edge is next the base point (i.e., the index is 0 or n - 1),
315  // then we swap it for the adjacent edge. This new edge is opposite the
316  // base point, and defines the tet with the original edge in it.
317  edgeI = min(max(1, edgeI), newFace.size() - 2);
318 
319  // Set the new face and tet point
320  tetFacei_ = newFaceI;
321  tetPti_ = edgeI;
322 
323  // Exit the loop now that the tet point has been found
324  break;
325  }
326 
327  if (tetPti_ == -1)
328  {
330  << "The search for an edge-connected face and tet-point failed."
331  << exit(FatalError);
332  }
333 
334  // Pre-rotation puts the shared edge opposite the base of the tetrahedron
335  if (sharedEdge.otherVertex(triOldIs[1]) == -1)
336  {
337  rotate(false);
338  }
339  else if (sharedEdge.otherVertex(triOldIs[2]) == -1)
340  {
341  rotate(true);
342  }
343 
344  // Get the new topology
345  const triFace triNewIs = currentTetIndices().faceTriIs(mesh_);
346 
347  // Reflect to account for the change of triangle orientation on the new face
348  reflect();
349 
350  // Post rotation puts the shared edge back in the correct location
351  if (sharedEdge.otherVertex(triNewIs[1]) == -1)
352  {
353  rotate(true);
354  }
355  else if (sharedEdge.otherVertex(triNewIs[2]) == -1)
356  {
357  rotate(false);
358  }
359 }
360 
361 
362 void Foam::particle::changeCell()
363 {
364  // Set the cell to be the one on the other side of the face
365  const label ownerCellI = mesh_.faceOwner()[tetFacei_];
366  const bool isOwner = celli_ == ownerCellI;
367  celli_ = isOwner ? mesh_.faceNeighbour()[tetFacei_] : ownerCellI;
368  // Reflect to account for the change of triangle orientation in the new cell
369  reflect();
370 }
371 
372 
373 void Foam::particle::changeToMasterPatch()
374 {
375  label thisPatch = patch();
376 
377  forAll(mesh_.cells()[celli_], cellFaceI)
378  {
379  // Skip the current face and any internal faces
380  const label otherFaceI = mesh_.cells()[celli_][cellFaceI];
381  if (facei_ == otherFaceI || mesh_.isInternalFace(otherFaceI))
382  {
383  continue;
384  }
385 
386  // Compare the two faces. If they are the same, chose the one with the
387  // lower patch index. In the case of an ACMI-wall pair, this will be
388  // the ACMI, which is what we want.
389  const class face& thisFace = mesh_.faces()[facei_];
390  const class face& otherFace = mesh_.faces()[otherFaceI];
391  if (face::compare(thisFace, otherFace) != 0)
392  {
393  const label otherPatch =
394  mesh_.boundaryMesh().whichPatch(otherFaceI);
395  if (thisPatch > otherPatch)
396  {
397  facei_ = otherFaceI;
398  thisPatch = otherPatch;
399  }
400  }
401  }
402 
403  tetFacei_ = facei_;
404 }
405 
406 
407 void Foam::particle::locate
408 (
409  const vector& position,
410  const vector* direction,
411  const label celli,
412  const bool boundaryFail,
413  const string& boundaryMsg
414 )
415 {
416  if (debug)
417  {
418  Pout<< "Particle " << origId() << nl << FUNCTION_NAME << nl << endl;
419  }
420 
421  celli_ = celli;
422 
423  // Find the cell, if it has not been given
424  if (celli_ < 0)
425  {
426  celli_ = mesh_.cellTree().findInside(position);
427  if (celli_ < 0)
428  {
430  << "Cell not found for particle position " << position << "."
431  << exit(FatalError);
432  }
433  }
434 
435  // Perturbing displacement to avoid zero in case position is the
436  // cellCentre
437  const vector displacement =
438  position
439  - mesh_.cellCentres()[celli_]
440  + vector(VSMALL, VSMALL, VSMALL);
441 
442  // Loop all cell tets to find the one containing the position. Track
443  // through each tet from the cell centre. If a tet contains the position
444  // then the track will end with a single trackToTri.
445  const class cell& c = mesh_.cells()[celli_];
446  scalar minF = VGREAT;
447  label minTetFacei = -1, minTetPti = -1;
448  forAll(c, cellTetFacei)
449  {
450  const class face& f = mesh_.faces()[c[cellTetFacei]];
451  for (label tetPti = 1; tetPti < f.size() - 1; ++tetPti)
452  {
453  coordinates_ = barycentric(1, 0, 0, 0);
454  tetFacei_ = c[cellTetFacei];
455  tetPti_ = tetPti;
456  facei_ = -1;
457 
458  label tetTriI = -1;
459  const scalar f = trackToTri(displacement, 0, tetTriI);
460 
461  if (tetTriI == -1)
462  {
463  return;
464  }
465 
466  if (f < minF)
467  {
468  minF = f;
469  minTetFacei = tetFacei_;
470  minTetPti = tetPti_;
471  }
472  }
473  }
474 
475  // The particle must be (hopefully only slightly) outside the cell. Track
476  // into the tet which got the furthest.
477  coordinates_ = barycentric(1, 0, 0, 0);
478  tetFacei_ = minTetFacei;
479  tetPti_ = minTetPti;
480  facei_ = -1;
481  track(displacement, 0);
482  if (!onFace())
483  {
484  return;
485  }
486 
487  // If we are here then we hit a boundary
488  if (boundaryFail)
489  {
490  FatalErrorInFunction << boundaryMsg << exit(FatalError);
491  }
492  else
493  {
494  static label nWarnings = 0;
495  static const label maxNWarnings = 100;
496  if ((nWarnings < maxNWarnings) && boundaryFail)
497  {
498  WarningInFunction << boundaryMsg.c_str() << endl;
499  ++ nWarnings;
500  }
501  if (nWarnings == maxNWarnings)
502  {
504  << "Suppressing any further warnings about particles being "
505  << "located outside of the mesh." << endl;
506  ++ nWarnings;
507  }
508  }
509 
510 }
511 
512 
513 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
514 
516 (
517  const polyMesh& mesh,
518  const barycentric& coordinates,
519  const label celli,
520  const label tetFacei,
521  const label tetPti
522 )
523 :
524  mesh_(mesh),
525  coordinates_(coordinates),
526  celli_(celli),
527  tetFacei_(tetFacei),
528  tetPti_(tetPti),
529  facei_(-1),
530  stepFraction_(1.0),
531  behind_(0.0),
532  nBehind_(0),
533  origProc_(Pstream::myProcNo()),
534  origId_(getNewParticleID())
535 {}
536 
537 
539 (
540  const polyMesh& mesh,
541  const vector& position,
542  const label celli
543 )
544 :
545  mesh_(mesh),
546  coordinates_(-VGREAT, -VGREAT, -VGREAT, -VGREAT),
547  celli_(celli),
548  tetFacei_(-1),
549  tetPti_(-1),
550  facei_(-1),
551  stepFraction_(1.0),
552  behind_(0.0),
553  nBehind_(0),
554  origProc_(Pstream::myProcNo()),
555  origId_(getNewParticleID())
556 {
557  locate
558  (
559  position,
560  nullptr,
561  celli,
562  false,
563  "Particle initialised with a location outside of the mesh"
564  );
565 }
566 
567 
569 (
570  const polyMesh& mesh,
571  const vector& position,
572  const label celli,
573  const label tetFacei,
574  const label tetPti,
575  bool doLocate
576 )
577 :
578  mesh_(mesh),
579  coordinates_(-VGREAT, -VGREAT, -VGREAT, -VGREAT),
580  celli_(celli),
581  tetFacei_(tetFacei),
582  tetPti_(tetPti),
583  facei_(-1),
584  stepFraction_(1.0),
585  behind_(0.0),
586  nBehind_(0),
587  origProc_(Pstream::myProcNo()),
588  origId_(getNewParticleID())
589 {
590  if (doLocate)
591  {
592  locate
593  (
594  position,
595  nullptr,
596  celli,
597  false,
598  "Particle initialised with a location outside of the mesh"
599  );
600  }
601 }
602 
603 
605 :
606  mesh_(p.mesh_),
607  coordinates_(p.coordinates_),
608  celli_(p.celli_),
609  tetFacei_(p.tetFacei_),
610  tetPti_(p.tetPti_),
611  facei_(p.facei_),
612  stepFraction_(p.stepFraction_),
613  behind_(p.behind_),
614  nBehind_(p.nBehind_),
615  origProc_(p.origProc_),
616  origId_(p.origId_)
617 {}
618 
619 
621 :
622  mesh_(mesh),
623  coordinates_(p.coordinates_),
624  celli_(p.celli_),
625  tetFacei_(p.tetFacei_),
626  tetPti_(p.tetPti_),
627  facei_(p.facei_),
628  stepFraction_(p.stepFraction_),
629  behind_(p.behind_),
630  nBehind_(p.nBehind_),
631  origProc_(p.origProc_),
632  origId_(p.origId_)
633 {}
634 
635 
636 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
637 
638 Foam::scalar Foam::particle::track
639 (
640  const vector& displacement,
641  const scalar fraction
642 )
643 {
644  scalar f = trackToFace(displacement, fraction);
645 
646  while (onInternalFace())
647  {
648  changeCell();
649 
650  f *= trackToFace(f*displacement, f*fraction);
651  }
652 
653  return f;
654 }
655 
656 
657 Foam::scalar Foam::particle::trackToFace
658 (
659  const vector& displacement,
660  const scalar fraction
661 )
662 {
663  scalar f = 1;
664 
665  label tetTriI = onFace() ? 0 : -1;
666 
667  facei_ = -1;
668 
669  // Loop the tets in the current cell
670  while (nBehind_ < maxNBehind_)
671  {
672  f *= trackToTri(f*displacement, f*fraction, tetTriI);
673 
674  if (tetTriI == -1)
675  {
676  // The track has completed within the current tet
677  return 0;
678  }
679  else if (tetTriI == 0)
680  {
681  // The track has hit a face, so set the current face and return
682  facei_ = tetFacei_;
683  return f;
684  }
685  else
686  {
687  // Move to the next tet and continue the track
688  changeTet(tetTriI);
689  }
690  }
691 
692  // Warn if stuck, and incorrectly advance the step fraction to completion
693  static label stuckID = -1, stuckProc = -1;
694  if (origId_ != stuckID && origProc_ != stuckProc)
695  {
697  << "Particle #" << origId_ << " got stuck at " << position()
698  << endl;
699  }
700 
701  stuckID = origId_;
702  stuckProc = origProc_;
703 
704  stepFraction_ += f*fraction;
705 
706  behind_ = 0;
707  nBehind_ = 0;
708 
709  return 0;
710 }
711 
712 
714 (
715  const vector& displacement,
716  const scalar fraction,
717  label& tetTriI
718 )
719 {
720  const vector x0 = position();
721  const vector x1 = displacement;
722  const barycentric y0 = coordinates_;
723 
724  if (debug)
725  {
726  Pout<< "Particle " << origId() << endl << "Tracking from " << x0
727  << " along " << x1 << " to " << x0 + x1 << endl;
728  }
729 
730  // Get the tet geometry
731  vector centre;
732  scalar detA;
734  stationaryTetReverseTransform(centre, detA, T);
735 
736  if (debug)
737  {
738  vector o, b, v1, v2;
739  stationaryTetGeometry(o, b, v1, v2);
740  Pout<< "Tet points o=" << o << ", b=" << b
741  << ", v1=" << v1 << ", v2=" << v2 << endl
742  << "Tet determinant = " << detA << endl
743  << "Start local coordinates = " << y0 << endl;
744  }
745 
746  // Calculate the local tracking displacement
747  barycentric Tx1(x1 & T);
748 
749  if (debug)
750  {
751  Pout<< "Local displacement = " << Tx1 << "/" << detA << endl;
752  }
753 
754  // Calculate the hit fraction
755  label iH = -1;
756  scalar muH = detA <= 0 ? VGREAT : 1/detA;
757  for (label i = 0; i < 4; ++ i)
758  {
759  if (Tx1[i] < - detA*SMALL)
760  {
761  scalar mu = - y0[i]/Tx1[i];
762 
763  if (debug)
764  {
765  Pout<< "Hit on tet face " << i << " at local coordinate "
766  << y0 + mu*Tx1 << ", " << mu*detA*100 << "% of the "
767  << "way along the track" << endl;
768  }
769 
770  if (0 <= mu && mu < muH)
771  {
772  iH = i;
773  muH = mu;
774  }
775  }
776  }
777 
778  // Set the new coordinates
779  barycentric yH = y0 + muH*Tx1;
780 
781  // Clamp to zero any negative coordinates generated by round-off error
782  for (label i = 0; i < 4; ++ i)
783  {
784  yH.replace(i, i == iH ? 0 : max(0, yH[i]));
785  }
786 
787  // Re-normalise if within the tet
788  if (iH == -1)
789  {
790  yH /= cmptSum(yH);
791  }
792 
793  // Set the new position and hit index
794  coordinates_ = yH;
795  tetTriI = iH;
796 
797  if (debug)
798  {
799  if (iH != -1)
800  {
801  Pout<< "Track hit tet face " << iH << " first" << endl;
802  }
803  else
804  {
805  Pout<< "Track hit no tet faces" << endl;
806  }
807  Pout<< "End local coordinates = " << yH << endl
808  << "End global coordinates = " << position() << endl
809  << "Tracking displacement = " << position() - x0 << endl
810  << muH*detA*100 << "% of the step from " << stepFraction_ << " to "
811  << stepFraction_ + fraction << " completed" << endl << endl;
812  }
813 
814  // Set the proportion of the track that has been completed
815  stepFraction_ += fraction*muH*detA;
816 
817  if (debug)
818  {
819  Pout << "Step Fraction : " << stepFraction_ << endl;
820  Pout << "fraction*muH*detA : " << fraction*muH*detA << endl;
821  Pout << "static muH : " << muH << endl;
822  Pout << "origId() : " << origId() << endl;
823  }
824 
825  // Accumulate displacement behind
826  if (detA <= 0 || nBehind_ > 0)
827  {
828  behind_ += muH*detA*mag(displacement);
829 
830  if (behind_ > 0)
831  {
832  behind_ = 0;
833  nBehind_ = 0;
834  }
835  else
836  {
837  ++ nBehind_;
838  }
839  }
840 
841  return iH != -1 ? 1 - muH*detA : 0;
842 }
843 
844 
846 (
847  const vector& displacement,
848  const scalar fraction,
849  label& tetTriI
850 )
851 {
852  const vector x0 = position();
853  const vector x1 = displacement;
854  const barycentric y0 = coordinates_;
855 
856  if (debug)
857  {
858  Pout<< "Particle " << origId() << endl << "Tracking from " << x0
859  << " along " << x1 << " to " << x0 + x1 << endl;
860  }
861 
862  // Get the tet geometry
863  Pair<vector> centre;
866  movingTetReverseTransform(fraction, centre, detA, T);
867 
868  if (debug)
869  {
870  Pair<vector> o, b, v1, v2;
871  movingTetGeometry(fraction, o, b, v1, v2);
872  Pout<< "Tet points o=" << o[0] << ", b=" << b[0]
873  << ", v1=" << v1[0] << ", v2=" << v2[0] << endl
874  << "Tet determinant = " << detA[0] << endl
875  << "Start local coordinates = " << y0[0] << endl;
876  }
877 
878 
879  // Get the relative global position
880  const vector x0Rel = x0 - centre[0];
881  const vector x1Rel = x1 - centre[1];
882 
883  // Form the determinant and hit equations
884  cubicEqn detAEqn(sqr(detA[0])*detA[3], detA[0]*detA[2], detA[1], 1);
885  const barycentric yC(1, 0, 0, 0);
886  const barycentric hitEqnA =
887  ((x1Rel & T[2]) + detA[3]*yC)*sqr(detA[0]);
888  const barycentric hitEqnB =
889  ((x1Rel & T[1]) + (x0Rel & T[2]) + detA[2]*yC)*detA[0];
890  const barycentric hitEqnC =
891  ((x1Rel & T[0]) + (x0Rel & T[1]) + detA[1]*yC);
892  const barycentric hitEqnD = y0;
893  FixedList<cubicEqn, 4> hitEqn;
894  forAll(hitEqn, i)
895  {
896  hitEqn[i] = cubicEqn(hitEqnA[i], hitEqnB[i], hitEqnC[i], hitEqnD[i]);
897  }
898 
899  if (debug)
900  {
901  for (label i = 0; i < 4; ++ i)
902  {
903  Pout<< (i ? " " : "Hit equation ") << i << " = "
904  << hitEqn[i] << endl;
905  }
906  Pout<< " DetA equation = " << detA << endl;
907  }
908 
909  // Calculate the hit fraction
910  label iH = -1;
911  scalar muH = detA[0] <= 0 ? VGREAT : 1/detA[0];
912  for (label i = 0; i < 4; ++i)
913  {
914  const Roots<3> mu = hitEqn[i].roots();
915 
916  for (label j = 0; j < 3; ++j)
917  {
918  if
919  (
920  mu.type(j) == roots::real
921  && hitEqn[i].derivative(mu[j]) < - detA[0]*SMALL
922  )
923  {
924  if (debug)
925  {
926  const barycentric yH
927  (
928  hitEqn[0].value(mu[j]),
929  hitEqn[1].value(mu[j]),
930  hitEqn[2].value(mu[j]),
931  hitEqn[3].value(mu[j])
932  );
933  const scalar detAH = detAEqn.value(mu[j]);
934 
935  Pout<< "Hit on tet face " << i << " at local coordinate "
936  << (std::isnormal(detAH) ? name(yH/detAH) : "???")
937  << ", " << mu[j]*detA[0]*100 << "% of the "
938  << "way along the track" << endl;
939 
940  Pout<< "derivative : " << hitEqn[i].derivative(mu[j]) << nl
941  << " coord " << j << " mu[j]: " << mu[j] << nl
942  << " hitEq " << i << endl;
943  }
944 
945  if (0 <= mu[j] && mu[j] < muH)
946  {
947  iH = i;
948  muH = mu[j];
949  }
950  }
951  }
952  }
953 
954  // Set the new coordinates
955  barycentric yH
956  (
957  hitEqn[0].value(muH),
958  hitEqn[1].value(muH),
959  hitEqn[2].value(muH),
960  hitEqn[3].value(muH)
961  );
962  // !!! <-- This fails if the tet collapses onto the particle, as detA tends
963  // to zero at the hit. In this instance, we can differentiate the hit and
964  // detA polynomials to find a limiting location, but this will not be on a
965  // triangle. We will then need to do a second track through the degenerate
966  // tet to find the final hit position. This second track is over zero
967  // distance and therefore can be of the static mesh type. This has not yet
968  // been implemented.
969  const scalar detAH = detAEqn.value(muH);
970  if (!std::isnormal(detAH))
971  {
973  << "A moving tet collapsed onto a particle. This is not supported. "
974  << "The mesh is too poor, or the motion too severe, for particle "
975  << "tracking to function." << exit(FatalError);
976  }
977  yH /= detAH;
978 
979  // Clamp to zero any negative coordinates generated by round-off error
980  for (label i = 0; i < 4; ++ i)
981  {
982  yH.replace(i, i == iH ? 0 : max(0, yH[i]));
983  }
984 
985  // Re-normalise if within the tet
986  if (iH == -1)
987  {
988  yH /= cmptSum(yH);
989  }
990 
991  // Set the new position and hit index
992  coordinates_ = yH;
993  tetTriI = iH;
994 
995  scalar advance = muH*detA[0];
996 
997  // Set the proportion of the track that has been completed
998  stepFraction_ += fraction*advance;
999 
1000  // Accumulate displacement behind
1001  if (detA[0] <= 0 || nBehind_ > 0)
1002  {
1003  behind_ += muH*detA[0]*mag(displacement);
1004 
1005  if (behind_ > 0)
1006  {
1007  behind_ = 0;
1008  nBehind_ = 0;
1009  }
1010  else
1011  {
1012  ++ nBehind_;
1013  }
1014  }
1015 
1016  if (debug)
1017  {
1018  if (iH != -1)
1019  {
1020  Pout<< "Track hit tet face " << iH << " first" << endl;
1021  }
1022  else
1023  {
1024  Pout<< "Track hit no tet faces" << endl;
1025  }
1026 // Pout<< "End local coordinates = " << yH << endl
1027 // << "End global coordinates = " << position() << endl
1028 // << "Tracking displacement = " << position() - x0 << endl
1029 // << muH*detA[0]*100 << "% of the step from " << stepFraction_
1030 // << " to " << stepFraction_ + fraction << " completed" << endl
1031 // << endl;
1032  }
1033 
1034 
1035  return iH != -1 ? 1 - muH*detA[0] : 0;
1036 }
1037 
1038 
1039 Foam::scalar Foam::particle::trackToTri
1041  const vector& displacement,
1042  const scalar fraction,
1043  label& tetTriI
1044 )
1045 {
1046  if ((mesh_.moving() && (stepFraction_ != 1 || fraction != 0)))
1047  {
1048  return trackToMovingTri(displacement, fraction, tetTriI);
1049  }
1050  else
1051  {
1052  return trackToStationaryTri(displacement, fraction, tetTriI);
1053  }
1054 }
1055 
1056 
1058 {
1059  if (cmptMin(mesh_.geometricD()) == -1)
1060  {
1061  vector pos = position(), posC = pos;
1062  meshTools::constrainToMeshCentre(mesh_, posC);
1063  return pos - posC;
1064  }
1065  else
1066  {
1067  return vector::zero;
1068  }
1069 }
1070 
1071 
1073 {}
1074 
1075 
1077 {}
1078 
1079 
1081 {
1082  // Convert the face index to be local to the processor patch
1083  facei_ = mesh_.boundaryMesh()[patch()].whichFace(facei_);
1084 }
1085 
1086 
1089  const label patchi,
1090  trackingData& td
1091 )
1092 {
1093  const coupledPolyPatch& ppp =
1094  refCast<const coupledPolyPatch>(mesh_.boundaryMesh()[patchi]);
1095 
1096  if (!ppp.parallel())
1097  {
1098  const tensor& T =
1099  (
1100  ppp.forwardT().size() == 1
1101  ? ppp.forwardT()[0]
1102  : ppp.forwardT()[facei_]
1103  );
1104  transformProperties(T);
1105  }
1106  else if (ppp.separated())
1107  {
1108  const vector& s =
1109  (
1110  (ppp.separation().size() == 1)
1111  ? ppp.separation()[0]
1112  : ppp.separation()[facei_]
1113  );
1114  transformProperties(-s);
1115  }
1116 
1117  // Set the topology
1118  celli_ = ppp.faceCells()[facei_];
1119  facei_ += ppp.start();
1120  tetFacei_ = facei_;
1121  // Faces either side of a coupled patch are numbered in opposite directions
1122  // as their normals both point away from their connected cells. The tet
1123  // point therefore counts in the opposite direction from the base point.
1124  tetPti_ = mesh_.faces()[tetFacei_].size() - 1 - tetPti_;
1125 
1126  // Reflect to account for the change of triangle orientation in the new cell
1127  reflect();
1128 
1129  // Note that the position does not need transforming explicitly. The face-
1130  // triangle on the receive patch is the transformation of the one on the
1131  // send patch, so whilst the barycentric coordinates remain the same, the
1132  // change of triangle implicitly transforms the position.
1133 }
1134 
1135 
1139 )
1140 {
1141  // Get the transformed position
1142  const vector pos = transform.invTransformPosition(position());
1143 
1144  // Break the topology
1145  celli_ = -1;
1146  tetFacei_ = -1;
1147  tetPti_ = -1;
1148  facei_ = -1;
1149 
1150  // Store the position in the barycentric data
1151  coordinates_ = barycentric(1 - cmptSum(pos), pos.x(), pos.y(), pos.z());
1152 
1153  // Transform the properties
1154  transformProperties(- transform.t());
1155  if (transform.hasR())
1156  {
1157  transformProperties(transform.R().T());
1158  }
1159 }
1160 
1161 
1163 {
1164  // Get the position from the barycentric data
1165  const vector pos(coordinates_.b(), coordinates_.c(), coordinates_.d());
1166 
1167  // Create some arbitrary topology for the supplied cell
1168  celli_ = celli;
1169  tetFacei_ = mesh_.cells()[celli_][0];
1170  tetPti_ = 1;
1171  facei_ = -1;
1172 
1173  // Get the reverse transform and directly set the coordinates from the
1174  // position. This isn't likely to be correct; the particle is probably not
1175  // in this tet. It will, however, generate the correct vector when the
1176  // position method is called. A referred particle should never be tracked,
1177  // so this approximate topology is good enough. By using the nearby cell we
1178  // minimise the error associated with the incorrect topology.
1179  coordinates_ = barycentric(1, 0, 0, 0);
1180  if (mesh_.moving() && stepFraction_ != 1)
1181  {
1182  Pair<vector> centre;
1183  FixedList<scalar, 4> detA;
1185  movingTetReverseTransform(0, centre, detA, T);
1186  coordinates_ += (pos - centre[0]) & T[0]/detA[0];
1187  }
1188  else
1189  {
1190  vector centre;
1191  scalar detA;
1193  stationaryTetReverseTransform(centre, detA, T);
1194  coordinates_ += (pos - centre) & T/detA;
1195  }
1196 }
1197 
1198 
1199 Foam::label Foam::particle::procTetPt
1201  const polyMesh& procMesh,
1202  const label procCell,
1203  const label procTetFace
1204 ) const
1205 {
1206  // The tet point on the procMesh differs from the current tet point if the
1207  // mesh and procMesh faces are of differing orientation. The change is the
1208  // same as in particle::correctAfterParallelTransfer.
1209 
1210  if
1211  (
1212  (mesh_.faceOwner()[tetFacei_] == celli_)
1213  == (procMesh.faceOwner()[procTetFace] == procCell)
1214  )
1215  {
1216  return tetPti_;
1217  }
1218  else
1219  {
1220  return procMesh.faces()[procTetFace].size() - 1 - tetPti_;
1221  }
1222 }
1223 
1224 
1227  const vector& position,
1228  const mapPolyMesh& mapper
1229 )
1230 {
1231  locate
1232  (
1233  position,
1234  nullptr,
1235  mapper.reverseCellMap()[celli_],
1236  true,
1237  "Particle mapped to a location outside of the mesh"
1238  );
1239 }
1240 
1241 
1242 void Foam::particle::relocate(const point& position, const label celli)
1243 {
1244  locate
1245  (
1246  position,
1247  nullptr,
1248  celli,
1249  true,
1250  "Particle mapped to a location outside of the mesh"
1251  );
1252 }
1253 
1254 
1255 // * * * * * * * * * * * * * * Friend Operators * * * * * * * * * * * * * * //
1256 
1257 bool Foam::operator==(const particle& pA, const particle& pB)
1258 {
1259  return (pA.origProc() == pB.origProc() && pA.origId() == pB.origId());
1260 }
1261 
1262 
1263 bool Foam::operator!=(const particle& pA, const particle& pB)
1264 {
1265  return !(pA == pB);
1266 }
1267 
1268 
1269 // ************************************************************************* //
Foam::expressions::patchExpr::debug
int debug
Static debugging option.
Foam::particle::prepareForParallelTransfer
void prepareForParallelTransfer()
Convert global addressing to the processor patch local equivalents.
Definition: particle.C:1080
Foam::reverse
void reverse(UList< T > &list, const label n)
Definition: UListI.H:449
Foam::Tensor< scalar >
p
volScalarField & p
Definition: createFieldRefs.H:8
Foam::coupledPolyPatch::separation
virtual const vectorField & separation() const
If the planes are separated the separation vector.
Definition: coupledPolyPatch.H:289
cubicEqn.H
Foam::particle::correctAfterInteractionListReferral
void correctAfterInteractionListReferral(const label celli)
Correct the topology after referral. The particle may still be.
Definition: particle.C:1162
Foam::barycentricTensor
BarycentricTensor< scalar > barycentricTensor
A scalar version of the templated BarycentricTensor.
Definition: barycentricTensor.H:48
s
gmvFile<< "tracers "<< particles.size()<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().x()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().y()<< " ";}gmvFile<< nl;for(const passiveParticle &p :particles){ gmvFile<< p.position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.timeName(), cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
Definition: gmvOutputSpray.H:25
Foam::meshTools::otherFace
label otherFace(const primitiveMesh &mesh, const label celli, const label facei, const label edgeI)
Return face on cell using edgeI but not facei. Throws error.
Definition: meshTools.C:555
Foam::constant::physicoChemical::mu
const dimensionedScalar mu
Atomic mass unit.
Definition: createFieldRefs.H:4
Foam::particle::origId
label origId() const
Return the particle ID on the originating processor.
Definition: particleI.H:221
Foam::particle::autoMap
void autoMap(const vector &position, const mapPolyMesh &mapper)
Map after a topology change.
Definition: particle.C:1226
Foam::particle::trackToMovingTri
scalar trackToMovingTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToTri, but for moving meshes.
Definition: particle.C:846
Foam::coupledPolyPatch::forwardT
virtual const tensorField & forwardT() const
Return face transformation tensor.
Definition: coupledPolyPatch.H:301
indexedOctree.H
A
static const Foam::dimensionedScalar A("", Foam::dimPressure, 611.21)
Foam::endl
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:369
Foam::coupledPolyPatch
The coupledPolyPatch is an abstract base class for patches that couple regions of the computational d...
Definition: coupledPolyPatch.H:54
Foam::particle::relocate
void relocate(const point &position, const label celli=-1)
Set the addressing based on the provided position.
Definition: particle.C:1242
Foam::Pout
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
Foam::transform
dimensionSet transform(const dimensionSet &ds)
Return the argument; transformations do not change the dimensions.
Definition: dimensionSet.C:521
Foam::particle::particleCount_
static label particleCount_
Cumulative particle counter - used to provide unique ID.
Definition: particle.H:349
Foam::min
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:33
Foam::polyMesh
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:77
Foam::particle::writeLagrangianPositions
static bool writeLagrangianPositions
Definition: particle.H:360
forAll
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:296
Foam::face::compare
static int compare(const face &a, const face &b)
Compare faces.
Definition: face.C:281
Foam::cmptMin
void cmptMin(FieldField< Field, typename FieldField< Field, Type >::cmptType > &cf, const FieldField< Field, Type > &f)
Definition: FieldFieldFunctions.C:302
Foam::coupledPolyPatch::parallel
virtual bool parallel() const
Are the cyclic planes parallel.
Definition: coupledPolyPatch.H:295
Foam::debug::infoSwitch
int infoSwitch(const char *name, const int deflt=0)
Lookup info switch or add default value.
Definition: debug.C:231
Foam::particle::track
scalar track(const vector &displacement, const scalar fraction)
Track along the displacement for a given fraction of the overall.
Definition: particle.C:639
Foam::operator!=
bool operator!=(const eddy &a, const eddy &b)
Definition: eddy.H:239
Foam::coupledPolyPatch::separated
virtual bool separated() const
Are the planes separated.
Definition: coupledPolyPatch.H:283
Foam::particle::trackToStationaryTri
scalar trackToStationaryTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToTri, but for stationary meshes.
Definition: particle.C:714
Foam::constant::physicoChemical::b
const dimensionedScalar b
Wien displacement law constant: default SI units: [m.K].
Definition: createFields.H:27
Foam::operator==
tmp< faMatrix< Type > > operator==(const faMatrix< Type > &, const faMatrix< Type > &)
Foam::cubicEqn::value
scalar value(const scalar x) const
Evaluate the cubic equation at x.
Definition: cubicEqnI.H:105
coordinates
PtrList< coordinateSystem > coordinates(solidRegions.size())
Foam::polyMesh::faceOwner
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1107
Foam::T
void T(FieldField< Field, Type > &f1, const FieldField< Field, Type > &f2)
Definition: FieldFieldFunctions.C:58
Foam::y0
dimensionedScalar y0(const dimensionedScalar &ds)
Definition: dimensionedScalar.C:281
Foam::Barycentric< scalar >
Foam::roots::real
Definition: Roots.H:56
Foam::particle::deviationFromMeshCentre
vector deviationFromMeshCentre() const
Get the displacement from the mesh centre. Used to correct the.
Definition: particle.C:1057
Foam::max
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
Foam::VectorSpace::replace
void replace(const direction, const Cmpt &)
Definition: VectorSpaceI.H:145
Foam::particle::origProc
label origProc() const
Return the originating processor ID.
Definition: particleI.H:209
Foam::FatalError
error FatalError
Foam::particle::correctAfterParallelTransfer
void correctAfterParallelTransfer(const label patchi, trackingData &td)
Convert processor patch addressing to the global equivalents.
Definition: particle.C:1088
Foam::particle::procTetPt
label procTetPt(const polyMesh &procMesh, const label procCell, const label procTetFace) const
Return the tet point appropriate for decomposition or reconstruction.
Definition: particle.C:1200
mesh
dynamicFvMesh & mesh
Definition: createDynamicFvMesh.H:6
Foam
Namespace for OpenFOAM.
Definition: atmBoundaryLayer.C:33
Foam::cmptSum
Cmpt cmptSum(const SphericalTensor< Cmpt > &st)
Return the sum of components of a SphericalTensor.
Definition: SphericalTensorI.H:164
Foam::polyPatch::faceCells
const labelUList & faceCells() const
Return face-cell addressing.
Definition: polyPatch.C:371
treeDataCell.H
T
const volScalarField & T
Definition: createFieldRefs.H:2
Foam::vector
Vector< scalar > vector
A scalar version of the templated Vector.
Definition: vector.H:51
Foam::polyPatch::start
label start() const
Return start label of this patch in the polyMesh face list.
Definition: polyPatch.H:361
Foam::particle::trackToFace
scalar trackToFace(const vector &displacement, const scalar fraction)
As particle::track, but also stops on internal faces.
Definition: particle.C:658
Foam::exit
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
Foam::particle::writeLagrangianCoordinates
static bool writeLagrangianCoordinates
Definition: particle.H:356
Foam::mapPolyMesh::reverseCellMap
const labelList & reverseCellMap() const
Reverse cell map.
Definition: mapPolyMesh.H:532
Foam::polyMesh::faces
virtual const faceList & faces() const
Return raw faces.
Definition: polyMesh.C:1094
FatalErrorInFunction
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
Foam::edge::compare
static int compare(const edge &a, const edge &b)
Compare edges.
Definition: edgeI.H:33
Foam::particle::trackToTri
scalar trackToTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToFace, but also stops on tet triangles. On.
Definition: particle.C:1040
Foam::sqr
dimensionedSymmTensor sqr(const dimensionedVector &dv)
Definition: dimensionedSymmTensor.C:51
Foam::UPstream::myProcNo
static int myProcNo(const label communicator=worldComm)
Number of this process (starting from masterNo() = 0)
Definition: UPstream.H:463
Foam::nl
constexpr char nl
Definition: Ostream.H:404
Foam::Pair
An ordered pair of two objects of type <T> with first() and second() elements.
Definition: Pair.H:54
f
labelList f(nPoints)
Foam::foamVersion::patch
const std::string patch
OpenFOAM patch number as a std::string.
Foam::particle::transformProperties
virtual void transformProperties(const tensor &T)
Transform the physical properties of the particle.
Definition: particle.C:1072
Foam::Vector< scalar >
Foam::vectorTensorTransform
Vector-tensor class used to perform translations and rotations in 3D space.
Definition: vectorTensorTransform.H:63
Foam::mag
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
Foam::FixedList
A 1D vector of objects of type <T> with a fixed length <N>.
Definition: HashTable.H:104
Foam::particle
Base particle class.
Definition: particle.H:76
registerInfoSwitch
registerInfoSwitch("writeLagrangianPositions", bool, Foam::particle::writeLagrangianPositions)
Foam::particle::prepareForInteractionListReferral
void prepareForInteractionListReferral(const vectorTensorTransform &transform)
Break the topology and store the particle position so that the.
Definition: particle.C:1137
Foam::direction
uint8_t direction
Definition: direction.H:52
Foam::BarycentricTensor
Templated 4x3 tensor derived from VectorSpace. Has 12 components. Can represent a barycentric transfo...
Definition: BarycentricTensor.H:57
FUNCTION_NAME
#define FUNCTION_NAME
Definition: messageStream.H:295
Foam::meshTools::constrainToMeshCentre
void constrainToMeshCentre(const polyMesh &mesh, point &pt)
Set the constrained components of position to mesh centre.
Definition: meshTools.C:629
particle.H
Foam::mapPolyMesh
Class containing mesh-to-mesh mapping information after a change in polyMesh topology.
Definition: mapPolyMesh.H:161
Foam::constant::universal::c
const dimensionedScalar c
Speed of light in a vacuum.
Foam::name
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
Foam::VectorSpace< Vector< scalar >, scalar, 3 >::zero
static const Vector< scalar > zero
Definition: VectorSpace.H:115
Foam::cubicEqn
Container to encapsulate various operations for cubic equation of the forms with real coefficients:
Definition: cubicEqn.H:112
registerSwitch.H
Foam::barycentric
Barycentric< scalar > barycentric
A scalar version of the templated Barycentric.
Definition: barycentric.H:47
transform.H
3D tensor transformation operations.
Foam::Roots
Templated storage for the roots of polynomial equations, plus flags to indicate the nature of the roo...
Definition: Roots.H:70
Foam::particle::trackingData
Definition: particle.H:95
Foam::defineTypeNameAndDebug
defineTypeNameAndDebug(combustionModel, 0)
triFace
face triFace(3)
WarningInFunction
#define WarningInFunction
Report a warning using Foam::Warning.
Definition: messageStream.H:328
Foam::particle::particle
particle(const polyMesh &mesh, const barycentric &coordinates, const label celli, const label tetFacei, const label tetPti)
Construct from components.
Definition: particle.C:516
Foam::pos
dimensionedScalar pos(const dimensionedScalar &ds)
Definition: dimensionedScalar.C:177