ATLAS Offline Software
Loading...
Searching...
No Matches
GlobalPatternFinder.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
10
11#include "Acts/Utilities/Helpers.hpp"
12
13#include "CxxUtils/phihelper.h"
14
15namespace {
16 const Muon::MuonSectorMapping sectorMap{};
17
19 bool areConsecutiveMdt(const MuonR4::SpacePoint& hit1, const MuonR4::SpacePoint& hit2) {
20 if (!hit1.isStraw() || !hit2.isStraw()) {
21 return false;
22 }
23 const uint16_t tubeNum1 {static_cast<const xAOD::MdtDriftCircle*>(hit1.primaryMeasurement())->driftTube()};
24 const uint16_t tubeNum2 {static_cast<const xAOD::MdtDriftCircle*>(hit2.primaryMeasurement())->driftTube()};
28 if(tubeNum1 == tubeNum2) {
29 return false;
30 };
31 return std::abs(tubeNum1-tubeNum2) < 2;
32 }
33 double inDegrees(double angle) {
34 return angle / Gaudi::Units::deg;
35 }
37 using PatternHitVisualInfoVec = std::vector<PatternHitVisualInfo>;
42 void resetVisualToOverlap(PatternHitVisualInfoVec* visualInfo) {
43 if (!visualInfo) return;
44 const auto [first, last] = std::ranges::remove_if(*visualInfo, [](const PatternHitVisualInfo& v){
45 return v.status == PatternHitVisualInfo::PatternStatus::eSuccessful; });
46
47 visualInfo->erase(first,last);
48 }
49}
50
51namespace MuonR4::FastReco {
52GlobalPatternFinder::GlobalPatternFinder(const std::string& name, Config&& config) :
53 AthMessaging{name},
54 m_cfg{config} {
55 static_assert(std::is_move_assignable_v<PatternState>);
56 static_assert(std::is_move_constructible_v<PatternState>);
57 static_assert(std::is_copy_assignable_v<PatternState>);
58 static_assert(std::is_copy_constructible_v<PatternState>);
59 static_assert(std::is_nothrow_move_constructible_v<PatternState>);
60 static_assert(std::is_nothrow_move_assignable_v<PatternState>);
61};
62
63
66 const SpacePointContainerVec& spacepoints,
67 BucketPerContainer& outBuckets) const {
69 auto visualInfo {m_cfg.visionTool ? std::make_unique<PatternHitVisualInfoVec>() : nullptr};
70 PatternStateVec patterns{findPatternsInEta(constructTree(gctx, spacepoints), visualInfo.get())};
71
74
76 for (const PatternState& pat : patterns) {
79
80 for (const auto& [spc, bucketVec] : pat.bucketsPerContainer) {
81 if (outBuckets.find(spc) == outBuckets.end()) {
82 throw std::runtime_error("The space point container associated to the pattern is not present in the output bucket map.");
83 }
84 for (const SpacePointBucket* bucket : bucketVec) {
85 auto& outBucketVec = outBuckets[spc];
86 if (std::ranges::find(outBucketVec, bucket) == outBucketVec.end()) {
87 outBucketVec.push_back(bucket);
88 }
89 }
90 }
91 }
93 if (visualInfo) {
94 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(), "GlobPatFind_", std::move(*visualInfo));
95 }
97}
98
101 PatternHitVisualInfoVec* visualInfo) const {
102 constexpr auto thetaIdx {Acts::toUnderlying(SeedCoords::eTheta)};
103 constexpr auto sectorIdx {Acts::toUnderlying(SeedCoords::eSector)};
105 using enum SeedCoords;
106 for (const auto seedingLayer : m_cfg.layerSeedings) {
107 ATH_MSG_VERBOSE(__func__<<"() Start pattern search in eta with seeding layer "<< layerName(seedingLayer));
109 for (const auto& [seedCoords, seed] : orderedSpacepoints) {
111 const LayerIndex seedLayer {toLayerIndex(seed.station)};
112 if (seedLayer != seedingLayer || (seed->isStraw() && !m_cfg.seedFromMdt)) {
113 continue;
114 }
115 ATH_MSG_VERBOSE(__func__<<"() New seed hit "<<*seed<<", coordinates "<<seedCoords);
117 auto nExistingPatterns {std::ranges::count_if(patterns, [&seed, &seedCoords, this](const PatternState& pattern){
118 if (std::abs(pattern.theta - seedCoords[thetaIdx]) > m_cfg.thetaSearchWindow ||
119 pattern.sectorCoord != static_cast<int>(seedCoords[sectorIdx])) {
120 return false;
121 }
122 return pattern.isInPattern(seed);
123 })};
124 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
125 ATH_MSG_VERBOSE(__func__<<"() Seed has already been used in "<<nExistingPatterns<<" patterns, which is above the limit - skip this seed.");
126 continue;
127 }
129 SearchTree_t::range_t selectRange{};
131 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
135 const double thetaWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
136 ? m_cfg.thetaSearchWindow : 0.5*m_cfg.thetaSearchWindow};
137 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
139 std::vector<TreeNode> candidateHits = orderedSpacepoints.rangeSearchWithKey(selectRange);
140 if (candidateHits.size() < m_cfg.minBendingTriggerHits + m_cfg.minBendingPrecisionHits) {
141 ATH_MSG_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits, below the minimum required - skip this seed.");
142 continue;
143 }
145 if (std::ranges::none_of(candidateHits, [this, seedLayer](const TreeNode& c){
146 return m_cfg.idHelperSvc->layerIndex(c.second->identify()) != seedLayer; }) ) {
147 ATH_MSG_VERBOSE(__func__<<"() All candidates are in the same station layer, and we need at least two - skip this seed.");
148 continue;
149 }
151 std::ranges::sort(candidateHits, [](const TreeNode& c1, const TreeNode& c2){
152 const HitPayload& hit1 {c1.second};
153 const HitPayload& hit2 {c2.second};
154 LayerOrdering ordering {checkLayerOrdering(hit1, hit2)};
155 if (ordering == eSameLayer) {
157 if (hit1.isPrecision != hit2.isPrecision) {
158 return hit2.isPrecision;
159 }
161 return hit1->localPosition().y() < hit2->localPosition().y();
162 }
163 return ordering == eLowerLayer;
164 });
165 if (msgLvl(MSG::VERBOSE)) {
166 ATH_MSG_VERBOSE(__func__<<"() Found "<< candidateHits.size()<<" candidate hits: ");
167 for (const auto& [coords, hit] : candidateHits) {
168 ATH_MSG_VERBOSE(__func__<<"() \t**"<<*hit<<", coords: "<<coords << ", glob Z/R/phi: "<<hit.Z<<" / "<<hit.R<<" / "<<inDegrees(hit.phi)
169 << ", st/layer: " << stName(hit.station) << " / "<< hit.layerNum);
170 }
171 }
172 const auto seedItr {std::ranges::find_if(candidateHits,
173 [&seed](const TreeNode& c){ return c.second == seed; })};
174 assert(seedItr != candidateHits.end());
176 std::vector<PatternState> activePatterns{};
177 activePatterns.emplace_back(seed, static_cast<int>(seedCoords[sectorIdx]), seedCoords[thetaIdx]);
178 if (visualInfo) {
179 activePatterns.back().visualInfo = std::make_unique<PatternHitVisualInfo>(
180 seed.hit, seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
181 }
182
183 const HitPayload* prevCandidate {&seed};
189 auto processNewHit = [&](const TreeNode& testPair){
190 const HitPayload& test {testPair.second};
191 ATH_MSG_VERBOSE("processNewHit() *** Test "<<*test<<" against " << activePatterns.size() << " active patterns.");
192 extendPatterns(activePatterns, test, seed, *prevCandidate, visualInfo);
193 prevCandidate = &test;
194 };
197 auto ensureOnePattern = [&activePatterns, &visualInfo, this]() {
198 if (activePatterns.size() > 1) {
199 ATH_MSG_VERBOSE("ensureOnePattern() Found "<<activePatterns.size()<<" patterns in current search stage - keep the best one.");
200 activePatterns = resolveOverlaps(std::move(activePatterns), visualInfo);
201 resetVisualToOverlap(visualInfo);
202 assert(activePatterns.size() == 1);
203 }
204 };
205
207 ATH_MSG_VERBOSE(__func__<<"() Search between " << candidateHits.size() <<" candidates for compatible hits...");
208 for (auto it = std::next(seedItr); it != candidateHits.end(); ++it) {
209 processNewHit(*it);
210 }
211 ensureOnePattern();
213 for (auto it = std::reverse_iterator(seedItr); it != candidateHits.rend(); ++it) {
214 processNewHit(*it);
215 }
216 ensureOnePattern();
217 PatternState& newPattern {activePatterns.back()};
219 if (newPattern.nBendingTriggerHits < m_cfg.minBendingTriggerHits ||
220 newPattern.nPrecisionHits < m_cfg.minBendingPrecisionHits ||
221 std::ranges::count_if(newPattern.hitsPerStation, [](const auto& st) { return st.second.size()>= 3; }) < 2) {
222 ATH_MSG_VERBOSE(__func__<<"() Pattern " << newPattern << "\ndoes not meet minimum hit requirements - reject.");
224 continue;
225 }
226 newPattern.meanNormResidual2 /= (newPattern.nPrecisionHits + newPattern.nBendingTriggerHits);
227 if (newPattern.meanNormResidual2 > m_cfg.meanNormRes2Cut) {
228 ATH_MSG_VERBOSE(__func__<<"() Pattern " << newPattern << "\ndoes not meet the mean norm residual2 cut - reject.");
230 continue;
231 }
232 newPattern.isFinalized = true;
233 ATH_MSG_VERBOSE(__func__<<"() Add new pattern "<<newPattern);
234 patterns.push_back(std::move(newPattern));
235 }
236 }
237 ATH_MSG_VERBOSE("Found in total "<<patterns.size()<<" patterns in eta before overlap removal");
238 return resolveOverlaps(std::move(patterns), visualInfo);
239}
241 const HitPayload& test,
242 const HitPayload& seed,
243 const HitPayload& prevCandidate,
244 PatternHitVisualInfoVec* visualInfo) const {
245 // Filter patterns: deduplicate groups sharing the same last hit, but only when the next
246 // test hit is on a different layer — if it's on the same layer we might branch again and need both
247 if (activePatterns.size() > 1) {
248 // Sort pointers to avoid moving pattern states during deduplication
249 std::vector<PatternState*> patPtrs(activePatterns.size());
250 std::ranges::transform(activePatterns, patPtrs.begin(), [](PatternState& p){ return &p; });
251 // Sort patteres by last inserted hit to find groups of patterns sharing the same last hit
252 std::ranges::sort(patPtrs, {}, [](const PatternState* p){ return p->lastInsertedHit; });
253
254 PatternStateVec deduplicated{};
255 deduplicated.reserve(activePatterns.size());
256 for (auto it = patPtrs.begin(); it != patPtrs.end(); ) {
257 // Find the group of patterns sharing the same last hit
258 const SpacePoint* groupLastHit {(*it)->lastInsertedHit};
259 auto groupEnd = std::ranges::find_if(it, patPtrs.end(), [&](const PatternState* p) {
260 return p->lastInsertedHit != groupLastHit;
261 });
262 if (checkLayerOrdering(test, (*it)->getNthLastHit(1u)) != eSameLayer) {
263 // If last hit of the group is on a different layer than the test hit, we deduplicate and pick the best pattern in the group
264 deduplicated.push_back(std::move(**std::ranges::max_element(it, groupEnd,
265 [this](const PatternState* a, const PatternState* b){ return isBetter(*b, *a); })));
266 } else {
267 // If the last hit of the group is on the same layer as the test hit, we keep all patterns in the group, as we might branch again with the next hits.
268 std::ranges::transform(it, groupEnd, std::back_inserter(deduplicated),
269 [](PatternState* p){ return std::move(*p); });
270 }
271 it = groupEnd;
272 }
273 std::swap(activePatterns, deduplicated);
274 }
275 PatternStateVec nextPatterns{};
276 nextPatterns.reserve(activePatterns.size()* 2);
277
278 const unsigned refMissedLayerHits {std::ranges::min_element(activePatterns,
279 [](const auto& a, const auto& b) { return a.nMissedLayerHits < b.nMissedLayerHits; }
280 )->nMissedLayerHits};
281
282 for (PatternState& pattern : activePatterns) {
283
284 if (pattern.nMissedLayerHits >= m_cfg.maxMissedLayerHits && pattern.nMissedLayerHits > refMissedLayerHits) {
285 ATH_MSG_VERBOSE(__func__<<"() Pattern " << pattern << " has missed " << pattern.nMissedLayerHits << " hits in different layers, above the maximum allowed - abort this pattern.");
287 continue;
288 }
289
291 const auto [result, residual, accWindow] {checkLineCompatibility(seed, test, pattern)};
292 switch (result) {
295 pattern.addHit(test, residual, accWindow);
296 ATH_MSG_VERBOSE(__func__<<"() Hit is compatible, add it to the pattern. Updated pattern: " << pattern);
297 break;
298 }
300 /* Check first if the branched pattern already exists*/
301 if (std::ranges::any_of(nextPatterns, [&test, &pattern](const PatternState& p) {
302 return p.lastInsertedHit == test.hit && p.prevLayerHit == pattern.prevLayerHit; })) {
303 ATH_MSG_VERBOSE(__func__<<"() Hit is compatible & on same layer of last added hit, but branched pattern already exists");
304 break;
305 }
307 PatternState newPattern {pattern};
308 const HitPayload& lastPatHit {pattern.getNthLastHit(1u)};
309 newPattern.overWriteHit(lastPatHit, test, residual, accWindow);
310 ATH_MSG_VERBOSE(__func__<<"() Hit is compatible & on same layer of last added hit - new branched pattern: " << newPattern);
311
313 if (newPattern.visualInfo && pattern.visualInfo) {
314 pattern.visualInfo->replacedHits.push_back(test.hit);
315 newPattern.visualInfo->replacedHits.push_back(lastPatHit.hit);
316 }
318 nextPatterns.push_back(std::move(newPattern));
319 break;
320 }
322 ATH_MSG_VERBOSE(__func__<<"() Hit is not compatible with the pattern.");
323 if (pattern.visualInfo) {
324 pattern.visualInfo->discardedHits.push_back(test.hit);
325 }
326 if (checkLayerOrdering(test, prevCandidate) != eSameLayer){
327 pattern.nMissedLayerHits++;
328 }
329 break;
330 }
331 }
332 nextPatterns.push_back(std::move(pattern));
333 }
334 std::swap(activePatterns, nextPatterns);
335};
338 const HitPayload& test,
339 const PatternState& pat) const {
340 // We test hits in the same **expanded** sector, so we need just to compare hit's phi with the pattern's phi, if both available
341 if (test->measuresPhi() && pat.nPhiHits &&
342 std::abs(CxxUtils::deltaPhi(pat.phi, test.phi)) > m_cfg.phiTolerance) {
343 ATH_MSG_VERBOSE(__func__<<"() The pattern with phi = "<<inDegrees(pat.phi)
344 <<" is not compatible with the test hit with phi "<<inDegrees(test.phi));
346 }
348 if (checkLayerOrdering(test, seed) == eSameLayer) {
349 ATH_MSG_VERBOSE(__func__<<"() Test hit is on the same layer as the seed - reject.");
351 }
352
354 const Amg::Vector3D beamSpot{Amg::Vector3D::Zero()};
355
360 auto makeResult = [&](const uint8_t patHitIdx,
361 const bool isNewLayer) -> LineCompatibilityResult {
362 LineCompatibilityResult res {computeResidual(seed, test, pat, patHitIdx, beamSpot)};
363 if (res.residual < res.accWindow) {
365 }
366 return res;
367 };
368
370 const HitPayload& lastPatHit {pat.getNthLastHit(1u)};
371 const int nMeas {pat.nBendingTriggerHits + pat.nPrecisionHits};
372
373 /*************** Test hit is on a new layer — draw line from seed to lastHit */
374 if(checkLayerOrdering(test, lastPatHit) != eSameLayer) {
375 return makeResult(1u, /*isNewLayer*/ true);
376 }
377 /*************** Test hit is on the same layer as the last inserted hit ***************/
379 if (test == lastPatHit) {
380 ATH_MSG_VERBOSE(__func__<<"() Test hit is the same as last inserted hit - rejecting.");
382 }
384 if (areConsecutiveMdt(*test, *lastPatHit)) {
385 ATH_MSG_VERBOSE(__func__<<"() Consecutive MDT hits on the same layer - accepting.");
386 return LineCompatibilityResult{CompatibilityResult::eAddHit, pat.lastResidual, pat.lastAcceptWindow};
387 }
389 if (!test.isPrecision && lastPatHit.isPrecision) {
390 ATH_MSG_VERBOSE(__func__<<"() Test hit is a trigger hit and last inserted hit is precision on the same layer - keep the precision hit.");
392 }
394 if (nMeas == 1) {
395 ATH_MSG_VERBOSE(__func__<<"() Test hit on same layer as seed with no prior hits, and they are not consecutive MDT hits - rejecting.");
397 }
399 uint8_t n {2u};
400 while (n < nMeas &&
401 checkLayerOrdering(test, pat.getNthLastHit(n)) == eSameLayer) {
402 ++n;
403 }
404 return makeResult(n, /*isNewLayer*/ false);
405}
408 const HitPayload& test,
409 const PatternState& pat,
410 const uint8_t patHitIdx,
411 const Amg::Vector3D& beamSpot) const {
412 const HitPayload& patHit {pat.getNthLastHit(patHitIdx)};
413 const int nMeas {pat.nBendingTriggerHits + pat.nPrecisionHits};
414
415 ATH_MSG_VERBOSE(__func__<<"() Start residual check given pat hit: " << *patHit << ", st: " << stName(patHit.station));
416 // Check whether we have to use the beamspot instead of the last pattern hit to draw the line with the seed.
417 const double patDeltaZ {std::abs(patHit.Z - seed.Z)};
418 const bool useBeamspot {patHit == seed || patDeltaZ < Acts::s_epsilon ||
419 (patHit.station == seed.station && (patHit->msSector()->barrel() ? std::abs(patHit.R - seed.R) : patDeltaZ) <= m_cfg.minLayerSeparation)};
420
421 if (useBeamspot && patHitIdx + 1 < nMeas) {
422 ATH_MSG_VERBOSE(__func__<<"() Distance seed to pat hit is very small - try with next pat hit as reference.");
423 return computeResidual(seed, test, pat, patHitIdx + 1u, beamSpot);
424 }
425
426 /* If the pat hit is a straw, find the consecutive (MDT) hits on the same layer and return use middle one
427 for next computations — gives a more central reference for the line direction */
428 const HitPayload& centralPatHit { patHit->isStraw() ? [&]() -> const HitPayload& {
429 uint8_t nSameLayer{1u};
430 while (patHitIdx + nSameLayer < nMeas &&
431 checkLayerOrdering(patHit, pat.getNthLastHit(patHitIdx + nSameLayer)) == eSameLayer) {
432 ++nSameLayer;
433 }
434 return nSameLayer > 2u ? pat.getNthLastHit(patHitIdx + (nSameLayer - 1u) / 2u) : patHit;
435 }() : patHit};
436
437 ATH_MSG_VERBOSE(__func__<<"() Distance seed to pat hit - deltaR_pat= " << (centralPatHit.R - seed.R) << ", deltaZ_pat= " << (centralPatHit.Z - seed.Z) << ". Use beamspot: " << useBeamspot);
439
440 // Determine the coordinates to use as reference for the line computation
441 const double refR {useBeamspot ? beamSpot.perp() : centralPatHit.R};
442 const double refZ {useBeamspot ? beamSpot.z() : centralPatHit.Z};
443
444 // Compute the line slope.
445 const double dZ_slope {refZ - seed.Z};
446 const double dR_slope {refR - seed.R};
447 assert(dZ_slope != 0);
448 const double lineSlope {dR_slope / dZ_slope};
449 ATH_MSG_VERBOSE(__func__<<"() Slope computation - deltaR_slope= " << dR_slope << ", deltaZ_slope= " << dZ_slope << ", slope= " << lineSlope);
450
451 // Compute the residual
452 const double dZ_res {test.Z - seed.Z};
453 res.residual = (test.R - seed.R) - lineSlope * dZ_res;
454 ATH_MSG_VERBOSE(__func__<<"() Residual computation - deltaR_res: " << (test.R - seed.R) << ", deltaZ_res: " << dZ_res << ", signed residual: " << res.residual);
455
458 const double geometricalFactor {1.+ Acts::square(lineSlope)};
459 const double alpha {dZ_res / dZ_slope};
460 const double propagationFactor {1. - alpha + Acts::square(alpha)};
461 res.accWindow = m_cfg.baseRWindow * std::sqrt(2*geometricalFactor * propagationFactor);
462 if (useBeamspot) res.accWindow *= 1.5;
463 ATH_MSG_VERBOSE(__func__<<"() Window computation - Geometrical Factor: " << std::sqrt(geometricalFactor) << " alpha: " << alpha
464 << ", Propagation Factor: " << std::sqrt(propagationFactor) << ", Computed Window: " << res.accWindow);
465
467 if (!useBeamspot && centralPatHit->isStraw()) {
468 const double LRcorrection {dZ_res * geometricalFactor * centralPatHit->driftRadius()/ Acts::fastHypot(dZ_slope, dR_slope)};
469 res.residual = std::min(std::abs(res.residual-LRcorrection), std::abs(res.residual+LRcorrection));
470 ATH_MSG_VERBOSE(__func__<<"() Apply LR correction: " << LRcorrection << ", corrected residual: " << res.residual);
471 } else {
472 res.residual = std::abs(res.residual);
473 }
474 if (pat.visualInfo) {
475 pat.visualInfo->hitLineInfo[test.hit] = std::make_pair(lineSlope, res.accWindow);
476 }
477 return res;
478}
480 const PatternState& b) const {
482 auto score = [this](const PatternState& p) {
483 const long nStations {std::ranges::count_if(p.hitsPerStation, [](const auto& pair){ return pair.second.size() >= 4u; })};
484 const double etaHitCount {1.*p.nBendingTriggerHits + m_cfg.precisionWeight * p.nPrecisionHits};
485 const double etaHitScore {std::tanh(etaHitCount / (std::max(nStations, 1L) * m_cfg.hitScoreSaturation))};
486 const double stationScore {std::tanh(1.*nStations / 2.)};
487 double residualRatio {p.meanNormResidual2 / (1.5*m_cfg.meanNormRes2Cut)};
488 if (!p.isFinalized) residualRatio /= (p.nBendingTriggerHits + p.nPrecisionHits);
489 const double resPenalty {m_cfg.residualPenalty * residualRatio / (1. + residualRatio)};
490 const double etaScore {stationScore * etaHitScore * (1. - resPenalty)};
491 const double phiScore {std::tanh(1.*p.nPhiHits / m_cfg.phiBonusSaturation)};
492 ATH_MSG_VERBOSE("score() stationScore: "<<stationScore<<", etaHitScore: "<<etaHitScore<<", resPenalty: "<<resPenalty<<", etaScore: "<<etaScore<<", phiScore: "<<phiScore);
493 return 0.9 * etaScore + 0.1 * phiScore;
494 };
495 const double scoreA {score(a)};
496 const double scoreB {score(b)};
497 ATH_MSG_VERBOSE(__func__<<"() Pattern " << a << " with score " << scoreA << " is " <<
498 (scoreA > scoreB ? "BETTER" : "WORSE") << " than " << b << " with score " << scoreB);
499 return scoreA > scoreB;
500}
503 PatternHitVisualInfoVec* visualInfo) const {
504 PatternStateVec outputPatterns{};
505 outputPatterns.reserve(toResolve.size());
507 auto areOverlapping = [this](const PatternState& a, const PatternState& b) {
509 if(std::abs(a.sectorCoord - b.sectorCoord) > 1) {
510 return false;
511 }
512 if (a.nPhiHits > 0 && b.nPhiHits > 0) {
513 if (std::abs(CxxUtils::deltaPhi(a.phi, b.phi)) > 2.*m_cfg.phiTolerance) return false;
514 } else if (a.nPhiHits > 0) {
515 if (!sectorMap.insideSector(b.sector1, a.phi) || !sectorMap.insideSector(b.sector2, a.phi)) {
516 return false;
517 }
518 } else if (b.nPhiHits > 0) {
519 if (!sectorMap.insideSector(a.sector1, b.phi) || !sectorMap.insideSector(a.sector2, b.phi)) {
520 return false;
521 }
522 }
523 if (std::abs(a.theta - b.theta) > 2.*m_cfg.thetaSearchWindow) {
524 return false;
525 }
527 int nSharedHits{0};
528 for (const auto& [stA, hitsA] : a.hitsPerStation) {
529 auto itB = b.hitsPerStation.find(stA);
530 if (itB == b.hitsPerStation.end()) {
531 continue;
532 }
533 nSharedHits += std::ranges::count_if(hitsA, [&](const HitPayload& hitA){
534 return std::ranges::find(itB->second, hitA) != itB->second.end();
535 });
536 }
538 const int minHits {std::min(a.nBendingTriggerHits + a.nPrecisionHits, b.nBendingTriggerHits + b.nPrecisionHits)};
539 return nSharedHits > minHits / 2;
540 };
541 for (auto it = toResolve.begin(); it != toResolve.end(); ++it) {
542 if (it->isOverlap) {
543 // If already marked as overlap, add to visual info, and discard the pattern
545 continue;
546 }
547 for (auto jt = std::next(it); jt != toResolve.end(); ++jt) {
548 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
549 continue;
550 }
551 if (isBetter(*it, *jt)) {
552 jt->isOverlap = true;
553 } else {
554 it->isOverlap = true;
555 break;
556 }
557 }
558 if (!it->isOverlap) {
559 it->finalizePatternEta();
560 outputPatterns.push_back( std::move(*it));
561 } else {
562 // If overlap, add to visual info, as the pattern will be discarded
564 }
565 }
566 ATH_MSG_VERBOSE(__func__<<"() Patterns surviving overlap removal: "<< outputPatterns.size());
567 return outputPatterns;
568}
570 PatternStateVec& patterns) const {
571 constexpr auto covIdxEta {Acts::toUnderlying(SpacePoint::CovIdx::etaCov)};
576 struct PhiStripProjectionModel {
577 StIndex station{};
578 double refLay{0.};
579 double refStrip{0.};
580 double invSlope{0.};
581 bool isBarrel{false};
582 bool isValid{false};
583
584 double project(const Amg::Vector3D& pos) const {
585 const double layCoord = isBarrel ? pos.perp() : pos.z();
586 return refStrip + (layCoord - refLay) * invSlope;
587 }
588 double residual(const Amg::Vector3D& pos) const {
589 const double stripCoord = isBarrel ? pos.z() : pos.perp();
590 return std::abs(project(pos) - stripCoord);
591 }
592 };
593 auto makeProjectionModel = [this](const PatternState& pat, const StIndex station) {
594 PhiStripProjectionModel result{};
595 result.station = station;
596 const std::vector<HitPayload>& hits {pat.hitsPerStation.at(station)};
597 if (hits.empty()) return result;
598 const bool isBarrel {hits.front().hit->msSector()->barrel()};
599 // Define the coordinate across the layers, i.e. orthogonal to the strip
600 const auto layCoord = [isBarrel](const HitPayload& hit) {
601 return (isBarrel) ? hit.R : hit.Z;
602 };
603 // Define the coordinate along the strip, i.e. orthogonal to the measureent layers
604 const auto stripCoord = [isBarrel](const HitPayload& hit) {
605 return (isBarrel) ? hit.Z : hit.R;
606 };
607
608 const HitPayload* sp1 {nullptr};
609 const HitPayload* sp2 {nullptr};
610 if (hits.size() > 1) {
611 // if we have two eta hits in the station, we use the furthestmost to define the pattern line
612 for (const HitPayload& hit : hits) {
613 if (!hit->measuresEta()) continue;
614 if (!sp1 || layCoord(hit) < layCoord(*sp1)) {
615 sp1 = &hit;
616 }
617 if (!sp2 || layCoord(hit) > layCoord(*sp2)) {
618 sp2 = &hit;
619 }
620 }
621 } else {
622 // if we have only one eta hit, we use it and look for the closest eta hit in the closest station to define the pattern line
623 sp1 = &hits.front();
624 auto layDistanceFromSp1 = [&layCoord,&sp1](const HitPayload& hit) {
625 return std::abs(layCoord(hit) - layCoord(*sp1));
626 };
627 const std::vector<HitPayload>& closestSt {std::ranges::min_element(pat.hitsPerStation,
628 [&layDistanceFromSp1, &station](const auto& st1, const auto& st2){
629 if (st1.first == station) return false;
630 if (st2.first == station) return true;
631 return layDistanceFromSp1(st1.second.front()) < layDistanceFromSp1(st2.second.front());
632 })->second};
633 for (const HitPayload& hit : closestSt) {
634 if (!hit->measuresEta()) continue;
635 if (!sp2 || layDistanceFromSp1(hit) < layDistanceFromSp1(*sp2)) {
636 sp2 = &hit;
637 }
638 }
639 }
640 if (!sp1 || !sp2 ) return result;
641 const double deltaLay {layCoord(*sp2) - layCoord(*sp1)};
642 if (std::abs(deltaLay) < m_cfg.minLayerSeparation) return result;
643
644 result.refLay = layCoord(*sp1);
645 result.refStrip = stripCoord(*sp1);
646 result.invSlope = (stripCoord(*sp2) - stripCoord(*sp1)) / deltaLay;
647 result.isBarrel = isBarrel;
648 result.isValid = true;
649 return result;
650 };
651
652 PatternStateVec survivingPatterns{};
653 survivingPatterns.reserve(patterns.size());
654 for (PatternState& pat : patterns) {
656 ATH_MSG_VERBOSE(__func__<<"() Search for phi-only hits for pattern: " << pat);
657 // Projection model of pattern line onto a given phi strip
658 std::optional<PhiStripProjectionModel> patProjOnStrip{};
659 for (const auto& [spc, bucketVec] : pat.bucketsPerContainer) {
660 for (const SpacePointBucket* bucket : bucketVec) {
661 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
662 const StIndex station {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
663 // If the projection model is not valid, we will use the pattern theta. We cache the local Y in glob frame
664 const Amg::Vector3D locY {localToGlobal.linear() * Amg::Vector3D::UnitY()};
665
666 for (const auto& hit : *bucket) {
667 // We are looking for phi-only hits
668 if (hit->measuresEta()){
669 continue;
670 }
671 ATH_MSG_VERBOSE(__func__<<"() *** Test phi-only hit "<<*hit);
672 // Check phi compatibility
673 const Amg::Vector3D locPosTest {hit->localPosition()};
674 const Amg::Vector3D globPosTest {localToGlobal * locPosTest};
675 const double globPhi {globPosTest.phi()};
676 if (!isPhiCompatible(globPhi, pat)) {
677 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit not compatible");
678 continue;
679 }
680 // Check there are not other phi hits in the same layer
681 const uint8_t layNum = m_spSorter.sectorLayerNum(*hit);
682 if (auto it {pat.hitsPerStation.find(station)}; it != pat.hitsPerStation.end()) {
683 if (std::ranges::any_of(it->second, [&](const HitPayload& h){
684 return h->measuresPhi() && hit->msSector() == h->msSector() && layNum == h.layerNum; })) {
685 ATH_MSG_VERBOSE(__func__<<"() The pattern already has a phi hit in the same layer - skip this test hit.");
686 continue;
687 }
688 }
689 // Check eta compatibility. Try first to use the pattern line projection model
690 bool isEtaCompatible {false};
691 const double sigmaEta {std::sqrt(hit->covariance()[covIdxEta])};
692 if (!patProjOnStrip.has_value() || patProjOnStrip->station != station) {
693 patProjOnStrip = makeProjectionModel(pat, station);
694 }
695 if(patProjOnStrip->isValid) {
696 isEtaCompatible = patProjOnStrip->residual(globPosTest) <= 1.1*sigmaEta;
697 ATH_MSG_VERBOSE(__func__<<"() Distance pattern line from strip center: "<<patProjOnStrip->residual(globPosTest)<<", strip half-length: "<<sigmaEta<<", isCompatible: "<<isEtaCompatible);
698 } else {
699 // Check pattern theta against global theta at the boundaries of the phi strip
700 double thetaMin {(globPosTest - sigmaEta * locY).theta()};
701 double thetaMax {(globPosTest + sigmaEta * locY).theta()};
702 if (thetaMax < thetaMin) {
703 std::swap(thetaMin, thetaMax);
704 }
705 isEtaCompatible = std::max(pat.theta - thetaMax, thetaMin - pat.theta) < 0.5 * m_cfg.thetaSearchWindow;
706 ATH_MSG_VERBOSE(__func__<<"() Pattern theta "<<inDegrees(pat.theta)<<", strip theta window: ["<<inDegrees(thetaMin)<<", "<<inDegrees(thetaMax)<<"]");
707
708 }
709 if (!isEtaCompatible) {
710 ATH_MSG_VERBOSE(__func__<<"() The pattern falls outside the test hit strip in eta - skip this test hit.");
711 continue;
712 }
713 // Create the hit payload and add the hit to the pattern
714 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit compatible - add it to the pattern.");
715 pat.addHit(HitPayload{hit.get(), station, layNum, globPhi}, 0., 0.);
716 }
717 }
718 }
719 if (pat.nPhiHits < m_cfg.minPhiHits) {
720 ATH_MSG_VERBOSE(__func__<<"() Pattern "<< pat<<" has only "<<pat.nPhiHits<<" phi hits, below the minimum required - reject this pattern.");
721 continue;
722 }
723 pat.finalizePatternPhi();
724 survivingPatterns.push_back(std::move(pat));
725 }
726 std::swap(patterns, survivingPatterns);
727}
728bool GlobalPatternFinder::isPhiCompatible(const double testPhi,
729 const PatternState& pattern) const {
733 if (pattern.nPhiHits) {
734 if (std::abs(CxxUtils::deltaPhi(pattern.phi, testPhi)) > m_cfg.phiTolerance) {
735 ATH_MSG_VERBOSE(__func__<<"() The pattern with phi = "<<inDegrees(pattern.phi)<<" is not compatible with the test hit with phi "<<inDegrees(testPhi));
736 return false;
737 }
738 } else {
739 const bool isCompatible {pattern.sector1 == pattern.sector2 ? sectorMap.insideSector(pattern.sector1, testPhi) :
740 sectorMap.insideSector(pattern.sector1, testPhi) && sectorMap.insideSector(pattern.sector2, testPhi)};
741 if (!isCompatible) {
742 ATH_MSG_VERBOSE(__func__<<"() The test hit with phi = "<<inDegrees(testPhi)<<" is not inside the pattern sectors: "<<pattern.sector1<<" and "<<pattern.sector2);
743 return false;
744 }
745 }
746 return true;
747}
749 GlobalPattern::HitCollection hitPerStation{};
750 for (const auto& [station, hits] : cache.hitsPerStation) {
751 auto& out = hitPerStation[station];
752 out.reserve(hits.size());
753 std::ranges::transform(hits, std::back_inserter(out), [](const HitPayload& h){ return h.hit;});
754 }
755 GlobalPattern pattern{std::move(hitPerStation)};
756 pattern.setTheta(cache.theta);
757 pattern.setPhi(cache.phi);
758 // Set the pattern sector(s) and theta.
759 pattern.setSector(cache.sector1);
760 pattern.setSecondarySector(cache.sector2);
761 // Set pattern quality information.
762 pattern.setNPrecisionHits(cache.nPrecisionHits);
763 pattern.setNEtaNonPrecisionHits(cache.nBendingTriggerHits);
764 pattern.setNPhiHits(cache.nPhiHits);
765 pattern.setMeanNormResidual2(cache.meanNormResidual2);
766 return pattern;
767}
768
772 patterns.reserve(cache.size());
773 std::transform(cache.begin(), cache.end(), std::back_inserter(patterns), [this](const PatternState& cacheEntry) {
774 GlobalPattern pattern {convertToPattern(cacheEntry)};
775 ATH_MSG_VERBOSE("convertToPattern() Converted new pattern "<<pattern);
776 return pattern;
777 });
778 return patterns;
779}
780
783 const SpacePointContainerVec& spacepoints) const {
784 SearchTree_t::vector_t rawData{};
786 // Before the loops: estimate the total number of hits
787 size_t totalHits = 0;
788 for (const SpacePointContainer* spc : spacepoints) {
789 for (const SpacePointBucket* bucket : *spc) {
790 totalHits += bucket->size();
791 }
792 }
793 // We can have up to 3 entries per hit (when the hit does not measure phi).
794 rawData.reserve(3 * totalHits);
795
796 for (const SpacePointContainer* spc : spacepoints) {
797 ATH_MSG_VERBOSE("Adding to the search tree "<<spc->size()<<" space point buckets");
798 for (const SpacePointBucket* bucket : *spc) {
799 ATH_MSG_VERBOSE("Processing " << bucket->size() << " spacepoint...");
800 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
801 const StIndex bucketStation {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
802 const int sector = bucket->msSector()->sector();
803
804 for (const auto& hit : *bucket) {
805 // Ignore only-phi hits and MDT hits if desired
806 if (!hit->measuresEta() || (!m_cfg.useMdtHits && hit->isStraw())) {
807 continue;
808 }
809 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
810 const double globalTheta {globalPos.theta()};
811 const HitPayload newHit {hit.get(), bucket, spc, bucketStation,
812 static_cast<uint8_t>(m_spSorter.sectorLayerNum(*hit)),globalPos.perp(), globalPos.z(), globalPos.phi()};
813
816 for (const auto proj : {SectorProjector::leftOverlap, SectorProjector::center, SectorProjector::rightOverlap}) {
818 const int projSector = MsTrackSeeder::ringSector(sector + Acts::toUnderlying(proj));
819 if (hit->measuresPhi() && proj != SectorProjector::center &&
820 !sectorMap.insideSector(projSector, newHit.phi)) {
821 ATH_MSG_VERBOSE(__func__<<"() Hit @"<< *hit<<"\nwith globPhi "<<inDegrees(newHit.phi)
822 <<" is not in sector "<<projSector<<" ["<<inDegrees(sectorMap.sectorPhi(projSector)-sectorMap.sectorWidth(projSector))
823 <<", "<<inDegrees(sectorMap.sectorPhi(projSector)+sectorMap.sectorWidth(projSector))
824 <<"] which is "<<MsTrackSeeder::to_string(proj) <<" to "<< sector);
825 continue;
826 }
827 std::array<double, 2> coords{};
828 coords[Acts::toUnderlying(SeedCoords::eTheta)] = globalTheta;
829
833 coords[Acts::toUnderlying(SeedCoords::eSector)] = MsTrackSeeder::ringOverlap(2*sector + Acts::toUnderlying(proj));
834 ATH_MSG_VERBOSE(__func__<<"() Add hit @"<< *hit
835 <<"\nwith global position "<<Amg::toString(globalPos) <<" and coordinates "<<coords<<" to the search tree");
836 rawData.emplace_back(std::move(coords), newHit);
837 }
838 }
839 }
840 }
841 ATH_MSG_VERBOSE("Create a new tree with "<<rawData.size()<<" entries. ");
842 return SearchTree_t{std::move(rawData)};
843}
846 const HitPayload& hit2) {
847 auto getLayerOrdering = [](const bool isLayer1Lower) {
848 return isLayer1Lower ? eLowerLayer : eHigherLayer;
849 };
850 if (hit1 == hit2) {
851 return eSameLayer;
852 }
854 if (hit1->msSector() == hit2->msSector()) {
855 if (hit1.layerNum == hit2.layerNum) {
856 return eSameLayer;
857 } else {
858 return getLayerOrdering(hit1.layerNum < hit2.layerNum);
859 }
860 }
861 StIndex st1 {hit1.station};
862 StIndex st2 {hit2.station};
864 if (st1 == st2) {
865 return getLayerOrdering(hit1->msSector()->barrel() ? hit1.R < hit2.R : std::abs(hit1.Z) < std::abs(hit2.Z));
866 }
867 LayerIndex layer1 {toLayerIndex(st1)};
868 LayerIndex layer2 {toLayerIndex(st2)};
869 if (layer1 == layer2) {
871 if (layer1 == LayerIndex::Middle) {
873 return getLayerOrdering(st1 == StIndex::BM);
874 } else if (layer1 == LayerIndex::Inner) {
876 return getLayerOrdering(hit1.R < hit2.R);
877 } else {
878 throw std::runtime_error("Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
879 }
880 }
881 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
883 return getLayerOrdering(layer1 == LayerIndex::Inner);
884 }
885 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
887 return getLayerOrdering(layer2 == LayerIndex::Outer);
888 }
889 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
891 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
892 }
894 if (layer1 == LayerIndex::Extended) {
895 return getLayerOrdering(st2 == StIndex::EM);
896 }
897 return getLayerOrdering(st1 == StIndex::BM);
898}
899
901 const uint8_t sectorCoord,
902 const double seedTheta)
903 : lastInsertedHit{seed.hit},
904 prevLayerHit{seed.hit},
905 theta{seedTheta},
907 sector1{static_cast<uint8_t>(MsTrackSeeder::ringSector((sectorCoord - sectorCoord % 2) / 2))},
908 sector2{static_cast<uint8_t>(MsTrackSeeder::ringSector((sectorCoord + sectorCoord % 2) / 2))} {
909
910 StIndex stationHit {seed.station};
911 hitsPerStation[stationHit].push_back(seed);
912 stations.push_back(stationHit);
913
914 if (seed.isPrecision) nPrecisionHits++;
915 else if (seed->measuresEta()) nBendingTriggerHits++;
916
917 if (seed->measuresPhi()) {
918 phi = seed.phi;
919 nPhiHits++;
920 }
921}
923 const double residual,
924 const double acceptWindow) {
926 lastInsertedHit = hit.hit;
927 if (const HitPayload& previousHit {getNthLastHit(1u)};
928 checkLayerOrdering(hit, previousHit) != eSameLayer) {
929 prevLayerHit = previousHit.hit;
930 }
932 StIndex stationHit {hit.station};
933 hitsPerStation[stationHit].push_back(hit);
934 if (stations.empty() || stations.back() != stationHit) {
935 stations.push_back(stationHit);
936 }
940 if (hit.isPrecision) nPrecisionHits++;
941 else if (hit->measuresEta()) nBendingTriggerHits++;
943 if (hit->measuresPhi()) {
944 if (nPhiHits == 0) phi = hit.phi;
945 nPhiHits++;
946 }
948 if (acceptWindow > Acts::s_epsilon) {
949 meanNormResidual2 += Acts::square(residual / acceptWindow);
950 lastAcceptWindow = acceptWindow;
951 lastResidual = residual;
952 }
953}
955 const HitPayload& newHit,
956 const double newResidual,
957 const double newAcceptWindow) {
958 if (oldHit.station != newHit.station) {
959 throw std::runtime_error(std::format("Trying to overwrite a hit in station {} with another one from station {}", stName(oldHit.station), stName(newHit.station)));
960 }
961 // We expect to overwrite hits of the same type (precision/trigger), since we only branch when we have
962 // compatible hits in the same layer, except for sTGC hits, where we have pad and strips in the same layer
963 if ((oldHit.isPrecision != newHit.isPrecision ||
964 oldHit->measuresEta() != newHit->measuresEta()) && newHit->type() != xAOD::UncalibMeasType::sTgcStripType) {
965 std::stringstream ss {"Trying to overwrite a hit with incompatible type\n"};
966 ss << "Old hit: " << *oldHit << ", isPrecision: " << oldHit.isPrecision << ", measuresEta: " << oldHit->measuresEta() << "\n";
967 ss << "New hit: " << *newHit << ", isPrecision: " << newHit.isPrecision << ", measuresEta: " << newHit->measuresEta();
968 throw std::runtime_error(ss.str());
969 }
970 assert(newAcceptWindow > Acts::s_epsilon && lastAcceptWindow > Acts::s_epsilon);
971 StIndex lastStation {oldHit.station};
972 auto it {hitsPerStation.find(lastStation)};
973 if (it == hitsPerStation.end() || lastStation != stations.back()) {
974 throw std::runtime_error("Trying to remove a hit from an invalid station");
975 }
976 std::vector<HitPayload>& hitsInStation {it->second};
977 // Remove ALL hits in the same layer
978 while (!hitsInStation.empty()) {
979 HitPayload& lastHit {hitsInStation.back()};
980 if (checkLayerOrdering(lastHit, oldHit) != eSameLayer) {
981 break;
982 }
983 if (lastHit.isPrecision) nPrecisionHits--;
984 else nBendingTriggerHits--; //This method is called only in pattern building in eta, so we don't have phi-only hits yet
985 if (lastHit->measuresPhi()) nPhiHits--;
986 hitsInStation.pop_back();
989 }
990 // then insert the new hit
991 hitsInStation.push_back(newHit);
992 if (newHit.isPrecision) nPrecisionHits++;
993 else nBendingTriggerHits++; //This method is called only in pattern building in eta, so we don't have phi-only hits yet
995 if (newHit->measuresPhi()) {
996 if (nPhiHits == 0) phi = newHit.phi;
997 nPhiHits++;
998 }
1000 meanNormResidual2 += Acts::square(newResidual / newAcceptWindow);
1001 lastResidual = newResidual;
1002 lastAcceptWindow = newAcceptWindow;
1004 lastInsertedHit = newHit.hit;
1005}
1008 theta = 0.;
1009 for (const auto& [_, hits] : hitsPerStation) {
1010 for (const auto& hit : hits) {
1012 auto& bucketVec {bucketsPerContainer[hit.container]};
1013 if (std::ranges::find(bucketVec, hit.bucket) == bucketVec.end()){
1014 bucketVec.push_back(hit.bucket);
1015 }
1017 theta += atan2(hit.R, hit.Z);
1018 }
1019 }
1020 assert(nPrecisionHits + nBendingTriggerHits > Acts::s_epsilon);
1022}
1024 if (!nPhiHits) {
1026 phi = sectorMap.sectorOverlapPhi(sector1, sector2);
1027 return;
1028 }
1029 double deltaPhiAcc {0.};
1030 std::optional<double> centralPhi {};
1031 for (const auto& [_, hits] : hitsPerStation) {
1032 for (const auto& hit : hits) {
1033 if (!hit->measuresPhi()) {
1034 continue;
1035 }
1036 if (!centralPhi) {
1037 centralPhi = hit.phi;
1038 }
1039 deltaPhiAcc += CxxUtils::deltaPhi(hit.phi, *centralPhi);
1040 }
1041 }
1042 phi = CxxUtils::wrapToPi(centralPhi.value_or(0.) + deltaPhiAcc / nPhiHits);
1043}
1046 PatternHitVisualInfoVec* visualInfo) const {
1047 if (!visualInfo) {
1048 return;
1049 }
1050 GlobalPattern pattern {convertToPattern(cache)};
1051 // Check whether the visual info about this pattern is already in the container
1052 if (auto it =std::ranges::find_if(*visualInfo, [&pattern](const auto& v){
1053 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
1054 it->status = status; // Update the status if the pattern is already in the container
1055 return;
1056 }
1057 visualInfo->push_back(*cache.visualInfo);
1058 auto& bucketVec {visualInfo->back().parentBuckets};
1059 for (const auto& [_, buckets] : cache.bucketsPerContainer) {
1060 bucketVec.insert(bucketVec.end(), buckets.begin(), buckets.end());
1061 }
1062 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
1063 visualInfo->back().status = status;
1064}
1068 // If we have not yet inserted n hits, we return the seed hit
1069 return hitsPerStation.at(stations.front()).front();
1070 }
1071 std::size_t remaining {n};
1072 for (auto stIt = stations.rbegin(); stIt != stations.rend(); ++stIt) {
1073 const std::vector<HitPayload>& hits {hitsPerStation.at(*stIt)};
1074 const size_t nHits {hits.size()};
1075 if (remaining <= nHits) {
1076 return hits.at(nHits - remaining);
1077 }
1078 remaining -= nHits;
1079 }
1080 // Fall-back return, should not happen if the input n is consistent with hit counts
1081 return hitsPerStation.at(stations.front()).front();
1082}
1084 const auto seedStationIt {hitsPerStation.find(hit.station)};
1085 return seedStationIt != hitsPerStation.end() &&
1086 std::ranges::find(seedStationIt->second, hit) != seedStationIt->second.end();
1087}
1089 const SpacePointBucket* bucket,
1092 uint8_t layerNum,
1093 double R,
1094 double Z,
1095 double phi)
1097 R{R}, Z{Z}, phi{phi}, station{station}, layerNum{layerNum} {}
1104 return hit == other.hit;
1105}
1106void GlobalPatternFinder::PatternState::print(std::ostream& ostr) const {
1107 ostr<<"Pattern state, Expanded Sector: "<<sectorCoord<<", Theta: "<<theta << ", Phi: "<<phi;
1108 ostr<<", nPrecisionHits: "<<nPrecisionHits<<", nEtaNonPrecisionHits: "<<nBendingTriggerHits<<", nPhiHits: "<<nPhiHits;
1109 ostr<<", mean normalized residual squared: "<<meanNormResidual2;
1110 ostr<<", Hit per station: \n";
1111 for (const auto& [station,hits] : hitsPerStation) {
1112 ostr<<" Station "<<Muon::MuonStationIndex::stName(station)<<" has "<<hits.size()<<" hits\n";
1113 for (const auto& hit : hits) {
1114 ostr<<" "<<*hit<<", R: "<<hit.R<<", Phi:"<<hit.phi<<"\n";
1115 }
1116 }
1117}
1118}
Scalar theta() const
theta method
#define ATH_MSG_VERBOSE(x)
std::pair< std::vector< unsigned int >, bool > res
static Double_t a
static Double_t ss
T_ResultType project(ParameterMapping::type< N > parameter_map, const T_Matrix &matrix)
static const uint32_t nHits
if(pathvar)
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
Header file for AthHistogramAlgorithm.
bool msgLvl(const MSG::Level lvl) const
Test the output level.
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
bool barrel() const
Returns whether the sector is placed in the barrel.
std::vector< const SpacePointContainer * > SpacePointContainerVec
Abrivation for a vector of space-point containers.
std::pair< SearchTree_t::coordinate_t, HitPayload > TreeNode
Type alias for a tree node, formed by a hit payload and its indexing coordinates.
@ eBranchPattern
Test successfull with multiple pattern hits in the same logical measurement layer,...
@ eAddHit
Test successfull, add the hit to the pattern.
PatternStateVec resolveOverlaps(PatternStateVec &&toResolve, PatternHitVisualInfoVec *visualInfo=nullptr) const
Method to remove overlapping patterns.
std::vector< PatternHitVisualInfo > PatternHitVisualInfoVec
Abrivation for a vector of visual information objects.
static LayerOrdering checkLayerOrdering(const HitPayload &hit1, const HitPayload &hit2)
Method to check the logical layer ordering of two hits.
SearchTree_t constructTree(const ActsTrk::GeometryContext &gctx, const SpacePointContainerVec &spacepoints) const
Method to construct the search tree by filling it up with spacepoints from the given containers.
void addVisualInfo(const PatternState &candidate, PatternHitVisualInfo::PatternStatus status, PatternHitVisualInfoVec *visualInfo) const
Helper function to add visual information of a given pattern (which is usually going to be destroyed)...
void extendPatterns(PatternStateVec &activePatterns, const HitPayload &test, const HitPayload &seed, const HitPayload &prevCandidate, PatternHitVisualInfoVec *visualInfo=nullptr) const
Function testing pattern compatibility of a set of active patterns (patterns produced from the same s...
Config m_cfg
Global Pattern Recognition configuration.
bool isBetter(const PatternState &a, const PatternState &b) const
Operator to compare two patterns.
bool isPhiCompatible(const double testPhi, const PatternState &pattern) const
Method to check the phi compatibility of a test hit with a given pattern.
LineCompatibilityResult checkLineCompatibility(const HitPayload &seed, const HitPayload &test, const PatternState &pat) const
Method to check the line compatibility of a test hit with a given pattern.
std::vector< GlobalPattern > PatternVec
Abrivation for a vector of global patterns.
Muon::MuonStationIndex::StIndex StIndex
Type alias for the station index.
LayerOrdering
Enum to express the logical measurement layer ordering given two hits.
void addPhiOnlyHits(const ActsTrk::GeometryContext &gctx, PatternStateVec &patterns) const
Method to add phi-only measurements to existing PatternStates.
GlobalPatternFinder(const std::string &name, Config &&config)
Standard constructor.
std::vector< PatternState > PatternStateVec
PatternVec findPatterns(const ActsTrk::GeometryContext &gctx, const SpacePointContainerVec &spacepoints, BucketPerContainer &outBuckets) const
Main methods steering the pattern finding.
SeedCoords
Abrivation of the seed coordinates.
@ eSector
Expanded sector coordinate of the associated spectrometer sector
SpacePointPerLayerSorter m_spSorter
Spacepoint sorter per logical measurement layer.
LineCompatibilityResult computeResidual(const HitPayload &seed, const HitPayload &test, const PatternState &pat, const uint8_t patHitIdx, const Amg::Vector3D &beamSpot) const
Helper method to compute the residual of a test hit against a reference pattern hit and check whether...
GlobalPattern convertToPattern(const PatternState &candidate) const
Method to convert a PatternState into a GlobalPattern object.
std::unordered_map< const SpacePointContainer *, std::vector< const SpacePointBucket * > > BucketPerContainer
Abrivation for a collection of space-point buckets grouped by their corresponding input container.
PatternStateVec findPatternsInEta(const SearchTree_t &orderedSpacepoints, PatternHitVisualInfoVec *visualInfo=nullptr) const
Method steering the building of patterns in eta.
Acts::KDTree< 2, HitPayload, double, std::array, 5 > SearchTree_t
Definition of the search tree class.
Muon::MuonStationIndex::LayerIndex LayerIndex
Type alias for the station layer index.
Data class to represent an eta maximum in hough space.
std::unordered_map< StIndex, std::vector< HitType > > HitCollection
Helper class to group muon sgements that may belong to a muon trajectory.
static std::string to_string(const SectorProjector proj)
Print the sector projector.
static int ringSector(const int sector)
Ensure that the parsed sector number is following the MS sector schema 0 is mapped to 16 and 17 is ma...
static int ringOverlap(const int sector)
Maps the sector 33 -> 0 to close the extended MS symmetry ring.
SectorProjector
Enumeration to select the sector projection.
: The muon space point bucket represents a collection of points that will bre processed together in t...
The muon space point is the combination of two uncalibrated measurements one of them measures the eta...
double driftRadius() const
: Returns the size of the drift radius
bool measuresPhi() const
: Does the space point contain a phi measurement
bool isStraw() const
Returns whether the measurement is a Mdt.
bool measuresEta() const
: Does the space point contain an eta measurement
double sectorOverlapPhi(int sector1, int sector2) const
returns the phi position of the overlap between the two sectors (which have to be neighboring) in rad...
double sectorPhi(int sector) const
returns the centeral phi position of a sector in radians
double sectorWidth(int sector) const
sector width (with overlap) in radians
bool insideSector(int sector, double phi) const
checks whether the phi position is consistent with sector
STL class.
std::vector< std::string > patterns
Definition listroot.cxx:187
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
T wrapToPi(T phi)
Wrap angle in radians to [-pi, pi].
Definition phihelper.h:24
T deltaPhi(T phiA, T phiB)
Return difference phiA - phiB in range [-pi, pi].
Definition phihelper.h:42
bool first
Definition DeMoScan.py:534
MsTrackSeeder::SectorProjector SectorProjector
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
const std::string & layerName(LayerIndex index)
convert LayerIndex into a string
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
const std::string & stName(StIndex index)
convert StIndex into a string
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
MdtDriftCircle_v1 MdtDriftCircle
setWord1 uint16_t
Helper for azimuthal angle calculations.
Hit information stored during pattern building.
HitPayload(const SpacePoint *hit, const SpacePointBucket *bucket, const SpacePointContainer *container, StIndex station, uint8_t layerNum, double R, double Z, double phi)
Full constructor for eta hits.
const SpacePoint * hit
Pointer to the underlying hit.
const SpacePointBucket * bucket
Pointer to the parent bucket.
const SpacePointContainer * container
Pointer to the parent container.
bool operator==(const HitPayload &other) const
Equal operator: it compares the underlying hit.
uint8_t layerNum
Logical layer number in the sector frame.
: Small struct to encapsulate the checkLineCompatibility result
Pattern state object storing pattern information during construction.
Acts::CloneablePtr< PatternHitVisualInfo > visualInfo
Pointer to Visual Information for pattern visualization.
double lastResidual
Residual & acceptance window of the last inserted hit (needed when replacing a hit).
void addHit(const HitPayload &hit, const double residual, const double acceptWindow)
Add a hit to the pattern and update the internal state.
BucketPerContainer bucketsPerContainer
Map of spacepoint buckets per spacepoint container associated to the pattern.
double meanNormResidual2
Mean over eta hits of the square of their residual divided by acceptance window.
bool isInPattern(const HitPayload &hit) const
Check wheter a hit is present in the pattern.
double theta
Average theta & average phi of the pattern.
const SpacePoint * lastInsertedHit
Pointer to the last inserted hit.
std::vector< StIndex > stations
Pattern hit stations to save the filling order.
void print(std::ostream &ostr) const
Print the pattern candidate and stream operator.
void finalizePatternEta()
Finalize the pattern building in eta and update its state.
bool isFinalized
Flag to indicate if the pattern has been finalized.
void finalizePatternPhi()
Finalize the pattern building in phi and update its state.
uint8_t nPrecisionHits
Counts of precision measurements / non-precision in bending direction / phi measurements.
PatternState()=delete
Delete default destructor - ensure patterns are always constructed from a seed or another pattern.
std::unordered_map< StIndex, std::vector< HitPayload > > hitsPerStation
Map collection of hits per station.
uint8_t nMissedLayerHits
Number of missed candidate hits in different measurement layers during pattern building.
const SpacePoint * prevLayerHit
Pointer to the last hit in the second-to-last layer.
uint8_t sectorCoord
expanded sector coordinate & the two corresponding physical sectors
void overWriteHit(const HitPayload &oldHit, const HitPayload &newHit, const double newResidual, const double newAcceptWindow)
Overwrite a hit in the pattern and update the internal state.
const HitPayload & getNthLastHit(const uint8_t n) const
Get the n-th last inserted hit.
Structure to hold visual information about a pattern.