11#include "Acts/Utilities/Helpers.hpp"
28 if(tubeNum1 == tubeNum2) {
31 return std::abs(tubeNum1-tubeNum2) < 2;
33 double inDegrees(
double angle) {
34 return angle / Gaudi::Units::deg;
37 using PatternHitVisualInfoVec = std::vector<PatternHitVisualInfo>;
42 void resetVisualToOverlap(PatternHitVisualInfoVec* visualInfo) {
43 if (!visualInfo)
return;
44 const auto [
first, last] = std::ranges::remove_if(*visualInfo, [](
const PatternHitVisualInfo& v){
45 return v.status == PatternHitVisualInfo::PatternStatus::eSuccessful; });
47 visualInfo->erase(first,last);
55 static_assert(std::is_move_assignable_v<PatternState>);
56 static_assert(std::is_move_constructible_v<PatternState>);
57 static_assert(std::is_copy_assignable_v<PatternState>);
58 static_assert(std::is_copy_constructible_v<PatternState>);
59 static_assert(std::is_nothrow_move_constructible_v<PatternState>);
60 static_assert(std::is_nothrow_move_assignable_v<PatternState>);
69 auto visualInfo {
m_cfg.visionTool ? std::make_unique<PatternHitVisualInfoVec>() :
nullptr};
80 for (
const auto& [spc, bucketVec] : pat.bucketsPerContainer) {
81 if (outBuckets.find(spc) == outBuckets.end()) {
82 throw std::runtime_error(
"The space point container associated to the pattern is not present in the output bucket map.");
85 auto& outBucketVec = outBuckets[spc];
86 if (std::ranges::find(outBucketVec, bucket) == outBucketVec.end()) {
87 outBucketVec.push_back(bucket);
94 m_cfg.visionTool->plotPatternBuckets(Gaudi::Hive::currentContext(),
"GlobPatFind_", std::move(*visualInfo));
106 for (
const auto seedingLayer :
m_cfg.layerSeedings) {
109 for (
const auto& [seedCoords, seed] : orderedSpacepoints) {
112 if (seedLayer != seedingLayer || (seed->isStraw() && !
m_cfg.seedFromMdt)) {
115 ATH_MSG_VERBOSE(__func__<<
"() New seed hit "<<*seed<<
", coordinates "<<seedCoords);
117 auto nExistingPatterns {std::ranges::count_if(
patterns, [&seed, &seedCoords,
this](
const PatternState& pattern){
118 if (std::abs(pattern.theta - seedCoords[thetaIdx]) >
m_cfg.thetaSearchWindow ||
119 pattern.sectorCoord !=
static_cast<int>(seedCoords[sectorIdx])) {
122 return pattern.isInPattern(seed);
124 if (nExistingPatterns >=
m_cfg.maxSeedAttempts) {
125 ATH_MSG_VERBOSE(__func__<<
"() Seed has already been used in "<<nExistingPatterns<<
" patterns, which is above the limit - skip this seed.");
129 SearchTree_t::range_t selectRange{};
131 selectRange[sectorIdx].shrink(seedCoords[sectorIdx] - 0.1, seedCoords[sectorIdx] + 0.1);
135 const double thetaWindow {(seedLayer == LayerIndex::Inner || seedLayer == LayerIndex::Outer)
136 ?
m_cfg.thetaSearchWindow : 0.5*
m_cfg.thetaSearchWindow};
137 selectRange[thetaIdx].shrink(seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
139 std::vector<TreeNode> candidateHits = orderedSpacepoints.rangeSearchWithKey(selectRange);
140 if (candidateHits.size() <
m_cfg.minBendingTriggerHits +
m_cfg.minBendingPrecisionHits) {
141 ATH_MSG_VERBOSE(__func__<<
"() Found "<<candidateHits.size()<<
" candidate hits, below the minimum required - skip this seed.");
145 if (std::ranges::none_of(candidateHits, [
this, seedLayer](
const TreeNode& c){
146 return m_cfg.idHelperSvc->layerIndex(c.second->identify()) != seedLayer; }) ) {
147 ATH_MSG_VERBOSE(__func__<<
"() All candidates are in the same station layer, and we need at least two - skip this seed.");
151 std::ranges::sort(candidateHits, [](
const TreeNode& c1,
const TreeNode& c2){
165 if (
msgLvl(MSG::VERBOSE)) {
166 ATH_MSG_VERBOSE(__func__<<
"() Found "<< candidateHits.size()<<
" candidate hits: ");
167 for (
const auto& [coords, hit] : candidateHits) {
168 ATH_MSG_VERBOSE(__func__<<
"() \t**"<<*hit<<
", coords: "<<coords <<
", glob Z/R/phi: "<<hit.Z<<
" / "<<hit.R<<
" / "<<inDegrees(hit.phi)
169 <<
", st/layer: " <<
stName(hit.station) <<
" / "<< hit.layerNum);
172 const auto seedItr {std::ranges::find_if(candidateHits,
173 [&seed](
const TreeNode& c){
return c.second == seed; })};
174 assert(seedItr != candidateHits.end());
176 std::vector<PatternState> activePatterns{};
177 activePatterns.emplace_back(seed,
static_cast<int>(seedCoords[sectorIdx]), seedCoords[thetaIdx]);
179 activePatterns.back().visualInfo = std::make_unique<PatternHitVisualInfo>(
180 seed.hit, seedCoords[thetaIdx] - thetaWindow, seedCoords[thetaIdx] + thetaWindow);
189 auto processNewHit = [&](
const TreeNode& testPair){
191 ATH_MSG_VERBOSE(
"processNewHit() *** Test "<<*test<<
" against " << activePatterns.size() <<
" active patterns.");
192 extendPatterns(activePatterns, test, seed, *prevCandidate, visualInfo);
193 prevCandidate = &test;
197 auto ensureOnePattern = [&activePatterns, &visualInfo,
this]() {
198 if (activePatterns.size() > 1) {
199 ATH_MSG_VERBOSE(
"ensureOnePattern() Found "<<activePatterns.size()<<
" patterns in current search stage - keep the best one.");
200 activePatterns =
resolveOverlaps(std::move(activePatterns), visualInfo);
201 resetVisualToOverlap(visualInfo);
202 assert(activePatterns.size() == 1);
207 ATH_MSG_VERBOSE(__func__<<
"() Search between " << candidateHits.size() <<
" candidates for compatible hits...");
208 for (
auto it = std::next(seedItr); it != candidateHits.end(); ++it) {
213 for (
auto it = std::reverse_iterator(seedItr); it != candidateHits.rend(); ++it) {
221 std::ranges::count_if(newPattern.
hitsPerStation, [](
const auto& st) { return st.second.size()>= 3; }) < 2) {
222 ATH_MSG_VERBOSE(__func__<<
"() Pattern " << newPattern <<
"\ndoes not meet minimum hit requirements - reject.");
228 ATH_MSG_VERBOSE(__func__<<
"() Pattern " << newPattern <<
"\ndoes not meet the mean norm residual2 cut - reject.");
234 patterns.push_back(std::move(newPattern));
247 if (activePatterns.size() > 1) {
249 std::vector<PatternState*> patPtrs(activePatterns.size());
250 std::ranges::transform(activePatterns, patPtrs.begin(), [](
PatternState& p){ return &p; });
252 std::ranges::sort(patPtrs, {}, [](
const PatternState* p){
return p->lastInsertedHit; });
255 deduplicated.reserve(activePatterns.size());
256 for (
auto it = patPtrs.begin(); it != patPtrs.end(); ) {
258 const SpacePoint* groupLastHit {(*it)->lastInsertedHit};
259 auto groupEnd = std::ranges::find_if(it, patPtrs.end(), [&](
const PatternState* p) {
260 return p->lastInsertedHit != groupLastHit;
264 deduplicated.push_back(std::move(**std::ranges::max_element(it, groupEnd,
268 std::ranges::transform(it, groupEnd, std::back_inserter(deduplicated),
276 nextPatterns.reserve(activePatterns.size()* 2);
278 const unsigned refMissedLayerHits {std::ranges::min_element(activePatterns,
279 [](
const auto&
a,
const auto& b) {
return a.nMissedLayerHits < b.nMissedLayerHits; }
280 )->nMissedLayerHits};
284 if (pattern.nMissedLayerHits >=
m_cfg.maxMissedLayerHits && pattern.nMissedLayerHits > refMissedLayerHits) {
285 ATH_MSG_VERBOSE(__func__<<
"() Pattern " << pattern <<
" has missed " << pattern.nMissedLayerHits <<
" hits in different layers, above the maximum allowed - abort this pattern.");
295 pattern.addHit(test, residual, accWindow);
296 ATH_MSG_VERBOSE(__func__<<
"() Hit is compatible, add it to the pattern. Updated pattern: " << pattern);
301 if (std::ranges::any_of(nextPatterns, [&test, &pattern](
const PatternState& p) {
302 return p.lastInsertedHit == test.hit && p.prevLayerHit == pattern.prevLayerHit; })) {
303 ATH_MSG_VERBOSE(__func__<<
"() Hit is compatible & on same layer of last added hit, but branched pattern already exists");
308 const HitPayload& lastPatHit {pattern.getNthLastHit(1u)};
309 newPattern.
overWriteHit(lastPatHit, test, residual, accWindow);
310 ATH_MSG_VERBOSE(__func__<<
"() Hit is compatible & on same layer of last added hit - new branched pattern: " << newPattern);
313 if (newPattern.
visualInfo && pattern.visualInfo) {
314 pattern.visualInfo->replacedHits.push_back(test.hit);
315 newPattern.
visualInfo->replacedHits.push_back(lastPatHit.
hit);
318 nextPatterns.push_back(std::move(newPattern));
322 ATH_MSG_VERBOSE(__func__<<
"() Hit is not compatible with the pattern.");
323 if (pattern.visualInfo) {
324 pattern.visualInfo->discardedHits.push_back(test.hit);
327 pattern.nMissedLayerHits++;
332 nextPatterns.push_back(std::move(pattern));
341 if (test->measuresPhi() && pat.nPhiHits &&
343 ATH_MSG_VERBOSE(__func__<<
"() The pattern with phi = "<<inDegrees(pat.phi)
344 <<
" is not compatible with the test hit with phi "<<inDegrees(test.phi));
349 ATH_MSG_VERBOSE(__func__<<
"() Test hit is on the same layer as the seed - reject.");
360 auto makeResult = [&](
const uint8_t patHitIdx,
363 if (
res.residual <
res.accWindow) {
370 const HitPayload& lastPatHit {pat.getNthLastHit(1u)};
371 const int nMeas {pat.nBendingTriggerHits + pat.nPrecisionHits};
375 return makeResult(1u,
true);
379 if (test == lastPatHit) {
380 ATH_MSG_VERBOSE(__func__<<
"() Test hit is the same as last inserted hit - rejecting.");
384 if (areConsecutiveMdt(*test, *lastPatHit)) {
385 ATH_MSG_VERBOSE(__func__<<
"() Consecutive MDT hits on the same layer - accepting.");
390 ATH_MSG_VERBOSE(__func__<<
"() Test hit is a trigger hit and last inserted hit is precision on the same layer - keep the precision hit.");
395 ATH_MSG_VERBOSE(__func__<<
"() Test hit on same layer as seed with no prior hits, and they are not consecutive MDT hits - rejecting.");
404 return makeResult(n,
false);
410 const uint8_t patHitIdx,
412 const HitPayload& patHit {pat.getNthLastHit(patHitIdx)};
413 const int nMeas {pat.nBendingTriggerHits + pat.nPrecisionHits};
417 const double patDeltaZ {std::abs(patHit.
Z - seed.Z)};
418 const bool useBeamspot {patHit == seed || patDeltaZ < Acts::s_epsilon ||
419 (patHit.
station == seed.station && (patHit->
msSector()->
barrel() ? std::abs(patHit.
R - seed.R) : patDeltaZ) <=
m_cfg.minLayerSeparation)};
421 if (useBeamspot && patHitIdx + 1 < nMeas) {
422 ATH_MSG_VERBOSE(__func__<<
"() Distance seed to pat hit is very small - try with next pat hit as reference.");
429 uint8_t nSameLayer{1u};
430 while (patHitIdx + nSameLayer < nMeas &&
434 return nSameLayer > 2u ? pat.getNthLastHit(patHitIdx + (nSameLayer - 1u) / 2u) : patHit;
437 ATH_MSG_VERBOSE(__func__<<
"() Distance seed to pat hit - deltaR_pat= " << (centralPatHit.
R - seed.R) <<
", deltaZ_pat= " << (centralPatHit.
Z - seed.Z) <<
". Use beamspot: " << useBeamspot);
441 const double refR {useBeamspot ? beamSpot.perp() : centralPatHit.
R};
442 const double refZ {useBeamspot ? beamSpot.z() : centralPatHit.
Z};
445 const double dZ_slope {refZ - seed.Z};
446 const double dR_slope {refR - seed.R};
447 assert(dZ_slope != 0);
448 const double lineSlope {dR_slope / dZ_slope};
449 ATH_MSG_VERBOSE(__func__<<
"() Slope computation - deltaR_slope= " << dR_slope <<
", deltaZ_slope= " << dZ_slope <<
", slope= " << lineSlope);
452 const double dZ_res {test.Z - seed.Z};
453 res.residual = (test.R - seed.R) - lineSlope * dZ_res;
454 ATH_MSG_VERBOSE(__func__<<
"() Residual computation - deltaR_res: " << (test.R - seed.R) <<
", deltaZ_res: " << dZ_res <<
", signed residual: " <<
res.residual);
458 const double geometricalFactor {1.+ Acts::square(lineSlope)};
459 const double alpha {dZ_res / dZ_slope};
460 const double propagationFactor {1. - alpha + Acts::square(alpha)};
461 res.accWindow =
m_cfg.baseRWindow * std::sqrt(2*geometricalFactor * propagationFactor);
462 if (useBeamspot)
res.accWindow *= 1.5;
463 ATH_MSG_VERBOSE(__func__<<
"() Window computation - Geometrical Factor: " << std::sqrt(geometricalFactor) <<
" alpha: " << alpha
464 <<
", Propagation Factor: " << std::sqrt(propagationFactor) <<
", Computed Window: " <<
res.accWindow);
467 if (!useBeamspot && centralPatHit->
isStraw()) {
468 const double LRcorrection {dZ_res * geometricalFactor * centralPatHit->
driftRadius()/ Acts::fastHypot(dZ_slope, dR_slope)};
469 res.residual = std::min(std::abs(
res.residual-LRcorrection), std::abs(
res.residual+LRcorrection));
470 ATH_MSG_VERBOSE(__func__<<
"() Apply LR correction: " << LRcorrection <<
", corrected residual: " <<
res.residual);
472 res.residual = std::abs(
res.residual);
474 if (pat.visualInfo) {
475 pat.visualInfo->hitLineInfo[test.hit] = std::make_pair(lineSlope,
res.accWindow);
483 const long nStations {std::ranges::count_if(p.hitsPerStation, [](
const auto&
pair){ return pair.second.size() >= 4u; })};
484 const double etaHitCount {1.*p.nBendingTriggerHits +
m_cfg.precisionWeight * p.nPrecisionHits};
485 const double etaHitScore {std::tanh(etaHitCount / (std::max(nStations, 1L) *
m_cfg.hitScoreSaturation))};
486 const double stationScore {std::tanh(1.*nStations / 2.)};
487 double residualRatio {p.meanNormResidual2 / (1.5*
m_cfg.meanNormRes2Cut)};
488 if (!p.isFinalized) residualRatio /= (p.nBendingTriggerHits + p.nPrecisionHits);
489 const double resPenalty {
m_cfg.residualPenalty * residualRatio / (1. + residualRatio)};
490 const double etaScore {stationScore * etaHitScore * (1. - resPenalty)};
491 const double phiScore {std::tanh(1.*p.nPhiHits /
m_cfg.phiBonusSaturation)};
492 ATH_MSG_VERBOSE(
"score() stationScore: "<<stationScore<<
", etaHitScore: "<<etaHitScore<<
", resPenalty: "<<resPenalty<<
", etaScore: "<<etaScore<<
", phiScore: "<<phiScore);
493 return 0.9 * etaScore + 0.1 * phiScore;
495 const double scoreA {score(
a)};
496 const double scoreB {score(b)};
497 ATH_MSG_VERBOSE(__func__<<
"() Pattern " <<
a <<
" with score " << scoreA <<
" is " <<
498 (scoreA > scoreB ?
"BETTER" :
"WORSE") <<
" than " << b <<
" with score " << scoreB);
499 return scoreA > scoreB;
505 outputPatterns.reserve(toResolve.size());
509 if(std::abs(
a.sectorCoord - b.sectorCoord) > 1) {
512 if (
a.nPhiHits > 0 && b.nPhiHits > 0) {
514 }
else if (
a.nPhiHits > 0) {
518 }
else if (b.nPhiHits > 0) {
523 if (std::abs(
a.theta - b.theta) > 2.*
m_cfg.thetaSearchWindow) {
528 for (
const auto& [stA, hitsA] :
a.hitsPerStation) {
529 auto itB = b.hitsPerStation.find(stA);
530 if (itB == b.hitsPerStation.end()) {
533 nSharedHits += std::ranges::count_if(hitsA, [&](
const HitPayload& hitA){
534 return std::ranges::find(itB->second, hitA) != itB->second.end();
538 const int minHits {std::min(
a.nBendingTriggerHits +
a.nPrecisionHits, b.nBendingTriggerHits + b.nPrecisionHits)};
539 return nSharedHits > minHits / 2;
541 for (
auto it = toResolve.begin(); it != toResolve.end(); ++it) {
547 for (
auto jt = std::next(it); jt != toResolve.end(); ++jt) {
548 if (jt->isOverlap || !areOverlapping(*it, *jt)) {
552 jt->isOverlap =
true;
554 it->isOverlap =
true;
558 if (!it->isOverlap) {
559 it->finalizePatternEta();
560 outputPatterns.push_back( std::move(*it));
566 ATH_MSG_VERBOSE(__func__<<
"() Patterns surviving overlap removal: "<< outputPatterns.size());
567 return outputPatterns;
576 struct PhiStripProjectionModel {
585 const double layCoord =
isBarrel ? pos.perp() : pos.z();
586 return refStrip + (layCoord - refLay) * invSlope;
589 const double stripCoord =
isBarrel ? pos.z() : pos.perp();
590 return std::abs(
project(pos) - stripCoord);
594 PhiStripProjectionModel result{};
595 result.station = station;
596 const std::vector<HitPayload>& hits {pat.hitsPerStation.at(station)};
597 if (hits.empty())
return result;
598 const bool isBarrel {hits.front().hit->msSector()->barrel()};
610 if (hits.size() > 1) {
613 if (!hit->measuresEta())
continue;
614 if (!sp1 || layCoord(hit) < layCoord(*sp1)) {
617 if (!sp2 || layCoord(hit) > layCoord(*sp2)) {
624 auto layDistanceFromSp1 = [&layCoord,&sp1](
const HitPayload& hit) {
625 return std::abs(layCoord(hit) - layCoord(*sp1));
627 const std::vector<HitPayload>& closestSt {std::ranges::min_element(pat.hitsPerStation,
628 [&layDistanceFromSp1, &station](
const auto& st1,
const auto& st2){
629 if (st1.first == station) return false;
630 if (st2.first == station) return true;
631 return layDistanceFromSp1(st1.second.front()) < layDistanceFromSp1(st2.second.front());
634 if (!hit->measuresEta())
continue;
635 if (!sp2 || layDistanceFromSp1(hit) < layDistanceFromSp1(*sp2)) {
640 if (!sp1 || !sp2 )
return result;
641 const double deltaLay {layCoord(*sp2) - layCoord(*sp1)};
642 if (std::abs(deltaLay) <
m_cfg.minLayerSeparation)
return result;
644 result.refLay = layCoord(*sp1);
645 result.refStrip = stripCoord(*sp1);
646 result.invSlope = (stripCoord(*sp2) - stripCoord(*sp1)) / deltaLay;
648 result.isValid =
true;
653 survivingPatterns.reserve(
patterns.size());
656 ATH_MSG_VERBOSE(__func__<<
"() Search for phi-only hits for pattern: " << pat);
658 std::optional<PhiStripProjectionModel> patProjOnStrip{};
659 for (
const auto& [spc, bucketVec] : pat.bucketsPerContainer) {
661 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
662 const StIndex station {
m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
664 const Amg::Vector3D locY {localToGlobal.linear() * Amg::Vector3D::UnitY()};
666 for (
const auto& hit : *bucket) {
668 if (hit->measuresEta()){
674 const Amg::Vector3D globPosTest {localToGlobal * locPosTest};
675 const double globPhi {globPosTest.phi()};
681 const uint8_t layNum =
m_spSorter.sectorLayerNum(*hit);
682 if (
auto it {pat.hitsPerStation.find(station)}; it != pat.hitsPerStation.end()) {
683 if (std::ranges::any_of(it->second, [&](
const HitPayload&
h){
684 return h->measuresPhi() && hit->msSector() == h->msSector() && layNum == h.layerNum; })) {
685 ATH_MSG_VERBOSE(__func__<<
"() The pattern already has a phi hit in the same layer - skip this test hit.");
690 bool isEtaCompatible {
false};
691 const double sigmaEta {std::sqrt(hit->covariance()[covIdxEta])};
692 if (!patProjOnStrip.has_value() || patProjOnStrip->station != station) {
693 patProjOnStrip = makeProjectionModel(pat, station);
695 if(patProjOnStrip->isValid) {
696 isEtaCompatible = patProjOnStrip->residual(globPosTest) <= 1.1*sigmaEta;
697 ATH_MSG_VERBOSE(__func__<<
"() Distance pattern line from strip center: "<<patProjOnStrip->residual(globPosTest)<<
", strip half-length: "<<sigmaEta<<
", isCompatible: "<<isEtaCompatible);
700 double thetaMin {(globPosTest - sigmaEta * locY).
theta()};
701 double thetaMax {(globPosTest + sigmaEta * locY).
theta()};
702 if (thetaMax < thetaMin) {
705 isEtaCompatible = std::max(pat.theta - thetaMax, thetaMin - pat.theta) < 0.5 *
m_cfg.thetaSearchWindow;
706 ATH_MSG_VERBOSE(__func__<<
"() Pattern theta "<<inDegrees(pat.theta)<<
", strip theta window: ["<<inDegrees(thetaMin)<<
", "<<inDegrees(thetaMax)<<
"]");
709 if (!isEtaCompatible) {
710 ATH_MSG_VERBOSE(__func__<<
"() The pattern falls outside the test hit strip in eta - skip this test hit.");
714 ATH_MSG_VERBOSE(__func__<<
"() Phi-only hit compatible - add it to the pattern.");
715 pat.addHit(
HitPayload{hit.get(), station, layNum, globPhi}, 0., 0.);
719 if (pat.nPhiHits <
m_cfg.minPhiHits) {
720 ATH_MSG_VERBOSE(__func__<<
"() Pattern "<< pat<<
" has only "<<pat.nPhiHits<<
" phi hits, below the minimum required - reject this pattern.");
723 pat.finalizePatternPhi();
724 survivingPatterns.push_back(std::move(pat));
733 if (pattern.nPhiHits) {
735 ATH_MSG_VERBOSE(__func__<<
"() The pattern with phi = "<<inDegrees(pattern.phi)<<
" is not compatible with the test hit with phi "<<inDegrees(testPhi));
739 const bool isCompatible {pattern.sector1 == pattern.sector2 ? sectorMap.
insideSector(pattern.sector1, testPhi) :
742 ATH_MSG_VERBOSE(__func__<<
"() The test hit with phi = "<<inDegrees(testPhi)<<
" is not inside the pattern sectors: "<<pattern.sector1<<
" and "<<pattern.sector2);
750 for (
const auto& [station, hits] : cache.hitsPerStation) {
751 auto& out = hitPerStation[station];
752 out.reserve(hits.size());
753 std::ranges::transform(hits, std::back_inserter(out), [](
const HitPayload&
h){
return h.hit;});
756 pattern.setTheta(cache.theta);
757 pattern.setPhi(cache.phi);
759 pattern.setSector(cache.sector1);
760 pattern.setSecondarySector(cache.sector2);
762 pattern.setNPrecisionHits(cache.nPrecisionHits);
763 pattern.setNEtaNonPrecisionHits(cache.nBendingTriggerHits);
764 pattern.setNPhiHits(cache.nPhiHits);
765 pattern.setMeanNormResidual2(cache.meanNormResidual2);
773 std::transform(cache.begin(), cache.end(), std::back_inserter(
patterns), [
this](
const PatternState& cacheEntry) {
774 GlobalPattern pattern {convertToPattern(cacheEntry)};
784 SearchTree_t::vector_t rawData{};
787 size_t totalHits = 0;
790 totalHits += bucket->size();
794 rawData.reserve(3 * totalHits);
797 ATH_MSG_VERBOSE(
"Adding to the search tree "<<spc->size()<<
" space point buckets");
800 const Amg::Transform3D& localToGlobal {bucket->msSector()->localToGlobalTransform(gctx)};
801 const StIndex bucketStation {
m_cfg.idHelperSvc->stationIndex(bucket->front()->identify())};
802 const int sector = bucket->msSector()->sector();
804 for (
const auto& hit : *bucket) {
806 if (!hit->measuresEta() || (!
m_cfg.useMdtHits && hit->isStraw())) {
809 const Amg::Vector3D globalPos {localToGlobal * hit->localPosition()};
810 const double globalTheta {globalPos.theta()};
811 const HitPayload newHit {hit.get(), bucket, spc, bucketStation,
812 static_cast<uint8_t
>(
m_spSorter.sectorLayerNum(*hit)),globalPos.perp(), globalPos.z(), globalPos.phi()};
816 for (
const auto proj : {SectorProjector::leftOverlap, SectorProjector::center, SectorProjector::rightOverlap}) {
819 if (hit->measuresPhi() && proj != SectorProjector::center &&
822 <<
" is not in sector "<<projSector<<
" ["<<inDegrees(sectorMap.
sectorPhi(projSector)-sectorMap.
sectorWidth(projSector))
827 std::array<double, 2> coords{};
835 <<
"\nwith global position "<<
Amg::toString(globalPos) <<
" and coordinates "<<coords<<
" to the search tree");
836 rawData.emplace_back(std::move(coords), newHit);
841 ATH_MSG_VERBOSE(
"Create a new tree with "<<rawData.size()<<
" entries. ");
847 auto getLayerOrdering = [](
const bool isLayer1Lower) {
865 return getLayerOrdering(hit1->
msSector()->
barrel() ? hit1.
R < hit2.
R : std::abs(hit1.
Z) < std::abs(hit2.
Z));
869 if (layer1 == layer2) {
871 if (layer1 == LayerIndex::Middle) {
873 return getLayerOrdering(st1 == StIndex::BM);
874 }
else if (layer1 == LayerIndex::Inner) {
876 return getLayerOrdering(hit1.
R < hit2.
R);
878 throw std::runtime_error(
"Unexpected to have two pattern-compatible hits one in BO and the other in EO.");
881 if (layer1 == LayerIndex::Inner || layer2 == LayerIndex::Inner) {
883 return getLayerOrdering(layer1 == LayerIndex::Inner);
885 if (layer1 == LayerIndex::Outer || layer2 == LayerIndex::Outer) {
887 return getLayerOrdering(layer2 == LayerIndex::Outer);
889 if (layer1 == LayerIndex::BarrelExtended || layer2 == LayerIndex::BarrelExtended) {
891 return getLayerOrdering(layer1 == LayerIndex::BarrelExtended);
894 if (layer1 == LayerIndex::Extended) {
895 return getLayerOrdering(st2 == StIndex::EM);
897 return getLayerOrdering(st1 == StIndex::BM);
902 const double seedTheta)
910 StIndex stationHit {seed.station};
917 if (seed->measuresPhi()) {
923 const double residual,
924 const double acceptWindow) {
948 if (acceptWindow > Acts::s_epsilon) {
956 const double newResidual,
957 const double newAcceptWindow) {
959 throw std::runtime_error(std::format(
"Trying to overwrite a hit in station {} with another one from station {}",
stName(oldHit.
station),
stName(newHit.
station)));
965 std::stringstream
ss {
"Trying to overwrite a hit with incompatible type\n"};
966 ss <<
"Old hit: " << *oldHit <<
", isPrecision: " << oldHit.
isPrecision <<
", measuresEta: " << oldHit->
measuresEta() <<
"\n";
967 ss <<
"New hit: " << *newHit <<
", isPrecision: " << newHit.
isPrecision <<
", measuresEta: " << newHit->
measuresEta();
968 throw std::runtime_error(
ss.str());
970 assert(newAcceptWindow > Acts::s_epsilon &&
lastAcceptWindow > Acts::s_epsilon);
974 throw std::runtime_error(
"Trying to remove a hit from an invalid station");
976 std::vector<HitPayload>& hitsInStation {it->second};
978 while (!hitsInStation.empty()) {
986 hitsInStation.pop_back();
991 hitsInStation.push_back(newHit);
1010 for (
const auto& hit : hits) {
1013 if (std::ranges::find(bucketVec, hit.bucket) == bucketVec.end()){
1014 bucketVec.push_back(hit.bucket);
1017 theta += atan2(hit.R, hit.Z);
1029 double deltaPhiAcc {0.};
1030 std::optional<double> centralPhi {};
1032 for (
const auto& hit : hits) {
1033 if (!hit->measuresPhi()) {
1037 centralPhi = hit.phi;
1052 if (
auto it =std::ranges::find_if(*visualInfo, [&pattern](
const auto& v){
1053 return v.patternCopy && *v.patternCopy == pattern; }); it != visualInfo->end()) {
1054 it->status = status;
1057 visualInfo->push_back(*cache.visualInfo);
1058 auto& bucketVec {visualInfo->back().parentBuckets};
1059 for (
const auto& [_, buckets] : cache.bucketsPerContainer) {
1060 bucketVec.insert(bucketVec.end(), buckets.begin(), buckets.end());
1062 visualInfo->back().patternCopy = std::make_unique<GlobalPattern>(std::move(pattern));
1063 visualInfo->back().status = status;
1071 std::size_t remaining {n};
1074 const size_t nHits {hits.size()};
1075 if (remaining <=
nHits) {
1076 return hits.at(
nHits - remaining);
1086 std::ranges::find(seedStationIt->second, hit) != seedStationIt->second.end();
1104 return hit == other.hit;
1107 ostr<<
"Pattern state, Expanded Sector: "<<
sectorCoord<<
", Theta: "<<
theta <<
", Phi: "<<
phi;
1110 ostr<<
", Hit per station: \n";
1113 for (
const auto& hit : hits) {
1114 ostr<<
" "<<*hit<<
", R: "<<hit.R<<
", Phi:"<<hit.phi<<
"\n";
Scalar theta() const
theta method
bool isValid() const
Test to see if the link can be dereferenced.
#define ATH_MSG_VERBOSE(x)
std::pair< std::vector< unsigned int >, bool > res
T_ResultType project(ParameterMapping::type< N > parameter_map, const T_Matrix &matrix)
static const uint32_t nHits
double angle(const GeoTrf::Vector2D &a, const GeoTrf::Vector2D &b)
Header file for AthHistogramAlgorithm.
bool msgLvl(const MSG::Level lvl) const
Test the output level.
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
bool barrel() const
Returns whether the sector is placed in the barrel.
std::vector< const SpacePointContainer * > SpacePointContainerVec
Abrivation for a vector of space-point containers.
std::pair< SearchTree_t::coordinate_t, HitPayload > TreeNode
Type alias for a tree node, formed by a hit payload and its indexing coordinates.
@ eRejectHit
Test failed, discard the hit.
@ eBranchPattern
Test successfull with multiple pattern hits in the same logical measurement layer,...
@ eAddHit
Test successfull, add the hit to the pattern.
PatternStateVec resolveOverlaps(PatternStateVec &&toResolve, PatternHitVisualInfoVec *visualInfo=nullptr) const
Method to remove overlapping patterns.
std::vector< PatternHitVisualInfo > PatternHitVisualInfoVec
Abrivation for a vector of visual information objects.
static LayerOrdering checkLayerOrdering(const HitPayload &hit1, const HitPayload &hit2)
Method to check the logical layer ordering of two hits.
SearchTree_t constructTree(const ActsTrk::GeometryContext &gctx, const SpacePointContainerVec &spacepoints) const
Method to construct the search tree by filling it up with spacepoints from the given containers.
void addVisualInfo(const PatternState &candidate, PatternHitVisualInfo::PatternStatus status, PatternHitVisualInfoVec *visualInfo) const
Helper function to add visual information of a given pattern (which is usually going to be destroyed)...
void extendPatterns(PatternStateVec &activePatterns, const HitPayload &test, const HitPayload &seed, const HitPayload &prevCandidate, PatternHitVisualInfoVec *visualInfo=nullptr) const
Function testing pattern compatibility of a set of active patterns (patterns produced from the same s...
Config m_cfg
Global Pattern Recognition configuration.
bool isBetter(const PatternState &a, const PatternState &b) const
Operator to compare two patterns.
bool isPhiCompatible(const double testPhi, const PatternState &pattern) const
Method to check the phi compatibility of a test hit with a given pattern.
LineCompatibilityResult checkLineCompatibility(const HitPayload &seed, const HitPayload &test, const PatternState &pat) const
Method to check the line compatibility of a test hit with a given pattern.
std::vector< GlobalPattern > PatternVec
Abrivation for a vector of global patterns.
Muon::MuonStationIndex::StIndex StIndex
Type alias for the station index.
LayerOrdering
Enum to express the logical measurement layer ordering given two hits.
void addPhiOnlyHits(const ActsTrk::GeometryContext &gctx, PatternStateVec &patterns) const
Method to add phi-only measurements to existing PatternStates.
GlobalPatternFinder(const std::string &name, Config &&config)
Standard constructor.
std::vector< PatternState > PatternStateVec
PatternVec findPatterns(const ActsTrk::GeometryContext &gctx, const SpacePointContainerVec &spacepoints, BucketPerContainer &outBuckets) const
Main methods steering the pattern finding.
SeedCoords
Abrivation of the seed coordinates.
@ eSector
Expanded sector coordinate of the associated spectrometer sector
SpacePointPerLayerSorter m_spSorter
Spacepoint sorter per logical measurement layer.
LineCompatibilityResult computeResidual(const HitPayload &seed, const HitPayload &test, const PatternState &pat, const uint8_t patHitIdx, const Amg::Vector3D &beamSpot) const
Helper method to compute the residual of a test hit against a reference pattern hit and check whether...
GlobalPattern convertToPattern(const PatternState &candidate) const
Method to convert a PatternState into a GlobalPattern object.
std::unordered_map< const SpacePointContainer *, std::vector< const SpacePointBucket * > > BucketPerContainer
Abrivation for a collection of space-point buckets grouped by their corresponding input container.
PatternStateVec findPatternsInEta(const SearchTree_t &orderedSpacepoints, PatternHitVisualInfoVec *visualInfo=nullptr) const
Method steering the building of patterns in eta.
Acts::KDTree< 2, HitPayload, double, std::array, 5 > SearchTree_t
Definition of the search tree class.
Muon::MuonStationIndex::LayerIndex LayerIndex
Type alias for the station layer index.
Data class to represent an eta maximum in hough space.
std::unordered_map< StIndex, std::vector< HitType > > HitCollection
Helper class to group muon sgements that may belong to a muon trajectory.
static std::string to_string(const SectorProjector proj)
Print the sector projector.
static int ringSector(const int sector)
Ensure that the parsed sector number is following the MS sector schema 0 is mapped to 16 and 17 is ma...
static int ringOverlap(const int sector)
Maps the sector 33 -> 0 to close the extended MS symmetry ring.
SectorProjector
Enumeration to select the sector projection.
: The muon space point bucket represents a collection of points that will bre processed together in t...
The muon space point is the combination of two uncalibrated measurements one of them measures the eta...
double driftRadius() const
: Returns the size of the drift radius
bool measuresPhi() const
: Does the space point contain a phi measurement
xAOD::UncalibMeasType type() const
bool isStraw() const
Returns whether the measurement is a Mdt.
const MuonGMR4::SpectrometerSector * msSector() const
const xAOD::MuonMeasurement * primaryMeasurement() const
const Amg::Vector3D & localPosition() const
bool measuresEta() const
: Does the space point contain an eta measurement
double sectorOverlapPhi(int sector1, int sector2) const
returns the phi position of the overlap between the two sectors (which have to be neighboring) in rad...
double sectorPhi(int sector) const
returns the centeral phi position of a sector in radians
double sectorWidth(int sector) const
sector width (with overlap) in radians
bool insideSector(int sector, double phi) const
checks whether the phi position is consistent with sector
std::vector< std::string > patterns
std::string toString(const Translation3D &translation, int precision=4)
GeoPrimitvesToStringConverter.
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
T wrapToPi(T phi)
Wrap angle in radians to [-pi, pi].
T deltaPhi(T phiA, T phiB)
Return difference phiA - phiB in range [-pi, pi].
MsTrackSeeder::SectorProjector SectorProjector
DataVector< SpacePointBucket > SpacePointContainer
Abrivation of the space point container type.
const std::string & layerName(LayerIndex index)
convert LayerIndex into a string
bool isBarrel(const ChIndex index)
Returns true if the chamber index points to a barrel chamber.
const std::string & stName(StIndex index)
convert StIndex into a string
LayerIndex toLayerIndex(ChIndex index)
convert ChIndex into LayerIndex
void swap(ElementLinkVector< DOBJ > &lhs, ElementLinkVector< DOBJ > &rhs)
MdtDriftCircle_v1 MdtDriftCircle
Helper for azimuthal angle calculations.
Hit information stored during pattern building.
HitPayload(const SpacePoint *hit, const SpacePointBucket *bucket, const SpacePointContainer *container, StIndex station, uint8_t layerNum, double R, double Z, double phi)
Full constructor for eta hits.
const SpacePoint * hit
Pointer to the underlying hit.
const SpacePointBucket * bucket
Pointer to the parent bucket.
bool isPrecision
Is precision hit.
const SpacePointContainer * container
Pointer to the parent container.
StIndex station
Station index.
bool operator==(const HitPayload &other) const
Equal operator: it compares the underlying hit.
uint8_t layerNum
Logical layer number in the sector frame.
: Small struct to encapsulate the checkLineCompatibility result
Pattern state object storing pattern information during construction.
Acts::CloneablePtr< PatternHitVisualInfo > visualInfo
Pointer to Visual Information for pattern visualization.
double lastResidual
Residual & acceptance window of the last inserted hit (needed when replacing a hit).
void addHit(const HitPayload &hit, const double residual, const double acceptWindow)
Add a hit to the pattern and update the internal state.
BucketPerContainer bucketsPerContainer
Map of spacepoint buckets per spacepoint container associated to the pattern.
double meanNormResidual2
Mean over eta hits of the square of their residual divided by acceptance window.
bool isInPattern(const HitPayload &hit) const
Check wheter a hit is present in the pattern.
double theta
Average theta & average phi of the pattern.
const SpacePoint * lastInsertedHit
Pointer to the last inserted hit.
std::vector< StIndex > stations
Pattern hit stations to save the filling order.
void print(std::ostream &ostr) const
Print the pattern candidate and stream operator.
void finalizePatternEta()
Finalize the pattern building in eta and update its state.
uint8_t nBendingTriggerHits
bool isFinalized
Flag to indicate if the pattern has been finalized.
void finalizePatternPhi()
Finalize the pattern building in phi and update its state.
uint8_t nPrecisionHits
Counts of precision measurements / non-precision in bending direction / phi measurements.
PatternState()=delete
Delete default destructor - ensure patterns are always constructed from a seed or another pattern.
std::unordered_map< StIndex, std::vector< HitPayload > > hitsPerStation
Map collection of hits per station.
uint8_t nMissedLayerHits
Number of missed candidate hits in different measurement layers during pattern building.
const SpacePoint * prevLayerHit
Pointer to the last hit in the second-to-last layer.
uint8_t sectorCoord
expanded sector coordinate & the two corresponding physical sectors
void overWriteHit(const HitPayload &oldHit, const HitPayload &newHit, const double newResidual, const double newAcceptWindow)
Overwrite a hit in the pattern and update the internal state.
const HitPayload & getNthLastHit(const uint8_t n) const
Get the n-th last inserted hit.