Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 11:00:28

0001 //===-- VecGeom/volumes/utilities/AABBSubdivider.h ------------------*- C++ -*-===//
0002 //
0003 /// @file   AABBSubdivider.h
0004 /// @brief  Split a placed volume's bounding box into conservative clipped sub-boxes.
0005 ///
0006 /// A single axis-aligned bounding box around an elongated, rotated or
0007 /// diagonally-placed solid encloses empty space. AABBSubdivider replaces that
0008 /// one loose box by a small collection of mother-frame slab boxes. Each slab is
0009 /// computed from the solid's host-only CalculateClippedExtent() contract; no
0010 /// sampling or Inside() probing is used here.
0011 ///
0012 /// Main entry points:
0013 ///   Subdivide(placed, M)   -> exactly M slabs along the best axis.
0014 ///   SubdivideAuto(placed)  -> chooses M and axis from the configured
0015 ///                             build/navigation trade-off mode.
0016 /// Both return boxes in the mother frame; M = 1 gives the plain transformed
0017 /// Extent() AABB used by the current BVH baseline.
0018 //
0019 //===--------------------------------------------------------------------------===//
0020 
0021 #pragma once
0022 
0023 #include "VecGeom/base/Vector3D.h"
0024 #include "VecGeom/base/Transformation3D.h"
0025 #include "VecGeom/management/BVHSubdivisionOptions.h"
0026 #include "VecGeom/volumes/PlacedVolume.h"
0027 #include "VecGeom/volumes/LogicalVolume.h"
0028 #include "VecGeom/volumes/UnplacedVolume.h"
0029 #include "VecGeom/volumes/utilities/ClippedExtent.h"
0030 
0031 #include <algorithm>
0032 #include <cassert>
0033 #include <cmath>
0034 #include <limits>
0035 #include <utility>
0036 #include <vector>
0037 
0038 namespace vecgeom {
0039 inline namespace cxx {
0040 
0041 /// One conservative axis-aligned bounding box in the mother frame.
0042 struct SlabBox {
0043   Vector3D<Precision> min{kInfLength, kInfLength, kInfLength};
0044   Vector3D<Precision> max{-kInfLength, -kInfLength, -kInfLength};
0045   int slabIndex{0}; ///< 0-based within the generated slabs.
0046 
0047   bool Valid() const { return min[0] <= max[0] && min[1] <= max[1] && min[2] <= max[2]; }
0048   Precision SurfaceArea() const
0049   {
0050     if (!Valid()) return Precision(0);
0051     auto d = max - min;
0052     return Precision(2) * (d[0] * d[1] + d[1] * d[2] + d[2] * d[0]);
0053   }
0054   Vector3D<Precision> HalfExtents() const { return (max - min) * Precision(0.5); }
0055   Vector3D<Precision> Center() const { return (max + min) * Precision(0.5); }
0056 };
0057 
0058 /// Diagnostic result returned by SubdivideAuto.
0059 ///
0060 /// `slabs` is always conservative. When no accepted split satisfies the mode's
0061 /// reduction and memory objective, the result contains the single transformed
0062 /// local-extent AABB with `chosenM == 1`.
0063 struct AABBSubdivisionResult {
0064   std::vector<SlabBox> slabs;       ///< Final conservative boxes in mother frame.
0065   int chosenM{1};                   ///< M that was selected.
0066   int chosenAxis{0};                ///< 0=X, 1=Y, 2=Z.
0067   Precision globalSA{0};            ///< Surface area of the M=1 global AABB.
0068   Precision finalSA{0};             ///< Total surface area of chosen slabs.
0069   bool usedSingleAxisSearch{false}; ///< Auto search fixed one axis instead of testing all axes.
0070   /// Per-step SA values indexed by M-1 (entry 0 = global SA at M=1).
0071   std::vector<Precision> saHistory;
0072 };
0073 
0074 class AABBSubdivider {
0075 public:
0076   static AABBSubdivider &Instance()
0077   {
0078     static AABBSubdivider inst;
0079     return inst;
0080   }
0081 
0082   /// Subdivide @p placed into exactly @p M clipped-extent AABBs.
0083   ///
0084   /// @param placed Placed volume.
0085   /// @param M Number of uniform slabs along the selected axis. M=1 returns the
0086   ///          transformed Extent() AABB used by the BVH baseline. Values above
0087   ///          `kMaximumAABBSubdivisionM` return the unsplit fallback.
0088   /// @param cfg Tuning knobs for axis objective.
0089   /// @return Conservative slab boxes in mother frame.
0090   std::vector<SlabBox> Subdivide(VPlacedVolume const *placed, int M, AABBSubdividerConfig const &cfg = {}) const;
0091 
0092   /// @brief Subdivide @p placed into one previously selected uniform slab sequence.
0093   ///
0094   /// This entry point reconstructs a persisted split decision without testing
0095   /// other axes or values of M. Slab bounds are derived from the transformed,
0096   /// padded baseline AABB, so callers do not need to retain an offset or width.
0097   ///
0098   /// @param placed Placed volume.
0099   /// @param axisIdx Mother-frame split axis: 0=X, 1=Y, or 2=Z.
0100   /// @param M Number of uniform slabs. M=1 returns the unsplit baseline AABB;
0101   ///          values above `kMaximumAABBSubdivisionM` return no slabs.
0102   /// @return Conservative slab boxes in mother coordinates, or an empty vector
0103   ///         when no valid slab can be reconstructed.
0104   std::vector<SlabBox> SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M) const;
0105 
0106   /// @brief Subdivide using an available padded mother-frame baseline AABB.
0107   ///
0108   /// @param placed Placed volume.
0109   /// @param axisIdx Mother-frame split axis: 0=X, 1=Y, or 2=Z.
0110   /// @param M Number of uniform slabs; values above
0111   ///          `kMaximumAABBSubdivisionM` return no slabs.
0112   /// @param globalBox Baseline AABB whose selected-axis bounds define the slab
0113   ///                  offset and spacing.
0114   /// @return Conservative slab boxes in mother coordinates, or an empty vector
0115   ///         when no valid slab can be reconstructed.
0116   /// @pre @p globalBox is a valid conservative padded AABB of @p placed in the
0117   ///      mother frame. M=1 returns this box unchanged.
0118   std::vector<SlabBox> SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M,
0119                                           SlabBox const &globalBox) const;
0120 
0121   /// Automatically determine M and axis using the configured search mode.
0122   ///
0123   /// Fast mode chooses one split axis from the transformed AABB shape and scans
0124   /// a small M range on that axis with a strong memory/build penalty.
0125   /// Balanced mode is tiered: moderately inflated daughter boxes use the same
0126   /// one-axis search as fast mode, while highly inflated boxes test all axes.
0127   /// Navigation mode scans up to autoMaxM with a small penalty, favoring
0128   /// navigation speed and accepting more split boxes.
0129   ///
0130   /// @param placed Placed volume.
0131   /// @param cfg Tuning knobs and mode.
0132   /// @return Selected slab boxes plus diagnostics. chosenM=1 means no split.
0133   AABBSubdivisionResult SubdivideAuto(VPlacedVolume const *placed, AABBSubdividerConfig const &cfg = {}) const;
0134 
0135 private:
0136   /// Return the unsplit transformed local-extent AABB used as the baseline.
0137   static SlabBox ComputeGlobalAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr);
0138 
0139   /// Compute one conservative mother-frame slab box for a voxel limit.
0140   ///
0141   /// If a solid returns inconsistent existence across axes, the helper falls
0142   /// back to clipping the unsplit AABB to avoid under-covering the daughter.
0143   static SlabBox ComputeOneSlabAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr, int axisIdx,
0144                                     Precision lo, Precision hi, SlabBox const &globalBox, int slabIndex);
0145 
0146   /// Build all valid uniform slabs along one selected mother-frame axis.
0147   static std::vector<SlabBox> ComputeSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr, int axisIdx,
0148                                            int M, SlabBox const &globalBox);
0149 
0150   /// Try all axes for a fixed M and return the lowest penalized objective.
0151   static std::vector<SlabBox> BestAxisSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr, int M,
0152                                             SlabBox const &globalBox, AABBSubdividerConfig const &cfg, int &outBestAxis,
0153                                             Precision &outSA);
0154 
0155   static Precision TotalSurfaceArea(std::vector<SlabBox> const &slabs);
0156   static Precision ExtentSurfaceArea(VUnplacedVolume const *unplaced);
0157   static Precision AABBInflationRatio(VUnplacedVolume const *unplaced, SlabBox const &globalBox);
0158   static Precision CandidateObjective(Precision totalSA, std::size_t nSlabs, Precision globalSA,
0159                                       AABBSubdividerConfig const &cfg);
0160   static Precision ModeMinReduction(AABBSubdividerConfig const &cfg);
0161   static int ModeMaxM(AABBSubdividerConfig const &cfg);
0162   static int LongestAxis(SlabBox const &box);
0163   static int EstimateFastMaxM(SlabBox const &box, int maxM);
0164 };
0165 
0166 inline SlabBox AABBSubdivider::ComputeGlobalAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr)
0167 {
0168   Vector3D<Precision> localMin, localMax;
0169   unplaced->Extent(localMin, localMax);
0170   const ClippedBox box = TransformLocalAABBToMother(localMin, localMax, tr);
0171 
0172   SlabBox result;
0173   result.min       = box.min;
0174   result.max       = box.max;
0175   result.slabIndex = 0;
0176   return result;
0177 }
0178 
0179 inline SlabBox AABBSubdivider::ComputeOneSlabAABB(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0180                                                   int axisIdx, Precision lo, Precision hi, SlabBox const &globalBox,
0181                                                   int slabIndex)
0182 {
0183   SlabBox result;
0184   result.slabIndex = slabIndex;
0185 
0186   VoxelLimits limits;
0187   limits.AddLimit(AxisFromIndex(axisIdx), lo, hi);
0188 
0189   bool axisResult[3]    = {false, false, false};
0190   Precision minValue[3] = {0., 0., 0.};
0191   Precision maxValue[3] = {0., 0., 0.};
0192   int nExisting         = 0;
0193   for (int axis = 0; axis < 3; ++axis) {
0194     axisResult[axis] =
0195         unplaced->CalculateClippedExtent(AxisFromIndex(axis), limits, *tr, minValue[axis], maxValue[axis]);
0196     if (axisResult[axis]) ++nExisting;
0197   }
0198 
0199   if (nExisting == 0) return result;
0200 
0201   if (nExisting == 3) {
0202     result.min = Vector3D<Precision>(minValue[0], minValue[1], minValue[2]);
0203     result.max = Vector3D<Precision>(maxValue[0], maxValue[1], maxValue[2]);
0204     return result;
0205   }
0206 
0207   // Projection existence should be identical for all axes. If a solid override
0208   // violates that contract, keep the slab conservative by clipping the global
0209   // AABB instead of trusting the partial answer.
0210   ClippedBox fallback(globalBox.min, globalBox.max);
0211   if (!IntersectBoxWithLimits(fallback, limits)) return result;
0212   result.min = fallback.min;
0213   result.max = fallback.max;
0214   return result;
0215 }
0216 
0217 inline std::vector<SlabBox> AABBSubdivider::ComputeSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0218                                                          int axisIdx, int M, SlabBox const &globalBox)
0219 {
0220   const Precision lo0 = globalBox.min[axisIdx];
0221   const Precision hi0 = globalBox.max[axisIdx];
0222   if (!(hi0 > lo0) || M < 1 || M > kMaximumAABBSubdivisionM) return {};
0223 
0224   const Precision slabW = (hi0 - lo0) / Precision(M);
0225   std::vector<SlabBox> slabs;
0226   slabs.reserve(M);
0227   for (int k = 0; k < M; ++k) {
0228     auto slab = ComputeOneSlabAABB(unplaced, tr, axisIdx, lo0 + k * slabW, lo0 + (k + 1) * slabW, globalBox, k);
0229     if (slab.Valid()) slabs.push_back(slab);
0230   }
0231   return slabs;
0232 }
0233 
0234 inline Precision AABBSubdivider::TotalSurfaceArea(std::vector<SlabBox> const &slabs)
0235 {
0236   Precision total = 0.;
0237   for (auto const &slab : slabs)
0238     total += slab.SurfaceArea();
0239   return total;
0240 }
0241 
0242 inline Precision AABBSubdivider::ExtentSurfaceArea(VUnplacedVolume const *unplaced)
0243 {
0244   Vector3D<Precision> localMin, localMax;
0245   unplaced->Extent(localMin, localMax);
0246   SlabBox localBox;
0247   localBox.min = localMin;
0248   localBox.max = localMax;
0249   return localBox.SurfaceArea();
0250 }
0251 
0252 inline Precision AABBSubdivider::AABBInflationRatio(VUnplacedVolume const *unplaced, SlabBox const &globalBox)
0253 {
0254   const Precision localSA = ExtentSurfaceArea(unplaced);
0255   return localSA > 0. ? globalBox.SurfaceArea() / localSA : Precision(0);
0256 }
0257 
0258 inline Precision AABBSubdivider::CandidateObjective(Precision totalSA, std::size_t nSlabs, Precision globalSA,
0259                                                     AABBSubdividerConfig const &cfg)
0260 {
0261   if (globalSA <= 0.) return std::numeric_limits<Precision>::max();
0262   Precision penalty = cfg.fastBoxPenalty;
0263   if (cfg.mode == AABBSubdivisionMode::kBalanced) penalty = cfg.balancedBoxPenalty;
0264   if (cfg.mode == AABBSubdivisionMode::kNavigation) penalty = cfg.navigationBoxPenalty;
0265   return totalSA / globalSA + penalty * Precision(nSlabs > 0 ? nSlabs - 1 : 0);
0266 }
0267 
0268 inline Precision AABBSubdivider::ModeMinReduction(AABBSubdividerConfig const &cfg)
0269 {
0270   if (cfg.mode == AABBSubdivisionMode::kNavigation) return cfg.navigationMinReduction;
0271   if (cfg.mode == AABBSubdivisionMode::kBalanced) return cfg.balancedMinReduction;
0272   return cfg.fastMinReduction;
0273 }
0274 
0275 inline int AABBSubdivider::ModeMaxM(AABBSubdividerConfig const &cfg)
0276 {
0277   if (cfg.mode == AABBSubdivisionMode::kNavigation) return std::clamp(cfg.autoMaxM, 1, kMaximumAABBSubdivisionM);
0278   if (cfg.mode == AABBSubdivisionMode::kBalanced) return std::clamp(cfg.balancedMaxM, 1, kMaximumAABBSubdivisionM);
0279   if (cfg.mode == AABBSubdivisionMode::kFast) return std::clamp(cfg.fastMaxM, 1, kMaximumAABBSubdivisionM);
0280   return 1;
0281 }
0282 
0283 inline int AABBSubdivider::LongestAxis(SlabBox const &box)
0284 {
0285   const auto extent = box.max - box.min;
0286   int axis          = 0;
0287   if (extent[1] > extent[axis]) axis = 1;
0288   if (extent[2] > extent[axis]) axis = 2;
0289   return axis;
0290 }
0291 
0292 inline int AABBSubdivider::EstimateFastMaxM(SlabBox const &box, int maxM)
0293 {
0294   if (maxM <= 1) return 1;
0295 
0296   // Fast mode uses only the transformed AABB aspect ratio. This intentionally
0297   // avoids trying several expensive clipped-extent scenarios before deciding
0298   // whether a production-style split is worth building.
0299   Precision extent[3] = {box.max[0] - box.min[0], box.max[1] - box.min[1], box.max[2] - box.min[2]};
0300   std::sort(extent, extent + 3, [](Precision a, Precision b) { return a > b; });
0301   if (!(extent[1] > 0.)) return 1;
0302 
0303   const int aspectM = static_cast<int>(std::ceil(extent[0] / extent[1]));
0304   return std::max(2, std::min(maxM, aspectM));
0305 }
0306 
0307 inline std::vector<SlabBox> AABBSubdivider::BestAxisSlabs(VUnplacedVolume const *unplaced, Transformation3D const *tr,
0308                                                           int M, SlabBox const &globalBox,
0309                                                           AABBSubdividerConfig const &cfg, int &outBestAxis,
0310                                                           Precision &outSA)
0311 {
0312   std::vector<SlabBox> bestSlabs;
0313   Precision bestObjective = std::numeric_limits<Precision>::max();
0314   outBestAxis             = 0;
0315   outSA                   = std::numeric_limits<Precision>::max();
0316 
0317   const Precision globalSA = globalBox.SurfaceArea();
0318   for (int axis = 0; axis < 3; ++axis) {
0319     if (globalBox.max[axis] - globalBox.min[axis] < Precision(1e-10)) continue;
0320 
0321     auto slabs                  = ComputeSlabs(unplaced, tr, axis, M, globalBox);
0322     const Precision totalSA     = TotalSurfaceArea(slabs);
0323     const Precision objective   = CandidateObjective(totalSA, slabs.size(), globalSA, cfg);
0324     const bool betterObjective  = objective < bestObjective;
0325     const bool tiedButTighterSA = objective == bestObjective && totalSA < outSA;
0326     if (betterObjective || tiedButTighterSA) {
0327       bestObjective = objective;
0328       outBestAxis   = axis;
0329       outSA         = totalSA;
0330       bestSlabs     = std::move(slabs);
0331     }
0332   }
0333   return bestSlabs;
0334 }
0335 
0336 inline std::vector<SlabBox> AABBSubdivider::Subdivide(VPlacedVolume const *placed, int M,
0337                                                       AABBSubdividerConfig const &cfg) const
0338 {
0339   assert(placed && "AABBSubdivider::Subdivide: null placed volume");
0340   assert(M >= 1 && "AABBSubdivider::Subdivide: M must be >= 1");
0341 
0342   VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0343   Transformation3D const *tr      = placed->GetTransformation();
0344   SlabBox globalBox               = ComputeGlobalAABB(unplaced, tr);
0345   if (M == 1) return {globalBox};
0346 
0347   int bestAxis      = 0;
0348   Precision totalSA = 0.;
0349   auto slabs        = BestAxisSlabs(unplaced, tr, M, globalBox, cfg, bestAxis, totalSA);
0350   return slabs.empty() ? std::vector<SlabBox>{globalBox} : slabs;
0351 }
0352 
0353 inline std::vector<SlabBox> AABBSubdivider::SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M) const
0354 {
0355   assert(placed && "AABBSubdivider::SubdivideAlongAxis: null placed volume");
0356   assert(axisIdx >= 0 && axisIdx < 3 && "AABBSubdivider::SubdivideAlongAxis: invalid axis");
0357   assert(M >= 1 && "AABBSubdivider::SubdivideAlongAxis: M must be >= 1");
0358 
0359   VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0360   Transformation3D const *tr      = placed->GetTransformation();
0361   SlabBox globalBox               = ComputeGlobalAABB(unplaced, tr);
0362 
0363   return SubdivideAlongAxis(placed, axisIdx, M, globalBox);
0364 }
0365 
0366 inline std::vector<SlabBox> AABBSubdivider::SubdivideAlongAxis(VPlacedVolume const *placed, int axisIdx, int M,
0367                                                                SlabBox const &globalBox) const
0368 {
0369   assert(placed && "AABBSubdivider::SubdivideAlongAxis: null placed volume");
0370   assert(axisIdx >= 0 && axisIdx < 3 && "AABBSubdivider::SubdivideAlongAxis: invalid axis");
0371   assert(M >= 1 && "AABBSubdivider::SubdivideAlongAxis: M must be >= 1");
0372   assert(globalBox.Valid() && "AABBSubdivider::SubdivideAlongAxis: invalid baseline AABB");
0373 
0374   if (M == 1) return {globalBox};
0375 
0376   VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0377   Transformation3D const *tr      = placed->GetTransformation();
0378   return ComputeSlabs(unplaced, tr, axisIdx, M, globalBox);
0379 }
0380 
0381 inline AABBSubdivisionResult AABBSubdivider::SubdivideAuto(VPlacedVolume const *placed,
0382                                                            AABBSubdividerConfig const &cfg) const
0383 {
0384   assert(placed && "AABBSubdivider::SubdivideAuto: null placed volume");
0385 
0386   VUnplacedVolume const *unplaced = placed->GetLogicalVolume()->GetUnplacedVolume();
0387   Transformation3D const *tr      = placed->GetTransformation();
0388   SlabBox globalBox               = ComputeGlobalAABB(unplaced, tr);
0389 
0390   AABBSubdivisionResult res;
0391   res.slabs      = {globalBox};
0392   res.chosenM    = 1;
0393   res.chosenAxis = 0;
0394   res.globalSA   = globalBox.SurfaceArea();
0395   res.finalSA    = res.globalSA;
0396   res.saHistory.push_back(res.globalSA);
0397 
0398   if (res.globalSA < std::numeric_limits<Precision>::epsilon() || cfg.mode == AABBSubdivisionMode::kOff) return res;
0399 
0400   std::vector<SlabBox> bestSlabs = res.slabs;
0401   Precision bestSA               = res.globalSA;
0402   Precision bestObjective        = CandidateObjective(bestSA, bestSlabs.size(), res.globalSA, cfg);
0403   int bestAxis                   = 0;
0404   int nonImproving               = 0;
0405   const int maxM                 = ModeMaxM(cfg);
0406   const bool fastMode            = cfg.mode == AABBSubdivisionMode::kFast;
0407   // Balanced mode is tiered: moderately inflated daughters use the same cheap
0408   // one-axis search as fast mode, while only the loosest boxes pay all-axis
0409   // search cost.
0410   const bool balancedSingleAxis = cfg.mode == AABBSubdivisionMode::kBalanced &&
0411                                   AABBInflationRatio(unplaced, globalBox) <= cfg.balancedFullSearchAABBInflation;
0412   const bool singleAxisMode     = fastMode || balancedSingleAxis;
0413   res.usedSingleAxisSearch      = singleAxisMode;
0414   const int fastAxis            = singleAxisMode ? LongestAxis(globalBox) : 0;
0415   const int scanMaxM            = singleAxisMode ? EstimateFastMaxM(globalBox, maxM) : maxM;
0416 
0417   for (int M = 2; M <= scanMaxM; ++M) {
0418     int candidateAxis     = 0;
0419     Precision candidateSA = std::numeric_limits<Precision>::max();
0420     // Single-axis tiers commit to one cheap axis estimate. Full balanced and
0421     // navigation tiers pay the higher build cost of testing all axes for each M.
0422     auto candidateSlabs = singleAxisMode ? ComputeSlabs(unplaced, tr, fastAxis, M, globalBox)
0423                                          : BestAxisSlabs(unplaced, tr, M, globalBox, cfg, candidateAxis, candidateSA);
0424     if (singleAxisMode) {
0425       candidateAxis = fastAxis;
0426       candidateSA   = TotalSurfaceArea(candidateSlabs);
0427     }
0428     res.saHistory.push_back(candidateSA);
0429     if (candidateSlabs.empty()) {
0430       ++nonImproving;
0431     } else {
0432       const Precision objective = CandidateObjective(candidateSA, candidateSlabs.size(), res.globalSA, cfg);
0433       if (objective < bestObjective) {
0434         bestSlabs     = std::move(candidateSlabs);
0435         bestSA        = candidateSA;
0436         bestObjective = objective;
0437         bestAxis      = candidateAxis;
0438         res.chosenM   = M;
0439         nonImproving  = 0;
0440       } else {
0441         ++nonImproving;
0442       }
0443     }
0444 
0445     if (!fastMode && nonImproving >= cfg.nonImprovingStopCount) break;
0446   }
0447 
0448   const Precision rawReduction = (res.globalSA - bestSA) / res.globalSA;
0449   if (res.chosenM <= 1 || rawReduction < ModeMinReduction(cfg)) return res;
0450 
0451   res.slabs      = std::move(bestSlabs);
0452   res.chosenAxis = bestAxis;
0453   res.finalSA    = bestSA;
0454   return res;
0455 }
0456 
0457 } // namespace cxx
0458 } // namespace vecgeom