/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | Copyright (C) 2011-2017 OpenFOAM Foundation \\/ M anipulation | Copyright (C) 2018 OpenCFD Ltd. ------------------------------------------------------------------------------- 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 . \*---------------------------------------------------------------------------*/ #include "particle.H" #include "transform.H" #include "treeDataCell.H" #include "cubicEqn.H" #include "registerSwitch.H" // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // namespace Foam { defineTypeNameAndDebug(particle, 0); } const Foam::scalar Foam::particle::negativeSpaceDisplacementFactor = 1.01; Foam::label Foam::particle::particleCount_ = 0; bool Foam::particle::writeLagrangianCoordinates = true; bool Foam::particle::writeLagrangianPositions ( Foam::debug::infoSwitch("writeLagrangianPositions", 0) ); registerInfoSwitch ( "writeLagrangianPositions", bool, Foam::particle::writeLagrangianPositions ); // * * * * * * * * * * * * * 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& centre, Pair& base, Pair& vertex1, Pair& 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 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::particle::movingTetTransform ( const scalar fraction ) const { Pair centre, base, vertex1, vertex2; movingTetGeometry(fraction, centre, base, vertex1, vertex2); return Pair ( 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& centre, FixedList& detA, FixedList& T ) const { Pair A = movingTetTransform(fraction); const Pair ab(A[0].b() - A[0].a(), A[1].b() - A[1].a()); const Pair ac(A[0].c() - A[0].a(), A[1].c() - A[1].a()); const Pair ad(A[0].d() - A[0].a(), A[1].d() - A[1].a()); const Pair bc(A[0].c() - A[0].b(), A[1].c() - A[1].b()); const Pair 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]; const label newOwner = mesh_.faceOwner()[newFaceI]; // Exclude the current face if (tetFacei_ == newFaceI) { continue; } // Loop over the edges, looking for the shared one. Note that we have to // match the direction of the edge as well as the end points in order to // avoid false positives when dealing with coincident ACMI faces. const label edgeComp = newOwner == celli_ ? -1 : +1; label edgeI = 0; for ( ; edgeI < newFace.size() && edge::compare(sharedEdge, newFace.faceEdge(edgeI)) != edgeComp; ++ edgeI ); // If the face does not contain the edge, then move on to the next face if (edgeI >= newFace.size()) { 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 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::changeToMasterPatch() { label thisPatch = patch(); forAll(mesh_.cells()[celli_], cellFaceI) { // Skip the current face and any internal faces const label otherFaceI = mesh_.cells()[celli_][cellFaceI]; if (facei_ == otherFaceI || mesh_.isInternalFace(otherFaceI)) { continue; } // Compare the two faces. If they are the same, chose the one with the // lower patch index. In the case of an ACMI-wall pair, this will be // the ACMI, which is what we want. const class face& thisFace = mesh_.faces()[facei_]; const class face& otherFace = mesh_.faces()[otherFaceI]; if (face::compare(thisFace, otherFace) != 0) { const label otherPatch = mesh_.boundaryMesh().whichPatch(otherFaceI); if (thisPatch > otherPatch) { facei_ = otherFaceI; thisPatch = otherPatch; } } } tetFacei_ = facei_; } 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 << " when tracking from centre " << mesh_.cellCentres()[celli_] << " of cell " << celli_ << " to position " << position << 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 polyMesh& mesh, const vector& position, const label celli, const label tetFacei, const label tetPti, bool doLocate ) : mesh_(mesh), coordinates_(-VGREAT, -VGREAT, -VGREAT, -VGREAT), celli_(celli), tetFacei_(tetFacei), tetPti_(tetPti), facei_(-1), stepFraction_(0.0), origProc_(Pstream::myProcNo()), origId_(getNewParticleID()) { if (doLocate) { 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 << " along " << x1 << " 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 << " along " << x1 << " to " << x0 + x1 << endl; } // Get the tet geometry Pair centre; FixedList detA; FixedList 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 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); } } Foam::vector Foam::particle::deviationFromMeshCentre() const { if (cmptMin(mesh_.geometricD()) == -1) { vector pos = position(), posC = pos; meshTools::constrainToMeshCentre(mesh_, posC); return pos - posC; } else { return vector::zero; } } 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 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&) {} void Foam::particle::prepareForParallelTransfer() { // Convert the face index to be local to the processor patch facei_ = mesh_.boundaryMesh()[patch()].whichFace(facei_); } void Foam::particle::correctAfterParallelTransfer ( const label patchi, trackingData& td ) { const coupledPolyPatch& ppp = refCast(mesh_.boundaryMesh()[patchi]); if (!ppp.parallel()) { const tensor& T = ( ppp.forwardT().size() == 1 ? ppp.forwardT()[0] : ppp.forwardT()[facei_] ); transformProperties(T); } else if (ppp.separated()) { const vector& s = ( (ppp.separation().size() == 1) ? ppp.separation()[0] : ppp.separation()[facei_] ); transformProperties(-s); } // Set the topology celli_ = ppp.faceCells()[facei_]; facei_ += ppp.start(); tetFacei_ = facei_; // Faces either side of a coupled patch are numbered in opposite directions // as their normals both point away from their connected cells. The tet // point therefore counts in the opposite direction from the base point. tetPti_ = mesh_.faces()[tetFacei_].size() - 1 - tetPti_; // Reflect to account for the change of triangle orientation in the new cell reflect(); // Note that the position does not need transforming explicitly. The face- // triangle on the receive patch is the transformation of the one on the // send patch, so whilst the barycentric coordinates remain the same, the // change of triangle implicitly transforms the position. } 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 centre; FixedList detA; FixedList 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." ); } void Foam::particle::relocate(const point& position) { locate ( position, nullptr, 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); } // ************************************************************************* //