diff --git a/PWGJE/Tasks/statPromptPhoton.cxx b/PWGJE/Tasks/statPromptPhoton.cxx index 98c52aa45e7..4e13332dcfe 100644 --- a/PWGJE/Tasks/statPromptPhoton.cxx +++ b/PWGJE/Tasks/statPromptPhoton.cxx @@ -51,9 +51,9 @@ #include #include +#include #include #include - using namespace o2; using namespace o2::framework; using namespace o2::framework::expressions; @@ -96,6 +96,7 @@ struct statPromptPhoton { Configurable cfgTrackFilter{"cfgTrackFilter", "globalTracks", "set track selections"}; Configurable cfgJETracks{"cfgJETracks", false, "Enables running on derived JE data"}; Configurable cfgGenHistograms{"cfgGenHistograms", false, "Enables Generated histograms"}; + Configurable cfgGenReqRec{"cfgGenReqRec", false, "Only consider generated events which are successfully reconstructed"}; Configurable cfgRecHistograms{"cfgRecHistograms", false, "Enables Reconstructed histograms"}; Configurable cfgDataHistograms{"cfgDataHistograms", false, "Enables Data histograms"}; Configurable cfgTriggerMasks{"cfgTriggerMasks", "", "possible JE Trigger masks: fJetChLowPt,fJetChHighPt,fTrackLowPt,fTrackHighPt,fJetD0ChLowPt,fJetD0ChHighPt,fJetLcChLowPt,fJetLcChHighPt,fEMCALReadout,fJetFullHighPt,fJetFullLowPt,fJetNeutralHighPt,fJetNeutralLowPt,fGammaVeryHighPtEMCAL,fGammaVeryHighPtDCAL,fGammaHighPtEMCAL,fGammaHighPtDCAL,fGammaLowPtEMCAL,fGammaLowPtDCAL,fGammaVeryLowPtEMCAL,fGammaVeryLowPtDCAL"}; @@ -191,9 +192,52 @@ struct statPromptPhoton { histos.add("REC_TrueTrigger_V_PtHadSum_Photon", "REC_Trigger_V_PtHadSum_Photon", kTH2F, {{100, 0, 100}, pthadAxis}); histos.add("REC_dR_Photon", "REC_dR_Photon", kTH1F, {{628, 0.0, 2 * TMath::Pi()}}); histos.add("REC_dR_Stern", "REC_dR_Stern", kTH1F, {{628, 0.0, 2 * TMath::Pi()}}); + histos.add("REC_prompt_phiQA", "REC_prompt_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_prompt_etaQA", "REC_prompt_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_prompt_ptQA", "REC_prompt_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_decay_phiQA", "REC_decay_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_decay_etaQA", "REC_decay_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_decay_ptQA", "REC_decay_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_frag_phiQA", "REC_frag_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_frag_etaQA", "REC_frag_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_frag_ptQA", "REC_frag_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_direct_phiQA", "REC_direct_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_direct_etaQA", "REC_direct_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_direct_ptQA", "REC_direct_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_cluster_phiQA", "REC_cluster_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_cluster_etaQA", "REC_cluster_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_cluster_energyQA", "REC_cluster_energyQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_clusteriso_phiQA", "REC_clusteriso_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_clusteriso_etaQA", "REC_clusteriso_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_clusteriso_energyQA", "REC_clusteriso_energyQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_track_phiQA", "REC_track_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_track_etaQA", "REC_track_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_track_ptQA", "REC_track_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_cluster_direct_phiQA", "REC_cluster_direct_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_cluster_direct_etaQA", "REC_cluster_direct_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_cluster_direct_energyQA", "REC_cluster_direct_energyQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_cluster_frag_phiQA", "REC_cluster_frag_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_cluster_frag_etaQA", "REC_cluster_frag_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_cluster_frag_energyQA", "REC_cluster_frag_energyQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("REC_cluster_both_phiQA", "REC_cluster_both_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("REC_cluster_both_etaQA", "REC_cluster_both_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("REC_cluster_both_energyQA", "REC_cluster_both_energyQA", kTH1F, {{82, -1.0, 40.0}}); } if (cfgGenHistograms) { + histos.add("GEN_prompt_phiQA", "GEN_prompt_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("GEN_prompt_etaQA", "GEN_prompt_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("GEN_prompt_ptQA", "GEN_prompt_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("GEN_decay_phiQA", "GEN_decay_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("GEN_decay_etaQA", "GEN_decay_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("GEN_decay_ptQA", "GEN_decay_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("GEN_frag_phiQA", "GEN_frag_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("GEN_frag_etaQA", "GEN_frag_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("GEN_frag_ptQA", "GEN_frag_ptQA", kTH1F, {{82, -1.0, 40.0}}); + histos.add("GEN_direct_phiQA", "GEN_direct_phiQA", kTH1F, {{640 * 2, 0, 2 * TMath::Pi()}}); + histos.add("GEN_direct_etaQA", "GEN_direct_etaQA", kTH1F, {{100, -1, 1}}); + histos.add("GEN_direct_ptQA", "GEN_direct_ptQA", kTH1F, {{82, -1.0, 40.0}}); histos.add("GEN_nEvents", "GEN_nEvents", kTH1F, {{4, 0.0, 4.0}}); + histos.add("GEN_nEvents_simple", "GEN_nEvents", kTH1F, {{4, 0.0, 4.0}}); histos.add("GEN_True_Trigger_Energy", "GEN_True_Trigger_Energy", kTH1F, {{82, -1.0, 40.0}}); histos.add("GEN_Particle_Pt", "GEN_Particle_Pt", kTH1F, {{82, -1.0, 40.0}}); histos.add("GEN_True_Photon_Energy", "GEN_True_Photon_Energy", kTH1F, {{8200, -1.0, 40.0}}); @@ -420,6 +464,71 @@ struct statPromptPhoton { }; // end of track selection ///////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// + // Below is shamelessly stolen from Florian's gammetreeproducer code. + template + T iTopCopy(const T& particle) const + { + int iUp = particle.globalIndex(); + T currentParticle = particle; + int pdgCode = particle.pdgCode(); + auto mothers = particle.template mothers_as(); + while (iUp > 0 && mothers.size() == 1 && mothers[0].globalIndex() > 0 && mothers[0].pdgCode() == pdgCode) { + iUp = mothers[0].globalIndex(); + currentParticle = mothers[0]; + mothers = currentParticle.template mothers_as(); + } + return currentParticle; + } + + /// \brief Checks if a particle is a prompt photon + /// \param particle The MC particle to check + /// \return true if particle is a prompt photon, false otherwise + bool isPromptPhoton(const auto& particle) + { + if (particle.pdgCode() == PDG_t::kGamma && particle.isPhysicalPrimary() && std::abs(particle.getGenStatusCode()) < 90) { + return true; + } + return false; + } + /// \brief Checks if a particle is a direct prompt photon + /// \param particle The particle to check + /// \return true if particle is a direct prompt photon, false otherwise + bool isDirectPromptPhoton(const auto& particle) + { + // check if particle isa prompt photon + if (particle.pdgCode() == PDG_t::kGamma && particle.isPhysicalPrimary() && std::abs(particle.getGenStatusCode()) < 90) { + // find the top carbon copy + auto topCopy = iTopCopy(particle); + if (topCopy.pdgCode() == PDG_t::kGamma && std::abs(topCopy.getGenStatusCode()) < 40) { // < 40 is particle directly produced in hard scattering + return true; + } + } + return false; + } + /// \brief Checks if a particle is a fragmentation photon + /// \param particle The particle to check + /// \return true if particle is a fragmentation photon, false otherwise + bool isFragmentationPhoton(const auto& particle) + { + if (particle.pdgCode() == PDG_t::kGamma && particle.isPhysicalPrimary() && std::abs(particle.getGenStatusCode()) < 90) { + // find the top carbon copy + auto topCopy = iTopCopy(particle); + if (topCopy.pdgCode() == PDG_t::kGamma && std::abs(topCopy.getGenStatusCode()) >= 40) { // frag photon + return true; + } + } + return false; + } + /// \brief Checks if a particle is a decay photon + /// \param particle The particle to check + /// \return true if particle is a decay photon, false otherwise + bool isDecayPhoton(const auto& particle) + { + if (particle.pdgCode() == PDG_t::kGamma && particle.isPhysicalPrimary() && std::abs(particle.getGenStatusCode()) >= 90) { + return true; + } + return false; + } ///////////////////////////////////////////////////////////////////////////// // PROCESS @@ -1364,6 +1473,362 @@ struct statPromptPhoton { PROCESS_SWITCH(statPromptPhoton, processData, "processJE data", false); + int nEventsGenMC_Simple = 0; + void processMCGen_simple(filteredMCCollisions::iterator const& collision, soa::SmallGroups> const& recocolls, aod::JMcParticles const& mcParticles, jfilteredMCClusters const&) + { + nEventsGenMC_Simple++; + if (cfgDebug) { + if ((nEventsGenMC_Simple + 1) % 10000 == 0) { + std::cout << "Processed Gen MC Events: " << nEventsGenMC_Simple << std::endl; + } + } + histos.fill(HIST("GEN_nEvents_simple"), 0.5); + if (fabs(collision.posZ()) > cfgVtxCut) + return; + + if (cfgGenReqRec) { + if (recocolls.size() <= 0) // not reconstructed + return; + for (auto& recocoll : recocolls) { // poorly reconstructed + if (!recocoll.sel8()) + return; + if (fabs(recocoll.posZ()) > cfgVtxCut) + + return; + histos.fill(HIST("GEN_nEvents_simple"), 1.5); + + if (cfgEmcTrigger) { + if (!recocoll.isEmcalReadout()) + return; + } + histos.fill(HIST("GEN_nEvents_simple"), 2.5); + } + } + + // First pass: find all status -23 particles + for (auto& hardParticle : mcParticles) { + if (hardParticle.getGenStatusCode() != -23) + continue; + + bool isPhoton23 = (hardParticle.pdgCode() == 22); + + // For prompt: find the final-state photon descending from this -23 photon// + // For frag: find any final-state photon descending from this -23 non-photon// + // We search all final-state photons and check if they trace back here// + + for (auto& mcParticle : mcParticles) { + if (mcParticle.pdgCode() != 22) + continue; + if (mcParticle.getGenStatusCode() < 0) + continue; + if (std::fabs(mcParticle.getGenStatusCode()) >= 81 || !mcParticle.isPhysicalPrimary()) + continue; + + // Chase this final-state photon upward + int chaseindex = -1; + for (auto& mom : mcParticle.mothers_as()) { + chaseindex = mom.globalIndex(); + break; + } + if (chaseindex < 0) + continue; + + std::set visited; + bool chase = true; + bool hadronInChain = false; + bool reachedThisHard = false; + bool cleanPhotonChain = true; // all intermediates are photons + + while (chase) { + if (visited.count(chaseindex)) { + chase = false; + break; + } + visited.insert(chaseindex); + + for (auto& particle : mcParticles) { + if (particle.globalIndex() != chaseindex) + continue; + + if (particle.globalIndex() == hardParticle.globalIndex()) { + reachedThisHard = true; + chase = false; + break; + } + + int abspdg = std::abs(particle.pdgCode()); + if (abspdg > 100) + hadronInChain = true; + if (abspdg != 22) + cleanPhotonChain = false; + + int nextindex = -1; + for (auto& mom : particle.mothers_as()) { + nextindex = mom.globalIndex(); + break; + } + if (nextindex < 0) { + chase = false; + } else { + chaseindex = nextindex; + } + break; + } + } + + if (!reachedThisHard) + continue; + + if (isPhoton23 && cleanPhotonChain) { + // Case 1: -23 photon, clean photon chain — direct prompt + histos.fill(HIST("GEN_direct_phiQA"), mcParticle.phi()); + histos.fill(HIST("GEN_direct_etaQA"), mcParticle.eta()); + histos.fill(HIST("GEN_direct_ptQA"), mcParticle.pt()); + } else if (!isPhoton23 && !hadronInChain) { + // Case 2: -23 non-photon, no hadrons — fragmentation + histos.fill(HIST("GEN_frag_phiQA"), mcParticle.phi()); + histos.fill(HIST("GEN_frag_etaQA"), mcParticle.eta()); + histos.fill(HIST("GEN_frag_ptQA"), mcParticle.pt()); + } + } // final-state photon loop + } // hard particle loop + } + PROCESS_SWITCH(statPromptPhoton, processMCGen_simple, "processMC_QA_Gen", false); + int nEventsRecMC_simple = 0; + void processMCRec_simple(jfilteredCollisions::iterator const& collision, jfilteredMCClusters const& mcclusters, jTrackCandidates const&, soa::Join const&, TrackCandidates const&, aod::JMcParticles const& mcparticles, BcCandidates const&, jEMCtracks const& emctracks, aod::JetMcCollisions const&) + { + nEventsRecMC_simple++; + if (cfgDebug) { + if ((nEventsRecMC_simple + 1) % 10000 == 0) { + std::cout << "Processed JE Rec MC Events: " << nEventsRecMC_simple << std::endl; + } + } + histos.fill(HIST("REC_nEvents"), 0.5); + if (fabs(collision.posZ()) > cfgVtxCut) + return; + if (!collision.sel8()) + return; + histos.fill(HIST("REC_nEvents"), 1.5); + if (cfgEmcTrigger) { + if (!collision.isEmcalReadout()) + return; + } + histos.fill(HIST("REC_nEvents"), 2.5); + if (!jetderiveddatautilities::selectTrigger(collision, triggerMaskBits)) + return; + + for (auto& mccluster : mcclusters) { + histos.fill(HIST("REC_M02_BC"), mccluster.m02()); + if (mccluster.m02() < cfgLowM02) + continue; + if (mccluster.m02() > cfgHighM02) + continue; + if (mccluster.energy() < cfgLowClusterE) + continue; + if (mccluster.energy() > cfgHighClusterE) + continue; + if (fabs(mccluster.eta()) > cfgtrkMaxEta) + continue; + int ClusterHasDirectPhoton = 0; + int ClusterHasFragPhoton = 0; + auto ClusterParticles = mccluster.mcParticles_as(); + for (auto& clusterparticle : ClusterParticles) { + if (clusterparticle.pdgCode() != 22 && std::fabs(clusterparticle.pdgCode()) != 13) + continue; + if (clusterparticle.getGenStatusCode() < 0) + continue; + if (std::fabs(clusterparticle.getGenStatusCode()) >= 81) + continue; + + int chaseindex = -1; + for (auto& mom : clusterparticle.mothers_as()) { + chaseindex = mom.globalIndex(); + break; + } + if (chaseindex < 0) + continue; + + std::set visited; + bool chase = true; + bool hadronInChain = false; + bool cleanPhotonChain = true; + bool adrianprompt = false; + bool adrianfrag = false; + + while (chase) { + if (visited.count(chaseindex)) { + chase = false; + break; + } + visited.insert(chaseindex); + + for (auto& particle : mcparticles) { + if (particle.globalIndex() != chaseindex) + continue; + + if (particle.getGenStatusCode() == -23) { + if (particle.pdgCode() == 22 && cleanPhotonChain) { + adrianprompt = true; + } else if (particle.pdgCode() != 22 && !hadronInChain) { + adrianfrag = true; + } + chase = false; + break; + } + + int abspdg = std::abs(particle.pdgCode()); + if (abspdg > 100) + hadronInChain = true; + if (abspdg != 22) + cleanPhotonChain = false; + + int nextindex = -1; + for (auto& mom : particle.mothers_as()) { + nextindex = mom.globalIndex(); + break; + } + if (nextindex < 0) { + chase = false; + } else { + chaseindex = nextindex; + } + break; + } + } // chase + + if (adrianprompt) { + ClusterHasDirectPhoton++; + histos.fill(HIST("REC_direct_phiQA"), clusterparticle.phi()); + histos.fill(HIST("REC_direct_etaQA"), clusterparticle.eta()); + histos.fill(HIST("REC_direct_ptQA"), clusterparticle.pt()); + } + if (adrianfrag) { + ClusterHasFragPhoton++; + histos.fill(HIST("REC_frag_phiQA"), clusterparticle.phi()); + histos.fill(HIST("REC_frag_etaQA"), clusterparticle.eta()); + histos.fill(HIST("REC_frag_ptQA"), clusterparticle.pt()); + } + } // clusterparticle loop + + if (ClusterHasFragPhoton > 0) { + histos.fill(HIST("REC_cluster_frag_phiQA"), mccluster.phi()); + histos.fill(HIST("REC_cluster_frag_etaQA"), mccluster.eta()); + histos.fill(HIST("REC_cluster_frag_energyQA"), mccluster.energy()); + } + if (ClusterHasDirectPhoton > 0) { + histos.fill(HIST("REC_cluster_direct_phiQA"), mccluster.phi()); + histos.fill(HIST("REC_cluster_direct_etaQA"), mccluster.eta()); + histos.fill(HIST("REC_cluster_direct_energyQA"), mccluster.energy()); + } + if (ClusterHasDirectPhoton > 0 && ClusterHasFragPhoton > 0) { + histos.fill(HIST("REC_cluster_both_phiQA"), mccluster.phi()); + histos.fill(HIST("REC_cluster_both_etaQA"), mccluster.eta()); + histos.fill(HIST("REC_cluster_both_energyQA"), mccluster.energy()); + } + + // now we do cluster tracks + bool photontrigger = false; // is a neutral cluster + bool chargetrigger = false; // is definitely not a neutral cluster + auto tracksofcluster = mccluster.matchedTracks_as>(); + // first, we check if veto is required + double sumptT = 0; + bool clusterqa = false; + for (auto& ctrack : tracksofcluster) { + double etaT, phiT; + if (cfgJETracks) { + if (!jetderiveddatautilities::selectTrack(ctrack, trackFilter)) { + continue; + } + auto emctracksPerTrack = emctracks.sliceBy(EMCTrackPerTrack, ctrack.globalIndex()); + auto emctrack = emctracksPerTrack.iteratorAt(0); + etaT = emctrack.etaEmcal(); + phiT = emctrack.phiEmcal(); + } else { + auto ogtrack = ctrack.track_as(); + if (!trackSelection(ogtrack)) { + continue; + } + if (!ogtrack.isGlobalTrack()) { + continue; + } + etaT = ogtrack.trackEtaEmcal(); + phiT = ogtrack.trackPhiEmcal(); + } + + double etaC = mccluster.eta(); + double phiC = mccluster.phi(); + double ptT = ctrack.pt(); + bool etatrigger = false; + bool phitrigger = false; + double phidiff = TVector2::Phi_mpi_pi(mccluster.phi() - ctrack.phi()); + double etadiff = mccluster.eta() - ctrack.eta(); + + if (cfgPtClusterCut) { + if (fabs(etaT - etaC) < (0.010 + pow(ptT + 4.07, -2.5))) { + etatrigger = true; + } + + if (fabs(TVector2::Phi_mpi_pi(phiT - phiC)) < (0.015 + pow(ptT + 3.65, -2.0))) { + phitrigger = true; + } + } else { + if (fabs(etadiff) < 0.05) { + etatrigger = true; + } + + if (fabs(phidiff) < 0.05) { + phitrigger = true; + } + } + + if (etatrigger && phitrigger) { + chargetrigger = true; + sumptT += ptT; + } + if (chargetrigger) { + if (!clusterqa) { + histos.fill(HIST("REC_Cluster_QA"), 1.5); + clusterqa = true; + } + } + histos.fill(HIST("REC_Track_v_Cluster_Phi"), phidiff); + histos.fill(HIST("REC_Track_v_Cluster_Eta"), etadiff); + histos.fill(HIST("REC_Track_v_Cluster_Phi_Eta"), phidiff, etadiff); + histos.fill(HIST("REC_track_phiQA"), ctrack.phi()); + histos.fill(HIST("REC_track_etaQA"), ctrack.eta()); + histos.fill(HIST("REC_track_ptQA"), ctrack.pt()); + } // track of cluster loop + + if (chargetrigger && sumptT > 0) { + double mccluster_over_sumptT = mccluster.energy() / sumptT; + histos.fill(HIST("REC_SumPt_BC"), mccluster_over_sumptT); + if (mccluster_over_sumptT < 1.7) { + histos.fill(HIST("REC_Cluster_QA"), 2.5); // veto fails, cluster is charged + } else { + histos.fill(HIST("REC_Cluster_QA"), 3.5); // veto is good, cluster is converted to neutral cluster + // chargetrigger = false; + histos.fill(HIST("REC_SumPt_AC"), mccluster_over_sumptT); + } + } // sumptT check + + if (!chargetrigger) { + photontrigger = true; + } + + if (photontrigger) { + histos.fill(HIST("REC_clusteriso_phiQA"), mccluster.phi()); + histos.fill(HIST("REC_clusteriso_etaQA"), mccluster.eta()); + histos.fill(HIST("REC_clusteriso_energyQA"), mccluster.energy()); + } + if (chargetrigger) { + histos.fill(HIST("REC_cluster_phiQA"), mccluster.phi()); + histos.fill(HIST("REC_cluster_etaQA"), mccluster.eta()); + histos.fill(HIST("REC_cluster_energyQA"), mccluster.energy()); + } + } // clusters + } // main function + PROCESS_SWITCH(statPromptPhoton, processMCRec_simple, "processMC_QA_Rce", false); + }; // end of main struct WorkflowSpec defineDataProcessing(ConfigContext const& cfgc)