ATLAS Offline Software
Loading...
Searching...
No Matches
TgcL0TruthValidationAlg.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
19
20#include <algorithm>
21#include <array>
22#include <cmath>
23#include <cstdint>
24#include <limits>
25#include <memory>
26#include <vector>
27
28namespace {
29
30struct StationPosition {
31 bool valid{false};
34};
35
36using StationPositions = std::array<StationPosition, 3>;
37
38std::uint8_t stationBit(const std::size_t station) {
39 return static_cast<std::uint8_t>(1U << station);
40}
41
42bool candidatePosition(const L0Muon::TgcL0Candidate& candidate,
43 const std::size_t station, float& eta, float& phi) {
44 if ((candidate.positionStationMask & stationBit(station)) == 0U) return false;
45 if (station == 0U) {
46 eta = candidate.m1Eta;
47 phi = candidate.m1Phi;
48 } else if (station == 1U) {
49 eta = candidate.m2Eta;
50 phi = candidate.m2Phi;
51 } else {
52 eta = candidate.m3Eta;
53 phi = candidate.m3Phi;
54 }
55 return std::isfinite(eta) && std::isfinite(phi);
56}
57
58float meanDeltaR(const L0Muon::TgcL0Candidate& candidate,
59 const StationPositions& truthPositions) {
60 float sumSquaredDeltaR{0.F};
61 std::size_t nStations{0U};
62 for (std::size_t station = 0U; station < truthPositions.size(); ++station) {
63 if (!truthPositions[station].valid) continue;
64 float candidateEta{0.F};
65 float candidatePhi{0.F};
66 if (!candidatePosition(candidate, station, candidateEta, candidatePhi)) {
67 continue;
68 }
69 const float deltaEta = candidateEta - truthPositions[station].eta;
70 const float deltaPhi = static_cast<float>(xAOD::P4Helpers::deltaPhi(
71 candidatePhi, truthPositions[station].phi));
72 sumSquaredDeltaR += deltaEta * deltaEta + deltaPhi * deltaPhi;
73 ++nStations;
74 }
75 if (nStations == 0U) return L0Muon::TgcL0ValidationInvalidValue;
76 return std::sqrt(sumSquaredDeltaR / static_cast<float>(nStations));
77}
78
79StationPositions truthPositions(const L0Muon::TgcL0ValidationEvent& event,
80 const std::size_t truth) {
81 const std::uint8_t mask = event.truth.extrapolatedStationMask[truth];
82 StationPositions positions{};
83 positions[0] = {(mask & 0x1U) != 0U, event.truth.m1Eta[truth],
84 event.truth.m1Phi[truth]};
85 positions[1] = {(mask & 0x2U) != 0U, event.truth.m2Eta[truth],
86 event.truth.m2Phi[truth]};
87 positions[2] = {(mask & 0x4U) != 0U, event.truth.m3Eta[truth],
88 event.truth.m3Phi[truth]};
89 return positions;
90}
91
92int pivotStation(const std::uint8_t stationMask) {
93 if ((stationMask & 0x4U) != 0U) return 2;
94 if ((stationMask & 0x2U) != 0U) return 1;
95 if ((stationMask & 0x1U) != 0U) return 0;
96 return -1;
97}
98
99struct CandidateMatch {
101 std::size_t truth{0U};
102 std::size_t candidate{0U};
103};
104
105struct SegmentMatch {
107 std::size_t truth{0U};
108 std::size_t segment{0U};
109};
110
111
112} // namespace
113
114namespace L0Muon {
115
117 ATH_CHECK(m_candidateKey.initialize());
118 ATH_CHECK(m_segmentKey.initialize());
119 ATH_CHECK(m_truthEventKey.initialize());
120 ATH_CHECK(m_outputKey.initialize());
121 ATH_CHECK(m_extrapolator.retrieve());
122
123 if (m_stationAbsZ.value().size() != 3U) {
124 ATH_MSG_ERROR("StationAbsZ must contain exactly M1, M2, and M3 values");
125 return StatusCode::FAILURE;
126 }
127 if (m_validationMinR.value() < 0.0 || m_validationMaxR.value() <= m_validationMinR.value()) {
128 ATH_MSG_ERROR("Validation radii are invalid: min=" << m_validationMinR.value()
129 << ", max=" << m_validationMaxR.value());
130 return StatusCode::FAILURE;
131 }
132 if (m_validationPlaneToleranceZ.value() < 0.0) {
133 ATH_MSG_ERROR("ValidationPlaneToleranceZ must be non-negative");
134 return StatusCode::FAILURE;
135 }
136 if (m_requiredBcTag.value() < -1 ||
137 m_requiredBcTag.value() >
138 static_cast<int>(std::numeric_limits<std::uint16_t>::max())) {
139 ATH_MSG_ERROR("RequiredBcTag must be -1 or a uint16 value");
140 return StatusCode::FAILURE;
141 }
142 return StatusCode::SUCCESS;
143}
144
145StatusCode TgcL0TruthValidationAlg::execute(const EventContext& ctx) const {
147 if (!candidates.isValid()) {
148 ATH_MSG_ERROR("Failed to retrieve " << m_candidateKey.fullKey());
149 return StatusCode::FAILURE;
150 }
152 if (!segments.isValid()) {
153 ATH_MSG_ERROR("Failed to retrieve " << m_segmentKey.fullKey());
154 return StatusCode::FAILURE;
155 }
157 if (!truthEvents.isValid()) {
158 ATH_MSG_ERROR("Failed to retrieve " << m_truthEventKey.fullKey());
159 return StatusCode::FAILURE;
160 }
161
162 auto output = std::make_unique<TgcL0ValidationEvent>();
163 output->event.runNumber = ctx.eventID().run_number();
164 output->event.eventNumber = ctx.eventID().event_number();
165 output->event.lumiBlock = ctx.eventID().lumi_block();
166 output->event.bcid = ctx.eventID().bunch_crossing_id();
167
168 const auto processParticle = [&](const auto& particle) -> StatusCode {
169 if (!particle || std::abs(particle->pdg_id()) != 13 ||
170 particle->status() != m_requiredTruthStatus.value() ||
171 std::abs(HepMC::barcode(particle)) > m_maxAbsBarcode.value()) {
172 return StatusCode::SUCCESS;
173 }
174
175 const auto& momentum = particle->momentum();
176 const double pt = momentum.perp();
177 const double eta = momentum.eta();
178 const double phi = momentum.phi();
179 if (pt < m_minPt.value() || std::abs(eta) < m_minAbsEta.value() ||
180 std::abs(eta) > m_maxAbsEta.value() || !std::isfinite(phi)) {
181 return StatusCode::SUCCESS;
182 }
183
184 const int pdgId = particle->pdg_id();
185 const double theta = 2.0 * std::atan(std::exp(-eta));
186 const double momentumMagnitude = pt * std::cosh(eta);
187 if (momentumMagnitude <= 0.0 || !std::isfinite(theta)) {
188 return StatusCode::SUCCESS;
189 }
190
191 const double charge = pdgId == 13 ? -1.0 : 1.0;
192 const Trk::PerigeeSurface perigeeSurface{Amg::Vector3D{0.0, 0.0, 0.0}};
193 const Trk::Perigee perigee{0.0, 0.0, phi, theta,
194 charge / momentumMagnitude, perigeeSurface};
195
196 StationPositions positions{};
197 std::uint8_t stationMask{0U};
198 const std::vector<double>& stationAbsZ = m_stationAbsZ.value();
199 for (std::size_t station = 0U; station < positions.size(); ++station) {
200 const double z = eta >= 0.0 ? stationAbsZ[station] : -stationAbsZ[station];
201 Amg::Transform3D transform = Amg::Transform3D::Identity();
202 transform.translation().z() = z;
203 const Trk::DiscSurface disc{transform, m_validationMinR.value(),
204 m_validationMaxR.value()};
205 const Trk::BoundaryCheck boundaryCheck{true};
206 const auto extrapolated = m_extrapolator->extrapolate(
207 ctx, perigee, disc, Trk::alongMomentum, boundaryCheck, Trk::muon);
208 if (!extrapolated) continue;
209 const Amg::Vector3D& position = extrapolated->position();
210 if (!std::isfinite(position.eta()) || !std::isfinite(position.phi()) ||
211 std::abs(position.z() - z) > m_validationPlaneToleranceZ.value()) {
212 continue;
213 }
214 positions[station] = StationPosition{
215 true, static_cast<float>(position.eta()),
216 static_cast<float>(xAOD::P4Helpers::deltaPhi(position.phi(), 0.))};
217 stationMask |= stationBit(station);
218 }
219
220 output->truth.pdgId.emplace_back(pdgId);
221 output->truth.barcode.emplace_back(HepMC::barcode(particle));
222 output->truth.pt.emplace_back(static_cast<float>(pt));
223 output->truth.eta.emplace_back(static_cast<float>(eta));
224 output->truth.phi.emplace_back(static_cast<float>(phi));
225 output->truth.charge.emplace_back(static_cast<float>(charge));
226 output->truth.extrapolatedStationMask.emplace_back(stationMask);
227 output->truth.m1Eta.emplace_back(positions[0].eta);
228 output->truth.m1Phi.emplace_back(positions[0].phi);
229 output->truth.m2Eta.emplace_back(positions[1].eta);
230 output->truth.m2Phi.emplace_back(positions[1].phi);
231 output->truth.m3Eta.emplace_back(positions[2].eta);
232 output->truth.m3Phi.emplace_back(positions[2].phi);
233 output->truth.matched.emplace_back(0U);
234 output->truth.matchedCandidateIndex.emplace_back(-1);
235 output->truth.matchMeanDeltaR.emplace_back(TgcL0ValidationInvalidValue);
236 output->truth.unmatchedReason.emplace_back(
237 stationMask == 0x7U
238 ? static_cast<std::uint8_t>(
240 : static_cast<std::uint8_t>(
242 output->truth.wireSegmentMatched.emplace_back(0U);
243 output->truth.stripSegmentMatched.emplace_back(0U);
244 output->truth.matchedWireSegmentIndex.emplace_back(-1);
245 output->truth.matchedStripSegmentIndex.emplace_back(-1);
246 output->truth.wireSegmentMatchResidual.emplace_back(
248 output->truth.stripSegmentMatchResidual.emplace_back(
250 return StatusCode::SUCCESS;
251 };
252
253 for (const HepMC::GenEvent* event : *truthEvents) {
254 if (event == nullptr) continue;
255#if __has_include("HepMC3/GenEvent.h")
256 for (const auto& particle : event->particles()) {
257 ATH_CHECK(processParticle(particle));
258 }
259#else
260 for (auto particle = event->particles_begin();
261 particle != event->particles_end(); ++particle) {
262 ATH_CHECK(processParticle(*particle));
263 }
264#endif
265 }
266
267 for (const TgcL0Candidate& candidate : *candidates) {
268 output->candidates.subdetectorId.emplace_back(candidate.subdetectorId);
269 output->candidates.triggerSector.emplace_back(candidate.sectorId);
270 output->candidates.readoutSector.emplace_back(candidate.readoutSector);
271 output->candidates.bcTag.emplace_back(candidate.bcTag);
272 output->candidates.stationMask.emplace_back(candidate.positionStationMask);
273 output->candidates.wireStationMask.emplace_back(candidate.wireStationMask);
274 output->candidates.stripStationMask.emplace_back(candidate.stripStationMask);
275 output->candidates.eta.emplace_back(candidate.eta);
276 output->candidates.phi.emplace_back(candidate.phi);
277 output->candidates.deltaTheta.emplace_back(candidate.deltaTheta);
278 output->candidates.deltaPhi.emplace_back(candidate.deltaPhi);
279 output->candidates.truthIndex.emplace_back(-1);
280 }
281
282 std::vector<CandidateMatch> candidateMatches;
283 std::vector<bool> hasCandidateInWindow(output->truth.pt.size(), false);
284 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
285 if (output->truth.extrapolatedStationMask[truth] != 0x7U) continue;
286 const StationPositions positions = truthPositions(*output, truth);
287 for (std::size_t candidate = 0U; candidate < candidates->size();
288 ++candidate) {
289 const TgcL0Candidate& inputCandidate = (*candidates)[candidate];
290 if (m_requiredBcTag.value() >= 0 &&
291 inputCandidate.bcTag != static_cast<std::uint16_t>(m_requiredBcTag.value())) {
292 continue;
293 }
294 if (output->truth.eta[truth] * inputCandidate.eta < 0.F) continue;
295 const float residual = meanDeltaR(inputCandidate, positions);
296 if (!std::isfinite(residual) || residual > m_maxMeanDeltaR.value()) continue;
297 hasCandidateInWindow[truth] = true;
298 candidateMatches.push_back({residual, truth, candidate});
299 }
300 }
301 std::stable_sort(candidateMatches.begin(), candidateMatches.end(),
302 [](const CandidateMatch& lhs, const CandidateMatch& rhs) {
303 if (lhs.residual != rhs.residual) {
304 return lhs.residual < rhs.residual;
305 }
306 if (lhs.truth != rhs.truth) return lhs.truth < rhs.truth;
307 return lhs.candidate < rhs.candidate;
308 });
309 for (const CandidateMatch& match : candidateMatches) {
310 if (output->truth.matchedCandidateIndex[match.truth] >= 0 ||
311 output->candidates.truthIndex[match.candidate] >= 0) {
312 continue;
313 }
314 output->truth.matched[match.truth] = 1U;
315 output->truth.matchedCandidateIndex[match.truth] =
316 static_cast<int>(match.candidate);
317 output->truth.matchMeanDeltaR[match.truth] = match.residual;
318 output->truth.unmatchedReason[match.truth] =
320 output->candidates.truthIndex[match.candidate] =
321 static_cast<int>(match.truth);
322 }
323 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
324 if (output->truth.matched[truth] != 0U ||
325 output->truth.extrapolatedStationMask[truth] != 0x7U) {
326 continue;
327 }
328 output->truth.unmatchedReason[truth] = static_cast<std::uint8_t>(
329 hasCandidateInWindow[truth]
332 }
333
334 for (const TgcL0Segment& segment : *segments) {
335 output->segments.subdetectorId.emplace_back(segment.subdetectorId);
336 output->segments.triggerSector.emplace_back(segment.triggerSector);
337 output->segments.bcTag.emplace_back(segment.bcTag);
338 output->segments.projection.emplace_back(
339 static_cast<std::uint8_t>(segment.projection));
340 output->segments.stationMask.emplace_back(segment.stationMask);
341 output->segments.summedQuality.emplace_back(segment.summedQuality);
342 output->segments.nStations.emplace_back(segment.nStations);
343 output->segments.eta.emplace_back(segment.eta);
344 output->segments.phi.emplace_back(segment.phi);
345 output->segments.residual.emplace_back(segment.residual);
346 output->segments.outputResidual.emplace_back(segment.outputResidual);
347 output->segments.consistency.emplace_back(segment.consistency);
348 output->segments.pivotChannel.emplace_back(segment.pivotChannel);
349 output->segments.truthIndex.emplace_back(-1);
350 output->segments.truthMatchResidual.emplace_back(
352 }
353
354 const auto matchProjection = [&](const TgcL0ValidationProjection projection,
355 const float maximumResidual) {
356 const auto projectionValue = static_cast<std::uint8_t>(projection);
357 std::vector<SegmentMatch> possibleMatches;
358 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
359 for (std::size_t segment = 0U;
360 segment < output->segments.projection.size(); ++segment) {
361 if (output->segments.projection[segment] != projectionValue) continue;
362 if (m_requiredBcTag.value() >= 0 &&
363 output->segments.bcTag[segment] !=
364 static_cast<std::uint16_t>(m_requiredBcTag.value())) {
365 continue;
366 }
367 if (output->truth.eta[truth] * output->segments.eta[segment] < 0.F) {
368 continue;
369 }
370 const int station = pivotStation(output->segments.stationMask[segment]);
371 if (station < 0 ||
372 (output->truth.extrapolatedStationMask[truth] &
373 stationBit(static_cast<std::size_t>(station))) == 0U) {
374 continue;
375 }
376 const std::array<float, 3> truthEta{
377 output->truth.m1Eta[truth], output->truth.m2Eta[truth],
378 output->truth.m3Eta[truth]};
379 const std::array<float, 3> truthPhi{
380 output->truth.m1Phi[truth], output->truth.m2Phi[truth],
381 output->truth.m3Phi[truth]};
382 const float residual =
384 ? std::abs(output->segments.eta[segment] - truthEta[station])
385 : std::abs(static_cast<float>(xAOD::P4Helpers::deltaPhi(
386 output->segments.phi[segment], truthPhi[station])));
387 if (std::isfinite(residual) && residual <= maximumResidual) {
388 possibleMatches.push_back({residual, truth, segment});
389 }
390 }
391 }
392 std::stable_sort(possibleMatches.begin(), possibleMatches.end(),
393 [](const SegmentMatch& lhs, const SegmentMatch& rhs) {
394 if (lhs.residual != rhs.residual) {
395 return lhs.residual < rhs.residual;
396 }
397 if (lhs.truth != rhs.truth) return lhs.truth < rhs.truth;
398 return lhs.segment < rhs.segment;
399 });
400 for (const SegmentMatch& match : possibleMatches) {
401 int& truthSegment =
403 ? output->truth.matchedWireSegmentIndex[match.truth]
404 : output->truth.matchedStripSegmentIndex[match.truth];
405 if (truthSegment >= 0 ||
406 output->segments.truthIndex[match.segment] >= 0) {
407 continue;
408 }
409 truthSegment = static_cast<int>(match.segment);
410 output->segments.truthIndex[match.segment] =
411 static_cast<int>(match.truth);
412 output->segments.truthMatchResidual[match.segment] = match.residual;
413 if (projection == TgcL0ValidationProjection::Wire) {
414 output->truth.wireSegmentMatched[match.truth] = 1U;
415 output->truth.wireSegmentMatchResidual[match.truth] = match.residual;
416 } else {
417 output->truth.stripSegmentMatched[match.truth] = 1U;
418 output->truth.stripSegmentMatchResidual[match.truth] = match.residual;
419 }
420 }
421 };
422
423 matchProjection(TgcL0ValidationProjection::Wire,
424 m_maxWireSegmentDeltaEta.value());
425 matchProjection(TgcL0ValidationProjection::Strip,
426 m_maxStripSegmentDeltaPhi.value());
427
428 const TgcL0ValidationCheckResult check = checkTgcL0ValidationEvent(*output);
429 if (!check.valid) {
430 ATH_MSG_ERROR("Refusing to record inconsistent validation data: "
431 << check.message);
432 return StatusCode::FAILURE;
433 }
434
435 SG::WriteHandle<TgcL0ValidationEvent> outputHandle{m_outputKey, ctx};
436 ATH_CHECK(outputHandle.record(std::move(output)));
437 return StatusCode::SUCCESS;
438}
439
440} // namespace L0Muon
Scalar eta() const
pseudorapidity method
Scalar deltaPhi(const MatrixBase< Derived > &vec) const
Scalar phi() const
phi method
Scalar theta() const
theta method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
double charge(const T &p)
Definition AtlasPID.h:997
if(pathvar)
Handle class for reading from StoreGate.
Handle class for recording to StoreGate.
#define z
Gaudi::Property< float > m_maxMeanDeltaR
Gaudi::Property< int > m_requiredTruthStatus
SG::WriteHandleKey< TgcL0ValidationEvent > m_outputKey
Gaudi::Property< std::vector< double > > m_stationAbsZ
Gaudi::Property< double > m_validationMinR
SG::ReadHandleKey< TgcL0CandidateContainer > m_candidateKey
Gaudi::Property< double > m_validationMaxR
SG::ReadHandleKey< TgcL0SegmentContainer > m_segmentKey
StatusCode execute(const EventContext &ctx) const override
Gaudi::Property< double > m_minAbsEta
Gaudi::Property< double > m_maxAbsEta
Gaudi::Property< double > m_validationPlaneToleranceZ
ToolHandle< Trk::IExtrapolator > m_extrapolator
SG::ReadHandleKey< McEventCollection > m_truthEventKey
virtual bool isValid() override final
Can the handle be successfully dereferenced?
StatusCode record(std::unique_ptr< T > data)
Record a const object to the store.
The BoundaryCheck class allows to steer the way surface boundaries are used for inside/outside checks...
Class for a DiscSurface in the ATLAS detector.
Definition DiscSurface.h:54
Class describing the Line to which the Perigee refers to.
bool match(std::string s1, std::string s2)
match the individual directories of two strings
Definition hcg.cxx:359
Eigen::Affine3d Transform3D
Eigen::Matrix< double, 3, 1 > Vector3D
int barcode(const T *p)
Definition Barcode.h:15
HepMC3::GenEvent GenEvent
Definition GenEvent.h:39
TgcL0ValidationProjection
Projection encoding used in the validation segment block.
TgcL0ValidationCheckResult checkTgcL0ValidationEvent(const TgcL0ValidationEvent &event)
Check vector sizes and reciprocal cross-block indices.
static constexpr float TgcL0ValidationInvalidValue
Common sentinel for unavailable floating-point validation data.
static constexpr std::uint8_t TgcL0ValidationNoUnmatchedReason
Sentinel indicating that a matched truth object has no failure code.
double deltaEta(const I4Momentum &p1, const I4Momentum &p2)
Computes efficiently .
Definition P4Helpers.h:66
@ alongMomentum
ParametersT< TrackParametersDim, Charged, PerigeeSurface > Perigee
output
Definition merge.py:16
void stable_sort(DataModel_detail::iterator< DVL > beg, DataModel_detail::iterator< DVL > end)
Specialization of stable_sort for DataVector/List.
double deltaPhi(double phiA, double phiB)
delta Phi in range [-pi,pi[
Event-local candidate used by the TGC simulation tools.
float deltaPhi
Signed azimuthal-angle residual, in radians.
std::uint8_t positionStationMask
Alias of stationMask retained for explicit validation/debug use.
std::uint16_t readoutSector
Run-3 detector/readout sector retained for diagnostics.
float m1Eta
Reconstructed station positions used only by validation/debug code.
std::uint16_t bcTag
Bunch-crossing tag.
std::uint8_t stripStationMask
Stations used by the strip projection segment.
std::uint16_t sectorId
Trigger Sector identifier.
std::uint8_t wireStationMask
Stations used by the wire projection segment.
float eta
Pseudorapidity at the TGC pivot plane.
float phi
Azimuth at the TGC pivot plane, in radians.
std::uint16_t subdetectorId
Subdetector identifier.
float deltaTheta
Signed polar-angle residual, in radians.
Event-local projection segment optionally exposed for validation.
ROOT-independent validation data for one event.