diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt b/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a8eefd921932c91ed1c9b78b3b085f08e9901645 --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/CMakeLists.txt @@ -0,0 +1,46 @@ +# Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +####################### +# TruthClassification # +####################### +atlas_subdir(TruthClassification) + +atlas_add_library(TruthClassificationLib + TruthClassification/*.h Root/*.cxx + PUBLIC_HEADERS TruthClassification + LINK_LIBRARIES AsgTools AsgAnalysisInterfaces xAODBase xAODEgamma xAODMuon + PRIVATE_LINK_LIBRARIES MCTruthClassifierLib) + +atlas_add_dictionary(TruthClassificationDict + TruthClassification/TruthClassificationDict.h + TruthClassification/selection.xml + LINK_LIBRARIES TruthClassificationLib) + +if (NOT XAOD_STANDALONE) + atlas_add_component(TruthClassification + src/*.h src/*.cxx src/components/*.cxx + LINK_LIBRARIES TruthClassificationLib) +endif () + +# Extra test libraries based on the build environment: +set(xaod_access_lib) +set(extra_libs) +# ... for AnalysisBase +if (XAOD_STANDALONE) + set(xaod_access_lib xAODRootAccess) +else() + # ... for AthAnalysisBase (Athena calls this POOLRootAccess) + set(xaod_access_lib POOLRootAccessLib) +endif() + +# Executable(s) in the package: +atlas_add_executable(testTruthClassificationTool + test/test_TruthClassificationTool.cxx + LINK_LIBRARIES TruthClassificationLib xAODEventInfo xAODTruth MuonAnalysisInterfacesLib + ${xaod_access_lib} ${extra_libs}) + +# Tests +# this test currently needs a special input file as the default ASG test files +# do not contain all necessary information +atlas_add_test(TruthClassificationToolTest + SCRIPT testTruthClassificationTool + /cvmfs/atlas-nightlies.cern.ch/repo/data/data-art/SUSYTools/DAOD_PHYSVAL.mc16_13TeV.410470.FS_mc16e_p4017.PHYSVAL.pool.root) diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx new file mode 100644 index 0000000000000000000000000000000000000000..b3b3b5a047f0e1097f7b41599ea6479bb2142056 --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/Root/TruthClassificationTool.cxx @@ -0,0 +1,622 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +#include <set> + +#include <MCTruthClassifier/MCTruthClassifierDefs.h> +#include <xAODTruth/TruthParticle.h> +#include <xAODTruth/xAODTruthHelpers.h> + +#include "TruthClassification/TruthClassificationTool.h" + +namespace +{ + bool isInSet(int origin, const std::set<int> &s) + { + return s.find(origin) != s.end(); + } +} + + +TruthClassificationTool::TruthClassificationTool(const std::string &type) + : asg::AsgTool(type) +{ + declareProperty ("separateChargeFlipElectrons", m_separateChargeFlipElectrons, "separate prompt charge-flipped electrons"); +} + + +StatusCode TruthClassificationTool::classify(const xAOD::IParticle &particle, + unsigned int &classification) const +{ + Truth::Type type = Truth::Type::Unknown; + ANA_CHECK(classify(particle, type)); + classification = static_cast<int>(type); + return StatusCode::SUCCESS; +} + + +StatusCode TruthClassificationTool::classify(const xAOD::IParticle &particle, + Truth::Type &classification) const +{ + if (dynamic_cast<const xAOD::Electron *> (&particle)) + { + ANA_CHECK(classify(*dynamic_cast<const xAOD::Electron *> (&particle), classification)); + } + else if (dynamic_cast<const xAOD::Muon *> (&particle)) + { + ANA_CHECK(classify(*dynamic_cast<const xAOD::Muon *> (&particle), classification)); + } + else + { + ANA_MSG_ERROR("Only electrons and muons are supported."); + return StatusCode::FAILURE; + } + + return StatusCode::SUCCESS; +} + + +StatusCode TruthClassificationTool::classify(const xAOD::Electron &electron, + Truth::Type &classification) const +{ + namespace MC = MCTruthPartClassifier; + + if (!m_truthPdgId.isAvailable(electron)) + { + ANA_MSG_ERROR("Electron does not have the 'truthPdgId' decoration."); + return StatusCode::FAILURE; + } + + if (!m_firstMotherTruthType.isAvailable(electron) + || !m_firstMotherTruthOrigin.isAvailable(electron) + || !m_firstMotherPdgId.isAvailable(electron)) + { + ANA_MSG_ERROR("Electron does not have one or more 'firstEgMother' decorations."); + return StatusCode::FAILURE; + } + + int type = m_truthType(electron); + int origin = m_truthOrigin(electron); + int pdgId = m_truthPdgId(electron); + int firstMotherType = m_firstMotherTruthType(electron); + int firstMotherOrigin = m_firstMotherTruthOrigin(electron); + int firstMotherPdgId = m_firstMotherPdgId(electron); + // not in the smart slimming list, thus only in few derivations + int lastMotherType = m_lastMotherTruthType.isAvailable(electron) ? m_lastMotherTruthType(electron) : -1; + int lastMotherOrigin = m_lastMotherTruthOrigin.isAvailable(electron) ? m_lastMotherTruthOrigin(electron) : -1; + int lastMotherPdgId = m_lastMotherPdgId.isAvailable(electron) ? m_lastMotherPdgId(electron) : -1; + // fallback recorations + int fallbackType{-1}; + if (m_fallbackTruthType.isAvailable(electron) && m_fallbackDR.isAvailable(electron)) + { + fallbackType = m_fallbackDR(electron) < 0.05 ? m_fallbackTruthType(electron) : -1; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Prompt Photon Conversions + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // gamma -> e+ e- + if (type == MC::BkgElectron + && (origin == MC::PhotonConv || origin == MC::ElMagProc) + && firstMotherType == MC::IsoPhoton && firstMotherOrigin == MC::PromptPhot) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // H -> gamma gamma, gamma -> e+ e- + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::IsoPhoton && firstMotherOrigin == MC::Higgs) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // bkg electrons from bkg photons + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::UnknownPhoton && firstMotherOrigin == MC::NonDefined) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // bkg photon from UndrPhot; (Here there is a generator level photon (not gen electron ) that later converts) + if (type == MC::BkgElectron + && (origin == MC::PhotonConv || origin == MC::ElMagProc ) + && firstMotherType == MC::BkgPhoton && firstMotherOrigin == MC::UndrPhot) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // type = 16 and origin = 38 (again, this is a photon) + if (type == MC::BkgPhoton && origin == MC::UndrPhot) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // Is an isolated photon + if (type == MC::IsoPhoton && pdgId == 22) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // electrons from ElMagProc + // when FSR, a better classification can be made with the fall back vars + if (type == MC::BkgElectron && origin == MC::ElMagProc + && firstMotherType == MC::UnknownPhoton && firstMotherOrigin == MC::NonDefined) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + if (type == MC::BkgElectron && origin == MC::ElMagProc + && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot) + { + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // TODO: Message from Otilia: """ + // but it's not clear if these electrons are really + // "fakes" or they should go in the real category (we don't know from where + // this photon is coming...). I would say more truth studies should be done. + // """ + // Hence the warning message... + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::Unknown && firstMotherOrigin == MC::ZBoson) + { + ANA_MSG_WARNING("Electron identified as from a PromptPhotonConversion, " + "but this type of electron needs further study!"); + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + + // when always a photon (last mum is a photon, even if the truth PDG is 11 and first mum PDG is 11 ): + // very likely these are internal conversions; last_mum_pdgId == 22 important as the cases with last_mum_pdgId == 11 were found to be quite often close to a true electron + if (type == MC::BkgElectron && firstMotherType == MC::BkgElectron + && origin == MC::PhotonConv && firstMotherOrigin == MC::PhotonConv + && std::abs(firstMotherPdgId) == 11 && std::abs(pdgId) == 11) + { // electron + if(lastMotherType == -1 || (lastMotherType == MC::GenParticle && (lastMotherPdgId == 22 || std::abs(lastMotherPdgId) == 11))) + { + // lastMotherType == -1 ==> when the last mother info is not stored in the derivations + classification = Truth::Type::PromptPhotonConversion; + return StatusCode::SUCCESS; + } + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Is muon reco as electron or ele radiated by muons + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if ((type == MC::NonIsoElectron || type == MC::NonIsoPhoton) + && (origin == MC::Mu || firstMotherOrigin == MC::Mu)) + { + classification = Truth::Type::ElectronFromMuon; + return StatusCode::SUCCESS; + } + + if (type == MC::BkgElectron && firstMotherOrigin == MC::Mu) + { + classification = Truth::Type::ElectronFromMuon; + return StatusCode::SUCCESS; + } + + if (type == MC::IsoMuon || type == MC::NonIsoMuon || type == MC::BkgMuon || type == MC::UnknownMuon) { + classification = Truth::Type::ElectronFromMuon; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Tau decays + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // Non-isolated electron/photon from tau decay + if ((type == MC::NonIsoElectron || type == MC::NonIsoPhoton) + && origin == MC::TauLep) + { + classification = Truth::Type::TauDecay; + return StatusCode::SUCCESS; + } + + // tau -> tau gamma, gamma -> e+ e-, etc + if ((firstMotherType == MC::NonIsoElectron || firstMotherType == MC::NonIsoPhoton) + && firstMotherOrigin == MC::TauLep) + { + classification = Truth::Type::TauDecay; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Light hadron sources + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type == MC::Hadron || fallbackType == MC::Hadron) + { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + + if (firstMotherType == MC::BkgElectron) + { + if ((origin == MC::DalitzDec || origin == MC::ElMagProc) + && (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin))) + { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + } + + if (type == MC::BkgElectron) + { + if (origin == MC::DalitzDec || firstMotherOrigin == MC::DalitzDec) { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + if (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin)) { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + } + + if (type == MC::BkgPhoton + && (hasLightHadronOrigin(origin) || hasLightHadronOrigin(firstMotherOrigin))) + { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // From B hadron + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (hasBHadronOrigin(origin) || hasBHadronOrigin(firstMotherOrigin)) + { + classification = Truth::Type::BHadronDecay; + return StatusCode::SUCCESS; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // From C hadron + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type != MC::IsoElectron && (hasCHadronOrigin(origin) || hasCHadronOrigin(firstMotherOrigin))) + { + classification = Truth::Type::CHadronDecay; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Prompt / Isolated electrons + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (isPromptElectron(electron)) + { + if (m_separateChargeFlipElectrons && isChargeFlipElectron(electron)) + { + classification = Truth::Type::ChargeFlipIsoElectron; + return StatusCode::SUCCESS; + } + + classification = Truth::Type::IsoElectron; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Unknown & known Unknown + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + // TODO: See if we want this or not. Now we check if this is something we are + // able to classify or not. Note that this might be a bit dangerous because + // the reasons for not having origin and status codes might be complex. The + // main idea is to weed out things we don't have a hope of classifying due to + // missing or unknown information. + const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(electron); + int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : 0; + bool stable = (status == 1); + + if (origin == MC::NonDefined && firstMotherOrigin == MC::NonDefined) + { + if (!stable) + { + if ((type == MC::Unknown || type == MC::UnknownPhoton) && firstMotherType == MC::Unknown) + { + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + } else { + if ((type == MC::Unknown && firstMotherType == MC::Unknown) + || (type == MC::UnknownElectron && firstMotherType == MC::UnknownElectron)) + { + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + + if (type == MC::UnknownPhoton) + { + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + } + } + + // non-iso photons with no info available to classify + if (type == MC::NonIsoPhoton && origin == MC::FSRPhot && pdgId == 22 + && firstMotherType == 0 && firstMotherOrigin == 0 && firstMotherPdgId == 0) + { + if (lastMotherType == -1 || (lastMotherType == 0 && lastMotherOrigin == 0 && lastMotherPdgId == 0)) + { // last_firstMotherType == -1 ==> when the last_mum info is not stored in the derivations + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + } + + ANA_MSG_WARNING("Electron type unknown: type = " << type << ", origin = " << origin); + + // debug printout + if (truthParticle != nullptr) + { + const xAOD::TruthParticle *parent = truthParticle; + ATH_MSG_DEBUG("Unknown particle decay chain:"); + std::string out = "\t"; + while (parent != nullptr) + { + out.append(std::to_string(parent->pdgId())); + parent = parent->parent(); + if (parent) out.append(" -> "); + } + ATH_MSG_DEBUG(out); + } + + classification = Truth::Type::Unknown; + return StatusCode::SUCCESS; +} + + +StatusCode TruthClassificationTool::classify(const xAOD::Muon &muon, + Truth::Type &classification) const +{ + namespace MC = MCTruthPartClassifier; + + int type = m_truthType(muon); + int origin = m_truthOrigin(muon); + // fallback recorations + int fallbackType{-1}; + if (m_fallbackTruthType.isAvailable(muon) && m_fallbackDR.isAvailable(muon)) + { + fallbackType = m_fallbackDR(muon) < 0.05 ? m_fallbackTruthType(muon) : -1; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // muons from taus + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type == MC::NonIsoMuon && origin == MC::TauLep) + { + classification = Truth::Type::TauDecay; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Light hadron sources + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type == MC::BkgMuon && hasLightHadronOrigin(origin)) + { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + + if (type == MC::Hadron || fallbackType == MC::Hadron ) + { + classification = Truth::Type::LightFlavorDecay; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // From B hadron + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (hasBHadronOrigin(origin)) + { + classification = Truth::Type::BHadronDecay; + return StatusCode::SUCCESS; + } + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // From C hadron + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type != MC::IsoMuon && hasCHadronOrigin(origin)) + { + classification = Truth::Type::CHadronDecay; + return StatusCode::SUCCESS; + } + // TODO:: There is a comment in the example code about J/psi but there is a + // separate origin code for that: `MC::JPsi == 28.` --> this might not be in all samples/generators? + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // prompt muons + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Check if the type of muon is IsoMuon(6) and whether the origin + // of the muon is from a prompt source + static const std::set<int> promptOrigin({ + MC::SingleMuon, // Single muon (origin = 2) from muon twiki + MC::top, + MC::WBoson, + MC::ZBoson, + MC::Higgs, + MC::HiggsMSSM, + MC::SUSY, + MC::DiBoson, + MC::CCbarMeson, // PromptQuarkoniumDecay + MC::BBbarMeson, + }); + if (type == MC::IsoMuon && isInSet(origin, promptOrigin)) + { + classification = Truth::Type::PromptMuon; + return StatusCode::SUCCESS; + } + + + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + // Unknown & known Unknown + // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if (type == MC::UnknownMuon && origin == MC::NonDefined) + { + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + + const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(muon); + int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : 0; + bool stable = (status == 1); + + if (!stable) { + if (type == MC::Unknown && origin == MC::NonDefined) + { // Data + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + if (type == -99999 && origin == -99999) + { // MC - no status = 1 truth particle associated with the primary track + classification = Truth::Type::KnownUnknown; + return StatusCode::SUCCESS; + } + } + + ATH_MSG_WARNING("Muon type unknown: type = " << type << ", origin = " << origin); + + classification = Truth::Type::Unknown; + return StatusCode::SUCCESS; +} + + +bool TruthClassificationTool::isPromptElectron(const xAOD::Electron &electron) const +{ + namespace MC = MCTruthPartClassifier; + + // Electron is IsoElectron - return true + int type = m_truthType(electron); + if (type == MC::IsoElectron) + { + return true; + } + + int origin = m_truthOrigin(electron); + int pdgId = m_truthPdgId(electron); + int firstMotherType = m_firstMotherTruthType(electron); + int firstMotherOrigin = m_firstMotherTruthOrigin(electron); + + // Adding these cases from ElectronEfficiencyHelpers + if (firstMotherType == MC::IsoElectron && std::abs(m_firstMotherPdgId(electron)) == 11) + { + return true; + } + + // FSR photons from electrons + if (origin == MC::FSRPhot && type == MC::NonIsoPhoton && std::abs(pdgId) == 11) + { + return true; + } + + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot + && std::abs(pdgId) == 11) + { + return true; + } + + // If we reach here then it is not a prompt electron + return false; +} + + + +bool TruthClassificationTool::isChargeFlipElectron(const xAOD::Electron &electron) const +{ + namespace MC = MCTruthPartClassifier; + + int type = m_truthType(electron); + int origin = m_truthOrigin(electron); + int pdgId = m_truthPdgId(electron); + int firstMotherType = m_firstMotherTruthType(electron); + int firstMotherOrigin = m_firstMotherTruthOrigin(electron); + int firstMotherPdgId = m_firstMotherPdgId(electron); + + // not consider FSR photons from electrons (the photon has no charge) + if (origin == MC::FSRPhot && type == MC::NonIsoPhoton && std::abs(pdgId) == 11) + { + return false; + } + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::NonIsoPhoton && firstMotherOrigin == MC::FSRPhot + && std::abs(pdgId) == 11) + { + return false; + } + + // bkg electrons with no additional info to help us classify them FSR -- not in the charge flip category + if (type == MC::BkgElectron && origin == MC::PhotonConv + && firstMotherType == MC::BkgElectron && firstMotherOrigin == MC::PhotonConv + && std::abs(pdgId) == 11) + { + return false; + } + + if (electron.charge() != 0) { + return (firstMotherPdgId * electron.charge()) > 0; + } + + return (firstMotherPdgId * (-pdgId)) > 0; +} + + +bool TruthClassificationTool::hasBHadronOrigin(int origin) const +{ + namespace MC = MCTruthPartClassifier; + static const std::set<int> b_hadrons({ + MC::BottomMeson, + MC::BBbarMeson, + MC::BottomBaryon, + }); + return isInSet(origin, b_hadrons); +} + +bool TruthClassificationTool::hasCHadronOrigin(int origin) const { + namespace MC = MCTruthPartClassifier; + static const std::set<int> c_hadrons({ + MC::CharmedMeson, + MC::CCbarMeson, + MC::CharmedBaryon, + }); + return isInSet(origin, c_hadrons); +} + +bool TruthClassificationTool::hasLightHadronOrigin(int origin) const { + namespace MC = MCTruthPartClassifier; + static const std::set<int> light_source({ + MC::PiZero, + MC::LightMeson, + MC::StrangeMeson, + MC::LightBaryon, + MC::StrangeBaryon, + MC::PionDecay, + MC::KaonDecay, + }); + return isInSet(origin, light_source); +} diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h new file mode 100644 index 0000000000000000000000000000000000000000..c7f86cf43a9617afa21aae615439b0b2fdbe9743 --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationDict.h @@ -0,0 +1,10 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_ +#define TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_ + +#include "TruthClassification/TruthClassificationTool.h" + +#endif // TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_DICT_H_ diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h new file mode 100644 index 0000000000000000000000000000000000000000..64dbdee157985fff91a72da983e80c40a46f32a1 --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/TruthClassificationTool.h @@ -0,0 +1,71 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_ +#define TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_ + +#include <string> + +#include <AsgAnalysisInterfaces/ITruthClassificationTool.h> +#include <AsgTools/AsgTool.h> +#include <xAODEgamma/Electron.h> +#include <xAODMuon/Muon.h> + + +/// \brief a tool to classify particles based on their type and origin +class TruthClassificationTool : virtual public ITruthClassificationTool, public asg::AsgTool +{ + ASG_TOOL_CLASS2(TruthClassificationTool, IClassificationTool, ITruthClassificationTool) + +public: + explicit TruthClassificationTool(const std::string &type); + + /// \brief classify and return unsigned int + virtual StatusCode classify(const xAOD::IParticle &particle, + unsigned int &classification) const override; + + /// \brief classify and return Truth::Type + virtual StatusCode classify(const xAOD::IParticle &particle, + Truth::Type &classification) const override; + +private: + /// \brief electron classification helper + StatusCode classify(const xAOD::Electron &electron, + Truth::Type &classification) const; + + /// \brief muon classification helper + StatusCode classify(const xAOD::Muon &muon, + Truth::Type &classification) const; + + /// \brief separately store charge-flip electrons + bool m_separateChargeFlipElectrons{}; + + // accessors + const SG::AuxElement::ConstAccessor<int> m_truthType{"truthType"}; + const SG::AuxElement::ConstAccessor<int> m_truthOrigin{"truthOrigin"}; + const SG::AuxElement::ConstAccessor<int> m_truthPdgId{"truthPdgId"}; + const SG::AuxElement::ConstAccessor<int> m_firstMotherTruthType{"firstEgMotherTruthType"}; + const SG::AuxElement::ConstAccessor<int> m_firstMotherTruthOrigin{"firstEgMotherTruthOrigin"}; + const SG::AuxElement::ConstAccessor<int> m_firstMotherPdgId{"firstEgMotherPdgId"}; + const SG::AuxElement::ConstAccessor<int> m_lastMotherTruthType{"lastEgMotherTruthType"}; + const SG::AuxElement::ConstAccessor<int> m_lastMotherTruthOrigin{"lastEgMotherTruthOrigin"}; + const SG::AuxElement::ConstAccessor<int> m_lastMotherPdgId{"lastEgMotherPdgId"}; + const SG::AuxElement::ConstAccessor<int> m_fallbackTruthType{"TruthClassifierFallback_truthType"}; + const SG::AuxElement::ConstAccessor<int> m_fallbackTruthOrigin{"TruthClassifierFallback_truthOrigin"}; + const SG::AuxElement::ConstAccessor<float> m_fallbackDR{"TruthClassifierFallback_dR"}; + + /// \brief a helper to check if an electron is prompt + bool isPromptElectron(const xAOD::Electron &electron) const; + /// \brief a helper to check if an electron has incorrectly reconstructed charge + bool isChargeFlipElectron(const xAOD::Electron &electron) const; + + /// \brief a helper to check if the origin is a b-hadron + bool hasBHadronOrigin(int origin) const; + /// \brief a helper to check if the origin is a c-hadron + bool hasCHadronOrigin(int origin) const; + /// \brief a helper to check if the origin is a light hadron + bool hasLightHadronOrigin(int origin) const; +}; + +#endif // TRUTH_CLASSIFICATION__TRUTH_CLASSIFICATION_TOOL_H_ diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml new file mode 100644 index 0000000000000000000000000000000000000000..d7ab4e95c19f66e6e8a26bfa9830ebd6dc295dc0 --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/TruthClassification/selection.xml @@ -0,0 +1,3 @@ +<lcgdict> + <class name="TruthClassificationTool" /> +</lcgdict> diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx new file mode 100644 index 0000000000000000000000000000000000000000..bc92f6d4147a0f585c31fdb7a2b4cf7a91c76ebf --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/src/components/TruthClassification_entries.cxx @@ -0,0 +1,7 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +#include "TruthClassification/TruthClassificationTool.h" + +DECLARE_COMPONENT(TruthClassificationTool) diff --git a/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx b/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx new file mode 100644 index 0000000000000000000000000000000000000000..79447d720ab325bb46d1da1a852e6a0eaa51db9c --- /dev/null +++ b/PhysicsAnalysis/AnalysisCommon/TruthClassification/test/test_TruthClassificationTool.cxx @@ -0,0 +1,346 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +// EDM include(s): +#include <AsgMessaging/MessageCheck.h> +#include <AsgTools/AnaToolHandle.h> + +// Project dependent include(s) +#ifdef XAOD_STANDALONE +#include <xAODRootAccess/Init.h> +#include <xAODRootAccess/TEvent.h> +#else +#include <POOLRootAccess/TEvent.h> +#endif + +// Tool interface(s): +#include <AsgAnalysisInterfaces/IClassificationTool.h> +#include <MuonAnalysisInterfaces/IMuonSelectionTool.h> + +// EDM include(s): +#include <xAODEventInfo/EventInfo.h> +#include <xAODEgamma/ElectronContainer.h> +#include <xAODMuon/MuonContainer.h> +#include <xAODTruth/TruthParticle.h> +#include <xAODTruth/xAODTruthHelpers.h> + +// ROOT include(s): +#include <TFile.h> + +// std +#include <iomanip> +#include <memory> + +// messaging +ANA_MSG_HEADER(Test) +ANA_MSG_SOURCE(Test, "TruthClassification") +using namespace Test; + +int main(int argc, char* argv[]) +{ + ANA_CHECK_SET_TYPE (int); // makes ANA_CHECK return ints if exiting function + + // Initialise the application: +#ifdef XAOD_STANDALONE + StatusCode::enableFailure(); + ANA_CHECK(xAOD::Init()); +#else + IAppMgrUI *app = POOL::Init(); +#endif + + // Use a default MC file for testing if none is provided + TString fileName = "$ASG_TEST_FILE_MC"; + if (argc < 2) { + ANA_MSG_WARNING("No file name received, using ASG_TEST_FILE_MC"); + } else { + fileName = argv[1]; // use the user provided file + } + + // The map of class names + std::map<int, std::string> classNames { + {0, "Unknown"}, + {1, "KnownUnknown"}, + {2, "IsoElectron"}, + {3, "ChargeFlipIsoElectron"}, + {4, "PromptMuon"}, + {5, "PromptPhotonConversion"}, + {6, "ElectronFromMuon"}, + {7, "TauDecay"}, + {8, "BHadronDecay"}, + {9, "CHadronDecay"}, + {10, "LightFlavorDecay"} + }; + + // Initialise TEvent reading +#ifdef XAOD_STANDALONE + std::unique_ptr<TFile> ptrFile(TFile::Open(fileName, "READ")); + xAOD::TEvent event(xAOD::TEvent::kClassAccess); + ANA_CHECK(event.readFrom(ptrFile.get())); +#else + POOL::TEvent event(POOL::TEvent::kClassAccess); + ANA_CHECK(event.readFrom(fileName)); +#endif + ANA_MSG_INFO("Opened file: " << fileName); + + // Decide how many events to run over: + uint64_t entries = event.getEntries(); + if (argc > 2) { + const uint64_t e = atoll(argv[2]); + if (e < entries) { + entries = e; + } + } + + bool do_electrons = true; + bool do_muons = true; + + // Create the truth classification tool: + ANA_MSG_INFO("Creating TruthClassificationTool..."); + asg::AnaToolHandle< CP::IClassificationTool > tool("TruthClassificationTool/TruthClassificationTool"); + ANA_CHECK(tool.retrieve()); + + const SG::AuxElement::ConstAccessor<int> truthTypeAcc("truthType"); + const SG::AuxElement::ConstAccessor<int> truthOriginAcc("truthOrigin"); + const SG::AuxElement::ConstAccessor<int> truthPdgIdAcc("truthPdgId"); + const SG::AuxElement::ConstAccessor<int> firstMotherTruthTypeAcc("firstEgMotherTruthType"); + const SG::AuxElement::ConstAccessor<int> firstMotherTruthOriginAcc("firstEgMotherTruthOrigin"); + const SG::AuxElement::ConstAccessor<int> firstMotherPdgIdAcc("firstEgMotherPdgId"); + const SG::AuxElement::ConstAccessor<int> lastMotherTruthTypeAcc("lastEgMotherTruthType"); + const SG::AuxElement::ConstAccessor<int> lastMotherTruthOriginAcc("lastEgMotherTruthOrigin"); + const SG::AuxElement::ConstAccessor<int> lastMotherPdgIdAcc("lastEgMotherPdgId"); + // Fallback variables + const SG::AuxElement::ConstAccessor<int> fallbackTruthTypeAcc("TruthClassifierFallback_truthType"); + const SG::AuxElement::ConstAccessor<int> fallbackTruthOriginAcc("TruthClassifierFallback_truthOrigin"); + const SG::AuxElement::ConstAccessor<float> fallbackDRAcc("TruthClassifierFallback_dR"); + // Ambiguity flag + const SG::AuxElement::ConstAccessor<int> ambiguityAcc("DFCommonAddAmbiguity"); + const SG::AuxElement::ConstAccessor<char> passDFLooseAcc("DFCommonElectronsLHLoose"); + const SG::AuxElement::ConstAccessor<char> passDFLooseBLAcc("DFCommonElectronsLHLooseBL"); + const SG::AuxElement::ConstAccessor<char> passDFTightAcc("DFCommonElectronsLHTight"); + + // Muon selection tool + asg::AnaToolHandle< CP::IMuonSelectionTool > muonSelectionTool("CP::MuonSelectionTool/MuonSelectionTool"); + // Tight: 0, Med: 1, Loose: 2, VeryLoose: 3 + ANA_CHECK(muonSelectionTool.setProperty("MuQuality", 1)); + ANA_CHECK(muonSelectionTool.setProperty("MaxEta", 2.4)); + ANA_CHECK(muonSelectionTool.retrieve()); + + std::map<int, int> el_counter; + std::map<int, int> mu_counter; + std::map<int, std::map<int, int>> unknown_el_type_origin_counter; + std::map<int, int> unknown_mu_origin_counter; + + std::map<int, std::map<int, std::map<int, std::map<int, int>>>> ambiguity_map; + + // Loop over the events: + for (uint64_t entry = 0; entry < entries; ++entry) + { + // Tell the object which entry to look at: + event.getEntry(entry); + + // Electrons + const xAOD::ElectronContainer *electrons{}; + ANA_CHECK(event.retrieve(electrons, "Electrons")); + + if (do_electrons) + { + for (const xAOD::Electron *el : *electrons) + { + // Look at electrons with pT > 1 GeV, |eta| < 2.47, LHLooseBL + if (el->pt() < 4500 || std::abs(el->eta()) > 2.47) + { + continue; + } + + if ((passDFLooseBLAcc.isAvailable(*el) && !passDFLooseBLAcc(*el)) + || (passDFLooseAcc.isAvailable(*el) && !passDFLooseAcc(*el))) + { + continue; + } + + unsigned int classification{}; + ANA_CHECK(tool->classify(*el, classification)); + + el_counter[classification]++; + + int ambiguity = ambiguityAcc.isAvailable(*el) ? ambiguityAcc(*el) : -99; + if (classification == 0) + { + /// not in the smart slimming list, thus only in few derivations + int type = truthTypeAcc(*el); + int origin = truthOriginAcc(*el); + int lastMotherType = lastMotherTruthTypeAcc.isAvailable(*el) ? lastMotherTruthTypeAcc(*el) : -1; + int lastMotherOrigin = lastMotherTruthOriginAcc.isAvailable(*el) ? lastMotherTruthOriginAcc(*el) : -1; + int lastMotherPdgId = lastMotherPdgIdAcc.isAvailable(*el) ? lastMotherPdgIdAcc(*el) : -1; + int fallbackType = (fallbackTruthTypeAcc.isAvailable(*el) && fallbackDRAcc(*el) < 0.05) ? fallbackTruthTypeAcc(*el) : -1; + int fallbackOrigin = (fallbackTruthOriginAcc.isAvailable(*el) && fallbackDRAcc(*el) < 0.05) ? fallbackTruthOriginAcc(*el) : -1; + float fallbackDR = fallbackDRAcc.isAvailable(*el) ? fallbackDRAcc(*el) : -1; + + const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(*el); + int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : -1; + int pdgId = truthPdgIdAcc.isAvailable(*el) + ? truthPdgIdAcc(*el) + : (truthParticle != nullptr && truthParticle->isAvailable<int>("pdgId")) ? truthParticle->pdgId() : 0; + + ANA_MSG_WARNING("Unknown electron passing loose selection with " + << "status = " << status + << ", type = " << type + << ", origin = " << origin + << ", PDG ID = " << pdgId + << ", first mother type = " << firstMotherTruthTypeAcc(*el) + << ", first mother origin = " << firstMotherTruthOriginAcc(*el) + << ", first mother PDG ID = " << firstMotherPdgIdAcc(*el) + << ", last mother type = " << lastMotherType + << ", last mother origin = " << lastMotherOrigin + << ", last mother PDG ID = " << lastMotherPdgId + << ", fallback type = " << fallbackType + << ", fallback origin = " << fallbackOrigin + << ", fallback DR= " << fallbackDR + << ", ambiguity = " << ambiguity + << ", pt = " << el->pt()); + + unknown_el_type_origin_counter[type][origin]++; + + if (truthParticle == nullptr) { + ANA_MSG_WARNING("Unknown electron is NOT truth matched!"); + } + } + + /// How ambiguity works (from + /// https://gitlab.cern.ch/atlas/athena/merge_requests/24852/) + /// https://indico.cern.ch/event/862748/contributions/3635039/attachments/1946503/3235131/conversionsIFF2019.11.18.pdf, page 13 + /// "It is meant to be used together with ambiguityType to tighten the conversion veto by requiring (ambiguityType == 0 && DFCommonAddAmbiguity<=0)" + /// Cuts to define the various types : + /// -1 : no other track, + /// 0 : other track exists but no good gamma reco, + /// 1 : gamma*, + /// 2 : material conversion + if (ambiguityAcc.isAvailable(*el)) + { + if (ambiguity >= -1) { + ambiguity_map[classification][ambiguity][truthTypeAcc(*el)][truthOriginAcc(*el)]++; + } + } + } + } + + // Muons + if (do_muons) + { + const xAOD::EventInfo *eventInfo{}; + ANA_CHECK(event.retrieve(eventInfo, "EventInfo")); + + const xAOD::MuonContainer *muons{}; + ANA_CHECK(event.retrieve(muons, "Muons")); + + for (const xAOD::Muon *mu : *muons) + { + if (mu->pt() < 3000 || std::abs(mu->eta()) > 2.5) + { + continue; + } + + if (!muonSelectionTool->accept(*mu)) { + continue; + } + + unsigned int classification{}; + ANA_CHECK(tool->classify(*mu, classification)); + + mu_counter[classification]++; + + if (classification == 0) + { + int type = truthTypeAcc(*mu); + int origin = truthOriginAcc(*mu); + int fallbackType = (fallbackTruthTypeAcc.isAvailable(*mu) && fallbackDRAcc(*mu) < 0.05) ? fallbackTruthTypeAcc(*mu) : -1; + int fallbackOrigin = (fallbackTruthOriginAcc.isAvailable(*mu) && fallbackDRAcc(*mu) < 0.05) ? fallbackTruthOriginAcc(*mu) : -1; + float fallbackDR = (fallbackDRAcc.isAvailable(*mu)) ? fallbackDRAcc(*mu) : -1; + + const xAOD::TruthParticle *truthParticle = xAOD::TruthHelpers::getTruthParticle(*mu); + int status = (truthParticle != nullptr && truthParticle->isAvailable<int>("status")) ? truthParticle->status() : -1; + int pdgId = truthPdgIdAcc.isAvailable(*mu) + ? truthPdgIdAcc(*mu) + : (truthParticle != nullptr && truthParticle->isAvailable<int>("pdgId")) ? truthParticle->pdgId() : 0; + + ANA_MSG_WARNING("Unknown muon passing Medium selection with " + << "status = " << status + << ", type = " << type + << ", origin = " << origin + << ", PDG ID = " << pdgId + << ", fallback type = " << fallbackType + << ", fallback origin = " << fallbackOrigin + << ", fallback DR= " << fallbackDR + << ", pt = " << mu->pt()); + + unknown_mu_origin_counter[origin]++; + + if (truthParticle == nullptr) { + ANA_MSG_WARNING("Unknown muon is NOT truth matched!"); + } + } + } + } + } // Event loop + + // Use standard c++ stdout for nice log + std::cout << std::endl << std::endl; + + std::cout << "Electron summary:" << std::endl; + for (const auto&[classification, count] : el_counter) + { + std::cout << " " << std::setw(25) << (classNames.at(classification) + ": ") << std::right << std::setw(6) << count << std::endl; + } + std::cout << std::endl; + + std::cout << "Muon summary:" << std::endl; + for (const auto&[classification, count] : mu_counter) + { + std::cout << " " << std::setw(25) << (classNames.at(classification) + ": ") << std::right << std::setw(6) << count << std::endl; + } + std::cout << std::endl; + + std::cout << "Unknown electron origin:" << std::endl; + for (const auto&[type, map] : unknown_el_type_origin_counter) + { + for (const auto&[origin, count] : map) + { + std::cout << " " << std::setw(2) << type << ", " << std::setw(2) << origin << ": " << std::setw(6) << count << std::endl; + } + } + std::cout << std::endl; + + std::cout << "Unknown muon origin:" << std::endl; + for (const auto&[origin, count] : unknown_mu_origin_counter) + { + std::cout << " " << std::setw(2) << origin << ": " << std::setw(6) << count << std::endl; + } + std::cout << std::endl; + + std::cout << "Electron ambiguity map:" << std::endl; + std::cout << " Classification | Amb. | Type | Origin | Count" << std::endl; + std::cout << "-----------------------------------------------------------" << std::endl; + for (const auto&[classification, map1] : ambiguity_map) { + for (const auto&[ambiguity, map2] : map1) { + for (const auto&[type, map3] : map2) { + for (const auto&[origin, count] : map3) { + std::cout << " " << std::setw(23) << classNames.at(classification) + << " | " << std::setw(4) << ambiguity + << " | " << std::setw(4) << type + << " | " << std::setw(6) << origin + << " | " << std::setw(6) << count + << std::endl; + } + } + } + } + std::cout << std::endl; + + // trigger finalization of all services and tools created by the Gaudi Application +#ifndef XAOD_STANDALONE + ANA_CHECK(app->finalize()); +#endif + + return 0; +} diff --git a/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h b/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h new file mode 100644 index 0000000000000000000000000000000000000000..d59900c858713b96c3b1caf7cb9e2ad6dcc0947c --- /dev/null +++ b/PhysicsAnalysis/Interfaces/AsgAnalysisInterfaces/AsgAnalysisInterfaces/ITruthClassificationTool.h @@ -0,0 +1,66 @@ +/* + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef ITRUTHCLASSIFICATIONTOOL_H_ +#define ITRUTHCLASSIFICATIONTOOL_H_ + +#include <iostream> +#include <type_traits> + +#include <AsgAnalysisInterfaces/IClassificationTool.h> +#include <xAODBase/IParticle.h> + + +namespace Truth +{ + +/// \brief truth classification type enum +enum class Type { + Unknown, + KnownUnknown, + IsoElectron, + ChargeFlipIsoElectron, + PromptMuon, + PromptPhotonConversion, + ElectronFromMuon, + TauDecay, + BHadronDecay, + CHadronDecay, + LightFlavorDecay, +}; + +} // namespace Truth + +inline std::ostream &operator<<(std::ostream &os, const Truth::Type &obj) +{ + os << static_cast<std::underlying_type<Truth::Type>::type>(obj); + return os; +} + + + +/// \brief a tool interface to classify particles into multiple categories +/// based on their truth information +/// +/// The tools either assignes them an unsigned integer using the base +/// \ref CP::IClassificationTool interface or a \ref Truth::Type enum +/// representing possible truth classification classes. +class ITruthClassificationTool : virtual public CP::IClassificationTool +{ + ASG_TOOL_INTERFACE(ITruthClassificationTool) + +public: + virtual ~ITruthClassificationTool() = default; + + /// \brief classify and return unsigned int + /// re-declaration needed due to the same name used + virtual StatusCode classify(const xAOD::IParticle &particle, + unsigned int &classification) const = 0; + + /// \brief classify and return Truth::Type + virtual StatusCode classify(const xAOD::IParticle &particle, + Truth::Type &classification) const = 0; +}; + +#endif // ITRUTHCLASSIFICATIONTOOL_H_ diff --git a/Projects/AnalysisBase/package_filters.txt b/Projects/AnalysisBase/package_filters.txt index 4e2490364b072feb664693e811b60e62428d70ee..d9631a2366a70fb67da56a99174899ae0ee073cf 100644 --- a/Projects/AnalysisBase/package_filters.txt +++ b/Projects/AnalysisBase/package_filters.txt @@ -67,6 +67,7 @@ + PhysicsAnalysis/AnalysisCommon/ParticleJetTools + PhysicsAnalysis/AnalysisCommon/PileupReweighting + PhysicsAnalysis/AnalysisCommon/ReweightUtils ++ PhysicsAnalysis/AnalysisCommon/TruthClassification + PhysicsAnalysis/D3PDTools/.* - PhysicsAnalysis/ElectronPhotonID/ElectronPhotonTagTools + PhysicsAnalysis/ElectronPhotonID/.*