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