Belle II Software development
HitOrderer Class Reference

Sorts CDC hits spatially using KD-tree nearest neighbor traversal. More...

#include <Utils.h>

Public Member Functions

 HitOrderer ()=default
 Constructor.
 
 ~HitOrderer ()=default
 Destructor.
 

Static Public Member Functions

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.
 

Static Private Member Functions

static KDTNodebuildKDTree (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.
 
template<typename Iterator, typename Compare>
static void insertionSort (Iterator begin, Iterator end, Compare cmp)
 Sorts a small range using insertion sort for performance.
 
static void freeKDTree (KDTNode *node)
 Recursively frees all nodes in a KD-tree.
 
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.
 
static bool markUsed (KDTNode *node, const KDTHit &hit)
 Marks a KD-tree node as used if its hit index matches the given hit.
 

Detailed Description

Sorts CDC hits spatially using KD-tree nearest neighbor traversal.

Constructs a 2D KD-tree from hit coordinates and performs iterative nearest-neighbor selection starting from a reference point to obtain a spatially coherent hit ordering.

Definition at line 123 of file Utils.h.

Member Function Documentation

◆ buildKDTree()

KDTNode * buildKDTree ( std::vector< KDTHit >::iterator begin,
std::vector< KDTHit >::iterator end,
const int depth,
KDTNodePool & pool,
const size_t insertionSortThreshold = 10 )
staticprivate

Recursively builds a balanced KD-tree from a range of KDTHits.

The KD-tree is built by recursively selecting median splits along alternating axes (x, y), using either std::nth_element or insertion sort for small partitions.

Parameters
beginIterator to the beginning of the KDTHit range.
endIterator to the end of the KDTHit range.
depthCurrent tree depth, used to alternate axis.
poolObject pool used to allocate KD-tree nodes.
insertionSortThresholdSwitch to insertion sort for small ranges.
Returns
Pointer to the root node of the constructed KD-tree.

Definition at line 57 of file Utils.cc.

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}
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
KDTNode * allocate()
Allocates and returns the next available KDTNode from the pool.
Definition Utils.cc:31
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

◆ freeKDTree()

void freeKDTree ( KDTNode * node)
staticprivate

Recursively frees all nodes in a KD-tree.

Parameters
nodeRoot node of the KD-tree to deallocate.

Definition at line 150 of file Utils.cc.

151{
152 if (!node)
153 return;
156 delete node;
157}
static void freeKDTree(KDTNode *node)
Recursively frees all nodes in a KD-tree.
Definition Utils.cc:150

◆ insertionSort()

template<typename Iterator, typename Compare>
void insertionSort ( Iterator begin,
Iterator end,
Compare cmp )
inlinestaticprivate

Sorts a small range using insertion sort for performance.

Template Parameters
IteratorType of iterator.
CompareComparison function type.
Parameters
beginIterator to the beginning of the range.
endIterator to the end of the range.
cmpComparator function for sorting.

Definition at line 47 of file Utils.cc.

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}
map< unsigned, size_t >::const_iterator Iterator
Iterator for m_map.

◆ markUsed()

bool markUsed ( KDTNode * node,
const KDTHit & hit )
staticprivate

Marks a KD-tree node as used if its hit index matches the given hit.

Used to ensure that hits are not reused in subsequent nearest-neighbor queries.

Parameters
nodeRoot of the KD-tree.
hitHit to match by index.
Returns
True if a matching node was found and marked as used.

Definition at line 126 of file Utils.cc.

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}
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
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

◆ nearestNeighbor()

void nearestNeighbor ( const KDTNode * node,
const KDTHit & query,
KDTHit & best,
double & bestDist )
staticprivate

Finds the closest unused neighbor to a query hit in the KD-tree.

The function recursively traverses the KD-tree and updates the best match and distance if a closer unused hit is found.

Parameters
nodeRoot of the KD-tree.
queryQuery hit to compare against.
bestBest match found so far (output).
bestDistSquared distance to the best match (output).

Definition at line 93 of file Utils.cc.

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}
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
double squaredDistanceTo(const KDTHit &other) const
Compute squared distance to another KDTHit.
Definition Utils.h:35

◆ orderHits()

std::vector< int > orderHits ( const double startingX,
const double startingY,
std::vector< KDTHit > kdtHits )
static

Sort hits spatially based on proximity to a starting position.

Builds a KD-tree from the input position and performs an iterative nearest-neighbor traversal to generate a spatially ordered list of hit indices.

Parameters
startingXStarting X position
startingYStarting Y position
kdtHitsVector of hits to order
Returns
Vector of hit indices sorted by spatial proximity to the start position.

Definition at line 159 of file Utils.cc.

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}

The documentation for this class was generated from the following files: