Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-09-15 09:06:16

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file orange/univ/SimpleUnitTracker.hh
0006 //---------------------------------------------------------------------------//
0007 #pragma once
0008 
0009 #include "corecel/Assert.hh"
0010 #include "corecel/math/Algorithms.hh"
0011 #include "corecel/math/ArrayUtils.hh"
0012 #include "orange/OrangeData.hh"
0013 #include "orange/OrangeTypes.hh"
0014 #include "orange/SenseUtils.hh"
0015 #include "orange/detail/BIHEnclosingVolFinder.hh"
0016 #include "orange/detail/BIHIntersectingVolFinder.hh"
0017 #include "orange/surf/LocalSurfaceVisitor.hh"
0018 
0019 #include "detail/InfixEvaluator.hh"
0020 #include "detail/LazySenseCalculator.hh"
0021 #include "detail/LogicEvaluator.hh"
0022 #include "detail/SurfaceFunctors.hh"
0023 #include "detail/Types.hh"
0024 #include "detail/Utils.hh"
0025 
0026 namespace celeritas
0027 {
0028 //---------------------------------------------------------------------------//
0029 /*!
0030  * Track a particle in a universe of well-connected volumes.
0031  *
0032  * The simple unit tracker is based on a set of non-overlapping volumes
0033  * comprised of surfaces. It is a faster but less "user-friendly" version of
0034  * the masked unit tracker because it requires all volumes to be exactly
0035  * defined by their connected surfaces. It does *not* check for overlaps.
0036  */
0037 class SimpleUnitTracker
0038 {
0039   public:
0040     //!@{
0041     //! \name Type aliases
0042     using ParamsRef = NativeCRef<OrangeParamsData>;
0043     using Initialization = detail::Initialization;
0044     using Intersection = detail::Intersection;
0045     using LocalState = detail::LocalState;
0046     //!@}
0047 
0048   public:
0049     // Construct with parameters (unit definitions and this one's ID)
0050     inline CELER_FUNCTION
0051     SimpleUnitTracker(ParamsRef const& params, SimpleUnitId id);
0052 
0053     //// ACCESSORS ////
0054 
0055     //! Number of local volumes
0056     CELER_FUNCTION LocalVolumeId::size_type num_volumes() const
0057     {
0058         return unit_record_.volumes.size();
0059     }
0060 
0061     //! Number of local surfaces
0062     CELER_FUNCTION LocalSurfaceId::size_type num_surfaces() const
0063     {
0064         return unit_record_.surfaces.size();
0065     }
0066 
0067     //! SimpleUnitRecord for this tracker
0068     CELER_FUNCTION SimpleUnitRecord const& unit_record() const
0069     {
0070         return unit_record_;
0071     }
0072 
0073     // DaughterId of universe embedded in a given volume
0074     inline CELER_FUNCTION DaughterId daughter(LocalVolumeId vol) const;
0075 
0076     //// OPERATIONS ////
0077 
0078     // Find the local volume from a position
0079     inline CELER_FUNCTION Initialization
0080     initialize(LocalState const& state) const;
0081 
0082     // Find the new volume by crossing a surface
0083     inline CELER_FUNCTION Initialization
0084     cross_boundary(LocalState const& state) const;
0085 
0086     // Calculate the distance to an exiting face for the current volume
0087     inline CELER_FUNCTION Intersection intersect(LocalState const& state) const;
0088 
0089     // Calculate nearby distance to an exiting face for the current volume
0090     inline CELER_FUNCTION Intersection intersect(LocalState const& state,
0091                                                  real_type max_dist) const;
0092 
0093     // Calculate closest distance to a surface in any direction
0094     inline CELER_FUNCTION real_type safety(Real3 const& pos,
0095                                            LocalVolumeId vol) const;
0096 
0097     // Calculate the local surface normal
0098     inline CELER_FUNCTION Real3 normal(Real3 const& pos,
0099                                        LocalSurfaceId surf) const;
0100 
0101   private:
0102     //// DATA ////
0103 
0104     ParamsRef const& params_;
0105     SimpleUnitRecord const& unit_record_;
0106 
0107     //// METHODS ////
0108 
0109     // Get volumes that have the given surface as a "face" (connectivity)
0110     inline CELER_FUNCTION LdgSpan<LocalVolumeId const>
0111         get_neighbors(LocalSurfaceId) const;
0112 
0113     template<class F>
0114     inline CELER_FUNCTION LocalVolumeId find_volume_where(Real3 const& pos,
0115                                                           F&& predicate) const;
0116 
0117     template<class F>
0118     inline CELER_FUNCTION Intersection intersect_impl(LocalState const&,
0119                                                       F&&) const;
0120 
0121     inline CELER_FUNCTION Intersection simple_intersect(LocalState const&,
0122                                                         VolumeView const&,
0123                                                         size_type) const;
0124     inline CELER_FUNCTION Intersection complex_intersect(LocalState const&,
0125                                                          VolumeView const&,
0126                                                          size_type,
0127                                                          Sense,
0128                                                          real_type) const;
0129     template<class F>
0130     inline CELER_FUNCTION Intersection background_intersect(LocalState const&,
0131                                                             F&&) const;
0132 
0133     // Create a Surfaces object from the params
0134     inline CELER_FUNCTION LocalSurfaceVisitor make_surface_visitor() const;
0135 
0136     // Create a Volumes object from the params
0137     inline CELER_FUNCTION VolumeView
0138     make_local_volume(LocalVolumeId vol_id) const;
0139 };
0140 
0141 //---------------------------------------------------------------------------//
0142 // INLINE DEFINITIONS
0143 //---------------------------------------------------------------------------//
0144 /*!
0145  * Construct with reference to persistent parameter data.
0146  *
0147  * \todo When adding multiple universes, this will calculate range of
0148  * LocalVolumeIds that belong to this unit. For now we assume all volumes and
0149  * surfaces belong to us.
0150  */
0151 CELER_FUNCTION
0152 SimpleUnitTracker::SimpleUnitTracker(ParamsRef const& params, SimpleUnitId suid)
0153     : params_(params), unit_record_(params.simple_units[suid])
0154 {
0155     CELER_EXPECT(params_);
0156 }
0157 
0158 //---------------------------------------------------------------------------//
0159 /*!
0160  * Find the local volume from a position.
0161  *
0162  * To avoid edge cases and inconsistent logical/physical states, it is
0163  * prohibited to initialize from an arbitrary point directly onto a surface.
0164  *
0165  * \todo This prohibition currently also extends to *internal* surfaces, even
0166  * if both sides of that surface are "in" the current cell. We may need to
0167  * relax that.
0168  */
0169 CELER_FUNCTION auto
0170 SimpleUnitTracker::initialize(LocalState const& state) const -> Initialization
0171 {
0172     CELER_EXPECT(params_);
0173     CELER_EXPECT(!state.surface && !state.volume);
0174 
0175     // Use the BIH to locate a position that's inside, and save whether it's on
0176     // a surface in the found volume
0177     detail::OnFace on_surface;
0178     auto is_inside = [this, &state, &on_surface](LocalVolumeId id) -> bool {
0179         VolumeView vol = this->make_local_volume(id);
0180         auto calc_senses = detail::LazySenseCalculator(
0181             this->make_surface_visitor(), vol, state.pos, on_surface);
0182         return detail::LogicEvaluator(vol.logic())(calc_senses);
0183     };
0184     LocalVolumeId id = this->find_volume_where(state.pos, is_inside);
0185 
0186     if (on_surface)
0187     {
0188         // Prohibit initialization on a surface
0189         id = {};
0190     }
0191     else if (!id)
0192     {
0193         // Not found: replace with background volume (if any)
0194         id = unit_record_.background;
0195     }
0196 
0197     return Initialization{id, {}};
0198 }
0199 
0200 //---------------------------------------------------------------------------//
0201 /*!
0202  * Find the local volume on the opposite side of a surface.
0203  */
0204 CELER_FUNCTION auto
0205 SimpleUnitTracker::cross_boundary(LocalState const& state) const
0206     -> Initialization
0207 {
0208     CELER_EXPECT(state.surface && state.volume);
0209 
0210     detail::OnLocalSurface on_surface;
0211     auto is_inside
0212         = [this, &state, &on_surface](LocalVolumeId const& id) -> bool {
0213         if (id == state.volume)
0214         {
0215             // Cannot cross surface into the same volume
0216             return false;
0217         }
0218 
0219         VolumeView vol = this->make_local_volume(id);
0220         detail::OnFace face{detail::find_face(vol, state.surface)};
0221         auto calc_senses = detail::LazySenseCalculator(
0222             this->make_surface_visitor(), vol, state.pos, face);
0223 
0224         if (detail::LogicEvaluator(vol.logic())(calc_senses))
0225         {
0226             // Inside: find and save the local surface ID, and end the search
0227             on_surface = get_surface(vol, face);
0228             return true;
0229         }
0230         return false;
0231     };
0232 
0233     auto neighbors = this->get_neighbors(state.surface.id());
0234 
0235     // If this surface has 2 neighbors or fewer (excluding the current cell),
0236     // use linear search to check neighbors. Otherwise, traverse the BIH tree.
0237     if (neighbors.size() < 3)
0238     {
0239         for (LocalVolumeId id : neighbors)
0240         {
0241             if (is_inside(id))
0242             {
0243                 return {id, on_surface};
0244             }
0245         }
0246     }
0247     else
0248     {
0249         if (LocalVolumeId id = this->find_volume_where(state.pos, is_inside))
0250         {
0251             return {id, on_surface};
0252         }
0253     }
0254 
0255     return {unit_record_.background, state.surface};
0256 }
0257 
0258 //---------------------------------------------------------------------------//
0259 /*!
0260  * Calculate distance-to-intercept for the next surface.
0261  */
0262 CELER_FUNCTION auto SimpleUnitTracker::intersect(LocalState const& state) const
0263     -> Intersection
0264 {
0265     Intersection result = this->intersect_impl(state, detail::IsFinite{});
0266     return result;
0267 }
0268 
0269 //---------------------------------------------------------------------------//
0270 /*!
0271  * Calculate distance-to-intercept for the next surface.
0272  */
0273 CELER_FUNCTION auto
0274 SimpleUnitTracker::intersect(LocalState const& state, real_type max_dist) const
0275     -> Intersection
0276 {
0277     CELER_EXPECT(max_dist > 0);
0278     Intersection result
0279         = this->intersect_impl(state, detail::IsNotFurtherThan{max_dist});
0280     if (!result)
0281     {
0282         result.distance = max_dist;
0283     }
0284     return result;
0285 }
0286 
0287 //---------------------------------------------------------------------------//
0288 /*!
0289  * Calculate nearest distance to a surface in any direction.
0290  *
0291  * The safety calculation uses a very limited method for calculating the safety
0292  * distance: it's the nearest distance to any surface, for a certain subset of
0293  * surfaces.  Other surface types will return a safety distance of zero.
0294  * Complex surfaces might return the distance to internal surfaces that do not
0295  * represent the edge of a volume. Such distances are conservative but will
0296  * necessarily slow down the simulation.
0297  */
0298 CELER_FUNCTION real_type SimpleUnitTracker::safety(Real3 const& pos,
0299                                                    LocalVolumeId vol_id) const
0300 {
0301     CELER_EXPECT(vol_id);
0302 
0303     VolumeView vol = this->make_local_volume(vol_id);
0304     if (!vol.simple_safety())
0305     {
0306         // Has a tricky surface: we can't use the simple algorithm to calculate
0307         // the safety, so return a conservative estimate.
0308         return 0;
0309     }
0310 
0311     // Calculate minimim distance to all local faces
0312     real_type result = numeric_limits<real_type>::infinity();
0313     LocalSurfaceVisitor visit_surface(params_, unit_record_.surfaces);
0314     detail::CalcSafetyDistance calc_safety{pos};
0315     for (LocalSurfaceId surface : vol.faces())
0316     {
0317         result = celeritas::min(result, visit_surface(calc_safety, surface));
0318     }
0319 
0320     CELER_ENSURE(result >= 0);
0321     return result;
0322 }
0323 
0324 //---------------------------------------------------------------------------//
0325 /*!
0326  * Calculate the local surface normal.
0327  */
0328 CELER_FUNCTION auto
0329 SimpleUnitTracker::normal(Real3 const& pos, LocalSurfaceId surf) const -> Real3
0330 {
0331     CELER_EXPECT(surf);
0332 
0333     LocalSurfaceVisitor visit_surface(params_, unit_record_.surfaces);
0334     return visit_surface(detail::CalcNormal{pos}, surf);
0335 }
0336 
0337 //---------------------------------------------------------------------------//
0338 // PRIVATE INLINE DEFINITIONS
0339 //---------------------------------------------------------------------------//
0340 /*!
0341  * Get volumes that have the given surface as a "face" (connectivity).
0342  */
0343 CELER_FUNCTION auto SimpleUnitTracker::get_neighbors(LocalSurfaceId surf) const
0344     -> LdgSpan<LocalVolumeId const>
0345 {
0346     CELER_EXPECT(surf < this->num_surfaces());
0347 
0348     OpaqueId<ConnectivityRecord> conn_id
0349         = unit_record_.connectivity[surf.unchecked_get()];
0350     ConnectivityRecord const& conn = params_.connectivity_records[conn_id];
0351 
0352     CELER_ENSURE(!conn.neighbors.empty());
0353     return params_.local_volume_ids[conn.neighbors];
0354 }
0355 
0356 //---------------------------------------------------------------------------//
0357 /*!
0358  * Search the BIH to find where the predicate is true for the point.
0359  *
0360  * The predicate should have the signature \code bool(LocalVolumeId) \endcode.
0361  */
0362 template<class F>
0363 CELER_FUNCTION LocalVolumeId
0364 SimpleUnitTracker::find_volume_where(Real3 const& pos, F&& predicate) const
0365 {
0366     detail::BIHEnclosingVolFinder find_volume{unit_record_.bih_tree,
0367                                               params_.bih_tree_data};
0368     return find_volume(pos, predicate);
0369 }
0370 
0371 //---------------------------------------------------------------------------//
0372 /*!
0373  * Calculate distance-to-intercept for the next surface.
0374  *
0375  * The algorithm is:
0376  * - If the volume is the "background" then search externally for the next
0377  *   volume with \c background_intersect (equivalent of DistanceToIn for
0378  *   Geant4)
0379  * - Use the current volume to find potential intersecting surfaces and maximum
0380  *   number of intersections.
0381  * - Loop over all surfaces and calculate the distance to intercept based on
0382  *   the given physical and logical state. Save to the thread-local buffer
0383  *   *only* intersections that are valid (either finite *or* less than the
0384  *   user-supplied maximum). The buffer contains the distances, the face
0385  *   indices, and an index used for sorting (if the volume has internal
0386  *   surfaes).
0387  * - If no intersecting surfaces are found, return immediately. (Rely on the
0388  *   caller to set the "maximum distance" if we're not searching to infinity.)
0389  * - If the volume has no special cases, find the closest surface by calling \c
0390  *   simple_intersect.
0391  * - If the volume has internal surfaces call \c complex_intersect.
0392  */
0393 template<class F>
0394 CELER_FUNCTION auto
0395 SimpleUnitTracker::intersect_impl(LocalState const& state, F&& is_valid) const
0396     -> Intersection
0397 {
0398     CELER_EXPECT(state.volume && !state.temp_sense.empty());
0399 
0400     // Resize temporaries based on volume properties
0401     VolumeView vol = this->make_local_volume(state.volume);
0402     CELER_ASSERT(state.temp_next.size >= vol.max_intersections());
0403 
0404     if (vol.implicit_vol())
0405     {
0406         // Search all the volumes "externally"
0407         return this->background_intersect(state, is_valid);
0408     }
0409 
0410     // Find all valid (nearby or finite, depending on F) surface intersection
0411     // distances inside this volume. Fill the `isect` array if the tracking
0412     // algorithm requires sorting.
0413     detail::CalcIntersections calc_intersections{
0414         celeritas::forward<F>(is_valid),
0415         state.pos,
0416         state.dir,
0417         state.surface ? vol.find_face(state.surface.id()) : FaceId{},
0418         vol.simple_intersection(),
0419         state.temp_next};
0420     LocalSurfaceVisitor visit_surface(params_, unit_record_.surfaces);
0421     for (LocalSurfaceId surface : vol.faces())
0422     {
0423         visit_surface(calc_intersections, surface);
0424     }
0425     CELER_ASSERT(calc_intersections.face_idx() == vol.num_faces());
0426     size_type num_isect = calc_intersections.isect_idx();
0427     CELER_ASSERT(num_isect <= vol.max_intersections());
0428 
0429     if (num_isect == 0)
0430     {
0431         // No intersection (no surfaces in this volume, no finite distances, or
0432         // no "nearby" distances depending on F)
0433         return {};
0434     }
0435     else if (vol.simple_intersection())
0436     {
0437         // No internal surfaces nor implicit volume: the closest distance is
0438         // the next boundary
0439         return this->simple_intersect(state, vol, num_isect);
0440     }
0441     else if (vol.internal_surfaces())
0442     {
0443         // Internal surfaces: sort valid intersection distances in ascending
0444         // order and find the closest surface that puts us outside.
0445         celeritas::sort(state.temp_next.isect,
0446                         state.temp_next.isect + num_isect,
0447                         [&state](size_type a, size_type b) {
0448                             return state.temp_next.distance[a]
0449                                    < state.temp_next.distance[b];
0450                         });
0451         // Call with a target sense of "inside," because we are seeking a
0452         // surface for which crossing will result leaving the volume
0453         return this->complex_intersect(state,
0454                                        vol,
0455                                        num_isect,
0456                                        Sense::outside,
0457                                        numeric_limits<real_type>::infinity());
0458     }
0459 
0460     CELER_ASSERT_UNREACHABLE();  // Unexpected set of flags
0461 }
0462 
0463 //---------------------------------------------------------------------------//
0464 /*!
0465  * Calculate distance to the next boundary for nonreentrant volumes.
0466  */
0467 CELER_FUNCTION auto
0468 SimpleUnitTracker::simple_intersect(LocalState const& state,
0469                                     VolumeView const& vol,
0470                                     size_type num_isect) const -> Intersection
0471 {
0472     CELER_EXPECT(num_isect > 0);
0473 
0474     // Crossing any surface will leave the volume; perform a linear search for
0475     // the smallest (but positive) distance
0476     size_type distance_idx
0477         = celeritas::min_element(state.temp_next.distance,
0478                                  state.temp_next.distance + num_isect,
0479                                  Less<real_type>{})
0480           - state.temp_next.distance;
0481     CELER_ASSERT(distance_idx < num_isect);
0482 
0483     // Determine the crossing surface
0484     LocalSurfaceId surface;
0485     {
0486         FaceId face = state.temp_next.face[distance_idx];
0487         CELER_ASSERT(face);
0488         surface = vol.get_surface(face);
0489         CELER_ASSERT(surface);
0490     }
0491 
0492     Sense cur_sense;
0493     if (surface == state.surface.id())
0494     {
0495         // Crossing the same surface that we're currently on and inside (e.g.
0496         // on the inside surface of a sphere, and the next intersection is the
0497         // other side)
0498         cur_sense = state.surface.sense();
0499     }
0500     else
0501     {
0502         LocalSurfaceVisitor visit_surface(params_, unit_record_.surfaces);
0503         SignedSense ss = visit_surface(detail::CalcSense{state.pos}, surface);
0504         CELER_ASSERT(ss != SignedSense::on);
0505         cur_sense = to_sense(ss);
0506     }
0507 
0508     // Post-surface sense will be on the other side of the surface
0509     Intersection result;
0510     result.surface = {surface, cur_sense};
0511     result.distance = state.temp_next.distance[distance_idx];
0512     return result;
0513 }
0514 
0515 //---------------------------------------------------------------------------//
0516 /*!
0517  * Calculate boundary distance if internal surfaces are present.
0518  *
0519  * In "complex" volumes, crossing a surface can still leave the particle in its
0520  * original state.
0521  *
0522  * We have to iteratively track through all surfaces, in order of minimum
0523  * distance, to determine whether crossing them in sequence will cause us
0524  * change our sense with respect to the volume.
0525  *
0526  * The target_sense argument denotes whether a valid intersection is one that
0527  * puts us inside or outside the volume.
0528  *
0529  * \pre The `state.temp_next.isect` array must be sorted by the caller by
0530  * ascending distance.
0531  */
0532 CELER_FUNCTION auto
0533 SimpleUnitTracker::complex_intersect(LocalState const& state,
0534                                      VolumeView const& vol,
0535                                      size_type num_isect,
0536                                      Sense target_sense,
0537                                      real_type max_search_dist) const
0538     -> Intersection
0539 {
0540     CELER_ASSERT(num_isect > 0);
0541 
0542     // Position and face state of the test point as we move across progressive
0543     // surfaces.
0544     // TODO: use rvalue references for local state since it's temporary; update
0545     // its \c pos in place
0546     Real3 pos{state.pos};
0547     detail::OnFace on_face(detail::find_face(vol, state.surface));
0548 
0549     // NOTE: if switching to the "eager" SenseCalculator, this must be moved
0550     // inside the loop, since it recalculates senses only on construction.
0551     detail::LazySenseCalculator calc_sense{
0552         this->make_surface_visitor(), vol, pos, on_face};
0553 
0554     // Calculate local senses, taking current face into account
0555     // Current senses should put us inside the volume
0556     detail::LogicEvaluator is_inside(vol.logic());
0557     CELER_ASSERT(is_inside(calc_sense) != (target_sense == Sense::inside));
0558 
0559     // previous isect distance for move delta
0560     real_type previous_distance{0};
0561 
0562     // Loop over distances and surface indices to cross by iterating over
0563     // temp_next.isect[:num_isect].
0564     // Evaluate the logic expression at each crossing to determine whether
0565     // we're actually leaving the volume.
0566     for (size_type isect_idx = 0; isect_idx != num_isect; ++isect_idx)
0567     {
0568         // Index into the distance/face arrays
0569         size_type const isect = state.temp_next.isect[isect_idx];
0570         real_type const distance = state.temp_next.distance[isect];
0571 
0572         if (distance >= max_search_dist)
0573         {
0574             // No intersection within search range; exit early
0575             return {};
0576         }
0577 
0578         // Update face state *before* movement, then position
0579         on_face = [&] {
0580             FaceId face{state.temp_next.face[isect]};
0581 
0582             // Calculate sense from old position
0583             return detail::OnFace{face, flip_sense(calc_sense(face))};
0584         }();
0585         axpy(distance - previous_distance, state.dir, &pos);
0586 
0587         // Intersection is found if is_inside is true and the target sense
0588         // is inside, or vice-versa
0589         if (is_inside(calc_sense) == (target_sense == Sense::inside))
0590         {
0591             // Flipping this sense puts us outside the current volume: in
0592             // other words, only after crossing all the internal surfaces along
0593             // this direction do we hit a surface that actually puts us
0594             // outside.
0595             CELER_ENSURE(distance > 0 && !std::isinf(distance));
0596             // Return the intersecting face and *pre*-crossing sense.
0597             return {
0598                 {vol.get_surface(on_face.id()), flip_sense(on_face.sense())},
0599                 distance};
0600         }
0601         previous_distance = distance;
0602     }
0603 
0604     // No intersection: perhaps leaving an exterior volume? Perhaps geometry
0605     // error.
0606     return {};
0607 }
0608 
0609 //---------------------------------------------------------------------------//
0610 /*!
0611  * Calculate distance from the background volume to enter any other volume.
0612  *
0613  * This function is accelerated with the BIH.
0614  */
0615 template<class F>
0616 CELER_FUNCTION auto
0617 SimpleUnitTracker::background_intersect(LocalState const& state,
0618                                         F&& is_valid) const -> Intersection
0619 {
0620     auto is_intersecting = [this, &state, &is_valid](
0621                                LocalVolumeId vol_id,
0622                                real_type max_search_dist) -> Intersection {
0623         VolumeView vol = this->make_local_volume(vol_id);
0624 
0625         detail::CalcIntersections calc_intersections{
0626             is_valid,
0627             state.pos,
0628             state.dir,
0629             state.surface ? vol.find_face(state.surface.id()) : FaceId{},
0630             false,
0631             state.temp_next};
0632 
0633         LocalSurfaceVisitor visit_surface(params_, unit_record_.surfaces);
0634         for (LocalSurfaceId surface : vol.faces())
0635         {
0636             visit_surface(calc_intersections, surface);
0637         }
0638 
0639         size_type num_isect = calc_intersections.isect_idx();
0640         if (num_isect == 0)
0641         {
0642             // No intersection in this unit
0643             return {};
0644         }
0645 
0646         // Sort valid intersection distances in ascending order
0647         celeritas::sort(state.temp_next.isect,
0648                         state.temp_next.isect + num_isect,
0649                         [&state](size_type a, size_type b) {
0650                             return state.temp_next.distance[a]
0651                                    < state.temp_next.distance[b];
0652                         });
0653 
0654         // Call with a target sense of "inside," because we are seeking a
0655         // surface for which crossing will result in entering the volume
0656         return this->complex_intersect(
0657             state, vol, num_isect, Sense::inside, max_search_dist);
0658     };
0659 
0660     detail::BIHIntersectingVolFinder find_intersection{unit_record_.bih_tree,
0661                                                        params_.bih_tree_data};
0662 
0663     return find_intersection({state.pos, state.dir}, is_intersecting);
0664 }
0665 
0666 //---------------------------------------------------------------------------//
0667 /*!
0668  * Create a surface visitor from the params for this unit.
0669  */
0670 CELER_FORCEINLINE_FUNCTION LocalSurfaceVisitor
0671 SimpleUnitTracker::make_surface_visitor() const
0672 {
0673     return LocalSurfaceVisitor{params_, unit_record_.surfaces};
0674 }
0675 
0676 //---------------------------------------------------------------------------//
0677 /*!
0678  * Create a Volume view object from the params for this unit.
0679  */
0680 CELER_FORCEINLINE_FUNCTION VolumeView
0681 SimpleUnitTracker::make_local_volume(LocalVolumeId vol_id) const
0682 {
0683     return VolumeView{params_, unit_record_, vol_id};
0684 }
0685 
0686 //---------------------------------------------------------------------------//
0687 /*!
0688  * DaughterId of universe embedded in a given volume.
0689  */
0690 CELER_FUNCTION DaughterId SimpleUnitTracker::daughter(LocalVolumeId vol) const
0691 {
0692     CELER_EXPECT(vol < unit_record_.volumes.size());
0693     return params_.volume_records[unit_record_.volumes[vol]].daughter_id;
0694 }
0695 
0696 //---------------------------------------------------------------------------//
0697 }  // namespace celeritas