@@ -78,6 +78,7 @@ using CollBracket = o2::math_utils::Bracket<int>;
7878using CollisionsFull = soa::Join<aod::Collisions, aod::EvSels, aod::CentFT0Cs, aod::FT0Mults>;
7979using CollisionsFullMC = soa::Join<aod::Collisions, aod::McCollisionLabels, aod::EvSels, aod::CentFT0Cs, aod::FT0Mults>;
8080using TrackCandidates = soa::Join<aod::TracksIU, aod::TracksExtra, aod::TracksCovIU, aod::TracksDCA, aod::TrackSelection, aod::pidTPCFullDe, aod::pidTPCFullTr, aod::pidTOFFullDe, aod::pidTOFFullTr, aod::pidTOFFullHe, aod::pidTPCFullPr, aod::pidTOFFullPr, aod::pidTPCFullPi, aod::pidTOFFullPi, aod::pidTPCFullKa, aod::pidTOFFullKa, aod::TOFSignal, aod::TOFEvTime>;
81+ using TrackCandidatesMC = soa::Join<aod::TracksIU, aod::TracksExtra, aod::TracksCovIU, aod::TracksDCA, aod::TrackSelection, aod::pidTPCFullDe, aod::pidTPCFullTr, aod::pidTOFFullDe, aod::pidTOFFullTr, aod::pidTOFFullHe, aod::pidTPCFullPr, aod::pidTOFFullPr, aod::pidTPCFullPi, aod::pidTOFFullPi, aod::pidTPCFullKa, aod::pidTOFFullKa, aod::TOFSignal, aod::TOFEvTime, aod::McTrackLabels>;
8182
8283namespace
8384{
@@ -179,6 +180,7 @@ struct HadNucandidate {
179180struct HadNucleiFemto {
180181
181182 Produces<aod::HadronNucleiTable> mOutputDataTable ;
183+ Produces<aod::HadronNucleiTableMC> mOutputMCTable ;
182184 Produces<aod::HadronHyperTable> mOutputHyperDataTable ;
183185 Produces<aod::HadronNucleiMult> mOutputMultiplicityTable ;
184186
@@ -242,7 +244,6 @@ struct HadNucleiFemto {
242244 std::string prefix{" deuteronPid" };
243245 // Deuteron purity and PID cuts
244246 Configurable<float > settingCutPinMinDe{" settingCutPinMinDe" , 0 .0f , " Minimum Pin for De" };
245- Configurable<float > settingCutClSizeItsDe{" settingCutClSizeItsDe" , 4 .0f , " Minimum ITS cluster size for De" };
246247 Configurable<float > settingCutDeptMin{" settingCutDeptMin" , 0 .6f , " Minimum PT cut on De" };
247248 Configurable<float > settingCutDeptMax{" settingCutDeptMax" , 1 .6f , " Maximum PT cut on De" };
248249 Configurable<float > settingCutPinMinTOFITSDe{" settingCutPinMinTOFITSDe" , 1 .2f , " Minimum p to apply the TOF ITS cut on De" };
@@ -289,6 +290,16 @@ struct HadNucleiFemto {
289290 Configurable<float > settingClosePairSpecificRadius{" settingClosePairSpecificRadius" , 85 .f , " TPC radius in cm used when close pair rejection mode is 2" };
290291 } CPR ;
291292
293+ struct : o2::framework::ConfigurableGroup {
294+ // cppcheck-suppress unusedStructMember
295+ std::string prefix{" mc" };
296+ Configurable<bool > settingRequireSel8{" settingRequireSel8" , true , " Apply the same sel8 event selection to reconstructed MC as to data" };
297+ Configurable<bool > settingRequireTruthSpecies{" settingRequireTruthSpecies" , true , " Store only truth-matched pion-nucleus pairs" };
298+ Configurable<bool > settingRequireSameMCCollision{" settingRequireSameMCCollision" , true , " Require the two truth particles to come from the same MC collision" };
299+ Configurable<bool > settingRequireRecoMCCollisionMatch{" settingRequireRecoMCCollisionMatch" , true , " Require both truth particles to match the reconstructed collision MC label" };
300+ Configurable<bool > settingRequirePhysicalPrimaries{" settingRequirePhysicalPrimaries" , false , " Store only pairs in which both truth particles are physical primaries" };
301+ } mc;
302+
292303 struct : o2::framework::ConfigurableGroup {
293304 // cppcheck-suppress unusedStructMember
294305 std::string prefix{" hypertriton" };
@@ -339,6 +350,7 @@ struct HadNucleiFemto {
339350 } pidCalibration;
340351
341352 Preslice<TrackCandidates> mPerCol = aod::track::collisionId;
353+ Preslice<TrackCandidatesMC> mPerColMC = aod::track::collisionId;
342354 PresliceUnsorted<o2::aod::DataHypCandsWColl> hypPerCol = o2::aod::hyperrec::collisionId;
343355
344356 // binning for EM background
@@ -398,6 +410,12 @@ struct HadNucleiFemto {
398410 {" h2CPRBefore" , " Close pair rejection before cut; #Delta#eta; #Delta#phi^{*}" , {HistType::kTH2F , {{300 , -0 .15f , 0 .15f }, {400 , -0 .2f , 0 .2f }}}},
399411 {" h2CPRAfter" , " Close pair rejection after cut; #Delta#eta; #Delta#phi^{*}" , {HistType::kTH2F , {{300 , -0 .15f , 0 .15f }, {400 , -0 .2f , 0 .2f }}}},
400412
413+ // Reconstructed MC pair QA
414+ {" MC/hPairFlow" , " MC pair flow;step;counts" , {HistType::kTH1F , {{7 , -0 .5f , 6 .5f }}}},
415+ {" MC/hKstarRecVsGen" , " Reconstructed versus generated k*;generated k* (GeV/c);reconstructed k* (GeV/c)" , {HistType::kTH2F , {{300 , 0 .f , 3 .f }, {300 , 0 .f , 3 .f }}}},
416+ {" MC/hPtNuRecVsGen" , " Reconstructed versus generated signed nucleus pT;generated pT (GeV/c);reconstructed pT (GeV/c)" , {HistType::kTH2F , {{280 , -7 .f , 7 .f }, {280 , -7 .f , 7 .f }}}},
417+ {" MC/hPtHadRecVsGen" , " Reconstructed versus generated signed pion pT;generated pT (GeV/c);reconstructed pT (GeV/c)" , {HistType::kTH2F , {{280 , -7 .f , 7 .f }, {280 , -7 .f , 7 .f }}}},
418+
401419 // dE/dx
402420 {" h2dEdxNucandidates" , " dEdx distribution; #it{p} (GeV/#it{c}); dE/dx (a.u.)" , {HistType::kTH2F , {{200 , -5 .0f , 5 .0f }, {100 , 0 .0f , 2000 .0f }}}},
403421 {" h2dEdxHadcandidates" , " dEdx distribution; #it{p} (GeV/#it{c}); dE/dx (a.u.)" , {HistType::kTH2F , {{200 , -5 .0f , 5 .0f }, {100 , 0 .0f , 2000 .0f }}}},
@@ -466,6 +484,20 @@ struct HadNucleiFemto {
466484
467485 void init (o2::framework::InitContext&)
468486 {
487+ constexpr int closePairRadiusModePv = 0 ;
488+ constexpr int closePairRadiusModeSpecificTpc = 2 ;
489+ if (CPR .settingEnableClosePairRejection .value ) {
490+ if (CPR .settingClosePairDeltaEtaMax .value <= 0 .f || CPR .settingClosePairDeltaPhiMax .value <= 0 .f ) {
491+ LOG (fatal) << " Close-pair rejection requires positive delta-eta and delta-phi-star limits" ;
492+ }
493+ if (CPR .settingClosePairRadiusMode .value < closePairRadiusModePv || CPR .settingClosePairRadiusMode .value > closePairRadiusModeSpecificTpc) {
494+ LOG (fatal) << " Invalid close-pair radius mode " << CPR .settingClosePairRadiusMode .value << " ; expected 0, 1, or 2" ;
495+ }
496+ if (CPR .settingClosePairRadiusMode .value == closePairRadiusModeSpecificTpc && CPR .settingClosePairSpecificRadius .value <= 0 .f ) {
497+ LOG (fatal) << " Close-pair rejection at a specific TPC radius requires a positive radius" ;
498+ }
499+ }
500+
469501 mZorroSummary .setObject (mZorro .getZorroSummary ());
470502 mRunNumber = 0 ;
471503
@@ -505,17 +537,31 @@ struct HadNucleiFemto {
505537 }
506538 }
507539
540+ template <bool isMC>
508541 void initCCDB (const aod::BCsWithTimestamps::iterator& bc)
509542 {
510543 if (mRunNumber == bc.runNumber ()) {
511544 return ;
512545 }
513- if (zorro.settingSkimmedProcessing ) {
514- mZorro .initCCDB (mCcdb .service , bc.runNumber (), bc.timestamp (), zorroTriggerMask ());
515- mZorro .populateHistRegistry (mQaRegistry , bc.runNumber ());
546+ if constexpr (!isMC) {
547+ if (zorro.settingSkimmedProcessing ) {
548+ mZorro .initCCDB (mCcdb .service , bc.runNumber (), bc.timestamp (), zorroTriggerMask ());
549+ mZorro .populateHistRegistry (mQaRegistry , bc.runNumber ());
550+ }
516551 }
517552 mRunNumber = bc.runNumber ();
518553 const float defaultBzValue = -999 .0f ;
554+
555+ // A fixed field is sufficient for CPR and DCAFitter when material
556+ // corrections are disabled. This also makes local MC tests independent
557+ // of an AliEn token when the CCDB payload is stored on Grid.
558+ if (ccdb.settingDbz > defaultBzValue && pidCalibration.settingMaterialCorrection == 0 ) {
559+ mDbz = ccdb.settingDbz ;
560+ mFitter .setBz (mDbz );
561+ LOG (info) << " Using configured magnetic field of " << mDbz << " kZG" ;
562+ return ;
563+ }
564+
519565 auto run3GrpTimestamp = bc.timestamp ();
520566 auto * grpo = mCcdb ->getForTimeStamp <o2::parameters::GRPObject>(ccdb.settingGrpPath , run3GrpTimestamp);
521567 o2::parameters::GRPMagField* grpmag = nullptr ;
@@ -542,31 +588,47 @@ struct HadNucleiFemto {
542588 mDbz = ccdb.settingDbz ;
543589 }
544590 }
591+ mFitter .setBz (mDbz );
545592 }
546593
547594 // ==================================================================================================================
548595
549596 template <bool isMC, typename Tcollision>
550- bool selectCollision (const Tcollision& collision, const aod::BCsWithTimestamps& )
597+ bool passesCollisionSelection (const Tcollision& collision)
551598 {
552- mQaRegistry .fill (HIST (" hEvents" ), 0 );
599+ // CPR uses phi* and therefore needs the magnetic field for MC as well.
600+ auto bc = collision.template bc_as <aod::BCsWithTimestamps>();
601+ initCCDB<isMC>(bc);
553602
554603 if constexpr (isMC) {
555- if (/* !collision.sel8() ||*/ std::abs (collision.posZ ()) > eventMixing.settingCutVertex ) {
604+ if ((mc. settingRequireSel8 . value && !collision.sel8 ()) || std::abs (collision.posZ ()) > eventMixing.settingCutVertex ) {
556605 return false ;
557606 }
558607 } else {
559- auto bc = collision.template bc_as <aod::BCsWithTimestamps>();
560- initCCDB (bc);
561-
562608 if (!collision.sel8 () || std::abs (collision.posZ ()) > eventMixing.settingCutVertex ) {
563609 return false ;
564610 }
565611 if (zorro.settingSkimmedProcessing ) {
566- const bool zorroSelected = mZorro .isSelected (collision.template bc_as <aod::BCsWithTimestamps>().globalBC ());
567- if (!zorroSelected) {
612+ if (!mZorro .isSelected (bc.globalBC ())) {
568613 return false ;
569614 }
615+ }
616+ }
617+
618+ return true ;
619+ }
620+
621+ template <bool isMC, typename Tcollision>
622+ bool selectCollision (const Tcollision& collision, const aod::BCsWithTimestamps&)
623+ {
624+ mQaRegistry .fill (HIST (" hEvents" ), 0 );
625+
626+ if (!passesCollisionSelection<isMC>(collision)) {
627+ return false ;
628+ }
629+
630+ if constexpr (!isMC) {
631+ if (zorro.settingSkimmedProcessing ) {
570632 mQaRegistry .fill (HIST (" hEvents" ), 2 );
571633 }
572634 }
@@ -894,7 +956,7 @@ struct HadNucleiFemto {
894956 }
895957
896958 template <typename Ttrack1, typename Ttrack2>
897- bool isClosePair (const Ttrack1& firstTrack, const Ttrack2& secondTrack)
959+ bool isClosePair (const Ttrack1& firstTrack, const Ttrack2& secondTrack, bool fillQA )
898960 {
899961 if (!CPR .settingEnableClosePairRejection .value ) {
900962 return false ;
@@ -909,11 +971,13 @@ struct HadNucleiFemto {
909971 return false ;
910972 }
911973
912- mQaRegistry .fill (HIST (" h2CPRBefore" ), deltaEta, deltaPhi);
974+ if (fillQA) {
975+ mQaRegistry .fill (HIST (" h2CPRBefore" ), deltaEta, deltaPhi);
976+ }
913977 const bool isRejected = std::pow (deltaPhi, 2 .f ) / std::pow (CPR .settingClosePairDeltaPhiMax .value , 2 .f ) +
914978 std::pow (deltaEta, 2 .f ) / std::pow (CPR .settingClosePairDeltaEtaMax .value , 2 .f ) <
915979 1 .f ;
916- if (!isRejected) {
980+ if (fillQA && !isRejected) {
917981 mQaRegistry .fill (HIST (" h2CPRAfter" ), deltaEta, deltaPhi);
918982 }
919983 return isRejected;
@@ -1673,7 +1737,7 @@ struct HadNucleiFemto {
16731737 continue ;
16741738 }
16751739 hasHadronSelected = true ;
1676- if (isClosePair (track0, track1)) {
1740+ if (isClosePair (track0, track1, /* fillQA */ true )) {
16771741 continue ;
16781742 }
16791743
@@ -1739,7 +1803,7 @@ struct HadNucleiFemto {
17391803 if (!selectTrackHadron (hadCand) || !selectionPIDHadron (hadCand)) {
17401804 continue ;
17411805 }
1742- if (isClosePair (DeCand, hadCand)) {
1806+ if (isClosePair (DeCand, hadCand, /* fillQA */ true )) {
17431807 continue ;
17441808 }
17451809
@@ -1811,6 +1875,35 @@ struct HadNucleiFemto {
18111875 }
18121876 }
18131877
1878+ template <typename TparticleNu, typename TparticleHad>
1879+ void fillMCTable (const HadNucandidate& hadNucand, const TparticleNu& particleNu, const TparticleHad& particleHad, bool sameMCCollision, bool matchesRecoMCCollision)
1880+ {
1881+ const float signedPtNuMC = particleNu.pdgCode () >= 0 ? particleNu.pt () : -particleNu.pt ();
1882+ const float signedPtHadMC = particleHad.pdgCode () >= 0 ? particleHad.pt () : -particleHad.pt ();
1883+ const std::array<float , 3 > momentumNuMC{particleNu.px (), particleNu.py (), particleNu.pz ()};
1884+ const std::array<float , 3 > momentumHadMC{particleHad.px (), particleHad.py (), particleHad.pz ()};
1885+ const float kstarMC = computePairKstar (momentumHadMC, mMassHad , momentumNuMC, nucleusMass ());
1886+
1887+ mOutputMCTable (
1888+ signedPtNuMC,
1889+ particleNu.eta (),
1890+ particleNu.phi (),
1891+ signedPtHadMC,
1892+ particleHad.eta (),
1893+ particleHad.phi (),
1894+ kstarMC,
1895+ particleNu.pdgCode (),
1896+ particleHad.pdgCode (),
1897+ particleNu.isPhysicalPrimary (),
1898+ particleHad.isPhysicalPrimary (),
1899+ sameMCCollision,
1900+ matchesRecoMCCollision);
1901+
1902+ mQaRegistry .fill (HIST (" MC/hKstarRecVsGen" ), kstarMC, hadNucand.kstar );
1903+ mQaRegistry .fill (HIST (" MC/hPtNuRecVsGen" ), signedPtNuMC, hadNucand.recoPtNu ());
1904+ mQaRegistry .fill (HIST (" MC/hPtHadRecVsGen" ), signedPtHadMC, hadNucand.recoPtHad ());
1905+ }
1906+
18141907 template <typename Tcoll>
18151908 void fillTableHyper (const HadNucandidate& hadNucand, const Tcoll& collision)
18161909 {
@@ -1954,6 +2047,89 @@ struct HadNucleiFemto {
19542047
19552048 // ==================================================================================================================
19562049
2050+ void processMC (const CollisionsFullMC& collisions, const TrackCandidatesMC& tracks, const aod::McParticles&, const aod::BCsWithTimestamps& bcs)
2051+ {
2052+ mGoodCollisions .clear ();
2053+ mGoodCollisions .resize (collisions.size (), false );
2054+
2055+ for (const auto & collision : collisions) {
2056+ mTrackPairs .clear ();
2057+
2058+ if (!selectCollision</* isMC*/ true >(collision, bcs)) {
2059+ continue ;
2060+ }
2061+ if (mc.settingRequireRecoMCCollisionMatch .value && !collision.has_mcCollision ()) {
2062+ continue ;
2063+ }
2064+
2065+ const uint64_t collIdx = collision.globalIndex ();
2066+ mGoodCollisions [collIdx] = true ;
2067+ auto tracksThisCollision = tracks.sliceBy (mPerColMC , collIdx);
2068+ tracksThisCollision.bindExternalIndices (&tracks);
2069+
2070+ // This is deliberately the same pair builder as for data. It applies
2071+ // nucleus/pion selections and CPR before truth matching.
2072+ pairTracksSameEvent (tracksThisCollision, collision.centFT0C ());
2073+
2074+ for (const auto & trackPair : mTrackPairs ) {
2075+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 0 );
2076+ auto trackNu = tracks.rawIteratorAt (trackPair.tr0Idx );
2077+ auto trackHad = tracks.rawIteratorAt (trackPair.tr1Idx );
2078+
2079+ if (!trackNu.has_mcParticle () || !trackHad.has_mcParticle ()) {
2080+ continue ;
2081+ }
2082+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 1 );
2083+
2084+ const auto particleNu = trackNu.template mcParticle_as <aod::McParticles>();
2085+ const auto particleHad = trackHad.template mcParticle_as <aod::McParticles>();
2086+ const bool truthSpeciesMatch = std::abs (particleNu.pdgCode ()) == std::abs (species.settingNuPDGCode .value ) &&
2087+ std::abs (particleHad.pdgCode ()) == std::abs (species.settingHadPDGCode .value );
2088+ if (mc.settingRequireTruthSpecies .value && !truthSpeciesMatch) {
2089+ continue ;
2090+ }
2091+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 2 );
2092+
2093+ const bool sameMCCollision = particleNu.mcCollisionId () == particleHad.mcCollisionId ();
2094+ if (mc.settingRequireSameMCCollision .value && !sameMCCollision) {
2095+ continue ;
2096+ }
2097+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 3 );
2098+
2099+ const bool matchesRecoMCCollision = collision.has_mcCollision () &&
2100+ particleNu.mcCollisionId () == collision.mcCollisionId () &&
2101+ particleHad.mcCollisionId () == collision.mcCollisionId ();
2102+ if (mc.settingRequireRecoMCCollisionMatch .value && !matchesRecoMCCollision) {
2103+ continue ;
2104+ }
2105+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 4 );
2106+
2107+ if (mc.settingRequirePhysicalPrimaries .value &&
2108+ (!particleNu.isPhysicalPrimary () || !particleHad.isPhysicalPrimary ())) {
2109+ continue ;
2110+ }
2111+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 5 );
2112+
2113+ HadNucandidate hadNucand;
2114+ if (!fillCandidateInfo (trackNu, trackHad, trackPair.collBracket , collisions, hadNucand, tracks, /* isMixedEvent*/ false )) {
2115+ continue ;
2116+ }
2117+ mQaRegistry .fill (HIST (" MC/hPairFlow" ), 6 );
2118+ auto selectedCollision = collisions.rawIteratorAt (hadNucand.collisionID );
2119+ fillKstar (hadNucand, selectedCollision);
2120+ fillHistograms (hadNucand);
2121+
2122+ if (output.settingFillTable && shouldFillOutputTable (hadNucand)) {
2123+ fillTable (hadNucand, selectedCollision);
2124+ fillMCTable (hadNucand, particleNu, particleHad, sameMCCollision, matchesRecoMCCollision);
2125+ }
2126+ }
2127+ }
2128+ }
2129+ PROCESS_SWITCH (HadNucleiFemto, processMC, " Process reconstructed MC same-event pairs" , false );
2130+
2131+ // ==================================================================================================================
2132+
19572133 void processSameEvent (const CollisionsFull& collisions, const TrackCandidates& tracks, const aod::BCsWithTimestamps& bcs)
19582134 {
19592135 mGoodCollisions .clear ();
@@ -2015,24 +2191,31 @@ struct HadNucleiFemto {
20152191 }
20162192 PROCESS_SWITCH (HadNucleiFemto, processSameEventHyper, " Process Same event" , false );
20172193
2018- void processMixedEvent (const CollisionsFull& collisions, const TrackCandidates& tracks)
2194+ void processMixedEvent (const CollisionsFull& collisions, const TrackCandidates& tracks, const aod::BCsWithTimestamps& )
20192195 {
20202196 LOG (debug) << " Processing mixed event" ;
2021- mTrackPairs .clear ();
20222197
20232198 for (const auto & [c1, tracks1, c2, tracks2] : mPair ) {
2024- if (!c1. sel8 ( ) || !c2. sel8 ( )) {
2199+ if (!passesCollisionSelection< /* isMC */ false >(c1 ) || !passesCollisionSelection< /* isMC */ false >(c2 )) {
20252200 continue ;
20262201 }
20272202
20282203 mQaRegistry .fill (HIST (" hNcontributor" ), c1.numContrib ());
20292204 mQaRegistry .fill (HIST (" hVtxZ" ), c1.posZ ());
20302205
2206+ auto bc1 = c1.template bc_as <aod::BCsWithTimestamps>();
2207+ auto bc2 = c2.template bc_as <aod::BCsWithTimestamps>();
2208+ initCCDB</* isMC*/ false >(bc1);
2209+ mTrackPairs .clear ();
20312210 pairTracksEventMixing (tracks1, tracks2);
2211+ fillPairs (collisions, tracks, /* isMixedEvent*/ true );
2212+
2213+ initCCDB</* isMC*/ false >(bc2);
2214+ mTrackPairs .clear ();
20322215 pairTracksEventMixing (tracks2, tracks1);
2216+ fillPairs (collisions, tracks, /* isMixedEvent*/ true );
20332217 }
2034-
2035- fillPairs (collisions, tracks, /* isMixedEvent*/ true );
2218+ mTrackPairs .clear ();
20362219 }
20372220 PROCESS_SWITCH (HadNucleiFemto, processMixedEvent, " Process Mixed event" , false );
20382221
0 commit comments