diff --git a/PWGLF/DataModel/lambdaJetPolarizationIons.h b/PWGLF/DataModel/lambdaJetPolarizationIons.h index 00d3f98d1c5..0a336ecf385 100644 --- a/PWGLF/DataModel/lambdaJetPolarizationIons.h +++ b/PWGLF/DataModel/lambdaJetPolarizationIons.h @@ -24,6 +24,7 @@ #include #include +#include namespace o2::aod { @@ -34,12 +35,16 @@ namespace lambdajetpol DECLARE_SOA_COLUMN(CentFT0M, centFT0M, float); DECLARE_SOA_COLUMN(CentFT0C, centFT0C, float); DECLARE_SOA_COLUMN(CentFV0A, centFV0A, float); +DECLARE_SOA_COLUMN(Zvtx, zvtx, float); +DECLARE_SOA_COLUMN(InteractionRate, interactionRate, float); +// DECLARE_SOA_COLUMN(MagField, magField, float); // Jet (and jet proxies) information: DECLARE_SOA_COLUMN(JetPt, jetPt, float); DECLARE_SOA_COLUMN(JetEta, jetEta, float); DECLARE_SOA_COLUMN(JetPhi, jetPhi, float); -DECLARE_SOA_COLUMN(JetNConstituents, jetNConstituents, int); +// DECLARE_SOA_COLUMN(JetNConstituents, jetNConstituents, uint16_t); // Currently removed from datamodel. +// Other variables can better reveal jet quenching and help identify good selection criteria for quenched jets proxies DECLARE_SOA_COLUMN(LeadParticlePt, leadParticlePt, float); DECLARE_SOA_COLUMN(LeadParticleEta, leadParticleEta, float); @@ -50,22 +55,27 @@ DECLARE_SOA_COLUMN(V0Pt, v0Pt, float); DECLARE_SOA_COLUMN(V0Eta, v0Eta, float); DECLARE_SOA_COLUMN(V0Phi, v0Phi, float); -DECLARE_SOA_COLUMN(IsLambda, isLambda, bool); -DECLARE_SOA_COLUMN(IsAntiLambda, isAntiLambda, bool); -DECLARE_SOA_COLUMN(MassLambda, massLambda, float); -DECLARE_SOA_COLUMN(MassAntiLambda, massAntiLambda, float); +DECLARE_SOA_COLUMN(IsLambda, isLambda, bool); //! 0: antiLambda, 1: Lambda. There are no ambiguous candidates stored (those that pass both Lambda-specific and AntiLambda-specific checks) +// DECLARE_SOA_COLUMN(IsAntiLambda, isAntiLambda, bool); +DECLARE_SOA_COLUMN(MassV0, massV0, float); +// DECLARE_SOA_COLUMN(MassLambda, massLambda, float); +// DECLARE_SOA_COLUMN(MassAntiLambda, massAntiLambda, float); -DECLARE_SOA_COLUMN(PosPt, posPt, float); +DECLARE_SOA_COLUMN(PosPt, posPt, float); // Could consider rewriting this as proton/pion-like Pt, as in TPCNSigma, instead of Pos/Neg DECLARE_SOA_COLUMN(PosEta, posEta, float); DECLARE_SOA_COLUMN(PosPhi, posPhi, float); DECLARE_SOA_COLUMN(NegPt, negPt, float); DECLARE_SOA_COLUMN(NegEta, negEta, float); DECLARE_SOA_COLUMN(NegPhi, negPhi, float); -DECLARE_SOA_COLUMN(PosTPCNSigmaPr, posTPCNSigmaPr, float); -DECLARE_SOA_COLUMN(PosTPCNSigmaPi, posTPCNSigmaPi, float); -DECLARE_SOA_COLUMN(NegTPCNSigmaPr, negTPCNSigmaPr, float); -DECLARE_SOA_COLUMN(NegTPCNSigmaPi, negTPCNSigmaPi, float); +// DECLARE_SOA_COLUMN(PosTPCNSigmaPr, posTPCNSigmaPr, float); +// DECLARE_SOA_COLUMN(PosTPCNSigmaPi, posTPCNSigmaPi, float); +// DECLARE_SOA_COLUMN(NegTPCNSigmaPr, negTPCNSigmaPr, float); +// DECLARE_SOA_COLUMN(NegTPCNSigmaPi, negTPCNSigmaPi, float); +// TPC Nsigma variables now stored only for non-ambiguous candidates (those that pass only the Lambda-specific or antiLambda-specific tests): +// (PrLike: proton-like daughter of the V0, either a proton (Lambda) or an antiproton (AntiLambda). PiLike is the pion daughter) +DECLARE_SOA_COLUMN(RoundPrLikeTPCNSigma, roundPrLikeTPCNSigma, int16_t); //! Stores nsigma with a precision of 0.01 nsigma. Following LFStrangenessPIDTables.h +DECLARE_SOA_COLUMN(RoundPiLikeTPCNSigma, roundPiLikeTPCNSigma, int16_t); DECLARE_SOA_COLUMN(V0CosPA, v0CosPA, float); DECLARE_SOA_COLUMN(V0Radius, v0Radius, float); @@ -74,38 +84,46 @@ DECLARE_SOA_COLUMN(DcaPosToPV, dcaPosToPV, float); DECLARE_SOA_COLUMN(DcaNegToPV, dcaNegToPV, float); // Dynamic columns for jets (Px,Py,Pz): -DECLARE_SOA_DYNAMIC_COLUMN(JetPx, jetPx, //! Jet px +DECLARE_SOA_DYNAMIC_COLUMN(JetPx, jetPx, [](float jetPt, float jetPhi) -> float { return jetPt * std::cos(jetPhi); }); -DECLARE_SOA_DYNAMIC_COLUMN(JetPy, jetPy, //! Jet py +DECLARE_SOA_DYNAMIC_COLUMN(JetPy, jetPy, [](float jetPt, float jetPhi) -> float { return jetPt * std::sin(jetPhi); }); -DECLARE_SOA_DYNAMIC_COLUMN(JetPz, jetPz, //! Jet pz +DECLARE_SOA_DYNAMIC_COLUMN(JetPz, jetPz, [](float jetPt, float jetEta) -> float { return jetPt * std::sinh(jetEta); }); // Same for leading particles: -DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePx, leadParticlePx, //! Leading particle px +DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePx, leadParticlePx, [](float leadParticlePt, float leadParticlePhi) -> float { return leadParticlePt * std::cos(leadParticlePhi); }); -DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePy, leadParticlePy, //! Leading particle py +DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePy, leadParticlePy, [](float leadParticlePt, float leadParticlePhi) -> float { return leadParticlePt * std::sin(leadParticlePhi); }); -DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePz, leadParticlePz, //! Leading particle pz +DECLARE_SOA_DYNAMIC_COLUMN(LeadParticlePz, leadParticlePz, [](float leadParticlePt, float leadParticleEta) -> float { return leadParticlePt * std::sinh(leadParticleEta); }); + +// Dynamic columns for retrieving rounded Nsigma columns: +DECLARE_SOA_DYNAMIC_COLUMN(PrLikeTPCNSigma, prLikeTPCNSigma, + [](int16_t packedValue) -> float { return packedValue / 100.f; }); +DECLARE_SOA_DYNAMIC_COLUMN(PiLikeTPCNSigma, piLikeTPCNSigma, + [](int16_t packedValue) -> float { return packedValue / 100.f; }); } // namespace lambdajetpol DECLARE_SOA_TABLE(RingCollisions, "AOD", "RINGCOLLISION", - o2::soa::Index<>, // self-index: auto-assigned row number + o2::soa::Index<>, //! self-index: auto-assigned row number lambdajetpol::CentFT0M, lambdajetpol::CentFT0C, - lambdajetpol::CentFV0A); + lambdajetpol::CentFV0A, + lambdajetpol::Zvtx, + lambdajetpol::InteractionRate); namespace lambdajetpol { -DECLARE_SOA_INDEX_COLUMN(RingCollision, ringCollision); // Declare index after table is available +DECLARE_SOA_INDEX_COLUMN(RingCollision, ringCollision); //! Declare index after table is available } // namespace lambdajetpol DECLARE_SOA_TABLE(RingJets, "AOD", "RINGJET", - lambdajetpol::RingCollisionId, // relational index -> RingCollisions + lambdajetpol::RingCollisionId, //! relational index -> RingCollisions lambdajetpol::JetPt, lambdajetpol::JetEta, lambdajetpol::JetPhi, - lambdajetpol::JetNConstituents, + // lambdajetpol::JetNConstituents, // Dynamic columns (explicitly bound to their static inputs): lambdajetpol::JetPx, lambdajetpol::JetPy, @@ -127,24 +145,23 @@ DECLARE_SOA_TABLE(RingLaV0s, "AOD", "RINGLAV0", lambdajetpol::V0Eta, lambdajetpol::V0Phi, lambdajetpol::IsLambda, - lambdajetpol::IsAntiLambda, - lambdajetpol::MassLambda, - lambdajetpol::MassAntiLambda, + lambdajetpol::MassV0, lambdajetpol::PosPt, lambdajetpol::PosEta, lambdajetpol::PosPhi, lambdajetpol::NegPt, lambdajetpol::NegEta, lambdajetpol::NegPhi, - lambdajetpol::PosTPCNSigmaPr, - lambdajetpol::PosTPCNSigmaPi, - lambdajetpol::NegTPCNSigmaPr, - lambdajetpol::NegTPCNSigmaPi, + lambdajetpol::RoundPrLikeTPCNSigma, + lambdajetpol::RoundPiLikeTPCNSigma, lambdajetpol::V0CosPA, lambdajetpol::V0Radius, lambdajetpol::DcaV0Daughters, lambdajetpol::DcaPosToPV, - lambdajetpol::DcaNegToPV); + lambdajetpol::DcaNegToPV, + // Dynamic columns: + lambdajetpol::PrLikeTPCNSigma, + lambdajetpol::PiLikeTPCNSigma); using RingCollision = RingCollisions::iterator; // Useful shorthand } // namespace o2::aod diff --git a/PWGLF/TableProducer/Strangeness/lambdaJetPolarizationIons.cxx b/PWGLF/TableProducer/Strangeness/lambdaJetPolarizationIons.cxx index d8e57ee1494..f9253f3469c 100644 --- a/PWGLF/TableProducer/Strangeness/lambdaJetPolarizationIons.cxx +++ b/PWGLF/TableProducer/Strangeness/lambdaJetPolarizationIons.cxx @@ -72,10 +72,10 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -133,9 +133,6 @@ enum BkgSubtraction { ////////////////////////////////////////////// struct lambdajetpolarizationions { - - // struct : ProducesGroup { - // } products; Produces tableV0s; Produces tableJets; Produces tableLeadParticles; @@ -146,23 +143,20 @@ struct lambdajetpolarizationions { // master analysis switches Configurable analyseLambda{"analyseLambda", true, "process Lambda-like candidates"}; - Configurable analyseAntiLambda{"analyseAntiLambda", false, "process AntiLambda-like candidates"}; // Will work only with Lambdas, in a first analysis + Configurable analyseAntiLambda{"analyseAntiLambda", true, "process AntiLambda-like candidates"}; Configurable doPPAnalysis{"doPPAnalysis", false, "if in pp, set to true. Default is HI"}; Configurable irSource{"irSource", "ZNChadronic", "Estimator of the interaction rate (Recommended: pp --> T0VTX, Pb-Pb --> ZNChadronic)"}; // Renamed David's "ZNC hadronic" to the proper code "ZNChadronic" Configurable centralityEstimatorForQA{"centralityEstimatorForQA", kCentFT0M, "Run 3 centrality estimator (0:CentFT0C, 1:CentFT0M, 2:CentFV0A)"}; // Default is FT0M - // (Now saving all centralities at the derived data level -- Makes them all available for consumer) - // (But still using this variable for QA histograms) ///////////////////////////////////////////// - Configurable doEventQA{"doEventQA", false, "do event QA histograms"}; + Configurable doEventQA{"doEventQA", true, "do event QA histograms"}; // Configurable qaCentrality{"qaCentrality", false, "qa centrality flag: check base raw values"}; - Configurable doCompleteTopoQA{"doCompleteTopoQA", false, "do topological variables QA histograms"}; // Includes doPlainTopoQA from derivedlambdakzeroanalysis - Configurable doV0KinematicQA{"doV0KinematicQA", false, "do kinematic variables QA histograms"}; + Configurable doTopoQA{"doTopoQA", true, "do topological variables QA histograms"}; + Configurable doV0KinematicQA{"doV0KinematicQA", true, "do kinematic variables QA histograms"}; Configurable doArmenterosQA{"doArmenterosQA", false, "do Armenteros QA histograms"}; Configurable doTPCQA{"doTPCQA", false, "do TPC QA histograms"}; Configurable doTOFQA{"doTOFQA", false, "do TOF QA histograms"}; - Configurable doEtaPhiQA{"doEtaPhiQA", false, "do Eta/Phi QA histograms for V0s and daughters"}; Configurable doJetKinematicsQA{"doJetKinematicsQA", false, "do pT,Eta,Phi QA histograms for jets"}; ///////////////////////////////////////////// @@ -184,30 +178,34 @@ struct lambdajetpolarizationions { std::string prefix = "eventSelections"; // JSON group name Configurable requireSel8{"requireSel8", true, "require sel8 event selection"}; Configurable requireTriggerTVX{"requireTriggerTVX", true, "require FT0 vertex (acceptable FT0C-FT0A time difference) at trigger level"}; // part of sel8, actually - Configurable rejectITSROFBorder{"rejectITSROFBorder", true, "reject events at ITS ROF border (Run 3 only)"}; // part of sel8, actually - Configurable rejectTFBorder{"rejectTFBorder", true, "reject events at TF border (Run 3 only)"}; // part of sel8, actually + // Detector status: + Configurable rejectITSROFBorder{"rejectITSROFBorder", true, "reject events at ITS ROF border (Run 3 only)"}; // part of sel8, actually + Configurable rejectTFBorder{"rejectTFBorder", true, "reject events at TF border (Run 3 only)"}; // part of sel8, actually + Configurable requireGoodITSLayersAll{"requireGoodITSLayersAll", false, "require number of inactive chips on all ITS layers to be below maximum allowed values"}; // Based off ITS holes DPG AOT selection + // Vertex quality: Configurable requireIsVertexITSTPC{"requireIsVertexITSTPC", false, "require events with at least one ITS-TPC track (Run 3 only)"}; Configurable requireIsGoodZvtxFT0VsPV{"requireIsGoodZvtxFT0VsPV", true, "require events with PV position along z consistent (within 1 cm) between PV reconstructed using tracks and PV using FT0 A-C time difference (Run 3 only)"}; // o2::aod::evsel::kIsGoodZvtxFT0vsPV. Recommended for OO Configurable requireIsVertexTOFmatched{"requireIsVertexTOFmatched", false, "require events with at least one of vertex contributors matched to TOF (Run 3 only)"}; Configurable requireIsVertexTRDmatched{"requireIsVertexTRDmatched", false, "require events with at least one of vertex contributors matched to TRD (Run 3 only)"}; + Configurable maxZVtxPosition{"maxZVtxPosition", 10., "max Z vtx position [cm]"}; + // Pileup and cannibalism rejection: Configurable rejectSameBunchPileup{"rejectSameBunchPileup", true, "reject collisions in case of pileup with another collision in the same foundBC (Run 3 only)"}; // o2::aod::evsel::kNoSameBunchPileup. Recommended for OO Configurable requireNoCollInTimeRangeStd{"requireNoCollInTimeRangeStd", false, "reject collisions corrupted by the cannibalism, with other collisions within +/- 2 microseconds or mult above a certain threshold in -4 - -2 microseconds (Run 3 only)"}; Configurable requireNoCollInTimeRangeStrict{"requireNoCollInTimeRangeStrict", false, "reject collisions corrupted by the cannibalism, with other collisions within +/- 10 microseconds (Run 3 only)"}; Configurable requireNoCollInTimeRangeNarrow{"requireNoCollInTimeRangeNarrow", false, "reject collisions corrupted by the cannibalism, with other collisions within +/- 2 microseconds (Run 3 only)"}; Configurable requireNoCollInROFStd{"requireNoCollInROFStd", false, "reject collisions corrupted by the cannibalism, with other collisions within the same ITS ROF with mult. above a certain threshold (Run 3 only)"}; Configurable requireNoCollInROFStrict{"requireNoCollInROFStrict", false, "reject collisions corrupted by the cannibalism, with other collisions within the same ITS ROF (Run 3 only)"}; - Configurable requireINEL0{"requireINEL0", true, "require INEL>0 event selection"}; // Only truly useful in pp - Configurable requireINEL1{"requireINEL1", false, "require INEL>1 event selection"}; - - Configurable maxZVtxPosition{"maxZVtxPosition", 10., "max Z vtx position"}; - + // Physics selections: + Configurable requireINEL0{"requireINEL0", true, "require INEL>0 event selection. Only applied in pp"}; // Only truly useful in pp. Also only ever applied in pp + Configurable requireINEL1{"requireINEL1", false, "require INEL>1 event selection. Only applied in pp"}; + // MC: Configurable useEvtSelInDenomEff{"useEvtSelInDenomEff", false, "Consider event selections in the recoed <-> gen collision association for the denominator (or numerator) of the acc. x eff. (or signal loss)?"}; Configurable applyZVtxSelOnMCPV{"applyZVtxSelOnMCPV", true, "Apply Z-vtx cut on the PV of the generated collision?"}; // I see no reason as to not do this by default + // Occupancy: Configurable useFT0CbasedOccupancy{"useFT0CbasedOccupancy", false, "Use sum of FT0-C amplitudes for estimating occupancy? (if not, use track-based definition)"}; - // fast check on occupancy Configurable minOccupancy{"minOccupancy", -1, "minimum occupancy from neighbouring collisions"}; Configurable maxOccupancy{"maxOccupancy", -1, "maximum occupancy from neighbouring collisions"}; - // fast check on interaction rate + // Interaction rate: Configurable minIR{"minIR", -1, "minimum IR collisions"}; Configurable maxIR{"maxIR", -1, "maximum IR collisions"}; } eventSelections; @@ -216,22 +214,21 @@ struct lambdajetpolarizationions { std::string prefix = "v0Selections"; // JSON group name Configurable v0TypeSelection{"v0TypeSelection", 1, "select on a certain V0 type (leave negative if no selection desired)"}; + Configurable rapidityCut{"rapidityCut", 1.0f, "rapidity"}; // This is actually a physics cut, not a proper acceptance cut // Selection criteria: acceptance - Configurable rapidityCut{"rapidityCut", 1.0f, "rapidity"}; - Configurable v0EtaCut{"v0EtaCut", 0.9f, "eta cut for v0"}; - Configurable daughterEtaCut{"daughterEtaCut", 0.9f, "max eta for daughters"}; // Default is 0.8. Changed to 0.9 to agree with jet selection. TODO: test the impact/biasing of this! + // Configurable daughterEtaCut{"daughterEtaCut", 0.9f, "max eta for daughters"}; // Default is 0.8. Changed to 0.9 to agree with jet selection. TODO: test the impact/biasing of this! // Standard 5 topological criteria -- Closed a bit more for the Lambda analysis Configurable v0cospa{"v0cospa", 0.995, "min V0 CosPA"}; // Default is 0.97 - Configurable dcav0dau{"dcav0dau", 1.0, "max DCA V0 Daughters (cm)"}; // Default is 1.0 + Configurable dcav0dau{"dcav0dau", 1.2, "max DCA V0 Daughters (cm)"}; // Default is 1.0 in v0builder // Configurable dcanegtopv{"dcanegtopv", .2, "min DCA Neg To PV (cm)"}; // Default is .05 // Configurable dcapostopv{"dcapostopv", .05, "min DCA Pos To PV (cm)"}; // Default is .05 // Renamed for better consistency of candidate selection (the cut is not determined by charge, but by mass and how deflected the daughter is): - Configurable dcaPionToPV{"dcaPionToPV", .2, "min DCA pion-like daughter To PV (cm)"}; // Default is .05. Suppresses pion background. + Configurable dcaPionToPV{"dcaPionToPV", .05, "min DCA pion-like daughter To PV (cm)"}; // .2 would suppress primary pion background, but we need to reduce fake-polarization signals enhanced by DCA-to-PV cuts Configurable dcaProtonToPV{"dcaProtonToPV", .05, "min DCA proton-like daughter To PV (cm)"}; // Default is .05 - Configurable v0radius{"v0radius", 1.2, "minimum V0 radius (cm)"}; // Default is 1.2 + Configurable v0radius{"v0radius", 1.0, "minimum V0 radius (cm)"}; // Default is 1.2 Configurable v0radiusMax{"v0radiusMax", 1E5, "maximum V0 radius (cm)"}; - Configurable lambdaLifetimeCut{"lambdaLifetimeCut", 30., "lifetime cut (c*tau) for Lambda (cm)"}; + Configurable lambdaLifetimeCut{"lambdaLifetimeCut", 30., "lifetime cut (c*tau) for Lambda (cm)"}; // For soft Lambdas, expect V0Radius < 30 cm. For hard ones, it can be > 30 cm. // invariant mass selection Configurable compMassRejection{"compMassRejection", -1, "Competing mass rejection (GeV/#it{c}^{2})"}; // This was creating bumps in the pp analysis code's invariant mass. Turned off for now. @@ -256,7 +253,7 @@ struct lambdajetpolarizationions { Configurable phiHighCut{"phiHighCut", "0.1/x+pi/18.0+0.06", "High azimuth cut parametrisation"}; // PID (TPC/TOF) - Configurable tpcPidNsigmaCut{"tpcPidNsigmaCut", 4, "tpcPidNsigmaCut"}; // Default is 5 + Configurable tpcPidNsigmaCut{"tpcPidNsigmaCut", 5, "tpcPidNsigmaCut"}; // Default is 5 Configurable tofPidNsigmaCutLaPr{"tofPidNsigmaCutLaPr", 1e+6, "tofPidNsigmaCutLaPr"}; Configurable tofPidNsigmaCutLaPi{"tofPidNsigmaCutLaPi", 1e+6, "tofPidNsigmaCutLaPi"}; @@ -272,7 +269,7 @@ struct lambdajetpolarizationions { // Run Condition Table (RCT) configurables struct : ConfigurableGroup { std::string prefix = "rctConfigurations"; // JSON group name - Configurable cfgRCTLabel{"cfgRCTLabel", "", "Which detector condition requirements? (CBT, CBT_hadronPID, CBT_electronPID, CBT_calo, CBT_muon, CBT_muon_glo)"}; + Configurable cfgRCTLabel{"cfgRCTLabel", "CBT", "Which detector condition requirements? (CBT, CBT_hadronPID, CBT_electronPID, CBT_calo, CBT_muon, CBT_muon_glo)"}; Configurable cfgCheckZDC{"cfgCheckZDC", false, "Include ZDC flags in the bit selection (for Pb-Pb only)"}; Configurable cfgTreatLimitedAcceptanceAsBad{"cfgTreatLimitedAcceptanceAsBad", false, "reject all events where the detectors relevant for the specified Runlist are flagged as LimitedAcceptance"}; } rctConfigurations; @@ -303,7 +300,9 @@ struct lambdajetpolarizationions { ctpRateFetcher rateFetcher; int mRunNumber; float magField; - std::map metadata; + // For some binning variables: + int mBinHasRingJet; + int mBinHasRingV0; o2::parameters::GRPMagField* grpmag = nullptr; // Histogram axes configuration: @@ -313,22 +312,38 @@ struct lambdajetpolarizationions { ConfigurableAxis axisPtXi{"axisPtXi", {VARIABLE_WIDTH, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f, 4.0f, 4.4f, 4.8f, 5.2f, 5.6f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 17.0f, 19.0f, 21.0f, 23.0f, 25.0f, 30.0f, 35.0f, 40.0f, 50.0f}, "pt axis for feeddown from Xi"}; ConfigurableAxis axisPtCoarse{"axisPtCoarse", {VARIABLE_WIDTH, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 7.0f, 10.0f, 15.0f}, "pt axis for QA"}; ConfigurableAxis axisLambdaMass{"axisLambdaMass", {450, 1.08f, 1.15f}, ""}; // Default is {200, 1.101f, 1.131f} + // ConfigurableAxis axisLambdaMassCoarse{"axisLambdaMassCoarse", {200, 1.101f, 1.131f}, ""}; + // Shared axis with the derived consumer, to focus on the peak-vs-sideband trends: + ConfigurableAxis axisLambdaMassSigExtract{ + "axisLambdaMassSigExtract", + {VARIABLE_WIDTH, + // Left sideband (7 bins, 0.004 width) + 1.0800, 1.0840, 1.0880, 1.0920, + 1.0960, 1.1000, 1.1040, 1.1080, + // Fine peak region (8 bins, 0.0016 width) + 1.1096, 1.1112, 1.1128, 1.1144, + 1.1160, 1.1176, 1.1192, 1.1208, + // Right sideband (7 bins, 0.004 width) + 1.1248, 1.1288, 1.1328, 1.1368, + 1.1408, 1.1448, 1.1488}, + "Lambda mass in GeV/c"}; + ConfigurableAxis axisPVz{"axisPVz", {100, -20.0f, +20.0f}, "Primary Vertex Z [cm]"}; + ConfigurableAxis axisPVzCoarse{"axisPVzCoarse", {20, -20.0f, +20.0f}, "Primary Vertex Z [cm]"}; + // Centrality/IR/Occupancy: ConfigurableAxis axisCentrality{"axisCentrality", {VARIABLE_WIDTH, 0.0f, 5.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f, 70.0f, 80.0f, 90.0f}, "Centrality"}; ConfigurableAxis axisNch{"axisNch", {500, 0.0f, +5000.0f}, "Number of charged particles"}; ConfigurableAxis axisIRBinning{"axisIRBinning", {500, 0, 50}, "Binning for the interaction rate (kHz)"}; ConfigurableAxis axisMultFT0M{"axisMultFT0M", {500, 0.0f, +100000.0f}, "Multiplicity FT0M"}; ConfigurableAxis axisMultFT0C{"axisMultFT0C", {500, 0.0f, +10000.0f}, "Multiplicity FT0C"}; ConfigurableAxis axisMultFV0A{"axisMultFV0A", {500, 0.0f, +100000.0f}, "Multiplicity FV0A"}; - ConfigurableAxis axisRawCentrality{"axisRawCentrality", {VARIABLE_WIDTH, 0.000f, 52.320f, 75.400f, 95.719f, 115.364f, 135.211f, 155.791f, 177.504f, 200.686f, 225.641f, 252.645f, 281.906f, 313.850f, 348.302f, 385.732f, 426.307f, 470.146f, 517.555f, 568.899f, 624.177f, 684.021f, 748.734f, 818.078f, 892.577f, 973.087f, 1058.789f, 1150.915f, 1249.319f, 1354.279f, 1465.979f, 1584.790f, 1710.778f, 1844.863f, 1985.746f, 2134.643f, 2291.610f, 2456.943f, 2630.653f, 2813.959f, 3006.631f, 3207.229f, 3417.641f, 3637.318f, 3865.785f, 4104.997f, 4354.938f, 4615.786f, 4885.335f, 5166.555f, 5458.021f, 5762.584f, 6077.881f, 6406.834f, 6746.435f, 7097.958f, 7462.579f, 7839.165f, 8231.629f, 8635.640f, 9052.000f, 9484.268f, 9929.111f, 10389.350f, 10862.059f, 11352.185f, 11856.823f, 12380.371f, 12920.401f, 13476.971f, 14053.087f, 14646.190f, 15258.426f, 15890.617f, 16544.433f, 17218.024f, 17913.465f, 18631.374f, 19374.983f, 20136.700f, 20927.783f, 21746.796f, 22590.880f, 23465.734f, 24372.274f, 25314.351f, 26290.488f, 27300.899f, 28347.512f, 29436.133f, 30567.840f, 31746.818f, 32982.664f, 34276.329f, 35624.859f, 37042.588f, 38546.609f, 40139.742f, 41837.980f, 43679.429f, 45892.130f, 400000.000f}, "raw centrality signal"}; // for QA - ConfigurableAxis axisOccupancy{"axisOccupancy", {VARIABLE_WIDTH, 0.0f, 250.0f, 500.0f, 750.0f, 1000.0f, 1500.0f, 2000.0f, 3000.0f, 4500.0f, 6000.0f, 8000.0f, 10000.0f, 50000.0f}, "Occupancy"}; - - // topological variable QA axes - ConfigurableAxis axisDCAtoPV{"axisDCAtoPV", {20, 0.0f, 1.0f}, "DCA (cm)"}; - ConfigurableAxis axisDCAdau{"axisDCAdau", {20, 0.0f, 2.0f}, "DCA (cm)"}; - ConfigurableAxis axisPointingAngle{"axisPointingAngle", {20, 0.0f, 2.0f}, "pointing angle (rad)"}; - ConfigurableAxis axisV0Radius{"axisV0Radius", {20, 0.0f, 60.0f}, "V0 2D radius (cm)"}; + // Topological variable QA axes: + ConfigurableAxis axisDCAtoPV{"axisDCAtoPV", {30, 0.0f, 2.0f}, "DCA (cm)"}; + ConfigurableAxis axisDCAdau{"axisDCAdau", {20, 0.0f, 1.2f}, "DCA (cm)"}; // This should follow v0Selections.dcav0dau's default + ConfigurableAxis axisPointingAngle{"axisPointingAngle", {20, 0.0f, 0.4f}, "pointing angle (rad)"}; // cos(0.4 rad) is ~0.92 for cosPA + ConfigurableAxis axisV0Radius{"axisV0Radius", {30, 0.0f, 60.0f}, "V0 2D radius (cm)"}; + // PID: ConfigurableAxis axisNsigmaTPC{"axisNsigmaTPC", {200, -10.0f, 10.0f}, "N sigma TPC"}; ConfigurableAxis axisTPCsignal{"axisTPCsignal", {200, 0.0f, 200.0f}, "TPC signal"}; ConfigurableAxis axisNsigmaTOF{"axisNsigmaTOF", {200, -10.0f, 10.0f}, "N sigma TOF"}; @@ -337,31 +352,29 @@ struct lambdajetpolarizationions { ConfigurableAxis axisPhiMod{"axisPhiMod", {100, 0.0f, constants::math::TwoPI / 18}, "Azimuth angle wrt TPC sector (rad.)"}; ConfigurableAxis axisEta{"axisEta", {50, -1.0f, 1.0f}, "#eta"}; ConfigurableAxis axisRapidity{"axisRapidity", {50, -1.0f, 1.0f}, "y"}; - ConfigurableAxis axisITSchi2{"axisITSchi2", {100, 0.0f, 100.0f}, "#chi^{2} per ITS clusters"}; - ConfigurableAxis axisTPCchi2{"axisTPCchi2", {100, 0.0f, 100.0f}, "#chi^{2} per TPC clusters"}; - ConfigurableAxis axisTPCrowsOverFindable{"axisTPCrowsOverFindable", {120, 0.0f, 1.2f}, "Fraction of TPC crossed rows over findable clusters"}; - ConfigurableAxis axisTPCfoundOverFindable{"axisTPCfoundOverFindable", {120, 0.0f, 1.2f}, "Fraction of TPC found over findable clusters"}; - ConfigurableAxis axisTPCsharedClusters{"axisTPCsharedClusters", {101, -0.005f, 1.005f}, "Fraction of TPC shared clusters"}; - // AP plot axes ConfigurableAxis axisAPAlpha{"axisAPAlpha", {220, -1.1f, 1.1f}, "V0 AP alpha"}; ConfigurableAxis axisAPQt{"axisAPQt", {220, 0.0f, 0.5f}, "V0 AP alpha"}; - // Track quality axes ConfigurableAxis axisTPCrows{"axisTPCrows", {160, 0.0f, 160.0f}, "N TPC rows"}; ConfigurableAxis axisITSclus{"axisITSclus", {7, 0.0f, 7.0f}, "N ITS Clusters"}; - ConfigurableAxis axisITScluMap{"axisITScluMap", {128, -0.5f, 127.5f}, "ITS Cluster map"}; - ConfigurableAxis axisDetMap{"axisDetMap", {16, -0.5f, 15.5f}, "Detector use map"}; - ConfigurableAxis axisITScluMapCoarse{"axisITScluMapCoarse", {16, -3.5f, 12.5f}, "ITS Coarse cluster map"}; - ConfigurableAxis axisDetMapCoarse{"axisDetMapCoarse", {5, -0.5f, 4.5f}, "Detector Coarse user map"}; + // ConfigurableAxis axisITScluMap{"axisITScluMap", {128, -0.5f, 127.5f}, "ITS Cluster map"}; + // ConfigurableAxis axisDetMap{"axisDetMap", {16, -0.5f, 15.5f}, "Detector use map"}; + // ConfigurableAxis axisITScluMapCoarse{"axisITScluMapCoarse", {16, -3.5f, 12.5f}, "ITS Coarse cluster map"}; + // ConfigurableAxis axisDetMapCoarse{"axisDetMapCoarse", {5, -0.5f, 4.5f}, "Detector Coarse user map"}; + ConfigurableAxis axisITSchi2{"axisITSchi2", {100, 0.0f, 100.0f}, "#chi^{2} per ITS clusters"}; + ConfigurableAxis axisTPCchi2{"axisTPCchi2", {100, 0.0f, 100.0f}, "#chi^{2} per TPC clusters"}; + // ConfigurableAxis axisTPCrowsOverFindable{"axisTPCrowsOverFindable", {120, 0.0f, 1.2f}, "Fraction of TPC crossed rows over findable clusters"}; + // ConfigurableAxis axisTPCfoundOverFindable{"axisTPCfoundOverFindable", {120, 0.0f, 1.2f}, "Fraction of TPC found over findable clusters"}; + // ConfigurableAxis axisTPCsharedClusters{"axisTPCsharedClusters", {101, -0.005f, 1.005f}, "Fraction of TPC shared clusters"}; // MC coll assoc QA axis ConfigurableAxis axisMonteCarloNch{"axisMonteCarloNch", {300, 0.0f, 3000.0f}, "N_{ch} MC"}; // Jet QA axes: - ConfigurableAxis JetsPerEvent{"JetsPerEvent", {20, 0, 20}, "Jets per event"}; - - ConfigurableAxis axisLeadingParticlePt{"axisLeadingParticlePt", {200, 0.f, 200.f}, "Leading particle p_{T} (GeV/c)"}; // Simpler version! + ConfigurableAxis axisJetsPerEvent{"axisJetsPerEvent", {20, 0, 20}, "Jets per event"}; + ConfigurableAxis axisJetConstituents{"axisJetConstituents", {200, 0, 200}, "Number of jet constituents"}; + ConfigurableAxis axisLeadingParticlePt{"axisLeadingParticlePt", {200, 0.f, 200.f}, "Leading particle p_{T} [GeV/c]"}; // Simpler version! ConfigurableAxis axisJetPt{"axisJetPt", {200, 0.f, 200.f}, "Jet p_{t} (GeV)"}; ConfigurableAxis axisCosTheta{"axisCosTheta", {50, -1.f, 1.f}, "cos(#Delta #theta_{jet})"}; ConfigurableAxis axisDeltaPhi{"axisDeltaPhi", {50, -constants::math::PI, constants::math::PI}, "#Delta #phi"}; @@ -374,14 +387,14 @@ struct lambdajetpolarizationions { // (TODO: create a reasonable track selection for full, photon, and Z-tagged jet tracks, including detector angular acceptance parameters for EMCal) struct : ConfigurableGroup { std::string prefix = "jetConfigurations"; // JSON group name - Configurable minJetPt{"minJetPt", 30.0f, "Minimum reconstructed pt of the jet (GeV/c)"}; // Something in between pp and PbPb minima. Change for bkgSubtraction true or false! - Configurable radiusJet{"radiusJet", 0.4f, "Jet resolution parameter (R)"}; // (TODO: check if the JE people don't define this as a rescaled int to not lose precision for stricter selections) + Configurable minJetPt{"minJetPt", 20.0f, "Minimum reconstructed pt of the jet (GeV/c)"}; // Something in between pp and PbPb minima. Change for bkgSubtraction true or false! + Configurable radiusJet{"radiusJet", 0.4f, "Jet resolution parameter (R)"}; // Notice that the maximum Eta of the jet will then be 0.9 - R to keep the jet contained within the ITS+TPC barrel. Configurable jetAlgorithm{"jetAlgorithm", kAntiKt, "jet clustering algorithm. 0 = kT, 1 = C/A, 2 = Anti-kT"}; Configurable jetRecombScheme{"jetRecombScheme", kEScheme, "Jet recombination scheme: 0: E_scheme, 1: pT-scheme, 2: pt2-scheme, 7: WTA_pt_scheme"}; // See PWGJE/JetFinders/jetFinder.h for more info. Configurable bkgSubtraction{"bkgSubtraction", kNoSubtraction, "Jet background subtraction: No subtraction (false), Area (true), Constituent (TODO)"}; // Selection bool for background subtraction strategy - Configurable GhostedAreaSpecRapidity{"GhostedAreaSpecRapidity", 1.1, "Max ghost particle rapidity for jet area estimates"}; // At least 1.0 for tracks and jets within the |eta| < 0.9 window of ITS+TPC + Configurable ghostedAreaSpecRapidity{"ghostedAreaSpecRapidity", 1.1, "Max ghost particle rapidity for jet area estimates"}; // At least 1.0 for tracks and jets within the |eta| < 0.9 window of ITS+TPC // Using an enum for readability: Configurable jetType{"jetType", kChargedJet, "Jet type: 0: Charged Jet, 1: Full Jet, 2: Photon-tagged, 3: Z-tagged"}; // TODO: implement full, photon and Z jets // (TODO: check the maximum pT of jets used in my analyses! If it is way too hard, it might not be the best jet to use!) @@ -457,6 +470,9 @@ struct lambdajetpolarizationions { // Configurable minTPCrowsOverFindableClusters{"minTPCrowsOverFindableClusters", -1, "minimum nbr of TPC crossed rows over findable clusters"}; // Configurable minTPCfoundOverFindableClusters{"minTPCfoundOverFindableClusters", 0.8f, "minimum nbr of found over findable TPC clusters"}; + // QA checks for detector asymmetry: + Configurable forceNoITS{"forceNoITS", false, "Use onlt TPC-onwards tracks as pseudoJet candidates to QA asymmetries/pile-up in detector"}; + // Jets typical cuts (suppress non-primary candidates): Configurable doDCAcuts{"doDCAcuts", false, "Apply DCA cuts to jet candidates (biases towards primary-vertex/prompt hadron jets)"}; Configurable maxDCAz{"maxDCAz", 3.2f, "Max DCAz to primary vertex [cm] (remove pileup influence)"}; @@ -470,9 +486,10 @@ struct lambdajetpolarizationions { JetBkgSubUtils backgroundSub; + /// \brief Books every histogram used by the producer and sets up the CCDB/RCT services. void init(InitContext const&) { - // setting CCDB service + // Setting CCDB service ccdb->setURL(ccdbConfigurations.ccdbUrl); ccdb->setCaching(true); ccdb->setFatalWhenNull(false); @@ -481,88 +498,94 @@ struct lambdajetpolarizationions { rctFlagsChecker.init(rctConfigurations.cfgRCTLabel.value, rctConfigurations.cfgCheckZDC, rctConfigurations.cfgTreatLimitedAcceptanceAsBad); // Event Counters - histos.add("hEventSelection", "hEventSelection", kTH1D, {{23, -0.5f, +20.5f}}); + histos.add("hEventSelection", "hEventSelection;;Counts", kTH1D, {{24, -0.5f, +23.5f}}); histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(1, "All collisions"); histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(2, "sel8 cut"); histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(3, "kIsTriggerTVX"); histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(4, "kNoITSROFrameBorder"); histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(5, "kNoTimeFrameBorder"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(6, "posZ cut"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(7, "kIsVertexITSTPC"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(8, "kIsGoodZvtxFT0vsPV"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(9, "kIsVertexTOFmatched"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(10, "kIsVertexTRDmatched"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(11, "kNoSameBunchPileup"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(12, "kNoCollInTimeRangeStd"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(13, "kNoCollInTimeRangeStrict"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(14, "kNoCollInTimeRangeNarrow"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(15, "kNoCollInRofStd"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(16, "kNoCollInRofStrict"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(6, "kIsGoodITSLayersAll"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(7, "posZ cut"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(8, "kIsVertexITSTPC"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(9, "kIsGoodZvtxFT0vsPV"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(10, "kIsVertexTOFmatched"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(11, "kIsVertexTRDmatched"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(12, "kNoSameBunchPileup"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(13, "kNoCollInTimeRangeStd"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(14, "kNoCollInTimeRangeStrict"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(15, "kNoCollInTimeRangeNarrow"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(16, "kNoCollInRofStd"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(17, "kNoCollInRofStrict"); if (doPPAnalysis) { - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(17, "INEL>0"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(18, "INEL>1"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(18, "INEL>0"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(19, "INEL>1"); } else { - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(17, "Below min occup."); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(18, "Above max occup."); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(18, "Below min occup."); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(19, "Above max occup."); } - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(19, "Below min IR"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(20, "Above max IR"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(21, "RCT flags"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(22, "hasRingJet"); - histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(23, "hasRingV0"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(20, "Below min IR"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(21, "Above max IR"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(22, "RCT flags"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(23, "hasRingJet"); + histos.get(HIST("hEventSelection"))->GetXaxis()->SetBinLabel(24, "hasRingV0"); // (notice we lack a hasRingJet AND hasRingV0 bin because the tasks run separately on all events!) // (this QA number can be obtained at derived data level with ease) + // Cache the x-values of the last two stages for convenience: + const int nEvSelBins = histos.get(HIST("hEventSelection"))->GetXaxis()->GetNbins(); + mBinHasRingJet = nEvSelBins - 2; + mBinHasRingV0 = nEvSelBins - 1; - histos.add("Centrality/hEventCentrality", "hEventCentrality", kTH1D, {{101, 0.0f, 101.0f}}); - histos.add("Centrality/hCentralityVsNch", "hCentralityVsNch", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisNch}); + histos.add("Centrality/hEventCentrality", "hEventCentrality;Centrality (%);Counts", kTH1D, {{101, 0.0f, 101.0f}}); + histos.add("Centrality/hCentralityVsNch", "hCentralityVsNch;Centrality (%);N_{ch}", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisNch}); if (doEventQA) { - histos.add("hEventSelectionVsCentrality", "hEventSelectionVsCentrality", kTH2D, {{23, -0.5f, +20.5f}, {101, 0.0f, 101.0f}}); + histos.add("hEventSelectionVsCentrality", "hEventSelectionVsCentrality;;Centrality (%)", kTH2D, {{24, -0.5f, +23.5f}, {101, 0.0f, 101.0f}}); histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(1, "All collisions"); histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(2, "sel8 cut"); histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(3, "kIsTriggerTVX"); histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(4, "kNoITSROFrameBorder"); histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(5, "kNoTimeFrameBorder"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(6, "posZ cut"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(7, "kIsVertexITSTPC"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(8, "kIsGoodZvtxFT0vsPV"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(9, "kIsVertexTOFmatched"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(10, "kIsVertexTRDmatched"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(11, "kNoSameBunchPileup"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(12, "kNoCollInTimeRangeStd"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(13, "kNoCollInTimeRangeStrict"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(14, "kNoCollInTimeRangeNarrow"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(15, "kNoCollInRofStd"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(16, "kNoCollInRofStrict"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(6, "kIsGoodITSLayersAll"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(7, "posZ cut"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(8, "kIsVertexITSTPC"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(9, "kIsGoodZvtxFT0vsPV"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(10, "kIsVertexTOFmatched"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(11, "kIsVertexTRDmatched"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(12, "kNoSameBunchPileup"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(13, "kNoCollInTimeRangeStd"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(14, "kNoCollInTimeRangeStrict"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(15, "kNoCollInTimeRangeNarrow"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(16, "kNoCollInRofStd"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(17, "kNoCollInRofStrict"); if (doPPAnalysis) { - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(17, "INEL>0"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(18, "INEL>1"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(18, "INEL>0"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(19, "INEL>1"); } else { - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(17, "Below min occup."); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(18, "Above max occup."); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(18, "Below min occup."); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(19, "Above max occup."); } - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(19, "Below min IR"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(20, "Above max IR"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(21, "RCT flags"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(22, "hasRingJet"); - histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(23, "hasRingV0"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(20, "Below min IR"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(21, "Above max IR"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(22, "RCT flags"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(23, "hasRingJet"); + histos.get(HIST("hEventSelectionVsCentrality"))->GetXaxis()->SetBinLabel(24, "hasRingV0"); // Centrality: - histos.add("Centrality/hEventCentVsMultFT0M", "hEventCentVsMultFT0M", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFT0M}); - histos.add("Centrality/hEventCentVsMultFT0C", "hEventCentVsMultFT0C", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFT0C}); - histos.add("Centrality/hEventCentVsMultFV0A", "hEventCentVsMultFV0A", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFV0A}); - histos.add("Centrality/hEventMultFT0CvsMultFV0A", "hEventMultFT0CvsMultFV0A", kTH2D, {axisConfigurations.axisMultFT0C, axisConfigurations.axisMultFV0A}); + histos.add("Centrality/hEventCentVsMultFT0M", "hEventCentVsMultFT0M;Centrality (%);Mult. FT0M", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFT0M}); + histos.add("Centrality/hEventCentVsMultFT0C", "hEventCentVsMultFT0C;Centrality (%);Mult. FT0C", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFT0C}); + histos.add("Centrality/hEventCentVsMultFV0A", "hEventCentVsMultFV0A;Centrality (%);Mult. FV0A", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisMultFV0A}); + histos.add("Centrality/hEventMultFT0CvsMultFV0A", "hEventMultFT0CvsMultFV0A;Mult. FT0C;Mult. FV0A", kTH2D, {axisConfigurations.axisMultFT0C, axisConfigurations.axisMultFV0A}); } - histos.add("hEventPVz", "hEventPVz", kTH1D, {{100, -20.0f, +20.0f}}); - histos.add("hCentralityVsPVz", "hCentralityVsPVz", kTH2D, {{101, 0.0f, 101.0f}, {100, -20.0f, +20.0f}}); + histos.add("hEventPVz", "hEventPVz;PV_{z} [cm];Counts", kTH1D, {axisConfigurations.axisPVz}); + histos.add("hCentralityVsPVz", "hCentralityVsPVz;Centrality (%);PV_{z} [cm]", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisPVz}); // (TODO: add MC centrality vs PVz histos) - histos.add("hEventOccupancy", "hEventOccupancy", kTH1D, {axisConfigurations.axisOccupancy}); - histos.add("hCentralityVsOccupancy", "hCentralityVsOccupancy", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisOccupancy}); - histos.add("hInteractionRate", "hInteractionRate", kTH1D, {axisConfigurations.axisIRBinning}); - histos.add("hCentralityVsInteractionRate", "hCentralityVsInteractionRate", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisIRBinning}); - histos.add("hInteractionRateVsOccupancy", "hInteractionRateVsOccupancy", kTH2D, {axisConfigurations.axisIRBinning, axisConfigurations.axisOccupancy}); + histos.add("hEventOccupancy", "hEventOccupancy;Occupancy;Counts", kTH1D, {axisConfigurations.axisOccupancy}); + histos.add("hCentralityVsOccupancy", "hCentralityVsOccupancy;Centrality (%);Occupancy", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisOccupancy}); + histos.add("hInteractionRate", "hInteractionRate;Interaction Rate;Counts", kTH1D, {axisConfigurations.axisIRBinning}); + histos.add("hCentralityVsInteractionRate", "hCentralityVsInteractionRate;Centrality (%);Interaction Rate", kTH2D, {{101, 0.0f, 101.0f}, axisConfigurations.axisIRBinning}); + histos.add("hInteractionRateVsOccupancy", "hInteractionRateVsOccupancy;Interaction Rate;Occupancy", kTH2D, {axisConfigurations.axisIRBinning, axisConfigurations.axisOccupancy}); // for QA and test purposes // auto hRawCentrality = histos.add("Centrality/hRawCentrality", "hRawCentrality", kTH1D, {axisConfigurations.axisRawCentrality}); @@ -625,7 +648,9 @@ struct lambdajetpolarizationions { {p + "TOF #Delta t #pi", v0Selections.maxDeltaTimePion < 1e+9}, {p + "TOF PID p", v0Selections.tofPidNsigmaCutLaPr < 1e+6}, {p + "TOF PID #pi", v0Selections.tofPidNsigmaCutLaPi < 1e+6}, - {p + "c#tau", v0Selections.lambdaLifetimeCut > 0}}); + {p + "c#tau", v0Selections.lambdaLifetimeCut > 0}, + {p + "Ambiguous rejection", true}, + {p + "Final accepted", true}}); }; constexpr bool Lambda = true; // Some constexpr to make it more readable (works at compile level) constexpr bool AntiLambda = false; // "false" is just a flag for this addHypothesis function! It just means fill "AntiLambda" labels @@ -640,12 +665,35 @@ struct lambdajetpolarizationions { lbl = "#color[16]{(off) " + lbl + "}"; hSelectionV0s->GetXaxis()->SetBinLabel(i + 1, lbl.c_str()); // First non-underflow bin is bin 1 } + + // TH2D: "selection flow" vs "Lambda invariant mass" (to investigate whether we are removing background or just signal) + auto h2dSelectionLambdaMass = histos.add("GeneralQA/h2dSelectionLambdaMass", "V0 selection flow vs M_{#Lambda}; ; M_{#Lambda} (GeV/#it{c}^{2})", kTH2D, + {{static_cast(v0LambdaSelectionLabels.size()), -0.5, static_cast(v0LambdaSelectionLabels.size()) - 0.5}, + axisConfigurations.axisLambdaMassSigExtract}); + for (size_t i = 0; i < v0LambdaSelectionLabels.size(); ++i) { + auto lbl = v0LambdaSelectionLabels[i].label; + if (!v0LambdaSelectionLabels[i].enabled) + lbl = "#color[16]{(off) " + lbl + "}"; + h2dSelectionLambdaMass->GetXaxis()->SetBinLabel(i + 1, lbl.c_str()); + } + + // Same for AntiLambda mass hypothesis: + auto h2dSelectionAntiLambdaMass = histos.add("GeneralQA/h2dSelectionAntiLambdaMass", "V0 selection flow vs M_{#bar{#Lambda}}; ; M_{#bar{#Lambda}} (GeV/#it{c}^{2})", kTH2D, + {{static_cast(v0LambdaSelectionLabels.size()), -0.5, static_cast(v0LambdaSelectionLabels.size()) - 0.5}, + axisConfigurations.axisLambdaMassSigExtract}); + for (size_t i = 0; i < v0LambdaSelectionLabels.size(); ++i) { + auto lbl = v0LambdaSelectionLabels[i].label; + if (!v0LambdaSelectionLabels[i].enabled) + lbl = "#color[16]{(off) " + lbl + "}"; + h2dSelectionAntiLambdaMass->GetXaxis()->SetBinLabel(i + 1, lbl.c_str()); + } //////////////////////////////////////////////// // Jet track candidate selection flow (analogous to hSelectionV0s): // Each label's "enabled" flag reflects whether the corresponding configurable // makes that cut active, so disabled stages are shown in grey in the output. std::vector jetTrackSelectionLabels = { {"All track candidates", true}, + {"forceNoITS", pseudoJetCandidateTrackSelections.forceNoITS}, {"ITS clusters (min)", pseudoJetCandidateTrackSelections.minITSnCls >= 0}, {"TPC crossed rows (min)", pseudoJetCandidateTrackSelections.minNCrossedRowsTPC > 0}, {"TPC #chi^{2}/N_{cls} (max)", pseudoJetCandidateTrackSelections.maxChi2TPC < 1.e8f}, @@ -667,257 +715,252 @@ struct lambdajetpolarizationions { // Histograms versus mass: if (analyseLambda) { - histos.add("Lambda/h2dNbrOfLambdaVsCentrality", "h2dNbrOfLambdaVsCentrality", kTH2D, {axisConfigurations.axisCentrality, {10, -0.5f, 9.5f}}); - histos.add("Lambda/h3dMassLambda", "h3dMassLambda", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); - // Non-UPC info - histos.add("Lambda/h3dMassLambdaHadronic", "h3dMassLambdaHadronic", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + histos.add("Lambda/h2dNbrOfLambdaVsCentrality", "h2dNbrOfLambdaVsCentrality;Centrality (%);N_{#Lambda} per event", kTH2D, {axisConfigurations.axisCentrality, {10, -0.5f, 9.5f}}); + histos.add("Lambda/h3dMassLambda", "h3dMassLambda;Centrality (%);p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + // // Non-UPC info + // histos.add("Lambda/h3dMassLambdaHadronic", "h3dMassLambdaHadronic;Centrality (%);p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); if (doTPCQA) { - histos.add("Lambda/h3dPosNsigmaTPC", "h3dPosNsigmaTPC", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dNegNsigmaTPC", "h3dNegNsigmaTPC", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dPosTPCsignal", "h3dPosTPCsignal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("Lambda/h3dNegTPCsignal", "h3dNegTPCsignal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("Lambda/h3dPosNsigmaTPCvsTrackPtot", "h3dPosNsigmaTPCvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dNegNsigmaTPCvsTrackPtot", "h3dNegNsigmaTPCvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dPosTPCsignalVsTrackPtot", "h3dPosTPCsignalVsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("Lambda/h3dNegTPCsignalVsTrackPtot", "h3dNegTPCsignalVsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("Lambda/h3dPosNsigmaTPCvsTrackPt", "h3dPosNsigmaTPCvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dNegNsigmaTPCvsTrackPt", "h3dNegNsigmaTPCvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("Lambda/h3dPosTPCsignalVsTrackPt", "h3dPosTPCsignalVsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("Lambda/h3dNegTPCsignalVsTrackPt", "h3dNegTPCsignalVsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dPosNsigmaTPC", "h3dPosNsigmaTPC;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dNegNsigmaTPC", "h3dNegNsigmaTPC;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dPosTPCsignal", "h3dPosTPCsignal;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dNegTPCsignal", "h3dNegTPCsignal;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dPosNsigmaTPCvsTrackPtot", "h3dPosNsigmaTPCvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dNegNsigmaTPCvsTrackPtot", "h3dNegNsigmaTPCvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dPosTPCsignalVsTrackPtot", "h3dPosTPCsignalVsTrackPtot;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dNegTPCsignalVsTrackPtot", "h3dNegTPCsignalVsTrackPtot;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dPosNsigmaTPCvsTrackPt", "h3dPosNsigmaTPCvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dNegNsigmaTPCvsTrackPt", "h3dNegNsigmaTPCvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("Lambda/h3dPosTPCsignalVsTrackPt", "h3dPosTPCsignalVsTrackPt;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("Lambda/h3dNegTPCsignalVsTrackPt", "h3dNegTPCsignalVsTrackPt;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); } if (doTOFQA) { - histos.add("Lambda/h3dPosNsigmaTOF", "h3dPosNsigmaTOF", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dNegNsigmaTOF", "h3dNegNsigmaTOF", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dPosTOFdeltaT", "h3dPosTOFdeltaT", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("Lambda/h3dNegTOFdeltaT", "h3dNegTOFdeltaT", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("Lambda/h3dPosNsigmaTOFvsTrackPtot", "h3dPosNsigmaTOFvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dNegNsigmaTOFvsTrackPtot", "h3dNegNsigmaTOFvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dPosTOFdeltaTvsTrackPtot", "h3dPosTOFdeltaTvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("Lambda/h3dNegTOFdeltaTvsTrackPtot", "h3dNegTOFdeltaTvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("Lambda/h3dPosNsigmaTOFvsTrackPt", "h3dPosNsigmaTOFvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dNegNsigmaTOFvsTrackPt", "h3dNegNsigmaTOFvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("Lambda/h3dPosTOFdeltaTvsTrackPt", "h3dPosTOFdeltaTvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("Lambda/h3dNegTOFdeltaTvsTrackPt", "h3dNegTOFdeltaTvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dPosNsigmaTOF", "h3dPosNsigmaTOF;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dNegNsigmaTOF", "h3dNegNsigmaTOF;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dPosTOFdeltaT", "h3dPosTOFdeltaT;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dNegTOFdeltaT", "h3dNegTOFdeltaT;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dPosNsigmaTOFvsTrackPtot", "h3dPosNsigmaTOFvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dNegNsigmaTOFvsTrackPtot", "h3dNegNsigmaTOFvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dPosTOFdeltaTvsTrackPtot", "h3dPosTOFdeltaTvsTrackPtot;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dNegTOFdeltaTvsTrackPtot", "h3dNegTOFdeltaTvsTrackPtot;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dPosNsigmaTOFvsTrackPt", "h3dPosNsigmaTOFvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dNegNsigmaTOFvsTrackPt", "h3dNegNsigmaTOFvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("Lambda/h3dPosTOFdeltaTvsTrackPt", "h3dPosTOFdeltaTvsTrackPt;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("Lambda/h3dNegTOFdeltaTvsTrackPt", "h3dNegTOFdeltaTvsTrackPt;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); } // (TODO: add collision association capabilities in MC) - if (doEtaPhiQA) { - histos.add("Lambda/h5dV0PhiVsEta", "h5dV0PhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); - histos.add("Lambda/h5dPosPhiVsEta", "h5dPosPhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); - histos.add("Lambda/h5dNegPhiVsEta", "h5dNegPhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); - } } if (analyseAntiLambda) { - histos.add("AntiLambda/h2dNbrOfAntiLambdaVsCentrality", "h2dNbrOfAntiLambdaVsCentrality", kTH2D, {axisConfigurations.axisCentrality, {10, -0.5f, 9.5f}}); - histos.add("AntiLambda/h3dMassAntiLambda", "h3dMassAntiLambda", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); - // Non-UPC info - histos.add("AntiLambda/h3dMassAntiLambdaHadronic", "h3dMassAntiLambdaHadronic", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + histos.add("AntiLambda/h2dNbrOfAntiLambdaVsCentrality", "h2dNbrOfAntiLambdaVsCentrality;Centrality (%);N_{#bar{#Lambda}} per event", kTH2D, {axisConfigurations.axisCentrality, {10, -0.5f, 9.5f}}); + histos.add("AntiLambda/h3dMassAntiLambda", "h3dMassAntiLambda;Centrality (%);p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + // // Non-UPC info + // histos.add("AntiLambda/h3dMassAntiLambdaHadronic", "h3dMassAntiLambdaHadronic;Centrality (%);p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); if (doTPCQA) { - histos.add("AntiLambda/h3dPosNsigmaTPC", "h3dPosNsigmaTPC", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dNegNsigmaTPC", "h3dNegNsigmaTPC", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dPosTPCsignal", "h3dPosTPCsignal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("AntiLambda/h3dNegTPCsignal", "h3dNegTPCsignal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("AntiLambda/h3dPosNsigmaTPCvsTrackPtot", "h3dPosNsigmaTPCvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dNegNsigmaTPCvsTrackPtot", "h3dNegNsigmaTPCvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dPosTPCsignalVsTrackPtot", "h3dPosTPCsignalVsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("AntiLambda/h3dNegTPCsignalVsTrackPtot", "h3dNegTPCsignalVsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("AntiLambda/h3dPosNsigmaTPCvsTrackPt", "h3dPosNsigmaTPCvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dNegNsigmaTPCvsTrackPt", "h3dNegNsigmaTPCvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); - histos.add("AntiLambda/h3dPosTPCsignalVsTrackPt", "h3dPosTPCsignalVsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); - histos.add("AntiLambda/h3dNegTPCsignalVsTrackPt", "h3dNegTPCsignalVsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dPosNsigmaTPC", "h3dPosNsigmaTPC;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dNegNsigmaTPC", "h3dNegNsigmaTPC;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dPosTPCsignal", "h3dPosTPCsignal;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dNegTPCsignal", "h3dNegTPCsignal;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dPosNsigmaTPCvsTrackPtot", "h3dPosNsigmaTPCvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dNegNsigmaTPCvsTrackPtot", "h3dNegNsigmaTPCvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dPosTPCsignalVsTrackPtot", "h3dPosTPCsignalVsTrackPtot;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dNegTPCsignalVsTrackPtot", "h3dNegTPCsignalVsTrackPtot;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dPosNsigmaTPCvsTrackPt", "h3dPosNsigmaTPCvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dNegNsigmaTPCvsTrackPt", "h3dNegNsigmaTPCvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TPC}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTPC}); + histos.add("AntiLambda/h3dPosTPCsignalVsTrackPt", "h3dPosTPCsignalVsTrackPt;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); + histos.add("AntiLambda/h3dNegTPCsignalVsTrackPt", "h3dNegTPCsignalVsTrackPt;Centrality (%);p_{T} [GeV/c];TPC signal", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTPCsignal}); } if (doTOFQA) { - histos.add("AntiLambda/h3dPosNsigmaTOF", "h3dPosNsigmaTOF", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dNegNsigmaTOF", "h3dNegNsigmaTOF", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dPosTOFdeltaT", "h3dPosTOFdeltaT", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("AntiLambda/h3dNegTOFdeltaT", "h3dNegTOFdeltaT", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("AntiLambda/h3dPosNsigmaTOFvsTrackPtot", "h3dPosNsigmaTOFvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dNegNsigmaTOFvsTrackPtot", "h3dNegNsigmaTOFvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dPosTOFdeltaTvsTrackPtot", "h3dPosTOFdeltaTvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("AntiLambda/h3dNegTOFdeltaTvsTrackPtot", "h3dNegTOFdeltaTvsTrackPtot", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("AntiLambda/h3dPosNsigmaTOFvsTrackPt", "h3dPosNsigmaTOFvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dNegNsigmaTOFvsTrackPt", "h3dNegNsigmaTOFvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); - histos.add("AntiLambda/h3dPosTOFdeltaTvsTrackPt", "h3dPosTOFdeltaTvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - histos.add("AntiLambda/h3dNegTOFdeltaTvsTrackPt", "h3dNegTOFdeltaTvsTrackPt", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); - } - if (doEtaPhiQA) { - histos.add("AntiLambda/h5dV0PhiVsEta", "h5dV0PhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); - histos.add("AntiLambda/h5dPosPhiVsEta", "h5dPosPhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); - histos.add("AntiLambda/h5dNegPhiVsEta", "h5dNegPhiVsEta", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPhi, axisConfigurations.axisEta}); + histos.add("AntiLambda/h3dPosNsigmaTOF", "h3dPosNsigmaTOF;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dNegNsigmaTOF", "h3dNegNsigmaTOF;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dPosTOFdeltaT", "h3dPosTOFdeltaT;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("AntiLambda/h3dNegTOFdeltaT", "h3dNegTOFdeltaT;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("AntiLambda/h3dPosNsigmaTOFvsTrackPtot", "h3dPosNsigmaTOFvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dNegNsigmaTOFvsTrackPtot", "h3dNegNsigmaTOFvsTrackPtot;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dPosTOFdeltaTvsTrackPtot", "h3dPosTOFdeltaTvsTrackPtot;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("AntiLambda/h3dNegTOFdeltaTvsTrackPtot", "h3dNegTOFdeltaTvsTrackPtot;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("AntiLambda/h3dPosNsigmaTOFvsTrackPt", "h3dPosNsigmaTOFvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dNegNsigmaTOFvsTrackPt", "h3dNegNsigmaTOFvsTrackPt;Centrality (%);p_{T} [GeV/c];N#sigma_{TOF}", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisNsigmaTOF}); + histos.add("AntiLambda/h3dPosTOFdeltaTvsTrackPt", "h3dPosTOFdeltaTvsTrackPt;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); + histos.add("AntiLambda/h3dNegTOFdeltaTvsTrackPt", "h3dNegTOFdeltaTvsTrackPt;Centrality (%);p_{T} [GeV/c];TOF #Delta t [ps]", kTH3D, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisTOFdeltaT}); } } if (analyseLambda) { - histos.add("hMassLambda", "hMassLambda", kTH1D, {axisConfigurations.axisLambdaMass}); - histos.add("Lambda/hLambdasPerEvent", "hLambdasPerEvent", kTH1D, {{15, 0, 15}}); + histos.add("hMassLambda", "hMassLambda;M(p#pi) [GeV/c];Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add("Lambda/hLambdasPerEvent", "hLambdasPerEvent;N_{#Lambda} per event;Counts", kTH1D, {{15, 0, 15}}); } if (analyseAntiLambda) { - histos.add("hMassAntiLambda", "hMassAntiLambda", kTH1D, {axisConfigurations.axisLambdaMass}); - histos.add("AntiLambda/hAntiLambdasPerEvent", "hAntiLambdasPerEvent", kTH1D, {{15, 0, 15}}); + histos.add("hMassAntiLambda", "hMassAntiLambda;M(p#pi) [GeV/c];Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add("AntiLambda/hAntiLambdasPerEvent", "hAntiLambdasPerEvent;N_{#bar{#Lambda}} per event;Counts", kTH1D, {{15, 0, 15}}); } if (analyseLambda && analyseAntiLambda) { - histos.add("hAmbiguousLambdaCandidates", "hAmbiguousLambdaCandidates", kTH1D, {{1, 0, 1}}); - histos.add("hAmbiguousPerEvent", "hAmbiguousPerEvent", kTH1D, {{15, 0, 15}}); + // histos.add("hAmbiguousLambdaCandidates", "hAmbiguousLambdaCandidates;Integrated counts;Counts", kTH1D, {{1, 0, 1}}); // No longer required: v0 selection flow already considers this + histos.add("hAmbiguousPerEvent", "hAmbiguousPerEvent;N_{ambiguous} per event;Counts", kTH1D, {{15, 0, 15}}); + histos.add("hNonAmbiguousPerEvent", "hNonAmbiguousPerEvent;N_{non-ambiguous} per event;Counts", kTH1D, {{25, 0, 25}}); // To understand the population of correlated Lambda-likes per event + histos.add("hLambdasAndAntiLambdasPerEvent", "hLambdasAndAntiLambdasPerEvent;N_{#Lambda}+N_{#bar{#Lambda}} per event;Counts", kTH1D, {{25, 0, 25}}); // Alternative check that shows how bad is the possibly correlated full population (Ambig+NonAmbig) } // QA histograms if requested if (doV0KinematicQA) { if (analyseLambda) { // --- Basic kinematics --- - histos.add("V0KinematicQA/Lambda/hPt", "Lambda p_{T}", kTH1D, {axisConfigurations.axisPt}); - histos.add("V0KinematicQA/Lambda/hY", "Lambda rapidity", kTH1D, {axisConfigurations.axisRapidity}); - histos.add("V0KinematicQA/Lambda/hPhi", "Lambda #varphi", kTH1D, {axisConfigurations.axisPhi}); + histos.add("V0KinematicQA/Lambda/hPt", "Lambda p_{T};p_{T} [GeV/c];Counts", kTH1D, {axisConfigurations.axisPt}); + histos.add("V0KinematicQA/Lambda/hY", "Lambda rapidity;y;Counts", kTH1D, {axisConfigurations.axisRapidity}); + histos.add("V0KinematicQA/Lambda/hPhi", "Lambda #varphi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); // --- Mass correlations --- - histos.add("V0KinematicQA/Lambda/hMassVsPt", "Lambda mass vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); - histos.add("V0KinematicQA/Lambda/hMassVsY", "Lambda mass vs y", kTH2D, {axisConfigurations.axisRapidity, axisConfigurations.axisLambdaMass}); - histos.add("V0KinematicQA/Lambda/hMassVsPhi", "Lambda mass vs #varphi", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/Lambda/hMassVsPt", "Lambda mass vs p_{T};p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/Lambda/hMassVsY", "Lambda mass vs y;y;M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisRapidity, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/Lambda/hMassVsPhi", "Lambda mass vs #varphi;#varphi;M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisLambdaMass}); // --- Kinematic correlations --- - histos.add("V0KinematicQA/Lambda/hYVsPt", "Lambda y vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisRapidity}); - histos.add("V0KinematicQA/Lambda/hPhiVsPt", "Lambda #varphi vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisPhi}); + histos.add("V0KinematicQA/Lambda/hYVsPt", "Lambda y vs p_{T};p_{T} [GeV/c];y", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisRapidity}); + histos.add("V0KinematicQA/Lambda/hPhiVsPt", "Lambda #varphi vs p_{T};p_{T} [GeV/c];#varphi", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisPhi}); } if (analyseAntiLambda) { // --- Basic kinematics --- - histos.add("V0KinematicQA/AntiLambda/hPt", "AntiLambda p_{T}", kTH1D, {axisConfigurations.axisPt}); - histos.add("V0KinematicQA/AntiLambda/hY", "AntiLambda rapidity", kTH1D, {axisConfigurations.axisRapidity}); - histos.add("V0KinematicQA/AntiLambda/hPhi", "AntiLambda #varphi", kTH1D, {axisConfigurations.axisPhi}); + histos.add("V0KinematicQA/AntiLambda/hPt", "AntiLambda p_{T};p_{T} [GeV/c];Counts", kTH1D, {axisConfigurations.axisPt}); + histos.add("V0KinematicQA/AntiLambda/hY", "AntiLambda rapidity;y;Counts", kTH1D, {axisConfigurations.axisRapidity}); + histos.add("V0KinematicQA/AntiLambda/hPhi", "AntiLambda #varphi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); // --- Mass correlations --- - histos.add("V0KinematicQA/AntiLambda/hMassVsPt", "AntiLambda mass vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); - histos.add("V0KinematicQA/AntiLambda/hMassVsY", "AntiLambda mass vs y", kTH2D, {axisConfigurations.axisRapidity, axisConfigurations.axisLambdaMass}); - histos.add("V0KinematicQA/AntiLambda/hMassVsPhi", "AntiLambda mass vs #varphi", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/AntiLambda/hMassVsPt", "AntiLambda mass vs p_{T};p_{T} [GeV/c];M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/AntiLambda/hMassVsY", "AntiLambda mass vs y;y;M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisRapidity, axisConfigurations.axisLambdaMass}); + histos.add("V0KinematicQA/AntiLambda/hMassVsPhi", "AntiLambda mass vs #varphi;#varphi;M(p#pi) [GeV/c]", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisLambdaMass}); // --- Kinematic correlations --- - histos.add("V0KinematicQA/AntiLambda/hYVsPt", "AntiLambda y vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisRapidity}); - histos.add("V0KinematicQA/AntiLambda/hPhiVsPt", "AntiLambda #varphi vs p_{T}", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisPhi}); + histos.add("V0KinematicQA/AntiLambda/hYVsPt", "AntiLambda y vs p_{T};p_{T} [GeV/c];y", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisRapidity}); + histos.add("V0KinematicQA/AntiLambda/hPhiVsPt", "AntiLambda #varphi vs p_{T};p_{T} [GeV/c];#varphi", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisPhi}); } } - if (doCompleteTopoQA) { - if (analyseLambda) { - histos.add("Lambda/h4dPosDCAToPV", "h4dPosDCAToPV", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAtoPV}); - histos.add("Lambda/h4dNegDCAToPV", "h4dNegDCAToPV", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAtoPV}); - histos.add("Lambda/h4dDCADaughters", "h4dDCADaughters", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAdau}); - histos.add("Lambda/h4dPointingAngle", "h4dPointingAngle", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPointingAngle}); - histos.add("Lambda/h4dV0Radius", "h4dV0Radius", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisV0Radius}); - } - if (analyseAntiLambda) { - histos.add("AntiLambda/h4dPosDCAToPV", "h4dPosDCAToPV", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAtoPV}); - histos.add("AntiLambda/h4dNegDCAToPV", "h4dNegDCAToPV", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAtoPV}); - histos.add("AntiLambda/h4dDCADaughters", "h4dDCADaughters", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisDCAdau}); - histos.add("AntiLambda/h4dPointingAngle", "h4dPointingAngle", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisPointingAngle}); - histos.add("AntiLambda/h4dV0Radius", "h4dV0Radius", kTHnD, {axisConfigurations.axisCentrality, axisConfigurations.axisPtCoarse, axisConfigurations.axisLambdaMass, axisConfigurations.axisV0Radius}); - } - + if (doTopoQA) { // For all received candidates: - histos.add("V0KinematicQA/hPosDCAToPV", "hPosDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("V0KinematicQA/hNegDCAToPV", "hNegDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("V0KinematicQA/hDCADaughters", "hDCADaughters", kTH1D, {axisConfigurations.axisDCAdau}); - histos.add("V0KinematicQA/hPointingAngle", "hPointingAngle", kTH1D, {axisConfigurations.axisPointingAngle}); - histos.add("V0KinematicQA/hV0Radius", "hV0Radius", kTH1D, {axisConfigurations.axisV0Radius}); - histos.add("V0KinematicQA/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("V0KinematicQA/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("V0KinematicQA/h2dPositivePtVsPhi", "h2dPositivePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); - histos.add("V0KinematicQA/h2dNegativePtVsPhi", "h2dNegativePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("V0KinematicQA/hPosDCAToPV", "hPosDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("V0KinematicQA/hNegDCAToPV", "hNegDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("V0KinematicQA/hDCADaughters", "hDCADaughters;DCAdau [cm];Counts", kTH1D, {axisConfigurations.axisDCAdau}); + histos.add("V0KinematicQA/hPointingAngle", "hPointingAngle;Pointing angle [rad];Counts", kTH1D, {axisConfigurations.axisPointingAngle}); + histos.add("V0KinematicQA/hV0Radius", "hV0Radius;V0 radius [cm];Counts", kTH1D, {axisConfigurations.axisV0Radius}); + histos.add("V0KinematicQA/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("V0KinematicQA/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("V0KinematicQA/h2dPositivePtVsPhi", "h2dPositivePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("V0KinematicQA/h2dNegativePtVsPhi", "h2dNegativePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); if (analyseLambda) { - histos.add("Lambda/hPosDCAToPV", "hPosDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("Lambda/hNegDCAToPV", "hNegDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("Lambda/hDCADaughters", "hDCADaughters", kTH1D, {axisConfigurations.axisDCAdau}); - histos.add("Lambda/hPointingAngle", "hPointingAngle", kTH1D, {axisConfigurations.axisPointingAngle}); - histos.add("Lambda/hV0Radius", "hV0Radius", kTH1D, {axisConfigurations.axisV0Radius}); - histos.add("Lambda/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("Lambda/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("Lambda/h2dPositivePtVsPhi", "h2dPositivePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); - histos.add("Lambda/h2dNegativePtVsPhi", "h2dNegativePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("Lambda/hPosDCAToPV", "hPosDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("Lambda/hNegDCAToPV", "hNegDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("Lambda/hDCADaughters", "hDCADaughters;DCAdau [cm];Counts", kTH1D, {axisConfigurations.axisDCAdau}); + histos.add("Lambda/hPointingAngle", "hPointingAngle;Pointing angle [rad];Counts", kTH1D, {axisConfigurations.axisPointingAngle}); + histos.add("Lambda/hV0Radius", "hV0Radius;V0 radius [cm];Counts", kTH1D, {axisConfigurations.axisV0Radius}); + histos.add("Lambda/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("Lambda/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("Lambda/h2dPositivePtVsPhi", "h2dPositivePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("Lambda/h2dNegativePtVsPhi", "h2dNegativePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); } if (analyseAntiLambda) { - histos.add("AntiLambda/hPosDCAToPV", "hPosDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("AntiLambda/hNegDCAToPV", "hNegDCAToPV", kTH1D, {axisConfigurations.axisDCAtoPV}); - histos.add("AntiLambda/hDCADaughters", "hDCADaughters", kTH1D, {axisConfigurations.axisDCAdau}); - histos.add("AntiLambda/hPointingAngle", "hPointingAngle", kTH1D, {axisConfigurations.axisPointingAngle}); - histos.add("AntiLambda/hV0Radius", "hV0Radius", kTH1D, {axisConfigurations.axisV0Radius}); - histos.add("AntiLambda/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("AntiLambda/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); - histos.add("AntiLambda/h2dPositivePtVsPhi", "h2dPositivePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); - histos.add("AntiLambda/h2dNegativePtVsPhi", "h2dNegativePtVsPhi", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("AntiLambda/hPosDCAToPV", "hPosDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("AntiLambda/hNegDCAToPV", "hNegDCAToPV;DCAtoPV [cm];Counts", kTH1D, {axisConfigurations.axisDCAtoPV}); + histos.add("AntiLambda/hDCADaughters", "hDCADaughters;DCAdau [cm];Counts", kTH1D, {axisConfigurations.axisDCAdau}); + histos.add("AntiLambda/hPointingAngle", "hPointingAngle;Pointing angle [rad];Counts", kTH1D, {axisConfigurations.axisPointingAngle}); + histos.add("AntiLambda/hV0Radius", "hV0Radius;V0 radius [cm];Counts", kTH1D, {axisConfigurations.axisV0Radius}); + histos.add("AntiLambda/h2dPositiveITSvsTPCpts", "h2dPositiveITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("AntiLambda/h2dNegativeITSvsTPCpts", "h2dNegativeITSvsTPCpts;N TPC crossed rows;N ITS clusters", kTH2D, {axisConfigurations.axisTPCrows, axisConfigurations.axisITSclus}); + histos.add("AntiLambda/h2dPositivePtVsPhi", "h2dPositivePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); + histos.add("AntiLambda/h2dNegativePtVsPhi", "h2dNegativePtVsPhi;p_{T} [GeV/c];#varphi mod TPC sector", kTH2D, {axisConfigurations.axisPtCoarse, axisConfigurations.axisPhiMod}); } } // Check ambiguous candidates in AP space: - histos.add("GeneralQA/h2dArmenterosAll", "h2dArmenterosAll", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosKinematicSelected", "h2dArmenterosKinematicSelected", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosFullSelected", "h2dArmenterosFullSelected", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosFullSelectedLambda", "h2dArmenterosFullSelectedLambda", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosFullSelectedAntiLambda", "h2dArmenterosFullSelectedAntiLambda", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosFullSelectedNonAmbiguous", "h2dArmenterosFullSelectedNonAmbiguous", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); - histos.add("GeneralQA/h2dArmenterosFullSelectedAmbiguous", "h2dArmenterosFullSelectedAmbiguous", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosAll", "h2dArmenterosAll;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosKinematicSelected", "h2dArmenterosKinematicSelected;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosFullSelected", "h2dArmenterosFullSelected;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosFullSelectedLambda", "h2dArmenterosFullSelectedLambda;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosFullSelectedAntiLambda", "h2dArmenterosFullSelectedAntiLambda;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosFullSelectedNonAmbiguous", "h2dArmenterosFullSelectedNonAmbiguous;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); + histos.add("GeneralQA/h2dArmenterosFullSelectedAmbiguous", "h2dArmenterosFullSelectedAmbiguous;Armenteros #alpha;Armenteros q_{T} (GeV/c)", kTH2D, {axisConfigurations.axisAPAlpha, axisConfigurations.axisAPQt}); // Jets histograms: - // Histogram that needs to be present even out of QA: - histos.add("hEventsWithJet", "hEventsWithJet", kTH1D, {{1, 0, 1}}); - histos.add("hJetsPerEvent", "hJetsPerEvent", kTH1D, {axisConfigurations.JetsPerEvent}); - // counter of events with jet (could be interesting to compare with the minimum pT cut or between the background subtraction vs no background subtraction cases) - // number of jets per event + // Histograms that need to be present even out of extensive QA-mode: + histos.add("hEventsWithJet", "hEventsWithJet;Integrated counts;Counts", kTH1D, {{1, 0, 1}}); // counter of events with jet (could be interesting to compare with the minimum pT cut or between the background subtraction vs no background subtraction cases) + histos.add("hJetsPerEvent", "hJetsPerEvent;Jets per event;Counts", kTH1D, {axisConfigurations.axisJetsPerEvent}); // number of jets per event if (doJetKinematicsQA) { - histos.add("JetKinematicsQA/hJetPt", "hJetPt", kTH1D, {axisConfigurations.axisJetPt}); - histos.add("JetKinematicsQA/hJetEta", "hJetEta", kTH1D, {axisConfigurations.axisEta}); - histos.add("JetKinematicsQA/hJetPhi", "hJetPhi", kTH1D, {axisConfigurations.axisPhi}); - - histos.add("JetKinematicsQA/hCosThetaToLeadingJet", "hCosThetaToLeadingJet", kTH1D, {axisConfigurations.axisCosTheta}); - histos.add("JetKinematicsQA/hDeltaPhiToLeadingJet", "hDeltaPhiToLeadingJet", kTH1D, {axisConfigurations.axisDeltaPhi}); - histos.add("JetKinematicsQA/hDeltaEtaToLeadingJet", "hDeltaEtaToLeadingJet", kTH1D, {axisConfigurations.axisDeltaEta}); - histos.add("JetKinematicsQA/hDeltaRToLeadingJet", "hDeltaRToLeadingJet", kTH1D, {axisConfigurations.axisDeltaR}); - - histos.add("JetKinematicsQA/hLeadingJetPt", "hLeadingJetPt", kTH1D, {axisConfigurations.axisJetPt}); - histos.add("JetKinematicsQA/hLeadingJetEta", "hLeadingJetEta", kTH1D, {axisConfigurations.axisEta}); - histos.add("JetKinematicsQA/hLeadingJetPhi", "hLeadingJetPhi", kTH1D, {axisConfigurations.axisPhi}); + histos.add("JetKinematicsQA/hJetPt", "hJetPt;Jet p_{T} [GeV/c];Counts", kTH1D, {axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/hJetEta", "hJetEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("JetKinematicsQA/hJetPhi", "hJetPhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); + histos.add("JetKinematicsQA/h2dJetEtaVsJetPhi", "h2dJetEtaVsJetPhi;#eta;#varphi", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPhi}); // An occupancy map of sorts. Checks for detector-dependent jet clustering artifacts + + histos.add("JetKinematicsQA/hCosThetaToLeadingJet", "hCosThetaToLeadingJet;cos(#Delta#theta_{jet});Counts", kTH1D, {axisConfigurations.axisCosTheta}); + histos.add("JetKinematicsQA/hDeltaPhiToLeadingJet", "hDeltaPhiToLeadingJet;#Delta#varphi;Counts", kTH1D, {axisConfigurations.axisDeltaPhi}); + histos.add("JetKinematicsQA/hDeltaEtaToLeadingJet", "hDeltaEtaToLeadingJet;#Delta#eta;Counts", kTH1D, {axisConfigurations.axisDeltaEta}); + // Acceptance-edge QA: same three observables, but filled only for the jets rejected by the |eta| < 0.9 - R cut: + // (Those jets lose constituents on one side, so this measures the pT biasing) + histos.add("JetKinematicsQA/hJetEtaOutOfAcceptance", "hJetEtaOutOfAcceptance;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("JetKinematicsQA/hDeltaEtaToLeadingJetOutOfAcceptance", "hDeltaEtaToLeadingJetOutOfAcceptance;#Delta#eta;Counts", kTH1D, {axisConfigurations.axisDeltaEta}); + histos.add("JetKinematicsQA/hDeltaRToLeadingJetOutOfAcceptance", "hDeltaRToLeadingJetOutOfAcceptance;#Delta R;Counts", kTH1D, {axisConfigurations.axisDeltaR}); + + histos.add("JetKinematicsQA/hDeltaRToLeadingJet", "hDeltaRToLeadingJet;#Delta R;Counts", kTH1D, {axisConfigurations.axisDeltaR}); + + histos.add("JetKinematicsQA/hLeadingJetPt", "hLeadingJetPt;Jet p_{T} [GeV/c];Counts", kTH1D, {axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/hLeadingJetEta", "hLeadingJetEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("JetKinematicsQA/hLeadingJetPhi", "hLeadingJetPhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); // 2D correlations: - histos.add("JetKinematicsQA/h2dJetsPerEventvsLeadJetPt", "h2dJetsPerEventvsLeadJetPt", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisJetPt}); - histos.add("JetKinematicsQA/h2dJetsPerEventvsJetPt", "h2dJetsPerEventvsJetPt", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisJetPt}); - histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaPhiToLead", "h2dCosThetaToLeadvsDeltaPhiToLead", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaPhi}); - histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaEtaToLead", "h2dCosThetaToLeadvsDeltaEtaToLead", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaEta}); - histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaRToLead", "h2dCosThetaToLeadvsDeltaRToLead", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaR}); - histos.add("JetKinematicsQA/h2dDeltaPhiToLeadvsDeltaEtaToLead", "h2dDeltaPhiToLeadvsDeltaEtaToLead", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDeltaEta}); // to see existence of back-to-back jets, and in which window + histos.add("JetKinematicsQA/h2dJetsPerEventvsLeadJetPt", "h2dJetsPerEventvsLeadJetPt;Jets per event;Jet p_{T} [GeV/c]", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/h2dJetsPerEventvsJetPt", "h2dJetsPerEventvsJetPt;Jets per event;Jet p_{T} [GeV/c]", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaPhiToLead", "h2dCosThetaToLeadvsDeltaPhiToLead;cos(#Delta#theta_{jet});#Delta#varphi", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaPhi}); + histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaEtaToLead", "h2dCosThetaToLeadvsDeltaEtaToLead;cos(#Delta#theta_{jet});#Delta#eta", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaEta}); + histos.add("JetKinematicsQA/h2dCosThetaToLeadvsDeltaRToLead", "h2dCosThetaToLeadvsDeltaRToLead;cos(#Delta#theta_{jet});#Delta R", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaR}); + histos.add("JetKinematicsQA/h2dDeltaPhiToLeadvsDeltaEtaToLead", "h2dDeltaPhiToLeadvsDeltaEtaToLead;#Delta#varphi;#Delta#eta", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDeltaEta}); // to see existence of back-to-back jets, and in which window // Comparisons to jet energy: - histos.add("JetKinematicsQA/h2dJetPtvsDeltaPhiToLead", "h2dJetPtvsDeltaPhiToLead", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisDeltaPhi}); - histos.add("JetKinematicsQA/h2dJetEnergyvsDeltaPhiToLead", "h2dJetEnergyvsDeltaPhiToLead", kTH2D, {axisConfigurations.axisEnergy, axisConfigurations.axisDeltaPhi}); - histos.add("JetKinematicsQA/h2dJetEnergyvsCosThetaToLead", "h2dJetEnergyvsCosThetaToLead", kTH2D, {axisConfigurations.axisEnergy, axisConfigurations.axisCosTheta}); + histos.add("JetKinematicsQA/h2dJetPtvsDeltaPhiToLead", "h2dJetPtvsDeltaPhiToLead;Jet p_{T} [GeV/c];#Delta#varphi", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisDeltaPhi}); + histos.add("JetKinematicsQA/h2dJetEnergyvsDeltaPhiToLead", "h2dJetEnergyvsDeltaPhiToLead;E_{jet} [GeV];#Delta#varphi", kTH2D, {axisConfigurations.axisEnergy, axisConfigurations.axisDeltaPhi}); + histos.add("JetKinematicsQA/h2dJetEnergyvsCosThetaToLead", "h2dJetEnergyvsCosThetaToLead;E_{jet} [GeV];cos(#Delta#theta_{jet})", kTH2D, {axisConfigurations.axisEnergy, axisConfigurations.axisCosTheta}); // Jets per event vs correlation to lead jet - histos.add("JetKinematicsQA/h2dJetsPerEventvsDeltaPhiToLead", "h2dJetsPerEventvsDeltaPhiToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisDeltaPhi}); - histos.add("JetKinematicsQA/h2dJetsPerEventvsDeltaEtaToLead", "h2dJetsPerEventvsDeltaEtaToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisDeltaEta}); - histos.add("JetKinematicsQA/h2dJetsPerEventvsCosThetaToLead", "h2dJetsPerEventvsCosThetaToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisCosTheta}); + histos.add("JetKinematicsQA/h2dJetsPerEventvsDeltaPhiToLead", "h2dJetsPerEventvsDeltaPhiToLead;Jets per event;#Delta#varphi", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisDeltaPhi}); + histos.add("JetKinematicsQA/h2dJetsPerEventvsDeltaEtaToLead", "h2dJetsPerEventvsDeltaEtaToLead;Jets per event;#Delta#eta", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisDeltaEta}); + histos.add("JetKinematicsQA/h2dJetsPerEventvsCosThetaToLead", "h2dJetsPerEventvsCosThetaToLead;Jets per event;cos(#Delta#theta_{jet})", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisCosTheta}); + + // For probing the correlation between number of jet constituents and centrality: + histos.add("JetKinematicsQA/h2dConstituentsPerJetVsCentrality", "Constituents per Jet Vs Centrality;N constituents;Centrality (%)", kTH2D, {axisConfigurations.axisJetConstituents, axisConfigurations.axisCentrality}); // Not quite kinematics, but kept it under this QAing switch for now + histos.add("JetKinematicsQA/h2dConstituentsPerJetVsJetPt", "Constituents per Jet Vs Jet Pt;N constituents;Jet p_{T} [GeV/c]", kTH2D, {axisConfigurations.axisJetConstituents, axisConfigurations.axisJetPt}); + + // Correlation with PVz: + histos.add("JetKinematicsQA/h2dJetEtaVsPVz", "Jet #eta Vs Primary Vertex Z;Jet #eta;PVz [cm]", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPVzCoarse}); + histos.add("JetKinematicsQA/h2dJetPhiVsPVz", "Jet #phi Vs Primary Vertex Z;Jet #phi;PVz [cm]", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisPVzCoarse}); //////////////////////////// // Leading particle 1D QA: - histos.add("JetVsLeadingParticleQA/hLeadingParticlePt", "hLeadingParticlePt", kTH1D, {axisConfigurations.axisLeadingParticlePt}); - histos.add("JetVsLeadingParticleQA/hLeadingParticleEta", "hLeadingParticleEta", kTH1D, {axisConfigurations.axisEta}); - histos.add("JetVsLeadingParticleQA/hLeadingParticlePhi", "hLeadingParticlePhi", kTH1D, {axisConfigurations.axisPhi}); + histos.add("LeadingParticleQA/hLeadingParticlePt", "hLeadingParticlePt;Leading particle p_{T} [GeV/c];Counts", kTH1D, {axisConfigurations.axisLeadingParticlePt}); + histos.add("LeadingParticleQA/hLeadingParticleEta", "hLeadingParticleEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("LeadingParticleQA/hLeadingParticlePhi", "hLeadingParticlePhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); // 1D correlations to lead jet: - histos.add("JetVsLeadingParticleQA/hCosThetaLeadParticleToJet", "hCosThetaLeadParticleToJet", kTH1D, {axisConfigurations.axisCosTheta}); - histos.add("JetVsLeadingParticleQA/hDeltaPhiLeadParticleToJet", "hDeltaPhiLeadParticleToJet", kTH1D, {axisConfigurations.axisDeltaPhi}); - histos.add("JetVsLeadingParticleQA/hDeltaEtaToLeadParticleToJet", "hDeltaEtaToLeadParticleToJet", kTH1D, {axisConfigurations.axisDeltaEta}); + histos.add("LeadingParticleQA/hCosThetaLeadParticleToJet", "hCosThetaLeadParticleToJet;cos(#Delta#theta_{jet});Counts", kTH1D, {axisConfigurations.axisCosTheta}); + histos.add("LeadingParticleQA/hDeltaPhiLeadParticleToJet", "hDeltaPhiLeadParticleToJet;#Delta#varphi;Counts", kTH1D, {axisConfigurations.axisDeltaPhi}); + histos.add("LeadingParticleQA/hDeltaEtaToLeadParticleToJet", "hDeltaEtaToLeadParticleToJet;#Delta#eta;Counts", kTH1D, {axisConfigurations.axisDeltaEta}); // Leading particle correlations: - histos.add("JetVsLeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead", "h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDeltaEta}); + histos.add("LeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead", "h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead;#Delta#varphi;#Delta#eta", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDeltaEta}); // Jets-per-event vs particle-to-lead correlations: - histos.add("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead", "h2dJetsPerEventvsDeltaPhiParticleToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisDeltaPhi}); - histos.add("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead", "h2dJetsPerEventvsDeltaEtaParticleToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisDeltaEta}); - histos.add("JetVsLeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead", "h2dJetsPerEventvsCosThetaParticleToLead", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisCosTheta}); + histos.add("LeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead", "h2dJetsPerEventvsDeltaPhiParticleToLead;Jets per event;#Delta#varphi", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisDeltaPhi}); + histos.add("LeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead", "h2dJetsPerEventvsDeltaEtaParticleToLead;Jets per event;#Delta#eta", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisDeltaEta}); + histos.add("LeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead", "h2dJetsPerEventvsCosThetaParticleToLead;Jets per event;cos(#Delta#theta_{jet})", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisCosTheta}); // Main "Leading jet vs leading particle" correlations: - histos.add("JetVsLeadingParticleQA/h2dJetsPerEventvsLeadParticlePt", "h2dJetsPerEventvsLeadParticlePt", kTH2D, {axisConfigurations.JetsPerEvent, axisConfigurations.axisLeadingParticlePt}); - histos.add("JetVsLeadingParticleQA/h2dLeadJetPtvsLeadParticlePt", "h2dLeadJetPtvsLeadParticlePt", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisLeadingParticlePt}); - histos.add("JetVsLeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead", "h2dLeadJetPtvsCosThetaParticleToLead", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisCosTheta}); - histos.add("JetVsLeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead", "h2dLeadParticlePtvsCosThetaParticleToLead", kTH2D, {axisConfigurations.axisLeadingParticlePt, axisConfigurations.axisCosTheta}); - histos.add("JetVsLeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead", "h2dLeadJetPtvsDeltaPhiParticleToLead", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisDeltaPhi}); - histos.add("JetVsLeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead", "h2dLeadParticlePtvsDeltaPhiParticleToLead", kTH2D, {axisConfigurations.axisLeadingParticlePt, axisConfigurations.axisDeltaPhi}); + histos.add("LeadingParticleQA/h2dJetsPerEventvsLeadParticlePt", "h2dJetsPerEventvsLeadParticlePt;Jets per event;Leading particle p_{T} [GeV/c]", kTH2D, {axisConfigurations.axisJetsPerEvent, axisConfigurations.axisLeadingParticlePt}); + histos.add("LeadingParticleQA/h2dLeadJetPtvsLeadParticlePt", "h2dLeadJetPtvsLeadParticlePt;Jet p_{T} [GeV/c];Leading particle p_{T} [GeV/c]", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisLeadingParticlePt}); + histos.add("LeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead", "h2dLeadJetPtvsCosThetaParticleToLead;Jet p_{T} [GeV/c];cos(#Delta#theta_{jet})", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisCosTheta}); + histos.add("LeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead", "h2dLeadParticlePtvsCosThetaParticleToLead;Leading particle p_{T} [GeV/c];cos(#Delta#theta_{jet})", kTH2D, {axisConfigurations.axisLeadingParticlePt, axisConfigurations.axisCosTheta}); + histos.add("LeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead", "h2dLeadJetPtvsDeltaPhiParticleToLead;Jet p_{T} [GeV/c];#Delta#varphi", kTH2D, {axisConfigurations.axisJetPt, axisConfigurations.axisDeltaPhi}); + histos.add("LeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead", "h2dLeadParticlePtvsDeltaPhiParticleToLead;Leading particle p_{T} [GeV/c];#Delta#varphi", kTH2D, {axisConfigurations.axisLeadingParticlePt, axisConfigurations.axisDeltaPhi}); + + // Correlation with PVz: + histos.add("LeadingParticleQA/h2dLeadPEtaVsPVz", "LeadPtc #eta Vs Primary Vertex Z;LeadPtc #eta;PVz [cm]", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPVzCoarse}); + histos.add("LeadingParticleQA/h2dLeadPPhiVsPVz", "LeadPtc #phi Vs Primary Vertex Z;LeadPtc #phi;PVz [cm]", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisPVzCoarse}); } // inspect histogram sizes, please histos.print(); } + /// \brief Returns the collision centrality for the configured estimator (FT0M/FT0C/FV0A). Returns -1 if none matched. template auto getCentrality(TCollision const& collision) { @@ -930,6 +973,7 @@ struct lambdajetpolarizationions { return -1.f; } + /// \brief Fetches the magnetic field for the current run from CCDB (or uses customMagField, if set). No-op if already initialized for this run. template void initCCDB(TBC const& bc) { @@ -960,28 +1004,38 @@ struct lambdajetpolarizationions { int binValue = -1; // Starts at x=-1, which will go to bin 0 (underflow) in the definition of hSelectionV0s // Made it like this because we use ++binValue when filling, so the first filled // bin will always be x=0 due to operator precedence. - HistogramRegistry* histos = nullptr; // Had to pass the histos group to this struct, as it was not visible to the members of this struct + HistogramRegistry* histos = nullptr; // Had to pass the histos group to this struct, as it was not visible to the members of the struct + float mL = -1.f; // mLambda for current V0, -1 at initialization + float mAL = -1.f; // mAntiLambda for current V0, -1 at initialization + void resetForNewV0(float massLambda, float massAntiLambda) + { + binValue = -1; + mL = massLambda; + mAL = massAntiLambda; + } + void advanceTo(int targetBinX) { binValue = targetBinX - 1; } // next fill() lands at targetBin. Needed to deal with early exits at isLambda vs isAntiLambda checks - void resetForNewV0() { binValue = -1; } - // Advance to targetBinX, filling all intermediate bins. + void fill() + { + histos->fill(HIST("GeneralQA/hSelectionV0s"), ++binValue); // Hardcoded hSelectionV0s histogram, as it will not change. Increments before filling, by default + histos->fill(HIST("GeneralQA/h2dSelectionLambdaMass"), binValue, mL); + histos->fill(HIST("GeneralQA/h2dSelectionAntiLambdaMass"), binValue, mAL); + } // Use this for DISABLED cuts within a single hypothesis - // (shows pass-through count as a flat line, making it visually - // clear that the stage was not active). - // (Replaces N dummy fill() calls) + // (shows pass-through count as a flat line, making it visually clear that the stage was not active. Replaces N dummy fill() calls) + // fillUpTo advances through disabled bins, filling all three histograms uniformly. void fillUpTo(int targetBinX) { while (binValue < targetBinX) - histos->fill(HIST("GeneralQA/hSelectionV0s"), ++binValue); + fill(); } - - void advanceTo(int targetBinX) { binValue = targetBinX - 1; } // next fill() lands at targetBin. Needed to deal with early exits at isLambda vs isAntiLambda checks - void fill() { histos->fill(HIST("GeneralQA/hSelectionV0s"), ++binValue); } // Hardcoded hSelectionV0s histogram, as it will not change. Increments before filling, by default }; - V0SelectionFlowCounter V0SelCounter{-1, &histos}; // Could initialize with any index (resetForNewV0 is always called for a new V0 anyways) - // Calculating some bins, for convenience: - int nGenericCuts = 31; // x=0 to x=30 - int nHypoCuts = 9; // per hypothesis (x=31..39 for Lambda) - int lambdaHypoEnd = nGenericCuts + nHypoCuts - 1; // x=39 + V0SelectionFlowCounter v0SelCounter{-1, &histos}; // Could initialize with any index (resetForNewV0 is always called for a new V0 anyways) + // Calculating some bins, for convenience: + const int nGenericCuts = 31; // x=0 to x=30 (bins 1 and 31, as the lower-edge is inclusive) + const int nHypoCuts = 11; // per hypothesis (x=31..41 for Lambda) + const int lambdaHypoEnd = nGenericCuts + nHypoCuts - 1; // x=41 (bin 42) + const int antiLambdaHypoEnd = nGenericCuts + 2 * nHypoCuts - 1; // x = 52 (bin 53) // Minimal helper to fill hSelectionJetTracks, mirroring V0SelectionFlowCounter. // Reset once per track candidate, fill once per passed cut stage. @@ -991,7 +1045,7 @@ struct lambdajetpolarizationions { void resetForNewTrack() { binValue = -1; } void fill() { histos->fill(HIST("GeneralQA/hSelectionJetTracks"), ++binValue); } }; - JetTrackSelectionFlowCounter JetTrackSelCounter{-1, &histos}; + JetTrackSelectionFlowCounter jetTrackSelCounter{-1, &histos}; // Short inlined helper to simplify QA inline void fillEventSelectionQA(int bin, float centrality) @@ -1017,11 +1071,12 @@ struct lambdajetpolarizationions { histos.fill(HIST("Centrality/hEventCentVsMultFV0A"), collision.centFV0A(), collision.multFV0A()); histos.fill(HIST("Centrality/hEventMultFT0CvsMultFV0A"), collision.multFT0C(), collision.multFV0A()); } - return; } ///////////////////////////////////////////// // Computation helper functions: + /// \brief Folds phi into the repeating ~20-degree TPC sector pattern used by the sector-boundary cut functions below. + /// \param sign track charge sign (+1 or -1) -- the fold direction depends on charge and field polarity. double computePhiMod(double phi, int sign) // Compute phi wrt to a TPC sector // Calculation taken from CF: https://github.com/AliceO2Group/O2Physics/blob/376392cb87349886a300c75fa2492b50b7f46725/PWGCF/Flow/Tasks/flowAnalysisGF.cxx#L470 @@ -1037,6 +1092,8 @@ struct lambdajetpolarizationions { return fmod(phi, o2::constants::math::PI / 9.0); } + /// \brief Checks the track against the TPC sector-boundary cut (see computePhiMod()). + /// \return true if the track is far enough from the boundary to be kept; false rejects it. bool isTrackFarFromTPCBoundary(double trackPt, double trackPhi, int sign) // check whether the track passes close to a TPC sector boundary { @@ -1057,10 +1114,19 @@ struct lambdajetpolarizationions { return dot / (magA * magB); } + const float nSigmaScale = 100.f; // The factor for packing the float as an int16_t + inline int16_t packNSigma(float x) + { + return static_cast(std::lround(x * nSigmaScale)); + } + ///////////////////////////////////////////// // Helper functions for event and candidate selection: + /// \brief Runs the full event-selection cutflow. returns false at the first failed cut (in configured order). + /// \param interactionRate output-only: filled with the fetched IR. + /// \param fillHists if true, also fills the per-stage QA histograms and the occupancy/IR/vertex histograms. template - bool isEventAccepted(TCollision const& collision, TBC const& bc, float centrality, bool fillHists) + bool isEventAccepted(TCollision const& collision, TBC const& bc, float centrality, double& interactionRate, bool fillHists) { // check whether the collision passes our collision selections int selectionIdx = 0; // To loop over QA histograms. First bin is already filled: first call will already increment this index (not actually the bin index, but a value in the X axis). if (eventSelections.requireSel8 && !collision.sel8()) @@ -1079,6 +1145,10 @@ struct lambdajetpolarizationions { return false; if (fillHists) fillEventSelectionQA(++selectionIdx, centrality); + if (eventSelections.requireGoodITSLayersAll && !collision.selection_bit(o2::aod::evsel::kIsGoodITSLayersAll)) + return false; + if (fillHists) + fillEventSelectionQA(++selectionIdx, centrality); const float collisionPVz = collision.posZ(); if (std::abs(collisionPVz) > eventSelections.maxZVtxPosition) @@ -1155,8 +1225,11 @@ struct lambdajetpolarizationions { if (fillHists) fillEventSelectionQA(++selectionIdx, centrality); - // Fetch interaction rate only if required (in order to limit ccdb calls) - const double interactionRate = (eventSelections.minIR >= 0 || eventSelections.maxIR >= 0) ? rateFetcher.fetch(ccdb.service, bc.timestamp(), bc.runNumber(), irSource) * 1.e-3 : -1; + // Fetch interaction rate after all inexpensive cuts were performed: + interactionRate = rateFetcher.fetch(ccdb.service, bc.timestamp(), bc.runNumber(), irSource) * 1.e-3; + // Optionally, do this only when required by the minIR/maxIR cuts, to further limit ccdb calls: + // const double interactionRate = (eventSelections.minIR >= 0 || eventSelections.maxIR >= 0) ? rateFetcher.fetch(ccdb.service, bc.timestamp(), bc.runNumber(), irSource) * 1.e-3 : -1; + if (eventSelections.minIR >= 0 && interactionRate < eventSelections.minIR) return false; if (fillHists) @@ -1167,11 +1240,10 @@ struct lambdajetpolarizationions { fillEventSelectionQA(++selectionIdx, centrality); if (!rctConfigurations.cfgRCTLabel.value.empty() && !rctFlagsChecker(collision)) return false; - if (fillHists) + if (fillHists) { fillEventSelectionQA(++selectionIdx, centrality); - // Filling histograms previously filled in fillReconstructedEventProperties here, to avoid re-accessing data: - if (fillHists) { + // Filling histograms previously filled in fillReconstructedEventProperties here, to avoid re-accessing data: histos.fill(HIST("hEventOccupancy"), collisionOccupancy); histos.fill(HIST("hCentralityVsOccupancy"), centrality, collisionOccupancy); histos.fill(HIST("hInteractionRate"), interactionRate); @@ -1187,87 +1259,94 @@ struct lambdajetpolarizationions { return true; } + /// \brief Track-level cutflow gate for FastJet pseudojet candidates (quality + kinematics + optional DCA cuts). + /// \note Mirrors isEventAccepted()'s early-exit pattern, but for tracks. template bool isCandidateForChargedPseudojetAccepted(JetCandidate const& track) { // (TODO: add an equivalent for photon jets and Z jets, which don't consider charged particles) // if (track.sign() == 0) return false; // Tracks are always either positive or negative, at least in TPC and ITS, which are the ones used (not looking at photon-jets right now) // ITS/TPC cuts: + if (pseudoJetCandidateTrackSelections.forceNoITS) { // reject tracks that have ITS bits (no need to reject hasTPC as we always demand minNCrossedRowsTPC > 70 in all analyses) + const auto detMap = track.detectorMap(); + if (detMap & o2::aod::track::ITS) + return false; + } + jetTrackSelCounter.fill(); // bin: forceNoITS + if (pseudoJetCandidateTrackSelections.minITSnCls >= 0) { if (track.itsNCls() < pseudoJetCandidateTrackSelections.minITSnCls) return false; } - JetTrackSelCounter.fill(); // bin: ITS clusters (min) + jetTrackSelCounter.fill(); // bin: ITS clusters (min) if (track.tpcNClsCrossedRows() < pseudoJetCandidateTrackSelections.minNCrossedRowsTPC) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); if (track.tpcChi2NCl() > pseudoJetCandidateTrackSelections.maxChi2TPC) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); if (track.itsChi2NCl() > pseudoJetCandidateTrackSelections.maxChi2ITS) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); // Kinematics: const float pt = track.pt(); if (pt < pseudoJetCandidateTrackSelections.minCandidatePt) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); if (std::fabs(track.eta()) > pseudoJetCandidateTrackSelections.etaCut) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); // DCA pseudojet candidate selections -- These select primary vertex particles for the jet: if (pseudoJetCandidateTrackSelections.doDCAcuts) { // if (std::fabs(track.dcaXY()) > pseudoJetCandidateTrackSelections.maxDCAxy) return false; if (std::fabs(track.dcaZ()) > pseudoJetCandidateTrackSelections.maxDCAz) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); // Slightly more physics-motivated cut (parametrizes the DCA resolution as function of pt) if (std::fabs(track.dcaXY()) > (pseudoJetCandidateTrackSelections.dcaxyMaxTrackPar0 + pseudoJetCandidateTrackSelections.dcaxyMaxTrackPar1 / std::pow(pt, pseudoJetCandidateTrackSelections.dcaxyMaxTrackPar2))) return false; - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); } else { // Should fill counters an equal number of times to advance indices (future-proofing, but could do it by just advancing indices by hand in JetTrackSelectionFlowCounter) - JetTrackSelCounter.fill(); - JetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); + jetTrackSelCounter.fill(); } return true; } // Lambda selections: - template - bool passesGenericV0Cuts(TV0 const& v0) + /// \brief Charge-independent V0 cuts (topology, ITS/TPC track quality) shared by both the Lambda and AntiLambda hypotheses. + /// \param posTrackExtra,negTrackExtra the V0's daughter tracks, cached once at the process function. + /// \note See passesLambdaLambdaBarHypothesis() for the hypothesis-specific cuts. + template + bool passesGenericV0Cuts(TV0 const& v0, TTrack const& posTrackExtra, TTrack const& negTrackExtra) { // Base topological variables (high rejection, low cost checks) if (v0.v0radius() < v0Selections.v0radius) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0.v0radius() > v0Selections.v0radiusMax) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0.v0cosPA() < v0Selections.v0cospa) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0.dcaV0daughters() > v0Selections.dcav0dau) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); - // pseudorapidity cuts: + // rapidity cuts (this is actually a physics cut, moreso than being an acceptance cut): if (std::fabs(v0.yLambda()) > v0Selections.rapidityCut) return false; - // if (std::fabs(v0.eta()) > v0Selections.v0EtaCut) return false; - V0SelCounter.fill(); - // if (std::fabs(v0.eta()) > v0Selections.daughterEtaCut) return false; // (TODO: properly consider this in daughter selection!) + v0SelCounter.fill(); // competing mass rejection (if compMassRejection < 0, this cut does nothing) if (std::fabs(v0.mK0Short() - o2::constants::physics::MassK0Short) < v0Selections.compMassRejection) return false; - V0SelCounter.fill(); - - const auto posTrackExtra = v0.template posTrack_as(); // (TODO: is it worth it to cache these transformations outside of the function? They are reused in the Lambda hypothesis checks) - const auto negTrackExtra = v0.template negTrack_as(); + v0SelCounter.fill(); // ITS quality cuts bool posIsFromAfterburner = posTrackExtra.isITSAfterburner(); @@ -1276,123 +1355,122 @@ struct lambdajetpolarizationions { // check minimum number of ITS clusters + maximum ITS chi2 per clusters + reject or select ITS afterburner tracks if requested if (posTrackExtra.itsNCls() < v0Selections.minITSclusters) return false; // check minimum ITS clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (posTrackExtra.itsChi2NCl() >= v0Selections.maxITSchi2PerNcls) return false; // check maximum ITS chi2 per clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.rejectPosITSafterburner && posIsFromAfterburner) return false; // reject afterburner track or not - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.requirePosITSafterburnerOnly && !posIsFromAfterburner) return false; // keep afterburner track or not - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.itsNCls() < v0Selections.minITSclusters) return false; // check minimum ITS clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.itsChi2NCl() >= v0Selections.maxITSchi2PerNcls) return false; // check maximum ITS chi2 per clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.rejectNegITSafterburner && negIsFromAfterburner) return false; // reject afterburner track or not - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.requireNegITSafterburnerOnly && !negIsFromAfterburner) return false; // keep afterburner track or not - V0SelCounter.fill(); + v0SelCounter.fill(); // TPC quality cuts if (posTrackExtra.tpcNClsCrossedRows() < v0Selections.minTPCrows) return false; // check minimum TPC crossed rows - V0SelCounter.fill(); + v0SelCounter.fill(); if (posTrackExtra.tpcChi2NCl() >= v0Selections.maxTPCchi2PerNcls) return false; // check maximum TPC chi2 per clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (posTrackExtra.tpcCrossedRowsOverFindableCls() < v0Selections.minTPCrowsOverFindableClusters) return false; // check minimum fraction of TPC rows over findable - V0SelCounter.fill(); + v0SelCounter.fill(); if (posTrackExtra.tpcFoundOverFindableCls() < v0Selections.minTPCfoundOverFindableClusters) return false; // check minimum fraction of found over findable TPC clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (posTrackExtra.tpcFractionSharedCls() >= v0Selections.maxFractionTPCSharedClusters) return false; // check the maximum fraction of allowed shared TPC clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.rejectTPCsectorBoundary && !isTrackFarFromTPCBoundary(v0.positivept(), v0.positivephi(), +1)) return false; // reject track far from TPC sector boundary or not - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.tpcNClsCrossedRows() < v0Selections.minTPCrows) return false; // check minimum TPC crossed rows - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.tpcChi2NCl() >= v0Selections.maxTPCchi2PerNcls) return false; // check maximum TPC chi2 per clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.tpcCrossedRowsOverFindableCls() < v0Selections.minTPCrowsOverFindableClusters) return false; // check minimum fraction of TPC rows over findable - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.tpcFoundOverFindableCls() < v0Selections.minTPCfoundOverFindableClusters) return false; // check minimum fraction of found over findable TPC clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (negTrackExtra.tpcFractionSharedCls() >= v0Selections.maxFractionTPCSharedClusters) return false; // check the maximum fraction of allowed shared TPC clusters - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.rejectTPCsectorBoundary && !isTrackFarFromTPCBoundary(v0.negativept(), v0.negativephi(), -1)) return false; // reject track far from TPC sector boundary or not - V0SelCounter.fill(); + v0SelCounter.fill(); // ITS only tag if (v0Selections.requirePosITSonly && posTrackExtra.tpcNClsCrossedRows() > 1) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.requireNegITSonly && negTrackExtra.tpcNClsCrossedRows() > 1) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); // TPC only tag if (v0Selections.skipTPConly && posTrackExtra.detectorMap() == o2::aod::track::TPC) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (v0Selections.skipTPConly && negTrackExtra.detectorMap() == o2::aod::track::TPC) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); return true; } - // Tests the hypothesis of the V0 being a Lambda or of it being an antiLambda. - template - bool passesLambdaLambdaBarHypothesis(TV0 const& v0, TCollision const& collision, bool Lambda_hypothesis) + /// \brief Tests the hypothesis of the V0 being a Lambda or of it being an antiLambda. + /// \param posTrackExtra,negTrackExtra the V0's daughter tracks, cached once at the process function. + /// \param lambdaHypothesis "true" tests the Lambda (proton+/pion-) hypothesis. "false" tests AntiLambda. + template + bool passesLambdaLambdaBarHypothesis(TV0 const& v0, TCollision const& collision, TTrack const& posTrackExtra, TTrack const& negTrackExtra, bool lambdaHypothesis) { // Remaining topological cuts that were charge-dependent: // (there is no real gain in doing a looser version of these in the passesGenericV0Cuts function. // The DCA check will be done anyways and is very unexpensive) // (even though they are high rejection, they demand a Lambda vs AntiLambda hypothesis, so they // only appear here...) - const float dcaProtonToPV = Lambda_hypothesis ? std::abs(v0.dcapostopv()) : std::abs(v0.dcanegtopv()); + const float dcaProtonToPV = lambdaHypothesis ? std::abs(v0.dcapostopv()) : std::abs(v0.dcanegtopv()); if (dcaProtonToPV < v0Selections.dcaProtonToPV) return false; - V0SelCounter.fill(); - const float dcaPionToPV = Lambda_hypothesis ? std::abs(v0.dcanegtopv()) : std::abs(v0.dcapostopv()); // Checks Lambda_hypothesis twice, but compiler can handle it cleanly. + v0SelCounter.fill(); + const float dcaPionToPV = lambdaHypothesis ? std::abs(v0.dcanegtopv()) : std::abs(v0.dcapostopv()); // Checks lambdaHypothesis twice, but compiler can handle it cleanly. if (dcaPionToPV < v0Selections.dcaPionToPV) return false; - V0SelCounter.fill(); - - const auto posTrackExtra = v0.template posTrack_as(); - const auto negTrackExtra = v0.template negTrack_as(); + v0SelCounter.fill(); // For the PID cuts to be properly applied while also keeping this function // general enough for Lambdas and AntiLambdas, we identify the roles of // proton-like and pion-like for the pos and neg tracks accordingly: - auto const& protonTrack = Lambda_hypothesis ? posTrackExtra : negTrackExtra; - auto const& pionTrack = Lambda_hypothesis ? negTrackExtra : posTrackExtra; + auto const& protonTrack = lambdaHypothesis ? posTrackExtra : negTrackExtra; + auto const& pionTrack = lambdaHypothesis ? negTrackExtra : posTrackExtra; ///// Expensive PID checks come last: // TPC PID if (std::fabs(protonTrack.tpcNSigmaPr()) > v0Selections.tpcPidNsigmaCut) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); if (std::fabs(pionTrack.tpcNSigmaPi()) > v0Selections.tpcPidNsigmaCut) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); // Only do TOF checks when actually using TOF subscriptions: // if (doprocessDataWithTOF) { @@ -1403,23 +1481,23 @@ struct lambdajetpolarizationions { const bool pionHasTOF = pionTrack.hasTOF(); // Proton-like track - if (protonHasTOF && std::abs(Lambda_hypothesis ? v0.posTOFDeltaTLaPr() : v0.negTOFDeltaTLaPr()) > v0Selections.maxDeltaTimeProton) + if (protonHasTOF && std::abs(lambdaHypothesis ? v0.posTOFDeltaTLaPr() : v0.negTOFDeltaTLaPr()) > v0Selections.maxDeltaTimeProton) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); // Pion-like track - if (pionHasTOF && std::abs(Lambda_hypothesis ? v0.negTOFDeltaTLaPi() : v0.posTOFDeltaTLaPi()) > v0Selections.maxDeltaTimePion) + if (pionHasTOF && std::abs(lambdaHypothesis ? v0.negTOFDeltaTLaPi() : v0.posTOFDeltaTLaPi()) > v0Selections.maxDeltaTimePion) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); // TOF PID in NSigma (TODO: add asymmetric NSigma windows for purity tuning?) // Proton-like track (notice usage of tofNSigmaLaPr vs tofNSigmaALaPr) - if (protonHasTOF && std::fabs(Lambda_hypothesis ? v0.tofNSigmaLaPr() : v0.tofNSigmaALaPr()) > v0Selections.tofPidNsigmaCutLaPr) - return false; // (No need to select which candidate is which with the Lambda_hypothesis. Automatically done already!) - V0SelCounter.fill(); + if (protonHasTOF && std::fabs(lambdaHypothesis ? v0.tofNSigmaLaPr() : v0.tofNSigmaALaPr()) > v0Selections.tofPidNsigmaCutLaPr) + return false; // (No need to select which candidate is which with the lambdaHypothesis. Automatically done already!) + v0SelCounter.fill(); // Pion-like track - if (pionHasTOF && std::fabs(Lambda_hypothesis ? v0.tofNSigmaLaPi() : v0.tofNSigmaALaPi()) > v0Selections.tofPidNsigmaCutLaPi) + if (pionHasTOF && std::fabs(lambdaHypothesis ? v0.tofNSigmaLaPi() : v0.tofNSigmaALaPi()) > v0Selections.tofPidNsigmaCutLaPi) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); // (CAUTION!) You cannot use the getter for raw data's PIDResponseTOF.h instead of LFStrangenessPIDTables.h (as below) // If you do use, TOF will just try to identify that track as a proton from the PV, instead of using the correct path @@ -1428,19 +1506,19 @@ struct lambdajetpolarizationions { // if (protonHasTOF && std::fabs(protonTrack.tofNSigmaPr()) > v0Selections.tofPidNsigmaCutLaPr) return false; // To properly use the LFStrangenessPIDTables version, you need to call o2-analysis-lf-strangenesstofpid too. } else { // Should fill counters an equal number of times to advance indices - V0SelCounter.fillUpTo(V0SelCounter.binValue + 4); // Fills the 4 times "V0SelCounter.fill()" would be called + v0SelCounter.fillUpTo(v0SelCounter.binValue + 4); // Fills the 4 times "v0SelCounter.fill()" would be called } // proper lifetime if (v0.distovertotmom(collision.posX(), collision.posY(), collision.posZ()) * o2::constants::physics::MassLambda0 > v0Selections.lambdaLifetimeCut) return false; - V0SelCounter.fill(); + v0SelCounter.fill(); return true; } // Function to help distinguish ambiguous candidates (via Armenteros) that pass both - // the Lambda_hypothesis true (i.e., a Lambda) or false (i.e., an AntiLambda) checks + // the lambdaHypothesis true (i.e., a Lambda) or false (i.e., an AntiLambda) checks // (This function is only called in about 1-3% of the Lambda-Like V0s which remain ambiguous after all other cuts) // int isCandidateArmenterosLambda(const float alpha, const float qt){ // // Remove K0s band @@ -1463,16 +1541,17 @@ struct lambdajetpolarizationions { // else return -1; // AntiLambda // } + /// \brief Clusters charged tracks into jets (FastJet, with optional background subtraction), fills the RingJets/RingLeadPs tables for this collision, and fills the jet-kinematics QA. template - void jetsProcess(TJetTracks const& tracks, const int ringCollIdx, const float centrality) + void jetsProcess(TJetTracks const& tracks, const int ringCollIdx, const float centrality, const float collisionPVz) { // Loop over reconstructed tracks: std::vector fjParticles; int leadingParticleIdx = -1; // Initialized as -1, but could leave it unitialized as well. We reject any invalid events where this could pose a problem (e.g., pT<=0) float leadingParticlePt = 0; for (auto const& track : tracks) { - JetTrackSelCounter.resetForNewTrack(); // reset bin counter for this candidate - JetTrackSelCounter.fill(); // bin: "All track candidates" + jetTrackSelCounter.resetForNewTrack(); // reset bin counter for this candidate + jetTrackSelCounter.fill(); // bin: "All track candidates" // Require that tracks pass selection criteria if (!isCandidateForChargedPseudojetAccepted(track)) @@ -1491,22 +1570,18 @@ struct lambdajetpolarizationions { leadingParticleIdx = fjParticles.size() - 1; } } - // Reject empty events if (fjParticles.size() < 1) return; - int lastBinEvSel = histos.get(HIST("hEventSelection"))->GetXaxis()->GetNbins(); - auto const& leadingParticle = fjParticles[leadingParticleIdx]; if (leadingParticle.pt() > jetConfigurations.minLeadParticlePt) { // If not, leading particle is probably a bad proxy tableLeadParticles(ringCollIdx, leadingParticle.pt(), leadingParticle.eta(), leadingParticle.phi()); } - // Start jet clusterization: - // Cluster particles using the anti-kt algorithm + // Cluster particles with the configured algorithm: fastjet::JetDefinition jetDef(mapFJAlgorithm(jetConfigurations.jetAlgorithm), jetConfigurations.radiusJet, mapFJRecombScheme(jetConfigurations.jetRecombScheme)); if (jetConfigurations.bkgSubtraction == kAreaBased) { - fastjet::AreaDefinition areaDef(fastjet::active_area, fastjet::GhostedAreaSpec(jetConfigurations.GhostedAreaSpecRapidity)); + fastjet::AreaDefinition areaDef(fastjet::active_area, fastjet::GhostedAreaSpec(jetConfigurations.ghostedAreaSpecRapidity)); fastjet::ClusterSequenceArea clustSeq(fjParticles, jetDef, areaDef); // Attributes an area for each pseudojet in the list std::vector jets = fastjet::sorted_by_pt(clustSeq.inclusive_jets()); // No minimum pt before background subtraction if (jets.empty()) @@ -1514,16 +1589,23 @@ struct lambdajetpolarizationions { // Perpendicular cone area subtraction, not the traditional subtraction (TODO: include an option for traditional area subtraction) auto [rhoPerp, rhoMPerp] = jetutilities::estimateRhoPerpCone(fjParticles, jets[0], jetConfigurations.radiusJet); // This uses a geometric, pi*R^2 area, not exactly a ghost-based area! - // Loop over clustered jets: int selectedJets = 0; fastjet::PseudoJet leadingJetSub; float leadingJetPt = -1.f; + // Storing the accepted subtracted jets in memory: + // (the QA loop below uses them, and this is inexpensive enough to store) + std::vector subtractedJets; + if (doJetKinematicsQA) + subtractedJets.reserve(jets.size()); for (const auto& jet : jets) { // Jet must be fully contained in the acceptance (0.9 for ITS+TPC barrel) - const float jet_eta = jet.eta(); - if (std::fabs(jet_eta) > (0.9f - jetConfigurations.radiusJet)) + const float jetEta = jet.eta(); + if (std::fabs(jetEta) > (0.9f - jetConfigurations.radiusJet)) { + if (doJetKinematicsQA) + histos.fill(HIST("JetKinematicsQA/hJetEtaOutOfAcceptance"), jetEta); continue; + } auto jetForSub = jet; // Subtracts same background estimated for highest pt jet, but every jet might have a slightly different area @@ -1534,13 +1616,14 @@ struct lambdajetpolarizationions { if (jetMinusBkg.pt() < jetConfigurations.minJetPt) continue; selectedJets++; + if (doJetKinematicsQA) + subtractedJets.emplace_back(jetMinusBkg); - // Store jet: tableJets(ringCollIdx, jetMinusBkg.pt(), jetMinusBkg.eta(), // Using eta instead of rapidity - jetMinusBkg.phi(), - jetMinusBkg.constituents().size()); + jetMinusBkg.phi()); + // jetMinusBkg.constituents().size()); // Finding the leading jet after subtraction (leading jet is NOT known a priori!): if (jetMinusBkg.pt() > leadingJetPt) { @@ -1554,22 +1637,17 @@ struct lambdajetpolarizationions { histos.fill(HIST("hEventsWithJet"), 0.5); // Another version of this counter, which is already integrated in the Event Selection flow: if (doEventQA) - fillEventSelectionQA(lastBinEvSel - 1, centrality); // hasRingJet passes + fillEventSelectionQA(mBinHasRingJet, centrality); // hasRingJet passes if (doJetKinematicsQA) { + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsCentrality"), leadingJetSub.constituents().size(), centrality); + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsJetPt"), leadingJetSub.constituents().size(), leadingJetSub.pt()); histos.fill(HIST("JetKinematicsQA/hLeadingJetPt"), leadingJetSub.pt()); histos.fill(HIST("JetKinematicsQA/hLeadingJetEta"), leadingJetSub.eta()); histos.fill(HIST("JetKinematicsQA/hLeadingJetPhi"), leadingJetSub.phi()); - // Now looping through jets again to calculate the correlations: - for (const auto& jet : jets) { - // Will recalculated background subtraction during QA to avoid storing jets in memory when running in non-QA cases: - auto jetForSub = jet; - fastjet::PseudoJet jetMinusBkg = backgroundSub.doRhoAreaSub(jetForSub, rhoPerp, rhoMPerp); - - if (jetMinusBkg.pt() < jetConfigurations.minJetPt) - continue; - + // Now looping through the accepted subtracted jets to calculate the correlations: + for (const auto& jetMinusBkg : subtractedJets) { float cosTheta = cosThetaJets(leadingJetSub, jetMinusBkg); float deltaPhi = RecoDecay::constrainAngle(leadingJetSub.phi() - jetMinusBkg.phi(), -o2::constants::math::PI); float deltaEta = leadingJetSub.eta() - jetMinusBkg.eta(); @@ -1595,34 +1673,42 @@ struct lambdajetpolarizationions { histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsDeltaPhiToLead"), selectedJets, deltaPhi); histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsDeltaEtaToLead"), selectedJets, deltaEta); histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsCosThetaToLead"), selectedJets, cosTheta); + + // Collision PVz: + histos.fill(HIST("JetKinematicsQA/h2dJetEtaVsPVz"), jetMinusBkg.eta(), collisionPVz); + histos.fill(HIST("JetKinematicsQA/h2dJetPhiVsPVz"), jetMinusBkg.phi(), collisionPVz); } // Leading particle comparisons: - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticlePt"), leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticleEta"), leadingParticle.eta()); - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticlePhi"), leadingParticle.phi()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticlePt"), leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticleEta"), leadingParticle.eta()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticlePhi"), leadingParticle.phi()); float deltaPhiParticleToJet = RecoDecay::constrainAngle(leadingJetSub.phi() - leadingParticle.phi(), -o2::constants::math::PI); float deltaEtaParticleToJet = leadingJetSub.eta() - leadingParticle.eta(); float cosThetaParticleToJet = cosThetaJets(leadingJetSub, leadingParticle); // Takes advantage of the fact that this leading particle is a PseudoJet object - histos.fill(HIST("JetVsLeadingParticleQA/hCosThetaLeadParticleToJet"), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/hDeltaPhiLeadParticleToJet"), deltaPhiParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/hDeltaEtaToLeadParticleToJet"), deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/hCosThetaLeadParticleToJet"), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/hDeltaPhiLeadParticleToJet"), deltaPhiParticleToJet); + histos.fill(HIST("LeadingParticleQA/hDeltaEtaToLeadParticleToJet"), deltaEtaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead"), deltaPhiParticleToJet, deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead"), deltaPhiParticleToJet, deltaEtaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead"), selectedJets, deltaPhiParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead"), selectedJets, deltaEtaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead"), selectedJets, cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead"), selectedJets, deltaPhiParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead"), selectedJets, deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead"), selectedJets, cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsLeadParticlePt"), selectedJets, leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsLeadParticlePt"), leadingJetSub.pt(), leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsLeadParticlePt"), selectedJets, leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsLeadParticlePt"), leadingJetSub.pt(), leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead"), leadingJetSub.pt(), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead"), leadingParticle.pt(), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead"), leadingJetSub.pt(), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead"), leadingParticle.pt(), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead"), leadingJetSub.pt(), deltaPhiParticleToJet); // To see if there is any backgound in phi due to soft jets (or soft particles below) - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead"), leadingParticle.pt(), deltaPhiParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead"), leadingJetSub.pt(), deltaPhiParticleToJet); // To see if there is any backgound in phi due to soft jets (or soft particles below) + histos.fill(HIST("LeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead"), leadingParticle.pt(), deltaPhiParticleToJet); + + // Collision PVz -- Leading Particle: + histos.fill(HIST("LeadingParticleQA/h2dLeadPEtaVsPVz"), leadingParticle.eta(), collisionPVz); + histos.fill(HIST("LeadingParticleQA/h2dLeadPPhiVsPVz"), leadingParticle.phi(), collisionPVz); } } else { // Otherwise, simple jet clustering (TODO: this is the fall back for kConstituentBased while not implemented) fastjet::ClusterSequence clustSeq(fjParticles, jetDef); @@ -1637,32 +1723,43 @@ struct lambdajetpolarizationions { histos.fill(HIST("hEventsWithJet"), 0.5); // Another version of this counter, which is already integrated in the Event Selection flow: if (doEventQA) - fillEventSelectionQA(lastBinEvSel - 1, centrality); // hasRingJet passes + fillEventSelectionQA(mBinHasRingJet, centrality); // hasRingJet passes const auto& leadingJet = jets[0]; for (const auto& jet : jets) { // Jet must be fully contained in the acceptance (0.9 for ITS+TPC barrel) - const float jet_eta = jet.eta(); - if (std::fabs(jet_eta) > (0.9f - jetConfigurations.radiusJet)) + const float jetEta = jet.eta(); + if (std::fabs(jetEta) > (0.9f - jetConfigurations.radiusJet)) { + if (doJetKinematicsQA) { + // QA before the acceptance cut: + const float deltaPhiOut = RecoDecay::constrainAngle(leadingJet.phi() - jet.phi(), -o2::constants::math::PI); + const float deltaEtaOut = leadingJet.eta() - jetEta; + histos.fill(HIST("JetKinematicsQA/hJetEtaOutOfAcceptance"), jetEta); + histos.fill(HIST("JetKinematicsQA/hDeltaEtaToLeadingJetOutOfAcceptance"), deltaEtaOut); + histos.fill(HIST("JetKinematicsQA/hDeltaRToLeadingJetOutOfAcceptance"), std::sqrt(deltaPhiOut * deltaPhiOut + deltaEtaOut * deltaEtaOut)); + } continue; + } tableJets(ringCollIdx, jet.pt(), - jet_eta, // Using eta instead of rapidity - jet.phi(), - jet.constituents().size()); + jetEta, // Using eta instead of rapidity + jet.phi()); + // jet.constituents().size()); // Currently removed from datamodel. + // Other variables can better reveal jet quenching and help identify good selection criteria for quenched jets proxies if (doJetKinematicsQA) { histos.fill(HIST("JetKinematicsQA/hJetPt"), jet.pt()); - histos.fill(HIST("JetKinematicsQA/hJetEta"), jet_eta); + histos.fill(HIST("JetKinematicsQA/hJetEta"), jetEta); histos.fill(HIST("JetKinematicsQA/hJetPhi"), jet.phi()); + histos.fill(HIST("JetKinematicsQA/h2dJetEtaVsJetPhi"), jetEta, jet.phi()); // Calculate angle to leading jet: float cosTheta = cosThetaJets(leadingJet, jet); // Calculate angular separation in projected angles: float deltaPhi = RecoDecay::constrainAngle(leadingJet.phi() - jet.phi(), -o2::constants::math::PI); - float deltaEta = leadingJet.eta() - jet_eta; + float deltaEta = leadingJet.eta() - jetEta; float deltaR = std::sqrt(deltaPhi * deltaPhi + deltaEta * deltaEta); // 2D angular distance in the eta-phi plane histos.fill(HIST("JetKinematicsQA/hCosThetaToLeadingJet"), cosTheta); // Measuring the cosine, not angle, because it is faster! @@ -1685,60 +1782,76 @@ struct lambdajetpolarizationions { histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsDeltaPhiToLead"), jetsInEvent, deltaPhi); histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsDeltaEtaToLead"), jetsInEvent, deltaEta); histos.fill(HIST("JetKinematicsQA/h2dJetsPerEventvsCosThetaToLead"), jetsInEvent, cosTheta); + + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsCentrality"), jet.constituents().size(), centrality); + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsJetPt"), jet.constituents().size(), jet.pt()); + + // Collision PVz: + histos.fill(HIST("JetKinematicsQA/h2dJetEtaVsPVz"), jet.eta(), collisionPVz); + histos.fill(HIST("JetKinematicsQA/h2dJetPhiVsPVz"), jet.phi(), collisionPVz); } } - if (doJetKinematicsQA) { + if (doJetKinematicsQA) { // Fills even when the leading jet is outside of the (0.9f - jetConfigurations.radiusJet) eta window. Fills at least for the leading jet. + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsCentrality"), leadingJet.constituents().size(), centrality); + histos.fill(HIST("JetKinematicsQA/h2dConstituentsPerJetVsJetPt"), leadingJet.constituents().size(), leadingJet.pt()); histos.fill(HIST("JetKinematicsQA/hLeadingJetPt"), leadingJet.pt()); histos.fill(HIST("JetKinematicsQA/hLeadingJetEta"), leadingJet.eta()); histos.fill(HIST("JetKinematicsQA/hLeadingJetPhi"), leadingJet.phi()); // Leading particle comparisons: - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticlePt"), leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticleEta"), leadingParticle.eta()); - histos.fill(HIST("JetVsLeadingParticleQA/hLeadingParticlePhi"), leadingParticle.phi()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticlePt"), leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticleEta"), leadingParticle.eta()); + histos.fill(HIST("LeadingParticleQA/hLeadingParticlePhi"), leadingParticle.phi()); double deltaPhiParticleToJet = RecoDecay::constrainAngle(leadingJet.phi() - leadingParticle.phi(), -o2::constants::math::PI); double deltaEtaParticleToJet = leadingJet.eta() - leadingParticle.eta(); double cosThetaParticleToJet = cosThetaJets(leadingJet, leadingParticle); // Takes advantage of the fact that this leading particle is a PseudoJet object - histos.fill(HIST("JetVsLeadingParticleQA/hCosThetaLeadParticleToJet"), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/hDeltaPhiLeadParticleToJet"), deltaPhiParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/hDeltaEtaToLeadParticleToJet"), deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/hCosThetaLeadParticleToJet"), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/hDeltaPhiLeadParticleToJet"), deltaPhiParticleToJet); + histos.fill(HIST("LeadingParticleQA/hDeltaEtaToLeadParticleToJet"), deltaEtaParticleToJet); + + histos.fill(HIST("LeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead"), deltaPhiParticleToJet, deltaEtaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dDeltaPhiParticleToLeadvsDeltaEtaParticleToLead"), deltaPhiParticleToJet, deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead"), jetsInEvent, deltaPhiParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead"), jetsInEvent, deltaEtaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead"), jetsInEvent, cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaPhiParticleToLead"), jetsInEvent, deltaPhiParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsDeltaEtaParticleToLead"), jetsInEvent, deltaEtaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsCosThetaParticleToLead"), jetsInEvent, cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dJetsPerEventvsLeadParticlePt"), jetsInEvent, leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsLeadParticlePt"), leadingJet.pt(), leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/h2dJetsPerEventvsLeadParticlePt"), jetsInEvent, leadingParticle.pt()); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsLeadParticlePt"), leadingJet.pt(), leadingParticle.pt()); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead"), leadingJet.pt(), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead"), leadingParticle.pt(), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsCosThetaParticleToLead"), leadingJet.pt(), cosThetaParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadParticlePtvsCosThetaParticleToLead"), leadingParticle.pt(), cosThetaParticleToJet); + histos.fill(HIST("LeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead"), leadingJet.pt(), deltaPhiParticleToJet); // To see if there is any backgound in phi due to soft jets (or soft particles below) + histos.fill(HIST("LeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead"), leadingParticle.pt(), deltaPhiParticleToJet); - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadJetPtvsDeltaPhiParticleToLead"), leadingJet.pt(), deltaPhiParticleToJet); // To see if there is any backgound in phi due to soft jets (or soft particles below) - histos.fill(HIST("JetVsLeadingParticleQA/h2dLeadParticlePtvsDeltaPhiParticleToLead"), leadingParticle.pt(), deltaPhiParticleToJet); + // Collision PVz -- Leading Particle: + histos.fill(HIST("LeadingParticleQA/h2dLeadPEtaVsPVz"), leadingParticle.eta(), collisionPVz); + histos.fill(HIST("LeadingParticleQA/h2dLeadPPhiVsPVz"), leadingParticle.phi(), collisionPVz); } } } - // No longer use a separate JetTracks joined table -- it was mostly a subset of DauTracks + TracksIU (which was not used) + /// \brief Main process function: applies event selection, fills the RingCollisions table, calls jetsProcess(), then tests every V0 against the Lambda/AntiLambda hypotheses and fills RingLaV0s. + /// \note No longer use a separate JetTracks joined table -- it was mostly a subset of DauTracks + TracksIU (which was not used) template void dataProcess(TCollision const& collision, TV0Candidates const& V0s, TDaughterTracks const& V0DauTracks, aod::BCsWithTimestamps const& bcs) { float centrality = getCentrality(collision); // Strictly for QA. We save other types of centrality estimators in the derived data! - // For event QA the last two indices never change for NEv_withJets and NEv_withV0s - // (Not the best way to initialize this: runs once per collision! TODO: think of a better way to do it) - int lastBinEvSel = histos.get(HIST("hEventSelection"))->GetXaxis()->GetNbins(); bool validV0AlreadyFound = false; histos.fill(HIST("hEventSelection"), 0. /* all collisions */); - histos.fill(HIST("hEventSelectionVsCentrality"), 0. /* all collisions */, centrality); + if (doEventQA) + histos.fill(HIST("hEventSelectionVsCentrality"), 0. /* all collisions */, centrality); auto bc = bcs.iteratorAt(collision.bcId()); - if (!isEventAccepted(collision, bc, centrality, doEventQA)) + + // Initialize interactionRate with a sentinel value. It will be stored in the derived data afterwards: + // (We pass this by reference and update it inside isEventAccepted after all less expensive checks were already done) + double interactionRate = -1.0; + if (!isEventAccepted(collision, bc, centrality, interactionRate, doEventQA)) return; // Uses return instead of continue, as there is no explicit loop here if (doEventQA) @@ -1746,26 +1859,39 @@ struct lambdajetpolarizationions { if (v0Selections.rejectTPCsectorBoundary) initCCDB(bc); // Substituted call from collision to bc for raw data - // Fill event table: + // If the event passed but skipped the Pb-Pb block (e.g. it was a pp event), fetch the rate before saving in the datamodel: + if (interactionRate < 0) + interactionRate = rateFetcher.fetch(ccdb.service, bc.timestamp(), bc.runNumber(), irSource) * 1.e-3; + + const float collisionPVz = collision.posZ(); + tableCollisions(collision.centFT0M(), collision.centFT0C(), - collision.centFV0A()); // (TODO: add InteractionRate info and other useful cuts for later on in the analysis?) + collision.centFV0A(), + collisionPVz, + interactionRate); // Get the derived collision row index for this event: const int ringCollIdx = tableCollisions.lastIndex(); - // Call to jets process: - jetsProcess(V0DauTracks, ringCollIdx, centrality); // V0DauTracks takes the place of jetTracks now + jetsProcess(V0DauTracks, ringCollIdx, centrality, collisionPVz); // V0DauTracks takes the place of jetTracks now - uint NLambdas = 0; // Counting particles per event - uint NAntiLambdas = 0; - uint NAmbiguous = 0; + // Counting particles per event: + uint16_t nLambdas = 0; + uint16_t nAntiLambdas = 0; + uint16_t nNonAmbiguous = 0; + uint16_t nAmbiguous = 0; for (auto const& v0 : V0s) { - V0SelCounter.resetForNewV0(); - V0SelCounter.fill(); // Fill for all v0 candidates + v0SelCounter.resetForNewV0(v0.mLambda(), v0.mAntiLambda()); + v0SelCounter.fill(); // Fill for all v0 candidates if (doArmenterosQA) histos.fill(HIST("GeneralQA/h2dArmenterosAll"), v0.alpha(), v0.qtarm()); // fill AP plot for all V0s - if (!passesGenericV0Cuts(v0)) + + // Daughter tracks cached once per candidate (needed by both hypothesis tests, by the QA and by the table fill) + const auto posTrackExtra = v0.template posTrack_as(); + const auto negTrackExtra = v0.template negTrack_as(); + + if (!passesGenericV0Cuts(v0, posTrackExtra, negTrackExtra)) continue; if (doArmenterosQA) @@ -1775,20 +1901,20 @@ struct lambdajetpolarizationions { bool isLambda = false; bool isAntiLambda = false; if (analyseLambda) - isLambda = passesLambdaLambdaBarHypothesis(v0, collision, true); + isLambda = passesLambdaLambdaBarHypothesis(v0, collision, posTrackExtra, negTrackExtra, true); if (analyseAntiLambda) { if (analyseLambda) // We only need to advance when the Lambda hypothesis had an early exit on the counters - V0SelCounter.advanceTo(lambdaHypoEnd + 1); // sync to bin 41 (x=40 means bin 41, the first #bar{#Lambda} bin) - isAntiLambda = passesLambdaLambdaBarHypothesis(v0, collision, false); + v0SelCounter.advanceTo(lambdaHypoEnd + 1); // sync to bin 43 (x=42 means bin 43, the first #bar{#Lambda} bin) + isAntiLambda = passesLambdaLambdaBarHypothesis(v0, collision, posTrackExtra, negTrackExtra, false); } if (!isLambda && !isAntiLambda) - continue; // Candidate is not considered to be a Lambda-like + continue; // Candidate is not considered to be Lambda-like if (isLambda) - NLambdas++; + nLambdas++; if (isAntiLambda) - NAntiLambdas++; + nAntiLambdas++; if (doArmenterosQA) histos.fill(HIST("GeneralQA/h2dArmenterosFullSelected"), v0.alpha(), v0.qtarm()); // cross-check @@ -1798,13 +1924,15 @@ struct lambdajetpolarizationions { histos.fill(HIST("GeneralQA/h2dArmenterosFullSelectedAntiLambda"), v0.alpha(), v0.qtarm()); // XOR check: - if (isLambda ^ isAntiLambda) + if (isLambda ^ isAntiLambda) { histos.fill(HIST("GeneralQA/h2dArmenterosFullSelectedNonAmbiguous"), v0.alpha(), v0.qtarm()); + nNonAmbiguous++; + } // int lambdaIdx = -1; // No need to pass armenteros if (isLambda && isAntiLambda) { - NAmbiguous++; - histos.fill(HIST("hAmbiguousLambdaCandidates"), 0); + nAmbiguous++; + // histos.fill(HIST("hAmbiguousLambdaCandidates"), 0); if (doArmenterosQA) histos.fill(HIST("GeneralQA/h2dArmenterosFullSelectedAmbiguous"), v0.alpha(), v0.qtarm()); // To know the discerning power of Armenteros in an Ambiguous Lambda vs AntiLambda case @@ -1816,27 +1944,47 @@ struct lambdajetpolarizationions { } // if (lambdaIdx == kIsArmenterosK0) continue; // Should just skip this step then! - if (doEventQA) - fillEventSelectionQA(lastBinEvSel, centrality); // hasRingV0 passes - - // // Extra competing mass rejection of Lambdas // (TODO: test competing mass cuts) + // // Extra competing mass rejection of Lambdas // (TODO: test competing mass cuts and implement it here) // v0.mLambda() + // Bookkeeping for the candidates that passed eveything until the ambiguity check: + if (isLambda) { // Go back to Lambda bin to fill it as well: + v0SelCounter.advanceTo(lambdaHypoEnd - 1); // Bin 41: Ambiguous Lambda rejection + v0SelCounter.fill(); + } + if (isAntiLambda) { + v0SelCounter.advanceTo(antiLambdaHypoEnd - 1); // Bin 52: Ambiguous antiLambda rejection + v0SelCounter.fill(); + } + + // Removing ambiguous Lambda candidates for final storage, AFTER some QAing: + if (isLambda && isAntiLambda) + continue; + + // Updating selection counters after ambiguous candidate rejection: + if (isLambda) { + v0SelCounter.advanceTo(lambdaHypoEnd); // Bin 42: Final accepted Lambda V0s + v0SelCounter.fill(); + } else { // i.e., isAntiLambda + v0SelCounter.advanceTo(antiLambdaHypoEnd); // Bin 53: Final accepted anti-Lambda V0s + v0SelCounter.fill(); + } + // Saving the Lambdas into a derived data column: auto const v0pt = v0.pt(); - const auto posTrackExtra = v0.template posTrack_as(); - const auto negTrackExtra = v0.template negTrack_as(); tableV0s(ringCollIdx, v0pt, v0.eta(), v0.phi(), // Using eta instead of rapidity - isLambda, isAntiLambda, - v0.mLambda(), v0.mAntiLambda(), + isLambda, // 0: antiLambda, 1: Lambda. No ambiguous candidates (isLambda && isAntiLambda) are stored + isLambda ? v0.mLambda() : v0.mAntiLambda(), v0.positivept(), v0.positiveeta(), v0.positivephi(), v0.negativept(), v0.negativeeta(), v0.negativephi(), - posTrackExtra.tpcNSigmaPr(), posTrackExtra.tpcNSigmaPi(), - negTrackExtra.tpcNSigmaPr(), negTrackExtra.tpcNSigmaPi(), + packNSigma(isLambda ? posTrackExtra.tpcNSigmaPr() : negTrackExtra.tpcNSigmaPr()), // Proton-like track, packed into int16_t + packNSigma(isLambda ? negTrackExtra.tpcNSigmaPi() : posTrackExtra.tpcNSigmaPi()), // Pion-like track v0.v0cosPA(), v0.v0radius(), v0.dcaV0daughters(), v0.dcapostopv(), v0.dcanegtopv()); + + // Perform extensive QAs for non-ambiguous Lambda/antiLambda candidates only: if (doEventQA && !validV0AlreadyFound) - fillEventSelectionQA(lastBinEvSel, centrality); // hasRingV0 passes + fillEventSelectionQA(mBinHasRingV0, centrality); // hasRingV0 passes validV0AlreadyFound = true; if (doV0KinematicQA) { @@ -1873,8 +2021,17 @@ struct lambdajetpolarizationions { } } - if (doCompleteTopoQA) { - // Remaking these variables outside of the passesLambdaLambdaBarHypothesis. Loses performance, but that should be OK for QA + // Invariant mass spectra: + if (analyseLambda && isLambda) { + histos.fill(HIST("hMassLambda"), v0.mLambda()); + histos.fill(HIST("Lambda/h3dMassLambda"), centrality, v0pt, v0.mLambda()); + } + if (analyseAntiLambda && isAntiLambda) { + histos.fill(HIST("hMassAntiLambda"), v0.mAntiLambda()); + histos.fill(HIST("AntiLambda/h3dMassAntiLambda"), centrality, v0pt, v0.mAntiLambda()); + } + + if (doTopoQA) { histos.fill(HIST("V0KinematicQA/hPosDCAToPV"), v0.dcapostopv()); histos.fill(HIST("V0KinematicQA/hNegDCAToPV"), v0.dcanegtopv()); histos.fill(HIST("V0KinematicQA/hDCADaughters"), v0.dcaV0daughters()); @@ -1884,9 +2041,7 @@ struct lambdajetpolarizationions { histos.fill(HIST("V0KinematicQA/h2dNegativeITSvsTPCpts"), negTrackExtra.tpcNClsCrossedRows(), negTrackExtra.itsNCls()); histos.fill(HIST("V0KinematicQA/h2dPositivePtVsPhi"), v0.positivept(), computePhiMod(v0.positivephi(), 1)); histos.fill(HIST("V0KinematicQA/h2dNegativePtVsPhi"), v0.negativept(), computePhiMod(v0.negativephi(), -1)); - if (isLambda && analyseLambda) { - histos.fill(HIST("hMassLambda"), v0.mLambda()); - histos.fill(HIST("Lambda/h3dMassLambda"), centrality, v0pt, v0.mLambda()); + if (analyseLambda && isLambda) { histos.fill(HIST("Lambda/hPosDCAToPV"), v0.dcapostopv()); histos.fill(HIST("Lambda/hNegDCAToPV"), v0.dcanegtopv()); histos.fill(HIST("Lambda/hDCADaughters"), v0.dcaV0daughters()); @@ -1926,15 +2081,8 @@ struct lambdajetpolarizationions { histos.fill(HIST("Lambda/h3dNegTOFdeltaTvsTrackPt"), centrality, v0.negativept(), v0.negTOFDeltaTLaPi()); } } - if (doEtaPhiQA) { - histos.fill(HIST("Lambda/h5dV0PhiVsEta"), centrality, v0pt, v0.mLambda(), v0.phi(), v0.eta()); - histos.fill(HIST("Lambda/h5dPosPhiVsEta"), centrality, v0.positivept(), v0.mLambda(), v0.positivephi(), v0.positiveeta()); - histos.fill(HIST("Lambda/h5dNegPhiVsEta"), centrality, v0.negativept(), v0.mLambda(), v0.negativephi(), v0.negativeeta()); - } } - if (isAntiLambda && analyseAntiLambda) { - histos.fill(HIST("hMassAntiLambda"), v0.mAntiLambda()); - histos.fill(HIST("AntiLambda/h3dMassAntiLambda"), centrality, v0pt, v0.mAntiLambda()); + if (analyseAntiLambda && isAntiLambda) { histos.fill(HIST("AntiLambda/hPosDCAToPV"), v0.dcapostopv()); histos.fill(HIST("AntiLambda/hNegDCAToPV"), v0.dcanegtopv()); histos.fill(HIST("AntiLambda/hDCADaughters"), v0.dcaV0daughters()); @@ -1974,21 +2122,24 @@ struct lambdajetpolarizationions { histos.fill(HIST("AntiLambda/h3dNegTOFdeltaTvsTrackPt"), centrality, v0.negativept(), v0.negTOFDeltaTLaPr()); } } - if (doEtaPhiQA) { - histos.fill(HIST("AntiLambda/h5dV0PhiVsEta"), centrality, v0pt, v0.mAntiLambda(), v0.phi(), v0.eta()); - histos.fill(HIST("AntiLambda/h5dPosPhiVsEta"), centrality, v0.positivept(), v0.mAntiLambda(), v0.positivephi(), v0.positiveeta()); - histos.fill(HIST("AntiLambda/h5dNegPhiVsEta"), centrality, v0.negativept(), v0.mAntiLambda(), v0.negativephi(), v0.negativeeta()); - } } } // end CompleteTopoQA } // end V0s loop // Fill histograms on a per-event level: - histos.fill(HIST("Lambda/hLambdasPerEvent"), NLambdas); - histos.fill(HIST("AntiLambda/hAntiLambdasPerEvent"), NAntiLambdas); - histos.fill(HIST("hAmbiguousPerEvent"), NAmbiguous); - histos.fill(HIST("Lambda/h2dNbrOfLambdaVsCentrality"), centrality, NLambdas); - histos.fill(HIST("AntiLambda/h2dNbrOfAntiLambdaVsCentrality"), centrality, NAntiLambdas); + if (analyseLambda) { + histos.fill(HIST("Lambda/hLambdasPerEvent"), nLambdas); + histos.fill(HIST("Lambda/h2dNbrOfLambdaVsCentrality"), centrality, nLambdas); + } + if (analyseAntiLambda) { + histos.fill(HIST("AntiLambda/hAntiLambdasPerEvent"), nAntiLambdas); + histos.fill(HIST("AntiLambda/h2dNbrOfAntiLambdaVsCentrality"), centrality, nAntiLambdas); + } + if (analyseLambda && analyseAntiLambda) { + histos.fill(HIST("hAmbiguousPerEvent"), nAmbiguous); + histos.fill(HIST("hNonAmbiguousPerEvent"), nNonAmbiguous); + histos.fill(HIST("hLambdasAndAntiLambdasPerEvent"), nLambdas + nAntiLambdas); + } } void processData(SelCollisions::iterator const& collision, aod::V0Datas const& V0s, DauTracks const& V0DauTracks, aod::BCsWithTimestamps const& bcs) diff --git a/PWGLF/Tasks/Strangeness/lambdaJetPolarizationIonsDerived.cxx b/PWGLF/Tasks/Strangeness/lambdaJetPolarizationIonsDerived.cxx index 1ad7c388aeb..72a6465ced8 100644 --- a/PWGLF/Tasks/Strangeness/lambdaJetPolarizationIonsDerived.cxx +++ b/PWGLF/Tasks/Strangeness/lambdaJetPolarizationIonsDerived.cxx @@ -9,9 +9,8 @@ // granted to it by virtue of its status as an Intergovernmental Organization // or submit itself to any jurisdiction. // -/// \file lambdajetpolarizationionsderived.cxx +/// \file lambdaJetPolarizationIonsDerived.cxx /// \brief Lambda and antiLambda polarization analysis task using derived data -/// /// \author Cicero Domenico Muncinelli , Campinas State University // // Jet Polarization Ions task -- Derived data @@ -32,15 +31,20 @@ #include "PWGLF/DataModel/lambdaJetPolarizationIons.h" +#include "Common/Core/RecoDecay.h" + #include #include #include #include #include +#include #include +#include #include #include #include +#include #include #include @@ -48,12 +52,22 @@ #include #include // IWYU pragma: keep (do not replace with Math/Vector4Dfwd.h) #include +#include +#include +#include #include +#include #include // For perpendicular jet direction QAs +#include // std::fill, for resetting the Delta Method accumulators +#include #include +#include +#include #include +#include #include +#include #include using namespace o2; @@ -61,14 +75,13 @@ using namespace o2::framework; using namespace o2::framework::expressions; using ROOT::Math::PtEtaPhiMVector; using ROOT::Math::XYZVector; -// using namespace o2::aod::lambdajetpol; // Used it explicitly along the code for clarity // Declaring constants: -constexpr double protonMass = o2::constants::physics::MassProton; // Assumes particle identification for daughter is perfect -constexpr double lambdaWeakDecayConstant = 0.749; // DPG 2025 update -constexpr double antiLambdaWeakDecayConstant = -0.758; // DPG 2025 update -constexpr double polPrefactorLambda = 3.0 / lambdaWeakDecayConstant; -constexpr double polPrefactorAntiLambda = 3.0 / antiLambdaWeakDecayConstant; +constexpr double ProtonMass = o2::constants::physics::MassProton; // Assumes particle identification for daughter is perfect +constexpr double LambdaWeakDecayConstant = 0.749; // DPG 2025 update +constexpr double AntiLambdaWeakDecayConstant = -0.758; // DPG 2025 update +constexpr double PolPrefactorLambda = 3.0 / LambdaWeakDecayConstant; +constexpr double PolPrefactorAntiLambda = 3.0 / AntiLambdaWeakDecayConstant; enum CentEstimator { kCentFT0C = 0, @@ -80,18 +93,31 @@ enum CentEstimator { #define RING_OBSERVABLE_FILL_LIST(X, FOLDER) \ /* Counters */ \ X(FOLDER "/QA/hDeltaPhi", deltaPhiJet) \ + X(FOLDER "/QA/hDeltaPhiVsDeltaEta", deltaPhiJet, deltaEtaJet) \ X(FOLDER "/QA/hDeltaTheta", deltaThetaJet) \ + X(FOLDER "/QA/hCosDeltaTheta", cosDeltaThetaJet) \ X(FOLDER "/QA/hIntegrated", 0.) \ + X(FOLDER "/QA/hPtJet", leadingJetPt) \ /* Lambda pT variation -- Youpeng's proposal */ \ X(FOLDER "/QA/hLambdaPt", v0pt) \ /* Counters */ \ X(FOLDER "/QA/h2dDeltaPhiVsLambdaPt", deltaPhiJet, v0pt) \ X(FOLDER "/QA/h2dDeltaThetaVsLambdaPt", deltaThetaJet, v0pt) \ + X(FOLDER "/QA/hDeltaPhiVsLeadJetPhi", deltaPhiJet, leadingJetPhi) \ /* Additional plots for instant gratification - 1D Profiles */ \ + X(FOLDER "/hRingObservableCounts", ringObservable) \ X(FOLDER "/pRingObservableDeltaPhi", deltaPhiJet, ringObservable) \ + X(FOLDER "/pRingObservablePhiJet", leadingJetPhi, ringObservable) \ + X(FOLDER "/pRingObservablePhiLambda", v0phi, ringObservable) \ X(FOLDER "/pRingObservableDeltaTheta", deltaThetaJet, ringObservable) \ + X(FOLDER "/EtaDependence/pRingObservableEtaLambda", v0eta, ringObservable) \ + X(FOLDER "/EtaDependence/pRingObservableEtaJet", leadingJetEta, ringObservable) \ X(FOLDER "/pRingObservableIntegrated", 0., ringObservable) \ X(FOLDER "/pRingObservableLambdaPt", v0pt, ringObservable) \ + X(FOLDER "/pRingObservableLeadJetPVz", collisionPVz, ringObservable) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtJet", leadingJetPt, ringObservable) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtJetVsEtaJet", leadingJetPt, leadingJetEta, ringObservable) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtJetVsEtaV0", leadingJetPt, v0eta, ringObservable) \ /* 2D Profiles */ \ X(FOLDER "/p2dRingObservableDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, ringObservable) \ X(FOLDER "/p2dRingObservableDeltaThetaVsLambdaPt", deltaThetaJet, v0pt, ringObservable) \ @@ -118,6 +144,13 @@ enum CentEstimator { /* 2D Profiles: Angle vs Mass */ \ X(FOLDER "/p2dRingObservableDeltaPhiVsMass", deltaPhiJet, v0LambdaLikeMass, ringObservable) \ X(FOLDER "/p2dRingObservableDeltaThetaVsMass", deltaThetaJet, v0LambdaLikeMass, ringObservable) \ + /* 2D Profile: Ring vs Eta variables */ \ + X(FOLDER "/EtaDependence/hCounterEtaLambdaMinusEtaJet", v0eta - leadingJetEta) \ + X(FOLDER "/EtaDependence/pRingObservableEtaLambdaMinusEtaJet", v0eta - leadingJetEta, ringObservable) \ + X(FOLDER "/EtaDependence/p2dRingObservableEtaLambdaVsEtaJet", v0eta, leadingJetEta, ringObservable) \ + X(FOLDER "/EtaDependence/h2dCounterEtaLambdaVsEtaJet", v0eta, leadingJetEta) \ + X(FOLDER "/EtaDependence/p2dRingObservableEtaLambdaVsEtaJet_FineBins", v0eta, leadingJetEta, ringObservable) \ + X(FOLDER "/EtaDependence/h2dCounterEtaLambdaVsEtaJet_FineBins", v0eta, leadingJetEta) \ /* 3D Profiles: Angle vs Mass vs Lambda pT */ \ X(FOLDER "/p3dRingObservableDeltaPhiVsMassVsLambdaPt", deltaPhiJet, v0LambdaLikeMass, v0pt, ringObservable) \ X(FOLDER "/p3dRingObservableDeltaThetaVsMassVsLambdaPt", deltaThetaJet, v0LambdaLikeMass, v0pt, ringObservable) \ @@ -129,67 +162,224 @@ enum CentEstimator { /* 3D Profiles: Angle vs Mass vs Centrality */ \ X(FOLDER "/p3dRingObservableDeltaPhiVsMassVsCent", deltaPhiJet, v0LambdaLikeMass, centrality, ringObservable) \ X(FOLDER "/p3dRingObservableDeltaThetaVsMassVsCent", deltaThetaJet, v0LambdaLikeMass, centrality, ringObservable) \ - X(FOLDER "/pRingIntVsCentrality", centrality, ringObservable) + X(FOLDER "/pRingVsCentrality", centrality, ringObservable) \ + X(FOLDER "/p2dRingObservableVsPxPy", v0px, v0py, ringObservable) \ + X(FOLDER "/p2dRingObservableVsPzPx", v0pz, v0px, ringObservable) // (TODO: add counters for regular TH2Ds about centrality) // For leading particle -#define RING_OBSERVABLE_LEADP_FILL_LIST(X, FOLDER) \ - X(FOLDER "/pRingObservableLeadPDeltaPhi", deltaPhiLeadP, ringObservableLeadP) \ - X(FOLDER "/pRingObservableLeadPDeltaTheta", deltaThetaLeadP, ringObservableLeadP) \ - X(FOLDER "/pRingObservableLeadPIntegrated", 0., ringObservableLeadP) \ - X(FOLDER "/pRingObservableLeadPLambdaPt", v0pt, ringObservableLeadP) +#define RING_OBSERVABLE_LEADP_FILL_LIST(X, FOLDER) \ + X(FOLDER "/QA/hDeltaPhiLeadP", deltaPhiLeadP) \ + X(FOLDER "/QA/hDeltaThetaLeadP", deltaThetaLeadP) \ + X(FOLDER "/QA/hPtLeadP", leadPPt) \ + X(FOLDER "/QA/hCosDeltaThetaLeadP", cosDeltaThetaLeadP) \ + X(FOLDER "/hRingObservableLeadPCounts", ringObservableLeadP) \ + X(FOLDER "/pRingObservableLeadPDeltaPhi", deltaPhiLeadP, ringObservableLeadP) \ + X(FOLDER "/pRingObservableLeadPDeltaTheta", deltaThetaLeadP, ringObservableLeadP) \ + X(FOLDER "/EtaDependence/pRingObservableEtaLambdaLeadP", v0eta, ringObservableLeadP) \ + X(FOLDER "/EtaDependence/pRingObservableEtaLeadP", leadPEta, ringObservableLeadP) \ + X(FOLDER "/pRingObservableLeadPIntegrated", 0., ringObservableLeadP) \ + X(FOLDER "/pRingObservableLeadPLambdaPt", v0pt, ringObservableLeadP) \ + X(FOLDER "/pRingObservableLeadPPVz", collisionPVz, ringObservableLeadP) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtLeadP", leadPPt, ringObservableLeadP) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtLeadPVsEtaLeadP", leadPPt, leadPEta, ringObservableLeadP) \ + X(FOLDER "/ProxyPtDependence/pRingVsPtLeadPVsEtaV0", leadPPt, v0eta, ringObservableLeadP) \ + X(FOLDER "/EtaDependence/p2dRingObservableEtaLambdaVsEtaLeadP", v0eta, leadPEta, ringObservableLeadP) \ + X(FOLDER "/EtaDependence/h2dCounterEtaLambdaVsEtaLeadP", v0eta, leadPEta) \ + X(FOLDER "/p2dRingObservableLeadPVsPxPy", v0px, v0py, ringObservableLeadP) \ + X(FOLDER "/p2dRingObservableLeadPVsPzPx", v0pz, v0px, ringObservableLeadP) + +// A macro that encapsulates all eta checks for leading particle and V0s, along with the fills +// Parameters: +// FOLDER -- histogram folder string (compile-time literal) +// LEADP_IS_POS -- bool: leadPEtaPos +// V0_IS_POS -- bool: lambdaEtaPos +#define RING_OBSERVABLE_LEADP_ETA_SPLIT_FILL_LIST(FOLDER, LEADP_IS_POS, V0_IS_POS) \ + do { \ + if (LEADP_IS_POS) { \ + /* leadP marginal: positive side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP", leadPPt, ringObservableLeadP) \ + if (V0_IS_POS) { \ + /* V0 marginal: positive side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_PosEtaV0", leadPPt, ringObservableLeadP) \ + /* Joint: (+leadP, +V0) */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP_PosEtaV0", leadPPt, ringObservableLeadP) \ + } else { \ + /* V0 marginal: negative side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_NegEtaV0", leadPPt, ringObservableLeadP) \ + /* Joint: (+leadP, -V0) */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP_NegEtaV0", leadPPt, ringObservableLeadP) \ + } \ + } else { \ + /* leadP marginal: negative side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP", leadPPt, ringObservableLeadP) \ + if (V0_IS_POS) { \ + /* V0 marginal: positive side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_PosEtaV0", leadPPt, ringObservableLeadP) \ + /* Joint: (-leadP, +V0) */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP_PosEtaV0", leadPPt, ringObservableLeadP) \ + } else { \ + /* V0 marginal: negative side */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_NegEtaV0", leadPPt, ringObservableLeadP) \ + /* Joint: (-leadP, -V0) */ \ + APPLY_HISTO_FILL(FOLDER "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP_NegEtaV0", leadPPt, ringObservableLeadP) \ + } \ + } \ + } while (0) // For subleading jet: -#define RING_OBSERVABLE_2NDJET_FILL_LIST(X, FOLDER) \ - X(FOLDER "/pRingObservable2ndJetDeltaPhi", deltaPhi2ndJet, ringObservable2ndJet) \ - X(FOLDER "/pRingObservable2ndJetDeltaTheta", deltaTheta2ndJet, ringObservable2ndJet) \ - X(FOLDER "/pRingObservable2ndJetIntegrated", 0., ringObservable2ndJet) \ - X(FOLDER "/pRingObservable2ndJetLambdaPt", v0pt, ringObservable2ndJet) +#define RING_OBSERVABLE_2NDJET_FILL_LIST(X, FOLDER) \ + X(FOLDER "/QA/hDeltaPhi2ndJet", deltaPhi2ndJet) \ + X(FOLDER "/QA/hDeltaTheta2ndJet", deltaTheta2ndJet) \ + X(FOLDER "/QA/hCosDeltaTheta2ndJet", cosDeltaTheta2ndJet) \ + X(FOLDER "/QA/hPt2ndJet", subleadingJetPt) \ + X(FOLDER "/hRingObservable2ndJetCounter", ringObservable2ndJet) \ + X(FOLDER "/pRingObservable2ndJetDeltaPhi", deltaPhi2ndJet, ringObservable2ndJet) \ + X(FOLDER "/pRingObservable2ndJetDeltaTheta", deltaTheta2ndJet, ringObservable2ndJet) \ + X(FOLDER "/EtaDependence/pRingObservableEtaLambda2ndJet", v0eta, ringObservable2ndJet) \ + X(FOLDER "/EtaDependence/pRingObservableEta2ndJet", subleadingJetEta, ringObservable2ndJet) \ + X(FOLDER "/pRingObservable2ndJetIntegrated", 0., ringObservable2ndJet) \ + X(FOLDER "/pRingObservable2ndJetLambdaPt", v0pt, ringObservable2ndJet) \ + X(FOLDER "/pRingObservableSubLeadPVz", collisionPVz, ringObservable2ndJet) \ + X(FOLDER "/ProxyPtDependence/pRingVsPt2ndJet", subleadingJetPt, ringObservable2ndJet) \ + X(FOLDER "/ProxyPtDependence/pRingVsPt2ndJetVsEta2ndJet", subleadingJetPt, subleadingJetEta, ringObservable2ndJet) \ + X(FOLDER "/ProxyPtDependence/pRingVsPt2ndJetVsEtaV0", subleadingJetPt, v0eta, ringObservable2ndJet) \ + X(FOLDER "/EtaDependence/p2dRingObservableEtaLambdaVsEta2ndJet", v0eta, subleadingJetEta, ringObservable2ndJet) \ + X(FOLDER "/EtaDependence/h2dCounterEtaLambdaVsEta2ndJet", v0eta, subleadingJetEta) #define POLARIZATION_PROFILE_FILL_LIST(X, FOLDER) \ /* =============================== */ \ /* 1D TProfiles vs v0phi */ \ /* =============================== */ \ - X(FOLDER "/QA/pPxStarPhi", v0phiToFillHists, PolStarX) \ - X(FOLDER "/QA/pPyStarPhi", v0phiToFillHists, PolStarY) \ - X(FOLDER "/QA/pPzStarPhi", v0phiToFillHists, PolStarZ) \ + X(FOLDER "/QA/pPxStarPhi", v0phiToFillHists, polStarX) \ + X(FOLDER "/QA/pPyStarPhi", v0phiToFillHists, polStarY) \ + X(FOLDER "/QA/pPzStarPhi", v0phiToFillHists, polStarZ) \ /* =============================== */ \ /* 1D TProfiles vs DeltaPhi_jet */ \ /* =============================== */ \ - X(FOLDER "/QA/pPxStarDeltaPhi", deltaPhiJet, PolStarX) \ - X(FOLDER "/QA/pPyStarDeltaPhi", deltaPhiJet, PolStarY) \ - X(FOLDER "/QA/pPzStarDeltaPhi", deltaPhiJet, PolStarZ) \ + X(FOLDER "/QA/pPxStarDeltaPhi", deltaPhiJet, polStarX) \ + X(FOLDER "/QA/pPyStarDeltaPhi", deltaPhiJet, polStarY) \ + X(FOLDER "/QA/pPzStarDeltaPhi", deltaPhiJet, polStarZ) \ /* =============================== */ \ /* 2D TProfiles vs DeltaPhi_jet and Lambda pT */ \ /* =============================== */ \ - X(FOLDER "/QA/p2dPxStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, PolStarX) \ - X(FOLDER "/QA/p2dPyStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, PolStarY) \ - X(FOLDER "/QA/p2dPzStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, PolStarZ) + X(FOLDER "/QA/p2dPxStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, polStarX) \ + X(FOLDER "/QA/p2dPyStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, polStarY) \ + X(FOLDER "/QA/p2dPzStarDeltaPhiVsLambdaPt", deltaPhiJet, v0pt, polStarZ) \ + /* 2D vector-field profiles vs Lambda momentum plane */ \ + X(FOLDER "/QA/p2dPxStar_vsPxPy", v0px, v0py, polStarX) \ + X(FOLDER "/QA/p2dPyStar_vsPxPy", v0px, v0py, polStarY) \ + X(FOLDER "/QA/p2dPzStar_vsPxPy", v0px, v0py, polStarZ) \ + X(FOLDER "/QA/p2dPxStar_vsPzPx", v0pz, v0px, polStarX) \ + X(FOLDER "/QA/p2dPyStar_vsPzPx", v0pz, v0px, polStarY) \ + X(FOLDER "/QA/p2dPzStar_vsPzPx", v0pz, v0px, polStarZ) // Apply the macros (notice I had to include the semicolon (";") after the function, so you don't need to // write that when calling this APPLY_HISTO_FILL. The code will look weird, but without this the compiler // would not know to end each statement with a semicolon): #define APPLY_HISTO_FILL(NAME, ...) histos.fill(HIST(NAME), __VA_ARGS__); -struct lambdajetpolarizationionsderived { +// Delta Method Fill Lists +#define DELTA_INTEGRATED_FILL_LIST(X, FOLDER, r, n) \ + X(FOLDER "/DeltaMethod/hIntegrated", 0.5, r) \ + X(FOLDER "/DeltaMethod/hIntegrated", 1.5, (double)(n)) \ + X(FOLDER "/DeltaMethod/hIntegrated", 2.5, (r) * (r)) \ + X(FOLDER "/DeltaMethod/hIntegrated", 3.5, (double)((n) * (n))) \ + X(FOLDER "/DeltaMethod/hIntegrated", 4.5, (r) * (n)) + +#define DELTA_2D_FILL_LIST(X, FOLDER, HIST_NAME, center, r, n) \ + X(FOLDER "/DeltaMethod/" HIST_NAME, center, 0.5, r) \ + X(FOLDER "/DeltaMethod/" HIST_NAME, center, 1.5, (double)(n)) \ + X(FOLDER "/DeltaMethod/" HIST_NAME, center, 2.5, (r) * (r)) \ + X(FOLDER "/DeltaMethod/" HIST_NAME, center, 3.5, (double)((n) * (n))) \ + X(FOLDER "/DeltaMethod/" HIST_NAME, center, 4.5, (r) * (n)) + +// Master flush macro to dump an event tracker into the histograms: +#define FLUSH_DELTA_TRACKER(FOLDER, TRACKER, AXIS_PT, AXIS_MASS, AXIS_DTHETA) \ + if ((TRACKER).nInt > 0) { \ + DELTA_INTEGRATED_FILL_LIST(APPLY_HISTO_FILL, FOLDER, (TRACKER).rInt, (TRACKER).nInt) \ + } \ + for (size_t bin = 0; bin < (TRACKER).rPt.size(); ++bin) { \ + int nVal = (TRACKER).nPt[bin]; \ + if (nVal == 0) \ + continue; \ + double rVal = (TRACKER).rPt[bin]; \ + double center = (AXIS_PT)->GetBinCenter(bin); \ + DELTA_2D_FILL_LIST(APPLY_HISTO_FILL, FOLDER, "h2dLambdaPtVsDeltaComp", center, rVal, nVal) \ + } \ + for (size_t bin = 0; bin < (TRACKER).rMass.size(); ++bin) { \ + int nVal = (TRACKER).nMass[bin]; \ + if (nVal == 0) \ + continue; \ + double rVal = (TRACKER).rMass[bin]; \ + double center = (AXIS_MASS)->GetBinCenter(bin); \ + DELTA_2D_FILL_LIST(APPLY_HISTO_FILL, FOLDER, "h2dMassVsDeltaComp", center, rVal, nVal) \ + } \ + for (size_t bin = 0; bin < (TRACKER).rDtheta.size(); ++bin) { \ + int nVal = (TRACKER).nDtheta[bin]; \ + if (nVal == 0) \ + continue; \ + double rVal = (TRACKER).rDtheta[bin]; \ + double center = (AXIS_DTHETA)->GetBinCenter(bin); \ + DELTA_2D_FILL_LIST(APPLY_HISTO_FILL, FOLDER, "h2dDeltaThetaVsDeltaComp", center, rVal, nVal) \ + } +struct lambdajetpolarizationionsderived { // Define histogram registries: HistogramRegistry histos{"Histos", {}, OutputObjHandlingPolicy::AnalysisObject}; - // Master analysis switches + // Master analysis switches: Configurable analyseLambda{"analyseLambda", true, "process Lambda-like candidates"}; Configurable analyseAntiLambda{"analyseAntiLambda", false, "process AntiLambda-like candidates"}; - Configurable doPPAnalysis{"doPPAnalysis", false, "if in pp, set to true. Default is HI"}; + Configurable analyseMagField{"analyseMagField", true, "analyse efficiency effects wrt magnetic field"}; // Older DerivedData lacks magField. "if constexpr (requires { collision.magField(); })" would only see the current header definition, so need a flag for retro-comp. + // Configurable doPPAnalysis{"doPPAnalysis", false, "if in pp, set to true. Default is HI"}; + Configurable doJetProxy5dQA{"doJetProxy5dQA", false, "generates expensive THnSparse histograms for joint distribution QA of the jet proxies and collisions"}; + + // A very inexpensive "signal extraction" imitation based on v0InMassPeak bool: + // (Uses a mass interval to remove or include V0s from the final AnalysisResults to take advantage of existing post-processing codes) + Configurable excludeOutOfPeakQA{"excludeOutOfPeakQA", false, "removes all V0s outside an approximate +/- 5*sigma window from the mass peak"}; // A naive estimator of signal + Configurable excludeInPeakQA{"excludeInPeakQA", false, "uses only the V0s outside an approximate +/- 5*sigma window from the mass peak"}; // A naive estimator of background + + // Per-family histogram switches: + // (Each family books >100 histograms, so it is necessary to keep some of these switches off to avoid the HistogramRegistry limit) + struct : ConfigurableGroup { + std::string prefix = "familySwitches"; // JSON group name + Configurable doFamilyRing{"doFamilyRing", true, "Book and fill the 'Ring' family (no additional cuts). Keep this on for most passes."}; + Configurable doFamilyRingKinematicCuts{"doFamilyRingKinematicCuts", false, "Book and fill the 'RingKinematicCuts' family (Lambda kinematic cuts applied)."}; + Configurable doFamilyJetKinematicCuts{"doFamilyJetKinematicCuts", true, "Book and fill the 'JetKinematicCuts' family (jet kinematic cuts applied)."}; + Configurable doFamilyJetAndLambdaKinematicCuts{"doFamilyJetAndLambdaKinematicCuts", false, "Book and fill the 'JetAndLambdaKinematicCuts' family (both cuts applied)."}; + } familySwitches; + + // QA switches: + struct : ConfigurableGroup { + std::string prefix = "qaSwitches"; // JSON group name + Configurable doFakePolDiagnosticsQA{"doFakePolDiagnosticsQA", true, "Book and fill the EtaStudy/ and HelicityEfficiencyQA/ folders."}; // The largest per-V0 fill cost in this task + } qaSwitches; // Centrality: Configurable centralityEstimator{"centralityEstimator", kCentFT0M, "Run 3 centrality estimator (0:CentFT0C, 1:CentFT0M, 2:CentFV0A)"}; // Default is FT0M + Configurable maxZVtxPosition{"maxZVtxPosition", 10., "max Z vtx position [cm]"}; // An additional post-processing cut after derived data was written. Same default as lambdaJetPolarizationIons.cxx producer - // QAs that purposefully break the analysis + // QAs that purposefully "break" the analysis // -- All of these tests should give us zero signal if the source is truly Lambda Polarization from vortices - Configurable forcePolSignQA{"forcePolSignQA", false, "force antiLambda decay constant to be positive: should kill all the signal, if any. For QA"}; - Configurable forcePerpToJet{"forcePerpToJet", false, "force jet direction to be perpendicular to jet estimator. For QA"}; - Configurable forceJetDirectionSmudge{"forceJetDirectionSmudge", false, "fluctuate jet direction by 10% of R around original axis. For QA (tests sensibility)"}; - Configurable jetRForSmuding{"jetRForSmuding", 0.4, "QA quantity: the chosen R scale for the jet direction smudge"}; + struct : ConfigurableGroup { + std::string prefix = "fakePolSwitches"; // JSON group name + Configurable forcePolSignQA{"forcePolSignQA", false, "force antiLambda decay constant to be positive: should kill all the signal, if any. For QA"}; + Configurable forcePerpToJet{"forcePerpToJet", false, "force jet direction to be perpendicular to jet estimator. For QA"}; + Configurable forceJetDirectionSmudge{"forceJetDirectionSmudge", false, "fluctuate jet direction by 10% of R around original axis. For QA (tests sensibility)"}; + Configurable forceRandJet{"forceRandJet", false, "makes jet direction random. A QA for AEE fake signal and its removal"}; + Configurable forcePreviousJet{"forcePreviousJet", false, "uses previous event's jet direction instead of a random sample. A baseline for fake signal removal"}; + Configurable forceDatalikeJet{"forceDatalikeJet", false, "a compromise between forceRandJet and forcePreviousJet. Parameterized distribution from data"}; + Configurable doMixedEventProxies{"doMixedEventProxies", false, "mix leadP/leadJet/subJet directions between events using (proxy pt, Zvtx, centrality) bins -- three independent mixings, one per proxy"}; + Configurable mixedEventWindowSize{"mixedEventWindowSize", 10, "number of neighbours for doMixedEventProxies: how many similar collisions stay eligible as mixing partners at once (shared by all 3 proxies)."}; + Configurable nProxyResamples{"nProxyResamples", 1, "The amount of resamplings of jet direction per event. Use ONLY for forceRandJet and forceDatalikeJet"}; + } fakePolSwitches; + + // Configurable jetRForSmudging{"jetRForSmudging", 0.4, "QA quantity: the chosen R scale for the jet direction smudge"}; // Superseeded by jetR: kept the same scale in analysis and QA + Configurable jetR{"jetR", 0.4f, "Radius of the jet"}; // Provide manually, please. + Configurable minLeadParticlePt{"minLeadParticlePt", 2.0f, "Minimum Pt for a lead track to be considered a valid proxy for a jet (may be more restrictive than TableProducer)"}; + Configurable minLeadJetPt{"minLeadJetPt", 10.0f, "Minimum Pt for leading jet to be considered valid (may be more restrictive than TableProducer)"}; + Configurable minSubLeadJetPt{"minSubLeadJetPt", 5.0f, "Minimum Pt for subleading jet to be considered valid (may be more restrictive than TableProducer)"}; ///////////////////////// // Configurable blocks: @@ -197,14 +387,44 @@ struct lambdajetpolarizationionsderived { struct : ConfigurableGroup { std::string prefix = "axisConfigurations"; // JSON group name ConfigurableAxis axisPt{"axisPt", {VARIABLE_WIDTH, 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.6f, 1.7f, 1.8f, 1.9f, 2.0f, 2.2f, 2.4f, 2.6f, 2.8f, 3.0f, 3.2f, 3.4f, 3.6f, 3.8f, 4.0f, 4.4f, 4.8f, 5.2f, 5.6f, 6.0f, 6.5f, 7.0f, 7.5f, 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 13.0f, 14.0f, 15.0f, 17.0f, 19.0f, 21.0f, 23.0f, 25.0f, 30.0f, 35.0f, 40.0f, 50.0f}, "pt axis for analysis"}; - ConfigurableAxis axisPtCoarseQA{"axisPtCoarse", {VARIABLE_WIDTH, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 7.0f, 10.0f, 15.0f}, "pt axis for QA"}; - ConfigurableAxis axisLambdaMass{"axisLambdaMass", {450, 1.08f, 1.15f}, "Lambda mass in GeV/c"}; // Default is {200, 1.101f, 1.131f} + ConfigurableAxis axisPtCoarseQA{"axisPtCoarseQA", {VARIABLE_WIDTH, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 7.0f, 10.0f, 15.0f}, "pt axis for QA"}; + ConfigurableAxis axisLambdaMass{"axisLambdaMass", {450, 1.08f, 1.15f}, "#Lambda mass in GeV/c"}; // Default is {200, 1.101f, 1.131f} + + // Symmetric momentum-plane axes for the vector-field / ring 2D profiles: + ConfigurableAxis axisLambdaPx{"axisLambdaPx", {40, -3.0f, 3.0f}, "#Lambda p_{x} (GeV/c)"}; + ConfigurableAxis axisLambdaPy{"axisLambdaPy", {40, -3.0f, 3.0f}, "#Lambda p_{y} (GeV/c)"}; + ConfigurableAxis axisLambdaPz{"axisLambdaPz", {40, -4.0f, 4.0f}, "#Lambda p_{z} (GeV/c)"}; + + // Event properties: + ConfigurableAxis axisPVz{"axisPVz", {60, -15.0f, +15.0f}, "Primary Vertex Z [cm]"}; // Jet axes: - ConfigurableAxis axisLeadingParticlePt{"axisLeadingParticlePt", {100, 0.f, 200.f}, "Leading particle p_{T} (GeV/c)"}; // Simpler version! - ConfigurableAxis axisJetPt{"axisJetPt", {50, 0.f, 200.f}, "Jet p_{t} (GeV)"}; + // ConfigurableAxis axisLeadingParticlePt{"axisLeadingParticlePt", {100, 0.f, 200.f}, "Leading particle p_{T} (GeV/c)"}; // Simpler version! + // ConfigurableAxis axisJetPt{"axisJetPt", {50, 0.f, 200.f}, "Jet p_{t} (GeV)"}; + ConfigurableAxis axisJetPt{ + "axisJetPt", + {VARIABLE_WIDTH, + 0, 2, 4, 6, 8, 10, // 2 GeV bins + 15, 20, // 5 GeV bins + 30, 40, // 10 GeV bins + 60, 80, // 20 GeV bins + 120, 160, 200}, // 40 GeV bins + "Jet p_{T} (GeV)"}; + ConfigurableAxis axisJetPtSigExtract{"axisJetPtSigExtract", {VARIABLE_WIDTH, 0, 5, 10, 12, 16, 20, 25, 30, 35, 40, 60, 100, 200}, "Jet p_{t} (GeV)"}; + ConfigurableAxis axisEta{"axisEta", {50, -1.0f, 1.0f}, "#eta"}; + ConfigurableAxis axisEtaCoarse{"axisEtaCoarse", {20, -0.9f, 0.9f}, "#eta coarse axis"}; + ConfigurableAxis axisV0Eta{"axisV0Eta", {75, -1.5f, 1.5f}, "V0 #eta"}; // An axis for V0 eta, which can go up to 1.5 given standard producer selections + ConfigurableAxis axisV0EtaCoarse{"axisV0EtaCoarse", {32, -1.5f, 1.5f}, "V0 #eta coarse"}; + ConfigurableAxis axisDeltaEtaCoarse{"axisDeltaEtaCoarse", {40, -1.8f, 1.8f}, "#Delta#eta coarse axis"}; ConfigurableAxis axisDeltaTheta{"axisDeltaTheta", {40, 0, constants::math::PI}, "#Delta #theta_{jet}"}; + ConfigurableAxis axisCosTheta{"axisCosTheta", {50, -1, 1}, "cos(#theta)"}; + ConfigurableAxis axisPhi{"axisPhi", {40, 0., constants::math::TwoPI}, "#varphi"}; ConfigurableAxis axisDeltaPhi{"axisDeltaPhi", {40, -constants::math::PI, constants::math::PI}, "#Delta #phi_{jet}"}; + ConfigurableAxis axisRingCounts{"axisRingCounts", {90, -4.5, 4.5}, "<#it{R}>"}; + ConfigurableAxis axisDeltaCollisionIndex{"axisDeltaCollisionIndex", {200, -0.5f, 199.5f}, "#Delta collision index"}; // Always positive: SameKindPair pairs strictly upper + + ConfigurableAxis axisDCAdau{"axisDCAdau", {10, 0., 2.0}, "DCA V0 daughters (cm)"}; + ConfigurableAxis axisDCAdauPV{"axisDCAdauPV", {10, 0., 1.2}, "DCA dauPV (cm)"}; // v0Selections.dcav0dau's default maximum is 1.2f in the TableProducer // Coarser axes for signal extraction: ConfigurableAxis axisPtSigExtract{"axisPtSigExtract", {VARIABLE_WIDTH, 0.0f, 0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f, 2.5f, 3.0f, 4.0f, 6.0f, 8.0f, 10.0f, 15.0f, 20.0f, 30.0f, 50.0f}, "pt axis for signal extraction"}; @@ -223,12 +443,15 @@ struct lambdajetpolarizationionsderived { 1.1248, 1.1288, 1.1328, 1.1368, 1.1408, 1.1448, 1.1488}, "Lambda mass in GeV/c"}; - ConfigurableAxis axisLeadingParticlePtSigExtract{"axisLeadingParticlePtSigExtract", {VARIABLE_WIDTH, 0, 4, 8, 12, 16, 20, 25, 30, 35, 40, 60, 100, 200}, "Leading particle p_{T} (GeV/c)"}; // Simpler version! - ConfigurableAxis axisJetPtSigExtract{"axisJetPtSigExtract", {VARIABLE_WIDTH, 0, 5, 10, 12, 16, 20, 25, 30, 35, 40, 60, 100, 200}, "Jet p_{t} (GeV)"}; + // ConfigurableAxis axisLeadingParticlePtSigExtract{"axisLeadingParticlePtSigExtract", {VARIABLE_WIDTH, 0, 4, 8, 12, 16, 20, 25, 30, 35, 40, 60, 100, 200}, "Leading particle p_{T} (GeV/c)"}; // Simpler version! // (TODO: add a lambdaPt axis that is pre-selected only on the 0.5 to 1.5 Pt region for the Ring observable with lambda cuts to not store a huge histogram with empty bins by construction) - ConfigurableAxis axisCentrality{"axisCentrality", {VARIABLE_WIDTH, 0.0f, 5.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f, 70.0f, 80.0f, 90.0f, 100.0f}, "Centrality"}; + // ConfigurableAxis axisCentrality{"axisCentrality", {VARIABLE_WIDTH, 0.0f, 5.0f, 10.0f, 20.0f, 30.0f, 40.0f, 50.0f, 60.0f, 70.0f, 80.0f, 90.0f, 100.0f}, "Centrality"}; + ConfigurableAxis axisCentrality{"axisCentrality", {VARIABLE_WIDTH, 0.0f, 20.0f, 50.0f, 100.0f}, "Centrality"}; + + // For the delta method error propagation (slightly better than just SEM error propagation with TProfiles): + ConfigurableAxis axisDeltaComponents{"axisDeltaComponents", {5, 0.0, 5.0}, "0: r_k, 1: n_k, 2: r_k^2, 3: n_k^2, 4: r_k*n_k"}; } axisConfigurations; // Helper functions: @@ -244,8 +467,76 @@ struct lambdajetpolarizationionsderived { return phi; } + // A small tracker struct for convenience -- Accumulates values for the Delta Method error estimator: + struct EventDeltaTracker { + double rInt = 0.0; // Ring accumulator + int nInt = 0; // Counts accumulator + std::vector rPt, rMass, rDtheta; + std::vector nPt, nMass, nDtheta; + + /// \brief Resizes every accumulator. Size includes ROOT's under/overflow bins, so the indices + /// returned by TAxis::FindBin() (0 .. nBins+1) can be used directly for dereferencing here. + void resize(int nBinsPt, int nBinsMass, int nBinsDTheta) + { + rPt.assign(nBinsPt + 2, 0.0); + rMass.assign(nBinsMass + 2, 0.0); + rDtheta.assign(nBinsDTheta + 2, 0.0); + nPt.assign(nBinsPt + 2, 0); + nMass.assign(nBinsMass + 2, 0); + nDtheta.assign(nBinsDTheta + 2, 0); + } + + void reset() + { + rInt = 0.0; + nInt = 0; + std::fill(rPt.begin(), rPt.end(), 0.0); + std::fill(rMass.begin(), rMass.end(), 0.0); + std::fill(rDtheta.begin(), rDtheta.end(), 0.0); + std::fill(nPt.begin(), nPt.end(), 0); + std::fill(nMass.begin(), nMass.end(), 0); + std::fill(nDtheta.begin(), nDtheta.end(), 0); + } + + void addV0(double ringObs, int binPt, int binMass, int binDTheta) + { + rInt += ringObs; + rPt[binPt] += ringObs; + rMass[binMass] += ringObs; + rDtheta[binDTheta] += ringObs; + nInt += 1; + nPt[binPt] += 1; + nMass[binMass] += 1; + nDtheta[binDTheta] += 1; + } + }; + + // Allocating one tracker per family so the accumulators are allocated only once: + EventDeltaTracker trackRing, trackRingKinCuts, trackJetKinCuts, trackJetLambdaKinCuts; + + // Axis pointers for Delta Method binning (fetched once in init, declared once here) + TAxis* mAxisPt = nullptr; + TAxis* mAxisMass = nullptr; + TAxis* mAxisDTheta = nullptr; + void init(InitContext const&) { + // Configuration validation: + // These combinations would not crash otherwise, so they are rejected at init() time. + const int nDistortionsOn = static_cast(fakePolSwitches.forcePerpToJet) + static_cast(fakePolSwitches.forceJetDirectionSmudge) + + static_cast(fakePolSwitches.forceRandJet) + static_cast(fakePolSwitches.forcePreviousJet) + + static_cast(fakePolSwitches.forceDatalikeJet) + static_cast(fakePolSwitches.doMixedEventProxies); + if (nDistortionsOn > 1) // applyProxyDistortion() is an if/else chain, so extra switches are silently ignored + LOG(fatal) << "fakePolSwitches: " << nDistortionsOn << " proxy distortions enabled at once. They are mutually exclusive -- " + << "applyProxyDistortion() would apply only the first and silently drop the rest."; + if (fakePolSwitches.nProxyResamples > 1 && (fakePolSwitches.forcePreviousJet || fakePolSwitches.doMixedEventProxies)) + LOG(fatal) << "fakePolSwitches: nProxyResamples > 1 is only meaningful for forceRandJet/forceDatalikeJet. " + << "Previous-jet/Mixed-Event proxies do not change between resamplings, so every extra pass would double-count the same proxy."; + if (excludeOutOfPeakQA && excludeInPeakQA) // Complementary selections + LOG(fatal) << "excludeOutOfPeakQA and excludeInPeakQA are complementary: enabling both rejects every V0."; + if (!familySwitches.doFamilyRing) // Todo: think of a smarter way of handling the axis getters for the DeltaMethod + LOG(fatal) << "doFamilyRing must be on: the Delta Method accumulators take their binning from the Ring/ histograms."; + // Ring observable histograms: // Helper to register one full histogram family (kinematic cut variation of ring observable) auto addRingObservableFamily = [&](const std::string& folder) { @@ -253,35 +544,67 @@ struct lambdajetpolarizationionsderived { // QA histograms: angle and pT distributions // (No mass dependency -- useful to check kinematic sculpting from cuts) // =============================== - histos.add((folder + "/QA/hDeltaPhi").c_str(), "hDeltaPhi", kTH1D, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/hDeltaTheta").c_str(), "hDeltaTheta", kTH1D, {axisConfigurations.axisDeltaTheta}); - histos.add((folder + "/QA/hIntegrated").c_str(), "hIntegrated", kTH1D, {{1, -0.5, 0.5}}); + histos.add((folder + "/QA/hDeltaPhi").c_str(), "#Delta#varphi_{jet};#Delta#varphi_{jet};Counts", kTH1D, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/hDeltaPhiVsDeltaEta").c_str(), "#Delta#varphi_{jet};#Delta#varphi_{jet}; #eta_{#Lambda}-#eta_{Jet};Counts", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDeltaEtaCoarse}); + histos.add((folder + "/QA/hDeltaPhiVsLeadJetPhi").c_str(), "#Delta#varphi_{jet};#varphi_{Jet};#varphi_{Jet};Counts", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/hDeltaTheta").c_str(), "#Delta#theta_{jet};#Delta#theta_{jet};Counts", kTH1D, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/QA/hCosDeltaTheta").c_str(), "cos(#Delta#theta_{jet});cos(#Delta#theta_{jet});Counts", kTH1D, {axisConfigurations.axisCosTheta}); // Should actually be flat due to the geometry + histos.add((folder + "/QA/hIntegrated").c_str(), "Integrated counts; ;Counts", kTH1D, {{1, -0.5, 0.5}}); + + histos.add((folder + "/QA/hDeltaPhiLeadP").c_str(), "#Delta#varphi_{LeadP};#Delta#varphi_{LeadP};Counts", kTH1D, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/hDeltaThetaLeadP").c_str(), "#Delta#theta_{LeadP};#Delta#theta_{LeadP};Counts", kTH1D, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/QA/hCosDeltaThetaLeadP").c_str(), "cos(#Delta#theta_{LeadP});cos(#Delta#theta_{LeadP});Counts", kTH1D, {axisConfigurations.axisCosTheta}); // Should actually be flat due to the geometry + histos.add((folder + "/QA/hDeltaPhi2ndJet").c_str(), "#Delta#varphi_{SubJet};#Delta#varphi_{SubJet};Counts", kTH1D, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/hDeltaTheta2ndJet").c_str(), "#Delta#theta_{SubJet};#Delta#theta_{SubJet};Counts", kTH1D, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/QA/hCosDeltaTheta2ndJet").c_str(), "cos(#Delta#theta_{SubJet});cos(#Delta#theta_{SubJet});Counts", kTH1D, {axisConfigurations.axisCosTheta}); // Should actually be flat due to the geometry + // =============================== // Lambda pT dependence // =============================== - histos.add((folder + "/QA/hLambdaPt").c_str(), "hLambdaPt", kTH1D, {axisConfigurations.axisPt}); - histos.add((folder + "/QA/h2dDeltaPhiVsLambdaPt").c_str(), "h2dDeltaPhiVsLambdaPt", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPt}); - histos.add((folder + "/QA/h2dDeltaThetaVsLambdaPt").c_str(), "h2dDeltaThetaVsLambdaPt", kTH2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisPt}); + histos.add((folder + "/QA/hLambdaPt").c_str(), "#Lambda #it{p}_{T};#it{p}_{T}^{#Lambda} (GeV/c);Counts", kTH1D, {axisConfigurations.axisPt}); + histos.add((folder + "/QA/h2dDeltaPhiVsLambdaPt").c_str(), "#Delta#varphi_{jet} vs #Lambda #it{p}_{T};#Delta#varphi_{jet};#it{p}_{T}^{#Lambda} (GeV/c)", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPt}); + histos.add((folder + "/QA/h2dDeltaThetaVsLambdaPt").c_str(), "#Delta#theta_{jet} vs #Lambda #it{p}_{T};#Delta#theta_{jet};#it{p}_{T}^{#Lambda} (GeV/c)", kTH2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisPt}); // =============================== // Polarization observable QAs // (not Ring: actual polarization!) // =============================== - // Will implement these as TProfiles, as polarization is also a measure like P_\Lambda = (3/\alpha_\Lambda) * , so the error is similar + // Will implement these as TProfiles, as polarization is also a measure like P_Lambda = (3/\alpha_Lambda) * , so the error is similar // =============================== // 1D TProfiles // =============================== - histos.add((folder + "/QA/pPxStarPhi").c_str(), "pPxStarPhi;#varphi_{#Lambda};_{x}", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/pPyStarPhi").c_str(), "pPyStarPhi;#varphi_{#Lambda};_{y}", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/pPzStarPhi").c_str(), "pPzStarPhi;#varphi_{#Lambda};_{z}", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/pPxStarDeltaPhi").c_str(), "pPxStarDeltaPhi;#Delta#varphi_{jet};_{x}", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/pPyStarDeltaPhi").c_str(), "pPyStarDeltaPhi;#Delta#varphi_{jet};_{y}", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/QA/pPzStarDeltaPhi").c_str(), "pPzStarDeltaPhi;#Delta#varphi_{jet};_{z}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPxStarPhi").c_str(), "_{x} vs #varphi_{#Lambda};#varphi_{#Lambda};_{x}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPyStarPhi").c_str(), "_{y} vs #varphi_{#Lambda};#varphi_{#Lambda};_{y}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPzStarPhi").c_str(), "_{z} vs #varphi_{#Lambda};#varphi_{#Lambda};_{z}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPxStarDeltaPhi").c_str(), "_{x} vs #Delta#varphi_{jet};#Delta#varphi_{jet};_{x}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPyStarDeltaPhi").c_str(), "_{y} vs #Delta#varphi_{jet};#Delta#varphi_{jet};_{y}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/QA/pPzStarDeltaPhi").c_str(), "_{z} vs #Delta#varphi_{jet};#Delta#varphi_{jet};_{z}", kTProfile, {axisConfigurations.axisDeltaPhi}); // =============================== // 2D TProfiles (Lambda correlations) // =============================== - histos.add((folder + "/QA/p2dPxStarDeltaPhiVsLambdaPt").c_str(), "p2dPxStarDeltaPhiVsLambdaPt;#Delta#varphi_{jet};#it{p}_{T}^{#Lambda};_{x}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); - histos.add((folder + "/QA/p2dPyStarDeltaPhiVsLambdaPt").c_str(), "p2dPyStarDeltaPhiVsLambdaPt;#Delta#varphi_{jet};#it{p}_{T}^{#Lambda};_{y}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); - histos.add((folder + "/QA/p2dPzStarDeltaPhiVsLambdaPt").c_str(), "p2dPzStarDeltaPhiVsLambdaPt;#Delta#varphi_{jet};#it{p}_{T}^{#Lambda};_{z}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/QA/p2dPxStarDeltaPhiVsLambdaPt").c_str(), "_{x} vs #Delta#varphi_{jet} vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};#it{p}_{T}^{#Lambda} (GeV/c);_{x}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/QA/p2dPyStarDeltaPhiVsLambdaPt").c_str(), "_{y} vs #Delta#varphi_{jet} vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};#it{p}_{T}^{#Lambda} (GeV/c);_{y}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/QA/p2dPzStarDeltaPhiVsLambdaPt").c_str(), "_{z} vs #Delta#varphi_{jet} vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};#it{p}_{T}^{#Lambda} (GeV/c);_{z}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPtSigExtract}); + + // =============================== + // Vector-field profiles: vs Lambda momentum plane: + // (a companion code plots these as a vector field) + // =============================== + histos.add((folder + "/QA/p2dPxStar_vsPxPy").c_str(), "_{x} vs (p_{x}^{#Lambda},p_{y}^{#Lambda});p_{x}^{#Lambda} (GeV/c);p_{y}^{#Lambda} (GeV/c);_{x}", kTProfile2D, {axisConfigurations.axisLambdaPx, axisConfigurations.axisLambdaPy}); + histos.add((folder + "/QA/p2dPyStar_vsPxPy").c_str(), "_{y} vs (p_{x}^{#Lambda},p_{y}^{#Lambda});p_{x}^{#Lambda} (GeV/c);p_{y}^{#Lambda} (GeV/c);_{y}", kTProfile2D, {axisConfigurations.axisLambdaPx, axisConfigurations.axisLambdaPy}); + histos.add((folder + "/QA/p2dPzStar_vsPxPy").c_str(), "_{z} vs (p_{x}^{#Lambda},p_{y}^{#Lambda}) [colormap];p_{x}^{#Lambda} (GeV/c);p_{y}^{#Lambda} (GeV/c);_{z}", kTProfile2D, {axisConfigurations.axisLambdaPx, axisConfigurations.axisLambdaPy}); + + histos.add((folder + "/QA/p2dPxStar_vsPzPx").c_str(), "_{x} vs (p_{z}^{#Lambda},p_{x}^{#Lambda});p_{z}^{#Lambda} (GeV/c);p_{x}^{#Lambda} (GeV/c);_{x}", kTProfile2D, {axisConfigurations.axisLambdaPz, axisConfigurations.axisLambdaPx}); + histos.add((folder + "/QA/p2dPyStar_vsPzPx").c_str(), "_{y} vs (p_{z}^{#Lambda},p_{x}^{#Lambda}) [colormap];p_{z}^{#Lambda} (GeV/c);p_{x}^{#Lambda} (GeV/c);_{y}", kTProfile2D, {axisConfigurations.axisLambdaPz, axisConfigurations.axisLambdaPx}); + histos.add((folder + "/QA/p2dPzStar_vsPzPx").c_str(), "_{z} vs (p_{z}^{#Lambda},p_{x}^{#Lambda});p_{z}^{#Lambda} (GeV/c);p_{x}^{#Lambda} (GeV/c);_{z}", kTProfile2D, {axisConfigurations.axisLambdaPz, axisConfigurations.axisLambdaPx}); + + // =============================== + // Ring observable, single scalar 2D profile: + // =============================== + histos.add((folder + "/p2dRingObservableVsPxPy").c_str(), "<#it{R}> vs (p_{x}^{#Lambda},p_{y}^{#Lambda});p_{x}^{#Lambda} (GeV/c);p_{y}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaPx, axisConfigurations.axisLambdaPy}); + histos.add((folder + "/p2dRingObservableVsPzPx").c_str(), "<#it{R}> vs (p_{z}^{#Lambda},p_{x}^{#Lambda});p_{z}^{#Lambda} (GeV/c);p_{x}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaPz, axisConfigurations.axisLambdaPx}); + // For LeadP estimators: + histos.add((folder + "/p2dRingObservableLeadPVsPxPy").c_str(), "<#it{R}>_{LeadP} vs (p_{x}^{#Lambda},p_{y}^{#Lambda});p_{x}^{#Lambda} (GeV/c);p_{y}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaPx, axisConfigurations.axisLambdaPy}); + histos.add((folder + "/p2dRingObservableLeadPVsPzPx").c_str(), "<#it{R}>_{LeadP} vs (p_{z}^{#Lambda},p_{x}^{#Lambda});p_{z}^{#Lambda} (GeV/c);p_{x}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaPz, axisConfigurations.axisLambdaPx}); // TProfiles with correct error bars:: // -- TProfiles will handle the error estimate of the Ring Observable via the variance, even though @@ -294,85 +617,149 @@ struct lambdajetpolarizationionsderived { // =============================== // 1D TProfiles // =============================== - histos.add((folder + "/pRingObservableDeltaPhi").c_str(), "pRingObservableDeltaPhi;#Delta#varphi_{jet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/pRingObservableDeltaTheta").c_str(), "pRingObservableDeltaTheta;#Delta#theta_{jet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); - histos.add((folder + "/pRingObservableIntegrated").c_str(), "pRingObservableIntegrated; ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); - histos.add((folder + "/pRingObservableLambdaPt").c_str(), "pRingObservableLambdaPt;#it{p}_{T}^{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisPt}); + histos.add((folder + "/pRingObservableDeltaPhi").c_str(), "<#it{R}> vs #Delta#varphi_{jet};#Delta#varphi_{jet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); + // To see the actual distribution of counts in data (another differential-like shape of the distribution we are taking an average of): + histos.add((folder + "/hRingObservableCounts").c_str(), "Counts vs <#it{R}>_{jet};<#it{R}>; Counts", kTH1D, {axisConfigurations.axisRingCounts}); + histos.add((folder + "/pRingObservablePhiJet").c_str(), "<#it{R}> vs #varphi_{jet};#varphi_{jet};<#it{R}>", kTProfile, {axisConfigurations.axisPhi}); + histos.add((folder + "/pRingObservablePhiLambda").c_str(), "<#it{R}> vs #varphi_{#Lambda};#varphi_{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisPhi}); + histos.add((folder + "/pRingObservableDeltaTheta").c_str(), "<#it{R}> vs #Delta#theta_{jet};#Delta#theta_{jet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/pRingObservableIntegrated").c_str(), "Integrated <#it{R}>; ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); + histos.add((folder + "/pRingObservableLambdaPt").c_str(), "<#it{R}> vs #it{p}_{T}^{#Lambda};#it{p}_{T}^{#Lambda} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisPt}); + + // Ring vs Jet proxy pT: + histos.add((folder + "/ProxyPtDependence/pRingVsPtJet").c_str(), "<#it{R}> vs Jet #it{p}_{T};#it{p}_{T}^{Jet} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP").c_str(), "<#it{R}> vs LeadP #it{p}_{T};#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPt2ndJet").c_str(), "<#it{R}> vs SubJet #it{p}_{T};#it{p}_{T}^{SubJet} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + // And some counters to be aware of the amount of Lambdas (and jets) in each pT interval: + histos.add((folder + "/QA/hPtJet").c_str(), "Jet #it{p}_{T};#it{p}_{T}^{Jet} (GeV/c);Counts", kTH1D, {axisConfigurations.axisJetPt}); + histos.add((folder + "/QA/hPtLeadP").c_str(), "LeadP #it{p}_{T};#it{p}_{T}^{LeadP} (GeV/c);Counts", kTH1D, {axisConfigurations.axisJetPt}); + histos.add((folder + "/QA/hPt2ndJet").c_str(), "SubJet #it{p}_{T};#it{p}_{T}^{SubJet} (GeV/c);Counts", kTH1D, {axisConfigurations.axisJetPt}); + + // Splitting into positive and negative eta contributions: + histos.add((folder + "/ProxyPtDependence/pRingVsPtJetVsEtaJet").c_str(), "<#it{R}> vs Jet #it{p}_{T} vs #eta_{Jet};#it{p}_{T}^{Jet} (GeV/c);#eta_{Jet};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadPVsEtaLeadP").c_str(), "<#it{R}> vs LeadP #it{p}_{T} vs #eta_{LeadP};#it{p}_{T}^{LeadP} (GeV/c);#eta_{LeadP};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + histos.add((folder + "/ProxyPtDependence/pRingVsPt2ndJetVsEta2ndJet").c_str(), "<#it{R}> vs SubJet #it{p}_{T} vs #eta_{SubJet};#it{p}_{T}^{SubJet} (GeV/c);#eta_{SubJet};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + // For each Lambda's eta: + histos.add((folder + "/ProxyPtDependence/pRingVsPtJetVsEtaV0").c_str(), "<#it{R}> vs Jet #it{p}_{T} vs #eta_{V0};#it{p}_{T}^{Jet} (GeV/c);#eta_{V0};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadPVsEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} vs #eta_{V0};#it{p}_{T}^{LeadP} (GeV/c);#eta_{V0};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + histos.add((folder + "/ProxyPtDependence/pRingVsPt2ndJetVsEtaV0").c_str(), "<#it{R}> vs SubJet #it{p}_{T} vs #eta_{V0};#it{p}_{T}^{SubJet} (GeV/c);#eta_{V0};<#it{R}>", kTProfile2D, {axisConfigurations.axisJetPt, {2, -0.9, 0.9}}); + + // Rasterizing, only for LeadP the TProfile2D into two TProfile 1Ds (easier to draw with "same") + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}>0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}<0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + // V0 eta: + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_PosEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{V0}>0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_NegEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{V0}<0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + + // Looking at V0Eta and JetEta combinations (only for LeadP): + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP_PosEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}>0, #eta_{V0}>0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP_PosEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}<0, #eta_{V0}>0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_PosEtaLeadP_NegEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}>0, #eta_{V0}<0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + histos.add((folder + "/ProxyPtDependence/pRingVsPtLeadP_NegEtaLeadP_NegEtaV0").c_str(), "<#it{R}> vs LeadP #it{p}_{T} (#eta_{LeadP}<0, #eta_{V0}<0);#it{p}_{T}^{LeadP} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisJetPt}); + + // Understanding eta dependence seen in pRingEtaCuts: + histos.add((folder + "/EtaDependence/pRingObservableEtaLambda").c_str(), "<#it{R}> vs #eta_{#Lambda};#eta_{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisV0EtaCoarse}); + histos.add((folder + "/EtaDependence/pRingObservableEtaJet").c_str(), "<#it{R}> vs #eta_{Jet};#eta_{Jet};<#it{R}>", kTProfile, {axisConfigurations.axisEtaCoarse}); + + histos.add((folder + "/EtaDependence/pRingObservableEtaLambda2ndJet").c_str(), "<#it{R}> vs #eta_{#Lambda} (SubJet);#eta_{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisV0EtaCoarse}); + histos.add((folder + "/EtaDependence/pRingObservableEta2ndJet").c_str(), "<#it{R}> vs #eta_{SubJet};#eta_{SubJet};<#it{R}>", kTProfile, {axisConfigurations.axisEtaCoarse}); + + histos.add((folder + "/EtaDependence/pRingObservableEtaLambdaLeadP").c_str(), "<#it{R}> vs #eta_{#Lambda} (LeadP);#eta_{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisV0EtaCoarse}); + histos.add((folder + "/EtaDependence/pRingObservableEtaLeadP").c_str(), "<#it{R}> vs #eta_{LeadP};#eta_{LeadP};<#it{R}>", kTProfile, {axisConfigurations.axisEtaCoarse}); // For the leading particle: - histos.add((folder + "/pRingObservableLeadPDeltaPhi").c_str(), "pRingObservableLeadPDeltaPhi;#Delta#varphi_{leadP};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/pRingObservableLeadPDeltaTheta").c_str(), "pRingObservableLeadPDeltaTheta;#Delta#theta_{leadP};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); - histos.add((folder + "/pRingObservableLeadPIntegrated").c_str(), "pRingObservableLeadPIntegrated; ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); - histos.add((folder + "/pRingObservableLeadPLambdaPt").c_str(), "pRingObservableLeadPLambdaPt;#it{p}_{T}^{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisPt}); + histos.add((folder + "/hRingObservableLeadPCounts").c_str(), "Counts vs <#it{R}>_{LeadP};<#it{R}>; Counts", kTH1D, {axisConfigurations.axisRingCounts}); + histos.add((folder + "/pRingObservableLeadPDeltaPhi").c_str(), "<#it{R}> vs #Delta#varphi_{LeadP};#Delta#varphi_{LeadP};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/pRingObservableLeadPDeltaTheta").c_str(), "<#it{R}> vs #Delta#theta_{LeadP};#Delta#theta_{LeadP};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/pRingObservableLeadPIntegrated").c_str(), "Integrated <#it{R}> (LeadP); ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); + histos.add((folder + "/pRingObservableLeadPLambdaPt").c_str(), "<#it{R}> vs #it{p}_{T}^{#Lambda} (LeadP);#it{p}_{T}^{#Lambda} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisPt}); // For the second-to-leading jet: - histos.add((folder + "/pRingObservable2ndJetDeltaPhi").c_str(), "pRingObservable2ndJetDeltaPhi;#Delta#varphi_{2ndJet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); - histos.add((folder + "/pRingObservable2ndJetDeltaTheta").c_str(), "pRingObservable2ndJetDeltaTheta;#Delta#theta_{2ndJet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); - histos.add((folder + "/pRingObservable2ndJetIntegrated").c_str(), "pRingObservable2ndJetIntegrated; ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); - histos.add((folder + "/pRingObservable2ndJetLambdaPt").c_str(), "pRingObservable2ndJetLambdaPt;#it{p}_{T}^{#Lambda};<#it{R}>", kTProfile, {axisConfigurations.axisPt}); + histos.add((folder + "/hRingObservable2ndJetCounter").c_str(), "Counts vs <#it{R}>_{SubJet};<#it{R}>; Counts", kTH1D, {axisConfigurations.axisRingCounts}); + histos.add((folder + "/pRingObservable2ndJetDeltaPhi").c_str(), "<#it{R}> vs #Delta#varphi_{SubJet};#Delta#varphi_{SubJet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add((folder + "/pRingObservable2ndJetDeltaTheta").c_str(), "<#it{R}> vs #Delta#theta_{SubJet};#Delta#theta_{SubJet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaTheta}); + histos.add((folder + "/pRingObservable2ndJetIntegrated").c_str(), "Integrated <#it{R}> (SubJet); ;<#it{R}>", kTProfile, {{1, -0.5, 0.5}}); + histos.add((folder + "/pRingObservable2ndJetLambdaPt").c_str(), "<#it{R}> vs #it{p}_{T}^{#Lambda} (SubJet);#it{p}_{T}^{#Lambda} (GeV/c);<#it{R}>", kTProfile, {axisConfigurations.axisPt}); + + // For the Zvtx dependence: + histos.add((folder + "/pRingObservableLeadJetPVz").c_str(), "<#it{R}>_{LeadJet} vs PVz;PVz (cm);<#it{R}>", kTProfile, {axisConfigurations.axisPVz}); + histos.add((folder + "/pRingObservableSubLeadPVz").c_str(), "<#it{R}>_{SubLead} vs PVz;PVz (cm);<#it{R}>", kTProfile, {axisConfigurations.axisPVz}); + histos.add((folder + "/pRingObservableLeadPPVz").c_str(), "<#it{R}>_{LeadP} vs PVz;PVz (cm);<#it{R}>", kTProfile, {axisConfigurations.axisPVz}); // =============================== // 2D TProfiles (Lambda correlations) // =============================== - histos.add((folder + "/p2dRingObservableDeltaPhiVsLambdaPt").c_str(), "p2dRingObservableDeltaPhiVsLambdaPt;#Delta#varphi_{jet};#it{p}_{T}^{#Lambda};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPt}); - histos.add((folder + "/p2dRingObservableDeltaThetaVsLambdaPt").c_str(), "p2dRingObservableDeltaThetaVsLambdaPt;#Delta#theta_{jet};#it{p}_{T}^{#Lambda};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisPt}); + histos.add((folder + "/p2dRingObservableDeltaPhiVsLambdaPt").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};#it{p}_{T}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisPt}); + histos.add((folder + "/p2dRingObservableDeltaThetaVsLambdaPt").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs #it{p}_{T}^{#Lambda};#Delta#theta_{jet};#it{p}_{T}^{#Lambda} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisPt}); // =============================== // 2D TProfiles (Jet correlations) // =============================== - histos.add((folder + "/p2dRingObservableDeltaPhiVsLeadJetPt").c_str(), "p2dRingObservableDeltaPhiVsLeadJetPt;#Delta#varphi_{jet};#it{p}_{T}^{lead jet};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisJetPt}); - histos.add((folder + "/p2dRingObservableDeltaThetaVsLeadJetPt").c_str(), "p2dRingObservableDeltaThetaVsLeadJetPt;#Delta#theta_{jet};#it{p}_{T}^{lead jet};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisJetPt}); + histos.add((folder + "/p2dRingObservableDeltaPhiVsLeadJetPt").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs Lead Jet #it{p}_{T};#Delta#varphi_{jet};#it{p}_{T}^{LeadJet} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisJetPt}); + histos.add((folder + "/p2dRingObservableDeltaThetaVsLeadJetPt").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs Lead Jet #it{p}_{T};#Delta#theta_{jet};#it{p}_{T}^{LeadJet} (GeV/c);<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisJetPt}); // =============================== // Multi-dimensional histograms for signal extraction // (Mass-dependent polarization extraction) // =============================== // Simple invariant mass plot for QA: - histos.add((folder + "/QA/hMass").c_str(), "hMass", kTH1D, {axisConfigurations.axisLambdaMass}); - histos.add((folder + "/hMassSigExtract").c_str(), "hMassSigExtract", kTH1D, {axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/QA/hMass").c_str(), "#Lambda Mass;m_{p#pi} (GeV/c^{2});Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add((folder + "/hMassSigExtract").c_str(), "#Lambda Mass (Sig Extract);m_{p#pi} (GeV/c^{2});Counts", kTH1D, {axisConfigurations.axisLambdaMassSigExtract}); // 1D Mass dependence of observable numerator: - histos.add((folder + "/QA/hRingObservableNumMass").c_str(), "hRingObservableNumMass", kTH1D, {axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/QA/hRingObservableNumMass").c_str(), "Ring Observable Numerator vs Mass;m_{p#pi} (GeV/c^{2});Counts", kTH1D, {axisConfigurations.axisLambdaMassSigExtract}); // --- 2D counters: Angle vs Mass vs --- - histos.add((folder + "/QA/h2dDeltaPhiVsMass").c_str(), "h2dDeltaPhiVsMass", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract}); - histos.add((folder + "/QA/h2dDeltaThetaVsMass").c_str(), "h2dDeltaThetaVsMass", kTH2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/QA/h2dDeltaPhiVsMass").c_str(), "#Delta#varphi_{jet} vs Mass;#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2})", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/QA/h2dDeltaThetaVsMass").c_str(), "#Delta#theta_{jet} vs Mass;#Delta#theta_{jet};m_{p#pi} (GeV/c^{2})", kTH2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract}); // --- 3D counters: Angle vs Mass vs Lambda pT --- - histos.add((folder + "/QA/h3dDeltaPhiVsMassVsLambdaPt").c_str(), "h3dDeltaPhiVsMassVsLambdaPt", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); - histos.add((folder + "/QA/h3dDeltaThetaVsMassVsLambdaPt").c_str(), "h3dDeltaThetaVsMassVsLambdaPt", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/QA/h3dDeltaPhiVsMassVsLambdaPt").c_str(), "#Delta#varphi_{jet} vs Mass vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{#Lambda} (GeV/c)", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/QA/h3dDeltaThetaVsMassVsLambdaPt").c_str(), "#Delta#theta_{jet} vs Mass vs #it{p}_{T}^{#Lambda};#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{#Lambda} (GeV/c)", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); // --- 3D counters: Angle vs Mass vs Lead Jet pT --- - histos.add((folder + "/QA/h3dDeltaPhiVsMassVsLeadJetPt").c_str(), "h3dDeltaPhiVsMassVsLeadJetPt", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); - histos.add((folder + "/QA/h3dDeltaThetaVsMassVsLeadJetPt").c_str(), "h3dDeltaThetaVsMassVsLeadJetPt", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); + histos.add((folder + "/QA/h3dDeltaPhiVsMassVsLeadJetPt").c_str(), "#Delta#varphi_{jet} vs Mass vs Lead Jet #it{p}_{T};#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{LeadJet} (GeV/c)", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); + histos.add((folder + "/QA/h3dDeltaThetaVsMassVsLeadJetPt").c_str(), "#Delta#theta_{jet} vs Mass vs Lead Jet #it{p}_{T};#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{LeadJet} (GeV/c)", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); // =============================== // TProfiles vs Mass: quick glancing before signal extraction // =============================== - // TProfile of ring vs mass (integrated in all phi, and properly normalized by N_\Lambda): - histos.add((folder + "/pRingObservableMass").c_str(), "pRingObservableMass;m_{p#pi};<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); - histos.add((folder + "/pRingObservableLeadPMass").c_str(), "pRingObservableLeadPMass;m_{p#pi};<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); - histos.add((folder + "/pRingObservable2ndJetMass").c_str(), "pRingObservable2ndJetMass;m_{p#pi};<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); + // TProfile of ring vs mass (integrated in all phi, and properly normalized by N_Lambda): + histos.add((folder + "/pRingObservableMass").c_str(), "<#it{R}> vs Mass;m_{p#pi} (GeV/c^{2});<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/pRingObservableLeadPMass").c_str(), "<#it{R}> vs Mass (LeadP);m_{p#pi} (GeV/c^{2});<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/pRingObservable2ndJetMass").c_str(), "<#it{R}> vs Mass (SubJet);m_{p#pi} (GeV/c^{2});<#it{R}>", kTProfile, {axisConfigurations.axisLambdaMassSigExtract}); // TProfile2D: vs Mass (DeltaPhi) - histos.add((folder + "/p2dRingObservableDeltaPhiVsMass").c_str(), "p2dRingObservableDeltaPhiVsMass;#Delta#varphi;m_{p#pi};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/p2dRingObservableDeltaPhiVsMass").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs Mass;#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract}); // TProfile2D: vs Mass (DeltaTheta) - histos.add((folder + "/p2dRingObservableDeltaThetaVsMass").c_str(), "p2dRingObservableDeltaThetaVsMass;#Delta#theta;m_{p#pi};<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract}); + histos.add((folder + "/p2dRingObservableDeltaThetaVsMass").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs Mass;#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});<#it{R}>", kTProfile2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract}); + // TProfile2D: vs Eta Lambda vs Eta Jet (Understanding eta dependence seen in pRingEtaCuts) + histos.add((folder + "/EtaDependence/hCounterEtaLambdaMinusEtaJet").c_str(), "N_{V0s} vs #eta_{#Lambda} - #eta_{Jet};#eta_{#Lambda} - #eta_{Jet}; N_{V0s}", kTH1D, {axisConfigurations.axisDeltaEtaCoarse}); + histos.add((folder + "/EtaDependence/pRingObservableEtaLambdaMinusEtaJet").c_str(), "<#it{R}> vs #eta_{#Lambda} - #eta_{Jet};#eta_{#Lambda} - #eta_{Jet};<#it{R}>", kTProfile, {axisConfigurations.axisDeltaEtaCoarse}); + histos.add((folder + "/EtaDependence/p2dRingObservableEtaLambdaVsEtaJet").c_str(), "<#it{R}> vs #eta_{#Lambda} vs #eta_{Jet};#eta_{#Lambda};#eta_{Jet};<#it{R}>", kTProfile2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add((folder + "/EtaDependence/p2dRingObservableEtaLambdaVsEtaJet_FineBins").c_str(), "<#it{R}> vs #eta_{#Lambda} vs #eta_{Jet} (fine bins);#eta_{#Lambda};#eta_{Jet};<#it{R}>", kTProfile2D, {axisConfigurations.axisV0Eta, axisConfigurations.axisEta}); + histos.add((folder + "/EtaDependence/p2dRingObservableEtaLambdaVsEtaLeadP").c_str(), "<#it{R}> vs #eta_{#Lambda} vs #eta_{LeadP};#eta_{#Lambda};#eta_{LeadP};<#it{R}>", kTProfile2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add((folder + "/EtaDependence/p2dRingObservableEtaLambdaVsEta2ndJet").c_str(), "<#it{R}> vs #eta_{#Lambda} vs #eta_{SubJet};#eta_{#Lambda};#eta_{SubJet};<#it{R}>", kTProfile2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); + // Counters for these histograms, instead of only TProfile2Ds: + histos.add((folder + "/EtaDependence/h2dCounterEtaLambdaVsEtaJet").c_str(), "Counts, #eta_{#Lambda} vs #eta_{Jet};#eta_{#Lambda};#eta_{Jet};Counts", kTH2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add((folder + "/EtaDependence/h2dCounterEtaLambdaVsEtaJet_FineBins").c_str(), "Counts (fine bins), #eta_{#Lambda} vs #eta_{Jet};#eta_{#Lambda};#eta_{Jet};Counts", kTH2D, {axisConfigurations.axisV0Eta, axisConfigurations.axisEta}); + histos.add((folder + "/EtaDependence/h2dCounterEtaLambdaVsEtaLeadP").c_str(), "Counts, #eta_{#Lambda} vs #eta_{LeadP};#eta_{#Lambda};#eta_{LeadP};Counts", kTH2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add((folder + "/EtaDependence/h2dCounterEtaLambdaVsEta2ndJet").c_str(), "Counts, #eta_{#Lambda} vs #eta_{SubJet};#eta_{#Lambda};#eta_{SubJet};Counts", kTH2D, {axisConfigurations.axisV0EtaCoarse, axisConfigurations.axisEtaCoarse}); // --- TProfile3D: vs DeltaPhi vs Mass vs LambdaPt --- - histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsLambdaPt").c_str(), "p3dRingObservableDeltaPhiVsMassVsLambdaPt;#Delta#varphi;m_{p#pi};p_{T}^{#Lambda};<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsLambdaPt").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs Mass vs #it{p}_{T}^{#Lambda};#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{#Lambda} (GeV/c)", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); // --- TProfile3D: vs DeltaTheta vs Mass vs LambdaPt --- - histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsLambdaPt").c_str(), "p3dRingObservableDeltaThetaVsMassVsLambdaPt;#Delta#theta;m_{p#pi};p_{T}^{#Lambda};<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); + histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsLambdaPt").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs Mass vs #it{p}_{T}^{#Lambda};#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{#Lambda} (GeV/c)", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisPtSigExtract}); // --- TProfile3D: vs DeltaPhi vs Mass vs LeadJetPt --- - histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsLeadJetPt").c_str(), "p3dRingObservableDeltaPhiVsMassVsLeadJetPt;#Delta#varphi;m_{p#pi};p_{T}^{jet};<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); + histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsLeadJetPt").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs Mass vs Lead Jet #it{p}_{T};#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{LeadJet} (GeV/c)", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); // --- TProfile3D: vs DeltaTheta vs Mass vs LeadJetPt --- - histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsLeadJetPt").c_str(), "p3dRingObservableDeltaThetaVsMassVsLeadJetPt;#Delta#theta;m_{p#pi};p_{T}^{jet};<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); + histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsLeadJetPt").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs Mass vs Lead Jet #it{p}_{T};#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});#it{p}_{T}^{LeadJet} (GeV/c)", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisJetPtSigExtract}); // =============================== // Mass histograms with centrality // =============================== // Counters - histos.add((folder + "/QA/h3dDeltaPhiVsMassVsCent").c_str(), "h3dDeltaPhiVsMassVsCent", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); - histos.add((folder + "/QA/h3dDeltaThetaVsMassVsCent").c_str(), "h3dDeltaThetaVsMassVsCent", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); + histos.add((folder + "/QA/h3dDeltaPhiVsMassVsCent").c_str(), "#Delta#varphi_{jet} vs Mass vs Centrality;#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});Centrality (%)", kTH3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); + histos.add((folder + "/QA/h3dDeltaThetaVsMassVsCent").c_str(), "#Delta#theta_{jet} vs Mass vs Centrality;#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});Centrality (%)", kTH3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); // Useful TProfiles: // --- TProfile1D: Integrated vs Centrality: - histos.add((folder + "/pRingIntVsCentrality").c_str(), "pRingIntVsCentrality; Centrality (%);<#it{R}>", kTProfile, {axisConfigurations.axisCentrality}); + histos.add((folder + "/pRingVsCentrality").c_str(), "<#it{R}> vs Centrality;Centrality (%);<#it{R}>", kTProfile, {axisConfigurations.axisCentrality}); // --- TProfile2D: vs Mass vs Centrality --- - histos.add((folder + "/p2dRingObservableMassVsCent").c_str(), "p2dRingObservableMassVsCent;m_{p#pi};Centrality;<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); + histos.add((folder + "/p2dRingObservableMassVsCent").c_str(), "<#it{R}> vs Mass vs Centrality;m_{p#pi} (GeV/c^{2});Centrality (%);<#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); // --- TProfile3D: vs DeltaPhi vs Mass vs Centrality --- - histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsCent").c_str(), "p3dRingObservableDeltaPhiVsMassVsCent;#Delta#varphi;m_{p#pi};Centrality;<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); + histos.add((folder + "/p3dRingObservableDeltaPhiVsMassVsCent").c_str(), "<#it{R}> vs #Delta#varphi_{jet} vs Mass vs Centrality;#Delta#varphi_{jet};m_{p#pi} (GeV/c^{2});Centrality (%)", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); // --- TProfile3D: vs DeltaTheta vs Mass vs Centrality --- - histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsCent").c_str(), "p3dRingObservableDeltaThetaVsMassVsCent;#Delta#theta;m_{p#pi};Centrality;<#it{R}>", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); + histos.add((folder + "/p3dRingObservableDeltaThetaVsMassVsCent").c_str(), "<#it{R}> vs #Delta#theta_{jet} vs Mass vs Centrality;#Delta#theta_{jet};m_{p#pi} (GeV/c^{2});Centrality (%)", kTProfile3D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisCentrality}); // =============================== // QA histograms - Useful numbers @@ -382,31 +769,390 @@ struct lambdajetpolarizationionsderived { // Added to a separate folder for further control (changed the usage of the "folder" string): // histos.add(("QA_Numbers/" + folder + "/hValidLeadJets").c_str(), "hValidLeadJets", kTH1D, {{1,0,1}}); // TODO: Add "frequency of jets per pT" histograms either here or in the TableProducer + + // Estimating error bars with the Delta Method for = A/B: + // 1D Delta Method for Integrated observable: + histos.add((folder + "/DeltaMethod/hIntegrated").c_str(), "Delta Method Accumulators Integrated;Component;Counts", kTH1D, {axisConfigurations.axisDeltaComponents}); + + // 2D Delta Method for Differentials + histos.add((folder + "/DeltaMethod/h2dDeltaThetaVsDeltaComp").c_str(), "Delta Method vs #Delta#theta_{jet};#Delta#theta_{jet};Component", kTH2D, {axisConfigurations.axisDeltaTheta, axisConfigurations.axisDeltaComponents}); + histos.add((folder + "/DeltaMethod/h2dLambdaPtVsDeltaComp").c_str(), "Delta Method vs #Lambda #it{p}_{T};#it{p}_{T}^{#Lambda} (GeV/c);Component", kTH2D, {axisConfigurations.axisPt, axisConfigurations.axisDeltaComponents}); + histos.add((folder + "/DeltaMethod/h2dMassVsDeltaComp").c_str(), "Delta Method vs Mass;m_{p#pi} (GeV/c^{2});Component", kTH2D, {axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisDeltaComponents}); }; // Execute local lambda to register histogram families: - addRingObservableFamily("Ring"); - addRingObservableFamily("RingKinematicCuts"); - addRingObservableFamily("JetKinematicCuts"); - addRingObservableFamily("JetAndLambdaKinematicCuts"); + if (familySwitches.doFamilyRing) + addRingObservableFamily("Ring"); + if (familySwitches.doFamilyRingKinematicCuts) + addRingObservableFamily("RingKinematicCuts"); + if (familySwitches.doFamilyJetKinematicCuts) + addRingObservableFamily("JetKinematicCuts"); + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) + addRingObservableFamily("JetAndLambdaKinematicCuts"); + + histos.add("IntegratedCuts/pRingCuts", "pRingCuts; ;<#it{R}>", kTProfile, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/pRingCuts"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/pRingCuts"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); // (v0pt > 0.5 && v0pt < 1.5) && std::abs(lambdaRapidity) < 0.5; + histos.get(HIST("IntegratedCuts/pRingCuts"))->GetXaxis()->SetBinLabel(3, "|Jet_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/pRingCuts"))->GetXaxis()->SetBinLabel(4, "#Lambda + Jet cuts"); - histos.add("pRingCuts", "pRingCuts; ;<#it{R}>", kTProfile, {{4, 0, 4}}); - histos.get(HIST("pRingCuts"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); - histos.get(HIST("pRingCuts"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); // (v0pt > 0.5 && v0pt < 1.5) && std::abs(lambdaRapidity) < 0.5; - histos.get(HIST("pRingCuts"))->GetXaxis()->SetBinLabel(3, "|Jet_{#eta}|<0.5"); - histos.get(HIST("pRingCuts"))->GetXaxis()->SetBinLabel(4, "#Lambda + Jet cuts"); + // Same for subleading jet and leading particle: + histos.add("IntegratedCuts/pRingCutsSubLeadingJet", "pRingCutsSubLeadingJet; ;<#it{R}>", kTProfile, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(2, "p_{T,#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); + histos.get(HIST("IntegratedCuts/pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(3, "|SubJet_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(4, "#Lambda + SubJet cuts"); + + histos.add("IntegratedCuts/pRingCutsLeadingP", "pRingCutsLeadingP; ;<#it{R}>", kTProfile, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); + histos.get(HIST("IntegratedCuts/pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(3, "|LeadP_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(4, "#Lambda + LeadP cuts"); + + // Counters for each case to understand statistics loss: + histos.add("IntegratedCuts/hCountCuts", "hCountCuts; ;N V0s", kTH1D, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/hCountCuts"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/hCountCuts"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); // (v0pt > 0.5 && v0pt < 1.5) && std::abs(lambdaRapidity) < 0.5; + histos.get(HIST("IntegratedCuts/hCountCuts"))->GetXaxis()->SetBinLabel(3, "|Jet_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/hCountCuts"))->GetXaxis()->SetBinLabel(4, "#Lambda + Jet cuts"); // Same for subleading jet and leading particle: - histos.add("pRingCutsSubLeadingJet", "pRingCutsSubLeadingJet; ;<#it{R}>", kTProfile, {{4, 0, 4}}); - histos.get(HIST("pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); - histos.get(HIST("pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(2, "p_{T,#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); - histos.get(HIST("pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(3, "|SubJet_{#eta}|<0.5"); - histos.get(HIST("pRingCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(4, "#Lambda + SubJet cuts"); - - histos.add("pRingCutsLeadingP", "pRingCutsLeadingP; ;<#it{R}>", kTProfile, {{4, 0, 4}}); - histos.get(HIST("pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); - histos.get(HIST("pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); - histos.get(HIST("pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(3, "|LeadP_{#eta}|<0.5"); - histos.get(HIST("pRingCutsLeadingP"))->GetXaxis()->SetBinLabel(4, "#Lambda + LeadP cuts"); + histos.add("IntegratedCuts/hCountCutsSubLeadingJet", "hCountCutsSubLeadingJet; ;N V0s", kTH1D, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/hCountCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/hCountCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(2, "p_{T,#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); + histos.get(HIST("IntegratedCuts/hCountCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(3, "|SubJet_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/hCountCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(4, "#Lambda + SubJet cuts"); + + histos.add("IntegratedCuts/hCountCutsLeadingP", "hCountCutsLeadingP; ;N V0s", kTH1D, {{4, 0, 4}}); + histos.get(HIST("IntegratedCuts/hCountCutsLeadingP"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("IntegratedCuts/hCountCutsLeadingP"))->GetXaxis()->SetBinLabel(2, "p_{T}^{#Lambda}@[0.5,1.5],|y_{#Lambda}|<0.5"); + histos.get(HIST("IntegratedCuts/hCountCutsLeadingP"))->GetXaxis()->SetBinLabel(3, "|LeadP_{#eta}|<0.5"); + histos.get(HIST("IntegratedCuts/hCountCutsLeadingP"))->GetXaxis()->SetBinLabel(4, "#Lambda + LeadP cuts"); + + // Fake-polarization diagnostics: + if (qaSwitches.doFakePolDiagnosticsQA) { + // Integrated observable dependent on jet proxy #eta to unfold possible asymmetries in detector: + histos.add("EtaStudy/pRingEtaCuts", "pRingEtaCuts; ;<#it{R}>", kTProfile, {{15, 0, 15}}); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(10, "#eta_{Jet} > R"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(11, "#eta_{Jet} < -R"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(12, "#eta_{Jet} > R, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(13, "#eta_{Jet} > R, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(14, "#eta_{Jet} < -R, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCuts"))->GetXaxis()->SetBinLabel(15, "#eta_{Jet} < -R, #eta_{#Lambda} < 0"); + + histos.add("EtaStudy/pRingEtaCutsSubLeadingJet", "pRingEtaCutsSubLeadingJet; ;<#it{R}>", kTProfile, {{15, 0, 15}}); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(2, "#eta_{SubJet} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(3, "#eta_{SubJet} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(6, "#eta_{SubJet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(7, "#eta_{SubJet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(8, "#eta_{SubJet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(9, "#eta_{SubJet} < 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(10, "#eta_{SubJet} > R"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(11, "#eta_{SubJet} < -R"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(12, "#eta_{SubJet} > R, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(13, "#eta_{SubJet} > R, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(14, "#eta_{SubJet} < -R, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"))->GetXaxis()->SetBinLabel(15, "#eta_{SubJet} < -R, #eta_{#Lambda} < 0"); + + histos.add("EtaStudy/pRingEtaCutsLeadingP", "pRingEtaCutsLeadingP; ;<#it{R}>", kTProfile, {{9, 0, 9}}); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(2, "#eta_{LeadP} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(3, "#eta_{LeadP} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(6, "#eta_{LeadP} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(7, "#eta_{LeadP} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(8, "#eta_{LeadP} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP"))->GetXaxis()->SetBinLabel(9, "#eta_{LeadP} < 0, #eta_{#Lambda} < 0"); + + // Studying the signal Vs background integral (a very naive estimative of the invariant mass peak) + histos.add("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground", "pRingEtaCutsLeadingP_MassSignalVsBackground; ; ;<#it{R}>", kTProfile2D, {{9, 0, 9}, {2, 0, 2}}); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(2, "#eta_{LeadP} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(3, "#eta_{LeadP} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(6, "#eta_{LeadP} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(7, "#eta_{LeadP} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(8, "#eta_{LeadP} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetXaxis()->SetBinLabel(9, "#eta_{LeadP} < 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetYaxis()->SetBinLabel(1, "#Lambda out of mass peak"); + histos.get(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"))->GetYaxis()->SetBinLabel(2, "#Lambda in mass peak"); + + // Fake polarization signal QA + // --> The "negative helicity problem", where topologies with a proton decaying opposite to the Lambda momentum are enhanced by + // efficiency of reconstruction. The geometries where the proton moves in the same direction as the boost will have a very small + // momentum pion, which is not as easily detected as the opposite case! This may insert a fake signal of polarization in the measurement! + histos.add("EtaStudy/hFakePolCounts", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda};", kTH2D, {axisConfigurations.axisCosTheta, {9, 0, 9}}); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetZaxis()->SetTitle("N_{V0s}"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCounts"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // The same, but for actual signal instead of counts: + histos.add("EtaStudy/pFakePolSignalVsCosTheta", "FakePolSignal; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda};", kTProfile2D, {axisConfigurations.axisCosTheta, {9, 0, 9}}); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetZaxis()->SetTitle("<#it{R}>"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalVsCosTheta"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // Seeing the dependence between phi* = atan2(p_p_star \cdot (p_Lambda_hat \times (z_hat \cross p_Lambda)), p_p_star \cdot (z_hat \cross p_Lambda)) + // e_z = p_Lambda_hat; // e_x = normalize(z_hat cross p_Lambda); // e_y = e_z cross e_x; + // phi_star = atan2(p_p_star dot e_y, p_p_star dot e_x); + histos.add("HelicityEfficiencyQA/hFakePolCounts_CosThetaVsPhiStar", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #phi^{*}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaPhi}); + histos.add("HelicityEfficiencyQA/pFakePolSignal_CosThetaVsPhiStar", "FakePolSignal; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #phi^{*}", kTProfile2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaPhi}); + // Specific counter for when we have leading jets (relates directly to pFakePolSignal_CosThetaVsPhiStar): + histos.add("HelicityEfficiencyQA/hFakePolCountsJet_CosThetaVsPhiStar", "FakePolCounts - HasValidLeadJet OK; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #phi^{*}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaPhi}); + + // Similar split, but for AEE instead of HEE: + histos.add("EtaStudy/hCountsVsPhiStar", "FakePolCounts, AEE dependence; #phi^{*};", kTH2D, {axisConfigurations.axisDeltaPhi, {9, 0, 9}}); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetZaxis()->SetTitle("Counts"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hCountsVsPhiStar"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + // For the ring observable as well: + // Explicitly, checking the Phi* dependence on a series of #eta cuts. + // The fake, AEE-induced, signal should be zero when integrating on full solid angle, and then have some shape for each eta slice. + histos.add("EtaStudy/pFakePolSignalvsPhiStar", "FakePolSignal, AEE dependence; #phi^{*};", kTProfile2D, {axisConfigurations.axisDeltaPhi, {9, 0, 9}}); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetZaxis()->SetTitle("<#it{R}>"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiStar"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // For the phi_Lambda - phi_D^* dependency as well: + histos.add("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar", "FakePolSignal, AEE dependence; #phi_{#Lambda} - #phi_{p}^{*};", kTProfile2D, {axisConfigurations.axisDeltaPhi, {9, 0, 9}}); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetZaxis()->SetTitle("<#it{R}>"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // More about possible AEE dependencies (should see an invariance with JetEta and /Jz): + // TODO: think about these error bars: do they still make sense via regular TProfile's SEM error? These are just a quick check, so wouldn't bother much about it. + histos.add("HelicityEfficiencyQA/pRingVsJetZcomponent", "<#it{R}> vs #hat{t}_{z}; #hat{t}_{z}; <#it{R}>", kTProfile, {{40, -1, 1}}); // Numerically stable and can also show the sign flip (essentially the <#it{R}> vs Eta Jet plot in another scale) + histos.add("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEta", "<#it{R}>/#hat{t}_{z}; #eta_{Jet}; <#it{R}>/#hat{t}_{z}", kTProfile, {axisConfigurations.axisEtaCoarse}); + histos.add("HelicityEfficiencyQA/pRingOverJetZcomponent_VsCosThetaHEE", "<#it{R}>/#hat{t}_{z} Vs cos(#theta) HEE; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; <#it{R}>/#hat{t}_{z}", kTProfile, {axisConfigurations.axisCosTheta}); + histos.add("HelicityEfficiencyQA/pRingOverJetZcomponent_VsPhiStar", "<#it{R}>/#hat{t}_{z} Vs #phi^{*}; #phi^{*} = atan2(#vec{p}^{*}_{p} #cdot [#hat{p}_{#Lambda} #times (#hat{z} #times #hat{p}_{#Lambda})] , #vec{p}^{*}_{p} #cdot (#hat{z} #times #hat{p}_{#Lambda})); <#it{R}>/#hat{t}_{z}", kTProfile, {axisConfigurations.axisDeltaPhi}); + histos.add("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEtaVsCosThetaHEE", "<#it{R}>/#hat{t}_{z}; #eta_{Jet}; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; <#it{R}>/#hat{t}_{z}", kTProfile2D, {axisConfigurations.axisEtaCoarse, axisConfigurations.axisCosTheta}); + histos.add("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEtaVsPhiStar", "<#it{R}>/#hat{t}_{z}; #eta_{Jet}; #phi^{*}; <#it{R}>/#hat{t}_{z}", kTProfile2D, {axisConfigurations.axisEtaCoarse, axisConfigurations.axisDeltaPhi}); + + // Seeing if phi* is indeed influenced by the DCA between daughters: + histos.add("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAdau", "FakePolCounts - HasValidLeadJet OK; #phi^{*}; DCA_{V0 Daughters}", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdau}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdau", "FakePolSignal; #phi^{*}; DCA_{V0 Daughters}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdau}); + + // Adding a way to check if the jet eta is positive or negative as well + // (AEE signal could be closer to zero otherwise: phi^* dependency may not make it fall to zero as we are no longer integrating in full solid angle, yet analyzing this other dependency is also important) + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdauVsEtaJet", "FakePolSignal; #phi^{*}; DCA_{V0 Daughters}; #eta_{Jet} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdau, {2, -0.9, 0.9}}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdauVsEtaLambda", "FakePolSignal; #phi^{*}; DCA_{V0 Daughters}; #eta_{#Lambda} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdau, {2, -0.9, 0.9}}); + + // Similar checks for dcaPosToPV and dcaNegToPV, which influence AEE the strongest: + histos.add("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAProLike", "FakePolCounts - HasValidLeadJet OK; #phi^{*}; DCA_{PosPV}", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLike", "FakePolSignal; #phi^{*}; DCA_{PosPV}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLikeVsEtaJet", "FakePolSignal; #phi^{*}; DCA_{PosPV}; #eta_{Jet} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV, {2, -0.9, 0.9}}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLikeVsEtaLambda", "FakePolSignal; #phi^{*}; DCA_{PosPV}; #eta_{#Lambda} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV, {2, -0.9, 0.9}}); + + histos.add("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAPiLike", "FakePolCounts - HasValidLeadJet OK; #phi^{*}; DCA_{NegPV}", kTH2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLike", "FakePolSignal; #phi^{*}; DCA_{NegPV}", kTProfile2D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLikeVsEtaJet", "FakePolSignal; #phi^{*}; DCA_{NegPV}; #eta_{Jet} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV, {2, -0.9, 0.9}}); + histos.add("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLikeVsEtaLambda", "FakePolSignal; #phi^{*}; DCA_{NegPV}; #eta_{#Lambda} sign", kTProfile3D, {axisConfigurations.axisDeltaPhi, axisConfigurations.axisDCAdauPV, {2, -0.9, 0.9}}); + + // Doing the same HEE study for leading particles: + // (eta_{Jet} may be a bad estimator!) + histos.add("EtaStudy/hFakePolCountsLeadP", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda};", kTH2D, {axisConfigurations.axisCosTheta, {9, 0, 9}}); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetZaxis()->SetTitle("N_{V0s}"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(2, "#eta_{LeadP} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(3, "#eta_{LeadP} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(6, "#eta_{LeadP} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(7, "#eta_{LeadP} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(8, "#eta_{LeadP} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLeadP"))->GetYaxis()->SetBinLabel(9, "#eta_{LeadP} < 0, #eta_{#Lambda} < 0"); + + // Avoid fake signal by jets boosting the Lambda in its own direction, then modifying efficiency of reconstruction in a similar way: + histos.add("EtaStudy/hFakePolCountsLambdaPtCut", "FakePol,p_{T}^{#Lambda}#in[0.5,1.5]; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda};", kTH2D, {axisConfigurations.axisCosTheta, {9, 0, 9}}); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetZaxis()->SetTitle("N_{V0s}"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtCut"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // Even stricter cut (also demands rapidity cut stricter than jets, so may see different boosting): + histos.add("EtaStudy/hFakePolCountsLambdaPtYCuts", "FakePol,p_{T}^{#Lambda}#in[0.5,1.5],|y_{#Lambda}|<0.5; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda};", kTH2D, {axisConfigurations.axisCosTheta, {9, 0, 9}}); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetZaxis()->SetTitle("N_{V0s}"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(1, "All #Lambda"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(2, "#eta_{Jet} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(3, "#eta_{Jet} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(4, "#eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(5, "#eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(6, "#eta_{Jet} #geq 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(7, "#eta_{Jet} #geq 0, #eta_{#Lambda} < 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(8, "#eta_{Jet} < 0, #eta_{#Lambda} #geq 0"); + histos.get(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"))->GetYaxis()->SetBinLabel(9, "#eta_{Jet} < 0, #eta_{#Lambda} < 0"); + + // Another useful quantity -- How much is the fake signal related to the jet's momentum (how much the fake signal is correlated with the Jet-Lambda angular separation): + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJet", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{Jet}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJetPosEta", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{Jet}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJetNegEta", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{Jet}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadP", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{LeadP}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadPPosEta", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{LeadP}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + histos.add("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadPNegEta", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; #Delta#theta_{LeadP}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisDeltaTheta}); + + // Understanding the dip at the cos = -1 end: + histos.add("HelicityEfficiencyQA/hFakePolCountsCosThetaVsPtForJets", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; p_{T}^{#Lambda}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisPtCoarseQA}); + histos.add("HelicityEfficiencyQA/hFakePolCountsCosThetaVsPtForLeadP", "FakePolCounts; cos(#theta)=#hat{p}^{*}_{D} . #vec{p}_{#Lambda}; p_{T}^{#Lambda}", kTH2D, {axisConfigurations.axisCosTheta, axisConfigurations.axisPtCoarseQA}); + + // Studying the magnetic field dependence of particle reconstruction efficiency (not magnitude, just sign of field): + // (also for the "negative helicity" problem) + if (analyseMagField) { + histos.add("HelicityEfficiencyQA/hLambdaMassDecayGeomRight", "hLambdaMassDecayGeomRight; m_{Inv}; Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add("HelicityEfficiencyQA/hLambdaMassDecayGeomLeft", "hLambdaMassDecayGeomLeft; m_{Inv}; Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add("HelicityEfficiencyQA/hAntiLambdaMassDecayGeomRight", "hAntiLambdaMassDecayGeomRight; m_{Inv}; Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + histos.add("HelicityEfficiencyQA/hAntiLambdaMassDecayGeomLeft", "hAntiLambdaMassDecayGeomLeft; m_{Inv}; Counts", kTH1D, {axisConfigurations.axisLambdaMass}); + } + + // Also including an observable that probes spectrum broadening due to the "Azimuthal Efficiency Effect": + histos.add("HelicityEfficiencyQA/hLambdaMassVsPhiLambdaMinusPhiProtonStar", "m_{#Lambda}, AEE probe; m_{Inv}; #phi_{#Lambda} - #phi_{p}^{*} ; Counts", kTH2D, {axisConfigurations.axisLambdaMass, axisConfigurations.axisDeltaPhi}); + histos.add("HelicityEfficiencyQA/hAntiLambdaMassVsPhiLambdaMinusPhiProtonStar", "m_{#bar{#Lambda}}, AEE probe; m_{Inv}; #phi_{#bar{#Lambda}} - #phi_{p}^{*} ; Counts", kTH2D, {axisConfigurations.axisLambdaMass, axisConfigurations.axisDeltaPhi}); + // Watching the effect on the ring observable as well: + histos.add("HelicityEfficiencyQA/p2dRing_LambdaMassVsPhiLambdaMinusPhiProtonStar", "<#it{R}>, AEE probe; m_{Inv}; #phi_{#Lambda} - #phi_{p}^{*} ; <#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisDeltaPhi}); + histos.add("HelicityEfficiencyQA/p2dRing_AntiLambdaMassVsPhiLambdaMinusPhiProtonStar", "<#it{R}>, AEE probe; m_{Inv}; #phi_{#bar{#Lambda}} - #phi_{p}^{*} ; <#it{R}>", kTProfile2D, {axisConfigurations.axisLambdaMassSigExtract, axisConfigurations.axisDeltaPhi}); + } // end doFakePolDiagnosticsQA bookings + + // Integrated observable for events with NLambda+NAntiLambda V0s per event + // (an interesting measurement of correlation between and Lambda-like V0s multiplicity. A proxy of covariance) + // (calculated for leading jets only) + histos.add("IntegratedCuts/pRingVsNV0s", "pRingVsNV0s; N_{#Lambda}+N_{#bar{#Lambda}};<#it{R}>", kTProfile, {{20, 0, 20}}); // See hNV0sVsCentrality below for the correlation between number of V0s and centrality + histos.add("hNV0sVsCentrality", "hNV0sVsCentrality; N_{#Lambda}+N_{#bar{#Lambda}};Centrality (%)", kTH2D, {{20, 0, 20}, axisConfigurations.axisCentrality}); + + // Proxy Eta QA: + histos.add("JetKinematicsQA/hLeadJetEta", "hLeadJetEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("JetKinematicsQA/hSubLeadJetEta", "hSubLeadJetEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + histos.add("JetKinematicsQA/hLeadPEta", "hLeadPEta;#eta;Counts", kTH1D, {axisConfigurations.axisEta}); + + // Proxy Phi QA: + histos.add("JetKinematicsQA/hLeadJetPhi", "hLeadJetPhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); + histos.add("JetKinematicsQA/hSubLeadJetPhi", "hSubLeadJetPhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); + histos.add("JetKinematicsQA/hLeadPPhi", "hLeadPPhi;#varphi;Counts", kTH1D, {axisConfigurations.axisPhi}); + + // Counting the number of jets/proxies themselves (these count at most once per event) -- Similar to the FOLDER/QA/hPtJet counters: + histos.add("JetKinematicsQA/hJetCounterPtJet", "hJetCounterPtJet; p_{T}^{Jet} (GeV/c)", kTH1D, {axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/hJetCounterPtLeadP", "hJetCounterPtLeadP; p_{T}^{LeadP} (GeV/c)", kTH1D, {axisConfigurations.axisJetPt}); + histos.add("JetKinematicsQA/hJetCounterPt2ndJet", "hJetCounterPt2ndJet; p_{T}^{SubJet} (GeV/c)", kTH1D, {axisConfigurations.axisJetPt}); + + // Proxy Eta vs PVz: + histos.add("JetKinematicsQA/h2dLeadJetEtaVsPVz", "Lead Jet #eta Vs PVz;#eta;Primary Vertex Z [cm];Counts", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPVz}); + histos.add("JetKinematicsQA/h2dSubLeadJetEtaVsPVz", "SubLead Jet #eta Vs PVz;#eta;Primary Vertex Z [cm];Counts", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPVz}); + histos.add("JetKinematicsQA/h2dLeadPEtaVsPVz", "Lead Ptc #eta Vs PVz;#eta;Primary Vertex Z [cm];Counts", kTH2D, {axisConfigurations.axisEta, axisConfigurations.axisPVz}); + + // For building and event-mixing-like procedure similar to forceDatalikeJet: + if (doJetProxy5dQA) { + histos.add("JetKinematicsQA/h5dLeadJetEtaPhiPtPVzCent", "h5dLeadJetEtaPhiPtPVzCent;#eta;#phi;p_{t};Primary Vertex Z [cm];Centrality (%);Counts", kTHnSparseF, + {axisConfigurations.axisEtaCoarse, axisConfigurations.axisPhi, axisConfigurations.axisJetPt, axisConfigurations.axisPVz, axisConfigurations.axisCentrality}); + histos.add("JetKinematicsQA/h5dSubLeadJetEtaPhiPtPVzCent", "h5dSubLeadJetEtaPhiPtPVzCent;#eta;#phi;p_{t};Primary Vertex Z [cm];Centrality (%);Counts", kTHnSparseF, + {axisConfigurations.axisEtaCoarse, axisConfigurations.axisPhi, axisConfigurations.axisJetPt, axisConfigurations.axisPVz, axisConfigurations.axisCentrality}); + histos.add("JetKinematicsQA/h5dLeadPEtaPhiPtPVzCent", "h5dLeadPEtaPhiPtPVzCent;#eta;#phi;p_{t};Primary Vertex Z [cm];Centrality (%);Counts", kTHnSparseF, + {axisConfigurations.axisEtaCoarse, axisConfigurations.axisPhi, axisConfigurations.axisJetPt, axisConfigurations.axisPVz, axisConfigurations.axisCentrality}); + } + + // doMixedEventProxies QA: gauge the size of the "too few collisions per bin" problem (see resonanceMergeDF.cxx) + // Booked per proxy, since the three mixings are independent and can succeed/fail at different rates. + if (fakePolSwitches.doMixedEventProxies) { + histos.add("EventMixingQA/hMixedEventLeadPOutcome", "hMixedEventLeadPOutcome;Outcome (0=skipped, 1=found);Counts", kTH1D, {{2, -0.5f, 1.5f}}); + histos.add("EventMixingQA/hMixedEventLeadJetOutcome", "hMixedEventLeadJetOutcome;Outcome (0=skipped, 1=found);Counts", kTH1D, {{2, -0.5f, 1.5f}}); + histos.add("EventMixingQA/hMixedEventSubJetOutcome", "hMixedEventSubJetOutcome;Outcome (0=skipped, 1=found);Counts", kTH1D, {{2, -0.5f, 1.5f}}); + + histos.add("EventMixingQA/hMixedEventLeadPWindowNeighbours", "hMixedEventLeadPWindowNeighbours;Neighbours found in bin window;Counts", kTH1D, {{22, -0.5f, 21.5f}}); + histos.add("EventMixingQA/hMixedEventLeadJetWindowNeighbours", "hMixedEventLeadJetWindowNeighbours;Neighbours found in bin window;Counts", kTH1D, {{22, -0.5f, 21.5f}}); + histos.add("EventMixingQA/hMixedEventSubJetWindowNeighbours", "hMixedEventSubJetWindowNeighbours;Neighbours found in bin window;Counts", kTH1D, {{22, -0.5f, 21.5f}}); + + histos.add("EventMixingQA/h2dMixedLeadPEtaVsLeadPEta", "MixedLeadP #eta vs LeadP #eta;MixedLeadP #eta; LeadP #eta;Counts", kTH2D, {axisConfigurations.axisEtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/h2dMixedLeadPPhiVsLeadPPhi", "MixedLeadP #phi vs LeadP #phi;MixedLeadP #phi; LeadP #phi;Counts", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisPhi}); + histos.add("EventMixingQA/h2dMixedLeadJetEtaVsLeadJetEta", "MixedLeadJet #eta vs LeadJet #eta;MixedLeadJet #eta; LeadJet #eta;Counts", kTH2D, {axisConfigurations.axisEtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/h2dMixedLeadJetPhiVsLeadJetPhi", "MixedLeadJet #phi vs LeadJet #phi;MixedLeadJet #phi; LeadJet #phi;Counts", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisPhi}); + histos.add("EventMixingQA/h2dMixedSubJetEtaVsSubJetEta", "MixedSubJet #eta vs SubJet #eta;MixedSubJet #eta; SubJet #eta;Counts", kTH2D, {axisConfigurations.axisEtaCoarse, axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/h2dMixedSubJetPhiVsSubJetPhi", "MixedSubJet #phi vs SubJet #phi;MixedSubJet #phi; SubJet #phi;Counts", kTH2D, {axisConfigurations.axisPhi, axisConfigurations.axisPhi}); + + // Collision-index proximity QA: + // (To understand possible continous readout effects on the choice of event mixing sources -- do notice index and time are both monotonic, but there is a conversion factor) + // The shape of the whole candidate pool: + histos.add("EventMixingQA/hMixedEventLeadPDeltaIndexEligible", "hMixedEventLeadPDeltaIndexEligible;#Delta collision index (pair);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + histos.add("EventMixingQA/hMixedEventLeadJetDeltaIndexEligible", "hMixedEventLeadJetDeltaIndexEligible;#Delta collision index (pair);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + histos.add("EventMixingQA/hMixedEventSubJetDeltaIndexEligible", "hMixedEventSubJetDeltaIndexEligible;#Delta collision index (pair);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + + // What the reservoir actually picked: + // (should be equally as narrow as the whole candidate pool, if no preferential mixing exists) + histos.add("EventMixingQA/hMixedEventLeadPDeltaIndexSelected", "hMixedEventLeadPDeltaIndexSelected;#Delta collision index (selected partner);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + histos.add("EventMixingQA/hMixedEventLeadJetDeltaIndexSelected", "hMixedEventLeadJetDeltaIndexSelected;#Delta collision index (selected partner);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + histos.add("EventMixingQA/hMixedEventSubJetDeltaIndexSelected", "hMixedEventSubJetDeltaIndexSelected;#Delta collision index (selected partner);Counts", kTH1D, {axisConfigurations.axisDeltaCollisionIndex}); + + // Event mixing source QA -- How repeated is each collision in the mix: + // For each source collision for mixing, counts how many times it was used. Flat distribution is ideal. + histos.add("EventMixingQA/hMixedEventLeadPSourceUsageCount", "hMixedEventLeadPSourceUsageCount;Times collision was used;Counts", kTH1D, {{50, -0.5f, 49.5f}}); + histos.add("EventMixingQA/hMixedEventLeadJetSourceUsageCount", "hMixedEventLeadJetSourceUsageCount;Times collision was used;Counts", kTH1D, {{50, -0.5f, 49.5f}}); + histos.add("EventMixingQA/hMixedEventSubJetSourceUsageCount", "hMixedEventSubJetSourceUsageCount;Times collision was used;Counts", kTH1D, {{50, -0.5f, 49.5f}}); + // Useful TProfiles -- the mean number of times a given source was used, as a function of eta or phi + // (more convenient than a single TH1, as it gives an average, not the raw counter) + histos.add("EventMixingQA/pMixedEventLeadPSourceUsageVsEta", "pMixedEventLeadPSourceUsageVsEta;Source #eta;", kTProfile, {axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/pMixedEventLeadPSourceUsageVsPhi", "pMixedEventLeadPSourceUsageVsPhi;Source #varphi;", kTProfile, {axisConfigurations.axisPhi}); + histos.add("EventMixingQA/pMixedEventLeadJetSourceUsageVsEta", "pMixedEventLeadJetSourceUsageVsEta;Source #eta;", kTProfile, {axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/pMixedEventLeadJetSourceUsageVsPhi", "pMixedEventLeadJetSourceUsageVsPhi;Source #varphi;", kTProfile, {axisConfigurations.axisPhi}); + histos.add("EventMixingQA/pMixedEventSubJetSourceUsageVsEta", "pMixedEventSubJetSourceUsageVsEta;Source #eta;", kTProfile, {axisConfigurations.axisEtaCoarse}); + histos.add("EventMixingQA/pMixedEventSubJetSourceUsageVsPhi", "pMixedEventSubJetSourceUsageVsPhi;Source #varphi;", kTProfile, {axisConfigurations.axisPhi}); + } + + // Fetch the X-axes from one of the families (since they all share the same ConfigurableAxis binning) + mAxisPt = histos.get(HIST("Ring/DeltaMethod/h2dLambdaPtVsDeltaComp"))->GetXaxis(); + mAxisMass = histos.get(HIST("Ring/DeltaMethod/h2dMassVsDeltaComp"))->GetXaxis(); + mAxisDTheta = histos.get(HIST("Ring/DeltaMethod/h2dDeltaThetaVsDeltaComp"))->GetXaxis(); + for (auto const& tracker : {&trackRing, &trackRingKinCuts, &trackJetKinCuts, &trackJetLambdaKinCuts}) + tracker->resize(mAxisPt->GetNbins(), mAxisMass->GetNbins(), mAxisDTheta->GetNbins()); } // Helper to get centrality (same from TableProducer, thanks to templating!): @@ -424,346 +1170,1244 @@ struct lambdajetpolarizationionsderived { // Initializing a random number generator for the worker (for perpendicular-to-jet direction QAs): TRandom3 randomGen{0}; // 0 means we auto-seed from machine entropy. This is called once per device in the pipeline, so we should not see repeated seeds across workers + std::mt19937 rng{std::random_device{}()}; + + // Pre-computed values for helper below: + const double smearSigma = 0.05 * jetR; + // Normalized eta weights spanning -0.9 to 0.9 (46 bins) and phi weights for the 50 bins spanning [0, 2pi]: + static constexpr std::array etaLeadPWeights = {{0.01782505198123039, 0.01826119427561306, 0.01890047073124532, 0.01942224199989093, 0.01993380780273602, 0.02047274597515178, 0.02094135547756474, 0.02140259654932778, 0.02178490245182078, 0.02218346916434517, 0.02252861343224298, 0.02278214932340838, 0.02297395476452691, 0.02311861709583109, 0.02322295246943318, 0.02329274166449468, 0.02335344516182264, 0.02335971904087711, 0.02340163522424806, 0.02352868368676468, 0.02345839093195849, 0.02295391718536531, 0.02306543716698383, 0.02293131780181040, 0.02265098126991631, 0.02318893563623931, 0.02322457978177088, 0.02316188601954564, 0.02308812992419636, 0.02305831097751334, 0.02300695504254397, 0.02296398287895598, 0.02286956017362988, 0.02274321888486162, 0.02254049413132752, 0.02233024817234042, 0.02204811997596179, 0.02170252191737713, 0.02130517220864903, 0.02086349641950970, 0.02036590755841183, 0.01987946074337928, 0.01934550418314029, 0.01879487530409137, 0.01812577211265362, 0.01766945805710696}}; + static constexpr std::array phiLeadPWeights = {{0.01907529231698144, 0.02044679008716434, 0.01948618554713157, 0.02046288887443206, 0.02142057576726765, 0.01961361841611185, 0.02174981627752354, 0.02160945937846856, 0.02027231236207667, 0.02153799273983672, 0.02107609996106984, 0.02001899849606885, 0.02196516947939817, 0.02047654705787587, 0.02059561382369167, 0.02148625027035289, 0.02001510925188416, 0.02183059661361331, 0.02111406548114694, 0.01881826666371129, 0.02112797285609031, 0.02034071592473790, 0.01968993216337670, 0.02126946766383166, 0.02025580366897253, 0.02061136834815962, 0.02083881238552183, 0.01994368379135331, 0.02046280212696892, 0.02131631148368759, 0.01967275608960357, 0.02064965975476278, 0.02155758091535052, 0.02012557837329328, 0.02084718400924722, 0.02065094443305587, 0.01969546187428681, 0.02136531489392276, 0.02084491659074202, 0.01970883494847568, 0.02080349992636310, 0.02098440611808174, 0.02159984658204099, 0.02045819959592145, 0.01952755742791563, 0.02166002908586709, 0.02017512590511229, 0.01932658087972190, 0.02108933627170306, 0.02019288778648293}}; + // Build discrete eta distribution for sampling: + std::discrete_distribution etaLeadPDist{etaLeadPWeights.begin(), etaLeadPWeights.end()}; // Will be passed as the etaDist variable + std::discrete_distribution phiLeadPDist{phiLeadPWeights.begin(), phiLeadPWeights.end()}; + + /// \brief One jet proxy (leadP, leadJet or subJet). Bound by reference so applyProxyDistortion() edits in-place. + struct ProxyState { + bool& hasValidProxy; //! whether the proxy is valid. Re-evaluated against minPtThreshold after distortion + float& pt; + float& eta; + float& phi; + XYZVector& unitVec; //! the proxy direction as a unit vector + }; + + /// \brief The cache slots shared by forcePreviousJet and doMixedEventProxies, also bound by reference. + /// \note Fallback skips this event using ProxyState::hasValidProxy. + /// \note forcePreviousJet updates these here for the next collision, in a simplistic event event mixing approach. + // doMixedEventProxies instead overwrites the caller's cache fields with this collision's mixed proxy right before the call. + struct ProxyCacheRef { + bool& hadProxy; + float& eta; + float& phi; + }; + + /// \brief Applies whichever fakePolSwitches distortion is active to a jet-proxy direction, in place. No-op if none are on. + /// \param proxy input/edited in-place: the proxy's kinematics, overwritten by whichever distortion is active. + /// \param minPtThreshold pT threshold for re-evaluating hasValidProxy after distortion. + /// \param cache input/edited in-place: the previous-jet/mixed-event caches. + /// \param etaDist,phiDist,rng sampling distributions/generator for forceRandJet and forceDatalikeJet. + /// \note Shared across leadP/leadJet/subJet: the caller resolves which proxy-specific procedure (e.g., LUT for evtMixing) applies. + // Helper to modify the jet direction for QA and for spurious signal baseline removal tests: + inline void applyProxyDistortion(ProxyState proxy, float minPtThreshold, ProxyCacheRef cache, + std::discrete_distribution& etaDist, std::discrete_distribution& phiDist, std::mt19937& rng) + { + if (!fakePolSwitches.forcePerpToJet && !fakePolSwitches.forceJetDirectionSmudge && !fakePolSwitches.forceRandJet && !fakePolSwitches.forcePreviousJet && !fakePolSwitches.forceDatalikeJet && !fakePolSwitches.doMixedEventProxies) [[likely]] { + return; // Skip this function if none of the modifications are actually being executed! + } + + // QA block -- Purposefully changing the jet direction (should kill signal, if any): + if (fakePolSwitches.forcePerpToJet) { + // First, we build a vector perpendicular to the jet by picking an arbitrary vector not parallel to the jet + XYZVector perpVec; + if (std::abs(proxy.unitVec.X()) > 0.99) { + perpVec = XYZVector(-proxy.unitVec.Z(), 0., proxy.unitVec.X()).Unit(); // Cross product with Y-axis (0, 1, 0) + } else { + perpVec = XYZVector(0., proxy.unitVec.Z(), -proxy.unitVec.Y()).Unit(); // Cross product with X-axis (1, 0, 0) + } + + // Now we rotate around the jet axis by a random angle, just to make sure we are not introducing a bias in the QA: + // We will use Rodrigues' rotation formula (v_rot = v*cos(randomAngle) + (Jet \cross v)*sin(randomAngle)) + const double randomAngle = randomGen.Uniform(0., o2::constants::math::TwoPI); + proxy.unitVec = perpVec * std::cos(randomAngle) + proxy.unitVec.Cross(perpVec) * std::sin(randomAngle); + } else if (fakePolSwitches.forceJetDirectionSmudge) { + // Smear the jet direction by a small random angle to estimate sensitivity to + // jet axis uncertainty. We rotate the jet axis by angle theta around a uniformly + // random perpendicular axis -- this is isotropic and coordinate-independent, + // unlike smearing eta and phi separately (which would break azimuthal symmetry + // around the jet axis and depend on where in eta the jet sits). + + // 1) We pick a uniformly random axis perpendicular to the jet. + // (re-using the same Rodrigues formula as in the forcePerpToJet block above) + XYZVector perpVec; + if (std::abs(proxy.unitVec.X()) > 0.99) { + perpVec = XYZVector(-proxy.unitVec.Z(), 0., proxy.unitVec.X()).Unit(); // Cross product with Y-axis (0, 1, 0) + } else { + perpVec = XYZVector(0., proxy.unitVec.Z(), -proxy.unitVec.Y()).Unit(); // Cross product with X-axis (1, 0, 0) + } + + // Rotate perpVec around the jet axis by a uniform random azimuth to get + // a uniformly distributed random perpendicular direction (the smear axis): + const double smearAzimuth = randomGen.Uniform(0., o2::constants::math::TwoPI); + XYZVector smearAxis = perpVec * std::cos(smearAzimuth) + proxy.unitVec.Cross(perpVec) * std::sin(smearAzimuth); + + // 2) draw the smearing polar angle from a Gaussian: + // sigma = 0.05 * R --> ~68% of events smeared within 5% of R, + // ~95% of events smeared within 10% of R, + // ~5% see a displacement > 0.1*R (a very "badly determined jet", for our QA purposes) + // std::abs() folds the symmetric Gaussian onto a half-normal ([0, inf)) + // -- R is not really an angle: just gives me a scale for the angular shift I am performing. + // -- This may pose problems for forward jets: a small displacement in \theta becomes a large displacement in \eta space + const double smearAngle = std::abs(randomGen.Gaus(0., smearSigma)); + + // 3) rotate the jet axis by smearAngle around smearAxis. + // Rodrigues is v_rot = v*cos(theta) + (k \cross v)*sin(theta) + k*(k \cdot v)*(1-cos(theta)) + // But the last term vanishes because smearAxis is perpendicular to unitVec: + proxy.unitVec = proxy.unitVec * std::cos(smearAngle) + smearAxis.Cross(proxy.unitVec) * std::sin(smearAngle); + // Also, rotation preserves the norm, so no re-normalisation is needed for this to be a unit vector. + } else if (fakePolSwitches.forceRandJet) { + // This randomization was made different for each proxy (LeadP, LeadJet, SubLeadJet): bear that in mind! + // 1) Uniformly sample cos(theta) and phi to ensure an isotropic distribution (could also use TRandom::Sphere as well, but may be slower) + // Notice that uniformly sampling theta would make the distribution non-isotropic, thus we use cos(theta)! + const double cosTheta = randomGen.Uniform(-1., 1.); + const double sinTheta = std::sqrt(1. - cosTheta * cosTheta); + const double randPhi = randomGen.Uniform(0., o2::constants::math::TwoPI); + + // 2) Construct the new random unit vector (there is no need to use the magnitude at all! We only need direction here): + proxy.unitVec = XYZVector(sinTheta * std::cos(randPhi), sinTheta * std::sin(randPhi), cosTheta); + } else if (fakePolSwitches.forcePreviousJet) { // Use the jet direction from the immediately preceding collision. The simplest event mixing + const bool usableProxy = cache.hadProxy; + if (usableProxy) { + const double inverseCoshEta = 1.0 / std::cosh(cache.eta); + const double sinPhi = std::sin(cache.phi); + const double cosPhi = std::cos(cache.phi); + proxy.unitVec = XYZVector(cosPhi * inverseCoshEta, sinPhi * inverseCoshEta, std::tanh(cache.eta)); + } + + // Update cache with the current event data: + cache.hadProxy = proxy.hasValidProxy; + cache.eta = proxy.eta; + cache.phi = proxy.phi; + + // Current event cannot use previous-jet mixing if previous event lacked a proxy + if (!usableProxy) + proxy.hasValidProxy = false; + } else if (fakePolSwitches.forceDatalikeJet) { // A compromise between forceRandJet and forcePreviousJet, using data-like weights for sampling jets + const float etaMin = -0.92f; + const float etaBinWidth = 0.04f; + constexpr float phiBinWidth = constants::math::TwoPI / 50.f; + + // Pick one of the 46 bins according to etaWeights: + const int binEtaIdx = etaDist(rng); + const int binPhiIdx = phiDist(rng); + + // Uniformly smear inside the chosen bin: + proxy.eta = etaMin + etaBinWidth * (binEtaIdx + std::generate_canonical(rng)); + proxy.phi = phiBinWidth * (binPhiIdx + std::generate_canonical(rng)); + + const double inverseCoshEta = 1.0 / std::cosh(proxy.eta); + const double sinPhi = std::sin(proxy.phi); + const double cosPhi = std::cos(proxy.phi); + proxy.unitVec = XYZVector(cosPhi * inverseCoshEta, sinPhi * inverseCoshEta, std::tanh(proxy.eta)); + } else if (fakePolSwitches.doMixedEventProxies) { + // Mixes this proxy's direction from a real, similar other collision, rather than sampling a fitted distribution as datalikeJet. + // Only unitVec is rebuilt, eta/phi are left as this collision's own real values -- same convention forcePreviousJet uses above. + if (cache.hadProxy) { + const double inverseCoshEta = 1.0 / std::cosh(cache.eta); + const double sinPhi = std::sin(cache.phi); + const double cosPhi = std::cos(cache.phi); + proxy.unitVec = XYZVector(cosPhi * inverseCoshEta, sinPhi * inverseCoshEta, std::tanh(cache.eta)); + } else { + // No mixing partner found for this collision (sparse bins!). + // Same skip that forcePreviousJet does above: an event without a valid mixed proxy cannot be used. + proxy.hasValidProxy = false; + } + } + + // Recalculating pT, phi and eta after distortions: + // (without this, later kinematic selections make no sense at all! In the forceRandJet case, the ring observable would always sum zero) + if (proxy.hasValidProxy) { // If you don't check this flag here, the "if (!usableProxy)" change would be silently overwritten + if (!fakePolSwitches.forcePreviousJet && !fakePolSwitches.forceDatalikeJet) { + // (In these two cases, we already know the eta and phi variables. No need to recompute) + // Calculating total jet momentum, which should be preserved, just to recalculate the new jet Pt: + const double mag = proxy.pt * std::cosh(proxy.eta); + + // For stability (Rho is the projection on the transverse plane, badly behaved for high |eta|): + const double transverseNorm = std::max(proxy.unitVec.Rho(), 1e-12); // Stability guard + + // Recalculate pT: + proxy.pt = mag * transverseNorm; + + // Recalculate phi: + proxy.phi = RecoDecay::constrainAngle(std::atan2(proxy.unitVec.Y(), proxy.unitVec.X()), 0.0f); // atan2 outputs [-PI, PI), and DataModel convention was [0,2PI) as per FastJet's phi() getter + // proxy.phi = std::atan2(proxy.unitVec.Y(), proxy.unitVec.X()); + // if (proxy.phi < 0.0f) + // proxy.phi += o2::constants::math::TwoPI; + + // Stable eta computation: + // Stabler than 0.5 * std::log((1. + cosTheta) / (1. - cosTheta)) + proxy.eta = std::asinh(proxy.unitVec.Z() / transverseNorm); + } + + // Recalculate the bool after distortion to see if we proceed with this jet proxy: + proxy.hasValidProxy = proxy.pt > minPtThreshold; + } + } + + // Caching the previous collision's jet directions -- An optional feature for forcePreviousJet QA: + struct PrevJetCache { + // Leading jet + bool hadLeadJet; + float leadJetEta; + float leadJetPhi; + // Subleading jet + bool hadSubJet; + float subJetEta; + float subJetPhi; + // Leading particle + bool hadLeadP; + float leadPEta; + float leadPPhi; + }; + + // Per-collision leading/subleading jet, computed once per dataframe and shared by both the main loop below + // (instead of re-scanning RingJets once per resampling pass) and the leadJet/subJet event mixing functions: + struct JetProxyCache { + bool hasValidLeadingJet = false; + float leadingJetPt = -1.f; + float leadingJetEta = 0.f; + float leadingJetPhi = 0.f; + bool hasValidSubJet = false; + float subleadingJetPt = -1.f; + float subleadingJetEta = 0.f; + float subleadingJetPhi = 0.f; + }; + + // A simple struct for doMixedEventProxies. Each proxy (leadP/leadJet/subJet) gets its own independent cache. + struct MixedProxyInfo { + float eta; + float phi; + int64_t sourceCollisionId; + }; + + /// \brief Uniform random pick among each target's candidate window, via reservoir sampling + /// (cannibalization of proxies by neighbouring collisions in Continuous Readout is not a worry as ITS hits are being demanded) + /// \note Reuses the task's own rng member rather than a separate generator per proxy. + void reservoirInsert(std::unordered_map& candidateCount, std::unordered_map& lut, + int64_t targetId, const MixedProxyInfo& candidate) + { + int& nSeen = candidateCount[targetId]; + ++nSeen; + std::uniform_int_distribution pick(1, nSeen); + if (pick(rng) == 1) { + lut[targetId] = candidate; + } + } + + /// \brief How many different target collisions each source collision ended up supplying, from a finished LUT. + static std::unordered_map tallySourceUsage(std::unordered_map const& lut) + { + std::unordered_map usage; + for (auto const& kv : lut) + usage[kv.second.sourceCollisionId]++; + return usage; + } + + // Defining filters for events: + Filter zvtxFilter = (nabs(o2::aod::lambdajetpol::zvtx) < maxZVtxPosition); // Preslices for correct collisions association: - // (TODO: test using custom grouping) + // (tested custom grouping and performs worse here) Preslice perColJets = o2::aod::lambdajetpol::ringCollisionId; Preslice perColV0s = o2::aod::lambdajetpol::ringCollisionId; Preslice perColLeadPs = o2::aod::lambdajetpol::ringCollisionId; - void processPolarizationData(o2::aod::RingCollisions const& collisions, o2::aod::RingJets const& jets, o2::aod::RingLaV0s const& v0s, + // For doMixedEventProxies: + SliceCache mixCache; + /// \brief Main analysis loop: for each collision, rebuilds the leading jet/particle proxies (with optional fakePolSwitches distortions), then loops over V0s computing the ring observable and polarization-vector profiles for every enabled kinematic-cut family. + void processPolarizationData(soa::Filtered const& collisions, o2::aod::RingJets const& jets, o2::aod::RingLaV0s const& v0s, o2::aod::RingLeadPs const& leadPs) { + // Event mixing, built once per dataframe + // Simplistic event mixing using previous collision: + PrevJetCache prevJetCache{}; // Initializing struct before the collisions loop. This zero-initializes, so bools start as false + // This should not be used along nProxyResamples > 1, as it does not apply to that case. + + // Leading/subleading jet per collision, resolved once here instead of inside the (possibly nProxyResamples times resampled) main loop. + // Both the main loop and the leadJet/subJet mixing functions read from this, so RingJets is scanned exactly once + std::unordered_map jetProxyByCollision; for (auto const& collision : collisions) { - const auto collId = collision.globalIndex(); // The self-index accessor - const double centrality = getCentrality(collision); - - // Slice jets, V0s and leading particle belonging to this collision: - // (global collision indices repeat a lot, but they are unique to a same TimeFrame (TF) subfolder in the derived data) - auto v0sInColl = v0s.sliceBy(perColV0s, collId); - auto leadPsInColl = leadPs.sliceBy(perColLeadPs, collId); - - // Check if there is at least one V0 and one jet in the collision: - // (in the way I fill the table, there is always at least one V0 in - // the stored collision, but the jets table can not be filled for - // that collision, and a collision may not be filled when the jets - // table is. Be mindful of that!) - // 1) Require at least one V0: - if (!v0sInColl.size()) - continue; - - // 2) We require at least a leading particle, then we get the leading jet only if it exists: - // (The goal is to see how diluted the signal gets with events which don't even have a loose FastJet jet) - // (The leading particle is built from all tracks that passed the pseudojet - // selection, so it exists whenever FastJet was run on this collision. - // Events that have a leading jet always have a leading particle too, but - // the converse is not true: events can have a leading particle with no jet - // if no jet survives the pT threshold/the background subtraction) - float leadPPt = -1.; // pT = -1 means "table entry not found for this collision". - float leadPEta = 0.; - float leadPPhi = 0.; - float leadPPx = 0., leadPPy = 0., leadPPz = 0.; - for (auto const& lp : leadPsInColl) { - // Table should contain exactly one entry per collision, - // but we break immediately to be safe: - leadPPt = lp.leadParticlePt(); - leadPEta = lp.leadParticleEta(); - leadPPhi = lp.leadParticlePhi(); - // Using dynamic columns to make code cleaner: - leadPPx = lp.leadParticlePx(); - leadPPy = lp.leadParticlePy(); - leadPPz = lp.leadParticlePz(); - break; - } - // Discard events with no leading particle (FastJet didn't even run in these cases!): - if (leadPPt < 0.) - continue; - - // Build leading particle unit vector, outside the V0 loop for performance: - XYZVector leadPUnitVec = XYZVector(leadPPx, leadPPy, leadPPz).Unit(); - - // 3) Checking if the event has a leading jet: - auto jetsInColl = jets.sliceBy(perColJets, collId); - float leadingJetPt = -1.; - float subleadingJetPt = -1.; + const auto collId = collision.globalIndex(); + JetProxyCache cache; // std::optional avoids undefined behaviour from a default-constructed iterator: - // (essentially, just protection for when we fetch jetEta() and the such) std::optional leadingJet; std::optional subleadingJet; - for (auto const& jet : jetsInColl) { + for (auto const& jet : jets.sliceBy(perColJets, collId)) { const auto jetpt = jet.jetPt(); - if (jetpt > leadingJetPt) { + if (jetpt > cache.leadingJetPt) { // Current leading becomes subleading: - subleadingJetPt = leadingJetPt; + cache.subleadingJetPt = cache.leadingJetPt; subleadingJet = leadingJet; // may still be std::nullopt on first pass -- that is fine! // Now update the leading jet: - leadingJetPt = jetpt; + cache.leadingJetPt = jetpt; leadingJet = jet; - } else if (jetpt > subleadingJetPt) { // Update subleading only: - subleadingJetPt = jetpt; + } else if (jetpt > cache.subleadingJetPt) { // Update subleading only: + cache.subleadingJetPt = jetpt; subleadingJet = jet; } } + // Finer control on jet momentum, further than TableProducer pre-selection: + cache.hasValidLeadingJet = cache.leadingJetPt > minLeadJetPt; + cache.hasValidSubJet = cache.subleadingJetPt > minSubLeadJetPt; + if (cache.hasValidLeadingJet) { + cache.leadingJetEta = leadingJet->jetEta(); + cache.leadingJetPhi = leadingJet->jetPhi(); + } + if (cache.hasValidSubJet) { + cache.subleadingJetEta = subleadingJet->jetEta(); + cache.subleadingJetPhi = subleadingJet->jetPhi(); + } + jetProxyByCollision[collId] = cache; + } + + // doMixedEventProxies caches: three independent mixings, one per proxy (a collision may have a valid LeadP, but no SubLeadJet). + // Mixing is binned on (Zvtx, proxy pt, centrality), varying only eta/phi. + std::unordered_map mixedLeadPByCollision; + std::unordered_map mixedLeadJetByCollision; + std::unordered_map mixedSubJetByCollision; + // First we build lookup tables based on current dataframe's collisions (connects pairs of jet proxies from similar collisions): + // (these proxies may come from collisions with no valid Lambdas, by construction, enabling more mixes) + // (This is performed out of the resampling loop, so nProxyResamples will not resample event mixing candidates) + if (fakePolSwitches.doMixedEventProxies) { + auto getMixCentrality = [this](o2::aod::RingCollision const& col) { return this->getCentrality(col); }; // Already filtered even though referencing only RingCollision, not an iterator to Filtered table - // Some useful bools to check if we have a leading jet and a subleading jet: - const bool hasValidLeadingJet = leadingJetPt > 0.; - const bool hasValidSubJet = subleadingJetPt > 0.; - - // Build jet vectors (only when the corresponding jet exists): - // Dummy initialisations are safe: all jet-dependent fills are gated on hasValidLeadingJet / hasValidSubJet. - float leadingJetEta = 0.; - float leadingJetPhi = 0.; - XYZVector leadingJetUnitVec(1., 0., 0.); // dummy (overwritten below) - if (hasValidLeadingJet) { - leadingJetEta = leadingJet->jetEta(); - leadingJetPhi = leadingJet->jetPhi(); - // Using internal getters to make code cleaner: - leadingJetUnitVec = XYZVector(leadingJet->jetPx(), leadingJet->jetPy(), leadingJet->jetPz()).Unit(); - - // QA block -- Purposefully changing the jet direction (should kill signal, if any): - if (forcePerpToJet) { // Use modified jet direction (done outside loop to guarantee all V0s inside event use same fake jet) - // First, we build a vector perpendicular to the jet by picking an arbitrary vector not parallel to the jet - XYZVector refVec(1., 0., 0.); - if (std::abs(leadingJetUnitVec.Dot(refVec)) > 0.99) - refVec = XYZVector(0., 1., 0.); - // Now we get a perpendicular vector to the jet direction: - XYZVector perpVec = leadingJetUnitVec.Cross(refVec).Unit(); - // Now we rotate around the jet axis by a random angle, just to make sure we are not introducing a bias in the QA: - // We will use Rodrigues' rotation formula (v_rot = v*cos(randomAngle) + (Jet \cross v)*sin(randomAngle)) - double randomAngle = randomGen.Uniform(0., o2::constants::math::TwoPI); - leadingJetUnitVec = perpVec * std::cos(randomAngle) + leadingJetUnitVec.Cross(perpVec) * std::sin(randomAngle); - } else if (forceJetDirectionSmudge) { - // Smear the jet direction by a small random angle to estimate sensitivity to - // jet axis uncertainty. We rotate the jet axis by angle theta around a uniformly - // random perpendicular axis -- this is isotropic and coordinate-independent, - // unlike smearing eta and phi separately (which would break azimuthal symmetry - // around the jet axis and depend on where in eta the jet sits). - - // 1) We pick a uniformly random axis perpendicular to the jet. - // (re-using the same Rodrigues formula as in the forcePerpToJet block above) - XYZVector refVec(1., 0., 0.); - if (std::abs(leadingJetUnitVec.Dot(refVec)) > 0.99) - refVec = XYZVector(0., 1., 0.); - XYZVector perpVec = leadingJetUnitVec.Cross(refVec).Unit(); - // Rotate perpVec around the jet axis by a uniform random azimuth to get - // a uniformly distributed random perpendicular direction (the smear axis): - double smearAzimuth = randomGen.Uniform(0., o2::constants::math::TwoPI); - XYZVector smearAxis = perpVec * std::cos(smearAzimuth) + leadingJetUnitVec.Cross(perpVec) * std::sin(smearAzimuth); - - // Step 2: draw the smearing polar angle from a Gaussian: - // sigma = 0.05 * R --> ~68% of events smeared within 5% of R, - // ~95% of events smeared within 10% of R, - // ~5% see a displacement > 0.1*R (a very "badly determined jet", for our QA purposes) - // std::abs() folds the symmetric Gaussian onto a half-normal ([0, inf)) - // -- R is not really an angle: just gives me a scale for the angular shift I am performing. - // -- This may pose problems for forward jets: a small displacemente in \theta becomes a large displacement in \eta space - double smearSigma = 0.05 * jetRForSmuding; - double smearAngle = std::abs(randomGen.Gaus(0., smearSigma)); - - // Step 3: rotate the jet axis by smearAngle around smearAxis. - // Rodrigues is v_rot = v*cos(theta) + (k \croos v)*sin(theta) + k*(k \cdot v)*(1-cos(theta)) - // But the last term vanishes because smearAxis is perpendicular to leadingJetUnitVec: - leadingJetUnitVec = leadingJetUnitVec * std::cos(smearAngle) + smearAxis.Cross(leadingJetUnitVec) * std::sin(smearAngle); - // Also, rotation preserves the norm, so no re-normalisation is needed for this to be a unit vector. + // For the leading particle: + // Pattern follows MixedEventsLambdaBinning tutorial (captures leadPs table and cache define in task's struct): + auto getMixLeadPPt = [&leadPs, this](o2::aod::RingCollision const& col) { + auto rows = leadPs.sliceByCached(o2::aod::lambdajetpol::ringCollisionId, col.globalIndex(), this->mixCache); // As it is cached, grouping happens only once + return rows.size() > 0 ? rows.begin().leadParticlePt() : -999.f; + }; + using LeadPBinningType = FlexibleBinningPolicy, + o2::aod::lambdajetpol::Zvtx, decltype(getMixLeadPPt), decltype(getMixCentrality)>; + LeadPBinningType leadPBinning{{getMixLeadPPt, getMixCentrality}, + {axisConfigurations.axisPVz, axisConfigurations.axisJetPt, axisConfigurations.axisCentrality}, + true}; // Ignore overflows true + + // SameKindPair defaults to CombinationsBlockStrictlyUpperSameIndexPolicy, so no same-event mixing should happen: + //(Already filtered by Zvtx even though we call by aod::RingCollisions, so no need to access the filtered table) + SameKindPair leadPPair{ + leadPBinning, fakePolSwitches.mixedEventWindowSize, -1, collisions, std::make_tuple(leadPs), &mixCache}; + + std::unordered_map leadPCandidateCount; + for (auto it = leadPPair.begin(); it != leadPPair.end(); ++it) { + auto& [c1, leadP1, c2, leadP2] = *it; // Iterates over collision pairs and leading particle pairs (structured binding) + if (leadP1.size() > 0 && leadP2.size() > 0) { // There should always be at least one leadP, given the overflow exclusion above + float eta1 = 0.f, phi1 = 0.f, eta2 = 0.f, phi2 = 0.f; + for (auto const& lp : leadP1) { + eta1 = lp.leadParticleEta(); + phi1 = lp.leadParticlePhi(); + break; + } // Retrieves the first entry + for (auto const& lp : leadP2) { + eta2 = lp.leadParticleEta(); + phi2 = lp.leadParticlePhi(); + break; + } + // Each side of the pair is one more candidate for the other collision's reservoir: + reservoirInsert(leadPCandidateCount, mixedLeadPByCollision, c1.globalIndex(), {eta2, phi2, c2.globalIndex()}); + reservoirInsert(leadPCandidateCount, mixedLeadPByCollision, c2.globalIndex(), {eta1, phi1, c1.globalIndex()}); + histos.fill(HIST("EventMixingQA/hMixedEventLeadPDeltaIndexEligible"), c2.globalIndex() - c1.globalIndex()); + } + if (it.isNewWindow()) { // Count each bin-window once, not once per pair inside it + histos.fill(HIST("EventMixingQA/hMixedEventLeadPWindowNeighbours"), it.currentWindowNeighbours()); } } - float subleadingJetEta = 0.; - float subleadingJetPhi = 0.; - XYZVector subJetUnitVec(1., 0., 0.); - if (hasValidSubJet) { - subleadingJetEta = subleadingJet->jetEta(); - subleadingJetPhi = subleadingJet->jetPhi(); - // Using internal getters to make code cleaner: - subJetUnitVec = XYZVector(subleadingJet->jetPx(), subleadingJet->jetPy(), subleadingJet->jetPz()).Unit(); + // Leading jet: + // pt comes from the jetProxyByCollision pre-pass above. + // (Collisions whose leading jet failed minLeadJetPt get the -999 sentinel, so they never enter this mixing) + auto getMixLeadJetPt = [&jetProxyByCollision](o2::aod::RingCollision const& col) { + auto cacheIt = jetProxyByCollision.find(col.globalIndex()); + if (cacheIt == jetProxyByCollision.end() || !cacheIt->second.hasValidLeadingJet) + return -999.f; + return cacheIt->second.leadingJetPt; + }; + using LeadJetBinningType = FlexibleBinningPolicy, + o2::aod::lambdajetpol::Zvtx, decltype(getMixLeadJetPt), decltype(getMixCentrality)>; + LeadJetBinningType leadJetBinning{{getMixLeadJetPt, getMixCentrality}, + {axisConfigurations.axisPVz, axisConfigurations.axisJetPt, axisConfigurations.axisCentrality}, + true}; + + // RingJets is still the associated table (SameKindPair requires one), but its sliced content goes unused here: + // the borrowed direction comes from jetProxyByCollision, which already knows which jet is the leading one. + SameKindPair leadJetPair{ + leadJetBinning, fakePolSwitches.mixedEventWindowSize, -1, collisions, std::make_tuple(jets), &mixCache}; + + std::unordered_map leadJetCandidateCount; + for (auto it = leadJetPair.begin(); it != leadJetPair.end(); ++it) { + auto& [c1, jets1, c2, jets2] = *it; // jets1/jets2 intentionally unused: the leading/subleading jet is already resolved in jetProxyByCollision + auto cachedLeadJet1 = jetProxyByCollision.find(c1.globalIndex()); + auto cachedLeadJet2 = jetProxyByCollision.find(c2.globalIndex()); + if (cachedLeadJet1 != jetProxyByCollision.end() && cachedLeadJet2 != jetProxyByCollision.end() && + cachedLeadJet1->second.hasValidLeadingJet && cachedLeadJet2->second.hasValidLeadingJet) { + reservoirInsert(leadJetCandidateCount, mixedLeadJetByCollision, c1.globalIndex(), + {cachedLeadJet2->second.leadingJetEta, cachedLeadJet2->second.leadingJetPhi, c2.globalIndex()}); + reservoirInsert(leadJetCandidateCount, mixedLeadJetByCollision, c2.globalIndex(), + {cachedLeadJet1->second.leadingJetEta, cachedLeadJet1->second.leadingJetPhi, c1.globalIndex()}); + histos.fill(HIST("EventMixingQA/hMixedEventLeadJetDeltaIndexEligible"), c2.globalIndex() - c1.globalIndex()); + } + if (it.isNewWindow()) { + histos.fill(HIST("EventMixingQA/hMixedEventLeadJetWindowNeighbours"), it.currentWindowNeighbours()); + } } - // (jet eta cuts only meaningful when the jet actually exists) - const bool kinematicJetCheck = hasValidLeadingJet && (std::abs(leadingJetEta) < 0.5); - const bool kinematic2ndJetCheck = hasValidSubJet && (std::abs(subleadingJetEta) < 0.5); - const bool kinematicLeadPCheck = std::abs(leadPEta) < 0.5; - - for (auto const& v0 : v0sInColl) { - const bool isLambda = v0.isLambda(); - const bool isAntiLambda = v0.isAntiLambda(); - // For now, removing the ambiguous candidates from the analysis. Derived data permits handling both. - // (From Podolanski-Armenteros plots, the population of ambiguous is ~2% without TOF, and without - // competing mass rejection. From those, ~99% seem to be K0s, so no real gain in considering the - // ambiguous candidates in the analysis) - if (isLambda && isAntiLambda) - continue; - const float v0pt = v0.v0Pt(); - const float v0eta = v0.v0Eta(); - const float v0phi = v0.v0Phi(); - - float v0LambdaLikeMass = 0; // Initialized just to catch any stray behavior - float protonLikePt = 0; - float protonLikeEta = 0; - float protonLikePhi = 0; - if (isLambda) { - if (!analyseLambda) - continue; - v0LambdaLikeMass = v0.massLambda(); - protonLikePt = v0.posPt(); - protonLikeEta = v0.posEta(); - protonLikePhi = v0.posPhi(); - } else if (isAntiLambda) { // (TODO: add a split histogram where you consider Lambda and AntiLambda polarization separately?) - if (!analyseAntiLambda) - continue; - v0LambdaLikeMass = v0.massAntiLambda(); - protonLikePt = v0.negPt(); - protonLikeEta = v0.negEta(); - protonLikePhi = v0.negPhi(); + // Subleading jet: + auto getMixSubJetPt = [&jetProxyByCollision](o2::aod::RingCollision const& col) { + auto cacheIt = jetProxyByCollision.find(col.globalIndex()); + if (cacheIt == jetProxyByCollision.end() || !cacheIt->second.hasValidSubJet) + return -999.f; + return cacheIt->second.subleadingJetPt; + }; + using SubJetBinningType = FlexibleBinningPolicy, + o2::aod::lambdajetpol::Zvtx, decltype(getMixSubJetPt), decltype(getMixCentrality)>; + SubJetBinningType subJetBinning{{getMixSubJetPt, getMixCentrality}, + {axisConfigurations.axisPVz, axisConfigurations.axisJetPt, axisConfigurations.axisCentrality}, + true}; + + SameKindPair subJetPair{ + subJetBinning, fakePolSwitches.mixedEventWindowSize, -1, collisions, std::make_tuple(jets), &mixCache}; + + std::unordered_map subJetCandidateCount; + for (auto it = subJetPair.begin(); it != subJetPair.end(); ++it) { + auto& [c1, jets1, c2, jets2] = *it; // jets1/jets2 intentionally unused: the leading/subleading jet is already resolved in jetProxyByCollision + auto cachedSubJet1 = jetProxyByCollision.find(c1.globalIndex()); + auto cachedSubJet2 = jetProxyByCollision.find(c2.globalIndex()); + if (cachedSubJet1 != jetProxyByCollision.end() && cachedSubJet2 != jetProxyByCollision.end() && + cachedSubJet1->second.hasValidSubJet && cachedSubJet2->second.hasValidSubJet) { + reservoirInsert(subJetCandidateCount, mixedSubJetByCollision, c1.globalIndex(), + {cachedSubJet2->second.subleadingJetEta, cachedSubJet2->second.subleadingJetPhi, c2.globalIndex()}); + reservoirInsert(subJetCandidateCount, mixedSubJetByCollision, c2.globalIndex(), + {cachedSubJet1->second.subleadingJetEta, cachedSubJet1->second.subleadingJetPhi, c1.globalIndex()}); + histos.fill(HIST("EventMixingQA/hMixedEventSubJetDeltaIndexEligible"), c2.globalIndex() - c1.globalIndex()); } - - PtEtaPhiMVector lambdaLike4Vec(v0pt, v0eta, v0phi, v0LambdaLikeMass); - PtEtaPhiMVector protonLike4Vec(protonLikePt, protonLikeEta, protonLikePhi, protonMass); - float lambdaRapidity = lambdaLike4Vec.Rapidity(); // For further kinematic selections - - // Boosting proton into lambda frame: - XYZVector beta = -lambdaLike4Vec.BoostToCM(); // Boost trivector that goes from laboratory frame to the rest frame - auto protonLike4VecStar = ROOT::Math::VectorUtil::boost(protonLike4Vec, beta); - - // Getting unit vectors and 3-components: - XYZVector lambdaLike3Vec = lambdaLike4Vec.Vect(); - XYZVector protonLikeStarUnit3Vec = protonLike4VecStar.Vect().Unit(); - - //////////////////////////////////////////// - // Ring observable: Leading particle proxy - // Always computed -- leading particle existence is guaranteed by the second check above - //////////////////////////////////////////// - // Cross product - XYZVector crossLeadP = leadPUnitVec.Cross(lambdaLike3Vec); - float ringObservableLeadP = protonLikeStarUnit3Vec.Dot(crossLeadP) / crossLeadP.R(); - // Adding the prefactor related to the CP-violating decay (decay constants have different signs) - if (!forcePolSignQA) - ringObservableLeadP *= (isLambda) ? polPrefactorLambda : polPrefactorAntiLambda; - else - ringObservableLeadP *= (isLambda) ? polPrefactorLambda : -1.0 * polPrefactorAntiLambda; - // Angular variables - float deltaPhiLeadP = wrapToPiFast(v0phi - leadPPhi); // Wrapped to [-PI, pi), for convenience - float deltaThetaLeadP = ROOT::Math::VectorUtil::Angle(leadPUnitVec, lambdaLike3Vec); // 3D angular separation - - ////////////////////////////////////////// - // Ring observable: Leading jet proxy - // Only computed when a leading jet exists in this collision. - ////////////////////////////////////////// - float ringObservable = 0.; - float deltaPhiJet = 0.; - float deltaThetaJet = 0.; - if (hasValidLeadingJet) { - // Cross product - XYZVector cross = leadingJetUnitVec.Cross(lambdaLike3Vec); - ringObservable = protonLikeStarUnit3Vec.Dot(cross) / cross.R(); - // Adding prefactor - if (!forcePolSignQA) - ringObservable *= (isLambda) ? polPrefactorLambda : polPrefactorAntiLambda; - else - ringObservable *= (isLambda) ? polPrefactorLambda : -1.0 * polPrefactorAntiLambda; - // Angular variables - deltaPhiJet = wrapToPiFast(v0phi - leadingJetPhi); - deltaThetaJet = ROOT::Math::VectorUtil::Angle(leadingJetUnitVec, lambdaLike3Vec); + if (it.isNewWindow()) { + histos.fill(HIST("EventMixingQA/hMixedEventSubJetWindowNeighbours"), it.currentWindowNeighbours()); } + } - ////////////////////////////////////////// - // Ring observable: Subleading jet proxy - // Only computed when a subleading jet exists in this collision. - ////////////////////////////////////////// - float ringObservable2ndJet = 0.; - float deltaPhi2ndJet = 0.; - float deltaTheta2ndJet = 0.; - if (hasValidSubJet) { - XYZVector cross2ndJet = subJetUnitVec.Cross(lambdaLike3Vec); - ringObservable2ndJet = protonLikeStarUnit3Vec.Dot(cross2ndJet) / cross2ndJet.R(); - // Adding prefactor - if (!forcePolSignQA) - ringObservable2ndJet *= (isLambda) ? polPrefactorLambda : polPrefactorAntiLambda; - else - ringObservable2ndJet *= (isLambda) ? polPrefactorLambda : -1.0 * polPrefactorAntiLambda; - // Angular variables - deltaPhi2ndJet = wrapToPiFast(v0phi - subleadingJetPhi); - deltaTheta2ndJet = ROOT::Math::VectorUtil::Angle(subJetUnitVec, lambdaLike3Vec); + // Selected-partner proximity: |target - source| for the partner the reservoir actually kept. + for (auto const& kv : mixedLeadPByCollision) + histos.fill(HIST("EventMixingQA/hMixedEventLeadPDeltaIndexSelected"), std::abs(kv.first - kv.second.sourceCollisionId)); + for (auto const& kv : mixedLeadJetByCollision) + histos.fill(HIST("EventMixingQA/hMixedEventLeadJetDeltaIndexSelected"), std::abs(kv.first - kv.second.sourceCollisionId)); + for (auto const& kv : mixedSubJetByCollision) + histos.fill(HIST("EventMixingQA/hMixedEventSubJetDeltaIndexSelected"), std::abs(kv.first - kv.second.sourceCollisionId)); + + // Source-usage QA: + // (Per-proxy (HIST() needs literal names, so the fills stay unrolled here) + auto leadPUsage = tallySourceUsage(mixedLeadPByCollision); + for (auto const& kv : leadPUsage) + histos.fill(HIST("EventMixingQA/hMixedEventLeadPSourceUsageCount"), kv.second); + // Eta/phi of a mixed proxy is stored under the collision that receives the mixing -- reuse the first hit found: + for (auto const& kv : mixedLeadPByCollision) { + auto usageIt = leadPUsage.find(kv.second.sourceCollisionId); + if (usageIt == leadPUsage.end()) // already consumed below + continue; + histos.fill(HIST("EventMixingQA/pMixedEventLeadPSourceUsageVsEta"), kv.second.eta, usageIt->second); + histos.fill(HIST("EventMixingQA/pMixedEventLeadPSourceUsageVsPhi"), kv.second.phi, usageIt->second); + leadPUsage.erase(usageIt); // Fill this source exactly once, not once per target it supplied + } + + auto leadJetUsage = tallySourceUsage(mixedLeadJetByCollision); + for (auto const& kv : leadJetUsage) + histos.fill(HIST("EventMixingQA/hMixedEventLeadJetSourceUsageCount"), kv.second); + for (auto const& kv : mixedLeadJetByCollision) { + auto usageIt = leadJetUsage.find(kv.second.sourceCollisionId); + if (usageIt == leadJetUsage.end()) + continue; + histos.fill(HIST("EventMixingQA/pMixedEventLeadJetSourceUsageVsEta"), kv.second.eta, usageIt->second); + histos.fill(HIST("EventMixingQA/pMixedEventLeadJetSourceUsageVsPhi"), kv.second.phi, usageIt->second); + leadJetUsage.erase(usageIt); + } + + auto subJetUsage = tallySourceUsage(mixedSubJetByCollision); + for (auto const& kv : subJetUsage) + histos.fill(HIST("EventMixingQA/hMixedEventSubJetSourceUsageCount"), kv.second); + for (auto const& kv : mixedSubJetByCollision) { + auto usageIt = subJetUsage.find(kv.second.sourceCollisionId); + if (usageIt == subJetUsage.end()) + continue; + histos.fill(HIST("EventMixingQA/pMixedEventSubJetSourceUsageVsEta"), kv.second.eta, usageIt->second); + histos.fill(HIST("EventMixingQA/pMixedEventSubJetSourceUsageVsPhi"), kv.second.phi, usageIt->second); + subJetUsage.erase(usageIt); + } + } + + for (int idxResampling = 0; idxResampling < fakePolSwitches.nProxyResamples; idxResampling++) { // resampling loop for forceRandJet and forceDatalikeJet + for (auto const& collision : collisions) { + const float collisionPVz = collision.zvtx(); + + const auto collId = collision.globalIndex(); // The self-index accessor + const float centrality = getCentrality(collision); + + // Used this dummy for backwards compatibility, under the reasonable assumption that the field points always in the same *direction* in the used runs + // (it is not worth it to fetch and store the magnetic field in the datamodel) + const float magField = 1.f; // Purely geometric. + + // Slice jets, V0s and leading particle belonging to this collision: + // (global collision indices repeat a lot, but they are unique to a same TimeFrame (TF) subfolder in the derived data) + auto v0sInColl = v0s.sliceBy(perColV0s, collId); + auto leadPsInColl = leadPs.sliceBy(perColLeadPs, collId); + + // Check if there is at least one V0 and one jet in the collision: + // (in the way I fill the table, there is always at least one V0 in + // the stored collision, but the jets table can not be filled for + // that collision, and a collision may not be filled when the jets + // table is. Be mindful of that!) + // 1) Require at least one V0: + const int nLambdaLikeV0s = v0sInColl.size(); // Caching this variable, as it will be reused in the loop + // In the latest datamodel format, only unambiguous V0s (Lambda XOR antiLambda) are saved, + // so the number of V0s in the collision table is the number of Lambdas/antiLambda identified + // by the table producer. + if (!nLambdaLikeV0s) + continue; + + // 2) We require at least one leading particle: + // (The goal is to see how diluted the signal gets with events which don't even have a loose FastJet jet) + // (The leading particle is built from all tracks that passed the pseudojet + // selection, so it exists whenever FastJet was run on this collision. + // Events that have a leading jet always have a leading particle too, but + // the converse is not true: events can have a leading particle with no jet + // if no jet survives the pT threshold/the background subtraction) + // (At least that is the case when minLeadParticlePt = 0) + float leadPPt = -1.; // pT = -1 means "table entry not found for this collision". + float leadPEta = 0.; + float leadPPhi = 0.; + float leadPPx = 0., leadPPy = 0., leadPPz = 0.; + for (auto const& lp : leadPsInColl) { + // Table should contain exactly one entry per collision, but we break immediately to be safe: + leadPPt = lp.leadParticlePt(); + leadPEta = lp.leadParticleEta(); + leadPPhi = lp.leadParticlePhi(); + // Using dynamic columns to make code cleaner: + leadPPx = lp.leadParticlePx(); + leadPPy = lp.leadParticlePy(); + leadPPz = lp.leadParticlePz(); } + // // Discard events with no leading particle (FastJet didn't even run in these cases!): + // if (leadPPt < 0.) + // continue; + + // Apply minimum pT selection for the leading particle (not necessarily the same as in derived data builder. Can be a stricter cut!): + bool hasValidLeadingP = leadPPt > minLeadParticlePt; + + // Build leading particle unit vector, outside the V0 loop for performance. + XYZVector leadPUnitVec(1., 0., 0.); // dummy (overwritten below when hasValidLeadingP) + if (hasValidLeadingP) { + leadPUnitVec = XYZVector(leadPPx, leadPPy, leadPPz).Unit(); + + // Apply distortion logic: + const bool hadPreviousProxy = prevJetCache.hadLeadP; + + // doMixedEventProxies: get this collision's mixing partner (if any) from the LUT built above: + // (this check is performed only if hasValidLeadingP is true for performance, as the LeadPPt matching for the event mixing would also demand a minimum jet pT) + if (fakePolSwitches.doMixedEventProxies) { + auto itMix = mixedLeadPByCollision.find(collId); + prevJetCache.hadLeadP = (itMix != mixedLeadPByCollision.end()); // Check if this event had a valid mixing target. Reusing prevJetCache + if (prevJetCache.hadLeadP) { + prevJetCache.leadPEta = itMix->second.eta; + prevJetCache.leadPPhi = itMix->second.phi; + } + histos.fill(HIST("EventMixingQA/hMixedEventLeadPOutcome"), prevJetCache.hadLeadP ? 1 : 0); + if (prevJetCache.hadLeadP) { // Only fill the comparison histograms on an actual hit: + histos.fill(HIST("EventMixingQA/h2dMixedLeadPEtaVsLeadPEta"), prevJetCache.leadPEta, leadPEta); + histos.fill(HIST("EventMixingQA/h2dMixedLeadPPhiVsLeadPPhi"), prevJetCache.leadPPhi, leadPPhi); + } + } + + applyProxyDistortion({hasValidLeadingP, leadPPt, leadPEta, leadPPhi, leadPUnitVec}, + minLeadParticlePt, {prevJetCache.hadLeadP, prevJetCache.leadPEta, prevJetCache.leadPPhi}, + etaLeadPDist, phiLeadPDist, rng); + + // Fill distorted-proxy QA histograms: + // Do not gate on the post-distortion hasValidLeadingP (a pT cut) value here! + if (!fakePolSwitches.forcePreviousJet || hadPreviousProxy) { + histos.fill(HIST("JetKinematicsQA/hLeadPEta"), leadPEta); + histos.fill(HIST("JetKinematicsQA/hLeadPPhi"), leadPPhi); + histos.fill(HIST("JetKinematicsQA/hJetCounterPtLeadP"), leadPPt); - // Calculating polarization observables (in the Lambda frame, because that is easier -- does not require boosts): - // To be precise, not actually the polarization, but a part of the summand in P^*_\Lambda = (3/\alpha_\Lambda) * - float PolStarX = 0, PolStarY = 0, PolStarZ = 0; // Dummy initialization: avoid warnings in compile time - if (isLambda) { // Notice there is no need to check analyseLambda again due to previous checks. - PolStarX = polPrefactorLambda * protonLikeStarUnit3Vec.X(); - PolStarY = polPrefactorLambda * protonLikeStarUnit3Vec.Y(); - PolStarZ = polPrefactorLambda * protonLikeStarUnit3Vec.Z(); - } else if (isAntiLambda) { - PolStarX = polPrefactorAntiLambda * protonLikeStarUnit3Vec.X(); - PolStarY = polPrefactorAntiLambda * protonLikeStarUnit3Vec.Y(); - PolStarZ = polPrefactorAntiLambda * protonLikeStarUnit3Vec.Z(); + histos.fill(HIST("JetKinematicsQA/h2dLeadPEtaVsPVz"), leadPEta, collisionPVz); + if (doJetProxy5dQA) + histos.fill(HIST("JetKinematicsQA/h5dLeadPEtaPhiPtPVzCent"), leadPEta, leadPPhi, leadPPt, collisionPVz, centrality); + } } - float v0phiToFillHists = wrapToPiFast(v0phi); // A short wrap to reuse some predefined axes + // 3) Fetching leading jet and subleading jet -- Resolved once per collision in the jetProxyByCollision pre-pass: + const JetProxyCache& jetProxies = jetProxyByCollision.at(collId); // .at() makes it sure we don't create a new key in the map + float leadingJetPt = jetProxies.leadingJetPt; + float subleadingJetPt = jetProxies.subleadingJetPt; - // Fill ring histograms: (1D, lambda 2D correlations and jet 2D correlations): - RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "Ring") // Notice the usage of macros! If you change the variable names, this WILL break the code! - // No, there should NOT be any ";" here! Read the macro definition for an explanation - histos.fill(HIST("pRingCutsLeadingP"), 0, ringObservableLeadP); // First bin of comparison - POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "Ring") + // Defining local bools that may be changed by applyProxyDistortion: + bool hasValidLeadingJet = jetProxies.hasValidLeadingJet; + bool hasValidSubJet = jetProxies.hasValidSubJet; + // Build jet vectors (only when the corresponding jet exists): + // Dummy initialisations are safe: all jet-dependent fills are gated on hasValidLeadingJet / hasValidSubJet. + float leadingJetEta = 0.; + float leadingJetPhi = 0.; + XYZVector leadingJetUnitVec(1., 0., 0.); // dummy (overwritten below) if (hasValidLeadingJet) { - RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "Ring") - histos.fill(HIST("pRingCuts"), 0, ringObservable); + leadingJetEta = jetProxies.leadingJetEta; + leadingJetPhi = jetProxies.leadingJetPhi; + // Rebuild the direction from the cached eta/phi (cheaper than calling jetPx() internal getters and then normalizing with .Unit()): + const double inverseCoshEta = 1.0 / std::cosh(leadingJetEta); + leadingJetUnitVec = XYZVector(std::cos(leadingJetPhi) * inverseCoshEta, std::sin(leadingJetPhi) * inverseCoshEta, std::tanh(leadingJetEta)); + + // Apply distortion logic: + const bool hadPreviousProxy = prevJetCache.hadLeadJet; + if (fakePolSwitches.doMixedEventProxies) { + // Get this collision's leading-jet mixing partner (if any) from the LUT built above: + auto itMix = mixedLeadJetByCollision.find(collId); + prevJetCache.hadLeadJet = (itMix != mixedLeadJetByCollision.end()); + if (prevJetCache.hadLeadJet) { + prevJetCache.leadJetEta = itMix->second.eta; + prevJetCache.leadJetPhi = itMix->second.phi; + } + histos.fill(HIST("EventMixingQA/hMixedEventLeadJetOutcome"), prevJetCache.hadLeadJet ? 1 : 0); + if (prevJetCache.hadLeadJet) { + histos.fill(HIST("EventMixingQA/h2dMixedLeadJetEtaVsLeadJetEta"), prevJetCache.leadJetEta, leadingJetEta); + histos.fill(HIST("EventMixingQA/h2dMixedLeadJetPhiVsLeadJetPhi"), prevJetCache.leadJetPhi, leadingJetPhi); + } + } + applyProxyDistortion({hasValidLeadingJet, leadingJetPt, leadingJetEta, leadingJetPhi, leadingJetUnitVec}, + minLeadJetPt, {prevJetCache.hadLeadJet, prevJetCache.leadJetEta, prevJetCache.leadJetPhi}, + etaLeadPDist, phiLeadPDist, rng); + + // Fill distorted-proxy QA histograms: + // Do not gate on the post-distortion hasValidLeadingJet (a pT cut) value here! + if (!fakePolSwitches.forcePreviousJet || hadPreviousProxy) { + histos.fill(HIST("JetKinematicsQA/hLeadJetEta"), leadingJetEta); + histos.fill(HIST("JetKinematicsQA/hLeadJetPhi"), leadingJetPhi); + histos.fill(HIST("JetKinematicsQA/hJetCounterPtJet"), leadingJetPt); + + histos.fill(HIST("JetKinematicsQA/h2dLeadJetEtaVsPVz"), leadingJetEta, collisionPVz); + if (doJetProxy5dQA) + histos.fill(HIST("JetKinematicsQA/h5dLeadJetEtaPhiPtPVzCent"), leadingJetEta, leadingJetPhi, leadingJetPt, collisionPVz, centrality); + } } + + float subleadingJetEta = 0.; + float subleadingJetPhi = 0.; + XYZVector subJetUnitVec(1., 0., 0.); if (hasValidSubJet) { - RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "Ring") - histos.fill(HIST("pRingCutsSubLeadingJet"), 0, ringObservable2ndJet); + subleadingJetEta = jetProxies.subleadingJetEta; + subleadingJetPhi = jetProxies.subleadingJetPhi; + const double inverseCoshEtaSub = 1.0 / std::cosh(subleadingJetEta); + subJetUnitVec = XYZVector(std::cos(subleadingJetPhi) * inverseCoshEtaSub, std::sin(subleadingJetPhi) * inverseCoshEtaSub, std::tanh(subleadingJetEta)); + + // Apply distortion logic: + const bool hadPreviousProxy = prevJetCache.hadSubJet; + if (fakePolSwitches.doMixedEventProxies) { + // Get this collision's subleading-jet mixing partner (if any) from the LUT built above: + auto itMix = mixedSubJetByCollision.find(collId); + prevJetCache.hadSubJet = (itMix != mixedSubJetByCollision.end()); + if (prevJetCache.hadSubJet) { + prevJetCache.subJetEta = itMix->second.eta; + prevJetCache.subJetPhi = itMix->second.phi; + } + histos.fill(HIST("EventMixingQA/hMixedEventSubJetOutcome"), prevJetCache.hadSubJet ? 1 : 0); + if (prevJetCache.hadSubJet) { + histos.fill(HIST("EventMixingQA/h2dMixedSubJetEtaVsSubJetEta"), prevJetCache.subJetEta, subleadingJetEta); + histos.fill(HIST("EventMixingQA/h2dMixedSubJetPhiVsSubJetPhi"), prevJetCache.subJetPhi, subleadingJetPhi); + } + } + applyProxyDistortion({hasValidSubJet, subleadingJetPt, subleadingJetEta, subleadingJetPhi, subJetUnitVec}, + minSubLeadJetPt, {prevJetCache.hadSubJet, prevJetCache.subJetEta, prevJetCache.subJetPhi}, + etaLeadPDist, phiLeadPDist, rng); + + // Fill distorted-proxy QA histograms: + // Do not gate on the post-distortion hasValidSubJet (a pT cut) value here! + if (!fakePolSwitches.forcePreviousJet || hadPreviousProxy) { + histos.fill(HIST("JetKinematicsQA/hSubLeadJetEta"), subleadingJetEta); + histos.fill(HIST("JetKinematicsQA/hSubLeadJetPhi"), subleadingJetPhi); + histos.fill(HIST("JetKinematicsQA/hJetCounterPt2ndJet"), subleadingJetPt); + + histos.fill(HIST("JetKinematicsQA/h2dSubLeadJetEtaVsPVz"), subleadingJetEta, collisionPVz); + if (doJetProxy5dQA) + histos.fill(HIST("JetKinematicsQA/h5dSubLeadJetEtaPhiPtPVzCent"), subleadingJetEta, subleadingJetPhi, subleadingJetPt, collisionPVz, centrality); + } } - // Extra kinematic criteria for Lambda candidates (removes polarization background): - const bool kinematicLambdaCheck = (v0pt > 0.5 && v0pt < 1.5) && std::abs(lambdaRapidity) < 0.5; - if (kinematicLambdaCheck) { - RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") - histos.fill(HIST("pRingCutsLeadingP"), 1, ringObservableLeadP); - POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") + // (jet eta cuts only meaningful when the jet actually exists) + const bool kinematicJetCheck = hasValidLeadingJet && (std::abs(leadingJetEta) < 0.5); + const bool kinematic2ndJetCheck = hasValidSubJet && (std::abs(subleadingJetEta) < 0.5); + const bool kinematicLeadPCheck = hasValidLeadingP && (std::abs(leadPEta) < 0.5); + + // Quick bools that are useful for detector asymmetry QA: + const bool jetEtaPos = hasValidLeadingJet && (leadingJetEta >= 0.); // Only perform >= check if has validJet + const bool subJetEtaPos = hasValidSubJet && (subleadingJetEta >= 0.); + const bool leadPEtaPos = hasValidLeadingP && (leadPEta >= 0.); + + // Stricter QA version of the bools -- Jets have a radius that makes it possible eta_{jet} > 0, yet half its tracks are in eta < 0 + // (This does not apply to leading particles, obviously. They have no substructure in eta) + const bool jetEtaStrict = hasValidLeadingJet && (std::abs(leadingJetEta) >= jetR); + const bool subJetEtaStrict = hasValidSubJet && (std::abs(subleadingJetEta) >= jetR); + // If one was to define bools for each side of the detector (not needed in the current if-else structure on TProfile fills) + // const bool jetEtaStrictPos = jetEtaPos && jetEtaStrict; + // const bool jetEtaStrictNeg = !jetEtaPos && jetEtaStrict; + // const bool subJetEtaStrictPos = subJetEtaPos && subJetEtaStrict; + // const bool subJetEtaStrictNeg = !subJetEtaPos && subJetEtaStrict; + + // Fetching number of Lambda-like V0s in collision (must be known before full loop, to fill "pRingVsNV0s"): + // int nLambdaLikeV0s = 0; + // for (auto const& v0 : v0sInColl) { + // if (v0.isLambda() ^ v0.isAntiLambda()){ // XOR (only the non-ambiguous candidates) + // nLambdaLikeV0s++; + // } + // } + // Code above was superseeded: new datamodel does not store ambiguous candidates! + // The new getter comes at the very start of processPolarizationData() now. + + // Initialize delta method accumulators (reset for new collision): + for (auto const& tracker : {&trackRing, &trackRingKinCuts, &trackJetKinCuts, &trackJetLambdaKinCuts}) + tracker->reset(); + for (auto const& v0 : v0sInColl) { + const bool isLambda = v0.isLambda(); // true: is a Lambda. false: is an antiLambda. + // For now, removing the ambiguous candidates from the analysis. New datamodel does NOT save ambiguous candidates. + // (From Podolanski-Armenteros plots, the population of ambiguous is ~3.8% without TOF, and without + // competing mass rejection. From those, ~99% seem to be K0s, so no real gain in considering the + // ambiguous candidates in the analysis) + // const bool isAntiLambda = v0.isAntiLambda(); // No longer used! + // if (isLambda && isAntiLambda) continue; + const float v0pt = v0.v0Pt(); + const float v0eta = v0.v0Eta(); + const float v0phi = v0.v0Phi(); + const float dcaDau = v0.dcaV0Daughters(); + + float v0LambdaLikeMass = v0.massV0(); + float protonLikePt = 0; + float protonLikeEta = 0; + float protonLikePhi = 0; + float protonLikeDCADauToPV = 0; + float pionLikeDCADauToPV = 0; + if (isLambda) { + if (!analyseLambda) + continue; + protonLikePt = v0.posPt(); + protonLikeEta = v0.posEta(); + protonLikePhi = v0.posPhi(); + protonLikeDCADauToPV = v0.dcaPosToPV(); + pionLikeDCADauToPV = v0.dcaNegToPV(); + } else { // Guaranteed to be an antiLambda candidate, not an ambiguous candidate + if (!analyseAntiLambda) + continue; + protonLikePt = v0.negPt(); + protonLikeEta = v0.negEta(); + protonLikePhi = v0.negPhi(); + protonLikeDCADauToPV = v0.dcaNegToPV(); + pionLikeDCADauToPV = v0.dcaPosToPV(); + } + + PtEtaPhiMVector lambdaLike4Vec(v0pt, v0eta, v0phi, v0LambdaLikeMass); + PtEtaPhiMVector protonLike4Vec(protonLikePt, protonLikeEta, protonLikePhi, ProtonMass); + const float lambdaRapidity = lambdaLike4Vec.Rapidity(); // For further kinematic selections + const int v0InMassPeak = (v0LambdaLikeMass >= 1.1020593 && v0LambdaLikeMass <= 1.1288811); // Very naive estimator. Based on signal extractions from outside this code + + // Inexpensive estimates of signal extraction effects on the observable: + if (excludeOutOfPeakQA && !v0InMassPeak) + continue; + else if (excludeInPeakQA && v0InMassPeak) + continue; + + // Boosting proton into lambda frame: + XYZVector beta = lambdaLike4Vec.BoostToCM(); // Boost trivector that goes from laboratory frame to Lambda's rest frame (convenient new function, different from TLorentzVector's BoostVector()) + auto protonLike4VecStar = ROOT::Math::VectorUtil::boost(protonLike4Vec, beta); + + // Getting unit vectors and 3-components: + XYZVector lambdaLike3Vec = lambdaLike4Vec.Vect(); + auto lambdaLikeUnit3Vec = lambdaLike3Vec.Unit(); + XYZVector protonLikeStarUnit3Vec = protonLike4VecStar.Vect().Unit(); + + // Lab-frame Lambda momentum components -- Not for polarization, but for actual momenta plotting in XY and ZX planes: + // (Used for the (px,py) and (pz,px) polarization vector-field / ring 2D profiles) + const float v0px = lambdaLike3Vec.X(); + const float v0py = lambdaLike3Vec.Y(); + const float v0pz = lambdaLike3Vec.Z(); + + // Calculating fake polarization ("negative helicity problem") estimator: + // (this estimator is calculated outside of any gate, as it does not depend on jet proxy used) + float cosFakePol = protonLikeStarUnit3Vec.Dot(lambdaLikeUnit3Vec); + + // Calculating the azimuthal angle between the Lambda and the proton: + float deltaPhiLambdaProtonStar = wrapToPiFast(lambdaLikeUnit3Vec.Phi() - protonLikeStarUnit3Vec.Phi()); // Phi is defined from -PI to PI in ROOT::Math::Cartesian3D, thus kept the wrapping + + // Calculating the phi* angle: + // e_z = p_Lambda_hat; // e_x = normalize(z_hat cross p_Lambda); // e_y = e_z cross e_x; + // // phi_star = atan2(p_p_star dot e_y, p_p_star dot e_x); + // XYZVector e_x(-lambdaLikeUnit3Vec.Y(), lambdaLikeUnit3Vec.X(), 0.); // Same as e_x = zHat.Cross(lambdaLikeUnit3Vec); + // XYZVector e_y = lambdaLikeUnit3Vec.Cross(e_x); // e_y completes the right-handed coordinate system (e_z is lambdaLikeUnit3Vec) + // float pX = protonLikeStarUnit3Vec.Dot(e_x); + // float pY = protonLikeStarUnit3Vec.Dot(e_y); + // float phiStar = std::atan2(pY, pX); + // Faster implementation: + // pX = p_y * L_x - p_x * L_y + float pX = protonLikeStarUnit3Vec.Y() * lambdaLikeUnit3Vec.X() - protonLikeStarUnit3Vec.X() * lambdaLikeUnit3Vec.Y(); + // pY = p_z - L_z * (p_proton_star dot p_lambda_hat) + float pY = protonLikeStarUnit3Vec.Z() - lambdaLikeUnit3Vec.Z() * cosFakePol; // (Reusing cosFakePol calculated earlier!) + float phiStar = std::atan2(pY, pX); // This will give an output from -PI to PI + + if (qaSwitches.doFakePolDiagnosticsQA) { + // Another reconstruction efficiency measure: + // (Formula is: p_{Lambda} \cross p_{Daughter}^{*} \cdot B, and B points in Z) + if (analyseMagField) { + auto crossGeom = lambdaLike3Vec.Cross(protonLikeStarUnit3Vec); + const bool positiveGeom = crossGeom.Z() * magField > 0; + + if (isLambda && positiveGeom) + histos.fill(HIST("HelicityEfficiencyQA/hLambdaMassDecayGeomRight"), v0LambdaLikeMass); + else if (isLambda && !positiveGeom) + histos.fill(HIST("HelicityEfficiencyQA/hLambdaMassDecayGeomLeft"), v0LambdaLikeMass); + else if (!isLambda && positiveGeom) + histos.fill(HIST("HelicityEfficiencyQA/hAntiLambdaMassDecayGeomRight"), v0LambdaLikeMass); + else + histos.fill(HIST("HelicityEfficiencyQA/hAntiLambdaMassDecayGeomLeft"), v0LambdaLikeMass); + } + + // Measuring the AEE effect differentially (azimuthal efficiency effect, which causes different V0 topologies to be enhanced/suppressed): + if (isLambda) + histos.fill(HIST("HelicityEfficiencyQA/hLambdaMassVsPhiLambdaMinusPhiProtonStar"), v0LambdaLikeMass, deltaPhiLambdaProtonStar); + else + histos.fill(HIST("HelicityEfficiencyQA/hAntiLambdaMassVsPhiLambdaMinusPhiProtonStar"), v0LambdaLikeMass, deltaPhiLambdaProtonStar); + // AEE and HEE correlation: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCounts_CosThetaVsPhiStar"), cosFakePol, phiStar); + } // end doFakePolDiagnosticsQA (per-V0 AEE/HEE) + + // Useful kinematic bools: + const bool lambdaEtaPos = v0eta >= 0.; + const bool pTLambdaCheck = v0pt > 0.5 && v0pt < 1.5; + const bool rapidityLambdaCheck = std::abs(lambdaRapidity) < 0.5; + const bool kinematicLambdaCheck = pTLambdaCheck && rapidityLambdaCheck; + + //////////////////////////////////////////// + // Ring observable: Leading particle proxy + // Only computed when a valid leading particle exists (pT > minLeadParticlePt) + //////////////////////////////////////////// + float ringObservableLeadP = 0.; + float deltaPhiLeadP = 0.; + float deltaThetaLeadP = 0.; + float cosDeltaThetaLeadP = 0.; + if (hasValidLeadingP) { + XYZVector crossLeadP = leadPUnitVec.Cross(lambdaLike3Vec); + ringObservableLeadP = protonLikeStarUnit3Vec.Dot(crossLeadP) / crossLeadP.R(); + // Adding the prefactor related to the CP-violating decay (decay constants have different signs) + if (!fakePolSwitches.forcePolSignQA) + ringObservableLeadP *= (isLambda) ? PolPrefactorLambda : PolPrefactorAntiLambda; + else + ringObservableLeadP *= (isLambda) ? PolPrefactorLambda : -1.0 * PolPrefactorAntiLambda; + // Angular variables + deltaPhiLeadP = wrapToPiFast(v0phi - leadPPhi); // Wrapped to [-PI, PI), for convenience + + cosDeltaThetaLeadP = leadPUnitVec.Dot(lambdaLikeUnit3Vec); // Uses the pre-calculated unit vectors to avoid recomputation + deltaThetaLeadP = std::acos(cosDeltaThetaLeadP); // 3D angular separation. Same as ROOT::Math::VectorUtil::Angle(leadPUnitVec, lambdaLike3Vec); + } + + ////////////////////////////////////////// + // Ring observable: Leading jet proxy + // Only computed when a leading jet exists in this collision. + ////////////////////////////////////////// + float ringObservable = 0.; + float deltaPhiJet = 0.; + float deltaEtaJet = 0.; + float deltaThetaJet = 0.; + float cosDeltaThetaJet = 0.; + float Jz = 0.; + float ringObservableOverJetZ = 0.; if (hasValidLeadingJet) { - RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") - histos.fill(HIST("pRingCuts"), 1, ringObservable); + XYZVector cross = leadingJetUnitVec.Cross(lambdaLike3Vec); + ringObservable = protonLikeStarUnit3Vec.Dot(cross) / cross.R(); + // Adding prefactor + if (!fakePolSwitches.forcePolSignQA) + ringObservable *= (isLambda) ? PolPrefactorLambda : PolPrefactorAntiLambda; + else + ringObservable *= (isLambda) ? PolPrefactorLambda : -1.0 * PolPrefactorAntiLambda; + // Angular variables + deltaPhiJet = wrapToPiFast(v0phi - leadingJetPhi); + deltaEtaJet = v0eta - leadingJetEta; + + cosDeltaThetaJet = leadingJetUnitVec.Dot(lambdaLikeUnit3Vec); + deltaThetaJet = std::acos(cosDeltaThetaJet); + + // Testing an invariance -- /\hat{t}_z -- Possible source of an artificial (trivial) sign flip in the observable: + Jz = leadingJetUnitVec.Z(); + if (std::abs(Jz) > 1e-4) + ringObservableOverJetZ = ringObservable / Jz; + else + ringObservableOverJetZ = 0.0; // A simple guard. May not be the best, but works + + // // A second projection schema, where e_x = normalize(t_hat cross p_Lambda), using t_hat instead of z_hat (different from phi*): + // // (this decomposes in an orthogonal basis related to the jet coordinates) + // XYZVector ez = lambdaLikeUnit3Vec; + // XYZVector ex = leadingJetUnitVec.Cross(lambdaLike3Vec); + // XYZVector ey = ez.Cross(ex); + + // ringObservableExProjection = ringObservable; + // ringObservableEyProjection = + // // Assuming that energy can get inside the average in: + // // P_Lambda \cdot p_Lambda = E_Lambda/m_Lambda * P_Lambda^* \cdot p_Lambda = * p_Lambda + // ringObservableEzProjection = cosFakePol * lambdaLike4Vec.E()/v0LambdaLikeMass; } + + ////////////////////////////////////////// + // Ring observable: Subleading jet proxy + // Only computed when a subleading jet exists in this collision. + ////////////////////////////////////////// + float ringObservable2ndJet = 0.; + float deltaPhi2ndJet = 0.; + float deltaTheta2ndJet = 0.; + float cosDeltaTheta2ndJet = 0.; if (hasValidSubJet) { - RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") - histos.fill(HIST("pRingCutsSubLeadingJet"), 1, ringObservable2ndJet); + XYZVector cross2ndJet = subJetUnitVec.Cross(lambdaLike3Vec); + ringObservable2ndJet = protonLikeStarUnit3Vec.Dot(cross2ndJet) / cross2ndJet.R(); + // Adding prefactor + if (!fakePolSwitches.forcePolSignQA) + ringObservable2ndJet *= (isLambda) ? PolPrefactorLambda : PolPrefactorAntiLambda; + else + ringObservable2ndJet *= (isLambda) ? PolPrefactorLambda : -1.0 * PolPrefactorAntiLambda; + // Angular variables + deltaPhi2ndJet = wrapToPiFast(v0phi - subleadingJetPhi); + cosDeltaTheta2ndJet = subJetUnitVec.Dot(lambdaLikeUnit3Vec); + deltaTheta2ndJet = std::acos(cosDeltaTheta2ndJet); } - } - // Extra selection criteria on jet candidates: - // (redundant for jets with R=0.4, but for jets with R<0.4 the leading jet may be farther in eta) - if (kinematicJetCheck) { // Already includes hasValidLeadingJet in the bool! (no need to check again) - RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") - histos.fill(HIST("pRingCuts"), 2, ringObservable); - POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") - } + // Calculating polarization observables (in the Lambda frame, because that is easier -- does not require boosts): + // To be precise, not actually the polarization, but a part of the summand in P^*_Lambda = (3/\alpha_Lambda) * + float polStarX = 0, polStarY = 0, polStarZ = 0; // Dummy initialization: avoid warnings in compile time + if (isLambda) { // Notice there is no need to check analyseLambda again due to previous checks. + polStarX = PolPrefactorLambda * protonLikeStarUnit3Vec.X(); + polStarY = PolPrefactorLambda * protonLikeStarUnit3Vec.Y(); + polStarZ = PolPrefactorLambda * protonLikeStarUnit3Vec.Z(); + } else { + polStarX = PolPrefactorAntiLambda * protonLikeStarUnit3Vec.X(); + polStarY = PolPrefactorAntiLambda * protonLikeStarUnit3Vec.Y(); + polStarZ = PolPrefactorAntiLambda * protonLikeStarUnit3Vec.Z(); + } - // Extra selection criteria on both Lambda and jet candidates: - if (kinematicLambdaCheck && kinematicJetCheck) { - RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") - histos.fill(HIST("pRingCuts"), 3, ringObservable); - POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") - } + float v0phiToFillHists = wrapToPiFast(v0phi); // A short wrap to reuse some predefined axes + + // Fill ring histograms: (1D, lambda 2D correlations and jet 2D correlations): + if (hasValidLeadingP) { + if (familySwitches.doFamilyRing) { + RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "Ring") // Notice the usage of macros! If you change the variable names, this WILL break the code! + // No, there should NOT be any ";" here! Read the macro definition for an explanation + } + histos.fill(HIST("IntegratedCuts/pRingCutsLeadingP"), 0, ringObservableLeadP); // First bin of comparison + histos.fill(HIST("IntegratedCuts/hCountCutsLeadingP"), 0); + + // Filling checks that rely on Eta>0 or Eta<0 checks for V0 and LeadingP eta: + if (familySwitches.doFamilyRing) { + RING_OBSERVABLE_LEADP_ETA_SPLIT_FILL_LIST("Ring", leadPEtaPos, lambdaEtaPos); + } + } + if (familySwitches.doFamilyRing) { + POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "Ring") + } + + // Binary search using the pre-fetched axes for delta method of error bar estimation: + int binPt = 0; // Dummy declarations + int binMass = 0; + int binDTheta = 0; + if (hasValidLeadingJet) { + if (familySwitches.doFamilyRing) { + RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "Ring") + } + histos.fill(HIST("IntegratedCuts/pRingCuts"), 0, ringObservable); + histos.fill(HIST("IntegratedCuts/hCountCuts"), 0); + histos.fill(HIST("IntegratedCuts/pRingVsNV0s"), nLambdaLikeV0s, ringObservable); + histos.fill(HIST("hNV0sVsCentrality"), nLambdaLikeV0s, centrality); + + // Properly fetching values as they are needed: + binPt = mAxisPt->FindBin(v0pt); + binMass = mAxisMass->FindBin(v0LambdaLikeMass); + binDTheta = mAxisDTheta->FindBin(deltaThetaJet); + if (familySwitches.doFamilyRing) { + trackRing.addV0(ringObservable, binPt, binMass, binDTheta); + } + + if (qaSwitches.doFakePolDiagnosticsQA) { + // Measuring the AEE differentially (azimuthal efficiency effect, which causes different V0 topologies to be enhanced/suppressed): + if (isLambda) + histos.fill(HIST("HelicityEfficiencyQA/p2dRing_LambdaMassVsPhiLambdaMinusPhiProtonStar"), v0LambdaLikeMass, deltaPhiLambdaProtonStar, ringObservable); + else + histos.fill(HIST("HelicityEfficiencyQA/p2dRing_AntiLambdaMassVsPhiLambdaMinusPhiProtonStar"), v0LambdaLikeMass, deltaPhiLambdaProtonStar, ringObservable); + // AEE and HEE correlation: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsJet_CosThetaVsPhiStar"), cosFakePol, phiStar); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignal_CosThetaVsPhiStar"), cosFakePol, phiStar, ringObservable); + + // AEE and DCA between daughters correlation: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAdau"), phiStar, dcaDau); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdau"), phiStar, dcaDau, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdauVsEtaJet"), phiStar, dcaDau, leadingJetEta, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAdauVsEtaLambda"), phiStar, dcaDau, v0eta, ringObservable); + + // DCA dau to PV correlation: + // For proton-like daughter: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAProLike"), phiStar, protonLikeDCADauToPV); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLike"), phiStar, protonLikeDCADauToPV, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLikeVsEtaJet"), phiStar, protonLikeDCADauToPV, leadingJetEta, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAProLikeVsEtaLambda"), phiStar, protonLikeDCADauToPV, v0eta, ringObservable); + // For pion-like daughter: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsJet_PhiStarVsDCAPiLike"), phiStar, pionLikeDCADauToPV); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLike"), phiStar, pionLikeDCADauToPV, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLikeVsEtaJet"), phiStar, pionLikeDCADauToPV, leadingJetEta, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pFakePolSignalJet_PhiStarVsDCAPiLikeVsEtaLambda"), phiStar, pionLikeDCADauToPV, v0eta, ringObservable); + + histos.fill(HIST("HelicityEfficiencyQA/pRingVsJetZcomponent"), Jz, ringObservable); + histos.fill(HIST("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEta"), leadingJetEta, ringObservableOverJetZ); + histos.fill(HIST("HelicityEfficiencyQA/pRingOverJetZcomponent_VsCosThetaHEE"), cosFakePol, ringObservableOverJetZ); + histos.fill(HIST("HelicityEfficiencyQA/pRingOverJetZcomponent_VsPhiStar"), phiStar, ringObservableOverJetZ); + histos.fill(HIST("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEtaVsCosThetaHEE"), leadingJetEta, cosFakePol, ringObservableOverJetZ); + histos.fill(HIST("HelicityEfficiencyQA/pRingOverJetZcomponent_VsJetEtaVsPhiStar"), leadingJetEta, phiStar, ringObservableOverJetZ); + } // end doFakePolDiagnosticsQA (leading-jet AEE/HEE) + } + if (hasValidSubJet) { + if (familySwitches.doFamilyRing) { + RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "Ring") + } + histos.fill(HIST("IntegratedCuts/pRingCutsSubLeadingJet"), 0, ringObservable2ndJet); + histos.fill(HIST("IntegratedCuts/hCountCutsSubLeadingJet"), 0); + } + + if (qaSwitches.doFakePolDiagnosticsQA) { + // Filling eta dependence QAs of the result (both for V0 and jet proxy): + // Defining shared binning which depend on the V0 only: + const int etaLambdaBin = lambdaEtaPos ? 3 : 4; + if (hasValidLeadingJet) { + histos.fill(HIST("EtaStudy/pRingEtaCuts"), 0, ringObservable); + histos.fill(HIST("EtaStudy/pRingEtaCuts"), etaLambdaBin, ringObservable); + + // Bin indices for this proxy: + // Bin 0: all, 1/2: proxy #eta sign, 3/4: #Lambda #eta sign, 5-8: joint, + // 9/10: |#eta_{proxy}| >= R, 11-14: strict joint. + const int etaProxyBin = jetEtaPos ? 1 : 2; + const int etaProxyLambdaBin = (jetEtaPos ? 5 : 7) + (lambdaEtaPos ? 0 : 1); + const int etaProxyStrictBin = jetEtaPos ? 9 : 10; + const int etaProxyStrictLambdaBin = (jetEtaPos ? 11 : 13) + (lambdaEtaPos ? 0 : 1); + + histos.fill(HIST("EtaStudy/pRingEtaCuts"), etaProxyBin, ringObservable); + histos.fill(HIST("EtaStudy/pRingEtaCuts"), etaProxyLambdaBin, ringObservable); + + // HEE study (helicity efficiency effect): + histos.fill(HIST("EtaStudy/hFakePolCounts"), cosFakePol, 0); + histos.fill(HIST("EtaStudy/hFakePolCounts"), cosFakePol, etaLambdaBin); + histos.fill(HIST("EtaStudy/hFakePolCounts"), cosFakePol, etaProxyBin); + histos.fill(HIST("EtaStudy/hFakePolCounts"), cosFakePol, etaProxyLambdaBin); + // Same for signal: + histos.fill(HIST("EtaStudy/pFakePolSignalVsCosTheta"), cosFakePol, 0, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalVsCosTheta"), cosFakePol, etaLambdaBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalVsCosTheta"), cosFakePol, etaProxyBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalVsCosTheta"), cosFakePol, etaProxyLambdaBin, ringObservable); + // Counter and ring accumulators for AEE study: + histos.fill(HIST("EtaStudy/hCountsVsPhiStar"), phiStar, 0); + histos.fill(HIST("EtaStudy/hCountsVsPhiStar"), phiStar, etaLambdaBin); + histos.fill(HIST("EtaStudy/hCountsVsPhiStar"), phiStar, etaProxyBin); + histos.fill(HIST("EtaStudy/hCountsVsPhiStar"), phiStar, etaProxyLambdaBin); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiStar"), phiStar, 0, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiStar"), phiStar, etaLambdaBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiStar"), phiStar, etaProxyBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiStar"), phiStar, etaProxyLambdaBin, ringObservable); + + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"), deltaPhiLambdaProtonStar, 0, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"), deltaPhiLambdaProtonStar, etaLambdaBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"), deltaPhiLambdaProtonStar, etaProxyBin, ringObservable); + histos.fill(HIST("EtaStudy/pFakePolSignalvsPhiLambdaMinusPhiProtonStar"), deltaPhiLambdaProtonStar, etaProxyLambdaBin, ringObservable); + + // Extra correlations test: + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJet"), cosFakePol, deltaThetaJet); + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsCosThetaVsPtForJets"), cosFakePol, v0pt); + // Split by proxy #eta sign: + if (jetEtaPos) + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJetPosEta"), cosFakePol, deltaThetaJet); + else + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaJetNegEta"), cosFakePol, deltaThetaJet); + + if (pTLambdaCheck) { + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtCut"), cosFakePol, 0); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtCut"), cosFakePol, etaLambdaBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtCut"), cosFakePol, etaProxyBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtCut"), cosFakePol, etaProxyLambdaBin); + if (rapidityLambdaCheck) { // Stricter check + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"), cosFakePol, 0); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"), cosFakePol, etaLambdaBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"), cosFakePol, etaProxyBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLambdaPtYCuts"), cosFakePol, etaProxyLambdaBin); + } + } + if (jetEtaStrict) { // |eta_{Jet}| >= R + histos.fill(HIST("EtaStudy/pRingEtaCuts"), etaProxyStrictBin, ringObservable); + histos.fill(HIST("EtaStudy/pRingEtaCuts"), etaProxyStrictLambdaBin, ringObservable); + } + } + if (hasValidSubJet) { + // Same bin scheme as the leading jet above: + const int etaProxyBin = subJetEtaPos ? 1 : 2; + const int etaProxyLambdaBin = (subJetEtaPos ? 5 : 7) + (lambdaEtaPos ? 0 : 1); + const int etaProxyStrictBin = subJetEtaPos ? 9 : 10; + const int etaProxyStrictLambdaBin = (subJetEtaPos ? 11 : 13) + (lambdaEtaPos ? 0 : 1); + + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), 0, ringObservable2ndJet); + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), etaLambdaBin, ringObservable2ndJet); + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), etaProxyBin, ringObservable2ndJet); + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), etaProxyLambdaBin, ringObservable2ndJet); + if (subJetEtaStrict) { // |eta_{SubJet}| >= R + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), etaProxyStrictBin, ringObservable2ndJet); + histos.fill(HIST("EtaStudy/pRingEtaCutsSubLeadingJet"), etaProxyStrictLambdaBin, ringObservable2ndJet); + } + } + if (hasValidLeadingP) { + // Same bin scheme, without the strict variant (this axis stops at 9 bins): + const int etaProxyBin = leadPEtaPos ? 1 : 2; + const int etaProxyLambdaBin = (leadPEtaPos ? 5 : 7) + (lambdaEtaPos ? 0 : 1); + + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP"), 0, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP"), etaLambdaBin, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP"), etaProxyBin, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP"), etaProxyLambdaBin, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"), 0, v0InMassPeak, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"), etaLambdaBin, v0InMassPeak, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"), etaProxyBin, v0InMassPeak, ringObservableLeadP); + histos.fill(HIST("EtaStudy/pRingEtaCutsLeadingP_MassSignalVsBackground"), etaProxyLambdaBin, v0InMassPeak, ringObservableLeadP); + + histos.fill(HIST("EtaStudy/hFakePolCountsLeadP"), cosFakePol, 0); + histos.fill(HIST("EtaStudy/hFakePolCountsLeadP"), cosFakePol, etaLambdaBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLeadP"), cosFakePol, etaProxyBin); + histos.fill(HIST("EtaStudy/hFakePolCountsLeadP"), cosFakePol, etaProxyLambdaBin); + + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsCosThetaVsPtForLeadP"), cosFakePol, v0pt); // Understanding the population of events that has a leading particle (even though this does not need one to be calculated!) + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadP"), cosFakePol, deltaThetaLeadP); + if (leadPEtaPos) + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadPPosEta"), cosFakePol, deltaThetaLeadP); + else + histos.fill(HIST("HelicityEfficiencyQA/hFakePolCountsVsDeltaThetaLeadPNegEta"), cosFakePol, deltaThetaLeadP); + } + } // end doFakePolDiagnosticsQA (eta-dependence block) + + // Extra kinematic criteria for Lambda candidates (removes polarization background): + if (kinematicLambdaCheck) { + if (hasValidLeadingP) { + if (familySwitches.doFamilyRingKinematicCuts) { + RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsLeadingP"), 1, ringObservableLeadP); + histos.fill(HIST("IntegratedCuts/hCountCutsLeadingP"), 1); + + // Filling checks that rely on Eta>0 or Eta<0 checks for V0 and LeadingP eta: + if (familySwitches.doFamilyRingKinematicCuts) { + RING_OBSERVABLE_LEADP_ETA_SPLIT_FILL_LIST("RingKinematicCuts", leadPEtaPos, lambdaEtaPos); + } + } + if (familySwitches.doFamilyRingKinematicCuts) { + POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") + } + if (hasValidLeadingJet) { + if (familySwitches.doFamilyRingKinematicCuts) { + RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCuts"), 1, ringObservable); + histos.fill(HIST("IntegratedCuts/hCountCuts"), 1); + if (familySwitches.doFamilyRingKinematicCuts) { + trackRingKinCuts.addV0(ringObservable, binPt, binMass, binDTheta); + } + } + if (hasValidSubJet) { + if (familySwitches.doFamilyRingKinematicCuts) { + RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "RingKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsSubLeadingJet"), 1, ringObservable2ndJet); + histos.fill(HIST("IntegratedCuts/hCountCutsSubLeadingJet"), 1); + } + } + + // Extra selection criteria on jet candidates: + // (redundant for jets with R=0.4, but for jets with R<0.4 the leading jet may be farther in eta) + if (kinematicJetCheck) { // Already includes hasValidLeadingJet in the bool! (no need to check again) + if (familySwitches.doFamilyJetKinematicCuts) { + RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCuts"), 2, ringObservable); + histos.fill(HIST("IntegratedCuts/hCountCuts"), 2); + if (familySwitches.doFamilyJetKinematicCuts) { + POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") + trackJetKinCuts.addV0(ringObservable, binPt, binMass, binDTheta); + } + } + + // Extra selection criteria on both Lambda and jet candidates: + if (kinematicLambdaCheck && kinematicJetCheck) { + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + RING_OBSERVABLE_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCuts"), 3, ringObservable); + histos.fill(HIST("IntegratedCuts/hCountCuts"), 3); + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + POLARIZATION_PROFILE_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") + trackJetLambdaKinCuts.addV0(ringObservable, binPt, binMass, binDTheta); + } + } + + // Same variations for the leading particle and for the subleading jet: + // (kinematicLeadPCheck already encodes hasValidLeadingP, so no extra gate needed here) + if (kinematicLeadPCheck) { + if (familySwitches.doFamilyJetKinematicCuts) { + RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsLeadingP"), 2, ringObservableLeadP); + histos.fill(HIST("IntegratedCuts/hCountCutsLeadingP"), 2); + + // Filling checks that rely on Eta>0 or Eta<0 checks for V0 and LeadingP eta: + if (familySwitches.doFamilyJetKinematicCuts) { + RING_OBSERVABLE_LEADP_ETA_SPLIT_FILL_LIST("JetKinematicCuts", leadPEtaPos, lambdaEtaPos); + } + } + if (kinematic2ndJetCheck) { + if (familySwitches.doFamilyJetKinematicCuts) { + RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsSubLeadingJet"), 2, ringObservable2ndJet); + histos.fill(HIST("IntegratedCuts/hCountCutsSubLeadingJet"), 2); + } + if (kinematicLambdaCheck && kinematicLeadPCheck) { + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsLeadingP"), 3, ringObservableLeadP); + histos.fill(HIST("IntegratedCuts/hCountCutsLeadingP"), 3); + + // Filling checks that rely on Eta>0 or Eta<0 checks for V0 and LeadingP eta: + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + RING_OBSERVABLE_LEADP_ETA_SPLIT_FILL_LIST("JetAndLambdaKinematicCuts", leadPEtaPos, lambdaEtaPos); + } + } + if (kinematicLambdaCheck && kinematic2ndJetCheck) { + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") + } + histos.fill(HIST("IntegratedCuts/pRingCutsSubLeadingJet"), 3, ringObservable2ndJet); + histos.fill(HIST("IntegratedCuts/hCountCutsSubLeadingJet"), 3); + } + } // end v0s loop - // Same variations for the leading particle and for the subleading jet: - if (kinematicLeadPCheck) { - RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") - histos.fill(HIST("pRingCutsLeadingP"), 2, ringObservableLeadP); + // Flush trackers to the actual O2 histograms (via macros, so that O2 compiles properly): + if (familySwitches.doFamilyRing) { + FLUSH_DELTA_TRACKER("Ring", trackRing, mAxisPt, mAxisMass, mAxisDTheta) } - if (kinematic2ndJetCheck) { - RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "JetKinematicCuts") - histos.fill(HIST("pRingCutsSubLeadingJet"), 2, ringObservable2ndJet); + if (familySwitches.doFamilyRingKinematicCuts) { + FLUSH_DELTA_TRACKER("RingKinematicCuts", trackRingKinCuts, mAxisPt, mAxisMass, mAxisDTheta) } - if (kinematicLambdaCheck && kinematicLeadPCheck) { - RING_OBSERVABLE_LEADP_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") - histos.fill(HIST("pRingCutsLeadingP"), 3, ringObservableLeadP); + if (familySwitches.doFamilyJetKinematicCuts) { + FLUSH_DELTA_TRACKER("JetKinematicCuts", trackJetKinCuts, mAxisPt, mAxisMass, mAxisDTheta) } - if (kinematicLambdaCheck && kinematic2ndJetCheck) { - RING_OBSERVABLE_2NDJET_FILL_LIST(APPLY_HISTO_FILL, "JetAndLambdaKinematicCuts") - histos.fill(HIST("pRingCutsSubLeadingJet"), 3, ringObservable2ndJet); + if (familySwitches.doFamilyJetAndLambdaKinematicCuts) { + FLUSH_DELTA_TRACKER("JetAndLambdaKinematicCuts", trackJetLambdaKinCuts, mAxisPt, mAxisMass, mAxisDTheta) } - } // end v0s loop - } // end collisions + } // end collisions + } // end of resampling loop for forceRandJet and forceDatalikeJet } PROCESS_SWITCH(lambdajetpolarizationionsderived, processPolarizationData, "Process derived data in Run 3 Data", true);