From 0275db75f3b96ff0a29630a695ba0e8a90bef672 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 11:57:53 +0530 Subject: [PATCH 01/17] [PWGLF] First commit for h-resonance correlation --- .../DataModel/LFHResonanceCorrelationTables.h | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 PWGLF/DataModel/LFHResonanceCorrelationTables.h diff --git a/PWGLF/DataModel/LFHResonanceCorrelationTables.h b/PWGLF/DataModel/LFHResonanceCorrelationTables.h new file mode 100644 index 00000000000..77365da3b86 --- /dev/null +++ b/PWGLF/DataModel/LFHResonanceCorrelationTables.h @@ -0,0 +1,151 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +/// \brief This task serves to do hadron-resonance correlation studies. +/// The yield will be calculated using the two-particle correlation method. +/// Trigger particle : Hadrons +/// Associated Particles : Phi, K*0 +/// +/// \author Hirak Kumar Koley (hirak.koley@cern.ch) + +#ifndef PWGLF_DATAMODEL_LFHRESONANCECORRELATIONTABLES_H_ +#define PWGLF_DATAMODEL_LFHRESONANCECORRELATIONTABLES_H_ + +#include +#include + +// Simple checker +#define bitcheck(var, nbit) ((var) & (1 << (nbit))) + +namespace o2::aod +{ +/// _________________________________________ +/// Table for storing trigger track indices +namespace triggerTracks +{ +DECLARE_SOA_INDEX_COLUMN(Collision, collision); //! +DECLARE_SOA_COLUMN(MCPhysicalPrimary, mcPhysicalPrimary, bool); // true physical primary flag +DECLARE_SOA_INDEX_COLUMN_FULL(Track, track, int, Tracks, "_Trigger"); //! +DECLARE_SOA_COLUMN(MCOriginalPt, mcOriginalPt, float); // true generated pt +DECLARE_SOA_COLUMN(IsLeading, isLeading, bool); // is leading track in the event +} // namespace triggerTracks +DECLARE_SOA_TABLE(TriggerTracks, "AOD", "TRIGGERTRACKS", o2::soa::Index<>, triggerTracks::CollisionId, triggerTracks::MCPhysicalPrimary, triggerTracks::TrackId, triggerTracks::MCOriginalPt, triggerTracks::IsLeading); +namespace triggerTrackExtras +{ +DECLARE_SOA_COLUMN(Extra, extra, int); // true physical primary flag +} // namespace triggerTrackExtras +DECLARE_SOA_TABLE(TriggerTrackExtras, "AOD", "TRIGGERTRACKEXTRAs", triggerTrackExtras::Extra); + +/// _________________________________________ +/// Table for storing assoc track indices +namespace assocHadrons +{ +DECLARE_SOA_INDEX_COLUMN(Collision, collision); //! +DECLARE_SOA_COLUMN(MCPhysicalPrimary, mcPhysicalPrimary, bool); // true physical primary flag +DECLARE_SOA_INDEX_COLUMN_FULL(Track, track, int, Tracks, "_Assoc"); //! +DECLARE_SOA_COLUMN(MCOriginalPt, mcOriginalPt, float); // true generated pt +DECLARE_SOA_COLUMN(PDGCode, pdgCode, int); // pdg code of the MC particle +} // namespace assocHadrons +DECLARE_SOA_TABLE(AssocHadrons, "AOD", "ASSOCHADRONS", o2::soa::Index<>, assocHadrons::CollisionId, assocHadrons::MCPhysicalPrimary, assocHadrons::TrackId, assocHadrons::MCOriginalPt, assocHadrons::PDGCode); +/// _________________________________________ +/// Table for storing assoc track PID +namespace assocPID +{ +DECLARE_SOA_COLUMN(NSigmaTPCPi, nSigmaTPCPi, float); +DECLARE_SOA_COLUMN(NSigmaTPCKa, nSigmaTPCKa, float); +DECLARE_SOA_COLUMN(NSigmaTPCPr, nSigmaTPCPr, float); +DECLARE_SOA_COLUMN(NSigmaTPCEl, nSigmaTPCEl, float); +DECLARE_SOA_COLUMN(NSigmaTOFPi, nSigmaTOFPi, float); +DECLARE_SOA_COLUMN(NSigmaTOFKa, nSigmaTOFKa, float); +DECLARE_SOA_COLUMN(NSigmaTOFPr, nSigmaTOFPr, float); +DECLARE_SOA_COLUMN(NSigmaTOFEl, nSigmaTOFEl, float); +} // namespace assocPID +DECLARE_SOA_TABLE(AssocPID, "AOD", "ASSOCPID", assocPID::NSigmaTPCPi, assocPID::NSigmaTPCKa, assocPID::NSigmaTPCPr, assocPID::NSigmaTPCEl, assocPID::NSigmaTOFPi, assocPID::NSigmaTOFKa, assocPID::NSigmaTOFPr, assocPID::NSigmaTOFEl); + +/// _________________________________________ +/// Table for storing associated phi candidate indices +namespace assocPhis +{ + +DECLARE_SOA_INDEX_COLUMN(Collision, collision); //! + +// MC information +DECLARE_SOA_COLUMN(MCTruePhi, mcTruePhi, bool); +DECLARE_SOA_COLUMN(MCPhysicalPrimary, mcPhysicalPrimary, bool); + +// Quality variable +DECLARE_SOA_COLUMN(Pt, pt, float); +DECLARE_SOA_COLUMN(Eta, eta, float); +DECLARE_SOA_COLUMN(Phi, phi, float); +DECLARE_SOA_COLUMN(Mass, mass, float); + +DECLARE_SOA_INDEX_COLUMN_FULL(PosTrackDaughter, posTrackDaughter, int, Tracks, "_PhiPosDaughter"); +DECLARE_SOA_INDEX_COLUMN_FULL(NegTrackDaughter, negTrackDaughter, int, Tracks, "_PhiNegDaughter"); + +} // namespace assocPhis + +DECLARE_SOA_TABLE( + AssocPhis, + "AOD", + "ASSOCPHIS", + o2::soa::Index<>, + assocPhis::CollisionId, + assocPhis::MCTruePhi, + assocPhis::MCPhysicalPrimary, + assocPhis::Pt, + assocPhis::Eta, + assocPhis::Phi, + assocPhis::Mass, + assocPhis::PosTrackDaughterId, + assocPhis::NegTrackDaughterId); + +/// _________________________________________ +/// Table for storing associated K*0 candidate indices +/// K*0 -> K+ pi- and anti-K*0 -> K- pi+ are stored together (sign-agnostic, +/// same convention as AssocPhis): PosTrackDaughter/NegTrackDaughter identify +/// the positively/negatively charged daughter, irrespective of which one is +/// the kaon and which one is the pion for that candidate. +namespace assocKstars +{ + +DECLARE_SOA_INDEX_COLUMN(Collision, collision); //! + +// MC information +DECLARE_SOA_COLUMN(MCTrueKstar, mcTrueKstar, bool); +DECLARE_SOA_COLUMN(MCPhysicalPrimary, mcPhysicalPrimary, bool); + +// Quality variable +DECLARE_SOA_COLUMN(Pt, pt, float); +DECLARE_SOA_COLUMN(Eta, eta, float); +DECLARE_SOA_COLUMN(Phi, phi, float); +DECLARE_SOA_COLUMN(Mass, mass, float); + +DECLARE_SOA_INDEX_COLUMN_FULL(PosTrackDaughter, posTrackDaughter, int, Tracks, "_KstarPosDaughter"); +DECLARE_SOA_INDEX_COLUMN_FULL(NegTrackDaughter, negTrackDaughter, int, Tracks, "_KstarNegDaughter"); + +} // namespace assocKstars + +DECLARE_SOA_TABLE( + AssocKstars, + "AOD", + "ASSOCKSTARS", + o2::soa::Index<>, + assocKstars::CollisionId, + assocKstars::MCTrueKstar, + assocKstars::MCPhysicalPrimary, + assocKstars::Pt, + assocKstars::Eta, + assocKstars::Phi, + assocKstars::Mass, + assocKstars::PosTrackDaughterId, + assocKstars::NegTrackDaughterId); +} // namespace o2::aod +#endif // PWGLF_DATAMODEL_LFHRESONANCECORRELATIONTABLES_H_ \ No newline at end of file From 6e258e29877971e6c48353b359514187797e3457 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 11:58:40 +0530 Subject: [PATCH 02/17] [PWGLF] First commit for h-resonance correlation --- .../hResonanceCorrelationFilter.cxx | 1341 +++++++++++++++++ 1 file changed, 1341 insertions(+) create mode 100644 PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx new file mode 100644 index 00000000000..446aa652a29 --- /dev/null +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -0,0 +1,1341 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +/// +/// \brief This task pre-filters tracks to do h-resonance (phi, K*0) +/// correlations with an analysis task. +/// +/// \file hResonanceCorrelationFilter.cxx +/// \author Hirak Kumar Koley (hirak.koley@cern.ch) + +#include "PWGLF/DataModel/LFHResonanceCorrelationTables.h" + +#include "Common/CCDB/EventSelectionParams.h" +#include "Common/CCDB/RCTSelectionFlags.h" +#include "Common/Core/Zorro.h" +#include "Common/Core/ZorroSummary.h" +#include "Common/DataModel/Centrality.h" +#include "Common/DataModel/EventSelection.h" +#include "Common/DataModel/Multiplicity.h" +#include "Common/DataModel/PIDResponseTOF.h" +#include "Common/DataModel/PIDResponseTPC.h" +#include "Common/DataModel/TrackSelectionTables.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace o2; +using namespace o2::soa; +using namespace o2::constants::math; +using namespace o2::aod::rctsel; +using namespace o2::framework; +using namespace o2::framework::expressions; + +using LorentzVectorPtEtaPhiMass = ROOT::Math::PtEtaPhiMVector; + +enum PIDCutType { + SquareType = 1, + CircularType, +}; + +#define BIT_SET(var, nbit) ((var) |= (1 << (nbit))) +#define BIT_CHECK(var, nbit) ((var) & (1 << (nbit))) + +struct HResonanceCorrelationFilter { + + Service ccdb; + + RCTFlagsChecker rctChecker; + + HistogramRegistry histos{"Histos", {}, OutputObjHandlingPolicy::AnalysisObject}; + + // master analysis switches + Configurable doPPAnalysis{"doPPAnalysis", true, "if in pp, set to true"}; + + // Operational + Configurable zorroMask{"zorroMask", "", "zorro trigger class to select on (empty: none)"}; + + Configurable cfgTPCNsigmaKaon{"cfgTPCNsigmaKaon", 3.0f, "TPC Kaon PID"}; + Configurable cfgTOFNsigmaKaon{"cfgTOFNsigmaKaon", 3.0f, "TOF Kaon PID"}; + + Configurable cfgMinMass{"cfgMinMass", 1.005f, "Minimum KK mass"}; + Configurable cfgMaxMass{"cfgMaxMass", 1.035f, "Maximum KK mass"}; + + Configurable cfgMinMassKstar{"cfgMinMassKstar", 0.796f, "Minimum K#pi mass"}; + Configurable cfgMaxMassKstar{"cfgMaxMassKstar", 0.996f, "Maximum K#pi mass"}; + + Configurable cfgRapidity{"cfgRapidity", 0.5f, "Rapidity cut"}; + + // used for event selections in Pb-Pb + Configurable cfgCutOccupancyHigh{"cfgCutOccupancyHigh", 3000, "High cut on TPC occupancy"}; + Configurable cfgCutOccupancyLow{"cfgCutOccupancyLow", 0, "Low cut on TPC occupancy"}; + + struct : ConfigurableGroup { + std::string prefix = "eventSelections"; + // event filtering + Configurable zVertexCut{"zVertexCut", 10, "Cut on PV position"}; + Configurable selectINELgtZERO{"selectINELgtZERO", true, "select INEL>0 events"}; + Configurable requireAllGoodITSLayers{"requireAllGoodITSLayers", false, " require that in the event all ITS are good"}; + Configurable requireGoodTriggerTVX{"requireGoodTriggerTVX", false, " require acceptable FT0C-FT0A time difference"}; + Configurable requireGoodZvtxFT0vsPV{"requireGoodZvtxFT0vsPV", false, " require small difference between z-vertex from PV and from FT0"}; + Configurable minCentPercent{"minCentPercent", 0, "minimum centrality percentage"}; + Configurable maxCentPercent{"maxCentPercent", 100, "maximum centrality percentage"}; + } eventSelections; + + struct : ConfigurableGroup { + Configurable cfgEvtZvtx{"cfgEvtZvtx", 10.0f, "Evt sel: Max. z-Vertex (cm)"}; + Configurable cfgEvtTriggerTVXSel{"cfgEvtTriggerTVXSel", true, "Evt sel: triggerTVX selection (MB)"}; + Configurable cfgEvtNoTFBorderCut{"cfgEvtNoTFBorderCut", true, "Evt sel: apply TF border cut"}; + Configurable cfgEvtNoITSROFrameBorderCut{"cfgEvtNoITSROFrameBorderCut", false, "Evt sel: apply NoITSRO border cut"}; + Configurable cfgEvtIsRCTFlagpassed{"cfgEvtIsRCTFlagpassed", false, "Evt sel: apply RCT flag selection"}; + Configurable cfgEvtRCTFlagCheckerLabel{"cfgEvtRCTFlagCheckerLabel", "CBT_hadronPID", "Evt sel: RCT flag checker label"}; + Configurable cfgEvtRCTFlagCheckerZDCCheck{"cfgEvtRCTFlagCheckerZDCCheck", false, "Evt sel: RCT flag checker ZDC check"}; + Configurable cfgEvtRCTFlagCheckerLimitAcceptAsBad{"cfgEvtRCTFlagCheckerLimitAcceptAsBad", true, "Evt sel: RCT flag checker treat Limited Acceptance As Bad"}; + Configurable cfgEvtSel8{"cfgEvtSel8", false, "Evt Sel 8 check for offline selection"}; + Configurable cfgEvtIsINELgt0{"cfgEvtIsINELgt0", false, "Evt sel: apply INEL>0 selection"}; + } configEvents; + + struct : ConfigurableGroup { + // Pre-selection Track cuts + Configurable trackSelection{"trackSelection", 0, "Track selection: 0 -> No Cut, 1 -> kGlobalTrack, 2 -> kGlobalTrackWoPtEta, 3 -> kGlobalTrackWoDCA, 4 -> kQualityTracks, 5 -> kInAcceptanceTracks"}; + Configurable cMinPtcut{"cMinPtcut", 0.15f, "Minimal pT for tracks"}; + Configurable cMinTPCNClsFound{"cMinTPCNClsFound", 120, "minimum TPCNClsFound value for good track"}; + Configurable cfgCutEta{"cfgCutEta", 0.8f, "Eta range for tracks"}; + Configurable cfgCutRapidity{"cfgCutRapidity", 0.5f, "rapidity range for particles"}; + Configurable cfgMinCrossedRows{"cfgMinCrossedRows", 70, "min crossed rows for good track"}; + + // DCA Selections + // DCAr to PV + Configurable cMaxDCArToPVcut{"cMaxDCArToPVcut", 0.1f, "Track DCAr cut to PV Maximum"}; + // DCAz to PV + Configurable cMaxDCAzToPVcut{"cMaxDCAzToPVcut", 0.1f, "Track DCAz cut to PV Maximum"}; + + // Track selections + Configurable cfgPrimaryTrack{"cfgPrimaryTrack", true, "Primary track selection"}; // kGoldenChi2 | kDCAxy | kDCAz + Configurable cfgGlobalWoDCATrack{"cfgGlobalWoDCATrack", true, "Global track selection without DCA"}; // kQualityTracks (kTrackType | kTPCNCls | kTPCCrossedRows | kTPCCrossedRowsOverNCls | kTPCChi2NDF | kTPCRefit | kITSNCls | kITSChi2NDF | kITSRefit | kITSHits) | kInAcceptanceTracks (kPtRange | kEtaRange) + Configurable cfgGlobalTrack{"cfgGlobalTrack", false, "Global track selection"}; // kGoldenChi2 | kDCAxy | kDCAz + Configurable cfgPVContributor{"cfgPVContributor", false, "PV contributor track selection"}; // PV Contriuibutor + Configurable cfgHasTOF{"cfgHasTOF", false, "Require TOF"}; + Configurable cfgUseTPCRefit{"cfgUseTPCRefit", false, "Require TPC Refit"}; + Configurable cfgUseITSRefit{"cfgUseITSRefit", false, "Require ITS Refit"}; + Configurable cTPCNClsFound{"cTPCNClsFound", false, "Switch to turn on/off TPCNClsFound cut"}; + Configurable cDCAr7SigCut{"cDCAr7SigCut", false, "Track DCAr 7 Sigma cut to PV Maximum"}; + } configTracks; + + struct : ConfigurableGroup { + std::string prefix = "generalSelections"; + + // Associated particle selections in phase space + Configurable assocEtaMin{"assocEtaMin", -0.8, "triggeretamin"}; + Configurable assocEtaMax{"assocEtaMax", 0.8, "triggeretamax"}; + Configurable assocPtCutMin{"assocPtCutMin", 0.2, "assocptmin"}; + Configurable assocPtCutMax{"assocPtCutMax", 10, "assocptmax"}; + + // Trigger particle selections in phase space + Configurable triggerEtaMin{"triggerEtaMin", -0.8, "triggeretamin"}; + Configurable triggerEtaMax{"triggerEtaMax", 0.8, "triggeretamax"}; + Configurable triggerPtCutMin{"triggerPtCutMin", 3, "triggerptmin"}; + Configurable triggerPtCutMax{"triggerPtCutMax", 20, "triggerptmax"}; + } generalSelections; + + struct : ConfigurableGroup { + std::string prefix = "trackSelections"; + // Track quality + Configurable minTPCNCrossedRows{"minTPCNCrossedRows", 70, "Minimum TPC crossed rows"}; + Configurable triggerRequireITS{"triggerRequireITS", true, "require ITS signal in trigger tracks"}; + Configurable assocRequireITS{"assocRequireITS", true, "require ITS signal in assoc tracks"}; + Configurable triggerMaxTPCSharedClusters{"triggerMaxTPCSharedClusters", 200, "maximum number of shared TPC clusters (inclusive)"}; + Configurable triggerRequireL0{"triggerRequireL0", false, "require ITS L0 cluster for trigger"}; + Configurable requireClusterInITS{"requireClusterInITS", false, "require cluster in ITS for phi daughter tracks"}; + Configurable minITSClustersForDaughterTracks{"minITSClustersForDaughterTracks", 1, "Minimum number of ITS clusters for phi daughter tracks"}; + + // Associated pion identification + Configurable pionMinBayesProb{"pionMinBayesProb", 0.95, "minimal Bayesian probability for pion ID"}; + Configurable assocPionNSigmaTPCFOF{"assocPionNSigmaTPCFOF", 3, "minimal n sigma in TOF and TPC for Pion ID"}; + Configurable rejectSigma{"rejectSigma", 1, "n sigma for rejecting pion candidates"}; + + // primary particle DCAxy selections + // formula: |DCAxy| < 0.004f + (0.013f / pt) + Configurable dcaXYconstant{"dcaXYconstant", 0.004, "[0] in |DCAxy| < [0]+[1]/pT"}; + Configurable dcaXYpTdep{"dcaXYpTdep", 0.013, "[1] in |DCAxy| < [0]+[1]/pT"}; + } trackSelections; + + struct : ConfigurableGroup { + /// PID Selections + Configurable pidnSigmaPreSelectionCut{"pidnSigmaPreSelectionCut", 4.0f, "pidnSigma Cut for pre-selection of tracks"}; + Configurable cByPassTOF{"cByPassTOF", false, "By pass TOF PID selection"}; // By pass TOF PID selection + Configurable cPIDcutType{"cPIDcutType", 2, "cPIDcutType = 1 for square cut, 2 for circular cut"}; // By pass TOF PID selection + Configurable ispTdepPID{"ispTdepPID", false, "enable pT dependent PID"}; + + // Kaon + Configurable> kaonTPCPIDpTintv{"kaonTPCPIDpTintv", {0.5f}, "pT intervals for Kaon TPC PID cuts"}; + Configurable> kaonTPCPIDcuts{"kaonTPCPIDcuts", {2}, "nSigma list for Kaon TPC PID cuts"}; + Configurable> kaonTOFPIDpTintv{"kaonTOFPIDpTintv", {999.0f}, "pT intervals for Kaon TOF PID cuts"}; + Configurable> kaonTOFPIDcuts{"kaonTOFPIDcuts", {2}, "nSigma list for Kaon TOF PID cuts"}; + Configurable> kaonTPCTOFCombinedpTintv{"kaonTPCTOFCombinedpTintv", {999.0f}, "pT intervals for Kaon TPC-TOF PID cuts"}; + Configurable> kaonTPCTOFCombinedPIDcuts{"kaonTPCTOFCombinedPIDcuts", {2}, "nSigma list for Kaon TPC-TOF PID cuts"}; + + // Pion (K*0 daughter) + Configurable> pionTPCPIDpTintv{"pionTPCPIDpTintv", {0.5f}, "pT intervals for Pion TPC PID cuts"}; + Configurable> pionTPCPIDcuts{"pionTPCPIDcuts", {2}, "nSigma list for Pion TPC PID cuts"}; + Configurable> pionTOFPIDpTintv{"pionTOFPIDpTintv", {999.0f}, "pT intervals for Pion TOF PID cuts"}; + Configurable> pionTOFPIDcuts{"pionTOFPIDcuts", {2}, "nSigma list for Pion TOF PID cuts"}; + Configurable> pionTPCTOFCombinedpTintv{"pionTPCTOFCombinedpTintv", {999.0f}, "pT intervals for Pion TPC-TOF PID cuts"}; + Configurable> pionTPCTOFCombinedPIDcuts{"pionTPCTOFCombinedPIDcuts", {2}, "nSigma list for Pion TPC-TOF PID cuts"}; + } configPID; + + Configurable ccdburl{"ccdburl", "http://alice-ccdb.cern.ch", "url of the ccdb repository to use"}; + Configurable parameterCCDBPath{"parameterCCDBPath", "Users/k/kcui/LHC25b4a/parameter", "Path of the mean and sigma"}; + + // must include windows for background and peak + Configurable maxMassNSigma{"maxMassNSigma", 12.0f, "max mass region to be considered for further analysis"}; + + // For extracting strangeness mass QA plots + struct : ConfigurableGroup { + ConfigurableAxis axisPtQA{"axisPtQA", {VARIABLE_WIDTH, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f, 4.0f, 4.4f, 4.8f, 5.2f, 5.6f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 17.0f, 19.0f, 21.0f, 23.0f, 25.0f, 30.0f, 35.0f, 40.0f, 50.0f}, "pt axis for QA histograms"}; + ConfigurableAxis axisPhiMass{"axisPhiMass", {200, 0.99f, 1.08f}, "M(K^{+}K^{-})"}; + ConfigurableAxis axisKstarMass{"axisKstarMass", {200, 0.75f, 1.05f}, "M(K#pi)"}; + ConfigurableAxis axisMult{"axisMult", {VARIABLE_WIDTH, 0.0f, 0.01f, 1.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 70.0f, 100.0f}, "Centrality percentile bins"}; + } axesConfigurations; + + // QA + Configurable doTrueSelectionInMass{"doTrueSelectionInMass", false, "Fill mass histograms only with true primary Particles for MC"}; + // Do declarative selections for DCAs, if possible + Filter preFilterTracks = nabs(aod::track::dcaXY) < trackSelections.dcaXYconstant + trackSelections.dcaXYpTdep * nabs(aod::track::signed1Pt); + + using FullTracks = soa::Join; + using FullTracksMC = soa::Join; + using DauTracks = soa::Join; + using DauTracksMC = soa::Join; + // using IDTracks= soa::Join; // prepared for Bayesian PID + using IDTracks = soa::Join; + using IDTracksMC = soa::Join; + + Produces triggerTrack; + Produces triggerTrackExtra; + Produces assocPhis; + Produces assocKstars; + Produces assocHadrons; + Produces assocPID; + + using EventCandidates = soa::Join; + using TrackCandidates = soa::Filtered>; + + // for MC reco + using MCEventCandidates = soa::Join; + using MCTrackCandidates = soa::Filtered>; + + Zorro zorro; + OutputObj zorroSummary{"zorroSummary"}; + int mRunNumber; + + struct TriggCandidate { + float pt; + int collisionId; + int trackId; + bool isPhysicalPrimary; + float origPt; + }; + TriggCandidate thisTrigg; + + std::vector triggerCandidates; + + void init(InitContext const&) + { + rctChecker.init(configEvents.cfgEvtRCTFlagCheckerLabel, configEvents.cfgEvtRCTFlagCheckerZDCCheck, configEvents.cfgEvtRCTFlagCheckerLimitAcceptAsBad); + + histos.add("CollCutCounts", "No. of event after cuts", kTH1I, {{10, 0, 10}}); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(1, "All Events"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(2, "|Vz| < cut"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(3, "kIsTriggerTVX"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(4, "kNoTimeFrameBorder"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(5, "kNoITSROFrameBorder"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(6, "rctChecker"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(7, "sel8"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(8, "IsINELgt0"); + histos.get(HIST("CollCutCounts"))->GetXaxis()->SetBinLabel(9, "All Passed Events"); + + zorroSummary.setObject(zorro.getZorroSummary()); + mRunNumber = -1; + } + + void initCCDB(aod::BCsWithTimestamps::iterator const& bc) + { + if (mRunNumber == bc.runNumber()) { + return; + } + + zorro.initCCDB(ccdb.service, bc.runNumber(), bc.timestamp(), zorroMask.value); + zorro.populateHistRegistry(histos, bc.runNumber()); + + mRunNumber = bc.runNumber(); + } + + template + bool isSelected(const Coll& collision, bool fillHist = true) + { + auto applyCut = [&](bool enabled, bool condition, int bin) { + if (!enabled) { + return true; + } + if (!condition) { + return false; + } + if (fillHist) { + histos.fill(HIST("CollCutCounts"), bin); + } + return true; + }; + + if (fillHist) { + histos.fill(HIST("CollCutCounts"), 0); + } + + if (!applyCut(true, std::abs(collision.posZ()) <= configEvents.cfgEvtZvtx, 1)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtTriggerTVXSel, + collision.selection_bit(aod::evsel::kIsTriggerTVX), 2)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtNoTFBorderCut, + collision.selection_bit(aod::evsel::kNoTimeFrameBorder), 3)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtNoITSROFrameBorderCut, + collision.selection_bit(aod::evsel::kNoITSROFrameBorder), 4)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtIsRCTFlagpassed, rctChecker(collision), 5)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtSel8, collision.sel8(), 6)) { + return false; + } + + if (!applyCut(configEvents.cfgEvtIsINELgt0, collision.isInelGt0(), 7)) { + return false; + } + + if (fillHist) { + histos.fill(HIST("CollCutCounts"), 8); + } + + return true; + } + + // this function allows for all event selections to be done in a modular way + template + bool isCollisionSelected(TCollision const& collision) + { + // ________________________________________________ + // Perform basic event selection + if (!collision.sel8()) { + return false; + } + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return false; + } + if (collision.centFT0M() > eventSelections.maxCentPercent || collision.centFT0M() < eventSelections.minCentPercent) { + return false; + } + if (!collision.isInelGt0() && eventSelections.selectINELgtZERO) { + return false; + } + if (!collision.selection_bit(aod::evsel::kIsGoodITSLayersAll) && eventSelections.requireAllGoodITSLayers) { + return false; + } + if (zorroMask.value != "") { + auto bc = collision.template bc_as(); + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.template bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return false; + } + } + return true; + } + + // more event selections in Pb-Pb + template + bool isCollisionSelectedPbPb(TCollision collision) + { + if (!collision.selection_bit(aod::evsel::kIsTriggerTVX) && eventSelections.requireGoodTriggerTVX) /* FT0 vertex (acceptable FT0C-FT0A time difference) collisions */ + return false; + if (!collision.selection_bit(o2::aod::evsel::kIsGoodITSLayersAll) && eventSelections.requireAllGoodITSLayers) // cut time intervals with dead ITS staves + return false; + if (!collision.selection_bit(o2::aod::evsel::kIsGoodZvtxFT0vsPV) && eventSelections.requireGoodZvtxFT0vsPV) // removes collisions with large differences between z of PV by tracks and z of PV from FT0 A-C time difference + return false; + auto occupancy = collision.trackOccupancyInTimeRange(); + if (occupancy < cfgCutOccupancyLow || occupancy > cfgCutOccupancyHigh) /* Below min occupancy and Above max occupancy*/ + return false; + if (!collision.selection_bit(o2::aod::evsel::kNoTimeFrameBorder)) // reject collisions close to Time Frame borders + return false; + if (!collision.selection_bit(o2::aod::evsel::kNoITSROFrameBorder)) // reject events affected by the ITS ROF border + return false; + if (!collision.selection_bit(o2::aod::evsel::kNoSameBunchPileup)) // rejects collisions which are associated with the same "found-by-T0" bunch crossing + return false; + return true; + } + + template + bool trackCut(const TrackType& track) + { + // basic track cuts + if (configTracks.cDCAr7SigCut && std::abs(track.dcaXY()) > (0.004f + 0.013f / (track.pt()))) // 7 - Sigma cut + { + return false; + } + if (configTracks.cTPCNClsFound && (track.tpcNClsFound() < configTracks.cMinTPCNClsFound)) { + return false; + } + if (track.tpcNClsCrossedRows() < configTracks.cfgMinCrossedRows) { + return false; + } + if (configTracks.cfgHasTOF && !track.hasTOF()) { + return false; + } + if (configTracks.cfgPrimaryTrack && !track.isPrimaryTrack()) { + return false; + } + if (configTracks.cfgGlobalWoDCATrack && !track.isGlobalTrackWoDCA()) { + return false; + } + if (configTracks.cfgPVContributor && !track.isPVContributor()) { + return false; + } + if (configTracks.cfgGlobalTrack && !track.isGlobalTrack()) { + return false; + } + + return true; + } + + template + bool ptDependentPidKaon(const T& candidate) + { + auto vKaonTPCPIDpTintv = configPID.kaonTPCPIDpTintv.value; + vKaonTPCPIDpTintv.insert(vKaonTPCPIDpTintv.begin(), configTracks.cMinPtcut); + auto vKaonTPCPIDcuts = configPID.kaonTPCPIDcuts.value; + auto vKaonTOFPIDpTintv = configPID.kaonTOFPIDpTintv.value; + auto vKaonTPCTOFCombinedpTintv = configPID.kaonTPCTOFCombinedpTintv.value; + auto vKaonTPCTOFCombinedPIDcuts = configPID.kaonTPCTOFCombinedPIDcuts.value; + auto vKaonTOFPIDcuts = configPID.kaonTOFPIDcuts.value; + + float pt = candidate.pt(); + float ptSwitchToTOF = vKaonTPCPIDpTintv.back(); + float tpcNsigmaKa = candidate.tpcNSigmaKa(); + float tofNsigmaKa = candidate.tofNSigmaKa(); + + bool tpcPIDPassed = false; + + // TPC PID interval-based check + for (size_t i = 0; i < vKaonTPCPIDpTintv.size() - 1; ++i) { + if (pt > vKaonTPCPIDpTintv[i] && pt < vKaonTPCPIDpTintv[i + 1]) { + if (std::abs(tpcNsigmaKa) < vKaonTPCPIDcuts[i]) { + tpcPIDPassed = true; + break; + } + } + } + + // TOF bypass option + if (configPID.cByPassTOF) { + return std::abs(tpcNsigmaKa) < vKaonTPCPIDcuts.back(); + } + + // Case 1: No TOF and pt ≤ ptSwitch → use TPC-only + if (!candidate.hasTOF() && pt <= ptSwitchToTOF) { + return tpcPIDPassed; + } + + // Case 2: No TOF but pt > ptSwitch → reject + if (!candidate.hasTOF() && pt > ptSwitchToTOF) { + return false; + } + + // Case 3: TOF is available → apply TPC+TOF PID logic + if (candidate.hasTOF()) { + if (configPID.cPIDcutType == SquareType) { + // Rectangular cut + for (size_t i = 0; i < vKaonTOFPIDpTintv.size(); ++i) { + if (pt < vKaonTOFPIDpTintv[i]) { + if (std::abs(tofNsigmaKa) < vKaonTOFPIDcuts[i] && + std::abs(tpcNsigmaKa) < vKaonTPCPIDcuts.back()) { + return true; + } + } + } + } else if (configPID.cPIDcutType == CircularType) { + // Circular cut + for (size_t i = 0; i < vKaonTPCTOFCombinedpTintv.size(); ++i) { + if (pt < vKaonTPCTOFCombinedpTintv[i]) { + float combinedSigma2 = tpcNsigmaKa * tpcNsigmaKa + + tofNsigmaKa * tofNsigmaKa; + if (combinedSigma2 < vKaonTPCTOFCombinedPIDcuts[i] * vKaonTPCTOFCombinedPIDcuts[i]) { + return true; + } + } + } + } + } + + return false; + } + + template + bool selectionPID(const T& candidate) + { + auto vKaonTPCPIDcuts = configPID.kaonTPCPIDcuts.value; + auto vKaonTPCTOFCombinedPIDcuts = configPID.kaonTPCTOFCombinedPIDcuts.value; + + if (!configPID.cByPassTOF && candidate.hasTOF() && (candidate.tofNSigmaKa() * candidate.tofNSigmaKa() + candidate.tpcNSigmaKa() * candidate.tpcNSigmaKa()) < (vKaonTPCTOFCombinedPIDcuts[0] * vKaonTPCTOFCombinedPIDcuts[0])) { + return true; + } + if (!configPID.cByPassTOF && !candidate.hasTOF() && std::abs(candidate.tpcNSigmaKa()) < vKaonTPCPIDcuts[0]) { + return true; + } + if (configPID.cByPassTOF && std::abs(candidate.tpcNSigmaKa()) < vKaonTPCPIDcuts[0]) { + return true; + } + return false; + } + + template + bool ptDependentPidPion(const T& candidate) + { + auto vPionTPCPIDpTintv = configPID.pionTPCPIDpTintv.value; + vPionTPCPIDpTintv.insert(vPionTPCPIDpTintv.begin(), configTracks.cMinPtcut); + auto vPionTPCPIDcuts = configPID.pionTPCPIDcuts.value; + auto vPionTOFPIDpTintv = configPID.pionTOFPIDpTintv.value; + auto vPionTPCTOFCombinedpTintv = configPID.pionTPCTOFCombinedpTintv.value; + auto vPionTPCTOFCombinedPIDcuts = configPID.pionTPCTOFCombinedPIDcuts.value; + auto vPionTOFPIDcuts = configPID.pionTOFPIDcuts.value; + + float pt = candidate.pt(); + float ptSwitchToTOF = vPionTPCPIDpTintv.back(); + float tpcNsigmaPi = candidate.tpcNSigmaPi(); + float tofNsigmaPi = candidate.tofNSigmaPi(); + + bool tpcPIDPassed = false; + + // TPC PID interval-based check + for (size_t i = 0; i < vPionTPCPIDpTintv.size() - 1; ++i) { + if (pt > vPionTPCPIDpTintv[i] && pt < vPionTPCPIDpTintv[i + 1]) { + if (std::abs(tpcNsigmaPi) < vPionTPCPIDcuts[i]) { + tpcPIDPassed = true; + break; + } + } + } + + // TOF bypass option + if (configPID.cByPassTOF) { + return std::abs(tpcNsigmaPi) < vPionTPCPIDcuts.back(); + } + + // Case 1: No TOF and pt ≤ ptSwitch → use TPC-only + if (!candidate.hasTOF() && pt <= ptSwitchToTOF) { + return tpcPIDPassed; + } + + // Case 2: No TOF but pt > ptSwitch → reject + if (!candidate.hasTOF() && pt > ptSwitchToTOF) { + return false; + } + + // Case 3: TOF is available → apply TPC+TOF PID logic + if (candidate.hasTOF()) { + if (configPID.cPIDcutType == SquareType) { + // Rectangular cut + for (size_t i = 0; i < vPionTOFPIDpTintv.size(); ++i) { + if (pt < vPionTOFPIDpTintv[i]) { + if (std::abs(tofNsigmaPi) < vPionTOFPIDcuts[i] && + std::abs(tpcNsigmaPi) < vPionTPCPIDcuts.back()) { + return true; + } + } + } + } else if (configPID.cPIDcutType == CircularType) { + // Circular cut + for (size_t i = 0; i < vPionTPCTOFCombinedpTintv.size(); ++i) { + if (pt < vPionTPCTOFCombinedpTintv[i]) { + float combinedSigma2 = tpcNsigmaPi * tpcNsigmaPi + + tofNsigmaPi * tofNsigmaPi; + if (combinedSigma2 < vPionTPCTOFCombinedPIDcuts[i] * vPionTPCTOFCombinedPIDcuts[i]) { + return true; + } + } + } + } + } + + return false; + } + + template + bool selectionPIDPion(const T& candidate) + { + auto vPionTPCPIDcuts = configPID.pionTPCPIDcuts.value; + auto vPionTPCTOFCombinedPIDcuts = configPID.pionTPCTOFCombinedPIDcuts.value; + + if (!configPID.cByPassTOF && candidate.hasTOF() && (candidate.tofNSigmaPi() * candidate.tofNSigmaPi() + candidate.tpcNSigmaPi() * candidate.tpcNSigmaPi()) < (vPionTPCTOFCombinedPIDcuts[0] * vPionTPCTOFCombinedPIDcuts[0])) { + return true; + } + if (!configPID.cByPassTOF && !candidate.hasTOF() && std::abs(candidate.tpcNSigmaPi()) < vPionTPCPIDcuts[0]) { + return true; + } + if (configPID.cByPassTOF && std::abs(candidate.tpcNSigmaPi()) < vPionTPCPIDcuts[0]) { + return true; + } + return false; + } + + // reco-level trigger quality checks (N.B.: DCA is filtered, not selected) + template + bool isValidTrigger(TTrack track) + { + if (track.eta() > generalSelections.triggerEtaMax || track.eta() < generalSelections.triggerEtaMin) { + return false; + } + // if (track.sign()= 1 ) {continue;} + if (track.pt() > generalSelections.triggerPtCutMax || track.pt() < generalSelections.triggerPtCutMin) { + return false; + } + if (track.tpcNClsCrossedRows() < trackSelections.minTPCNCrossedRows) { + return false; // crossed rows + } + if (!track.hasITS() && trackSelections.triggerRequireITS) { + return false; // skip, doesn't have ITS signal (skips lots of TPC-only!) + } + if (track.tpcNClsShared() > trackSelections.triggerMaxTPCSharedClusters) { + return false; // skip, has shared clusters + } + if (!(BIT_CHECK(track.itsClusterMap(), 0)) && trackSelections.triggerRequireL0) { + return false; // skip, doesn't have cluster in ITS L0 + } + return true; + } + + template + bool isValidAssocTrack(TTrack assoc) + { + if (assoc.eta() > generalSelections.assocEtaMax || assoc.eta() < generalSelections.assocEtaMin) { + return false; + } + if (assoc.pt() > generalSelections.assocPtCutMax || assoc.pt() < generalSelections.assocPtCutMin) { + return false; + } + if (assoc.tpcNClsCrossedRows() < trackSelections.minTPCNCrossedRows) { + return false; // crossed rows + } + if (!assoc.hasITS() && trackSelections.assocRequireITS) { + return false; // skip, doesn't have ITS signal (skips lots of TPC-only!) + } + + // do this only if information is available + float nSigmaTPCTOF[8] = {-10, -10, -10, -10, -10, -10, -10, -10}; + if constexpr (requires { assoc.tofSignal(); } && !requires { assoc.mcParticle(); }) { + if (assoc.tofSignal() > 0) { + if (std::sqrt(assoc.tofNSigmaPi() * assoc.tofNSigmaPi() + assoc.tpcNSigmaPi() * assoc.tpcNSigmaPi()) > trackSelections.assocPionNSigmaTPCFOF) + return false; + if (assoc.tofNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tofNSigmaKa() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) + return false; + nSigmaTPCTOF[4] = assoc.tofNSigmaPi(); + nSigmaTPCTOF[5] = assoc.tofNSigmaKa(); + nSigmaTPCTOF[6] = assoc.tofNSigmaPr(); + nSigmaTPCTOF[7] = assoc.tofNSigmaEl(); + } else { + if (assoc.tpcNSigmaPi() > trackSelections.assocPionNSigmaTPCFOF) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) + return false; + } + nSigmaTPCTOF[0] = assoc.tpcNSigmaPi(); + nSigmaTPCTOF[1] = assoc.tpcNSigmaKa(); + nSigmaTPCTOF[2] = assoc.tpcNSigmaPr(); + nSigmaTPCTOF[3] = assoc.tpcNSigmaEl(); + } + + bool physicalPrimary = false; + float origPt = -1; + float code = -9999; + if constexpr (requires { assoc.mcParticle(); }) { + if (assoc.has_mcParticle()) { + auto mcParticle = assoc.mcParticle(); + physicalPrimary = mcParticle.isPhysicalPrimary(); + origPt = mcParticle.pt(); + code = mcParticle.pdgCode(); + } + } + + assocHadrons( + assoc.collisionId(), + physicalPrimary, + assoc.globalIndex(), + origPt, + code); + assocPID( + nSigmaTPCTOF[0], + nSigmaTPCTOF[1], + nSigmaTPCTOF[2], + nSigmaTPCTOF[3], + nSigmaTPCTOF[4], + nSigmaTPCTOF[5], + nSigmaTPCTOF[6], + nSigmaTPCTOF[7]); + return true; + } + + // for real data processing + void processTriggers(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::BCsWithTimestamps const&) + { + triggerCandidates.clear(); + if (((doPPAnalysis && !isCollisionSelected(collision))) || (!doPPAnalysis && !isCollisionSelectedPbPb(collision))) { + return; + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + double leadingPt = -1.; + int leadingId = -1; + for (auto const& track : tracks) { + if (!isValidTrigger(track)) + continue; + thisTrigg.pt = track.pt(); + thisTrigg.trackId = track.globalIndex(); + thisTrigg.collisionId = track.collisionId(); + thisTrigg.isPhysicalPrimary = false; // if you decide to check real data for primaries, you'll have a hard time + thisTrigg.origPt = 0; + triggerCandidates.push_back(thisTrigg); + if (track.pt() > leadingPt) { + leadingPt = track.pt(); + leadingId = track.globalIndex(); + } + } + for (auto const& TriggCandidate : triggerCandidates) { + bool isLeading = (leadingId == TriggCandidate.trackId); + triggerTrack( + TriggCandidate.collisionId, + TriggCandidate.isPhysicalPrimary, + TriggCandidate.trackId, + TriggCandidate.origPt, + isLeading); + triggerTrackExtra(1); + } + } + + // for MC processing + void processTriggersMC(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::McParticles const&, aod::BCsWithTimestamps const&) + { + triggerCandidates.clear(); + if (((doPPAnalysis && !isCollisionSelected(collision))) || (!doPPAnalysis && !isCollisionSelectedPbPb(collision))) { + return; + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + double leadingPt = -1.; + int leadingId = -1; + for (auto const& track : tracks) { + if (!isValidTrigger(track)) + continue; + thisTrigg.pt = track.pt(); + thisTrigg.trackId = track.globalIndex(); + thisTrigg.collisionId = track.collisionId(); + if (track.has_mcParticle()) { + auto mcParticle = track.mcParticle(); + thisTrigg.isPhysicalPrimary = mcParticle.isPhysicalPrimary(); + thisTrigg.origPt = mcParticle.pt(); + } + triggerCandidates.push_back(thisTrigg); + if (track.pt() > leadingPt) { + leadingPt = track.pt(); + leadingId = track.globalIndex(); + } + } + + for (auto const& TriggCandidate : triggerCandidates) { + bool isLeading = (leadingId == TriggCandidate.trackId); + triggerTrack( + TriggCandidate.collisionId, + TriggCandidate.isPhysicalPrimary, + TriggCandidate.trackId, + TriggCandidate.origPt, + isLeading); + triggerTrackExtra(1); + } + } + + void processAssocPions(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) + continue; + } + } + + void processAssocPionsMC(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::McParticles const&, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) + continue; + } + } + + void processAssocHadrons(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) + continue; + } + } + void processAssocHadronsMC(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::McParticles const&, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) + continue; + } + } + + void processPhis(EventCandidates::iterator const& collision, + TrackCandidates const& tracks) + { + if (!isSelected(collision)) { + return; + } + + LorentzVectorPtEtaPhiMass kplus; + LorentzVectorPtEtaPhiMass kminus; + LorentzVectorPtEtaPhiMass phi; + + for (auto const& [trk1, trk2] : + combinations(CombinationsFullIndexPolicy(tracks, tracks))) { + + if (trk2.index() <= trk1.index()) { + continue; + } + + // same collision + if (trk1.collisionId() != collision.globalIndex() || + trk2.collisionId() != collision.globalIndex()) { + continue; + } + + // quality cuts + if (!trackCut(trk1) || !trackCut(trk2)) { + continue; + } + + // unlike-sign only + if (trk1.sign() * trk2.sign() > 0) { + continue; + } + + // kaon PID + if (configPID.ispTdepPID) { + if (!ptDependentPidKaon(trk1) || + !ptDependentPidKaon(trk2)) { + continue; + } + } else { + if (!selectionPID(trk1) || + !selectionPID(trk2)) { + continue; + } + } + + // assign K+ and K- + if (trk1.sign() > 0) { + kplus = LorentzVectorPtEtaPhiMass(trk1.pt(), trk1.eta(), trk1.phi(), + o2::constants::physics::MassKPlus); + kminus = LorentzVectorPtEtaPhiMass(trk2.pt(), trk2.eta(), trk2.phi(), + o2::constants::physics::MassKPlus); + } else { + kplus = LorentzVectorPtEtaPhiMass(trk2.pt(), trk2.eta(), trk2.phi(), + o2::constants::physics::MassKPlus); + kminus = LorentzVectorPtEtaPhiMass(trk1.pt(), trk1.eta(), trk1.phi(), + o2::constants::physics::MassKPlus); + } + + phi = kplus + kminus; + + float invMass = phi.M(); + + // rapidity cut + if (std::abs(phi.Rapidity()) > configTracks.cfgCutRapidity) { + continue; + } + + // optional mass window + if (invMass < cfgMinMass || invMass > cfgMaxMass) { + continue; + } + + assocPhis( + collision.globalIndex(), + false, + false, + phi.Pt(), + phi.Eta(), + phi.Phi(), + invMass, + trk1.globalIndex(), + trk2.globalIndex()); + } + } + + void processPhisMC( + MCEventCandidates::iterator const& collision, + aod::McCollisions const&, + MCTrackCandidates const& tracks, + aod::McParticles const&) + { + if (!collision.has_mcCollision()) { + return; + } + + if (!isSelected(collision)) { + return; + } + + for (auto const& [trk1, trk2] : + combinations(CombinationsFullIndexPolicy(tracks, tracks))) { + + // ----------------------------- + // Same cuts as phi1020analysis + // ----------------------------- + + if (trk2.index() <= trk1.index()) { + continue; + } + + if (trk1.collisionId() != collision.globalIndex() || + trk2.collisionId() != collision.globalIndex()) { + continue; + } + + if (!trackCut(trk1) || !trackCut(trk2)) { + continue; + } + + if (trk1.sign() * trk2.sign() > 0) { + continue; + } + + if (!selectionPID(trk1) || !selectionPID(trk2)) { + continue; + } + + LorentzVectorPtEtaPhiMass kplus; + LorentzVectorPtEtaPhiMass kminus; + LorentzVectorPtEtaPhiMass phi; + + // assign K+ and K- + if (trk1.sign() > 0) { + kplus = LorentzVectorPtEtaPhiMass(trk1.pt(), trk1.eta(), trk1.phi(), + o2::constants::physics::MassKPlus); + kminus = LorentzVectorPtEtaPhiMass(trk2.pt(), trk2.eta(), trk2.phi(), + o2::constants::physics::MassKPlus); + } else { + kplus = LorentzVectorPtEtaPhiMass(trk2.pt(), trk2.eta(), trk2.phi(), + o2::constants::physics::MassKPlus); + kminus = LorentzVectorPtEtaPhiMass(trk1.pt(), trk1.eta(), trk1.phi(), + o2::constants::physics::MassKPlus); + } + + phi = kplus + kminus; + + float invMass = phi.M(); + + // rapidity cut + if (std::abs(phi.Rapidity()) > configTracks.cfgCutRapidity) { + continue; + } + + // optional mass window + if (invMass < cfgMinMass || invMass > cfgMaxMass) { + continue; + } + + bool mcTruePhi = false; + bool mcPhysicalPrimary = false; + + // ----------------------------- + // MC matching + // ----------------------------- + + if (trk1.has_mcParticle() && trk2.has_mcParticle()) { + + auto mc1 = trk1.mcParticle(); + auto mc2 = trk2.mcParticle(); + + if (mc1.has_mothers() && mc2.has_mothers()) { + + for (auto const& mother1 : mc1.mothers_as()) { + for (auto const& mother2 : mc2.mothers_as()) { + + if (mother1.globalIndex() != mother2.globalIndex()) { + continue; + } + + if (std::abs(mother1.pdgCode()) != 333) { + continue; + } + + mcTruePhi = true; + mcPhysicalPrimary = mother1.isPhysicalPrimary(); + } + } + } + } + + assocPhis( + collision.globalIndex(), + mcTruePhi, + mcPhysicalPrimary, + phi.Pt(), + phi.Eta(), + phi.Phi(), + invMass, + trk1.globalIndex(), + trk2.globalIndex()); + } + } + + // K*0 -> K+ pi- (and c.c.) reconstruction. Unlike phi (K+K-, symmetric daughter + // mass), the two daughter tracks are different species, so both mass hypotheses + // (trk1=kaon,trk2=pion) and (trk1=pion,trk2=kaon) are tried independently; either, + // both, or neither may pass PID for a given unlike-sign pair. + void processKstars(EventCandidates::iterator const& collision, + TrackCandidates const& tracks) + { + if (!isSelected(collision)) { + return; + } + + LorentzVectorPtEtaPhiMass kaon; + LorentzVectorPtEtaPhiMass pion; + LorentzVectorPtEtaPhiMass kstar; + + for (auto const& [trk1, trk2] : + combinations(CombinationsFullIndexPolicy(tracks, tracks))) { + + if (trk2.index() <= trk1.index()) { + continue; + } + + // same collision + if (trk1.collisionId() != collision.globalIndex() || + trk2.collisionId() != collision.globalIndex()) { + continue; + } + + // quality cuts + if (!trackCut(trk1) || !trackCut(trk2)) { + continue; + } + + // unlike-sign only + if (trk1.sign() * trk2.sign() > 0) { + continue; + } + + for (int hypothesis = 0; hypothesis < 2; hypothesis++) { + auto const& kaonTrack = (hypothesis == 0) ? trk1 : trk2; + auto const& pionTrack = (hypothesis == 0) ? trk2 : trk1; + + // kaon + pion PID + bool passKaonPID = configPID.ispTdepPID ? ptDependentPidKaon(kaonTrack) : selectionPID(kaonTrack); + bool passPionPID = configPID.ispTdepPID ? ptDependentPidPion(pionTrack) : selectionPIDPion(pionTrack); + if (!passKaonPID || !passPionPID) { + continue; + } + + kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), + o2::constants::physics::MassKPlus); + pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), + o2::constants::physics::MassPiPlus); + + kstar = kaon + pion; + + float invMass = kstar.M(); + + // rapidity cut + if (std::abs(kstar.Rapidity()) > configTracks.cfgCutRapidity) { + continue; + } + + // mass window + if (invMass < cfgMinMassKstar || invMass > cfgMaxMassKstar) { + continue; + } + + auto const& posTrack = (kaonTrack.sign() > 0) ? kaonTrack : pionTrack; + auto const& negTrack = (kaonTrack.sign() > 0) ? pionTrack : kaonTrack; + + assocKstars( + collision.globalIndex(), + false, + false, + kstar.Pt(), + kstar.Eta(), + kstar.Phi(), + invMass, + posTrack.globalIndex(), + negTrack.globalIndex()); + } + } + } + + void processKstarsMC( + MCEventCandidates::iterator const& collision, + aod::McCollisions const&, + MCTrackCandidates const& tracks, + aod::McParticles const&) + { + if (!collision.has_mcCollision()) { + return; + } + + if (!isSelected(collision)) { + return; + } + + for (auto const& [trk1, trk2] : + combinations(CombinationsFullIndexPolicy(tracks, tracks))) { + + // ----------------------------- + // Same cuts as processKstars + // ----------------------------- + + if (trk2.index() <= trk1.index()) { + continue; + } + + if (trk1.collisionId() != collision.globalIndex() || + trk2.collisionId() != collision.globalIndex()) { + continue; + } + + if (!trackCut(trk1) || !trackCut(trk2)) { + continue; + } + + if (trk1.sign() * trk2.sign() > 0) { + continue; + } + + for (int hypothesis = 0; hypothesis < 2; hypothesis++) { + auto const& kaonTrack = (hypothesis == 0) ? trk1 : trk2; + auto const& pionTrack = (hypothesis == 0) ? trk2 : trk1; + + if (!selectionPID(kaonTrack) || !selectionPIDPion(pionTrack)) { + continue; + } + + LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), + o2::constants::physics::MassKPlus); + LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), + o2::constants::physics::MassPiPlus); + LorentzVectorPtEtaPhiMass kstar = kaon + pion; + + float invMass = kstar.M(); + + // rapidity cut + if (std::abs(kstar.Rapidity()) > configTracks.cfgCutRapidity) { + continue; + } + + // mass window + if (invMass < cfgMinMassKstar || invMass > cfgMaxMassKstar) { + continue; + } + + bool mcTrueKstar = false; + bool mcPhysicalPrimary = false; + + // ----------------------------- + // MC matching: common mother with |pdgCode| == 313 (K*0/anti-K*0) + // ----------------------------- + + if (kaonTrack.has_mcParticle() && pionTrack.has_mcParticle()) { + + auto mcKaon = kaonTrack.mcParticle(); + auto mcPion = pionTrack.mcParticle(); + + if (mcKaon.has_mothers() && mcPion.has_mothers()) { + + for (auto const& motherKaon : mcKaon.mothers_as()) { + for (auto const& motherPion : mcPion.mothers_as()) { + + if (motherKaon.globalIndex() != motherPion.globalIndex()) { + continue; + } + + if (std::abs(motherKaon.pdgCode()) != 313) { + continue; + } + + mcTrueKstar = true; + mcPhysicalPrimary = motherKaon.isPhysicalPrimary(); + } + } + } + } + + auto const& posTrack = (kaonTrack.sign() > 0) ? kaonTrack : pionTrack; + auto const& negTrack = (kaonTrack.sign() > 0) ? pionTrack : kaonTrack; + + assocKstars( + collision.globalIndex(), + mcTrueKstar, + mcPhysicalPrimary, + kstar.Pt(), + kstar.Eta(), + kstar.Phi(), + invMass, + posTrack.globalIndex(), + negTrack.globalIndex()); + } + } + } + + PROCESS_SWITCH(HResonanceCorrelationFilter, processTriggers, "Produce trigger tables", true); + PROCESS_SWITCH(HResonanceCorrelationFilter, processTriggersMC, "Produce trigger tables for MC", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocPions, "Produce associated Pion tables", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocPionsMC, "Produce associated Pion tables for MC", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocHadrons, "Produce associated Hadron tables", true); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocHadronsMC, "Produce associated Hadron tables for MC", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processPhis, "Produce associated phi tables", true); + PROCESS_SWITCH(HResonanceCorrelationFilter, processPhisMC, "Produce associated phi tables for MC", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processKstars, "Produce associated K*0 tables", true); + PROCESS_SWITCH(HResonanceCorrelationFilter, processKstarsMC, "Produce associated K*0 tables for MC", false); +}; + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + return WorkflowSpec{ + adaptAnalysisTask(cfgc)}; +} \ No newline at end of file From b23bfdc2300182bff2ed8e21715925d4aaf34c24 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 11:59:51 +0530 Subject: [PATCH 03/17] Add hResonanceCorrelationFilter workflow to CMakeLists --- PWGLF/TableProducer/Resonances/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PWGLF/TableProducer/Resonances/CMakeLists.txt b/PWGLF/TableProducer/Resonances/CMakeLists.txt index 5ce14867844..bafd0b12ea0 100644 --- a/PWGLF/TableProducer/Resonances/CMakeLists.txt +++ b/PWGLF/TableProducer/Resonances/CMakeLists.txt @@ -69,3 +69,8 @@ o2physics_add_dpl_workflow(xi1530kaonreducedtable SOURCES xi1530kaonreducedtable.cxx PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore COMPONENT_NAME Analysis) + +o2physics_add_dpl_workflow(hresonancecorrelationfilter + SOURCES hResonanceCorrelationFilter.cxx + PUBLIC_LINK_LIBRARIES O2::DCAFitter O2Physics::AnalysisCore O2Physics::EventFilteringUtils + COMPONENT_NAME Analysis) From 6e55f99eff88be7cad62af5ef72cb4ae201c7c40 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 12:00:43 +0530 Subject: [PATCH 04/17] [PWGLF] First commit of h-resonance correlation --- .../Resonances/hResonanceCorrelation.cxx | 3077 +++++++++++++++++ 1 file changed, 3077 insertions(+) create mode 100644 PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx new file mode 100644 index 00000000000..765caf4a7d0 --- /dev/null +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -0,0 +1,3077 @@ +// Copyright 2019-2020 CERN and copyright holders of ALICE O2. +// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders. +// All rights not expressly granted are reserved. +// +// This software is distributed under the terms of the GNU General Public +// License v3 (GPL Version 3), copied verbatim in the file "COPYING". +// +// In applying this license CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. +// +/// \file hResonanceCorrelation.cxx +/// \brief This task serves to do hadron-resonance correlation studies. +/// The yield will be calculated using the two-particle correlation method. +/// Trigger particle : Hadrons +/// Associated Particles : Phi, K*0 +/// this task requires the hResonanceCorrelationFilter to have been run before. +/// +/// \author Hirak Kumar Koley (hirak.koley@cern.ch) + +#include "PWGLF/DataModel/LFHResonanceCorrelationTables.h" +#include "PWGLF/DataModel/mcCentrality.h" +#include "PWGLF/Utils/inelGt.h" + +#include "Common/CCDB/EventSelectionParams.h" +#include "Common/Core/RecoDecay.h" +#include "Common/Core/Zorro.h" +#include "Common/Core/ZorroSummary.h" +#include "Common/DataModel/Centrality.h" +#include "Common/DataModel/EventSelection.h" +#include "Common/DataModel/Multiplicity.h" +#include "Common/DataModel/TrackSelectionTables.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace o2; +using namespace o2::constants::math; +using namespace o2::framework; +using namespace o2::framework::expressions; +using namespace o2::constants::physics; + +using TracksComplete = soa::Join; +using AssocPhis = soa::Join; +using AssocKstars = soa::Join; + +struct HResonanceCorrelation { + // for efficiency corrections if requested + Service ccdb; + + Service pdgDB; + o2::pwglf::ParticleCounter mCounter; + + HistogramRegistry histos{"Histos", {}, OutputObjHandlingPolicy::AnalysisObject}; + + // event filtering + Configurable zorroMask{"zorroMask", "", "zorro trigger class to select on (empty: none)"}; + + Zorro zorro; + OutputObj zorroSummary{"zorroSummary"}; + + struct : ConfigurableGroup { + std::string prefix = "masterConfigurations"; + Configurable doPPAnalysis{"doPPAnalysis", true, "if in pp, set to true"}; + Configurable collisionHasTriggOrAssoc{"collisionHasTriggOrAssoc", 0, "require the collisions containing (0:no requirement 1:trig 2:assoc 3:trig or assoc 4:trig and assoc"}; + Configurable doFullCorrelationStudy{"doFullCorrelationStudy", true, "if true, do full correlation study by creating all THnSparse histograms for the correlation function"}; + Configurable doCorrelationHadron{"doCorrelationHadron", false, "do Hadron correlation"}; + Configurable doCorrelationPhi{"doCorrelationPhi", false, "do Phi correlation"}; + Configurable doCorrelationKstar{"doCorrelationKstar", false, "do K*0 correlation"}; + Configurable doCorrelationPion{"doCorrelationPion", false, "do Pion correlation"}; + Configurable doGenEventSelection{"doGenEventSelection", true, "use event selections when performing closure test for the gen events"}; + Configurable selectINELgtZERO{"selectINELgtZERO", true, "select INEL>0 events"}; + Configurable zVertexCut{"zVertexCut", 10, "Cut on PV position"}; + Configurable requireAllGoodITSLayers{"requireAllGoodITSLayers", false, " require that in the event all ITS are good"}; + Configurable requireGoodTriggerTVX{"requireGoodTriggerTVX", false, " require acceptable FT0C-FT0A time difference"}; + Configurable requireGoodZvtxFT0vsPV{"requireGoodZvtxFT0vsPV", false, " require small difference between z-vertex from PV and from FT0"}; + Configurable skipUnderOverflowInTHn{"skipUnderOverflowInTHn", false, "skip under/overflow in THns"}; + Configurable mixingParameter{"mixingParameter", 10, "how many events are mixed"}; + Configurable doMCassociation{"doMCassociation", false, "fill everything only for MC associated"}; + Configurable doTriggPhysicalPrimary{"doTriggPhysicalPrimary", false, "require physical primary for trigger particles"}; + Configurable applyNewMCSelection{"applyNewMCSelection", false, "apply new MC Generated selection"}; + Configurable doSeparateFT0Prediction{"doSeparateFT0Prediction", false, "separate FT0M to FT0A and FT0C in prediction process"}; + Configurable useCentralityinPrediction{"useCentralityinPrediction", false, "if true, use centrality instead of multiplisity"}; + Configurable doMirroringInDelataEta{"doMirroringInDelataEta", false, "if true, fill only positive delta eta and mirror the negative side in post processing, Adjust the delta axis!"}; + Configurable fillCorrelationHistWithMass{"fillCorrelationHistWithMass", false, "if true, fill correlation histograms with particle mass"}; + } masterConfigurations; + + // master analysis switches + Configurable doAssocPhysicalPrimary{"doAssocPhysicalPrimary", false, "require physical primary for associated particles"}; + Configurable doAssocPhysicalPrimaryInGen{"doAssocPhysicalPrimaryInGen", false, "require physical primary for associated particles in Generated Partilces"}; + Configurable doAutocorrelationRejection{"doAutocorrelationRejection", true, "reject pairs where trigger Id is the same as daughter particle Id"}; + Configurable doMixingQAandEventQA{"doMixingQAandEventQA", true, "if true, add EvnetQA and MixingQA hist to histos"}; + Configurable doITSClustersQA{"doITSClustersQA", true, "if true, add ITSCluster hist to histos"}; + Configurable doDeltaPhiStarCheck{"doDeltaPhiStarCheck", false, "if true, create and fill delta phi star histograms"}; + + Configurable triggerBinToSelect{"triggerBinToSelect", 0, "trigger bin to select on if processSelectEventWithTrigger enabled"}; + Configurable triggerParticleCharge{"triggerParticleCharge", 0, "For checks, if 0 all charged tracks, if -1 only neg., if 1 only positive"}; + Configurable etaSel{"etaSel", 0.8, "Selection in eta for trigger and associated particles"}; + Configurable ySel{"ySel", 0.5, "Selection in rapidity for consistency checks"}; + + Configurable useTheLeadingParticleAsTrigger{"useTheLeadingParticleAsTrigger", false, "if true, use the leading particle in the event as trigger particle"}; + // used for event selections in Pb-Pb + Configurable cfgCutOccupancyHigh{"cfgCutOccupancyHigh", 3000, "High cut on TPC occupancy"}; + Configurable cfgCutOccupancyLow{"cfgCutOccupancyLow", 0, "Low cut on TPC occupancy"}; + + // Axes - configurable for smaller sizes + struct : ConfigurableGroup { + std::string prefix = "axesConfigurations"; + ConfigurableAxis axisMult{"axisMult", {VARIABLE_WIDTH, 0.0f, 0.01f, 1.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 70.0f, 100.0f}, "Mixing bins - multiplicity"}; + ConfigurableAxis axisVtxZ{"axisVtxZ", {VARIABLE_WIDTH, -10.0f, -8.f, -6.f, -4.f, -2.f, 0.f, 2.f, 4.f, 6.f, 8.f, 10.f}, "Mixing bins - z-vertex"}; + ConfigurableAxis axisPhi{"axisPhi", {72, 0, TwoPI}, "#phi"}; + ConfigurableAxis axisEta{"axisEta", {80, -0.8, +0.8}, "#eta"}; + ConfigurableAxis axisDeltaPhi{"axisDeltaPhi", {72, -PIHalf, PIHalf * 3}, "delta #varphi axis for histograms"}; + ConfigurableAxis axisDeltaEta{"axisDeltaEta", {50, -1.6, 1.6}, "delta eta axis for histograms"}; + ConfigurableAxis axisPtAssoc{"axisPtAssoc", {VARIABLE_WIDTH, 0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 10.0}, "pt associated axis for histograms"}; + ConfigurableAxis axisPtTrigger{"axisPtTrigger", {VARIABLE_WIDTH, 0.0, 1.0, 2.0, 3.0, 100}, "pt associated axis for histograms"}; + ConfigurableAxis axisPtQA{"axisPtQA", {VARIABLE_WIDTH, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f, 4.0f, 4.4f, 4.8f, 5.2f, 5.6f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 17.0f, 19.0f, 21.0f, 23.0f, 25.0f, 30.0f, 35.0f, 40.0f, 50.0f}, "pt axis for QA histograms"}; + ConfigurableAxis axisMassNSigma{"axisMassNSigma", {40, -2, 2}, "Axis for mass Nsigma"}; + ConfigurableAxis axisPhiMass{"axisPhiMass", {300, 1.01f, 1.31f}, "Inv. Mass (GeV/c^{2})"}; + ConfigurableAxis axisKstarMass{"axisKstarMass", {300, 0.596f, 1.196f}, "Inv. Mass (GeV/c^{2})"}; + ConfigurableAxis axisMultiplicity{"axisMultiplicity", {VARIABLE_WIDTH, 0, 20, 40, 60, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300}, "Binning of the Multiplicity axis in model prediction process"}; + ConfigurableAxis axisMidrapidityMultiplicity{"axisMidrapidityMultiplicity", {VARIABLE_WIDTH, 0, 20, 40, 60, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300}, "Binning of the Midrapidity Multiplicity axis in model prediction process"}; + } axesConfigurations; + + // for topo var QA + // Per-species, peak-relative mass windows: a single shared + // `massWindowConfigurations` comparing raw assocCandidate.mass() directly + // against these bounds would break isLeftBg structurally (it requires a + // NEGATIVE mass, which raw mass never is), and sharing one window between + // Phi and K*0 leaves one species' tuned range (Phi's, ~1.0-1.04 GeV) + // entirely outside the other's candidate mass range (K*0's filter-level + // cut is 0.796-0.996 GeV), making K*0's RightBg structurally unreachable. + // + // Instead, classify using delta = mass - peakMass (a real signed quantity, + // so isLeftBg is reachable), with each species' own peak mass and its own + // effective sigma, in its own struct. maxPeakNSigma/minBgNSigma/maxBgNSigma + // are genuinely "number of sigma" (2, 3, 6 by default), multiplied by + // `sigma` (in GeV) to get the actual mass-offset bounds. + // + // `sigma` defaults here are PDG Gamma/2.355 (treating the natural + // Breit-Wigner width as if it were a Gaussian FWHM) -- a reasonable + // placeholder for K*0, where the ~47 MeV natural width likely dominates + // over detector resolution, but LIKELY AN UNDERESTIMATE for Phi, whose + // ~4.2 MeV natural width is probably smaller than your actual track + // momentum resolution. Refit both from your own reconstructed invariant + // mass peak (Voigt / BW (x) Gaussian) once you have statistics, and update + // `sigma` here -- everything else (the Nsigma multipliers) stays valid. + struct : ConfigurableGroup { + std::string prefix = "massWindowConfigurationsPhi"; + Configurable peakMass{"peakMass", 1.019455f, "Phi(1020) PDG mass (GeV), used as delta = mass - peakMass"}; + Configurable sigma{"sigma", 0.0018f, "effective width (GeV) for the Nsigma windows below -- PDG Gamma/2.355 placeholder, refit from your own peak"}; + Configurable maxPeakNSigma{"maxPeakNSigma", 2, "Signal region half-width, in units of sigma"}; + Configurable minBgNSigma{"minBgNSigma", 3, "Bg region edge closest to peak, in units of sigma"}; + Configurable maxBgNSigma{"maxBgNSigma", 6, "Bg region edge furthest from peak, in units of sigma"}; + } massWindowConfigurationsPhi; // allows for gap between peak and bg in case someone wants to + + struct : ConfigurableGroup { + std::string prefix = "massWindowConfigurationsKstar"; + Configurable peakMass{"peakMass", 0.89555f, "K*0(892) PDG mass (GeV), used as delta = mass - peakMass"}; + Configurable sigma{"sigma", 0.0201f, "effective width (GeV) for the Nsigma windows below -- PDG Gamma/2.355 placeholder, refit from your own peak"}; + Configurable maxPeakNSigma{"maxPeakNSigma", 2, "Signal region half-width, in units of sigma"}; + Configurable minBgNSigma{"minBgNSigma", 3, "Bg region edge closest to peak, in units of sigma"}; + Configurable maxBgNSigma{"maxBgNSigma", 6, "Bg region edge furthest from peak, in units of sigma"}; + } massWindowConfigurationsKstar; // allows for gap between peak and bg in case someone wants to + + // Implementation of on-the-spot efficiency correction + struct : ConfigurableGroup { + std::string prefix = "efficiencyFlags"; + Configurable applyEfficiencyCorrection{"applyEfficiencyCorrection", false, "apply efficiency correction"}; + Configurable applyEfficiencyForTrigger{"applyEfficiencyForTrigger", false, "apply efficiency correction for the trigger particle"}; + Configurable applyEfficiencyPropagation{"applyEfficiencyPropagation", false, "propagate also the efficiency uncertainty"}; + Configurable applyPurityHadron{"applyPurityHadron", false, "apply the purity correction for associated hadrons"}; + Configurable applyPurityTrigger{"applyPurityTrigger", false, "apply the purity correction for trigger particle"}; + Configurable applyEffAsFunctionOfMult{"applyEffAsFunctionOfMult", false, "apply efficiency as a function of multiplicity as well"}; + Configurable applyEffAsFunctionOfMultAndPhi{"applyEffAsFunctionOfMultAndPhi", false, "apply efficiency as a function of multiplicity and phi"}; + } efficiencyFlags; + Configurable ccdburl{"ccdburl", "http://alice-ccdb.cern.ch", "url of the ccdb repository to use"}; + Configurable efficiencyCCDBPath{"efficiencyCCDBPath", "GLO/Config/GeometryAligned", "Path of the efficiency corrections"}; + + // Configurables for doing subwagon systematics + struct : ConfigurableGroup { + std::string prefix = "trackSelection"; + // --- Track quality variations (single track, both trigger and assoc daughters) + Configurable minTPCNCrossedRowsTrigger{"minTPCNCrossedRowsTrigger", 70, "Minimum TPC crossed rows (trigger)"}; + Configurable minTPCNCrossedRowsAssociated{"minTPCNCrossedRowsAssociated", 70, "Minimum TPC crossed rows (associated)"}; + Configurable triggerRequireITS{"triggerRequireITS", true, "require ITS signal in trigger tracks"}; + Configurable assocRequireITS{"assocRequireITS", true, "require ITS signal in associated primary tracks"}; + Configurable triggerMaxTPCSharedClusters{"triggerMaxTPCSharedClusters", 200, "maximum number of shared TPC clusters (inclusive)"}; + Configurable assocMaxTPCSharedClusters{"assocMaxTPCSharedClusters", 200, "maximum number of shared TPC clusters (inclusive) for assoc primary tracks"}; + Configurable triggerRequireL0{"triggerRequireL0", false, "require ITS L0 cluster for trigger"}; + Configurable assocRequireL0{"assocRequireL0", true, "require ITS L0 cluster for assoc primary track"}; + Configurable requireDCAzCut{"requireDCAzCut", false, "require DCAz cut for trigger and associated primary tracks"}; + + // --- Trigger: DCA variation from basic formula: |DCAxy| < 0.004f + (0.013f / pt) + Configurable dcaXYconstant{"dcaXYconstant", 0.004, "[0] in |DCAxy| < [0]+[1]/pT"}; + Configurable dcaXYpTdep{"dcaXYpTdep", 0.013, "[1] in |DCAxy| < [0]+[1]/pT"}; + // --- Assoc track: DCA variation from basic formula: |DCAxy| < 0.004f + (0.013f / pt) + Configurable dcaXYconstantAssoc{"dcaXYconstantAssoc", 0.004, "[0] in |DCAxy| < [0]+[1]/pT"}; + Configurable dcaXYpTdepAssoc{"dcaXYpTdepAssoc", 0.013, "[1] in |DCAxy| < [0]+[1]/pT"}; + + Configurable dcaZconstant{"dcaZconstant", 0.004, "[0] in |DCAz| < [0]+[1]/pT"}; + Configurable dcaZpTdep{"dcaZpTdep", 0.013, "[1] in |DCAz| < [0]+[1]/pT"}; + Configurable dcaZconstantAssoc{"dcaZconstantAssoc", 0.004, "[0] in |DCAz| < [0]+[1]/pT"}; + Configurable dcaZpTdepAssoc{"dcaZpTdepAssoc", 0.013, "[1] in |DCAz| < [0]+[1]/pT"}; + } trackSelection; + + struct : ConfigurableGroup { + std::string prefix = "checks"; + + // on the fly correction instead of mixingParameter + Configurable doOnTheFlyFlattening{"doOnTheFlyFlattening", 0, "enable an on-the-fly correction instead of using mixing"}; + } checks; + + struct ValidCollision { + struct ValidParticle { + float eta; + float phi; + float pt; + int region; + float efficiency; + float efficiencyError; + int type; + }; + float pvz; + float mult; + std::vector trigParticles; + std::vector assocParticles; + void addValidParticle(float eta, float phi, float pt, int region, float efficiency, float efficiencyError, int type) + { + ValidParticle particle{eta, phi, pt, region, efficiency, efficiencyError, type}; + + if (type == -1) { + trigParticles.push_back(particle); + } else { + assocParticles.push_back(particle); + } + } + }; + + using ValidCollisions = std::vector>; + ValidCollisions validCollisions; + + // objects to use for efficiency corrections + TH2F* hEfficiencyTrigger; + TH3F* hEfficiencyTriggerMult; + THnF* hEfficiencyTriggerMultVsPhi; + TH2F* hEfficiencyPion; + TH2F* hEfficiencyPhi; + THnF* hEfficiencyPhiMultVsPhi; + TH2F* hEfficiencyKstar; + THnF* hEfficiencyKstarMultVsPhi; + TH2F* hEfficiencyHadron; + TH3F* hEfficiencyHadronMult; + TH1F* hPurityHadron; + TH2F* hPurityHadronMult; + + // objects to propagate the efficiency uncertainty + TH2F* hEfficiencyUncertaintyTrigger; + TH3F* hEfficiencyUncertaintyTriggerMult; + TH2F* hEfficiencyUncertaintyPion; + TH2F* hEfficiencyUncertaintyPhi; + TH2F* hEfficiencyUncertaintyKstar; + TH2F* hEfficiencyUncertaintyHadron; + TH3F* hEfficiencyUncertaintyHadronMult; + TH1F* hPurityUncertaintyHadron; + TH2F* hPurityUncertaintyHadronMult; + + using BinningTypePP = ColumnBinningPolicy; + using BinningTypePbPb = ColumnBinningPolicy; + + // collision slicing for mixed events + Preslice collisionSliceTracks = aod::triggerTracks::collisionId; + // Preslice collisionSlicePions = aod::assocHadrons::collisionId; + Preslice collisionSliceHadrons = aod::assocHadrons::collisionId; + Preslice perCollision = aod::mcparticle::mcCollisionId; + Preslice collisionSlicePhis = aod::assocPhis::collisionId; + Preslice collisionSliceKstars = aod::assocKstars::collisionId; + + static constexpr std::string_view Particlenames[] = {"Phi", "Kstar0", "Pion", "Hadron"}; + static constexpr int PdgCodes[] = {333, 313, 211, 0}; // Hadron has no single PDG code; 0 is a harmless placeholder + + static constexpr int IndexPhi = 0; + static constexpr int IndexKstar = 1; + static constexpr int IndexPion = 2; + + uint16_t doCorrelation; + int mRunNumber; + int mRunNumberZorro; + + std::vector> axisRanges; + + static constexpr float MinRadiusTPC = 0.8; + static constexpr float MaxRadiusTPC = 2.5; + + static constexpr float Neutral = 0.0; + + static constexpr int AssocParticleTypes = 4; // Phi, Kstar0, Pion, Hadron + static constexpr int AssocParticleTypesNoHadron = 3; // Phi, Kstar0, Pion + + /// Function to aid in calculating delta-phi + /// \param phi1 first phi value + /// \param phi2 second phi value + double computeDeltaPhi(double phi1, double phi2) + { + double deltaPhi = phi1 - phi2; + double shiftedDeltaPhi = RecoDecay::constrainAngle(deltaPhi, -PIHalf); + return shiftedDeltaPhi; + } + + /// Function to load zorro + /// \param bc provided such that the run number + timestamp can be used + void initZorro(aod::BCsWithTimestamps::iterator const& bc) + { + if (mRunNumberZorro == bc.runNumber()) { + return; + } + + zorro.initCCDB(ccdb.service, bc.runNumber(), bc.timestamp(), zorroMask.value); + zorro.populateHistRegistry(histos, bc.runNumber()); + + mRunNumberZorro = bc.runNumber(); + } + + /// Function to load efficiencies to memory from CCDB + /// \param bc provided such that the run number can be used + void initEfficiencyFromCCDB(aod::BCsWithTimestamps::iterator const& bc) + { + if (mRunNumber == bc.runNumber()) { + return; + } + mRunNumber = bc.runNumber(); + LOG(info) << "Loading efficiencies from CCDB for run " << mRunNumber << " now..."; + auto timeStamp = bc.timestamp(); + + TList* listEfficiencies = ccdb->getForTimeStamp(efficiencyCCDBPath, timeStamp); + + if (!listEfficiencies) { + LOG(fatal) << "Problem getting TList object with efficiencies!"; + } + + hEfficiencyTrigger = static_cast(listEfficiencies->FindObject("hEfficiencyTrigger")); + hEfficiencyTriggerMult = static_cast(listEfficiencies->FindObject("hEfficiencyTriggerMult")); + hEfficiencyTriggerMultVsPhi = static_cast(listEfficiencies->FindObject("hEfficiencyTriggerMultVsPhi")); + hEfficiencyPhi = static_cast(listEfficiencies->FindObject("hEfficiencyPhi")); + hEfficiencyPhiMultVsPhi = static_cast(listEfficiencies->FindObject("hEfficiencyPhiMultVsPhi")); + hEfficiencyKstar = static_cast(listEfficiencies->FindObject("hEfficiencyKstar")); + hEfficiencyKstarMultVsPhi = static_cast(listEfficiencies->FindObject("hEfficiencyKstarMultVsPhi")); + hEfficiencyHadron = static_cast(listEfficiencies->FindObject("hEfficiencyHadron")); + hEfficiencyHadronMult = static_cast(listEfficiencies->FindObject("hEfficiencyHadronMult")); + hEfficiencyPion = static_cast(listEfficiencies->FindObject("hEfficiencyPion")); + hPurityHadron = static_cast(listEfficiencies->FindObject("hPurityHadron")); + hPurityHadronMult = static_cast(listEfficiencies->FindObject("hPurityHadronMult")); + hEfficiencyUncertaintyTrigger = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyTrigger")); + hEfficiencyUncertaintyTriggerMult = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyTriggerMult")); + hEfficiencyUncertaintyPhi = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyPhi")); + hEfficiencyUncertaintyKstar = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyKstar")); + hEfficiencyUncertaintyPion = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyPion")); + hEfficiencyUncertaintyHadron = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyHadron")); + hEfficiencyUncertaintyHadronMult = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyHadronMult")); + hPurityUncertaintyHadron = static_cast(listEfficiencies->FindObject("hPurityUncertaintyHadron")); + hPurityUncertaintyHadronMult = static_cast(listEfficiencies->FindObject("hPurityUncertaintyHadronMult")); + if (efficiencyFlags.applyEfficiencyPropagation && !efficiencyFlags.applyEffAsFunctionOfMultAndPhi && !hEfficiencyUncertaintyTrigger) + LOG(fatal) << "Problem getting hEfficiencyUncertaintyTrigger!"; + if (efficiencyFlags.applyEffAsFunctionOfMult && !hEfficiencyTriggerMult) + LOG(fatal) << "Problem getting hEfficiencyTriggerMult!"; + LOG(info) << "Efficiencies now loaded for " << mRunNumber; + } + + void init(InitContext const&) + { + zorroSummary.setObject(zorro.getZorroSummary()); + mRunNumber = 0; + mRunNumberZorro = 0; + hEfficiencyPion = 0x0; + hEfficiencyPhi = 0x0; + hEfficiencyKstar = 0x0; + hEfficiencyUncertaintyTrigger = 0x0; + hEfficiencyUncertaintyPion = 0x0; + hEfficiencyUncertaintyPhi = 0x0; + hEfficiencyUncertaintyKstar = 0x0; + + hEfficiencyHadron = 0x0; + hPurityHadron = 0x0; + hPurityUncertaintyHadron = 0x0; + hEfficiencyUncertaintyHadron = 0x0; + + // set bitmap for convenience + doCorrelation = 0; + if (masterConfigurations.doCorrelationPhi) + SETBIT(doCorrelation, IndexPhi); + if (masterConfigurations.doCorrelationKstar) + SETBIT(doCorrelation, IndexKstar); + if (masterConfigurations.doCorrelationPion) + SETBIT(doCorrelation, IndexPion); + if (masterConfigurations.doCorrelationHadron) + SETBIT(doCorrelation, 3); + + // Store axis ranges to prevent spurious filling + // axis status: + // --- Delta-phi is safe -> math forbids insanity + // --- Delta-eta depends on pre-filter -> check + // --- pT assoc depends on binning -> check + // --- vertex Z is safe -> skipped at evsel level + // --- multiplicity -> check + + // grab axis edge from ConfigurableAxes + const AxisSpec preAxisDeltaPhi{axesConfigurations.axisDeltaPhi, "#Delta#varphi"}; + const AxisSpec preAxisDeltaEta{axesConfigurations.axisDeltaEta, "#Delta#eta"}; + const AxisSpec preAxisPtAssoc{axesConfigurations.axisPtAssoc, "#it{p}_{T}^{assoc} (GeV/c)"}; + const AxisSpec preAxisPtTrigger{axesConfigurations.axisPtTrigger, "#it{p}_{T}^{trigger} (GeV/c)"}; + const AxisSpec preAxisVtxZ{axesConfigurations.axisVtxZ, "vertex Z (cm)"}; + const AxisSpec preAxisMult{axesConfigurations.axisMult, "mult percentile"}; + const AxisSpec axisPtPhi{axesConfigurations.axisPtAssoc, "#it{p}_{T}^{#phi} (GeV/c)"}; + const AxisSpec preAxisMultiplicity{axesConfigurations.axisMultiplicity, "multiplicity"}; + + // store the original axes in specific TH1Cs for completeness + histos.add("axes/hDeltaPhiAxis", "", kTH1C, {preAxisDeltaPhi}); + histos.add("axes/hDeltaEtaAxis", "", kTH1C, {preAxisDeltaEta}); + histos.add("axes/hPtAssocAxis", "", kTH1C, {preAxisPtAssoc}); + histos.add("axes/hPtTriggerAxis", "", kTH1C, {preAxisPtTrigger}); + histos.add("axes/hVertexZAxis", "", kTH1C, {preAxisVtxZ}); + histos.add("axes/hMultAxis", "", kTH1C, {preAxisMult}); + histos.add("axes/hMultiplicityAxis", "", kTH1C, {preAxisMultiplicity}); + + std::vector edgesDeltaPhiOrig = preAxisDeltaPhi.binEdges; + std::vector edgesDeltaEtaOrig = preAxisDeltaEta.binEdges; + std::vector edgesPtAssocOrig = preAxisPtAssoc.binEdges; + std::vector edgesPtTriggerOrig = preAxisPtTrigger.binEdges; + std::vector edgesVtxZOrig = preAxisVtxZ.binEdges; + std::vector edgesMultOrig = preAxisMult.binEdges; + std::vector edgesMultiplicityOrig = preAxisMultiplicity.binEdges; + + std::vector rangesDeltaPhi = {static_cast(edgesDeltaPhiOrig[0]), static_cast(edgesDeltaPhiOrig[edgesDeltaPhiOrig.size() - 1])}; + std::vector rangesDeltaEta = {static_cast(edgesDeltaEtaOrig[0]), static_cast(edgesDeltaEtaOrig[edgesDeltaEtaOrig.size() - 1])}; + std::vector rangesPtAssoc = {static_cast(edgesPtAssocOrig[0]), static_cast(edgesPtAssocOrig[edgesPtAssocOrig.size() - 1])}; + std::vector rangesPtTrigger = {static_cast(edgesPtTriggerOrig[0]), static_cast(edgesPtTriggerOrig[edgesPtTriggerOrig.size() - 1])}; + std::vector rangesVtxZ = {static_cast(edgesVtxZOrig[0]), static_cast(edgesVtxZOrig[edgesVtxZOrig.size() - 1])}; + std::vector rangesMult = {static_cast(edgesMultOrig[0]), static_cast(edgesMultOrig[edgesMultOrig.size() - 1])}; + std::vector rangesMultiplicity = {static_cast(edgesMultiplicityOrig[0]), static_cast(edgesMultiplicityOrig[edgesMultiplicityOrig.size() - 1])}; + + axisRanges.emplace_back(rangesDeltaPhi); + axisRanges.emplace_back(rangesDeltaEta); + axisRanges.emplace_back(rangesPtAssoc); + axisRanges.emplace_back(rangesPtTrigger); + axisRanges.emplace_back(rangesVtxZ); + axisRanges.emplace_back(rangesMult); + axisRanges.emplace_back(rangesMultiplicity); + + std::vector edgesDeltaPhi; + std::vector edgesDeltaEta; + std::vector edgesPtAssoc; + std::vector edgesPtTrigger; + std::vector edgesVtxZ; + std::vector edgesMult; + std::vector edgesMultiplicity; + + // v--- skipUnderOverflowInTHn ---v + // + // if enabled, this will change the axes such that they will solely cover the interval from + // edge[1] to edge[n-1]; this will mean that the bin 1 and bin N will be stored in + // under / overflow bins and will have to be manually unpacked. Do not forget to do the manual + // unpacking a posteriori! + // + // this feature is meant to save memory conveniently. + // it should actually be implemented centrally in ROOT but ok, this will do it for now. + + int offset = masterConfigurations.skipUnderOverflowInTHn ? 1 : 0; + // ===] delta-phi [=== + if (!preAxisDeltaPhi.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesDeltaPhiOrig.size()) - offset; i++) + edgesDeltaPhi.emplace_back(edgesDeltaPhiOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesDeltaPhiOrig[0]; + double delta = (edgesDeltaPhiOrig[1] - edgesDeltaPhiOrig[0]) / preAxisDeltaPhi.nBins.value(); + for (int i = offset; i < preAxisDeltaPhi.nBins.value() + 1 - offset; i++) + edgesDeltaPhi.emplace_back(min + static_cast(i) * delta); + } + // ===] delta-eta [=== + if (!preAxisDeltaEta.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesDeltaEtaOrig.size()) - offset; i++) + edgesDeltaEta.emplace_back(edgesDeltaEtaOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesDeltaEtaOrig[0]; + double delta = (edgesDeltaEtaOrig[1] - edgesDeltaEtaOrig[0]) / preAxisDeltaEta.nBins.value(); + for (int i = offset; i < preAxisDeltaEta.nBins.value() + 1 - offset; i++) + edgesDeltaEta.emplace_back(min + static_cast(i) * delta); + } + // ===] pt assoc [=== + if (!preAxisPtAssoc.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesPtAssocOrig.size()) - offset; i++) + edgesPtAssoc.emplace_back(edgesPtAssocOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesPtAssocOrig[0]; + double delta = (edgesPtAssocOrig[1] - edgesPtAssocOrig[0]) / preAxisPtAssoc.nBins.value(); + for (int i = offset; i < preAxisPtAssoc.nBins.value() + 1 - offset; i++) + edgesPtAssoc.emplace_back(min + static_cast(i) * delta); + } + // ===] pt trigger [=== + if (!preAxisPtTrigger.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesPtTriggerOrig.size()) - offset; i++) + edgesPtTrigger.emplace_back(edgesPtTriggerOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesPtTriggerOrig[0]; + double delta = (edgesPtTriggerOrig[1] - edgesPtTriggerOrig[0]) / preAxisPtTrigger.nBins.value(); + for (int i = offset; i < preAxisPtTrigger.nBins.value() + 1 - offset; i++) + edgesPtTrigger.emplace_back(min + static_cast(i) * delta); + } + // ===] vtx Z [=== + if (!preAxisVtxZ.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesVtxZOrig.size()) - offset; i++) + edgesVtxZ.emplace_back(edgesVtxZOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesVtxZOrig[0]; + double delta = (edgesVtxZOrig[1] - edgesVtxZOrig[0]) / preAxisVtxZ.nBins.value(); + for (int i = offset; i < preAxisVtxZ.nBins.value() + 1 - offset; i++) + edgesVtxZ.emplace_back(min + static_cast(i) * delta); + } + // ===] mult percentile [=== + if (!preAxisMult.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesMultOrig.size()) - offset; i++) + edgesMult.emplace_back(edgesMultOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesMultOrig[0]; + double delta = (edgesMultOrig[1] - edgesMultOrig[0]) / preAxisMult.nBins.value(); + for (int i = offset; i < preAxisMult.nBins.value() + 1 - offset; i++) + edgesMult.emplace_back(min + static_cast(i) * delta); + } + // ===] multiplicity count [=== + if (!preAxisMultiplicity.nBins.has_value()) { + // variable binning, use bins provided + for (int i = offset; i < static_cast(edgesMultiplicityOrig.size()) - offset; i++) + edgesMultiplicity.emplace_back(edgesMultiplicityOrig[i]); + } else { + // fixed binning, generate the bin edges on-the-spot + double min = edgesMultiplicityOrig[0]; + double delta = (edgesMultiplicityOrig[1] - edgesMultiplicityOrig[0]) / preAxisMultiplicity.nBins.value(); + for (int i = offset; i < preAxisMultiplicity.nBins.value() + 1 - offset; i++) + edgesMultiplicity.emplace_back(min + static_cast(i) * delta); + } + + LOGF(info, "Initialized THnF axis delta-phi with %i bins.", edgesDeltaPhi.size() - 1); + LOGF(info, "Initialized THnF axis delta-eta with %i bins.", edgesDeltaEta.size() - 1); + LOGF(info, "Initialized THnF axis pTassoc with %i bins.", edgesPtAssoc.size() - 1); + LOGF(info, "Initialized THnF axis pTtrigger with %i bins.", edgesPtTrigger.size() - 1); + LOGF(info, "Initialized THnF axis vertex-Z with %i bins.", edgesVtxZ.size() - 1); + LOGF(info, "Initialized THnF axis mult cent with %i bins.", edgesMult.size() - 1); + LOGF(info, "Initialized THnF axis multiplicity with %i bins.", edgesMultiplicity.size() - 1); + + const AxisSpec axisDeltaPhiNDim{edgesDeltaPhi, "#Delta#varphi"}; + const AxisSpec axisDeltaEtaNDim{edgesDeltaEta, "#Delta#eta"}; + const AxisSpec axisPtAssocNDim{edgesPtAssoc, "#it{p}_{T}^{assoc} (GeV/c)"}; + const AxisSpec axisPtTriggerNDim{edgesPtTrigger, "#it{p}_{T}^{trigger} (GeV/c)"}; + const AxisSpec axisVtxZNDim{edgesVtxZ, "vertex Z (cm)"}; + const AxisSpec axisMultNDim{edgesMult, "mult percentile"}; + const AxisSpec axisMultiplicityNDim{edgesMultiplicity, "Multiplicity"}; + + if (doprocessMixedEventHPhisInBuffer || doprocessMixedEventHKstarsInBuffer) { + validCollisions.resize(histos.get(HIST("axes/hMultAxis"))->GetNbinsX() * histos.get(HIST("axes/hVertexZAxis"))->GetNbinsX()); + for (size_t i = 0; i < validCollisions.size(); ++i) { + validCollisions[i].reserve(masterConfigurations.mixingParameter); + } + } + if (!masterConfigurations.doPPAnalysis) { + // event selections in Pb-Pb + histos.add("hEventSelection", "hEventSelection", kTH1F, {{10, 0, 10}}); + TString eventSelLabel[] = {"all", "sel8", "kIsTriggerTVX", "PV_{z}", "kIsGoodITSLayersAll", "kIsGoodZvtxFT0vsPV", "OccupCut", "kNoTimeFrameBorder", "kNoITSROFrameBorder", "kNoSameBunchPileup "}; + for (int i = 1; i <= histos.get(HIST("hEventSelection"))->GetNbinsX(); i++) { + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(i, eventSelLabel[i - 1]); + } + } + + // ======================================================================== + // SHARED QA HISTOGRAMS + // ======================================================================== + bool anySameEvent = doprocessSameEventHPhis || doprocessSameEventHKstars || doprocessSameEventHPions || doprocessSameEventHHadrons; + bool anyMixedEvent = doprocessMixedEventHPhis || doprocessMixedEventHPhisInBuffer || doprocessMixedEventHKstars || doprocessMixedEventHKstarsInBuffer || doprocessMixedEventHPions || doprocessMixedEventHHadrons; + + if (doMixingQAandEventQA && (anySameEvent || anyMixedEvent)) { + if (anySameEvent) + histos.add("MixingQA/hSECollisionBins", ";bin;Entries", kTH1F, {{140, -0.5, 139.5}}); + if (anyMixedEvent) { + histos.add("MixingQA/hMECollisionBins", ";bin;Entries", kTH1F, {{140, -0.5, 139.5}}); + histos.add("MixingQA/hMEpvz1", ";pvz;Entries", kTH1F, {{30, -15, 15}}); + histos.add("MixingQA/hMEpvz2", ";pvz;Entries", kTH1F, {{30, -15, 15}}); + histos.add("MixingQA/hMixingQA", "mixing QA", kTH1F, {{2, -0.5, 1.5}}); + } + histos.add("EventQA/hMult", "Multiplicity", kTH1F, {axesConfigurations.axisMult}); + histos.add("EventQA/hPvz", ";pvz;Entries", kTH1F, {{30, -15, 15}}); + } + + if (anySameEvent) { + histos.add("hTriggerAllSelectedEtaVsPt", "hTriggerAllSelectedEtaVsPt", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hTriggerPtResolution", ";p_{T}^{reconstructed} (GeV/c); p_{T}^{generated} (GeV/c)", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisPtQA}); + histos.add("hTrackEtaVsPtVsPhi", "hTrackEtaVsPtVsPhi", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.add("hTriggerPrimaryEtaVsPt", "hTriggerPrimaryEtaVsPt", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } else { + histos.add("hTriggerPrimaryEtaVsPt", "hTriggerPrimaryEtaVsPt", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axesConfigurations.axisMult}); + } + } + + // ======================================================================== + // MAIN CORRELATION LOOP (SE & ME) + // ======================================================================== + bool needsSignalBgCloneSE = false; + bool needsSignalBgCloneME = false; + for (int i = 0; i < AssocParticleTypes; i++) { + if (TESTBIT(doCorrelation, i)) { + + bool doSE = false; + bool doME = false; + if (i == IndexPhi) { // Phi + doSE = doprocessSameEventHPhis; + doME = doprocessMixedEventHPhis || doprocessMixedEventHPhisInBuffer; + } else if (i == IndexKstar) { // K*0 + doSE = doprocessSameEventHKstars; + doME = doprocessMixedEventHKstars || doprocessMixedEventHKstarsInBuffer; + } else if (i == IndexPion) { // Pion + doSE = doprocessSameEventHPions; + doME = doprocessMixedEventHPions; + } else { // Hadron + doSE = doprocessSameEventHHadrons; + doME = doprocessMixedEventHHadrons; + } + + // Kinematic QA (Only for Phi) + if (i < IndexPion && (doSE || doME || doprocessPrediction || doprocessClosureTest)) { + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.add(fmt::format("h{}EtaVsPtVsPhi", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + histos.add(fmt::format("h{}EtaVsPtVsPhiBg", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + } else { + histos.add(fmt::format("h{}EtaVsPtVsPhiVsCent", Particlenames[i]).c_str(), "", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axesConfigurations.axisMult}); + histos.add(fmt::format("h{}EtaVsPtVsPhiVsCentBg", Particlenames[i]).c_str(), "", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axesConfigurations.axisMult}); + } + histos.add(fmt::format("h3d{}Spectrum", Particlenames[i]).c_str(), fmt::format("h3d{}Spectrum", Particlenames[i]).c_str(), kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult, axesConfigurations.axisMassNSigma}); + histos.add(fmt::format("h3d{}SpectrumY", Particlenames[i]).c_str(), fmt::format("h3d{}SpectrumY", Particlenames[i]).c_str(), kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult, axesConfigurations.axisMassNSigma}); + + // REMOVED (2026-09-24): hITSClusters{Phi,Kstar0}{Positive,Negative}Daughter{Toward,Transverse}. + // These were registered but never filled anywhere in this file (confirmed by + // exhaustive grep for any fill using these names). The V0/cascade analog in + // hStrangeCorrelation.cxx fills the equivalent histograms with the DAUGHTER's + // ITS cluster count vs the V0's DECAY RADIUS (assoc.v0radius()), split into + // "Toward"/"Transverse" via a delta-phi cut using checks.towardDeltaEtaRange / + // checks.transwerseDeltaEtaRangeMin/Max -- but neither of those two things has + // an equivalent here: Phi/K*0 candidates are reconstructed at the primary + // vertex from track pairs, not displaced V0 decays, so there is no "decay + // radius" analog on AssocPhis/AssocKstars, and the towardDeltaEtaRange / + // transwerseDeltaEtaRangeMin/Max configurables that drive the region split + // don't exist in this task's `checks` struct at all (only doOnTheFlyFlattening + // does). Implementing this would mean inventing both a physical observable for + // the third axis and the region-cut configurables, so it's removed rather than + // guessed. If you want it back, tell me what the third axis should represent + // for a non-displaced daughter pair (e.g. a DCA quantity, or drop to a TH2), + // and I'll add the config fields and fill it properly. + } + + // Core Correlation THnFs + if (masterConfigurations.doFullCorrelationStudy) { + if (doSE) { + if (masterConfigurations.fillCorrelationHistWithMass && (i == IndexPhi || i == IndexKstar)) { + histos.add(fmt::format("sameEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + } else { + histos.add(fmt::format("sameEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + } + if (doDeltaPhiStarCheck) + histos.add(fmt::format("sameEvent/Signal/{}DeltaPhiStar", Particlenames[i]).c_str(), "", kTH3F, {{100, -0.3, 0.3}, {50, -0.05, 0.05}, {2, -1, 1}}); + if ((i == IndexPhi || i == IndexKstar) && !masterConfigurations.fillCorrelationHistWithMass) { + needsSignalBgCloneSE = true; + } + } + + if (doME) { + if (masterConfigurations.fillCorrelationHistWithMass && (i == IndexPhi || i == IndexKstar)) { + histos.add(fmt::format("mixedEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + } else { + histos.add(fmt::format("mixedEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + } + if (doDeltaPhiStarCheck) + histos.add(fmt::format("mixedEvent/Signal/{}DeltaPhiStar", Particlenames[i]).c_str(), "", kTH3F, {{100, -0.3, 0.3}, {50, -0.05, 0.05}, {2, -1, 1}}); + if ((i == IndexPhi || i == IndexKstar) && !masterConfigurations.fillCorrelationHistWithMass) { + needsSignalBgCloneME = true; + } + } + } + } + } + // Clone Signal/ into LeftBg/ and RightBg/ once, after all species for this + // loop have booked their Signal/ histograms (Phi and/or K*0 use sideband + // subtraction; avoids cloning the same prefix twice, which the histogram + // registry does not allow). + if (needsSignalBgCloneSE) { + histos.addClone("sameEvent/Signal/", "sameEvent/LeftBg/"); + histos.addClone("sameEvent/Signal/", "sameEvent/RightBg/"); + } + if (needsSignalBgCloneME) { + histos.addClone("mixedEvent/Signal/", "mixedEvent/LeftBg/"); + histos.addClone("mixedEvent/Signal/", "mixedEvent/RightBg/"); + } + + // ======================================================================== + // TRIGGER AND ASSOC SPECIFIC QA + // ======================================================================== + if (TESTBIT(doCorrelation, IndexPhi) && doprocessSameEventHPhis && masterConfigurations.doFullCorrelationStudy) { + histos.add("sameEvent/TriggerParticlesPhi", "TriggersPhi", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + histos.add("hNumberOfRejectedPairsPhi", "hNumberOfRejectedPairsPhi", kTH1F, {{1, 0, 1}}); + } + + if (TESTBIT(doCorrelation, IndexKstar) && doprocessSameEventHKstars && masterConfigurations.doFullCorrelationStudy) { + histos.add("sameEvent/TriggerParticlesKstar0", "TriggersKstar0", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + histos.add("hNumberOfRejectedPairsKstar0", "hNumberOfRejectedPairsKstar0", kTH1F, {{1, 0, 1}}); + } + + if (TESTBIT(doCorrelation, IndexPion) && doprocessSameEventHPions) { + if (masterConfigurations.doFullCorrelationStudy) { + histos.add("sameEvent/TriggerParticlesPion", "TriggersPion", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + } + histos.add("hNumberOfRejectedPairsPion", "hNumberOfRejectedPairsPion", kTH1F, {{1, 0, 1}}); + histos.add("hPionEtaVsPtAllSelected", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hPionEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hPositivePionEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hNegativePionEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } + + if (TESTBIT(doCorrelation, 3) && doprocessSameEventHHadrons) { + if (masterConfigurations.doFullCorrelationStudy) { + histos.add("sameEvent/TriggerParticlesHadron", "TriggersHadron", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + } + histos.add("hNumberOfRejectedPairsHadron", "hNumberOfRejectedPairsHadron", kTH1F, {{1, 0, 1}}); + histos.add("hDCAzTriggerHadron", "hDCAzTriggerHadron", kTH2F, {{200, -0.5, 0.5}, axesConfigurations.axisPtQA}); + histos.add("hDCAxyTriggerHadron", "hDCAxyTriggerHadron", kTH2F, {{200, -0.5, 0.5}, axesConfigurations.axisPtQA}); + histos.add("hDCAzAssociatedHadron", "hDCAzAssociatedHadron", kTH2F, {{200, -0.5, 0.5}, axesConfigurations.axisPtQA}); + histos.add("hDCAxyAssociatedHadron", "hDCAxyAssociatedHadron", kTH2F, {{200, -0.5, 0.5}, axesConfigurations.axisPtQA}); + histos.add("hAsssocTrackEtaVsPtVsPhi", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + histos.add("hAssocPrimaryEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hAssocHadronsAllSelectedEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hAssocPtResolution", ";p_{T}^{reconstructed} (GeV/c); p_{T}^{generated} (GeV/c)", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisPtQA}); + } + + LOGF(info, "Init THnFs done"); + + // ======================================================================== + // MC GENERATED, CLOSURE TEST, AND PREDICTION + // ======================================================================== + if (doprocessMCGenerated) { + histos.add("hGeneratedQAPtTrigger", "hGeneratedQAPtTrigger", kTH2F, {axesConfigurations.axisPtQA, {5, -0.5f, 4.5f}}); + histos.add("hGeneratedQAPtAssociatedPhi", "hGeneratedQAPtAssociatedPhi", kTH2F, {axesConfigurations.axisPtQA, {5, -0.5f, 4.5f}}); + histos.add("hClosureTestEventCounter", "hClosureTestEventCounter", kTH1F, {{10, 0, 10}}); + + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.add("Generated/hTrigger", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } else { + histos.add("Generated/hTrigger", "", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axesConfigurations.axisMult}); + } + histos.add("Generated/hPositiveTrigger", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("Generated/hNegativeTrigger", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + + for (int i = 0; i < AssocParticleTypes; i++) { + if (TESTBIT(doCorrelation, i)) { // Fixed missing check here + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.add(fmt::format("Generated/h{}", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } else { + histos.add(fmt::format("Generated/h{}", Particlenames[i]).c_str(), "", kTHnF, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi, axesConfigurations.axisMult}); + } + if (i == IndexPion) { + histos.add(fmt::format("Generated/hPositive{}", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add(fmt::format("Generated/hNegative{}", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } + } + } + histos.addClone("Generated/", "GeneratedWithPV/"); + + for (int i = 0; i < AssocParticleTypesNoHadron; i++) { + if (TESTBIT(doCorrelation, i)) { + histos.add(fmt::format("GeneratedWithPV/h{}_MidYVsMult", Particlenames[i]).c_str(), "", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + histos.add(fmt::format("GeneratedWithPV/h{}_MidYVsMult_TwoPVsOrMore", Particlenames[i]).c_str(), "", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + } + } + } + + if (doprocessClosureTest) { + if (!doprocessMCGenerated) + histos.add("hClosureTestEventCounter", "hClosureTestEventCounter", kTH1F, {{10, 0, 10}}); + histos.add("hClosureQAPtTrigger", "hClosureQAPtTrigger", kTH2F, {axesConfigurations.axisPtQA, {5, -0.5f, 4.5f}}); + histos.add("hClosureQAPtAssociatedPhi", "hClosureQAPtAssociatedPhi", kTH2F, {axesConfigurations.axisPtQA, {5, -0.5f, 4.5f}}); + + for (int i = 0; i < AssocParticleTypes; i++) { + if (TESTBIT(doCorrelation, i)) { + histos.add(fmt::format("ClosureTest/sameEvent/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + histos.add(fmt::format("ClosureTest/h{}", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + } + } + histos.add("ClosureTest/hTrigger", "Trigger Tracks", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } + + if (doprocessPrediction) { + mCounter.mPdgDatabase = pdgDB.service; + mCounter.mSelectPrimaries = doAssocPhysicalPrimary.value; + histos.add("Prediction/hEventSelection", "hEventSelection", kTH1F, {{3, 0, 3}}); + TString eventSelLabel[] = {"Read", "INELgt0", "|Z|<10"}; + for (int i = 1; i <= histos.get(HIST("Prediction/hEventSelection"))->GetNbinsX(); i++) { + histos.get(HIST("Prediction/hEventSelection"))->GetXaxis()->SetBinLabel(i, eventSelLabel[i - 1]); + } + if (masterConfigurations.useCentralityinPrediction) { + if (masterConfigurations.doSeparateFT0Prediction) { + histos.add("Prediction/hTriggerFT0A", "Trigger Tracks FT0A", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("Prediction/hTriggerFT0C", "Trigger Tracks FT0C", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } + histos.add("Prediction/hTrigger", "Trigger Tracks", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } else { + if (masterConfigurations.doSeparateFT0Prediction) { + histos.add("Prediction/hTriggerFT0A", "Trigger Tracks FT0A", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMultiplicity}); + histos.add("Prediction/hFT0AvsNchEta08", "Nch in 0.8 vs FT0A multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + histos.add("Prediction/hFT0AvsNchEta05", "Nch in 0.5 vs FT0A multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + histos.add("Prediction/hTriggerFT0C", "Trigger Tracks FT0C", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMultiplicity}); + histos.add("Prediction/hFT0CvsNchEta08", "Nch in 0.8 vs FT0C multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + histos.add("Prediction/hFT0CvsNchEta05", "Nch in 0.5 vs FT0C multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + } + histos.add("Prediction/hTrigger", "Trigger Tracks", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMultiplicity}); + histos.add("Prediction/hFT0MvsNchEta08", "Nch in 0.8 vs FT0M multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + histos.add("Prediction/hFT0MvsNchEta05", "Nch in 0.5 vs FT0M multiplicity", kTH2F, {axesConfigurations.axisMultiplicity, axesConfigurations.axisMidrapidityMultiplicity}); + } + for (int i = 0; i < AssocParticleTypes; i++) { + if (TESTBIT(doCorrelation, i)) { + histos.add(fmt::format("Prediction/h{}", Particlenames[i]).c_str(), "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisPhi}); + if (masterConfigurations.useCentralityinPrediction) { + histos.add(fmt::format("Prediction/sameEvent/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + } else { + histos.add(fmt::format("Prediction/sameEvent/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultiplicityNDim}); + } + } + } + if (masterConfigurations.doSeparateFT0Prediction) { + histos.addClone("Prediction/sameEvent/", "Prediction/sameEventFT0A/"); + histos.addClone("Prediction/sameEvent/", "Prediction/sameEventFT0C/"); + } + } + + // visual inspection of sizes + histos.print(); + + // initialize CCDB *only* if efficiency correction requested + // skip if not requested, saves a bit of time + if (efficiencyFlags.applyEfficiencyCorrection) { + ccdb->setURL(ccdburl); + ccdb->setCaching(true); + ccdb->setLocalObjectValidityChecking(); + ccdb->setFatalWhenNull(false); + } + } + + // this function allows for all event selections to be done in a modular way + template + bool isisCollisionSelect(TCollision const& collision) + { + // ________________________________________________ + // Perform basic event selection + if (!collision.sel8()) { + return false; + } + if (std::abs(collision.posZ()) > masterConfigurations.zVertexCut) { + return false; + } + if (collision.centFT0M() > axisRanges[5][1] || collision.centFT0M() < axisRanges[5][0]) { + return false; + } + if (!collision.isInelGt0() && masterConfigurations.selectINELgtZERO) { + return false; + } + if (!collision.selection_bit(aod::evsel::kIsGoodITSLayersAll) && masterConfigurations.requireAllGoodITSLayers) { + return false; + } + if (zorroMask.value != "") { + auto bc = collision.template bc_as(); + initZorro(bc); + bool zorroSelected = zorro.isSelected(collision.template bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return false; + } + } + return true; + } + + // event selections in Pb-Pb + template + bool isisCollisionSelectPbPb(TCollision collision, bool fillHists) + { + if (fillHists) + histos.fill(HIST("hEventSelection"), 0.5 /* all collisions */); + + // Perform basic event selection + if (!collision.sel8()) { + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 1.5 /* collisions after sel8*/); + + if (!collision.selection_bit(aod::evsel::kIsTriggerTVX) && masterConfigurations.requireGoodTriggerTVX) { + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 2.5 /* FT0 vertex (acceptable FT0C-FT0A time difference) collisions */); + + if (std::abs(collision.posZ()) > masterConfigurations.zVertexCut) { + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 3.5 /* collisions after sel pvz sel*/); + + if (!collision.selection_bit(aod::evsel::kIsGoodITSLayersAll) && masterConfigurations.requireAllGoodITSLayers) { + // cut time intervals with dead ITS staves + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 4.5 /* collisions after cut time intervals with dead ITS staves*/); + + if (!collision.selection_bit(o2::aod::evsel::kIsGoodZvtxFT0vsPV) && masterConfigurations.requireGoodZvtxFT0vsPV) { + // removes collisions with large differences between z of PV by tracks and z of PV from FT0 A-C time difference + // use this cut at low multiplicities with caution + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 5.5 /* removes collisions with large differences between z of PV by tracks and z of PV from FT0 A-C time difference*/); + + auto occupancy = collision.trackOccupancyInTimeRange(); + if (occupancy < cfgCutOccupancyLow || occupancy > cfgCutOccupancyHigh) + return false; + if (fillHists) + histos.fill(HIST("hEventSelection"), 6.5 /* Below min occupancy and Above max occupancy*/); + + /* + if (collision.alias_bit(kTVXinTRD)) { + // TRD triggered + return false; + } + */ + + if (!collision.selection_bit(o2::aod::evsel::kNoTimeFrameBorder)) { + // reject collisions close to Time Frame borders + // O2-4623 + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 7.5 /* reject collisions close to Time Frame borders*/); + + if (!collision.selection_bit(o2::aod::evsel::kNoITSROFrameBorder)) { + // reject events affected by the ITS ROF border + // O2-4309 + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 8.5 /* reject events affected by the ITS ROF border*/); + + if (!collision.selection_bit(o2::aod::evsel::kNoSameBunchPileup)) { + // rejects collisions which are associated with the same "found-by-T0" bunch crossing + // https://indico.cern.ch/event/1396220/#1-event-selection-with-its-rof + return false; + } + if (fillHists) + histos.fill(HIST("hEventSelection"), 9.5 /* rejects collisions which are associated with the same "found-by-T0" bunch crossing*/); + return true; + } + + template + bool isValidTrigger(TTrack track, bool isLeading) + { + if (track.tpcNClsCrossedRows() < trackSelection.minTPCNCrossedRowsTrigger) { + return false; // crossed rows + } + if (!track.hasITS() && trackSelection.triggerRequireITS) { + return false; // skip, doesn't have ITS signal (skips lots of TPC-only!) + } + if (track.tpcNClsShared() > trackSelection.triggerMaxTPCSharedClusters) { + return false; // skip, has shared clusters + } + if (!(TESTBIT(track.itsClusterMap(), 0)) && trackSelection.triggerRequireL0) { + return false; // skip, doesn't have cluster in ITS L0 + } + // systematic variations: trigger DCAxy + if (std::abs(track.dcaXY()) > trackSelection.dcaXYconstant + trackSelection.dcaXYpTdep * std::abs(track.signed1Pt())) { + return false; + } + // systematic variations: trigger DCAz + if (trackSelection.requireDCAzCut && std::abs(track.dcaZ()) > trackSelection.dcaZconstant + trackSelection.dcaZpTdep * std::abs(track.signed1Pt())) { + return false; + } + if (track.pt() > axisRanges[3][1] || track.pt() < axisRanges[3][0]) { + return false; + } + if (triggerParticleCharge > 0 && track.sign() < 0) { + return false; + } + if (triggerParticleCharge < 0 && track.sign() > 0) { + return false; + } + if (useTheLeadingParticleAsTrigger && !isLeading) { + return false; + } + return true; + } + template + bool isValidAssocHadron(TTrack track) + { + if (track.tpcNClsCrossedRows() < trackSelection.minTPCNCrossedRowsAssociated) { + return false; // crossed rows + } + if (!track.hasITS() && trackSelection.assocRequireITS) { + return false; // skip, doesn't have ITS signal (skips lots of TPC-only!) + } + if (track.tpcNClsShared() > trackSelection.assocMaxTPCSharedClusters) { + return false; // skip, has shared clusters + } + if (!(TESTBIT(track.itsClusterMap(), 0)) && trackSelection.assocRequireL0) { + return false; // skip, doesn't have cluster in ITS L0 + } + // systematic variations: trigger DCAxy + if (std::abs(track.dcaXY()) > trackSelection.dcaXYconstantAssoc + trackSelection.dcaXYpTdepAssoc * std::abs(track.signed1Pt())) { + return false; + } + // systematic variations: trigger DCAz + if (trackSelection.requireDCAzCut && std::abs(track.dcaZ()) > trackSelection.dcaZconstantAssoc + trackSelection.dcaZpTdepAssoc * std::abs(track.signed1Pt())) { + return false; + } + if (track.pt() > axisRanges[2][1] || track.pt() < axisRanges[2][0]) { + return false; + } + return true; + } + + double calculateAverageDeltaPhiStar(double* trigg, double* assoc, double B) + { + double dPhiStar = 0; + double dPhiStarMean = 0; + + double dPhi = assoc[0] - trigg[0]; + double phaseProton = (-0.3 * B * assoc[2]) / (2 * assoc[1]); + double phaseTrack = (-0.3 * B * trigg[2]) / (2 * trigg[1]); + + for (double r = MinRadiusTPC; r <= MaxRadiusTPC; r += 0.05) { + dPhiStar = dPhi + std::asin(phaseProton * r) - std::asin(phaseTrack * r); + dPhiStarMean += (dPhiStar / 34); + } + + return dPhiStarMean; + } + void fillTriggerHistogram(std::shared_ptr hist, double pt, double mult, float eff, float effUncert, float purity, float purityErr) + { + int binx = hist->GetXaxis()->FindBin(pt); + int biny = hist->GetYaxis()->FindBin(mult); + float previousContent = hist->GetBinContent(binx, biny); + float previousUncert = hist->GetBinError(binx, biny); + float newContent = previousContent + purity / eff; + float newUncert = std::sqrt(previousUncert * previousUncert + std::pow(purity / eff, 2) + std::pow(purityErr / eff, 2) + std::pow(effUncert, 2) / std::pow(eff, 4)); + hist->SetBinContent(binx, biny, newContent); + hist->SetBinError(binx, biny, newUncert); + } + void fillCorrelationHistogram(std::shared_ptr hist, double binFillThn[], float etaWeight, float efficiency, float totalEffUncert, float purity, float totalPurityUncert) + { + float previousContent, previousError2, currentContent, currentError2; + int bin = hist->GetBin(binFillThn); + previousContent = hist->GetBinContent(bin); + previousError2 = hist->GetBinError2(bin); + currentContent = previousContent + etaWeight * purity / (efficiency); + currentError2 = previousError2 + std::pow(etaWeight * purity / (efficiency), 2) + std::pow(etaWeight * totalPurityUncert / (efficiency), 2) + std::pow(totalEffUncert * purity * etaWeight, 2) / std::pow(efficiency, 4); + hist->SetBinContent(bin, currentContent); + hist->SetBinError2(bin, currentError2); + } + + template + void fillCorrelationsHadron(TTriggers const& triggers, THadrons const& assocs, bool mixing, float pvz, float mult, double bField) + { + + for (auto const& triggerTrack : triggers) { + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + auto trigg = triggerTrack.template track_as(); + if (!isValidTrigger(trigg, triggerTrack.isLeading())) + continue; + + float efficiencyTrigger = 1.0f; + float efficiencyTriggerError = 0.0f; + float purityTrigger = 1.0f; + float purityTriggerError = 0.0f; + if (efficiencyFlags.applyEfficiencyForTrigger) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + efficiencyTrigger = hEfficiencyTriggerMult->Interpolate(trigg.pt(), trigg.eta(), mult); + else + efficiencyTrigger = hEfficiencyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + purityTrigger = hPurityHadronMult->Interpolate(trigg.pt(), mult); + else + purityTrigger = hPurityHadron->Interpolate(trigg.pt()); + } + if (efficiencyFlags.applyEfficiencyPropagation) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + efficiencyTriggerError = hEfficiencyUncertaintyTriggerMult->Interpolate(trigg.pt(), trigg.eta(), mult); + else + efficiencyTriggerError = hEfficiencyUncertaintyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + purityTriggerError = hPurityUncertaintyHadronMult->Interpolate(trigg.pt(), mult); + else + purityTriggerError = hPurityUncertaintyHadron->Interpolate(trigg.pt()); + } + } + if (efficiencyTrigger == 0) { // check for zero efficiency, do not apply if the case + efficiencyTrigger = 1; + efficiencyTriggerError = 0; + } + } + if (!mixing) { + if constexpr (requires { triggerTrack.extra(); }) + fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesPion")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); + else + fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesHadron")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); + } + double triggSign = trigg.sign(); + double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + for (auto const& assocTrack : assocs) { + auto assoc = assocTrack.template track_as(); + + //---] removing autocorrelations [--- + if (doAutocorrelationRejection) { + if (trigg.globalIndex() == assoc.globalIndex()) { + if constexpr (requires { assocTrack.nSigmaTPCPi(); }) + histos.fill(HIST("hNumberOfRejectedPairsPion"), 0.5); + else + histos.fill(HIST("hNumberOfRejectedPairsHadron"), 0.5); + continue; + } + } + //---] track quality check [--- + if (!isValidAssocHadron(assoc)) + continue; + if (doAssocPhysicalPrimary && !assocTrack.mcPhysicalPrimary()) { + continue; + } + float deltaphi = computeDeltaPhi(trigg.phi(), assoc.phi()); + float deltaeta = trigg.eta() - assoc.eta(); + if (masterConfigurations.doMirroringInDelataEta) { + deltaeta = std::abs(deltaeta); + } + float ptassoc = assoc.pt(); + float pttrigger = trigg.pt(); + + double assocSign = assoc.sign(); + double assocForDeltaPhiStar[] = {assoc.phi(), assoc.pt(), assocSign}; + + float etaWeight = 1.; + if (checks.doOnTheFlyFlattening) { + float preWeight = 1 - std::abs(deltaeta) / 1.6; + etaWeight = preWeight != 0 ? 1.0f / preWeight : 1.0f; + } + + // skip if basic ranges not met + if (deltaphi < axisRanges[0][0] || deltaphi > axisRanges[0][1]) + continue; + if (deltaeta < axisRanges[1][0] || deltaeta > axisRanges[1][1]) + continue; + if (ptassoc < axisRanges[2][0] || ptassoc > axisRanges[2][1]) + continue; + + float efficiency = 1; + float purity = 1.0f; + float purityUncertainty = 0.0f; + float totalEffUncert = 0.0; + float efficiencyUncertainty = 0.0f; + float totalPurityUncert = 0.0; + if (efficiencyFlags.applyEfficiencyCorrection) { + if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + efficiency = hEfficiencyPion->Interpolate(ptassoc, assoc.eta()); + if (efficiencyFlags.applyEfficiencyPropagation) + efficiencyUncertainty = hEfficiencyUncertaintyPion->Interpolate(ptassoc, assoc.eta()); + } else { + if (efficiencyFlags.applyEffAsFunctionOfMult) + efficiency = hEfficiencyHadronMult->Interpolate(ptassoc, assoc.eta(), mult); + else + efficiency = hEfficiencyHadron->Interpolate(ptassoc, assoc.eta()); + if (efficiencyFlags.applyPurityHadron) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + purity = hPurityHadronMult->Interpolate(ptassoc, mult); + else + purity = hPurityHadron->Interpolate(ptassoc); + } + if (efficiencyFlags.applyEfficiencyPropagation) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + efficiencyUncertainty = hEfficiencyUncertaintyHadronMult->Interpolate(ptassoc, assoc.eta(), mult); + else + efficiencyUncertainty = hEfficiencyUncertaintyHadron->Interpolate(ptassoc, assoc.eta()); + if (efficiencyFlags.applyPurityHadron) { + if (efficiencyFlags.applyEffAsFunctionOfMult) + purityUncertainty = hPurityUncertaintyHadronMult->Interpolate(ptassoc, mult); + else + purityUncertainty = hPurityUncertaintyHadron->Interpolate(ptassoc); + } + } + } + } + if (efficiency == 0) { // check for zero efficiency, do not apply if the case + efficiency = 1; + efficiencyUncertainty = 0.0; + } + if (efficiencyFlags.applyEfficiencyPropagation) { + totalEffUncert = std::sqrt(std::pow(efficiencyTrigger * efficiencyUncertainty, 2) + std::pow(efficiencyTriggerError * efficiency, 2)); + totalPurityUncert = std::sqrt(std::pow(purityTrigger * purityUncertainty, 2) + std::pow(purity * purityTriggerError, 2)); + } + + double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + double deltaPhiStar = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStar, bField); + if (!mixing) { + if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Pion")), binFillThn, etaWeight, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + if (triggSign == assocSign && doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Pion") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), 0.5); + } else if (doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Pion") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), -0.5); + } + } else { + if (triggSign == assocSign && doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Hadron") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), 0.5); + } else if (doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Hadron") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), -0.5); + } + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Hadron")), binFillThn, etaWeight, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + } + } else { + if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Pion")), binFillThn, 1, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Hadron")), binFillThn, 1, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + } + } + } + } + } + + // ---- OPTIMIZED (2026-09-24): daughter-track resolution, efficiency lookup and + // mass-window classification hoisted out of the trigger loop; see comment block + // originally delivered in fillCorrelationsPhi_Kstar_optimized.cxx for rationale ---- + void fillCorrelationsPhi(aod::TriggerTracks const& triggers, aod::AssocPhis const& assocs, bool mixing, bool mixingInBf, float pvz, float mult, double bField) + { + ValidCollision currentCollision; + int binMult = 0; + int nBinsMult = 0; + int nBinsVtxZ = 0; + int binVtxZ = 0; + currentCollision.pvz = pvz; + currentCollision.mult = mult; + if (mixingInBf) { + nBinsMult = histos.get(HIST("axes/hMultAxis"))->GetNbinsX(); + binMult = histos.get(HIST("axes/hMultAxis"))->GetXaxis()->FindBin(mult) - 1; + nBinsVtxZ = histos.get(HIST("axes/hVertexZAxis"))->GetNbinsX(); + binVtxZ = histos.get(HIST("axes/hVertexZAxis"))->GetXaxis()->FindBin(pvz) - 1; + } + + // --------------------------------------------------------------------- + // Pre-resolve everything that does NOT depend on the trigger, once per + // assoc candidate instead of once per (trigger, assoc) pair. + // --------------------------------------------------------------------- + struct CachedPhi { + float phi, eta, pt, mass; + float phiKplus, etaKplus, ptKplus, signKplus; + float phiKminus, etaKminus, ptKminus, signKminus; + int32_t globalIndexPos, globalIndexNeg; + float efficiency, efficiencyError; + bool isLeftBg, isSignal, isRightBg; + bool passesMcSelection; // (!doMCassociation || mcTruePhi()) && (!doAssocPhysicalPrimary || mcPhysicalPrimary()) + }; + std::vector cache; + cache.reserve(assocs.size()); + for (auto const& assocCandidate : assocs) { + auto postrack = assocCandidate.posTrackDaughter_as(); + auto negtrack = assocCandidate.negTrackDaughter_as(); + + CachedPhi c; + c.phi = assocCandidate.phi(); + c.eta = assocCandidate.eta(); + c.pt = assocCandidate.pt(); + c.mass = assocCandidate.mass(); + + c.phiKplus = postrack.phi(); + c.etaKplus = postrack.eta(); + c.ptKplus = postrack.pt(); + c.signKplus = postrack.sign(); + c.phiKminus = negtrack.phi(); + c.etaKminus = negtrack.eta(); + c.ptKminus = negtrack.pt(); + c.signKminus = negtrack.sign(); + c.globalIndexPos = postrack.globalIndex(); + c.globalIndexNeg = negtrack.globalIndex(); + + c.efficiency = 1.0f; + c.efficiencyError = 0.0f; + if (efficiencyFlags.applyEfficiencyCorrection) { + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + c.efficiency = hEfficiencyPhi->Interpolate(c.pt, c.eta); + if (efficiencyFlags.applyEfficiencyPropagation) + c.efficiencyError = hEfficiencyUncertaintyPhi->Interpolate(c.pt, c.eta); + } else { + double bin[4] = {c.pt, c.eta, c.phi, mult}; + c.efficiency = hEfficiencyPhiMultVsPhi->GetBinContent(hEfficiencyPhiMultVsPhi->GetBin(bin)); + if (efficiencyFlags.applyEfficiencyPropagation) + c.efficiencyError = hEfficiencyPhiMultVsPhi->GetBinError(hEfficiencyPhiMultVsPhi->GetBin(bin)); + } + } + + // Peak-relative classification using Phi's own mass window. + float delta = c.mass - massWindowConfigurationsPhi.peakMass; + float sig = massWindowConfigurationsPhi.sigma; + c.isLeftBg = (-massWindowConfigurationsPhi.maxBgNSigma * sig < delta && delta < -massWindowConfigurationsPhi.minBgNSigma * sig); + c.isSignal = (-massWindowConfigurationsPhi.maxPeakNSigma * sig < delta && delta < massWindowConfigurationsPhi.maxPeakNSigma * sig); + c.isRightBg = (massWindowConfigurationsPhi.minBgNSigma * sig < delta && delta < massWindowConfigurationsPhi.maxBgNSigma * sig); + + c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTruePhi()) && + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + + // Candidate-level kinematic/spectrum QA, ported from the equivalent + // block in hStrangeCorrelation.cxx (h3dSpectrum[Y], + // hEtaVsPtVsPhi[Bg]). These are registered (histos.add(...) + // above) but only filled here. Filled once per candidate per + // SAME EVENT only (never during mixing -- these are candidate QA, not + // correlation-pair QA, so filling them again per mixed pair would + // overcount each candidate by the mixing-pool depth). + // "SpectrumY" mirrors the V0 analog's rapidity cut (std::abs(rapidity) < + // ySel); AssocPhis has no exposed rapidity() accessor in this file, so + // pseudorapidity (c.eta) is used as a stand-in -- swap in a real + // rapidity() call here if/when the table exposes one. + if (!mixing) { + float nSigmaVal = delta / sig; + histos.fill(HIST("h3dPhiSpectrum"), c.pt, mult, nSigmaVal); + if (std::abs(c.eta) < ySel) { + histos.fill(HIST("h3dPhiSpectrumY"), c.pt, mult, nSigmaVal); + } + // matches the two registration branches above (TH3F vs THnF-with-mult, + // selected by efficiencyFlags.applyEffAsFunctionOfMultAndPhi) + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + if (c.isSignal) { + histos.fill(HIST("hPhiEtaVsPtVsPhi"), c.pt, c.eta, c.phi); + } + if (c.isLeftBg || c.isRightBg) { + histos.fill(HIST("hPhiEtaVsPtVsPhiBg"), c.pt, c.eta, c.phi); + } + } else { + if (c.isSignal) { + histos.fill(HIST("hPhiEtaVsPtVsPhiVsCent"), c.pt, c.eta, c.phi, mult); + } + if (c.isLeftBg || c.isRightBg) { + histos.fill(HIST("hPhiEtaVsPtVsPhiVsCentBg"), c.pt, c.eta, c.phi, mult); + } + } + } + + cache.push_back(c); + } + // --------------------------------------------------------------------- + + bool firstLoop = false; + for (auto const& triggerTrack : triggers) { + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + auto trigg = triggerTrack.track_as(); + if (!isValidTrigger(trigg, triggerTrack.isLeading())) + continue; + + float efficiencyTrigg = 1.0f; + float efficiencyTriggError = 0.0f; + float purityTrigg = 1.0f; + float purityTriggErr = 0.0; + double bintrig[4] = {trigg.pt(), trigg.eta(), trigg.phi(), mult}; + + if (efficiencyFlags.applyEfficiencyForTrigger) { + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + efficiencyTrigg = hEfficiencyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) + purityTrigg = hPurityHadron->Interpolate(trigg.pt()); + if (efficiencyFlags.applyEfficiencyPropagation) { + efficiencyTriggError = hEfficiencyUncertaintyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) + purityTriggErr = hPurityHadron->Interpolate(trigg.pt()); + } + } else { + efficiencyTrigg = hEfficiencyTriggerMultVsPhi->GetBinContent(hEfficiencyTriggerMultVsPhi->GetBin(bintrig)); + if (efficiencyFlags.applyEfficiencyPropagation) { + efficiencyTriggError = hEfficiencyTriggerMultVsPhi->GetBinError(hEfficiencyTriggerMultVsPhi->GetBin(bintrig)); + } + } + if (efficiencyTrigg == 0) { + efficiencyTrigg = 1; + efficiencyTriggError = 0; + } + } + + if (!mixing) { + fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesPhi")), trigg.pt(), mult, efficiencyTrigg, efficiencyTriggError, purityTrigg, purityTriggErr); + } + + double triggSign = trigg.sign(); + double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + + if (mixingInBf) { + currentCollision.addValidParticle(trigg.eta(), trigg.phi(), trigg.pt(), -1, efficiencyTrigg, efficiencyTriggError, -1); + if (firstLoop) + continue; + } + + for (auto const& c : cache) { + firstLoop = true; + + //---] removing autocorrelations [--- + if (doAutocorrelationRejection) { + if (trigg.globalIndex() == c.globalIndexPos || trigg.globalIndex() == c.globalIndexNeg) { + histos.fill(HIST("hNumberOfRejectedPairsPhi"), 0.5); + continue; + } + } + + float deltaphi = computeDeltaPhi(trigg.phi(), c.phi); + float deltaeta = trigg.eta() - c.eta; + if (masterConfigurations.doMirroringInDelataEta) { + deltaeta = std::abs(deltaeta); + } + float ptassoc = c.pt; + float pttrigger = trigg.pt(); + float massassoc = c.mass; + + // skip if basic ranges not met + if (deltaphi < axisRanges[0][0] || deltaphi > axisRanges[0][1]) + continue; + if (deltaeta < axisRanges[1][0] || deltaeta > axisRanges[1][1]) + continue; + if (ptassoc < axisRanges[2][0] || ptassoc > axisRanges[2][1]) + continue; + + float etaWeight = 1; + if (checks.doOnTheFlyFlattening) { + float preWeight = 1 - std::abs(deltaeta) / 1.6; + etaWeight = preWeight != 0 ? 1.0f / preWeight : 1.0f; + } + + float totalEffUncert = 0.0f; + if (efficiencyFlags.applyEfficiencyPropagation) { + totalEffUncert = std::sqrt(std::pow(efficiencyTrigg * c.efficiencyError, 2) + std::pow(efficiencyTriggError * c.efficiency, 2)); + } + + double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + + double assocForDeltaPhiStarPlus[] = {c.phiKplus, c.ptKplus, c.signKplus}; + double assocForDeltaPhiStarMinus[] = {c.phiKminus, c.ptKminus, c.signKminus}; + + if (!c.passesMcSelection) + continue; + + if (!mixing && c.isLeftBg && !masterConfigurations.fillCorrelationHistWithMass) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/LeftBg/Phi")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, +0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, -0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, +0.5); + } + } + } + if (!mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { + if (masterConfigurations.fillCorrelationHistWithMass) { + binFillThn[1] = massassoc; + } + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Phi")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, +0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, -0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, +0.5); + } + } + } + if (!mixing && c.isRightBg && !masterConfigurations.fillCorrelationHistWithMass) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/RightBg/Phi")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, +0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaKplus, -0.5); + histos.fill(HIST("sameEvent/Signal/PhiDeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaKminus, +0.5); + } + } + } + + // Mixed Event Logic buffered in ValidCollision + if (mixing && c.isLeftBg) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 0, c.efficiency, c.efficiencyError, 0 /* 0 = Phi type */); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Phi")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + if (mixing && c.isSignal) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 0); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + if (mixing && c.isRightBg) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 2, c.efficiency, c.efficiencyError, 0); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Phi")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + } + } + + // Process Buffer for Mixed Events -- unchanged from original. + if (!mixingInBf || binVtxZ < 0 || binVtxZ > nBinsVtxZ - 1 || binMult < 0 || binMult > nBinsMult - 1) + return; + + int binnumb = binMult * nBinsVtxZ + binVtxZ; + int hastirgorassoc = masterConfigurations.collisionHasTriggOrAssoc; + if ((hastirgorassoc == 1 && currentCollision.trigParticles.empty()) || + (hastirgorassoc == 2 && currentCollision.assocParticles.empty()) || + (hastirgorassoc == 3 && currentCollision.trigParticles.empty() && currentCollision.assocParticles.empty()) || + (hastirgorassoc == 4 && (currentCollision.trigParticles.empty() || currentCollision.assocParticles.empty()))) + return; + + for (const auto& collision : validCollisions[binnumb]) { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision.pvz, collision.mult})); + + for (const auto& trigger : collision.trigParticles) { + for (const auto& assoc : currentCollision.assocParticles) { + float deltaeta = trigger.eta - assoc.eta; + float deltaphi = computeDeltaPhi(trigger.phi, assoc.phi); + float efficiencyTrigg = trigger.efficiency; + float efficiencyAssoc = assoc.efficiency; + float efficiencyTriggError = trigger.efficiencyError; + float efficiencyAssocError = assoc.efficiencyError; + float totalEffUncert = 0.0; + float ptassoc = assoc.pt; + float pttrigger = trigger.pt; + + if (efficiencyFlags.applyEfficiencyPropagation) { + totalEffUncert = std::sqrt(std::pow(efficiencyTrigg * efficiencyAssocError, 2) + std::pow(efficiencyTriggError * efficiencyAssoc, 2)); + } + if (masterConfigurations.doMirroringInDelataEta) { + deltaeta = std::abs(deltaeta); + } + double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + + if (assoc.region == 0) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + if (assoc.region == 1) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + if (assoc.region == 2) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + } + } + } + + if (validCollisions[binnumb].size() >= static_cast(masterConfigurations.mixingParameter)) { + validCollisions[binnumb].erase(validCollisions[binnumb].begin()); + } + if (!currentCollision.trigParticles.empty()) + validCollisions[binnumb].push_back(currentCollision); + } + + // ============================================================================ + // fillCorrelationsKstar -- identical restructuring, mirrors fillCorrelationsPhi + // exactly (K*0 has no separate LeftBg/Signal/RightBg physics difference from + // Phi in this file -- same three-region sideband pattern, same daughter-pair + // caching opportunity). mcTruePhi() -> mcTrueKstar() is the only selection + // difference, and hEfficiencyPhi* -> hEfficiencyKstar* for the efficiency + // histograms. + // ============================================================================ + + void fillCorrelationsKstar(aod::TriggerTracks const& triggers, aod::AssocKstars const& assocs, bool mixing, bool mixingInBf, float pvz, float mult, double bField) + { + ValidCollision currentCollision; + int binMult = 0; + int nBinsMult = 0; + int nBinsVtxZ = 0; + int binVtxZ = 0; + currentCollision.pvz = pvz; + currentCollision.mult = mult; + if (mixingInBf) { + nBinsMult = histos.get(HIST("axes/hMultAxis"))->GetNbinsX(); + binMult = histos.get(HIST("axes/hMultAxis"))->GetXaxis()->FindBin(mult) - 1; + nBinsVtxZ = histos.get(HIST("axes/hVertexZAxis"))->GetNbinsX(); + binVtxZ = histos.get(HIST("axes/hVertexZAxis"))->GetXaxis()->FindBin(pvz) - 1; + } + + // --------------------------------------------------------------------- + // Same pre-pass as fillCorrelationsPhi -- this matters most for K*0, + // since assocKstars typically has far more candidates per event than + // assocPhis, so the N_trig-fold redundant daughter-track resolution is + // multiplied by a much bigger candidate count here than for Phi. + // --------------------------------------------------------------------- + struct CachedKstar { + float phi, eta, pt, mass; + float phiPos, etaPos, ptPos, signPos; + float phiNeg, etaNeg, ptNeg, signNeg; + int32_t globalIndexPos, globalIndexNeg; + float efficiency, efficiencyError; + bool isLeftBg, isSignal, isRightBg; + bool passesMcSelection; // (!doMCassociation || mcTrueKstar()) && (!doAssocPhysicalPrimary || mcPhysicalPrimary()) + }; + std::vector cache; + cache.reserve(assocs.size()); + for (auto const& assocCandidate : assocs) { + auto postrack = assocCandidate.posTrackDaughter_as(); + auto negtrack = assocCandidate.negTrackDaughter_as(); + + CachedKstar c; + c.phi = assocCandidate.phi(); + c.eta = assocCandidate.eta(); + c.pt = assocCandidate.pt(); + c.mass = assocCandidate.mass(); + + c.phiPos = postrack.phi(); + c.etaPos = postrack.eta(); + c.ptPos = postrack.pt(); + c.signPos = postrack.sign(); + c.phiNeg = negtrack.phi(); + c.etaNeg = negtrack.eta(); + c.ptNeg = negtrack.pt(); + c.signNeg = negtrack.sign(); + c.globalIndexPos = postrack.globalIndex(); + c.globalIndexNeg = negtrack.globalIndex(); + + c.efficiency = 1.0f; + c.efficiencyError = 0.0f; + if (efficiencyFlags.applyEfficiencyCorrection) { + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + c.efficiency = hEfficiencyKstar->Interpolate(c.pt, c.eta); + if (efficiencyFlags.applyEfficiencyPropagation) + c.efficiencyError = hEfficiencyUncertaintyKstar->Interpolate(c.pt, c.eta); + } else { + double bin[4] = {c.pt, c.eta, c.phi, mult}; + c.efficiency = hEfficiencyKstarMultVsPhi->GetBinContent(hEfficiencyKstarMultVsPhi->GetBin(bin)); + if (efficiencyFlags.applyEfficiencyPropagation) + c.efficiencyError = hEfficiencyKstarMultVsPhi->GetBinError(hEfficiencyKstarMultVsPhi->GetBin(bin)); + } + } + + // Peak-relative classification using K*0's own mass window. + float delta = c.mass - massWindowConfigurationsKstar.peakMass; + float sig = massWindowConfigurationsKstar.sigma; + c.isLeftBg = (-massWindowConfigurationsKstar.maxBgNSigma * sig < delta && delta < -massWindowConfigurationsKstar.minBgNSigma * sig); + c.isSignal = (-massWindowConfigurationsKstar.maxPeakNSigma * sig < delta && delta < massWindowConfigurationsKstar.maxPeakNSigma * sig); + c.isRightBg = (massWindowConfigurationsKstar.minBgNSigma * sig < delta && delta < massWindowConfigurationsKstar.maxBgNSigma * sig); + + c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTrueKstar()) && + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + + // Same candidate-level Spectrum/EtaVsPtVsPhi QA as fillCorrelationsPhi + // above -- see the comment there for rationale. + if (!mixing) { + float nSigmaVal = delta / sig; + histos.fill(HIST("h3dKstar0Spectrum"), c.pt, mult, nSigmaVal); + if (std::abs(c.eta) < ySel) { + histos.fill(HIST("h3dKstar0SpectrumY"), c.pt, mult, nSigmaVal); + } + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + if (c.isSignal) { + histos.fill(HIST("hKstar0EtaVsPtVsPhi"), c.pt, c.eta, c.phi); + } + if (c.isLeftBg || c.isRightBg) { + histos.fill(HIST("hKstar0EtaVsPtVsPhiBg"), c.pt, c.eta, c.phi); + } + } else { + if (c.isSignal) { + histos.fill(HIST("hKstar0EtaVsPtVsPhiVsCent"), c.pt, c.eta, c.phi, mult); + } + if (c.isLeftBg || c.isRightBg) { + histos.fill(HIST("hKstar0EtaVsPtVsPhiVsCentBg"), c.pt, c.eta, c.phi, mult); + } + } + } + + cache.push_back(c); + } + // --------------------------------------------------------------------- + + bool firstLoop = false; + for (auto const& triggerTrack : triggers) { + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + auto trigg = triggerTrack.track_as(); + if (!isValidTrigger(trigg, triggerTrack.isLeading())) + continue; + + float efficiencyTrigg = 1.0f; + float efficiencyTriggError = 0.0f; + float purityTrigg = 1.0f; + float purityTriggErr = 0.0; + double bintrig[4] = {trigg.pt(), trigg.eta(), trigg.phi(), mult}; + + if (efficiencyFlags.applyEfficiencyForTrigger) { + if (!efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + efficiencyTrigg = hEfficiencyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) + purityTrigg = hPurityHadron->Interpolate(trigg.pt()); + if (efficiencyFlags.applyEfficiencyPropagation) { + efficiencyTriggError = hEfficiencyUncertaintyTrigger->Interpolate(trigg.pt(), trigg.eta()); + if (efficiencyFlags.applyPurityTrigger) + purityTriggErr = hPurityHadron->Interpolate(trigg.pt()); + } + } else { + efficiencyTrigg = hEfficiencyTriggerMultVsPhi->GetBinContent(hEfficiencyTriggerMultVsPhi->GetBin(bintrig)); + if (efficiencyFlags.applyEfficiencyPropagation) { + efficiencyTriggError = hEfficiencyTriggerMultVsPhi->GetBinError(hEfficiencyTriggerMultVsPhi->GetBin(bintrig)); + } + } + if (efficiencyTrigg == 0) { + efficiencyTrigg = 1; + efficiencyTriggError = 0; + } + } + + if (!mixing) { + fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesKstar0")), trigg.pt(), mult, efficiencyTrigg, efficiencyTriggError, purityTrigg, purityTriggErr); + } + + double triggSign = trigg.sign(); + double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + + if (mixingInBf) { + currentCollision.addValidParticle(trigg.eta(), trigg.phi(), trigg.pt(), -1, efficiencyTrigg, efficiencyTriggError, -1); + if (firstLoop) + continue; + } + + for (auto const& c : cache) { + firstLoop = true; + + //---] removing autocorrelations [--- + if (doAutocorrelationRejection) { + if (trigg.globalIndex() == c.globalIndexPos || trigg.globalIndex() == c.globalIndexNeg) { + histos.fill(HIST("hNumberOfRejectedPairsKstar0"), 0.5); + continue; + } + } + + float deltaphi = computeDeltaPhi(trigg.phi(), c.phi); + float deltaeta = trigg.eta() - c.eta; + if (masterConfigurations.doMirroringInDelataEta) { + deltaeta = std::abs(deltaeta); + } + float ptassoc = c.pt; + float pttrigger = trigg.pt(); + float massassoc = c.mass; + + // skip if basic ranges not met + if (deltaphi < axisRanges[0][0] || deltaphi > axisRanges[0][1]) + continue; + if (deltaeta < axisRanges[1][0] || deltaeta > axisRanges[1][1]) + continue; + if (ptassoc < axisRanges[2][0] || ptassoc > axisRanges[2][1]) + continue; + + float etaWeight = 1; + if (checks.doOnTheFlyFlattening) { + float preWeight = 1 - std::abs(deltaeta) / 1.6; + etaWeight = preWeight != 0 ? 1.0f / preWeight : 1.0f; + } + + float totalEffUncert = 0.0f; + if (efficiencyFlags.applyEfficiencyPropagation) { + totalEffUncert = std::sqrt(std::pow(efficiencyTrigg * c.efficiencyError, 2) + std::pow(efficiencyTriggError * c.efficiency, 2)); + } + + double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + + double assocForDeltaPhiStarPlus[] = {c.phiPos, c.ptPos, c.signPos}; + double assocForDeltaPhiStarMinus[] = {c.phiNeg, c.ptNeg, c.signNeg}; + + if (!c.passesMcSelection) + continue; + + if (!mixing && c.isLeftBg && !masterConfigurations.fillCorrelationHistWithMass) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/LeftBg/Kstar0")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, +0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, -0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, +0.5); + } + } + } + if (!mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { + if (masterConfigurations.fillCorrelationHistWithMass) { + binFillThn[1] = massassoc; + } + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Kstar0")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, +0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, -0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, +0.5); + } + } + } + if (!mixing && c.isRightBg && !masterConfigurations.fillCorrelationHistWithMass) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/RightBg/Kstar0")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + if (doDeltaPhiStarCheck) { + double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); + double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); + if (triggSign > 0) { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, +0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, -0.5); + } else { + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarPlus, trigg.eta() - c.etaPos, -0.5); + histos.fill(HIST("sameEvent/Signal/Kstar0DeltaPhiStar"), deltaPhiStarMinus, trigg.eta() - c.etaNeg, +0.5); + } + } + } + + // Mixed-event logic: buffered into ValidCollision when mixing via the + // collision pool (processMixedEventHKstarsInBuffer), filled directly + // otherwise (processMixedEventHKstars). + if (mixing && c.isLeftBg) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 0, c.efficiency, c.efficiencyError, 1 /* 1 = Kstar0 type */); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Kstar0")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + if (mixing && c.isSignal) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 1); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + if (mixing && c.isRightBg) { + if (mixingInBf) { + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 2, c.efficiency, c.efficiencyError, 1); + } else { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Kstar0")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + } + } + } + } + + // Process buffer for mixed events (mirrors fillCorrelationsPhi; reached + // when called from processMixedEventHKstarsInBuffer with mixingInBf=true) + if (!mixingInBf || binVtxZ < 0 || binVtxZ > nBinsVtxZ - 1 || binMult < 0 || binMult > nBinsMult - 1) + return; + + int binnumb = binMult * nBinsVtxZ + binVtxZ; + int hastirgorassoc = masterConfigurations.collisionHasTriggOrAssoc; + if ((hastirgorassoc == 1 && currentCollision.trigParticles.empty()) || + (hastirgorassoc == 2 && currentCollision.assocParticles.empty()) || + (hastirgorassoc == 3 && currentCollision.trigParticles.empty() && currentCollision.assocParticles.empty()) || + (hastirgorassoc == 4 && (currentCollision.trigParticles.empty() || currentCollision.assocParticles.empty()))) + return; + + for (const auto& collision : validCollisions[binnumb]) { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision.pvz, collision.mult})); + + for (const auto& trigger : collision.trigParticles) { + for (const auto& assoc : currentCollision.assocParticles) { + float deltaeta = trigger.eta - assoc.eta; + float deltaphi = computeDeltaPhi(trigger.phi, assoc.phi); + float efficiencyTrigg = trigger.efficiency; + float efficiencyAssoc = assoc.efficiency; + float efficiencyTriggError = trigger.efficiencyError; + float efficiencyAssocError = assoc.efficiencyError; + float totalEffUncert = 0.0; + float ptassoc = assoc.pt; + float pttrigger = trigger.pt; + + if (efficiencyFlags.applyEfficiencyPropagation) { + totalEffUncert = std::sqrt(std::pow(efficiencyTrigg * efficiencyAssocError, 2) + std::pow(efficiencyTriggError * efficiencyAssoc, 2)); + } + if (masterConfigurations.doMirroringInDelataEta) { + deltaeta = std::abs(deltaeta); + } + double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + + if (assoc.region == 0) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + if (assoc.region == 1) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + if (assoc.region == 2) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + } + } + } + } + + if (validCollisions[binnumb].size() >= static_cast(masterConfigurations.mixingParameter)) { + validCollisions[binnumb].erase(validCollisions[binnumb].begin()); + } + if (!currentCollision.trigParticles.empty()) + validCollisions[binnumb].push_back(currentCollision); + } + + double getMagneticField(uint64_t timestamp) + { + static parameters::GRPMagField* grpo = nullptr; + if (grpo == nullptr) { + grpo = ccdb->getForTimeStamp("GLO/Config/GRPMagField", timestamp); + if (grpo == nullptr) { + LOGF(fatal, "GRP object not found for timestamp %llu", timestamp); + return 0; + } + LOGF(info, "Retrieved GRP for timestamp %llu with magnetic field of %d kG", timestamp, grpo->getNominalL3Field()); + } + + return 0.1 * (grpo->getNominalL3Field()); // 1 T = 10 kG + } + // if this process function is enabled, it will be such that only events with trigger particles within a given + // trigger pt bin are taken for the entire processing. This allows for the calculation of e.g. efficiencies + // within an event class that has a trigger (which may differ with respect to other cases, to be checked) + + // for map determining which trigger bins are present and which aren't + std::vector triggerPresenceMap; + + void processSelectEventWithTrigger(soa::Join const& collisions, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + // setup + triggerPresenceMap.clear(); + triggerPresenceMap.resize(collisions.size(), 0); + + for (auto const& collision : collisions) { + // ________________________________________________ + // Perform basic event selection + if (!isisCollisionSelect(collision)) { + continue; + } + + // do not forget to re-group ... + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision.globalIndex()); + + for (auto const& triggerTrack : slicedTriggerTracks) { + auto track = triggerTrack.track_as(); + if (!isValidTrigger(track, triggerTrack.isLeading())) { + continue; + } + auto binNumber = histos.get(HIST("axes/hPtTriggerAxis"))->FindFixBin(track.pt()) - 1; + SETBIT(triggerPresenceMap[collision.globalIndex()], binNumber); + } + } + } + + void processSameEventHHadrons(soa::Join::iterator const& collision, aod::AssocHadrons const& assocHadrons, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + LOGF(info, "SameEventHadron: collisions=%d triggers=%zu assocHadrons=%zu", collision.globalIndex(), triggerTracks.size(), assocHadrons.size()); + + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; // true is for 'ignore overflows' (true by default). Underflows and overflows will have bin -1. + + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(triggerPresenceMap[collision.globalIndex()], triggerBinToSelect)) { + return; + } + + auto bc = collision.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + // ________________________________________________ + // Perform basic event selection + if (!isisCollisionSelect(collision)) { + return; + } + // ________________________________________________ + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions && doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); + histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); + histos.fill(HIST("EventQA/hPvz"), collision.posZ()); + } + + // Do basic QA + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions) { + for (auto const& triggerTrack : triggerTracks) { + auto track = triggerTrack.track_as(); + if (!isValidTrigger(track, triggerTrack.isLeading())) + continue; + histos.fill(HIST("hDCAzTriggerHadron"), track.dcaZ(), track.pt()); + histos.fill(HIST("hDCAxyTriggerHadron"), track.dcaXY(), track.pt()); + float efficiency = 1.0f; + if (efficiencyFlags.applyEfficiencyCorrection) { + efficiency = hEfficiencyTrigger->Interpolate(track.pt(), track.eta()); + } + if (efficiency == 0) { // check for zero efficiency, do not apply if the case + efficiency = 1; + } + float weight = efficiencyFlags.applyEfficiencyCorrection ? 1. / efficiency : 1.0f; + histos.fill(HIST("hTriggerAllSelectedEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTriggerPtResolution"), track.pt(), triggerTrack.mcOriginalPt()); + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + histos.fill(HIST("hTriggerPrimaryEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTrackEtaVsPtVsPhi"), track.pt(), track.eta(), track.phi(), weight); + } + } + for (auto const& assocTrack : assocHadrons) { + auto assoc = assocTrack.track_as(); + if (!isValidAssocHadron(assoc)) + continue; + float efficiency = 1.0f; + float purity = 1.0f; + histos.fill(HIST("hDCAzAssociatedHadron"), assoc.dcaZ(), assoc.pt()); + histos.fill(HIST("hDCAxyAssociatedHadron"), assoc.dcaXY(), assoc.pt()); + if (efficiencyFlags.applyEfficiencyCorrection) { + efficiency = hEfficiencyHadron->Interpolate(assoc.pt(), assoc.eta()); + if (efficiencyFlags.applyPurityHadron) + purity = hPurityHadron->Interpolate(assoc.pt()); + } + if (efficiency == 0) { // check for zero efficiency, do not apply if the case + efficiency = 1; + } + float weight = efficiencyFlags.applyEfficiencyCorrection ? purity / efficiency : 1.0f; + histos.fill(HIST("hAssocHadronsAllSelectedEtaVsPt"), assoc.pt(), assoc.eta(), collision.centFT0M(), weight); + histos.fill(HIST("hAssocPtResolution"), assoc.pt(), assocTrack.mcOriginalPt()); + if (doAssocPhysicalPrimary && !assocTrack.mcPhysicalPrimary()) + continue; + histos.fill(HIST("hAssocPrimaryEtaVsPt"), assoc.pt(), assoc.eta(), collision.centFT0M()); + histos.fill(HIST("hAsssocTrackEtaVsPtVsPhi"), assoc.pt(), assoc.eta(), assoc.phi(), weight); + } + + // ________________________________________________ + // Do hadron - hadron correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(triggerTracks, assocHadrons, false, collision.posZ(), collision.centFT0M(), bField); + } + + void processSameEventHPions(soa::Join::iterator const& collision, soa::Join const& associatedPions, soa::Join const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + LOGF(info, "SameEventPion: collisions=%d triggers=%zu assocPions=%zu", collision.globalIndex(), triggerTracks.size(), associatedPions.size()); + + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(triggerPresenceMap[collision.globalIndex()], triggerBinToSelect)) { + return; + } + auto bc = collision.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + // ________________________________________________ + // Perform basic event selection + if (!isisCollisionSelect(collision)) { + return; + } + // ________________________________________________ + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); + histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); + histos.fill(HIST("EventQA/hPvz"), collision.posZ()); + } + // Do basic QA + for (auto const& pion : associatedPions) { + auto pionTrack = pion.track_as(); + if (!isValidAssocHadron(pionTrack)) + continue; + + histos.fill(HIST("hPionEtaVsPtAllSelected"), pionTrack.pt(), pionTrack.eta(), collision.centFT0M()); + if (doAssocPhysicalPrimary && !pion.mcPhysicalPrimary()) + continue; + if (masterConfigurations.doMCassociation && std::abs(pion.pdgCode()) != PdgCodes[IndexPion]) + continue; + histos.fill(HIST("hPionEtaVsPt"), pionTrack.pt(), pionTrack.eta(), collision.centFT0M()); + if (pionTrack.sign() > 0) + histos.fill(HIST("hPositivePionEtaVsPt"), pionTrack.pt(), pionTrack.eta(), collision.centFT0M()); + else + histos.fill(HIST("hNegativePionEtaVsPt"), pionTrack.pt(), pionTrack.eta(), collision.centFT0M()); + } + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars) { + for (auto const& triggerTrack : triggerTracks) { + auto track = triggerTrack.track_as(); + if (!isValidTrigger(track, triggerTrack.isLeading())) + continue; + histos.fill(HIST("hTriggerAllSelectedEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTriggerPtResolution"), track.pt(), triggerTrack.mcOriginalPt()); + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + histos.fill(HIST("hTriggerPrimaryEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTrackEtaVsPtVsPhi"), track.pt(), track.eta(), track.phi()); + } + } + + // ________________________________________________ + // Do hadron - Pion correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(triggerTracks, associatedPions, false, collision.posZ(), collision.centFT0M(), bField); + } + + void processSameEventHPhis(soa::Join::iterator const& collision, aod::AssocPhis const& associatedPhis, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + LOGF(info, "SameEventPhi: collisions=%d triggers=%zu assocPhi=%zu", collision.globalIndex(), triggerTracks.size(), associatedPhis.size()); + + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + + // Skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(triggerPresenceMap[collision.globalIndex()], triggerBinToSelect)) { + return; + } + + auto bc = collision.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + if (!isisCollisionSelect(collision)) { + return; + } + + if (doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); + histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); + histos.fill(HIST("EventQA/hPvz"), collision.posZ()); + } + + // ----------------------------- + // Trigger QA + // ----------------------------- + for (auto const& triggerTrack : triggerTracks) { + auto track = triggerTrack.track_as(); + + if (!isValidTrigger(track, triggerTrack.isLeading())) { + continue; + } + + histos.fill(HIST("hTriggerAllSelectedEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + + histos.fill(HIST("hTriggerPtResolution"), track.pt(), triggerTrack.mcOriginalPt()); + + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) { + continue; + } + + histos.fill(HIST("hTriggerPrimaryEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + + histos.fill(HIST("hTrackEtaVsPtVsPhi"), track.pt(), track.eta(), track.phi()); + } + + // ----------------------------- + // h-phi correlation + // ----------------------------- + fillCorrelationsPhi(triggerTracks, associatedPhis, false, false, collision.posZ(), collision.centFT0M(), bField); + } + + void processSameEventHKstars(soa::Join::iterator const& collision, aod::AssocKstars const& associatedKstars, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + LOGF(info, "SameEventKstar: collisions=%d triggers=%zu assocKstar=%zu", collision.globalIndex(), triggerTracks.size(), associatedKstars.size()); + + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + + // Skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(triggerPresenceMap[collision.globalIndex()], triggerBinToSelect)) { + return; + } + + auto bc = collision.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + if (!isisCollisionSelect(collision)) { + return; + } + + if (doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); + histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); + histos.fill(HIST("EventQA/hPvz"), collision.posZ()); + } + + // ----------------------------- + // Trigger QA + // ----------------------------- + for (auto const& triggerTrack : triggerTracks) { + auto track = triggerTrack.track_as(); + + if (!isValidTrigger(track, triggerTrack.isLeading())) { + continue; + } + + histos.fill(HIST("hTriggerAllSelectedEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + + histos.fill(HIST("hTriggerPtResolution"), track.pt(), triggerTrack.mcOriginalPt()); + + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) { + continue; + } + + histos.fill(HIST("hTriggerPrimaryEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + + histos.fill(HIST("hTrackEtaVsPtVsPhi"), track.pt(), track.eta(), track.phi()); + } + + // ----------------------------- + // h-K*0 correlation + // ----------------------------- + fillCorrelationsKstar(triggerTracks, associatedKstars, false, false, collision.posZ(), collision.centFT0M(), bField); + } + + void + processMixedEventHHadrons(soa::Join const& collisions, aod::AssocHadrons const& assocHadrons, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + for (auto const& [collision1, collision2] : soa::selfCombinations(colBinning, masterConfigurations.mixingParameter, -1, collisions, collisions)) { + auto bc = collision1.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + // ________________________________________________ + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && (!TESTBIT(triggerPresenceMap[collision1.globalIndex()], triggerBinToSelect) || !TESTBIT(triggerPresenceMap[collision2.globalIndex()], triggerBinToSelect))) { + return; + } + + // ________________________________________________ + // Perform basic event selection on both collisions + if (!isisCollisionSelect(collision1) || !isisCollisionSelect(collision2)) { + continue; + } + if (collision1.centFT0M() > axisRanges[5][1] || collision1.centFT0M() < axisRanges[5][0]) + continue; + if (collision2.centFT0M() > axisRanges[5][1] || collision2.centFT0M() < axisRanges[5][0]) + continue; + if (doMixingQAandEventQA) { + if (collision1.globalIndex() == collision2.globalIndex()) { + histos.fill(HIST("MixingQA/hMixingQA"), 0.0f); // same-collision pair counting + } + histos.fill(HIST("MixingQA/hMEpvz1"), collision1.posZ()); + histos.fill(HIST("MixingQA/hMEpvz2"), collision2.posZ()); + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision1.posZ(), collision1.centFT0M()})); + } + // ________________________________________________ + // Do slicing + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision1.globalIndex()); + auto slicedAssocHadrons = assocHadrons.sliceBy(collisionSliceHadrons, collision2.globalIndex()); + // ________________________________________________ + // Do hadron - hadron correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(slicedTriggerTracks, slicedAssocHadrons, true, collision1.posZ(), collision1.centFT0M(), bField); + } + } + + void processMixedEventHPions(soa::Join const& collisions, soa::Join const& assocPions, soa::Join const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + for (auto const& [collision1, collision2] : soa::selfCombinations(colBinning, masterConfigurations.mixingParameter, -1, collisions, collisions)) { + auto bc = collision1.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + // ________________________________________________ + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && (!TESTBIT(triggerPresenceMap[collision1.globalIndex()], triggerBinToSelect) || !TESTBIT(triggerPresenceMap[collision2.globalIndex()], triggerBinToSelect))) { + continue; + } + + // ________________________________________________ + // Perform basic event selection on both collisions + if (!isisCollisionSelect(collision1) || !isisCollisionSelect(collision2)) { + continue; + } + if (collision1.centFT0M() > axisRanges[5][1] || collision1.centFT0M() < axisRanges[5][0]) + continue; + if (collision2.centFT0M() > axisRanges[5][1] || collision2.centFT0M() < axisRanges[5][0]) + continue; + if (doMixingQAandEventQA) { + if (collision1.globalIndex() == collision2.globalIndex()) { + histos.fill(HIST("MixingQA/hMixingQA"), 0.0f); // same-collision pair counting + } + histos.fill(HIST("MixingQA/hMEpvz1"), collision1.posZ()); + histos.fill(HIST("MixingQA/hMEpvz2"), collision2.posZ()); + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision1.posZ(), collision1.centFT0M()})); + } + // ________________________________________________ + // Do slicing + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision1.globalIndex()); + auto slicedAssocPions = assocPions.sliceBy(collisionSliceHadrons, collision2.globalIndex()); + // ________________________________________________ + // Do hadron - phi correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(slicedTriggerTracks, slicedAssocPions, true, collision1.posZ(), collision1.centFT0M(), bField); + } + } + + void processMixedEventHPhis(soa::Join const& collisions, aod::AssocPhis const& assocPhis, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + + for (auto const& [collision1, collision2] : soa::selfCombinations(colBinning, masterConfigurations.mixingParameter, -1, collisions, collisions)) { + + auto bc = collision1.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + if (triggerPresenceMap.size() > 0 && (!TESTBIT(triggerPresenceMap[collision1.globalIndex()], triggerBinToSelect) || !TESTBIT(triggerPresenceMap[collision2.globalIndex()], triggerBinToSelect))) { + continue; + } + + if (!isisCollisionSelect(collision1) || !isisCollisionSelect(collision2)) { + continue; + } + + if (collision1.centFT0M() > axisRanges[5][1] || collision1.centFT0M() < axisRanges[5][0]) { + continue; + } + + if (collision2.centFT0M() > axisRanges[5][1] || collision2.centFT0M() < axisRanges[5][0]) { + continue; + } + + if (doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hMEpvz1"), collision1.posZ()); + histos.fill(HIST("MixingQA/hMEpvz2"), collision2.posZ()); + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision1.posZ(), collision1.centFT0M()})); + } + + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision1.globalIndex()); + + auto slicedAssocPhis = assocPhis.sliceBy(collisionSlicePhis, collision2.globalIndex()); + + if (masterConfigurations.doFullCorrelationStudy) { + // Use the per-collision slices computed above, not the full unsliced + // triggerTracks/assocPhis tables -- passing the whole dataframe here + // would mix every trigger with every candidate in the TF regardless + // of which two collisions are actually being paired. + fillCorrelationsPhi(slicedTriggerTracks, slicedAssocPhis, true, false, collision1.posZ(), collision1.centFT0M(), bField); + } + } + } + + void processMixedEventHKstars(soa::Join const& collisions, aod::AssocKstars const& assocKstars, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + + for (auto const& [collision1, collision2] : soa::selfCombinations(colBinning, masterConfigurations.mixingParameter, -1, collisions, collisions)) { + + auto bc = collision1.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + if (triggerPresenceMap.size() > 0 && (!TESTBIT(triggerPresenceMap[collision1.globalIndex()], triggerBinToSelect) || !TESTBIT(triggerPresenceMap[collision2.globalIndex()], triggerBinToSelect))) { + continue; + } + + if (!isisCollisionSelect(collision1) || !isisCollisionSelect(collision2)) { + continue; + } + + if (collision1.centFT0M() > axisRanges[5][1] || collision1.centFT0M() < axisRanges[5][0]) { + continue; + } + + if (collision2.centFT0M() > axisRanges[5][1] || collision2.centFT0M() < axisRanges[5][0]) { + continue; + } + + if (doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hMEpvz1"), collision1.posZ()); + histos.fill(HIST("MixingQA/hMEpvz2"), collision2.posZ()); + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision1.posZ(), collision1.centFT0M()})); + } + + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision1.globalIndex()); + + auto slicedAssocKstars = assocKstars.sliceBy(collisionSliceKstars, collision2.globalIndex()); + + if (masterConfigurations.doFullCorrelationStudy) { + // Same reasoning as processMixedEventHPhis above -- use this collision + // pair's own slices, not the full unsliced triggerTracks/assocKstars. + fillCorrelationsKstar(slicedTriggerTracks, slicedAssocKstars, true, false, collision1.posZ(), collision1.centFT0M(), bField); + } + } + } + + void processMCGenerated(aod::McCollision const& /*mcCollision*/, soa::SmallGroups> const& collisions, aod::McParticles const& mcParticles) + { + histos.fill(HIST("hClosureTestEventCounter"), 2.5f); + + //--------------------------------------------- + // Generated particles before event selection + //--------------------------------------------- + + for (auto const& mcParticle : mcParticles) { + + if (std::abs(mcParticle.eta()) > etaSel) + continue; + + float pt = mcParticle.pt(); + + // trigger particles + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + histos.fill(HIST("hGeneratedQAPtTrigger"), pt, 0.0f); + } + } + + // generated phi + + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi && masterConfigurations.doCorrelationPhi) { + + // if (!doAssocPhysicalPrimaryInGen || + // mcParticle.isPhysicalPrimary()) { + + histos.fill(HIST("hGeneratedQAPtAssociatedPhi"), pt, 0.0f); + + //} + } + } + + for (auto const& mcParticle : mcParticles) { + + // phi + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi) { + if (efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.fill(HIST("Generated/hPhi"), mcParticle.pt(), mcParticle.eta(), mcParticle.phi(), 1.0); + } else { + histos.fill(HIST("Generated/hPhi"), mcParticle.pt(), mcParticle.eta(), 1.0); + } + } + + if (doAssocPhysicalPrimaryInGen && !mcParticle.isPhysicalPrimary()) + continue; + // NOTE: intentionally restricted to Pion only (IndexPion..IndexPion); Kstar0 is + // not extended to processMCGenerated/ClosureTest/Prediction in this version. + static_for([&](auto i) { + constexpr int Index = i.value; + if (i == IndexPion && mcParticle.pdgCode() > Neutral) { + histos.fill(HIST("Generated/hPositive") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), 1); + } else if (i == IndexPion && mcParticle.pdgCode() < Neutral) { + histos.fill(HIST("Generated/hNegative") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), 1); + } else if (mcParticle.pdgCode() == PdgCodes[i]) { + if (efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.fill(HIST("Generated/h") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), mcParticle.phi(), 1); + } else { + histos.fill(HIST("Generated/h") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), 1); + } + } + }); + } + + //--------------------------------------------- + // Require reconstructed collision + //--------------------------------------------- + + if (collisions.size() < 1) + return; + + int biggestNContribs = -1; + float bestCollisionVtxZ = 0.f; + float bestCollisionFT0Mpercentile = -1.f; + float bestCollisionFT0Cpercentile = -1.f; + bool bestCollisionSel8 = false; + bool bestCollisionINELgtZERO = false; + bool isCollisionSelect = false; + uint32_t bestCollisionTriggerPresenceMap = 0; + + for (auto const& collision : collisions) { + + if (collision.numContrib() <= biggestNContribs) + continue; + + biggestNContribs = collision.numContrib(); + + bestCollisionFT0Mpercentile = collision.centFT0M(); + bestCollisionFT0Cpercentile = collision.centFT0C(); + + if (masterConfigurations.applyNewMCSelection) { + isCollisionSelect = (masterConfigurations.doPPAnalysis && isisCollisionSelect(collision)) || (!masterConfigurations.doPPAnalysis && isisCollisionSelectPbPb(collision, false)); + } else { + bestCollisionSel8 = collision.sel8(); + bestCollisionVtxZ = collision.posZ(); + bestCollisionINELgtZERO = collision.isInelGt0(); + } + + if (triggerPresenceMap.size() > 0) + bestCollisionTriggerPresenceMap = triggerPresenceMap[collision.globalIndex()]; + } + + if (collisions.size() > 1) { + for (auto const& mcParticle : mcParticles) { + + if (std::abs(mcParticle.y()) > ySel) + continue; + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi) { + histos.fill(HIST("GeneratedWithPV/hPhi") + HIST("_MidYVsMult_TwoPVsOrMore"), mcParticle.pt(), bestCollisionFT0Mpercentile); + } + + if (doAssocPhysicalPrimaryInGen && !mcParticle.isPhysicalPrimary()) + continue; + + // NOTE: Pion only, see note above; Kstar0 not extended here. + static_for([&](auto i) { + constexpr int Index = i.value; + + if (std::abs(mcParticle.pdgCode()) == std::abs(PdgCodes[i])) + histos.fill(HIST("GeneratedWithPV/h") + HIST(Particlenames[Index]) + HIST("_MidYVsMult_TwoPVsOrMore"), mcParticle.pt(), bestCollisionFT0Mpercentile); + }); + } + } + + //--------------------------------------------- + // Collision selection + //--------------------------------------------- + + if (triggerPresenceMap.size() > 0 && !TESTBIT(bestCollisionTriggerPresenceMap, triggerBinToSelect)) + return; + + if (masterConfigurations.applyNewMCSelection) { + + if (!isCollisionSelect) + return; + + } else { + + if (!bestCollisionSel8) + return; + + if (std::abs(bestCollisionVtxZ) > masterConfigurations.zVertexCut) + return; + + if (!bestCollisionINELgtZERO) + return; + } + + histos.fill(HIST("hClosureTestEventCounter"), 3.5f); + + //--------------------------------------------- + // Generated particles after event selection + //--------------------------------------------- + + for (auto const& mcParticle : mcParticles) { + + if (std::abs(mcParticle.eta()) > etaSel) + continue; + + float pt = mcParticle.pt(); + + // trigger QA + + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + histos.fill(HIST("hGeneratedQAPtTrigger"), pt, 1.0f); + } + } + + // generated phi + + if (std::abs(mcParticle.pdgCode()) != Pdg::kPhi) + continue; + + // if (doAssocPhysicalPrimaryInGen && + // !mcParticle.isPhysicalPrimary()) + // continue; + + histos.fill(HIST("hGeneratedQAPtAssociatedPhi"), pt, 1.0f); + } + + for (auto const& mcParticle : mcParticles) { + + double eta = mcParticle.eta(); + double pt = mcParticle.pt(); + + // generated phi + + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi) { + + if (efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + + histos.fill(HIST("GeneratedWithPV/hPhi"), pt, mcParticle.eta(), mcParticle.phi(), bestCollisionFT0Cpercentile); + + } else { + histos.fill(HIST("GeneratedWithPV/hPhi"), pt, mcParticle.eta(), bestCollisionFT0Mpercentile); + } + if (std::abs(mcParticle.y()) < ySel) + histos.fill(HIST("GeneratedWithPV/hPhi") + HIST("_MidYVsMult"), pt, bestCollisionFT0Mpercentile); + } + + if (doAssocPhysicalPrimaryInGen && !mcParticle.isPhysicalPrimary()) { + continue; + } + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.fill(HIST("GeneratedWithPV/hTrigger"), pt, eta, mcParticle.phi(), bestCollisionFT0Cpercentile); + } else { + histos.fill(HIST("GeneratedWithPV/hTrigger"), pt, eta, bestCollisionFT0Mpercentile); + } + if (mcParticle.pdgCode() > 0) + histos.fill(HIST("GeneratedWithPV/hPositiveTrigger"), pt, eta, bestCollisionFT0Mpercentile); + else + histos.fill(HIST("GeneratedWithPV/hNegativeTrigger"), pt, eta, bestCollisionFT0Mpercentile); + } + + // NOTE: Pion only, see note above; Kstar0 not extended here. + static_for([&](auto i) { + constexpr int Index = i.value; + if (i == IndexPion && std::abs(mcParticle.pdgCode()) == std::abs(PdgCodes[i]) && mcParticle.pdgCode() > Neutral) { + histos.fill(HIST("GeneratedWithPV/hPositive") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), bestCollisionFT0Mpercentile); + } else if (i == IndexPion && std::abs(mcParticle.pdgCode()) == std::abs(PdgCodes[i]) && mcParticle.pdgCode() < Neutral) { + histos.fill(HIST("GeneratedWithPV/hNegative") + HIST(Particlenames[Index]), mcParticle.pt(), mcParticle.eta(), bestCollisionFT0Mpercentile); + } + + if (std::abs(mcParticle.pdgCode()) == std::abs(PdgCodes[i])) { + if (efficiencyFlags.applyEffAsFunctionOfMultAndPhi) { + histos.fill(HIST("GeneratedWithPV/h") + HIST(Particlenames[Index]), pt, eta, mcParticle.phi(), bestCollisionFT0Cpercentile); + } else { + histos.fill(HIST("GeneratedWithPV/h") + HIST(Particlenames[Index]), pt, eta, bestCollisionFT0Mpercentile); + } + if (std::abs(mcParticle.y()) < ySel) + histos.fill(HIST("GeneratedWithPV/h") + HIST(Particlenames[Index]) + HIST("_MidYVsMult"), pt, bestCollisionFT0Mpercentile); + } + }); + } + } + + void processClosureTest(aod::McCollision const& /*mcCollision*/, soa::SmallGroups> const& recCollisions, aod::McParticles const& mcParticles) + { + + std::vector triggerIndices; + std::vector> associatedIndices; + std::vector assocHadronIndices; + std::vector piIndices; + std::vector phiIndices; + std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not + // extended to this process function in this version + + for (auto const& mcParticle : mcParticles) { + double geta = mcParticle.eta(); + if (std::abs(geta) > etaSel) { + continue; + } + double gpt = mcParticle.pt(); + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + histos.fill(HIST("hClosureQAPtTrigger"), gpt, 0.0f); // step 1: no event selection whatsoever + } + } + + // if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi && masterConfigurations.doCorrelationPhi) { + histos.fill(HIST("hClosureQAPtAssociatedPhi"), gpt, 0.0f); // step 1: no event selection whatsoever + } + // } + } + + histos.fill(HIST("hClosureTestEventCounter"), 0.5f); + + int bestCollisionFT0Mpercentile = -1; + float bestCollisionVtxZ = 0.0f; + bool bestCollisionSel8 = false; + bool bestCollisionINELgtZERO = false; + int biggestNContribs = -1; + uint32_t bestCollisionTriggerPresenceMap = 0; + + for (auto const& recCollision : recCollisions) { + if (biggestNContribs < recCollision.numContrib()) { + biggestNContribs = recCollision.numContrib(); + bestCollisionFT0Mpercentile = recCollision.centFT0M(); + bestCollisionSel8 = recCollision.sel8(); + bestCollisionVtxZ = recCollision.posZ(); + bestCollisionINELgtZERO = recCollision.isInelGt0(); + if (triggerPresenceMap.size() > 0) + bestCollisionTriggerPresenceMap = triggerPresenceMap[recCollision.globalIndex()]; + } + } + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(bestCollisionTriggerPresenceMap, triggerBinToSelect)) { + return; + } + + if (masterConfigurations.doGenEventSelection) { + if (!bestCollisionSel8) + return; + if (std::abs(bestCollisionVtxZ) > masterConfigurations.zVertexCut) + return; + if (!bestCollisionINELgtZERO) + return; + if (bestCollisionFT0Mpercentile > axisRanges[5][1] || bestCollisionFT0Mpercentile < axisRanges[5][0]) { + return; + } + } + + histos.fill(HIST("hClosureTestEventCounter"), 1.5f); + + for (auto const& mcParticle : mcParticles) { + double geta = mcParticle.eta(); + if (std::abs(geta) > etaSel) { + continue; + } + double gpt = mcParticle.pt(); + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + histos.fill(HIST("hClosureQAPtTrigger"), gpt, 1.0f); // step 2: after event selection + } + } + + // if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi && masterConfigurations.doCorrelationPhi) { + histos.fill(HIST("hClosureQAPtAssociatedPhi"), gpt, 1.0f); // step 2: after event selection + } + // } + } + + int iteratorNum = -1; + for (auto const& mcParticle : mcParticles) { + iteratorNum = iteratorNum + 1; + double geta = mcParticle.eta(); + double gpt = mcParticle.pt(); + double gphi = mcParticle.phi(); + if (std::abs(geta) > etaSel) { + continue; + } + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + triggerIndices.emplace_back(iteratorNum); + histos.fill(HIST("ClosureTest/hTrigger"), gpt, geta, bestCollisionFT0Mpercentile); + } + if (masterConfigurations.doCorrelationHadron) { + if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + assocHadronIndices.emplace_back(iteratorNum); + histos.fill(HIST("ClosureTest/hHadron"), gpt, geta, gphi); + } + } + } + if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus && masterConfigurations.doCorrelationPion) { + piIndices.emplace_back(iteratorNum); + histos.fill(HIST("ClosureTest/hPion"), gpt, geta, gphi); + } + } + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi && masterConfigurations.doCorrelationPhi) { + phiIndices.emplace_back(iteratorNum); + histos.fill(HIST("ClosureTest/hPhi"), gpt, geta, gphi); + } + } + + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 + + for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { + auto triggerParticle = mcParticles.iteratorAt(triggerIndices[iTrigger]); + // check range of trigger particle + if (triggerParticle.pt() > axisRanges[3][1] || triggerParticle.pt() < axisRanges[3][0]) { + continue; + } + double getatrigger = triggerParticle.eta(); + double gphitrigger = triggerParticle.phi(); + double pttrigger = triggerParticle.pt(); + + auto mothers = triggerParticle.mothers_as(); + auto globalIndex = (mothers.size() > 0) ? triggerParticle.mothers_first_as().globalIndex() : -1; + + static_for<0, 3>([&](auto i) { // associated loop (Kstar0 slot is always empty, see above) + constexpr int Index = i.value; + for (std::size_t iassoc = 0; iassoc < associatedIndices[Index].size(); iassoc++) { + auto assocParticle = mcParticles.iteratorAt(associatedIndices[Index][iassoc]); + if (triggerIndices[iTrigger] != associatedIndices[Index][iassoc] && globalIndex != assocParticle.globalIndex()) { // avoid self + double getaassoc = assocParticle.eta(); + double gphiassoc = assocParticle.phi(); + double ptassoc = assocParticle.pt(); + double deltaphi = computeDeltaPhi(gphitrigger, gphiassoc); + double deltaeta = getatrigger - getaassoc; + + // skip if basic ranges not met + if (deltaphi < axisRanges[0][0] || deltaphi > axisRanges[0][1]) + continue; + if (deltaeta < axisRanges[1][0] || deltaeta > axisRanges[1][1]) + continue; + if (ptassoc < axisRanges[2][0] || ptassoc > axisRanges[2][1]) + continue; + if (TESTBIT(doCorrelation, i)) + histos.fill(HIST("ClosureTest/sameEvent/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, bestCollisionVtxZ, bestCollisionFT0Mpercentile); + } + } + }); + } + } + + void processMixedEventHPhisInBuffer(soa::Join const& collisions, aod::TriggerTracks const& triggerTracks, aod::AssocPhis const& assocPhis, TracksComplete const&, aod::BCsWithTimestamps const&) + { + for (auto const& collision : collisions) { + + auto bc = collision.bc_as(); + auto dBz = getMagneticField(bc.timestamp()); + + // Slice down to this collision's own triggers/candidates before + // buffering into ValidCollision -- without this, every collision would + // buffer the entire dataframe's triggers/candidates instead of just + // its own, defeating the point of per-collision buffering. + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision.globalIndex()); + auto slicedAssocPhis = assocPhis.sliceBy(collisionSlicePhis, collision.globalIndex()); + + fillCorrelationsPhi(slicedTriggerTracks, slicedAssocPhis, true, true, collision.posZ(), collision.centFT0M(), dBz); + } + } + + void processMixedEventHKstarsInBuffer(soa::Join const& collisions, aod::TriggerTracks const& triggerTracks, aod::AssocKstars const& assocKstars, TracksComplete const&, aod::BCsWithTimestamps const&) + { + for (auto const& collision : collisions) { + + auto bc = collision.bc_as(); + auto dBz = getMagneticField(bc.timestamp()); + + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision.globalIndex()); + auto slicedAssocKstars = assocKstars.sliceBy(collisionSliceKstars, collision.globalIndex()); + + fillCorrelationsKstar(slicedTriggerTracks, slicedAssocKstars, true, true, collision.posZ(), collision.centFT0M(), dBz); + } + } + + void processPrediction(soa::Join::iterator const& mcCollision, aod::McParticles const& mcParticles) + { + std::vector triggerIndices; + std::vector> associatedIndices; + std::vector assocHadronIndices; + std::vector piIndices; + std::vector phiIndices; + std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not + // extended to this process function in this version + float centMultFT0M = -1; + float centMultFT0A = -1; + float centMultFT0C = -1; + float multFT0M = -1; + float multFT0A = -1; + float multFT0C = -1; + float multEta08 = -1; + float multEta05 = -1; + histos.fill(HIST("Prediction/hEventSelection"), 0.5); + if (masterConfigurations.selectINELgtZERO && !o2::pwglf::isINELgt0mc(mcParticles, pdgDB)) { + return; + } + histos.fill(HIST("Prediction/hEventSelection"), 1.5); + if (std::abs(mcCollision.posZ()) > masterConfigurations.zVertexCut) { + return; + } + histos.fill(HIST("Prediction/hEventSelection"), 2.5); + if (masterConfigurations.useCentralityinPrediction) { + centMultFT0M = mcCollision.centFT0M(); + centMultFT0A = mcCollision.centFT0A(); + centMultFT0C = mcCollision.centFT0C(); + } else { + multFT0M = mCounter.countFT0A(mcParticles) + mCounter.countFT0C(mcParticles); + multFT0A = mCounter.countFT0A(mcParticles); + multFT0C = mCounter.countFT0C(mcParticles); + multEta08 = mCounter.countEta08(mcParticles); + multEta05 = mCounter.countEta05(mcParticles); + } + if (!masterConfigurations.useCentralityinPrediction) { + if (masterConfigurations.doSeparateFT0Prediction) { + histos.fill(HIST("Prediction/hFT0AvsNchEta08"), multFT0A, multEta08); + histos.fill(HIST("Prediction/hFT0AvsNchEta05"), multFT0A, multEta05); + histos.fill(HIST("Prediction/hFT0CvsNchEta08"), multFT0C, multEta08); + histos.fill(HIST("Prediction/hFT0CvsNchEta05"), multFT0C, multEta05); + } + histos.fill(HIST("Prediction/hFT0MvsNchEta08"), multFT0M, multEta08); + histos.fill(HIST("Prediction/hFT0MvsNchEta05"), multFT0M, multEta05); + } + int iteratorNum = -1; + for (auto const& mcParticle : mcParticles) { + iteratorNum = iteratorNum + 1; + double geta = mcParticle.eta(); + double gpt = mcParticle.pt(); + double gphi = mcParticle.phi(); + if (std::abs(geta) > etaSel) { + continue; + } + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kKPlus || std::abs(mcParticle.pdgCode()) == PDG_t::kProton || std::abs(mcParticle.pdgCode()) == PDG_t::kElectron || std::abs(mcParticle.pdgCode()) == PDG_t::kMuonMinus) { + if (!masterConfigurations.doTriggPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + triggerIndices.emplace_back(iteratorNum); + if (masterConfigurations.useCentralityinPrediction) { + if (masterConfigurations.doSeparateFT0Prediction) { + histos.fill(HIST("Prediction/hTriggerFT0A"), gpt, geta, centMultFT0A); + histos.fill(HIST("Prediction/hTriggerFT0C"), gpt, geta, centMultFT0C); + } + histos.fill(HIST("Prediction/hTrigger"), gpt, geta, centMultFT0M); + } else { + if (masterConfigurations.doSeparateFT0Prediction) { + histos.fill(HIST("Prediction/hTriggerFT0A"), gpt, geta, multFT0A); + histos.fill(HIST("Prediction/hTriggerFT0C"), gpt, geta, multFT0C); + } + histos.fill(HIST("Prediction/hTrigger"), gpt, geta, multFT0M); + } + } + if (masterConfigurations.doCorrelationHadron) { + if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + assocHadronIndices.emplace_back(iteratorNum); + histos.fill(HIST("Prediction/hHadron"), gpt, geta, gphi); + } + } + } + if (!doAssocPhysicalPrimary || mcParticle.isPhysicalPrimary()) { + if (std::abs(mcParticle.pdgCode()) == PDG_t::kPiPlus && masterConfigurations.doCorrelationPion) { + piIndices.emplace_back(iteratorNum); + histos.fill(HIST("Prediction/hPion"), gpt, geta, gphi); + } + } + if (std::abs(mcParticle.pdgCode()) == Pdg::kPhi && masterConfigurations.doCorrelationPhi) { + phiIndices.emplace_back(iteratorNum); + histos.fill(HIST("Prediction/hPhi"), gpt, geta, gphi); + } + } + + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 + for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { + auto triggerParticle = mcParticles.iteratorAt(triggerIndices[iTrigger]); + // check range of trigger particle + if (triggerParticle.pt() > axisRanges[3][1] || triggerParticle.pt() < axisRanges[3][0]) { + continue; + } + double getatrigger = triggerParticle.eta(); + double gphitrigger = triggerParticle.phi(); + double pttrigger = triggerParticle.pt(); + auto mothers = triggerParticle.mothers_as(); + auto globalIndex = (mothers.size() > 0) ? triggerParticle.mothers_first_as().globalIndex() : -1; + static_for<0, 3>([&](auto i) { // associated loop (Kstar0 slot is always empty, see above) + constexpr int Index = i.value; + for (std::size_t iassoc = 0; iassoc < associatedIndices[Index].size(); iassoc++) { + auto assocParticle = mcParticles.iteratorAt(associatedIndices[Index][iassoc]); + if (triggerIndices[iTrigger] != associatedIndices[Index][iassoc] && globalIndex != assocParticle.globalIndex()) { // avoid self + double getaassoc = assocParticle.eta(); + double gphiassoc = assocParticle.phi(); + double ptassoc = assocParticle.pt(); + double deltaphi = computeDeltaPhi(gphitrigger, gphiassoc); + double deltaeta = getatrigger - getaassoc; + + // skip if basic ranges not met + if (deltaphi < axisRanges[0][0] || deltaphi > axisRanges[0][1]) + continue; + if (deltaeta < axisRanges[1][0] || deltaeta > axisRanges[1][1]) + continue; + if (ptassoc < axisRanges[2][0] || ptassoc > axisRanges[2][1]) + continue; + if (TESTBIT(doCorrelation, i)) { + if (masterConfigurations.useCentralityinPrediction) { + histos.fill(HIST("Prediction/sameEvent/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), centMultFT0M); + if (masterConfigurations.doSeparateFT0Prediction) { + histos.fill(HIST("Prediction/sameEventFT0A/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), centMultFT0A); + histos.fill(HIST("Prediction/sameEventFT0C/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), centMultFT0C); + } + } else { + histos.fill(HIST("Prediction/sameEvent/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), multFT0M); + if (masterConfigurations.doSeparateFT0Prediction) { + histos.fill(HIST("Prediction/sameEventFT0A/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), multFT0A); + histos.fill(HIST("Prediction/sameEventFT0C/") + HIST(Particlenames[Index]), computeDeltaPhi(gphitrigger, gphiassoc), deltaeta, ptassoc, pttrigger, mcCollision.posZ(), multFT0C); + } + } + } + } + } + }); + } + } + + PROCESS_SWITCH(HResonanceCorrelation, processSelectEventWithTrigger, "Select events with trigger only", true); + + PROCESS_SWITCH(HResonanceCorrelation, processSameEventHPions, "Process same events, h-Pion", true); + PROCESS_SWITCH(HResonanceCorrelation, processSameEventHHadrons, "Process same events, h-h", true); + PROCESS_SWITCH(HResonanceCorrelation, processSameEventHPhis, "Process same events, h-phi", true); + PROCESS_SWITCH(HResonanceCorrelation, processSameEventHKstars, "Process same events, h-K*0", true); + + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPions, "Process mixed events, h-Pion", true); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHHadrons, "Process mixed events, h-h", true); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPhis, "Process mixed events, h-phi", true); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPhisInBuffer, "Process mixed-event, h-phi using collision buffer", false); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHKstars, "Process mixed events, h-K*0", true); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHKstarsInBuffer, "Process mixed-event, h-K*0 using collision buffer", false); + + PROCESS_SWITCH(HResonanceCorrelation, processMCGenerated, "Process MC generated", false); + PROCESS_SWITCH(HResonanceCorrelation, processClosureTest, "Process Closure Test", false); + PROCESS_SWITCH(HResonanceCorrelation, processPrediction, "process model prediction", false); +}; + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + return WorkflowSpec{adaptAnalysisTask(cfgc)}; +} \ No newline at end of file From 641c8aee81d98486df2fe6ded96b229bf03d0639 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 12:01:47 +0530 Subject: [PATCH 05/17] Add hResonanceCorrelation workflow to CMakeLists --- PWGLF/Tasks/Resonances/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/PWGLF/Tasks/Resonances/CMakeLists.txt b/PWGLF/Tasks/Resonances/CMakeLists.txt index 6eb48ce242f..06d226e887c 100644 --- a/PWGLF/Tasks/Resonances/CMakeLists.txt +++ b/PWGLF/Tasks/Resonances/CMakeLists.txt @@ -348,3 +348,8 @@ o2physics_add_dpl_workflow(lambdastarproxy SOURCES lambdastarproxy.cxx PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore COMPONENT_NAME Analysis) + +o2physics_add_dpl_workflow(hresonancecorrelation + SOURCES hResonanceCorrelation.cxx + PUBLIC_LINK_LIBRARIES O2::Framework O2::DetectorsBase O2Physics::AnalysisCore O2Physics::EventFilteringUtils + COMPONENT_NAME Analysis) From 069067f36ec4d77c76f5b377a6aa6a78b3603c1e Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Thu, 24 Sep 2026 06:34:31 +0000 Subject: [PATCH 06/17] Please consider the following formatting changes --- .../DataModel/LFHResonanceCorrelationTables.h | 2 +- .../hResonanceCorrelationFilter.cxx | 6 ++--- .../Resonances/hResonanceCorrelation.cxx | 22 +++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/PWGLF/DataModel/LFHResonanceCorrelationTables.h b/PWGLF/DataModel/LFHResonanceCorrelationTables.h index 77365da3b86..2bc281daaf6 100644 --- a/PWGLF/DataModel/LFHResonanceCorrelationTables.h +++ b/PWGLF/DataModel/LFHResonanceCorrelationTables.h @@ -148,4 +148,4 @@ DECLARE_SOA_TABLE( assocKstars::PosTrackDaughterId, assocKstars::NegTrackDaughterId); } // namespace o2::aod -#endif // PWGLF_DATAMODEL_LFHRESONANCECORRELATIONTABLES_H_ \ No newline at end of file +#endif // PWGLF_DATAMODEL_LFHRESONANCECORRELATIONTABLES_H_ diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index 446aa652a29..c65310540c9 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -1256,9 +1256,9 @@ struct HResonanceCorrelationFilter { } LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), - o2::constants::physics::MassKPlus); + o2::constants::physics::MassKPlus); LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), - o2::constants::physics::MassPiPlus); + o2::constants::physics::MassPiPlus); LorentzVectorPtEtaPhiMass kstar = kaon + pion; float invMass = kstar.M(); @@ -1338,4 +1338,4 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) { return WorkflowSpec{ adaptAnalysisTask(cfgc)}; -} \ No newline at end of file +} diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 765caf4a7d0..0d877838233 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -1361,7 +1361,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsPhi.minBgNSigma * sig < delta && delta < massWindowConfigurationsPhi.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTruePhi()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Candidate-level kinematic/spectrum QA, ported from the equivalent // block in hStrangeCorrelation.cxx (h3dSpectrum[Y], @@ -1712,7 +1712,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsKstar.minBgNSigma * sig < delta && delta < massWindowConfigurationsKstar.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTrueKstar()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Same candidate-level Spectrum/EtaVsPtVsPhi QA as fillCorrelationsPhi // above -- see the comment there for rationale. @@ -2717,7 +2717,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version for (auto const& mcParticle : mcParticles) { double geta = mcParticle.eta(); @@ -2830,9 +2830,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { @@ -2914,7 +2914,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version float centMultFT0M = -1; float centMultFT0A = -1; float centMultFT0C = -1; @@ -2998,9 +2998,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { auto triggerParticle = mcParticles.iteratorAt(triggerIndices[iTrigger]); @@ -3074,4 +3074,4 @@ struct HResonanceCorrelation { WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) { return WorkflowSpec{adaptAnalysisTask(cfgc)}; -} \ No newline at end of file +} From 3a3e00548c7b334e0302fd8d852b01b8073eca00 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 13:44:17 +0530 Subject: [PATCH 07/17] Set default values for TriggCandidate and mRunNumber Initialized member variables in TriggCandidate struct and set default values. --- .../Resonances/hResonanceCorrelationFilter.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index c65310540c9..9e2629e0eac 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -255,14 +255,14 @@ struct HResonanceCorrelationFilter { Zorro zorro; OutputObj zorroSummary{"zorroSummary"}; - int mRunNumber; + int mRunNumber = -1; struct TriggCandidate { - float pt; - int collisionId; - int trackId; - bool isPhysicalPrimary; - float origPt; + float pt = 0.f; + int collisionId = -1; + int trackId = -1; + bool isPhysicalPrimary = false; + float origPt = 0.f; }; TriggCandidate thisTrigg; @@ -1256,9 +1256,9 @@ struct HResonanceCorrelationFilter { } LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), - o2::constants::physics::MassKPlus); + o2::constants::physics::MassKPlus); LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), - o2::constants::physics::MassPiPlus); + o2::constants::physics::MassPiPlus); LorentzVectorPtEtaPhiMass kstar = kaon + pion; float invMass = kstar.M(); From e5ca55127eb85db416dc5ce42b71f2fc3981a817 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 13:44:57 +0530 Subject: [PATCH 08/17] Refactor hResonanceCorrelation.cxx for clarity and safety --- .../Resonances/hResonanceCorrelation.cxx | 80 ++++++++++--------- 1 file changed, 41 insertions(+), 39 deletions(-) diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 0d877838233..17ca39f9aa4 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -256,15 +256,17 @@ struct HResonanceCorrelation { int region; float efficiency; float efficiencyError; - int type; + // no species tag stored here: the `type` argument to addValidParticle + // below (-1 = trigger, else Phi/K*0) only decides which vector this + // particle goes into; once stored, nothing reads it back. }; - float pvz; - float mult; + float pvz = 0.f; + float mult = 0.f; std::vector trigParticles; std::vector assocParticles; void addValidParticle(float eta, float phi, float pt, int region, float efficiency, float efficiencyError, int type) { - ValidParticle particle{eta, phi, pt, region, efficiency, efficiencyError, type}; + ValidParticle particle{eta, phi, pt, region, efficiency, efficiencyError}; if (type == -1) { trigParticles.push_back(particle); @@ -278,29 +280,29 @@ struct HResonanceCorrelation { ValidCollisions validCollisions; // objects to use for efficiency corrections - TH2F* hEfficiencyTrigger; - TH3F* hEfficiencyTriggerMult; - THnF* hEfficiencyTriggerMultVsPhi; - TH2F* hEfficiencyPion; - TH2F* hEfficiencyPhi; - THnF* hEfficiencyPhiMultVsPhi; - TH2F* hEfficiencyKstar; - THnF* hEfficiencyKstarMultVsPhi; - TH2F* hEfficiencyHadron; - TH3F* hEfficiencyHadronMult; - TH1F* hPurityHadron; - TH2F* hPurityHadronMult; + TH2F* hEfficiencyTrigger = nullptr; + TH3F* hEfficiencyTriggerMult = nullptr; + THnF* hEfficiencyTriggerMultVsPhi = nullptr; + TH2F* hEfficiencyPion = nullptr; + TH2F* hEfficiencyPhi = nullptr; + THnF* hEfficiencyPhiMultVsPhi = nullptr; + TH2F* hEfficiencyKstar = nullptr; + THnF* hEfficiencyKstarMultVsPhi = nullptr; + TH2F* hEfficiencyHadron = nullptr; + TH3F* hEfficiencyHadronMult = nullptr; + TH1F* hPurityHadron = nullptr; + TH2F* hPurityHadronMult = nullptr; // objects to propagate the efficiency uncertainty - TH2F* hEfficiencyUncertaintyTrigger; - TH3F* hEfficiencyUncertaintyTriggerMult; - TH2F* hEfficiencyUncertaintyPion; - TH2F* hEfficiencyUncertaintyPhi; - TH2F* hEfficiencyUncertaintyKstar; - TH2F* hEfficiencyUncertaintyHadron; - TH3F* hEfficiencyUncertaintyHadronMult; - TH1F* hPurityUncertaintyHadron; - TH2F* hPurityUncertaintyHadronMult; + TH2F* hEfficiencyUncertaintyTrigger = nullptr; + TH3F* hEfficiencyUncertaintyTriggerMult = nullptr; + TH2F* hEfficiencyUncertaintyPion = nullptr; + TH2F* hEfficiencyUncertaintyPhi = nullptr; + TH2F* hEfficiencyUncertaintyKstar = nullptr; + TH2F* hEfficiencyUncertaintyHadron = nullptr; + TH3F* hEfficiencyUncertaintyHadronMult = nullptr; + TH1F* hPurityUncertaintyHadron = nullptr; + TH2F* hPurityUncertaintyHadronMult = nullptr; using BinningTypePP = ColumnBinningPolicy; using BinningTypePbPb = ColumnBinningPolicy; @@ -320,9 +322,9 @@ struct HResonanceCorrelation { static constexpr int IndexKstar = 1; static constexpr int IndexPion = 2; - uint16_t doCorrelation; - int mRunNumber; - int mRunNumberZorro; + uint16_t doCorrelation = 0; + int mRunNumber = 0; + int mRunNumberZorro = 0; std::vector> axisRanges; @@ -1075,7 +1077,7 @@ struct HResonanceCorrelation { return true; } - double calculateAverageDeltaPhiStar(double* trigg, double* assoc, double B) + double calculateAverageDeltaPhiStar(const double* trigg, const double* assoc, double B) { double dPhiStar = 0; double dPhiStarMean = 0; @@ -1361,7 +1363,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsPhi.minBgNSigma * sig < delta && delta < massWindowConfigurationsPhi.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTruePhi()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Candidate-level kinematic/spectrum QA, ported from the equivalent // block in hStrangeCorrelation.cxx (h3dSpectrum[Y], @@ -1712,7 +1714,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsKstar.minBgNSigma * sig < delta && delta < massWindowConfigurationsKstar.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTrueKstar()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Same candidate-level Spectrum/EtaVsPtVsPhi QA as fillCorrelationsPhi // above -- see the comment there for rationale. @@ -2717,7 +2719,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version for (auto const& mcParticle : mcParticles) { double geta = mcParticle.eta(); @@ -2830,9 +2832,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { @@ -2914,7 +2916,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version float centMultFT0M = -1; float centMultFT0A = -1; float centMultFT0C = -1; @@ -2998,9 +3000,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { auto triggerParticle = mcParticles.iteratorAt(triggerIndices[iTrigger]); From eb0578016cfd5f07ed76546fb5171f22b1e2b4ca Mon Sep 17 00:00:00 2001 From: ALICE Action Bot Date: Thu, 24 Sep 2026 08:15:35 +0000 Subject: [PATCH 09/17] Please consider the following formatting changes --- .../hResonanceCorrelationFilter.cxx | 4 ++-- .../Resonances/hResonanceCorrelation.cxx | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index 9e2629e0eac..04ee20109d5 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -1256,9 +1256,9 @@ struct HResonanceCorrelationFilter { } LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), - o2::constants::physics::MassKPlus); + o2::constants::physics::MassKPlus); LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), - o2::constants::physics::MassPiPlus); + o2::constants::physics::MassPiPlus); LorentzVectorPtEtaPhiMass kstar = kaon + pion; float invMass = kstar.M(); diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 17ca39f9aa4..3bffb7c1c69 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -1363,7 +1363,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsPhi.minBgNSigma * sig < delta && delta < massWindowConfigurationsPhi.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTruePhi()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Candidate-level kinematic/spectrum QA, ported from the equivalent // block in hStrangeCorrelation.cxx (h3dSpectrum[Y], @@ -1714,7 +1714,7 @@ struct HResonanceCorrelation { c.isRightBg = (massWindowConfigurationsKstar.minBgNSigma * sig < delta && delta < massWindowConfigurationsKstar.maxBgNSigma * sig); c.passesMcSelection = (!masterConfigurations.doMCassociation || assocCandidate.mcTrueKstar()) && - (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); + (!doAssocPhysicalPrimary || assocCandidate.mcPhysicalPrimary()); // Same candidate-level Spectrum/EtaVsPtVsPhi QA as fillCorrelationsPhi // above -- see the comment there for rationale. @@ -2719,7 +2719,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version for (auto const& mcParticle : mcParticles) { double geta = mcParticle.eta(); @@ -2832,9 +2832,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { @@ -2916,7 +2916,7 @@ struct HResonanceCorrelation { std::vector piIndices; std::vector phiIndices; std::vector kstarIndices; // intentionally left unpopulated: Kstar0 is not - // extended to this process function in this version + // extended to this process function in this version float centMultFT0M = -1; float centMultFT0A = -1; float centMultFT0C = -1; @@ -3000,9 +3000,9 @@ struct HResonanceCorrelation { } } - associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 - associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) - associatedIndices.emplace_back(piIndices); // IndexPion = 2 + associatedIndices.emplace_back(phiIndices); // IndexPhi = 0 + associatedIndices.emplace_back(kstarIndices); // IndexKstar = 1 (always empty here) + associatedIndices.emplace_back(piIndices); // IndexPion = 2 associatedIndices.emplace_back(assocHadronIndices); // Hadron = 3 for (std::size_t iTrigger = 0; iTrigger < triggerIndices.size(); iTrigger++) { auto triggerParticle = mcParticles.iteratorAt(triggerIndices[iTrigger]); From bfb506919a3b1abf70e9d8d2add9d42008eaec2f Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Thu, 24 Sep 2026 15:16:46 +0530 Subject: [PATCH 10/17] Change arrays to const for trigger and associated tracks --- PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 3bffb7c1c69..12aee33da4c 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -1166,7 +1166,7 @@ struct HResonanceCorrelation { fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesHadron")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); } double triggSign = trigg.sign(); - double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + const double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; for (auto const& assocTrack : assocs) { auto assoc = assocTrack.template track_as(); @@ -1195,7 +1195,7 @@ struct HResonanceCorrelation { float pttrigger = trigg.pt(); double assocSign = assoc.sign(); - double assocForDeltaPhiStar[] = {assoc.phi(), assoc.pt(), assocSign}; + const double assocForDeltaPhiStar[] = {assoc.phi(), assoc.pt(), assocSign}; float etaWeight = 1.; if (checks.doOnTheFlyFlattening) { @@ -1446,7 +1446,7 @@ struct HResonanceCorrelation { } double triggSign = trigg.sign(); - double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + const double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; if (mixingInBf) { currentCollision.addValidParticle(trigg.eta(), trigg.phi(), trigg.pt(), -1, efficiencyTrigg, efficiencyTriggError, -1); @@ -1495,8 +1495,8 @@ struct HResonanceCorrelation { double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; - double assocForDeltaPhiStarPlus[] = {c.phiKplus, c.ptKplus, c.signKplus}; - double assocForDeltaPhiStarMinus[] = {c.phiKminus, c.ptKminus, c.signKminus}; + const double assocForDeltaPhiStarPlus[] = {c.phiKplus, c.ptKplus, c.signKplus}; + const double assocForDeltaPhiStarMinus[] = {c.phiKminus, c.ptKminus, c.signKminus}; if (!c.passesMcSelection) continue; @@ -1786,7 +1786,7 @@ struct HResonanceCorrelation { } double triggSign = trigg.sign(); - double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; + const double triggForDeltaPhiStar[] = {trigg.phi(), trigg.pt(), triggSign}; if (mixingInBf) { currentCollision.addValidParticle(trigg.eta(), trigg.phi(), trigg.pt(), -1, efficiencyTrigg, efficiencyTriggError, -1); @@ -1835,8 +1835,8 @@ struct HResonanceCorrelation { double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; - double assocForDeltaPhiStarPlus[] = {c.phiPos, c.ptPos, c.signPos}; - double assocForDeltaPhiStarMinus[] = {c.phiNeg, c.ptNeg, c.signNeg}; + const double assocForDeltaPhiStarPlus[] = {c.phiPos, c.ptPos, c.signPos}; + const double assocForDeltaPhiStarMinus[] = {c.phiNeg, c.ptNeg, c.signNeg}; if (!c.passesMcSelection) continue; From 6122b6bc2c3ec76a534e3003ce6895ca4ccb878c Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 15:40:04 +0530 Subject: [PATCH 11/17] Refactor track validation and add kaon processing Updated the isValidAssocTrack function to use a template parameter for species. Added processAssocKaons and processAssocKaonsMC functions for handling associated kaon tracks. --- .../hResonanceCorrelationFilter.cxx | 166 +++++++++++++++--- 1 file changed, 137 insertions(+), 29 deletions(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index 04ee20109d5..388d4cd556b 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -42,13 +43,11 @@ #include #include -#include #include #include -#include -#include #include +#include #include #include @@ -182,6 +181,14 @@ struct HResonanceCorrelationFilter { Configurable assocPionNSigmaTPCFOF{"assocPionNSigmaTPCFOF", 3, "minimal n sigma in TOF and TPC for Pion ID"}; Configurable rejectSigma{"rejectSigma", 1, "n sigma for rejecting pion candidates"}; + // Associated kaon identification (mirrors the pion selection above, with + // the accept/reject roles swapped: accept kaon-consistent tracks, reject + // pion-/proton-consistent ones). Selected via the Species template tag on + // isValidAssocTrack() below -- production path (which table rows + // get made) is still separate per process function, only the PID gate + // logic itself is shared. + Configurable assocKaonNSigmaTPCFOF{"assocKaonNSigmaTPCFOF", 3, "minimal n sigma in TOF and TPC for Kaon ID"}; + // primary particle DCAxy selections // formula: |DCAxy| < 0.004f + (0.013f / pt) Configurable dcaXYconstant{"dcaXYconstant", 0.004, "[0] in |DCAxy| < [0]+[1]/pT"}; @@ -391,7 +398,7 @@ struct HResonanceCorrelationFilter { // more event selections in Pb-Pb template - bool isCollisionSelectedPbPb(TCollision collision) + bool isCollisionSelectedPbPb(TCollision const& collision) { if (!collision.selection_bit(aod::evsel::kIsTriggerTVX) && eventSelections.requireGoodTriggerTVX) /* FT0 vertex (acceptable FT0C-FT0A time difference) collisions */ return false; @@ -626,7 +633,7 @@ struct HResonanceCorrelationFilter { // reco-level trigger quality checks (N.B.: DCA is filtered, not selected) template - bool isValidTrigger(TTrack track) + bool isValidTrigger(TTrack const& track) { if (track.eta() > generalSelections.triggerEtaMax || track.eta() < generalSelections.triggerEtaMin) { return false; @@ -650,9 +657,30 @@ struct HResonanceCorrelationFilter { return true; } - template - bool isValidAssocTrack(TTrack assoc) + // Species tag for isValidAssocTrack()'s PID gate below. AssocPion + // and AssocKaon select which nSigma cut direction applies (accept that + // species, reject the other two); AssocHadron carries no PID logic of its + // own -- passed for clarity at Hadron call sites, but never actually reaches + // the `if constexpr (Species == AssocKaon) ... else ...` branch, since the + // outer `requires { assoc.tofSignal(); }` is already false for FullTracks + // (Hadron's track type, which has no PID columns at all). + enum AssocSpecies { AssocPion = 0, + AssocKaon = 1, + AssocHadron = 2 }; + + // Merged predicate for the Pion/Kaon/Hadron associated-track pools. Pion and + // Kaon both run over IDTracks/IDTracksMC (identical C++ type), so the two + // PID directions cannot be told apart by a `requires{}` type check the way + // Hadron (FullTracks, no PID columns) is told apart from the other two -- + // hence the explicit non-type template parameter instead. Everything except + // the PID nSigma cut direction (phase-space cuts, MC-truth lookup, table + // fills) is identical across all three species, so it stays unduplicated + // here; only the accept/reject block branches on Species. + template + bool isValidAssocTrack(TTrack const& assoc) { + static_assert(Species == AssocPion || Species == AssocKaon || Species == AssocHadron, + "isValidAssocTrack: unknown species tag"); if (assoc.eta() > generalSelections.assocEtaMax || assoc.eta() < generalSelections.assocEtaMin) { return false; } @@ -670,27 +698,49 @@ struct HResonanceCorrelationFilter { float nSigmaTPCTOF[8] = {-10, -10, -10, -10, -10, -10, -10, -10}; if constexpr (requires { assoc.tofSignal(); } && !requires { assoc.mcParticle(); }) { if (assoc.tofSignal() > 0) { - if (std::sqrt(assoc.tofNSigmaPi() * assoc.tofNSigmaPi() + assoc.tpcNSigmaPi() * assoc.tpcNSigmaPi()) > trackSelections.assocPionNSigmaTPCFOF) - return false; - if (assoc.tofNSigmaPr() < trackSelections.rejectSigma) - return false; - if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) - return false; - if (assoc.tofNSigmaKa() < trackSelections.rejectSigma) - return false; - if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) - return false; + if constexpr (Species == AssocKaon) { + if (std::sqrt(assoc.tofNSigmaKa() * assoc.tofNSigmaKa() + assoc.tpcNSigmaKa() * assoc.tpcNSigmaKa()) > trackSelections.assocKaonNSigmaTPCFOF) + return false; + if (assoc.tofNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tofNSigmaPi() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaPi() < trackSelections.rejectSigma) + return false; + } else { + if (std::sqrt(assoc.tofNSigmaPi() * assoc.tofNSigmaPi() + assoc.tpcNSigmaPi() * assoc.tpcNSigmaPi()) > trackSelections.assocPionNSigmaTPCFOF) + return false; + if (assoc.tofNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tofNSigmaKa() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) + return false; + } nSigmaTPCTOF[4] = assoc.tofNSigmaPi(); nSigmaTPCTOF[5] = assoc.tofNSigmaKa(); nSigmaTPCTOF[6] = assoc.tofNSigmaPr(); nSigmaTPCTOF[7] = assoc.tofNSigmaEl(); } else { - if (assoc.tpcNSigmaPi() > trackSelections.assocPionNSigmaTPCFOF) - return false; - if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) - return false; - if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) - return false; + if constexpr (Species == AssocKaon) { + if (assoc.tpcNSigmaKa() > trackSelections.assocKaonNSigmaTPCFOF) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaPi() < trackSelections.rejectSigma) + return false; + } else { + if (assoc.tpcNSigmaPi() > trackSelections.assocPionNSigmaTPCFOF) + return false; + if (assoc.tpcNSigmaPr() < trackSelections.rejectSigma) + return false; + if (assoc.tpcNSigmaKa() < trackSelections.rejectSigma) + return false; + } } nSigmaTPCTOF[0] = assoc.tpcNSigmaPi(); nSigmaTPCTOF[1] = assoc.tpcNSigmaKa(); @@ -831,7 +881,7 @@ struct HResonanceCorrelationFilter { /// _________________________________________________ /// Step 1: Populate table with trigger tracks for (auto const& track : tracks) { - if (!isValidAssocTrack(track)) + if (!isValidAssocTrack(track)) continue; } } @@ -859,7 +909,63 @@ struct HResonanceCorrelationFilter { /// _________________________________________________ /// Step 1: Populate table with trigger tracks for (auto const& track : tracks) { - if (!isValidAssocTrack(track)) + if (!isValidAssocTrack(track)) + continue; + } + } + + void processAssocKaons(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) + continue; + } + } + + void processAssocKaonsMC(soa::Join::iterator const& collision, soa::Filtered const& tracks, aod::McParticles const&, aod::BCsWithTimestamps const&) + { + // Load parameters for sideband subtraction + auto bc = collision.bc_as(); + // Perform basic event selection + if (!collision.sel8()) { + return; + } + // No need to correlate stuff that's in far collisions + if (std::abs(collision.posZ()) > eventSelections.zVertexCut) { + return; + } + if (zorroMask.value != "") { + initCCDB(bc); + bool zorroSelected = zorro.isSelected(collision.bc_as().globalBC()); /// Just let Zorro do the accounting + if (!zorroSelected) { + return; + } + } + + /// _________________________________________________ + /// Step 1: Populate table with trigger tracks + for (auto const& track : tracks) { + if (!isValidAssocTrack(track)) continue; } } @@ -887,7 +993,7 @@ struct HResonanceCorrelationFilter { /// _________________________________________________ /// Step 1: Populate table with trigger tracks for (auto const& track : tracks) { - if (!isValidAssocTrack(track)) + if (!isValidAssocTrack(track)) continue; } } @@ -914,7 +1020,7 @@ struct HResonanceCorrelationFilter { /// _________________________________________________ /// Step 1: Populate table with trigger tracks for (auto const& track : tracks) { - if (!isValidAssocTrack(track)) + if (!isValidAssocTrack(track)) continue; } } @@ -1256,9 +1362,9 @@ struct HResonanceCorrelationFilter { } LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), - o2::constants::physics::MassKPlus); + o2::constants::physics::MassKPlus); LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), - o2::constants::physics::MassPiPlus); + o2::constants::physics::MassPiPlus); LorentzVectorPtEtaPhiMass kstar = kaon + pion; float invMass = kstar.M(); @@ -1326,6 +1432,8 @@ struct HResonanceCorrelationFilter { PROCESS_SWITCH(HResonanceCorrelationFilter, processTriggersMC, "Produce trigger tables for MC", false); PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocPions, "Produce associated Pion tables", false); PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocPionsMC, "Produce associated Pion tables for MC", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocKaons, "Produce associated Kaon tables", false); + PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocKaonsMC, "Produce associated Kaon tables for MC", false); PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocHadrons, "Produce associated Hadron tables", true); PROCESS_SWITCH(HResonanceCorrelationFilter, processAssocHadronsMC, "Produce associated Hadron tables for MC", false); PROCESS_SWITCH(HResonanceCorrelationFilter, processPhis, "Produce associated phi tables", true); From 63c4344983c372e7f2789c9ea960c8305d7969ee Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 15:40:42 +0530 Subject: [PATCH 12/17] Add Kaon correlation support in hResonanceCorrelation Added support for Kaon correlation and updated related configurations. --- .../Resonances/hResonanceCorrelation.cxx | 65 ++++++++++++++----- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 12aee33da4c..739266c5519 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -68,7 +68,6 @@ #include #include #include -#include #include using namespace o2; @@ -105,6 +104,7 @@ struct HResonanceCorrelation { Configurable doCorrelationPhi{"doCorrelationPhi", false, "do Phi correlation"}; Configurable doCorrelationKstar{"doCorrelationKstar", false, "do K*0 correlation"}; Configurable doCorrelationPion{"doCorrelationPion", false, "do Pion correlation"}; + Configurable doCorrelationKaon{"doCorrelationKaon", false, "do Kaon correlation"}; Configurable doGenEventSelection{"doGenEventSelection", true, "use event selections when performing closure test for the gen events"}; Configurable selectINELgtZERO{"selectINELgtZERO", true, "select INEL>0 events"}; Configurable zVertexCut{"zVertexCut", 10, "Cut on PV position"}; @@ -284,6 +284,7 @@ struct HResonanceCorrelation { TH3F* hEfficiencyTriggerMult = nullptr; THnF* hEfficiencyTriggerMultVsPhi = nullptr; TH2F* hEfficiencyPion = nullptr; + TH2F* hEfficiencyKaon = nullptr; TH2F* hEfficiencyPhi = nullptr; THnF* hEfficiencyPhiMultVsPhi = nullptr; TH2F* hEfficiencyKstar = nullptr; @@ -297,6 +298,7 @@ struct HResonanceCorrelation { TH2F* hEfficiencyUncertaintyTrigger = nullptr; TH3F* hEfficiencyUncertaintyTriggerMult = nullptr; TH2F* hEfficiencyUncertaintyPion = nullptr; + TH2F* hEfficiencyUncertaintyKaon = nullptr; TH2F* hEfficiencyUncertaintyPhi = nullptr; TH2F* hEfficiencyUncertaintyKstar = nullptr; TH2F* hEfficiencyUncertaintyHadron = nullptr; @@ -315,12 +317,22 @@ struct HResonanceCorrelation { Preslice collisionSlicePhis = aod::assocPhis::collisionId; Preslice collisionSliceKstars = aod::assocKstars::collisionId; - static constexpr std::string_view Particlenames[] = {"Phi", "Kstar0", "Pion", "Hadron"}; - static constexpr int PdgCodes[] = {333, 313, 211, 0}; // Hadron has no single PDG code; 0 is a harmless placeholder + // Kaon sits after Pion, taking the slot Hadron used to occupy (Hadron + // moves to IndexHadron below) -- same reason Hadron itself sits outside + // AssocParticleTypesNoHadron: the generator-level MC-truth matching used by + // the closure test/prediction blocks further down is "intentionally + // restricted to Pion only" (see the NOTE comments at their static_for<...> + // calls) and was never extended to Kstar0 either, so Kaon joins Hadron + // outside that group rather than shifting Pion's/Kstar0's/Phi's indices or + // AssocParticleTypesNoHadron's boundary. + static constexpr std::string_view Particlenames[] = {"Phi", "Kstar0", "Pion", "Kaon", "Hadron"}; + static constexpr int PdgCodes[] = {333, 313, 211, 321, 0}; // Hadron has no single PDG code; 0 is a harmless placeholder static constexpr int IndexPhi = 0; static constexpr int IndexKstar = 1; static constexpr int IndexPion = 2; + static constexpr int IndexKaon = 3; + static constexpr int IndexHadron = 4; uint16_t doCorrelation = 0; int mRunNumber = 0; @@ -333,8 +345,8 @@ struct HResonanceCorrelation { static constexpr float Neutral = 0.0; - static constexpr int AssocParticleTypes = 4; // Phi, Kstar0, Pion, Hadron - static constexpr int AssocParticleTypesNoHadron = 3; // Phi, Kstar0, Pion + static constexpr int AssocParticleTypes = 5; // Phi, Kstar0, Pion, Kaon, Hadron + static constexpr int AssocParticleTypesNoHadron = 3; // Phi, Kstar0, Pion (Kaon excluded too -- see the comment above IndexKaon) /// Function to aid in calculating delta-phi /// \param phi1 first phi value @@ -387,6 +399,7 @@ struct HResonanceCorrelation { hEfficiencyHadron = static_cast(listEfficiencies->FindObject("hEfficiencyHadron")); hEfficiencyHadronMult = static_cast(listEfficiencies->FindObject("hEfficiencyHadronMult")); hEfficiencyPion = static_cast(listEfficiencies->FindObject("hEfficiencyPion")); + hEfficiencyKaon = static_cast(listEfficiencies->FindObject("hEfficiencyKaon")); hPurityHadron = static_cast(listEfficiencies->FindObject("hPurityHadron")); hPurityHadronMult = static_cast(listEfficiencies->FindObject("hPurityHadronMult")); hEfficiencyUncertaintyTrigger = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyTrigger")); @@ -394,6 +407,7 @@ struct HResonanceCorrelation { hEfficiencyUncertaintyPhi = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyPhi")); hEfficiencyUncertaintyKstar = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyKstar")); hEfficiencyUncertaintyPion = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyPion")); + hEfficiencyUncertaintyKaon = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyKaon")); hEfficiencyUncertaintyHadron = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyHadron")); hEfficiencyUncertaintyHadronMult = static_cast(listEfficiencies->FindObject("hEfficiencyUncertaintyHadronMult")); hPurityUncertaintyHadron = static_cast(listEfficiencies->FindObject("hPurityUncertaintyHadron")); @@ -411,10 +425,12 @@ struct HResonanceCorrelation { mRunNumber = 0; mRunNumberZorro = 0; hEfficiencyPion = 0x0; + hEfficiencyKaon = 0x0; hEfficiencyPhi = 0x0; hEfficiencyKstar = 0x0; hEfficiencyUncertaintyTrigger = 0x0; hEfficiencyUncertaintyPion = 0x0; + hEfficiencyUncertaintyKaon = 0x0; hEfficiencyUncertaintyPhi = 0x0; hEfficiencyUncertaintyKstar = 0x0; @@ -431,8 +447,10 @@ struct HResonanceCorrelation { SETBIT(doCorrelation, IndexKstar); if (masterConfigurations.doCorrelationPion) SETBIT(doCorrelation, IndexPion); + if (masterConfigurations.doCorrelationKaon) + SETBIT(doCorrelation, IndexKaon); if (masterConfigurations.doCorrelationHadron) - SETBIT(doCorrelation, 3); + SETBIT(doCorrelation, IndexHadron); // Store axis ranges to prevent spurious filling // axis status: @@ -623,8 +641,8 @@ struct HResonanceCorrelation { // ======================================================================== // SHARED QA HISTOGRAMS // ======================================================================== - bool anySameEvent = doprocessSameEventHPhis || doprocessSameEventHKstars || doprocessSameEventHPions || doprocessSameEventHHadrons; - bool anyMixedEvent = doprocessMixedEventHPhis || doprocessMixedEventHPhisInBuffer || doprocessMixedEventHKstars || doprocessMixedEventHKstarsInBuffer || doprocessMixedEventHPions || doprocessMixedEventHHadrons; + bool anySameEvent = doprocessSameEventHPhis || doprocessSameEventHKstars || doprocessSameEventHPions || doprocessSameEventHKaons || doprocessSameEventHHadrons; + bool anyMixedEvent = doprocessMixedEventHPhis || doprocessMixedEventHPhisInBuffer || doprocessMixedEventHKstars || doprocessMixedEventHKstarsInBuffer || doprocessMixedEventHPions || doprocessMixedEventHKaons || doprocessMixedEventHHadrons; if (doMixingQAandEventQA && (anySameEvent || anyMixedEvent)) { if (anySameEvent) @@ -669,6 +687,9 @@ struct HResonanceCorrelation { } else if (i == IndexPion) { // Pion doSE = doprocessSameEventHPions; doME = doprocessMixedEventHPions; + } else if (i == IndexKaon) { // Kaon + doSE = doprocessSameEventHKaons; + doME = doprocessMixedEventHKaons; } else { // Hadron doSE = doprocessSameEventHHadrons; doME = doprocessMixedEventHHadrons; @@ -772,7 +793,18 @@ struct HResonanceCorrelation { histos.add("hNegativePionEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); } - if (TESTBIT(doCorrelation, 3) && doprocessSameEventHHadrons) { + if (TESTBIT(doCorrelation, IndexKaon) && doprocessSameEventHKaons) { + if (masterConfigurations.doFullCorrelationStudy) { + histos.add("sameEvent/TriggerParticlesKaon", "TriggersKaon", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); + } + histos.add("hNumberOfRejectedPairsKaon", "hNumberOfRejectedPairsKaon", kTH1F, {{1, 0, 1}}); + histos.add("hKaonEtaVsPtAllSelected", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hKaonEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hPositiveKaonEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + histos.add("hNegativeKaonEtaVsPt", "", kTH3F, {axesConfigurations.axisPtQA, axesConfigurations.axisEta, axesConfigurations.axisMult}); + } + + if (TESTBIT(doCorrelation, IndexHadron) && doprocessSameEventHHadrons) { if (masterConfigurations.doFullCorrelationStudy) { histos.add("sameEvent/TriggerParticlesHadron", "TriggersHadron", kTH2F, {axesConfigurations.axisPtQA, axesConfigurations.axisMult}); } @@ -933,7 +965,7 @@ struct HResonanceCorrelation { // event selections in Pb-Pb template - bool isisCollisionSelectPbPb(TCollision collision, bool fillHists) + bool isisCollisionSelectPbPb(TCollision const& collision, bool fillHists) { if (fillHists) histos.fill(HIST("hEventSelection"), 0.5 /* all collisions */); @@ -1012,7 +1044,7 @@ struct HResonanceCorrelation { } template - bool isValidTrigger(TTrack track, bool isLeading) + bool isValidTrigger(TTrack const& track, bool isLeading) { if (track.tpcNClsCrossedRows() < trackSelection.minTPCNCrossedRowsTrigger) { return false; // crossed rows @@ -1049,7 +1081,7 @@ struct HResonanceCorrelation { return true; } template - bool isValidAssocHadron(TTrack track) + bool isValidAssocHadron(TTrack const& track) { if (track.tpcNClsCrossedRows() < trackSelection.minTPCNCrossedRowsAssociated) { return false; // crossed rows @@ -1086,14 +1118,17 @@ struct HResonanceCorrelation { double phaseProton = (-0.3 * B * assoc[2]) / (2 * assoc[1]); double phaseTrack = (-0.3 * B * trigg[2]) / (2 * trigg[1]); - for (double r = MinRadiusTPC; r <= MaxRadiusTPC; r += 0.05) { + constexpr double kRadiusStep = 0.05; + int nSteps = static_cast((MaxRadiusTPC - MinRadiusTPC) / kRadiusStep); + for (int iR = 0; iR <= nSteps; ++iR) { + double r = MinRadiusTPC + iR * kRadiusStep; dPhiStar = dPhi + std::asin(phaseProton * r) - std::asin(phaseTrack * r); dPhiStarMean += (dPhiStar / 34); } return dPhiStarMean; } - void fillTriggerHistogram(std::shared_ptr hist, double pt, double mult, float eff, float effUncert, float purity, float purityErr) + void fillTriggerHistogram(std::shared_ptr const& hist, double pt, double mult, float eff, float effUncert, float purity, float purityErr) { int binx = hist->GetXaxis()->FindBin(pt); int biny = hist->GetYaxis()->FindBin(mult); @@ -1104,7 +1139,7 @@ struct HResonanceCorrelation { hist->SetBinContent(binx, biny, newContent); hist->SetBinError(binx, biny, newUncert); } - void fillCorrelationHistogram(std::shared_ptr hist, double binFillThn[], float etaWeight, float efficiency, float totalEffUncert, float purity, float totalPurityUncert) + void fillCorrelationHistogram(std::shared_ptr const& hist, double binFillThn[], float etaWeight, float efficiency, float totalEffUncert, float purity, float totalPurityUncert) { float previousContent, previousError2, currentContent, currentError2; int bin = hist->GetBin(binFillThn); From 6aebba01e3ff092f229a22b26fe32af5bc02f6aa Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 21:28:24 +0530 Subject: [PATCH 13/17] Implement same and mixed event processing for Kaons Added processing functions for hadron-Kaon correlations in both same and mixed events. Updated histogram filling logic to accommodate mass axis for Kaons. --- .../Resonances/hResonanceCorrelation.cxx | 261 +++++++++++++++--- 1 file changed, 221 insertions(+), 40 deletions(-) diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 739266c5519..5aa08850485 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -153,8 +153,17 @@ struct HResonanceCorrelation { ConfigurableAxis axisPtTrigger{"axisPtTrigger", {VARIABLE_WIDTH, 0.0, 1.0, 2.0, 3.0, 100}, "pt associated axis for histograms"}; ConfigurableAxis axisPtQA{"axisPtQA", {VARIABLE_WIDTH, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f, 4.0f, 4.4f, 4.8f, 5.2f, 5.6f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 17.0f, 19.0f, 21.0f, 23.0f, 25.0f, 30.0f, 35.0f, 40.0f, 50.0f}, "pt axis for QA histograms"}; ConfigurableAxis axisMassNSigma{"axisMassNSigma", {40, -2, 2}, "Axis for mass Nsigma"}; - ConfigurableAxis axisPhiMass{"axisPhiMass", {300, 1.01f, 1.31f}, "Inv. Mass (GeV/c^{2})"}; - ConfigurableAxis axisKstarMass{"axisKstarMass", {300, 0.596f, 1.196f}, "Inv. Mass (GeV/c^{2})"}; + // Bounds must stay >= the filter task's own acceptance pre-cut + // (hResonanceCorrelationFilter.cxx: peakMass +/- maxMassNSigma*sigma, + // ~[0.998, 1.041] GeV for Phi and ~[0.654, 1.137] GeV for K*0 by + // default) -- anything narrower here silently underflows/overflows + // candidates the filter already let through, before fillCorrelationHistWithMass + // ever gets a chance to keep them. Bin count trimmed from the previous + // 300 (which spanned mostly-empty ranges, e.g. Phi out to 1.31 GeV) down + // to what the narrower, physically-relevant range actually needs while + // keeping comparable or finer MeV/bin resolution for the post-processing fit. + ConfigurableAxis axisPhiMass{"axisPhiMass", {100, 0.99f, 1.05f}, "Inv. Mass (GeV/c^{2})"}; + ConfigurableAxis axisKstarMass{"axisKstarMass", {150, 0.65f, 1.15f}, "Inv. Mass (GeV/c^{2})"}; ConfigurableAxis axisMultiplicity{"axisMultiplicity", {VARIABLE_WIDTH, 0, 20, 40, 60, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300}, "Binning of the Multiplicity axis in model prediction process"}; ConfigurableAxis axisMidrapidityMultiplicity{"axisMidrapidityMultiplicity", {VARIABLE_WIDTH, 0, 20, 40, 60, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300}, "Binning of the Midrapidity Multiplicity axis in model prediction process"}; } axesConfigurations; @@ -256,6 +265,10 @@ struct HResonanceCorrelation { int region; float efficiency; float efficiencyError; + float mass = 0.f; // only meaningful for Phi/K*0 buffered candidates when + // masterConfigurations.fillCorrelationHistWithMass is + // set; unused (stays 0) for trigger particles and for + // Pion/Kaon/Hadron, which never carry a mass axis. // no species tag stored here: the `type` argument to addValidParticle // below (-1 = trigger, else Phi/K*0) only decides which vector this // particle goes into; once stored, nothing reads it back. @@ -264,9 +277,9 @@ struct HResonanceCorrelation { float mult = 0.f; std::vector trigParticles; std::vector assocParticles; - void addValidParticle(float eta, float phi, float pt, int region, float efficiency, float efficiencyError, int type) + void addValidParticle(float eta, float phi, float pt, int region, float efficiency, float efficiencyError, int type, float mass = 0.f) { - ValidParticle particle{eta, phi, pt, region, efficiency, efficiencyError}; + ValidParticle particle{eta, phi, pt, region, efficiency, efficiencyError, mass}; if (type == -1) { trigParticles.push_back(particle); @@ -730,7 +743,14 @@ struct HResonanceCorrelation { if (masterConfigurations.doFullCorrelationStudy) { if (doSE) { if (masterConfigurations.fillCorrelationHistWithMass && (i == IndexPhi || i == IndexKstar)) { - histos.add(fmt::format("sameEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + // Mass is ADDED as its own axis here, not swapped in for + // Delta-eta: the mixed-event acceptance correction needs + // Delta-eta (pair acceptance isn't flat in it), so it has to + // stay present even when mass is also kept for post-processing + // signal/sideband extraction. 7 axes total; see + // fillCorrelationsPhi/fillCorrelationsKstar's binFillThnMass + // for the matching fill order. + histos.add(fmt::format("sameEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); } else { histos.add(fmt::format("sameEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); } @@ -743,7 +763,11 @@ struct HResonanceCorrelation { if (doME) { if (masterConfigurations.fillCorrelationHistWithMass && (i == IndexPhi || i == IndexKstar)) { - histos.add(fmt::format("mixedEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); + // Same 7-axis layout as the sameEvent/Signal booking above -- + // must match, since CorrelationBuilder-style same/mixed + // acceptance correction requires identical axis definitions on + // both sides. + histos.add(fmt::format("mixedEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, (i == IndexPhi ? axesConfigurations.axisPhiMass : axesConfigurations.axisKstarMass), axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); } else { histos.add(fmt::format("mixedEvent/Signal/{}", Particlenames[i]).c_str(), "", kTHnF, {axisDeltaPhiNDim, axisDeltaEtaNDim, axisPtAssocNDim, axisPtTriggerNDim, axisVtxZNDim, axisMultNDim}); } @@ -1151,9 +1175,18 @@ struct HResonanceCorrelation { hist->SetBinError2(bin, currentError2); } - template + // Species is an explicit compile-time tag (IndexPion, IndexKaon or + // IndexHadron) rather than inferred from column presence on THadrons: + // Pion's and Kaon's assoc tables are both Join + // (AssocPID carries all four species' nSigmas generically), so a + // `requires { assocTrack.nSigmaTPCPi(); }` check can no longer tell them + // apart the way it could tell Pion apart from the plain (no-PID) Hadron + // table. Only Hadron's THadrons type lacks PID columns at all. + template void fillCorrelationsHadron(TTriggers const& triggers, THadrons const& assocs, bool mixing, float pvz, float mult, double bField) { + static_assert(Species == IndexPion || Species == IndexKaon || Species == IndexHadron, + "fillCorrelationsHadron: Species must be IndexPion, IndexKaon or IndexHadron"); for (auto const& triggerTrack : triggers) { if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) @@ -1195,8 +1228,10 @@ struct HResonanceCorrelation { } } if (!mixing) { - if constexpr (requires { triggerTrack.extra(); }) + if constexpr (Species == IndexPion) fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesPion")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); + else if constexpr (Species == IndexKaon) + fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesKaon")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); else fillTriggerHistogram(histos.get(HIST("sameEvent/TriggerParticlesHadron")), trigg.pt(), mult, efficiencyTrigger, efficiencyTriggerError, purityTrigger, purityTriggerError); } @@ -1208,8 +1243,10 @@ struct HResonanceCorrelation { //---] removing autocorrelations [--- if (doAutocorrelationRejection) { if (trigg.globalIndex() == assoc.globalIndex()) { - if constexpr (requires { assocTrack.nSigmaTPCPi(); }) + if constexpr (Species == IndexPion) histos.fill(HIST("hNumberOfRejectedPairsPion"), 0.5); + else if constexpr (Species == IndexKaon) + histos.fill(HIST("hNumberOfRejectedPairsKaon"), 0.5); else histos.fill(HIST("hNumberOfRejectedPairsHadron"), 0.5); continue; @@ -1253,10 +1290,14 @@ struct HResonanceCorrelation { float efficiencyUncertainty = 0.0f; float totalPurityUncert = 0.0; if (efficiencyFlags.applyEfficiencyCorrection) { - if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + if constexpr (Species == IndexPion) { efficiency = hEfficiencyPion->Interpolate(ptassoc, assoc.eta()); if (efficiencyFlags.applyEfficiencyPropagation) efficiencyUncertainty = hEfficiencyUncertaintyPion->Interpolate(ptassoc, assoc.eta()); + } else if constexpr (Species == IndexKaon) { + efficiency = hEfficiencyKaon->Interpolate(ptassoc, assoc.eta()); + if (efficiencyFlags.applyEfficiencyPropagation) + efficiencyUncertainty = hEfficiencyUncertaintyKaon->Interpolate(ptassoc, assoc.eta()); } else { if (efficiencyFlags.applyEffAsFunctionOfMult) efficiency = hEfficiencyHadronMult->Interpolate(ptassoc, assoc.eta(), mult); @@ -1294,13 +1335,20 @@ struct HResonanceCorrelation { double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; double deltaPhiStar = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStar, bField); if (!mixing) { - if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + if constexpr (Species == IndexPion) { fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Pion")), binFillThn, etaWeight, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); if (triggSign == assocSign && doDeltaPhiStarCheck) { histos.fill(HIST("sameEvent/Signal/Pion") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), 0.5); } else if (doDeltaPhiStarCheck) { histos.fill(HIST("sameEvent/Signal/Pion") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), -0.5); } + } else if constexpr (Species == IndexKaon) { + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Kaon")), binFillThn, etaWeight, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + if (triggSign == assocSign && doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Kaon") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), 0.5); + } else if (doDeltaPhiStarCheck) { + histos.fill(HIST("sameEvent/Signal/Kaon") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), -0.5); + } } else { if (triggSign == assocSign && doDeltaPhiStarCheck) { histos.fill(HIST("sameEvent/Signal/Hadron") + HIST("DeltaPhiStar"), deltaPhiStar, trigg.eta() - assoc.eta(), 0.5); @@ -1310,8 +1358,10 @@ struct HResonanceCorrelation { fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Hadron")), binFillThn, etaWeight, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); } } else { - if constexpr (requires { assocTrack.nSigmaTPCPi(); }) { + if constexpr (Species == IndexPion) { fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Pion")), binFillThn, 1, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); + } else if constexpr (Species == IndexKaon) { + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kaon")), binFillThn, 1, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); } else { fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Hadron")), binFillThn, 1, efficiency * efficiencyTrigger, totalEffUncert, purity * purityTrigger, totalPurityUncert); } @@ -1529,6 +1579,9 @@ struct HResonanceCorrelation { } double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + // 7-slot layout matching the mass-mode Signal booking above: mass + // ADDED after Delta-eta, everything else shifted one slot right. + double binFillThnMass[7] = {deltaphi, deltaeta, massassoc, ptassoc, pttrigger, pvz, mult}; const double assocForDeltaPhiStarPlus[] = {c.phiKplus, c.ptKplus, c.signKplus}; const double assocForDeltaPhiStarMinus[] = {c.phiKminus, c.ptKminus, c.signKminus}; @@ -1551,10 +1604,7 @@ struct HResonanceCorrelation { } } if (!mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { - if (masterConfigurations.fillCorrelationHistWithMass) { - binFillThn[1] = massassoc; - } - fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Phi")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Phi")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); if (doDeltaPhiStarCheck) { double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); @@ -1582,22 +1632,27 @@ struct HResonanceCorrelation { } } - // Mixed Event Logic buffered in ValidCollision - if (mixing && c.isLeftBg) { + // Mixed Event Logic buffered in ValidCollision. LeftBg/RightBg are + // only booked when !fillCorrelationHistWithMass (see booking above), + // so both are skipped entirely in mass mode -- filling/replaying into + // an unbooked histogram would abort. Signal carries a real mass + // value through the buffer (ValidParticle::mass) so the replay loop + // below can build the correct 7-slot fill array in mass mode too. + if (mixing && c.isLeftBg && !masterConfigurations.fillCorrelationHistWithMass) { if (mixingInBf) { currentCollision.addValidParticle(c.eta, c.phi, c.pt, 0, c.efficiency, c.efficiencyError, 0 /* 0 = Phi type */); } else { fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Phi")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); } } - if (mixing && c.isSignal) { + if (mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { if (mixingInBf) { - currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 0); + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 0, c.mass); } else { - fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); } } - if (mixing && c.isRightBg) { + if (mixing && c.isRightBg && !masterConfigurations.fillCorrelationHistWithMass) { if (mixingInBf) { currentCollision.addValidParticle(c.eta, c.phi, c.pt, 2, c.efficiency, c.efficiencyError, 0); } else { @@ -1642,12 +1697,18 @@ struct HResonanceCorrelation { deltaeta = std::abs(deltaeta); } double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + double binFillThnMass[7] = {deltaphi, deltaeta, assoc.mass, ptassoc, pttrigger, pvz, mult}; + // region 0/2 (LeftBg/RightBg) are never buffered in mass mode (see + // the buffering guard above), so no separate guard is needed here + // beyond region itself never being 0/2 in that case; region 1 + // (Signal) picks the 7-slot mass array when mass mode is on, to + // match the booking above. if (assoc.region == 0) { fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); } if (assoc.region == 1) { - fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Phi")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); } if (assoc.region == 2) { fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Phi")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); @@ -1869,6 +1930,9 @@ struct HResonanceCorrelation { } double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + // 7-slot layout matching the mass-mode Signal booking above: mass + // ADDED after Delta-eta, everything else shifted one slot right. + double binFillThnMass[7] = {deltaphi, deltaeta, massassoc, ptassoc, pttrigger, pvz, mult}; const double assocForDeltaPhiStarPlus[] = {c.phiPos, c.ptPos, c.signPos}; const double assocForDeltaPhiStarMinus[] = {c.phiNeg, c.ptNeg, c.signNeg}; @@ -1891,10 +1955,7 @@ struct HResonanceCorrelation { } } if (!mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { - if (masterConfigurations.fillCorrelationHistWithMass) { - binFillThn[1] = massassoc; - } - fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Kstar0")), binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + fillCorrelationHistogram(histos.get(HIST("sameEvent/Signal/Kstar0")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, etaWeight, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); if (doDeltaPhiStarCheck) { double deltaPhiStarPlus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarPlus, bField); double deltaPhiStarMinus = calculateAverageDeltaPhiStar(triggForDeltaPhiStar, assocForDeltaPhiStarMinus, bField); @@ -1924,22 +1985,27 @@ struct HResonanceCorrelation { // Mixed-event logic: buffered into ValidCollision when mixing via the // collision pool (processMixedEventHKstarsInBuffer), filled directly - // otherwise (processMixedEventHKstars). - if (mixing && c.isLeftBg) { + // otherwise (processMixedEventHKstars). LeftBg/RightBg are only + // booked when !fillCorrelationHistWithMass (see booking above), so + // both are skipped entirely in mass mode -- filling/replaying into an + // unbooked histogram would abort. Signal carries a real mass value + // through the buffer (ValidParticle::mass) so the replay loop below + // can build the correct 7-slot fill array in mass mode too. + if (mixing && c.isLeftBg && !masterConfigurations.fillCorrelationHistWithMass) { if (mixingInBf) { currentCollision.addValidParticle(c.eta, c.phi, c.pt, 0, c.efficiency, c.efficiencyError, 1 /* 1 = Kstar0 type */); } else { fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Kstar0")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); } } - if (mixing && c.isSignal) { + if (mixing && (masterConfigurations.fillCorrelationHistWithMass || c.isSignal)) { if (mixingInBf) { - currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 1); + currentCollision.addValidParticle(c.eta, c.phi, c.pt, 1, c.efficiency, c.efficiencyError, 1, c.mass); } else { - fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, 1, c.efficiency * efficiencyTrigg, totalEffUncert, purityTrigg, purityTriggErr); } } - if (mixing && c.isRightBg) { + if (mixing && c.isRightBg && !masterConfigurations.fillCorrelationHistWithMass) { if (mixingInBf) { currentCollision.addValidParticle(c.eta, c.phi, c.pt, 2, c.efficiency, c.efficiencyError, 1); } else { @@ -1985,12 +2051,16 @@ struct HResonanceCorrelation { deltaeta = std::abs(deltaeta); } double binFillThn[6] = {deltaphi, deltaeta, ptassoc, pttrigger, pvz, mult}; + double binFillThnMass[7] = {deltaphi, deltaeta, assoc.mass, ptassoc, pttrigger, pvz, mult}; + // region 0/2 (LeftBg/RightBg) are never buffered in mass mode (see + // the buffering guard above); region 1 (Signal) picks the 7-slot + // mass array when mass mode is on, to match the booking above. if (assoc.region == 0) { fillCorrelationHistogram(histos.get(HIST("mixedEvent/LeftBg/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); } if (assoc.region == 1) { - fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); + fillCorrelationHistogram(histos.get(HIST("mixedEvent/Signal/Kstar0")), masterConfigurations.fillCorrelationHistWithMass ? binFillThnMass : binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); } if (assoc.region == 2) { fillCorrelationHistogram(histos.get(HIST("mixedEvent/RightBg/Kstar0")), binFillThn, 1, efficiencyTrigg * efficiencyAssoc, totalEffUncert, 1., 0.); @@ -2079,14 +2149,14 @@ struct HResonanceCorrelation { return; } // ________________________________________________ - if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions && doMixingQAandEventQA) { + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions && !doprocessSameEventHKaons && doMixingQAandEventQA) { histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); histos.fill(HIST("EventQA/hPvz"), collision.posZ()); } // Do basic QA - if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions) { + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions && !doprocessSameEventHKaons) { for (auto const& triggerTrack : triggerTracks) { auto track = triggerTrack.track_as(); if (!isValidTrigger(track, triggerTrack.isLeading())) @@ -2137,7 +2207,7 @@ struct HResonanceCorrelation { // ________________________________________________ // Do hadron - hadron correlations if (masterConfigurations.doFullCorrelationStudy) - fillCorrelationsHadron(triggerTracks, assocHadrons, false, collision.posZ(), collision.centFT0M(), bField); + fillCorrelationsHadron(triggerTracks, assocHadrons, false, collision.posZ(), collision.centFT0M(), bField); } void processSameEventHPions(soa::Join::iterator const& collision, soa::Join const& associatedPions, soa::Join const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) @@ -2202,7 +2272,72 @@ struct HResonanceCorrelation { // ________________________________________________ // Do hadron - Pion correlations if (masterConfigurations.doFullCorrelationStudy) - fillCorrelationsHadron(triggerTracks, associatedPions, false, collision.posZ(), collision.centFT0M(), bField); + fillCorrelationsHadron(triggerTracks, associatedPions, false, collision.posZ(), collision.centFT0M(), bField); + } + + void processSameEventHKaons(soa::Join::iterator const& collision, soa::Join const& associatedKaons, soa::Join const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + LOGF(info, "SameEventKaon: collisions=%d triggers=%zu assocKaons=%zu", collision.globalIndex(), triggerTracks.size(), associatedKaons.size()); + + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && !TESTBIT(triggerPresenceMap[collision.globalIndex()], triggerBinToSelect)) { + return; + } + auto bc = collision.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + + // ________________________________________________ + // Perform basic event selection + if (!isisCollisionSelect(collision)) { + return; + } + // ________________________________________________ + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions && doMixingQAandEventQA) { + histos.fill(HIST("MixingQA/hSECollisionBins"), colBinning.getBin({collision.posZ(), collision.centFT0M()})); + histos.fill(HIST("EventQA/hMult"), collision.centFT0M()); + histos.fill(HIST("EventQA/hPvz"), collision.posZ()); + } + // Do basic QA + for (auto const& kaon : associatedKaons) { + auto kaonTrack = kaon.track_as(); + if (!isValidAssocHadron(kaonTrack)) + continue; + + histos.fill(HIST("hKaonEtaVsPtAllSelected"), kaonTrack.pt(), kaonTrack.eta(), collision.centFT0M()); + if (doAssocPhysicalPrimary && !kaon.mcPhysicalPrimary()) + continue; + if (masterConfigurations.doMCassociation && std::abs(kaon.pdgCode()) != PdgCodes[IndexKaon]) + continue; + histos.fill(HIST("hKaonEtaVsPt"), kaonTrack.pt(), kaonTrack.eta(), collision.centFT0M()); + if (kaonTrack.sign() > 0) + histos.fill(HIST("hPositiveKaonEtaVsPt"), kaonTrack.pt(), kaonTrack.eta(), collision.centFT0M()); + else + histos.fill(HIST("hNegativeKaonEtaVsPt"), kaonTrack.pt(), kaonTrack.eta(), collision.centFT0M()); + } + if (!doprocessSameEventHPhis && !doprocessSameEventHKstars && !doprocessSameEventHPions) { + for (auto const& triggerTrack : triggerTracks) { + auto track = triggerTrack.track_as(); + if (!isValidTrigger(track, triggerTrack.isLeading())) + continue; + histos.fill(HIST("hTriggerAllSelectedEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTriggerPtResolution"), track.pt(), triggerTrack.mcOriginalPt()); + if (masterConfigurations.doTriggPhysicalPrimary && !triggerTrack.mcPhysicalPrimary()) + continue; + histos.fill(HIST("hTriggerPrimaryEtaVsPt"), track.pt(), track.eta(), collision.centFT0M()); + histos.fill(HIST("hTrackEtaVsPtVsPhi"), track.pt(), track.eta(), track.phi()); + } + } + + // ________________________________________________ + // Do hadron - Kaon correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(triggerTracks, associatedKaons, false, collision.posZ(), collision.centFT0M(), bField); } void processSameEventHPhis(soa::Join::iterator const& collision, aod::AssocPhis const& associatedPhis, aod::TriggerTracks const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) @@ -2360,7 +2495,7 @@ struct HResonanceCorrelation { // ________________________________________________ // Do hadron - hadron correlations if (masterConfigurations.doFullCorrelationStudy) - fillCorrelationsHadron(slicedTriggerTracks, slicedAssocHadrons, true, collision1.posZ(), collision1.centFT0M(), bField); + fillCorrelationsHadron(slicedTriggerTracks, slicedAssocHadrons, true, collision1.posZ(), collision1.centFT0M(), bField); } } @@ -2404,7 +2539,51 @@ struct HResonanceCorrelation { // ________________________________________________ // Do hadron - phi correlations if (masterConfigurations.doFullCorrelationStudy) - fillCorrelationsHadron(slicedTriggerTracks, slicedAssocPions, true, collision1.posZ(), collision1.centFT0M(), bField); + fillCorrelationsHadron(slicedTriggerTracks, slicedAssocPions, true, collision1.posZ(), collision1.centFT0M(), bField); + } + } + + void processMixedEventHKaons(soa::Join const& collisions, soa::Join const& assocKaons, soa::Join const& triggerTracks, TracksComplete const&, aod::BCsWithTimestamps const&) + { + BinningTypePP colBinning{{axesConfigurations.axisVtxZ, axesConfigurations.axisMult}, true}; + for (auto const& [collision1, collision2] : soa::selfCombinations(colBinning, masterConfigurations.mixingParameter, -1, collisions, collisions)) { + auto bc = collision1.bc_as(); + auto bField = getMagneticField(bc.timestamp()); + // ________________________________________________ + if (efficiencyFlags.applyEfficiencyCorrection) { + initEfficiencyFromCCDB(bc); + } + // ________________________________________________ + // skip if desired trigger not found + if (triggerPresenceMap.size() > 0 && (!TESTBIT(triggerPresenceMap[collision1.globalIndex()], triggerBinToSelect) || !TESTBIT(triggerPresenceMap[collision2.globalIndex()], triggerBinToSelect))) { + continue; + } + + // ________________________________________________ + // Perform basic event selection on both collisions + if (!isisCollisionSelect(collision1) || !isisCollisionSelect(collision2)) { + continue; + } + if (collision1.centFT0M() > axisRanges[5][1] || collision1.centFT0M() < axisRanges[5][0]) + continue; + if (collision2.centFT0M() > axisRanges[5][1] || collision2.centFT0M() < axisRanges[5][0]) + continue; + if (doMixingQAandEventQA) { + if (collision1.globalIndex() == collision2.globalIndex()) { + histos.fill(HIST("MixingQA/hMixingQA"), 0.0f); // same-collision pair counting + } + histos.fill(HIST("MixingQA/hMEpvz1"), collision1.posZ()); + histos.fill(HIST("MixingQA/hMEpvz2"), collision2.posZ()); + histos.fill(HIST("MixingQA/hMECollisionBins"), colBinning.getBin({collision1.posZ(), collision1.centFT0M()})); + } + // ________________________________________________ + // Do slicing + auto slicedTriggerTracks = triggerTracks.sliceBy(collisionSliceTracks, collision1.globalIndex()); + auto slicedAssocKaons = assocKaons.sliceBy(collisionSliceHadrons, collision2.globalIndex()); + // ________________________________________________ + // Do hadron - Kaon correlations + if (masterConfigurations.doFullCorrelationStudy) + fillCorrelationsHadron(slicedTriggerTracks, slicedAssocKaons, true, collision1.posZ(), collision1.centFT0M(), bField); } } @@ -3092,11 +3271,13 @@ struct HResonanceCorrelation { PROCESS_SWITCH(HResonanceCorrelation, processSelectEventWithTrigger, "Select events with trigger only", true); PROCESS_SWITCH(HResonanceCorrelation, processSameEventHPions, "Process same events, h-Pion", true); + PROCESS_SWITCH(HResonanceCorrelation, processSameEventHKaons, "Process same events, h-Kaon", true); PROCESS_SWITCH(HResonanceCorrelation, processSameEventHHadrons, "Process same events, h-h", true); PROCESS_SWITCH(HResonanceCorrelation, processSameEventHPhis, "Process same events, h-phi", true); PROCESS_SWITCH(HResonanceCorrelation, processSameEventHKstars, "Process same events, h-K*0", true); PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPions, "Process mixed events, h-Pion", true); + PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHKaons, "Process mixed events, h-Kaon", true); PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHHadrons, "Process mixed events, h-h", true); PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPhis, "Process mixed events, h-phi", true); PROCESS_SWITCH(HResonanceCorrelation, processMixedEventHPhisInBuffer, "Process mixed-event, h-phi using collision buffer", false); From 9c87fbaf5913da5a91a728b9ea671ee82d982c62 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 21:28:59 +0530 Subject: [PATCH 14/17] Refactor mass window configurations and filtering logic Updated mass window configurations for Phi and K*0 resonances, removing old mass limits and adding new configurable groups for peak mass and sigma. Adjusted mass window checks in the filtering logic to use the new configurations. --- .../hResonanceCorrelationFilter.cxx | 57 +++++++++++++++---- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index 388d4cd556b..303697f18ac 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -40,10 +40,11 @@ #include #include #include +#include #include #include -#include +#include #include #include @@ -85,12 +86,6 @@ struct HResonanceCorrelationFilter { Configurable cfgTPCNsigmaKaon{"cfgTPCNsigmaKaon", 3.0f, "TPC Kaon PID"}; Configurable cfgTOFNsigmaKaon{"cfgTOFNsigmaKaon", 3.0f, "TOF Kaon PID"}; - Configurable cfgMinMass{"cfgMinMass", 1.005f, "Minimum KK mass"}; - Configurable cfgMaxMass{"cfgMaxMass", 1.035f, "Maximum KK mass"}; - - Configurable cfgMinMassKstar{"cfgMinMassKstar", 0.796f, "Minimum K#pi mass"}; - Configurable cfgMaxMassKstar{"cfgMaxMassKstar", 0.996f, "Maximum K#pi mass"}; - Configurable cfgRapidity{"cfgRapidity", 0.5f, "Rapidity cut"}; // used for event selections in Pb-Pb @@ -223,7 +218,38 @@ struct HResonanceCorrelationFilter { Configurable parameterCCDBPath{"parameterCCDBPath", "Users/k/kcui/LHC25b4a/parameter", "Path of the mean and sigma"}; // must include windows for background and peak - Configurable maxMassNSigma{"maxMassNSigma", 12.0f, "max mass region to be considered for further analysis"}; + // + // This filter-level cut is an acceptance pre-cut only -- it decides what + // ever reaches AssocPhis/AssocKstars, not what counts as signal. It must + // stay wider than whatever signal+background region hResonanceCorrelation.cxx + // wants downstream (its massWindowConfigurationsPhi/Kstar go out to + // maxBgNSigma=6 by default), or that background region gets silently + // truncated before it ever reaches the analysis task. peakMass/sigma here + // mirror the analysis task's own massWindowConfigurationsPhi/Kstar + // (sigma = PDG Gamma/2.355 placeholder -- refit from your own peak and + // update both files together, they must agree on what "sigma" means). + Configurable maxMassNSigma{"maxMassNSigma", 12.0f, "max mass region to be considered for further analysis, in units of sigma"}; + + struct : ConfigurableGroup { + std::string prefix = "massWindowConfigurationsPhi"; + Configurable peakMass{"peakMass", 1.019455f, "Phi(1020) PDG mass (GeV)"}; + Configurable sigma{"sigma", 0.0018f, "effective width (GeV) for the maxMassNSigma pre-cut -- keep in sync with hResonanceCorrelation.cxx's massWindowConfigurationsPhi.sigma"}; + } massWindowConfigurationsPhi; + + struct : ConfigurableGroup { + std::string prefix = "massWindowConfigurationsKstar"; + Configurable peakMass{"peakMass", 0.89555f, "K*0(892) PDG mass (GeV)"}; + Configurable sigma{"sigma", 0.0201f, "effective width (GeV) for the maxMassNSigma pre-cut -- keep in sync with hResonanceCorrelation.cxx's massWindowConfigurationsKstar.sigma"}; + } massWindowConfigurationsKstar; + + // Derived acceptance-window bounds, computed once in init() from the + // Configurables above (peakMass +/- maxMassNSigma * sigma) rather than + // hand-entered GeV numbers, so there is exactly one place to widen the + // pre-cut instead of four independent literals that can drift apart. + float mMinMassPhi = 0.f; + float mMaxMassPhi = 0.f; + float mMinMassKstar = 0.f; + float mMaxMassKstar = 0.f; // For extracting strangeness mass QA plots struct : ConfigurableGroup { @@ -292,6 +318,13 @@ struct HResonanceCorrelationFilter { zorroSummary.setObject(zorro.getZorroSummary()); mRunNumber = -1; + + mMinMassPhi = massWindowConfigurationsPhi.peakMass - maxMassNSigma * massWindowConfigurationsPhi.sigma; + mMaxMassPhi = massWindowConfigurationsPhi.peakMass + maxMassNSigma * massWindowConfigurationsPhi.sigma; + mMinMassKstar = massWindowConfigurationsKstar.peakMass - maxMassNSigma * massWindowConfigurationsKstar.sigma; + mMaxMassKstar = massWindowConfigurationsKstar.peakMass + maxMassNSigma * massWindowConfigurationsKstar.sigma; + LOGF(info, "Assoc mass pre-cut windows: Phi [%.4f, %.4f] GeV, K*0 [%.4f, %.4f] GeV", + mMinMassPhi, mMaxMassPhi, mMinMassKstar, mMaxMassKstar); } void initCCDB(aod::BCsWithTimestamps::iterator const& bc) @@ -1095,7 +1128,7 @@ struct HResonanceCorrelationFilter { } // optional mass window - if (invMass < cfgMinMass || invMass > cfgMaxMass) { + if (invMass < mMinMassPhi || invMass > mMaxMassPhi) { continue; } @@ -1181,7 +1214,7 @@ struct HResonanceCorrelationFilter { } // optional mass window - if (invMass < cfgMinMass || invMass > cfgMaxMass) { + if (invMass < mMinMassPhi || invMass > mMaxMassPhi) { continue; } @@ -1294,7 +1327,7 @@ struct HResonanceCorrelationFilter { } // mass window - if (invMass < cfgMinMassKstar || invMass > cfgMaxMassKstar) { + if (invMass < mMinMassKstar || invMass > mMaxMassKstar) { continue; } @@ -1375,7 +1408,7 @@ struct HResonanceCorrelationFilter { } // mass window - if (invMass < cfgMinMassKstar || invMass > cfgMaxMassKstar) { + if (invMass < mMinMassKstar || invMass > mMaxMassKstar) { continue; } From 6bef334a612df032e753120a952153f1c702653f Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 21:31:30 +0530 Subject: [PATCH 15/17] Fix formatting and alignment in hResonanceCorrelationFilter.cxx --- .../Resonances/hResonanceCorrelationFilter.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index 303697f18ac..a96abe068d9 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -698,8 +698,8 @@ struct HResonanceCorrelationFilter { // outer `requires { assoc.tofSignal(); }` is already false for FullTracks // (Hadron's track type, which has no PID columns at all). enum AssocSpecies { AssocPion = 0, - AssocKaon = 1, - AssocHadron = 2 }; + AssocKaon = 1, + AssocHadron = 2 }; // Merged predicate for the Pion/Kaon/Hadron associated-track pools. Pion and // Kaon both run over IDTracks/IDTracksMC (identical C++ type), so the two @@ -1395,9 +1395,9 @@ struct HResonanceCorrelationFilter { } LorentzVectorPtEtaPhiMass kaon = LorentzVectorPtEtaPhiMass(kaonTrack.pt(), kaonTrack.eta(), kaonTrack.phi(), - o2::constants::physics::MassKPlus); + o2::constants::physics::MassKPlus); LorentzVectorPtEtaPhiMass pion = LorentzVectorPtEtaPhiMass(pionTrack.pt(), pionTrack.eta(), pionTrack.phi(), - o2::constants::physics::MassPiPlus); + o2::constants::physics::MassPiPlus); LorentzVectorPtEtaPhiMass kstar = kaon + pion; float invMass = kstar.M(); From aabe916d91d2693e2350719310fb8f90ef098191 Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Fri, 25 Sep 2026 21:39:32 +0530 Subject: [PATCH 16/17] Set default values for ValidParticle members Initialize member variables in ValidParticle struct. --- PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx index 5aa08850485..35f54137039 100644 --- a/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx +++ b/PWGLF/Tasks/Resonances/hResonanceCorrelation.cxx @@ -259,12 +259,12 @@ struct HResonanceCorrelation { struct ValidCollision { struct ValidParticle { - float eta; - float phi; - float pt; - int region; - float efficiency; - float efficiencyError; + float eta = 0.f; + float phi = 0.f; + float pt = 0.f; + int region = -1; + float efficiency = 1.f; + float efficiencyError = 0.f; float mass = 0.f; // only meaningful for Phi/K*0 buffered candidates when // masterConfigurations.fillCorrelationHistWithMass is // set; unused (stays 0) for trigger particles and for From 1792b50f8e93abc8d400823299b4bb192f62e1df Mon Sep 17 00:00:00 2001 From: Hirak Koley Date: Sat, 26 Sep 2026 00:15:43 +0530 Subject: [PATCH 17/17] Include Vector4Dfwd.h for forward declaration Added forward declaration header for Vector4D. --- PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx index a96abe068d9..959014c61ce 100644 --- a/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx +++ b/PWGLF/TableProducer/Resonances/hResonanceCorrelationFilter.cxx @@ -44,7 +44,8 @@ #include #include -#include +#include // IWYU pragma: keep (do not replace with Math/Vector4Dfwd.h) +#include #include #include