@@ -598,6 +598,9 @@ struct TrackBuilderProducts : o2::framework::ProducesGroup {
598598 o2::framework::Produces<o2::aod::FDeuteronPids> producedDeuteronPids;
599599 o2::framework::Produces<o2::aod::FTritonPids> producedTritonPids;
600600 o2::framework::Produces<o2::aod::FHeliumPids> producedHeliumPids;
601+ o2::framework::Produces<o2::aod::FItsPurity> producedItsPurity;
602+ o2::framework::Produces<o2::aod::FTpcPurity> producedTpcPurity;
603+ o2::framework::Produces<o2::aod::FTofPurity> producedTofPurity;
601604};
602605
603606struct ConfTrackTables : o2::framework::ConfigurableGroup {
@@ -615,6 +618,9 @@ struct ConfTrackTables : o2::framework::ConfigurableGroup {
615618 o2::framework::Configurable<int > produceDeuteronPids{" produceDeuteronPids" , -1 , " Produce DeuteronPids (-1: auto; 0 off; 1 on)" };
616619 o2::framework::Configurable<int > produceTritonPids{" produceTritonPids" , -1 , " Produce TritonPids (-1: auto; 0 off; 1 on)" };
617620 o2::framework::Configurable<int > produceHeliumPids{" produceHeliumPids" , -1 , " Produce HeliumPids (-1: auto; 0 off; 1 on)" };
621+ o2::framework::Configurable<int > produceItsPurity{" produceItsPurity" , -1 , " Produce ItsPurity (-1: auto; 0 off; 1 on)" };
622+ o2::framework::Configurable<int > produceTpcPurity{" produceTpcPurity" , -1 , " Produce TpcPurity (-1: auto; 0 off; 1 on)" };
623+ o2::framework::Configurable<int > produceTofPurity{" produceTofPurity" , -1 , " Produce TofPurity (-1: auto; 0 off; 1 on)" };
618624};
619625
620626template <auto & SelectionHistName, auto & FilterHistName>
@@ -642,6 +648,52 @@ class TrackBuilder
642648 mProduceDeuteronPids = utils::enableTable (" FDeuteronPids_001" , table.produceDeuteronPids .value , initContext);
643649 mProduceTritonPids = utils::enableTable (" FTritonPids_001" , table.produceTritonPids .value , initContext);
644650 mProduceHeliumPids = utils::enableTable (" FHeliumPids_001" , table.produceHeliumPids .value , initContext);
651+ mProduceItsPurity = utils::enableTable (" FItsPurity_001" , table.produceItsPurity .value , initContext);
652+ mProduceTpcPurity = utils::enableTable (" FTpcPurity_001" , table.produceTpcPurity .value , initContext);
653+ mProduceTofPurity = utils::enableTable (" FTofPurity_001" , table.produceTofPurity .value , initContext);
654+
655+ if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity ) {
656+ // assign each active species (requirePidXxx == true) to the next free slot (0-3), in
657+ // canonical order (PuritySpeciesName); species with requirePidXxx == false get no slot.
658+ const std::array<bool , PuritySpeciesLast> requirePid = {
659+ config.requirePidElectron .value , config.requirePidPion .value , config.requirePidKaon .value ,
660+ config.requirePidProton .value , config.requirePidDeuteron .value , config.requirePidTriton .value , config.requirePidHelium .value };
661+
662+ int nextSlot = 0 ;
663+ for (int i = 0 ; i < PuritySpeciesLast; i++) {
664+ if (!requirePid[i]) {
665+ continue ;
666+ }
667+ if (nextSlot >= utils::PurityNSlots) {
668+ LOG (fatal) << " More than " << utils::PurityNSlots << " species have requirePidXxx enabled, "
669+ << " but the purity tables only have " << utils::PurityNSlots << " slots. "
670+ << " Disable requirePidXxx for species you don't need purity info for. Breaking..." ;
671+ }
672+ mPuritySlot [i] = nextSlot;
673+ nextSlot++;
674+ }
675+
676+ // one bin per species (own SelectionName -> own cutculator group), plus the usual
677+ // trailing "All analyzed"/"All passed" pair that cutculator always skips by position.
678+ // BitPosition___X (unassigned species) is deliberately excluded from cutculator's
679+ // "neutral" category, so nothing is selectable for a species that isn't configured here.
680+ // Picking the one assigned bit for a species gives exactly 1<<slot -- the one-hot
681+ // puritySlotMask value to paste into the downstream TrackSelection, with no changes
682+ // needed in cutculator.py itself.
683+ registry->add (" hPuritySlotAssignment" , " Purity slot assignment" , o2::framework::HistType::kTH1F , {{PuritySpeciesLast + 2 , -0.5 , PuritySpeciesLast + 2 - 0.5 }});
684+ auto hSlotAssignment = registry->get <TH1 >(HIST (" hPuritySlotAssignment" ));
685+ for (int i = 0 ; i < PuritySpeciesLast; i++) {
686+ std::string bitPosition = mPuritySlot [i] >= 0 ? std::to_string (mPuritySlot [i]) : " X" ;
687+ std::string value = mPuritySlot [i] >= 0 ? (" Slot " + std::to_string (mPuritySlot [i])) : " Not configured" ;
688+ std::string label = std::string (" SelectionName___" ) + PuritySpeciesName[i] +
689+ " :::MinimalCut___0:::OptionalCut___0:::BitPosition___" + bitPosition +
690+ " :::Value___" + value;
691+ hSlotAssignment->GetXaxis ()->SetBinLabel (i + 1 , label.c_str ());
692+ hSlotAssignment->Fill (i, 1 );
693+ }
694+ hSlotAssignment->GetXaxis ()->SetBinLabel (PuritySpeciesLast + 1 , " All analyzed" );
695+ hSlotAssignment->GetXaxis ()->SetBinLabel (PuritySpeciesLast + 2 , " All passed" );
696+ }
645697
646698 if (mProduceTracks && mProduceLiteTracks ) {
647699 LOG (fatal) << " FTracks and FLiteTracks are mutually exclusive -- enable only one. "
@@ -660,7 +712,7 @@ class TrackBuilder
660712 << " or switch to FTracks if CollisionTables.produceCollisions is enabled instead." ;
661713 }
662714
663- if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids ) {
715+ if (mProduceTracks || mProduceLiteTracks || mProduceTrackMasks || mProduceTrackMass || mProduceTrackDcas || mProduceTrackExtras || mProduceElectronPids || mProducePionPids || mProduceKaonPids || mProduceProtonPids || mProduceDeuteronPids || mProduceTritonPids || mProduceHeliumPids || mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity ) {
664716 mFillAnyTable = true ;
665717 } else {
666718 LOG (info) << " No tables configured, Selection object will not be configured..." ;
@@ -797,6 +849,41 @@ class TrackBuilder
797849 }
798850 trackProducts.producedHeliumPids (itsHe, track.tpcNSigmaHe (), track.tofNSigmaHe ());
799851 }
852+ if (mProduceItsPurity || mProduceTpcPurity || mProduceTofPurity ) {
853+ // raw nSigma per species, same values already used for the FXxxPids blocks above --
854+ // unused slots (species without a requirePidXxx flag, or not covered by this track
855+ // type) stay at the 0 placeholder, same contract as the missing-Tof case
856+ std::array<float , PuritySpeciesLast> itsNSigma = {0 .f , 0 .f , 0 .f , 0 .f , 0 .f , 0 .f , 0 .f };
857+ std::array<float , PuritySpeciesLast> tpcNSigma = {track.tpcNSigmaEl (), track.tpcNSigmaPi (), track.tpcNSigmaKa (), track.tpcNSigmaPr (), track.tpcNSigmaDe (), track.tpcNSigmaTr (), track.tpcNSigmaHe ()};
858+ std::array<float , PuritySpeciesLast> tofNSigma = {track.tofNSigmaEl (), track.tofNSigmaPi (), track.tofNSigmaKa (), track.tofNSigmaPr (), track.tofNSigmaDe (), track.tofNSigmaTr (), track.tofNSigmaHe ()};
859+
860+ if constexpr (requires (T1 t) { t.itsNSigmaEl (); }) {
861+ itsNSigma = {track.itsNSigmaEl (), track.itsNSigmaPi (), track.itsNSigmaKa (), track.itsNSigmaPr (), track.itsNSigmaDe (), track.itsNSigmaTr (), track.itsNSigmaHe ()};
862+ }
863+
864+ std::array<float , utils::PurityNSlots> itsSlots = {0 .f , 0 .f , 0 .f , 0 .f };
865+ std::array<float , utils::PurityNSlots> tpcSlots = {0 .f , 0 .f , 0 .f , 0 .f };
866+ std::array<float , utils::PurityNSlots> tofSlots = {0 .f , 0 .f , 0 .f , 0 .f };
867+ bool hasTof = track.hasTOF ();
868+ for (int i = 0 ; i < PuritySpeciesLast; i++) {
869+ if (mPuritySlot [i] < 0 ) {
870+ continue ;
871+ }
872+ itsSlots[mPuritySlot [i]] = itsNSigma[i];
873+ tpcSlots[mPuritySlot [i]] = tpcNSigma[i];
874+ tofSlots[mPuritySlot [i]] = hasTof ? tofNSigma[i] : 0 .f ; // PurityHasTof is the gate; 0 here is just a placeholder
875+ }
876+
877+ if (mProduceItsPurity ) {
878+ trackProducts.producedItsPurity (utils::packPurityNSigma4 (itsSlots[0 ], itsSlots[1 ], itsSlots[2 ], itsSlots[3 ], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax));
879+ }
880+ if (mProduceTpcPurity ) {
881+ trackProducts.producedTpcPurity (utils::packPurityNSigma4 (tpcSlots[0 ], tpcSlots[1 ], tpcSlots[2 ], tpcSlots[3 ], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax));
882+ }
883+ if (mProduceTofPurity ) {
884+ trackProducts.producedTofPurity (utils::packPurityNSigma4 (tofSlots[0 ], tofSlots[1 ], tofSlots[2 ], tofSlots[3 ], o2::aod::femtotracks::NSigmaPurityMagMin, o2::aod::femtotracks::NSigmaPurityMagMax), hasTof);
885+ }
886+ }
800887 return true ;
801888 }
802889
@@ -892,6 +979,16 @@ class TrackBuilder
892979 bool mProduceDeuteronPids = false ;
893980 bool mProduceTritonPids = false ;
894981 bool mProduceHeliumPids = false ;
982+ bool mProduceItsPurity = false ;
983+ bool mProduceTpcPurity = false ;
984+ bool mProduceTofPurity = false ;
985+
986+ // canonical species order for purity slot assignment -- fixed, not configurable (see
987+ // init() for how requirePidXxx flags map onto slots 0-3)
988+ static constexpr int PuritySpeciesLast = 7 ;
989+ static constexpr std::array<const char *, PuritySpeciesLast> PuritySpeciesName = {" Electron" , " Pion" , " Kaon" , " Proton" , " Deuteron" , " Triton" , " Helium" };
990+ // mPuritySlot[i] = assigned slot (0-3) for species i (PuritySpeciesName order), or -1 if not assigned
991+ std::array<int , PuritySpeciesLast> mPuritySlot = {-1 , -1 , -1 , -1 , -1 , -1 , -1 };
895992
896993 std::unordered_map<int64_t , int64_t > indexMap; // for mapping tracks to daughters of lambdas, cascades and resonances ...
897994};
@@ -900,6 +997,12 @@ struct TrackBuilderDerivedToDerivedProducts : o2::framework::ProducesGroup {
900997 o2::framework::Produces<o2::aod::StoredFTracks> producedTracks;
901998 o2::framework::Produces<o2::aod::StoredFTrackMasks> producedTrackMasks;
902999 o2::framework::Produces<o2::aod::StoredFTrackMass> producedTrackMass;
1000+ // purity/fraction, copied through if the input track type has them joined (same
1001+ // compile-time-detected, opt-in-by-type pattern as producedTrackMass above)
1002+ o2::framework::Produces<o2::aod::StoredFItsPurity> producedItsPurity;
1003+ o2::framework::Produces<o2::aod::StoredFTpcPurity> producedTpcPurity;
1004+ o2::framework::Produces<o2::aod::StoredFTofPurity> producedTofPurity;
1005+ o2::framework::Produces<o2::aod::StoredFTrackDcas> producedTrackDcas;
9031006};
9041007
9051008struct ConfTrackTablesDerivedToDerived : o2::framework::ConfigurableGroup {
@@ -977,6 +1080,14 @@ class TrackBuilderDerivedToDerived
9771080 if constexpr (utils::HasMass<T1 >) {
9781081 trackProducts.producedTrackMass (track.mass ());
9791082 }
1083+ if constexpr (requires (T1 t) { t.itsPackedNSigma (); }) {
1084+ trackProducts.producedItsPurity (track.itsPackedNSigma ());
1085+ trackProducts.producedTpcPurity (track.tpcPackedNSigma ());
1086+ trackProducts.producedTofPurity (track.tofPackedNSigma (), track.purityHasTof ());
1087+ }
1088+ if constexpr (requires (T1 t) { t.dcaXY (); }) {
1089+ trackProducts.producedTrackDcas (track.dcaXY (), track.dcaZ ());
1090+ }
9801091
9811092 const int64_t idx = trackProducts.producedTracks .lastIndex ();
9821093 indexMap.emplace (track.globalIndex (), idx);
0 commit comments