diff --git a/PWGCF/TwoParticleCorrelations/Tasks/nucleibalance.cxx b/PWGCF/TwoParticleCorrelations/Tasks/nucleibalance.cxx index b70f6037b85..ded525fd309 100644 --- a/PWGCF/TwoParticleCorrelations/Tasks/nucleibalance.cxx +++ b/PWGCF/TwoParticleCorrelations/Tasks/nucleibalance.cxx @@ -86,13 +86,14 @@ struct Nucleibalance { O2_DEFINE_CONFIGURABLE(cfgCutMCPt, float, 0.5f, "Minimal pT for MC particles (AO2D-MC mode)") O2_DEFINE_CONFIGURABLE(cfgCutMCEta, float, 0.8f, "Eta range for MC particles (AO2D-MC mode)") O2_DEFINE_CONFIGURABLE(cfgUseFT0M, int, 1, "Use FT0M centrality (0-100) as multiplicity axis (1=ON, 0=use Ntracks/multiplicity())") - // Track-quality options (AO2D mode). Default selection corresponds to global tracks. - O2_DEFINE_CONFIGURABLE(cfgTPCNClsMin, int, 70, "Minimum number of TPC clusters (tpcNClsFound) in AO2D mode") - O2_DEFINE_CONFIGURABLE(cfgDcaXYMax, float, 0.1f, "Max |DCA_{xy}| to PV (cm) in AO2D mode") - O2_DEFINE_CONFIGURABLE(cfgDcaZMax, float, 0.2f, "Max |DCA_{z}| to PV (cm) in AO2D mode") - O2_DEFINE_CONFIGURABLE(chi2pertpccluster, float, 2.5f, "Maximum Chi2/cluster for the TPC track segment in AO2D mode") - O2_DEFINE_CONFIGURABLE(chi2peritscluster, float, 36.f, "Maximum Chi2/cluster for the ITS track segment in AO2D mode") - O2_DEFINE_CONFIGURABLE(itsnclusters, int, 5, "Minimum number of ITS clusters in AO2D mode") + // Track-quality options. Default selection corresponds to global tracks. + O2_DEFINE_CONFIGURABLE(cfgTPCNClsMin, int, 70, "Minimum number of TPC clusters (tpcNClsFound)") + O2_DEFINE_CONFIGURABLE(cfgDcaXYMax, float, 0.1f, "Max |DCA_{xy}| to PV (cm)") + O2_DEFINE_CONFIGURABLE(cfgDcaZMax, float, 0.2f, "Max |DCA_{z}| to PV (cm)") + O2_DEFINE_CONFIGURABLE(cfgRequirePrimaryTrack, int, 1, "Require isPrimaryTrack()= true (1=ON, 0=OFF)") + O2_DEFINE_CONFIGURABLE(chi2pertpccluster, float, 2.5f, "Maximum Chi2/cluster for the TPC track segment") + O2_DEFINE_CONFIGURABLE(chi2peritscluster, float, 36.f, "Maximum Chi2/cluster for the ITS track segment") + O2_DEFINE_CONFIGURABLE(itsnclusters, int, 1, "Minimum number of ITS clusters") O2_DEFINE_CONFIGURABLE(cfgPtOrder, int, 1, "Only consider pairs for which pT,1 < pT,2 (0 = OFF, 1 = ON)"); O2_DEFINE_CONFIGURABLE(cfgTriggerCharge, int, 0, "Select on charge of trigger particle: 0 = all; 1 = positive; -1 = negative"); @@ -343,6 +344,15 @@ struct Nucleibalance { return false; } } + // Match the standard Lambda(1520) task option `cfgPrimaryTrack`: + // require primary tracks when the corresponding selection column is available. + if (cfgRequirePrimaryTrack.value != 0) { + if constexpr (requires { trk.isPrimaryTrack(); }) { + if (!trk.isPrimaryTrack()) { + return false; + } + } + } if constexpr (requires { trk.itsNCls(); }) { if (itsnclusters.value > 0 && trk.itsNCls() < itsnclusters.value) { @@ -1680,7 +1690,7 @@ struct Lambdastarproxy { // PID strategy values static constexpr int PidStrategyRectangular = 0; static constexpr int PidStrategyCircularTPCAndTOF = 1; - static constexpr int PidStrategyTOFOnly = 2; + static constexpr int PidStrategyNucleiDeuteronTPC = 2; // Basic configuration for event and track selection Configurable lstarCutVertex{"lstarCutVertex", float{CutVertexDefault}, "Accepted z-vertex range (cm)"}; Configurable lstarCutPtMin{"lstarCutPtMin", float{CutPtMinDefault}, "Minimal pT for tracks (GeV/c)"}; @@ -1700,16 +1710,26 @@ struct Lambdastarproxy { Configurable lstarCutNsigmaTOFKaon{"lstarCutNsigmaTOFKaon", float{NsigmaTOFDefault}, "|nSigma^{TOF}_{K}| cut"}; Configurable lstarCutNsigmaTPCDe{"lstarCutNsigmaTPCDe", float{NsigmaTPCDefault}, "|nSigma^{TPC}_{d}| cut"}; Configurable lstarCutNsigmaTOFDe{"lstarCutNsigmaTOFDe", float{NsigmaTOFDefault}, "|nSigma^{TOF}_{d}| cut"}; + // Optional deuteron-only TOF auxiliary selections. + // Defaults are OFF, so strategy 2 remains TPC nSigma only for deuterons. + Configurable lstarEnableBetaCutDe{"lstarEnableBetaCutDe", 0, "Enable deuteron-only TOF beta cut using beta() > lstarBetaCutDe"}; + Configurable lstarBetaCutDe{"lstarBetaCutDe", 0.4f, "Minimum TOF beta for deuteron-only beta cut"}; + Configurable lstarEnableExpSignalTOFDe{"lstarEnableExpSignalTOFDe", 0, "Enable deuteron-only TOF expected-signal-difference cut when lstarTOFExpSignalDiffDeMax > 0"}; + Configurable lstarTOFExpSignalDiffDeMax{"lstarTOFExpSignalDiffDeMax", -1.f, "Maximum |tofExpSignalDiffDe| for deuterons; <=0 disables the numeric cut"}; + // PID strategy for final K/p/d candidate selection. - // 0 = rectangular cuts: |TPC| < cut and, if TOF exists, |TOF| < cut + // 0 = pT-ref dependent rectangular cuts: + // pT < pTref: require |TPC| < TPC cut only + // pT >= pTref and TOF exists: require |TPC| < TPC cut and |TOF| < TOF cut + // pT >= pTref and TOF missing: reject // 1 = pT-ref dependent circular cut: // pT < pTref: require |TPC| < TPC cut only // pT >= pTref and TOF exists: require sqrt(TPC^2 + TOF^2) < circular cut // pT >= pTref and TOF missing: reject - // 2 = TOF-only above pTref: - // pT < pTref: require |TPC| < TPC cut only - // pT >= pTref: require TOF hit and |TOF| < TOF cut - Configurable lstarPidStrategy{"lstarPidStrategy", int{PidStrategyRectangular}, "PID strategy: 0=rectangular TPC/TOF, 1=pTref circular TPC+TOF, 2=pTref TOF-only"}; + // 2 = hybrid official-like PID: + // deuterons: require |TPC_d| < TPC cut only + // kaons/protons: use the Lambda(1520)-like pT-ref circular TPC+TOF logic + Configurable lstarPidStrategy{"lstarPidStrategy", int{PidStrategyRectangular}, "PID strategy: 0=pTref rectangular TPC/TOF, 1=pTref circular TPC+TOF, 2=hybrid: K/p circular, d TPC-only"}; Configurable lstarPidCircularCutKaon{"lstarPidCircularCutKaon", 2.0f, "Circular PID cut sqrt(nSigmaTPC_K^2+nSigmaTOF_K^2) for kaons"}; Configurable lstarPidCircularCutPr{"lstarPidCircularCutPr", 2.0f, "Circular PID cut sqrt(nSigmaTPC_p^2+nSigmaTOF_p^2) for protons"}; @@ -1720,8 +1740,12 @@ struct Lambdastarproxy { Configurable lstarPidPtRefDe{"lstarPidPtRefDe", 0.8f, "pT reference for deuteron PID strategy"}; // Track quality + Configurable lstarRequireINELgt0{"lstarRequireINELgt0", 1, "Require INEL>0 event selection using isInelGt0() when available; fallback to multNTracksPVeta1()/numContrib()"}; Configurable lstarRequireGlobalTrack{"lstarRequireGlobalTrack", bool{RequireGlobalTrackDefault}, "Require global tracks (default)"}; - Configurable lstarTPCNClsMin{"lstarTPCNClsMin", int{TPCNClsMinDefault}, "Minimum number of TPC clusters (tpcNClsFound)"}; + Configurable lstarRequirePrimaryTrack{"lstarRequirePrimaryTrack", 1, "Require isPrimaryTrack() for Lambda* proxy candidates when the column is available"}; + Configurable lstarOnlyGlobalTrackCuts{"lstarOnlyGlobalTrackCuts", 0, "If enabled, apply only the global-track plus primary-track requirements and skip additional ITS/TPC/DCA/chi2 track-quality cuts"}; + Configurable lstarTPCNClsMin{"lstarTPCNClsMin", int{TPCNClsMinDefault}, "Minimum number of TPC crossed rows (tpcNClsCrossedRows)"}; + Configurable lstarTPCCrossedRowsOverFindableMin{"lstarTPCCrossedRowsOverFindableMin", 0.8f, "Minimum TPC crossed rows over findable clusters"}; Configurable lstarDcaXYMax{"lstarDcaXYMax", float{DcaXYMaxDefault}, "Max |DCA_{xy}| to PV (cm)"}; Configurable lstarDcaZMax{"lstarDcaZMax", float{DcaZMaxDefault}, "Max |DCA_{z}| to PV (cm)"}; Configurable lstarChi2PerTPCCluster{"lstarChi2PerTPCCluster", float{Chi2PerTPCClusterDefault}, "Maximum Chi2/cluster for the TPC track segment"}; @@ -1790,6 +1814,22 @@ struct Lambdastarproxy { template bool keepCollisionAO2D(TCollision const& collision) const { + // Prefer the official event-selection flag when available. + if (lstarRequireINELgt0.value != 0) { + bool isINELgt0 = true; + + if constexpr (requires { collision.isInelGt0(); }) { + isINELgt0 = collision.isInelGt0(); + } else if constexpr (requires { collision.multNTracksPVeta1(); }) { + isINELgt0 = collision.multNTracksPVeta1() > 0; + } else if constexpr (requires { collision.numContrib(); }) { + isINELgt0 = collision.numContrib() > 0; + } + + if (!isINELgt0) { + return false; + } + } if (lstarCfgTrigger.value == TriggerNone) { return true; } else if (lstarCfgTrigger.value == TriggerSel8) { @@ -1903,14 +1943,40 @@ struct Lambdastarproxy { } } + // Always require primary tracks + if (lstarRequirePrimaryTrack.value != 0) { + if constexpr (requires { trk.isPrimaryTrack(); }) { + if (!trk.isPrimaryTrack()) { + return false; + } + } + } + + // Optional official-baseline mode: use only the O2 global-track and primary-track definitions. + // This bypasses the extra explicit ITS/TPC/DCA/chi2 cuts below. + if (lstarOnlyGlobalTrackCuts.value != 0) { + return true; + } + if constexpr (requires { trk.itsNCls(); }) { if (lstarITSNClusters.value > 0 && trk.itsNCls() < lstarITSNClusters.value) { return false; } } - if constexpr (requires { trk.tpcNClsFound(); }) { - if (lstarTPCNClsMin.value > 0 && trk.tpcNClsFound() < lstarTPCNClsMin.value) { + if constexpr (requires { trk.tpcNClsCrossedRows(); }) { + if (lstarTPCNClsMin.value > 0 && trk.tpcNClsCrossedRows() < lstarTPCNClsMin.value) { + return false; + } + } else if constexpr (requires { trk.tpcNClsFindable(); trk.tpcCrossedRowsOverFindableCls(); }) { + if (lstarTPCNClsMin.value > 0 && trk.tpcNClsFindable() * trk.tpcCrossedRowsOverFindableCls() < lstarTPCNClsMin.value) { + return false; + } + } + + if constexpr (requires { trk.tpcCrossedRowsOverFindableCls(); }) { + if (lstarTPCCrossedRowsOverFindableMin.value > 0.f && + trk.tpcCrossedRowsOverFindableCls() < lstarTPCCrossedRowsOverFindableMin.value) { return false; } } @@ -2327,14 +2393,48 @@ struct Lambdastarproxy { return true; // fallback: if column not present, assume available } - bool passFinalCandidatePID(float pt, - float nsTPC, - float nsTOF, - bool hasTof, - float tpcCut, - float tofCut, - float circularCut, - float ptRef) const + template + bool passOptionalDeuteronTOFExtras(const TTrack& trk) const + { + const bool needTOF = + (lstarEnableBetaCutDe.value != 0) || + (lstarEnableExpSignalTOFDe.value != 0 && lstarTOFExpSignalDiffDeMax.value > 0.f); + + if (needTOF) { + if constexpr (requires { trk.hasTOF(); }) { + if (!trk.hasTOF()) { + return false; + } + } + } + + if (lstarEnableBetaCutDe.value != 0) { + if constexpr (requires { trk.beta(); }) { + if (trk.beta() <= lstarBetaCutDe.value) { + return false; + } + } else { + return false; + } + } + + if (lstarEnableExpSignalTOFDe.value != 0 && + lstarTOFExpSignalDiffDeMax.value > 0.f) { + if constexpr (requires { trk.tofExpSignalDiffDe(); }) { + if (std::abs(trk.tofExpSignalDiffDe()) > lstarTOFExpSignalDiffDeMax.value) { + return false; + } + } else { + return false; + } + } + + return true; + } + + bool passFinalCandidatePID(float pt, float nsTPC, float nsTOF, bool hasTof, + float tpcCut, float tofCut, float circularCut, + float ptRef, bool isDeuteron = false) const { // Strategy 1: analysis-note style circular TPC+TOF cut if (lstarPidStrategy.value == PidStrategyCircularTPCAndTOF) { @@ -2349,8 +2449,14 @@ struct Lambdastarproxy { return std::sqrt(nsTPC * nsTPC + nsTOF * nsTOF) < circularCut; } - // Strategy 2: TOF-only above pTref - if (lstarPidStrategy.value == PidStrategyTOFOnly) { + if (lstarPidStrategy.value == PidStrategyNucleiDeuteronTPC) { + // For deuterons, follow the default idea of the official nuclei task: + // use TPC nσ as the main hard PID selection. + if (isDeuteron) { + return std::abs(nsTPC) < tpcCut; + } + + // For kaons/protons, use the Lambda(1520)-like pT-ref circular TPC+TOF logic. if (pt < ptRef) { return std::abs(nsTPC) < tpcCut; } @@ -2359,11 +2465,18 @@ struct Lambdastarproxy { return false; } - return std::abs(nsTOF) < tofCut; + return std::sqrt(nsTPC * nsTPC + nsTOF * nsTOF) < circularCut; } - // Strategy 0: old rectangular logic - return (std::abs(nsTPC) < tpcCut) && (!hasTof || (std::abs(nsTOF) < tofCut)); + // Strategy 0: pT-ref dependent rectangular TPC+TOF cut. + // Below pTref use TPC only; above pTref require TOF and apply both TPC and TOF cuts. + if (pt < ptRef) { + return std::abs(nsTPC) < tpcCut; + } + if (!hasTof) { + return false; + } + return (std::abs(nsTPC) < tpcCut) && (std::abs(nsTOF) < tofCut); } // Return: 0=#pi, 1=K, 2=p, 3=d, -1=unclassified @@ -2650,14 +2763,12 @@ struct Lambdastarproxy { const float nsTPCDe = trkD.tpcNSigmaDe(); const float nsTOFDe = trkD.tofNSigmaDe(); const bool hasTofDe = hasTOFMatch(trkD); - const bool isDeuteron = passFinalCandidatePID(ptD, - nsTPCDe, - nsTOFDe, - hasTofDe, - lstarCutNsigmaTPCDe.value, - lstarCutNsigmaTOFDe.value, - lstarPidCircularCutDe.value, - lstarPidPtRefDe.value); + if (!passOptionalDeuteronTOFExtras(trkD)) { + continue; + } + const bool isDeuteron = passFinalCandidatePID(trkD.pt(), trkD.tpcNSigmaDe(), nsTOFDe, hasTofDe, + lstarCutNsigmaTPCDe.value, lstarCutNsigmaTOFDe.value, + lstarPidCircularCutDe.value, lstarPidPtRefDe.value, true); if (!isDeuteron) { continue; } @@ -2724,7 +2835,7 @@ struct Lambdastarproxy { lstarCutNsigmaTPCPr.value, lstarCutNsigmaTOFPr.value, lstarPidCircularCutPr.value, - lstarPidPtRefPr.value); + lstarPidPtRefPr.value, false); if (!isProton) { continue; } @@ -2786,7 +2897,7 @@ struct Lambdastarproxy { lstarCutNsigmaTPCKaon.value, lstarCutNsigmaTOFKaon.value, lstarPidCircularCutKaon.value, - lstarPidPtRefKaon.value); + lstarPidPtRefKaon.value, false); if (!isKaon) { continue; }