From 54d7c4832790fd06035a0debfc082fe51e7555e8 Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Sat, 31 Jan 2026 18:25:31 +0530 Subject: [PATCH 1/9] Adjust DCA and invariant mass axis specifications --- PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx index 16c44ab832b..d8edd64cdb9 100644 --- a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx +++ b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx @@ -102,6 +102,7 @@ struct lambdaAnalysis_pb { Configurable> kaonTOFPIDcut{"kaonTOFPIDcut", {3.0}, "TOF nsigma cuts kaons"}; // Event Mixing. Configurable cNumMixEv{"cNumMixEv", 20, "Number of Events to be mixed"}; + ConfigurableAxis cDCAzBins{"dcazbins", {VARIABLE_WIDTH, -1.2f, -1.0f, -0.9f, -0.8f, -0.7f, -0.6f, -0.5f, -0.4f, -0.3f, -0.2f, -0.1f, 0.f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.2f}, "DCA - z-vertex"}; ConfigurableAxis cMixVtxBins{"cMixVtxBins", {VARIABLE_WIDTH, -10.0f, -9.f, -8.f, -7.f, -6.f, -5.f, -4.f, -3.f, -2.f, -1.f, 0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f}, "Mixing bins - z-vertex"}; ConfigurableAxis cMixMultBins{"cMixMultBins", {VARIABLE_WIDTH, 0.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f, 70.0f, 80.0f, 90.0f, 100.0f, 200.0f}, "Mixing bins - multiplicity"}; @@ -117,7 +118,7 @@ struct lambdaAnalysis_pb { const AxisSpec axisPt_pid(200, 0., 10., "p_{T} (GeV/c)"); const AxisSpec axisPt(nBinsPt, 0., 10., "p_{T} (GeV/c)"); const AxisSpec axisEta(40, -1, 1, "#eta"); - const AxisSpec axisDCAz(500, -0.5, 0.5, {"DCA_{z} (cm)"}); + // const AxisSpec axisDCAz(500, -0.5, 0.5, {"DCA_{z} (cm)"}); const AxisSpec axisDCAxy(240, -0.12, 0.12, {"DCA_{xy} (cm)"}); const AxisSpec axisTPCNCls(200, 0, 200, {"TPCNCls"}); const AxisSpec axisTPCNsigma(401, -10.025, 10.025, {"n#sigma^{TPC}"}); @@ -125,8 +126,9 @@ struct lambdaAnalysis_pb { const AxisSpec axisdEdx(380, 10, 200, {"#frac{dE}{dx}"}); const AxisSpec axisVz(120, -12, 12, {"vz"}); const AxisSpec axisEP(120, -3.14, 3.14, {"#theta"}); - const AxisSpec axisInvM(nBinsInvM, 1.44, 2.04, {"M_{inv} (GeV/c^{2})"}); + const AxisSpec axisInvM(nBinsInvM, 1.2, 1.8, {"M_{inv} (GeV/c^{2})"}); AxisSpec axisOccupancy = {occupancy_bins, "Occupancy [-40,100]"}; + AxisSpec axisDCAz = {cDCAzBins, "DCA_{z} (cm)"}; histos.add("Event/h1d_ft0_mult_percentile", "FT0 (%)", kTH2F, {axisCent, axisOccupancy}); if (doprocessMix || doprocessMixDF || doprocessMixepDF) { @@ -633,7 +635,7 @@ struct lambdaAnalysis_pb { } } - PROCESS_SWITCH(lambdaAnalysis_pb, processMix, "Process for Mixed Events", false); + PROCESS_SWITCH(lambdaAnalysis_pb, processMix, "Process for Mixed Events", true); Preslice perRColdf = aod::resodaughter::resoCollisionDFId; From 57f02d53b47e3d21773180f8da698cecd9d00d38 Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 11:05:59 +0530 Subject: [PATCH 2/9] Enhance lambda1520 analysis with new configurations Added new configurable parameters for track selection and histogram filling. Updated invariant mass calculation and added new histograms for QA and analysis. --- PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx | 293 +++++++++++++++++---- 1 file changed, 244 insertions(+), 49 deletions(-) diff --git a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx index 6af3bde09f8..3a479836501 100644 --- a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx +++ b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx @@ -14,22 +14,33 @@ /// /// \author Yash Patley /// \author Nasir Mehdi Malik +/// \author Durgesh Bhatt #include "PWGLF/DataModel/LFResonanceTables.h" -#include "Common/DataModel/Centrality.h" -#include "Common/DataModel/EventSelection.h" +#include "Common/Core/RecoDecay.h" -#include "CommonConstants/PhysicsConstants.h" -#include "Framework/ASoAHelpers.h" -#include "Framework/AnalysisTask.h" -#include "Framework/runDataProcessing.h" +#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::framework; @@ -41,18 +52,26 @@ struct lambdaAnalysis_pb { Preslice perRCol = aod::resodaughter::resoCollisionId; Preslice perCollision = aod::track::collisionId; // Configurables. - + aod::ResoMCParents const* mResoParents = nullptr; Configurable ConfEvtOccupancyInTimeRange{"ConfEvtOccupancyInTimeRange", false, "occupancy selection true or false"}; Configurable nBinsPt{"nBinsPt", 100, "N bins in pT histogram"}; Configurable nBinsInvM{"nBinsInvM", 120, "N bins in InvMass histogram"}; Configurable lambda1520id{"lambda1520id", 3124, "pdg"}; Configurable doRotate{"doRotate", true, "rotated inv mass spectra"}; - // Tracks Configurable cPtMin{"cPtMin", 0.15, "Minimum Track pT"}; Configurable cPMin{"cPMin", 0., "Minimum Track p"}; - Configurable cEtaCut{"cEtaCut", 0.8, "Pseudorapidity cut"}; + Configurable cEtaMin{"cEtaMin", -0.8, "Minimum Pseudorapidity"}; + Configurable cEtaMax{"cEtaMax", 0.8, "Maximum Pseudorapidity"}; Configurable cDcaz{"cDcazMin", 1., "Minimum DCAz"}; + Configurable cfgRapidityMin{"cfgRapidityMin", -0.5, "Minimum rapidity"}; + Configurable cfgRapidityMax{"cfgRapidityMax", 0.5, "Maximum rapidity"}; + // TPC crossed rows (absolute) + Configurable cfgMinCrossedRows{"cfgMinCrossedRows", 70, "min TPC crossed rows"}; + Configurable cfgUseCrossedRows{"cfgUseCrossedRows", false, "apply crossed rows cut"}; + + Configurable cfgMinTPCcls{"cfgMinTPCcls", 70, "min TPC clusters found"}; + Configurable cfgUseTPCcls{"cfgUseTPCcls", false, "apply TPC clusters cut"}; Configurable> cDcaPtBinsPr{"cDcaPtBinsPr", {0.0f, 0.5f, 1.0f, 2.0f, 3.0f, 5.0f, 1000.0f}, "Proton pT bin edges for DCAxy cut"}; @@ -141,7 +160,7 @@ struct lambdaAnalysis_pb { const AxisSpec axisdEdx(380, 10, 200, {"#frac{dE}{dx}"}); const AxisSpec axisVz(120, -12, 12, {"vz"}); const AxisSpec axisEP(120, -3.14, 3.14, {"#theta"}); - const AxisSpec axisInvM(nBinsInvM, 1.2, 1.8, {"M_{inv} (GeV/c^{2})"}); + const AxisSpec axisInvM(nBinsInvM, 1.4, 2.0, {"M_{inv} (GeV/c^{2})"}); AxisSpec axisOccupancy = {occupancy_bins, "Occupancy [-40,100]"}; AxisSpec axisDCAz = {cDCAzBins, "DCA_{z} (cm)"}; @@ -150,7 +169,11 @@ struct lambdaAnalysis_pb { if (doprocessMix || doprocessMixDF || doprocessMixepDF) { histos.add("Event/mixing_vzVsmultpercentile", "FT0(%)", kTH3F, {axisCent, axisVz, axisEP}); } - // QA Before + // QA Beforei + histos.add("QAbefore/hEta_rec", "Reco dN/d#eta; #eta; dN/d#eta", kTH1F, {{50, -1.0, 1.0}}); + histos.add("QAbefore/hPt_rec", "Reco pT; p_{T} (GeV/c); Tracks", kTH1F, {axisP_pid}); + histos.add("QAbefore/hPhi_rec", "Reco #varphi; #varphi (rad); Tracks", kTH1F, {{72, 0, 6.2832}}); + histos.add("QAbefore/hEtaPhi_rec", "Reco #eta vs #varphi; #eta; #varphi", kTH2F, {axisEta, {72, 0, 6.2832}}); histos.add("QAbefore/Proton/h2d_pr_nsigma_tpc_p", "n#sigma^{TPC} Protons", kTH2F, {axisP_pid, axisTPCNsigma}); histos.add("QAbefore/Proton/h2d_pr_nsigma_tof_p", "n#sigma^{TOF} Protons", kTH2F, {axisP_pid, axisTOFNsigma}); histos.add("QAbefore/Proton/h2d_pr_nsigma_tof_vs_tpc", "n#sigma^{TPC} vs n#sigma^{TOF} Protons", kTH2F, {axisTPCNsigma, axisTOFNsigma}); @@ -172,6 +195,8 @@ struct lambdaAnalysis_pb { histos.add("QAafter/Proton/h2d_Prpi_nsigma_tof_p", " Protons pion", kTH2F, {axisP_pid, axisTOFNsigma}); histos.add("QAafter/Proton/h2d_Prka_nsigma_tof_p", " Protons kaon", kTH2F, {axisP_pid, axisTOFNsigma}); histos.add("QAafter/Proton/h2d_pr_nsigma_tof_vs_tpc", "n#sigma(TOF) vs n#sigma(TPC) Protons", kTH2F, {axisTPCNsigma, axisTOFNsigma}); + histos.add("QAafter/Proton/hTPCNClsCrossedRowsVsPt", "TPC Crossed Rows vs pT;p_{T} (GeV/c);N_{cls,crossed};Counts", kTH2F, {axisPt_pid, {200, 0, 200}}); + histos.add("QAafter/Proton/hTPCNClsFoundVsPt", "TPC Found Clusters vs pT;p_{T} (GeV/c);N_{cls,found};Counts", kTH2F, {axisPt_pid, {200, 0, 200}}); histos.add("QAafter/Kaon/hd_ka_pt", "p_{T}-spectra Kaons", kTH2F, {axisPt_pid, axisCent}); histos.add("QAafter/Kaon/h2d_ka_dca_z", "dca_{z} Kaons", kTH2F, {axisPt_pid, axisDCAz}); histos.add("QAafter/Kaon/h2d_ka_dca_xy", "dca_{xy} Kaons", kTH2F, {axisPt_pid, axisDCAxy}); @@ -185,7 +210,8 @@ struct lambdaAnalysis_pb { histos.add("QAafter/Kaon/h2d_Kapi_nsigma_tof_p", " Kaons pion", kTH2F, {axisP_pid, axisTOFNsigma}); histos.add("QAafter/Kaon/h2d_Kapr_nsigma_tof_p", " Kaons proton", kTH2F, {axisP_pid, axisTOFNsigma}); histos.add("QAafter/Kaon/h2d_ka_nsigma_tof_vs_tpc", "n#sigma(TOF) vs n#sigma(TPC) Kaons", kTH2F, {axisTPCNsigma, axisTOFNsigma}); - + histos.add("QAafter/Kaon/hTPCNClsCrossedRowsVsPt", "TPC Crossed Rows vs pT;p_{T} (GeV/c);N_{cls,crossed};Counts", kTH2F, {axisPt_pid, {200, 0, 200}}); + histos.add("QAafter/Kaon/hTPCNClsFoundVsPt", "TPC Found Clusters vs pT;p_{T} (GeV/c);N_{cls,found};Counts", kTH2F, {axisPt_pid, {200, 0, 200}}); // Analysis // Lambda Invariant Mass if (!doprocessMC) { @@ -193,7 +219,8 @@ struct lambdaAnalysis_pb { histos.add("Analysis/h4d_lstar_invm_US_MP", "THn #bar #Lambda(1520)", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); histos.add("Analysis/h4d_lstar_invm_PP", "THn Like Signs p K^{+}", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); histos.add("Analysis/h4d_lstar_invm_MM", "THn Like Signs #bar{p} K^{-}", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); - histos.add("Analysis/h4d_lstar_invm_rot", "THn Rotated", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); + histos.add("Analysis/h4d_lstar_invm_rot_PM", "THn Rotated", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); + histos.add("Analysis/h4d_lstar_invm_rot_MP", "THn Rotated", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); histos.add("Analysis/h4d_lstar_invm_US_PM_mix", "THn Mixed Events", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); histos.add("Analysis/h4d_lstar_invm_US_MP_mix", "THn anti Mixed Events", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); histos.add("Analysis/h4d_lstar_invm_LS_PP_mix", "THn Mixed Events PP", kTHnSparseF, {axisInvM, axisPt, axisCent, axisOccupancy}); @@ -201,7 +228,7 @@ struct lambdaAnalysis_pb { } // MC if (doprocessMC) { - + histos.add("Event/hMCEventCutflow", "MC Event Cutflow", kTH1F, {{7, 0, 7}}); histos.add("QAChecks/h1d_pr_rec_pt", "Reconstructed p_{T}-spectra Protons", kTH1F, {axisPt_pid}); histos.add("QAChecks/h1d_ka_rec_pt", "Recondstucted p_{T}-spectra Kaons", kTH1F, {axisPt_pid}); histos.add("QAChecks/h1d_pr_gen_pt", "Generated p_{T}-spectra Protons", kTH1F, {axisPt_pid}); @@ -211,17 +238,36 @@ struct lambdaAnalysis_pb { histos.add("Analysis/h3d_gen_lstar_MP", "Generated #bar{#Lambda}(1520) p_{T}", kTHnSparseF, {axisInvM, axisPt, axisCent}); histos.add("Analysis/h3d_rec_lstar_PM", "Reconstructed #Lambda(1520) p_{T}", kTHnSparseF, {axisInvM, axisPt, axisCent}); histos.add("Analysis/h3d_rec_lstar_MP", "Reconstructed #bar{#Lambda}(1520) p_{T}", kTHnSparseF, {axisInvM, axisPt, axisCent}); + histos.add("Analysis/h3d_reso_lstar_PM", "Resolution #Lambda(1520) p_{T}", kTHnSparseF, {{200, -0.05, 0.05}, axisPt, axisCent}); + histos.add("Analysis/h3d_reso_lstar_MP", "Resolution #bar{#Lambda}(1520) p_{T}", kTHnSparseF, {{200, -0.05, 0.05}, axisPt, axisCent}); + } + + if (doprocessMCGen) { + histos.add("SignalLoss/hMCEventCutflow", "MC Event Cutflow", kTH1F, {{7, 0, 7}}); + histos.add("SignalLoss/hGen_mT_scaled_Proton", "mT Scaled #Lambda(1520) from Proton", kTHnSparseF, {axisPt, axisCent}); + histos.add("SignalLoss/hGen_mT_scaled_AntiProton", "mT Scaled #bar{#Lambda}(1520) from AntiProton", kTHnSparseF, {axisPt, axisCent}); + + histos.add("SignalLoss/hGen_mT_scaled_Lambda0", "mT Scaled #Lambda(1520) from Lambda0", kTHnSparseF, {axisPt, axisCent}); + histos.add("SignalLoss/hGen_mT_scaled_AntiLambda0", "mT Scaled #bar{#Lambda}(1520) from AntiLambda0", kTHnSparseF, {axisPt, axisCent}); + + histos.add("SignalLoss/hGen_mT_scaled_XiMinus", "mT Scaled #Lambda(1520) from Xi-", kTHnSparseF, {axisPt, axisCent}); + histos.add("SignalLoss/hGen_mT_scaled_XiPlus", "mT Scaled #bar{#Lambda}(1520) from Xi+", kTHnSparseF, {axisPt, axisCent}); + + histos.add("SignalLoss/hGen_mT_scaled_Xi0", "mT Scaled #Lambda(1520) from Xi0", kTHnSparseF, {axisPt, axisCent}); + histos.add("SignalLoss/hGen_mT_scaled_AntiXi0", "mT Scaled #bar{#Lambda}(1520) from AntiXi0", kTHnSparseF, {axisPt, axisCent}); + + histos.add("SignalLoss/hGen_mT_scaled_OmegaMinus", "mT Scaled #Lambda(1520) from Omega-", kTHnSparseF, {axisPt, axisCent}); + histos.add("SignalLoss/hGen_mT_scaled_OmegaPlus", "mT Scaled #bar{#Lambda}(1520) from Omega+", kTHnSparseF, {axisPt, axisCent}); } } template bool selTracks(T const& track) { - if (track.pt() < cPtMin) return false; - if (std::abs(track.eta()) > cEtaCut) + if (track.eta() < cEtaMin || track.eta() > cEtaMax) return false; if (cPrimaryTrack && !track.isPrimaryTrack()) @@ -233,9 +279,15 @@ struct lambdaAnalysis_pb { if (cPVContributor && !track.isPVContributor()) return false; + if (cfgUseCrossedRows && track.tpcNClsCrossedRows() < cfgMinCrossedRows) + return false; + + if (cfgUseTPCcls && track.tpcNClsFound() < cfgMinTPCcls) + return false; + return true; } - // ── Proton DCA Selection ────────────────────────────────────────────────── + template bool dcaSelectionProton(T const& track, float p) { @@ -342,6 +394,7 @@ struct lambdaAnalysis_pb { } } if (tpcPIDPassed && tofPIDPassed) { + return true; } return false; @@ -408,6 +461,7 @@ struct lambdaAnalysis_pb { } } if (tpcPIDPassed && tofPIDPassed) { + return true; } return false; @@ -420,14 +474,13 @@ struct lambdaAnalysis_pb { float p_ptot = 0., k_ptot = 0.; for (auto const& [trkPr, trkKa] : soa::combinations(soa::CombinationsFullIndexPolicy(trk1, trk2))) { - // Do not analyse same index tracks. - if (trkPr.index() == trkKa.index() && !mix) + if (trkPr.index() == trkKa.index()) // && !mix) continue; - // pT, DCA, Global Tracks and PVcontrib selection. if (!selTracks(trkPr) || !selTracks(trkKa)) continue; + // LOGF(info, "eork 4 %d, %d %d ",trkPr.index(),trk1.size(),trkPr.size()); auto _pxPr = trkPr.px(); auto _pyPr = trkPr.py(); @@ -442,7 +495,6 @@ struct lambdaAnalysis_pb { // Fill QA before track selection. if (!mix) { auto _tpcnsigmaPr = trkPr.tpcNSigmaPr(); - histos.fill(HIST("QAbefore/Proton/h2d_pr_nsigma_tpc_p"), p_ptot, _tpcnsigmaPr); if (trkPr.hasTOF()) { auto _tofnsigmaPr = trkPr.tofNSigmaPr(); @@ -485,6 +537,9 @@ struct lambdaAnalysis_pb { histos.fill(HIST("QAafter/Proton/h2d_Prka_nsigma_tpc_p"), p_ptot, trkPr.tpcNSigmaKa()); histos.fill(HIST("QAafter/Proton/h2d_pr_nsigma_tpc_p"), p_ptot, _tpcnsigmaPr); histos.fill(HIST("QAafter/Proton/h2d_pr_nsigma_tpc_pt"), _ptPr, _tpcnsigmaPr); + histos.fill(HIST("QAafter/Proton/hTPCNClsCrossedRowsVsPt"), _ptPr, trkPr.tpcNClsCrossedRows()); + histos.fill(HIST("QAafter/Proton/hTPCNClsFoundVsPt"), _ptPr, trkPr.tpcNClsFound()); + if (!cUseTpcOnly && trkPr.hasTOF()) { auto _tofnsigmaPr = trkPr.tofNSigmaPr(); histos.fill(HIST("QAafter/Proton/h2d_pr_nsigma_tof_p"), p_ptot, _tofnsigmaPr); @@ -505,6 +560,9 @@ struct lambdaAnalysis_pb { histos.fill(HIST("QAafter/Kaon/h2d_Kapr_nsigma_tpc_p"), k_ptot, trkKa.tpcNSigmaPr()); histos.fill(HIST("QAafter/Kaon/h2d_ka_nsigma_tpc_p"), k_ptot, _tpcnsigmaKa); histos.fill(HIST("QAafter/Kaon/h2d_ka_nsigma_tpc_pt"), _ptKa, _tpcnsigmaKa); + histos.fill(HIST("QAafter/Kaon/hTPCNClsCrossedRowsVsPt"), _ptKa, trkKa.tpcNClsCrossedRows()); + histos.fill(HIST("QAafter/Kaon/hTPCNClsFoundVsPt"), _ptKa, trkKa.tpcNClsFound()); + if (!cUseTpcOnly && trkKa.hasTOF()) { auto _tofnsigmaKa = trkKa.tofNSigmaKa(); histos.fill(HIST("QAafter/Kaon/h2d_ka_nsigma_tof_p"), k_ptot, _tofnsigmaKa); @@ -525,9 +583,10 @@ struct lambdaAnalysis_pb { float _M = RecoDecay::m(arrMomrec, std::array{MassProton, MassKaonCharged}); float _pt = RecoDecay::pt(std::array{_pxPr + _pxKa, _pyPr + _pyKa}); - if (std::abs(RecoDecay::y(std::array{_pxPr + _pxKa, _pyPr + _pyKa, _pzPr + _pzKa}, MassLambda1520)) > 0.5) - continue; + float _y = RecoDecay::y(std::array{_pxPr + _pxKa, _pyPr + _pyKa, _pzPr + _pzKa}, _M); + if (_y < cfgRapidityMin || _y > cfgRapidityMax) + continue; // Apply kinematic cuts. // Fill Invariant Mass Histograms. @@ -543,7 +602,14 @@ struct lambdaAnalysis_pb { for (int i = 0; i < cNofRotations; i++) { float delta = o2::constants::math::PI / rotationalcut; - float theta2 = (o2::constants::math::PI - delta) + i * (2.f * delta / (cNofRotations - 1)); + float theta2; + if (cNofRotations == 1) { + // Single rotation — just rotate by exactly PI + theta2 = o2::constants::math::PI; + } else { + theta2 = (o2::constants::math::PI - delta) + + i * (2.f * delta / (cNofRotations - 1)); + } float phiRot = trkKa.phi() + theta2; if (phiRot > o2::constants::math::TwoPI) @@ -561,12 +627,17 @@ struct lambdaAnalysis_pb { float _Mrot = RecoDecay::m(arrMomRot, std::array{MassProton, MassKaonCharged}); float _ptRot = RecoDecay::pt(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot}); - if (std::abs(RecoDecay::y( - std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, - MassLambda1520)) > 0.5f) + float _yrot =RecoDecay::y(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, MassLambda1520); + + if (_yrot < cfgRapidityMin || _yrot > cfgRapidityMax) continue; - histos.fill(HIST("Analysis/h4d_lstar_invm_rot"), _Mrot, _ptRot, mult, occup); + if (trkPr.sign() * trkKa.sign() < 0) { + if (trkPr.sign() > 0) + histos.fill(HIST("Analysis/h4d_lstar_invm_rot_PM"), _Mrot, _ptRot, mult, occup); + else + histos.fill(HIST("Analysis/h4d_lstar_invm_rot_MP"), _Mrot, _ptRot, mult, occup); + } } } } else { @@ -591,7 +662,6 @@ struct lambdaAnalysis_pb { histos.fill(HIST("Analysis/h4d_lstar_invm_LS_MM_mix"), _M, _pt, mult, occup); } } - if constexpr (mc) { if (trkPr.sign() * trkKa.sign() < 0) { if (std::abs(trkPr.pdgCode()) != 2212 || std::abs(trkKa.pdgCode()) != 321) @@ -599,15 +669,34 @@ struct lambdaAnalysis_pb { if (trkPr.motherId() != trkKa.motherId()) continue; + if (trkPr.motherPDG() != trkKa.motherPDG()) + continue; + + if (trkPr.pdgCode() == 0 || trkKa.pdgCode() == 0) + continue; + + if (trkPr.motherPDG() == -1 || trkKa.motherPDG() == -1) + continue; if (std::abs(trkPr.motherPDG()) != lambda1520id) // L* pdg_code = 3124 continue; - // MC histograms + float massParent = 0.; + for (auto const& _part : *mResoParents) { + if (_part.mcParticleId() == trkPr.motherId()) { + std::array pvecParent = {_part.px(), _part.py(), _part.pz()}; + massParent = RecoDecay::m(pvecParent, _part.e()); + break; + } + } + + float _MRes = _M - massParent; if (trkPr.motherPDG() > 0) { histos.fill(HIST("Analysis/h3d_rec_lstar_PM"), _M, _pt, mult); + histos.fill(HIST("Analysis/h3d_reso_lstar_PM"), _MRes, _pt, mult); } else { histos.fill(HIST("Analysis/h3d_rec_lstar_MP"), _M, _pt, mult); + histos.fill(HIST("Analysis/h3d_reso_lstar_MP"), _MRes, _pt, mult); } } } @@ -621,7 +710,6 @@ struct lambdaAnalysis_pb { void processData(resoCols::iterator const& collision, resoTracks const& tracks) { - // LOGF(info, " collisions: Index = %d %d", collision.globalIndex(),tracks.size()); histos.fill(HIST("Event/h1d_ft0_mult_percentile"), collision.cent(), 100); histos.fill(HIST("Event/h_ft0_vz"), collision.posZ()); @@ -634,55 +722,75 @@ struct lambdaAnalysis_pb { void processMC(resoMCCols::iterator const& collision, soa::Join const& tracks, aod::ResoMCParents const& resoParents) { - if (cEvtMCAfterAllCuts && !collision.isInAfterAllCuts()) + histos.fill(HIST("Event/hMCEventCutflow"), 0); // All collisions + + if (cEvtMCTriggerTVX && !collision.isTriggerTVX()) + return; + histos.fill(HIST("Event/hMCEventCutflow"), 1); // After TriggerTVX + + if (cEvtMCVtxIn10 && !collision.isVtxIn10()) return; + histos.fill(HIST("Event/hMCEventCutflow"), 2); // After VtxIn10 + if (cEvtMCINELgt0 && !collision.isINELgt0()) return; + histos.fill(HIST("Event/hMCEventCutflow"), 3); // After INELgt0 + if (cEvtMCSel8 && !collision.isInSel8()) return; - if (cEvtMCVtxIn10 && !collision.isVtxIn10()) - return; - if (cEvtMCTriggerTVX && !collision.isTriggerTVX()) - return; + histos.fill(HIST("Event/hMCEventCutflow"), 4); // After Sel8 + if (cEvtMCRecINELgt0 && !collision.isRecINELgt0()) return; + histos.fill(HIST("Event/hMCEventCutflow"), 5); // After RecINELgt0 + + if (cEvtMCAfterAllCuts && !collision.isInAfterAllCuts()) + return; + histos.fill(HIST("Event/hMCEventCutflow"), 6); // After AfterAllCuts auto mult = collision.cent(); auto mult = collision.cent(); histos.fill(HIST("Event/h1d_ft0_mult_percentile"), mult, 100); histos.fill(HIST("Event/h_ft0_vz"), collision.posZ()); + mResoParents = &resoParents; fillDataHistos(tracks, tracks, mult); // get MC pT-spectra for (auto const& track : tracks) { + histos.fill(HIST("QAbefore/hEta_rec"), track.eta()); + histos.fill(HIST("QAbefore/hPt_rec"), track.pt()); + histos.fill(HIST("QAbefore/hPhi_rec"), track.phi()); + histos.fill(HIST("QAbefore/hEtaPhi_rec"), track.eta(), track.phi()); // get the generated level pT spectra of protons and kaons if (std::abs(track.pdgCode()) == 321) histos.fill(HIST("QAChecks/h1d_ka_gen_pt"), track.pt()); - if (std::abs(track.pdgCode()) == 2212) histos.fill(HIST("QAChecks/h1d_pr_gen_pt"), track.pt()); - // get the reconstructed level pT spectra of protons and kaons if (!selTracks(track)) continue; float p = TMath::Sqrt(track.px() * track.px() + track.py() * track.py() + track.pz() * track.pz()); - if (selectionPIDKaon(track, p) && std::abs(track.pdgCode()) == 321) { - histos.fill(HIST("QAChecks/h1d_ka_rec_pt"), track.pt()); + if (selectionPIDKaon(track, p)) { + if (std::abs(track.pdgCode()) == 321) { + histos.fill(HIST("QAChecks/h1d_ka_rec_pt"), track.pt()); + } } - if (selectionPIDProton(track, p) && std::abs(track.pdgCode()) == 2212) { - histos.fill(HIST("QAChecks/h1d_pr_rec_pt"), track.pt()); + if (selectionPIDProton(track, p)) { + if (std::abs(track.pdgCode()) == 2212) { + histos.fill(HIST("QAChecks/h1d_pr_rec_pt"), track.pt()); + } } } for (auto const& part : resoParents) { if (std::abs(part.pdgCode()) != lambda1520id) // // L* pdg_code = 3124 continue; - if (std::abs(part.y()) > 0.5) { // rapidity cut - continue; - } + + if (part.y() < cfgRapidityMin || part.y() > cfgRapidityMax) + continue; bool pass1 = false; bool pass2 = false; @@ -696,9 +804,7 @@ struct lambdaAnalysis_pb { if (!pass1 || !pass2) // If we have both decay products continue; std::array pvec = {part.px(), part.py(), part.pz()}; - float mass = RecoDecay::m(pvec, part.e()); - if (part.pdgCode() > 0) histos.fill(HIST("Analysis/h3d_gen_lstar_PM"), mass, part.pt(), mult); else @@ -707,11 +813,100 @@ struct lambdaAnalysis_pb { } PROCESS_SWITCH(lambdaAnalysis_pb, processMC, "Process Event for MC", false); + void processMCGen(resoMCCols::iterator const& collision, + aod::ResoMCParents const& resoParents) + { + + float centrality = collision.cent(); + + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 0); // All collisions + + if (cEvtMCTriggerTVX && !collision.isTriggerTVX()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 1); // After TriggerTVX + + if (cEvtMCVtxIn10 && !collision.isVtxIn10()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 2); // After VtxIn10 + + if (cEvtMCINELgt0 && !collision.isINELgt0()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 3); // After INELgt0 + + if (cEvtMCSel8 && !collision.isInSel8()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 4); // After Sel8 + + if (cEvtMCRecINELgt0 && !collision.isRecINELgt0()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 5); // After RecINELgt0 + + if (cEvtMCAfterAllCuts && !collision.isInAfterAllCuts()) + return; + histos.fill(HIST("SignalLoss/hMCEventCutflow"), 6); // After AfterAllCuts + + for (auto const& part : resoParents) { + + if (part.y() < cfgRapidityMin || part.y() > cfgRapidityMax) + continue; + + int pdg = part.pdgCode(); + float ptRef = part.pt(); + double ptSq = -1.0; + + std::array pvec = {part.px(), part.py(), part.pz()}; + float mass = RecoDecay::m(pvec, part.e()); + + if (pdg == 2212) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_Proton"), std::sqrt(ptSq), centrality); + } else if (pdg == -2212) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_AntiProton"), std::sqrt(ptSq), centrality); + } else if (pdg == 3122) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_Lambda0"), std::sqrt(ptSq), centrality); + } else if (pdg == -3122) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_AntiLambda0"), std::sqrt(ptSq), centrality); + } else if (pdg == 3312) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_XiMinus"), std::sqrt(ptSq), centrality); + } else if (pdg == -3312) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_XiPlus"), std::sqrt(ptSq), centrality); + } else if (pdg == 3322) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_Xi0"), std::sqrt(ptSq), centrality); + } else if (pdg == -3322) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_AntiXi0"), std::sqrt(ptSq), centrality); + } else if (pdg == 3334) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_OmegaMinus"), std::sqrt(ptSq), centrality); + } else if (pdg == -3334) { + ptSq = (ptRef * ptRef) + (mass * mass) - (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + if (ptSq > 0) + histos.fill(HIST("SignalLoss/hGen_mT_scaled_OmegaPlus"), std::sqrt(ptSq), centrality); + } + } + } + + PROCESS_SWITCH(lambdaAnalysis_pb, processMCGen, "Process Event for MC", false); + using BinningType2 = ColumnBinningPolicy; void processMix(resoCols& collisions, resoTracks const& tracks) { - LOGF(debug, "Event Mixing Started"); BinningType2 binningPositions2{{cMixVtxBins, cMixMultBins}, true}; From 4ef1213b0c33868ffbdb4686ff4c6f9ce86faf90 Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 11:28:30 +0530 Subject: [PATCH 3/9] Fix formatting and comments in lambda1520_PbPb.cxx --- PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx index 3a479836501..9ad42047138 100644 --- a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx +++ b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx @@ -62,10 +62,10 @@ struct lambdaAnalysis_pb { Configurable cPtMin{"cPtMin", 0.15, "Minimum Track pT"}; Configurable cPMin{"cPMin", 0., "Minimum Track p"}; Configurable cEtaMin{"cEtaMin", -0.8, "Minimum Pseudorapidity"}; - Configurable cEtaMax{"cEtaMax", 0.8, "Maximum Pseudorapidity"}; + Configurable cEtaMax{"cEtaMax", 0.8, "Maximum Pseudorapidity"}; Configurable cDcaz{"cDcazMin", 1., "Minimum DCAz"}; Configurable cfgRapidityMin{"cfgRapidityMin", -0.5, "Minimum rapidity"}; - Configurable cfgRapidityMax{"cfgRapidityMax", 0.5, "Maximum rapidity"}; + Configurable cfgRapidityMax{"cfgRapidityMax", 0.5, "Maximum rapidity"}; // TPC crossed rows (absolute) Configurable cfgMinCrossedRows{"cfgMinCrossedRows", 70, "min TPC crossed rows"}; Configurable cfgUseCrossedRows{"cfgUseCrossedRows", false, "apply crossed rows cut"}; @@ -604,7 +604,7 @@ struct lambdaAnalysis_pb { float delta = o2::constants::math::PI / rotationalcut; float theta2; if (cNofRotations == 1) { - // Single rotation — just rotate by exactly PI + // Single rotation â just rotate by exactly PI theta2 = o2::constants::math::PI; } else { theta2 = (o2::constants::math::PI - delta) + @@ -627,7 +627,7 @@ struct lambdaAnalysis_pb { float _Mrot = RecoDecay::m(arrMomRot, std::array{MassProton, MassKaonCharged}); float _ptRot = RecoDecay::pt(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot}); - float _yrot =RecoDecay::y(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, MassLambda1520); + float _yrot = RecoDecay::y(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, MassLambda1520); if (_yrot < cfgRapidityMin || _yrot > cfgRapidityMax) continue; @@ -788,7 +788,6 @@ struct lambdaAnalysis_pb { if (std::abs(part.pdgCode()) != lambda1520id) // // L* pdg_code = 3124 continue; - if (part.y() < cfgRapidityMin || part.y() > cfgRapidityMax) continue; bool pass1 = false; From 07d4a9b98a9dedfa7708b57d5efe762b177d5482 Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 14:37:57 +0530 Subject: [PATCH 4/9] Add files via upload --- .../Tasks/Resonances/lambda1520Analysispo.cxx | 1265 +++++++++++++++++ 1 file changed, 1265 insertions(+) create mode 100644 PWGLF/Tasks/Resonances/lambda1520Analysispo.cxx diff --git a/PWGLF/Tasks/Resonances/lambda1520Analysispo.cxx b/PWGLF/Tasks/Resonances/lambda1520Analysispo.cxx new file mode 100644 index 00000000000..522b0052ceb --- /dev/null +++ b/PWGLF/Tasks/Resonances/lambda1520Analysispo.cxx @@ -0,0 +1,1265 @@ +// 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 Lambda1520Analysispo.cxx +/// \brief Task for Lambda(1520) resonance reconstruction via proton-kaon invariant mass analysis +/// +/// \author Yash Patley +/// \author Durgesh Bhatt + +#include "PWGLF/DataModel/LFResonanceTables.h" + +#include "Common/Core/RecoDecay.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace o2; +using namespace o2::framework; +using namespace o2::framework::expressions; +using namespace o2::constants::physics; + +struct Lambda1520Analysispo { + + SliceCache sliceCache; + + // ── Named PDG codes (fixes pdg/explicit-code and magic-number linter errors) ─ + static constexpr int kPdgProton{2212}; + static constexpr int kPdgKaon{321}; + static constexpr int kPdgLambda0{3122}; + static constexpr int kPdgXiMinus{3312}; + static constexpr int kPdgXi0{3322}; + static constexpr int kPdgOmegaMinus{3334}; + + // Preslice helpers: allow fast lookup of tracks belonging to a collision + Preslice tracksPerResonanceCollision = aod::resodaughter::resoCollisionId; + Preslice tracksPerStandardCollision = aod::track::collisionId; + + // Pointer to MC parent particle table (used only in MC processing) + aod::ResoMCParents const* mcResonanceParentTable = nullptr; + + // ── Event-level configurables ──────────────────────────────────────────── + Configurable applyOccupancyInTimeRangeCut{"applyOccupancyInTimeRangeCut", false, "If true, apply a cut on the number of tracks in a time window around the collision (occupancy cut)"}; + + // ── Histogram binning configurables ───────────────────────────────────── + Configurable numberOfPtBins{"numberOfPtBins", 100, "Number of bins along the transverse momentum (pT) axis"}; + Configurable numberOfInvMassBins{"numberOfInvMassBins", 120, "Number of bins along the invariant mass axis"}; + + // ── Physics configurables ──────────────────────────────────────────────── + Configurable pdgCodeLambda1520{"pdgCodeLambda1520", 3124, "PDG code of the Lambda(1520) resonance "}; + Configurable enableRotationalBackground{"enableRotationalBackground", true, "If true, compute rotational background (kaon phi rotated by ~pi) to estimate combinatorial background"}; + + // ── Track quality cuts ─────────────────────────────────────────────────── + Configurable minTrackPt{"minTrackPt", 0.15f, "Minimum transverse momentum pT of a track [GeV/c]"}; + Configurable minTrackMomentum{"minTrackMomentum", 0.f, "Minimum total momentum p of a track [GeV/c]"}; + Configurable minPseudorapidity{"minPseudorapidity", -0.8f, "Minimum pseudorapidity eta (detector acceptance)"}; + Configurable maxPseudorapidity{"maxPseudorapidity", 0.8f, "Maximum pseudorapidity eta (detector acceptance)"}; + Configurable maxDCAz{"maxDCAz", 1.0f, "Maximum allowed distance of closest approach along z-axis (DCAz) [cm]."}; + Configurable minPairRapidity{"minPairRapidity", -0.5f, "Minimum rapidity y of the reconstructed proton-kaon pair"}; + Configurable maxPairRapidity{"maxPairRapidity", 0.5f, "Maximum rapidity y of the reconstructed proton-kaon pair"}; + + // ── TPC cluster quality cuts ───────────────────────────────────────────── + Configurable minTPCCrossedRows{"minTPCCrossedRows", 70, "Minimum number of TPC crossed pad rows (track quality)"}; + Configurable applyCrossedRowsCut{"applyCrossedRowsCut", false, "If true, require at least minTPCCrossedRows crossed rows in the TPC"}; + Configurable minTPCClustersFound{"minTPCClustersFound", 70, "Minimum number of TPC clusters found on the track"}; + Configurable applyTPCClustersCut{"applyTPCClustersCut", false, "If true, require at least minTPCClustersFound TPC clusters"}; + + // ── pT-dependent DCAxy cuts for Protons ───────────────────────────────── + Configurable> protonDCAPtBinEdges{"protonDCAPtBinEdges", {0.0f, 0.5f, 1.0f, 2.0f, 3.0f, 5.0f, 1000.0f}, "Proton pT bin edges [GeV/c] for the pT-dependent DCAxy selection"}; + Configurable> protonMaxDCAxyPerPtBin{"protonMaxDCAxyPerPtBin", {0.020f, 0.015f, 0.010f, 0.007f, 0.005f, 0.004f}, "Maximum |DCAxy| [cm] for protons in each pT bin defined by protonDCAPtBinEdges"}; + + // ── pT-dependent DCAxy cuts for Kaons ─────────────────────────────────── + Configurable> kaonDCAPtBinEdges{"kaonDCAPtBinEdges", {0.0f, 0.3f, 0.6f, 1.0f, 2.0f, 1000.0f}, "Kaon pT bin edges [GeV/c] for the pT-dependent DCAxy selection"}; + Configurable> kaonMaxDCAxyPerPtBin{"kaonMaxDCAxyPerPtBin", {0.025f, 0.018f, 0.012f, 0.008f, 0.004f}, "Maximum |DCAxy| [cm] for kaons in each pT bin defined by kaonDCAPtBinEdges"}; + + // ── Analysis mode switches ─────────────────────────────────────────────── + Configurable runQualityChecksOnly{"runQualityChecksOnly", false, "If true, only fill QA histograms and skip invariant mass computation"}; + Configurable applyDeepAngleCut{"applyDeepAngleCut", false, "If true, reject proton-kaon pairs with very small opening angle (removes split-track background)"}; + Configurable deepAngleCutValue{"deepAngleCutValue", 0.04, "Minimum allowed opening angle [rad] between proton and kaon (used if applyDeepAngleCut = true)"}; + Configurable applyKinematicPairCuts{"applyKinematicPairCuts", false, "If true, apply additional kinematic cuts on the p-K opening angle"}; + + // ── Global track selection flags ───────────────────────────────────────── + Configurable requirePrimaryTrack{"requirePrimaryTrack", true, + "Require track to pass the 'isPrimaryTrack' flag (kGoldenChi2 | kDCAxy | kDCAz)"}; + Configurable requireGlobalTrackNoDCA{"requireGlobalTrackNoDCA", true, + "Require track to pass 'isGlobalTrackWoDCA' (quality cuts without DCA requirement)"}; + Configurable requirePVContributor{"requirePVContributor", true, + "Require track to be a contributor to the primary vertex reconstruction"}; + + // ── PID configurables ──────────────────────────────────────────────────── + Configurable requireTOFForProton{"requireTOFForProton", false, + "If true, only accept proton candidates that have a TOF measurement"}; + Configurable requireTOFForKaon{"requireTOFForKaon", false, + "If true, only accept kaon candidates that have a TOF measurement"}; + Configurable useTPCOnlyPID{"useTPCOnlyPID", false, + "If true, use only TPC nSigma for PID (ignore TOF even if available)"}; + + Configurable tpcNSigmaVetoOtherSpecies{"tpcNSigmaVetoOtherSpecies", 3.0f, + "Reject track if its TPC nSigma for a different species is below this threshold (avoids misID)"}; + Configurable tpcNSigmaVetoPion{"tpcNSigmaVetoPion", 3.0f, + "TPC nSigma threshold below which a track is vetoed as a pion"}; + Configurable tpcNSigmaVetoKaonForProton{"tpcNSigmaVetoKaonForProton", 3.0f, + "TPC nSigma threshold: veto proton candidates that look like kaons"}; + Configurable tpcNSigmaVetoPionForKaon{"tpcNSigmaVetoPionForKaon", 3.0f, + "TPC nSigma threshold: veto kaon candidates that look like pions"}; + Configurable tpcNSigmaVetoProtonForKaon{"tpcNSigmaVetoProtonForKaon", 3.0f, + "TPC nSigma threshold: veto kaon candidates that look like protons"}; + + Configurable minTPCNSigmaKaon{"minTPCNSigmaKaon", -6.0f, "Minimum (most negative) TPC nSigma for kaon"}; + Configurable minTPCNSigmaProton{"minTPCNSigmaProton", -6.0f, "Minimum (most negative) TPC nSigma for proton"}; + Configurable minTOFNSigmaKaon{"minTOFNSigmaKaon", -6.0f, "Minimum (most negative) TOF nSigma for kaon"}; + Configurable minTOFNSigmaProton{"minTOFNSigmaProton", -6.0f, "Minimum (most negative) TOF nSigma for proton"}; + Configurable minCombinedTPCTOFNSigmaKaon{"minCombinedTPCTOFNSigmaKaon", -6.0f, + "Minimum combined TPC+TOF nSigma for kaon (used in combined PID mode)"}; + Configurable minCombinedTPCTOFNSigmaProton{"minCombinedTPCTOFNSigmaProton", -6.0f, + "Minimum combined TPC+TOF nSigma for proton (used in combined PID mode)"}; + + Configurable tpcNSigmaVetoThreshold{"tpcNSigmaVetoThreshold", 3.0f, + "General TPC nSigma veto cut to reject misidentified particles when TPC bands overlap"}; + Configurable tofNSigmaVetoThreshold{"tofNSigmaVetoThreshold", 3.0f, + "General TOF nSigma veto cut to reject misidentified particles"}; + + // ── Proton PID momentum-dependent TPC cuts ─────────────────────────────── + Configurable maxTPCNSigmaProton{"maxTPCNSigmaProton", 3.0, + "Maximum |TPC nSigma| for proton identification (symmetric cut)"}; + Configurable combinedNSigmaCutProton{"combinedNSigmaCutProton", 3.0, + "Cut on sqrt(nSigmaTPC^2 + nSigmaTOF^2) for proton. Negative value switches to asymmetric mode."}; + Configurable> protonTPCPIDMomentumBins{"protonTPCPIDMomentumBins", + {0, 0.5, 0.7, 0.8}, + "Momentum p bin edges [GeV/c] for momentum-dependent TPC PID cuts on protons"}; + Configurable> protonTPCNSigmaCutPerBin{"protonTPCNSigmaCutPerBin", + {5., 3.5, 2.5}, + "Maximum TPC nSigma for proton in each momentum bin (tighter at higher p)"}; + Configurable> protonTOFPIDMomentumBins{"protonTOFPIDMomentumBins", + {0., 999.}, + "Momentum p bin edges [GeV/c] for momentum-dependent TOF PID cuts on protons"}; + Configurable> protonTOFNSigmaCutPerBin{"protonTOFNSigmaCutPerBin", + {3.0}, + "Maximum TOF nSigma for proton in each momentum bin"}; + + // ── Kaon PID momentum-dependent TPC cuts ──────────────────────────────── + Configurable maxTPCNSigmaKaon{"maxTPCNSigmaKaon", 3.0, + "Maximum |TPC nSigma| for kaon identification (symmetric cut)"}; + Configurable combinedNSigmaCutKaon{"combinedNSigmaCutKaon", 3.0, + "Cut on sqrt(nSigmaTPC^2 + nSigmaTOF^2) for kaon. Negative value switches to asymmetric mode."}; + Configurable> kaonTPCPIDMomentumBins{"kaonTPCPIDMomentumBins", + {0., 0.25, 0.3, 0.45}, + "Momentum p bin edges [GeV/c] for momentum-dependent TPC PID cuts on kaons"}; + Configurable> kaonTPCNSigmaCutPerBin{"kaonTPCNSigmaCutPerBin", + {6, 3.5, 2.5}, + "Maximum TPC nSigma for kaon in each momentum bin"}; + Configurable> kaonTOFPIDMomentumBins{"kaonTOFPIDMomentumBins", + {0., 999.}, + "Momentum p bin edges [GeV/c] for momentum-dependent TOF PID cuts on kaons"}; + Configurable> kaonTOFNSigmaCutPerBin{"kaonTOFNSigmaCutPerBin", + {3.0}, + "Maximum TOF nSigma for kaon in each momentum bin"}; + + // ── Event mixing configurables ─────────────────────────────────────────── + Configurable numberOfEventsToMix{"numberOfEventsToMix", 20, + "Number of events to mix with each signal event for background estimation"}; + ConfigurableAxis dcaZMixingBins{"dcaZMixingBins", + {VARIABLE_WIDTH, -1.2f, -1.0f, -0.9f, -0.8f, -0.7f, -0.6f, -0.5f, -0.4f, -0.3f, + -0.2f, -0.1f, 0.f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.2f}, + "DCAz vertex bins used for event mixing pairing"}; + ConfigurableAxis vertexZMixingBins{"vertexZMixingBins", + {VARIABLE_WIDTH, -10.f, -9.f, -8.f, -7.f, -6.f, -5.f, -4.f, -3.f, -2.f, -1.f, + 0.f, 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f, 9.f, 10.f}, + "Primary vertex z-position bins used for event mixing pairing"}; + ConfigurableAxis centralityMixingBins{"centralityMixingBins", + {VARIABLE_WIDTH, 0.f, 10.f, 20.f, 30.f, 40.f, 50.f, 60.f, 70.f, 80.f, 90.f, 100.f, 200.f}, + "Centrality (FT0 %) bins used for event mixing pairing"}; + ConfigurableAxis eventPlaneMixingBins{"eventPlaneMixingBins", + {VARIABLE_WIDTH, -1.5708f, -1.25664f, -0.942478f, -0.628319f, 0.f, + 0.628319f, 0.942478f, 1.25664f, 1.5708f}, + "Event plane angle bins used for event mixing (EP-dependent analysis)"}; + ConfigurableAxis occupancyBins{"occupancyBins", + {VARIABLE_WIDTH, 0.0, 100, 500, 600, 1000, 1100, 1500, 1600, 2000, 2100, 2500, + 2600, 3000, 3100, 3500, 3600, 4000, 4100, 4500, 4600, 5000, 5100, 9999}, + "Track occupancy bins in the time range around the collision"}; + + // ── Rotational background configurables ───────────────────────────────── + Configurable numberOfRotations{"numberOfRotations", 10, + "How many times to rotate the kaon phi for the rotational background estimate"}; + Configurable rotationAngleWindow{"rotationAngleWindow", 6.f, + "The kaon is rotated by angles near PI, within a window of PI/rotationAngleWindow"}; + + // ── MC event selection flags ───────────────────────────────────────────── + Configurable mcRequireAfterAllCuts{"mcRequireAfterAllCuts", false, + "MC event selection: require isInAfterAllCuts flag"}; + Configurable mcRequireINELgt0{"mcRequireINELgt0", false, + "MC event selection: require at least 1 charged particle in |eta|<1 (INEL>0)"}; + Configurable mcRequireSel8{"mcRequireSel8", false, + "MC event selection: require the standard Sel8 event selection"}; + Configurable mcRequireVtxWithin10cm{"mcRequireVtxWithin10cm", false, + "MC event selection: require primary vertex |z| < 10 cm"}; + Configurable mcRequireTriggerTVX{"mcRequireTriggerTVX", false, + "MC event selection: require the TVX (T0 vertex) trigger"}; + Configurable mcRequireRecoINELgt0{"mcRequireRecoINELgt0", false, + "MC event selection: require reconstructed INEL>0 condition"}; + + // ── Histogram registry ─────────────────────────────────────────────────── + HistogramRegistry allHistograms{"allHistograms", {}, OutputObjHandlingPolicy::AnalysisObject}; + + // ============================================================ + // init() + // ============================================================ + void init(InitContext const&) + { + const AxisSpec axisCentralityPercent(110, 0, 110, "FT0 centrality (%)"); + const AxisSpec axisMomentumForPID(200, 0., 10., "p (GeV/c)"); + const AxisSpec axisPtForPID(200, 0., 10., "p_{T} (GeV/c)"); + const AxisSpec axisPt(numberOfPtBins, 0., 10., "p_{T} (GeV/c)"); + const AxisSpec axisPseudorapidity(40, -1, 1, "#eta"); + const AxisSpec axisDCAxy(240, -0.12, 0.12, "DCA_{xy} (cm)"); + const AxisSpec axisTPCNClusters(200, 0, 200, "TPC N_{clusters}"); + const AxisSpec axisTPCNSigma(401, -10.025, 10.025, "n#sigma^{TPC}"); + const AxisSpec axisTOFNSigma(401, -10.025, 10.025, "n#sigma^{TOF}"); + const AxisSpec axisTPCdEdx(380, 10, 200, "#frac{dE}{dx}"); + const AxisSpec axisVertexZ(120, -12, 12, "v_{z} (cm)"); + const AxisSpec axisEventPlaneAngle(120, -3.14, 3.14, "#theta (rad)"); + const AxisSpec axisInvariantMass(numberOfInvMassBins, 1.4, 2.0, + "M_{inv} (GeV/c^{2})"); + + AxisSpec axisOccupancy = {occupancyBins, "Track occupancy [-40,100]"}; + AxisSpec axisDCAz = {dcaZMixingBins, "DCA_{z} (cm)"}; + + allHistograms.add("Event/centralityVsOccupancy", + "Collision centrality vs track occupancy", kTH2F, + {axisCentralityPercent, axisOccupancy}); + allHistograms.add("Event/primaryVertexZ", + "Primary vertex z-position distribution", kTH1F, + {{100, -15., 15.}}); + if (doprocessMix || doprocessMixDF || doprocessMixepDF) { + allHistograms.add("Event/mixingBins_centralityVsVtxZVsEventPlane", + "Event mixing bin occupancy: centrality vs vtxZ vs event plane", kTH3F, + {axisCentralityPercent, axisVertexZ, axisEventPlaneAngle}); + } + + allHistograms.add("QAbefore/trackEta", "Track pseudorapidity (before cuts)", kTH1F, {{50, -1.0, 1.0}}); + allHistograms.add("QAbefore/trackPt", "Track pT (before cuts)", kTH1F, {axisMomentumForPID}); + allHistograms.add("QAbefore/trackPhi", "Track azimuthal angle (before cuts)", kTH1F, {{72, 0, 6.2832}}); + allHistograms.add("QAbefore/trackEtaVsPhi", "Track eta vs phi (before cuts)", kTH2F, {axisPseudorapidity, {72, 0, 6.2832}}); + + allHistograms.add("QAbefore/Proton/tpcNSigmaVsMomentum", + "TPC nSigma proton vs momentum (before PID cuts)", kTH2F, + {axisMomentumForPID, axisTPCNSigma}); + allHistograms.add("QAbefore/Proton/tofNSigmaVsMomentum", + "TOF nSigma proton vs momentum (before PID cuts)", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAbefore/Proton/tofNSigmaVsTPCNSigma", + "TOF nSigma vs TPC nSigma proton (before PID cuts)", kTH2F, + {axisTPCNSigma, axisTOFNSigma}); + + allHistograms.add("QAbefore/Kaon/tpcNSigmaVsMomentum", + "TPC nSigma kaon vs momentum (before PID cuts)", kTH2F, + {axisMomentumForPID, axisTPCNSigma}); + allHistograms.add("QAbefore/Kaon/tofNSigmaVsMomentum", + "TOF nSigma kaon vs momentum (before PID cuts)", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAbefore/Kaon/tofNSigmaVsTPCNSigma", + "TOF nSigma vs TPC nSigma kaon (before PID cuts)", kTH2F, + {axisTPCNSigma, axisTOFNSigma}); + + allHistograms.add("QAafter/Proton/ptVsCentrality", + "Proton pT vs centrality (after cuts)", kTH2F, {axisPtForPID, axisCentralityPercent}); + allHistograms.add("QAafter/Proton/dcaZVsPt", + "Proton DCAz vs pT (after cuts)", kTH2F, {axisPtForPID, axisDCAz}); + allHistograms.add("QAafter/Proton/dcaXYVsPt", + "Proton DCAxy vs pT (after cuts)", kTH2F, {axisPtForPID, axisDCAxy}); + allHistograms.add("QAafter/Proton/tpcDedxVsMomentum", + "Proton TPC dE/dx signal vs momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTPCdEdx}); + allHistograms.add("QAafter/Proton/tpcNSigmaVsPt", + "Proton TPC nSigma vs pT (after cuts)", kTH2F, {axisPtForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Proton/tpcNSigmaPionContamVsPt", + "Proton track: TPC nSigma pion contamination check", kTH2F, + {axisPtForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Proton/tpcNSigmaKaonContamVsPt", + "Proton track: TPC nSigma kaon contamination check", kTH2F, + {axisPtForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Proton/tpcNSigmaVsMomentum", + "Proton TPC nSigma vs total momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Proton/tofNSigmaVsPt", + "Proton TOF nSigma vs pT (after cuts)", kTH2F, {axisPtForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Proton/tofNSigmaVsMomentum", + "Proton TOF nSigma vs total momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Proton/tofNSigmaPionContamVsMomentum", + "Proton track: TOF nSigma pion contamination check", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Proton/tofNSigmaKaonContamVsMomentum", + "Proton track: TOF nSigma kaon contamination check", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Proton/tofNSigmaVsTPCNSigma", + "Proton TOF nSigma vs TPC nSigma (after cuts)", kTH2F, + {axisTPCNSigma, axisTOFNSigma}); + allHistograms.add("QAafter/Proton/tpcCrossedRowsVsPt", + "Proton TPC crossed rows vs pT", kTH2F, {axisPtForPID, {200, 0, 200}}); + allHistograms.add("QAafter/Proton/tpcClustersFoundVsPt", + "Proton TPC clusters found vs pT", kTH2F, {axisPtForPID, {200, 0, 200}}); + + allHistograms.add("QAafter/Kaon/ptVsCentrality", + "Kaon pT vs centrality (after cuts)", kTH2F, {axisPtForPID, axisCentralityPercent}); + allHistograms.add("QAafter/Kaon/dcaZVsPt", + "Kaon DCAz vs pT (after cuts)", kTH2F, {axisPtForPID, axisDCAz}); + allHistograms.add("QAafter/Kaon/dcaXYVsPt", + "Kaon DCAxy vs pT (after cuts)", kTH2F, {axisPtForPID, axisDCAxy}); + allHistograms.add("QAafter/Kaon/tpcDedxVsMomentum", + "Kaon TPC dE/dx signal vs momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTPCdEdx}); + allHistograms.add("QAafter/Kaon/tpcNSigmaPionContamVsPt", + "Kaon track: TPC nSigma pion contamination check", kTH2F, + {axisPtForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Kaon/tpcNSigmaProtonContamVsMomentum", + "Kaon track: TPC nSigma proton contamination check", kTH2F, + {axisMomentumForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Kaon/tpcNSigmaVsPt", + "Kaon TPC nSigma vs pT (after cuts)", kTH2F, {axisPtForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Kaon/tpcNSigmaVsMomentum", + "Kaon TPC nSigma vs total momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTPCNSigma}); + allHistograms.add("QAafter/Kaon/tofNSigmaVsPt", + "Kaon TOF nSigma vs pT (after cuts)", kTH2F, {axisPtForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Kaon/tofNSigmaVsMomentum", + "Kaon TOF nSigma vs total momentum (after cuts)", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Kaon/tofNSigmaPionContamVsMomentum", + "Kaon track: TOF nSigma pion contamination check", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Kaon/tofNSigmaProtonContamVsMomentum", + "Kaon track: TOF nSigma proton contamination check", kTH2F, + {axisMomentumForPID, axisTOFNSigma}); + allHistograms.add("QAafter/Kaon/tofNSigmaVsTPCNSigma", + "Kaon TOF nSigma vs TPC nSigma (after cuts)", kTH2F, + {axisTPCNSigma, axisTOFNSigma}); + allHistograms.add("QAafter/Kaon/tpcCrossedRowsVsPt", + "Kaon TPC crossed rows vs pT", kTH2F, {axisPtForPID, {200, 0, 200}}); + allHistograms.add("QAafter/Kaon/tpcClustersFoundVsPt", + "Kaon TPC clusters found vs pT", kTH2F, {axisPtForPID, {200, 0, 200}}); + + if (!doprocessMC) { + allHistograms.add("Analysis/invMass_UnlikeSign_ProtonPlusKaonMinus", + "Invariant mass: p^{+}K^{-} (Lambda(1520) signal)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_UnlikeSign_ProtonMinusKaonPlus", + "Invariant mass: p^{-}K^{+} (anti-Lambda(1520) signal)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_LikeSign_ProtonPlusKaonPlus", + "Invariant mass: p^{+}K^{+} (like-sign background)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_LikeSign_ProtonMinusKaonMinus", + "Invariant mass: p^{-}K^{-} (like-sign background)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Rotated_ProtonPlusKaonMinus", + "Invariant mass: rotational background (Lambda(1520))", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Rotated_ProtonMinusKaonPlus", + "Invariant mass: rotational background (anti-Lambda(1520))", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Mixed_ProtonPlusKaonMinus", + "Invariant mass: mixed events p^{+}K^{-} (background)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Mixed_ProtonMinusKaonPlus", + "Invariant mass: mixed events p^{-}K^{+} (background)", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Mixed_LikeSign_PlusPlus", + "Invariant mass: mixed events like-sign ++", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + allHistograms.add("Analysis/invMass_Mixed_LikeSign_MinusMinus", + "Invariant mass: mixed events like-sign --", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent, axisOccupancy}); + } + + if (doprocessMC) { + allHistograms.add("Event/mcEventSelectionCutflow", + "MC event selection cutflow (how many events pass each cut)", kTH1F, {{7, 0, 7}}); + allHistograms.add("QAChecks/protonRecoLevelPt", + "Reconstructed proton pT (after PID cuts) - MC", kTH1F, {axisPtForPID}); + allHistograms.add("QAChecks/kaonRecoLevelPt", + "Reconstructed kaon pT (after PID cuts) - MC", kTH1F, {axisPtForPID}); + allHistograms.add("QAChecks/protonGenLevelPt", + "Generated proton pT (truth level) - MC", kTH1F, {axisPtForPID}); + allHistograms.add("QAChecks/kaonGenLevelPt", + "Generated kaon pT (truth level) - MC", kTH1F, {axisPtForPID}); + allHistograms.add("Analysis/mcGenerated_Lambda1520", + "Generated Lambda(1520) invariant mass vs pT vs centrality", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent}); + allHistograms.add("Analysis/mcGenerated_AntiLambda1520", + "Generated anti-Lambda(1520) invariant mass vs pT vs centrality", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent}); + allHistograms.add("Analysis/mcReconstructed_Lambda1520", + "Reconstructed Lambda(1520) invariant mass vs pT vs centrality - MC", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent}); + allHistograms.add("Analysis/mcReconstructed_AntiLambda1520", + "Reconstructed anti-Lambda(1520) invariant mass vs pT vs centrality - MC", kTHnSparseF, + {axisInvariantMass, axisPt, axisCentralityPercent}); + allHistograms.add("Analysis/mcMassResolution_Lambda1520", + "Mass resolution (Mreco - Mgen) vs pT - Lambda(1520)", kTHnSparseF, + {{200, -0.05, 0.05}, axisPt, axisCentralityPercent}); + allHistograms.add("Analysis/mcMassResolution_AntiLambda1520", + "Mass resolution (Mreco - Mgen) vs pT - anti-Lambda(1520)", kTHnSparseF, + {{200, -0.05, 0.05}, axisPt, axisCentralityPercent}); + } + + if (doprocessMCGen) { + allHistograms.add("SignalLoss/mcEventSelectionCutflow", + "MC event selection cutflow (signal loss study)", kTH1F, {{7, 0, 7}}); + allHistograms.add("SignalLoss/mTScaled_fromProton", + "mT-scaled Lambda(1520) pT from proton parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromAntiProton", + "mT-scaled anti-Lambda(1520) pT from anti-proton parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromLambda0", + "mT-scaled Lambda(1520) pT from Lambda0 parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromAntiLambda0", + "mT-scaled anti-Lambda(1520) pT from anti-Lambda0 parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromXiMinus", + "mT-scaled Lambda(1520) pT from Xi- parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromXiPlus", + "mT-scaled anti-Lambda(1520) pT from Xi+ parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromXi0", + "mT-scaled Lambda(1520) pT from Xi0 parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromAntiXi0", + "mT-scaled anti-Lambda(1520) pT from anti-Xi0 parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromOmegaMinus", + "mT-scaled Lambda(1520) pT from Omega- parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + allHistograms.add("SignalLoss/mTScaled_fromOmegaPlus", + "mT-scaled anti-Lambda(1520) pT from Omega+ parent", kTHnSparseF, + {axisPt, axisCentralityPercent}); + } + } + + // ============================================================ + // passesBasicTrackSelection() + // ============================================================ + template + bool passesBasicTrackSelection(TrackType const& track) + { + if (track.pt() < minTrackPt) + return false; + if (track.eta() < minPseudorapidity || track.eta() > maxPseudorapidity) + return false; + if (requirePrimaryTrack && !track.isPrimaryTrack()) + return false; + if (requireGlobalTrackNoDCA && !track.isGlobalTrackWoDCA()) + return false; + if (requirePVContributor && !track.isPVContributor()) + return false; + if (applyCrossedRowsCut && track.tpcNClsCrossedRows() < minTPCCrossedRows) + return false; + if (applyTPCClustersCut && track.tpcNClsFound() < minTPCClustersFound) + return false; + return true; + } + + // ============================================================ + // passesProtonDCASelection() + // ============================================================ + template + bool passesProtonDCASelection(TrackType const& track, float totalMomentum) + { + auto ptBinEdges = static_cast>(protonDCAPtBinEdges); + auto maxDCAxyValues = static_cast>(protonMaxDCAxyPerPtBin); + int numberOfBins = static_cast(ptBinEdges.size()) - 1; + + bool dcaXYPassed = false; + for (int iBin = 0; iBin < numberOfBins; iBin++) { + if (totalMomentum >= ptBinEdges[iBin] && totalMomentum < ptBinEdges[iBin + 1] && + std::abs(track.dcaXY()) < maxDCAxyValues[iBin]) + dcaXYPassed = true; + } + if (!dcaXYPassed) + return false; + if (std::abs(track.dcaZ()) > maxDCAz) + return false; + return true; + } + + // ============================================================ + // passesKaonDCASelection() + // ============================================================ + template + bool passesKaonDCASelection(TrackType const& track, float totalMomentum) + { + auto ptBinEdges = static_cast>(kaonDCAPtBinEdges); + auto maxDCAxyValues = static_cast>(kaonMaxDCAxyPerPtBin); + int numberOfBins = static_cast(ptBinEdges.size()) - 1; + + bool dcaXYPassed = false; + for (int iBin = 0; iBin < numberOfBins; iBin++) { + if (totalMomentum >= ptBinEdges[iBin] && totalMomentum < ptBinEdges[iBin + 1] && + std::abs(track.dcaXY()) < maxDCAxyValues[iBin]) + dcaXYPassed = true; + } + if (!dcaXYPassed) + return false; + if (std::abs(track.dcaZ()) > maxDCAz) + return false; + return true; + } + + // ============================================================ + // passesProtonPID() + // ============================================================ + template + bool passesProtonPID(const TrackType& track, float totalMomentum) + { + bool tpcPIDPassed{false}, tofPIDPassed{false}; + + auto tpcMomBins = static_cast>(protonTPCPIDMomentumBins); + auto tpcNSigCuts = static_cast>(protonTPCNSigmaCutPerBin); + auto tofMomBins = static_cast>(protonTOFPIDMomentumBins); + auto tofNSigCuts = static_cast>(protonTOFNSigmaCutPerBin); + int nTPCBins = static_cast(tpcMomBins.size()); + int nTOFBins = static_cast(tofMomBins.size()); + + float tpcNSigPion = std::abs(track.tpcNSigmaPi()); + float tpcNSigKaon = std::abs(track.tpcNSigmaKa()); + float tpcNSigProton = std::abs(track.tpcNSigmaPr()); + float tofNSigPion = std::abs(track.tofNSigmaPi()); + float tofNSigKaon = std::abs(track.tofNSigmaKa()); + float tofNSigProton = std::abs(track.tofNSigmaPr()); + + float combinedNSigPion = tpcNSigPion * tpcNSigPion + tofNSigPion * tofNSigPion; + float combinedNSigKaon = tpcNSigKaon * tpcNSigKaon + tofNSigKaon * tofNSigKaon; + float combinedNSigProton = tpcNSigProton * tpcNSigProton + tofNSigProton * tofNSigProton; + + float circularCutSquared = combinedNSigmaCutProton * combinedNSigmaCutProton; + float circularRejCutSquared = tofNSigmaVetoThreshold * tpcNSigmaVetoThreshold; + + if (!useTPCOnlyPID && track.hasTOF()) { + if (track.tofNSigmaPr() < minTOFNSigmaProton) + return false; + + if (combinedNSigmaCutProton < 0 && totalMomentum >= minTrackMomentum) { + for (int i = 0; i < nTOFBins - 1; ++i) { + if (totalMomentum >= tofMomBins[i] && totalMomentum < tofMomBins[i + 1] && + (tofNSigProton < tofNSigCuts[i] && + tofNSigPion > tofNSigmaVetoThreshold && + tofNSigKaon > tofNSigmaVetoThreshold)) + tofPIDPassed = true; + } + if (track.tpcNSigmaPr() < minCombinedTPCTOFNSigmaProton) + return false; + if (tpcNSigProton < maxTPCNSigmaProton && + tpcNSigPion > tpcNSigmaVetoThreshold && + tpcNSigKaon > tpcNSigmaVetoThreshold) + tpcPIDPassed = true; + } + + if ((combinedNSigmaCutProton > 0) && totalMomentum >= minTrackMomentum && + (combinedNSigProton < circularCutSquared && + combinedNSigPion > circularRejCutSquared && + combinedNSigKaon > circularRejCutSquared)) { + tofPIDPassed = true; + tpcPIDPassed = true; + } + + if (totalMomentum < minTrackMomentum && tpcNSigProton < maxTPCNSigmaProton) { + tofPIDPassed = true; + tpcPIDPassed = true; + } + + } else { + tofPIDPassed = true; + if (track.tpcNSigmaPr() < minTPCNSigmaProton) + return false; + for (int i = 0; i < nTPCBins - 1; ++i) { + if (totalMomentum >= tpcMomBins[i] && totalMomentum < tpcMomBins[i + 1] && + (tpcNSigProton < tpcNSigCuts[i] && + tpcNSigPion > tpcNSigmaVetoPion && + tpcNSigKaon > tpcNSigmaVetoKaonForProton)) + tpcPIDPassed = true; + } + } + + return (tpcPIDPassed && tofPIDPassed); + } + + // ============================================================ + // passesKaonPID() + // ============================================================ + template + bool passesKaonPID(const TrackType& track, float totalMomentum) + { + bool tpcPIDPassed{false}, tofPIDPassed{false}; + + auto tpcMomBins = static_cast>(kaonTPCPIDMomentumBins); + auto tpcNSigCuts = static_cast>(kaonTPCNSigmaCutPerBin); + auto tofMomBins = static_cast>(kaonTOFPIDMomentumBins); + auto tofNSigCuts = static_cast>(kaonTOFNSigmaCutPerBin); + int nTPCBins = static_cast(tpcMomBins.size()); + int nTOFBins = static_cast(tofMomBins.size()); + + float tpcNSigPion = std::abs(track.tpcNSigmaPi()); + float tpcNSigKaon = std::abs(track.tpcNSigmaKa()); + float tpcNSigProton = std::abs(track.tpcNSigmaPr()); + float tofNSigPion = std::abs(track.tofNSigmaPi()); + float tofNSigKaon = std::abs(track.tofNSigmaKa()); + float tofNSigProton = std::abs(track.tofNSigmaPr()); + + float combinedNSigPion = tpcNSigPion * tpcNSigPion + tofNSigPion * tofNSigPion; + float combinedNSigKaon = tpcNSigKaon * tpcNSigKaon + tofNSigKaon * tofNSigKaon; + float combinedNSigProton = tpcNSigProton * tpcNSigProton + tofNSigProton * tofNSigProton; + float circularCutSquared = combinedNSigmaCutKaon * combinedNSigmaCutKaon; + float circularRejCutSquared = tpcNSigmaVetoOtherSpecies * tofNSigmaVetoThreshold; + + if (!useTPCOnlyPID && track.hasTOF()) { + if (track.tofNSigmaKa() < minTOFNSigmaKaon) + return false; + + if (combinedNSigmaCutKaon < 0 && totalMomentum >= minTrackMomentum) { + for (int i = 0; i < nTOFBins - 1; ++i) { + if (totalMomentum >= tofMomBins[i] && totalMomentum < tofMomBins[i + 1] && + (tofNSigKaon < tofNSigCuts[i] && + tofNSigPion > tofNSigmaVetoThreshold && + tofNSigProton > tofNSigmaVetoThreshold)) + tofPIDPassed = true; + } + if (track.tpcNSigmaKa() < minCombinedTPCTOFNSigmaKaon) + return false; + if (tpcNSigKaon < maxTPCNSigmaKaon && + tpcNSigPion > tpcNSigmaVetoThreshold && + tpcNSigProton > tpcNSigmaVetoThreshold) + tpcPIDPassed = true; + } + + if ((combinedNSigmaCutKaon > 0) && totalMomentum >= minTrackMomentum && + (combinedNSigKaon < circularCutSquared && + combinedNSigPion > circularRejCutSquared && + combinedNSigProton > circularRejCutSquared)) { + tofPIDPassed = true; + tpcPIDPassed = true; + } + + if (totalMomentum < minTrackMomentum && tpcNSigKaon < maxTPCNSigmaKaon) { + tofPIDPassed = true; + tpcPIDPassed = true; + } + + } else { + tofPIDPassed = true; + if (track.tpcNSigmaKa() < minTPCNSigmaKaon) + return false; + for (int i = 0; i < nTPCBins - 1; ++i) { + if (totalMomentum >= tpcMomBins[i] && totalMomentum < tpcMomBins[i + 1] && + (tpcNSigKaon < tpcNSigCuts[i] && + tpcNSigPion > tpcNSigmaVetoPionForKaon && + tpcNSigProton > tpcNSigmaVetoProtonForKaon)) + tpcPIDPassed = true; + } + } + + return (tpcPIDPassed && tofPIDPassed); + } + + // ============================================================ + // fillInvariantMassHistograms() + // ============================================================ + template + void fillInvariantMassHistograms(TrackCollectionType const& protonCandidates, + TrackCollectionType const& kaonCandidates, + float centralityPercent, + int occupancyValue = 100) + { + float protonTotalMomentum = 0., kaonTotalMomentum = 0.; + + for (auto const& [protonTrack, kaonTrack] : + soa::combinations(soa::CombinationsFullIndexPolicy(protonCandidates, kaonCandidates))) { + + if (protonTrack.index() == kaonTrack.index()) + continue; + + if (!passesBasicTrackSelection(protonTrack) || !passesBasicTrackSelection(kaonTrack)) + continue; + + auto pxProton = protonTrack.px(); + auto pyProton = protonTrack.py(); + auto pzProton = protonTrack.pz(); + auto pxKaon = kaonTrack.px(); + auto pyKaon = kaonTrack.py(); + auto pzKaon = kaonTrack.pz(); + + // FIX root/entity: replaced TMath::Sqrt with RecoDecay::p + protonTotalMomentum = RecoDecay::p(pxProton, pyProton, pzProton); + kaonTotalMomentum = RecoDecay::p(pxKaon, pyKaon, pzKaon); + + if (!isMixedEvent) { + auto tpcNSigProton = protonTrack.tpcNSigmaPr(); + allHistograms.fill(HIST("QAbefore/Proton/tpcNSigmaVsMomentum"), protonTotalMomentum, tpcNSigProton); + if (protonTrack.hasTOF()) { + auto tofNSigProton = protonTrack.tofNSigmaPr(); + allHistograms.fill(HIST("QAbefore/Proton/tofNSigmaVsMomentum"), protonTotalMomentum, tofNSigProton); + allHistograms.fill(HIST("QAbefore/Proton/tofNSigmaVsTPCNSigma"), tpcNSigProton, tofNSigProton); + } + auto tpcNSigKaon = kaonTrack.tpcNSigmaKa(); + allHistograms.fill(HIST("QAbefore/Kaon/tpcNSigmaVsMomentum"), kaonTotalMomentum, tpcNSigKaon); + if (kaonTrack.hasTOF()) { + auto tofNSigKaon = kaonTrack.tofNSigmaKa(); + allHistograms.fill(HIST("QAbefore/Kaon/tofNSigmaVsMomentum"), kaonTotalMomentum, tofNSigKaon); + allHistograms.fill(HIST("QAbefore/Kaon/tofNSigmaVsTPCNSigma"), tpcNSigKaon, tofNSigKaon); + } + } + + if (requireTOFForProton && !protonTrack.hasTOF()) + continue; + if (requireTOFForKaon && !kaonTrack.hasTOF()) + continue; + + if (!passesProtonPID(protonTrack, protonTotalMomentum) || + !passesKaonPID(kaonTrack, kaonTotalMomentum)) + continue; + + if (!passesProtonDCASelection(protonTrack, protonTotalMomentum) || + !passesKaonDCASelection(kaonTrack, kaonTotalMomentum)) + continue; + + // FIX root/entity: replaced TMath::ACos with std::acos + if (applyDeepAngleCut && + std::acos((protonTrack.pt() * kaonTrack.pt() + pzProton * pzKaon) / + (protonTotalMomentum * kaonTotalMomentum)) < deepAngleCutValue) + continue; + + if constexpr (!isMixedEvent) { + auto ptProton = protonTrack.pt(); + auto tpcNSigProton = protonTrack.tpcNSigmaPr(); + + allHistograms.fill(HIST("QAafter/Proton/ptVsCentrality"), ptProton, centralityPercent); + allHistograms.fill(HIST("QAafter/Proton/dcaZVsPt"), ptProton, protonTrack.dcaZ()); + allHistograms.fill(HIST("QAafter/Proton/dcaXYVsPt"), ptProton, protonTrack.dcaXY()); + allHistograms.fill(HIST("QAafter/Proton/tpcDedxVsMomentum"), protonTotalMomentum, protonTrack.tpcSignal()); + allHistograms.fill(HIST("QAafter/Proton/tpcNSigmaPionContamVsPt"), protonTotalMomentum, protonTrack.tpcNSigmaPi()); + allHistograms.fill(HIST("QAafter/Proton/tpcNSigmaKaonContamVsPt"), protonTotalMomentum, protonTrack.tpcNSigmaKa()); + allHistograms.fill(HIST("QAafter/Proton/tpcNSigmaVsMomentum"), protonTotalMomentum, tpcNSigProton); + allHistograms.fill(HIST("QAafter/Proton/tpcNSigmaVsPt"), ptProton, tpcNSigProton); + allHistograms.fill(HIST("QAafter/Proton/tpcCrossedRowsVsPt"), ptProton, protonTrack.tpcNClsCrossedRows()); + allHistograms.fill(HIST("QAafter/Proton/tpcClustersFoundVsPt"), ptProton, protonTrack.tpcNClsFound()); + + if (!useTPCOnlyPID && protonTrack.hasTOF()) { + auto tofNSigProton = protonTrack.tofNSigmaPr(); + allHistograms.fill(HIST("QAafter/Proton/tofNSigmaVsMomentum"), protonTotalMomentum, tofNSigProton); + allHistograms.fill(HIST("QAafter/Proton/tofNSigmaVsPt"), ptProton, tofNSigProton); + allHistograms.fill(HIST("QAafter/Proton/tofNSigmaPionContamVsMomentum"), protonTotalMomentum, protonTrack.tofNSigmaPi()); + allHistograms.fill(HIST("QAafter/Proton/tofNSigmaKaonContamVsMomentum"), protonTotalMomentum, protonTrack.tofNSigmaKa()); + allHistograms.fill(HIST("QAafter/Proton/tofNSigmaVsTPCNSigma"), tpcNSigProton, tofNSigProton); + } + + auto ptKaon = kaonTrack.pt(); + auto tpcNSigKaon = kaonTrack.tpcNSigmaKa(); + + allHistograms.fill(HIST("QAafter/Kaon/ptVsCentrality"), ptKaon, centralityPercent); + allHistograms.fill(HIST("QAafter/Kaon/dcaZVsPt"), ptKaon, kaonTrack.dcaZ()); + allHistograms.fill(HIST("QAafter/Kaon/dcaXYVsPt"), ptKaon, kaonTrack.dcaXY()); + allHistograms.fill(HIST("QAafter/Kaon/tpcDedxVsMomentum"), kaonTotalMomentum, kaonTrack.tpcSignal()); + allHistograms.fill(HIST("QAafter/Kaon/tpcNSigmaPionContamVsPt"), kaonTotalMomentum, kaonTrack.tpcNSigmaPi()); + allHistograms.fill(HIST("QAafter/Kaon/tpcNSigmaProtonContamVsMomentum"), kaonTotalMomentum, kaonTrack.tpcNSigmaPr()); + allHistograms.fill(HIST("QAafter/Kaon/tpcNSigmaVsMomentum"), kaonTotalMomentum, tpcNSigKaon); + allHistograms.fill(HIST("QAafter/Kaon/tpcNSigmaVsPt"), ptKaon, tpcNSigKaon); + allHistograms.fill(HIST("QAafter/Kaon/tpcCrossedRowsVsPt"), ptKaon, kaonTrack.tpcNClsCrossedRows()); + allHistograms.fill(HIST("QAafter/Kaon/tpcClustersFoundVsPt"), ptKaon, kaonTrack.tpcNClsFound()); + + if (!useTPCOnlyPID && kaonTrack.hasTOF()) { + auto tofNSigKaon = kaonTrack.tofNSigmaKa(); + allHistograms.fill(HIST("QAafter/Kaon/tofNSigmaVsMomentum"), kaonTotalMomentum, tofNSigKaon); + allHistograms.fill(HIST("QAafter/Kaon/tofNSigmaVsPt"), ptKaon, tofNSigKaon); + allHistograms.fill(HIST("QAafter/Kaon/tofNSigmaPionContamVsMomentum"), kaonTotalMomentum, kaonTrack.tofNSigmaPi()); + allHistograms.fill(HIST("QAafter/Kaon/tofNSigmaProtonContamVsMomentum"), kaonTotalMomentum, kaonTrack.tofNSigmaPr()); + allHistograms.fill(HIST("QAafter/Kaon/tofNSigmaVsTPCNSigma"), tpcNSigKaon, tofNSigKaon); + } + } + + if (runQualityChecksOnly) + continue; + + std::array momProton = {pxProton, pyProton, pzProton}; + std::array momKaon = {pxKaon, pyKaon, pzKaon}; + std::array, 2> bothMomenta = {momProton, momKaon}; + + float pairInvMass = RecoDecay::m(bothMomenta, std::array{MassProton, MassKaonCharged}); + float pairPt = RecoDecay::pt(std::array{pxProton + pxKaon, pyProton + pyKaon}); + float pairRapidity = RecoDecay::y( + std::array{pxProton + pxKaon, pyProton + pyKaon, pzProton + pzKaon}, + pairInvMass); + + if (pairRapidity < minPairRapidity || pairRapidity > maxPairRapidity) + continue; + + if constexpr (!isMixedEvent && !isMCAnalysis) { + if (protonTrack.sign() * kaonTrack.sign() < 0) { + if (protonTrack.sign() > 0) + allHistograms.fill(HIST("Analysis/invMass_UnlikeSign_ProtonPlusKaonMinus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + else + allHistograms.fill(HIST("Analysis/invMass_UnlikeSign_ProtonMinusKaonPlus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + + if (enableRotationalBackground) { + for (int iRot = 0; iRot < numberOfRotations; iRot++) { + float rotationWindowHalfWidth = o2::constants::math::PI / rotationAngleWindow; + float rotatedKaonPhi; + if (numberOfRotations == 1) { + rotatedKaonPhi = o2::constants::math::PI; + } else { + rotatedKaonPhi = (o2::constants::math::PI - rotationWindowHalfWidth) + + iRot * (2.f * rotationWindowHalfWidth / (numberOfRotations - 1)); + } + + // FIX two-pi-add-subtract: replaced manual +/-TwoPI with RecoDecay::constrainAngle + float newKaonPhi = RecoDecay::constrainAngle(kaonTrack.phi() + rotatedKaonPhi, 0.f); + + float pxKaonRotated = kaonTrack.pt() * std::cos(newKaonPhi); + float pyKaonRotated = kaonTrack.pt() * std::sin(newKaonPhi); + + std::array momProtonRot = {pxProton, pyProton, pzProton}; + std::array momKaonRot = {pxKaonRotated, pyKaonRotated, pzKaon}; + std::array, 2> rotatedMomenta = {momProtonRot, momKaonRot}; + + float rotatedPairInvMass = RecoDecay::m(rotatedMomenta, std::array{MassProton, MassKaonCharged}); + float rotatedPairPt = RecoDecay::pt(std::array{pxProton + pxKaonRotated, pyProton + pyKaonRotated}); + float rotatedPairRapidity = RecoDecay::y( + std::array{pxProton + pxKaonRotated, pyProton + pyKaonRotated, pzProton + pzKaon}, + MassLambda1520); + + if (rotatedPairRapidity < minPairRapidity || rotatedPairRapidity > maxPairRapidity) + continue; + + if (protonTrack.sign() * kaonTrack.sign() < 0) { + if (protonTrack.sign() > 0) + allHistograms.fill(HIST("Analysis/invMass_Rotated_ProtonPlusKaonMinus"), + rotatedPairInvMass, rotatedPairPt, centralityPercent, occupancyValue); + else + allHistograms.fill(HIST("Analysis/invMass_Rotated_ProtonMinusKaonPlus"), + rotatedPairInvMass, rotatedPairPt, centralityPercent, occupancyValue); + } + } + } + + } else { + if (protonTrack.sign() > 0) + allHistograms.fill(HIST("Analysis/invMass_LikeSign_ProtonPlusKaonPlus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + else + allHistograms.fill(HIST("Analysis/invMass_LikeSign_ProtonMinusKaonMinus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + } + } + + if constexpr (isMixedEvent) { + if (protonTrack.sign() * kaonTrack.sign() < 0) { + if (protonTrack.sign() > 0) + allHistograms.fill(HIST("Analysis/invMass_Mixed_ProtonPlusKaonMinus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + else + allHistograms.fill(HIST("Analysis/invMass_Mixed_ProtonMinusKaonPlus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + } else { + if (protonTrack.sign() > 0) + allHistograms.fill(HIST("Analysis/invMass_Mixed_LikeSign_PlusPlus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + else + allHistograms.fill(HIST("Analysis/invMass_Mixed_LikeSign_MinusMinus"), + pairInvMass, pairPt, centralityPercent, occupancyValue); + } + } + + if constexpr (isMCAnalysis) { + if (protonTrack.sign() * kaonTrack.sign() < 0) { + // FIX pdg/explicit-code: replaced 2212 and 321 with named constants + if (std::abs(protonTrack.pdgCode()) != kPdgProton || std::abs(kaonTrack.pdgCode()) != kPdgKaon) + continue; + if (protonTrack.motherId() != kaonTrack.motherId()) + continue; + if (protonTrack.motherPDG() != kaonTrack.motherPDG()) + continue; + if (protonTrack.pdgCode() == 0 || kaonTrack.pdgCode() == 0) + continue; + if (protonTrack.motherPDG() == -1 || kaonTrack.motherPDG() == -1) + continue; + if (std::abs(protonTrack.motherPDG()) != pdgCodeLambda1520) + continue; + + float trueMassOfParent = 0.; + for (auto const& parentParticle : *mcResonanceParentTable) { + if (parentParticle.mcParticleId() == protonTrack.motherId()) { + std::array parentMom = {parentParticle.px(), + parentParticle.py(), + parentParticle.pz()}; + trueMassOfParent = RecoDecay::m(parentMom, parentParticle.e()); + break; + } + } + + float massResidual = pairInvMass - trueMassOfParent; + + if (protonTrack.motherPDG() > 0) { + allHistograms.fill(HIST("Analysis/mcReconstructed_Lambda1520"), + pairInvMass, pairPt, centralityPercent); + allHistograms.fill(HIST("Analysis/mcMassResolution_Lambda1520"), + massResidual, pairPt, centralityPercent); + } else { + allHistograms.fill(HIST("Analysis/mcReconstructed_AntiLambda1520"), + pairInvMass, pairPt, centralityPercent); + allHistograms.fill(HIST("Analysis/mcMassResolution_AntiLambda1520"), + massResidual, pairPt, centralityPercent); + } + } + } + } + } + + // ── Type aliases ───────────────────────────────────────────────────────── + using ResonanceCollisionsWithEP = soa::Join; + using ResonanceMCCollisions = soa::Join; + using ResonanceTrackTable = aod::ResoTracks; + + // ============================================================ + // processData() + // ============================================================ + void processData(ResonanceCollisionsWithEP::iterator const& collision, + ResonanceTrackTable const& tracks) + { + allHistograms.fill(HIST("Event/centralityVsOccupancy"), collision.cent(), 100); + allHistograms.fill(HIST("Event/primaryVertexZ"), collision.posZ()); + fillInvariantMassHistograms(tracks, tracks, collision.cent()); + } + PROCESS_SWITCH(Lambda1520Analysispo, processData, + "Process real collision data (same-event analysis)", true); + + // ============================================================ + // processMC() + // ============================================================ + void processMC(ResonanceMCCollisions::iterator const& collision, + soa::Join const& tracks, + aod::ResoMCParents const& mcParents) + { + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 0); + + if (mcRequireTriggerTVX && !collision.isTriggerTVX()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 1); + + if (mcRequireVtxWithin10cm && !collision.isVtxIn10()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 2); + + if (mcRequireINELgt0 && !collision.isINELgt0()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 3); + + if (mcRequireSel8 && !collision.isInSel8()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 4); + + if (mcRequireRecoINELgt0 && !collision.isRecINELgt0()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 5); + + if (mcRequireAfterAllCuts && !collision.isInAfterAllCuts()) + return; + allHistograms.fill(HIST("Event/mcEventSelectionCutflow"), 6); + + auto centralityPercent = collision.cent(); + allHistograms.fill(HIST("Event/centralityVsOccupancy"), centralityPercent, 100); + allHistograms.fill(HIST("Event/primaryVertexZ"), collision.posZ()); + + mcResonanceParentTable = &mcParents; + fillInvariantMassHistograms(tracks, tracks, centralityPercent); + + // FIX const-ref-in-for-loop: added const to range-based for loop + for (const auto& track : tracks) { + allHistograms.fill(HIST("QAbefore/trackEta"), track.eta()); + allHistograms.fill(HIST("QAbefore/trackPt"), track.pt()); + allHistograms.fill(HIST("QAbefore/trackPhi"), track.phi()); + allHistograms.fill(HIST("QAbefore/trackEtaVsPhi"), track.eta(), track.phi()); + + // FIX pdg/explicit-code: replaced 321 and 2212 with named constants + if (std::abs(track.pdgCode()) == kPdgKaon) + allHistograms.fill(HIST("QAChecks/kaonGenLevelPt"), track.pt()); + if (std::abs(track.pdgCode()) == kPdgProton) + allHistograms.fill(HIST("QAChecks/protonGenLevelPt"), track.pt()); + + if (!passesBasicTrackSelection(track)) + continue; + + // FIX root/entity: replaced TMath::Sqrt with RecoDecay::p + float totalMom = RecoDecay::p(track.px(), track.py(), track.pz()); + + if (passesKaonPID(track, totalMom) && std::abs(track.pdgCode()) == kPdgKaon) + allHistograms.fill(HIST("QAChecks/kaonRecoLevelPt"), track.pt()); + if (passesProtonPID(track, totalMom) && std::abs(track.pdgCode()) == kPdgProton) + allHistograms.fill(HIST("QAChecks/protonRecoLevelPt"), track.pt()); + } + + // FIX const-ref-in-for-loop: added const to range-based for loop + for (const auto& parentParticle : mcParents) { + if (std::abs(parentParticle.pdgCode()) != pdgCodeLambda1520) + continue; + if (parentParticle.y() < minPairRapidity || parentParticle.y() > maxPairRapidity) + continue; + + // FIX pdg/explicit-code: replaced 2212 and 321 with named constants + bool hasProtonDaughter = (std::abs(parentParticle.daughterPDG1()) == kPdgProton || + std::abs(parentParticle.daughterPDG2()) == kPdgProton); + bool hasKaonDaughter = (std::abs(parentParticle.daughterPDG1()) == kPdgKaon || + std::abs(parentParticle.daughterPDG2()) == kPdgKaon); + if (!hasProtonDaughter || !hasKaonDaughter) + continue; + + std::array parentMom = {parentParticle.px(), parentParticle.py(), parentParticle.pz()}; + float parentMass = RecoDecay::m(parentMom, parentParticle.e()); + + if (parentParticle.pdgCode() > 0) + allHistograms.fill(HIST("Analysis/mcGenerated_Lambda1520"), + parentMass, parentParticle.pt(), centralityPercent); + else + allHistograms.fill(HIST("Analysis/mcGenerated_AntiLambda1520"), + parentMass, parentParticle.pt(), centralityPercent); + } + } + PROCESS_SWITCH(Lambda1520Analysispo, processMC, + "Process Monte Carlo simulated events", false); + + // ============================================================ + // processMCGen() + // ============================================================ + void processMCGen(ResonanceMCCollisions::iterator const& collision, + aod::ResoMCParents const& mcParents) + { + float centralityPercent = collision.cent(); + + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 0); + if (mcRequireTriggerTVX && !collision.isTriggerTVX()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 1); + if (mcRequireVtxWithin10cm && !collision.isVtxIn10()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 2); + if (mcRequireINELgt0 && !collision.isINELgt0()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 3); + if (mcRequireSel8 && !collision.isInSel8()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 4); + if (mcRequireRecoINELgt0 && !collision.isRecINELgt0()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 5); + if (mcRequireAfterAllCuts && !collision.isInAfterAllCuts()) + return; + allHistograms.fill(HIST("SignalLoss/mcEventSelectionCutflow"), 6); + + // FIX const-ref-in-for-loop: added const to range-based for loop + for (const auto& parentParticle : mcParents) { + if (parentParticle.y() < minPairRapidity || parentParticle.y() > maxPairRapidity) + continue; + + int pdgCode = parentParticle.pdgCode(); + float parentPt = parentParticle.pt(); + double mTScaledPtSquared = -1.0; + + std::array parentMom = {parentParticle.px(), parentParticle.py(), parentParticle.pz()}; + float parentMass = RecoDecay::m(parentMom, parentParticle.e()); + + auto computeMtScaledPtSquared = [&]() -> double { + return (parentPt * parentPt) + (parentMass * parentMass) - + (o2::constants::physics::MassLambda1520 * o2::constants::physics::MassLambda1520); + }; + + // FIX pdg/explicit-code: replaced all bare PDG integers with named constants + if (pdgCode == kPdgProton) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromProton"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == -kPdgProton) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromAntiProton"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == kPdgLambda0) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromLambda0"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == -kPdgLambda0) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromAntiLambda0"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == kPdgXiMinus) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromXiMinus"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == -kPdgXiMinus) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromXiPlus"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == kPdgXi0) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromXi0"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == -kPdgXi0) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromAntiXi0"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == kPdgOmegaMinus) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromOmegaMinus"), std::sqrt(mTScaledPtSquared), centralityPercent); + } else if (pdgCode == -kPdgOmegaMinus) { + mTScaledPtSquared = computeMtScaledPtSquared(); + if (mTScaledPtSquared > 0) + allHistograms.fill(HIST("SignalLoss/mTScaled_fromOmegaPlus"), std::sqrt(mTScaledPtSquared), centralityPercent); + } + } + } + PROCESS_SWITCH(Lambda1520Analysispo, processMCGen, + "Generator-level MC signal loss study (mT scaling)", false); + + // ── Event-mixing binning types ─────────────────────────────────────────── + using MixingBinningVtxZAndCentrality = + ColumnBinningPolicy; + + // ============================================================ + // processMix() + // FIX const-ref-in-process: collisions is now const& + // ============================================================ + void processMix(ResonanceCollisionsWithEP const& collisions, + ResonanceTrackTable const& tracks) + { + LOGF(debug, "Event mixing started"); + MixingBinningVtxZAndCentrality mixingBins{{vertexZMixingBins, centralityMixingBins}, true}; + auto trackPool = std::make_tuple(tracks); + SameKindPair + eventPairs{mixingBins, numberOfEventsToMix, -1, collisions, trackPool, &sliceCache}; + + // FIX const-ref-in-for-loop: added const to structured binding + for (const auto& [col1, tracks1, col2, tracks2] : eventPairs) { + allHistograms.fill(HIST("Event/mixingBins_centralityVsVtxZVsEventPlane"), + col1.cent(), col1.posZ(), col1.evtPl()); + fillInvariantMassHistograms(tracks1, tracks2, col1.cent()); + } + } + PROCESS_SWITCH(Lambda1520Analysispo, processMix, + "Event mixing for background estimation (standard format)", true); + + // ── Merged-DF type aliases ─────────────────────────────────────────────── + Preslice tracksPerMergedDFCollision = aod::resodaughter::resoCollisionDFId; + using MergedDFCollisions = aod::ResoCollisionDFs; + using MergedDFTracks = aod::ResoTrackDFs; + + // ============================================================ + // processDatadf() + // ============================================================ + void processDatadf(MergedDFCollisions::iterator const& collision, + MergedDFTracks const& tracks) + { + if (doprocessData) + LOG(error) << "Disable processData() first when using processDatadf()!"; + + auto occupancyValue = 100; + if (applyOccupancyInTimeRangeCut) + occupancyValue = collision.trackOccupancyInTimeRange(); + + allHistograms.fill(HIST("Event/centralityVsOccupancy"), collision.cent(), occupancyValue); + fillInvariantMassHistograms(tracks, tracks, collision.cent(), occupancyValue); + } + PROCESS_SWITCH(Lambda1520Analysispo, processDatadf, + "Process real data in merged derived-data (DF) format", false); + + // ============================================================ + // processMixDF() + // FIX const-ref-in-process: collisions is now const& + // ============================================================ + using MixingBinningDF = ColumnBinningPolicy; + + void processMixDF(MergedDFCollisions const& collisions, MergedDFTracks const& tracks) + { + if (doprocessMix) + LOG(fatal) << "Disable processMix() first when using processMixDF()!"; + LOGF(debug, "Event mixing (DF format) started"); + + MixingBinningDF mixingBins{{vertexZMixingBins, centralityMixingBins}, true}; + auto trackPool = std::make_tuple(tracks); + SameKindPair + eventPairs{mixingBins, numberOfEventsToMix, -1, collisions, trackPool, &sliceCache}; + + // FIX const-ref-in-for-loop: added const to structured binding + for (const auto& [col1, tracks1, col2, tracks2] : eventPairs) { + auto occupancyValue = 100; + if (applyOccupancyInTimeRangeCut) + occupancyValue = col1.trackOccupancyInTimeRange(); + + allHistograms.fill(HIST("Event/mixingBins_centralityVsVtxZVsEventPlane"), + col1.cent(), col1.posZ(), col1.evtPl()); + fillInvariantMassHistograms(tracks1, tracks2, col1.cent(), occupancyValue); + } + } + PROCESS_SWITCH(Lambda1520Analysispo, processMixDF, + "Event mixing for DF-format data", false); + + // ============================================================ + // processMixepDF() + // FIX const-ref-in-process: collisions is now const& + // ============================================================ + using MixingBinningWithEventPlane = + ColumnBinningPolicy; + + void processMixepDF(MergedDFCollisions const& collisions, MergedDFTracks const& tracks) + { + if (doprocessMix || doprocessMixDF) + LOG(fatal) << "Disable processMix() or processMixDF() first!"; + LOGF(debug, "Event-plane-dependent event mixing (DF format) started"); + + MixingBinningWithEventPlane mixingBins{ + {vertexZMixingBins, centralityMixingBins, eventPlaneMixingBins}, true}; + auto trackPool = std::make_tuple(tracks); + SameKindPair + eventPairs{mixingBins, numberOfEventsToMix, -1, collisions, trackPool, &sliceCache}; + + // FIX const-ref-in-for-loop: added const to structured binding + for (const auto& [col1, tracks1, col2, tracks2] : eventPairs) { + allHistograms.fill(HIST("Event/mixingBins_centralityVsVtxZVsEventPlane"), + col1.cent(), col1.posZ(), col1.evtPl()); + fillInvariantMassHistograms(tracks1, tracks2, col1.cent()); + } + } + PROCESS_SWITCH(Lambda1520Analysispo, processMixepDF, + "Event-plane dependent event mixing for DF-format data", false); +}; + +WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) +{ + return WorkflowSpec{adaptAnalysisTask(cfgc)}; +} From a1aed14c55f3f52ef7b43ca7c514c8762f428fdb Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 14:39:50 +0530 Subject: [PATCH 5/9] Add lambda1520-analysispo 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 e129ce85dcf..cc491751767 100644 --- a/PWGLF/Tasks/Resonances/CMakeLists.txt +++ b/PWGLF/Tasks/Resonances/CMakeLists.txt @@ -308,3 +308,8 @@ o2physics_add_dpl_workflow(k892hadronphoton SOURCES k892hadronphoton.cxx PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB COMPONENT_NAME Analysis) + +o2physics_add_dpl_workflow(lambda1520-analysispo + SOURCES lambda1520Analysispo.cxx + PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB + COMPONENT_NAME Analysis) From 4fd076f426e288ce0f23a97da2402c9f82fe2ffb Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 14:43:44 +0530 Subject: [PATCH 6/9] Add files via upload From e996f57dfa65c3587c5726a6dce7191001183f7d Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 14:44:30 +0530 Subject: [PATCH 7/9] Update CMakeLists.txt From 3578e067d9a3c488c39746dd05b55b7794e8a1ea Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 16:03:50 +0530 Subject: [PATCH 8/9] Update lambda1520_PbPb.cxx --- PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx index 9ad42047138..40589f98e58 100644 --- a/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx +++ b/PWGLF/Tasks/Resonances/lambda1520_PbPb.cxx @@ -14,7 +14,6 @@ /// /// \author Yash Patley /// \author Nasir Mehdi Malik -/// \author Durgesh Bhatt #include "PWGLF/DataModel/LFResonanceTables.h" @@ -61,11 +60,10 @@ struct lambdaAnalysis_pb { // Tracks Configurable cPtMin{"cPtMin", 0.15, "Minimum Track pT"}; Configurable cPMin{"cPMin", 0., "Minimum Track p"}; - Configurable cEtaMin{"cEtaMin", -0.8, "Minimum Pseudorapidity"}; - Configurable cEtaMax{"cEtaMax", 0.8, "Maximum Pseudorapidity"}; + Configurable cEtaCut{"cEtaCut", 0.8, "Pseudorapidity cut"}; Configurable cDcaz{"cDcazMin", 1., "Minimum DCAz"}; - Configurable cfgRapidityMin{"cfgRapidityMin", -0.5, "Minimum rapidity"}; - Configurable cfgRapidityMax{"cfgRapidityMax", 0.5, "Maximum rapidity"}; + Configurable cfgRapidityShift{"cfgRapidityShift", 0., " rapidity shift"}; + Configurable cfgRapidityCut{"cfgRapidityCut", 0.5, "Rapidity window"}; // TPC crossed rows (absolute) Configurable cfgMinCrossedRows{"cfgMinCrossedRows", 70, "min TPC crossed rows"}; Configurable cfgUseCrossedRows{"cfgUseCrossedRows", false, "apply crossed rows cut"}; @@ -267,7 +265,7 @@ struct lambdaAnalysis_pb { if (track.pt() < cPtMin) return false; - if (track.eta() < cEtaMin || track.eta() > cEtaMax) + if (std::abs(track.eta()) > cEtaCut) return false; if (cPrimaryTrack && !track.isPrimaryTrack()) @@ -583,9 +581,11 @@ struct lambdaAnalysis_pb { float _M = RecoDecay::m(arrMomrec, std::array{MassProton, MassKaonCharged}); float _pt = RecoDecay::pt(std::array{_pxPr + _pxKa, _pyPr + _pyKa}); - float _y = RecoDecay::y(std::array{_pxPr + _pxKa, _pyPr + _pyKa, _pzPr + _pzKa}, _M); + float _y = std::abs(RecoDecay::y(std::array{_pxPr + _pxKa, _pyPr + _pyKa, _pzPr + _pzKa}, _M)); - if (_y < cfgRapidityMin || _y > cfgRapidityMax) + float _yshift = _y - cfgRapidityShift; + + if (std::abs(_yshift) > cfgRapidityCut) continue; // Apply kinematic cuts. @@ -604,7 +604,7 @@ struct lambdaAnalysis_pb { float delta = o2::constants::math::PI / rotationalcut; float theta2; if (cNofRotations == 1) { - // Single rotation â just rotate by exactly PI + // Single rotation — just rotate by exactly PI theta2 = o2::constants::math::PI; } else { theta2 = (o2::constants::math::PI - delta) + @@ -627,9 +627,11 @@ struct lambdaAnalysis_pb { float _Mrot = RecoDecay::m(arrMomRot, std::array{MassProton, MassKaonCharged}); float _ptRot = RecoDecay::pt(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot}); - float _yrot = RecoDecay::y(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, MassLambda1520); + float _yrot = std::abs(RecoDecay::y(std::array{_pxPr + _pxKaRot, _pyPr + _pyKaRot, _pzPr + _pzKa}, MassLambda1520)); + + float _yshiftrot = _yrot - cfgRapidityShift; - if (_yrot < cfgRapidityMin || _yrot > cfgRapidityMax) + if (std::abs(_yshiftrot) > cfgRapidityCut) continue; if (trkPr.sign() * trkKa.sign() < 0) { @@ -788,7 +790,9 @@ struct lambdaAnalysis_pb { if (std::abs(part.pdgCode()) != lambda1520id) // // L* pdg_code = 3124 continue; - if (part.y() < cfgRapidityMin || part.y() > cfgRapidityMax) + float yshift = std::abs(part.y()) - cfgRapidityShift; + + if (std::abs(yshift) > cfgRapidityCut) continue; bool pass1 = false; bool pass2 = false; @@ -846,7 +850,9 @@ struct lambdaAnalysis_pb { for (auto const& part : resoParents) { - if (part.y() < cfgRapidityMin || part.y() > cfgRapidityMax) + float yshift = std::abs(part.y()) - cfgRapidityShift; + + if (std::abs(yshift) > cfgRapidityCut) continue; int pdg = part.pdgCode(); From 303d2335d56663b3bbc1468cf8febe39cfdfcfd4 Mon Sep 17 00:00:00 2001 From: bhattdurgesh921-sys Date: Mon, 20 Apr 2026 16:10:45 +0530 Subject: [PATCH 9/9] Update CMakeLists.txt --- PWGLF/Tasks/Resonances/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PWGLF/Tasks/Resonances/CMakeLists.txt b/PWGLF/Tasks/Resonances/CMakeLists.txt index cc491751767..ef9699be941 100644 --- a/PWGLF/Tasks/Resonances/CMakeLists.txt +++ b/PWGLF/Tasks/Resonances/CMakeLists.txt @@ -309,7 +309,7 @@ o2physics_add_dpl_workflow(k892hadronphoton PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB COMPONENT_NAME Analysis) -o2physics_add_dpl_workflow(lambda1520-analysispo +o2physics_add_dpl_workflow(lambda1520analysisinpo SOURCES lambda1520Analysispo.cxx PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore O2Physics::AnalysisCCDB COMPONENT_NAME Analysis)