Belle II Software development
Utils.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <tracking/gnnFinder/Utils.h>
10#include <framework/logging/Logger.h>
11
12#include <algorithm>
13#include <limits>
14
15
16using namespace Belle2::GNNFinder::Utils;
17
18KDTNodePool::KDTNodePool(size_t capacity) : m_index(0)
19{
20 m_pool.resize(capacity);
21 std::generate(m_pool.begin(), m_pool.end(), [] { return new KDTNode(); });
22}
23
25{
26 for (KDTNode* node : m_pool) {
27 delete node;
28 }
29}
30
32{
33 if (m_index >= m_pool.size())
34 B2ERROR("KDTNodePool:allocate() exceeded pool capacity.");
35 // Hand out the next pre-allocated slot and advance the cursor
36 return m_pool[m_index++];
37}
38
40{
41 // Rewind the cursor so all slots can be reused in the next event
42 // without freeing / reallocating memory
43 m_index = 0;
44}
45
46template<typename Iterator, typename Compare>
47inline void HitOrderer::insertionSort(Iterator begin, Iterator end, Compare cmp)
48{
49 for (Iterator i = begin; i != end; ++i) {
50 // Bubble element i leftward until the sequence is locally sorted
51 for (Iterator j = i; j != begin && cmp(*j, *(j - 1)); --j) {
52 std::iter_swap(j, j - 1);
53 }
54 }
55}
56
57KDTNode* HitOrderer::buildKDTree(std::vector<KDTHit>::iterator begin, std::vector<KDTHit>::iterator end, const int depth,
58 KDTNodePool& pool, const size_t insertionSortThreshold)
59{
60 if (begin >= end)
61 return nullptr;
62
63 // Alternate splitting dimension with each level of recursion
64 const int dim = depth % 2;
65 const auto cmp = [dim](const KDTHit & a, const KDTHit & b) {
66 return (dim == 0) ? a.x < b.x : a.y < b.y;
67 };
68
69 const size_t n = std::distance(begin, end);
70 const auto mid = begin + n / 2; // Median element becomes the node's pivot
71
72 if (n < insertionSortThreshold) {
73 // Full sort for small ranges: cheaper than nth_element + recursion overhead
74 HitOrderer::insertionSort(begin, end, cmp);
75 } else {
76 // Partial sort: guarantees *mid is the true median, rest unsorted
77 std::nth_element(begin, mid, end, cmp);
78 }
79
80 // Claim a node from the pool and populate it with the median hit
81 KDTNode* node = pool.allocate();
82 node->hit = *mid;
83 node->used = false; // Will be set to true once this hit is added to the track
84 node->dim = dim;
85
86 // Recurse on the left (< median) and right (> median) sub-ranges
87 node->left = HitOrderer::buildKDTree(begin, mid, depth + 1, pool);
88 node->right = HitOrderer::buildKDTree(mid + 1, end, depth + 1, pool);
89
90 return node;
91}
92
93void HitOrderer::nearestNeighbor(const KDTNode* node, const KDTHit& query, KDTHit& best, double& bestDist)
94{
95 if (!node)
96 return;
97
98 // Evaluate the current node if it hasn't been assigned to the track yet
99 if (!node->used) {
100 const double d = query.squaredDistanceTo(node->hit);
101 if (d < bestDist) {
102 bestDist = d;
103 best = node->hit;
104 }
105 }
106
107 // Determine which child subtree lies on the same side as the query point
108 const int dim = node->dim;
109 const double qVal = (dim == 0) ? query.x : query.y;
110 const double nVal = (dim == 0) ? node->hit.x : node->hit.y;
111
112 KDTNode* near = (qVal < nVal) ? node->left : node->right;
113 KDTNode* far = (qVal < nVal) ? node->right : node->left;
114
115 // Always descend into the near subtree first (more likely to improve bestDist)
116 nearestNeighbor(near, query, best, bestDist);
117
118 // Pruning: search only the "far" side if the distance to the
119 // splitting plane is less than the best distance found so far
120 const double diff = qVal - nVal;
121 if ((diff * diff) < bestDist) {
122 nearestNeighbor(far, query, best, bestDist);
123 }
124}
125
126bool HitOrderer::markUsed(KDTNode* node, const KDTHit& hit)
127{
128 if (!node)
129 return false;
130
131 // Found the matching node: mark and return
132 if (node->hit.hitIndex == hit.hitIndex) {
133 node->used = true;
134 return true;
135 }
136
137 // Descend into the child whose partition contains the hit's coordinate
138 const int dim = node->dim;
139 const double hVal = (dim == 0) ? hit.x : hit.y;
140 const double nVal = (dim == 0) ? node->hit.x : node->hit.y;
141
142 KDTNode* first = (hVal < nVal) ? node->left : node->right;
143 KDTNode* second = (hVal < nVal) ? node->right : node->left;
144
145 // If the primary branch fails (e.g. due to equal coordinates on the
146 // splitting axis), fall back to the other child rather than giving up
147 return markUsed(first, hit) or markUsed(second, hit);
148}
149
151{
152 if (!node)
153 return;
156 delete node;
157}
158
159std::vector<int> HitOrderer::orderHits(const double startingX, const double startingY,
160 // cppcheck-suppress passedByValue
161 std::vector<KDTHit> kdtHits)
162{
163 // Return an empty vector if kdtHits is empty
164 if (kdtHits.empty())
165 return std::vector<int> {};
166
167 // Build a KD-tree over the hits; pool size equals number of hits (one node per hit)
168 KDTNodePool pool(kdtHits.size());
169 KDTNode* root = buildKDTree(kdtHits.begin(), kdtHits.end(), 0, pool);
170
171 // Define the starting hit (-1 here means it's not a real hit)
172 const KDTHit startingHit{startingX, startingY, -1};
173
174 // Linear scan to find the real hit closest to the starting position.
175 // A KD-tree query is not used here because the starting position is not in the tree,
176 // and the list is typically short enough that a scan is negligible.
177 const KDTHit& firstHit = *std::min_element(kdtHits.begin(), kdtHits.end(),
178 [&startingHit](const KDTHit & a, const KDTHit & b) {
179 return a.squaredDistanceTo(startingHit) < b.squaredDistanceTo(startingHit);
180 });
181
182 // Mark the first hit as consumed so it won't be returned by nearestNeighbor
183 markUsed(root, firstHit);
184
185 // Prepare the output
186 std::vector<int> sortedIndices;
187 sortedIndices.reserve(kdtHits.size());
188 sortedIndices.push_back(firstHit.hitIndex);
189
190 // Greedy chain: at each step find the nearest unused hit to the current one
191 KDTHit currentHit = firstHit;
192 for (size_t i = 1; i < kdtHits.size(); ++i) {
193 KDTHit bestNeighbor;
194 double bestNeighborDist = std::numeric_limits<double>::max();
195 nearestNeighbor(root, currentHit, bestNeighbor, bestNeighborDist);
196
197 // If no unused hit was found (all remaining are marked used), stop early
198 if (bestNeighborDist == std::numeric_limits<double>::max())
199 break;
200
201 currentHit = bestNeighbor;
202 sortedIndices.push_back(currentHit.hitIndex);
203 markUsed(root, currentHit); // Prevent this hit from being selected again
204 }
205
206 return sortedIndices;
207}
208
209std::pair<double, double> Belle2::GNNFinder::Utils::intersectCylinderXY(const ROOT::Math::XYZVector& pos,
210 const ROOT::Math::XYZVector& mom,
211 const double targetR)
212{
213 // Check if we are already outside or at the boundary
214 const double rSq = pos.X() * pos.X() + pos.Y() * pos.Y();
215 if (rSq >= targetR * targetR)
216 return {pos.X(), pos.Y()};
217 // Coefficients for a*t^2 + b*t + c = 0
218 // Solving for |(pos + t*mom).xy| = targetR
219 const double a = mom.X() * mom.X() + mom.Y() * mom.Y();
220 const double b = 2.0 * (pos.X() * mom.X() + pos.Y() * mom.Y());
221 const double c = rSq - (targetR * targetR);
222 const double discriminant = b * b - 4.0 * a * c;
223 if (discriminant < 0 or a == 0)
224 return {pos.X(), pos.Y()};
225 const double sqrtD = std::sqrt(discriminant);
226 const double invA = 1.0 / a;
227 const double t1 = 0.5 * (-b + sqrtD) * invA;
228 const double t2 = 0.5 * (-b - sqrtD) * invA;
229 // Get the first positive intersection point
230 double t = -1.0;
231 if (t1 > 0 && t2 > 0) t = std::min(t1, t2);
232 else if (t1 > 0) t = t1;
233 else if (t2 > 0) t = t2;
234 if (t > 0)
235 return {pos.X() + t * mom.X(), pos.Y() + t * mom.Y()};
236 return {pos.X(), pos.Y()};
237}
static void nearestNeighbor(const KDTNode *node, const KDTHit &query, KDTHit &best, double &bestDist)
Finds the closest unused neighbor to a query hit in the KD-tree.
Definition Utils.cc:93
static bool markUsed(KDTNode *node, const KDTHit &hit)
Marks a KD-tree node as used if its hit index matches the given hit.
Definition Utils.cc:126
static void freeKDTree(KDTNode *node)
Recursively frees all nodes in a KD-tree.
Definition Utils.cc:150
static KDTNode * buildKDTree(std::vector< KDTHit >::iterator begin, std::vector< KDTHit >::iterator end, const int depth, KDTNodePool &pool, const size_t insertionSortThreshold=10)
Recursively builds a balanced KD-tree from a range of KDTHits.
Definition Utils.cc:57
static void insertionSort(Iterator begin, Iterator end, Compare cmp)
Sorts a small range using insertion sort for performance.
Definition Utils.cc:47
static std::vector< int > orderHits(const double startingX, const double startingY, std::vector< KDTHit > kdtHits)
Sort hits spatially based on proximity to a starting position.
Definition Utils.cc:159
Memory pool for pre-allocating and reusing KDTNode objects.
Definition Utils.h:70
size_t m_index
Current index for the next available node in the pool.
Definition Utils.h:75
KDTNodePool(size_t capacity)
Constructs a pool with the specified capacity.
Definition Utils.cc:18
std::vector< KDTNode * > m_pool
Internal storage for preallocated KD-tree nodes.
Definition Utils.h:73
void reset()
Resets the pool index, making all nodes available for reuse.
Definition Utils.cc:39
KDTNode * allocate()
Allocates and returns the next available KDTNode from the pool.
Definition Utils.cc:31
map< unsigned, size_t >::const_iterator Iterator
Iterator for m_map.
Lightweight 2D representation of a CDC hit for KD-tree sorting.
Definition Utils.h:23
double squaredDistanceTo(const KDTHit &other) const
Compute squared distance to another KDTHit.
Definition Utils.h:35
double y
Y coordinate of the hit.
Definition Utils.h:27
int hitIndex
Index of the hit in the original input collection.
Definition Utils.h:29
double x
X coordinate of the hit.
Definition Utils.h:25
Node structure for a 2D KD-tree used in spatial hit sorting.
Definition Utils.h:50
KDTHit hit
The hit associated with this node (contains x, y, and index).
Definition Utils.h:52
int dim
Splitting dimension: 0 for x-axis, 1 for y-axis.
Definition Utils.h:60
KDTNode * left
Pointer to the left subtree (for values less than split).
Definition Utils.h:54
KDTNode * right
Pointer to the right subtree (for values greater than split).
Definition Utils.h:56
bool used
Flag indicating whether the node has already been used in traversal.
Definition Utils.h:58