OpenFOAM-5.x/src/lagrangian/basic/particle/particle.C
Will Bainbridge e879da1341 lagrangian: Fixed infinite loops
Tracking through an inverted region of the mesh happens in a reversed
direction relative to a non-inverted region. Usually, this allows the
tracking to propagate normally, regardless of the sign of the space.
However, in rare cases, it is possible for a straight trajectory to form
a closed loop through both positive and negative regions. This causes
the tracking to loop indefinitely.

To fix this, the displacement through inverted regions has been
artifically increased by a small amount (1% at the moment). This has the
effect that the change in track fraction over the negative part of the
loop no longer exactly cancels the change over the positive part, and
the track therefore terminates.
2017-07-19 16:56:58 +01:00

1112 lines
28 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | Copyright (C) 2011-2017 OpenFOAM Foundation
\\/ M anipulation |
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
\*---------------------------------------------------------------------------*/
#include "particle.H"
#include "transform.H"
#include "treeDataCell.H"
#include "cubicEqn.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
const Foam::scalar Foam::particle::negativeSpaceDisplacementFactor = 1.01;
Foam::label Foam::particle::particleCount_ = 0;
namespace Foam
{
defineTypeNameAndDebug(particle, 0);
}
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
void Foam::particle::stationaryTetGeometry
(
vector& centre,
vector& base,
vector& vertex1,
vector& vertex2
) const
{
const triFace triIs(currentTetIndices().faceTriIs(mesh_));
const vectorField& ccs = mesh_.cellCentres();
const pointField& pts = mesh_.points();
centre = ccs[celli_];
base = pts[triIs[0]];
vertex1 = pts[triIs[1]];
vertex2 = pts[triIs[2]];
}
Foam::barycentricTensor Foam::particle::stationaryTetTransform() const
{
vector centre, base, vertex1, vertex2;
stationaryTetGeometry(centre, base, vertex1, vertex2);
return barycentricTensor(centre, base, vertex1, vertex2);
}
void Foam::particle::stationaryTetReverseTransform
(
vector& centre,
scalar& detA,
barycentricTensor& T
) const
{
barycentricTensor A = stationaryTetTransform();
const vector ab = A.b() - A.a();
const vector ac = A.c() - A.a();
const vector ad = A.d() - A.a();
const vector bc = A.c() - A.b();
const vector bd = A.d() - A.b();
centre = A.a();
detA = ab & (ac ^ ad);
T = barycentricTensor
(
bd ^ bc,
ac ^ ad,
ad ^ ab,
ab ^ ac
);
}
void Foam::particle::movingTetGeometry
(
const scalar fraction,
Pair<vector>& centre,
Pair<vector>& base,
Pair<vector>& vertex1,
Pair<vector>& vertex2
) const
{
const triFace triIs(currentTetIndices().faceTriIs(mesh_));
const pointField& ptsOld = mesh_.oldPoints();
const pointField& ptsNew = mesh_.points();
// !!! <-- We would be better off using mesh_.cellCentres() here. However,
// we need to put a mesh_.oldCellCentres() method in for this to work. The
// values obtained from the mesh and those obtained from the cell do not
// necessarily match. See mantis #1993.
const vector ccOld = mesh_.cells()[celli_].centre(ptsOld, mesh_.faces());
const vector ccNew = mesh_.cells()[celli_].centre(ptsNew, mesh_.faces());
// Old and new points and cell centres are not sub-cycled. If we are sub-
// cycling, then we have to account for the timestep change here by
// modifying the fractions that we take of the old and new geometry.
const Pair<scalar> s = stepFractionSpan();
const scalar f0 = s[0] + stepFraction_*s[1], f1 = fraction*s[1];
centre[0] = ccOld + f0*(ccNew - ccOld);
base[0] = ptsOld[triIs[0]] + f0*(ptsNew[triIs[0]] - ptsOld[triIs[0]]);
vertex1[0] = ptsOld[triIs[1]] + f0*(ptsNew[triIs[1]] - ptsOld[triIs[1]]);
vertex2[0] = ptsOld[triIs[2]] + f0*(ptsNew[triIs[2]] - ptsOld[triIs[2]]);
centre[1] = f1*(ccNew - ccOld);
base[1] = f1*(ptsNew[triIs[0]] - ptsOld[triIs[0]]);
vertex1[1] = f1*(ptsNew[triIs[1]] - ptsOld[triIs[1]]);
vertex2[1] = f1*(ptsNew[triIs[2]] - ptsOld[triIs[2]]);
}
Foam::Pair<Foam::barycentricTensor> Foam::particle::movingTetTransform
(
const scalar fraction
) const
{
Pair<vector> centre, base, vertex1, vertex2;
movingTetGeometry(fraction, centre, base, vertex1, vertex2);
return
Pair<barycentricTensor>
(
barycentricTensor(centre[0], base[0], vertex1[0], vertex2[0]),
barycentricTensor(centre[1], base[1], vertex1[1], vertex2[1])
);
}
void Foam::particle::movingTetReverseTransform
(
const scalar fraction,
Pair<vector>& centre,
FixedList<scalar, 4>& detA,
FixedList<barycentricTensor, 3>& T
) const
{
Pair<barycentricTensor> A = movingTetTransform(fraction);
const Pair<vector> ab(A[0].b() - A[0].a(), A[1].b() - A[1].a());
const Pair<vector> ac(A[0].c() - A[0].a(), A[1].c() - A[1].a());
const Pair<vector> ad(A[0].d() - A[0].a(), A[1].d() - A[1].a());
const Pair<vector> bc(A[0].c() - A[0].b(), A[1].c() - A[1].b());
const Pair<vector> bd(A[0].d() - A[0].b(), A[1].d() - A[1].b());
centre[0] = A[0].a();
centre[1] = A[1].a();
detA[0] = ab[0] & (ac[0] ^ ad[0]);
detA[1] =
(ab[1] & (ac[0] ^ ad[0]))
+ (ab[0] & (ac[1] ^ ad[0]))
+ (ab[0] & (ac[0] ^ ad[1]));
detA[2] =
(ab[0] & (ac[1] ^ ad[1]))
+ (ab[1] & (ac[0] ^ ad[1]))
+ (ab[1] & (ac[1] ^ ad[0]));
detA[3] = ab[1] & (ac[1] ^ ad[1]);
T[0] = barycentricTensor
(
bd[0] ^ bc[0],
ac[0] ^ ad[0],
ad[0] ^ ab[0],
ab[0] ^ ac[0]
);
T[1] = barycentricTensor
(
(bd[0] ^ bc[1]) + (bd[1] ^ bc[0]),
(ac[0] ^ ad[1]) + (ac[1] ^ ad[0]),
(ad[0] ^ ab[1]) + (ad[1] ^ ab[0]),
(ab[0] ^ ac[1]) + (ab[1] ^ ac[0])
);
T[2] = barycentricTensor
(
bd[1] ^ bc[1],
ac[1] ^ ad[1],
ad[1] ^ ab[1],
ab[1] ^ ac[1]
);
}
void Foam::particle::reflect()
{
Swap(coordinates_.c(), coordinates_.d());
}
void Foam::particle::rotate(const bool reverse)
{
if (!reverse)
{
scalar temp = coordinates_.b();
coordinates_.b() = coordinates_.c();
coordinates_.c() = coordinates_.d();
coordinates_.d() = temp;
}
else
{
scalar temp = coordinates_.d();
coordinates_.d() = coordinates_.c();
coordinates_.c() = coordinates_.b();
coordinates_.b() = temp;
}
}
void Foam::particle::changeTet(const label tetTriI)
{
const bool isOwner = mesh_.faceOwner()[tetFacei_] == celli_;
const label firstTetPtI = 1;
const label lastTetPtI = mesh_.faces()[tetFacei_].size() - 2;
if (tetTriI == 1)
{
changeFace(tetTriI);
}
else if (tetTriI == 2)
{
if (isOwner)
{
if (tetPti_ == lastTetPtI)
{
changeFace(tetTriI);
}
else
{
reflect();
tetPti_ += 1;
}
}
else
{
if (tetPti_ == firstTetPtI)
{
changeFace(tetTriI);
}
else
{
reflect();
tetPti_ -= 1;
}
}
}
else if (tetTriI == 3)
{
if (isOwner)
{
if (tetPti_ == firstTetPtI)
{
changeFace(tetTriI);
}
else
{
reflect();
tetPti_ -= 1;
}
}
else
{
if (tetPti_ == lastTetPtI)
{
changeFace(tetTriI);
}
else
{
reflect();
tetPti_ += 1;
}
}
}
else
{
FatalErrorInFunction
<< "Changing tet without changing cell should only happen when the "
<< "track is on triangle 1, 2 or 3."
<< exit(FatalError);
}
}
void Foam::particle::changeFace(const label tetTriI)
{
// Get the old topology
const triFace triOldIs(currentTetIndices().faceTriIs(mesh_));
// Get the shared edge and the pre-rotation
edge sharedEdge;
if (tetTriI == 1)
{
sharedEdge = edge(triOldIs[1], triOldIs[2]);
}
else if (tetTriI == 2)
{
sharedEdge = edge(triOldIs[2], triOldIs[0]);
}
else if (tetTriI == 3)
{
sharedEdge = edge(triOldIs[0], triOldIs[1]);
}
else
{
FatalErrorInFunction
<< "Changing face without changing cell should only happen when the"
<< " track is on triangle 1, 2 or 3."
<< exit(FatalError);
sharedEdge = edge(-1, -1);
}
// Find the face in the same cell that shares the edge, and the
// corresponding tetrahedra point
tetPti_ = -1;
forAll(mesh_.cells()[celli_], cellFaceI)
{
const label newFaceI = mesh_.cells()[celli_][cellFaceI];
const class face& newFace = mesh_.faces()[newFaceI];
// Exclude the current face
if (tetFacei_ == newFaceI)
{
continue;
}
// Loop over the edges, looking for the shared one
label edgeComp = 0;
label edgeI = 0;
for (; edgeI < newFace.size(); ++ edgeI)
{
edgeComp = edge::compare(sharedEdge, newFace.faceEdge(edgeI));
if (edgeComp != 0)
{
break;
}
}
// If the face does not contain the edge, then move on to the next face
if (edgeComp == 0)
{
continue;
}
// Make the edge index relative to the base point
const label newBaseI = max(0, mesh_.tetBasePtIs()[newFaceI]);
edgeI = (edgeI - newBaseI + newFace.size()) % newFace.size();
// If the edge is next the base point (i.e., the index is 0 or n - 1),
// then we swap it for the adjacent edge. This new edge is opposite the
// base point, and defines the tet with the original edge in it.
edgeI = min(max(1, edgeI), newFace.size() - 2);
// Set the new face and tet point
tetFacei_ = newFaceI;
tetPti_ = edgeI;
// Exit the loop now that the the tet point has been found
break;
}
if (tetPti_ == -1)
{
FatalErrorInFunction
<< "The search for an edge-connected face and tet-point failed."
<< exit(FatalError);
}
// Pre-rotation puts the shared edge opposite the base of the tetrahedron
if (sharedEdge.otherVertex(triOldIs[1]) == -1)
{
rotate(false);
}
else if (sharedEdge.otherVertex(triOldIs[2]) == -1)
{
rotate(true);
}
// Get the new topology
const triFace triNewIs = currentTetIndices().faceTriIs(mesh_);
// Reflect to account for the change of triangle orientation on the new face
reflect();
// Post rotation puts the shared edge back in the correct location
if (sharedEdge.otherVertex(triNewIs[1]) == -1)
{
rotate(true);
}
else if (sharedEdge.otherVertex(triNewIs[2]) == -1)
{
rotate(false);
}
}
void Foam::particle::changeCell()
{
// Set the cell to be the one on the other side of the face
const label ownerCellI = mesh_.faceOwner()[tetFacei_];
const bool isOwner = celli_ == ownerCellI;
celli_ = isOwner ? mesh_.faceNeighbour()[tetFacei_] : ownerCellI;
// Reflect to account for the change of triangle orientation in the new cell
reflect();
}
void Foam::particle::locate
(
const vector& position,
const vector* direction,
const label celli,
const bool boundaryFail,
const string boundaryMsg
)
{
celli_ = celli;
// Find the cell, if it has not been given
if (celli_ < 0)
{
celli_ = mesh_.cellTree().findInside(position);
}
if (celli_ < 0)
{
FatalErrorInFunction
<< "Cell not found for particle position " << position << "."
<< exit(FatalError);
}
// Put the particle at the cell centre and in a random tet
coordinates_ = barycentric(1, 0, 0, 0);
tetFacei_ = mesh_.cells()[celli_][0];
tetPti_ = 1;
facei_ = -1;
// Track to the injection point
track(position - mesh_.cellCentres()[celli_], 0);
if (!onFace())
{
return;
}
// We hit a boundary ...
if (boundaryFail)
{
FatalErrorInFunction << boundaryMsg << exit(FatalError);
}
else
{
// Re-do the track, but this time do the bit tangential to the
// direction/patch first. This gets us as close as possible to the
// original path/position.
if (direction == nullptr)
{
const polyPatch& p = mesh_.boundaryMesh()[patch()];
direction = &p.faceNormals()[p.whichFace(facei_)];
}
const vector n = *direction/mag(*direction);
const vector s = position - mesh_.cellCentres()[celli_];
const vector sN = (s & n)*n;
const vector sT = s - sN;
coordinates_ = barycentric(1, 0, 0, 0);
tetFacei_ = mesh_.cells()[celli_][0];
tetPti_ = 1;
facei_ = -1;
track(sT, 0);
track(sN, 0);
static label nWarnings = 0;
static const label maxNWarnings = 100;
if (nWarnings < maxNWarnings)
{
WarningInFunction << boundaryMsg << endl;
++ nWarnings;
}
if (nWarnings == maxNWarnings)
{
WarningInFunction
<< "Suppressing any further warnings about particles being "
<< "located outside of the mesh." << endl;
++ nWarnings;
}
}
}
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
Foam::particle::particle
(
const polyMesh& mesh,
const barycentric& coordinates,
const label celli,
const label tetFacei,
const label tetPti
)
:
mesh_(mesh),
coordinates_(coordinates),
celli_(celli),
tetFacei_(tetFacei),
tetPti_(tetPti),
facei_(-1),
stepFraction_(0.0),
origProc_(Pstream::myProcNo()),
origId_(getNewParticleID())
{}
Foam::particle::particle
(
const polyMesh& mesh,
const vector& position,
const label celli
)
:
mesh_(mesh),
coordinates_(- VGREAT, - VGREAT, - VGREAT, - VGREAT),
celli_(celli),
tetFacei_(-1),
tetPti_(-1),
facei_(-1),
stepFraction_(0.0),
origProc_(Pstream::myProcNo()),
origId_(getNewParticleID())
{
locate
(
position,
nullptr,
celli,
false,
"Particle initialised with a location outside of the mesh."
);
}
Foam::particle::particle(const particle& p)
:
mesh_(p.mesh_),
coordinates_(p.coordinates_),
celli_(p.celli_),
tetFacei_(p.tetFacei_),
tetPti_(p.tetPti_),
facei_(p.facei_),
stepFraction_(p.stepFraction_),
origProc_(p.origProc_),
origId_(p.origId_)
{}
Foam::particle::particle(const particle& p, const polyMesh& mesh)
:
mesh_(mesh),
coordinates_(p.coordinates_),
celli_(p.celli_),
tetFacei_(p.tetFacei_),
tetPti_(p.tetPti_),
facei_(p.facei_),
stepFraction_(p.stepFraction_),
origProc_(p.origProc_),
origId_(p.origId_)
{}
// * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
Foam::scalar Foam::particle::track
(
const vector& displacement,
const scalar fraction
)
{
scalar f = trackToFace(displacement, fraction);
while (onInternalFace())
{
changeCell();
f *= trackToFace(f*displacement, f*fraction);
}
return f;
}
Foam::scalar Foam::particle::trackToFace
(
const vector& displacement,
const scalar fraction
)
{
scalar f = 1;
label tetTriI = onFace() ? 0 : -1;
facei_ = -1;
while (true)
{
f *= trackToTri(f*displacement, f*fraction, tetTriI);
if (tetTriI == -1)
{
// The track has completed within the current tet
return 0;
}
else if (tetTriI == 0)
{
// The track has hit a face, so set the current face and return
facei_ = tetFacei_;
return f;
}
else
{
// Move to the next tet and continue the track
changeTet(tetTriI);
}
}
}
Foam::scalar Foam::particle::trackToStationaryTri
(
const vector& displacement,
const scalar fraction,
label& tetTriI
)
{
const vector x0 = position();
const vector x1 = displacement;
const barycentric y0 = coordinates_;
if (debug)
{
Info<< "Particle " << origId() << endl
<< "Tracking from " << x0 << " to " << x0 + x1 << endl;
}
// Get the tet geometry
vector centre;
scalar detA;
barycentricTensor T;
stationaryTetReverseTransform(centre, detA, T);
if (debug)
{
vector o, b, v1, v2;
stationaryTetGeometry(o, b, v1, v2);
Info<< "Tet points o=" << o << ", b=" << b
<< ", v1=" << v1 << ", v2=" << v2 << endl
<< "Tet determinant = " << detA << endl
<< "Start local coordinates = " << y0 << endl;
}
// Get the factor by which the displacement is increased
const scalar f = detA >= 0 ? 1 : negativeSpaceDisplacementFactor;
// Calculate the local tracking displacement
barycentric Tx1(f*x1 & T);
if (debug)
{
Info<< "Local displacement = " << Tx1 << "/" << detA << endl;
}
// Calculate the hit fraction
label iH = -1;
scalar muH = std::isnormal(detA) && detA <= 0 ? VGREAT : 1/detA;
for (label i = 0; i < 4; ++ i)
{
if (std::isnormal(Tx1[i]) && Tx1[i] < 0)
{
scalar mu = - y0[i]/Tx1[i];
if (debug)
{
Info<< "Hit on tet face " << i << " at local coordinate "
<< y0 + mu*Tx1 << ", " << mu*detA*100 << "\% of the "
<< "way along the track" << endl;
}
if (0 <= mu && mu < muH)
{
iH = i;
muH = mu;
}
}
}
// Set the new coordinates
barycentric yH = y0 + muH*Tx1;
// Clamp to zero any negative coordinates generated by round-off error
for (label i = 0; i < 4; ++ i)
{
yH.replace(i, i == iH ? 0 : max(0, yH[i]));
}
// Re-normalise if within the tet
if (iH == -1)
{
yH /= cmptSum(yH);
}
// Set the new position and hit index
coordinates_ = yH;
tetTriI = iH;
if (debug)
{
if (iH != -1)
{
Info<< "Track hit tet face " << iH << " first" << endl;
}
else
{
Info<< "Track hit no tet faces" << endl;
}
Info<< "End local coordinates = " << yH << endl
<< "End global coordinates = " << position() << endl
<< "Tracking displacement = " << position() - x0 << endl
<< muH*detA*100 << "\% of the step from " << stepFraction_ << " to "
<< stepFraction_ + fraction << " completed" << endl << endl;
}
// Set the proportion of the track that has been completed
stepFraction_ += fraction*muH*detA;
return iH != -1 ? 1 - muH*detA : 0;
}
Foam::scalar Foam::particle::trackToMovingTri
(
const vector& displacement,
const scalar fraction,
label& tetTriI
)
{
const vector x0 = position();
const vector x1 = displacement;
const barycentric y0 = coordinates_;
if (debug)
{
Info<< "Particle " << origId() << endl
<< "Tracking from " << x0 << " to " << x0 + x1 << endl;
}
// Get the tet geometry
Pair<vector> centre;
FixedList<scalar, 4> detA;
FixedList<barycentricTensor, 3> T;
movingTetReverseTransform(fraction, centre, detA, T);
// Get the factor by which the displacement is increased
const scalar f = detA[0] >= 0 ? 1 : negativeSpaceDisplacementFactor;
// Get the relative global position
const vector x0Rel = x0 - centre[0];
const vector x1Rel = f*x1 - centre[1];
// Form the determinant and hit equations
cubicEqn detAEqn(sqr(detA[0])*detA[3], detA[0]*detA[2], detA[1], 1);
const barycentric yC(1, 0, 0, 0);
const barycentric hitEqnA =
((x1Rel & T[2]) + detA[3]*yC)*sqr(detA[0]);
const barycentric hitEqnB =
((x1Rel & T[1]) + (x0Rel & T[2]) + detA[2]*yC)*detA[0];
const barycentric hitEqnC =
((x1Rel & T[0]) + (x0Rel & T[1]) + detA[1]*yC);
const barycentric hitEqnD = y0;
FixedList<cubicEqn, 4> hitEqn;
forAll(hitEqn, i)
{
hitEqn[i] = cubicEqn(hitEqnA[i], hitEqnB[i], hitEqnC[i], hitEqnD[i]);
}
// Calculate the hit fraction
label iH = -1;
scalar muH = std::isnormal(detA[0]) && detA[0] <= 0 ? VGREAT : 1/detA[0];
for (label i = 0; i < 4; ++ i)
{
const Roots<3> mu = hitEqn[i].roots();
for (label j = 0; j < 3; ++ j)
{
if (mu.type(j) == roots::real && hitEqn[i].derivative(mu[j]) < 0)
{
if (0 <= mu[j] && mu[j] < muH)
{
iH = i;
muH = mu[j];
}
}
}
}
// Set the new coordinates
barycentric yH
(
hitEqn[0].value(muH),
hitEqn[1].value(muH),
hitEqn[2].value(muH),
hitEqn[3].value(muH)
);
// !!! <-- This fails if the tet collapses onto the particle, as detA tends
// to zero at the hit. In this instance, we can differentiate the hit and
// detA polynomials to find a limiting location, but this will not be on a
// triangle. We will then need to do a second track through the degenerate
// tet to find the final hit position. This second track is over zero
// distance and therefore can be of the static mesh type. This has not yet
// been implemented.
const scalar detAH = detAEqn.value(muH);
if (!std::isnormal(detAH))
{
FatalErrorInFunction
<< "A moving tet collapsed onto a particle. This is not supported. "
<< "The mesh is too poor, or the motion too severe, for particle "
<< "tracking to function." << exit(FatalError);
}
yH /= detAH;
// Clamp to zero any negative coordinates generated by round-off error
for (label i = 0; i < 4; ++ i)
{
yH.replace(i, i == iH ? 0 : max(0, yH[i]));
}
// Re-normalise if within the tet
if (iH == -1)
{
yH /= cmptSum(yH);
}
// Set the new position and hit index
coordinates_ = yH;
tetTriI = iH;
// Set the proportion of the track that has been completed
stepFraction_ += fraction*muH*detA[0];
if (debug)
{
if (iH != -1)
{
Info<< "Track hit tet face " << iH << " first" << endl;
}
else
{
Info<< "Track hit no tet faces" << endl;
}
Info<< "End local coordinates = " << yH << endl
<< "End global coordinates = " << position() << endl;
}
return iH != -1 ? 1 - muH*detA[0] : 0;
}
Foam::scalar Foam::particle::trackToTri
(
const vector& displacement,
const scalar fraction,
label& tetTriI
)
{
if (mesh_.moving())
{
return trackToMovingTri(displacement, fraction, tetTriI);
}
else
{
return trackToStationaryTri(displacement, fraction, tetTriI);
}
}
void Foam::particle::constrainToMeshCentre()
{
const Vector<label>& dirs = mesh_.geometricD();
bool isConstrained = false;
forAll(dirs, dirI)
{
if (dirs[dirI] == -1)
{
isConstrained = true;
break;
}
}
if (isConstrained)
{
vector pos = position();
meshTools::constrainToMeshCentre(mesh_, pos);
track(pos - position(), 0);
}
}
void Foam::particle::patchData(vector& n, vector& U) const
{
if (!onBoundaryFace())
{
FatalErrorInFunction
<< "Patch data was requested for a particle that isn't on a patch"
<< exit(FatalError);
}
if (mesh_.moving())
{
Pair<vector> centre, base, vertex1, vertex2;
movingTetGeometry(1, centre, base, vertex1, vertex2);
n = triPointRef(base[0], vertex1[0], vertex2[0]).normal();
n /= mag(n);
// Interpolate the motion of the three face vertices to the current
// coordinates
U =
coordinates_.b()*base[1]
+ coordinates_.c()*vertex1[1]
+ coordinates_.d()*vertex2[1];
// The movingTetGeometry method gives the motion as a displacement
// across the time-step, so we divide by the time-step to get velocity
U /= mesh_.time().deltaTValue();
}
else
{
vector centre, base, vertex1, vertex2;
stationaryTetGeometry(centre, base, vertex1, vertex2);
n = triPointRef(base, vertex1, vertex2).normal();
n /= mag(n);
U = Zero;
}
}
void Foam::particle::transformProperties(const tensor&)
{}
void Foam::particle::transformProperties(const vector&)
{}
Foam::scalar Foam::particle::wallImpactDistance(const vector&) const
{
return 0.0;
}
void Foam::particle::prepareForInteractionListReferral
(
const vectorTensorTransform& transform
)
{
// Get the transformed position
const vector pos = transform.invTransformPosition(position());
// Break the topology
celli_ = -1;
tetFacei_ = -1;
tetPti_ = -1;
facei_ = -1;
// Store the position in the barycentric data
coordinates_ = barycentric(1 - cmptSum(pos), pos.x(), pos.y(), pos.z());
// Transform the properties
transformProperties(- transform.t());
if (transform.hasR())
{
transformProperties(transform.R().T());
}
}
void Foam::particle::correctAfterInteractionListReferral(const label celli)
{
// Get the position from the barycentric data
const vector pos(coordinates_.b(), coordinates_.c(), coordinates_.d());
// Create some arbitrary topology for the supplied cell
celli_ = celli;
tetFacei_ = mesh_.cells()[celli_][0];
tetPti_ = 1;
facei_ = -1;
// Get the reverse transform and directly set the coordinates from the
// position. This isn't likely to be correct; the particle is probably not
// in this tet. It will, however, generate the correct vector when the
// position method is called. A referred particle should never be tracked,
// so this approximate topology is good enough. By using the nearby cell we
// minimise the error associated with the incorrect topology.
coordinates_ = barycentric(1, 0, 0, 0);
if (mesh_.moving())
{
Pair<vector> centre;
FixedList<scalar, 4> detA;
FixedList<barycentricTensor, 3> T;
movingTetReverseTransform(0, centre, detA, T);
coordinates_ += (pos - centre[0]) & T[0]/detA[0];
}
else
{
vector centre;
scalar detA;
barycentricTensor T;
stationaryTetReverseTransform(centre, detA, T);
coordinates_ += (pos - centre) & T/detA;
}
}
Foam::label Foam::particle::procTetPt
(
const polyMesh& procMesh,
const label procCell,
const label procTetFace
) const
{
// The tet point on the procMesh differs from the current tet point if the
// mesh and procMesh faces are of differing orientation. The change is the
// same as in particle::correctAfterParallelTransfer.
if
(
(mesh_.faceOwner()[tetFacei_] == celli_)
== (procMesh.faceOwner()[procTetFace] == procCell)
)
{
return tetPti_;
}
else
{
return procMesh.faces()[procTetFace].size() - 1 - tetPti_;
}
}
void Foam::particle::autoMap
(
const vector& position,
const mapPolyMesh& mapper
)
{
locate
(
position,
nullptr,
mapper.reverseCellMap()[celli_],
true,
"Particle mapped to a location outside of the mesh."
);
}
// * * * * * * * * * * * * * * Friend Operators * * * * * * * * * * * * * * //
bool Foam::operator==(const particle& pA, const particle& pB)
{
return (pA.origProc() == pB.origProc() && pA.origId() == pB.origId());
}
bool Foam::operator!=(const particle& pA, const particle& pB)
{
return !(pA == pB);
}
// ************************************************************************* //