diff --git a/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/ParticleJetTools/CopyTruthJetParticles.h b/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/ParticleJetTools/CopyTruthJetParticles.h index e5c3d5ea5702d372e7492928ddaf70991086d774..e472a5a961bb05cf4d7ea329be7f407b4e47326b 100644 --- a/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/ParticleJetTools/CopyTruthJetParticles.h +++ b/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/ParticleJetTools/CopyTruthJetParticles.h @@ -1,7 +1,7 @@ // this file is -*- C++ -*- /* - Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration */ #ifndef COPYTRUTHJETPARTICLES_H @@ -47,10 +47,12 @@ public: private: // Options for storate + bool m_includeBSMNonInt; //!< Include non-interacting BSM particles in particles bool m_includeNu; //!< Include neutrinos in particles bool m_includeMu; //!< Include muons in particles bool m_includePromptLeptons; //!< Include particles from prompt decays, i.e. not from hadrons - // bool m_includeTau; //!< Include particles from tau decays + bool m_includePromptPhotons; //!< Include photons from prompt decays, e.g. Higgs decays + bool m_chargedOnly; //!< Include only charged particles // -- added for dark jet clustering -- // bool m_includeSM; //!< Include SM particles bool m_includeDark; //!< Include dark hadrons @@ -64,19 +66,30 @@ private: MCTruthPartClassifier::ParticleOrigin getPartOrigin(const xAOD::TruthParticle* tp, std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap) const; + /// Maximum allowed eta for particles in jets float m_maxAbsEta; + /// Offset for Geant4 particle barcodes // this is set to mutable so that it changes if the metadata information is available //http://stackoverflow.com/questions/12247970/error-in-assignment-of-member-in-read-only-object mutable int m_barcodeOffset; /// Determine how the barcode offset is set from metadata /// 0 -> no metdata access, use BarCodeOffset property - /// >0 from metadata. If it is not found it will keep the BarCodeOffset property + /// 1 -> from metadata. Fails if not found + /// 2 -> from metadata, use BarCodeOffset property if not found (default) int m_barcodeFromMetadata; + /// Cone to be used for removing FSR photons from around prompt leptons float m_photonCone; + std::vector<int> m_vetoPDG_IDs; //! List of PDG IDs that should be ignored (and whose children should be ignored) + bool comesFrom( const xAOD::TruthParticle* tp, const int pdgID, std::vector<int>& used_vertices ) const; + + /// Name of the decoration to be used for identifying FSR (dressing) photons + std::string m_dressingName; + + /// Handle on MCTruthClassifier for finding prompt leptons ToolHandle<IMCTruthClassifier> m_classif; }; diff --git a/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/Root/CopyTruthJetParticles.cxx b/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/Root/CopyTruthJetParticles.cxx index 067c435ce0bc0d8d94a1865bb804c27f07195184..d15d62e2c90ab5de2f0c4794613444d62eacc4f1 100644 --- a/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/Root/CopyTruthJetParticles.cxx +++ b/PhysicsAnalysis/AnalysisCommon/ParticleJetTools/Root/CopyTruthJetParticles.cxx @@ -1,5 +1,5 @@ /* - Copyright (C) 2002-2017 CERN for the benefit of the ATLAS collaboration + Copyright (C) 2002-2020 CERN for the benefit of the ATLAS collaboration */ #include "ParticleJetTools/CopyTruthJetParticles.h" @@ -20,6 +20,9 @@ #include "AthAnalysisBaseComps/AthAnalysisHelper.h" #endif +// For std::find in comesFrom() +#include <algorithm> + using namespace std; using namespace MCTruthPartClassifier; @@ -31,10 +34,12 @@ CopyTruthJetParticles::CopyTruthJetParticles(const std::string& name) , m_photonCone(0.1) , m_classif("",this) { + declareProperty("IncludeBSMNonInteracting", m_includeBSMNonInt=false, "Include noninteracting BSM particles (excluding neutrinos) in the output collection"); declareProperty("IncludeNeutrinos", m_includeNu=false, "Include neutrinos in the output collection"); declareProperty("IncludeMuons", m_includeMu=false, "Include muons in the output collection"); declareProperty("IncludePromptLeptons", m_includePromptLeptons=true, "Include leptons from prompt decays (i.e. not from hadron decays) in the output collection"); - // declareProperty("IncludeTauLeptons", m_includeTau=true, "Include leptons from tau decays in the output collection"); + declareProperty("IncludePromptPhotons", m_includePromptPhotons=true, "Include photons from Higgs and other decays that produce isolated photons"); + declareProperty("ChargedParticlesOnly", m_chargedOnly=false, "Include only charged particles in the output collection"); // -- added for dark jet clustering -- // declareProperty("IncludeSMParts", m_includeSM=true, "Include SM particles in the output collection"); @@ -48,6 +53,10 @@ CopyTruthJetParticles::CopyTruthJetParticles(const std::string& name) declareProperty("MCTruthClassifier", m_classif); declareProperty("FSRPhotonCone", m_photonCone); + + declareProperty("VetoPDG_IDs", m_vetoPDG_IDs, "List of PDG IDs (python list) to veto. Will ignore these and all children of these."); + + declareProperty("DressingDecorationName", m_dressingName="", "Name of the dressed photon decoration (if one should be used)"); } StatusCode CopyTruthJetParticles::initialize() { @@ -55,6 +64,13 @@ StatusCode CopyTruthJetParticles::initialize() { ATH_CHECK(m_truthEventKey.initialize()); ATH_CHECK(m_outTruthPartKey.initialize()); + + // Ensure consistency in the photon dressing treatment + if (m_photonCone>0 && !m_dressingName.empty()){ + ATH_MSG_ERROR("Requested both dR and decoration based photon dressing. This is unphysical; please choose one."); + return StatusCode::FAILURE; + } + return StatusCode::SUCCESS; } @@ -76,22 +92,22 @@ bool CopyTruthJetParticles::classifyJetInput(const xAOD::TruthParticle* tp, int // ----------------------------------- // // Easy classifiers by PDG ID - if (!m_includeNu && MC::isNonInteracting(pdgid)) return false; + if(MCUtils::PID::isNeutrino(pdgid)) { + if (!m_includeNu) return false; + } else { + if (!m_includeBSMNonInt && MC::isNonInteracting(pdgid)) return false; + } if (!m_includeMu && abs(pdgid)==13) return false; - // Cannot use the truth helper functions; they're written for HepMC - // Last two switches only apply if the thing is a lepton and not a tau - if (MC::PID::isLepton(pdgid) && abs(pdgid)!=15 && tp->hasProdVtx()){ - bool isPromptLepton = isPrompt( tp, originMap ); - if(isPromptLepton && (abs(pdgid)==11 || abs(pdgid)==13)) promptLeptons.push_back(tp); - if (!m_includePromptLeptons && isPromptLepton) { - ATH_MSG_VERBOSE("Veto prompt lepton (" << pdgid << ") with pt " << tp->pt() << " origin " << getPartOrigin( tp, originMap )); - return false; - } - // if (!m_includeTau && fromTau( tp, originMap )) { - // ATH_MSG_VERBOSE("Veto lepton (" << pdgid << ") from tau"); - // return false; - // } + // Already built a list of prompt leptons, just use it here + if (!m_includePromptLeptons && std::find(promptLeptons.begin(),promptLeptons.end(),tp)!=promptLeptons.end()){ + ATH_MSG_VERBOSE("Veto prompt lepton (" << pdgid << ") with pt " << tp->pt() << " origin " << getPartOrigin( tp, originMap )); + return false; + } + + // Extra catch. If we aren't supposed to include prompt leptons, we aren't supposed to include prompt neutrinos + if (!m_includePromptLeptons && MCUtils::PID::isNeutrino(pdgid) && isPrompt(tp,originMap)){ + return false; } // -- added for dark jet clustering -- // @@ -110,24 +126,48 @@ bool CopyTruthJetParticles::classifyJetInput(const xAOD::TruthParticle* tp, int // Only exclude photons within deltaR of leptons (if m_photonCone<0, exclude all photons) if(m_photonCone>0) { for(const auto& lep : promptLeptons) { - double deltaR = tp->p4().DeltaR(lep->p4()); - // if photon within deltaR of lepton, remove along with lepton - if( deltaR < m_photonCone ) { - ATH_MSG_VERBOSE("Veto photon with pt " << tp->pt() << " with dR=" << deltaR - << " near to a " << lep->pdgId() << " with pt " << lep->pt()); - return false; - } + double deltaR = tp->p4().DeltaR(lep->p4()); + // if photon within deltaR of lepton, remove along with lepton + if( deltaR < m_photonCone ) { + ATH_MSG_VERBOSE("Veto photon with pt " << tp->pt() << " with dR=" << deltaR + << " near to a " << lep->pdgId() << " with pt " << lep->pt()); + return false; + } } } } + // Exclude prompt photons, particularly those from Higgs decays to start + if (!m_includePromptPhotons && MC::PID::isPhoton(pdgid) && tp->hasProdVtx()){ + ParticleOrigin orig = getPartOrigin(tp, originMap); + if (orig==Higgs || orig==HiggsMSSM) return false; + } + + // If we want to remove photons via the dressing decoration + if (!m_dressingName.empty()){ + // Accessor for the dressing decoration above + const static SG::AuxElement::Accessor<char> dressAcc(m_dressingName); + if (pdgid==22 && dressAcc(*tp)) return false; + } // End of removal via dressing decoration + + // Pseudo-rapidity cut if(fabs(tp->eta())>m_maxAbsEta) return false; + + // Vetoes of specific origins. Not fast, but if no particles are specified should not execute + if (m_vetoPDG_IDs.size()>0){ + std::vector<int> used_vertices; + for (int anID : m_vetoPDG_IDs){ + used_vertices.clear(); + if (comesFrom(tp,anID,used_vertices)) return false; + } + } + // Made it! return true; } bool CopyTruthJetParticles::isPrompt( const xAOD::TruthParticle* tp, - std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap ) const + std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap ) const { ParticleOrigin orig = getPartOrigin(tp, originMap); switch(orig) { @@ -156,17 +196,8 @@ bool CopyTruthJetParticles::isPrompt( const xAOD::TruthParticle* tp, return true; } -// bool CopyTruthJetParticles::fromTau( const xAOD::TruthParticle* tp, -// std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap ) const -// { -// ParticleOrigin orig = getPartOrigin(tp, originMap); -// if(orig==TauLep) return true; -// return false; -// } - - MCTruthPartClassifier::ParticleOrigin CopyTruthJetParticles::getPartOrigin(const xAOD::TruthParticle* tp, - std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap) const + std::map<const xAOD::TruthParticle*,MCTruthPartClassifier::ParticleOrigin>& originMap) const { if(originMap.find(tp)==originMap.end()) { std::pair<ParticleType, ParticleOrigin> classification = m_classif->particleTruthClassifier( tp ); @@ -177,7 +208,7 @@ MCTruthPartClassifier::ParticleOrigin CopyTruthJetParticles::getPartOrigin(const int CopyTruthJetParticles::setBarCodeFromMetaDataCheck() const{ - ATH_MSG_DEBUG(" in call once barcode offset is"<<m_barcodeOffset); + ATH_MSG_DEBUG(" in call once barcode offset is"<<m_barcodeOffset); // if m_barcodeFromMetadata is set to zero, no search for barcode offset is performed in metadata if(m_barcodeFromMetadata == 0 ) ATH_MSG_INFO( "No barcode offset is searched for in metadata, its value is set to: "<<m_barcodeOffset); @@ -205,12 +236,25 @@ int CopyTruthJetParticles::setBarCodeFromMetaDataCheck() const{ m_barcodeOffset = barcodeOffset_tmp; ATH_MSG_INFO(" Barcode offset retrieved from metadata. Its value is : "<< m_barcodeOffset); } else { - ATH_MSG_WARNING( "Could not retrieve barcode offset from metadata under the name SimBarcodeOffset from /Simulation/Parameters, so barcode is set to "<<m_barcodeOffset); - return 1; + + if(m_barcodeFromMetadata==1) { + // Required that it come from metadata -- fail if this didn't work + ATH_MSG_ERROR("Can not retrieve metadata info /Simulation/Parameters SimBarcodeOffset"); + throw runtime_error("Tried to retrieve metadata info and failed, and was not supposed to fail"); + } + // m_barcodeFromMetadata == 2 + // Requested it from the metadata, with a fall-back + ATH_MSG_DEBUG("NOT Retrieved from metadata, use default "<<m_barcodeOffset); } #else // standalone : - ATH_MSG_WARNING(" Can't automatically retrieve the truth barcode offset outside Athena. Please set the CopyTruthJetParticles::BarCodeOffset for this specific sample"); - return 1; + if (m_barcodeFromMetadata==1) { + // Required it from the metadata -- fail if not in athena + ATH_MSG_ERROR("Can't retrieve automatically the truth barcode offset outside Athena. Please set the CopyTruthJetParticles::BarCodeOffset for this specific sample"); + throw runtime_error("Tried to retrieve metadata info and failed, and was not supposed to fail"); + } else { + // Requested it from the metadata -- fall back is the only option + ATH_MSG_WARNING("Can't retrieve automatically the truth barcode offset outside Athena. Falling back to offset property: " << m_barcodeOffset); + } #endif } return 0; @@ -238,6 +282,7 @@ int CopyTruthJetParticles::execute() const { // std::call_once(metaDataFlag,&CopyTruthJetParticles::basicMetaDataCheck,this,barcodeOffset); // this call happens only once and it modifies m_barcodeOffset + // Note that catching the return value of this is rather complicated, so it throws rather than returning errors std::call_once(metaDataFlag,&CopyTruthJetParticles::setBarCodeFromMetaDataCheck, this); std::vector<const xAOD::TruthParticle*> promptLeptons; @@ -263,6 +308,23 @@ int CopyTruthJetParticles::execute() const { ATH_MSG_ERROR("Null pointer received for first truth event!"); return 1; } + + for (unsigned int ip = 0; ip < hsevt->nTruthParticles(); ++ip) { + const xAOD::TruthParticle* tp = hsevt->truthParticle(ip); + if(tp == NULL) continue; + if (tp->pt() < m_ptmin) + continue; + // Cannot use the truth helper functions; they're written for HepMC + // Last two switches only apply if the thing is a lepton and not a tau + int pdgid = tp->pdgId(); + if ((abs(pdgid)==11 || abs(pdgid)==13) && tp->hasProdVtx()){ + // If this is a prompt, generator stable lepton, then we can use it + if(tp->status()==1 && tp->barcode()<m_barcodeOffset && isPrompt(tp,originMap)){ + promptLeptons.push_back(tp); + } + } + } + for (size_t itp(0); itp<hsevt->nTruthParticles(); ++itp) { const xAOD::TruthParticle* tp = hsevt->truthParticle(itp); if(tp == NULL) continue; @@ -293,3 +355,26 @@ int CopyTruthJetParticles::execute() const { return 0; } + +bool CopyTruthJetParticles::comesFrom( const xAOD::TruthParticle* tp, const int pdgID, std::vector<int>& used_vertices ) const { + // If it's not a particle, then it doesn't come from something... + if (!tp) return false; + // If it doesn't have a production vertex or has no parents, it doesn't come from much of anything + if (!tp->prodVtx() || tp->nParents()==0) return false; + // If we have seen it before, then skip this production vertex + if (std::find(used_vertices.begin(),used_vertices.end(), tp->prodVtx()->barcode())!=used_vertices.end()) return false; + // Add the production vertex to our used list + used_vertices.push_back( tp->prodVtx()->barcode() ); + // Loop over the parents + for (size_t par=0;par<tp->nParents();++par){ + // Check for null pointers in case of skimming + if (!tp->parent(par)) continue; + // Check for a match + if (tp->parent(par)->absPdgId()==pdgID) return true; + // Recurse on this parent + if (comesFrom(tp->parent(par), pdgID, used_vertices)) return true; + } + // No hits -- all done with the checks! + return false; +} +