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
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 inDeg(double angle) {
23 return angle / Gaudi::Units::deg;
24 }
25 /* Function to extract the distance between two points in the direction
26 * perpendicular to measurement layers */
27 double layerDistance(Muon::MuonStationIndex::StIndex station,
28 const Amg::Vector3D& pos1,
29 const Amg::Vector3D& pos2) {
30 return isBarrel(station) ? std::abs(pos2.perp() - pos1.perp())
31 : std::abs(pos2.z() - pos1.z());
32 }
33}
34
35namespace MuonR4::FastReco {
36using namespace Acts::UnitLiterals;
37
38GlobalPatternFinder::GlobalPatternFinder(const std::string& name, Config&& config) :
39 AthMessaging{name},
40 m_cfg{config} {
41 static_assert(std::is_move_assignable_v<PatternState>);
42 static_assert(std::is_move_constructible_v<PatternState>);
43 static_assert(std::is_copy_assignable_v<PatternState>);
44 static_assert(std::is_copy_constructible_v<PatternState>);
45 static_assert(std::is_nothrow_move_constructible_v<PatternState>);
46 static_assert(std::is_nothrow_move_assignable_v<PatternState>);
47};
48
49
52 const SpacePointContainerVec& spacepoints,
53 BucketPerContainer& outBuckets) const {
56 const SearchTree_t orderedSpacepoints {constructTree(gctx, spacepoints)};
57 auto visualInfo {m_cfg.visionTool ? std::make_unique<PatternHitVisualInfoVec>() : nullptr};
58 PatternStateVec patterns{findPatternsInEta(orderedSpacepoints, visualInfo.get())};
59
62
63 for (const PatternState& pat : patterns) {
66
68 for (const std::vector<CandidateHit>& hits : pat.hitsPerStation) {
69 for (const auto& hit : hits) {
70 if (outBuckets.find(hit->container) == outBuckets.end()) {
71 throw std::runtime_error("The space point container associated to the pattern is not present in the output bucket map.");
72 }
73 auto& outBucketVec = outBuckets[hit->container];
74 if (std::ranges::find(outBucketVec, hit->bucket) == outBucketVec.end()) {
75 outBucketVec.push_back(hit->bucket);
76 }
77 }
78 }
79 }
81 if (visualInfo) {
82 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(), "GlobPatFind_", std::move(*visualInfo));
83 }
85}
88 PatternHitVisualInfoVec* visualInfo) const {
89 constexpr auto thetaIdx {Acts::toUnderlying(SeedCoords::eTheta)};
90 constexpr auto sectorIdx {Acts::toUnderlying(SeedCoords::eSector)};
91
93 PatternStateVec startPatternBuff{};
94 startPatternBuff.reserve(20);
95 PatternStateVec endPatternBuff{};
96 endPatternBuff.reserve(20);
97
99 const Amg::Vector3D beamSpot{Amg::Vector3D::Zero()};
100
101 PatternStateVec outPatterns{};
102 outPatterns.reserve(40);
107 auto countPatterns = [this](const PatternStateVec& patterns,
108 const HitPayload& hit,
109 const SearchTree_t::coordinate_t& coords) -> uint8_t {
110 return std::ranges::count_if(patterns, [&](const PatternState& pattern){
111 const double patSeedTheta {pattern.seedHit->position.theta()};
112 if (std::abs(patSeedTheta - coords[thetaIdx]) > 2.*m_cfg.thetaSearchWindow ||
113 !pattern.expSect.isNeighbour(ExpandedSector{static_cast<std::int8_t>(coords[sectorIdx])})) {
114 return false;
115 }
116 return pattern.isInPattern(hit);
117 });
118 };
119 using enum SeedCoords;
120 for (const auto seedingLayer : m_cfg.layerSeedings) {
122 for (const auto& [seedCoords, seed] : orderedSpacepoints) {
124 const LayerIndex seedLayer {toLayerIndex(seed.station)};
125 if (seedLayer != seedingLayer || (seed.isStraw && !m_cfg.seedFromMdt)) {
126 continue;
127 }
128 ATH_MSG_VERBOSE(__func__<<"() New seed hit "<<*seed<<", coordinates "<<seedCoords);
130 uint8_t nExistingPatterns {countPatterns(outPatterns, seed, seedCoords)};
131 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
132 // Try first to resolve overlaps and re-count the number of patterns containing the seed
133 outPatterns = resolveOverlaps(outPatterns, visualInfo);
134 nExistingPatterns = countPatterns(outPatterns,seed, seedCoords);
135 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
136 ATH_MSG_VERBOSE(__func__<<"() Seed has already been used in "<<nExistingPatterns<<" patterns, which is above the limit - skip this seed.");
137 continue;
138 }
139 }
141 SearchTree_t::range_t selectRange{};
143 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
147 const double thetaHalfWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
148 ? m_cfg.thetaSearchWindow : 0.5*m_cfg.thetaSearchWindow};
149 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
151 std::vector<CandidateHit> candidateHits{};
152 orderedSpacepoints.rangeSearchMapDiscard(selectRange, [&candidateHits](const SearchTree_t::coordinate_t& /*coords*/,
153 const HitPayload& hit){
154 candidateHits.emplace_back(&hit, hit.station, 0u, hit.sector, hit.isStraw);
155 });
156 if (candidateHits.size() < m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers) {
157 ATH_MSG_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits, below minimum required - skip seed.");
158 continue;
159 }
161 if (std::ranges::none_of(candidateHits, [this, seedLayer](const CandidateHit& c){
162 return m_cfg.idHelperSvc->layerIndex(c.sp()->identify()) != seedLayer; }) ) {
163 ATH_MSG_VERBOSE(__func__<<"() All candidates in same station layer, and we need at least two - skip seed.");
164 continue;
165 }
167 std::ranges::sort(candidateHits, [](const CandidateHit& c1, const CandidateHit& c2){
168 LayerOrdering ordering {checkLayerOrdering(*c1, *c2)};
169 if (ordering == eSameLayer) {
171 return c1.sp()->localPosition().y() < c2.sp()->localPosition().y();
172 }
173 return ordering == eLowerLayer;
174 });
176 for (std::size_t i {1}; i < candidateHits.size(); ++i) {
177 candidateHits[i].globLayer = candidateHits[i - 1].globLayer +
178 (checkLayerOrdering(*candidateHits[i - 1], *candidateHits[i]) != eSameLayer);
179 }
180 if (candidateHits.back().globLayer + 1u < (m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers)) {
181 ATH_MSG_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits on "<<candidateHits.back().globLayer + 1u
182 <<" layers, below the minimum required - skip this seed.");
183 continue;
184 }
185 if (msgLvl(MSG::VERBOSE)) {
186 ATH_MSG_VERBOSE(__func__<<"() Found "<< candidateHits.size()<<" candidate hits: ");
187 for (const auto& c : candidateHits) {
188 ATH_MSG_VERBOSE(__func__<<"() \t**"<<c);
189 }
190 }
191
193 const auto seedItr {std::ranges::find_if(candidateHits,
194 [&seed](const CandidateHit& c){ return *c == seed; })};
195 assert(seedItr != candidateHits.end());
196 const CandidateHit& seedCand {*seedItr};
197
198 PatternState patternSeed{seedCand, static_cast<std::int8_t>(seedCoords[sectorIdx]), &m_cfg, this};
199 if (visualInfo) {
200 patternSeed.visualInfo = std::make_unique<PatternHitVisualInfo>(
201 seed.hit, seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
202 }
203
212 auto processHitRange = [&](const auto begin,
213 const auto end,
214 PatternState&& toExtend) -> PatternStateVec {
215 startPatternBuff.clear();
216 startPatternBuff.push_back(std::move(toExtend));
217
218 for (auto testItr = begin; testItr != end; ++testItr) {
219 const CandidateHit& testHit {*testItr};
220 if (testHit.globLayer == seedCand.globLayer && !seedCand.isStraw) {
221 continue; // skip hits on the same layer as the seed, if straw
222 }
223 extendPatterns(startPatternBuff, endPatternBuff, testHit, beamSpot, visualInfo);
224 // Swap the buffers for the next iteration
225 std::swap(startPatternBuff, endPatternBuff);
226 }
227 return startPatternBuff.size() > 1
228 ? resolveOverlaps(startPatternBuff, visualInfo)
229 : PatternStateVec{std::move(startPatternBuff.back())};
230 };
231
233 PatternStateVec forwardExtended {processHitRange(std::next(seedItr), candidateHits.end(), std::move(patternSeed))};
234
236 ATH_MSG_VERBOSE(__func__<<"() Finished forward search, found "<<forwardExtended.size()<<" forward patterns, start backward search.");
237 PatternStateVec backwardExtended{};
238 backwardExtended.reserve(2*forwardExtended.size());
239
240 for (PatternState& pat : forwardExtended) {
241 pat.moveLineAnchorHit(seedCand);
242 pat.lastInsertedHit = seedCand;
243
245 std::ranges::move(
246 processHitRange(std::reverse_iterator(seedItr), candidateHits.rend(), std::move(pat)),
247 std::back_inserter(backwardExtended)
248 );
249 }
251 if (backwardExtended.size() > 1) {
252 backwardExtended = resolveOverlaps(backwardExtended, visualInfo);
253 }
254
255 for (PatternState& pat : backwardExtended) {
256 pat.meanNormResidual2 /= pat.nBendingLayers();
257 if (!passPatternCuts(pat)) {
259 continue;
260 }
261 ATH_MSG_VERBOSE(__func__<<"() Add new pattern "<<detailed(pat));
262 pat.isFinalized = true;
263 outPatterns.push_back(std::move(pat));
264 }
265 }
266 }
267 ATH_MSG_VERBOSE(__func__<<"() Found in total "<<outPatterns.size()<<" patterns in eta before overlap removal");
268 return resolveOverlaps(outPatterns, visualInfo);
269}
271 PatternStateVec& endPatterns,
272 const CandidateHit& testHit,
273 const Amg::Vector3D& beamSpot,
274 PatternHitVisualInfoVec* visualInfo) const {
275 endPatterns.clear();
276 ATH_MSG_VERBOSE(__func__<<"() *** Test "<<testHit<<" against " << startPatterns.size() << " active patterns.");
277
278 // Compute the minimum number of missed layer hits among the active patterns,
279 // to use as reference for pruning patterns with too many missed layers.
280 std::vector<unsigned> missedLayersVec{};
281 missedLayersVec.reserve(startPatterns.size());
282 std::ranges::transform(startPatterns, std::back_inserter(missedLayersVec),
283 [&testHit](const PatternState& pat){
284 return std::abs(pat.lastInsertedHit.globLayer - testHit.globLayer);
285 });
286 const unsigned minMissedLayers {std::ranges::min(missedLayersVec)};
287
288 const bool shouldPrune {startPatterns.size() > 1 &&
289 std::ranges::any_of(startPatterns, [](const PatternState& p){
290 return p.nBendingLayers() > 2; })};
291
292 for (auto [i, pat] : Acts::enumerate(startPatterns)) {
293 if (pat.isOverlap) {
295 continue;
296 }
298 if (pat.lastInsertedHit.station == testHit.station &&
299 missedLayersVec[i] > std::max(m_cfg.maxMissLayersInStation, minMissedLayers)) {
300 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\nhas missed " << (int)missedLayersVec[i]
301 << " layer hits, above the max allowed - abort pattern.");
303 continue;
304 }
308 if (shouldPrune && pat.lastInsertedHit.globLayer != testHit.globLayer &&
309 std::ranges::find_if(std::next(startPatterns.begin(), i + 1), startPatterns.end(), [&](PatternState& p){
310 if (p.lastInsertedHit != pat.lastInsertedHit || p.isOverlap) return false;
311
312 if (isBetter(pat, p)) {
313 ATH_MSG_VERBOSE("extendPatterns() Pruning: "<<detailed(pat)<<"\nis BETTER than "<<detailed(p));
314 p.isOverlap = true;
315 return false;
316 }
317 ATH_MSG_VERBOSE("extendPatterns() Pruning: "<<detailed(p)<<"\nis BETTER than "<<detailed(pat));
318 return true; }) != startPatterns.end()) {
320 continue;
321 }
323 const auto [result, residual, accWindow] {pat.checkLineComp(testHit, beamSpot)};
324 switch (result) {
326 if (accWindow > 4.*m_cfg.baseResidualSigma && residual > m_cfg.baseResidualSigma) {
330 if (std::ranges::any_of(endPatterns, [&testHit, &pat](const PatternState& p) {
331 return p.isInLastLayer(testHit) &&
332 (p.prevLayerHit == pat.lastInsertedHit || p.nBendingLayers() > (pat.nBendingLayers() + 1u)); })) {
333 ATH_MSG_VERBOSE(__func__<<"() Low-confidence hit: forking leads to existing pattern - reject.");
334 break;
335 }
336 ATH_MSG_VERBOSE(__func__<<"() Low-confidence hit: forking leads to new pattern - fork.");
338 endPatterns.push_back(pat);
339 endPatterns.back().addHit(testHit, residual, accWindow);
341 if (visualInfo) {
342 pat.visualInfo->discardedHits.push_back(testHit.sp());
343 }
344 ATH_MSG_VERBOSE("New pattern: " << brief(endPatterns.back()));
345 break;
346 }
347 ATH_MSG_VERBOSE(__func__<<"() Hit compatible - add to pattern.");
348 pat.addHit(testHit, residual, accWindow);
349 break;
350 }
352 /* Check first if the branched pattern already exists*/
353 if (std::ranges::any_of(endPatterns, [&testHit, &pat](const PatternState& p) {
354 return p.isInLastLayer(testHit) && p.prevLayerHit == pat.prevLayerHit; })) {
355 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branched pattern already exists.");
356 break;
357 }
359 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branch pattern.");
360 endPatterns.push_back(pat);
361 endPatterns.back().overWriteHit(testHit, residual, accWindow);
362
364 if (visualInfo) {
365 pat.visualInfo->discardedHits.push_back(testHit.sp());
366 }
367 ATH_MSG_VERBOSE("New pattern: " << brief(endPatterns.back()));
368 break;
369 }
371 ATH_MSG_VERBOSE(__func__<<"() Hit is not compatible with the pattern - reject hit.");
372 if (visualInfo) {
373 pat.visualInfo->discardedHits.push_back(testHit.sp());
374 }
375 break;
376 }
378 ATH_MSG_VERBOSE(__func__<<"() Compatible MDT hits on same layer - accept.");
379 /* For consecutive MDT hits we don't add their residuals to not penalize patterns with many such hits */
380 pat.addHit(testHit, -1., -1.);
381 break;
382 }
384 ATH_MSG_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - overwrite last hit.");
385 pat.overWriteHit(testHit, residual, accWindow);
386 break;
387 }
388 }
389 endPatterns.push_back(std::move(pat));
390 }
391 startPatterns.clear();
392};
395 if (pat.nTriggerLayers < m_cfg.minTriggerLayers ||
396 pat.nPrecisionLayers < m_cfg.minPrecisionLayers ||
397 std::ranges::count_if(pat.nMeasurementLayers,
398 [this](const uint8_t nLayers) { return nLayers >= m_cfg.minStationLayers; }) < 2) {
399 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet minimum layer requirements - reject.");
400 return false;
401 }
403 if (pat.meanNormResidual2 > m_cfg.meanNormRes2Cut) {
404 ATH_MSG_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet the mean norm residual2 cut - reject.");
405 return false;
406 }
407 return true;
408}
411 PatternHitVisualInfoVec* visualInfo) const {
412 PatternStateVec outputPatterns{};
413 outputPatterns.reserve(toResolve.size());
415 auto areOverlapping = [this](const PatternState& a, const PatternState& b) {
417 if(!a.expSect.isNeighbour(b.expSect)) {
418 return false;
419 }
420 const double deltaThetaSeed {a.seedHit->position.theta() - b.seedHit->position.theta()};
421 if (std::abs(deltaThetaSeed) > 2.*m_cfg.thetaSearchWindow) {
422 return false;
423 }
424
425 if (a.nPhiLayers > 0 && b.nPhiLayers > 0) {
426 if (std::abs(P4Helpers::deltaPhi(a.patPhi, b.patPhi)) > 2.*m_cfg.phiTolerance) {
427 return false;
428 }
429 } else if (a.nPhiLayers > 0) {
430 if (!sectorMap.insideSector(b.expSect.msSector(), a.patPhi) ||
431 !sectorMap.insideSector(b.expSect.adjacentMsSector(), a.patPhi)) {
432 return false;
433 }
434 } else if (b.nPhiLayers > 0) {
435 if (!sectorMap.insideSector(a.expSect.msSector(), b.patPhi) ||
436 !sectorMap.insideSector(a.expSect.adjacentMsSector(), b.patPhi)) {
437 return false;
438 }
439 }
441 int nSharedHits{0};
442 for (std::size_t st{0u}; st < s_nStations; ++st) {
443 const auto& hitsA {a.hitsPerStation[st]};
444 const auto& hitsB {b.hitsPerStation[st]};
445 if (hitsA.empty() || hitsB.empty()) {
446 continue;
447 }
448 nSharedHits += std::ranges::count_if(hitsA, [&](const CandidateHit& hitA){
449 return std::ranges::any_of(hitsB, [&hitA](const CandidateHit& hitB) {
450 return hitA.sp()->primaryMeasurement() == hitB.sp()->primaryMeasurement();
451 });
452 });
453 }
455 const int minHits {std::min(a.nBendingHits(), b.nBendingHits())};
456 return nSharedHits >= 0.5 *minHits;
457 };
459 auto isBetterOverlap = [](const PatternState& a, const PatternState& b) {
460 const int nGoodStationDiff {a.nStations(/*onlyGoodStations=*/ true) - b.nStations(/*onlyGoodStations=*/ true)};
461 if (nGoodStationDiff != 0) {
462 return nGoodStationDiff > 0;
463 }
464 return isBetter(a,b);
465 };
466
467 for (auto it = toResolve.begin(); it != toResolve.end(); ++it) {
468 if (it->isOverlap) {
469 // If already marked as overlap, add to visual info, and discard the pattern
471 continue;
472 }
473 for (auto jt = std::next(it); jt != toResolve.end(); ++jt) {
474 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
475 continue;
476 }
477 if (isBetterOverlap(*it, *jt)) {
478 ATH_MSG_VERBOSE(__func__<<"() Pattern "<<detailed(*it)<<"\nis BETTER than "<<detailed(*jt));
479 jt->isOverlap = true;
480 } else {
481 it->isOverlap = true;
482 ATH_MSG_VERBOSE(__func__<<"() Pattern "<<detailed(*jt)<<"\nis BETTER than "<<detailed(*it));
483 break;
484 }
485 }
486 if (!it->isOverlap) {
487 outputPatterns.push_back( std::move(*it));
488 } else {
489 // If overlap, add to visual info, as the pattern will be discarded
491 }
492 }
493 ATH_MSG_VERBOSE(__func__<<"() Patterns surviving overlap removal: "<< outputPatterns.size());
494 return outputPatterns;
495}
497 PatternStateVec& patterns) const {
498 constexpr auto covIdxEta {Acts::toUnderlying(SpacePoint::CovIdx::etaCov)};
504 struct PhiStripProjectionModel {
505 StIndex station{};
506 Amg::Vector3D patPosition{Amg::Vector3D::Zero()};
507 Amg::Vector3D patDirection{Amg::Vector3D::Zero()};
508 bool isValid{false};
509
510 double residual(const Amg::Vector3D& stripPos, const Amg::Vector3D& stripDir) const {
511 return Acts::detail::LineHelper::lineIntersect<3>(
512 patPosition, patDirection, stripPos, stripDir).pathLength();
513 }
514 };
515 auto makeProjectionModel = [this](PatternState& pat, const StIndex station) {
516 PhiStripProjectionModel result{};
517 result.station = station;
518 const std::vector<CandidateHit>& stationHits {
519 pat.hitsPerStation[Acts::toUnderlying(station)]};
520 if (stationHits.empty()) return result;
521
522 const HitPayload* sp1 {nullptr};
523 const HitPayload* sp2 {nullptr};
524 if (pat.nMeasurementLayers[Acts::toUnderlying(station)] > 1) {
525 // if we have >= 2 eta hits in the station, we use the furthestmost to define the pattern line
526 const auto [minIt, maxIt] {std::ranges::minmax_element(stationHits, {},
527 [](const CandidateHit& c){ return c.globLayer; })};
528 sp1 = minIt->hit;
529 sp2 = maxIt->hit;
530 }
531 if (!sp1 || !sp2 ||
532 layerDistance(station, pat.projToPhiPlane(*sp1), pat.projToPhiPlane(*sp2)) < m_cfg.minHitDistance4Line) {
533 // if we have only one eta hit or the layer separation is too small, to find the second hit
534 // we use the functionality of anchor hit
535 pat.moveLineAnchorHit(stationHits.front());
536 sp1 = stationHits.front().hit;
537 sp2 = pat.lineAnchorHit.hit;
538 }
539 if (!sp1 || !sp2 ) return result;
540
541 Amg::Vector3D pos1 {pat.projToPhiPlane(*sp1)};
542 Amg::Vector3D pos2 {pat.projToPhiPlane(*sp2)};
543
544 result.patPosition = pos1;
545 result.patDirection = (pos2 - pos1).unit();
546 result.isValid = true;
547 return result;
548 };
549
550 PatternStateVec survivingPatterns{};
551 survivingPatterns.reserve(patterns.size());
552 for (PatternState& pat : patterns) {
554 ATH_MSG_VERBOSE(__func__<<"() Search for phi-only hits for pattern: " << brief(pat));
555
556 // Projection model of pattern line onto a given phi strip
557 std::optional<PhiStripProjectionModel> patProjOnStrip{};
558 bool stopSearch {false};
559 for (const SpacePointBucket* bucket : pat.getParentBuckets()) {
560 if (stopSearch) break;
561
562 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
563 const StIndex station {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
564 // If the projection model is not valid, we will use the pattern theta. We cache the local Y in glob frame
565 const Amg::Vector3D locY {localToGlobal.linear() * Amg::Vector3D::UnitY()};
566
567 for (const auto& hit : *bucket) {
568 if (pat.nPhiLayers >= m_cfg.minPhiLayers) {
569 stopSearch = true;
570 break;
571 }
572 // We are looking for phi-only hits
573 if (hit->measuresEta()){
574 continue;
575 }
576 ATH_MSG_VERBOSE(__func__<<"() *** Test phi-only hit "<<*hit);
577 // Check phi compatibility
578 const Amg::Vector3D locPosTest {hit->localPosition()};
579 const Amg::Vector3D globPosTest {localToGlobal * locPosTest};
580 const double globPhi {globPosTest.phi()};
581 if (!pat.isPhiCompatible(globPhi)) {
582 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit not compatible");
583 continue;
584 }
585 // Check there are not other phi hits in the same layer
586 const uint8_t layNum = m_spSorter.sectorLayerNum(*hit);
587 const std::vector<CandidateHit>& stationHits {
588 pat.hitsPerStation[Acts::toUnderlying(station)]};
589 assert(!stationHits.empty());
590 if (std::ranges::any_of(stationHits, [&](const CandidateHit& h){
591 return h.sp()->measuresPhi() && hit->msSector() == h.sp()->msSector() && layNum == h->locLayer; }) ||
592 std::ranges::any_of(pat.phiOnlyHits, [&](const HitPayload& h){
593 return station == h.station && hit->msSector() == h->msSector() && layNum == h.locLayer; })) {
594 ATH_MSG_VERBOSE(__func__<<"() The pattern already has a phi hit in the same layer - skip hit.");
595 continue;
596 }
597 // Check eta compatibility.
598 if (!patProjOnStrip.has_value() || patProjOnStrip->station != station) {
599 patProjOnStrip = makeProjectionModel(pat, station);
600 }
601 if (!patProjOnStrip->isValid) {
602 ATH_MSG_VERBOSE(__func__<<"() Invalid projection model for station "<<station<<" - skip hit.");
603 continue;
604 }
605 const Amg::Vector3D stripDir {localToGlobal.linear() * hit->sensorDirection()};
606 const double stripHalfLength {std::sqrt(hit->covariance()[covIdxEta])};
607 ATH_MSG_VERBOSE(__func__<<"() Distance pattern line from strip center: "
608 <<patProjOnStrip->residual(globPosTest, stripDir)<<", strip half-length: "<<stripHalfLength);
609
610 if (patProjOnStrip->residual(globPosTest, stripDir) > 1.1*stripHalfLength) {
611 ATH_MSG_VERBOSE(__func__<<"() The pattern falls outside the test hit strip in eta - skip hit.");
612 continue;
613 }
614 // Create the hit payload and add the hit to the pattern. Save only relevant quantities for phi-only hits.
615 ATH_MSG_VERBOSE(__func__<<"() Phi-only hit compatible - add it to the pattern.");
616 pat.phiOnlyHits.emplace_back(hit.get(), /*bucket*/nullptr, /*container*/nullptr, globPosTest, Amg::Vector3D::Zero(),
617 station, layNum, /*sector*/0u, /*isPrecision*/false, /*isStraw*/false);
618
619 if (pat.nPhiLayers == 0) pat.updatePatternPhi(globPhi);
620 pat.nPhiLayers++;
621 }
622 }
623 if (pat.nPhiLayers < m_cfg.minPhiLayers) {
624 ATH_MSG_VERBOSE(__func__<<"() Pattern "<< detailed(pat)<<" has only "<<pat.nPhiLayers
625 <<" phi layers, below the minimum required - reject this pattern.");
626 continue;
627 }
628 pat.finalizePatternPhi();
629 survivingPatterns.push_back(std::move(pat));
630 }
631 std::swap(patterns, survivingPatterns);
632}
634 GlobalPattern::HitCollection hitPerStation{};
635 GlobalPattern::BucketCollection bucketPerStation{};
637 for (uint8_t st{0u}; st < s_nStations; ++st) {
638 const auto& hits {cache.hitsPerStation[st]};
639 if (hits.empty()) continue;
640
641 auto& outHits {hitPerStation[static_cast<StIndex>(st)]};
642 outHits.reserve(hits.size());
643 auto& outBuckets {bucketPerStation[static_cast<StIndex>(st)]};
644
645 std::ranges::for_each(hits, [&outHits, &outBuckets](const CandidateHit& h){
646 outHits.push_back(h.sp());
647 if (std::ranges::find(outBuckets, h->bucket) == outBuckets.end()) {
648 outBuckets.push_back(h->bucket);
649 }
650 });
651 }
653 for (const HitPayload& hit : cache.phiOnlyHits) {
654 hitPerStation[static_cast<StIndex>(hit.station)].push_back(hit.sp());
655 }
656 GlobalPattern pattern{std::move(hitPerStation), std::move(bucketPerStation)};
657 pattern.setTheta(cache.seedHit->position.theta());
658 pattern.setPhi(cache.patPhi);
659 // Set the pattern sector(s) and theta.
660 pattern.setSector(cache.expSect.sector());
661 // Set pattern quality information.
662 pattern.setNPrecisionLayers(cache.nPrecisionLayers);
663 pattern.setNTriggerLayers(cache.nTriggerLayers);
664 pattern.setNPhiLayers(cache.nPhiLayers);
665 pattern.setMeanNormResidual2(cache.getMeanResidual2());
666 return pattern;
667}
668
672 patterns.reserve(cache.size());
673 std::transform(cache.begin(), cache.end(), std::back_inserter(patterns),
674 [this](const PatternState& cacheEntry) {
675 return convertToPattern(cacheEntry);
676 });
677 return patterns;
678}
679
682 const SpacePointContainerVec& spacepoints) const {
683 SearchTree_t::vector_t rawData{};
684 using SectorProjector = ExpandedSector::SectorProjector;
685 using enum SectorProjector;
686 // Before the loops: estimate the total number of hits
687 size_t totalHits = 0;
688 for (const SpacePointContainer* spc : spacepoints) {
689 for (const SpacePointBucket* bucket : *spc) {
690 totalHits += bucket->size();
691 }
692 }
693 // We can have up to 3 entries per hit (when the hit does not measure phi).
694 rawData.reserve(3 * totalHits);
695
696 for (const SpacePointContainer* spc : spacepoints) {
697 ATH_MSG_VERBOSE(__func__<<"() Processing "<<spc->size()<<" space point buckets...");
698 for (const SpacePointBucket* bucket : *spc) {
699 ATH_MSG_VERBOSE(__func__<<"() Processing " << bucket->size() << " spacepoints...");
700 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
701 const Acts::SquareMatrix<3> rotation {localToGlobal.linear()};
702 const StIndex bucketStation {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
703 const uint8_t sector = bucket->msSector()->sector();
704
705 for (const auto& hit : *bucket) {
706 // Ignore only-phi hits and MDT hits if desired
707 const bool isStraw {hit->isStraw()};
708 if (!hit->measuresEta() || (!m_cfg.useMdtHits && isStraw)) {
709 continue;
710 }
711 ATH_MSG_VERBOSE(__func__<<"() Spacepoint: " << *hit);
712 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
713 const Amg::Vector3D globWireDir {rotation * hit->sensorDirection()};
714 const ExpandedSector hitExpSector {globalPos.phi()};
715
718 for (const SectorProjector proj : {leftOverlap, center, rightOverlap}) {
720 const ExpandedSector expSect {sector, proj};
721 if (proj != SectorProjector::center && hit->measuresPhi() && expSect != hitExpSector) {
722 ATH_MSG_VERBOSE(__func__<<"() Hit with "<<hitExpSector<<" is not compatible with "<<expSect);
723 continue;
724 }
725
726 /* Project the hit onto the plane along the sector radial direction.
727 * This allows to remove the bias of hit displacement in phi direction */
728 const Amg::Vector3D planeNormal {expSect.normalDir()};
729 const double projR {hit->measuresPhi() ? globalPos.perp() : (globalPos - globalPos.dot(planeNormal) * planeNormal).perp()};
730
731 std::array<double, 2> coords{};
732 coords[Acts::toUnderlying(SeedCoords::eTheta)] = atan2(projR, globalPos.z());
733 coords[Acts::toUnderlying(SeedCoords::eSector)] = expSect.sector();
734
735 ATH_MSG_VERBOSE(__func__<<"() Add hit: Z: " << globalPos.z() << ", R: " << globalPos.perp()
736 <<", ProjR: " << globalPos.perp()<< ", Phi: "<< inDeg(globalPos.phi())
737 <<", SectorPhi: "<< inDeg(expSect.phi())<<" and coordinates "<<coords<<" to search tree");
738 rawData.emplace_back(std::move(coords), HitPayload{hit.get(), bucket, spc, globalPos, globWireDir, bucketStation,
739 static_cast<uint8_t>(m_spSorter.sectorLayerNum(*hit)), sector, isPrecisionHit(*hit), isStraw});
740 }
741 }
742 }
743 }
744 ATH_MSG_VERBOSE(__func__<<"() Create a new tree with "<<rawData.size()<<" entries. ");
745 return SearchTree_t{std::move(rawData)};
746}
748 const std::int8_t expSector,
749 const Config* cfg,
750 const AthMessaging* logger)
751 : cfg{cfg},
752 logger{logger},
753 lastInsertedHit{seed},
754 prevLayerHit{seed},
755 lineAnchorHit{seed},
756 seedHit{seed},
757 expSect{ExpandedSector{expSector}} {
758
760 nMeasurementLayers[Acts::toUnderlying(seed.station)]++;
761 if (seed->isPrecision) nPrecisionLayers++;
762 else nTriggerLayers++;
763 if (seed->sp()->measuresPhi()) nPhiLayers++;
764
765 updatePatternPhi(seed->position.phi());
766
768 hitsPerStation[Acts::toUnderlying(seed.station)].push_back(seed);
769 needLineUpdate = true;
770}
773 const Amg::Vector3D& beamSpot) {
774 // We test hits in same **expanded** sector, so we need just to compare hit's phi with pattern's phi, if available
775 const double phiHit {testHit->position.phi()};
776 if (nPhiLayers) {
777 const double maxPhiDiff {testHit.sp()->measuresPhi() ?
778 cfg->phiTolerance : sectorMap.sectorWidth(testHit.sector)};
779 if (std::abs(P4Helpers::deltaPhi(patPhi, phiHit)) > maxPhiDiff) {
780 PRINT_VERBOSE(__func__<<"() The pattern with phi = "<<inDeg(patPhi)
781 <<" is not compatible with the test hit with phi "<<inDeg(phiHit) << " - reject.");
782 return LineTestRes{};
783 }
784 }
785
789 auto makeResult = [&testHit, this](const LineTestDecision decision) -> LineTestRes {
791 if (res.residual < res.accWindow) {
792 res.result = decision;
793 }
794 if (visualInfo) {
795 visualInfo->hitLineInfo[testHit.sp()] =
796 std::make_pair(std::tan(lineDir.theta()), res.accWindow);
797 }
798 return res;
799 };
800
801 /*************** Test hit is on a new layer — draw line from line anchor to lastHit */
802 if(testHit.globLayer != lastInsertedHit.globLayer) {
803 updateLineParameters(beamSpot);
804 return makeResult(LineTestDecision::eAddHit);
805 }
806 /*************** Test hit is on the same layer as the last inserted hit ***************/
808 if (testHit == lastInsertedHit) {
809 PRINT_VERBOSE(__func__<<"() Test hit is the same as last inserted hit - reject.");
810 return LineTestRes{};
811 }
813 if (areConsecutiveMdt(testHit, lastInsertedHit)) {
814 return makeResult(LineTestDecision::eConsecutiveMdt);
815 }
817 if (lineAnchorHit.globLayer == lastInsertedHit.globLayer) {
818 PRINT_VERBOSE(__func__<<"() Test hit on same layer as seed with no prior hits, but not consecutive MDT hits - reject.");
819 return LineTestRes{};
820 }
822 if (testHit.sp()->primaryMeasurement() == lastInsertedHit.sp()->primaryMeasurement()) {
823 if (testHit.sp()->measuresPhi() && !lastInsertedHit.sp()->measuresPhi()) {
824 return makeResult(LineTestDecision::eOverwriteLastHit);
825 }
826 if (!testHit.sp()->measuresPhi() && lastInsertedHit.sp()->measuresPhi()) {
827 return LineTestRes{};
828 }
829 if (nPhiLayers && std::abs(P4Helpers::deltaPhi(patPhi, phiHit)) <
830 std::abs(P4Helpers::deltaPhi(patPhi, lastInsertedHit->position.phi()))) {
831 return makeResult(LineTestDecision::eOverwriteLastHit);
832 }
833 return LineTestRes{};
834 }
836 if (testHit->isPrecision != lastInsertedHit->isPrecision) {
837 if (lastInsertedHit->isPrecision) {
839 PRINT_VERBOSE(__func__<<"() Test hit is trigger hit and last inserted hit is precision, on the same layer - keep precision hit.");
840 return LineTestRes{};
841 }
843 PRINT_VERBOSE(__func__<<"() Test hit is a precision hit and last inserted hit is trigger on the same layer - check residual...");
844 return makeResult(LineTestDecision::eOverwriteLastHit);
845 }
846 return makeResult(LineTestDecision::eBranchPattern);
847}
849 // Treat first the special case where we have only one station
850 if (nStations(/*onlyGoodStations=*/ false) < 2) {
851 // If we call this method with only one station, it means that we inverted the hit search direction without
852 // finding any hit in other stations beside the initial one. So the anchor is the last added hit.
854 return;
855 }
856 // Find first the closest station to the reference station among the pattern stations
857 const auto& closestStIt = std::ranges::min_element(hitsPerStation, std::ranges::less{},
858 [&refHit](const auto& hits){
859 if (hits.empty() || hits.front().station == refHit.station) {
860 return std::numeric_limits<int>::max();
861 }
862 return std::abs(hits.front().globLayer - refHit.globLayer);
863 });
864
865 // Then find the closest hit in that station to the reference hit
866 const auto& hits {*closestStIt};
867 auto it {std::ranges::min_element(hits, std::ranges::less{},
868 [&refHit](const CandidateHit& hit){
869 return std::abs(hit.globLayer - refHit.globLayer); })};
870
871 // Find how many hits in the same layer we have, to be able to set the line anchor at the central hit
872 uint8_t nSameLayer {1u};
873 for (auto jt = std::next(it); jt != hits.end() && jt->globLayer == it->globLayer; ++jt) {
874 ++nSameLayer;
875 }
876 lineAnchorHit = *std::next(it, (nSameLayer - 1u) / 2u);
877}
879 if (!needLineUpdate) {
880 return;
881 }
882 const StIndex lastSt {lastInsertedHit.station};
885
886 // Check whether we have to use the beamspot instead of the last pattern hit to draw the line with the line anchor.
887 useBeamspot = (lastSt == lineAnchorHit.station) &&
888 (pos1 - pos2).mag() < cfg->minHitDistance4Line;
889
890 if (useBeamspot) {
891 pos1 = beamSpot;
892 }
893
894 /* Determine the (average) coordinates of the last added hit(s). If it is a straw, find
895 * the consecutive (MDT) hits on the same layer and return use average position
896 * for next computations — gives a more central reference for the line direction */
897 std::vector<CandidateHit>& hitsLastSt {hitsPerStation[Acts::toUnderlying(lastSt)]};
898 uint8_t nSameLayer {1u};
899 for (const CandidateHit& hit : hitsLastSt) {
900 if (hit == lastInsertedHit ||
901 hit.globLayer != lastInsertedHit.globLayer) {
902 continue;
903 }
904 pos2 += projToPhiPlane(*hit);
905 ++nSameLayer;
906 }
907 if (nSameLayer > 1u) {
908 pos2 = pos2 / nSameLayer;
909 }
910
911 const Amg::Vector3D d {pos2 - pos1};
912 linePos = pos1;
913 leverArm = d.mag();
914 lineDir = d / leverArm;
915 needLineUpdate = false;
916
917 PRINT_VERBOSE(__func__<<"() Update line parameters --> Pos: "<<Amg::toString(linePos)
918 <<" R/Z: "<<linePos.perp()<<"/"<<linePos.z()<<", Dir: " << Amg::toString(lineDir)
919 <<", slope: "<<std::tan(lineDir.theta())<<", LeverArm: " << leverArm);
920}
924
925 // Compute the residual
926 const Amg::Vector3D pos {projToPhiPlane(*testHit)};
927 const Amg::Vector3D K {pos - linePos};
928 res.residual = (K.cross(lineDir)).mag();
929
934 const double alpha {K.dot(lineDir) / leverArm};
935 const double varianceScale {2. * (1. - alpha + Acts::square(alpha))};
936 res.accWindow = cfg->baseResidualSigma * std::sqrt(varianceScale);
939 if (useBeamspot || testHit->station != lastInsertedHit.station ||
940 (testHit->station != prevLayerHit.station && testHit.globLayer == lastInsertedHit.globLayer)) {
941 res.accWindow *= 2.;
942 }
943 PRINT_VERBOSE(__func__<<"() "<< brief(*this)<<"\nUse beamspot: "<<useBeamspot<<", Slope: "
944 <<std::tan(lineDir.theta())<<", Residual: "<<res.residual<<", Window: "<<res.accWindow
945 <<", alpha: "<<alpha<<", Scale Factor: "<<std::sqrt(varianceScale));
946 return res;
947}
949 const Amg::Vector3D& toProject {hit.position};
950 if (hit->measuresPhi()) {
951 const double R {toProject.perp()};
952 return Amg::Vector3D{R * std::cos(patPhi), R * std::sin(patPhi), toProject.z()};
953 }
954 return Acts::PlanarHelper::intersectPlane(toProject, hit.sensorDir,
955 bendPlaneNorm, Amg::Vector3D::Zero()).position();
956}
961 if (nPhiLayers) {
962 if (std::abs(P4Helpers::deltaPhi(patPhi, testPhi)) > cfg->phiTolerance) {
963 PRINT_VERBOSE(__func__<<"() The pattern with phi = "<<inDeg(patPhi)
964 <<" is not compatible with the test hit with phi "<<inDeg(testPhi));
965 return false;
966 }
967 } else {
968 const unsigned sector1 {expSect.msSector()};
969 const unsigned sector2 {expSect.adjacentMsSector()};
970 const bool isCompatible {sector1 == sector2
971 ? sectorMap.insideSector(sector1, testPhi)
972 : sectorMap.insideSector(sector1, testPhi) && sectorMap.insideSector(sector2, testPhi)};
973 if (!isCompatible) {
974 PRINT_VERBOSE(__func__<<"() The test hit with phi = "<<inDeg(testPhi)
975 <<" is not inside the pattern sectors: "<<sector1<<" and "<<sector2);
976 return false;
977 }
978 }
979 return true;
980}
982 const double residual,
983 const double acceptWindow) {
984
985 if (hit.globLayer != lastInsertedHit.globLayer) {
988
990 nMeasurementLayers[Acts::toUnderlying(hit.station)]++;
991 if (hit->isPrecision) nPrecisionLayers++;
992 else nTriggerLayers++;
994 if (hit.sp()->measuresPhi()) {
995 if (nPhiLayers == 0) {\
996 updatePatternPhi(hit->position.phi());
997 }
998 nPhiLayers++;
999 }
1000 }
1002 hitsPerStation[Acts::toUnderlying(hit.station)].push_back(hit);
1003 lastInsertedHit = hit;
1004
1006 if (acceptWindow > 0.) {
1007 meanNormResidual2 += Acts::square(residual / acceptWindow);
1008 lastAcceptWindow = acceptWindow;
1009 lastResidual = residual;
1010 }
1011 // If the new compatible hit is in a different station, update the line anchor
1012 if (hit.station != lastInsertedHit.station) {
1013 moveLineAnchorHit(hit);
1014 }
1015 needLineUpdate = true;
1016}
1018 const double newResidual,
1019 const double newAcceptWindow) {
1020 const StIndex st {newHit.station};
1021 if (st != lastInsertedHit.station || lastInsertedHit.globLayer != newHit.globLayer) {
1022 throw std::runtime_error(std::format(
1023 "Trying to overwrite a hit in station/layer {}/{} with another one from station/layer {}/{}",
1024 stName(lastInsertedHit.station), lastInsertedHit.globLayer, stName(st), newHit.globLayer));
1025 }
1026 /* We expect to overwrite hits of the same type (precision/trigger), since we only branch when we have
1027 * compatible hits in the same layer, except for sTGC hits, where we have pad and strips in the same layer */
1028 if (lastInsertedHit->isPrecision != newHit->isPrecision) {
1029 if (newHit.sp()->type() != xAOD::UncalibMeasType::sTgcStripType) {
1030 std::stringstream ss {};
1031 ss << "Trying to overwrite a hit with incompatible type\n";
1032 ss << "Old hit: " << **lastInsertedHit << ", isPrecision: " << lastInsertedHit->isPrecision << ", measuresEta: " << lastInsertedHit.sp()->measuresEta() << "\n";
1033 ss << "New hit: " << **newHit << ", isPrecision: " << newHit->isPrecision << ", measuresEta: " << newHit.sp()->measuresEta();
1034 throw std::runtime_error(ss.str());
1035 }
1038 }
1040 if (lastInsertedHit.sp()->measuresPhi()) nPhiLayers--;
1041 if (newHit.sp()->measuresPhi()) {
1042 if (nPhiLayers == 0) {
1043 updatePatternPhi(newHit->position.phi());
1044 }
1045 nPhiLayers++;
1046 }
1048 meanNormResidual2 += Acts::square(newResidual / newAcceptWindow) - Acts::square(lastResidual / lastAcceptWindow);
1049 lastAcceptWindow = newAcceptWindow;
1050 lastResidual = newResidual;
1051
1052 auto& stHits {hitsPerStation[Acts::toUnderlying(st)]};
1053 /* Remove ALL hits in the same layer */
1054 while (!stHits.empty()) {
1055 if (stHits.back().globLayer != lastInsertedHit.globLayer) {
1056 break;
1057 }
1058 if (visualInfo) {
1059 visualInfo->replacedHits.push_back(stHits.back().sp());
1060 }
1061 stHits.pop_back();
1062 }
1064 hitsPerStation[Acts::toUnderlying(st)].push_back(newHit);
1065 lastInsertedHit = newHit;
1066 needLineUpdate = true;
1067}
1069 const auto& hits {hitsPerStation[Acts::toUnderlying(hit.station)]};
1070 return std::ranges::find_if(hits,
1071 [&hit](const CandidateHit& c){ return *c == hit; }) != hits.end();
1072}
1074 if (!nPhiLayers) {
1076 patPhi = sectorMap.sectorOverlapPhi(expSect.msSector(), expSect.adjacentMsSector());
1077 return;
1078 }
1079 double deltaPhiAcc {0.};
1080 std::optional<double> centralPhi {};
1081 auto processPhiHit = [&deltaPhiAcc, &centralPhi](const HitPayload& hit){
1082 if (!hit->measuresPhi()) {
1083 return;
1084 }
1085 const double hitPhi {hit.position.phi()};
1086 if (!centralPhi) {
1087 centralPhi = hitPhi;
1088 }
1089 deltaPhiAcc += P4Helpers::deltaPhi(hitPhi, *centralPhi);
1090 };
1091 for (const std::vector<CandidateHit>& hits : hitsPerStation) {
1092 for (const auto& hit : hits) {
1093 processPhiHit(*hit);
1094 }
1095 }
1096 for (const HitPayload& hit : phiOnlyHits) {
1097 processPhiHit(hit);
1098 }
1099 patPhi = P4Helpers::deltaPhi(centralPhi.value_or(0.) + deltaPhiAcc / nPhiLayers, 0.);
1100}
1101uint8_t GlobalPatternFinder::PatternState::nStations(const bool onlyGoodStations) const {
1102 uint8_t nStations {0u};
1103 for (uint8_t st{0u}; st < s_nStations; ++st) {
1104 if (!hitsPerStation[st].empty() && (!onlyGoodStations || nMeasurementLayers[st] >= cfg->minStationLayers)) {
1105 nStations++;
1106 }
1107 }
1108 return nStations;
1109}
1111 return std::accumulate(hitsPerStation.begin(), hitsPerStation.end(), uint8_t{0u},
1112 [](uint8_t acc, const auto& hits){
1113 return acc + hits.size(); });
1114}
1119 if (isFinalized) {
1120 return meanNormResidual2;
1121 }
1123}
1124std::vector<const SpacePointBucket*> GlobalPatternFinder::PatternState::getParentBuckets() const {
1125 std::vector<const SpacePointBucket*> buckets{};
1126 for (const std::vector<CandidateHit>& hits : hitsPerStation) {
1127 for (const auto& hit : hits) {
1128 if (std::ranges::find(buckets, hit->bucket) == buckets.end()) {
1129 buckets.push_back(hit->bucket);
1130 }
1131 }
1132 }
1133 return buckets;
1134}
1136 if (lastInsertedHit.globLayer != hit.globLayer) {
1137 return false;
1138 }
1139 if (hit.isStraw && lastInsertedHit.isStraw) {
1140 const auto& stationHits {hitsPerStation[Acts::toUnderlying(hit.station)]};
1141 for (auto it = stationHits.rbegin(); it != stationHits.rend(); ++it) {
1142 if (it->globLayer != hit.globLayer) {
1143 return false;
1144 }
1145 if (*it == hit) {
1146 return true;
1147 }
1148 }
1149 return false;
1150 }
1151 return lastInsertedHit == hit;
1152}
1154 patPhi = newPhi;
1155 bendPlaneNorm = Acts::makeDirectionFromPhiTheta(newPhi + 90._degree, 90._degree);
1156}
1158 const int nLayerDiff {a.nBendingLayers() - b.nBendingLayers()};
1159 if (std::abs(nLayerDiff) >= 3) {
1160 return nLayerDiff > 0;
1161 }
1162 return a.getMeanResidual2() < b.getMeanResidual2();
1163}
1166 const HitPayload& hit2) {
1167 auto getLayerOrdering = [](const bool isLayer1Lower) {
1168 return isLayer1Lower ? eLowerLayer : eHigherLayer;
1169 };
1170 if (hit1 == hit2) {
1171 return eSameLayer;
1172 }
1174 if (hit1->msSector() == hit2->msSector()) {
1175 if (hit1.locLayer == hit2.locLayer) {
1176 return eSameLayer;
1177 } else {
1178 return getLayerOrdering(hit1.locLayer < hit2.locLayer);
1179 }
1180 }
1181 StIndex st1 {hit1.station};
1182 StIndex st2 {hit2.station};
1184 if (st1 == st2) {
1185 return getLayerOrdering(isBarrel(st1)
1186 ? hit1.position.perp() < hit2.position.perp()
1187 : std::abs(hit1.position.z()) < std::abs(hit2.position.z()));
1188 }
1189 LayerIndex layer1 {toLayerIndex(st1)};
1190 LayerIndex layer2 {toLayerIndex(st2)};
1191 if (layer1 == layer2) {
1193 if (layer1 == LayerIndex::Middle) {
1195 return getLayerOrdering(st1 == StIndex::BM);
1196 }
1197 if (layer1 == LayerIndex::Inner) {
1199 return getLayerOrdering(hit1.position.perp() < hit2.position.perp());
1200 }
1201 throw std::runtime_error("Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
1202 }
1203 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
1205 return getLayerOrdering(layer1 == LayerIndex::Inner);
1206 }
1207 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
1209 return getLayerOrdering(layer2 == LayerIndex::Outer);
1210 }
1211 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
1213 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
1214 }
1216 if (layer1 == LayerIndex::Extended) {
1217 return getLayerOrdering(st2 == StIndex::EM);
1218 }
1219 return getLayerOrdering(st1 == StIndex::BM);
1220}
1222 const CandidateHit& hit2) {
1223 if (!hit1.isStraw || !hit2.isStraw) {
1224 return false;
1225 }
1226 const uint16_t tubeNum1 {static_cast<const xAOD::MdtDriftCircle*>(hit1.sp()->primaryMeasurement())->driftTube()};
1227 const uint16_t tubeNum2 {static_cast<const xAOD::MdtDriftCircle*>(hit2.sp()->primaryMeasurement())->driftTube()};
1231 if(tubeNum1 == tubeNum2) {
1232 return false;
1233 };
1234 return std::abs(tubeNum1-tubeNum2) < 2;
1235 }
1238 PatternHitVisualInfoVec* visualInfo) const {
1239 if (!visualInfo) {
1240 return;
1241 }
1242 // First save the buckets
1243 std::vector<const SpacePointBucket*> buckets{cache.getParentBuckets()};
1244
1245 GlobalPattern pattern {convertToPattern(cache)};
1246 // Check whether the visual info about this pattern is already in the container
1247 if (auto it =std::ranges::find_if(*visualInfo, [&pattern](const auto& v){
1248 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
1249 it->status = status; // Update the status if the pattern is already in the container
1250 return;
1251 }
1252 visualInfo->push_back(*cache.visualInfo);
1253
1254 std::ranges::copy(buckets, std::back_inserter(visualInfo->back().parentBuckets));
1255
1256 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
1257 visualInfo->back().status = status;
1258}
1260 return hit == other.hit;
1261}
1262void GlobalPatternFinder::CandidateHit::print(std::ostream& ostr) const {
1263 ostr<<**hit<<", glob Z/R/phi: "<<hit->position.z()<<" / "<<hit->position.perp()<<" / "
1264 <<inDeg(hit->position.phi())<< ", st: " << station <<", loc/glob lay: "
1265 <<static_cast<int>(hit->locLayer)<<"/"<<static_cast<int>(globLayer);
1266}
1267void GlobalPatternFinder::PatternState::print(std::ostream& ostr, bool detailed) const {
1268 ostr<<"PatternState Exp Sector: "<<static_cast<int>(expSect.sector())
1269 <<", Theta: "<<inDeg(seedHit->position.theta()) << ", Phi: "<<inDeg(patPhi);
1270 ostr<<", nPrec: "<<(int)nPrecisionLayers<<", nEtaNonPrec: "<<(int)nTriggerLayers<<", nPhi: "<<(int)nPhiLayers;
1271 ostr<<", mean norma res sq: "<<getMeanResidual2();
1272 ostr<<", Hit per station: \n";
1273 for (uint8_t st{0u}; st < s_nStations; ++st) {
1274 const auto& hits {hitsPerStation[st]};
1275 if (hits.empty()) continue;
1276
1277 ostr<<" Station "<<static_cast<StIndex>(st)<<" has "<<hits.size()<<" hits ";
1278 if (detailed) {
1279 ostr<<"\n";
1280 for (const auto& hit : hits) {
1281 ostr<<" "<<hit<<"\n";
1282 }
1283 }
1284 }
1285 if (!detailed) {
1286 ostr <<"\n Last hit: "<<lastInsertedHit<<"\n prevLayerHit: "
1287 <<prevLayerHit << "\n lineAnchorHit: "<<lineAnchorHit;
1288 }
1289}
1292 return {p, /*detailed=*/false};
1293}
1296 return {p, /*detailed=*/true};
1297}
1298std::ostream& operator<<(std::ostream& os, const GlobalPatternFinder::PatternPrintView& v) {
1299 v.pat.print(os, v.detailed);
1300 return os;
1301}
1302}
const PlainObject unit() const
This is a plugin that makes Eigen look like CLHEP & defines some convenience methods.
#define ATH_MSG_VERBOSE(x)
std::pair< std::vector< unsigned int >, bool > res
static Double_t a
static Double_t ss
#define PRINT_VERBOSE(MSG)
Helper macro for printing verbose messages for debugging.
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
constexpr float inDeg(const float rad)
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.
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
std::unordered_map< StIndex, std::vector< const SpacePointBucket * > > BucketCollection
: 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
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
constexpr auto thetaIdx
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.
constexpr float inDeg(const float rad)
StIndex
enum to classify the different station layers in the muon spectrometer
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
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
Definition P4Helpers.h:34
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
MdtDriftCircle_v1 MdtDriftCircle
Small wrapper for candidate hits used to build patterns.
uint8_t globLayer
Global measurement layer number.
const HitPayload * hit
Pointer to the underlying hit.
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.
Amg::Vector3D sensorDir
Sensor direction in global 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.
void updatePatternPhi(const double newPhi)
Helper method to update the pattern phi and bending plane normal.
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.
Amg::Vector3D projToPhiPlane(const HitPayload &hit) const
Project a certain hit position onto the bending plane where the pattern is defined.
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.
Amg::Vector3D linePos
Position and direction of the pattern line.
bool useBeamspot
Whether we used the beamspot to compute the line parameters.
double leverArm
Distance between the two points defining the pattern line.
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.
std::vector< const SpacePointBucket * > getParentBuckets() const
Get the buckets associated with the pattern.
bool isFinalized
Flag to indicate if the pattern has been finalized.
Amg::Vector3D bendPlaneNorm
Normal vector to the bending plane where the pattern lies.
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
Last hit in the second-to-last layer.
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.
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.
double patPhi
Pattern phi, which is the phi of the bending plane where the pattern lies.
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.
double getMeanResidual2() const
Return the mean normalized residual squared.