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-------------------------------------------------------------------------------
11License
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
38namespace Foam
39{
41}
42
43const Foam::label Foam::particle::maxNBehind_ = 10;
44
45Foam::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
64void Foam::particle::stationaryTetReverseTransform
65(
66 vector& centre,
67 scalar& detA,
68 barycentricTensor& T
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
93void 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
124 (
125 bd[0] ^ bc[0],
126 ac[0] ^ ad[0],
127 ad[0] ^ ab[0],
128 ab[0] ^ ac[0]
129 );
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 );
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
147void Foam::particle::reflect()
148{
149 std::swap(coordinates_.c(), coordinates_.d());
150}
151
152
153void 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
172void 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
247void 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
362void 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
373void 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
407void 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,
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
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
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;
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
1040(
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;
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
1088(
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
1137(
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;
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
1200(
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
1226(
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
1242void 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
1257bool Foam::operator==(const particle& pA, const particle& pB)
1258{
1259 return (pA.origProc() == pB.origProc() && pA.origId() == pB.origId());
1260}
1261
1262
1263bool Foam::operator!=(const particle& pA, const particle& pB)
1264{
1265 return !(pA == pB);
1266}
1267
1268
1269// ************************************************************************* //
static const Foam::dimensionedScalar A("", Foam::dimPressure, 611.21)
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 length <N>.
Definition: FixedList.H:81
An ordered pair of two objects of type <T> with first() and second() elements.
Definition: Pair.H:69
Inter-processor communications stream.
Definition: Pstream.H:63
Templated storage for the roots of polynomial equations, plus flags to indicate the nature of the roo...
Definition: Roots.H:73
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:114
void replace(const direction, const Cmpt &)
Definition: VectorSpaceI.H:145
The coupledPolyPatch is an abstract base class for patches that couple regions of the computational d...
virtual bool separated() const
Are the planes separated.
virtual bool parallel() const
Are the cyclic planes parallel.
virtual const vectorField & separation() const
If the planes are separated the separation vector.
virtual const tensorField & forwardT() const
Return face transformation tensor.
Container to encapsulate various operations for cubic equation of the forms with real coefficients:
Definition: cubicEqn.H:115
scalar value(const scalar x) const
Evaluate the cubic equation at x.
Definition: cubicEqnI.H:105
static int compare(const edge &a, const edge &b)
Compare edges.
Definition: edgeI.H:33
static int compare(const face &a, const face &b)
Compare faces.
Definition: face.C:281
virtual void track()
Do the actual tracking to fill the track data.
Definition: streamLine.C:48
Class containing mesh-to-mesh mapping information after a change in polyMesh topology.
Definition: mapPolyMesh.H:162
const labelList & reverseCellMap() const
Reverse cell map.
Definition: mapPolyMesh.H:532
Base particle class.
Definition: particle.H:79
void correctAfterInteractionListReferral(const label celli)
Correct the topology after referral. The particle may still be.
Definition: particle.C:1162
virtual void transformProperties(const tensor &T)
Transform the physical properties of the particle.
Definition: particle.C:1072
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
vector deviationFromMeshCentre() const
Get the displacement from the mesh centre. Used to correct the.
Definition: particle.C:1057
scalar trackToStationaryTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToTri, but for stationary meshes.
Definition: particle.C:714
void correctAfterParallelTransfer(const label patchi, trackingData &td)
Convert processor patch addressing to the global equivalents.
Definition: particle.C:1088
scalar trackToFace(const vector &displacement, const scalar fraction)
As particle::track, but also stops on internal faces.
Definition: particle.C:658
vector position() const
Return current particle position.
Definition: particleI.H:314
void prepareForInteractionListReferral(const vectorTensorTransform &transform)
Break the topology and store the particle position so that the.
Definition: particle.C:1137
void autoMap(const vector &position, const mapPolyMesh &mapper)
Map after a topology change.
Definition: particle.C:1226
scalar trackToMovingTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToTri, but for moving meshes.
Definition: particle.C:846
void prepareForParallelTransfer()
Convert global addressing to the processor patch local equivalents.
Definition: particle.C:1080
static label particleCount_
Cumulative particle counter - used to provide unique ID.
Definition: particle.H:375
static bool writeLagrangianPositions
Definition: particle.H:383
label origId() const noexcept
Return the particle ID on the originating processor.
Definition: particleI.H:221
void relocate(const point &position, const label celli=-1)
Set the addressing based on the provided position.
Definition: particle.C:1242
scalar trackToTri(const vector &displacement, const scalar fraction, label &tetTriI)
As particle::trackToFace, but also stops on tet triangles. On.
Definition: particle.C:1040
static bool writeLagrangianCoordinates
Definition: particle.H:379
label origProc() const noexcept
Return the originating processor ID.
Definition: particleI.H:209
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:81
virtual const faceList & faces() const
Return raw faces.
Definition: polyMesh.C:1108
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1121
label start() const
Return start label of this patch in the polyMesh face list.
Definition: polyPatch.H:364
const labelUList & faceCells() const
Return face-cell addressing.
Definition: polyPatch.C:371
Vector-tensor class used to perform translations and rotations in 3D space.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
#define defineTypeNameAndDebug(Type, DebugSwitch)
Define the typeName and debug information.
Definition: className.H:121
volScalarField & p
const volScalarField & T
const volScalarField & mu
dynamicFvMesh & mesh
PtrList< coordinateSystem > coordinates(solidRegions.size())
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:453
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))
#define WarningInFunction
Report a warning using Foam::Warning.
#define FUNCTION_NAME
const dimensionedScalar c
Speed of light in a vacuum.
int infoSwitch(const char *name, const int deflt=0)
Lookup info switch or add default value.
Definition: debug.C:231
const std::string patch
OpenFOAM patch number as a std::string.
void constrainToMeshCentre(const polyMesh &mesh, point &pt)
Set the constrained components of position to mesh centre.
Definition: meshTools.C:629
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
@ real
Definition: Roots.H:56
Namespace for OpenFOAM.
bool operator!=(const eddy &a, const eddy &b)
Definition: eddy.H:239
dimensionedScalar pos(const dimensionedScalar &ds)
label max(const labelHashSet &set, label maxValue=labelMin)
Find the max value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:47
Barycentric< scalar > barycentric
A scalar version of the templated Barycentric.
Definition: barycentric.H:51
dimensionSet transform(const dimensionSet &ds)
Return the argument; transformations do not change the dimensions.
Definition: dimensionSet.C:536
dimensionedSymmTensor sqr(const dimensionedVector &dv)
dimensionedScalar y0(const dimensionedScalar &ds)
void reverse(UList< T > &list, const label n)
Reverse the first n elements of the list.
Definition: UListI.H:449
void cmptMin(FieldField< Field, typename FieldField< Field, Type >::cmptType > &cf, const FieldField< Field, Type > &f)
Cmpt cmptSum(const SphericalTensor< Cmpt > &st)
Return the sum of components of a SphericalTensor.
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:372
tmp< faMatrix< Type > > operator==(const faMatrix< Type > &, const faMatrix< Type > &)
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
label min(const labelHashSet &set, label minValue=labelMax)
Find the min value in labelHashSet, optionally limited by second argument.
Definition: hashSets.C:33
uint8_t direction
Definition: direction.H:56
BarycentricTensor< scalar > barycentricTensor
A scalar version of the templated BarycentricTensor.
error FatalError
word name(const expressions::valueTypeCode typeCode)
A word representation of a valueTypeCode. Empty for INVALID.
Definition: exprTraits.C:59
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
Vector< scalar > vector
Definition: vector.H:61
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:130
constexpr char nl
The newline '\n' character (0x0a)
Definition: Ostream.H:53
face triFace(3)
labelList f(nPoints)
#define registerInfoSwitch(Name, Type, SwitchVar)
volScalarField & b
Definition: createFields.H:27
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:333
A non-counting (dummy) refCount.
Definition: refCount.H:59
3D tensor transformation operations.