Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:14:10

0001 /*
0002  * PolyhedronStruct.h
0003  *
0004  *  Created on: 09.12.2016
0005  *      Author: mgheata
0006  */
0007 #ifndef VECGEOM_POLYHEDRONSTRUCT_H_
0008 #define VECGEOM_POLYHEDRONSTRUCT_H_
0009 
0010 #include <ostream>
0011 
0012 #include <VecCore/VecCore>
0013 #include "VecGeom/base/Vector3D.h"
0014 #include "VecGeom/volumes/Quadrilaterals.h"
0015 #include "VecGeom/volumes/Wedge_Evolution.h"
0016 #include "VecGeom/base/Array.h"
0017 #include "VecGeom/base/SOA3D.h"
0018 #include "VecGeom/volumes/TubeStruct.h"
0019 
0020 // These enums should be in the scope vecgeom::Polyhedron, but when used in the
0021 // shape implementation helper instantiations, nvcc gets confused:
0022 
0023 enum struct EInnerRadii { kFalse = -1, kGeneric = 0, kTrue = 1 };
0024 enum struct EPhiCutout { kFalse = -1, kGeneric = 0, kTrue = 1, kLarge = 2 };
0025 
0026 namespace vecgeom {
0027 
0028 VECGEOM_DEVICE_FORWARD_DECLARE(struct ZSegment;);
0029 VECGEOM_DEVICE_DECLARE_CONV(struct, ZSegment);
0030 
0031 // Declare types shared by cxx and cuda.
0032 namespace Polyhedron {
0033 using ::EInnerRadii;
0034 using ::EPhiCutout;
0035 } // namespace Polyhedron
0036 
0037 inline namespace VECGEOM_IMPL_NAMESPACE {
0038 
0039 /// Represents one segment along the Z-axis, containing one or more sets of
0040 /// quadrilaterals that represent the outer, inner and phi shells.
0041 struct ZSegment {
0042   Quadrilaterals outer; ///< Should always be non-empty.
0043   Quadrilaterals phi;   ///< Is empty if fHasPhiCutout is false.
0044   Quadrilaterals inner; ///< Is empty hasInnerRadius is false.
0045 
0046   VECCORE_ATT_HOST_DEVICE
0047   bool hasInnerRadius() const { return inner.size() > 0; }
0048 };
0049 
0050 // a plain and lightweight struct to encapsulate data members of a polyhedron
0051 template <typename T = double>
0052 struct PolyhedronStruct {
0053   int fSideCount;              ///< Number of segments along phi.
0054   bool fHasInnerRadii;         ///< Has any Z-segments with an inner radius != 0.
0055   bool fHasPhiCutout;          ///< Has a cutout angle along phi.
0056   bool fHasLargePhiCutout;     ///< Phi cutout is larger than pi.
0057   T fPhiStart;                 ///< Phi start in radians (input to constructor)
0058   T fPhiDelta;                 ///< Phi delta in radians (input to constructor)
0059   evolution::Wedge fPhiWedge;  ///< Phi wedge
0060   Array<ZSegment> fZSegments;  ///< AOS'esque collections of quadrilaterals
0061   Array<T> fZPlanes;           ///< Z-coordinate of each plane separating segments
0062   Array<T> fRMin;              ///< Inner radii as specified in constructor.
0063   Array<T> fRMax;              ///< Outer radii as specified in constructor.
0064   Array<bool> fSameZ;          ///< Array of flags marking that the following plane is at same Z
0065   SOA3D<T> fPhiSections;       ///< Unit vectors marking the bounds between
0066                                ///  phi segments, represented by planes
0067                                ///  through the origin with the normal
0068                                ///  point along the positive phi direction.
0069   TubeStruct<T> fBoundingTube; ///< Tube enclosing the outer bounds of the
0070                                ///  polyhedron. Used in Contains, Inside and
0071                                ///  DistanceToIn.
0072   T fBoundingTubeOffset;       ///< Offset in Z of the center of the bounding
0073                                ///  tube. Used as a quick substitution for
0074                                ///  running a full transformation.
0075 
0076   /// Internal structure to cache component surface areas per Z segment
0077   struct AreaStruct {
0078     Precision area        = 0.;      ///< Cached total surface area
0079     Precision top_area    = 0.;      ///< Area of top surface
0080     Precision bottom_area = 0.;      ///< Area of top surface
0081     Precision *outer      = nullptr; ///< Array of surface areas for the auter part
0082     Precision *inner      = nullptr; ///< Array of surface areas for the inner part
0083     Precision *phi        = nullptr; ///< Array of surface areas for the phi part
0084 
0085     AreaStruct(int nseg)
0086     {
0087       inner = new Precision[nseg];
0088       outer = new Precision[nseg];
0089       phi   = new Precision[nseg];
0090     }
0091 
0092     VECCORE_ATT_HOST_DEVICE
0093     ~AreaStruct()
0094     {
0095       delete[] inner;
0096       delete[] outer;
0097       delete[] phi;
0098     }
0099   };
0100 
0101   mutable AreaStruct *fAreaStruct = nullptr; ///< Cached surface area values
0102   mutable Precision fCapacity     = 0.;      ///< Stored Capacity
0103 
0104   // These data member and member functions are added for convexity detection
0105   bool fContinuousInSlope;
0106   bool fConvexityPossible;
0107   bool fEqualRmax;
0108 
0109   VECCORE_ATT_HOST_DEVICE
0110   PolyhedronStruct()
0111       : fSideCount(0), fHasInnerRadii(false), fHasPhiCutout(false), fHasLargePhiCutout(false), fPhiStart(0),
0112         fPhiDelta(0), fPhiWedge(0., 0.), fBoundingTube(0, 0, 0, 0, 0), fBoundingTubeOffset(0)
0113   {
0114   }
0115 
0116   VECCORE_ATT_HOST_DEVICE
0117   PolyhedronStruct(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0118                    Precision const zPlanes[], Precision const rMin[], Precision const rMax[])
0119       : fSideCount(sideCount), fHasInnerRadii(false), fHasPhiCutout(phiDelta < kTwoPi),
0120         fHasLargePhiCutout(phiDelta < kPi), fPhiStart(NormalizeAngle<kScalar>(phiStart)),
0121         fPhiDelta((phiDelta > kTwoPi) ? kTwoPi : phiDelta), fPhiWedge(fPhiDelta, fPhiStart),
0122         fZSegments(zPlaneCount - 1), fZPlanes(zPlaneCount), fRMin(zPlaneCount), fRMax(zPlaneCount),
0123         fPhiSections(sideCount + 1), fBoundingTube(0, 1, 1, fPhiStart, fPhiDelta), fContinuousInSlope(true),
0124         fConvexityPossible(true), fEqualRmax(true)
0125   {
0126     // initialize polyhedron internals
0127     Initialize(phiStart, phiDelta, sideCount, zPlaneCount, zPlanes, rMin, rMax);
0128   }
0129 
0130   PolyhedronStruct(Precision phiStart, Precision phiDelta, const int sideCount, const int verticesCount,
0131                    Precision const r[], Precision const z[])
0132       : fSideCount(sideCount), fHasInnerRadii(false), fHasPhiCutout(phiDelta < kTwoPi),
0133         fHasLargePhiCutout(phiDelta < kPi), fPhiStart(NormalizeAngle<kScalar>(phiStart)),
0134         fPhiDelta((phiDelta > kTwoPi) ? kTwoPi : phiDelta), fPhiWedge(fPhiDelta, fPhiStart), fZSegments(), fZPlanes(),
0135         fRMin(), fRMax(), fPhiSections(sideCount + 1), fBoundingTube(0, 1, 1, fPhiStart, fPhiDelta),
0136         fContinuousInSlope(true), fConvexityPossible(true), fEqualRmax(true)
0137   {
0138     if (verticesCount < 3) throw std::runtime_error("A Polyhedron needs at least 3 (rz) vertices");
0139 
0140     // Geant4-like construction (n = verticesCount). The rz section is described
0141     // as a sequence of connected vertices (r[i], z[i]). We have to associate
0142     // the vertices with (rmin, rmax, z) plane representation.
0143 
0144     // detect if vertices are defined clockwise
0145     Precision area = 0;
0146     for (int i = 0; i < verticesCount; ++i) {
0147       int j = (i + 1) % verticesCount;
0148       area += r[i] * z[j] - r[j] * z[i];
0149     }
0150 
0151     bool cw      = (area < 0);
0152     int inc      = cw ? -1 : 1;
0153     Precision zt = z[0];
0154     Precision zb = z[0];
0155     // Find min/max on Z
0156     for (int i = 0; i < verticesCount; ++i) {
0157       if (z[i] > zt) zt = z[i];
0158       if (z[i] < zb) zb = z[i];
0159     }
0160 
0161     // Add implicit vertices
0162     Precision *rnew    = new Precision[2 * verticesCount];
0163     Precision *znew    = new Precision[2 * verticesCount];
0164     int verticesCount1 = 0;
0165     for (int i0 = 0; i0 < verticesCount; ++i0) {
0166       rnew[verticesCount1]   = r[i0];
0167       znew[verticesCount1++] = z[i0];
0168       // Check if top/bottom vertex is singular
0169       if (vecCore::math::Abs(z[i0] - zt) < kTolerance || vecCore::math::Abs(z[i0] - zb) < kTolerance) {
0170         if (vecCore::math::Abs(z[i0] - z[(i0 + verticesCount - 1) % verticesCount]) > kTolerance &&
0171             vecCore::math::Abs(z[i0] - z[(i0 + 1) % verticesCount]) > kTolerance) {
0172           rnew[verticesCount1]   = r[i0];
0173           znew[verticesCount1++] = z[i0];
0174         }
0175       }
0176       int i1       = (i0 + 1) % verticesCount;
0177       Precision dz = z[i1] - z[i0];
0178       if (vecCore::math::Abs(dz) < kTolerance) continue;
0179       Precision zmin = vecCore::math::Min(z[i0], z[i1]);
0180       Precision zmax = vecCore::math::Max(z[i0], z[i1]);
0181       for (int j = 0; j < verticesCount - 2; ++j) {
0182         // go backward
0183         int k = (i0 - 1 - j + verticesCount) % verticesCount;
0184         if (z[k] > zmin + kTolerance && z[k] < zmax - kTolerance) {
0185           // Project the vertex on current segment to get a new vertex
0186           Precision rp = r[i0] + (r[i1] - r[i0]) * (z[k] - z[i0]) / dz;
0187           assert(rp >= 0);
0188           // We need to insert point (rp, z[k]) after i1
0189           rnew[verticesCount1]   = rp;
0190           znew[verticesCount1++] = z[k];
0191         }
0192       }
0193     }
0194 
0195     // detect index of outer vertex with minimum Z
0196     int i0 = -1;
0197     for (int i = 0; i < verticesCount1; ++i) {
0198       if (znew[i] == zb) {
0199         i0 = i;
0200         break;
0201       }
0202     }
0203     if (vecCore::math::Abs(zb - znew[(i0 + inc) % verticesCount1]) < kTolerance) i0 = (i0 + inc) % verticesCount1;
0204 
0205     if (phiDelta > kTwoPi) phiDelta = kTwoPi;
0206     Precision sidePhi         = phiDelta / sideCount;
0207     Precision cosHalfDeltaPhi = cos(0.5 * sidePhi);
0208 
0209     // We count vertices starting from imin, making sure we move counter-clockwise
0210 
0211     int Nz          = verticesCount1 / 2;
0212     Precision *rMin = new Precision[Nz];
0213     Precision *rMax = new Precision[Nz];
0214     Precision *zArg = new Precision[Nz];
0215 
0216     for (int i = 0; i < Nz; ++i) {
0217       // Current vertex index going always ccw from (rmin,zmin)
0218       int j    = (i0 + verticesCount1 + inc * i) % verticesCount1;
0219       int jsim = (i0 + verticesCount1 + inc * (verticesCount1 - 1 - i)) % verticesCount1;
0220       assert(znew[j] == znew[jsim]);
0221       zArg[i] = znew[j];
0222       rMax[i] = rnew[j] * cosHalfDeltaPhi;
0223       rMin[i] = rnew[jsim] * cosHalfDeltaPhi;
0224       assert(rMax[i] >= rMin[i] &&
0225              "UnplPolycone ERROR: r[] provided has problems of the Rmax < Rmin type, please check!\n");
0226     }
0227 
0228     // Allocate arrays
0229     fZSegments.Allocate(Nz - 1);
0230     fZPlanes.Allocate(Nz);
0231     fRMin.Allocate(Nz);
0232     fRMax.Allocate(Nz);
0233 
0234     // Delegate to full constructor
0235     Initialize(phiStart, phiDelta, sideCount, Nz, zArg, rMin, rMax);
0236     delete[] rnew;
0237     delete[] znew;
0238     delete[] rMin;
0239     delete[] rMax;
0240     delete[] zArg;
0241   }
0242 
0243   VECCORE_ATT_HOST_DEVICE
0244   ~PolyhedronStruct() { delete fAreaStruct; }
0245 
0246   VECCORE_ATT_HOST_DEVICE
0247   bool CheckContinuityInSlope(const Precision rOuter[], const Precision zPlane[], const unsigned int nz)
0248   {
0249 
0250     Precision prevSlope = kInfLength;
0251     for (unsigned int j = 0; j < nz - 1; ++j) {
0252       if (zPlane[j + 1] == zPlane[j]) {
0253         if (rOuter[j + 1] != rOuter[j]) return false;
0254       } else {
0255         Precision currentSlope = (rOuter[j + 1] - rOuter[j]) / (zPlane[j + 1] - zPlane[j]);
0256         if (currentSlope > prevSlope) return false;
0257         prevSlope = currentSlope;
0258       }
0259     }
0260     return true;
0261   }
0262 
0263   // This method does the proper construction of planes and segments.
0264   // Used by multiple constructors.
0265   VECCORE_ATT_HOST_DEVICE
0266   void Initialize(Precision phiStart, Precision phiDelta, const int sideCount, const int zPlaneCount,
0267                   Precision const zPlanes[], Precision const rMin[], Precision const rMax[])
0268   {
0269     typedef Vector3D<Precision> Vec_t;
0270 
0271     // Sanity check of input parameters
0272     assert(zPlaneCount > 1);
0273     assert(fSideCount > 0);
0274 
0275     copy(zPlanes, zPlanes + zPlaneCount, &fZPlanes[0]);
0276     copy(rMin, rMin + zPlaneCount, &fRMin[0]);
0277     copy(rMax, rMax + zPlaneCount, &fRMax[0]);
0278     fSameZ.Allocate(zPlaneCount);
0279 
0280     Precision startRmax = rMax[0];
0281     for (int i = 0; i < zPlaneCount; i++) {
0282       fConvexityPossible &= (rMin[i] == 0.);
0283       fEqualRmax &= (startRmax == rMax[i]);
0284       fSameZ[i] = false;
0285       if (i > 0 && i < zPlaneCount - 1 && fZPlanes[i] == fZPlanes[i + 1]) fSameZ[i] = true;
0286     }
0287     fContinuousInSlope = CheckContinuityInSlope(rMax, zPlanes, zPlaneCount);
0288 
0289     // Initialize segments
0290     // sometimes there will be no quadrilaterals: for instance when
0291     // rmin jumps at some z and rmax remains continouus
0292     for (int i = 0; i < zPlaneCount - 1; ++i) {
0293       // Z-planes must be monotonically increasing
0294       assert(zPlanes[i] <= zPlanes[i + 1]);
0295 
0296       bool hasInnerRadius = rMin[i] > 0 || rMin[i + 1] > 0;
0297 
0298       int multiplier = (zPlanes[i] == zPlanes[i + 1] && rMax[i] == rMax[i + 1]) ? 0 : 1;
0299 
0300       // create quadrilaterals in a predefined place with placement new
0301       new (&fZSegments[i].outer) Quadrilaterals(sideCount * multiplier);
0302 
0303       // no phi segment here if degenerate z;
0304       if (fHasPhiCutout) {
0305         multiplier = (zPlanes[i] == zPlanes[i + 1]) ? 0 : 1;
0306         new (&fZSegments[i].phi) Quadrilaterals(2 * multiplier, phiDelta <= kPi);
0307       }
0308 
0309       multiplier = (zPlanes[i] == zPlanes[i + 1] && rMin[i] == rMin[i + 1]) ? 0 : 1;
0310 
0311       if (hasInnerRadius && multiplier > 0) {
0312         new (&fZSegments[i].inner) Quadrilaterals(sideCount * multiplier);
0313         fHasInnerRadii = true;
0314       } else {
0315         new (&fZSegments[i].inner) Quadrilaterals(0);
0316       }
0317     }
0318 
0319     // Compute the cylindrical coordinate phi along which the corners are placed
0320     assert(phiDelta > 0);
0321     phiStart = NormalizeAngle<kScalar>(phiStart);
0322     if (phiDelta > kTwoPi) phiDelta = kTwoPi;
0323     Precision sidePhi = phiDelta / sideCount;
0324 
0325     auto getPhi = [&](int side) {
0326       if (!fHasPhiCutout && side == sideCount) {
0327         side = 0;
0328       }
0329       return NormalizeAngle<kScalar>(phiStart + side * sidePhi);
0330     };
0331 
0332     for (int i = 0, iMax = sideCount + 1; i < iMax; ++i) {
0333       Vector3D<Precision> cornerVector = Vec_t::FromCylindrical(1., getPhi(i), 0).Normalized().FixZeroes();
0334       fPhiSections.set(i, cornerVector.Normalized().Cross(Vector3D<Precision>(0, 0, -1)));
0335     }
0336 
0337     // Specified radii are to the sides, not to the corners. Change these values,
0338     // as corners and not sides are used to build the structure
0339     Precision cosHalfDeltaPhi = cos(0.5 * sidePhi);
0340     Precision innerRadius = kInfLength, outerRadius = -kInfLength;
0341     for (int i = 0; i < zPlaneCount; ++i) {
0342       // Use distance to side for minimizing inner radius of bounding tube
0343       if (rMin[i] < innerRadius) innerRadius = rMin[i];
0344       // rMin[i] /= cosHalfDeltaPhi;
0345       // rMax[i] /= cosHalfDeltaPhi;
0346       assert(rMin[i] >= 0 && rMax[i] >= 0);
0347       // Use distance to corner for minimizing outer radius of bounding tube
0348       if (rMax[i] > outerRadius) outerRadius = rMax[i];
0349     }
0350     // need to convert from distance to planes to real radius in case of outerradius
0351     // the inner radius of the bounding tube is given by min(rMin[])
0352     outerRadius /= cosHalfDeltaPhi;
0353 
0354     // Create bounding tube with biggest outer radius and smallest inner radius
0355     Precision boundingTubeZ = 0.5 * (zPlanes[zPlaneCount - 1] - zPlanes[0]) + kTolerance;
0356     // Make bounding tube phi range a bit larger to contain all points on phi boundaries
0357     const Precision kPhiTolerance = 100 * kTolerance;
0358     // The increase in the angle has to be large enough to contain most of
0359     // kSurface points. There will be some points close to the Z axis which will
0360     // not be contained. The value is empirical to satisfy ShapeTester
0361     Precision boundsPhiStart = !fHasPhiCutout ? 0 : phiStart - kPhiTolerance;
0362     Precision boundsPhiDelta = !fHasPhiCutout ? kTwoPi : phiDelta + 2 * kPhiTolerance;
0363     // correct inner and outer Radius with conversion factor
0364     // innerRadius /= cosHalfDeltaPhi;
0365     // outerRadius /= cosHalfDeltaPhi;
0366 
0367     fBoundingTube = TubeStruct<Precision>(innerRadius - kHalfTolerance, outerRadius + kHalfTolerance, boundingTubeZ,
0368                                           boundsPhiStart, boundsPhiDelta);
0369 
0370     // The offset has to match the middle of the polyhedron
0371     fBoundingTubeOffset = 0.5 * (zPlanes[0] + zPlanes[zPlaneCount - 1]);
0372 
0373     auto getVertexImpl = [&](Precision const r[], int i, int j) {
0374       if (!fHasPhiCutout && j == sideCount) {
0375         j = 0;
0376       }
0377       return Vec_t::FromCylindrical(r[i] / cosHalfDeltaPhi, getPhi(j), zPlanes[i]).FixZeroes();
0378     };
0379 
0380     auto getInnerVertex = [&](int i, int j) { return getVertexImpl(rMin, i, j); };
0381     auto getOuterVertex = [&](int i, int j) { return getVertexImpl(rMax, i, j); };
0382 
0383     // Build segments by drawing quadrilaterals between vertices
0384     for (int iPlane = 0; iPlane < zPlaneCount - 1; ++iPlane) {
0385 
0386       auto WrongNormal = [](Vector3D<Precision> const &normal, Vector3D<Precision> const &corner) {
0387         return normal[0] * corner[0] + normal[1] * corner[1] < 0;
0388       };
0389 
0390       // Draw the regular quadrilaterals along phi
0391       for (int iSide = 0; iSide < fZSegments[iPlane].outer.size(); ++iSide) {
0392         fZSegments[iPlane].outer.Set(iSide, getOuterVertex(iPlane, iSide), getOuterVertex(iPlane, iSide + 1),
0393                                      getOuterVertex(iPlane + 1, iSide + 1), getOuterVertex(iPlane + 1, iSide));
0394         // Normal has to point away from Z-axis
0395         if (WrongNormal(fZSegments[iPlane].outer.GetNormal(iSide), getOuterVertex(iPlane, iSide))) {
0396           fZSegments[iPlane].outer.FlipSign(iSide);
0397         }
0398       }
0399       for (int iSide = 0; iSide < fZSegments[iPlane].inner.size(); ++iSide) {
0400         fZSegments[iPlane].inner.Set(iSide, getInnerVertex(iPlane, iSide), getInnerVertex(iPlane, iSide + 1),
0401                                      getInnerVertex(iPlane + 1, iSide + 1), getInnerVertex(iPlane + 1, iSide));
0402         // Normal has to point away from Z-axis
0403         if (WrongNormal(fZSegments[iPlane].inner.GetNormal(iSide), getInnerVertex(iPlane, iSide))) {
0404           fZSegments[iPlane].inner.FlipSign(iSide);
0405         }
0406       }
0407 
0408       if (fHasPhiCutout && fZSegments[iPlane].phi.size() == 2) {
0409         // If there's a phi cutout, draw two quadrilaterals connecting the four
0410         // corners (two inner, two outer) of the first and last phi coordinate,
0411         // respectively
0412         fZSegments[iPlane].phi.Set(0, getInnerVertex(iPlane, 0), getInnerVertex(iPlane + 1, 0),
0413                                    getOuterVertex(iPlane + 1, 0), getOuterVertex(iPlane, 0));
0414         // Make sure normal points backwards along phi
0415         if (fZSegments[iPlane].phi.GetNormal(0).Dot(fPhiSections[0]) > 0) {
0416           fZSegments[iPlane].phi.FlipSign(0);
0417         }
0418         fZSegments[iPlane].phi.Set(1, getOuterVertex(iPlane, sideCount), getOuterVertex(iPlane + 1, sideCount),
0419                                    getInnerVertex(iPlane + 1, sideCount), getInnerVertex(iPlane, sideCount));
0420         // Make sure normal points forwards along phi
0421         if (fZSegments[iPlane].phi.GetNormal(1).Dot(fPhiSections[fSideCount]) < 0) {
0422           fZSegments[iPlane].phi.FlipSign(1);
0423         }
0424       }
0425 
0426     } // End loop over segments
0427   }
0428 };
0429 } // namespace VECGEOM_IMPL_NAMESPACE
0430 } // namespace vecgeom
0431 
0432 #endif