ATLAS Offline Software
Loading...
Searching...
No Matches
Muon/L1MuonNSW/src/MMTriggerTool.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
5#include "MMTriggerTool.h"
6#include "MMT_Hit.h"
7#include "MMT_Road.h"
8#include "NSWEncoding.h"
9
10#include "GaudiKernel/SystemOfUnits.h"
14#include "Acts/Utilities/MathHelpers.hpp"
15
16#include <algorithm>
17#include <cmath>
18#include <memory>
19
20namespace {
22 constexpr double kBunchSpacing = 25. * Gaudi::Units::ns;
23}
24
25namespace L1Muon::L1MM {
26
28 ATH_MSG_DEBUG("Initializing " << name() << "...");
29 ATH_CHECK(m_keyMmDigit.initialize());
30 ATH_CHECK(m_geoCtxKey.initialize());
31 ATH_CHECK(m_idHelperSvc.retrieve());
32 ATH_CHECK(detStore()->retrieve(m_detMgr));
33
34 m_diamond = std::make_unique<MMT_Diamond>(m_diamXthreshold, m_uv, m_diamUVthreshold, m_diamRoadSize,
37 return StatusCode::SUCCESS;
38 }
39
40 StatusCode MMTriggerTool::runTrigger(const EventContext& ctx, std::vector<MMCandidate>& candidates,
41 MMDebugInfo* debug) const {
42 candidates.clear();
43 if (debug) {
44 debug->hits.clear();
45 debug->nTrig.clear();
46 }
47
48 const MmDigitContainer* mmDigits{nullptr};
49 ATH_CHECK(SG::get(mmDigits, m_keyMmDigit, ctx));
50 const ActsTrk::GeometryContext* gctx{nullptr};
51 ATH_CHECK(SG::get(gctx, m_geoCtxKey, ctx));
52
53 const MmIdHelper& idHelper = m_idHelperSvc->mmIdHelper();
54
55 unsigned int nCollections{0}, nHitsTotal{0};
56 for (const MmDigitCollection* digitCollection : *mmDigits) {
57
58 std::vector<MMT_Hit> ev_hits;
59 double tanStereoAngle{0.}; // tan of the U/V stereo angle, from the geometry of the stereo layers
60 unsigned int nOutOfRange{0}, nNegTime{0}, nInfSlope{0};
61 Identifier firstHitId{}; // any accepted hit of the collection, to get the sector centre from the geometry
62
63 for (const MmDigit* digit : *digitCollection) {
64 const Identifier id = digit->identify();
65 if (!m_idHelperSvc->isMM(id)) continue;
66
67 // The Run 3 DCS / data-quality selection for real data is not ported: MC only for now.
68
69 const int channel = idHelper.channel(id);
70
71 // Strip-range check. Run 3: 1 <= channel <= totalStrips. Phase II: the active
72 // strips of the layer, [firstStrip, firstStrip + numStrips). Mandatory here, since
73 // the Phase-II strip position of an out-of-range strip is undefined.
74 const MuonGMR4::MmReadoutElement* readout = m_detMgr->getMmReadoutElement(id);
75 const IdentifierHash layHash = readout->layerHash(id);
76 const int firstStrip = readout->firstStrip(layHash);
77 const int numStrips = readout->numStrips(layHash);
78 const MuonGMR4::StripDesign& design = readout->stripLayer(layHash).design();
79 // X layers have no stereo angle (0): keep the value of the stereo layers of the collection
80 tanStereoAngle = std::max(tanStereoAngle, std::tan(std::abs(design.stereoAngle())));
81 if (channel < firstStrip || channel >= firstStrip + numStrips) {
82 ++nOutOfRange;
83 ATH_MSG_DEBUG("Strip out of active range: " << m_idHelperSvc->toString(id)
84 << " allowed [" << firstStrip << ", " << firstStrip + numStrips - 1 << "]");
85 continue;
86 }
87
88 const float stripTime = digit->stripResponseTime();
89 // Checking positive digitization time
90 if (stripTime < 0.) {
91 ++nNegTime;
92 continue;
93 }
94 const int BC = static_cast<int>(std::ceil(stripTime / kBunchSpacing));
95 ev_hits.emplace_back(id, stripTime, BC, m_detMgr, *gctx);
96
97 if (ev_hits.back().infSlope()) {
98 ATH_MSG_WARNING("Infinite slope, removing hit");
99 ++nInfSlope;
100 ev_hits.pop_back();
101 continue;
102 }
103 if (!firstHitId.is_valid()) firstHitId = id;
104
105 // Every accepted hit, also in collections without coincidence (as the Run 3 ntuple)
106 if (debug) {
107 const MMT_Hit& h = ev_hits.back();
108 MMHitInfo& info = debug->hits.emplace_back();
109 info.stationEta = h.getStationEta();
110 info.stationPhi = h.getStationPhi();
111 info.sectorPhi = h.getSectorPhi();
112 info.sector = h.getSector();
113 info.plane = h.getPlane();
114 info.strip = h.getChannel();
115 info.VMM = h.getVMM();
116 info.ART = h.getART();
117 info.BC = h.getBC();
118 info.time = h.getTime();
119 info.R = h.getR();
120 info.Z = h.getZ();
121 info.RZslope = h.getRZSlope();
122 info.shift = h.getShift();
123 }
124 }
125
126 if (ev_hits.empty() && nOutOfRange == 0) continue;
127 ++nCollections;
128 nHitsTotal += ev_hits.size();
129
130 ATH_MSG_DEBUG("MM station " << m_idHelperSvc->toStringChamber((*digitCollection->begin())->identify())
131 << ": " << ev_hits.size() << " hits"
132 << " (dropped: out-of-range " << nOutOfRange << ", negative time " << nNegTime
133 << ", inf slope " << nInfSlope << ")");
134
135 // Go ahead when hits are more than X+UV thresholds (as in Run 3)
136 if (ev_hits.size() < (m_diamond->getXthreshold() + m_diamond->getUVthreshold())) {
137 ATH_MSG_DEBUG("Available hits are " << ev_hits.size()
138 << ", less than X+UV threshold, skipping digit collection");
139 continue;
140 }
141
142 const bool isLarge = std::ranges::all_of(ev_hits, [](const auto& hit) { return hit.getSector() == 'L'; });
143 const SideType side = std::ranges::all_of(ev_hits, [](const auto& hit) { return hit.getStationEta() < 0; })
145 const int sectorPhi = ev_hits[0].getSectorPhi();
146 const bool allSectorPhi = std::ranges::all_of(ev_hits,
147 [&](const auto& hit) { return hit.getSectorPhi() == sectorPhi; });
148 if (!allSectorPhi) {
149 ATH_MSG_ERROR("Available digits belongs to different sectors IDs, unable to assign an unique ID");
150 return StatusCode::FAILURE;
151 }
152 const bool isEta1 = std::ranges::all_of(ev_hits,
153 [](const auto& hit) { return std::abs(hit.getStationEta()) == 1; });
154
155 // Setup roads
156 std::vector<MMT_Road> ev_roads;
157 m_diamond->createRoads(ev_roads, isLarge, isEta1);
158
159 // Evaluate coincidences
160 std::vector<slope_t> diamondSlopes;
161 m_diamond->findDiamonds(ev_hits, ev_roads, diamondSlopes, sectorPhi, tanStereoAngle);
162 ATH_MSG_DEBUG(" -> " << diamondSlopes.size() << " coincidence(s) in sector " << sectorPhi << side);
163 if (diamondSlopes.empty()) continue;
164 if (debug) debug->nTrig.push_back(diamondSlopes.size());
165
166 // Sector centre phi from the Phase-II geometry: the readout element is a trapezoid centred on
167 // the sector axis. It is used for the global phi instead of MMT_Diamond::phiShift(sectorPhi, ...),
168 // which places sector n at n*pi/8 while the Athena sectors (1-16) are centred at (n-1)*pi/8.
169 // The Run 3 output carries the local phi, so there the offset only affects MM_diamond_phiShf.
170 const MuonGMR4::MmReadoutElement* stationEl = m_detMgr->getMmReadoutElement(firstHitId);
171 const double sectorCentrePhi = stationEl->center(*gctx).phi();
172
173 for (const slope_t& slope : diamondSlopes) {
174 MMCandidate& cand = candidates.emplace_back();
175 cand.sectorPhi = sectorPhi;
176 cand.side = side;
177 cand.BC = slope.BC;
178 cand.eta = slope.eta;
179 cand.phi = slope.phi;
180 cand.phiShf = slope.phiShf;
181 // Same local-phi sign convention as Run 3 phiShift: flipped on side A
182 cand.phiGlobal = NSWEncoding::wrapTwoPi(sectorCentrePhi - Acts::copySign(slope.phi, slope.side));
183 cand.dtheta = slope.dtheta;
184 cand.lowRes = slope.lowRes;
185 cand.iX = slope.iRoad;
186 cand.iU = slope.iRoadu;
187 cand.iV = slope.iRoadv;
188 cand.nHits = slope.totalCount;
189 // Rest of the Run 3 slope_t, for the validation ntuple
190 cand.sector = isLarge ? 'L' : 'S';
191 cand.xCount = slope.xCount;
192 cand.uCount = slope.uCount;
193 cand.age = slope.age;
194 cand.mx = slope.mx;
195 cand.my = slope.my;
196 cand.mxl = slope.mxl;
197 cand.uavg = slope.uavg;
198 cand.vavg = slope.vavg;
199 cand.theta = slope.theta;
200 cand.secCentrePhi = sectorCentrePhi;
201 }
202 }
203
204 ATH_MSG_DEBUG("MM trigger: " << nHitsTotal << " hits in " << nCollections
205 << " digit collections -> " << candidates.size() << " candidates");
206 return StatusCode::SUCCESS;
207 }
208
209} // namespace L1Muon::L1MM
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_DEBUG(x,...)
#define ATH_MSG_ERROR(x,...)
#define ATH_MSG_WARNING(x,...)
bool hit(const Container &ids, int pdgId)
const bool debug
Handle class for reading from StoreGate.
Header file for AthHistogramAlgorithm.
This is a "hash" representation of an Identifier.
bool is_valid() const
Check if id is in a valid state.
One MM strip hit as seen by the MM trigger processor.
ActsTrk::GeoContextReadKey_t m_geoCtxKey
Phase-II geometry context, carrying the alignment (same key as L1MuonMDT and L1MuonS1RPC).
virtual StatusCode runTrigger(const EventContext &ctx, std::vector< MMCandidate > &candidates, MMDebugInfo *debug) const override
const MuonGMR4::MuonDetectorManager * m_detMgr
SG::ReadHandleKey< MmDigitContainer > m_keyMmDigit
ServiceHandle< Muon::IMuonIdHelperSvc > m_idHelperSvc
std::unique_ptr< MMT_Diamond > m_diamond
Use IdentifiableContainer with MmDigitCollection.
int channel(const Identifier &id) const override
const StripLayer & stripLayer(const Identifier &measId) const
unsigned int numStrips(const IdentifierHash &layerHash) const
Returns the number of total active strips.
IdentifierHash layerHash(const Identifier &measId) const override final
The layer hash removes the bits from the IdentifierHash corresponding to the measurement's channel nu...
unsigned int firstStrip(const IdentifierHash &layerHash) const
Returns the first active strip.
Amg::Vector3D center(const ActsTrk::GeometryContext &ctx) const
Returns the geometrical center point of the readout element.
double stereoAngle() const
Returns the value of the stereo angle.
const StripDesign & design(bool phiView=false) const
Returns the underlying strip design.
Definitions shared by the classes of the MM trigger processor emulation.
SideType
Detector side. The value is the sign of z, so that it can be used as a sign (e.g. Acts::copySign).
double wrapTwoPi(double phi)
Map any angle to [0, 2pi).
Definition NSWEncoding.h:29
const T * get(const ReadCondHandleKey< T > &key, const EventContext &ctx)
Convenience function to retrieve an object given a ReadCondHandleKey.
One MM trigger candidate (one road that passed the coincidence), before any quantisation into the Run...
double phiGlobal
Global phi in [0, 2pi): sector centre phi from the geometry -/+ local phi.
unsigned int xCount
X hits in the road.
int sectorPhi
Trigger sector 1-16 (Athena MM sector convention).
double dtheta
Angle w.r.t. the infinite-momentum track [rad].
int age
BC of the candidate relative to the start of the processing window.
double vavg
Average R/Z slope of the V hits.
double mx
phi-sensitive slope, from the U and V slopes
double phi
Local phi (relative to the sector centre).
unsigned int uCount
U hits in the road.
bool lowRes
Not all 8 planes in the road (Run 3 flag, not part of the Run 4 word).
int BC
Bunch crossing of the candidate (mode of the hit BCs, as Run 3).
double secCentrePhi
Sector centre phi from the Phase-II geometry.
double phiShf
Global phi as computed in Run 3 (MMT_Diamond::phiShift), for comparison.
double my
Average R/Z slope of the X hits.
double mxl
Local slope, R vs Z fit of the X hits.
unsigned int nHits
Hits in the road (Run 3 slope.totalCount).
double uavg
Average R/Z slope of the U hits.
double eta
Global eta of the segment.
SideType side
A (z>0) or C (z<0).
double theta
Polar angle of the candidate [rad].
Extra information returned by runTrigger on request (used by the validation algorithm).
One MM trigger hit, for the validation ntuple only (MM_* hit branches).
One MM trigger candidate as found by MMT_Diamond::findDiamonds: road indices, hit counts,...