Back to home page

sPhenix code displayed by LXR

 
 

    


File indexing completed on 2025-08-06 08:10:18

0001 // This file is part of the Acts project.
0002 //
0003 // Copyright (C) 2021 CERN for the benefit of the Acts project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Utilities/RangeXD.hpp"
0012 
0013 #include <algorithm>
0014 #include <array>
0015 #include <cmath>
0016 #include <functional>
0017 #include <memory>
0018 #include <string>
0019 #include <vector>
0020 
0021 namespace Acts {
0022 /// @brief A general k-d tree with fast range search.
0023 ///
0024 /// This is a generalized k-d tree, with a configurable number of dimension,
0025 /// scalar type, content type, index type, vector type, and leaf size. This
0026 /// class is purposefully generalized to support a wide range of use cases.
0027 ///
0028 /// A k-d tree is, in essence, a k-dimensional binary search tree. Each internal
0029 /// node splits the content of the tree in half, with the pivot point being an
0030 /// orthogonal hyperplane in one of the k dimensions. This allows us to
0031 /// efficiently look up points within certain k-dimensional ranges.
0032 ///
0033 /// This particular class is mostly a wrapper class around an underlying node
0034 /// class which does all the actual work.
0035 ///
0036 /// @note This type is completely immutable after construction.
0037 ///
0038 /// @tparam Dims The number of dimensions.
0039 /// @tparam Type The type of value contained in the tree.
0040 /// @tparam Scalar The scalar type used to construct position vectors.
0041 /// @tparam Vector The general vector type used to construct coordinates.
0042 /// @tparam LeafSize The maximum number of elements stored in a leaf node.
0043 template <std::size_t Dims, typename Type, typename Scalar = double,
0044           template <typename, std::size_t> typename Vector = std::array,
0045           std::size_t LeafSize = 4>
0046 class KDTree {
0047  public:
0048   /// @brief The type of value contained in this k-d tree.
0049   using value_t = Type;
0050 
0051   /// @brief The type describing a multi-dimensional orthogonal range.
0052   using range_t = RangeXD<Dims, Scalar>;
0053 
0054   /// @brief The type of coordinates for points.
0055   using coordinate_t = Vector<Scalar, Dims>;
0056 
0057   /// @brief The type of coordinate-value pairs.
0058   using pair_t = std::pair<coordinate_t, Type>;
0059 
0060   /// @brief The type of a vector of coordinate-value pairs.
0061   using vector_t = std::vector<pair_t>;
0062 
0063   /// @brief The type of iterators in our vectors.
0064   using iterator_t = typename vector_t::iterator;
0065 
0066   using const_iterator_t = typename vector_t::const_iterator;
0067 
0068   // We do not need an empty constructor - this is never useful.
0069   KDTree() = delete;
0070 
0071   /// @brief Construct a k-d tree from a vector of position-value pairs.
0072   ///
0073   /// This constructor takes an r-value reference to a vector of position-value
0074   /// pairs and constructs a k-d tree from those pairs.
0075   ///
0076   /// @param d The vector of position-value pairs to construct the k-d tree
0077   /// from.
0078   KDTree(vector_t &&d) : m_elems(d) {
0079     // To start out, we need to check whether we need to construct a leaf node
0080     // or an internal node. We create a leaf only if we have at most as many
0081     // elements as the number of elements that can fit into a leaf node.
0082     // Hopefully most invocations of this constructor will have more than a few
0083     // elements!
0084     //
0085     // One interesting thing to note is that all of the nodes in the k-d tree
0086     // have a range in the element vector of the outermost node. They simply
0087     // make in-place changes to this array, and they hold no memory of their
0088     // own.
0089     m_root = std::make_unique<KDTreeNode>(m_elems.begin(), m_elems.end(),
0090                                           m_elems.size() > LeafSize
0091                                               ? KDTreeNode::NodeType::Internal
0092                                               : KDTreeNode::NodeType::Leaf,
0093                                           0UL);
0094   }
0095 
0096   /// @brief Perform an orthogonal range search within the k-d tree.
0097   ///
0098   /// A range search operation is one that takes a k-d tree and an orthogonal
0099   /// range, and returns all values associated with coordinates in the k-d tree
0100   /// that lie within the orthogonal range. k-d trees can do this operation
0101   /// quickly.
0102   ///
0103   /// @param r The range to search for.
0104   ///
0105   /// @return The vector of all values that lie within the given range.
0106   std::vector<Type> rangeSearch(const range_t &r) const {
0107     std::vector<Type> out;
0108 
0109     rangeSearch(r, out);
0110 
0111     return out;
0112   }
0113 
0114   /// @brief Perform an orthogonal range search within the k-d tree, returning
0115   /// keys as well as values.
0116   ///
0117   /// Performs the same logic as the keyless version, but includes a copy of
0118   /// the key with each element.
0119   ///
0120   /// @param r The range to search for.
0121   ///
0122   /// @return The vector of all key-value pairs that lie within the given
0123   /// range.
0124   std::vector<pair_t> rangeSearchWithKey(const range_t &r) const {
0125     std::vector<pair_t> out;
0126 
0127     rangeSearchWithKey(r, out);
0128 
0129     return out;
0130   }
0131 
0132   /// @brief Perform an in-place orthogonal range search within the k-d tree.
0133   ///
0134   /// This range search module operates in place, writing its results to the
0135   /// given output vector.
0136   ///
0137   /// @param r The range to search for.
0138   /// @param v The vector to write the output to.
0139   void rangeSearch(const range_t &r, std::vector<Type> &v) const {
0140     rangeSearchInserter(r, std::back_inserter(v));
0141   }
0142 
0143   /// @brief Perform an in-place orthogonal range search within the k-d tree,
0144   /// including keys in the result.
0145   ///
0146   /// Performs the same operation as the keyless version, but includes the keys
0147   /// in the results.
0148   ///
0149   /// @param r The range to search for.
0150   /// @param v The vector to write the output to.
0151   void rangeSearchWithKey(const range_t &r, std::vector<pair_t> &v) const {
0152     rangeSearchInserterWithKey(r, std::back_inserter(v));
0153   }
0154 
0155   /// @brief Perform an orthogonal range search within the k-d tree, writing
0156   /// the resulting values to an output iterator.
0157   ///
0158   /// This method allows the user more control in where the result is written
0159   /// to.
0160   ///
0161   /// @tparam OutputIt The type of the output iterator.
0162   ///
0163   /// @param r The range to search for.
0164   /// @param i The iterator to write the output to.
0165   template <typename OutputIt>
0166   void rangeSearchInserter(const range_t &r, OutputIt i) const {
0167     rangeSearchMapDiscard(
0168         r, [i](const coordinate_t &, const Type &v) mutable { i = v; });
0169   }
0170 
0171   /// @brief Perform an orthogonal range search within the k-d tree, writing
0172   /// the resulting values to an output iterator, including the keys.
0173   ///
0174   /// Performs the same operation as the keyless version, but includes the key
0175   /// in the output.
0176   ///
0177   /// @tparam OutputIt The type of the output iterator.
0178   ///
0179   /// @param r The range to search for.
0180   /// @param i The iterator to write the output to.
0181   template <typename OutputIt>
0182   void rangeSearchInserterWithKey(const range_t &r, OutputIt i) const {
0183     rangeSearchMapDiscard(r, [i](const coordinate_t &c, const Type &v) mutable {
0184       i = {c, v};
0185     });
0186   }
0187 
0188   /// @brief Perform an orthogonal range search within the k-d tree, applying
0189   /// a mapping function to the values found.
0190   ///
0191   /// In some cases, we may wish to transform the values in some way. This
0192   /// method allows the user to provide a mapping function which takes a set of
0193   /// coordinates and a value and transforms them to a new value, which is
0194   /// returned.
0195   ///
0196   /// @note Your compiler may not be able to deduce the result type
0197   /// automatically, in which case you will need to specify it manually.
0198   ///
0199   /// @tparam Result The return type of the map operation.
0200   ///
0201   /// @param r The range to search for.
0202   /// @param f The mapping function to apply to key-value pairs.
0203   ///
0204   /// @return A vector of elements matching the range after the application of
0205   /// the mapping function.
0206   template <typename Result>
0207   std::vector<Result> rangeSearchMap(
0208       const range_t &r,
0209       std::function<Result(const coordinate_t &, const Type &)> f) const {
0210     std::vector<Result> out;
0211 
0212     rangeSearchMapInserter(r, f, std::back_inserter(out));
0213 
0214     return out;
0215   }
0216 
0217   /// @brief Perform an orthogonal range search within the k-d tree, applying a
0218   /// mapping function to the values found, and inserting them into an
0219   /// inserter.
0220   ///
0221   /// Performs the same operation as the interter-less version, but allows the
0222   /// user additional control over the insertion process.
0223   ///
0224   /// @note Your compiler may not be able to deduce the result type
0225   /// automatically, in which case you will need to specify it manually.
0226   ///
0227   /// @tparam Result The return type of the map operation.
0228   /// @tparam OutputIt The type of the output iterator.
0229   ///
0230   /// @param r The range to search for.
0231   /// @param f The mapping function to apply to key-value pairs.
0232   /// @param i The inserter to insert the results into.
0233   template <typename Result, typename OutputIt>
0234   void rangeSearchMapInserter(
0235       const range_t &r,
0236       std::function<Result(const coordinate_t &, const Type &)> f,
0237       OutputIt i) const {
0238     rangeSearchMapDiscard(r, [i, f](const coordinate_t &c,
0239                                     const Type &v) mutable { i = f(c, v); });
0240   }
0241 
0242   /// @brief Perform an orthogonal range search within the k-d tree, applying a
0243   /// a void-returning function with side-effects to each key-value pair.
0244   ///
0245   /// This is the most general version of range search in this class, and every
0246   /// other operation can be reduced to this operation as long as we allow
0247   /// arbitrary side-effects.
0248   ///
0249   /// Functional programmers will know this method as mapM_.
0250   ///
0251   /// @param r The range to search for.
0252   /// @param f The mapping function to apply to key-value pairs.
0253   template <typename Callable>
0254   void rangeSearchMapDiscard(const range_t &r, Callable &&f) const {
0255     m_root->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0256   }
0257 
0258   /// @brief Return the number of elements in the k-d tree.
0259   ///
0260   /// We simply defer this method to the root node of the k-d tree.
0261   ///
0262   /// @return The number of elements in the k-d tree.
0263   std::size_t size(void) const { return m_root->size(); }
0264 
0265   const_iterator_t begin(void) const { return m_elems.begin(); }
0266 
0267   const_iterator_t end(void) const { return m_elems.end(); }
0268 
0269  private:
0270   static Scalar nextRepresentable(Scalar v) {
0271     // I'm not super happy with this bit of code, but since 1D ranges are
0272     // semi-open, we can't simply incorporate values by setting the maximum to
0273     // them. Instead, what we need to do is get the next representable value.
0274     // For integer values, this means adding one. For floating point types, we
0275     // rely on the nextafter method to get the smallest possible value that is
0276     // larger than the one we requested.
0277     if constexpr (std::is_integral_v<Scalar>) {
0278       return v + 1;
0279     } else if constexpr (std::is_floating_point_v<Scalar>) {
0280       return std::nextafter(v, std::numeric_limits<Scalar>::max());
0281     }
0282   }
0283 
0284   static range_t boundingBox(iterator_t b, iterator_t e) {
0285     // Firstly, we find the minimum and maximum value in each dimension to
0286     // construct a bounding box around this node's values.
0287     std::array<Scalar, Dims> min_v{}, max_v{};
0288 
0289     for (std::size_t i = 0; i < Dims; ++i) {
0290       min_v[i] = std::numeric_limits<Scalar>::max();
0291       max_v[i] = std::numeric_limits<Scalar>::lowest();
0292     }
0293 
0294     for (iterator_t i = b; i != e; ++i) {
0295       for (std::size_t j = 0; j < Dims; ++j) {
0296         min_v[j] = std::min(min_v[j], i->first[j]);
0297         max_v[j] = std::max(max_v[j], i->first[j]);
0298       }
0299     }
0300 
0301     // Then, we construct a k-dimensional range from the given minima and
0302     // maxima, which again is just a bounding box.
0303     range_t r;
0304 
0305     for (std::size_t j = 0; j < Dims; ++j) {
0306       r[j] = Range1D<Scalar>{min_v[j], nextRepresentable(max_v[j])};
0307     }
0308 
0309     return r;
0310   }
0311 
0312   /// @brief An abstract class containing common features of k-d tree node
0313   /// types.
0314   ///
0315   /// A k-d tree consists of two different node types: leaf nodes and inner
0316   /// nodes. These nodes have some common functionality, which is captured by
0317   /// this common parent node type.
0318   class KDTreeNode {
0319    public:
0320     /// @brief Enumeration type for the possible node types (internal and leaf).
0321     enum class NodeType { Internal, Leaf };
0322 
0323     /// @brief Construct the common data for all node types.
0324     ///
0325     /// The node types share a few concepts, like an n-dimensional range, and a
0326     /// begin and end of the range of elements managed. This constructor
0327     /// calculates these things so that the individual child constructors don't
0328     /// have to.
0329     KDTreeNode(iterator_t _b, iterator_t _e, NodeType _t, std::size_t _d)
0330         : m_type(_t),
0331           m_begin_it(_b),
0332           m_end_it(_e),
0333           m_range(boundingBox(m_begin_it, m_end_it)) {
0334       if (m_type == NodeType::Internal) {
0335         // This constant determines the maximum number of elements where we
0336         // still
0337         // calculate the exact median of the values for the purposes of
0338         // splitting. In general, the closer the pivot value is to the true
0339         // median, the more balanced the tree will be. However, calculating the
0340         // median exactly is an O(n log n) operation, while approximating it is
0341         // an O(1) time.
0342         constexpr std::size_t max_exact_median = 128;
0343 
0344         iterator_t pivot;
0345 
0346         // Next, we need to determine the pivot point of this node, that is to
0347         // say the point in the selected pivot dimension along which point we
0348         // will split the range. To do this, we check how large the set of
0349         // elements is. If it is sufficiently small, we use the median.
0350         // Otherwise we use the mean.
0351         if (size() > max_exact_median) {
0352           // In this case, we have a lot of elements, and sorting the range to
0353           // find the true median might be too expensive. Therefore, we will
0354           // just use the middle value between the minimum and maximum. This is
0355           // not nearly as accurate as using the median, but it's a nice cheat.
0356           Scalar mid = static_cast<Scalar>(0.5) *
0357                        (m_range[_d].max() + m_range[_d].min());
0358 
0359           pivot = std::partition(m_begin_it, m_end_it, [=](const pair_t &i) {
0360             return i.first[_d] < mid;
0361           });
0362         } else {
0363           // If the number of elements is fairly small, we will just calculate
0364           // the median exactly. We do this by finding the values in the
0365           // dimension, sorting it, and then taking the middle one.
0366           std::sort(m_begin_it, m_end_it,
0367                     [_d](const typename iterator_t::value_type &a,
0368                          const typename iterator_t::value_type &b) {
0369                       return a.first[_d] < b.first[_d];
0370                     });
0371 
0372           pivot = m_begin_it + (std::distance(m_begin_it, m_end_it) / 2);
0373         }
0374 
0375         // This should never really happen, but in very select cases where there
0376         // are a lot of equal values in the range, the pivot can end up all the
0377         // way at the end of the array and we end up in an infinite loop. We
0378         // check for pivot points which would not split the range, and fix them
0379         // if they occur.
0380         if (pivot == m_begin_it || pivot == std::prev(m_end_it)) {
0381           pivot = std::next(m_begin_it, LeafSize);
0382         }
0383 
0384         // Calculate the number of elements on the left-hand side, as well as
0385         // the right-hand side. We do this by calculating the difference from
0386         // the begin and end of the array to the pivot point.
0387         std::size_t lhs_size = std::distance(m_begin_it, pivot);
0388         std::size_t rhs_size = std::distance(pivot, m_end_it);
0389 
0390         // Next, we check whether the left-hand node should be another internal
0391         // node or a leaf node, and we construct the node recursively.
0392         m_lhs = std::make_unique<KDTreeNode>(
0393             m_begin_it, pivot,
0394             lhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0395             (_d + 1) % Dims);
0396 
0397         // Same on the right hand side.
0398         m_rhs = std::make_unique<KDTreeNode>(
0399             pivot, m_end_it,
0400             rhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0401             (_d + 1) % Dims);
0402       }
0403     }
0404 
0405     /// @brief Perform a range search in the k-d tree, mapping the key-value
0406     /// pairs to a side-effecting function.
0407     ///
0408     /// This is the most powerful range search method we have, assuming that we
0409     /// can use arbitrary side effects, which we can. All other range search
0410     /// methods are implemented in terms of this particular function.
0411     ///
0412     /// @param r The range to search for.
0413     /// @param f The mapping function to apply to matching elements.
0414     template <typename Callable>
0415     void rangeSearchMapDiscard(const range_t &r, Callable &&f) const {
0416       // Determine whether the range completely covers the bounding box of
0417       // this leaf node. If it is, we can copy all values without having to
0418       // check for them being inside the range again.
0419       bool contained = r >= m_range;
0420 
0421       if (m_type == NodeType::Internal) {
0422         // Firstly, we can check if the range completely contains the bounding
0423         // box of this node. If that is the case, we know for certain that any
0424         // value contained below this node should end up in the output, and we
0425         // can stop recursively looking for them.
0426         if (contained) {
0427           // We can also pre-allocate space for the number of elements, since we
0428           // are inserting all of them anyway.
0429           for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0430             f(i->first, i->second);
0431           }
0432 
0433           return;
0434         }
0435 
0436         assert(m_lhs && m_rhs && "Did not find lhs and rhs");
0437 
0438         // If we have a left-hand node (which we should!), then we check if
0439         // there is any overlap between the target range and the bounding box of
0440         // the left-hand node. If there is, we recursively search in that node.
0441         if (m_lhs->range() && r) {
0442           m_lhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0443         }
0444 
0445         // Then, we perform exactly the same procedure for the right hand side.
0446         if (m_rhs->range() && r) {
0447           m_rhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0448         }
0449       } else {
0450         // Iterate over all the elements in this leaf node. This should be a
0451         // relatively small number (the LeafSize template parameter).
0452         for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0453           // We need to check whether the element is actually inside the range.
0454           // In case this node's bounding box is fully contained within the
0455           // range, we don't actually need to check this.
0456           if (contained || r.contains(i->first)) {
0457             f(i->first, i->second);
0458           }
0459         }
0460       }
0461     }
0462 
0463     /// @brief Determine the number of elements managed by this node.
0464     ///
0465     /// Conveniently, this number is always equal to the distance between the
0466     /// begin iterator and the end iterator, so we can simply delegate to the
0467     /// relevant standard library method.
0468     ///
0469     /// @return The number of elements below this node.
0470     std::size_t size() const { return std::distance(m_begin_it, m_end_it); }
0471 
0472     /// @brief The axis-aligned bounding box containing all elements in this
0473     /// node.
0474     ///
0475     /// @return The minimal axis-aligned bounding box that contains all the
0476     /// elements under this node.
0477     const range_t &range() const { return m_range; }
0478 
0479    protected:
0480     NodeType m_type;
0481 
0482     /// @brief The start and end of the range of coordinate-value pairs under
0483     /// this node.
0484     const iterator_t m_begin_it, m_end_it;
0485 
0486     /// @brief The axis-aligned bounding box of the coordinates under this
0487     /// node.
0488     const range_t m_range;
0489 
0490     /// @brief Pointers to the left and right children.
0491     std::unique_ptr<KDTreeNode> m_lhs;
0492     std::unique_ptr<KDTreeNode> m_rhs;
0493   };
0494 
0495   /// @brief Vector containing all of the elements in this k-d tree, including
0496   /// the elements managed by the nodes inside of it.
0497   vector_t m_elems;
0498 
0499   /// @brief Pointer to the root node of this k-d tree.
0500   std::unique_ptr<KDTreeNode> m_root;
0501 };
0502 }  // namespace Acts