1818
1919#include " PWGHF/Core/CentralityEstimation.h"
2020#include " PWGHF/D2H/Utils/utilsFlow.h"
21+ #include " PWGHF/Core/DecayChannels.h"
2122#include " PWGHF/DataModel/CandidateReconstructionTables.h"
2223#include " PWGHF/DataModel/CandidateSelectionTables.h"
2324
3536#include < Framework/InitContext.h>
3637#include < Framework/runDataProcessing.h>
3738
39+ #include < Math/GenVector/Boost.h>
40+ #include < Math/GenVector/RotationZ.h>
41+ #include < Math/Vector3D.h> // IWYU pragma: keep (do not replace with Math/Vector3Dfwd.h)
42+ #include < Math/Vector3Dfwd.h>
43+ #include < Math/Vector4D.h> // IWYU pragma: keep (do not replace with Math/Vector4Dfwd.h)
44+ #include < Math/Vector4Dfwd.h>
45+ #include < TPDGCode.h>
46+ #include < TRandom3.h>
47+
3848#include < cstdint>
3949#include < vector>
4050
@@ -44,6 +54,7 @@ using namespace o2::framework::expressions;
4454using namespace o2 ::hf_centrality;
4555using namespace o2 ::hf_occupancy;
4656using namespace o2 ::analysis::hf_flow_utils;
57+ using namespace o2 ::constants::physics;
4758
4859namespace o2 ::aod
4960{
@@ -70,6 +81,7 @@ DECLARE_SOA_COLUMN(Pt, pt, float);
7081DECLARE_SOA_COLUMN (Eta, eta, float );
7182DECLARE_SOA_COLUMN (Phi, phi, float );
7283DECLARE_SOA_COLUMN (Y, y, float );
84+ DECLARE_SOA_COLUMN (CosThetaStarEP, cosThetaStarEP, float );
7385DECLARE_SOA_COLUMN (MlProbDstarToD0PiBkg, mlProbDstarToD0PiBkg, float );
7486DECLARE_SOA_COLUMN (MlProbDstarToD0PiPrompt, mlProbDstarToD0PiPrompt, float );
7587DECLARE_SOA_COLUMN (MlProbDstarToD0PiNonPrompt, mlProbDstarToD0PiNonPrompt, float );
@@ -112,24 +124,45 @@ DECLARE_SOA_TABLE(HfCandDstMix, "AOD", "HFCANDDSTMIX",
112124 mixing_dstar::MinNumTpcCls,
113125 mixing_dstar::GIndexCol,
114126 mixing_dstar::TimeStamp);
127+ DECLARE_SOA_TABLE (HfMcParticles, " AOD" , " HFMCPARTICLES" ,
128+ mixing_dstar::Pt,
129+ mixing_dstar::Eta,
130+ mixing_dstar::Phi,
131+ mixing_dstar::Y,
132+ mixing_dstar::CosThetaStarEP,
133+ mixing_dstar::Centrality);
115134} // namespace o2::aod
116135
117136// / Writes the full information in an output TTree
118137struct HfTreeCreatorDstarSpinAlignMixing {
119138 Produces<o2::aod::HfCandDstMix> rowCandidateMix;
139+ Produces<o2::aod::HfMcParticles> rowGenMcParticles;
120140
121141 Configurable<int > qVecDetector{" qVecDetector" , 2 , " Detector for Q vector estimation (FV0A: 0, FT0M: 1, FT0C: 2)" };
122142 Configurable<int > centEstimator{" centEstimator" , 2 , " Centrality estimator ((None: 0, FT0C: 2, FT0M: 3))" };
123143 Configurable<int > occEstimator{" occEstimator" , 2 , " If enabled, replace number of PV contributors with occupancy estimation (0: don't use, 1: ITS, 2: FT0C)" };
144+ Configurable<bool > activateAxisRotation{" activateAxisRotation" , false , " Activate the rotation of the quantisation axis for cos(Theta*) calculation for phi dependence studies" };
145+ Configurable<float > axisRotationAngle{" axisRotationAngle" , 0 .f , " Rotation angle of the quantisation axis for cos(Theta*) calculation for phi dependence studies (in rad)" };
146+ Configurable<bool > activatePartRecoDstar{" activatePartRecoDstar" , false , " Activate the study of partly reconstructed D*+ -> D0 (-> KPiPi0) Pi decays" };
124147
125148 using CollsWithQVecs = soa::Join<aod::Collisions, aod::EvSels, aod::QvectorFT0Cs, aod::QvectorFT0As, aod::QvectorFT0Ms, aod::QvectorFV0As, aod::QvectorBPoss, aod::QvectorBNegs, aod::QvectorBTots, aod::CentFT0Ms, aod::CentFT0Cs>;
126149 using TracksWithExtra = soa::Join<aod::Tracks, aod::TracksExtra>;
127150 using CandDstarWSelFlag = soa::Join<aod::HfCandDstars, aod::HfSelDstarToD0Pi>;
151+
152+ using CollisionsWithMcLabels = soa::SmallGroups<soa::Join<aod::Collisions, aod::EvSels, aod::PVMults, aod::McCollisionLabels>>;
153+ using CollisionsWithMcLabelsAndCent = soa::SmallGroups<soa::Join<aod::Collisions, aod::EvSels, aod::PVMults, aod::McCollisionLabels, aod::CentFT0Ms, aod::CentFT0Cs>>;
154+ using McParticlesDstarMatched = soa::Join<aod::McParticles, aod::HfCandDstarMcGen>;
155+ using BCsInfo = soa::Join<aod::BCs, aod::Timestamps, aod::BcSels>;
156+
128157 using FilteredCandDstarWSelFlagAndMl = soa::Filtered<soa::Join<CandDstarWSelFlag, aod::HfMlDstarToD0Pi>>;
158+ using FilteredCandDstarWSelFlagAndMcAndMl = soa::Filtered<soa::Join<CandDstarWSelFlag, aod::HfMlDstarToD0Pi, aod::HfCandDstarMcRec>>;
129159
130160 Filter filterSelectDstarCandidates = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true ;
131161
132162 Preslice<FilteredCandDstarWSelFlagAndMl> dstarWithMlPerCollision = aod::hf_cand::collisionId;
163+ Preslice<FilteredCandDstarWSelFlagAndMcAndMl> dstarWithMcAndMlPerCollision = aod::hf_cand::collisionId;
164+
165+ PresliceUnsorted<aod::McCollisionLabels> colPerMcCollision = aod::mcparticle::mcCollisionId;
133166
134167 void init (InitContext const &)
135168 {
@@ -159,9 +192,74 @@ struct HfTreeCreatorDstarSpinAlignMixing {
159192 }
160193 }
161194
162- template <typename CollType, typename T, typename Trk, typename BcType>
195+ // / \param ptCharmHad is the pt of the candidate
196+ double sampleDeltaPhi (float ptCharmHad)
197+ {
198+
199+ double y0 = 6.49e-2 ;
200+ double cons = 8.42e-1 ;
201+ double coef = -3.78e-1 ;
202+ double x0 = -6.37e-1 ;
203+ double norm = -3.64e-1 ;
204+ double mu = 4.68e-2 ;
205+ double sigma = 1.513 ;
206+
207+ double sigmoid = y0 + (cons - y0) / (1 + std::exp (-coef * (ptCharmHad - x0)));
208+ double peak = norm * std::exp (-(ptCharmHad - mu) * (ptCharmHad - mu) / (2 * sigma * sigma));
209+
210+ double v2 = sigmoid + peak;
211+ const double funcMax = 1.0 + 2.0 * std::abs (v2);
212+
213+ while (true ) {
214+ double deltaPhi = gRandom ->Uniform (0 , constants::math::TwoPI);
215+ double funcVal = gRandom ->Uniform (0 , funcMax);
216+
217+ double funcDeltaPhi = 1.0 + 2.0 * v2 * std::cos (2.0 * deltaPhi);
218+
219+ if (funcVal < funcDeltaPhi)
220+ return deltaPhi;
221+ }
222+ return -1 .;
223+ }
224+
225+ // / \param daughter is the daughter particle
226+ // / \param ptMin is the minimum pt
227+ // / \param etaMax is the maximum eta
228+ // / \return true if daughter is in acceptance
229+ template <typename Part>
230+ bool isDaughterInAcceptance (Part const & daughter, float ptMin, float etaMax)
231+ {
232+ if (daughter.pt () < ptMin) {
233+ return false ;
234+ }
235+ if (std::abs (daughter.eta ()) > etaMax) {
236+ return false ;
237+ }
238+
239+ return true ;
240+ }
241+
242+ template <bool DoMc, typename CollType, typename T, typename Trk, typename BcType>
163243 void fillCandidateTable (CollType const & collision, const T& candidate, Trk const & /* tracks*/ , BcType const & /* bcs*/ )
164244 {
245+ int8_t origin{RecoDecay::OriginType::None};
246+ float ptBhadMother{-1 .f };
247+ bool partRecoDstar{false };
248+ if constexpr (DoMc) {
249+ partRecoDstar = std::abs (candidate.flagMcMatchRec ()) == hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPiPi0 && std::abs (candidate.flagMcMatchRecD0 ()) == hf_decay::hf_cand_2prong::DecayChannelMain::D0ToPiKPi0;
250+ bool const signalDstar = std::abs (candidate.flagMcMatchRec ()) == hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPi && std::abs (candidate.flagMcMatchRecD0 ()) == hf_decay::hf_cand_2prong::DecayChannelMain::D0ToPiK;
251+ if (!signalDstar && (!partRecoDstar || !activatePartRecoDstar)) { // this candidate is not signal and not partially reconstructed signal, skip
252+ return ;
253+ }
254+ origin = candidate.originMcRec ();
255+ ptBhadMother = candidate.ptBhadMotherPart ();
256+ int const pdgBhadMother = candidate.pdgBhadMotherPart ();
257+ // For unknown reasons there are charm hadrons coming directly from beauty diquarks without an intermediate B-hadron which have an unreasonable correlation between the pT of the charm hadron and the beauty mother. We also remove charm hadrons from quarkonia.
258+ if (origin == RecoDecay::OriginType::NonPrompt && (pdgBhadMother == 5101 || pdgBhadMother == 5103 || pdgBhadMother == 5201 || pdgBhadMother == 5203 || pdgBhadMother == 5301 || pdgBhadMother == 5303 || pdgBhadMother == 5401 || pdgBhadMother == 5403 || pdgBhadMother == 5503 || pdgBhadMother == 553 || pdgBhadMother == 555 || pdgBhadMother == 557 )) { // o2-linter: disable=pdg/explicit-code, magic-number (constants not in the PDG header)
259+ return ;
260+ }
261+ }
262+
165263 const auto bc = collision.template bc_as <BcType>();
166264 const int64_t timeStamp = bc.timestamp ();
167265
@@ -186,7 +284,12 @@ struct HfTreeCreatorDstarSpinAlignMixing {
186284 auto trackProng1 = candidate.template prong1_as <Trk>();
187285 auto trackProng2 = candidate.template prongPi_as <Trk>();
188286 getTrackingInfos (std::vector{trackProng0, trackProng1, trackProng2}, absEtaTrackMin, numItsClsMin, numTpcClsMin);
189- std::array<float , 3 > const Qvector = getQvec (collision, qVecDetector.value );
287+ std::array<float , 3 > Qvector = getQvec (collision, qVecDetector.value );
288+ if constexpr (DoMc) {
289+ double const deltaPhi = sampleDeltaPhi (candidate.pt ());
290+ double psi = candidate.phi () - deltaPhi;
291+ Qvector = {static_cast <float >(-std::sin (psi)), static_cast <float >(std::cos (psi)), 0 .f };
292+ }
190293
191294 rowCandidateMix (
192295 massD0,
@@ -214,6 +317,91 @@ struct HfTreeCreatorDstarSpinAlignMixing {
214317 timeStamp);
215318 }
216319
320+ // / \param mcParticle is the Mc particle
321+ // / \param mcParticles is the table of Mc particles
322+ // / \param centrality is the centrality of the collision
323+ template <typename Part, typename Particles>
324+ void fillGenParticleTable (Part const & mcParticle, Particles const & mcParticles, float const centrality)
325+ {
326+ int8_t origin{RecoDecay::OriginType::None};
327+ bool partRecoDstar{false };
328+ partRecoDstar = (std::abs (mcParticle.flagMcMatchGen ()) == hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPiPi0) && (std::abs (mcParticle.flagMcMatchGenD0 ()) == hf_decay::hf_cand_2prong::DecayChannelMain::D0ToPiKPi0);
329+ bool const signalDstar = (std::abs (mcParticle.flagMcMatchGen ()) == hf_decay::hf_cand_dstar::DecayChannelMain::DstarToPiKPi) && (std::abs (mcParticle.flagMcMatchGenD0 ()) == hf_decay::hf_cand_2prong::DecayChannelMain::D0ToPiK);
330+
331+ if (!signalDstar && (!activatePartRecoDstar || !partRecoDstar)) { // this particle is not signal and not partially reconstructed signal, skip
332+ return ;
333+ }
334+ origin = mcParticle.originMcGen ();
335+ float massDau{0 .f }, massCharmHad{0 .f };
336+ if (origin == RecoDecay::OriginType::NonPrompt) {
337+ auto bHadMother = mcParticles.rawIteratorAt (mcParticle.idxBhadMotherPart () - mcParticles.offset ());
338+ int const pdgBhadMother = std::abs (bHadMother.pdgCode ());
339+ // For unknown reasons there are charm hadrons coming directly from beauty diquarks without an intermediate B-hadron which have an unreasonable correlation between the pT of the charm hadron and the beauty mother. We also remove charm hadrons from quarkonia.
340+ if (pdgBhadMother == 5101 || pdgBhadMother == 5103 || pdgBhadMother == 5201 || pdgBhadMother == 5203 || pdgBhadMother == 5301 || pdgBhadMother == 5303 || pdgBhadMother == 5401 || pdgBhadMother == 5403 || pdgBhadMother == 5503 || pdgBhadMother == 553 || pdgBhadMother == 555 || pdgBhadMother == 557 ) { // o2-linter: disable=pdg/explicit-code, magic-number (constants not in the PDG header)
341+ return ;
342+ }
343+ }
344+ std::vector<int > listDaughters{};
345+ std::array<int , 2 > const dauPdgs = {kPiPlus , o2::constants::physics::Pdg::kD0 };
346+ RecoDecay::getDaughters (mcParticle, &listDaughters, dauPdgs, 1 );
347+ massDau = MassPiPlus;
348+ massCharmHad = MassDStar;
349+
350+ float const rapidity = mcParticle.y ();
351+ if (std::abs (rapidity) > 1 .f ) { // we do not keep particles with |y| > 1
352+ return ;
353+ }
354+
355+ bool areDauInAcc{true };
356+ float pxDau{-1000 .f }, pyDau{-1000 .f }, pzDau{-1000 .f };
357+ for (const auto & dauIdx : listDaughters) {
358+ auto dauPart = mcParticles.rawIteratorAt (dauIdx - mcParticles.offset ());
359+ if (std::abs (dauPart.pdgCode ()) == kPiPlus ) {
360+ pxDau = dauPart.px ();
361+ pyDau = dauPart.py ();
362+ pzDau = dauPart.pz ();
363+ if (areDauInAcc) {
364+ areDauInAcc = isDaughterInAcceptance (dauPart, 0.1 , 0.8 );
365+ }
366+ } else if (areDauInAcc) { // check also D0 daughters
367+ std::vector<int > listDaughtersD0{};
368+ std::array<int , 2 > const dauPdgsD0 = {kPiPlus , -kKPlus };
369+ RecoDecay::getDaughters (mcParticle, &listDaughtersD0, dauPdgsD0, 1 );
370+ for (const auto & dauIdxD0 : listDaughtersD0) {
371+ auto dauPartD0 = mcParticles.rawIteratorAt (dauIdxD0 - mcParticles.offset ());
372+ areDauInAcc = isDaughterInAcceptance (dauPartD0, 0.3 , 0.8 );
373+ }
374+ }
375+ }
376+
377+ float const pxCharmHad = mcParticle.px ();
378+ float const pyCharmHad = mcParticle.py ();
379+ float const pzCharmHad = mcParticle.pz ();
380+ ROOT ::Math::PxPyPzMVector const fourVecDau = ROOT::Math::PxPyPzMVector (pxDau, pyDau, pzDau, massDau);
381+ ROOT ::Math::PxPyPzMVector const fourVecMother = ROOT::Math::PxPyPzMVector (pxCharmHad, pyCharmHad, pzCharmHad, massCharmHad);
382+ ROOT ::Math::Boost const boost{fourVecMother.BoostToCM ()};
383+ ROOT ::Math::PxPyPzMVector const fourVecDauCM = boost (fourVecDau);
384+ ROOT ::Math::XYZVector const threeVecDauCM = fourVecDauCM.Vect ();
385+
386+ // EP analysis for MC
387+ double const deltaPhi = sampleDeltaPhi (mcParticle.pt ());
388+ double psi = mcParticle.phi () - deltaPhi;
389+ ROOT ::Math::XYZVector qVecNorm = ROOT::Math::XYZVector (-std::sin (psi), std::cos (psi), 0 .f );
390+ if (activateAxisRotation) {
391+ qVecNorm = ROOT::Math::RotationZ (axisRotationAngle)(qVecNorm);
392+ }
393+ float const cosThetaStarEP = qVecNorm.Dot (threeVecDauCM) / std::sqrt (threeVecDauCM.Mag2 ());
394+
395+ rowGenMcParticles (
396+ mcParticle.pt (),
397+ mcParticle.eta (),
398+ mcParticle.phi (),
399+ mcParticle.y (),
400+ cosThetaStarEP,
401+ centrality);
402+
403+ }
404+
217405 void processData (CollsWithQVecs const & collisions,
218406 FilteredCandDstarWSelFlagAndMl const & dstarCandidates,
219407 TracksWithExtra const & tracks,
@@ -223,11 +411,35 @@ struct HfTreeCreatorDstarSpinAlignMixing {
223411 auto thisCollId = collision.globalIndex ();
224412 auto groupedDstarCandidates = dstarCandidates.sliceBy (dstarWithMlPerCollision, thisCollId);
225413 for (const auto & dstarCandidate : groupedDstarCandidates) {
226- fillCandidateTable (collision, dstarCandidate, tracks, bcWithTimeStamps);
414+ fillCandidateTable< false > (collision, dstarCandidate, tracks, bcWithTimeStamps);
227415 }
228416 }
229417 }
230418 PROCESS_SWITCH (HfTreeCreatorDstarSpinAlignMixing, processData, " Process data" , true );
419+
420+ void processDstarMcWithMlInPbPb (aod::McCollisions::iterator const &,
421+ McParticlesDstarMatched const & mcParticles,
422+ CollisionsWithMcLabelsAndCent const & collisions, // this is grouped with SmallGroupsCollisionsWithMcLabels const& collisions,
423+ FilteredCandDstarWSelFlagAndMcAndMl const & dstarCandidates,
424+ TracksWithExtra const & tracks,
425+ BCsInfo const & bcWithTimeStamps)
426+ {
427+ for (const auto & collision : collisions) { // loop over reco collisions associated to this gen collision
428+ auto thisCollId = collision.globalIndex ();
429+ auto groupedDstarCandidates = dstarCandidates.sliceBy (dstarWithMcAndMlPerCollision, thisCollId);
430+ for (const auto & dstarCandidate : groupedDstarCandidates) {
431+ fillCandidateTable<true >(collision, dstarCandidate, tracks, bcWithTimeStamps);
432+ }
433+ }
434+ rowGenMcParticles.reserve (mcParticles.size ());
435+
436+ for (const auto & mcParticle : mcParticles) {
437+ const auto & recoCollsPerMcColl = collisions.sliceBy (colPerMcCollision, mcParticle.mcCollision ().globalIndex ());
438+ const auto centrality = getCentralityGenColl (recoCollsPerMcColl, centEstimator);
439+ fillGenParticleTable (mcParticle, mcParticles, centrality);
440+ }
441+ }
442+ PROCESS_SWITCH (HfTreeCreatorDstarSpinAlignMixing, processDstarMcWithMlInPbPb, " Process Dstar candidates in PbPb MC with ML" , false );
231443};
232444
233445WorkflowSpec defineDataProcessing (ConfigContext const & cfgc)
0 commit comments