ATLAS Offline Software
Loading...
Searching...
No Matches
CompactSegmentFinderTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
40
43
44namespace L0MDT {
45
47 // Retrieve the MDT identifier helper service
48 ATH_CHECK(m_idHelperSvc.retrieve());
49 return StatusCode::SUCCESS;
50 }
51
52
54 const std::vector<const xAOD::MdtDriftCircle*>& driftCircles,
55 const ActsTrk::GeometryContext& gctx,
56 float m,
57 float rpcB,
58 std::vector<L0MDT::Segment>& segments) const {
59
60 // Reset the output container for this call
61 segments.clear();
62
63 ATH_MSG_DEBUG("In CompactSegmentFinderTool::findSegments()");
64 ATH_MSG_DEBUG("Size of drift circles vector: " << driftCircles.size()
65 << ", size of segments vector: " << segments.size());
66
67 // Build a compact per-hit representation containing all quantities
68 // needed for clustering and final fitting
69 std::vector<HitInfo> hitInfos;
70 ATH_CHECK(buildHitInfo(driftCircles, gctx, m, hitInfos));
71
72 // A straight-line fit requires at least two usable hits
73 if (hitInfos.size() < 2) {
74 ATH_MSG_DEBUG("Not enough hits for clustering");
75 return StatusCode::SUCCESS;
76 }
77
78 // Split hit indices by multilayer.
79 // The current algorithm clusters each multilayer independently.
80 std::array<std::vector<std::size_t>, 2> hitsPerML;
81 for (std::size_t i = 0; i < hitInfos.size(); ++i) {
82 hitsPerML[hitInfos[i].multilayer].push_back(i);
83 }
84
85 // Sort hits by increasing drift radius.
86 // This follows the standalone logic and gives a deterministic cluster growth order.
87 for (auto& indices : hitsPerML) {
88 std::sort(indices.begin(), indices.end(),
89 [&hitInfos](std::size_t a, std::size_t b) {
90 return hitInfos[a].driftRadius < hitInfos[b].driftRadius;
91 });
92 }
93
94 // Store all clusters per multilayer, and the best cluster eventually selected
95 std::array<std::vector<Cluster>, 2> clustersPerML;
96 std::array<std::optional<Cluster>, 2> bestClusters;
97
98 // Build clusters and choose the best one in each multilayer
99 for (int ml = 0; ml < 2; ++ml) {
100 clusterHits(hitInfos, hitsPerML[ml], clustersPerML[ml]);
101
102 if (m_debugClusters) {
103 ATH_MSG_INFO("ML " << ml << " has " << clustersPerML[ml].size() << " clusters");
104 for (const Cluster& cl : clustersPerML[ml]) {
105 ATH_MSG_INFO(" refB=" << cl.refB << " nHits=" << cl.hitIndices.size());
106 }
107 }
108
109 bestClusters[ml] = chooseBestCluster(clustersPerML[ml], rpcB);
110 }
111
112 // Build corrected hit positions from the selected clusters.
113 // The correction moves the tube center to the estimated track point using
114 // the left/right solution selected during clustering.
115 std::vector<float> correctedZ;
116 std::vector<float> correctedR;
117 correctedZ.reserve(hitInfos.size());
118 correctedR.reserve(hitInfos.size());
119
120 for (int ml = 0; ml < 2; ++ml) {
121 if (!bestClusters[ml]) continue;
122
123 const Cluster& cl = *bestClusters[ml];
124 for (std::size_t i = 0; i < cl.hitIndices.size(); ++i) {
125 const HitInfo& h = hitInfos[cl.hitIndices[i]];
126 const bool plus = cl.plusFlags[i];
127
128 // If the selected branch is b+, move to the corresponding side of the tube.
129 // Otherwise use the opposite correction.
130 if (plus) {
131 correctedZ.push_back(h.z - h.deltaZ);
132 correctedR.push_back(h.r + h.deltaR);
133 } else {
134 correctedZ.push_back(h.z + h.deltaZ);
135 correctedR.push_back(h.r - h.deltaR);
136 }
137 }
138 }
139
140 // Require at least two corrected points for the final segment fit
141 if (correctedZ.size() < 2) {
142 ATH_MSG_DEBUG("Not enough corrected hits after cluster selection");
143 return StatusCode::SUCCESS;
144 }
145
146 // Perform the final straight-line fit in the (z, R) plane
147 const FitResult fit = fitLine(correctedZ, correctedR);
148 if (!fit.valid) {
149 ATH_MSG_DEBUG("Final fit failed");
150 return StatusCode::SUCCESS;
151 }
152
153
154 float segZMin = *std::min_element(correctedZ.begin(), correctedZ.end());
155 float segZMax = *std::max_element(correctedZ.begin(), correctedZ.end());
156 float segZRef = 0.f;
157 for (float z : correctedZ) {
158 segZRef += z;
159 }
160 segZRef /= static_cast<float>(correctedZ.size());
161
162 // Put the representative point on the fitted line
163 float segRRef = fit.m * segZRef + fit.b;
164
165 // Fill the output segment object here according to the actual EDM API
166
167 L0MDT::Segment seg;
168 seg.setM(fit.m);
169 seg.setB(fit.b);
170 seg.setChi2(fit.chi2);
171 seg.setNHits(correctedZ.size());
172
173 seg.setZMin(segZMin);
174 seg.setZMax(segZMax);
175 seg.setZRef(segZRef);
176 seg.setRRef(segRRef);
177
178 segments.push_back(seg);
179
180
181 // Print the final CSF segment parameters in a parser-friendly format
182 ATH_MSG_DEBUG("CSF segment fit | "
183 << "m=" << fit.m
184 << " b=" << fit.b
185 << " chi2=" << fit.chi2
186 << " nHits=" << correctedZ.size()
187 << " zMin=" << segZMin
188 << " zMax=" << segZMax
189 << " zRef=" << segZRef
190 << " rRef=" << segRRef);
191 return StatusCode::SUCCESS;
192 }
193
195
197 const std::vector<const xAOD::MdtDriftCircle*>& driftCircles,
198 const ActsTrk::GeometryContext& gctx,
199 float m,
200 std::vector<HitInfo>& hitInfos) const {
201
202 // Reset and pre-allocate the output hit container
203 hitInfos.clear();
204 hitInfos.reserve(driftCircles.size());
205
206 // Common normalization factor used in the b+/b- construction and in the
207 // geometrical correction from tube center to track point
208 const float slopeNormalization = std::sqrt(1.f + m * m);
209
210
211
212
213 for (const xAOD::MdtDriftCircle* dc : driftCircles) {
214
215 const Identifier id = dc->identify();
216 const auto& idh = m_idHelperSvc->mdtIdHelper();
217
218 // Convert multilayer numbering from {1,2} to {0,1}
219 const int ml = idh.multilayer(id) - 1;
220
221 const MuonGMR4::MdtReadoutElement* detEl = dc->readoutElement();
222
223 // Build the global position of the hit from the local measurement
224 const Amg::Vector3D gpos= detEl->globalTubePos(gctx, dc->measurementHash());
225
226 const float zHit = gpos.z();
227 const float rHit = gpos.perp();
228
229
230 // Drift radius is the distance between the wire and the track.
231 // The sign is not used here because the left/right ambiguity is treated
232 // explicitly through the two branches b+ and b-.
233 const float driftRadius = dc->driftRadius();
234
235 HitInfo info;
236 info.dc = dc;
237 info.z = zHit;
238 info.r = rHit;
239 info.driftRadius = driftRadius;
240 info.multilayer = ml;
241
242 // Temporary intercepts associated with the two possible sides of the tube
243 info.bPlus = (rHit - m * zHit) + slopeNormalization * driftRadius;
244 info.bMinus = (rHit - m * zHit) - slopeNormalization * driftRadius;
245
246 // Geometrical correction from tube center to track point
247 info.deltaZ = (m / slopeNormalization) * driftRadius;
248 info.deltaR = (1.f / slopeNormalization) * driftRadius;
249
250 hitInfos.push_back(info);
251 }
252
253 return StatusCode::SUCCESS;
254 }
255
256
257 void CompactSegmentFinderTool::clusterHits(const std::vector<HitInfo>& hitInfos,
258 const std::vector<std::size_t>& orderedIndices,
259 std::vector<Cluster>& clusters) const {
260 clusters.clear();
261
262
263 for (std::size_t idx : orderedIndices) {
264 const HitInfo& hit = hitInfos[idx];
265 bool assigned = false;
266
267 // Try to attach the hit to one of the existing clusters.
268 // The hit can enter through either its b+ or b- branch.
269 for (Cluster& cl : clusters) {
270 const float dPlus = std::abs(hit.bPlus - cl.refB);
271 const float dMinus = std::abs(hit.bMinus - cl.refB);
272
273 // Prefer the b+ branch if it is both closer than b- and within tolerance
274 if (dPlus < dMinus && dPlus < m_clusterTolerance) {
275 cl.hitIndices.push_back(idx);
276 cl.plusFlags.push_back(true);
277 assigned = true;
278 break;
279 }
280
281 // Otherwise try the b- branch if it is within tolerance
282 else if (dMinus < m_clusterTolerance) {
283 cl.hitIndices.push_back(idx);
284 cl.plusFlags.push_back(false);
285 assigned = true;
286 break;
287 }
288 }
289
290 // If the hit does not match any existing cluster, spawn two new clusters:
291 // one for b+ and one for b-.
292 if (!assigned && static_cast<int>(clusters.size()) + 2 <= m_maxClusters) {
293 Cluster plusCluster;
294 plusCluster.refB = hit.bPlus;
295 plusCluster.hitIndices.push_back(idx);
296 plusCluster.plusFlags.push_back(true);
297 clusters.push_back(std::move(plusCluster));
298
299 Cluster minusCluster;
300 minusCluster.refB = hit.bMinus;
301 minusCluster.hitIndices.push_back(idx);
302 minusCluster.plusFlags.push_back(false);
303 clusters.push_back(std::move(minusCluster));
304 }
305 }
306 }
307
308
309 std::optional<CompactSegmentFinderTool::Cluster>
310 CompactSegmentFinderTool::chooseBestCluster(const std::vector<Cluster>& clusters,
311 float rpcB) const {
312 // No clusters available
313 if (clusters.empty()) return std::nullopt;
314
315 // First selection criterion: maximize the number of hits in the cluster
316 std::size_t maxSize = 0;
317 for (const Cluster& cl : clusters) {
318 maxSize = std::max(maxSize, cl.hitIndices.size());
319 }
320
321 // Keep only clusters with maximal multiplicity
322 std::vector<const Cluster*> candidates;
323 for (const Cluster& cl : clusters) {
324 if (cl.hitIndices.size() == maxSize) {
325 candidates.push_back(&cl);
326 }
327 }
328
329 // Tie-break criterion: choose the cluster closest to the RPC seed intercept
330 auto it = std::min_element(
331 candidates.begin(), candidates.end(),
332 [rpcB](const Cluster* cl_a, const Cluster* cl_b) {
333 return std::abs(cl_a->refB - rpcB) < std::abs(cl_b->refB - rpcB);
334 });
335
336 return **it;
337 }
338
339
341 CompactSegmentFinderTool::fitLine(const std::vector<float>& zVals,
342 const std::vector<float>& rVals,
343 float sigma) const {
344 FitResult out;
345
346 const std::size_t n = zVals.size();
347
348 // The fit is only meaningful if both vectors are consistent and contain
349 // at least two points
350 if (n < 2 || rVals.size() != n) return out;
351
352 float sumZ = 0.f;
353 float sumR = 0.f;
354 float sumZZ = 0.f;
355 float sumZR = 0.f;
356
357 // Accumulate the standard least-squares sums for R = m*z + b
358 for (std::size_t i = 0; i < n; ++i) {
359 sumZ += zVals[i];
360 sumR += rVals[i];
361 sumZZ += zVals[i] * zVals[i];
362 sumZR += zVals[i] * rVals[i];
363 }
364
365 const float N = static_cast<float>(n);
366 const float den = N * sumZZ - sumZ * sumZ;
367
368 // Degenerate case: all points have effectively the same z
369 if (std::abs(den) < 1e-6f) return out;
370
371 // Standard straight-line least-squares solution
372 out.m = (N * sumZR - sumZ * sumR) / den;
373 out.b = (sumR - out.m * sumZ) / N;
374
375 // Compute a simple chi2-like quantity using a common sigma
376 float chi2 = 0.f;
377 for (std::size_t i = 0; i < n; ++i) {
378 const float res = (rVals[i] - (out.m * zVals[i] + out.b)) / sigma;
379 chi2 += res * res;
380 }
381
382 out.chi2 = chi2;
383 out.valid = true;
384 return out;
385 }
386
387} // end of namespace
388
389
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_INFO(x)
#define ATH_MSG_DEBUG(x)
std::pair< std::vector< unsigned int >, bool > res
static Double_t a
size_t size() const
Number of registered mappings.
#define z
Header file for AthHistogramAlgorithm.
virtual StatusCode findSegments(const std::vector< const xAOD::MdtDriftCircle * > &driftCircles, const ActsTrk::GeometryContext &gctx, float m, float b, std::vector< L0MDT::Segment > &segments) const override
Main segment finding entry point.
std::optional< Cluster > chooseBestCluster(const std::vector< Cluster > &clusters, float b) const
Select the best cluster among the available candidates.
Gaudi::Property< int > m_maxClusters
Maximum number of temporary clusters allowed during clustering.
virtual StatusCode buildHitInfo(const std::vector< const xAOD::MdtDriftCircle * > &driftCircles, const ActsTrk::GeometryContext &gctx, float m, std::vector< HitInfo > &hitInfos) const
Build the compact per-hit representation used by the algorithm.
ServiceHandle< Muon::IMuonIdHelperSvc > m_idHelperSvc
MDT identifier helper service.
FitResult fitLine(const std::vector< float > &zVals, const std::vector< float > &rVals, float sigma=1.f/8.f) const
Fit a straight line in the (z, R) plane.
Gaudi::Property< bool > m_debugClusters
Debug flag for printing cluster content and selection.
void clusterHits(const std::vector< HitInfo > &hitInfos, const std::vector< std::size_t > &orderedIndices, std::vector< Cluster > &clusters) const
Cluster ordered hits in intercept space.
Gaudi::Property< float > m_clusterTolerance
Maximum allowed distance in intercept space when attaching a hit to an existing cluster.
virtual StatusCode initialize() override
Standard Athena initialize method.
Class describing a reconstructed MDT segment used by the L0Muon trigger.
void setM(float x)
void setRRef(float x)
void setZMax(float x)
void setB(float x)
void setChi2(float x)
void setZRef(float x)
void setZMin(float x)
void setNHits(unsigned int x)
Readout element to describe the Monitored Drift Tube (Mdt) chambers Mdt chambers usually comrpise out...
Amg::Vector3D globalTubePos(const ActsTrk::GeometryContext &ctx, const Identifier &measId) const
Returns the position of the tube mid point in the ATLAS coordinate frame.
double chi2(TH1 *h0, TH1 *h1)
Eigen::Matrix< double, 3, 1 > Vector3D
Compact Segment Finder algorithm overview.
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
MdtDriftCircle_v1 MdtDriftCircle
Temporary cluster of hit intercept hypotheses.
Output of the final straight-line fit.
Compact representation of one MDT hit used by the clustering and fit steps.