Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:11:11

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 <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(
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   const_iterator_t begin(void) const { return m_elems.begin(); }
0265 
0266   const_iterator_t end(void) const { return m_elems.end(); }
0267 
0268  private:
0269   static Scalar nextRepresentable(Scalar v) {
0270     // I'm not super happy with this bit of code, but since 1D ranges are
0271     // semi-open, we can't simply incorporate values by setting the maximum to
0272     // them. Instead, what we need to do is get the next representable value.
0273     // For integer values, this means adding one. For floating point types, we
0274     // rely on the nextafter method to get the smallest possible value that is
0275     // larger than the one we requested.
0276     if constexpr (std::is_integral_v<Scalar>) {
0277       return v + 1;
0278     } else if constexpr (std::is_floating_point_v<Scalar>) {
0279       return std::nextafter(v, std::numeric_limits<Scalar>::max());
0280     }
0281   }
0282 
0283   static range_t boundingBox(iterator_t b, iterator_t e) {
0284     // Firstly, we find the minimum and maximum value in each dimension to
0285     // construct a bounding box around this node's values.
0286     std::array<Scalar, Dims> min_v{}, max_v{};
0287 
0288     for (std::size_t i = 0; i < Dims; ++i) {
0289       min_v[i] = std::numeric_limits<Scalar>::max();
0290       max_v[i] = std::numeric_limits<Scalar>::lowest();
0291     }
0292 
0293     for (iterator_t i = b; i != e; ++i) {
0294       for (std::size_t j = 0; j < Dims; ++j) {
0295         min_v[j] = std::min(min_v[j], i->first[j]);
0296         max_v[j] = std::max(max_v[j], i->first[j]);
0297       }
0298     }
0299 
0300     // Then, we construct a k-dimensional range from the given minima and
0301     // maxima, which again is just a bounding box.
0302     range_t r;
0303 
0304     for (std::size_t j = 0; j < Dims; ++j) {
0305       r[j] = Range1D<Scalar>{min_v[j], nextRepresentable(max_v[j])};
0306     }
0307 
0308     return r;
0309   }
0310 
0311   /// @brief An abstract class containing common features of k-d tree node
0312   /// types.
0313   ///
0314   /// A k-d tree consists of two different node types: leaf nodes and inner
0315   /// nodes. These nodes have some common functionality, which is captured by
0316   /// this common parent node type.
0317   class KDTreeNode {
0318    public:
0319     /// @brief Enumeration type for the possible node types (internal and leaf).
0320     enum class NodeType { Internal, Leaf };
0321 
0322     /// @brief Construct the common data for all node types.
0323     ///
0324     /// The node types share a few concepts, like an n-dimensional range, and a
0325     /// begin and end of the range of elements managed. This constructor
0326     /// calculates these things so that the individual child constructors don't
0327     /// have to.
0328     KDTreeNode(iterator_t _b, iterator_t _e, NodeType _t, std::size_t _d)
0329         : m_type(_t),
0330           m_begin_it(_b),
0331           m_end_it(_e),
0332           m_range(boundingBox(m_begin_it, m_end_it)) {
0333       if (m_type == NodeType::Internal) {
0334         // This constant determines the maximum number of elements where we
0335         // still
0336         // calculate the exact median of the values for the purposes of
0337         // splitting. In general, the closer the pivot value is to the true
0338         // median, the more balanced the tree will be. However, calculating the
0339         // median exactly is an O(n log n) operation, while approximating it is
0340         // an O(1) time.
0341         constexpr std::size_t max_exact_median = 128;
0342 
0343         iterator_t pivot;
0344 
0345         // Next, we need to determine the pivot point of this node, that is to
0346         // say the point in the selected pivot dimension along which point we
0347         // will split the range. To do this, we check how large the set of
0348         // elements is. If it is sufficiently small, we use the median.
0349         // Otherwise we use the mean.
0350         if (size() > max_exact_median) {
0351           // In this case, we have a lot of elements, and sorting the range to
0352           // find the true median might be too expensive. Therefore, we will
0353           // just use the middle value between the minimum and maximum. This is
0354           // not nearly as accurate as using the median, but it's a nice cheat.
0355           Scalar mid = static_cast<Scalar>(0.5) *
0356                        (m_range[_d].max() + m_range[_d].min());
0357 
0358           pivot = std::partition(m_begin_it, m_end_it, [=](const pair_t &i) {
0359             return i.first[_d] < mid;
0360           });
0361         } else {
0362           // If the number of elements is fairly small, we will just calculate
0363           // the median exactly. We do this by finding the values in the
0364           // dimension, sorting it, and then taking the middle one.
0365           std::sort(m_begin_it, m_end_it,
0366                     [_d](const typename iterator_t::value_type &a,
0367                          const typename iterator_t::value_type &b) {
0368                       return a.first[_d] < b.first[_d];
0369                     });
0370 
0371           pivot = m_begin_it + (std::distance(m_begin_it, m_end_it) / 2);
0372         }
0373 
0374         // This should never really happen, but in very select cases where there
0375         // are a lot of equal values in the range, the pivot can end up all the
0376         // way at the end of the array and we end up in an infinite loop. We
0377         // check for pivot points which would not split the range, and fix them
0378         // if they occur.
0379         if (pivot == m_begin_it || pivot == std::prev(m_end_it)) {
0380           pivot = std::next(m_begin_it, LeafSize);
0381         }
0382 
0383         // Calculate the number of elements on the left-hand side, as well as
0384         // the right-hand side. We do this by calculating the difference from
0385         // the begin and end of the array to the pivot point.
0386         std::size_t lhs_size = std::distance(m_begin_it, pivot);
0387         std::size_t rhs_size = std::distance(pivot, m_end_it);
0388 
0389         // Next, we check whether the left-hand node should be another internal
0390         // node or a leaf node, and we construct the node recursively.
0391         m_lhs = std::make_unique<KDTreeNode>(
0392             m_begin_it, pivot,
0393             lhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0394             (_d + 1) % Dims);
0395 
0396         // Same on the right hand side.
0397         m_rhs = std::make_unique<KDTreeNode>(
0398             pivot, m_end_it,
0399             rhs_size > LeafSize ? NodeType::Internal : NodeType::Leaf,
0400             (_d + 1) % Dims);
0401       }
0402     }
0403 
0404     /// @brief Perform a range search in the k-d tree, mapping the key-value
0405     /// pairs to a side-effecting function.
0406     ///
0407     /// This is the most powerful range search method we have, assuming that we
0408     /// can use arbitrary side effects, which we can. All other range search
0409     /// methods are implemented in terms of this particular function.
0410     ///
0411     /// @param r The range to search for.
0412     /// @param f The mapping function to apply to matching elements.
0413     template <typename Callable>
0414     void rangeSearchMapDiscard(const range_t &r, Callable &&f) const {
0415       // Determine whether the range completely covers the bounding box of
0416       // this leaf node. If it is, we can copy all values without having to
0417       // check for them being inside the range again.
0418       bool contained = r >= m_range;
0419 
0420       if (m_type == NodeType::Internal) {
0421         // Firstly, we can check if the range completely contains the bounding
0422         // box of this node. If that is the case, we know for certain that any
0423         // value contained below this node should end up in the output, and we
0424         // can stop recursively looking for them.
0425         if (contained) {
0426           // We can also pre-allocate space for the number of elements, since we
0427           // are inserting all of them anyway.
0428           for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0429             f(i->first, i->second);
0430           }
0431 
0432           return;
0433         }
0434 
0435         assert(m_lhs && m_rhs && "Did not find lhs and rhs");
0436 
0437         // If we have a left-hand node (which we should!), then we check if
0438         // there is any overlap between the target range and the bounding box of
0439         // the left-hand node. If there is, we recursively search in that node.
0440         if (m_lhs->range() && r) {
0441           m_lhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0442         }
0443 
0444         // Then, we perform exactly the same procedure for the right hand side.
0445         if (m_rhs->range() && r) {
0446           m_rhs->rangeSearchMapDiscard(r, std::forward<Callable>(f));
0447         }
0448       } else {
0449         // Iterate over all the elements in this leaf node. This should be a
0450         // relatively small number (the LeafSize template parameter).
0451         for (iterator_t i = m_begin_it; i != m_end_it; ++i) {
0452           // We need to check whether the element is actually inside the range.
0453           // In case this node's bounding box is fully contained within the
0454           // range, we don't actually need to check this.
0455           if (contained || r.contains(i->first)) {
0456             f(i->first, i->second);
0457           }
0458         }
0459       }
0460     }
0461 
0462     /// @brief Determine the number of elements managed by this node.
0463     ///
0464     /// Conveniently, this number is always equal to the distance between the
0465     /// begin iterator and the end iterator, so we can simply delegate to the
0466     /// relevant standard library method.
0467     ///
0468     /// @return The number of elements below this node.
0469     std::size_t size() const { return std::distance(m_begin_it, m_end_it); }
0470 
0471     /// @brief The axis-aligned bounding box containing all elements in this
0472     /// node.
0473     ///
0474     /// @return The minimal axis-aligned bounding box that contains all the
0475     /// elements under this node.
0476     const range_t &range() const { return m_range; }
0477 
0478    protected:
0479     NodeType m_type;
0480 
0481     /// @brief The start and end of the range of coordinate-value pairs under
0482     /// this node.
0483     const iterator_t m_begin_it, m_end_it;
0484 
0485     /// @brief The axis-aligned bounding box of the coordinates under this
0486     /// node.
0487     const range_t m_range;
0488 
0489     /// @brief Pointers to the left and right children.
0490     std::unique_ptr<KDTreeNode> m_lhs;
0491     std::unique_ptr<KDTreeNode> m_rhs;
0492   };
0493 
0494   /// @brief Vector containing all of the elements in this k-d tree, including
0495   /// the elements managed by the nodes inside of it.
0496   vector_t m_elems;
0497 
0498   /// @brief Pointer to the root node of this k-d tree.
0499   std::unique_ptr<KDTreeNode> m_root;
0500 };
0501 }  // namespace Acts