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
11
12#include "Acts/Utilities/VectorHelpers.hpp"
13#include "Acts/Utilities/Helpers.hpp"
14
15#include "CxxUtils/phihelper.h"
16
17namespace {
18 static const Muon::MuonSectorMapping sectorMap{};
19
21 bool areConsecutiveMdt(const MuonR4::SpacePoint& hit1, const MuonR4::SpacePoint& hit2) {
22 if (!hit1.isStraw() || !hit2.isStraw()) {
23 return false;
24 }
25 const uint16_t tubeNum1 {static_cast<const xAOD::MdtDriftCircle*>(hit1.primaryMeasurement())->driftTube()};
26 const uint16_t tubeNum2 {static_cast<const xAOD::MdtDriftCircle*>(hit2.primaryMeasurement())->driftTube()};
30 if(tubeNum1 == tubeNum2) {
31 return false;
32 };
33 return std::abs(tubeNum1-tubeNum2) < 2;
34 }
35 double inDegrees(double angle) {
36 return angle / Gaudi::Units::deg;
37 }
39 using PatternHitVisualInfoVec = std::vector<PatternHitVisualInfo>;
44 void resetVisualToOverlap(PatternHitVisualInfoVec* visualInfo) {
45 if (!visualInfo) return;
46 const auto [first, last] = std::ranges::remove_if(*visualInfo, [](const PatternHitVisualInfo& v){
47 return v.status == PatternHitVisualInfo::PatternStatus::eSuccessful;});
48
49 visualInfo->erase(first,last);
50 }
51}
52
53namespace MuonR4::FastReco {
54GlobalPatternFinder::GlobalPatternFinder(const std::string& name, Config&& config) :
55 AthMessaging{name},
56 m_cfg{config} {
57};
58
59
62 const SpacePointContainerVec& spacepoints,
63 BucketPerContainer& outBuckets) const {
65 auto visualInfo {m_cfg.visionTool ? std::make_unique<PatternHitVisualInfoVec>() : nullptr};
66 PatternStateVec patterns{findPatternsInEta(constructTree(gctx, spacepoints), visualInfo.get())};
67
70
72 for (const PatternState& pat : patterns) {
75
76 for (const auto& [spc, bucketVec] : pat.bucketsPerContainer) {
77 if (outBuckets.find(spc) == outBuckets.end()) {
78 throw std::runtime_error("The space point container associated to the pattern is not present in the output bucket map.");
79 }
80 for (const SpacePointBucket* bucket : bucketVec) {
81 auto& outBucketVec = outBuckets[spc];
82 if (std::ranges::find(outBucketVec, bucket) == outBucketVec.end()) {
83 outBucketVec.push_back(bucket);
84 }
85 }
86 }
87 }
89 if (visualInfo) {
90 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(), "GlobPatternFinder", std::move(*visualInfo));
91 }
93}
94
97 PatternHitVisualInfoVec* visualInfo) const {
98 constexpr auto thetaIdx {Acts::toUnderlying(SeedCoords::eTheta)};
99 constexpr auto sectorIdx {Acts::toUnderlying(SeedCoords::eSector)};
101 using enum SeedCoords;
102 for (const auto seedingLayer : m_cfg.layerSeedings) {
103 ATH_MSG_VERBOSE("Start pattern search in eta with seeding layer "<< layerName(seedingLayer));
105 for (const auto& [seedCoords, seed] : orderedSpacepoints) {
106 const SpacePoint* seedHit {seed.hit};
108 const LayerIndex seedLayer {toLayerIndex(seed.station)};
109 if (seedLayer != seedingLayer || (seedHit->isStraw() && !m_cfg.seedFromMdt)) {
110 continue;
111 }
113 auto nExistingPatterns {std::ranges::count_if(patterns, [&seed, &seedCoords, this](const PatternState& pattern){
114 if (std::abs(pattern.theta - seedCoords[thetaIdx]) > m_cfg.thetaSearchWindow ||
115 pattern.sectorCoord != static_cast<int>(seedCoords[sectorIdx])) {
116 return false;
117 }
118 return pattern.isInPattern(seed);
119 })};
120 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
121 ATH_MSG_DEBUG("The seed hit "<<*seedHit<<"\n coordinates "<<seedCoords<<" has already been used to build "<<nExistingPatterns<<" patterns, which is above the maximum number of attempts allowed to build a pattern from hits already used in existing patterns. Do not use it as seed for a new pattern.");
122 continue;
123 }
125 SearchTree_t::range_t selectRange{};
127 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
129 const double thetaWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
130 ? m_cfg.thetaSearchWindow : 0.5*m_cfg.thetaSearchWindow};
131 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
133 auto candidateHits = orderedSpacepoints.rangeSearchWithKey(selectRange);
134 if (candidateHits.size() < m_cfg.minBendingTriggerHits + m_cfg.minBendingPrecisionHits) {
135 ATH_MSG_VERBOSE("Found "<<candidateHits.size()<<" candidate hits for seed hit "<<*seedHit<<", coordinates "<<seedCoords
136 <<". This is below the minimum number of hits required to build a pattern. Do not create a pattern.");
137 continue;
138 }
140 if (std::ranges::find_if(candidateHits, [this, seedLayer](const auto& c){
141 return m_cfg.idHelperSvc->layerIndex(c.second.hit->identify()) != seedLayer;
142 }) == candidateHits.end()) {
143 ATH_MSG_VERBOSE("All candidate hits for seed hit "<<*seedHit<<", coordinates "<<seedCoords
144 <<" are in the same layer. We need hits in at least two layers to build a pattern. Do not create a pattern.");
145 continue;
146 }
148 std::ranges::sort(candidateHits, [this](const auto& c1, const auto& c2){
149 const HitPayload& hit1 {c1.second};
150 const HitPayload& hit2 {c2.second};
151 LayerOrdering ordering {checkLayerOrdering(hit1, hit2)};
152 if (ordering == LayerOrdering::eSameLayer) {
154 return hit1.hit->localPosition().y() < hit2.hit->localPosition().y();
155 }
156 return ordering == LayerOrdering::eLowerLayer;
157 });
158 const auto seedItr {std::ranges::find_if(candidateHits,
159 [&seed](const auto& c){ return c.second == seed; })};
160 assert(seedItr != candidateHits.end());
162 std::vector<PatternState> activePatterns{};
163 activePatterns.emplace_back(seed, static_cast<int>(seedCoords[sectorIdx]), seedCoords[thetaIdx]);
164 if (visualInfo) {
165 activePatterns.back().visualInfo = std::make_unique<PatternHitVisualInfo>(seedHit, seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
166 }
167
168 const HitPayload* prevCandidate {&seed};
172 auto processNewHit = [&](const TreeNode& testPair){
173 const HitPayload& test {testPair.second};
174 ATH_MSG_VERBOSE("** Check compatibility of hit "<<*test.hit<<", coordinates "<<testPair.first << ". We start from " << activePatterns.size() << " active patterns.");
175 extendPatterns(activePatterns, test, seed, *prevCandidate, visualInfo);
176 prevCandidate = &test;
177 };
179 auto ensureOnePattern = [&activePatterns, &visualInfo, this]() {
180 if (activePatterns.size() > 1) {
181 ATH_MSG_VERBOSE("Found "<<activePatterns.size()<<" patterns during the search. Remove overlaps.");
182 activePatterns = resolveOverlaps(std::move(activePatterns), visualInfo);
183 resetVisualToOverlap(visualInfo);
184 assert(activePatterns.size() == 1);
185 }
186 };
187 activePatterns.back().nInsertedHits = 0;
189 ATH_MSG_VERBOSE("Search between " << candidateHits.size() <<" candidates for compatible hits to "<<*seedHit<<", coordinates: "<<seedCoords);
190 for (auto it = std::next(seedItr); it != candidateHits.end(); ++it) {
191 processNewHit(*it);
192 }
193 ensureOnePattern();
194 activePatterns.back().nInsertedHits = 0;
196 for (auto it = std::reverse_iterator(seedItr); it != candidateHits.rend(); ++it) {
197 processNewHit(*it);
198 }
199 ensureOnePattern();
200 PatternState newPattern {std::move(activePatterns.back())};
202 if (newPattern.nBendingTriggerHits < m_cfg.minBendingTriggerHits || newPattern.nPrecisionHits < m_cfg.minBendingPrecisionHits) {
203 ATH_MSG_VERBOSE("Pattern " << newPattern << " does not meet minimum hit requirements, n bending precision/trigger hits = " << newPattern.nPrecisionHits << "/" << newPattern.nBendingTriggerHits << ". Do not create a pattern.");
205 continue;
206 }
207 newPattern.finalizePatternEta();
208 ATH_MSG_VERBOSE("Add new pattern "<<newPattern);
209 patterns.push_back(std::move(newPattern));
210 }
211 }
212 ATH_MSG_VERBOSE("Found in total "<<patterns.size()<<" patterns in eta before overlap removal");
213 return resolveOverlaps(std::move(patterns), visualInfo);
214}
216 const HitPayload& test,
217 const HitPayload& seed,
218 const HitPayload& prevCandidate,
219 PatternHitVisualInfoVec* visualInfo) const {
220 PatternStateVec nextPatterns{};
221 nextPatterns.reserve(activePatterns.size()* 2);
222
223 const unsigned refMissedLayerHits {std::ranges::min_element(activePatterns,
224 [](const auto& a, const auto& b) { return a.nMissedLayerHits < b.nMissedLayerHits; }
225 )->nMissedLayerHits};
226
227 for (PatternState& pattern : activePatterns) {
228
229 if (pattern.nMissedLayerHits >= m_cfg.maxMissedLayerHits && (!nextPatterns.empty() || pattern.nMissedLayerHits > refMissedLayerHits)) {
230 ATH_MSG_VERBOSE("Pattern " << pattern << " has already missed " << pattern.nMissedLayerHits << " hits in different layers, which is above the maximum allowed. Do not try to extend it with new hits.");
232 continue;
233 }
234
236 LineCompatibilityResult compResult {checkLineCompatibility(seed, test, pattern)};
237 switch (compResult.result) {
240 pattern.addHit(test, compResult.residual, compResult.acceptanceWindow);
241 ATH_MSG_VERBOSE("Hit is compatible, add it to the pattern. Updated pattern: " << pattern);
242 break;
243 }
246 auto isNew = std::ranges::find_if(nextPatterns, [&test](const PatternState& p) {
247 const HitPayload& lastHit {p.getNthLastHit(1u)};
248 return lastHit == test;
249 }) == nextPatterns.end();
250 if (!isNew) {
251 ATH_MSG_VERBOSE("Hit is compatible & in the same layer of the last added hit. The branched pattern is already in the list of next patterns. Do not add it again.");
252 continue;
253 }
255 PatternState newPattern {pattern};
256 const HitPayload& lastPatHit {pattern.getNthLastHit(1u)};
257 newPattern.overWriteHit(lastPatHit, test, compResult.residual, compResult.acceptanceWindow);
258 ATH_MSG_VERBOSE("Hit is compatible & in the same layer of the last added hit. Branch the pattern, new pattern: " << newPattern);
260 if (pattern.visualInfo) {
261 pattern.visualInfo->replacedHits.push_back(test.hit);
262 newPattern.visualInfo->replacedHits.push_back(lastPatHit.hit);
263 }
265 nextPatterns.push_back(std::move(newPattern));
266 break;
267 }
269 ATH_MSG_VERBOSE("Hit is not compatible with the pattern.");
270 if (pattern.visualInfo) {
271 pattern.visualInfo->discardedHits.push_back(test.hit);
272 }
273 if (checkLayerOrdering(test, prevCandidate) != LayerOrdering::eSameLayer){
274 pattern.nMissedLayerHits++;
275 }
276 break;
277 }
278 }
279 nextPatterns.push_back(std::move(pattern));
280 }
281 std::swap(activePatterns, nextPatterns);
282};
285 const HitPayload& test,
286 const PatternState& pattern) const {
287 // Since we test hits in the same **expanded** sector, we can compare the phi of the test hit with the pattern phi, if both available
288 if (test.hit->measuresPhi() && pattern.nPhiHits &&
289 std::abs(CxxUtils::deltaPhi(pattern.phi, test.phi)) > m_cfg.phiTolerance) {
290 ATH_MSG_VERBOSE("The pattern with phi = "<<inDegrees(pattern.phi)<<" is not compatible with the test hit with phi "<<inDegrees(test.phi));
292 }
294 const Amg::Vector3D beamSpot{Amg::Vector3D::Zero()};
295
297 auto checkResidualInWindow = [&](const HitPayload& lastPatHit,
298 const bool isUnique) {
299 // Check whether we have to use the beamspot instead of the last pattern hit to draw the line with the seed.
300 const bool useSeed2Beamspot {lastPatHit.hit == seed.hit ||
301 std::abs( lastPatHit.Z - seed.Z) <= m_cfg.minZDiff4Line ||
302 std::abs( lastPatHit.R - seed.R) <= m_cfg.minRDiff4Line};
303 const double lineSlope {computeLineSlope(lastPatHit, seed, useSeed2Beamspot, beamSpot)};
304 const double testResidual {computeResidual(test, seed, lineSlope)};
305 const double acceptanceWindow {computeAcceptanceWindow(test, seed, lastPatHit, lineSlope, useSeed2Beamspot, beamSpot)};
306 ATH_MSG_VERBOSE("Check residual in window... Residual: " << testResidual << ", Acceptance Window: " << acceptanceWindow);
307 if (pattern.visualInfo) {
308 pattern.visualInfo->hitLineInfo[test.hit] = std::make_pair(lineSlope, acceptanceWindow);
309 }
310 if (testResidual < acceptanceWindow) {
311 return LineCompatibilityResult{isUnique ? CompatibilityResult::eAddHit : CompatibilityResult::eBranchPattern, testResidual, acceptanceWindow};
312 }
314 };
315
317 const HitPayload& lastPatHit {pattern.getNthLastHit(1u)};
318 ATH_MSG_VERBOSE("Last pat hit: " << *lastPatHit.hit << ", station: " << Muon::MuonStationIndex::stName(lastPatHit.station));
320 const LayerOrdering orderingTest2Last {checkLayerOrdering(test, lastPatHit)};
321
323 if(orderingTest2Last == LayerOrdering::eSameLayer) {
325 if (test == lastPatHit) {
326 ATH_MSG_VERBOSE("The test hit is the same as the last inserted hit. Do not add it to the pattern.");
328 }
330 if (areConsecutiveMdt(*test.hit, *lastPatHit.hit)) {
331 ATH_MSG_VERBOSE("The test hit is in the same layer of the last inserted one. They are consecutive MDT hits, so they are compatible.");
332 return LineCompatibilityResult{CompatibilityResult::eAddHit, pattern.lastResidual, pattern.lastAccepWindow};
333 }
335 if (pattern.nInsertedHits == 0) {
336 ATH_MSG_VERBOSE("The test hit is in the same layer of the seed. They are not consecutive MDT hits, so they are not compatible.");
338 }
342 std::size_t n {2};
343 while (n < pattern.nInsertedHits) {
344 const HitPayload& nthHit {pattern.getNthLastHit(n)};
345 if (checkLayerOrdering(test, nthHit) != LayerOrdering::eSameLayer) {
346 return checkResidualInWindow(nthHit, false);
347 }
348 ++n;
349 }
351 return checkResidualInWindow(pattern.getNthLastHit(n), false);
352 }
355 return checkResidualInWindow(lastPatHit, true);
356}
357
359 const HitPayload& seed,
360 const bool useSeed2Beamspot,
361 const Amg::Vector3D& beamSpot) const {
363 const double deltaR {(useSeed2Beamspot ? beamSpot.perp() - seed.R : lastPatHit.R - seed.R) };
364 const double deltaZ {(useSeed2Beamspot ? beamSpot.z() - seed.Z : lastPatHit.Z - seed.Z)};
365 ATH_MSG_VERBOSE("Computing line seed-to-lastPatHit... deltaR = " << lastPatHit.R - seed.R << ", deltaZ = " << lastPatHit.Z - seed.Z
366 << ", we use" << (useSeed2Beamspot ? " beamSpot" : " lastPatHit") << " giving " << std::format("deltaR_slope: {}, deltaZ_slope: {}", deltaR, deltaZ)
367 << ". The line slope is: " << deltaR / deltaZ);
368 return deltaR / deltaZ;
369}
370
372 const HitPayload& seed,
373 const double lineSlope) const {
374 const double signedResidual { testHit.R - seed.R - lineSlope * (testHit.Z - seed.Z)};
375 ATH_MSG_VERBOSE("Computing residual... slope: " << lineSlope << ", deltaR_residual: " << (testHit.R - seed.R) << ", deltaZ_residual: " << (testHit.Z - seed.Z) << ", signed residual: " << signedResidual);
376 return std::abs(signedResidual);
377}
378
380 const HitPayload& seed,
381 const HitPayload& lastPatHit,
382 const double lineSlope,
383 const bool useSeed2Beamspot,
384 const Amg::Vector3D& beamSpot) const {
388 const double geometricalFactor {1.+ Acts::square(lineSlope)};
390 const double& refZ {useSeed2Beamspot ? beamSpot.z() : lastPatHit.Z};
392 const double alpha {(testHit.Z-seed.Z) / (refZ - seed.Z)};
393 const double propagationFactor {1. - alpha + Acts::square(alpha)};
394 ATH_MSG_VERBOSE("Computing the acceptance window... Geometrical Factor: " << std::sqrt(geometricalFactor) << " alpha: " << alpha
395 << ", Propagation Factor: " << std::sqrt(propagationFactor) << ", Computed Window: " << m_cfg.baseRWindow * std::sqrt(geometricalFactor * propagationFactor));
396 return m_cfg.baseRWindow * std::sqrt(2*geometricalFactor * propagationFactor);
397}
398
401 PatternHitVisualInfoVec* visualInfo) const {
402 PatternStateVec outputPatterns{};
403 outputPatterns.reserve(toResolve.size());
405 auto areOverlapping = [this](const PatternState& a, const PatternState& b) {
406 const bool overlapInSector {a.sectorCoord == b.sectorCoord ||
407 (std::abs(a.sectorCoord - b.sectorCoord) == 1 && std::abs(a.phi - b.phi) <= m_cfg.phiTolerance)};
408 return overlapInSector && std::abs(a.theta - b.theta) <= m_cfg.thetaSearchWindow;
409 };
411 auto isBetter = [](const PatternState& a, const PatternState& b) {
412 if (a.nBendingTriggerHits != b.nBendingTriggerHits) {
413 return a.nBendingTriggerHits > b.nBendingTriggerHits;
414 }
415 if (a.nPrecisionHits != b.nPrecisionHits) {
416 return a.nPrecisionHits > b.nPrecisionHits;
417 }
418 return a.meanNormResidual2 < b.meanNormResidual2;
419 };
420 for (auto it = toResolve.begin(); it != toResolve.end(); ++it) {
421 if (it->isOverlap) {
422 // If already marked as overlap, add to visual info, and discard the pattern
424 continue;
425 }
426 for (auto jt = std::next(it); jt != toResolve.end(); ++jt) {
427 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
428 continue;
429 }
430 if (isBetter(*it, *jt)) {
431 jt->isOverlap = true;
432 } else {
433 it->isOverlap = true;
434 break;
435 }
436 }
437 if (!it->isOverlap) {
438 outputPatterns.push_back( std::move(*it));
439 } else {
440 // If overlap, add to visual info, as the pattern will be discarded
442 }
443 }
444 ATH_MSG_VERBOSE("Patterns surviving overlap removal: "<< outputPatterns.size());
445 return outputPatterns;
446}
448 PatternStateVec& patterns) const {
449 constexpr auto covIdxEta {Acts::toUnderlying(SpacePoint::CovIdx::etaCov)};
450 for (PatternState& pattern : patterns) {
452 for (const auto& [spc, bucketVec] : pattern.bucketsPerContainer) {
453 for (const SpacePointBucket* bucket : bucketVec) {
454 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
455 const Amg::Vector3D locY {localToGlobal.linear() * Amg::Vector3D::UnitY()};
456 for (const auto& hit : *bucket) {
457 // We are looking for phi-only hits
458 if (hit->measuresEta()){
459 continue;
460 }
461 // Check phi compatibility
462 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
463 const double globPhi {globalPos.phi()};
464 if (!isPhiCompatible(globPhi, pattern)) {
465 continue;
466 }
467 // Check eta compatibility. We compute the global theta at the boundaries of the phi strip
468 const double sigmaEta {std::sqrt(hit->covariance()[covIdxEta])};
469 double thetaMin {(globalPos - sigmaEta * locY).theta()};
470 double thetaMax {(globalPos + sigmaEta * locY).theta()};
471 if (thetaMax < thetaMin) {
472 std::swap(thetaMin, thetaMax);
473 }
474 if (pattern.theta > thetaMax || pattern.theta < thetaMin) {
475 ATH_MSG_VERBOSE("The pattern with theta = "<<inDegrees(pattern.theta)<<" is not compatible with the test hit theta strip: ["<<inDegrees(thetaMin)<<", "<<inDegrees(thetaMax)<<"]");
476 continue;
477 }
478 // Create the hit payload and add the hit to the pattern
479 pattern.addHit(HitPayload{hit.get(), m_cfg.idHelperSvc->stationIndex(hit->identify()), globPhi}, 0., 0.);
480 }
481 }
482 }
483 pattern.finalizePatternPhi();
484 }
485}
486bool GlobalPatternFinder::isPhiCompatible(const double testPhi,
487 const PatternState& pattern) const {
491 if (pattern.nPhiHits) {
492 if (std::abs(CxxUtils::deltaPhi(pattern.phi, testPhi)) > m_cfg.phiTolerance) {
493 ATH_MSG_VERBOSE("The pattern with phi = "<<inDegrees(pattern.phi)<<" is not compatible with the test hit with phi "<<inDegrees(testPhi));
494 return false;
495 }
496 } else {
497 const bool isCompatible {pattern.sector1 == pattern.sector2 ? sectorMap.insideSector(pattern.sector1, testPhi) :
498 sectorMap.insideSector(pattern.sector1, testPhi) && sectorMap.insideSector(pattern.sector2, testPhi)};
499 if (!isCompatible) {
500 ATH_MSG_VERBOSE("The test hit with phi = "<<inDegrees(testPhi)<<" is not inside the pattern sectors: "<<pattern.sector1<<" and "<<pattern.sector2);
501 return false;
502 }
503 }
504 return true;
505}
507 GlobalPattern::HitCollection hitPerStation{};
508 for (const auto& [station, hits] : cache.hitsPerStation) {
509 auto& out = hitPerStation[station];
510 out.reserve(hits.size());
511 std::ranges::transform(hits, std::back_inserter(out), [](const HitPayload& h){ return h.hit;});
512 }
513 GlobalPattern pattern{std::move(hitPerStation)};
514 pattern.setTheta(cache.theta);
515 pattern.setPhi(cache.phi);
516 // Set the pattern sector(s) and theta.
517 pattern.setSector(cache.sector1);
518 pattern.setSecondarySector(cache.sector2);
519 // Set pattern quality information.
520 pattern.setNPrecisionHits(cache.nPrecisionHits);
521 pattern.setNEtaNonPrecisionHits(cache.nBendingTriggerHits);
522 pattern.setNPhiHits(cache.nPhiHits);
523 pattern.setMeanNormResidual2(cache.meanNormResidual2);
524 return pattern;
525}
526
530 patterns.reserve(cache.size());
531 std::transform(cache.begin(), cache.end(), std::back_inserter(patterns), [this](const PatternState& cacheEntry) {
532 GlobalPattern pattern {convertToPattern(cacheEntry)};
533 ATH_MSG_VERBOSE("Converted new pattern "<<pattern);
534 return pattern;
535 });
536 return patterns;
537}
538
541 const SpacePointContainerVec& spacepoints) const {
542 SearchTree_t::vector_t rawData{};
544 // Before the loops: estimate the total number of hits
545 size_t totalHits = 0;
546 for (const SpacePointContainer* spc : spacepoints) {
547 for (const SpacePointBucket* bucket : *spc) {
548 totalHits += bucket->size();
549 }
550 }
551 // We can have up to 3 entries per hit (when the hit does not measure phi).
552 rawData.reserve(3 * totalHits);
553
554 for (const SpacePointContainer* spc : spacepoints) {
555 ATH_MSG_VERBOSE("Adding to the search tree "<<spc->size()<<" space point buckets");
556 for (const SpacePointBucket* bucket : *spc) {
557 ATH_MSG_VERBOSE("Processing " << bucket->size() << " spacepoint...");
558 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
559 const int sector = bucket->msSector()->sector();
560
561 for (const auto& hit : *bucket) {
562 // Ignore only-phi hits and MDT hits if desired
563 if (!hit->measuresEta() || (!m_cfg.useMdtHits && hit->isStraw())) {
564 continue;
565 }
566 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
567 const double globalTheta {globalPos.theta()};
568 const HitPayload newHit {hit.get(), bucket, spc, m_cfg.idHelperSvc->stationIndex(hit->identify()),
569 m_spSorter.sectorLayerNum(*hit),globalPos.perp(), globalPos.z(), globalPos.phi()};
570
573 for (const auto proj : {SectorProjector::leftOverlap, SectorProjector::center, SectorProjector::rightOverlap}) {
575 const int projSector = MsTrackSeeder::ringSector(sector + Acts::toUnderlying(proj));
576 if (hit->measuresPhi() && proj != SectorProjector::center &&
577 !sectorMap.insideSector(projSector, newHit.phi)) {
578 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" Hit @"<< *hit
579 <<" is not in sector "<<projSector<<" which is "<<MsTrackSeeder::to_string(proj) <<" to "<< sector);
580 continue;
581 }
582 std::array<double, 2> coords{};
583 coords[Acts::toUnderlying(SeedCoords::eTheta)] = globalTheta;
584
588 coords[Acts::toUnderlying(SeedCoords::eSector)] = MsTrackSeeder::ringOverlap(2*sector + Acts::toUnderlying(proj));
589 ATH_MSG_VERBOSE(__func__<<"() "<<__LINE__<<" Add hit @"<< *hit
590 <<"\nwith global position "<<Amg::toString(globalPos) <<" and coordinates "<<coords<<" to the search tree");
591 rawData.emplace_back(std::move(coords), newHit);
592 }
593 }
594 }
595 }
596 ATH_MSG_VERBOSE("Create a new tree with "<<rawData.size()<<" entries. ");
597 return SearchTree_t{std::move(rawData)};
598}
599
600
603 const HitPayload& hit2) const {
604 auto getLayerOrdering = [](const bool isLayer1Lower) {
606 };
608 if (hit1.hit->msSector() == hit2.hit->msSector()) {
609 if (hit1.layerNum == hit2.layerNum) {
611 } else {
612 return getLayerOrdering(hit1.layerNum < hit2.layerNum);
613 }
614 }
615 StIndex st1 {hit1.station};
616 StIndex st2 {hit2.station};
618 if (st1 == st2) {
619 return getLayerOrdering(hit1.hit->msSector()->barrel() ? hit1.R < hit2.R : hit1.Z < hit2.Z);
620 }
621 LayerIndex layer1 {toLayerIndex(st1)};
622 LayerIndex layer2 {toLayerIndex(st2)};
623 if (layer1 == layer2) {
625 if (layer1 == LayerIndex::Middle) {
627 return getLayerOrdering(st1 == StIndex::BM);
628 } else if (layer1 == LayerIndex::Inner) {
630 return getLayerOrdering(hit1.R < hit2.R);
631 } else {
632 throw std::runtime_error("Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
633 }
634 }
635 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
637 return getLayerOrdering(layer1 == LayerIndex::Inner);
638 }
639 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
641 return getLayerOrdering(layer2 == LayerIndex::Outer);
642 }
643 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
645 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
646 }
648 if (layer1 == LayerIndex::Extended) {
649 return getLayerOrdering(st2 == StIndex::EM);
650 }
651 return getLayerOrdering(st1 == StIndex::BM);
652}
653
655 const int sectorCoord,
656 const double seedTheta)
658 sector1{MsTrackSeeder::ringSector((sectorCoord - sectorCoord % 2) / 2)},
659 sector2{MsTrackSeeder::ringSector((sectorCoord + sectorCoord % 2) / 2)},
660 theta{seedTheta} {
661 addHit(seed, 0.0, 0.0);
662}
664 const double residual,
665 const double acceptWindow) {
666 StIndex stationHit {hit.station};
667 hitsPerStation[stationHit].push_back(hit);
668 if (stations.empty() || stations.back() != stationHit) {
669 stations.push_back(stationHit);
670 }
676 if (isPrecisionHit(*hit.hit)) nPrecisionHits++;
677 else if (hit.hit->measuresEta()) nBendingTriggerHits++;
679 if (hit.hit->measuresPhi()) {
680 if (nPhiHits == 0) phi = hit.phi;
681 nPhiHits++;
682 }
684 if (acceptWindow > 1e-3) {
685 meanNormResidual2 += Acts::square(residual / acceptWindow);
686 lastAccepWindow = acceptWindow;
687 lastResidual = residual;
688 }
689}
690
692 const HitPayload& newHit,
693 const double newResidual,
694 const double newAcceptWindow) {
695 if (oldHit.station != newHit.station) {
696 throw std::runtime_error(std::format("Trying to overwrite a hit in station {} with another one from station {}", stName(oldHit.station), stName(newHit.station)));
697 return;
698 }
699 // We expect to overwrite hits of the same type (precision/trigger), since we only branch when we have
700 // compatible hits in the same layer, except for sTGC hits, where we have pad and strips in the same layer
701 if ((isPrecisionHit(*oldHit.hit) != isPrecisionHit(*newHit.hit) ||
702 oldHit.hit->measuresEta() != newHit.hit->measuresEta()) && newHit.hit->type() != xAOD::UncalibMeasType::sTgcStripType) {
703 std::stringstream ss {"Trying to overwrite a hit with incompatible type\n"};
704 ss << "Old hit: " << *oldHit.hit << ", isPrecision: " << isPrecisionHit(*oldHit.hit) << ", measuresEta: " << oldHit.hit->measuresEta() << "\n";
705 ss << "New hit: " << *newHit.hit << ", isPrecision: " << isPrecisionHit(*newHit.hit) << ", measuresEta: " << newHit.hit->measuresEta();
706 throw std::runtime_error(ss.str());
707 return;
708 }
709 auto it = hitsPerStation.find(oldHit.station);
710 if (it == hitsPerStation.end()) {
711 throw std::runtime_error("Trying to remove a hit from a station that is not in the pattern cache");
712 return;
713 }
714 auto& hitsInThisStation = it->second;
715 auto toRemove = std::ranges::find(hitsInThisStation, oldHit);
716 if (toRemove == hitsInThisStation.end()) {
717 throw std::runtime_error("Trying to remove a hit that is not in the pattern cache station hits");
718 return;
719 }
720 *toRemove = newHit;
721
723 if (oldHit.hit->measuresPhi()) nPhiHits--;
724 if (newHit.hit->measuresPhi()) {
725 if (nPhiHits == 0) phi = newHit.phi;
726 nPhiHits++;
727 } else if (nPhiHits == 0) {
728 phi = 0.;
729 }
731 meanNormResidual2 += Acts::square(newResidual / newAcceptWindow) - Acts::square(lastResidual / lastAccepWindow);
732 lastResidual = newResidual;
733 lastAccepWindow = newAcceptWindow;
734}
737 theta = 0.;
738 for (const auto& [_, hits] : hitsPerStation) {
739 for (const auto& hit : hits) {
741 auto& bucketVec {bucketsPerContainer[hit.container]};
742 if (std::ranges::find(bucketVec, hit.bucket) == bucketVec.end()){
743 bucketVec.push_back(hit.bucket);
744 }
746 theta += atan2(hit.R, hit.Z);
747 }
748 }
749 const unsigned nEtaHits {nPrecisionHits + nBendingTriggerHits};
750 theta /= nEtaHits;
752 meanNormResidual2 /= nEtaHits;
753}
755 if (!nPhiHits) {
757 phi = sectorMap.sectorOverlapPhi(sector1, sector2);
758 return;
759 }
760 double deltaPhiAcc {0.};
761 std::optional<double> centralPhi {};
762 for (const auto& [_, hits] : hitsPerStation) {
763 for (const auto& hit : hits) {
764 if (!hit.hit->measuresPhi()) {
765 continue;
766 }
767 if (!centralPhi) {
768 centralPhi = hit.phi;
769 }
770 deltaPhiAcc += CxxUtils::deltaPhi(hit.phi, *centralPhi);
771 }
772 }
773 phi = CxxUtils::wrapToPi(centralPhi.value_or(0.) + deltaPhiAcc / nPhiHits);
774}
775
778 PatternHitVisualInfoVec* visualInfo) const {
779 if (!visualInfo) {
780 return;
781 }
782 GlobalPattern pattern {convertToPattern(cache)};
783 // Check whether the visual info about this pattern is already in the container
784 if (auto it =std::ranges::find_if(*visualInfo, [&pattern](const auto& v){
785 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
786 it->status = status; // Update the status if the pattern is already in the container
787 return;
788 }
789 visualInfo->push_back(*cache.visualInfo);
790 auto& bucketVec {visualInfo->back().parentBuckets};
791 for (const auto& [_, buckets] : cache.bucketsPerContainer) {
792 bucketVec.insert(bucketVec.end(), buckets.begin(), buckets.end());
793 }
794 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
795 visualInfo->back().status = status;
796}
799 if (n > nInsertedHits) {
800 // If we have not yet inserted n hits, we return the seed hit
801 return hitsPerStation.at(stations.front()).front();
802 }
803 std::size_t remaining {n};
804 for (auto stIt = stations.rbegin(); stIt != stations.rend(); ++stIt) {
805 const auto& hits {hitsPerStation.at(*stIt)};
806 const size_t nHits {hits.size()};
807 if (remaining <= nHits) {
808 return hits.at(nHits - remaining);
809 }
810 remaining -= nHits;
811 }
812 // Fall-back return, should not happen if the input n is consistent with nInsertedHits
813 return hitsPerStation.at(stations.front()).front();
814}
816 const auto seedStationIt {hitsPerStation.find(hit.station)};
817 return seedStationIt != hitsPerStation.end() &&
818 std::ranges::find(seedStationIt->second, hit) != seedStationIt->second.end();
819}
821 return hitsPerStation == other.hitsPerStation;
822}
837 return hit == other.hit;
838}
839void GlobalPatternFinder::PatternState::print(std::ostream& ostr) const {
840 ostr<<"Pattern state, Expanded Sector: "<<sectorCoord<<", Theta: "<<theta << ", Phi: "<<phi;
841 ostr<<", nPrecisionHits: "<<nPrecisionHits<<", nEtaNonPrecisionHits: "<<nBendingTriggerHits<<", nPhiHits: "<<nPhiHits;
842 ostr<<", mean normalized residual squared: "<<meanNormResidual2;
843 ostr<<", Hit per station: \n";
844 for (const auto& [station,hits] : hitsPerStation) {
845 ostr<<" Station "<<Muon::MuonStationIndex::stName(station)<<" has "<<hits.size()<<" hits\n";
846 for (const auto& hit : hits) {
847 ostr<<" "<<*hit.hit<<", R: "<<hit.R<<", Phi:"<<hit.phi<<"\n";
848 }
849 }
850}
851}
Scalar theta() const
theta method
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_DEBUG(x)
static Double_t a
static Double_t ss
static const uint32_t nHits
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
Header file for AthHistogramAlgorithm.
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.
LayerOrdering checkLayerOrdering(const HitPayload &hit1, const HitPayload &hit2) const
Method to check the logical layer ordering of two hits.
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.
LineCompatibilityResult checkLineCompatibility(const HitPayload &seed, const HitPayload &test, const PatternState &pattern) const
Method to check the line compatibility of a test hit with a given 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.
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 isPhiCompatible(const double testPhi, const PatternState &pattern) const
Method to check the phi compatibility of a test hit with a given pattern.
std::vector< GlobalPattern > PatternVec
Abrivation for a vector of global patterns.
double computeAcceptanceWindow(const HitPayload &testHit, const HitPayload &seed, const HitPayload &lastPatHit, const double lineSlope, const bool useSeed2Beamspot, const Amg::Vector3D &beamSpot) const
Method to compute the acceptance window in global R for a given pattern line and test hit.
Muon::MuonStationIndex::StIndex StIndex
Type alias for the station index.
LayerOrdering
Enum to express the logical measurement layer ordering given two hits.
double computeLineSlope(const HitPayload &lastPatHit, const HitPayload &seed, const bool useSeed2Beamspot, const Amg::Vector3D &beamSpot) const
Helper method to compute the line slope between the seed and the last hit in a given pattern in the R...
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.
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.
double computeResidual(const HitPayload &testHit, const HitPayload &seed, const double lineSlope) const
Method to compute the residual in globalR given the pattern line and test hit.
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...
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
const xAOD::UncalibratedMeasurement * primaryMeasurement() const
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...
bool insideSector(int sector, double phi) const
checks whether the phi position is consistent with sector
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
double deltaR(double eta1, double eta2, double phi1, double phi2)
MsTrackSeeder::SectorProjector SectorProjector
bool isPrecisionHit(const SpacePoint &hit)
Returns whether the uncalibrated spacepoint is a precision hit (Mdt, micromegas, stgc strips)
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
const std::string & layerName(LayerIndex index)
convert LayerIndex into a string
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, unsigned 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.
unsigned layerNum
Logical layer number in the sector frame.
bool operator==(const HitPayload &other) const
Equal operator: it compares the underlying hit.
: 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.
unsigned nMissedLayerHits
Number of missed candidate hits in different measurement layers during pattern building.
bool isInPattern(const HitPayload &hit) const
Check wheter a hit is present in the pattern.
int sectorCoord
expanded sector coordinate & the two corresponding physical sectors
double theta
Average theta & average phi of the pattern.
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.
unsigned nInsertedHits
Number of inserted hits during one of the two search stages (from seed outward and from seed inward)
void finalizePatternEta()
Finalize the pattern building in eta and update its state.
bool operator==(const PatternState &other) const
Equal operator, it checks the hit-per-station map.
void finalizePatternPhi()
Finalize the pattern building in phi and update its state.
unsigned nPrecisionHits
Counts of precision measurements / non-precision in bending direction / phi measurements.
std::unordered_map< StIndex, std::vector< HitPayload > > hitsPerStation
Map collection of hits per station.
PatternState(const HitPayload &seed, const int sectorCoord, const double seedTheta)
Constructor taking the seed information.
const HitPayload & getNthLastHit(const std::size_t n) const
Get the nth last inserted hit.
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.
Structure to hold visual information about a pattern.