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 L1Muon::L1MDT {
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<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 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_DEBUG(x,...)
#define ATH_MSG_INFO(x,...)
std::vector< std::vector< int64_t > > indices
std::pair< std::vector< unsigned int >, bool > res
bool hit(const Container &ids, int pdgId)
static Double_t a
size_t size() const
Number of registered mappings.
#define z
Header file for AthHistogramAlgorithm.
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.
Gaudi::Property< bool > m_debugClusters
Debug flag for printing cluster content and selection.
Gaudi::Property< float > m_clusterTolerance
Maximum allowed distance in intercept space when attaching a hit to an existing cluster.
Gaudi::Property< int > m_maxClusters
Maximum number of temporary clusters allowed during clustering.
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.
std::optional< Cluster > chooseBestCluster(const std::vector< Cluster > &clusters, float b) const
Select the best cluster among the available candidates.
virtual StatusCode findSegments(const std::vector< const xAOD::MdtDriftCircle * > &driftCircles, const ActsTrk::GeometryContext &gctx, float m, float b, std::vector< Segment > &segments) const override
Main segment finding entry point.
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.
virtual StatusCode initialize() override
Standard Athena initialize method.
ServiceHandle< Muon::IMuonIdHelperSvc > m_idHelperSvc
MDT identifier helper service.
Class describing a reconstructed MDT segment used by the L1Muon trigger.
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.
Compact representation of one MDT hit used by the clustering and fit steps.