![]() |
|
|||
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
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] | [ general search ] |
This page was automatically generated by the 2.3.7 LXR engine. The LXR team |
![]() ![]() |