Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-10-29 09:13:54

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 geocel/VolumePathFinder.hh
0006 //! \sa test/geocel/Volume.test.cc
0007 //---------------------------------------------------------------------------//
0008 #pragma once
0009 
0010 #include "corecel/Assert.hh"
0011 #include "corecel/Macros.hh"
0012 #include "corecel/cont/Span.hh"
0013 #include "corecel/math/Algorithms.hh"
0014 
0015 #include "VolumeData.hh"
0016 #include "VolumeView.hh"
0017 
0018 namespace celeritas
0019 {
0020 //---------------------------------------------------------------------------//
0021 /*!
0022  * Reconstruct the volume-instance path for a \c VolumeUniqueInstanceId.
0023  *
0024  * Each \c VolumeUniqueInstanceId uniquely identifies a root-to-node path
0025  * (i.e., a Geant4 "touchable") in the volume DAG.  This class performs the
0026  * inverse of \c VolumePathAccumulator: given an ID it fills a
0027  * caller-supplied scratch buffer with the \c VolumeInstanceId sequence and
0028  * returns a (possibly shorter) span of the result.
0029  *
0030  * \c VolumeUniqueInstanceId{0} (or \c world_unique_instance ) always denotes
0031  * the world volume itself (empty path), and \c VolumeUniqueInstanceId{}
0032  * (null/invalid) is rejected with a precondition failure.
0033  * The valid range is \f$[0,\, N_\text{unique})\f$.
0034  *
0035  * The algorithm descends from the world volume level by level, always seeding
0036  * from the world's direct children.
0037  * At each level it scans the current volume's children to find the unique
0038  * child whose subtree contains the remaining UID, exploiting the fact that
0039  * sibling offsets are strictly increasing.
0040  * The cost is \f$O(D \log C)\f$ where \f$D\f$ is the path depth and \f$C\f$ is
0041  * the maximum number of children of any volume.
0042  *
0043  * The scratch buffer must be at least \c num_volume_levels - 1 long (the
0044  * maximum possible path depth).
0045  * Successive calls reuse the same buffer, so callers must consume the returned
0046  * span before the next call.
0047  *
0048  * \par Example:
0049  * \code
0050    std::vector<VolumeInstanceId> buf(params.num_volume_levels());
0051    VolumePathFinder find_path{params.host_ref(), make_span(buf)};
0052 
0053    VolumeUniqueInstanceId uid = ...;
0054    auto path = find_path(uid);   // [vi_0, vi_1, ..., vi_k]
0055  * \endcode
0056  */
0057 class VolumePathFinder
0058 {
0059   public:
0060     //!@{
0061     //! \name Type aliases
0062     using ParamsRef = NativeCRef<VolumeParamsData>;
0063     using SpanVI = Span<VolumeInstanceId>;
0064     //!@}
0065 
0066   public:
0067     //! Construct from volume hierarchy data and a scratch buffer
0068     CELER_FUNCTION VolumePathFinder(ParamsRef const& params, SpanVI scratch)
0069         : params_(params), scratch_(scratch)
0070     {
0071         CELER_EXPECT(scratch_.size() + 1 >= params_.scalars.num_volume_levels);
0072     }
0073 
0074     // Reconstruct the path whose unique instance ID equals uid
0075     inline CELER_FUNCTION SpanVI operator()(VolumeUniqueInstanceId uid) const;
0076 
0077   private:
0078     ParamsRef const& params_;
0079     SpanVI scratch_;
0080 };
0081 
0082 //---------------------------------------------------------------------------//
0083 // INLINE DEFINITIONS
0084 //---------------------------------------------------------------------------//
0085 /*!
0086  * Reconstruct the path whose unique instance ID equals \c uid.
0087  *
0088  * Returns an empty span for UID 0 (the world volume, before any instance is
0089  * entered).  Otherwise writes the sequence of \c VolumeInstanceId values
0090  * from the world's first child down to the node identified by \c uid, and
0091  * returns a sub-span of the scratch buffer containing exactly those entries.
0092  */
0093 CELER_FUNCTION auto VolumePathFinder::operator()(
0094     VolumeUniqueInstanceId uid) const -> SpanVI
0095 {
0096     CELER_EXPECT(uid < params_.scalars.num_unique_instances);
0097     using size_type = VolumeUniqueInstanceId::size_type;
0098 
0099     size_type offset = uid.unchecked_get();
0100 
0101     // Always seed from world's direct children
0102     auto parents = VolumeView{params_, params_.scalars.world}.children();
0103     VolumeLevelId::size_type depth = 0;
0104 
0105     while (offset > 0)
0106     {
0107         CELER_ASSERT(depth < scratch_.size());
0108         CELER_ASSERT(!parents.empty());
0109 
0110         // Sibling offsets are strictly increasing prefix sums; use
0111         // lower_bound to find the first child whose offset >= offset,
0112         // then step back one to get the chosen child. The offset of the first
0113         // parent is always greater than the current offset, so leave it out of
0114         // the search.
0115         auto comp = [this](VolumeInstanceId vi, size_type val) {
0116             return params_.unique_instance_offsets[vi] < val;
0117         };
0118         auto it = lower_bound(parents.begin() + 1, parents.end(), offset, comp);
0119         VolumeInstanceId chosen = *--it;
0120 
0121         scratch_[depth++] = chosen;
0122         offset -= params_.unique_instance_offsets[chosen] + size_type{1};
0123         parents = VolumeView{params_, params_.volume_ids[chosen]}.children();
0124     }
0125 
0126     return scratch_.first(depth);
0127 }
0128 
0129 //---------------------------------------------------------------------------//
0130 }  // namespace celeritas