5050#include < TH2.h>
5151#include < TH3.h>
5252#include < THn.h>
53+ #include < TMCProcess.h>
5354#include < TPDGCode.h>
5455
5556#include < algorithm>
5960#include < cstdint>
6061#include < cstdlib>
6162#include < format>
63+ #include < iterator>
6264#include < optional>
6365#include < string>
6466#include < string_view>
@@ -119,6 +121,36 @@ enum class LegMatchStatus : uint8_t {
119121 kNStatus
120122};
121123
124+ // / \brief Species of the closest common ancestor of an "other hadron" pair
125+ enum class OtherHadronSpecies : uint8_t {
126+ kChargedPion = 0 ,
127+ kChargedKaon ,
128+ kK0L ,
129+ kRho ,
130+ kKstar ,
131+ kPhi ,
132+ kOtherMeson , // incl. f0, f2, a, b, h resonances
133+ kProton ,
134+ kNeutron ,
135+ kDelta ,
136+ kLambda ,
137+ kSigma ,
138+ kXi ,
139+ kOtherBaryon ,
140+ kOther ,
141+ kNSpecies
142+ };
143+
144+ // / \brief How the particles directly below the common ancestor were produced, i.e. how the ancestor split into the two branches.
145+ // / Ordered such that std::max of the two branches gives the material interaction if one branch comes from it.
146+ enum class SplitProduction : uint8_t {
147+ kGeneratorDecay = 0 , // produced by the event generator: decay in the generator (resonances, Sigma0, ...)
148+ kTransportDecay , // produced by the transport code in a decay (weak decays of K+-, K0L, Lambda, Xi, ...)
149+ kHadronicInteraction , // produced by the transport code in a hadronic interaction in the detector material
150+ kOtherProcess , // produced by the transport code in any other process
151+ kNProduction
152+ };
153+
122154enum CentralityEstimator {
123155 None = 0 ,
124156 CFT0A = 1 ,
@@ -388,6 +420,25 @@ struct TwoPhotonMcTask {
388420 for (std::size_t i = 0 ; i < BucketLabels.size (); ++i) {
389421 hPairAncestor->GetAxis (4 )->SetBinLabel (static_cast <int >(i) + 1 , BucketLabels[i].data ());
390422 }
423+ // "other hadron" pairs: which hadron is the common ancestor, and did it split in a decay or a material interaction
424+ const AxisSpec axisOHMass{160 , 0 ., 0.8 , " #it{M}_{#gamma#gamma} (GeV/#it{c}^{2})" };
425+ const AxisSpec axisOHPt{20 , 0 ., 20 ., " #it{p}_{T} (GeV/#it{c})" };
426+ const AxisSpec axisOHSpecies{static_cast <int >(OtherHadronSpecies::kNSpecies ), -0.5 , static_cast <int >(OtherHadronSpecies::kNSpecies ) - 0.5 , " common ancestor" };
427+ const AxisSpec axisOHProduction{static_cast <int >(SplitProduction::kNProduction ), -0.5 , static_cast <int >(SplitProduction::kNProduction ) - 0.5 , " split of the common ancestor" };
428+ auto hOtherHadron = registry.add <THn>(" PCM/OtherHadron/hAncestor" , " other hadron pairs: minv vs pT vs ancestor species vs split production vs pair truth" , HistType::kTHnF , {axisOHMass, axisOHPt, axisOHSpecies, axisOHProduction, thnAxisPairTruth});
429+ static constexpr std::array<std::string_view, static_cast <int >(OtherHadronSpecies::kNSpecies )> SpeciesLabels = {" #pi^{#pm}" , " K^{#pm}" , " K^{0}_{L}" , " #rho" , " K^{*}" , " #phi" , " other meson" , " p" , " n" , " #Delta" , " #Lambda" , " #Sigma" , " #Xi" , " other baryon" , " other" };
430+ for (std::size_t i = 0 ; i < SpeciesLabels.size (); ++i) {
431+ hOtherHadron->GetAxis (2 )->SetBinLabel (static_cast <int >(i) + 1 , SpeciesLabels[i].data ());
432+ }
433+ static constexpr std::array<std::string_view, static_cast <int >(SplitProduction::kNProduction )> ProductionLabels = {" generator decay" , " transport decay" , " hadronic interaction" , " other process" };
434+ for (std::size_t i = 0 ; i < ProductionLabels.size (); ++i) {
435+ hOtherHadron->GetAxis (3 )->SetBinLabel (static_cast <int >(i) + 1 , ProductionLabels[i].data ());
436+ }
437+ for (std::size_t i = 0 ; i < TruthLabels.size (); ++i) {
438+ hOtherHadron->GetAxis (4 )->SetBinLabel (static_cast <int >(i) + 1 , TruthLabels[i].data ());
439+ }
440+ registry.add (" PCM/OtherHadron/hAncestorPdg" , " other hadron pairs: |PDG| of the common ancestor;|PDG| (last bin: #geq 9999);pairs" , HistType::kTH1D , {{10000 , -0.5 , 9999.5 }});
441+
391442 // mixed event, same axes as hPairAncestor without the bucket (pairs from different events share no ancestor)
392443 auto hMixed = registry.add <THn>(" PCM/hMixed" , " minv vs pT vs cent vs pair truth, mixed event" , HistType::kTHnF , {thnAxisInvMass, thnAxisPtRec, thnAxisCentOrMult, thnAxisPairTruth});
393444 for (std::size_t i = 0 ; i < TruthLabels.size (); ++i) {
@@ -808,6 +859,112 @@ struct TwoPhotonMcTask {
808859 fillLegMatch<true >(matchedSeconds.sliceBy (perEMCClusterMS, emc.globalIndex ()), emc.e (), pLeg, ptLeg);
809860 }
810861
862+ // / \brief Species class of the common ancestor of an "other hadron" pair
863+ // / \param pdg PDG code of the ancestor
864+ static OtherHadronSpecies otherHadronSpecies (const int pdg)
865+ {
866+ const int absPdg = std::abs (pdg);
867+ switch (absPdg) {
868+ case PDG_t::kPiPlus :
869+ return OtherHadronSpecies::kChargedPion ;
870+ case PDG_t::kKPlus :
871+ return OtherHadronSpecies::kChargedKaon ;
872+ case PDG_t::kK0Long :
873+ return OtherHadronSpecies::kK0L ;
874+ case PDG_t::kRho770_0 :
875+ case PDG_t::kRho770Plus :
876+ return OtherHadronSpecies::kRho ;
877+ case o2::constants::physics::Pdg::kK0Star892 :
878+ case o2::constants::physics::Pdg::kKPlusStar892 :
879+ return OtherHadronSpecies::kKstar ;
880+ case o2::constants::physics::Pdg::kPhi :
881+ return OtherHadronSpecies::kPhi ;
882+ case PDG_t::kProton :
883+ return OtherHadronSpecies::kProton ;
884+ case PDG_t::kNeutron :
885+ return OtherHadronSpecies::kNeutron ;
886+ case 1114 : // o2-linter: disable=pdg/explicit-code (Delta-, no named constant)
887+ case 2114 : // o2-linter: disable=pdg/explicit-code (Delta0, no named constant)
888+ case 2214 : // o2-linter: disable=pdg/explicit-code (Delta+, no named constant)
889+ case 2224 : // o2-linter: disable=pdg/explicit-code (Delta++, no named constant)
890+ return OtherHadronSpecies::kDelta ;
891+ case PDG_t::kLambda0 :
892+ return OtherHadronSpecies::kLambda ;
893+ case PDG_t::kSigmaMinus :
894+ case PDG_t::kSigma0 :
895+ case PDG_t::kSigmaPlus :
896+ return OtherHadronSpecies::kSigma ;
897+ case PDG_t::kXiMinus :
898+ case 3322 : // o2-linter: disable=pdg/explicit-code (Xi0, no named constant)
899+ return OtherHadronSpecies::kXi ;
900+ default :
901+ break ;
902+ }
903+ constexpr int MinHadronPdg = 100 ;
904+ constexpr int MaxHadronPdg = 10000000 ;
905+ if (absPdg < MinHadronPdg || absPdg >= MaxHadronPdg) {
906+ return OtherHadronSpecies::kOther ;
907+ }
908+ // PDG numbering: mesons have no third quark, i.e. the thousands digit is 0 (also for 9000000 type resonances)
909+ return (absPdg / 1000 ) % 10 == 0 ? OtherHadronSpecies::kOtherMeson : OtherHadronSpecies::kOtherBaryon ; // o2-linter: disable=magic-number (PDG digit)
910+ }
911+
912+ // / \brief How an MC particle was produced
913+ // / \param mcIter iterator of the full MC particle table, WILL BE CHANGED
914+ // / \param id index of the particle
915+ template <o2::soa::is_iterator TMCIter>
916+ static SplitProduction productionOf (TMCIter& mcIter, const int id)
917+ {
918+ mcIter.setCursor (id);
919+ if (mcIter.producedByGenerator ()) {
920+ return SplitProduction::kGeneratorDecay ;
921+ }
922+ switch (mcIter.getProcess ()) {
923+ case TMCProcess::kPDecay :
924+ return SplitProduction::kTransportDecay ;
925+ case TMCProcess::kPHadronic :
926+ case TMCProcess::kPHInhelastic :
927+ case TMCProcess::kPNuclearAbsorption :
928+ case TMCProcess::kPNCapture :
929+ case TMCProcess::kPPbarAnnihilation :
930+ case TMCProcess::kPNbarAnnihilation :
931+ case TMCProcess::kPEvaporation :
932+ case TMCProcess::kPNuclearFission :
933+ case TMCProcess::kPPhotoNuclear :
934+ case TMCProcess::kPPhotonInhelastic :
935+ return SplitProduction::kHadronicInteraction ;
936+ default :
937+ return SplitProduction::kOtherProcess ;
938+ }
939+ }
940+
941+ // / \brief Fill which hadron is the common ancestor of an "other hadron" pair and how it split into the two branches
942+ // / \param chain1 ancestor chain of the first candidate
943+ // / \param chain2 ancestor chain of the second candidate
944+ // / \param pairTruth truth flag of the pair
945+ // / \param vMeson pair four vector
946+ // / \param mcIter iterator of the full MC particle table, WILL BE CHANGED
947+ template <int pairType, o2::soa::is_iterator TMCIter>
948+ void diagnoseOtherHadron (Chain const & chain1, Chain const & chain2, const PhotonTruth pairTruth, ROOT ::Math::PtEtaPhiMVector const & vMeson, TMCIter& mcIter)
949+ {
950+ static constexpr std::array<std::string_view, 3 > HistAncestor = {" PCM/OtherHadron/hAncestor" , " EMC/OtherHadron/hAncestor" , " PCMEMC/OtherHadron/hAncestor" };
951+ static constexpr std::array<std::string_view, 3 > HistAncestorPdg = {" PCM/OtherHadron/hAncestorPdg" , " EMC/OtherHadron/hAncestorPdg" , " PCMEMC/OtherHadron/hAncestorPdg" };
952+ static constexpr int MaxPdgBin = 9999 ;
953+
954+ const int ancestor = getFirstCommonAncestor (chain1, chain2);
955+ const auto pos1 = std::distance (chain1.begin (), std::ranges::find (chain1, ancestor));
956+ const auto pos2 = std::distance (chain2.begin (), std::ranges::find (chain2, ancestor));
957+ if (ancestor < 0 || pos1 < 1 || pos2 < 1 || pos1 >= std::ssize (chain1) || pos2 >= std::ssize (chain2)) {
958+ return ; // not possible for "other hadron" (the ancestor is neither of the candidates), protection only
959+ }
960+ // the particle directly below the ancestor in each branch tells how the ancestor split
961+ const SplitProduction production = std::max (productionOf (mcIter, chain1[pos1 - 1 ]), productionOf (mcIter, chain2[pos2 - 1 ]));
962+ mcIter.setCursor (ancestor);
963+ const int pdg = mcIter.pdgCode ();
964+ registry.fill (HIST (HistAncestor[pairType]), vMeson.M (), vMeson.Pt (), static_cast <int >(otherHadronSpecies (pdg)), static_cast <int >(production), static_cast <int >(pairTruth));
965+ registry.fill (HIST (HistAncestorPdg[pairType]), std::min (std::abs (pdg), MaxPdgBin));
966+ }
967+
811968 // / \brief Apply the pair cuts and fill the pair ancestor histogram
812969 // / \return bucket of the pair, std::nullopt if the pair was rejected
813970 template <int pairType, o2::soa::is_iterator TPhoton1, o2::soa::is_iterator TPhoton2, o2::soa::is_iterator TMCIter>
@@ -830,6 +987,9 @@ struct TwoPhotonMcTask {
830987 const PhotonTruth pairTruth = std::max (truth1, truth2);
831988 const PairBucket bucket = classifyPair (chain1, chain2, mcIter);
832989 registry.fill (HIST (HistPairAncestor[pairType]), vMeson.M (), vMeson.Pt (), centOrMult, static_cast <int >(pairTruth), static_cast <int >(bucket));
990+ if (bucket == PairBucket::kCrossOtherHadron ) {
991+ diagnoseOtherHadron<pairType>(chain1, chain2, pairTruth, vMeson, mcIter);
992+ }
833993 if (mesonConfig.cfgEnableQA .value ) {
834994 registry.fill (HIST (" mesonQA/hInvMassPt" ), vMeson.M (), vMeson.Pt ());
835995 }
0 commit comments