Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-10-18 08:20:54

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 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 https://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 <vector>
0019 
0020 namespace Acts {
0021 /// @brief A general k-d tree with fast range search.
0022 ///
0023 /// This is a generalized k-d tree, with a configurable number of dimension,
0024 /// scalar type, content type, index type, vector type, and leaf size. This
0025 /// class is purposefully generalized to support a wide range of use cases.
0026 ///
0027 /// A k-d tree is, in essence, a k-dimensional binary search tree. Each internal
0028 /// node splits the content of the tree in half, with the pivot point being an
0029 /// orthogonal hyperplane in one of the k dimensions. This allows us to
0030 /// efficiently look up points within certain k-dimensional ranges.
0031 ///
0032 /// This particular class is mostly a wrapper class around an underlying node
0033 /// class which does all the actual work.
0034 ///
0035 /// @note This type is completely immutable after construction.
0036 ///
0037 /// @tparam Dims The number of dimensions.
0038 /// @tparam Type The type of value contained in the tree.
0039 /// @tparam Scalar The scalar type used to construct position vectors.
0040 /// @tparam Vector The general vector type used to construct coordinates.
0041 /// @tparam LeafSize The maximum number of elements stored in a leaf node.
0042 template <std::size_t Dims, typename Type, typename Scalar = double,
0043           template <typename, std::size_t> typename Vector = std::array,
0044           std::size_t LeafSize = 4>
0045 class KDTree {
0046  public:
0047   /// @brief The type of value contained in this k-d tree.
0048   using value_t = Type;
0049 
0050   /// @brief The type describing a multi-dimensional orthogonal range.
0051   using range_t = RangeXD<Dims, Scalar>;
0052 
0053   /// @brief The type of coordinates for points.
0054   using coordinate_t = Vector<Scalar, Dims>;
0055 
0056   /// @brief The type of coordinate-value pairs.
0057   using pair_t = std::pair<coordinate_t, Type>;
0058 
0059   /// @brief The type of a vector of coordinate-value pairs.
0060   using vector_t = std::vector<pair_t>;
0061 
0062   /// @brief The type of iterators in our vectors.
0063   using iterator_t = typename vector_t::iterator;
0064 
0065   /// Type alias for const iterator over coordinate-value pairs
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   explicit 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(
0184         r, [i](const coordinate_t &c, const Type &v) mutable { i = {c, v}; });
0185   }
0186 
0187   /// @brief Perform an orthogonal range search within the k-d tree, applying
0188   /// a mapping function to the values found.
0189   ///
0190   /// In some cases, we may wish to transform the values in some way. This
0191   /// method allows the user to provide a mapping function which takes a set of
0192   /// coordinates and a value and transforms them to a new value, which is
0193   /// returned.
0194   ///
0195   /// @note Your compiler may not be able to deduce the result type
0196   /// automatically, in which case you will need to specify it manually.
0197   ///
0198   /// @tparam Result The return type of the map operation.
0199   ///
0200   /// @param r The range to search for.
0201   /// @param f The mapping function to apply to key-value pairs.
0202   ///
0203   /// @return A vector of elements matching the range after the application of
0204   /// the mapping function.
0205   template <typename Result>
0206   std::vector<Result> rangeSearchMap(
0207       const range_t &r,
0208       std::function<Result(const coordinate_t &, const Type &)> f) const {
0209     std::vector<Result> out;
0210 
0211     rangeSearchMapInserter(r, f, std::back_inserter(out));
0212 
0213     return out;
0214   }
0215 
0216   /// @brief Perform an orthogonal range search within the k-d tree, applying a
0217   /// mapping function to the values found, and inserting them into an
0218   /// inserter.
0219   ///
0220   /// Performs the same operation as the interter-less version, but allows the
0221   /// user additional control over the insertion process.
0222   ///
0223   /// @note Your compiler may not be able to deduce the result type
0224   /// automatically, in which case you will need to specify it manually.
0225   ///
0226   /// @tparam Result The return type of the map operation.
0227   /// @tparam OutputIt The type of the output iterator.
0228   ///
0229   /// @param r The range to search for.
0230   /// @param f The mapping function to apply to key-value pairs.
0231   /// @param i The inserter to insert the results into.
0232   template <typename Result, typename OutputIt>
0233   void rangeSearchMapInserter(
0234       const range_t &r,
0235       std::function<Result(const coordinate_t &, const Type &)> f,
0236       OutputIt i) const {
0237     rangeSearchMapDiscard(r, [i, f](const coordinate_t &c,
0238                                     const Type &v) mutable { i = f(c, v); });
0239   }
0240 
0241   /// @brief Perform an orthogonal range search within the k-d tree, applying a
0242   /// a void-returning function with side-effects to each key-value pair.
0243   ///
0244   /// This is the most general version of range search in this class, and every
0245   /// other operation can be reduced to this operation as long as we allow
0246   /// arbitrary side-effects.
0247   ///
0248   /// Functional programmers will know this method as mapM_.
0249   ///
0250   /// @param r The range to search for.
0251   /// @param f The mapping function to apply to key-value pairs.
0252   template <typename Callable>
0253   void rangeSearchMapDiscard(const range_t &r, Callable &&f) const {
0254     m_root->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0255   }
0256 
0257   /// @brief Return the number of elements in the k-d tree.
0258   ///
0259   /// We simply defer this method to the root node of the k-d tree.
0260   ///
0261   /// @return The number of elements in the k-d tree.
0262   std::size_t size(void) const { return m_root->size(); }
0263 
0264   /// Get iterator to first element
0265   /// @return Const iterator to the beginning of the tree elements
0266   const_iterator_t begin(void) const { return m_elems.begin(); }
0267 
0268   /// Get iterator to one past the last element
0269   /// @return Const iterator to the end of the tree elements
0270   const_iterator_t end(void) const { return m_elems.end(); }
0271 
0272  private:
0273   static Scalar nextRepresentable(Scalar v) {
0274     // I'm not super happy with this bit of code, but since 1D ranges are
0275     // semi-open, we can't simply incorporate values by setting the maximum to
0276     // them. Instead, what we need to do is get the next representable value.
0277     // For integer values, this means adding one. For floating point types, we
0278     // rely on the nextafter method to get the smallest possible value that is
0279     // larger than the one we requested.
0280     if constexpr (std::is_integral_v<Scalar>) {
0281       return v + 1;
0282     } else if constexpr (std::is_floating_point_v<Scalar>) {
0283       return std::nextafter(v, std::numeric_limits<Scalar>::max());
0284     }
0285   }
0286 
0287   static range_t boundingBox(iterator_t b, iterator_t e) {
0288     // Firstly, we find the minimum and maximum value in each dimension to
0289     // construct a bounding box around this node's values.
0290     std::array<Scalar, Dims> min_v{}, max_v{};
0291 
0292     for (std::size_t i = 0; i < Dims; ++i) {
0293       min_v[i] = std::numeric_limits<Scalar>::max();
0294       max_v[i] = std::numeric_limits<Scalar>::lowest();
0295     }
0296 
0297     for (iterator_t i = b; i != e; ++i) {
0298       for (std::size_t j = 0; j < Dims; ++j) {
0299         min_v[j] = std::min(min_v[j], i->first[j]);
0300         max_v[j] = std::max(max_v[j], i->first[j]);
0301       }
0302     }
0303 
0304     // Then, we construct a k-dimensional range from the given minima and
0305     // maxima, which again is just a bounding box.
0306     range_t r;
0307 
0308     for (std::size_t j = 0; j < Dims; ++j) {
0309       r[j] = Range1D<Scalar>{min_v[j], nextRepresentable(max_v[j])};
0310     }
0311 
0312     return r;
0313   }
0314 
0315   /// @brief An abstract class containing common features of k-d tree node
0316   /// types.
0317   ///
0318   /// A k-d tree consists of two different node types: leaf nodes and inner
0319   /// nodes. These nodes have some common functionality, which is captured by
0320   /// this common parent node type.
0321   class KDTreeNode {
0322    public:
0323     /// @brief Enumeration type for the possible node types (internal and leaf).
0324     enum class NodeType { Internal, Leaf };
0325 
0326     /// @brief Construct the common data for all node types.
0327     ///
0328     /// The node types share a few concepts, like an n-dimensional range, and a
0329     /// begin and end of the range of elements managed. This constructor
0330     /// calculates these things so that the individual child constructors don't
0331     /// have to.
0332     KDTreeNode(iterator_t _b, iterator_t _e, NodeType _t, std::size_t _d)
0333         : m_type(_t),
0334           m_begin_it(_b),
0335           m_end_it(_e),
0336           m_range(boundingBox(m_begin_it, m_end_it)) {
0337       if (m_type == NodeType::Internal) {
0338         // This constant determines the maximum number of elements where we
0339         // still
0340         // calculate the exact median of the values for the purposes of
0341         // splitting. In general, the closer the pivot value is to the true
0342         // median, the more balanced the tree will be. However, calculating the
0343         // median exactly is an O(n log n) operation, while approximating it is
0344         // an O(1) time.
0345         constexpr std::size_t max_exact_median = 128;
0346 
0347         iterator_t pivot;
0348 
0349         // Next, we need to determine the pivot point of this node, that is to
0350         // say the point in the selected pivot dimension along which point we
0351         // will split the range. To do this, we check how large the set of
0352         // elements is. If it is sufficiently small, we use the median.
0353         // Otherwise we use the mean.
0354         if (size() > max_exact_median) {
0355           // In this case, we have a lot of elements, and sorting the range to
0356           // find the true median might be too expensive. Therefore, we will
0357           // just use the middle value between the minimum and maximum. This is
0358           // not nearly as accurate as using the median, but it's a nice cheat.
0359           Scalar mid = static_cast<Scalar>(0.5) *
0360                        (m_range[_d].max() + m_range[_d].min());
0361 
0362           pivot = std::partition(m_begin_it, m_end_it, [=](const pair_t &i) {
0363             return i.first[_d] < mid;
0364           });
0365         } else {
0366           // If the number of elements is fairly small, we will just calculate
0367           // the median exactly. We do this by finding the values in the
0368           // dimension, sorting it, and then taking the middle one.
0369           std::sort(m_begin_it, m_end_it,
0370                     [_d](const typename iterator_t::value_type &a,
0371                          const typename iterator_t::value_type &b) {
0372                       return a.first[_d] < b.first[_d];
0373                     });
0374 
0375           pivot = m_begin_it + (std::distance(m_begin_it, m_end_it) / 2);
0376         }
0377 
0378         // This should never really happen, but in very select cases where there
0379         // are a lot of equal values in the range, the pivot can end up all the
0380         // way at the end of the array and we end up in an infinite loop. We
0381         // check for pivot points which would not split the range, and fix them
0382         // if they occur.
0383         if (pivot == m_begin_it || pivot == std::prev(m_end_it)) {
0384           pivot = std::next(m_begin_it, LeafSize);
0385         }
0386 
0387         // Calculate the number of elements on the left-hand side, as well as
0388         // the right-hand side. We do this by calculating the difference from
0389         // the begin and end of the array to the pivot point.
0390         std::size_t lhs_size = std::distance(m_begin_it, pivot);
0391         std::size_t rhs_size = std::distance(pivot, m_end_it);
0392 
0393         // Next, we check whether the left-hand node should be another internal
0394         // node or a leaf node, and we construct the node recursively.
0395         m_lhs = std::make_unique<KDTreeNode>(
0396             m_begin_it, pivot,
0397             lhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0398             (_d + 1) % Dims);
0399 
0400         // Same on the right hand side.
0401         m_rhs = std::make_unique<KDTreeNode>(
0402             pivot, m_end_it,
0403             rhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0404             (_d + 1) % Dims);
0405       }
0406     }
0407 
0408     /// @brief Perform a range search in the k-d tree, mapping the key-value
0409     /// pairs to a side-effecting function.
0410     ///
0411     /// This is the most powerful range search method we have, assuming that we
0412     /// can use arbitrary side effects, which we can. All other range search
0413     /// methods are implemented in terms of this particular function.
0414     ///
0415     /// @param r The range to search for.
0416     /// @param f The mapping function to apply to matching elements.
0417     template <typename Callable>
0418     void rangeSearchMapDiscard(const range_t &r, Callable &&f) const {
0419       // Determine whether the range completely covers the bounding box of
0420       // this leaf node. If it is, we can copy all values without having to
0421       // check for them being inside the range again.
0422       bool contained = r >= m_range;
0423 
0424       if (m_type == NodeType::Internal) {
0425         // Firstly, we can check if the range completely contains the bounding
0426         // box of this node. If that is the case, we know for certain that any
0427         // value contained below this node should end up in the output, and we
0428         // can stop recursively looking for them.
0429         if (contained) {
0430           // We can also pre-allocate space for the number of elements, since we
0431           // are inserting all of them anyway.
0432           for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0433             f(i->first, i->second);
0434           }
0435 
0436           return;
0437         }
0438 
0439         assert(m_lhs && m_rhs && "Did not find lhs and rhs");
0440 
0441         // If we have a left-hand node (which we should!), then we check if
0442         // there is any overlap between the target range and the bounding box of
0443         // the left-hand node. If there is, we recursively search in that node.
0444         if (m_lhs->range() && r) {
0445           m_lhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0446         }
0447 
0448         // Then, we perform exactly the same procedure for the right hand side.
0449         if (m_rhs->range() && r) {
0450           m_rhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0451         }
0452       } else {
0453         // Iterate over all the elements in this leaf node. This should be a
0454         // relatively small number (the LeafSize template parameter).
0455         for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0456           // We need to check whether the element is actually inside the range.
0457           // In case this node's bounding box is fully contained within the
0458           // range, we don't actually need to check this.
0459           if (contained || r.contains(i->first)) {
0460             f(i->first, i->second);
0461           }
0462         }
0463       }
0464     }
0465 
0466     /// @brief Determine the number of elements managed by this node.
0467     ///
0468     /// Conveniently, this number is always equal to the distance between the
0469     /// begin iterator and the end iterator, so we can simply delegate to the
0470     /// relevant standard library method.
0471     ///
0472     /// @return The number of elements below this node.
0473     std::size_t size() const { return std::distance(m_begin_it, m_end_it); }
0474 
0475     /// @brief The axis-aligned bounding box containing all elements in this
0476     /// node.
0477     ///
0478     /// @return The minimal axis-aligned bounding box that contains all the
0479     /// elements under this node.
0480     const range_t &range() const { return m_range; }
0481 
0482    protected:
0483     NodeType m_type;
0484 
0485     /// @brief The start and end of the range of coordinate-value pairs under
0486     /// this node.
0487     const iterator_t m_begin_it, m_end_it;
0488 
0489     /// @brief The axis-aligned bounding box of the coordinates under this
0490     /// node.
0491     const range_t m_range;
0492 
0493     /// @brief Pointers to the left and right children.
0494     std::unique_ptr<KDTreeNode> m_lhs;
0495     std::unique_ptr<KDTreeNode> m_rhs;
0496   };
0497 
0498   /// @brief Vector containing all of the elements in this k-d tree, including
0499   /// the elements managed by the nodes inside of it.
0500   vector_t m_elems;
0501 
0502   /// @brief Pointer to the root node of this k-d tree.
0503   std::unique_ptr<KDTreeNode> m_root;
0504 };
0505 }  // namespace Acts