ATLAS Offline Software
Loading...
Searching...
No Matches
MuonSelectionTool.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
11
12namespace {
13 static constexpr double const MeVtoGeV = 1. / 1000.;
14 // This function defines the order of chamber indices for the low-pT MVA,
15 // i.e. defining the meaning of "the first two segments", which are used in the BDT
16 std::vector<int> initializeChamberIdxOrder() {
17 // This vector defines the order. The current order follows the order of the enum "ChIndex"
18 // except for the CSCs, which appear first. Since the order is not strictly innermost-to-outermost,
19 // a reordering could be considered for a rel. 22 retuning, which can then easily be achieved by
20 // swapping around the elements in the below initialization.
22 using namespace Muon::MuonStationIndex;
23 const std::vector<ChIdx> orderedChIndices{
24 ChIdx::CSS, ChIdx::CSL, ChIdx::BIS, ChIdx::BIL,
25 ChIdx::BMS, ChIdx::BML, ChIdx::BOS, ChIdx::BOL,
26 ChIdx::BEE, ChIdx::EIS, ChIdx::EIL, ChIdx::EMS,
27 ChIdx::EML, ChIdx::EOS, ChIdx::EOL, ChIdx::EES,
28 ChIdx::EEL};
29
30 // This vector will hold the equivalent information in a form that can be efficiently accessed in the
31 // below function "chamberIndexCompare", using the chamber index as the vector index
32 std::vector<int> chamberIndexOrder(orderedChIndices.size());
33
34 for (unsigned int i = 0; i < orderedChIndices.size(); i++) {
35 chamberIndexOrder[toInt(orderedChIndices[i])] = i;
36 }
37 return chamberIndexOrder;
38 }
39
40 // This is the comparison function for the sorting of segments according to the chamber index
41 bool chamberIndexCompare(const xAOD::MuonSegment* first, const xAOD::MuonSegment* second) {
42 static const std::vector<int> chamberIndexOrder = initializeChamberIdxOrder();
43 return (chamberIndexOrder[toInt(first->chamberIndex())] <
44 chamberIndexOrder[toInt(second->chamberIndex())]);
45 }
46
47 static const SG::AuxElement::Accessor<float> mePt_acc("MuonSpectrometerPt");
48 static const SG::AuxElement::Accessor<float> idPt_acc("InnerDetectorPt");
49 static const SG::AuxElement::Accessor<uint8_t> eta1stgchits_acc("etaLayer1STGCHits");
50 static const SG::AuxElement::Accessor<uint8_t> eta2stgchits_acc("etaLayer2STGCHits");
51 static const SG::AuxElement::Accessor<uint8_t> mmhits_acc("MMHits");
52} // namespace
53
54namespace CP {
55
56 MuonSelectionTool::MuonSelectionTool(const std::string& tool_name):
57 asg::AsgTool(tool_name){}
58
60
62
63 // Greet the user:
64 ATH_MSG_INFO("Initialising...");
65
66 m_geoOnTheFly ? ATH_MSG_INFO("Is Run-3 geometry: On-the-fly determination. THIS OPTION IS DEPRECATED AND WILL BE REMOVED SOON. Use IsRun3Geo property instead.")
67 : ATH_MSG_INFO("Is Run-3 geometry: " << m_isRun3.value());
68 ATH_MSG_INFO("Maximum muon |eta|: " << m_maxEta.value());
69 ATH_MSG_INFO("Muon quality: "<< m_quality.value());
70 if (m_toroidOff) ATH_MSG_INFO("!! CONFIGURED FOR TOROID-OFF COLLISIONS !!");
71 if (m_SctCutOff) ATH_MSG_WARNING("!! SWITCHING SCT REQUIREMENTS OFF !! FOR DEVELOPMENT USE ONLY !!");
72 if (m_PixCutOff) ATH_MSG_WARNING("!! SWITCHING PIXEL REQUIREMENTS OFF !! FOR DEVELOPMENT USE ONLY !!");
73 if (m_SiHolesCutOff) ATH_MSG_WARNING("!! SWITCHING SILICON HOLES REQUIREMENTS OFF !! FOR DEVELOPMENT USE ONLY !!");
74 if (m_custom_dir != "")
75 ATH_MSG_WARNING("!! SETTING UP WITH USER SPECIFIED INPUT LOCATION \"" << m_custom_dir << "\"!! FOR DEVELOPMENT USE ONLY !! ");
76 if (!m_useAllAuthors)
78 "Not using allAuthors variable as currently missing in many derivations; LowPtEfficiency working point will always return "
79 "false, but this is expected at the moment. Have a look here: "
80 "https://twiki.cern.ch/twiki/bin/view/Atlas/MuonSelectionToolR21#New_LowPtEfficiency_working_poin");
81
82 // Print message to ensure that users excluding 2-station muons in the high-pT selection are aware of this
84 ATH_MSG_INFO("You have opted to select only 3-station muons in the high-pT selection! "
85 << "Please feed 'HighPt3Layers' to the 'WorkingPoint' property to retrieve the appropriate scale-factors");
86
87 // Only an MVA-based selection is defined for segment-tagged muons for the Low-pT working point
89 ATH_MSG_WARNING("No cut-based selection is defined for segment-tagged muons in the Low-pT working point. "
90 << "Please set UseMVALowPt=true if you want to try the UseSegmentTaggedLowPt=true option.");
92 }
93 if (m_useLRT) {
94 ATH_MSG_INFO("MuonSelectionTool will assume both Standard and LRT Muons are being used, and that the necessary information is available to identify the type (standard or LRT).");
95 if (m_quality!=1) ATH_MSG_WARNING("Currently, only Medium quality is supported for LRT muons. Your chosen WP will be applied (w/o ID cuts), but no recommendations are available for this quality.");
96 }
97
98 // Set up the TAccept object:
99 m_acceptInfo.addCut("Eta", "Selection of muons according to their pseudorapidity");
100 m_acceptInfo.addCut("IDHits", "Selection of muons according to whether they passed the MCP ID Hit cuts");
101 m_acceptInfo.addCut("Preselection", "Selection of muons according to their type/author");
102 m_acceptInfo.addCut("Quality", "Selection of muons according to their tightness");
103 // Sanity check
104 if (m_quality > 5) {
106 "Invalid quality (i.e. selection WP) set: "
107 << m_quality
108 << " - it must be an integer between 0 and 5! (0=Tight, 1=Medium, 2=Loose, 3=Veryloose, 4=HighPt, 5=LowPtEfficiency)");
109 return StatusCode::FAILURE;
110 }
111 if (m_quality == 5 && !m_useAllAuthors) {
112 ATH_MSG_ERROR("Cannot use lowPt working point if allAuthors is not available!");
113 return StatusCode::FAILURE;
114 }
115
117 ATH_MSG_FATAL("CaloScoreWP property must be set to 1, 2, 3 or 4");
118 return StatusCode::FAILURE;
119 }
120
121 // Load Tight WP cut-map
122 ATH_MSG_INFO("Initialising tight working point histograms...");
123 std::string tightWP_rootFile_fullPath;
124 if (!m_custom_dir.empty()) {
125 tightWP_rootFile_fullPath = PathResolverFindCalibFile(m_custom_dir + "/muonSelection_tightWPHisto.root");
126 } else {
127 tightWP_rootFile_fullPath = PathResolverFindCalibFile(
128 Form("MuonSelectorTools/%s/muonSelection_tightWPHisto.root", m_calibration_version.value().c_str()));
129 }
130
131 ATH_MSG_INFO("Reading muon tight working point histograms from " << tightWP_rootFile_fullPath);
132 //
133 std::unique_ptr<TFile> file(TFile::Open(tightWP_rootFile_fullPath.c_str(), "READ"));
134
135 if (!file->IsOpen()) {
136 ATH_MSG_ERROR("Cannot read tight working point file from " << tightWP_rootFile_fullPath);
137 return StatusCode::FAILURE;
138 }
139
140 // Retrieve all the relevant histograms
141 ATH_CHECK(getHist(file.get(), "tightWP_lowPt_rhoCuts", m_tightWP_lowPt_rhoCuts));
142 ATH_CHECK(getHist(file.get(), "tightWP_lowPt_qOverPCuts", m_tightWP_lowPt_qOverPCuts));
143 ATH_CHECK(getHist(file.get(), "tightWP_mediumPt_rhoCuts", m_tightWP_mediumPt_rhoCuts));
144 ATH_CHECK(getHist(file.get(), "tightWP_highPt_rhoCuts", m_tightWP_highPt_rhoCuts));
145 //
146 file->Close();
147
148 // Read bad muon veto efficiency histograms
149 std::string BMVcutFile_fullPath = PathResolverFindCalibFile(m_BMVcutFile);
150
151 ATH_MSG_INFO("Reading bad muon veto cut functions from " << BMVcutFile_fullPath);
152 //
153 std::unique_ptr<TFile> BMVfile(TFile::Open(BMVcutFile_fullPath.c_str(), "READ"));
154
155 if (!BMVfile->IsOpen()) {
156 ATH_MSG_ERROR("Cannot read bad muon veto cut function file from " << BMVcutFile_fullPath);
157 return StatusCode::FAILURE;
158 }
159
160 m_BMVcutFunction_barrel = std::unique_ptr<TF1>((TF1*)BMVfile->Get("BMVcutFunction_barrel"));
161 m_BMVcutFunction_endcap = std::unique_ptr<TF1>((TF1*)BMVfile->Get("BMVcutFunction_endcap"));
162
163 BMVfile->Close();
164
166 ATH_MSG_ERROR("Cannot read bad muon veto cut functions");
167 return StatusCode::FAILURE;
168 }
169
170 if (m_useMVALowPt) {
171 if (m_isRun3) {
172 // Helper lambda to load BDT model and scaler
173 auto loadLowPtMVABDTModel = [&](const std::string& calibFile,
174 std::unique_ptr<MVAUtils::BDT>& bdt,
175 std::vector<double>& means,
176 std::vector<double>& scales) -> StatusCode {
177 const std::string modelFile = PathResolverFindCalibFile(calibFile);
178 std::unique_ptr<TFile> rootFile(TFile::Open(modelFile.c_str(), "READ"));
179
180 if (!rootFile || rootFile->IsZombie()) {
181 ATH_MSG_ERROR("Failed to open model file for Run-3 LowPtMVA: " << modelFile);
182 return StatusCode::FAILURE;
183 }
184
185 // Load BDT model
186 auto* bdt_tree = static_cast<TTree*>(rootFile->Get("xgboost"));
187 bdt = std::make_unique<MVAUtils::BDT>(bdt_tree);
188
189 // Load scaler
190 std::unique_ptr<TTree> scaler_tree(static_cast<TTree*>(rootFile->Get("scaler")));
191 std::vector<double>* mean_ptr = nullptr;
192 std::vector<double>* scale_ptr = nullptr;
193 scaler_tree->SetBranchAddress("mean", &mean_ptr);
194 scaler_tree->SetBranchAddress("scale", &scale_ptr);
195
196 for (Long64_t i = 0; i < scaler_tree->GetEntries(); ++i) {
197 scaler_tree->GetEntry(i);
198 for (size_t j = 0; j < mean_ptr->size(); ++j) {
199 means.push_back((*mean_ptr)[j]);
200 scales.push_back((*scale_ptr)[j]);
201 }
202 }
203 return StatusCode::SUCCESS;
204 };
205
206 // Load MuidCO model for Run-3 LowPtMVA
207 ATH_CHECK(loadLowPtMVABDTModel("MuonSelectorTools/260130_LowPtMVA_r24run3/xgb_lowPtMVA_MuidCO_wScaler.root",
209
210 // Load MuGirl model for Run-3 LowPtMVA
211 ATH_CHECK(loadLowPtMVABDTModel("MuonSelectorTools/260130_LowPtMVA_r24run3/xgb_lowPtMVA_MuGirl_wScaler.root",
213 }
214 else{
215 // Set up TMVA readers for MVA-based low-pT working point
216 // E and O refer to even and odd event numbers to avoid applying the MVA on events used for training
217 TString weightPath_EVEN_MuidCB = PathResolverFindCalibFile(m_MVAreaderFile_EVEN_MuidCB);
218 TString weightPath_ODD_MuidCB = PathResolverFindCalibFile(m_MVAreaderFile_ODD_MuidCB);
219 TString weightPath_EVEN_MuGirl = PathResolverFindCalibFile(m_MVAreaderFile_EVEN_MuGirl);
220 TString weightPath_ODD_MuGirl = PathResolverFindCalibFile(m_MVAreaderFile_ODD_MuGirl);
221
222 auto make_mva_reader = [](TString file_path) {
223 std::vector<std::string> mva_var_names{"momentumBalanceSignificance",
224 "scatteringCurvatureSignificance",
225 "scatteringNeighbourSignificance",
226 "EnergyLoss",
227 "middleLargeHoles+middleSmallHoles",
228 "muonSegmentDeltaEta",
229 "muonSeg1ChamberIdx",
230 "muonSeg2ChamberIdx"};
231 std::unique_ptr<TMVA::Reader> reader = std::make_unique<TMVA::Reader>(mva_var_names);
232 reader->BookMVA("BDTG", file_path);
233 return reader;
234 };
235 m_readerE_MUID = make_mva_reader(weightPath_EVEN_MuidCB);
236
237 m_readerO_MUID = make_mva_reader(weightPath_ODD_MuidCB);
238
239 m_readerE_MUGIRL = make_mva_reader(weightPath_EVEN_MuGirl);
240
241 m_readerO_MUGIRL = make_mva_reader(weightPath_ODD_MuGirl);
242
244 TString weightPath_MuTagIMO_etaBin1 = PathResolverFindCalibFile(m_MVAreaderFile_MuTagIMO_etaBin1);
245 TString weightPath_MuTagIMO_etaBin2 = PathResolverFindCalibFile(m_MVAreaderFile_MuTagIMO_etaBin2);
246 TString weightPath_MuTagIMO_etaBin3 = PathResolverFindCalibFile(m_MVAreaderFile_MuTagIMO_etaBin3);
247
248 auto make_mva_reader_MuTagIMO = [](TString file_path, bool useSeg2ChamberIndex) {
249 std::vector<std::string> mva_var_names;
250 if (useSeg2ChamberIndex) mva_var_names.push_back("muonSeg2ChamberIndex");
251 mva_var_names.push_back("muonSeg1ChamberIndex");
252 mva_var_names.push_back("muonSeg1NPrecisionHits");
253 mva_var_names.push_back("muonSegmentDeltaEta");
254 mva_var_names.push_back("muonSeg1GlobalR");
255 mva_var_names.push_back("muonSeg1Chi2OverDoF");
256 mva_var_names.push_back("muonSCS");
257
258 std::unique_ptr<TMVA::Reader> reader = std::make_unique<TMVA::Reader>(mva_var_names);
259 reader->BookMVA("BDT", file_path);
260 return reader;
261 };
262
263 m_reader_MUTAGIMO_etaBin1 = make_mva_reader_MuTagIMO(weightPath_MuTagIMO_etaBin1, false);
264 m_reader_MUTAGIMO_etaBin2 = make_mva_reader_MuTagIMO(weightPath_MuTagIMO_etaBin2, false);
265 m_reader_MUTAGIMO_etaBin3 = make_mva_reader_MuTagIMO(weightPath_MuTagIMO_etaBin3, true);
266 }
267 }
268 }
269
270 ATH_MSG_DEBUG("TightNNScore calculation is " << m_calculateTightNNScore);
272 ATH_CHECK(m_onnxTool.retrieve());
273 } else {
274 m_onnxTool.disable();
275 }
276 ATH_MSG_DEBUG("Finished ONNX tool setup");
277
278 ATH_CHECK(m_eventInfo.initialize());
279 // Return gracefully:
280 return StatusCode::SUCCESS;
281 }
282
283 StatusCode MuonSelectionTool::getHist(TFile* file, const std::string& histName, std::unique_ptr<TH1>& hist) const {
284 //
285 if (!file) {
286 ATH_MSG_ERROR(" getHist(...) TFile is nullptr! Check that the Tight cut map is loaded correctly");
287 return StatusCode::FAILURE;
288 }
289 TH1* h_ptr = nullptr;
290 file->GetObject(histName.c_str(), h_ptr);
291 //
292 //
293 if (!h_ptr) {
294 ATH_MSG_ERROR("Cannot retrieve histogram " << histName);
295 return StatusCode::FAILURE;
296 }
297 hist = std::unique_ptr<TH1>{h_ptr};
298 hist->SetDirectory(nullptr);
299 ATH_MSG_INFO("Successfully read tight working point histogram: " << hist->GetName());
300 //
301 return StatusCode::SUCCESS;
302 }
303
305
307 // Check if this is a muon:
308 if (p->type() != xAOD::Type::Muon) {
309 ATH_MSG_ERROR("accept(...) Function received a non-muon");
311 }
312
313 // Cast it to a muon:
314 const xAOD::Muon* mu = dynamic_cast<const xAOD::Muon*>(p);
315 if (!mu) {
316 ATH_MSG_FATAL("accept(...) Failed to cast particle to muon");
318 }
319
320 // Let the specific function do the work:
321 return accept(*mu);
322 }
323
324 //============================================================================
326
327 static std::atomic<bool> checkDone{false};
328
329 if(!checkDone) {
330 // Check that the user either set the correct geometry or enabled on-the-fly determination
331 // This can happen intentionally in developer mode. In any case don't throw an exception
332 // (we should either trust the user or ignore in the first place and force on-the-fly determination).
333 if (isRun3() != isRun3(true)) {
334 ATH_MSG_WARNING("MuonSelectionTool is configured with isRun3Geo="<<isRun3()
335 <<" while on-the fly check for runNumber "<<getRunNumber(true)<<" indicates isRun3Geo="<<isRun3(true));
336 }
337
338 // Check that the requested WP is currently supported by the MCP group.
339 if (isRun3()) {
340 if(m_quality!=0 && m_quality!=1 && m_quality!=2 && m_quality!=4 && m_quality!=5) {
341 ATH_MSG_WARNING("MuonSelectionTool currently supports Loose, Medium, Tight, HighPt, and LowPtEfficiency WPs for Run3; all other WPs can only be used in ExpertDevelopMode mode");
342 }
343
345 ATH_MSG_WARNING("For Run3, Tight WP is supported only when ExcludeNSWFromPrecisionLayers=False and RecalcPrecisionLayerswNSW=True");
346 }
347 }
348
349 checkDone = true;
350 }
351 }
352
353 //============================================================================
355 // Verbose information
356 ATH_MSG_VERBOSE("-----------------------------------");
357 ATH_MSG_VERBOSE("New muon passed to accept function:");
358 if (mu.muonType() == xAOD::Muon::MuonType::Combined)
359 ATH_MSG_VERBOSE("Muon type: combined");
360 else if (mu.muonType() == xAOD::Muon::MuonType::MuonStandAlone)
361 ATH_MSG_VERBOSE("Muon type: stand-alone");
362 else if (mu.muonType() == xAOD::Muon::MuonType::SegmentTagged)
363 ATH_MSG_VERBOSE("Muon type: segment-tagged");
364 else if (mu.muonType() == xAOD::Muon::MuonType::CaloTagged)
365 ATH_MSG_VERBOSE("Muon type: calorimeter-tagged");
366 else if (mu.muonType() == xAOD::Muon::MuonType::SiliconAssociatedForwardMuon)
367 ATH_MSG_VERBOSE("Muon type: silicon-associated forward");
368 ATH_MSG_VERBOSE("Muon pT [GeV]: " << mu.pt() * MeVtoGeV);
369 ATH_MSG_VERBOSE("Muon eta: " << mu.eta());
370 ATH_MSG_VERBOSE("Muon phi: " << mu.phi());
371
372 checkSanity();
373
374 asg::AcceptData acceptData(&m_acceptInfo);
375
376 // Do the eta cut:
377 if (std::abs(mu.eta()) >= m_maxEta) {
378 ATH_MSG_VERBOSE("Failed eta cut");
379 return acceptData;
380 }
381 acceptData.setCutResult("Eta", true);
382
383 // Passes ID hit cuts
384 bool passIDCuts = passedIDCuts(mu);
385 ATH_MSG_VERBOSE("Passes ID Hit cuts " << passIDCuts);
386 acceptData.setCutResult("IDHits", passIDCuts);
387
388 // passes muon preselection
389 bool passMuonCuts = passedMuonCuts(mu);
390 ATH_MSG_VERBOSE("Passes preselection cuts " << passMuonCuts);
391 acceptData.setCutResult("Preselection", passMuonCuts);
392
393 if (!passIDCuts || !passMuonCuts) { return acceptData; }
394
395 // Passes quality requirements
396 xAOD::Muon::Quality thisMu_quality = getQuality(mu);
397 bool thisMu_highpt = false;
398 thisMu_highpt = passedHighPtCuts(mu);
399 bool thisMu_lowptE = false;
400 thisMu_lowptE = passedLowPtEfficiencyCuts(mu, thisMu_quality);
401 ATH_MSG_VERBOSE("Summary of quality information for this muon: ");
402 ATH_MSG_VERBOSE("Muon quality: " << thisMu_quality << " passes HighPt: " << thisMu_highpt
403 << " passes LowPtEfficiency: " << thisMu_lowptE);
404 if (m_quality < 4 && Muon::MuonStationIndex::toInt(thisMu_quality) > m_quality) { return acceptData; }
405 if (m_quality == 4 && !thisMu_highpt) { return acceptData; }
406 if (m_quality == 5 && !thisMu_lowptE) { return acceptData; }
407 acceptData.setCutResult("Quality", true);
408 // Return the result:
409 return acceptData;
410 }
411
413 mu.setQuality(getQuality(mu));
414 return;
415 }
416 void MuonSelectionTool::IdMsPt(const xAOD::Muon& mu, float& idPt, float& mePt) const {
417 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
418 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
419 if (!idtrack || !metrack) idPt = mePt = -1.;
420 else if (m_turnOffMomCorr) {
421 mePt = metrack->pt();
422 idPt = idtrack->pt();
423 } else {
424 if (!mePt_acc.isAvailable(mu) || !idPt_acc.isAvailable(mu)) {
425 ATH_MSG_FATAL("The muon with pT " << mu.pt() * MeVtoGeV << " eta: " << mu.eta() << ", phi:" << mu.phi()
426 << " q:" << mu.charge() << ", author:" << mu.author()
427 << " is not decorated with calibrated momenta. Please fix");
428 throw std::runtime_error("MuonSelectionTool() - qOverP significance calculation failed");
429 }
430 mePt = mePt_acc(mu);
431 idPt = idPt_acc(mu);
432 }
433 }
434
436 // Avoid spurious FPEs in the clang build.
438
439 if (m_disablePtCuts) {
440 ATH_MSG_VERBOSE(__FILE__ << ":"<<__LINE__
441 << " Momentum dependent cuts are disabled. Return 0.");
442 return 0.;
443 }
444 const xAOD::TrackParticle* idtrack = muon.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
445 const xAOD::TrackParticle* metrack = muon.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
446 if (!idtrack || !metrack) {
447 ATH_MSG_VERBOSE("No ID / MS track. Return dummy large value of 1 mio");
448 return 1.e6;
449 }
450 float mePt{-1.}, idPt{-1.};
451 IdMsPt(muon, idPt, mePt);
452
453 const float meP = mePt / std::sin(metrack->theta());
454 const float idP = idPt / std::sin(idtrack->theta());
455
456 float qOverPsigma = std::sqrt(idtrack->definingParametersCovMatrix()(4, 4) + metrack->definingParametersCovMatrix()(4, 4));
457 return std::abs((metrack->charge() / meP) - (idtrack->charge() / idP)) / qOverPsigma;
458 }
460 if (m_disablePtCuts) {
461 ATH_MSG_VERBOSE(__FILE__ << ":"<<__LINE__
462 << "Momentum dependent cuts are disabled. Return 0.");
463 return 0.;
464 }
465 float mePt{-1.}, idPt{-1.};
466 IdMsPt(muon, idPt, mePt);
467 return std::abs(idPt - mePt) / muon.pt();
468 }
469
470 xAOD::Muon::Quality MuonSelectionTool::getQuality(const xAOD::Muon& mu) const {
471 ATH_MSG_VERBOSE("Evaluating muon quality...");
472 // SegmentTagged muons
473 if (mu.muonType() == xAOD::Muon::MuonType::SegmentTagged) {
474 ATH_MSG_VERBOSE("Muon is segment-tagged");
475
476 if (std::abs(mu.eta()) < 0.1) {
477 ATH_MSG_VERBOSE("Muon is loose");
478 return xAOD::Muon::Quality::Loose;
479 } else {
480 ATH_MSG_VERBOSE("Do not allow segment-tagged muon at |eta| > 0.1 - return VeryLoose");
481 return xAOD::Muon::Quality::VeryLoose;
482 }
483 }
484
485 // CaloTagged muons
486 if (mu.muonType() == xAOD::Muon::MuonType::CaloTagged) {
487 ATH_MSG_VERBOSE("Muon is calorimeter-tagged");
488
489 if (std::abs(mu.eta()) < 0.1 && passedCaloTagQuality(mu)) {
490 ATH_MSG_VERBOSE("Muon is loose");
491 return xAOD::Muon::Quality::Loose;
492 }
493 }
494
495 // Combined muons
496 hitSummary summary{};
497 fillSummary(mu, summary);
498
499 if (mu.muonType() == xAOD::Muon::MuonType::Combined) {
500 ATH_MSG_VERBOSE("Muon is combined");
501 if (mu.author() == xAOD::Muon::Author::STACO) {
502 ATH_MSG_VERBOSE("Muon is STACO - return VeryLoose");
503 return xAOD::Muon::Quality::VeryLoose;
504 }
505
506 // rejection muons with out-of-bounds hits
509
511 ATH_MSG_VERBOSE("Muon has out-of-bounds precision hits - return VeryLoose");
512 return xAOD::Muon::Quality::VeryLoose;
513 }
514
515 // LOOSE / MEDIUM / TIGHT WP
516 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
517 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
518 if (idtrack && metrack && metrack->definingParametersCovMatrix()(4, 4) > 0) {
519 const float qOverPsignif = qOverPsignificance(mu);
520 const float rho = rhoPrime(mu);
521 const float reducedChi2 = mu.primaryTrackParticle()->chiSquared() / mu.primaryTrackParticle()->numberDoF();
522
523 ATH_MSG_VERBOSE("Relevant cut variables:");
524 ATH_MSG_VERBOSE("number of precision layers = " << (int)summary.nprecisionLayers);
525 ATH_MSG_VERBOSE("reduced Chi2 = " << reducedChi2);
526 ATH_MSG_VERBOSE("qOverP significance = " << qOverPsignif);
527
528 // NEW TIGHT WP
529 if (summary.nprecisionLayers > 1 && reducedChi2 < 8 && std::abs(qOverPsignif) < 7) {
530 if (passTight(mu, rho, qOverPsignif)) {
531 ATH_MSG_VERBOSE("Muon is tight");
532 return xAOD::Muon::Quality::Tight;
533 }
534 }
535
536 ATH_MSG_VERBOSE("Muon did not pass requirements for tight combined muon");
537
538 // MEDIUM WP
539 if ((std::abs(qOverPsignif) < 7 || m_toroidOff) &&
540 (summary.nprecisionLayers > 1 ||(summary.nprecisionLayers == 1 && summary.nprecisionHoleLayers < 2 && std::abs(mu.eta()) < 0.1))
541
542 ) {
543 ATH_MSG_VERBOSE("Muon is medium");
544 return xAOD::Muon::Quality::Medium;
545 }
546
547 ATH_MSG_VERBOSE("Muon did not pass requirements for medium combined muon");
548
549 } else {
550 ATH_MSG_VERBOSE("Muon is missing the ID and/or ME tracks...");
551
552 // CB muons with missing ID or ME track
553 if ((summary.nprecisionLayers > 1 ||
554 (summary.nprecisionLayers == 1 && summary.nprecisionHoleLayers < 2 && std::abs(mu.eta()) < 0.1))) {
555 // In toroid-off data ME/MS tracks often missing - need special treatment => flagging as "Medium"
556 // In toroid-on data ME/MS tracks missing only for <1% of CB muons, mostly MuGirl (to be fixed) => flagging as "Loose"
557 if (m_toroidOff) {
558 ATH_MSG_VERBOSE("...this is toroid-off data - returning medium");
559 return xAOD::Muon::Quality::Medium;
560 } else {
561 ATH_MSG_VERBOSE("...this is not toroid-off data - returning loose");
562 return xAOD::Muon::Quality::Loose;
563 }
564 }
565 }
566
567 // Improvement for Loose targeting low-pT muons (pt<7 GeV)
568 if ((m_disablePtCuts || mu.pt() * MeVtoGeV < 7.) && std::abs(mu.eta()) < 1.3 && summary.nprecisionLayers > 0 &&
569 (mu.author() == xAOD::Muon::Author::MuGirl && mu.isAuthor(xAOD::Muon::Author::MuTagIMO))) {
570 ATH_MSG_VERBOSE("Muon passed selection for loose working point at low pT");
571 return xAOD::Muon::Quality::Loose;
572 }
573
574 // didn't pass the set of requirements for a medium or tight combined muon
575 ATH_MSG_VERBOSE("Did not pass selections for combined muon - returning VeryLoose");
576 return xAOD::Muon::Quality::VeryLoose;
577 }
578
579 // SA muons
580 if (mu.author() == xAOD::Muon::Author::MuidSA) {
581 ATH_MSG_VERBOSE("Muon is stand-alone");
582
583 if (std::abs(mu.eta()) > 2.5) {
584 ATH_MSG_VERBOSE("number of precision layers = " << (int)summary.nprecisionLayers);
585
586 // 3 station requirement for medium
587 if (summary.nprecisionLayers > 2 && !m_toroidOff) {
588 ATH_MSG_VERBOSE("Muon is medium");
589 return xAOD::Muon::Quality::Medium;
590 }
591 }
592
593 // didn't pass the set of requirements for a medium SA muon
594 ATH_MSG_VERBOSE("Muon did not pass selection for medium stand-alone muon - return VeryLoose");
595 return xAOD::Muon::Quality::VeryLoose;
596 }
597
598 // SiliconAssociatedForward (SAF) muons
599 if (mu.muonType() == xAOD::Muon::MuonType::SiliconAssociatedForwardMuon) {
600 ATH_MSG_VERBOSE("Muon is silicon-associated forward muon");
601
602 const xAOD::TrackParticle* cbtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
603 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
604
605 if (cbtrack && metrack) {
606 if (std::abs(cbtrack->eta()) > 2.5) {
607 ATH_MSG_VERBOSE("number of precision layers = " << (int)summary.nprecisionLayers);
608
609 if (summary.nprecisionLayers > 2 && !m_toroidOff) {
610 if (mu.trackParticle(xAOD::Muon::TrackParticleType::Primary) == mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle) &&
611 !m_developMode) {
613 "SiliconForwardAssociated muon has ID track as primary track particle. "
614 << "This is a bug fixed starting with xAODMuon-00-17-07, which should be present in this release. "
615 << "Please report this to the Muon CP group!");
616 }
617 ATH_MSG_VERBOSE("Muon is medium");
618 return xAOD::Muon::Quality::Medium;
619 }
620 }
621 }
622
623 // didn't pass the set of requirements for a medium SAF muon
624 ATH_MSG_VERBOSE("Muon did not pass selection for medium silicon-associated forward muon - return VeryLoose");
625 return xAOD::Muon::Quality::VeryLoose;
626 }
627
628 ATH_MSG_VERBOSE("Muon did not pass selection for loose/medium/tight for any muon type - return VeryLoose");
629 return xAOD::Muon::Quality::VeryLoose;
630 }
631
632 void MuonSelectionTool::setPassesIDCuts(xAOD::Muon& mu) const { mu.setPassesIDCuts(passedIDCuts(mu)); }
633
635 if (m_useLRT) {
636 static const SG::AuxElement::Accessor<char> isLRTmuon("isLRT");
637 if (isLRTmuon.isAvailable(mu)) {
638 if (isLRTmuon(mu)) return true;
639 }
640 else {
641 static const SG::AuxElement::Accessor<uint64_t> patternAcc("patternRecoInfo");
642 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
643 if(idtrack) {
644 if(!patternAcc.isAvailable(*idtrack)) {
645 ATH_MSG_FATAL("No information available to tell if the muon is LRT or standard. Either run MuonLRTMergingAlg to decorate with `isLRT` flag, or supply the patternRecoInfo for the original ID track.");
646 throw std::runtime_error("MuonSelectionTool() - isLRT decor and patternRecoInfo both unavailable for a muon.");
647 }
648 std::bitset<xAOD::NumberOfTrackRecoInfo> patternBitSet(patternAcc(*idtrack));
649 if (patternBitSet.test(xAOD::SiSpacePointsSeedMaker_LargeD0)) return true;
650 }
651 }
652 }
653 // do not apply the ID hit requirements for SA muons for |eta| > 2.5
654 if (mu.author() == xAOD::Muon::Author::MuidSA && std::abs(mu.eta()) > 2.5) {
655 return true;
656 } else if (mu.muonType() == xAOD::Muon::MuonType::SiliconAssociatedForwardMuon) {
657 const xAOD::TrackParticle* cbtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
658 if (cbtrack && std::abs(cbtrack->eta()) > 2.5) { return true; }
659 return false;
660 } else {
661 if (mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle))
662 return passedIDCuts(*mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle));
663 else if (mu.primaryTrackParticle())
664 return passedIDCuts(*mu.primaryTrackParticle());
665 }
666 return false;
667 }
668
670 if (mu.muonType() != xAOD::Muon::MuonType::Combined) return false;
671 // ::
672 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
673 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
674 const xAOD::TrackParticle* cbtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
675 // ::
676 // Some spurious muons are found to have negative ME track fit covariance, and are typically poorly reconstructed
677 if (metrack && metrack->definingParametersCovMatrix()(4, 4) < 0.0) return true;
678 // ::
679 bool IsBadMuon = false;
680 if (idtrack && metrack && cbtrack) {
681 // ::
682 double qOverP_ID = idtrack->qOverP();
683 double qOverPerr_ID = std::sqrt(idtrack->definingParametersCovMatrix()(4, 4));
684 double qOverP_ME = metrack->qOverP();
685 double qOverPerr_ME = std::sqrt(metrack->definingParametersCovMatrix()(4, 4));
686 double qOverP_CB = cbtrack->qOverP();
687 double qOverPerr_CB = std::sqrt(cbtrack->definingParametersCovMatrix()(4, 4));
688 // ::
689 if (m_quality == 4) {
690 // recipe for high-pt selection
691 IsBadMuon = !passedErrorCutCB(mu);
692
693 hitSummary summary{};
694 fillSummary(mu, summary);
695
696 // temporarily apply same recipe as for other working points in addition to CB error
697 // cut for 2-station muons, pending better treatment of ID/MS misalignments
698 if (m_use2stationMuonsHighPt && summary.nprecisionLayers == 2) {
699 double IdCbRatio = std::abs((qOverPerr_ID / qOverP_ID) / (qOverPerr_CB / qOverP_CB));
700 double MeCbRatio = std::abs((qOverPerr_ME / qOverP_ME) / (qOverPerr_CB / qOverP_CB));
701 IsBadMuon = (IdCbRatio < 0.8 || MeCbRatio < 0.8 || IsBadMuon);
702 }
703 } else {
704 // recipe for other WP
705 double IdCbRatio = std::abs((qOverPerr_ID / qOverP_ID) / (qOverPerr_CB / qOverP_CB));
706 double MeCbRatio = std::abs((qOverPerr_ME / qOverP_ME) / (qOverPerr_CB / qOverP_CB));
707 IsBadMuon = (IdCbRatio < 0.8 || MeCbRatio < 0.8);
708 }
709 } else {
710 return true;
711 }
712 return IsBadMuon;
713 }
714
716 xAOD::Muon::Quality thisMu_quality = getQuality(mu);
717 return passedLowPtEfficiencyCuts(mu, thisMu_quality);
718 }
719
720 bool MuonSelectionTool::passedLowPtEfficiencyCuts(const xAOD::Muon& mu, xAOD::Muon::Quality thisMu_quality) const {
721 ATH_MSG_VERBOSE("Checking whether muon passes low-pT selection...");
722
723 if (!m_useAllAuthors) { // no allAuthors, always fail the WP
724 ATH_MSG_VERBOSE("Do not have allAuthors variable - fail low-pT");
725 return false;
726 }
727
728 // requiring combined muons, unless segment-tags are included
730 if (mu.muonType() != xAOD::Muon::MuonType::Combined) {
731 ATH_MSG_VERBOSE("Muon is not combined - fail low-pT");
732 return false;
733 }
734 } else {
735 if (mu.muonType() != xAOD::Muon::MuonType::Combined && mu.muonType() != xAOD::Muon::MuonType::SegmentTagged) {
736 ATH_MSG_VERBOSE("Muon is not combined or segment-tagged - fail low-pT");
737 return false;
738 }
739 }
740
741 // author check
743 if (mu.author() != xAOD::Muon::Author::MuGirl && mu.author() != xAOD::Muon::Author::MuidCo) {
744 ATH_MSG_VERBOSE("Muon is neither MuGirl nor MuidCo - fail low-pT");
745 return false;
746 }
747 } else {
748 if (mu.author() != xAOD::Muon::Author::MuGirl && mu.author() != xAOD::Muon::Author::MuidCo && mu.author() != xAOD::Muon::Author::MuTagIMO) {
749 ATH_MSG_VERBOSE("Muon is neither MuGirl / MuidCo / MuTagIMO - fail low-pT");
750 return false;
751 }
752 }
753
754 // applying Medium selection above pT = 18 GeV
755 if (mu.pt() * MeVtoGeV > 18.) {
756 ATH_MSG_VERBOSE("pT > 18 GeV - apply medium selection");
757 if (thisMu_quality <= xAOD::Muon::Quality::Medium) {
758 ATH_MSG_VERBOSE("Muon passed low-pT selection");
759 return true;
760 } else {
761 ATH_MSG_VERBOSE("Muon failed low-pT selection");
762 return false;
763 }
764 }
765
766 // requiring Medium in forward regions
767 if (!m_useMVALowPt && std::abs(mu.eta()) > 1.55 && thisMu_quality > xAOD::Muon::Quality::Medium) {
768 ATH_MSG_VERBOSE("Not using MVA selection, failing low-pT selection due to medium requirement in forward region");
769 return false;
770 }
771
772 // rejection of muons with out-of-bounds hits
776 ATH_MSG_VERBOSE("Muon has out-of-bounds precision hits - fail low-pT");
777 return false;
778 }
779
780 // requiring explicitely >=1 station (2 in the |eta|>1.3 region when Medium selection is not explicitely required)
781 if (mu.muonType() == xAOD::Muon::MuonType::Combined) {
782 hitSummary summary{};
783 fillSummary(mu, summary);
784 uint nStationsCut = (std::abs(mu.eta()) > 1.3 && std::abs(mu.eta()) < 1.55) ? 2 : 1;
785 if (summary.nprecisionLayers < nStationsCut) {
786 ATH_MSG_VERBOSE("number of precision layers = " << (int)summary.nprecisionLayers << " is lower than cut value " << nStationsCut
787 << " - fail low-pT");
788 return false;
789 }
790 }
791
792 // reject MuGirl muon if not found also by MuTagIMO
793 if (m_useAllAuthors) {
794 if (mu.author() == xAOD::Muon::Author::MuGirl && !mu.isAuthor(xAOD::Muon::Author::MuTagIMO)) {
795 ATH_MSG_VERBOSE("MuGirl muon is not confirmed by MuTagIMO - fail low-pT");
796 return false;
797 }
798 } else
799 return false;
800
801 if (m_useMVALowPt) {
802 if (isRun3()) {
803 ATH_MSG_VERBOSE("Applying Run-3 MVA-based selection");
805 } else {
806 ATH_MSG_VERBOSE("Applying Run-2 MVA-based selection");
808 }
809
810 }
811
812 ATH_MSG_VERBOSE("Applying cut-based selection");
813
814 // apply some loose quality requirements
815 float momentumBalanceSignificance{0.}, scatteringCurvatureSignificance{0.}, scatteringNeighbourSignificance{0.};
816
817 retrieveParam(mu, momentumBalanceSignificance, xAOD::Muon::ParamDef::momentumBalanceSignificance);
818 retrieveParam(mu, scatteringCurvatureSignificance, xAOD::Muon::ParamDef::scatteringCurvatureSignificance);
819 retrieveParam(mu, scatteringNeighbourSignificance, xAOD::Muon::ParamDef::scatteringNeighbourSignificance);
820
821 ATH_MSG_VERBOSE("momentum balance significance: " << momentumBalanceSignificance);
822 ATH_MSG_VERBOSE("scattering curvature significance: " << scatteringCurvatureSignificance);
823 ATH_MSG_VERBOSE("scattering neighbour significance: " << scatteringNeighbourSignificance);
824
825 if (std::abs(momentumBalanceSignificance) > 3. || std::abs(scatteringCurvatureSignificance) > 3. ||
826 std::abs(scatteringNeighbourSignificance) > 3.) {
827 ATH_MSG_VERBOSE("Muon failed cut-based low-pT selection");
828 return false;
829 }
830
831 // passed low pt selection
832 ATH_MSG_VERBOSE("Muon passed cut-based low-pT selection");
833 return true;
834 }
835
836 std::vector<const xAOD::MuonSegment*> MuonSelectionTool::getSegmentsSorted(const xAOD::Muon& mu) const {
837 std::vector<const xAOD::MuonSegment*> segments_sorted;
838 segments_sorted.reserve(mu.nMuonSegments());
839
840 for (unsigned int i = 0; i < mu.nMuonSegments(); i++) {
841 if (!mu.muonSegment(i))
842 ATH_MSG_WARNING("The muon reports more segments than are available. Please report this to the muon software community!");
843 else
844 segments_sorted.push_back(mu.muonSegment(i));
845 }
846
847 std::sort(segments_sorted.begin(), segments_sorted.end(), chamberIndexCompare);
848
849 return segments_sorted;
850 }
851
853 //LowPt Not supported in run3 for the time being
854 if(isRun3() && !m_developMode){
855 ATH_MSG_VERBOSE("LowPt WP currently not supported for run3 if not in expert mode");
856 return false;
857 }
858 if (!m_useMVALowPt) {
859 ATH_MSG_DEBUG("Low pt MVA disabled. Return... ");
860 return false;
861 }
862 std::lock_guard<std::mutex> guard(m_low_pt_mva_mutex);
863 // set values for all BDT input variables from the muon in question
864 float momentumBalanceSig{-1}, CurvatureSig{-1}, energyLoss{-1}, muonSegmentDeltaEta{-1}, scatteringNeigbour{-1};
865 retrieveParam(mu, momentumBalanceSig, xAOD::Muon::ParamDef::momentumBalanceSignificance);
866 retrieveParam(mu, CurvatureSig, xAOD::Muon::ParamDef::scatteringCurvatureSignificance);
867 retrieveParam(mu, scatteringNeigbour, xAOD::Muon::ParamDef::scatteringNeighbourSignificance);
868 retrieveParam(mu, energyLoss, xAOD::Muon::ParamDef::EnergyLoss);
869 retrieveParam(mu, muonSegmentDeltaEta, xAOD::Muon::ParamDef::segmentDeltaEta);
870
874
875 float seg1ChamberIdx{-1.}, seg2ChamberIdx{-1.}, middleHoles{-1.}, seg1NPrecisionHits{-1.}, seg1GlobalR{-1.}, seg1Chi2OverDoF{-1.};
876
877 std::vector<const xAOD::MuonSegment*> muonSegments = getSegmentsSorted(mu);
878
879 if (mu.author() == xAOD::Muon::Author::MuTagIMO && muonSegments.size() == 0)
880 ATH_MSG_WARNING("passedLowPtEfficiencyMVACut - found segment-tagged muon with no segments!");
881
882 using namespace Muon::MuonStationIndex;
883 seg1ChamberIdx = (!muonSegments.empty()) ? toInt(muonSegments[0]->chamberIndex()) : -9;
884 seg2ChamberIdx = (muonSegments.size() > 1) ? toInt(muonSegments[1]->chamberIndex()) : -9;
885
886 // these variables are only used for MuTagIMO
887 if (mu.author() == xAOD::Muon::Author::MuTagIMO) {
888 seg1NPrecisionHits = (!muonSegments.empty()) ? muonSegments[0]->nPrecisionHits() : -1;
889 seg1GlobalR = (!muonSegments.empty())
890 ? std::hypot(muonSegments[0]->x(), muonSegments[0]->y(), muonSegments[0]->z())
891 : 0;
892 seg1Chi2OverDoF = (!muonSegments.empty()) ? muonSegments[0]->chiSquared() / muonSegments[0]->numberDoF() : -1;
893 }
894
895 middleHoles = middleSmallHoles + middleLargeHoles;
896
897 // get event number from event info
899 //overwrite event number
900 unsigned long long eventNumber = 0;
902 else eventNumber = eventInfo->eventNumber();
903
904 // variables for the BDT
905 std::vector<float> var_vector;
906 if (mu.author() == xAOD::Muon::Author::MuidCo || mu.author() == xAOD::Muon::Author::MuGirl) {
907 var_vector = {momentumBalanceSig, CurvatureSig, scatteringNeigbour, energyLoss,
908 middleHoles, muonSegmentDeltaEta, seg1ChamberIdx, seg2ChamberIdx};
909 } else {
910 if (std::abs(mu.eta()) >= 1.3)
911 var_vector = {seg2ChamberIdx, seg1ChamberIdx, seg1NPrecisionHits, muonSegmentDeltaEta,
912 seg1GlobalR, seg1Chi2OverDoF, std::abs(CurvatureSig)};
913 else
914 var_vector = {seg1ChamberIdx, seg1NPrecisionHits, muonSegmentDeltaEta,
915 seg1GlobalR, seg1Chi2OverDoF, std::abs(CurvatureSig)};
916 }
917
918 // use different trainings for even/odd numbered events
919 TMVA::Reader *reader_MUID, *reader_MUGIRL;
920 if (eventNumber % 2 == 1) {
921 reader_MUID = m_readerE_MUID.get();
922 reader_MUGIRL = m_readerE_MUGIRL.get();
923 } else {
924 reader_MUID = m_readerO_MUID.get();
925 reader_MUGIRL = m_readerO_MUGIRL.get();
926 }
927
928 // BDT for MuTagIMO is binned in |eta|
929 TMVA::Reader* reader_MUTAGIMO;
930 if (std::abs(mu.eta()) < 0.7)
931 reader_MUTAGIMO = m_reader_MUTAGIMO_etaBin1.get();
932 else if (std::abs(mu.eta()) < 1.3)
933 reader_MUTAGIMO = m_reader_MUTAGIMO_etaBin2.get();
934 else
935 reader_MUTAGIMO = m_reader_MUTAGIMO_etaBin3.get();
936
937 // get the BDT discriminant response
938 float BDTdiscriminant;
939
940 if (mu.author() == xAOD::Muon::Author::MuidCo)
941 BDTdiscriminant = reader_MUID->EvaluateMVA(var_vector, "BDTG");
942 else if (mu.author() == xAOD::Muon::Author::MuGirl)
943 BDTdiscriminant = reader_MUGIRL->EvaluateMVA(var_vector, "BDTG");
944 else if (mu.author() == xAOD::Muon::Author::MuTagIMO && m_useSegmentTaggedLowPt)
945 BDTdiscriminant = reader_MUTAGIMO->EvaluateMVA(var_vector, "BDT");
946 else {
947 ATH_MSG_WARNING("Invalid author for low-pT MVA, failing selection...");
948 return false;
949 }
950
951 // cut on dicriminant
952 float BDTcut = (mu.author() == xAOD::Muon::Author::MuTagIMO) ? 0.12 : -0.6;
953
954 if (BDTdiscriminant > BDTcut) {
955 ATH_MSG_VERBOSE("Passed low-pT MVA cut");
956 return true;
957 } else {
958 ATH_MSG_VERBOSE("Failed low-pT MVA cut");
959 return false;
960 }
961 }
962
964 if (!m_useMVALowPt) {
965 ATH_MSG_DEBUG("Low pt MVA disabled. Return... ");
966 return false;
967 }
968
969 const xAOD::TrackParticle* primary = mu.primaryTrackParticle();
970 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
971 const xAOD::TrackParticle* metrk = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
972
973 if (!primary || !idtrack || !metrk) {
974 ATH_MSG_VERBOSE("Missing primary, ID, or extrapolated MS track for Run-3 low-pT MVA; failing selection");
975 return false;
976 }
977
978 if (mu.author() == xAOD::Muon::Author::MuidCo) {
979 ATH_MSG_VERBOSE("passedLowPtEfficiencyMVACutRun3() for MuidCO");
980
981 //-- Prepare BDT input feature variables
982 float momentumBalanceSig{-1}, CurvatureSig{-1}, scatteringNeigbour{-1};
983 int CaloMuonIDTag{-1};
984 uint8_t nPixelHits{0}, nTRTOutliers{0};
985 float reducedChi2{-1};
986 float etaBalanceSig{-1}, phiBalanceSig{-1};
987 float seg1ChamberIdx{-1};
988
989 retrieveParam(mu, momentumBalanceSig, xAOD::Muon::ParamDef::momentumBalanceSignificance);
990 retrieveParam(mu, CurvatureSig, xAOD::Muon::ParamDef::scatteringCurvatureSignificance);
991 retrieveParam(mu, scatteringNeigbour, xAOD::Muon::ParamDef::scatteringNeighbourSignificance);
992
995
996 mu.parameter(CaloMuonIDTag, xAOD::Muon::ParamDef::CaloMuonIDTag);
997
998 reducedChi2 = primary->chiSquared() / primary->numberDoF();
999 etaBalanceSig = std::abs(idtrack->eta() - metrk->eta());
1000 phiBalanceSig = std::abs(idtrack->phi() - metrk->phi());
1001
1002 std::vector<const xAOD::MuonSegment*> muonSegments = getSegmentsSorted(mu);
1003 using namespace Muon::MuonStationIndex;
1004 seg1ChamberIdx = (!muonSegments.empty()) ? toInt(muonSegments[0]->chamberIndex()) : -9;
1005
1006 hitSummary summary{};
1007 fillSummary(mu, summary);
1008
1009 //-- Apply clipping
1010 etaBalanceSig = std::min(etaBalanceSig, 1.0f);
1011 reducedChi2 = std::min(reducedChi2, 100.0f);
1012 CurvatureSig = std::clamp(CurvatureSig, -10.0f, 10.0f);
1013 scatteringNeigbour = std::clamp(scatteringNeigbour, -10.0f, 10.0f);
1014
1015 //-- Prepare BDT input feature variables vector
1016 std::vector<float> muidCO_feature_vector = {
1017 static_cast<float>(nPixelHits),
1018 static_cast<float>(nTRTOutliers),
1019 static_cast<float>(CaloMuonIDTag),
1020 static_cast<float>(mu.energyLossType()),
1021 etaBalanceSig,
1022 momentumBalanceSig,
1023 static_cast<float>(summary.nprecisionLayers),
1024 phiBalanceSig,
1025 reducedChi2,
1026 CurvatureSig,
1027 scatteringNeigbour,
1028 seg1ChamberIdx
1029 };
1030
1031 //-- Apply scaling
1032 for (size_t j=0; j<muidCO_feature_vector.size(); ++j){
1033 muidCO_feature_vector[j] = (muidCO_feature_vector[j] - m_lowPtMuidCO_means[j]) / m_lowPtMuidCO_scaler[j];
1034 }
1035
1036 //-- Get BDT response
1037 float bdt_score = m_MuidCO->GetClassification(muidCO_feature_vector);
1038 ATH_MSG_VERBOSE(" Low-pT MVA BDT score (MuidCo, Run-3) : " << bdt_score);
1039
1040 //-- cut on discriminant (value motivated by study, slide 10):
1041 // https://indico.cern.ch/event/1639238/contributions/6896745/attachments/3208593/5714230/Run-3%20LowPt%20MVA%20WP%20Update.pdf
1042 float lowPtMVARun3_MuidCO_cut_value = 0.1300;
1043 return (bdt_score > lowPtMVARun3_MuidCO_cut_value);
1044
1045 } else if (mu.author() == xAOD::Muon::Author::MuGirl && mu.isAuthor(xAOD::Muon::Author::MuTagIMO)) {
1046 ATH_MSG_VERBOSE("passedLowPtEfficiencyMVACutRun3() for MuGirl");
1047
1048 //-- Prepare BDT input feature variables
1050 float momentumBalanceSig{-1}, seg1ChamberIdx{-1}, energyLoss{-1};
1051 int CaloMuonIDTag{-1};
1052 float reducedChi2{-1}, etaBalanceSig{-1}, phiBalanceSig{-1};
1053 float segmentDeltaEta{-1}, etaPrime{-1}, innerHits{-1};
1054 float middleHoles{-1}, nGoodPrecLayers{-1};
1055 float nprecisionHoleLayers{-1}, outerHoles{-1};
1056
1057 retrieveParam(mu, momentumBalanceSig, xAOD::Muon::ParamDef::momentumBalanceSignificance);
1058 retrieveParam(mu, segmentDeltaEta, xAOD::Muon::ParamDef::segmentDeltaEta);
1059 retrieveParam(mu, energyLoss, xAOD::Muon::ParamDef::EnergyLoss);
1060
1070
1071 mu.parameter(CaloMuonIDTag, xAOD::Muon::ParamDef::CaloMuonIDTag);
1072
1073 middleHoles = middleSmallHoles + middleLargeHoles;
1074 outerHoles = outerSmallHoles + outerLargeHoles;
1075 reducedChi2 = primary->chiSquared() / primary->numberDoF();
1076 etaBalanceSig = std::abs(idtrack->eta() - metrk->eta());
1077 etaPrime = std::abs((idtrack->eta() - metrk->eta())/mu.eta());
1078 phiBalanceSig = std::abs(idtrack->phi() - metrk->phi());
1079
1080 std::vector<const xAOD::MuonSegment*> muonSegments = getSegmentsSorted(mu);
1081 using namespace Muon::MuonStationIndex;
1082 seg1ChamberIdx = (!muonSegments.empty()) ? toInt(muonSegments[0]->chamberIndex()) : -9;
1083
1084 hitSummary summary{};
1085 fillSummary(mu, summary);
1086 innerHits = summary.innerSmallHits + summary.innerLargeHits;
1087 nGoodPrecLayers = summary.nGoodPrecLayers;
1088 nprecisionHoleLayers = summary.nprecisionHoleLayers;
1089
1090 //-- Apply clipping
1091 etaBalanceSig = std::min(etaBalanceSig, 1.0f);
1092 reducedChi2 = std::min(reducedChi2, 100.0f);
1093 etaPrime = std::clamp(etaPrime, -10.0f, 10.0f);
1094
1095 //-- Prepare BDT input feature variables vector
1096 std::vector<float> muGirl_feature_vector = {
1097 static_cast<float>(nTRTOutliers),
1098 static_cast<float>(CaloMuonIDTag),
1099 energyLoss,
1100 etaBalanceSig,
1101 etaPrime,
1102 innerHits,
1103 static_cast<float>(middleClosePrecisionHits),
1104 middleHoles,
1105 momentumBalanceSig,
1106 nGoodPrecLayers,
1107 nprecisionHoleLayers,
1108 static_cast<float>(summary.nprecisionLayers),
1109 static_cast<float>(outerClosePrecisionHits),
1110 outerHoles,
1111 phiBalanceSig,
1112 reducedChi2,
1113 seg1ChamberIdx,
1114 segmentDeltaEta
1115 };
1116
1117 //-- Apply scaling
1118 for (size_t j=0; j<muGirl_feature_vector.size(); ++j){
1119 muGirl_feature_vector[j] = (muGirl_feature_vector[j] - m_lowPtMuGirl_means[j]) / m_lowPtMuGirl_scaler[j];
1120 }
1121
1122 //-- Get BDT response
1123 float bdt_score = m_MuGirl->GetClassification(muGirl_feature_vector);
1124 ATH_MSG_VERBOSE(" Low-pT MVA BDT score (MuGirl, Run-3) : " << bdt_score);
1125
1126 //-- cut on discriminant (value motivated by study, slide 11):
1127 // https://indico.cern.ch/event/1639238/contributions/6896745/attachments/3208593/5714230/Run-3%20LowPt%20MVA%20WP%20Update.pdf
1128 float lowPtMVARun3_MuGirl_cut_value = 0.1550;
1129 return (bdt_score > lowPtMVARun3_MuGirl_cut_value);
1130 } else {
1131 ATH_MSG_WARNING("Invalid author for low-pT MVA in Run-3, failing selection...");
1132 return false;
1133 }
1134 }
1135
1137 ATH_MSG_VERBOSE("Checking whether muon passes high-pT selection...");
1138
1139 // :: Request combined muons
1140 if (mu.muonType() != xAOD::Muon::MuonType::Combined) {
1141 ATH_MSG_VERBOSE("Muon is not combined - fail high-pT");
1142 return false;
1143 }
1144 if (mu.author() == xAOD::Muon::Author::STACO) {
1145 ATH_MSG_VERBOSE("Muon is STACO - fail high-pT");
1146 return false;
1147 }
1148
1149 // :: Reject muons with out-of-bounds hits
1153 ATH_MSG_VERBOSE("Muon has out-of-bounds precision hits - fail high-pT");
1154 return false;
1155 }
1156
1157 // :: Access MS hits information
1158 hitSummary summary{};
1159 fillSummary(mu, summary);
1160
1161
1162 ATH_MSG_VERBOSE("number of precision layers: " << (int)summary.nprecisionLayers);
1163
1164 //::: Apply MS Chamber Vetoes
1165 // Given according to their eta-phi locations in the muon spectrometer
1166 // FORM: CHAMBERNAME[ array of four values ] = { eta 1, eta 2, phi 1, phi 2}
1167 // The vetoes are applied based on the MS track if available. If the MS track is not available,
1168 // the vetoes are applied according to the combined track, and runtime warning is printed to
1169 // the command line.
1170 const xAOD::TrackParticle* CB_track = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1171 const xAOD::TrackParticle* MS_track = mu.trackParticle(xAOD::Muon::TrackParticleType::MuonSpectrometerTrackParticle);
1172 if (!MS_track) {
1173 ATH_MSG_VERBOSE("passedHighPtCuts - No MS track available for muon. Using combined track.");
1174 MS_track = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1175 }
1176
1177 if (MS_track && CB_track) {
1178 float etaMS = MS_track->eta();
1179 float phiMS = MS_track->phi();
1180 float etaCB = CB_track->eta();
1181
1182 //::: no unspoiled clusters in CSC
1183 if (!isRun3() && (std::abs(etaMS) > 2.0 || std::abs(etaCB) > 2.0)) {
1184 if (summary.cscUnspoiledEtaHits == 0) {
1185 ATH_MSG_VERBOSE("Muon has only spoiled CSC clusters - fail high-pT");
1186 return false;
1187 }
1188 }
1189
1190 // veto bad CSC giving troubles with scale factors
1191 if (!isRun3() && mu.eta() < -1.899 && std::abs(mu.phi()) < 0.211) {
1192 ATH_MSG_VERBOSE("Muon is in eta/phi region vetoed due to disabled chambers in MC - fail high-pT");
1193 return false;
1194 }
1195
1196 //::: Barrel/Endcap overlap region
1197 if ((1.01 < std::abs(etaMS) && std::abs(etaMS) < 1.1) || (1.01 < std::abs(etaCB) && std::abs(etaCB) < 1.1)) {
1198 ATH_MSG_VERBOSE("Muon is in barrel/endcap overlap region - fail high-pT");
1199 return false;
1200 }
1201
1202 //::: BIS78
1203 if (isBIS78(etaMS, phiMS)) {
1204 if (!isRun3() || !m_useBEEBISInHighPtRun3) {
1205 ATH_MSG_VERBOSE("Muon is in BIS7/8 eta/phi region - fail high-pT");
1206 return false;
1207 }
1208 }
1209
1212 //if (getRunNumber(true) >= 324320) {
1213 //if (isBMG(etaMS, phiMS)) {
1214 //ATH_MSG_VERBOSE("Muon is in BMG eta/phi region - fail high-pT");
1215 //return false;
1216 //}
1217 //}
1218
1219 //::: BEE
1220 if (isBEE(etaMS, phiMS)) {
1221 // in Run3, large mis-alignment on the BEE chamber was found. temporarily mask the BEE region
1222 if (isRun3() && !m_useBEEBISInHighPtRun3) {
1223 ATH_MSG_VERBOSE("Muon is in BEE eta/phi region - fail high-pT");
1224 return false;
1225 }
1226 // Muon falls in the BEE eta-phi region: asking for 4 good precision layers
1227 // if( nGoodPrecLayers < 4 ) return false; // postponed (further studies needed)
1228 if (summary.nprecisionLayers < 4) {
1229 ATH_MSG_VERBOSE("Muon is in BEE eta/phi region and does not have 4 precision layers - fail high-pT");
1230 return false;
1231 }
1232 }
1233 if (std::abs(etaCB) > 1.4) {
1234 // Veto residual 3-station muons in BEE region due to MS eta/phi resolution effects
1235 // if( nGoodPrecLayers<4 && (extendedSmallHits>0||extendedSmallHoles>0) ) return false; // postponed (further studies
1236 // needed)
1237 if (summary.nprecisionLayers < 4 && (summary.extendedSmallHits > 0 || summary.extendedSmallHoles > 0)) {
1238 ATH_MSG_VERBOSE("Muon is in BEE eta/phi region and does not have 4 precision layers - fail high-pT");
1239 return false;
1240 }
1241 }
1242 } else {
1243 ATH_MSG_WARNING("passedHighPtCuts - MS or CB track missing in muon! Failing High-pT selection...");
1244 return false;
1245 }
1246
1247 //::: Apply 1/p significance cut
1248 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
1249 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
1250 if (idtrack && metrack && metrack->definingParametersCovMatrix()(4, 4) > 0) { const float qOverPsignif = qOverPsignificance(mu);
1251
1252 ATH_MSG_VERBOSE("qOverP significance: " << qOverPsignif);
1253
1254 if (std::abs(qOverPsignif) > 7) {
1255 ATH_MSG_VERBOSE("Muon failed qOverP significance cut");
1256 return false;
1257 }
1258 } else {
1259 ATH_MSG_VERBOSE("Muon missing ID or ME tracks - fail high-pT");
1260 return false;
1261 }
1262
1263 // Accept good 2-station muons if the user has opted to include these
1264 if (m_use2stationMuonsHighPt && summary.nprecisionLayers == 2) {
1265 // should not accept EM+EO muons due to ID/MS alignment issues
1266 if (std::abs(mu.eta()) > 1.2 && summary.extendedSmallHits < 3 && summary.extendedLargeHits < 3) {
1267 ATH_MSG_VERBOSE("2-station muon with EM+EO - fail high-pT");
1268 return false;
1269 }
1270
1271 // only select muons missing the inner precision layer
1272 // apply strict veto on overlap between small and large sectors
1273
1274 if (summary.innerLargeHits == 0 && summary.middleLargeHits == 0 && summary.outerLargeHits == 0 &&
1275 summary.extendedLargeHits == 0 && summary.middleSmallHits > 2 &&
1276 (summary.outerSmallHits > 2 || summary.extendedSmallHits > 2)) {
1277 ATH_MSG_VERBOSE("Accepted 2-station muon in small sector");
1278 return true;
1279 }
1280
1281 if (summary.innerSmallHits == 0 && summary.middleSmallHits == 0 && summary.outerSmallHits == 0 &&
1282 summary.extendedSmallHits == 0 && summary.middleLargeHits > 2 &&
1283 (summary.outerLargeHits > 2 || summary.extendedLargeHits > 2)) {
1284 ATH_MSG_VERBOSE("Accepted 2-station muon in large sector");
1285 return true;
1286 }
1287 }
1288
1289 //::: Require 3 (good) station muons
1290 if (summary.nprecisionLayers < 3) {
1291 ATH_MSG_VERBOSE("Muon has less than 3 precision layers - fail high-pT");
1292 return false;
1293 }
1294
1295 // Remove 3-station muons with small-large sectors overlap
1296 if (summary.isSmallGoodSectors) {
1297 if (!(summary.innerSmallHits > 2 && summary.middleSmallHits > 2 &&
1298 (summary.outerSmallHits > 2 || summary.extendedSmallHits > 2))) {
1299 ATH_MSG_VERBOSE("Muon has small/large sectors overlap - fail high-pT");
1300 return false;
1301 }
1302 } else {
1303 if (!(summary.innerLargeHits > 2 && summary.middleLargeHits > 2 &&
1304 (summary.outerLargeHits > 2 || summary.extendedLargeHits > 2))) {
1305 ATH_MSG_VERBOSE("Muon has small/large sectors overlap - fail high-pT");
1306 return false;
1307 }
1308 }
1309
1310 ATH_MSG_VERBOSE("Muon passed high-pT selection");
1311 return true;
1312 }
1313
1315 // ::
1316 if (mu.muonType() != xAOD::Muon::MuonType::Combined) return false;
1317 // ::
1318 double start_cut = 3.0;
1319 double end_cut = 1.6;
1320 double abs_eta = std::abs(mu.eta());
1321
1322 // parametrization of expected q/p error as function of pT
1323 double p0(8.0), p1(0.), p2(0.);
1324 if(isRun3()) //MC21 optimization
1325 {
1326 if(abs_eta<=1.05){
1327 p1=0.046;
1328 p2=0.00005;
1329 }
1330 else if (abs_eta > 1.05 && abs_eta <= 1.3) {
1331 p1 = 0.052;
1332 p2 = 0.00008;
1333 } else if (abs_eta > 1.3 && abs_eta <= 1.7) {
1334 p1 = 0.068;
1335 p2 = 0.00006;
1336 } else if (abs_eta > 1.7 && abs_eta <= 2.0) {
1337 p1 = 0.048;
1338 p2 = 0.00006;
1339 } else if (abs_eta > 2.0) {
1340 p1 = 0.037;
1341 p2 = 0.00006;
1342 }
1343 }
1344 else
1345 {
1346 if(abs_eta<=1.05){
1347 p1=0.039;
1348 p2=0.00006;
1349 }
1350 else if (abs_eta > 1.05 && abs_eta <= 1.3) {
1351 p1 = 0.040;
1352 p2 = 0.00009;
1353 } else if (abs_eta > 1.3 && abs_eta <= 1.7) {
1354 p1 = 0.056;
1355 p2 = 0.00008;
1356 } else if (abs_eta > 1.7 && abs_eta <= 2.0) {
1357 p1 = 0.041;
1358 p2 = 0.00006;
1359 } else if (abs_eta > 2.0) {
1360 p1 = 0.031;
1361 p2 = 0.00006;
1362 }
1363 }
1364 // ::
1365 hitSummary summary{};
1366 fillSummary(mu, summary);
1367
1368 // independent parametrization for 2-station muons
1369 if (m_use2stationMuonsHighPt && summary.nprecisionLayers == 2) {
1370 start_cut = 1.1;
1371 end_cut=0.7;
1372 p1 = 0.0739568;
1373 p2 = 0.00012443;
1374 if (abs_eta > 1.05 && abs_eta < 1.3) {
1375 p1 = 0.0674484;
1376 p2 = 0.000119879;
1377 } else if (abs_eta >= 1.3 && abs_eta < 1.7) {
1378 p1 = 0.041669;
1379 p2 = 0.000178349;
1380 } else if (abs_eta >= 1.7 && abs_eta < 2.0) {
1381 p1 = 0.0488664;
1382 p2 = 0.000137648;
1383 } else if (abs_eta >= 2.0) {
1384 p1 = 0.028077;
1385 p2 = 0.000152707;
1386 }
1387 }
1388 // ::
1389 bool passErrorCutCB = false;
1390 const xAOD::TrackParticle* cbtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1391 if (cbtrack) {
1392 // ::
1393 double pt_CB = (cbtrack->pt() * MeVtoGeV < 5000.) ? cbtrack->pt() * MeVtoGeV : 5000.; // GeV
1394 double qOverP_CB = cbtrack->qOverP();
1395 double qOverPerr_CB = std::sqrt(cbtrack->definingParametersCovMatrix()(4, 4));
1396 // sigma represents the average expected error at the muon's pt/eta
1397 double sigma = std::sqrt(std::pow(p0 / pt_CB, 2) + std::pow(p1, 2) + std::pow(p2 * pt_CB, 2));
1398 // cutting at start_cut*sigma for pt <=1 TeV depending on eta region,
1399 // then linearly tightening until end_cut*sigma is reached at pt >= 5TeV.
1400 double a = (end_cut - start_cut) / 4000.0;
1401 double b = end_cut - a * 5000.0;
1402 double coefficient = (pt_CB > 1000.) ? (a * pt_CB + b) : start_cut;
1403 if (std::abs(qOverPerr_CB / qOverP_CB) < coefficient * sigma) { passErrorCutCB = true; }
1404 }
1405 // ::
1406 if (m_use2stationMuonsHighPt && m_doBadMuonVetoMimic && summary.nprecisionLayers == 2) {
1408
1409 if (eventInfo->eventType(xAOD::EventInfo::IS_SIMULATION)) {
1410 ATH_MSG_DEBUG("The current event is a MC event. Use bad muon veto mimic.");
1411 return passErrorCutCB && passedBMVmimicCut(mu);
1412 }
1413 }
1414
1415 // ::
1416 return passErrorCutCB;
1417 }
1418
1420 TF1* cutFunction;
1421 double p1, p2;
1422 if (std::abs(mu.eta()) < 1.05) {
1423 cutFunction = m_BMVcutFunction_barrel.get();
1424 p1 = 0.066265;
1425 p2 = 0.000210047;
1426 } else {
1427 cutFunction = m_BMVcutFunction_endcap.get();
1428 p1 = 0.0629747;
1429 p2 = 0.000196466;
1430 }
1431
1432 double qOpRelResolution = std::hypot(p1, p2 * mu.primaryTrackParticle()->pt() * MeVtoGeV);
1433
1434 double qOverPabs_unsmeared = std::abs(mu.primaryTrackParticle()->definingParameters()[4]);
1435 double qOverPabs_smeared = 1.0 / (mu.pt() * std::cosh(mu.eta()));
1436
1437 if ((qOverPabs_smeared - qOverPabs_unsmeared) / (qOpRelResolution * qOverPabs_unsmeared) <
1438 cutFunction->Eval(mu.primaryTrackParticle()->pt() * MeVtoGeV))
1439 return false;
1440 else
1441 return true;
1442 }
1443
1445 // ::
1446 if (mu.muonType() == xAOD::Muon::MuonType::Combined) { return mu.author() != xAOD::Muon::Author::STACO; }
1447 // ::
1448 if (mu.muonType() == xAOD::Muon::MuonType::CaloTagged && std::abs(mu.eta()) < 0.105)
1449 return passedCaloTagQuality(mu);
1450 // ::
1451 if (mu.muonType() == xAOD::Muon::MuonType::SegmentTagged && (std::abs(mu.eta()) < 0.105 || m_useSegmentTaggedLowPt)) return true;
1452 // ::
1453 if (mu.author() == xAOD::Muon::Author::MuidSA && std::abs(mu.eta()) > 2.4) return true;
1454 // ::
1455 if (mu.muonType() == xAOD::Muon::MuonType::SiliconAssociatedForwardMuon) {
1456 const xAOD::TrackParticle* cbtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1457 return (cbtrack && std::abs(cbtrack->eta()) > 2.4);
1458 }
1459 // ::
1460 return false;
1461 }
1462
1464 uint8_t value1{0}, value2{0};
1465
1468 " !! Tool configured with some of the ID hits requirements changed... FOR DEVELOPMENT ONLY: muon efficiency SF won't be "
1469 "valid !! ");
1470
1473 if ((value1 + value2 == 0) && !m_PixCutOff) return false;
1474
1477 if ((value1 + value2 <= 4) && !m_SctCutOff) return false;
1478
1481 if ((value1 + value2 >= 3) && !m_SiHolesCutOff) return false;
1482
1483 if (!m_TrtCutOff) {
1484 const float abseta = std::abs(track.eta());
1487 const uint8_t totTRThits = value1 + value2;
1488 if (!((0.1 < abseta && abseta <= 1.9 && totTRThits > 5 && value2 < (0.9 * totTRThits)) || (abseta <= 0.1 || abseta > 1.9)))
1489 return false;
1490 }
1491 // Reached end - all ID hit cuts are passed.
1492 return true;
1493 } // passedIDCuts
1494
1496 // Use CaloScore variable based on Neural Network if enabled
1497 // The neural network is only trained until eta = 1
1498 // cf. https://cds.cern.ch/record/2802605/files/CERN-THESIS-2021-290.pdf
1499 constexpr float eta_range = 1.;
1500 if (std::abs(mu.eta()) < eta_range && m_useCaloScore) return passedCaloScore(mu);
1501
1502 // Otherwise we use CaloMuonIDTag
1503 int CaloMuonIDTag = -20;
1504
1505 // Extract CaloMuonIDTag variable
1506 bool readID = mu.parameter(CaloMuonIDTag, xAOD::Muon::ParamDef::CaloMuonIDTag);
1507 if (!readID) {
1508 ATH_MSG_WARNING("Unable to read CaloMuonIDTag Quality information! Rejecting the CALO muon!");
1509 return false;
1510 }
1511
1512 // Cut on CaloMuonIDTag variable
1513 return (CaloMuonIDTag > 10);
1514 }
1515
1517 // We use a working point with a pT-dependent cut on the NN discriminant, designed to achieve a constant
1518 // fakes rejection as function of pT in Z->mumu MC
1519
1520 // Extract the relevant score variable (NN discriminant)
1521
1522 float CaloMuonScore{-999.0};
1523 retrieveParam(mu, CaloMuonScore, xAOD::Muon::ParamDef::CaloMuonScore);
1524
1525 if(m_caloScoreWP==1) return (CaloMuonScore >= 0.92);
1526 if(m_caloScoreWP==2) return (CaloMuonScore >= 0.56);
1527 else if(m_caloScoreWP==3 || m_caloScoreWP==4)
1528 {
1529 // Cut on the score variable
1530 float pT = mu.pt() * MeVtoGeV; // GeV
1531
1532 if (pT > 20.0) // constant cut above 20 GeV
1533 return (CaloMuonScore >= 0.77);
1534 else {
1535 // pT-dependent cut below 20 GeV
1536 // The pT-dependent cut is based on a fit of a third-degree polynomial, with coefficients as given below
1537
1538 if(m_caloScoreWP==3) return (CaloMuonScore >= (-1.98e-4 * std::pow(pT, 3) +6.04e-3 * std::pow(pT, 2) -6.13e-2 * pT + 1.16));
1539 if(m_caloScoreWP==4) return (CaloMuonScore >= (-1.80e-4 * std::pow(pT, 3) +5.02e-3 * std::pow(pT, 2) -4.62e-2 * pT + 1.12));
1540 }
1541 }
1542
1543 return false;
1544 }
1545
1546 bool MuonSelectionTool::passTight(const xAOD::Muon& mu, float rho, float oneOverPSig) const {
1547
1549 ATH_MSG_VERBOSE("for run3, Tight WP is only supported when ExcludeNSWFromPrecisionLayers=False and RecalcPrecisionLayerswNSW=True");
1550 return false;
1551 }
1552 float symmetric_eta = std::abs(mu.eta());
1553 float pt = mu.pt() * MeVtoGeV; // GeV
1554
1555 // Impose pT and eta cuts; the bounds of the cut maps
1556 if (pt < 4.0 || symmetric_eta >= 2.5) return false;
1557 ATH_MSG_VERBOSE("Muon is passing tight WP kinematic cuts with pT,eta " << mu.pt() << " , " << mu.eta());
1558
1559 // ** Low pT specific cuts ** //
1560 if (pt < 20.0) {
1561 double rhoCut = m_tightWP_lowPt_rhoCuts->Interpolate(pt, symmetric_eta);
1562 double qOverPCut = m_tightWP_lowPt_qOverPCuts->Interpolate(pt, symmetric_eta);
1563
1564 ATH_MSG_VERBOSE("Applying tight WP cuts to a low pt muon with (pt,eta) ( " << pt << " , " << mu.eta() << " ) ");
1565 ATH_MSG_VERBOSE("Rho value " << rho << ", required to be less than " << rhoCut);
1566 ATH_MSG_VERBOSE("Momentum significance value " << oneOverPSig << ", required to be less than " << qOverPCut);
1567
1568 if (rho > rhoCut) return false;
1569 ATH_MSG_VERBOSE("Muon passed tight WP, low pT rho cut!");
1570
1571 if (oneOverPSig > qOverPCut) return false;
1572 ATH_MSG_VERBOSE("Muon passed tight WP, low pT momentum significance cut");
1573
1574 // Tight muon!
1575 return true;
1576
1577 }
1578
1579 // ** Medium pT specific cuts ** //
1580 else if (pt < 100.0) {
1581 double rhoCut = m_tightWP_mediumPt_rhoCuts->Interpolate(pt, symmetric_eta);
1582 //
1583 ATH_MSG_VERBOSE("Applying tight WP cuts to a medium pt muon with (pt,eta) (" << pt << "," << mu.eta() << ")");
1584 ATH_MSG_VERBOSE("Rho value " << rho << " required to be less than " << rhoCut);
1585
1586 // Apply cut
1587 if (rho > rhoCut) return false;
1588 ATH_MSG_VERBOSE("Muon passed tight WP, medium pT rho cut!");
1589
1590 // Tight muon!
1591 return true;
1592 }
1593
1594 // ** High pT specific cuts
1595 else if (pt < 500.0) {
1596 //
1597 ATH_MSG_VERBOSE("Applying tight WP cuts to a high pt muon with (pt,eta) (" << pt << "," << mu.eta() << ")");
1598 // No interpolation, since bins with -1 mean we should cut really loose
1599 double rhoCut = m_tightWP_highPt_rhoCuts->GetBinContent(m_tightWP_highPt_rhoCuts->FindFixBin(pt, symmetric_eta));
1600 ATH_MSG_VERBOSE("Rho value " << rho << ", required to be less than " << rhoCut << " unless -1, in which no cut is applied");
1601 //
1602 if (rhoCut < 0.0) return true;
1603 if (rho > rhoCut) return false;
1604 ATH_MSG_VERBOSE("Muon passed tight WP, high pT rho cut!");
1605
1606 return true;
1607 }
1608 // For muons with pT > 500 GeV, no extra cuts
1609 else {
1610 ATH_MSG_VERBOSE("Not applying any tight WP cuts to a very high pt muon with (pt,eta) (" << pt << "," << mu.eta() << ")");
1611 return true;
1612 }
1613
1614 // you should never reach this point
1615 return false;
1616 }
1617
1618 //============================================================================
1620
1621 checkSanity();
1622
1636
1637 if (!isRun3()) {
1638 // ignore missing of cscUnspoiledEtaHits in case we are running in expert developer mode
1639 // e.g. for when we want to apply Run2 WPs in Run3
1641
1642 if (std::abs(muon.eta()) > 2.0) {
1643 ATH_MSG_VERBOSE("Recalculating number of precision layers for combined muon");
1644 summary.nprecisionLayers = (summary.innerSmallHits > 1 || summary.innerLargeHits > 1)
1645 + (summary.middleSmallHits > 2 || summary.middleLargeHits > 2)
1646 + (summary.outerSmallHits > 2 || summary.outerLargeHits > 2);
1647 }
1648
1649 } else if (std::abs(muon.eta()) > 1.3 && (m_excludeNSWFromPrecisionLayers || m_recalcPrecisionLayerswNSW)) {
1650 summary.nprecisionLayers = (summary.middleSmallHits > 2 || summary.middleLargeHits > 2)
1651 + (summary.outerSmallHits > 2 || summary.outerLargeHits > 2)
1652 + (summary.extendedSmallHits > 2 || summary.extendedLargeHits > 2);
1653
1655
1656 if (!eta1stgchits_acc.isAvailable(muon) || !eta2stgchits_acc.isAvailable(muon) || !mmhits_acc.isAvailable(muon)) {
1657 ATH_MSG_FATAL(__FILE__ << ":" << __LINE__ << " Failed to retrieve NSW hits!"
1658 << " (Please use DxAODs with p-tags >= p5834 OR set ExcludeNSWFromPrecisionLayers to True (tests only)");
1659 throw std::runtime_error("Failed to retrieve NSW hits");
1660 }
1661
1665 summary.nprecisionLayers += ((summary.etaLayer1STGCHits + summary.etaLayer2STGCHits) > 3 || summary.MMHits > 3);
1666 }
1667 }
1668 }
1669
1670
1671 void MuonSelectionTool::retrieveParam(const xAOD::Muon& muon, float& value, const xAOD::Muon::ParamDef param) const {
1672 if (!muon.parameter(value, param)) {
1673 ATH_MSG_FATAL(__FILE__ << ":" << __LINE__ << " Failed to retrieve parameter " << param
1674 << " for muon with pT:" << muon.pt() * MeVtoGeV << ", eta:" << muon.eta() << ", phi: " << muon.phi()
1675 << ", q:" << muon.charge() << ", author: " << muon.author());
1676 throw std::runtime_error("Failed to retrieve Parameter");
1677 }
1678 }
1679
1680 // Returns an integer corresponding to categorization of muons with different resolutions
1682 // Resolutions have only been evaluated for medium combined muons
1683 if (mu.muonType() != xAOD::Muon::MuonType::Combined || getQuality(mu) > xAOD::Muon::Quality::Medium) return ResolutionCategory::unclassified;
1684
1685 // :: Access MS hits information
1686 hitSummary summary{};
1687 fillSummary(mu, summary);
1688
1689 // For muons passing the high-pT working point, distinguish between 2-station tracks and the rest
1690 if (passedHighPtCuts(mu)) {
1691 if (summary.nprecisionLayers == 2)
1693 else
1695 }
1696
1697 const xAOD::TrackParticle* CB_track = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1698 const xAOD::TrackParticle* MS_track = mu.trackParticle(xAOD::Muon::TrackParticleType::MuonSpectrometerTrackParticle);
1699 if (!MS_track) {
1700 ATH_MSG_VERBOSE("getResolutionCategory - No MS track available for muon. Using combined track.");
1701 MS_track = mu.trackParticle(xAOD::Muon::TrackParticleType::CombinedTrackParticle);
1702 }
1703
1704 if (!MS_track || !CB_track) return ResolutionCategory::unclassified;
1705 const float etaMS = MS_track->eta();
1706 const float etaCB = CB_track->eta();
1707 const float phiMS = MS_track->phi();
1708
1709 int category = ResolutionCategory::unclassified;
1710
1711 if ((summary.isSmallGoodSectors && summary.innerSmallHits < 3) || (!summary.isSmallGoodSectors && summary.innerLargeHits < 3))
1712 category = ResolutionCategory::missingInner; // missing-inner
1713
1714 if ((summary.isSmallGoodSectors && summary.middleSmallHits < 3) || (!summary.isSmallGoodSectors && summary.middleLargeHits < 3))
1715 category = ResolutionCategory::missingMiddle; // missing-middle
1716
1717 if ((summary.isSmallGoodSectors && summary.outerSmallHits < 3 && summary.extendedSmallHits < 3) ||
1718 (!summary.isSmallGoodSectors && summary.outerLargeHits < 3 && summary.extendedLargeHits < 3))
1719 category = ResolutionCategory::missingOuter; // missing-outer
1720
1721 if (!isRun3() && (std::abs(etaMS) > 2.0 || std::abs(etaCB) > 2.0) && summary.cscUnspoiledEtaHits == 0)
1722 category = ResolutionCategory::spoiledCSC; // spoiled CSC
1723
1724 if ((1.01 < std::abs(etaMS) && std::abs(etaMS) < 1.1) || (1.01 < std::abs(etaCB) && std::abs(etaCB) < 1.1))
1725 category = ResolutionCategory::BEoverlap; // barrel-end-cap overlap
1726
1727 if (isBIS78(etaMS, phiMS)) category = ResolutionCategory::BIS78; // BIS7/8
1728
1729 //::: BEE
1730 if (isBEE(etaMS, phiMS) || (std::abs(etaCB) > 1.4 && (summary.extendedSmallHits > 0 || summary.extendedSmallHoles > 0))) {
1731 if (summary.extendedSmallHits < 3 && summary.middleSmallHits >= 3 && summary.outerSmallHits >= 3)
1732 category = ResolutionCategory::missingBEE; // missing-BEE
1733
1734 if (summary.extendedSmallHits >= 3 && summary.outerSmallHits < 3) category = ResolutionCategory::missingOuter; // missing-outer
1735
1736 if (!summary.isSmallGoodSectors)
1737 category = ResolutionCategory::unclassified; // ambiguity due to eta/phi differences between MS and CB track
1738 }
1739
1740 if (summary.nprecisionLayers == 1) category = ResolutionCategory::oneStation; // one-station track
1741
1742 return category;
1743 }
1744
1745 //============================================================================
1746 // need run number (or random run number) to apply period-dependent selections
1747 unsigned int MuonSelectionTool::getRunNumber(bool needOnlyCorrectYear /*=false*/) const {
1748
1749 static const SG::AuxElement::ConstAccessor<unsigned int> acc_rnd("RandomRunNumber");
1750
1752 //overwrite run number
1753 unsigned int runNumber = 0;
1754 if(m_expertMode_RunNumber.value()!=0) runNumber=m_expertMode_RunNumber.value();
1755 else runNumber = eventInfo->runNumber();
1756
1757 // Case of data
1758 if (!eventInfo->eventType(xAOD::EventInfo::IS_SIMULATION)) {
1759 ATH_MSG_DEBUG("The current event is a data event. Return runNumber.");
1760 return runNumber;
1761 }
1762
1763 // Case of MC
1764 // attempt to get the run number assigned by the PRW tool
1765 static std::atomic<bool> issuedWarningPRW{false};
1766 if (acc_rnd.isAvailable(*eventInfo)) {
1767 unsigned int rn = acc_rnd(*eventInfo);
1768 if (rn != 0) return acc_rnd(*eventInfo);
1769
1770 if (!issuedWarningPRW) {
1771 ATH_MSG_WARNING("Pile up tool has assigned runNumber = 0");
1772 issuedWarningPRW = true;
1773 }
1774 }
1775
1776 // otherwise return a dummy run number
1777 if (needOnlyCorrectYear) {
1778 if (runNumber < 300000) { // mc16a (2016): 284500
1779 ATH_MSG_DEBUG("Random run number not available and this is mc16a or mc20a, returning dummy 2016 run number.");
1780 return 311071;
1781
1782 } else if (runNumber < 310000) { // mc16d (2017): 300000
1783 ATH_MSG_DEBUG("Random run number not available and this is mc16d or mc20d, returning dummy 2017 run number.");
1784 return 340072;
1785
1786 } else if (runNumber < 320000) { // mc16e (2018): 310000
1787 ATH_MSG_DEBUG("Random run number not available and this is mc16e or mc20e, returning dummy 2018 run number.");
1788 return 351359;
1789
1790 } else if (runNumber < 600000) { //mc21: 330000, mc23a: 410000, mc23c: 450000
1791 ATH_MSG_DEBUG("Random run number not available and this is mc21/mc23, for the time being we're returing a dummy run number.");
1792 return 399999;
1793 } else {
1794 ATH_MSG_DEBUG("Detected some run 4 / phase II runnumber "<<runNumber<<". ");
1795 return 666666;
1796 }
1797
1798 ATH_MSG_FATAL("Random run number not available, fallback option of using runNumber failed since "<<runNumber<<" cannot be recognised");
1799 throw std::runtime_error("MuonSelectionTool() - need RandomRunNumber decoration by the PileupReweightingTool");
1800 }
1801
1802 ATH_MSG_FATAL("Failed to find the RandomRunNumber decoration by the PileupReweightingTool");
1803 throw std::runtime_error("MuonSelectionTool() - need RandomRunNumber decoration from PileupReweightingTool");
1804 }
1805
1806
1807 // Check if eta/phi coordinates correspond to BIS7/8 chambers
1808 bool MuonSelectionTool::isBIS78(const float eta, const float phi) const {
1809 static constexpr std::array<float, 2> BIS78_eta{1.05, 1.3};
1810 static constexpr std::array<float, 8> BIS78_phi{0.21, 0.57, 1.00, 1.33, 1.78, 2.14, 2.57, 2.93};
1811
1812 float abs_eta = std::abs(eta);
1813 float abs_phi = std::abs(phi);
1814
1815 if (abs_eta >= BIS78_eta[0] && abs_eta <= BIS78_eta[1]) {
1816 if ((abs_phi >= BIS78_phi[0] && abs_phi <= BIS78_phi[1]) || (abs_phi >= BIS78_phi[2] && abs_phi <= BIS78_phi[3]) ||
1817 (abs_phi >= BIS78_phi[4] && abs_phi <= BIS78_phi[5]) || (abs_phi >= BIS78_phi[6] && abs_phi <= BIS78_phi[7])) {
1818 return true;
1819 }
1820 }
1821
1822 return false;
1823 }
1824
1825 // Check if eta/phi coordinates correspond to BEE chambers
1826 bool MuonSelectionTool::isBEE(const float eta, const float phi) const {
1827 static constexpr std::array<float, 2> BEE_eta{1.440, 1.692};
1828 static constexpr std::array<float, 8> BEE_phi{0.301, 0.478, 1.086, 1.263, 1.872, 2.049, 2.657, 2.834};
1829
1830 float abs_eta = std::abs(eta);
1831 float abs_phi = std::abs(phi);
1832
1833 if (abs_eta >= BEE_eta[0] && abs_eta <= BEE_eta[1]) {
1834 if ((abs_phi >= BEE_phi[0] && abs_phi <= BEE_phi[1]) || (abs_phi >= BEE_phi[2] && abs_phi <= BEE_phi[3]) ||
1835 (abs_phi >= BEE_phi[4] && abs_phi <= BEE_phi[5]) || (abs_phi >= BEE_phi[6] && abs_phi <= BEE_phi[7])) {
1836 return true;
1837 }
1838 }
1839
1840 return false;
1841 }
1842
1843 // Check if eta/phi coordinates correspond to BMG chambers
1844 bool MuonSelectionTool::isBMG(const float eta, const float phi) const {
1845 static constexpr std::array<float, 6> BMG_eta{0.35, 0.47, 0.68, 0.80, 0.925, 1.04};
1846 static constexpr std::array<float, 4> BMG_phi{-1.93, -1.765, -1.38, -1.21};
1847
1848 float abs_eta = std::abs(eta);
1849
1850 if ((abs_eta >= BMG_eta[0] && abs_eta <= BMG_eta[1]) || (abs_eta >= BMG_eta[2] && abs_eta <= BMG_eta[3]) ||
1851 (abs_eta >= BMG_eta[4] && abs_eta <= BMG_eta[5])) {
1852 if ((phi >= BMG_phi[0] && phi <= BMG_phi[1]) || (phi >= BMG_phi[2] && phi <= BMG_phi[3])) { return true; }
1853 }
1854
1855 return false;
1856 }
1857
1860 {
1861 ATH_MSG_ERROR("TightNNScore calculation is disabled. Please set the property CalculateTightNNScore to true.");
1862 throw std::runtime_error("cannot calculate TightNNScore");
1863 }
1864 //this score currently only can be calculated for combined muons
1865 if (mu.muonType() != xAOD::Muon::MuonType::Combined) return -999;
1866 const xAOD::TrackParticle* idtrack = mu.trackParticle(xAOD::Muon::TrackParticleType::InnerDetectorTrackParticle);
1867 const xAOD::TrackParticle* metrack = mu.trackParticle(xAOD::Muon::TrackParticleType::ExtrapolatedMuonSpectrometerTrackParticle);
1868 if(!idtrack || !metrack) return -999;
1869 //the score is only calculated for muons which pass the Medium WP
1870 if (getQuality(mu) > xAOD::Muon::Quality::Medium) return -999;
1871 //only muons with pt > 4 GeV and |eta|<2.5 are considered
1872 if (std::abs(mu.eta())>2.5) return -999;
1873 if(mu.pt()<4000.) return -999;
1874
1875 std::vector<float> input_features;
1876 // 1. Fill input features
1877 int mu_author=static_cast<int>(mu.author());
1878 float mu_rhoPrime=rhoPrime(mu);
1879 float mu_scatteringCurvatureSignificance=0.;
1880 retrieveParam(mu, mu_scatteringCurvatureSignificance, xAOD::Muon::ParamDef::scatteringCurvatureSignificance);
1881 float mu_scatteringNeighbourSignificance=0.;
1882 retrieveParam(mu, mu_scatteringNeighbourSignificance, xAOD::Muon::ParamDef::scatteringNeighbourSignificance);
1883 float mu_momentumBalanceSignificance=0.;
1884 retrieveParam(mu, mu_momentumBalanceSignificance, xAOD::Muon::ParamDef::momentumBalanceSignificance);
1885 float mu_qOverPSignificance=qOverPsignificance(mu);
1886 float mu_reducedChi2=mu.primaryTrackParticle()->chiSquared() / mu.primaryTrackParticle()->numberDoF();
1887 float mu_reducedChi2_ID=idtrack->chiSquared() / idtrack->numberDoF();
1888 float mu_reducedChi2_ME=metrack->chiSquared() / metrack->numberDoF();
1889 float mu_spectrometerFieldIntegral=0.;
1890 retrieveParam(mu, mu_spectrometerFieldIntegral, xAOD::Muon::ParamDef::spectrometerFieldIntegral);
1891 float mu_segmentDeltaEta=0;
1892 retrieveParam(mu, mu_segmentDeltaEta, xAOD::Muon::ParamDef::segmentDeltaEta);
1893 uint8_t mu_numberOfPixelHits=0;
1895 uint8_t mu_numberOfPixelDeadSensors=0;
1896 retrieveSummaryValue(mu, mu_numberOfPixelDeadSensors, xAOD::SummaryType::numberOfPixelDeadSensors);
1897 uint8_t mu_innerLargeHits=0;
1899 uint8_t mu_innerSmallHits=0;
1901 uint8_t mu_middleLargeHits=0;
1903 uint8_t mu_middleSmallHits=0;
1905 uint8_t mu_outerLargeHits=0;
1907 uint8_t mu_outerSmallHits=0;
1909
1910 if(!isRun3())
1911 {
1912 input_features = {(float)mu_author,
1913 mu_rhoPrime,
1914 mu_scatteringCurvatureSignificance,
1915 mu_scatteringNeighbourSignificance,
1916 mu_momentumBalanceSignificance,
1917 mu_qOverPSignificance,
1918 mu_reducedChi2,
1919 mu_reducedChi2_ID,
1920 mu_reducedChi2_ME,
1921 mu_spectrometerFieldIntegral,
1922 mu_segmentDeltaEta,
1923 (float)mu_numberOfPixelHits,
1924 (float)mu_numberOfPixelDeadSensors,
1925 (float)mu_innerLargeHits,
1926 (float)mu_innerSmallHits,
1927 (float)mu_middleLargeHits,
1928 (float)mu_middleSmallHits,
1929 (float)mu_outerLargeHits,
1930 (float)mu_outerSmallHits};
1931 }
1932 else
1933 {
1934 uint8_t mu_phiLayer1STGCHits=0;
1936 uint8_t mu_phiLayer2STGCHits=0;
1938 uint8_t mu_etaLayer1STGCHits=0;
1940 uint8_t mu_etaLayer2STGCHits=0;
1942 uint8_t mu_MMHits=0;
1944 input_features = {(float)mu_author,
1945 mu_rhoPrime,
1946 mu_scatteringCurvatureSignificance,
1947 mu_scatteringNeighbourSignificance,
1948 mu_momentumBalanceSignificance,
1949 mu_qOverPSignificance,
1950 mu_reducedChi2,
1951 mu_reducedChi2_ID,
1952 mu_reducedChi2_ME,
1953 mu_spectrometerFieldIntegral,
1954 mu_segmentDeltaEta,
1955 (float)mu_numberOfPixelHits,
1956 (float)mu_numberOfPixelDeadSensors,
1957 (float)mu_innerLargeHits,
1958 (float)mu_innerSmallHits,
1959 (float)mu_middleLargeHits,
1960 (float)mu_middleSmallHits,
1961 (float)mu_outerLargeHits,
1962 (float)mu_outerSmallHits,
1963 (float)mu_phiLayer1STGCHits,
1964 (float)mu_phiLayer2STGCHits,
1965 (float)mu_etaLayer1STGCHits,
1966 (float)mu_etaLayer2STGCHits,
1967 (float)mu_MMHits};
1968 }
1969
1970 float score=-999.;
1971 std::vector<int64_t> inputShape = {1, static_cast<int64_t>(input_features.size())};
1972
1973 AthInfer::InputDataMap inputData;
1974 inputData["flatten_input"] = std::make_pair(
1975 inputShape, std::move(input_features)
1976 );
1977
1978 AthInfer::OutputDataMap outputData;
1979 outputData["sequential"] = std::make_pair(
1980 std::vector<int64_t>{1, 1}, std::vector<float>{}
1981 );
1982
1983 if (!m_onnxTool->inference(inputData, outputData).isSuccess()) {
1984 ATH_MSG_WARNING("ONNX inference failed!");
1985 return -999.;
1986 }
1987 const auto& variant = outputData["sequential"].second;
1988 if (std::holds_alternative<std::vector<float>>(variant)) {
1989 const auto& vec = std::get<std::vector<float>>(variant);
1990 if (!vec.empty()) score = vec[0];
1991 else {
1992 ATH_MSG_WARNING("ONNX output vector is empty!");
1993 return -999.;
1994 }
1995 } else {
1996 ATH_MSG_WARNING("ONNX output is not a float vector!");
1997 return -999.;
1998 }
1999
2000 ATH_MSG_DEBUG("TightNNScore for muon with pT " << mu.pt() << " GeV, eta " << mu.eta() << " is " << score);
2001
2002 return score;
2003 }
2004
2005} // namespace CP
Scalar eta() const
pseudorapidity method
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_FATAL(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_VERBOSE(x)
#define ATH_MSG_WARNING(x)
#define ATH_MSG_DEBUG(x)
Handle class for reading from StoreGate.
std::vector< size_t > vec
unsigned int uint
static Double_t a
std::string PathResolverFindCalibFile(const std::string &logical_file_name)
#define y
#define x
#define z
Gaudi::Property< bool > m_developMode
float rhoPrime(const xAOD::Muon &muon) const
Returns rhoPrime of the muon (see definition in https://cds.cern.ch/record/2665711 ).
virtual bool passedHighPtCuts(const xAOD::Muon &) const override
Returns true if the muon passes the standard MCP High Pt cuts.
float qOverPsignificance(const xAOD::Muon &muon) const
Returns q/p significance of the muon (see definition in https://cds.cern.ch/record/2665711 ).
std::unique_ptr< TH1 > m_tightWP_mediumPt_rhoCuts
Gaudi::Property< bool > m_SctCutOff
Gaudi::Property< bool > m_PixCutOff
virtual xAOD::Muon::Quality getQuality(const xAOD::Muon &mu) const override
Returns the quality of the muon. To set the value on the muon, instead call setQuality(xAOD::Muon&) c...
std::unique_ptr< MVAUtils::BDT > m_MuidCO
virtual void setQuality(xAOD::Muon &mu) const override
set the passes low pT cuts variable of the muon
std::vector< double > m_lowPtMuidCO_scaler
std::unique_ptr< TMVA::Reader > m_readerE_MUID
std::unique_ptr< TMVA::Reader > m_reader_MUTAGIMO_etaBin2
Gaudi::Property< bool > m_isRun3
std::unique_ptr< TMVA::Reader > m_readerO_MUGIRL
std::unique_ptr< TH1 > m_tightWP_highPt_rhoCuts
std::unique_ptr< TH1 > m_tightWP_lowPt_qOverPCuts
Gaudi::Property< double > m_maxEta
bool passedBMVmimicCut(const xAOD::Muon &) const
Returns true if the muon passes a cut which mimics the effect of the combined error cut This is neces...
virtual bool isBadMuon(const xAOD::Muon &) const override
Returns true if a CB muon fails some loose quaility requirements designed to remove pathological trac...
virtual ~MuonSelectionTool()
virtual bool passedCaloTagQuality(const xAOD::Muon &mu) const override
Returns true if the muon passed additional calo-tag quality cuts.
std::vector< double > m_lowPtMuidCO_means
Gaudi::Property< bool > m_SiHolesCutOff
bool isRun3(bool forceOnTheFly=false) const
Gaudi::Property< bool > m_toroidOff
Gaudi::Property< bool > m_geoOnTheFly
void fillSummary(const xAOD::Muon &muon, hitSummary &summary) const
functions that fills a hitSummary for a muon
Gaudi::Property< bool > m_doBadMuonVetoMimic
Gaudi::Property< bool > m_useLRT
Gaudi::Property< std::string > m_MVAreaderFile_ODD_MuidCB
Gaudi::Property< bool > m_excludeNSWFromPrecisionLayers
Gaudi::Property< std::string > m_MVAreaderFile_MuTagIMO_etaBin2
virtual int getResolutionCategory(const xAOD::Muon &) const override
Returns an integer corresponding to categorization of muons with different resolutions.
std::vector< double > m_lowPtMuGirl_scaler
bool passedLowPtEfficiencyMVACutRun3(const xAOD::Muon &) const
Gaudi::Property< bool > m_useBEEBISInHighPtRun3
virtual bool passedLowPtEfficiencyCuts(const xAOD::Muon &) const override
Returns true if the muon passes the standard MCP low pt cuts.
Gaudi::Property< std::string > m_calibration_version
Gaudi::Property< bool > m_useCaloScore
Gaudi::Property< bool > m_recalcPrecisionLayerswNSW
Gaudi::Property< bool > m_turnOffMomCorr
void retrieveParam(const xAOD::Muon &muon, float &value, const xAOD::Muon::ParamDef param) const
std::unique_ptr< TF1 > m_BMVcutFunction_endcap
std::unique_ptr< TH1 > m_tightWP_lowPt_rhoCuts
Gaudi::Property< bool > m_calculateTightNNScore
Gaudi::Property< int > m_caloScoreWP
Gaudi::Property< std::string > m_MVAreaderFile_ODD_MuGirl
Gaudi::Property< std::string > m_MVAreaderFile_MuTagIMO_etaBin3
virtual bool passedCaloScore(const xAOD::Muon &mu) const override
Returns true if the muon passed the CaloScore calo-tag working point.
asg::AcceptInfo m_acceptInfo
Store selection information.
virtual StatusCode initialize() override
Function initialising the tool.
virtual void setPassesIDCuts(xAOD::Muon &) const override
set the passes ID cuts variable of the muon
Gaudi::Property< unsigned long long > m_expertMode_EvtNumber
std::vector< const xAOD::MuonSegment * > getSegmentsSorted(const xAOD::Muon &mu) const
Returns a vector of the muon's segments, sorted according to chamber index.
Gaudi::Property< int > m_quality
Gaudi::Property< std::string > m_BMVcutFile
file for bad muon veto mimic cut functions
virtual float getTightNNScore(const xAOD::Muon &muon) const override
Returns the TightNNscore of the muon, an experimental ML-based score for the identification of muons ...
Gaudi::Property< bool > m_use2stationMuonsHighPt
virtual bool passedIDCuts(const xAOD::Muon &) const override
Returns true if the muon passes the standard MCP ID cuts.
Gaudi::Property< bool > m_disablePtCuts
unsigned int getRunNumber(bool needOnlyCorrectYear=false) const
bool isBMG(const float eta, const float phi) const
Check if muon eta/phi falls in BMG chambers.
virtual asg::AcceptData accept(const xAOD::IParticle *p) const override
Get the decision using a generic IParticle pointer.
virtual const asg::AcceptInfo & getAcceptInfo() const override
Get an object describing the "selection steps" of the tool.
Gaudi::Property< std::string > m_MVAreaderFile_EVEN_MuidCB
std::unique_ptr< TF1 > m_BMVcutFunction_barrel
std::unique_ptr< TMVA::Reader > m_readerE_MUGIRL
void IdMsPt(const xAOD::Muon &muon, float &idPt, float &msPt) const
bool passTight(const xAOD::Muon &mu, float rho, float oneOverPSig) const
Returns true if the muon passed the tight working point cuts.
std::unique_ptr< TMVA::Reader > m_readerO_MUID
Gaudi::Property< std::string > m_MVAreaderFile_EVEN_MuGirl
Gaudi::Property< bool > m_TrtCutOff
ToolHandle< AthInfer::IAthInferenceTool > m_onnxTool
Gaudi::Property< bool > m_useSegmentTaggedLowPt
virtual bool passedErrorCutCB(const xAOD::Muon &) const override
Returns true if a CB muon passes a pt- and eta-dependent cut on the relative CB q/p error.
virtual bool passedMuonCuts(const xAOD::Muon &) const override
Returns true if the muon passes a standardized loose preselection.
StatusCode getHist(TFile *file, const std::string &histName, std::unique_ptr< TH1 > &hist) const
Checks for each histogram.
std::unique_ptr< TMVA::Reader > m_reader_MUTAGIMO_etaBin1
Gaudi::Property< bool > m_useAllAuthors
std::vector< double > m_lowPtMuGirl_means
Gaudi::Property< int > m_expertMode_RunNumber
std::unique_ptr< TMVA::Reader > m_reader_MUTAGIMO_etaBin3
bool isBIS78(const float eta, const float phi) const
Check if muon eta/phi falls in BIS7/8 chambers.
bool passedLowPtEfficiencyMVACut(const xAOD::Muon &) const
void retrieveSummaryValue(const P &muon, T &value, const S type, bool ignoreMissing=false) const
helper function to retrieve a hitSummary value
SG::ReadHandleKey< xAOD::EventInfo > m_eventInfo
MuonSelectionTool(const std::string &name="MuonSelection")
Create a proper constructor for Athena.
std::unique_ptr< MVAUtils::BDT > m_MuGirl
Gaudi::Property< bool > m_useMVALowPt
Gaudi::Property< std::string > m_custom_dir
Gaudi::Property< std::string > m_MVAreaderFile_MuTagIMO_etaBin1
bool isBEE(const float eta, const float phi) const
Check if muon eta/phi falls in BEE chambers.
void setCutResult(const std::string &cutName, bool cutResult)
Set the result of a cut, based on the cut name (safer).
Definition AcceptData.h:135
AsgTool(const std::string &name)
Constructor specifying the tool instance's name.
Definition AsgTool.cxx:58
@ IS_SIMULATION
true: simulation, false: data
Class providing the definition of the 4-vector interface.
float theta() const
Returns the parameter, which has range 0 to .
float numberDoF() const
Returns the number of degrees of freedom of the overall track or vertex fit as float.
virtual double phi() const override final
The azimuthal angle ( ) of the particle (has range to .).
const ParametersCovMatrix_t definingParametersCovMatrix() const
Returns the 5x5 symmetric matrix containing the defining parameters covariance matrix.
float qOverP() const
Returns the parameter.
virtual double pt() const override final
The transverse momentum ( ) of the particle.
float chiSquared() const
Returns the of the overall track fit.
virtual double eta() const override final
The pseudorapidity ( ) of the particle.
float charge() const
Returns the charge.
std::map< std::string, InferenceData > OutputDataMap
std::map< std::string, InferenceData > InputDataMap
Select isolated Photons, Electrons and Muons.
constexpr float MeVtoGeV
bool first
Definition DeMoScan.py:534
Muon::MuonStationIndex::ChIndex ChIdx
constexpr int toInt(const EnumType enumVal)
ChIndex
enum to classify the different chamber layers in the muon spectrometer
void sort(typename DataModel_detail::iterator< DVL > beg, typename DataModel_detail::iterator< DVL > end)
Specialization of sort for DataVector/List.
@ Muon
The object is a muon.
Definition ObjectType.h:48
setRcore setEtHad setFside pt
setSAddress etaMS
TrackParticle_v1 TrackParticle
Reference the current persistent version:
Muon_v1 Muon
Reference the current persistent version:
MuonSegment_v1 MuonSegment
Reference the current persistent version:
@ numberOfPixelHoles
number of pixel layers on track with absence of hits [unit8_t].
@ numberOfPrecisionLayers
layers with at least 3 hits [unit8_t].
@ numberOfTRTHits
number of TRT hits [unit8_t].
@ numberOfSCTDeadSensors
number of dead SCT sensors crossed [unit8_t].
@ numberOfPrecisionHoleLayers
layers with holes AND no hits [unit8_t].
@ numberOfSCTHits
number of hits in SCT [unit8_t].
@ numberOfGoodPrecisionLayers
layers with at least 3 hits that are not deweighted [uint8_t]
@ numberOfPixelHits
these are the pixel hits, including the b-layer [unit8_t].
@ numberOfTRTOutliers
number of TRT outliers [unit8_t].
@ numberOfPixelDeadSensors
number of dead pixel sensors crossed [unit8_t].
@ numberOfSCTHoles
number of SCT holes [unit8_t].
@ SiSpacePointsSeedMaker_LargeD0
@ middleSmallHoles
number of precision holes in the middle small layer
@ outerSmallHits
number of precision hits in the outer small layer
@ middleSmallHits
number of precision hits in the middle small layer
@ middleClosePrecisionHits
number of close precision hits in the middle layer
@ outerLargeHits
number of precision hits in the outer large layer
@ middleLargeHits
number of precision hits in the middle large layer
@ outerClosePrecisionHits
number of close precision hits in the outer layer
@ isSmallGoodSectors
if non-deweighted track chambers are small
@ extendedSmallHits
number of precision hits in the extended small layer
@ extendedLargeHits
number of precision hits in the extended large layer
@ phiLayer1STGCHits
number of phi hits in the first STGC trigger layer (STGC1)
@ outerLargeHoles
number of precision holes in the outer large layer
@ extendedSmallHoles
number of precision holes in the extended small layer
@ innerLargeHits
number of precision hits in the inner large layer
@ middleLargeHoles
number of precision holes in the middle large layer
@ etaLayer2STGCHits
number of eta hits in the second STGC trigger layer (STGC2)
@ etaLayer1STGCHits
number of eta hits in the first STGC trigger layer (STGC1)
@ phiLayer2STGCHits
number of phi hits in the second STGC trigger layer (STGC2)
@ combinedTrackOutBoundsPrecisionHits
total out-of-bounds hits on the combined track
@ cscUnspoiledEtaHits
number of unspoiled CSC eta clusters on track
@ outerSmallHoles
number of precision holes in the outer small layer
@ innerSmallHits
number of precision hits in the inner small layer
struct to handle easily number of hits in different parts of the MS
TFile * file
Tell the compiler to optimize assuming that FP may trap.
#define CXXUTILS_TRAPPING_FP
Definition trapping_fp.h:24