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