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
7
10namespace {
11 const Muon::MuonSectorMapping sectorMap{};
12
14 double inDeg(double angle) {
15 return angle / Gaudi::Units::deg;
16 }
17}
18
19namespace MuonR4::FastReco {
20using namespace Acts::UnitLiterals;
21
23 std::unique_ptr<const Acts::Logger> logger) :
24 m_cfg{config},
25 m_logger{std::move(logger)} {
26 static_assert(std::is_move_assignable_v<PatternState>);
27 static_assert(std::is_move_constructible_v<PatternState>);
28 static_assert(std::is_copy_assignable_v<PatternState>);
29 static_assert(std::is_copy_constructible_v<PatternState>);
30 static_assert(std::is_nothrow_move_constructible_v<PatternState>);
31 static_assert(std::is_nothrow_move_assignable_v<PatternState>);
32};
33
34
35std::vector<GlobalPattern>
37 std::span<const SpacePointContainer*> spacepoints) const {
41 const SearchTreeData treeData {constructTree(gctx, spacepoints)};
42
43 auto visualInfo {m_cfg.visionTool ? std::make_unique<std::vector<PatHitVisual>>() : nullptr};
44 PatternStateVec patterns{findPatternsInEta(gctx.context(), treeData.tree, visualInfo.get())};
45
48
49 for (const PatternState& pat : patterns) {
52 }
53
55 if (visualInfo) {
56 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(), "GlobPatFind_", std::move(*visualInfo));
57 }
59}
61GlobalPatternFinder::findPatternsInEta(const Acts::GeometryContext& gctx,
62 const SearchTree_t& orderedSpacepoints,
63 std::vector<PatHitVisual>* visualInfo) const {
64 constexpr auto thetaIdx {Acts::toUnderlying(SeedCoords::eTheta)};
65 constexpr auto sectorIdx {Acts::toUnderlying(SeedCoords::eSector)};
66
68 std::vector<CandidateHit> candidateHits{};
69 candidateHits.reserve(100);
70
72 PatternStateVec startPatternBuff{}, endPatternBuff{};
73 startPatternBuff.reserve(10);
74 endPatternBuff.reserve(10);
75
77 const Amg::Vector3D beamSpot{Amg::Vector3D::Zero()};
78
79 PatternStateVec outPatterns{};
80 outPatterns.reserve(10);
85 auto countPatterns = [this](const PatternStateVec& patterns,
86 const HitPayload& hit,
87 const SearchTree_t::coordinate_t& coords) -> uint8_t {
88 return std::ranges::count_if(patterns, [&](const PatternState& pattern){
89 if (std::abs(pattern.patTheta - coords[thetaIdx]) > 2.*m_cfg.thetaSearchWindow ||
90 !pattern.expSect.isNeighbour(
91 ExpandedSector{static_cast<std::int8_t>(coords[sectorIdx])})) {
92 return false;
93 }
94 return pattern.isInPattern(hit);
95 });
96 };
97 using enum SeedCoords;
98 for (const auto seedingLayer : m_cfg.layerSeedings) {
100 for (const auto& [seedCoords, seedPtr] : orderedSpacepoints) {
102 const HitPayload& seed {*seedPtr};
104 const LayerIndex seedLayer {toLayerIndex(seed.station)};
105 if (seedLayer != seedingLayer || (seed->isStraw() && !m_cfg.seedFromMdt)) {
106 continue;
107 }
108 ACTS_VERBOSE(__func__<<"() New seed hit "<<*seed<<", coordinates ["<<seedCoords[0]<<", "<<seedCoords[1]<<"]");
110 uint8_t nExistingPatterns {countPatterns(outPatterns, seed, seedCoords)};
111 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
112 // Try first to resolve overlaps and re-count the number of patterns containing the seed
113 outPatterns = resolveOverlaps(outPatterns, visualInfo);
114 nExistingPatterns = countPatterns(outPatterns,seed, seedCoords);
115 if (nExistingPatterns >= m_cfg.maxSeedAttempts) {
116 ACTS_VERBOSE(__func__<<"() Seed has already been used in "
117 <<static_cast<int>(nExistingPatterns)
118 <<" patterns, which is above the limit - skip this seed.");
119 continue;
120 }
121 }
123 SearchTree_t::range_t selectRange{};
125 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
129 const double thetaHalfWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
130 ? m_cfg.thetaSearchWindow : 0.5*m_cfg.thetaSearchWindow};
131 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
133 candidateHits.clear();
134 orderedSpacepoints.rangeSearchMapDiscard(selectRange, [&](const SearchTree_t::coordinate_t& /*coords*/,
135 const HitPayload* hit) {
136 candidateHits.emplace_back(hit, 0u);
137 });
138 if (candidateHits.size() < m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers) {
139 ACTS_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits, below minimum required - skip seed.");
140 continue;
141 }
143 if (std::ranges::none_of(candidateHits, [this, seedLayer](const CandidateHit& c){
144 return m_cfg.idHelperSvc->layerIndex(c.sp()->identify()) != seedLayer; }) ) {
145 ACTS_VERBOSE(__func__<<"() All candidates in same station layer, and we need at least two - skip seed.");
146 continue;
147 }
149 std::ranges::sort(candidateHits, [](const CandidateHit& c1, const CandidateHit& c2){
150 LayerOrdering ordering {checkLayerOrdering(*c1, *c2)};
151 if (ordering == LayerOrdering::eSameLayer) {
153 return c1.sp()->localPosition().y() < c2.sp()->localPosition().y();
154 }
155 return ordering == LayerOrdering::eLowerLayer;
156 });
158 for (std::size_t i {1}; i < candidateHits.size(); ++i) {
159 candidateHits[i].globLayer = candidateHits[i - 1].globLayer +
160 (checkLayerOrdering(*candidateHits[i - 1], *candidateHits[i]) != LayerOrdering::eSameLayer);
161 }
162 if (candidateHits.back().globLayer + 1u < (m_cfg.minTriggerLayers + m_cfg.minPrecisionLayers)) {
163 ACTS_VERBOSE(__func__<<"() Found "<<candidateHits.size()<<" candidate hits on "
164 <<static_cast<int>(candidateHits.back().globLayer + 1u)
165 <<" layers, below the minimum required - skip this seed.");
166 continue;
167 }
168 if (m_logger->level() <= Acts::Logging::Level::VERBOSE) {
169 ACTS_VERBOSE(__func__<<"() Found "<< candidateHits.size()<<" candidate hits: ");
170 for (const auto& c : candidateHits) {
171 ACTS_VERBOSE(__func__<<"() \t**"<<c);
172 }
173 }
174
176 const auto seedItr {std::ranges::find_if(candidateHits,
177 [&seed](const CandidateHit& c){ return *c == seed; })};
178 assert(seedItr != candidateHits.end());
179 const CandidateHit& seedCand {*seedItr};
180
181 PatternState patternSeed{seedCand, static_cast<std::int8_t>(seedCoords[sectorIdx]), &m_cfg, m_logger.get()};
182 if (visualInfo) {
183 patternSeed.visualInfo = std::make_unique<PatHitVisual>(
184 seed.spacePoint, seedCoords[thetaIdx] - thetaHalfWindow, seedCoords[thetaIdx] + thetaHalfWindow);
185 }
186
195 auto processHitRange = [&](const auto begin,
196 const auto end,
197 PatternState&& toExtend) -> PatternStateVec {
198 startPatternBuff.clear();
199 startPatternBuff.push_back(std::move(toExtend));
200
201 for (auto testItr = begin; testItr != end; ++testItr) {
202 const CandidateHit& testHit {*testItr};
203 if (testHit.globLayer == seedCand.globLayer) {
204 continue; // skip hits on the same layer as the seed
205 }
206 extendPatterns(gctx, startPatternBuff, endPatternBuff, testHit, beamSpot, visualInfo);
207 // Swap the buffers for the next iteration
208 std::swap(startPatternBuff, endPatternBuff);
209 }
210 return startPatternBuff.size() > 1
211 ? resolveOverlaps(startPatternBuff, visualInfo)
212 : PatternStateVec{std::move(startPatternBuff.back())};
213 };
214
216 PatternStateVec forwardExtended {processHitRange(std::next(seedItr), candidateHits.end(), std::move(patternSeed))};
217
219 ACTS_VERBOSE(__func__<<"() Finished forward search, found "<<forwardExtended.size()<<" forward patterns, start backward search.");
220 PatternStateVec backwardExtended{};
221 backwardExtended.reserve(2*forwardExtended.size());
222
223 for (PatternState& pat : forwardExtended) {
224 ACTS_VERBOSE(__func__<<"() Start backward search for pattern "<<detailed(pat));
225 pat.moveLineAnchorHit(seedCand);
226 pat.lastInsertedHit = seedCand;
227
229 std::ranges::move(
230 processHitRange(std::reverse_iterator(seedItr), candidateHits.rend(), std::move(pat)),
231 std::back_inserter(backwardExtended)
232 );
233 }
235 if (backwardExtended.size() > 1) {
236 backwardExtended = resolveOverlaps(backwardExtended, visualInfo);
237 }
238
239 for (PatternState& pat : backwardExtended) {
240 pat.meanNormResidual2 /= pat.nBendingLayers();
241 if (!passPatternCuts(pat)) {
243 continue;
244 }
245 ACTS_VERBOSE(__func__<<"() Add new pattern "<<detailed(pat));
246 pat.isFinalized = true;
247 outPatterns.push_back(std::move(pat));
248 }
249 }
250 }
251 ACTS_VERBOSE(__func__<<"() Found in total "<<outPatterns.size()<<" patterns in eta before overlap removal");
252 return resolveOverlaps(outPatterns, visualInfo);
253}
254void GlobalPatternFinder::extendPatterns(const Acts::GeometryContext& gctx,
255 PatternStateVec& startPatterns,
256 PatternStateVec& endPatterns,
257 const CandidateHit& testHit,
258 const Amg::Vector3D& beamSpot,
259 std::vector<PatHitVisual>* visualInfo) const {
260 endPatterns.clear();
261 ACTS_VERBOSE(__func__<<"() *** Test "<<testHit<<" against " << startPatterns.size() << " active patterns.");
262
263 // Compute the minimum number of missed layer hits among the active patterns,
264 // to use as reference for pruning patterns with too many missed layers.
265 auto missedLayers = [&testHit](const PatternState& pat) -> unsigned {
266 return std::abs(pat.lastInsertedHit.globLayer - testHit.globLayer);
267 };
268
269 unsigned minMissedLayers {std::numeric_limits<unsigned>::max()};
270 std::ranges::for_each(startPatterns, [&missedLayers, &minMissedLayers](const PatternState& pat){
271 minMissedLayers = std::min(minMissedLayers, missedLayers(pat));
272 });
273
274 const bool shouldPrune {startPatterns.size() > 1 &&
275 std::ranges::any_of(startPatterns, [](const PatternState& p){
276 return p.nBendingLayers() > 2; })};
277
278 for (auto [i, pat] : Acts::enumerate(startPatterns)) {
279 if (pat.isOverlap) {
281 continue;
282 }
284 if (pat.lastInsertedHit->station == testHit->station &&
285 missedLayers(pat) > std::max(m_cfg.maxMissLayersInStation, minMissedLayers)) {
286 ACTS_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\nhas missed " << (int)missedLayers(pat)
287 << " layer hits, above the max allowed - abort pattern.");
289 continue;
290 }
294 if (shouldPrune && pat.lastInsertedHit.globLayer != testHit.globLayer &&
295 std::ranges::find_if(std::next(startPatterns.begin(), i + 1), startPatterns.end(), [&](PatternState& p){
296 if (p.lastInsertedHit != pat.lastInsertedHit || p.isOverlap) return false;
297
298 if (isBetter(pat, p)) {
299 ACTS_VERBOSE("extendPatterns() Pruning: "<<detailed(pat)<<"\nis BETTER than "<<detailed(p));
300 p.isOverlap = true;
301 return false;
302 }
303 ACTS_VERBOSE("extendPatterns() Pruning: "<<detailed(p)<<"\nis BETTER than "<<detailed(pat));
304 return true; }) != startPatterns.end()) {
306 continue;
307 }
309 const auto [residual, resSigma, result] {pat.checkLineComp(gctx,testHit, beamSpot)};
310 switch (result) {
313 const bool lowConfidenceRes {resSigma > m_cfg.lowConfidenceResSigma &&
314 residual / resSigma > 2.};
315 if (lowConfidenceRes) {
316 ACTS_VERBOSE(__func__<<"() Low-confidence hit: residual pull "<<residual / resSigma);
320 if (std::ranges::any_of(endPatterns, [&testHit, &pat](const PatternState& p) {
321 return p.lastInsertedHit == testHit &&
322 (p.prevLayerHit == pat.lastInsertedHit || p.nBendingLayers() > (pat.nBendingLayers() + 1u)); })) {
323 ACTS_VERBOSE(__func__<<"() Forking leads to existing pattern - reject.");
324 break;
325 }
327 endPatterns.push_back(pat);
328 endPatterns.back().addHit(testHit, residual, resSigma);
330 if (visualInfo) {
331 pat.visualInfo->discardedHits.push_back(testHit.sp());
332 }
333 break;
334 }
335 ACTS_VERBOSE(__func__<<"() Hit compatible - add to pattern. Residual pull "<<residual / resSigma);
336 pat.addHit(testHit, residual, resSigma);
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.lastInsertedHit == testHit && p.prevLayerHit == pat.prevLayerHit; })) {
343 ACTS_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branched pattern already exists.");
344 break;
345 }
347 ACTS_VERBOSE(__func__<<"() Hit compatible & on same layer of last added hit - branch pattern.");
348 endPatterns.push_back(pat);
349 endPatterns.back().overWriteHit(testHit, residual, resSigma);
350
352 if (visualInfo) {
353 pat.visualInfo->discardedHits.push_back(testHit.sp());
354 }
355 break;
356 }
358 ACTS_VERBOSE(__func__<<"() Hit is not compatible with the pattern - reject hit.");
359 if (visualInfo) {
360 pat.visualInfo->discardedHits.push_back(testHit.sp());
361 }
362 break;
363 }
364 }
365 endPatterns.push_back(std::move(pat));
366 }
367 startPatterns.clear();
368};
371 if (pat.nTriggerLayers < m_cfg.minTriggerLayers ||
372 pat.nPrecisionLayers < m_cfg.minPrecisionLayers ||
373 std::ranges::count_if(pat.hitsPerStation,
374 [this](const auto& hits) { return hits.size() >= m_cfg.minStationLayers; }) < 2) {
375 ACTS_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet minimum layer requirements - reject.");
376 return false;
377 }
379 if (pat.meanNormResidual2 > m_cfg.meanNormRes2Cut) {
380 ACTS_VERBOSE(__func__<<"() Pattern " << detailed(pat) << "\ndoes not meet the mean norm residual2 cut - reject.");
381 return false;
382 }
383 return true;
384}
387 std::vector<PatHitVisual>* visualInfo) const {
388 ACTS_VERBOSE(__func__<<"() Resolving overlaps among "<<toResolve.size()<<" patterns.");
389 PatternStateVec outputPatterns{};
390 outputPatterns.reserve(toResolve.size());
392 auto areOverlapping = [this](const PatternState& a, const PatternState& b) {
394 if(!a.expSect.isNeighbour(b.expSect)) {
395 return false;
396 }
398 if (std::abs(a.patTheta - b.patTheta) > 2.*m_cfg.thetaSearchWindow) {
399 return false;
400 }
401 if (a.nPhiLayers > 0 && b.nPhiLayers > 0) {
402 if (std::abs(P4Helpers::deltaPhi(a.patPhi, b.patPhi)) > 5.*Gaudi::Units::deg) {
403 return false;
404 }
405 } else if (a.nPhiLayers > 0) {
406 if (!sectorMap.insideSector(b.expSect.msSector(), a.patPhi) ||
407 !sectorMap.insideSector(b.expSect.adjacentMsSector(), a.patPhi)) {
408 return false;
409 }
410 } else if (b.nPhiLayers > 0) {
411 if (!sectorMap.insideSector(a.expSect.msSector(), b.patPhi) ||
412 !sectorMap.insideSector(a.expSect.adjacentMsSector(), b.patPhi)) {
413 return false;
414 }
415 }
417 std::size_t nSharedHits{0}, nSharedStations{0};
418 for (std::size_t st{0u}; st < s_nStations; ++st) {
419 const auto& hitsA {a.hitsPerStation[st]};
420 const auto& hitsB {b.hitsPerStation[st]};
421 if (hitsA.empty() || hitsB.empty()) {
422 continue;
423 }
424 const std::size_t nSharedInStation = std::ranges::count_if(hitsA, [&](const CandidateHit& hitA){
425 return std::ranges::any_of(hitsB, [&hitA](const CandidateHit& hitB) {
426 return hitA.sp()->primaryMeasurement() == hitB.sp()->primaryMeasurement();
427 });
428 });
429 nSharedHits += nSharedInStation;
430 if (nSharedInStation >= m_cfg.minStationLayers) {
431 nSharedStations++;
432 }
433 }
435 const std::size_t minHits {std::min(a.nBendingLayers(), b.nBendingLayers())};
436 const std::size_t minStations {std::min(a.nStations(/*onlyGoodStations=*/ true), b.nStations(/*onlyGoodStations=*/ true))};
437 return nSharedHits >= 0.5 *minHits && nSharedStations >= std::min(2ul, minStations);
438 };
440 auto isBetterOverlap = [](const PatternState& a, const PatternState& b) {
441 const int nGoodStationDiff {a.nStations(/*onlyGoodStations=*/ true) - b.nStations(/*onlyGoodStations=*/ true)};
442 if (nGoodStationDiff != 0) {
443 return nGoodStationDiff > 0;
444 }
445 return isBetter(a,b);
446 };
447
448 for (auto it = toResolve.begin(); it != toResolve.end(); ++it) {
449 if (it->isOverlap) {
450 // If already marked as overlap, add to visual info, and discard the pattern
452 continue;
453 }
454 for (auto jt = std::next(it); jt != toResolve.end(); ++jt) {
455 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
456 continue;
457 }
458 if (isBetterOverlap(*it, *jt)) {
459 ACTS_VERBOSE(__func__<<"() Pattern "<<detailed(*it)<<"\nis BETTER than "<<detailed(*jt));
460 jt->isOverlap = true;
461 } else {
462 it->isOverlap = true;
463 ACTS_VERBOSE(__func__<<"() Pattern "<<detailed(*jt)<<"\nis BETTER than "<<detailed(*it));
464 break;
465 }
466 }
467 if (!it->isOverlap) {
468 outputPatterns.push_back( std::move(*it));
469 } else {
470 // If overlap, add to visual info, as the pattern will be discarded
472 }
473 }
474 ACTS_VERBOSE(__func__<<"() Patterns surviving overlap removal: "<< outputPatterns.size());
475 return outputPatterns;
476}
478 PatternStateVec& patterns) const {
479 auto computePatternLineInStation = [&](PatternState& pat,
480 const StIndex station) -> bool {
481 const std::vector<CandidateHit>& stationHits {
482 pat.hitsPerStation[Acts::toUnderlying(station)]};
483 if(stationHits.empty()) {
484 return false;
485 }
486
488 pat.useBeamspot = true;
489
490 if (stationHits.size() > 1) {
491 // if we have >= 2 eta hits in the station, we use the furthestmost to define the pattern line
492 const auto [minIt, maxIt] {std::ranges::minmax_element(stationHits, {},
493 [](const CandidateHit& c){ return c.globLayer; })};
494 pat.lineAnchorHit = *minIt;
495 pat.lastInsertedHit = *maxIt;
496 pat.updateLineParameters(gctx.context(), Amg::Vector3D::Zero());
497 }
498 if (pat.useBeamspot) {
499 // if we have only one eta hit or the layer separation is too small, to find the second hit
500 // we use the functionality of anchor hit
501 pat.moveLineAnchorHit(stationHits.front());
502 pat.lastInsertedHit = *std::ranges::max_element(stationHits, {},
503 [&](const CandidateHit& c){
504 return (pat.projToPhiPlane(gctx.context(), *c) -
505 pat.projToPhiPlane(gctx.context(), *pat.lineAnchorHit)).mag(); });
506 pat.updateLineParameters(gctx.context(), Amg::Vector3D::Zero());
507 }
508 return !pat.useBeamspot;
509 };
510
511 PatternStateVec survivingPatterns{};
512 survivingPatterns.reserve(patterns.size());
513 for (PatternState& pat : patterns) {
515 ACTS_VERBOSE(__func__<<"() Search for phi-only hits for pattern: " << brief(pat));
516
517 std::optional<StIndex> patterLineStation{std::nullopt};
518
519 auto projOntoPhiPlane = [&pat](const Amg::Vector3D& pos) -> Amg::Vector3D {
520 return pos - pos.dot(pat.bendPlaneNorm) * pat.bendPlaneNorm;
521 };
522
523 for (const SpacePointBucket* bucket : pat.getParentBuckets()) {
524
525 const Acts::Transform3& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
526 const StIndex station {m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
527
528 for (const auto& hit : *bucket) {
529 // We are looking for phi-only hits
530 if (hit->measuresEta()){
531 continue;
532 }
533 ACTS_VERBOSE(__func__<<"() *** Test phi-only hit "<<*hit);
534 HitPayload newHit {gctx.context(), hit.get(), bucket, localToGlobal};
535
537 const std::vector<CandidateHit>& stationHits {
538 pat.hitsPerStation[Acts::toUnderlying(station)]};
539 assert(!stationHits.empty());
540 if (std::ranges::any_of(stationHits, [&](const CandidateHit& h){
541 return h.sp()->measuresPhi() &&
542 hit->msSector() == h.sp()->msSector() &&
543 newHit.locLayer == h->locLayer;
544 }) ||
545 std::ranges::any_of(pat.phiOnlyHits, [&](const HitPayload& h){
546 return station == h.station &&
547 hit->msSector() == h->msSector() &&
548 newHit.locLayer == h.locLayer;
549 })) {
550 ACTS_VERBOSE(__func__<<"() The pattern already has a phi hit in the same layer - skip hit.");
551 continue;
552 }
553
554 if (!pat.isPhiCompatible(newHit)) {
555 ACTS_VERBOSE(__func__<<"() Phi-only hit not compatible");
556 continue;
557 }
558
559 if (!patterLineStation || *patterLineStation != station) {
560 if (!computePatternLineInStation(pat, station)) {
561 ACTS_VERBOSE(__func__<<"() Invalid projection model for station "<<station<<" - skip hit.");
562 continue;
563 }
564 patterLineStation = station;
565 }
566
567 const double stripHalfLength {
568 std::sqrt(hit->covariance()[Acts::toUnderlying(SpacePoint::CovIdx::etaCov)])};
569 const Amg::Vector3D sensorDir {newHit.sensorDir(gctx.context())};
570 const Amg::Vector3D stripLow {
571 projOntoPhiPlane(newHit.position - stripHalfLength * sensorDir)};
572 const Amg::Vector3D stripHigh {
573 projOntoPhiPlane(newHit.position + stripHalfLength * sensorDir)};
574 const Amg::Vector3D stripDirOnPlane {(stripHigh - stripLow).unit()};
575
576 const double stripIntersect {Acts::detail::LineHelper::lineIntersect<3>(
577 pat.linePos, pat.lineDir, stripLow, stripDirOnPlane).pathLength()};
578 const double stripProjLength {(stripHigh - stripLow).mag()};
579
580 ACTS_VERBOSE(__func__<<"() Intersect distance from lower strip edge: "
581 <<stripIntersect<<", proj strip length: "<< (stripHigh - stripLow).mag());
582
583 constexpr double margin {10 * Gaudi::Units::mm};
584 if (stripIntersect < -margin || stripIntersect > (stripProjLength + margin)) {
585 ACTS_VERBOSE(__func__<<"() The pattern falls outside the test hit strip in eta - skip hit.");
586 continue;
587 }
588 pat.phiOnlyHits.push_back(std::move(newHit));
589 pat.nPhiLayers++;
590 pat.updatePatternPhi();
591 }
592 }
593 if (pat.nPhiLayers < m_cfg.minPhiLayers) {
594 ACTS_VERBOSE(__func__<<"() Pattern "<<detailed(pat)
595 <<" has only "<<static_cast<int>(pat.nPhiLayers)
596 <<" phi layers, below the minimum required - reject this pattern.");
597 continue;
598 }
599 survivingPatterns.push_back(std::move(pat));
600 }
601 std::swap(patterns, survivingPatterns);
602}
604 GlobalPattern::HitCollection hitPerStation{};
605 std::vector<const SpacePointBucket*> parentBuckets{};
607 for (uint8_t st{0u}; st < s_nStations; ++st) {
608 const auto& hits {cache.hitsPerStation[st]};
609 if (hits.empty()) continue;
610
611 auto& outHits {hitPerStation[static_cast<StIndex>(st)]};
612 outHits.reserve(hits.size());
613
614 std::ranges::for_each(hits, [&outHits, &parentBuckets](const CandidateHit& h){
615 outHits.push_back(h.sp());
616 if (std::ranges::find(parentBuckets, h->bucket) == parentBuckets.end()) {
617 parentBuckets.push_back(h->bucket);
618 }
619 });
620 }
621
623 for (const HitPayload& hit : cache.phiOnlyHits) {
624 hitPerStation[static_cast<StIndex>(hit.station)].push_back(hit.spacePoint);
625 }
626 GlobalPattern pattern{std::move(hitPerStation), std::move(parentBuckets)};
627 pattern.setTheta(cache.patTheta);
628 pattern.setPhi(cache.patPhi);
629 // Set the pattern sector(s) and theta.
630 pattern.setSector(cache.expSect.sector());
631 // Set pattern quality information.
632 pattern.setNPrecisionLayers(cache.nPrecisionLayers);
633 pattern.setNTriggerLayers(cache.nTriggerLayers);
634 pattern.setNPhiLayers(cache.nPhiLayers);
635 pattern.setMeanNormResidual2(cache.getMeanResidual2());
636 return pattern;
637}
638
639std::vector<GlobalPattern>
641 std::vector<GlobalPattern> patterns{};
642 patterns.reserve(cache.size());
643 std::transform(cache.begin(), cache.end(), std::back_inserter(patterns),
644 [this](const PatternState& cacheEntry) {
645 return convertToPattern(cacheEntry);
646 });
647 return patterns;
648}
649
652 std::span<const SpacePointContainer*> spacepoints) const {
653
654 std::vector<HitPayload> hitPayloads{};
655 using SectorProjector = ExpandedSector::SectorProjector;
656 using enum SectorProjector;
658 size_t totalHits = 0;
659 for (const SpacePointContainer* spc : spacepoints) {
660 for (const SpacePointBucket* bucket : *spc) {
661 totalHits += bucket->size();
662 }
663 }
664 hitPayloads.reserve(totalHits);
665
666 for (const SpacePointContainer* spc : spacepoints) {
667 for (const SpacePointBucket* bucket : *spc) {
668
669 const Acts::Transform3& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
670
671 for (const auto& hit : *bucket) {
672 // Ignore only-phi hits and MDT hits if desired
673 if (!hit->measuresEta() || (!m_cfg.useMdtHits && hit->isStraw())) {
674 continue;
675 }
676
677 hitPayloads.emplace_back(gctx.context(), hit.get(), bucket, localToGlobal);
678
679 if (m_logger->level() <= Acts::Logging::Level::VERBOSE) {
680 const HitPayload& newHit {hitPayloads.back()};
681 std::ostringstream oss{};
682 oss<<__func__<<"() Building hit from "<<*hit<<std::endl<<"PhiCov: "<<newHit.phiCov
683 <<", Pos: "<<Amg::toString(newHit.position)<<", SensorDir: "<<Amg::toString(newHit.sensorDir(gctx.context()));
684 if (newHit->isStraw()) {
685 oss<<", discCov: "<<newHit.stripAngle;
686 } else {
687 oss<<"orthogonalStrips: "<<!newHit.nonOrthogonalStrips;
688 }
689 ACTS_VERBOSE(oss.str());
690 }
691 }
692 }
693 }
694
695 SearchTree_t::vector_t treeData{};
696 treeData.reserve(3 * hitPayloads.size());
697
698 uint8_t msSector = hitPayloads.front()->msSector()->sector();
699 const SpacePointBucket* currentBucket = hitPayloads.front().bucket;
700 for (const HitPayload& hit : hitPayloads) {
701 ACTS_VERBOSE(__func__<<"() Spacepoint: " << *hit);
702 const Amg::Vector3D& pos {hit.position};
703 const ExpandedSector hitExpSector {pos.phi()};
704 if (hit.bucket != currentBucket) {
705 currentBucket = hit.bucket;
706 msSector = hit->msSector()->sector();
707 }
708
711 for (const SectorProjector proj : {leftOverlap, center, rightOverlap}) {
713 const ExpandedSector expSect {msSector, proj};
714 if (proj != SectorProjector::center && hit->measuresPhi() && expSect != hitExpSector) {
715 ACTS_VERBOSE("addHitToTree() Hit with "<<hitExpSector<<" is not compatible with "<<expSect);
716 continue;
717 }
718
719 /* Project the hit onto the plane along the sector radial direction.
720 * This allows to remove the bias of hit displacement in phi direction */
721 const Amg::Vector3D planeNormal {expSect.normalDir()};
722 const double projR {hit->measuresPhi()
723 ? pos.perp()
724 : (pos - pos.dot(planeNormal) * planeNormal).perp()};
725
726 std::array<double, 2> coords{};
727 coords[Acts::toUnderlying(SeedCoords::eTheta)] = atan2(projR, pos.z());
728 coords[Acts::toUnderlying(SeedCoords::eSector)] = expSect.sector();
729
730 ACTS_VERBOSE("addHitToTree() Add hit: Z: " << pos.z() << ", R: " << pos.perp()
731 <<", ProjR: "<<projR<< ", Phi: "<< inDeg(pos.phi())
732 <<", SectorPhi: "<< inDeg(expSect.phi())<<" and coordinates ["
733 <<coords[0]<<", "<<coords[1]<<"] to search tree");
734 treeData.emplace_back(std::move(coords), &hit);
735 }
736 }
737 ACTS_VERBOSE(__func__<<"() Create a new tree with "<<treeData.size()
738 <<" entries and "<<hitPayloads.size()<<" hits.");
739 return SearchTreeData{std::move(hitPayloads), SearchTree_t{std::move(treeData)}};
740}
742 const double resA {a.getMeanResidual2()};
743 const double resB {b.getMeanResidual2()};
744 const double resDiff {
745 std::abs(resA - resB) / std::max(resA, resB)
746 };
747 const int nLayerDiff {a.nBendingLayers() - b.nBendingLayers()};
748 const int nPrecLayDiff {a.nPrecisionLayers - b.nPrecisionLayers};
749
753 if ((nLayerDiff == 0 && nPrecLayDiff == 0) ||
754 (std::abs(nLayerDiff) < 3 && resDiff > 0.1)) {
755 return resA < resB;
756 }
757 if (nLayerDiff == 0) {
758 return nPrecLayDiff > 0;
759 }
760 return nLayerDiff > 0;
761}
764 const HitPayload& hit2) {
765 using enum LayerOrdering;
766 auto getLayerOrdering = [](const bool isLayer1Lower) {
767 return isLayer1Lower ? eLowerLayer : eHigherLayer;
768 };
769 if (hit1 == hit2) {
770 return eSameLayer;
771 }
772 StIndex st1 {hit1.station};
773 StIndex st2 {hit2.station};
774 if (st1 == st2) {
776 if (hit1->msSector() == hit2->msSector()) {
777 if (hit1.locLayer == hit2.locLayer) {
778 return eSameLayer;
779 }
780 return getLayerOrdering(hit1.locLayer < hit2.locLayer);
781 }
784 const double delta {isBarrel(st1)
785 ? hit1.position.perp() - hit2.position.perp()
786 : std::abs(hit1.position.z()) - std::abs(hit2.position.z())};
787 if (std::abs(delta) <= Acts::s_epsilon) {
788 return eSameLayer;
789 }
790 return getLayerOrdering(delta < 0.);
791 }
792 LayerIndex layer1 {toLayerIndex(st1)};
793 LayerIndex layer2 {toLayerIndex(st2)};
794 if (layer1 == layer2) {
796 if (layer1 == LayerIndex::Middle) {
798 return getLayerOrdering(st1 == StIndex::BM);
799 }
800 if (layer1 == LayerIndex::Inner) {
802 return getLayerOrdering(hit1.position.perp() < hit2.position.perp());
803 }
804 throw std::runtime_error("Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
805 }
806 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
808 return getLayerOrdering(layer1 == LayerIndex::Inner);
809 }
810 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
812 return getLayerOrdering(layer2 == LayerIndex::Outer);
813 }
814 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
816 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
817 }
819 if (layer1 == LayerIndex::Extended) {
820 return getLayerOrdering(st2 == StIndex::EM);
821 }
822 return getLayerOrdering(st1 == StIndex::BM);
823}
826 std::vector<PatHitVisual>* visualInfo) const {
827 if (!visualInfo) {
828 return;
829 }
830 // First save the buckets
831 std::vector<const SpacePointBucket*> buckets{cache.getParentBuckets()};
832
833 GlobalPattern pattern {convertToPattern(cache)};
834 // Check whether the visual info about this pattern is already in the container
835 if (auto it =std::ranges::find_if(*visualInfo, [&pattern](const auto& v){
836 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
837 it->status = status; // Update the status if the pattern is already in the container
838 return;
839 }
840 visualInfo->push_back(*cache.visualInfo);
841
842 std::ranges::copy(buckets, std::back_inserter(visualInfo->back().parentBuckets));
843
844 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
845 visualInfo->back().status = status;
846}
847}
Scalar mag() const
mag method
detray::unit< scalar_t > unit
bool hit(const Container &ids, int pdgId)
static Double_t a
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
constexpr float inDeg(const float rad)
Acts::GeometryContext context() const
Header file for AthHistogramAlgorithm.
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.
static LayerOrdering checkLayerOrdering(const HitPayload &hit1, const HitPayload &hit2)
Method to check the logical layer ordering of two hits.
Acts::KDTree< 2, const HitPayload *, double, std::array, 50 > SearchTree_t
Definition of the search tree class.
Config m_cfg
Global Pattern Recognition configuration.
static PatternPrintView brief(const PatternState &p)
Print the pattern state with brief information.
PatternStateVec resolveOverlaps(PatternStateVec &toResolve, std::vector< PatHitVisual > *visualInfo=nullptr) const
Method to remove overlapping patterns.
static bool isBetter(const PatternState &a, const PatternState &b)
Method to compare two patterns and define which one is better.
std::unique_ptr< const Acts::Logger > m_logger
Logger for the Global Pattern Finder.
void addVisualInfo(const PatternState &candidate, PatHitVisual::PatternStatus status, std::vector< PatHitVisual > *visualInfo) const
Helper function to add visual information of a given pattern (which is usually going to be destroyed)...
bool passPatternCuts(const PatternState &pat) const
Method to check if a pattern passes the quality cuts.
SearchTreeData constructTree(const ActsTrk::GeometryContext &gctx, std::span< const SpacePointContainer * > spacepoints) const
Construct the search tree from the given spacepoint containers.
GlobalPatternFinder(Config &&config, std::unique_ptr< const Acts::Logger > logger=Acts::getDefaultLogger("GlobalPatternFinder", Acts::Logging::Level::INFO))
Standard constructor.
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.
std::vector< PatternState > PatternStateVec
Type alias for a vector of pattern states.
PatternStateVec findPatternsInEta(const Acts::GeometryContext &gctx, const SearchTree_t &orderedSpacepoints, std::vector< PatHitVisual > *visualInfo=nullptr) const
Method steering the global pattern building in the bending plane.
static const int s_nStations
Number of stations.
SeedCoords
Abrivation of the seed coordinates.
@ eSector
Expanded sector coordinate of the associated spectrometer sector
void extendPatterns(const Acts::GeometryContext &gctx, PatternStateVec &startPatterns, PatternStateVec &endPatterns, const CandidateHit &testHit, const Amg::Vector3D &beamSpot, std::vector< PatHitVisual > *visualInfo=nullptr) const
Main function controlling the development of patterns, including pattern branching when necessary.
GlobalPattern convertToPattern(const PatternState &candidate) const
Method to convert a PatternState into a GlobalPattern object.
std::vector< GlobalPattern > findPatterns(const ActsTrk::GeometryContext &gctx, std::span< const SpacePointContainer * > spacepoints) const
Main methods steering the pattern finding.
static PatternPrintView detailed(const PatternState &p)
Print the pattern state with detailed information.
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 isStraw() const
Returns whether the measurement is a Mdt.
std::vector< std::string > patterns
Definition listroot.cxx:187
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Matrix< double, 3, 1 > Vector3D
@ eRejectHit
Test failed, discard the hit.
@ eBranchPattern
Test successfull with multiple pattern hits on same layer, branch the pattern.
@ eAddHit
Test successfull, add hit to pattern.
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
constexpr float inDeg(const float rad)
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
Definition P4Helpers.h:34
STL namespace.
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
Small wrapper for candidate hits used to build patterns.
uint8_t globLayer
Global measurement layer number.
Configuration object for the patter finder.
Base class for hit struct containing hit information.
uint8_t locLayer
Layer number in the sector frame.
double phiCov
Cached angular covariance [rad^2] of the hit in the phi angle.
Amg::Vector3D sensorDir(const Acts::GeometryContext &gctx) const
Sensor direction.
double stripAngle
Strip angle when the strips are non-orthogonal.
Pattern state object storing pattern information during construction.
Acts::CloneablePtr< PatHitVisual > visualInfo
Pointer to Visual Information for pattern visualization.