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 <memory>
25#include <numbers>
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 FinalCandidateMatch {
107 std::size_t truth{0U};
108 std::size_t candidate{0U};
109};
110
111struct SegmentMatch {
113 std::size_t truth{0U};
114 std::size_t segment{0U};
115};
116
117bool matchesTgcFields(const xAOD::TGCCandData& input,
118 const xAOD::SectorLogicCandData& output) {
119 return output.pT() == input.pt() &&
120 output.charge() == input.candCharge() &&
121 output.rawPhi() == input.phi() &&
122 output.rawEta() == input.eta() &&
123 output.ptThresh() == input.threshold() &&
124 output.TCID() == input.tcId() &&
125 output.coinType() == input.coinType();
126}
127
128bool hasOnlyTgcFields(const xAOD::SectorLogicCandData& candidate) {
129 return candidate.isMDT() == 0U && candidate.mdtFlag() == 0U &&
130 candidate.numMDTSeg() == 0U && candidate.mdtSegQual() == 0U &&
131 candidate.tileCoin() == 0U && candidate.exotTrig() == 0U;
132}
133
134float decodeEta(const xAOD::TGCCandData& candidate) {
135 const float fraction =
136 static_cast<float>(candidate.eta()) /
137 static_cast<float>(xAOD::TGCCandData::etaBitRange());
138 return fraction * (2.F * xAOD::TGCCandData::etaRange()) -
140}
141
142float decodePhi(const xAOD::TGCCandData& candidate) {
143 const float fraction =
144 static_cast<float>(candidate.phi()) /
145 static_cast<float>(xAOD::TGCCandData::phiBitRange());
146 return fraction * xAOD::TGCCandData::phiRange() -
147 std::numbers::pi_v<float>;
148}
149
150int sourceCandidateIndex(const xAOD::TGCCandData& candidate,
151 const L0Muon::TgcL0CandidateContainer& sources,
152 const float eta, const float phi) {
153 const float etaTolerance =
154 0.51F * 2.F * xAOD::TGCCandData::etaRange() /
155 static_cast<float>(xAOD::TGCCandData::etaBitRange());
156 const float phiTolerance =
158 static_cast<float>(xAOD::TGCCandData::phiBitRange());
159 int result{-1};
160 for (std::size_t index = 0U; index < sources.size(); ++index) {
161 const L0Muon::TgcL0Candidate& source = sources[index];
162 if (source.subdetectorId != candidate.subdetectorId() ||
163 source.sectorId != candidate.sectorId() ||
164 source.bcTag != candidate.bcTag() ||
165 (source.charge > 0 ? 1U : 0U) != candidate.candCharge() ||
166 source.goodMagneticField != candidate.goodMagneticField() ||
167 std::abs(source.eta - eta) > etaTolerance ||
168 std::abs(static_cast<float>(
169 xAOD::P4Helpers::deltaPhi(source.phi, phi))) > phiTolerance) {
170 continue;
171 }
172 if (result >= 0) return -2;
173 result = static_cast<int>(index);
174 }
175 return result;
176}
177
178} // namespace
179
180namespace L0Muon {
181
183 ATH_CHECK(m_candidateKey.initialize());
184 ATH_CHECK(m_segmentKey.initialize());
185 ATH_CHECK(m_truthEventKey.initialize());
189 ATH_CHECK(m_outputKey.initialize());
190 ATH_CHECK(m_extrapolator.retrieve());
191
192 if (m_stationAbsZ.value().size() != 3U) {
193 ATH_MSG_ERROR("StationAbsZ must contain exactly M1, M2, and M3 values");
194 return StatusCode::FAILURE;
195 }
196 if (m_validationMinR.value() < 0.0 || m_validationMaxR.value() <= m_validationMinR.value()) {
197 ATH_MSG_ERROR("Validation radii are invalid: min=" << m_validationMinR.value()
198 << ", max=" << m_validationMaxR.value());
199 return StatusCode::FAILURE;
200 }
201 if (m_validationPlaneToleranceZ.value() < 0.0) {
202 ATH_MSG_ERROR("ValidationPlaneToleranceZ must be non-negative");
203 return StatusCode::FAILURE;
204 }
205 return StatusCode::SUCCESS;
206}
207
208StatusCode TgcL0TruthValidationAlg::execute(const EventContext& ctx) const {
210 if (!candidates.isValid()) {
211 ATH_MSG_ERROR("Failed to retrieve " << m_candidateKey.fullKey());
212 return StatusCode::FAILURE;
213 }
215 if (!segments.isValid()) {
216 ATH_MSG_ERROR("Failed to retrieve " << m_segmentKey.fullKey());
217 return StatusCode::FAILURE;
218 }
220 if (!truthEvents.isValid()) {
221 ATH_MSG_ERROR("Failed to retrieve " << m_truthEventKey.fullKey());
222 return StatusCode::FAILURE;
223 }
224 auto output = std::make_unique<TgcL0ValidationEvent>();
225 output->event.runNumber = ctx.eventID().run_number();
226 output->event.eventNumber = ctx.eventID().event_number();
227 output->event.lumiBlock = ctx.eventID().lumi_block();
228 output->event.bcid = ctx.eventID().bunch_crossing_id();
229
230 const bool readFinalCandidates = m_validateFinalCandidates.value() ||
231 m_validateSectorLogic.value();
232 const xAOD::TGCCandDataContainer* finalCandidateContainer{nullptr};
233 if (readFinalCandidates) {
236 if (!finalCandidates.isValid()) {
237 ATH_MSG_ERROR("Failed to retrieve " << m_finalCandidateKey.fullKey());
238 return StatusCode::FAILURE;
239 }
240 finalCandidateContainer = finalCandidates.cptr();
241 for (std::size_t inputIndex = 0U;
242 inputIndex < finalCandidateContainer->size(); ++inputIndex) {
243 const xAOD::TGCCandData* inputCandidate =
244 (*finalCandidateContainer)[inputIndex];
245 if (inputCandidate == nullptr) {
246 ATH_MSG_ERROR("Null final TGC candidate at index " << inputIndex);
247 return StatusCode::FAILURE;
248 }
249
250 const float eta = decodeEta(*inputCandidate);
251 const float phi = decodePhi(*inputCandidate);
252 int sourceIndex{-1};
253 int station{-1};
254 if (inputCandidate->tcId() != 0U) {
255 sourceIndex =
256 sourceCandidateIndex(*inputCandidate, *candidates, eta, phi);
257 if (sourceIndex < 0) {
258 ATH_MSG_ERROR("Final TGC candidate " << inputIndex
259 << (sourceIndex == -2 ? " has ambiguous"
260 : " has no")
261 << " pre-Inner source candidate");
262 return StatusCode::FAILURE;
263 }
264 station = pivotStation(
265 (*candidates)[static_cast<std::size_t>(sourceIndex)]
266 .positionStationMask);
267 if (station < 0) {
268 ATH_MSG_ERROR("Final TGC candidate " << inputIndex
269 << " has no valid reference station");
270 return StatusCode::FAILURE;
271 }
272 }
273
274 output->finalCandidates.sourceCandidateIndex.emplace_back(sourceIndex);
275 output->finalCandidates.referenceStation.emplace_back(
276 station < 0
277 ? static_cast<std::uint8_t>(TgcL0ValidationStation::Invalid)
278 : static_cast<std::uint8_t>(station));
279 output->finalCandidates.subdetectorId.emplace_back(
280 inputCandidate->subdetectorId());
281 output->finalCandidates.triggerSector.emplace_back(
282 inputCandidate->sectorId());
283 output->finalCandidates.bcTag.emplace_back(inputCandidate->bcTag());
284 output->finalCandidates.tcId.emplace_back(inputCandidate->tcId());
285 output->finalCandidates.rawEta.emplace_back(inputCandidate->eta());
286 output->finalCandidates.rawPhi.emplace_back(inputCandidate->phi());
287 output->finalCandidates.eta.emplace_back(eta);
288 output->finalCandidates.phi.emplace_back(phi);
289 output->finalCandidates.ptCode.emplace_back(inputCandidate->pt());
290 output->finalCandidates.pt.emplace_back(inputCandidate->ptValueGeV());
291 output->finalCandidates.threshold.emplace_back(
292 inputCandidate->threshold());
293 output->finalCandidates.charge.emplace_back(
294 inputCandidate->candCharge() != 0U ? 1 : -1);
295 output->finalCandidates.innerCoincidence.emplace_back(
296 inputCandidate->hasInnerCoincidence() ? 1U : 0U);
297 output->finalCandidates.goodMagneticField.emplace_back(
298 inputCandidate->goodMagneticField() ? 1U : 0U);
299 output->finalCandidates.truthIndex.emplace_back(-1);
300 output->finalCandidates.truthMatchDeltaR.emplace_back(
302 }
303 }
304
307 m_sectorLogicKey, ctx};
308 if (!sectorLogicCandidates.isValid()) {
309 ATH_MSG_ERROR("Failed to retrieve " << m_sectorLogicKey.fullKey());
310 return StatusCode::FAILURE;
311 }
312 if (!finalCandidateContainer)[[unlikely]]{
313 ATH_MSG_ERROR("finalCandidateContainer is null.");
314 return StatusCode::FAILURE;
315 }
316 std::size_t sectorLogicIndex{0U};
317 for (std::size_t inputIndex = 0U;
318 inputIndex < finalCandidateContainer->size();
319 ++inputIndex) {
320 const xAOD::TGCCandData* inputCandidate =
321 (*finalCandidateContainer)[inputIndex];
322 if (inputCandidate->tcId() == 0U) continue;
323 if (sectorLogicIndex >= sectorLogicCandidates->size()) {
325 "Fewer Sector Logic candidates than non-empty TGC candidates");
326 return StatusCode::FAILURE;
327 }
328 const xAOD::SectorLogicCandData* sectorLogicCandidate =
329 (*sectorLogicCandidates)[sectorLogicIndex];
330 if (sectorLogicCandidate == nullptr) {
331 ATH_MSG_ERROR("Null Sector Logic candidate at index "
332 << sectorLogicIndex);
333 return StatusCode::FAILURE;
334 }
335 if (!matchesTgcFields(*inputCandidate, *sectorLogicCandidate)) {
336 ATH_MSG_ERROR("Sector Logic candidate " << sectorLogicIndex
337 << " does not preserve TGC candidate " << inputIndex);
338 return StatusCode::FAILURE;
339 }
340 if (!hasOnlyTgcFields(*sectorLogicCandidate)) {
341 ATH_MSG_ERROR("Sector Logic candidate " << sectorLogicIndex
342 << " contains non-TGC payload");
343 return StatusCode::FAILURE;
344 }
345 if (sectorLogicCandidate->boardID() != 0U ||
346 sectorLogicCandidate->fiberID() != 0U ||
347 sectorLogicCandidate->BCIDOffset() != 0 ||
348 sectorLogicCandidate->veto() != 0U) {
349 ATH_MSG_ERROR("Sector Logic candidate " << sectorLogicIndex
350 << " has unexpected placeholder metadata");
351 return StatusCode::FAILURE;
352 }
353 output->sectorLogic.inputCandidateIndex.emplace_back(
354 static_cast<std::uint32_t>(inputIndex));
355 output->sectorLogic.candWord.emplace_back(
356 sectorLogicCandidate->candWord());
357 output->sectorLogic.candExtraWord.emplace_back(
358 sectorLogicCandidate->candExtraWord());
359 output->sectorLogic.boardId.emplace_back(sectorLogicCandidate->boardID());
360 output->sectorLogic.fiberId.emplace_back(sectorLogicCandidate->fiberID());
361 output->sectorLogic.bcidOffset.emplace_back(
362 sectorLogicCandidate->BCIDOffset());
363 output->sectorLogic.veto.emplace_back(sectorLogicCandidate->veto());
364 ++sectorLogicIndex;
365 }
366 if (sectorLogicIndex != sectorLogicCandidates->size()) {
368 "More Sector Logic candidates than non-empty TGC candidates");
369 return StatusCode::FAILURE;
370 }
371 }
372
373 const auto processParticle = [&](const auto& particle) -> StatusCode {
374 if (!particle || std::abs(particle->pdg_id()) != 13 ||
375 particle->status() != m_requiredTruthStatus.value() ||
376 std::abs(HepMC::barcode(particle)) > m_maxAbsBarcode.value()) {
377 return StatusCode::SUCCESS;
378 }
379
380 const auto& momentum = particle->momentum();
381 const double pt = momentum.perp();
382 const double eta = momentum.eta();
383 const double phi = momentum.phi();
384 if (pt < m_minPt.value() || std::abs(eta) < m_minAbsEta.value() ||
385 std::abs(eta) > m_maxAbsEta.value() || !std::isfinite(phi)) {
386 return StatusCode::SUCCESS;
387 }
388
389 const int pdgId = particle->pdg_id();
390 const double theta = 2.0 * std::atan(std::exp(-eta));
391 const double momentumMagnitude = pt * std::cosh(eta);
392 if (momentumMagnitude <= 0.0 || !std::isfinite(theta)) {
393 return StatusCode::SUCCESS;
394 }
395
396 const double charge = pdgId == 13 ? -1.0 : 1.0;
397 const Trk::PerigeeSurface perigeeSurface{Amg::Vector3D{0.0, 0.0, 0.0}};
398 const Trk::Perigee perigee{0.0, 0.0, phi, theta,
399 charge / momentumMagnitude, perigeeSurface};
400
401 StationPositions positions{};
402 std::uint8_t stationMask{0U};
403 const std::vector<double>& stationAbsZ = m_stationAbsZ.value();
404 for (std::size_t station = 0U; station < positions.size(); ++station) {
405 const double z = eta >= 0.0 ? stationAbsZ[station] : -stationAbsZ[station];
406 Amg::Transform3D transform = Amg::Transform3D::Identity();
407 transform.translation().z() = z;
408 const Trk::DiscSurface disc{transform, m_validationMinR.value(),
409 m_validationMaxR.value()};
410 const Trk::BoundaryCheck boundaryCheck{true};
411 const auto extrapolated = m_extrapolator->extrapolate(
412 ctx, perigee, disc, Trk::alongMomentum, boundaryCheck, Trk::muon);
413 if (!extrapolated) continue;
414 const Amg::Vector3D& position = extrapolated->position();
415 if (!std::isfinite(position.eta()) || !std::isfinite(position.phi()) ||
416 std::abs(position.z() - z) > m_validationPlaneToleranceZ.value()) {
417 continue;
418 }
419 positions[station] = StationPosition{
420 true, static_cast<float>(position.eta()),
421 static_cast<float>(xAOD::P4Helpers::deltaPhi(position.phi(), 0.))};
422 stationMask |= stationBit(station);
423 }
424
425 output->truth.pdgId.emplace_back(pdgId);
426 output->truth.barcode.emplace_back(HepMC::barcode(particle));
427 output->truth.pt.emplace_back(static_cast<float>(pt));
428 output->truth.eta.emplace_back(static_cast<float>(eta));
429 output->truth.phi.emplace_back(static_cast<float>(phi));
430 output->truth.charge.emplace_back(static_cast<float>(charge));
431 output->truth.extrapolatedStationMask.emplace_back(stationMask);
432 output->truth.m1Eta.emplace_back(positions[0].eta);
433 output->truth.m1Phi.emplace_back(positions[0].phi);
434 output->truth.m2Eta.emplace_back(positions[1].eta);
435 output->truth.m2Phi.emplace_back(positions[1].phi);
436 output->truth.m3Eta.emplace_back(positions[2].eta);
437 output->truth.m3Phi.emplace_back(positions[2].phi);
438 output->truth.matched.emplace_back(0U);
439 output->truth.matchedCandidateIndex.emplace_back(-1);
440 output->truth.matchMeanDeltaR.emplace_back(TgcL0ValidationInvalidValue);
441 output->truth.unmatchedReason.emplace_back(
442 stationMask == 0x7U
443 ? static_cast<std::uint8_t>(
445 : static_cast<std::uint8_t>(
447 output->truth.finalCandidateMatched.emplace_back(0U);
448 output->truth.matchedFinalCandidateIndex.emplace_back(-1);
449 output->truth.finalCandidateMatchDeltaR.emplace_back(
451 output->truth.finalCandidateUnmatchedReason.emplace_back(
452 stationMask == 0x7U
453 ? static_cast<std::uint8_t>(
455 : static_cast<std::uint8_t>(
457 output->truth.wireSegmentMatched.emplace_back(0U);
458 output->truth.stripSegmentMatched.emplace_back(0U);
459 output->truth.matchedWireSegmentIndex.emplace_back(-1);
460 output->truth.matchedStripSegmentIndex.emplace_back(-1);
461 output->truth.wireSegmentMatchResidual.emplace_back(
463 output->truth.stripSegmentMatchResidual.emplace_back(
465 return StatusCode::SUCCESS;
466 };
467
468 for (const HepMC::GenEvent* event : *truthEvents) {
469 if (event == nullptr) continue;
470#if __has_include("HepMC3/GenEvent.h")
471 for (const auto& particle : event->particles()) {
472 ATH_CHECK(processParticle(particle));
473 }
474#else
475 for (auto particle = event->particles_begin();
476 particle != event->particles_end(); ++particle) {
477 ATH_CHECK(processParticle(*particle));
478 }
479#endif
480 }
481
482 for (const TgcL0Candidate& candidate : *candidates) {
483 output->candidates.subdetectorId.emplace_back(candidate.subdetectorId);
484 output->candidates.triggerSector.emplace_back(candidate.sectorId);
485 output->candidates.readoutSector.emplace_back(candidate.readoutSector);
486 output->candidates.bcTag.emplace_back(candidate.bcTag);
487 output->candidates.stationMask.emplace_back(candidate.positionStationMask);
488 output->candidates.wireStationMask.emplace_back(candidate.wireStationMask);
489 output->candidates.stripStationMask.emplace_back(candidate.stripStationMask);
490 output->candidates.eta.emplace_back(candidate.eta);
491 output->candidates.phi.emplace_back(candidate.phi);
492 output->candidates.deltaTheta.emplace_back(candidate.deltaTheta);
493 output->candidates.deltaPhi.emplace_back(candidate.deltaPhi);
494 output->candidates.pt.emplace_back(candidate.preInnerCoincidencePt);
495 output->candidates.threshold.emplace_back(
496 candidate.preInnerCoincidenceThreshold);
497 output->candidates.charge.emplace_back(candidate.charge);
498 output->candidates.goodMagneticField.emplace_back(
499 candidate.goodMagneticField ? 1U : 0U);
500 output->candidates.truthIndex.emplace_back(-1);
501 }
502
503 std::vector<CandidateMatch> candidateMatches;
504 std::vector<bool> hasCandidateInWindow(output->truth.pt.size(), false);
505 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
506 if (output->truth.extrapolatedStationMask[truth] != 0x7U) continue;
507 const StationPositions positions = truthPositions(*output, truth);
508 for (std::size_t candidate = 0U; candidate < candidates->size();
509 ++candidate) {
510 const TgcL0Candidate& inputCandidate = (*candidates)[candidate];
511 if (m_requiredBcTagMask.value() != 0U &&
512 (inputCandidate.bcTag & m_requiredBcTagMask.value()) == 0U) {
513 continue;
514 }
515 if (output->truth.eta[truth] * inputCandidate.eta < 0.F) continue;
516 const float residual = meanDeltaR(inputCandidate, positions);
517 if (!std::isfinite(residual) || residual > m_maxMeanDeltaR.value()) continue;
518 hasCandidateInWindow[truth] = true;
519 candidateMatches.push_back({residual, truth, candidate});
520 }
521 }
522 std::stable_sort(candidateMatches.begin(), candidateMatches.end(),
523 [](const CandidateMatch& lhs, const CandidateMatch& rhs) {
524 if (lhs.residual != rhs.residual) {
525 return lhs.residual < rhs.residual;
526 }
527 if (lhs.truth != rhs.truth) return lhs.truth < rhs.truth;
528 return lhs.candidate < rhs.candidate;
529 });
530 for (const CandidateMatch& match : candidateMatches) {
531 if (output->truth.matchedCandidateIndex[match.truth] >= 0 ||
532 output->candidates.truthIndex[match.candidate] >= 0) {
533 continue;
534 }
535 output->truth.matched[match.truth] = 1U;
536 output->truth.matchedCandidateIndex[match.truth] =
537 static_cast<int>(match.candidate);
538 output->truth.matchMeanDeltaR[match.truth] = match.residual;
539 output->truth.unmatchedReason[match.truth] =
541 output->candidates.truthIndex[match.candidate] =
542 static_cast<int>(match.truth);
543 }
544 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
545 if (output->truth.matched[truth] != 0U ||
546 output->truth.extrapolatedStationMask[truth] != 0x7U) {
547 continue;
548 }
549 output->truth.unmatchedReason[truth] = static_cast<std::uint8_t>(
550 hasCandidateInWindow[truth]
553 }
554
555 if (m_validateFinalCandidates) {
556 std::vector<FinalCandidateMatch> finalCandidateMatches;
557 std::vector<bool> hasFinalCandidateInWindow(output->truth.pt.size(),
558 false);
559 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
560 if (output->truth.extrapolatedStationMask[truth] != 0x7U) continue;
561 const StationPositions positions = truthPositions(*output, truth);
562 for (std::size_t candidate = 0U;
563 candidate < output->finalCandidates.tcId.size(); ++candidate) {
564 if (output->finalCandidates.tcId[candidate] == 0U) continue;
565 if (m_requiredBcTagMask.value() != 0U &&
566 (output->finalCandidates.bcTag[candidate] &
567 m_requiredBcTagMask.value()) == 0U) {
568 continue;
569 }
570 if (output->truth.eta[truth] *
571 output->finalCandidates.eta[candidate] <
572 0.F) {
573 continue;
574 }
575 const std::size_t station =
576 output->finalCandidates.referenceStation[candidate];
577 if (station >= positions.size() || !positions[station].valid) continue;
578 const float deltaEta = output->finalCandidates.eta[candidate] -
579 positions[station].eta;
580 const float deltaPhi = static_cast<float>(
581 xAOD::P4Helpers::deltaPhi(output->finalCandidates.phi[candidate],
582 positions[station].phi));
583 const float residual = std::hypot(deltaEta, deltaPhi);
584 if (residual > m_maxFinalCandidateDeltaR.value()) {
585 continue;
586 }
587 hasFinalCandidateInWindow[truth] = true;
588 finalCandidateMatches.push_back({residual, truth, candidate});
589 }
590 }
592 finalCandidateMatches.begin(), finalCandidateMatches.end(),
593 [](const FinalCandidateMatch& lhs, const FinalCandidateMatch& rhs) {
594 if (lhs.residual != rhs.residual) {
595 return lhs.residual < rhs.residual;
596 }
597 if (lhs.truth != rhs.truth) return lhs.truth < rhs.truth;
598 return lhs.candidate < rhs.candidate;
599 });
600 for (const FinalCandidateMatch& match : finalCandidateMatches) {
601 if (output->truth.matchedFinalCandidateIndex[match.truth] >= 0 ||
602 output->finalCandidates.truthIndex[match.candidate] >= 0) {
603 continue;
604 }
605 output->truth.finalCandidateMatched[match.truth] = 1U;
606 output->truth.matchedFinalCandidateIndex[match.truth] =
607 static_cast<int>(match.candidate);
608 output->truth.finalCandidateMatchDeltaR[match.truth] = match.residual;
609 output->truth.finalCandidateUnmatchedReason[match.truth] =
611 output->finalCandidates.truthIndex[match.candidate] =
612 static_cast<int>(match.truth);
613 output->finalCandidates.truthMatchDeltaR[match.candidate] =
614 match.residual;
615 }
616 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
617 if (output->truth.finalCandidateMatched[truth] != 0U ||
618 output->truth.extrapolatedStationMask[truth] != 0x7U) {
619 continue;
620 }
621 output->truth.finalCandidateUnmatchedReason[truth] =
622 static_cast<std::uint8_t>(
623 hasFinalCandidateInWindow[truth]
626 }
627 }
628
629 for (const TgcL0Segment& segment : *segments) {
630 output->segments.subdetectorId.emplace_back(segment.subdetectorId);
631 output->segments.triggerSector.emplace_back(segment.triggerSector);
632 output->segments.bcTag.emplace_back(segment.bcTag);
633 output->segments.projection.emplace_back(
634 static_cast<std::uint8_t>(segment.projection));
635 output->segments.stationMask.emplace_back(segment.stationMask);
636 output->segments.summedQuality.emplace_back(segment.summedQuality);
637 output->segments.nStations.emplace_back(segment.nStations);
638 output->segments.eta.emplace_back(segment.eta);
639 output->segments.phi.emplace_back(segment.phi);
640 output->segments.residual.emplace_back(segment.residual);
641 output->segments.outputResidual.emplace_back(segment.outputResidual);
642 output->segments.consistency.emplace_back(segment.consistency);
643 output->segments.pivotChannel.emplace_back(segment.pivotChannel);
644 output->segments.truthIndex.emplace_back(-1);
645 output->segments.truthMatchResidual.emplace_back(
646 TgcL0ValidationInvalidValue);
647 }
648
649 const auto matchProjection = [&](const TgcL0ValidationProjection projection,
650 const float maximumResidual) {
651 const auto projectionValue = static_cast<std::uint8_t>(projection);
652 std::vector<SegmentMatch> possibleMatches;
653 for (std::size_t truth = 0U; truth < output->truth.pt.size(); ++truth) {
654 for (std::size_t segment = 0U;
655 segment < output->segments.projection.size(); ++segment) {
656 if (output->segments.projection[segment] != projectionValue) continue;
657 if (m_requiredBcTagMask.value() != 0U &&
658 (output->segments.bcTag[segment] &
659 m_requiredBcTagMask.value()) == 0U) {
660 continue;
661 }
662 if (output->truth.eta[truth] * output->segments.eta[segment] < 0.F) {
663 continue;
664 }
665 const int station = pivotStation(output->segments.stationMask[segment]);
666 if (station < 0 ||
667 (output->truth.extrapolatedStationMask[truth] &
668 stationBit(static_cast<std::size_t>(station))) == 0U) {
669 continue;
670 }
671 const std::array<float, 3> truthEta{
672 output->truth.m1Eta[truth], output->truth.m2Eta[truth],
673 output->truth.m3Eta[truth]};
674 const std::array<float, 3> truthPhi{
675 output->truth.m1Phi[truth], output->truth.m2Phi[truth],
676 output->truth.m3Phi[truth]};
677 const float residual =
678 projection == TgcL0ValidationProjection::Wire
679 ? std::abs(output->segments.eta[segment] - truthEta[station])
680 : std::abs(static_cast<float>(xAOD::P4Helpers::deltaPhi(
681 output->segments.phi[segment], truthPhi[station])));
682 if (std::isfinite(residual) && residual <= maximumResidual) {
683 possibleMatches.push_back({residual, truth, segment});
684 }
685 }
686 }
687 std::stable_sort(possibleMatches.begin(), possibleMatches.end(),
688 [](const SegmentMatch& lhs, const SegmentMatch& rhs) {
689 if (lhs.residual != rhs.residual) {
690 return lhs.residual < rhs.residual;
691 }
692 if (lhs.truth != rhs.truth) return lhs.truth < rhs.truth;
693 return lhs.segment < rhs.segment;
694 });
695 for (const SegmentMatch& match : possibleMatches) {
696 int& truthSegment =
697 projection == TgcL0ValidationProjection::Wire
698 ? output->truth.matchedWireSegmentIndex[match.truth]
699 : output->truth.matchedStripSegmentIndex[match.truth];
700 if (truthSegment >= 0 ||
701 output->segments.truthIndex[match.segment] >= 0) {
702 continue;
703 }
704 truthSegment = static_cast<int>(match.segment);
705 output->segments.truthIndex[match.segment] =
706 static_cast<int>(match.truth);
707 output->segments.truthMatchResidual[match.segment] = match.residual;
708 if (projection == TgcL0ValidationProjection::Wire) {
709 output->truth.wireSegmentMatched[match.truth] = 1U;
710 output->truth.wireSegmentMatchResidual[match.truth] = match.residual;
711 } else {
712 output->truth.stripSegmentMatched[match.truth] = 1U;
713 output->truth.stripSegmentMatchResidual[match.truth] = match.residual;
714 }
715 }
716 };
717
718 matchProjection(TgcL0ValidationProjection::Wire,
719 m_maxWireSegmentDeltaEta.value());
720 matchProjection(TgcL0ValidationProjection::Strip,
721 m_maxStripSegmentDeltaPhi.value());
722
723 const TgcL0ValidationCheckResult check = checkTgcL0ValidationEvent(*output);
724 if (!check.valid) {
725 ATH_MSG_ERROR("Refusing to record inconsistent validation data: "
726 << check.message);
727 return StatusCode::FAILURE;
728 }
729
730 SG::WriteHandle<TgcL0ValidationEvent> outputHandle{m_outputKey, ctx};
731 ATH_CHECK(outputHandle.record(std::move(output)));
732 return StatusCode::SUCCESS;
733}
734
735} // 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:1003
if(pathvar)
Handle class for reading from StoreGate.
Handle class for recording to StoreGate.
#define z
size_type size() const noexcept
Returns the number of elements in the collection.
Gaudi::Property< float > m_maxMeanDeltaR
Gaudi::Property< int > m_requiredTruthStatus
SG::WriteHandleKey< TgcL0ValidationEvent > m_outputKey
Gaudi::Property< std::uint16_t > m_requiredBcTagMask
Gaudi::Property< std::vector< double > > m_stationAbsZ
Gaudi::Property< double > m_validationMinR
SG::ReadHandleKey< TgcL0CandidateContainer > m_candidateKey
SG::ReadHandleKey< xAOD::TGCCandDataContainer > m_finalCandidateKey
Gaudi::Property< bool > m_validateSectorLogic
Gaudi::Property< double > m_validationMaxR
SG::ReadHandleKey< TgcL0SegmentContainer > m_segmentKey
StatusCode execute(const EventContext &ctx) const override
Gaudi::Property< double > m_minAbsEta
Gaudi::Property< bool > m_validateFinalCandidates
Gaudi::Property< double > m_maxAbsEta
Gaudi::Property< double > m_validationPlaneToleranceZ
ToolHandle< Trk::IExtrapolator > m_extrapolator
SG::ReadHandleKey< xAOD::SectorLogicCandDataContainer > m_sectorLogicKey
SG::ReadHandleKey< McEventCollection > m_truthEventKey
virtual bool isValid() override final
Can the handle be successfully dereferenced?
const_pointer_type cptr()
Dereference the pointer.
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.
uint16_t sectorId() const
Retrieve the sector id.
uint16_t eta() const
Retrieve the eta.
uint8_t candCharge() const
Retrieve the candidate charge.
static constexpr uint16_t phiBitRange()
static constexpr float etaRange()
static constexpr float phiRange()
uint8_t threshold() const
Retrieve the threshold.
uint16_t bcTag() const
Retrieve the bunch crossing tag.
uint8_t pt() const
Retrieve the encoded candidate pT.
uint16_t phi() const
Retrieve the phi.
uint16_t subdetectorId() const
Retrieve the sub detector id.
static constexpr uint16_t etaBitRange()
uint16_t fiberID() const
Retrieve the fiber ID.
unsigned short veto() const
Retrieve the veto flag.
uint32_t exotTrig() const
Retrieve the exotic trigger.
uint32_t mdtSegQual() const
Retrieve the MDT segment quality flag.
uint32_t candWord() const
Retrieve the candidate word (first 32-bits).
int BCIDOffset() const
Retrieve the bunch crossing identifier offset.
uint16_t boardID() const
Retrieve the board ID.
uint32_t isMDT() const
Retrieve whether the candidate was processed by MDTTP.
uint32_t candExtraWord() const
Retrieve the candidate extra word (second 32-bits).
uint32_t numMDTSeg() const
Retrieve the number of MDT segments.
uint32_t tileCoin() const
Retrieve the TILE coincidence presence.
uint32_t mdtFlag() const
Retrieve the MDT flag.
float deltaTheta() const
Retrieve the delta theta value wrt vector from IP to segment position.
bool goodMagneticField() const
Check whether the candidate is in a good magnetic-field region.
bool hasInnerCoincidence() const
Check whether the candidate passed Inner Coincidence.
float deltaPhi() const
Retrieve the delta phi wrt vector from IP to segment position.
float ptValueGeV() const
Retrieve the inherited encoded pT value in GeV.
uint8_t tcId() const
Retrieve the trigger-candidate identifier.
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.
std::vector< TgcL0Candidate > TgcL0CandidateContainer
Event-local candidate collection.
static constexpr std::uint8_t TgcL0ValidationNoUnmatchedReason
Sentinel indicating that a matched truth object has no failure code.
P4Helpers provides static helper functions for kinematic calculation on objects deriving from I4Momen...
Definition P4Helpers.h:32
double deltaEta(const I4Momentum &p1, const I4Momentum &p2)
Computes efficiently .
Definition P4Helpers.h:66
@ alongMomentum
ParametersT< TrackParametersDim, Charged, PerigeeSurface > Perigee
Definition index.py:1
output
Definition merge.py:16
STL namespace.
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[
ICaloAffectedTool is abstract interface for tools checking if 4 mom is in calo affected region.
SectorLogicCandData_v1 SectorLogicCandData
#define unlikely(x)
Event-local candidate used by the TGC simulation tools.
std::uint8_t positionStationMask
Alias of stationMask retained for explicit validation/debug use.
float m1Eta
Reconstructed station positions used only by validation/debug code.
std::uint16_t bcTag
Bunch-crossing tag.
float eta
Pseudorapidity at the TGC pivot plane.
ROOT-independent validation data for one event.