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
9
10#include "CxxUtils/phihelper.h"
11
13#define PRINT_VERBOSE( xmsg ) \
14 do { \
15 if( logger->msgLvl( MSG::VERBOSE ) ) { \
16 logger->msg( MSG::VERBOSE ) << xmsg << endmsg; \
17 } \
18 } while( 0 )
19namespace {
20 const Muon::MuonSectorMapping sectorMap{};
21
22 double inDegrees(double angle) {
23 return angle / Gaudi::Units::deg;
24 }
25}
26
27namespace MuonR4::FastReco {
28GlobalPatternFinder::GlobalPatternFinder(const std::string& name, Config&& config) :
29 AthMessaging{name},
30 m_cfg{config} {
31 static_assert(std::is_move_assignable_v<PatternState>);
32 static_assert(std::is_move_constructible_v<PatternState>);
33 static_assert(std::is_copy_assignable_v<PatternState>);
34 static_assert(std::is_copy_constructible_v<PatternState>);
35 static_assert(std::is_nothrow_move_constructible_v<PatternState>);
36 static_assert(std::is_nothrow_move_assignable_v<PatternState>);
37};
38
39
42 const SpacePointContainerVec& spacepoints,
43 BucketPerContainer& outBuckets) const {
46 const SearchTree_t orderedSpacepoints {constructTree(gctx, spacepoints)};
47 auto visualInfo {m_cfg.visionTool ? std::make_unique<PatternHitVisualInfoVec>() : nullptr};
48 PatternStateVec patterns{findPatternsInEta(orderedSpacepoints, visualInfo.get())};
49
52
53
54 for (const PatternState& pat : patterns) {
57
59 for (const std::vector<CandidateHit>& hits : pat.hitsPerStation) {
60 for (const auto& hit : hits) {
61 if (outBuckets.find(hit->container) == outBuckets.end()) {
62 throw std::runtime_error("The space point container associated to the pattern is not present in the output bucket map.");
63 }
64 auto& outBucketVec = outBuckets[hit->container];
65 if (std::ranges::find(outBucketVec, hit->bucket) == outBucketVec.end()) {
66 outBucketVec.push_back(hit->bucket);
67 }
68 }
69 }
70 }
72 if (visualInfo) {
73 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(), "GlobPatFind_", std::move(*visualInfo));
74 }
76}
79 PatternHitVisualInfoVec* visualInfo) const {
80 constexpr auto thetaIdx {Acts::toUnderlying(SeedCoords::eTheta)};
81 constexpr auto sectorIdx {Acts::toUnderlying(SeedCoords::eSector)};
82
84 PatternStateVec startPatternBuff{};
85 startPatternBuff.reserve(20);
86 PatternStateVec endPatternBuff{};
87 endPatternBuff.reserve(20);
88
90 const Amg::Vector3D beamSpot{Amg::Vector3D::Zero()};
91
92 PatternStateVec outPatterns{};
93 outPatterns.reserve(40);
98 auto countPatterns = [&outPatterns, this](const HitPayload& hit,
99 const SearchTree_t::coordinate_t& coords) -> uint8_t {
100 return std::ranges::count_if(outPatterns, [&](const PatternState& pattern){
101 if (std::abs(pattern.theta - coords[thetaIdx]) > 2.*m_cfg.thetaSearchWindow ||
102 !pattern.expSect.isNeighbour(ExpandedSector{static_cast<std::int8_t>(coords[sectorIdx])})) {
103 return false;
104 }
105 return pattern.isInPattern(hit);
106 });
107 };
108 using enum SeedCoords;
109 for (const auto seedingLayer : m_cfg.layerSeedings) {
111 for (const auto& [seedCoords, seed] : orderedSpacepoints) {
113 const LayerIndex seedLayer {toLayerIndex(seed.station)};
114 if (seedLayer != seedingLayer || (seed.isStraw && !m_cfg.seedFromMdt)) {
115 continue;
116 }
117 ATH_MSG_VERBOSE(__func__<<"() New seed hit "<<*seed<<", coordinates "<<seedCoords);
119 uint8_t nExistingPatterns {countPatterns(seed, seedCoords)};
120 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
121 // Try first to resolve overlaps and re-count the number of patterns containing the seed
122 outPatterns = resolveOverlaps(outPatterns, visualInfo);
123 nExistingPatterns = countPatterns(seed, seedCoords);
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 }
128 }
130 SearchTree_t::range_t selectRange{};
132 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
136 const double thetaHalfWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
137 ? m_cfg.thetaSearchWindow : 0.5*m_cfg.thetaSearchWindow};
138 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
140 std::vector<CandidateHit> candidateHits{};
141 orderedSpacepoints.rangeSearchMapDiscard(selectRange, [&candidateHits](const SearchTree_t::coordinate_t& /*coords*/,
142 const HitPayload& hit){
143 candidateHits.emplace_back(&hit, hit.R, hit.Z, hit.station, 0u, hit.sector, hit.isStraw);
144 });
145 if (candidateHits.size() < m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers) {
146 ATH_MSG_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits, below the minimum required - skip this seed.");
147 continue;
148 }
150 if (std::ranges::none_of(candidateHits, [this, seedLayer](const CandidateHit& c){
151 return m_cfg.idHelperSvc->layerIndex(c.sp()->identify()) != seedLayer; }) ) {
152 ATH_MSG_VERBOSE(__func__<<"() All candidates are in the same station layer, and we need at least two - skip this seed.");
153 continue;
154 }
156 std::ranges::sort(candidateHits, [](const CandidateHit& c1, const CandidateHit& c2){
157 LayerOrdering ordering {checkLayerOrdering(*c1, *c2)};
158 if (ordering == eSameLayer) {
160 return c1.sp()->localPosition().y() < c2.sp()->localPosition().y();
161 }
162 return ordering == eLowerLayer;
163 });
165 for (std::size_t i {1}; i < candidateHits.size(); ++i) {
166 candidateHits[i].globLayer = candidateHits[i - 1].globLayer +
167 (checkLayerOrdering(*candidateHits[i - 1], *candidateHits[i]) != eSameLayer);
168 }
169 if (candidateHits.back().globLayer + 1u < (m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers)) {
170 ATH_MSG_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits on "<<candidateHits.back().globLayer + 1
171 <<" layers, below the minimum required - skip this seed.");
172 continue;
173 }
174 if (msgLvl(MSG::VERBOSE)) {
175 ATH_MSG_VERBOSE(__func__<<"() Found "<< candidateHits.size()<<" candidate hits: ");
176 for (const auto& c : candidateHits) {
177 ATH_MSG_VERBOSE(__func__<<"() \t**"<<**c<<", glob Z/R/phi: "<<c.Z<<" / "<<c.R<<" / "<<inDegrees(c->phi)
178 << ", st/layer: " << stName(c.station) << " / "<< (int)c.globLayer);
179 }
180 }
181
183 const auto seedItr {std::ranges::find_if(candidateHits,
184 [&seed](const CandidateHit& c){ return *c == seed; })};
185 assert(seedItr != candidateHits.end());
186 const CandidateHit& seedCand {*seedItr};
187
188 PatternState patternSeed{seedCand, static_cast<std::int8_t>(seedCoords[sectorIdx]),
189 seedCoords[thetaIdx], &m_cfg, this};
190 if (visualInfo) {
191 patternSeed.visualInfo = std::make_unique<PatternHitVisualInfo>(
192 seed.hit, seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
193 }
194
203 auto processHitRange = [&](const auto begin,
204 const auto end,
205 PatternState&& toExtend) -> PatternStateVec {
206 startPatternBuff.clear();
207 startPatternBuff.push_back(std::move(toExtend));
208
209 for (auto testItr = begin; testItr != end; ++testItr) {
210 const CandidateHit& testHit {*testItr};
211 if (testHit.globLayer == seedCand.globLayer && !seedCand.isStraw) {
212 continue; // skip hits on the same layer as the seed, if straw
213 }
214 extendPatterns(startPatternBuff, endPatternBuff, testHit, beamSpot, visualInfo);
215 // Swap the buffers for the next iteration
216 std::swap(startPatternBuff, endPatternBuff);
217 }
218 return startPatternBuff.size() > 1
219 ? resolveOverlaps(startPatternBuff, visualInfo)
220 : PatternStateVec{std::move(startPatternBuff.back())};
221 };
222
224 PatternStateVec forwardExtended {processHitRange(std::next(seedItr), candidateHits.end(), std::move(patternSeed))};
225
227 ATH_MSG_VERBOSE(__func__<<"() Finished forward search, found "<<forwardExtended.size()<<" forward patterns, start backward search.");
228 PatternStateVec backwardExtended{};
229 backwardExtended.reserve(2*forwardExtended.size());
230
231 for (PatternState& pat : forwardExtended) {
232 pat.moveLineAnchorHit(seedCand);
233 pat.lastInsertedHit = seedCand;
234
236 std::ranges::move(
237 processHitRange(std::reverse_iterator(seedItr), candidateHits.rend(), std::move(pat)),
238 std::back_inserter(backwardExtended)
239 );
240 }
242 if (backwardExtended.size() > 1) {
243 backwardExtended = resolveOverlaps(backwardExtended, visualInfo);
244 }
245
246 for (PatternState& pat : backwardExtended) {
247 pat.meanNormResidual2 /= pat.nBendingLayers();
248 if (!passPatternCuts(pat)) {
250 continue;
251 }
252 ATH_MSG_VERBOSE(__func__<<"() Add new pattern "<<detailed(pat));
253 pat.isFinalized = true;
254 outPatterns.push_back(std::move(pat));
255 }
256 }
257 }
258 ATH_MSG_VERBOSE(__func__<<"() Found in total "<<outPatterns.size()<<" patterns in eta before overlap removal");
259 return resolveOverlaps(outPatterns, visualInfo);
260}
262 PatternStateVec& endPatterns,
263 const CandidateHit& testHit,
264 const Amg::Vector3D& beamSpot,
265 PatternHitVisualInfoVec* visualInfo) const {
266 endPatterns.clear();
267 ATH_MSG_VERBOSE("processHitRange() *** Test "<<**testHit<<" R/Z/globLayer: "<<testHit.R<<", "<<testHit.Z
268 <<", "<<static_cast<int>(testHit.globLayer)<<" against " << startPatterns.size() << " active patterns.");
269
270 // Compute the minimum number of missed layer hits among the active patterns,
271 // to use as reference for pruning patterns with too many missed layers.
272 std::vector<unsigned> missedLayersVec{};
273 missedLayersVec.reserve(startPatterns.size());
274 std::ranges::transform(startPatterns, std::back_inserter(missedLayersVec), [&testHit](const PatternState& pat){
275 return std::abs(pat.lastInsertedHit.globLayer - testHit.globLayer);
276 });
277 const unsigned minMissedLayers {std::ranges::min(missedLayersVec)};
278
279 const bool shouldPrune {startPatterns.size() > 1 &&
280 std::ranges::any_of(startPatterns, [](const PatternState& p){
281 return p.nBendingLayers() > 2; })};
282
283 for (auto [i, pat] : Acts::enumerate(startPatterns)) {
284 if (pat.isOverlap) {
285 continue;
286 }
287 // Check the pattern has not already missed too many layers compared to other patterns.
288 if (pat.lastInsertedHit.station == testHit.station &&
289 missedLayersVec[i] > std::max(m_cfg.maxMissLayersInStation, minMissedLayers)) {
290 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\nhas missed " << (int)missedLayersVec[i]
291 << " layer hits, above the max allowed - abort pattern.");
293 continue;
294 }
298 if (shouldPrune && pat.lastInsertedHit.globLayer != testHit.globLayer &&
299 std::ranges::find_if(std::next(startPatterns.begin(), i + 1), startPatterns.end(), [&](PatternState& p){
300 if (p.lastInsertedHit != pat.lastInsertedHit || p.isOverlap) return false;
301
302 if (isBetter(pat, p)) {
303 ATH_MSG_VERBOSE("extendPatterns() Pruning: REJECT " << detailed(p));
304 p.isOverlap = true;
305 return false;
306 }
307 ATH_MSG_VERBOSE("extendPatterns() Pruning: REJECT " << detailed(pat));
308 return true; }) != startPatterns.end()) {
309 continue;
310 }
312 const auto [result, residual, accWindow] {pat.checkLineComp(testHit, beamSpot)};
313 switch (result) {
315 if (accWindow > 4.*m_cfg.baseRWindow && residual > m_cfg.baseRWindow) {
319 if (std::ranges::any_of(endPatterns, [&testHit, &pat](const PatternState& p) {
320 return p.isInLastLayer(testHit) &&
321 (p.prevLayerHit == pat.lastInsertedHit || p.nBendingLayers() > (pat.nBendingLayers() + 1u)); })) {
322 ATH_MSG_VERBOSE(__func__<<"() Low-confidence hit: forking leads to existing pattern - reject.");
323 break;
324 }
325 ATH_MSG_VERBOSE(__func__<<"() Low-confidence hit: forking leads to new pattern - fork.");
327 endPatterns.push_back(pat);
328 endPatterns.back().addHit(testHit, residual, accWindow);
330 if (visualInfo) {
331 pat.visualInfo->discardedHits.push_back(testHit.sp());
332 }
333 break;
334 }
335 ATH_MSG_VERBOSE(__func__<<"() Hit compatible - add to pattern.");
336 pat.addHit(testHit, residual, accWindow);
337 break;
338 }
340 /* Check first if the branched pattern already exists*/
341 if (std::ranges::any_of(endPatterns, [&testHit, &pat](const PatternState& p) {
342 return p.isInLastLayer(testHit) && p.prevLayerHit == pat.prevLayerHit; })) {
343 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branched pattern already exists.");
344 break;
345 }
347 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branch pattern.");
348 endPatterns.push_back(pat);
349 endPatterns.back().overWriteHit(testHit, residual, accWindow);
350
352 if (visualInfo) {
353 pat.visualInfo->replacedHits.push_back(testHit.sp());
354 endPatterns.back().visualInfo->replacedHits.push_back(pat.lastInsertedHit.sp());
355 }
356 ATH_MSG_VERBOSE("New pattern: " << brief(endPatterns.back()));
357 break;
358 }
360 ATH_MSG_VERBOSE(__func__<<"() Hit is not compatible with the pattern - reject hit.");
361 if (visualInfo) {
362 pat.visualInfo->discardedHits.push_back(testHit.sp());
363 }
364 break;
365 }
368 if (const uint8_t nMdtLastLayer{pat.nMDTLastLayer()}; nMdtLastLayer >= 3) {
370 LineTestRes newClusterRes {pat.computeLineResidual(pat.lastInsertedHit)};
371 if (newClusterRes.residual > newClusterRes.accWindow) {
372 ATH_MSG_VERBOSE(__func__<<"() Hit is the #"<<nMdtLastLayer+1
373 <<" consecutive MDT hit: new cluster is not compatible - reject.");
374 break;
375 }
376 ATH_MSG_VERBOSE(__func__<<"() Hit is the #"<<nMdtLastLayer + 1
377 <<" consecutive MDT hit: new cluster is compatible - create new pattern with new cluster.");
378 endPatterns.push_back(pat);
380 endPatterns.back().overWriteHit(pat.lastInsertedHit, newClusterRes.residual, newClusterRes.accWindow);
381 endPatterns.back().addHit(testHit, residual, accWindow);
382
384 if (visualInfo) {
385 pat.visualInfo->discardedHits.push_back(testHit.sp());
386 endPatterns.back().visualInfo->replacedHits.push_back(pat.lastInsertedHit.sp());
387 }
388 ATH_MSG_VERBOSE("New pattern: " << brief(endPatterns.back()));
389 break;
390 }
391 ATH_MSG_VERBOSE(__func__<<"() Consecutive MDT hits on same layer - accept. " << brief(pat));
392 pat.addHit(testHit, residual, accWindow);
393 break;
394 }
396 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - overwrite last hit.");
397 pat.overWriteHit(testHit, residual, accWindow);
398
399 if (visualInfo) {
400 pat.visualInfo->replacedHits.push_back(pat.lastInsertedHit.sp());
401 }
402 break;
403 }
404 }
405 endPatterns.push_back(std::move(pat));
406 }
407 startPatterns.clear();
408};
411 if (pat.nTriggerLayers < m_cfg.minTriggerLayers ||
412 pat.nPrecisionLayers < m_cfg.minPrecisionLayers ||
413 std::ranges::count_if(pat.nMeasurementLayers,
414 [this](const uint8_t nLayers) { return nLayers >= m_cfg.minStationLayers; }) < 2) {
415 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet minimum layer requirements - reject.");
416 return false;
417 }
419 if (pat.meanNormResidual2 > m_cfg.meanNormRes2Cut) {
420 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet the mean norm residual2 cut - reject.");
421 return false;
422 }
423 return true;
424}
427 PatternHitVisualInfoVec* visualInfo) const {
428 PatternStateVec outputPatterns{};
429 outputPatterns.reserve(toResolve.size());
431 auto areOverlapping = [this](const PatternState& a, const PatternState& b) {
433 if(!a.expSect.isNeighbour(b.expSect)) {
434 return false;
435 }
436 if (std::abs(a.theta - b.theta) > 2.*m_cfg.thetaSearchWindow) {
437 return false;
438 }
439 if (a.nPhiLayers > 0 && b.nPhiLayers > 0) {
440 if (std::abs(CxxUtils::deltaPhi(a.phi, b.phi)) > 2.*m_cfg.phiTolerance) {
441 return false;
442 }
443 } else if (a.nPhiLayers > 0) {
444 if (!sectorMap.insideSector(b.expSect.msSector(), a.phi) ||
445 !sectorMap.insideSector(b.expSect.adjacentMsSector(), a.phi)) {
446 return false;
447 }
448 } else if (b.nPhiLayers > 0) {
449 if (!sectorMap.insideSector(a.expSect.msSector(), b.phi) ||
450 !sectorMap.insideSector(a.expSect.adjacentMsSector(), b.phi)) {
451 return false;
452 }
453 }
455 int nSharedHits{0};
456 for (std::size_t st{0u}; st < s_nStations; ++st) {
457 const auto& hitsA {a.hitsPerStation[st]};
458 const auto& hitsB {b.hitsPerStation[st]};
459 if (hitsA.empty() || hitsB.empty()) {
460 continue;
461 }
462 nSharedHits += std::ranges::count_if(hitsA, [&](const CandidateHit& hitA){
463 return std::ranges::any_of(hitsB, [&hitA](const CandidateHit& hitB) {
464 return hitA.sp()->primaryMeasurement() == hitB.sp()->primaryMeasurement();
465 });
466 });
467 }
469 const int minHits {std::min(a.nBendingHits(), b.nBendingHits())};
470 return nSharedHits >= 0.5 *minHits;
471 };
473 auto isBetterOverlap = [](const PatternState& a, const PatternState& b) {
474 const int nGoodStationDiff {a.nStations(/*onlyGoodStations=*/ true) - b.nStations(/*onlyGoodStations=*/ true)};
475 if (nGoodStationDiff != 0) {
476 return nGoodStationDiff > 0;
477 }
478 return isBetter(a,b);
479 };
480
481 for (auto it = toResolve.begin(); it != toResolve.end(); ++it) {
482 if (it->isOverlap) {
483 // If already marked as overlap, add to visual info, and discard the pattern
485 continue;
486 }
487 for (auto jt = std::next(it); jt != toResolve.end(); ++jt) {
488 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
489 continue;
490 }
491 if (isBetterOverlap(*it, *jt)) {
492 ATH_MSG_VERBOSE(__func__<<"() Pattern "<<detailed(*it)<<"\nis BETTER than\n"<<detailed(*jt));
493 jt->isOverlap = true;
494 } else {
495 it->isOverlap = true;
496 ATH_MSG_VERBOSE(__func__<<"() Pattern "<<detailed(*jt)<<"\nis BETTER than\n"<<detailed(*it));
497 break;
498 }
499 }
500 if (!it->isOverlap) {
501 outputPatterns.push_back( std::move(*it));
502 } else {
503 // If overlap, add to visual info, as the pattern will be discarded
505 }
506 }
507 ATH_MSG_VERBOSE(__func__<<"() Patterns surviving overlap removal: "<< outputPatterns.size());
508 return outputPatterns;
509}
511 PatternStateVec& patterns) const {
512 constexpr auto covIdxEta {Acts::toUnderlying(SpacePoint::CovIdx::etaCov)};
517 struct PhiStripProjectionModel {
518 StIndex station{};
519 double refLay{0.};
520 double refStrip{0.};
521 double invSlope{0.};
522 bool isValid{false};
523
524 double project(const Amg::Vector3D& pos) const {
525 const double layCoord = isBarrel(station) ? pos.perp() : pos.z();
526 return refStrip + (layCoord - refLay) * invSlope;
527 }
528 double residual(const Amg::Vector3D& pos) const {
529 const double stripCoord = isBarrel(station) ? pos.z() : pos.perp();
530 return std::abs(project(pos) - stripCoord);
531 }
532 };
533 auto makeProjectionModel = [this](PatternState& pat, const StIndex station) {
534 PhiStripProjectionModel result{};
535 result.station = station;
536 const auto& hits {pat.hitsPerStation[Acts::toUnderlying(station)]};
537 if (hits.empty()) return result;
538
539 // Define the coordinate across the layers, i.e. orthogonal to the strip
540 const auto layCoord = [&result](const HitPayload& hit) {
541 return isBarrel(result.station) ? hit.R : hit.Z;
542 };
543 // Define the coordinate along the strip, i.e. orthogonal to the measureent layers
544 const auto stripCoord = [&result](const HitPayload& hit) {
545 return isBarrel(result.station) ? hit.Z : hit.R;
546 };
547
548 const HitPayload* sp1 {nullptr};
549 const HitPayload* sp2 {nullptr};
550 if (pat.nMeasurementLayers[Acts::toUnderlying(station)] > 1) {
551 // if we have >= 2 eta hits in the station, we use the furthestmost to define the pattern line
552 const auto [minIt, maxIt] = std::ranges::minmax_element(hits, {},
553 [](const CandidateHit& c){ return c.globLayer; });
554 sp1 = minIt->hit;
555 sp2 = maxIt->hit;
556 }
557 if (!sp1 || !sp2 || std::abs(layCoord(*sp2) - layCoord(*sp1)) < m_cfg.minLayerSeparation) {
558 // if we have only one eta hit, to find the second hit we use the we use the functionality of anchor hit
559 pat.moveLineAnchorHit(hits.front());
560
561 sp1 = hits.front().hit;
562 sp2 = pat.lineAnchorHit.hit;
563 }
564 if (!sp1 || !sp2 ) return result;
565
566 result.refLay = layCoord(*sp1);
567 result.refStrip = stripCoord(*sp1);
568 result.invSlope = (stripCoord(*sp2) - stripCoord(*sp1)) / (layCoord(*sp2) - layCoord(*sp1));
569 result.isValid = true;
570 return result;
571 };
572
573 PatternStateVec survivingPatterns{};
574 survivingPatterns.reserve(patterns.size());
575 for (PatternState& pat : patterns) {
576 pat.finalizePatternEta();
578 ATH_MSG_VERBOSE(__func__<<"() Search for phi-only hits for pattern: " << brief(pat));
579 // Projection model of pattern line onto a given phi strip
580 std::optional<PhiStripProjectionModel> patProjOnStrip{};
581 for (const SpacePointBucket* bucket : pat.getParentBuckets()) {
582 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
583 const StIndex station {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
584 // If the projection model is not valid, we will use the pattern theta. We cache the local Y in glob frame
585 const Amg::Vector3D locY {localToGlobal.linear() * Amg::Vector3D::UnitY()};
586
587 for (const auto& hit : *bucket) {
588 // We are looking for phi-only hits
589 if (hit->measuresEta()){
590 continue;
591 }
592 ATH_MSG_VERBOSE(__func__<<"() *** Test phi-only hit "<<*hit);
593 // Check phi compatibility
594 const Amg::Vector3D locPosTest {hit->localPosition()};
595 const Amg::Vector3D globPosTest {localToGlobal * locPosTest};
596 const double globPhi {globPosTest.phi()};
597 if (!pat.isPhiCompatible(globPhi)) {
598 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit not compatible");
599 continue;
600 }
601 // Check there are not other phi hits in the same layer
602 const uint8_t layNum = m_spSorter.sectorLayerNum(*hit);
603 const uint8_t stIdx = Acts::toUnderlying(station);
604 assert(!pat.hitsPerStation[stIdx].empty());
605 if (std::ranges::any_of(pat.hitsPerStation[stIdx], [&](const CandidateHit& h){
606 return h.sp()->measuresPhi() && hit->msSector() == h.sp()->msSector() && layNum == h->locLayer; }) ||
607 std::ranges::any_of(pat.phiOnlyHits, [&](const HitPayload& h){
608 return station == h.station && hit->msSector() == h->msSector() && layNum == h.locLayer; })) {
609 ATH_MSG_VERBOSE(__func__<<"() The pattern already has a phi hit in the same layer - skip this test hit.");
610 continue;
611 }
612 // Check eta compatibility. Try first to use the pattern line projection model
613 bool isEtaCompatible {false};
614 const double sigmaEta {std::sqrt(hit->covariance()[covIdxEta])};
615 if (!patProjOnStrip.has_value() || patProjOnStrip->station != station) {
616 patProjOnStrip = makeProjectionModel(pat, station);
617 }
618 if(patProjOnStrip->isValid) {
619 isEtaCompatible = patProjOnStrip->residual(globPosTest) <= 1.1*sigmaEta;
620 ATH_MSG_VERBOSE(__func__<<"() Distance pattern line from strip center: "<<patProjOnStrip->residual(globPosTest)
621 <<", strip half-length: "<<sigmaEta<<", isCompatible: "<<isEtaCompatible);
622 } else {
623 // Check pattern theta against global theta at the boundaries of the phi strip
624 double thetaMin {(globPosTest - sigmaEta * locY).theta()};
625 double thetaMax {(globPosTest + sigmaEta * locY).theta()};
626 if (thetaMax < thetaMin) {
627 std::swap(thetaMin, thetaMax);
628 }
629 isEtaCompatible = std::max(pat.theta - thetaMax, thetaMin - pat.theta) < 0.5 * m_cfg.thetaSearchWindow;
630 ATH_MSG_VERBOSE(__func__<<"() Pattern theta "<<inDegrees(pat.theta)
631 <<", strip theta window: ["<<inDegrees(thetaMin)<<", "<<inDegrees(thetaMax)<<"]");
632
633 }
634 if (!isEtaCompatible) {
635 ATH_MSG_VERBOSE(__func__<<"() The pattern falls outside the test hit strip in eta - skip this test hit.");
636 continue;
637 }
638 // Create the hit payload and add the hit to the pattern. Save only relevant quantities for phi-only hits.
639 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit compatible - add it to the pattern.");
640 pat.phiOnlyHits.emplace_back(hit.get(), /*bucket*/nullptr, /*container*/nullptr, /*R*/0.f, /*Z*/0.f,
641 globPhi, station, layNum, /*sector*/0u, /*isPrecision*/false, /*isStraw*/false);
642 if (pat.nPhiLayers == 0) pat.phi = globPhi;
643 pat.nPhiLayers++;
644 }
645 }
646 if (pat.nPhiLayers < m_cfg.minPhiLayers) {
647 ATH_MSG_VERBOSE(__func__<<"() Pattern "<< detailed(pat)<<" has only "<<pat.nPhiLayers
648 <<" phi layers, below the minimum required - reject this pattern.");
649 continue;
650 }
651 pat.finalizePatternPhi();
652 survivingPatterns.push_back(std::move(pat));
653 }
654 std::swap(patterns, survivingPatterns);
655}
657 GlobalPattern::HitCollection hitPerStation{};
659 for (uint8_t st{0u}; st < s_nStations; ++st) {
660 const auto& hits {cache.hitsPerStation[st]};
661 if (hits.empty()) continue;
662
663 auto& out {hitPerStation[static_cast<StIndex>(st)]};
664 out.reserve(hits.size());
665 std::ranges::transform(hits, std::back_inserter(out),
666 [](const CandidateHit& h){ return h.sp();});
667 }
669 for (const HitPayload& hit : cache.phiOnlyHits) {
670 auto& out {hitPerStation[static_cast<StIndex>(hit.station)]};
671 out.push_back(hit.sp());
672 }
673 GlobalPattern pattern{std::move(hitPerStation)};
674 pattern.setTheta(cache.theta);
675 pattern.setPhi(cache.phi);
676 // Set the pattern sector(s) and theta.
677 pattern.setSector(cache.expSect.sector());
678 // Set pattern quality information.
679 pattern.setNPrecisionLayers(cache.nPrecisionLayers);
680 pattern.setNTriggerLayers(cache.nTriggerLayers);
681 pattern.setNPhiLayers(cache.nPhiLayers);
682 pattern.setMeanNormResidual2(cache.getMeanResidual2());
683 return pattern;
684}
685
689 patterns.reserve(cache.size());
690 std::transform(cache.begin(), cache.end(), std::back_inserter(patterns),
691 [this](const PatternState& cacheEntry) {
692 return convertToPattern(cacheEntry);
693 });
694 return patterns;
695}
696
699 const SpacePointContainerVec& spacepoints) const {
700 SearchTree_t::vector_t rawData{};
701 using SectorProjector = ExpandedSector::SectorProjector;
702 // Before the loops: estimate the total number of hits
703 size_t totalHits = 0;
704 for (const SpacePointContainer* spc : spacepoints) {
705 for (const SpacePointBucket* bucket : *spc) {
706 totalHits += bucket->size();
707 }
708 }
709 // We can have up to 3 entries per hit (when the hit does not measure phi).
710 rawData.reserve(3 * totalHits);
711
712 for (const SpacePointContainer* spc : spacepoints) {
713 ATH_MSG_VERBOSE(__func__<<"() Processing "<<spc->size()<<" space point buckets...");
714 for (const SpacePointBucket* bucket : *spc) {
715 ATH_MSG_VERBOSE(__func__<<"() Processing " << bucket->size() << " spacepoints...");
716 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
717 const StIndex bucketStation {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
718 const uint8_t sector = bucket->msSector()->sector();
719
720 for (const auto& hit : *bucket) {
721 // Ignore only-phi hits and MDT hits if desired
722 const bool isStraw {hit->isStraw()};
723 if (!hit->measuresEta() || (!m_cfg.useMdtHits && isStraw)) {
724 continue;
725 }
726 ATH_MSG_VERBOSE(__func__<<"() Spacepoint: " << *hit);
727 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
728 const double globalPhi {globalPos.phi()};
729 const ExpandedSector hitExpSector {globalPhi};
730 const uint8_t localLayer = m_spSorter.sectorLayerNum(*hit);
731 const bool isPrecision {MuonR4::isPrecisionHit(*hit)};
732
733 /* Determine the projection direction, which is the direction normal to the radial direction */
734 const Amg::Vector3D projDir {ExpandedSector{sector, SectorProjector::center}.normalDir()};
735
738 for (const auto proj : {SectorProjector::leftOverlap, SectorProjector::center, SectorProjector::rightOverlap}) {
740 const ExpandedSector expSect {sector, proj};
741 if (proj != SectorProjector::center && hit->measuresPhi() && expSect != hitExpSector) {
742 ATH_MSG_VERBOSE(__func__<<"() Hit with "<<hitExpSector<<" is not compatible with "<<expSect);
743 continue;
744 }
745
746 /* Project the hit onto the plane along the sector radial direction.
747 * This allows to remove the bias of hit displacement in phi direction */
748 const double projR {(globalPos - globalPos.dot(projDir) * projDir).perp()};
749
750 std::array<double, 2> coords{};
751 coords[Acts::toUnderlying(SeedCoords::eTheta)] = atan2(projR, globalPos.z());
752 coords[Acts::toUnderlying(SeedCoords::eSector)] = expSect.sector();
753
754 ATH_MSG_VERBOSE(__func__<<"() Add hit: Z: " << globalPos.z() << ", R: " << globalPos.perp() << ", ProjR: " << projR
755 << ", Phi: "<< inDegrees(globalPhi) << ", SectorPhi: " << inDegrees(expSect.phi()) << " and coordinates "<<coords<<" to the search tree");
756 rawData.emplace_back(std::move(coords), HitPayload{hit.get(), bucket, spc,
757 static_cast<float>(projR), static_cast<float>(globalPos.z()), static_cast<float>(globalPhi),
758 bucketStation, localLayer, sector, isPrecision, isStraw});
759 }
760 }
761 }
762 }
763 ATH_MSG_VERBOSE(__func__<<"() Create a new tree with "<<rawData.size()<<" entries. ");
764 return SearchTree_t{std::move(rawData)};
765}
767 const std::int8_t expSector,
768 const double seedTheta,
769 const Config* cfg,
770 const AthMessaging* logger)
771 : cfg{cfg},
772 logger{logger},
773 lastInsertedHit{seed},
774 prevLayerHit{seed},
775 lineAnchorHit{seed},
776 theta{seedTheta},
777 expSect{ExpandedSector{expSector}} {
778
780 nMeasurementLayers[Acts::toUnderlying(seed.station)]++;
781 if (seed->isPrecision) nPrecisionLayers++;
782 else nTriggerLayers++;
784 if (seed->sp()->measuresPhi()) {
785 phi = seed->phi;
786 nPhiLayers++;
787 }
789 hitsPerStation[Acts::toUnderlying(seed.station)].push_back(seed);
790 needLineUpdate = true;
791}
794 const Amg::Vector3D& beamSpot) {
795 // 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
796 if (nPhiLayers) {
797 const double maxPhiDiff {testHit.sp()->measuresPhi() ?
798 cfg->phiTolerance : sectorMap.sectorWidth(testHit.sector)};
799 if (std::abs(CxxUtils::deltaPhi(phi, static_cast<double>(testHit->phi))) > maxPhiDiff) {
800 PRINT_VERBOSE(__func__<<"() The pattern with phi = "<<inDegrees(phi)
801 <<" is not compatible with the test hit with phi "<<inDegrees(testHit->phi) << " - reject.");
802 return LineTestRes{};
803 }
804 }
805
809 auto makeResult = [&testHit, this](const LineTestDecision decision) -> LineTestRes {
811 if (res.residual < res.accWindow) {
812 res.result = decision;
813 }
814 if (visualInfo) {
815 visualInfo->hitLineInfo[testHit.sp()] = std::make_pair(lineSlope, res.accWindow);
816 }
817 return res;
818 };
819
820 /*************** Test hit is on a new layer — draw line from line anchor to lastHit */
821 if(testHit.globLayer != lastInsertedHit.globLayer) {
822 updateLineParameters(beamSpot);
823 return makeResult(LineTestDecision::eAddHit);
824 }
825 /*************** Test hit is on the same layer as the last inserted hit ***************/
827 if (testHit == lastInsertedHit) {
828 PRINT_VERBOSE(__func__<<"() Test hit is the same as last inserted hit - reject.");
829 return LineTestRes{};
830 }
832 if (areConsecutiveMdt(testHit, lastInsertedHit)) {
834 }
836 if (lineAnchorHit.globLayer == lastInsertedHit.globLayer) {
837 PRINT_VERBOSE(__func__<<"() Test hit on same layer as seed with no prior hits, but not consecutive MDT hits - reject.");
838 return LineTestRes{};
839 }
841 if (testHit.sp()->primaryMeasurement() == lastInsertedHit.sp()->primaryMeasurement()) {
842 if (testHit.sp()->measuresPhi() && !lastInsertedHit.sp()->measuresPhi()) {
843 return makeResult(LineTestDecision::eOverwriteLastHit);
844 }
845 if (!testHit.sp()->measuresPhi() && lastInsertedHit.sp()->measuresPhi()) {
846 return LineTestRes{};
847 }
848 if (nPhiLayers && std::abs(CxxUtils::deltaPhi(phi, static_cast<double>(testHit->phi))) <
849 std::abs(CxxUtils::deltaPhi(phi, static_cast<double>(lastInsertedHit->phi)))) {
850 return makeResult(LineTestDecision::eOverwriteLastHit);
851 }
852 return LineTestRes{};
853 }
855 if (testHit->isPrecision != lastInsertedHit->isPrecision) {
856 if (lastInsertedHit->isPrecision) {
858 PRINT_VERBOSE(__func__<<"() Test hit is a trigger hit and last inserted hit is precision on the same layer - keep the precision hit.");
859 return LineTestRes{};
860 }
862 PRINT_VERBOSE(__func__<<"() Test hit is a precision hit and last inserted hit is trigger on the same layer - check residual...");
863 return makeResult(LineTestDecision::eOverwriteLastHit);
864 }
865 return makeResult(LineTestDecision::eBranchPattern);
866}
868 // Treat first the special case where we have only one station
869 if (nStations(/*onlyGoodStations=*/ false) < 2) {
870 // If we call this method with only one station, it means that we inverted the hit search direction without
871 // finding any hit in other stations beside the initial one. So the anchor is the last added hit.
873 return;
874 }
875 // Find first the closest station to the reference station among the pattern stations
876 const auto& closestStIt = std::ranges::min_element(hitsPerStation, std::ranges::less{},
877 [&refHit](const auto& hits){
878 if (hits.empty() || hits.front().station == refHit.station) {
879 return std::numeric_limits<int>::max();
880 }
881 return std::abs(hits.front().globLayer - refHit.globLayer);
882 });
883
884 // Then find the closest hit in that station to the reference hit
885 const auto& hits {*closestStIt};
886 auto it {std::ranges::min_element(hits, std::ranges::less{},
887 [&refHit](const CandidateHit& hit){
888 return std::abs(hit.globLayer - refHit.globLayer); })};
889
890 // Find how many hits in the same layer we have, to be able to set the line anchor at the central hit
891 uint8_t nSameLayer {1u};
892 for (auto jt = std::next(it); jt != hits.end() && jt->globLayer == it->globLayer; ++jt) {
893 ++nSameLayer;
894 }
895 lineAnchorHit = *std::next(it, (nSameLayer - 1u) / 2u);
896}
898 if (!needLineUpdate) {
899 return;
900 }
901 const StIndex lastSt {lastInsertedHit.station};
902 auto& hitsLastSt {hitsPerStation[Acts::toUnderlying(lastSt)]};
903 // Check whether we have to use the beamspot instead of the last pattern hit to draw the line with the line anchor.
904 const bool new_useBeamspot {lastSt == lineAnchorHit.station &&
905 (isBarrel(lastSt) ? std::abs(lastInsertedHit.R - lineAnchorHit.R)
906 : std::abs(lastInsertedHit.Z - lineAnchorHit.Z)) <= cfg->minLayerSeparation};
907
908 const double new_anchorR {new_useBeamspot ? beamSpot.perp() : lineAnchorHit.R};
909 const double new_anchorZ {new_useBeamspot ? beamSpot.z() : lineAnchorHit.Z};
910
911 /* Determine the (average) coordinates of the last added hit(s). If it is a straw, find
912 * the consecutive (MDT) hits on the same layer and return use average position
913 * for next computations — gives a more central reference for the line direction */
914 double refR {0.}, refZ {0.};
915 uint8_t nSameLayer {0u};
916 for (const auto& hit : hitsLastSt) {
917 if (hit.globLayer != lastInsertedHit.globLayer) continue;
918 refR += hit.R;
919 refZ += hit.Z;
920 ++nSameLayer;
921 }
922 refR /= nSameLayer;
923 refZ /= nSameLayer;
924
925 const double new_dZ_slope {refZ - new_anchorZ};
926 if (std::abs(new_dZ_slope) < 1.) {
927 PRINT_VERBOSE("updateLineParameters() Couldn't update the line parameters.");
928 return;
929 }
930 const double dR_slope {refR - new_anchorR};
931 PRINT_VERBOSE("updateLineParameters() Update line parameters dZ/slope: "<<dZ_slope<<", " << lineSlope
932 << " --> " << new_dZ_slope << ", " << dR_slope / new_dZ_slope);
933
934 lineSlope = dR_slope / new_dZ_slope;
935 dZ_slope = new_dZ_slope;
936 anchorR = new_anchorR;
937 anchorZ = new_anchorZ;
938 useBeamspot = new_useBeamspot;
939 // If we have only one hit and it is a straw, store the LR correction factor
940 LR_factor = (nSameLayer == 1u && lastInsertedHit.isStraw) ?
941 lastInsertedHit.sp()->driftRadius()/ Acts::fastHypot(dZ_slope, dR_slope) : -1.;
942 needLineUpdate = false;
943}
947
948 // Compute the residual
949 const double dZ_res {testHit.Z - anchorZ};
950 res.residual = (testHit.R - anchorR) - lineSlope * dZ_res;
951
955 const double geoFactor {1.+ Acts::square(lineSlope)};
956 const double alpha {dZ_res / dZ_slope};
957 const double propFactor {1. - alpha + Acts::square(alpha)};
958 res.accWindow = cfg->baseRWindow * std::sqrt(2*geoFactor * propFactor);
960 if (useBeamspot) res.accWindow *= 1.5;
961 if (testHit->station != lastInsertedHit.station ||
962 (testHit->station != prevLayerHit.station && testHit.globLayer == lastInsertedHit.globLayer)) {
963 res.accWindow *= 2.;
964 }
965
967 if (LR_factor > 0.) {
968 const double LRcorrection {dZ_res * geoFactor * LR_factor};
969 PRINT_VERBOSE(__func__<<"() signed residual: " << res.residual << " -> Apply LR correction: " << LRcorrection);
970 res.residual = std::min(std::abs(res.residual-LRcorrection), std::abs(res.residual+LRcorrection));
971 } else {
972 res.residual = std::abs(res.residual);
973 }
974
975 PRINT_VERBOSE(__func__<<"() "<< brief(*this) << "\nUse beamspot: " << useBeamspot << ", Slope: " << lineSlope
976 << ", Residual: " << res.residual << ", Window: " << res.accWindow << ", Geometrical Factor: "
977 << std::sqrt(geoFactor) << ", Propagation Factor: " << std::sqrt(propFactor));
978 return res;
979}
984 if (nPhiLayers) {
985 if (std::abs(CxxUtils::deltaPhi(phi, testPhi)) > cfg->phiTolerance) {
986 PRINT_VERBOSE(__func__<<"() The pattern with phi = "<<inDegrees(phi)
987 <<" is not compatible with the test hit with phi "<<inDegrees(testPhi));
988 return false;
989 }
990 } else {
991 const unsigned sector1 {expSect.msSector()};
992 const unsigned sector2 {expSect.adjacentMsSector()};
993 const bool isCompatible {sector1 == sector2
994 ? sectorMap.insideSector(sector1, testPhi)
995 : sectorMap.insideSector(sector1, testPhi) && sectorMap.insideSector(sector2, testPhi)};
996 if (!isCompatible) {
997 PRINT_VERBOSE(__func__<<"() The test hit with phi = "<<inDegrees(testPhi)
998 <<" is not inside the pattern sectors: "<<sector1<<" and "<<sector2);
999 return false;
1000 }
1001 }
1002 return true;
1003}
1005 const double residual,
1006 const double acceptWindow) {
1007
1008 if (hit.globLayer != lastInsertedHit.globLayer) {
1011
1013 nMeasurementLayers[Acts::toUnderlying(hit.station)]++;
1014 if (hit->isPrecision) nPrecisionLayers++;
1015 else nTriggerLayers++;
1017 if (hit.sp()->measuresPhi()) {
1018 if (nPhiLayers == 0) phi = hit->phi;
1019 nPhiLayers++;
1020 }
1021 }
1023 hitsPerStation[Acts::toUnderlying(hit.station)].push_back(hit);
1024 lastInsertedHit = hit;
1025
1027 if (acceptWindow > 0.) {
1028 meanNormResidual2 += Acts::square(residual / acceptWindow);
1029 lastAcceptWindow = acceptWindow;
1030 lastResidual = residual;
1031 }
1032 // If the new compatible hit is in a different station, update the line anchor
1033 if (hit.station != lastInsertedHit.station) {
1034 moveLineAnchorHit(hit);
1035 }
1036 needLineUpdate = true;
1037}
1039 const double newResidual,
1040 const double newAcceptWindow) {
1041 const StIndex st {newHit.station};
1042 if (st != lastInsertedHit.station || lastInsertedHit.globLayer != newHit.globLayer) {
1043 throw std::runtime_error(std::format(
1044 "Trying to overwrite a hit in station/layer {}/{} with another one from station/layer {}/{}",
1045 stName(lastInsertedHit.station), lastInsertedHit.globLayer, stName(st), newHit.globLayer));
1046 }
1047 /* We expect to overwrite hits of the same type (precision/trigger), since we only branch when we have
1048 * compatible hits in the same layer, except for sTGC hits, where we have pad and strips in the same layer */
1049 if (lastInsertedHit->isPrecision != newHit->isPrecision) {
1050 if (/*lastInsertedHit->isPrecision ||*/ newHit.sp()->type() != xAOD::UncalibMeasType::sTgcStripType) {
1051 std::stringstream ss {};
1052 ss << "Trying to overwrite a hit with incompatible type\n";
1053 ss << "Old hit: " << **lastInsertedHit << ", isPrecision: " << lastInsertedHit->isPrecision << ", measuresEta: " << lastInsertedHit.sp()->measuresEta() << "\n";
1054 ss << "New hit: " << **newHit << ", isPrecision: " << newHit->isPrecision << ", measuresEta: " << newHit.sp()->measuresEta();
1055 throw std::runtime_error(ss.str());
1056 }
1059 }
1061 if (lastInsertedHit.sp()->measuresPhi()) nPhiLayers--;
1062 if (newHit.sp()->measuresPhi()) {
1063 if (nPhiLayers == 0) phi = newHit->phi;
1064 nPhiLayers++;
1065 }
1067 meanNormResidual2 += Acts::square(newResidual / newAcceptWindow) - Acts::square(lastResidual / lastAcceptWindow);
1068 lastAcceptWindow = newAcceptWindow;
1069 lastResidual = newResidual;
1070
1071 auto& stHits {hitsPerStation[Acts::toUnderlying(st)]};
1072 /* Remove ALL hits in the same layer */
1073 while (!stHits.empty()) {
1074 if (stHits.back().globLayer != lastInsertedHit.globLayer) {
1075 break;
1076 }
1077 stHits.pop_back();
1078 }
1080 hitsPerStation[Acts::toUnderlying(st)].push_back(newHit);
1081 lastInsertedHit = newHit;
1082 needLineUpdate = true;
1083}
1085 const auto& hits {hitsPerStation[Acts::toUnderlying(hit.station)]};
1086 return std::ranges::find_if(hits,
1087 [&hit](const CandidateHit& c){ return *c == hit; }) != hits.end();
1088}
1091 theta = 0.;
1092 for (const std::vector<CandidateHit>& hits : hitsPerStation) {
1093 for (const auto& hit : hits) {
1094 theta += atan2(hit.R, hit.Z);
1095 }
1096 }
1097 theta /= nBendingHits();
1098}
1100 if (!nPhiLayers) {
1102 phi = sectorMap.sectorOverlapPhi(expSect.msSector(), expSect.adjacentMsSector());
1103 return;
1104 }
1105 double deltaPhiAcc {0.};
1106 std::optional<double> centralPhi {};
1107 auto processPhiHit = [&deltaPhiAcc, &centralPhi](const HitPayload& hit){
1108 if (!hit->measuresPhi()) {
1109 return;
1110 }
1111 if (!centralPhi) {
1112 centralPhi = hit.phi;
1113 }
1114 deltaPhiAcc += CxxUtils::deltaPhi(static_cast<double>(hit.phi), *centralPhi);
1115 };
1116 for (const std::vector<CandidateHit>& hits : hitsPerStation) {
1117 for (const auto& hit : hits) {
1118 processPhiHit(*hit);
1119 }
1120 }
1121 for (const HitPayload& hit : phiOnlyHits) {
1122 processPhiHit(hit);
1123 }
1124 phi = CxxUtils::wrapToPi(centralPhi.value_or(0.) + deltaPhiAcc / nPhiLayers);
1125}
1126uint8_t GlobalPatternFinder::PatternState::nStations(const bool onlyGoodStations) const {
1127 uint8_t nStations {0u};
1128 for (uint8_t st{0u}; st < s_nStations; ++st) {
1129 if (!hitsPerStation[st].empty() && (!onlyGoodStations || nMeasurementLayers[st] >= cfg->minStationLayers)) {
1130 nStations++;
1131 }
1132 }
1133 return nStations;
1134}
1136 return std::accumulate(hitsPerStation.begin(), hitsPerStation.end(), uint8_t{0u},
1137 [](uint8_t acc, const auto& hits){
1138 return acc + hits.size(); });
1139}
1144 if (isFinalized) {
1145 return meanNormResidual2;
1146 }
1148}
1150 const auto& stationHits {hitsPerStation[Acts::toUnderlying(lastInsertedHit.station)]};
1151 uint8_t nMdtLastayer {0u};
1152 for (auto it = stationHits.rbegin(); it != stationHits.rend(); ++it) {
1153 if (it->globLayer != lastInsertedHit.globLayer) {
1154 break;
1155 }
1156 nMdtLastayer++;
1157 }
1158 return nMdtLastayer;
1159}
1160std::vector<const SpacePointBucket*> GlobalPatternFinder::PatternState::getParentBuckets() const {
1161 std::vector<const SpacePointBucket*> buckets{};
1162 for (const std::vector<CandidateHit>& hits : hitsPerStation) {
1163 for (const auto& hit : hits) {
1164 if (std::ranges::find(buckets, hit->bucket) == buckets.end()) {
1165 buckets.push_back(hit->bucket);
1166 }
1167 }
1168 }
1169 return buckets;
1170}
1172 if (lastInsertedHit.globLayer != hit.globLayer) {
1173 return false;
1174 }
1175 if (hit.isStraw) {
1176 const auto& stationHits {hitsPerStation[Acts::toUnderlying(hit.station)]};
1177 for (auto it = stationHits.rbegin(); it != stationHits.rend(); ++it) {
1178 if (it->globLayer != hit.globLayer) {
1179 return false;
1180 }
1181 if (*it == hit) {
1182 return true;
1183 }
1184 }
1185 return false;
1186 }
1187 return lastInsertedHit == hit;
1188}
1190 const int nLayerDiff {a.nBendingLayers() - b.nBendingLayers()};
1191 if (std::abs(nLayerDiff) >= 3) {
1192 return nLayerDiff > 0;
1193 }
1194 return a.getMeanResidual2() < b.getMeanResidual2();
1195}
1198 const HitPayload& hit2) {
1199 auto getLayerOrdering = [](const bool isLayer1Lower) {
1200 return isLayer1Lower ? eLowerLayer : eHigherLayer;
1201 };
1202 if (hit1 == hit2) {
1203 return eSameLayer;
1204 }
1206 if (hit1->msSector() == hit2->msSector()) {
1207 if (hit1.locLayer == hit2.locLayer) {
1208 return eSameLayer;
1209 } else {
1210 return getLayerOrdering(hit1.locLayer < hit2.locLayer);
1211 }
1212 }
1213 StIndex st1 {hit1.station};
1214 StIndex st2 {hit2.station};
1216 if (st1 == st2) {
1217 return getLayerOrdering(hit1->msSector()->barrel() ? hit1.R < hit2.R : std::abs(hit1.Z) < std::abs(hit2.Z));
1218 }
1219 LayerIndex layer1 {toLayerIndex(st1)};
1220 LayerIndex layer2 {toLayerIndex(st2)};
1221 if (layer1 == layer2) {
1223 if (layer1 == LayerIndex::Middle) {
1225 return getLayerOrdering(st1 == StIndex::BM);
1226 }
1227 if (layer1 == LayerIndex::Inner) {
1229 return getLayerOrdering(hit1.R < hit2.R);
1230 }
1231 throw std::runtime_error("Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
1232 }
1233 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
1235 return getLayerOrdering(layer1 == LayerIndex::Inner);
1236 }
1237 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
1239 return getLayerOrdering(layer2 == LayerIndex::Outer);
1240 }
1241 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
1243 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
1244 }
1246 if (layer1 == LayerIndex::Extended) {
1247 return getLayerOrdering(st2 == StIndex::EM);
1248 }
1249 return getLayerOrdering(st1 == StIndex::BM);
1250}
1252 const CandidateHit& hit2) {
1253 if (!hit1.isStraw || !hit2.isStraw) {
1254 return false;
1255 }
1256 const uint16_t tubeNum1 {static_cast<const xAOD::MdtDriftCircle*>(hit1.sp()->primaryMeasurement())->driftTube()};
1257 const uint16_t tubeNum2 {static_cast<const xAOD::MdtDriftCircle*>(hit2.sp()->primaryMeasurement())->driftTube()};
1261 if(tubeNum1 == tubeNum2) {
1262 return false;
1263 };
1264 return std::abs(tubeNum1-tubeNum2) < 2;
1265 }
1268 PatternHitVisualInfoVec* visualInfo) const {
1269 if (!visualInfo) {
1270 return;
1271 }
1272 // First save the buckets
1273 std::vector<const SpacePointBucket*> buckets{cache.getParentBuckets()};
1274
1275 GlobalPattern pattern {convertToPattern(cache)};
1276 // Check whether the visual info about this pattern is already in the container
1277 if (auto it =std::ranges::find_if(*visualInfo, [&pattern](const auto& v){
1278 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
1279 it->status = status; // Update the status if the pattern is already in the container
1280 return;
1281 }
1282 visualInfo->push_back(*cache.visualInfo);
1283
1284 std::ranges::copy(buckets, std::back_inserter(visualInfo->back().parentBuckets));
1285
1286 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
1287 visualInfo->back().status = status;
1288}
1290 return hit == other.hit;
1291}
1292void GlobalPatternFinder::PatternState::print(std::ostream& ostr, bool detailed) const {
1293 ostr<<"Pattern state, Exp Sector: "<<(int)expSect.sector()<<", Theta: "<<inDegrees(theta) << ", Phi: "<<inDegrees(phi);
1294 ostr<<", nPrec: "<<(int)nPrecisionLayers<<", nEtaNonPrec: "<<(int)nTriggerLayers<<", nPhi: "<<(int)nPhiLayers;
1295 ostr<<", mean norma res sq: "<<getMeanResidual2();
1296 ostr<<", Hit per station: \n";
1297 for (uint8_t st{0u}; st < s_nStations; ++st) {
1298 const auto& hits {hitsPerStation[st]};
1299 if (hits.empty()) continue;
1300
1301 ostr<<" Station "<<Muon::MuonStationIndex::stName(static_cast<StIndex>(st))<<" has "<<hits.size()<<" hits ";
1302 if (detailed) {
1303 ostr<<"\n";
1304 for (const auto& hit : hits) {
1305 ostr<<" "<<**hit<<", R: "<<hit.R<<", Z: "<<hit.Z<<", Phi: "<<inDegrees(hit->phi)<<", loc/glob lay: "<<(int)hit->locLayer<<"/"<<(int)hit.globLayer<<"\n";
1306 }
1307 }
1308 }
1309 if (!detailed) {
1310 ostr <<"\n Last hit: "<<**lastInsertedHit<<"\n prevLayerHit: "<<**prevLayerHit << "\n lineAnchorHit: "<<**lineAnchorHit;
1311 }
1312}
1315 return {p, /*detailed=*/false};
1316}
1319 return {p, /*detailed=*/true};
1320}
1321std::ostream& operator<<(std::ostream& os, const GlobalPatternFinder::PatternPrintView& v) {
1322 v.pat.print(os, v.detailed);
1323 return os;
1324}
1325}
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)
#define PRINT_VERBOSE(MSG)
Helper macro for printing verbose messages for debugging.
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
static const Attributes_t empty
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.
double phi() const
Returns the phi angle of the expanded sector.
Amg::Vector3D normalDir() const
Returns the vector that is normal to the plane spanned by the expanded sector.
SectorProjector
Enumeration to select the sector projection of the regular MS sector.
std::int8_t sector() const
Returns the expanded sector number.
void extendPatterns(PatternStateVec &startPatterns, PatternStateVec &endPatterns, const CandidateHit &testHit, const Amg::Vector3D &beamSpot, PatternHitVisualInfoVec *visualInfo=nullptr) const
Main function controlling the development of patterns, including pattern branching when necessary.
std::vector< const SpacePointContainer * > SpacePointContainerVec
Abrivation for a vector of space-point containers.
PatternStateVec resolveOverlaps(PatternStateVec &toResolve, PatternHitVisualInfoVec *visualInfo=nullptr) const
Method to remove overlapping patterns.
static bool areConsecutiveMdt(const CandidateHit &hit1, const CandidateHit &hit2)
Helper function to check whether two hits are consecutive MDT measurements.
friend std::ostream & operator<<(std::ostream &os, const PatternPrintView &v)
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)...
Config m_cfg
Global Pattern Recognition configuration.
static PatternPrintView brief(const PatternState &p)
Print the pattern candidate and stream operator.
std::vector< GlobalPattern > PatternVec
Abrivation for a vector of global patterns.
static bool isBetter(const PatternState &a, const PatternState &b)
Method to compare two patterns and define which one is better.
LineTestDecision
: Enum for possible outcomes of pattern line compatibility test
@ eBranchPattern
Test successfull with multiple pattern hits on same layer, branch the pattern.
bool passPatternCuts(const PatternState &pat) const
Method to check if a pattern passes the quality cuts.
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.
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 global pattern building in the bending plane.
Acts::KDTree< 2, HitPayload, double, std::array, 5 > SearchTree_t
Definition of the search tree class.
static PatternPrintView detailed(const PatternState &p)
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
: The muon space point bucket represents a collection of points that will bre processed together in t...
bool measuresPhi() const
: Does the space point contain a phi measurement
bool measuresEta() const
: Does the space point contain an eta measurement
std::vector< std::string > patterns
Definition listroot.cxx:187
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 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.
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
Helper for azimuthal angle calculations.
Small wrapper for candidate hits used to build patterns.
uint8_t globLayer
Global measurement layer number.
Hit information stored during pattern building.
const SpacePoint * hit
Pointer to the underlying hit.
const SpacePoint * sp() const
Get the pointer to the underlying hit.
uint8_t locLayer
Layer number in the sector frame.
bool operator==(const HitPayload &other) const
Equal operator: it compares the underlying hit.
: Small struct to encapsulate the result of the line compatibility test
Pattern state object storing pattern information during construction.
Acts::CloneablePtr< PatternHitVisualInfo > visualInfo
Pointer to Visual Information for pattern visualization.
void moveLineAnchorHit(const CandidateHit &refHit)
Move the line anchor hit given a reference hit.
double lastResidual
Residual & acceptance window of the last inserted hit (needed when replacing a hit).
std::array< uint8_t, s_nStations > nMeasurementLayers
Counts of measurement layers per station.
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.
uint8_t nStations(const bool onlyGoodStations) const
Method returning the number of stations.
uint8_t nBendingHits() const
Return the number of hits in bending coordinate.
bool useBeamspot
Whether we used the beamspot to compute the line parameters.
double theta
Average theta & average phi of the pattern.
bool needLineUpdate
Whether we need to update the pattern line the next time we find a hit in a new layer.
void overWriteHit(const CandidateHit &newHit, const double newResidual, const double newAcceptWindow)
Overwrite the hits on the last layer with the new one.
std::vector< HitPayload > phiOnlyHits
Array holding phi-only hits.
LineTestRes checkLineComp(const CandidateHit &testHit, const Amg::Vector3D &beamSpot)
Method checking line compatibility of a test hit against the pattern.
LineTestRes computeLineResidual(const CandidateHit &testHit) const
Method to compute the residual of a test hit against the pattern line.
void finalizePatternEta()
Finalize the pattern building in eta and update its state.
std::vector< const SpacePointBucket * > getParentBuckets() const
Get the buckets associated with the pattern.
double LR_factor
Simple line model: factor for LR correction to be applied.
bool isFinalized
Flag to indicate if the pattern has been finalized.
void updateLineParameters(const Amg::Vector3D &beamSpot)
Update the line parameters based on the current hits.
uint8_t nBendingLayers() const
Return the number of layers in bending coordinate.
void finalizePatternPhi()
Finalize the pattern building in phi and update its state.
CandidateHit prevLayerHit
Pointer to the last hit in the second-to-last layer.
double anchorZ
Simple line model: anchor Z and R.
PatternState()=delete
Delete default destructor - ensure patterns are always constructed from a seed or another pattern.
bool isPhiCompatible(const double testPhi) const
Method to check the phi compatibility of a test hit with a given pattern.
double lineSlope
Simple line model: slope and associated delta Z, i.e.
std::array< std::vector< CandidateHit >, s_nStations > hitsPerStation
Map collection of hits per station.
void print(std::ostream &ostr, bool detailed) const
Print the pattern candidate.
CandidateHit lineAnchorHit
Pointer to the line anchor hit.
void addHit(const CandidateHit &hit, const double residual, const double acceptWindow)
Add a hit to the pattern and update the internal state.
bool isInLastLayer(const CandidateHit &hit) const
Check whether a given hit is in the last layer.
uint8_t nPrecisionLayers
Counts of precision / non-precision / phi layers.
CandidateHit lastInsertedHit
Pointer to the last inserted hit.
double getMeanResidual2() const
Return the mean normalized residual squared.
uint8_t nMDTLastLayer() const
Gives the number of MDT hits on the last layer.