Repository: AliceO2Group/O2Physics
Pull request #11147: [PWGHF] Add derived-data creator for D* -> D0 pi
Author: login: Mingze129
Labels: name: pwghf
description: PWG-HF
name: datamodel
Created: 2025-05-09T12:44:35Z | Merged: 2025-05-13T15:06:39Z



Diff:
diff --git a/PWGHF/DataModel/DerivedTables.h b/PWGHF/DataModel/DerivedTables.h
index 6900ef25039..19f8d30ddbe 100644
--- a/PWGHF/DataModel/DerivedTables.h
+++ b/PWGHF/DataModel/DerivedTables.h
@@ -39,7 +39,7 @@ namespace o2::aod
 // composite species
 // B0 → D− π+ (todo)
 // B+ → D0 π+
-// D*+ → D0 π+ (todo)
+// D*+ → D0 π+
 
 // ================
 // Collision tables
@@ -274,6 +274,7 @@ DECLARE_TABLES_2P(D0, "D0", d0, 2);
 DECLARE_TABLES_3P(Lc, "LC", lc, 3);
 DECLARE_TABLES_3P(Dplus, "DP", dplus, 4);
 DECLARE_TABLES_3P(Bplus, "BP", bplus, 5);
+DECLARE_TABLES_3P(Dstar, "DST", dstar, 6);
 
 // ================
 // Additional species-specific candidate tables
@@ -302,6 +303,15 @@ DECLARE_SOA_COLUMN(PtProng0, ptProng0, float);
 DECLARE_SOA_COLUMN(PtProng1, ptProng1, float);                                     //! transverse momentum of prong 1
 DECLARE_SOA_COLUMN(PtProng2, ptProng2, float);                                     //! transverse momentum of prong 2
 DECLARE_SOA_COLUMN(RSecondaryVertex, rSecondaryVertex, float);                     //! distance of the secondary vertex from the z axis
+// D*± → D0(bar) π±
+DECLARE_SOA_COLUMN(MassD0, massD0, float);                                       //! invariant mass of D0
+DECLARE_SOA_COLUMN(CpaD0, cpaD0, float);                                         //! cosine of pointing angle of D0
+DECLARE_SOA_COLUMN(CpaXYD0, cpaXYD0, float);                                     //! cosine of pointing angle in the transverse plane of D0
+DECLARE_SOA_COLUMN(DecayLengthD0, decayLengthD0, float);                         //! decay length of D0
+DECLARE_SOA_COLUMN(DecayLengthXYD0, decayLengthXYD0, float);                     //! decay length in the transverse plane of D0
+DECLARE_SOA_COLUMN(DecayLengthNormalisedD0, decayLengthNormalisedD0, float);     //! decay length of D0 divided by its uncertainty
+DECLARE_SOA_COLUMN(DecayLengthXYNormalisedD0, decayLengthXYNormalisedD0, float); //! decay length in the transverse plane of D0 divided by its uncertainty
+DECLARE_SOA_COLUMN(NormalisedImpParamSoftPi, normalisedImpParamSoftPi, float);   //! impact parameter of soft pion divided by its uncertainty
 // TOF
 DECLARE_SOA_COLUMN(NSigTofKa0, nSigTofKa0, float);
 DECLARE_SOA_COLUMN(NSigTofKa1, nSigTofKa1, float);
@@ -316,6 +326,7 @@ DECLARE_SOA_COLUMN(NSigTofPiExpKa, nSigTofPiExpKa, float);
 DECLARE_SOA_COLUMN(NSigTofPr0, nSigTofPr0, float);
 DECLARE_SOA_COLUMN(NSigTofPr1, nSigTofPr1, float);
 DECLARE_SOA_COLUMN(NSigTofPr2, nSigTofPr2, float);
+DECLARE_SOA_COLUMN(NSigTofPiSoftPi, nSigTofPiSoftPi, float);
 // TPC
 DECLARE_SOA_COLUMN(NSigTpcKa0, nSigTpcKa0, float);
 DECLARE_SOA_COLUMN(NSigTpcKa1, nSigTpcKa1, float);
@@ -330,6 +341,7 @@ DECLARE_SOA_COLUMN(NSigTpcPiExpKa, nSigTpcPiExpKa, float);
 DECLARE_SOA_COLUMN(NSigTpcPr0, nSigTpcPr0, float);
 DECLARE_SOA_COLUMN(NSigTpcPr1, nSigTpcPr1, float);
 DECLARE_SOA_COLUMN(NSigTpcPr2, nSigTpcPr2, float);
+DECLARE_SOA_COLUMN(NSigTpcPiSoftPi, nSigTpcPiSoftPi, float);
 // TPC+TOF
 DECLARE_SOA_COLUMN(NSigTpcTofKa0, nSigTpcTofKa0, float);
 DECLARE_SOA_COLUMN(NSigTpcTofKa1, nSigTpcTofKa1, float);
@@ -344,6 +356,7 @@ DECLARE_SOA_COLUMN(NSigTpcTofPiExpKa, nSigTpcTofPiExpKa, float);
 DECLARE_SOA_COLUMN(NSigTpcTofPr0, nSigTpcTofPr0, float);
 DECLARE_SOA_COLUMN(NSigTpcTofPr1, nSigTpcTofPr1, float);
 DECLARE_SOA_COLUMN(NSigTpcTofPr2, nSigTpcTofPr2, float);
+DECLARE_SOA_COLUMN(NSigTpcTofPiSoftPi, nSigTpcTofPiSoftPi, float);
 } // namespace hf_cand_par
 
 // Candidate properties of the charm daughter candidate used for selection of the beauty candidate
@@ -731,6 +744,59 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// D*+
+// ----------------
+
+DECLARE_SOA_TABLE_STAGED(HfDstarPars, "HFDSTARPAR", //! Table with candidate properties used for selection
+                         hf_cand_dstar::Chi2PCAD0,
+                         hf_cand_par::CpaD0,
+                         hf_cand_par::CpaXYD0,
+                         hf_cand_par::DecayLengthD0,
+                         hf_cand_par::DecayLengthXYD0,
+                         hf_cand_par::DecayLengthNormalisedD0,
+                         hf_cand_par::DecayLengthXYNormalisedD0,
+                         hf_cand::PxProng0,
+                         hf_cand::PyProng0,
+                         hf_cand::PzProng0,
+                         hf_cand::PxProng1,
+                         hf_cand::PyProng1,
+                         hf_cand::PzProng1,
+                         hf_cand_dstar::PxD0,
+                         hf_cand_dstar::PyD0,
+                         hf_cand_dstar::PzD0,
+                         hf_cand_dstar::PxSoftPi,
+                         hf_cand_dstar::PySoftPi,
+                         hf_cand_dstar::PzSoftPi,
+                         hf_cand_dstar::PtSoftPi<hf_cand_dstar::PxSoftPi, hf_cand_dstar::PySoftPi>,
+                         hf_cand_dstar::SignSoftPi,
+                         hf_cand_dstar::PtD0<hf_cand_dstar::PxD0, hf_cand_dstar::PyD0>,
+                         hf_cand_par::MassD0,
+                         hf_cand::ImpactParameter0,
+                         hf_cand::ImpactParameter1,
+                         hf_cand_dstar::ImpParamSoftPi,
+                         hf_cand_par::ImpactParameterNormalised0,
+                         hf_cand_par::ImpactParameterNormalised1,
+                         hf_cand_par::NormalisedImpParamSoftPi,
+                         hf_cand_par::NSigTpcPi0,
+                         hf_cand_par::NSigTofPi0,
+                         hf_cand_par::NSigTpcTofPi0,
+                         hf_cand_par::NSigTpcKa1,
+                         hf_cand_par::NSigTofKa1,
+                         hf_cand_par::NSigTpcTofKa1,
+                         hf_cand_par::NSigTpcPiSoftPi,
+                         hf_cand_par::NSigTofPiSoftPi,
+                         hf_cand_par::NSigTpcTofPiSoftPi,
+                         o2::soa::Marker<MarkerDstar>);
+
+DECLARE_SOA_TABLE_STAGED(HfDstarMls, "HFDSTARML", //! Table with candidate selection ML scores
+                         hf_cand_mc::MlScores,
+                         o2::soa::Marker<MarkerDstar>);
+
+DECLARE_SOA_TABLE_STAGED(HfDstarMcs, "HFDSTARMC", //! Table with MC candidate info
+                         hf_cand_mc::FlagMcMatchRec,
+                         hf_cand_mc::OriginMcRec,
+                         o2::soa::Marker<MarkerDstar>);
 } // namespace o2::aod
 
 #endif // PWGHF_DATAMODEL_DERIVEDTABLES_H_
diff --git a/PWGHF/TableProducer/CMakeLists.txt b/PWGHF/TableProducer/CMakeLists.txt
index 22895a583ce..c017c8bf361 100644
--- a/PWGHF/TableProducer/CMakeLists.txt
+++ b/PWGHF/TableProducer/CMakeLists.txt
@@ -315,6 +315,10 @@ o2physics_add_dpl_workflow(derived-data-creator-lc-to-p-k-pi
                     PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
                     COMPONENT_NAME Analysis)
 
+o2physics_add_dpl_workflow(derived-data-creator-dstar-to-d0-pi
+                    SOURCES derivedDataCreatorDstarToD0Pi.cxx
+                    PUBLIC_LINK_LIBRARIES O2Physics::AnalysisCore
+                    COMPONENT_NAME Analysis)
 # Converters
 
 o2physics_add_dpl_workflow(converter-dstar-indices
diff --git a/PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx b/PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
new file mode 100644
index 00000000000..084605c2f3b
--- /dev/null
+++ b/PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
@@ -0,0 +1,372 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of D*+ candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
+///
+/// \author Mingze Li <mingze.li@cern.cn>, CCNU
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "CommonConstants/PhysicsConstants.h"
+#include "Framework/AnalysisTask.h"
+#include "Framework/runDataProcessing.h"
+
+#include "Common/Core/RecoDecay.h"
+#include "Common/DataModel/Centrality.h"
+#include "Common/DataModel/Multiplicity.h"
+
+#include "PWGLF/DataModel/mcCentrality.h"
+
+#include "PWGHF/DataModel/CandidateReconstructionTables.h"
+#include "PWGHF/DataModel/CandidateSelectionTables.h"
+#include "PWGHF/DataModel/DerivedTables.h"
+#include "PWGHF/Utils/utilsDerivedData.h"
+
+using namespace o2;
+using namespace o2::framework;
+using namespace o2::framework::expressions;
+using namespace o2::analysis::hf_derived;
+
+/// Writes the full information in an output TTree
+struct HfDerivedDataCreatorDstarToD0Pi {
+  HfProducesDerivedData<
+    o2::aod::HfDstarBases,
+    o2::aod::HfDstarCollBases,
+    o2::aod::HfDstarCollIds,
+    o2::aod::HfDstarMcCollBases,
+    o2::aod::HfDstarMcCollIds,
+    o2::aod::HfDstarMcRCollIds,
+    o2::aod::HfDstarPBases,
+    o2::aod::HfDstarPIds>
+    rowsCommon;
+  // Candidates
+  Produces<o2::aod::HfDstarPars> rowCandidatePar;
+  Produces<o2::aod::HfDstarSels> rowCandidateSel;
+  Produces<o2::aod::HfDstarMls> rowCandidateMl;
+  Produces<o2::aod::HfDstarIds> rowCandidateId;
+  Produces<o2::aod::HfDstarMcs> rowCandidateMc;
+
+  // Switches for filling tables
+  HfConfigurableDerivedData confDerData;
+  Configurable<bool> fillCandidatePar{"fillCandidatePar", true, "Fill candidate parameters"};
+  Configurable<bool> fillCandidateSel{"fillCandidateSel", true, "Fill candidate selection flags"};
+  Configurable<bool> fillCandidateMl{"fillCandidateMl", true, "Fill candidate selection ML scores"};
+  Configurable<bool> fillCandidateId{"fillCandidateId", true, "Fill original indices from the candidate table"};
+  Configurable<bool> fillCandidateMc{"fillCandidateMc", true, "Fill candidate MC info"};
+  // Parameters for production of training samples
+  Configurable<float> downSampleBkgFactor{"downSampleBkgFactor", 1., "Fraction of background candidates to keep for ML trainings"};
+  Configurable<float> ptMaxForDownSample{"ptMaxForDownSample", 10., "Maximum pt for the application of the downsampling factor"};
+
+  SliceCache cache;
+  static constexpr double mass{o2::constants::physics::MassDStar};
+
+  using CollisionsWCentMult = soa::Join<aod::Collisions, aod::CentFV0As, aod::CentFT0Ms, aod::CentFT0As, aod::CentFT0Cs, aod::PVMultZeqs>;
+  using CollisionsWMcCentMult = soa::Join<aod::Collisions, aod::McCollisionLabels, aod::CentFV0As, aod::CentFT0Ms, aod::CentFT0As, aod::CentFT0Cs, aod::PVMultZeqs>;
+  using TracksWPid = soa::Join<aod::Tracks, aod::TracksPidPi, aod::PidTpcTofFullPi, aod::TracksPidKa, aod::PidTpcTofFullKa>;
+  using SelectedCandidates = soa::Filtered<soa::Join<aod::HfD0FromDstar, aod::HfCandDstars, aod::HfSelDstarToD0Pi>>;
+  using SelectedCandidatesMc = soa::Filtered<soa::Join<aod::HfD0FromDstar, aod::HfCandDstars, aod::HfCandDstarMcRec, aod::HfSelDstarToD0Pi>>;
+  using SelectedCandidatesMl = soa::Filtered<soa::Join<aod::HfD0FromDstar, aod::HfCandDstars, aod::HfSelDstarToD0Pi, aod::HfMlDstarToD0Pi>>;
+  using SelectedCandidatesMcMl = soa::Filtered<soa::Join<aod::HfD0FromDstar, aod::HfCandDstars, aod::HfCandDstarMcRec, aod::HfSelDstarToD0Pi, aod::HfMlDstarToD0Pi>>;
+  using MatchedGenCandidatesMc = soa::Filtered<soa::Join<aod::McParticles, aod::HfCandDstarMcGen>>;
+  using TypeMcCollisions = soa::Join<aod::McCollisions, aod::McCentFT0Ms>;
+
+  Filter filterSelectCandidates = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true;
+  Filter filterMcGenMatching = nabs(aod::hf_cand_dstar::flagMcMatchGen) == static_cast<int8_t>(BIT(aod::hf_cand_dstar::DecayType::DstarToD0Pi));
+
+  Preslice<SelectedCandidates> candidatesPerCollision = aod::hf_cand::collisionId;
+  Preslice<SelectedCandidatesMc> candidatesMcPerCollision = aod::hf_cand::collisionId;
+  Preslice<SelectedCandidatesMl> candidatesMlPerCollision = aod::hf_cand::collisionId;
+  Preslice<SelectedCandidatesMcMl> candidatesMcMlPerCollision = aod::hf_cand::collisionId;
+  Preslice<MatchedGenCandidatesMc> mcParticlesPerMcCollision = aod::mcparticle::mcCollisionId;
+
+  // trivial partitions for all candidates to allow "->sliceByCached" inside processCandidates
+  Partition<SelectedCandidates> candidatesAll = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true;
+  Partition<SelectedCandidatesMc> candidatesMcAll = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true;
+  Partition<SelectedCandidatesMl> candidatesMlAll = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true;
+  Partition<SelectedCandidatesMcMl> candidatesMcMlAll = aod::hf_sel_candidate_dstar::isSelDstarToD0Pi == true;
+  // partitions for signal and background
+  Partition<SelectedCandidatesMc> candidatesMcSig = nabs(aod::hf_cand_dstar::flagMcMatchRec) == static_cast<int8_t>(BIT(aod::hf_cand_dstar::DecayType::DstarToD0Pi));
+  Partition<SelectedCandidatesMc> candidatesMcBkg = nabs(aod::hf_cand_dstar::flagMcMatchRec) != static_cast<int8_t>(BIT(aod::hf_cand_dstar::DecayType::DstarToD0Pi));
+  Partition<SelectedCandidatesMcMl> candidatesMcMlSig = nabs(aod::hf_cand_dstar::flagMcMatchRec) == static_cast<int8_t>(BIT(aod::hf_cand_dstar::DecayType::DstarToD0Pi));
+  Partition<SelectedCandidatesMcMl> candidatesMcMlBkg = nabs(aod::hf_cand_dstar::flagMcMatchRec) != static_cast<int8_t>(BIT(aod::hf_cand_dstar::DecayType::DstarToD0Pi));
+
+  void init(InitContext const&)
+  {
+    std::array<bool, 9> doprocess{doprocessData, doprocessMcSig, doprocessMcBkg, doprocessMcAll, doprocessDataMl, doprocessMcMlSig, doprocessMcMlBkg, doprocessMcMlAll, doprocessMcGenOnly};
+    if (std::accumulate(doprocess.begin(), doprocess.end(), 0) != 1) {
+      LOGP(fatal, "Only one process function can be enabled at a time.");
+    }
+    rowsCommon.init(confDerData);
+  }
+
+  template <typename T, typename U>
+  void fillTablesCandidate(const T& candidate, const U& prong0, const U& prong1, const U& prongSoftPi, int candFlag, double invMass,
+                           double y, int8_t flagMc, int8_t origin, const std::vector<float>& mlScores)
+  {
+    rowsCommon.fillTablesCandidate(candidate, invMass, y);
+    if (fillCandidatePar) {
+      rowCandidatePar(
+        candidate.chi2PCAD0(),
+        candidate.cpaD0(),
+        candidate.cpaXYD0(),
+        candidate.decayLengthD0(),
+        candidate.decayLengthXYD0(),
+        candidate.decayLengthNormalisedD0(),
+        candidate.decayLengthXYNormalisedD0(),
+        candidate.pxProng0(),
+        candidate.pyProng0(),
+        candidate.pzProng0(),
+        candidate.pxProng1(),
+        candidate.pyProng1(),
+        candidate.pzProng1(),
+        candidate.pxD0(),
+        candidate.pyD0(),
+        candidate.pzD0(),
+        candidate.pxSoftPi(),
+        candidate.pySoftPi(),
+        candidate.pzSoftPi(),
+        candidate.signSoftPi(),
+        candidate.invMassD0(),
+        candidate.impactParameter0(),
+        candidate.impactParameter1(),
+        candidate.impParamSoftPi(),
+        candidate.impactParameterNormalised0(),
+        candidate.impactParameterNormalised1(),
+        candidate.normalisedImpParamSoftPi(),
+        prong0.tpcNSigmaPi(),
+        prong0.tofNSigmaPi(),
+        prong0.tpcTofNSigmaPi(),
+        prong1.tpcNSigmaKa(),
+        prong1.tofNSigmaKa(),
+        prong1.tpcTofNSigmaKa(),
+        prongSoftPi.tpcNSigmaPi(),
+        prongSoftPi.tofNSigmaPi(),
+        prongSoftPi.tpcTofNSigmaPi());
+    }
+    if (fillCandidateSel) {
+      rowCandidateSel(
+        BIT(candFlag));
+    }
+    if (fillCandidateMl) {
+      rowCandidateMl(
+        mlScores);
+    }
+    if (fillCandidateId) {
+      rowCandidateId(
+        candidate.collisionId(),
+        candidate.prong0Id(),
+        candidate.prong1Id(),
+        candidate.prongPiId());
+    }
+    if (fillCandidateMc) {
+      rowCandidateMc(
+        flagMc,
+        origin);
+    }
+  }
+
+  template <bool isMl, bool isMc, bool onlyBkg, bool onlySig, typename CollType, typename CandType>
+  void processCandidates(CollType const& collisions,
+                         Partition<CandType>& candidates,
+                         TracksWPid const&,
+                         aod::BCs const&)
+  {
+    // Fill collision properties
+    if constexpr (isMc) {
+      if (confDerData.fillMcRCollId) {
+        rowsCommon.matchedCollisions.clear();
+      }
+    }
+    auto sizeTableColl = collisions.size();
+    rowsCommon.reserveTablesColl(sizeTableColl);
+    for (const auto& collision : collisions) {
+      auto thisCollId = collision.globalIndex();
+      auto candidatesThisColl = candidates->sliceByCached(aod::hf_cand::collisionId, thisCollId, cache); // FIXME
+      auto sizeTableCand = candidatesThisColl.size();
+      LOGF(debug, "Rec. collision %d has %d candidates", thisCollId, sizeTableCand);
+      // Skip collisions without HF candidates (and without HF particles in matched MC collisions if saving indices of reconstructed collisions matched to MC collisions)
+      bool mcCollisionHasMcParticles{false};
+      if constexpr (isMc) {
+        mcCollisionHasMcParticles = confDerData.fillMcRCollId && collision.has_mcCollision() && rowsCommon.hasMcParticles[collision.mcCollisionId()];
+        LOGF(debug, "Rec. collision %d has MC collision %d with MC particles? %s", thisCollId, collision.mcCollisionId(), mcCollisionHasMcParticles ? "yes" : "no");
+      }
+      if (sizeTableCand == 0 && (!confDerData.fillMcRCollId || !mcCollisionHasMcParticles)) {
+        LOGF(debug, "Skipping rec. collision %d", thisCollId);
+        continue;
+      }
+      LOGF(debug, "Filling rec. collision %d at derived index %d", thisCollId, rowsCommon.rowCollBase.lastIndex() + 1);
+      rowsCommon.fillTablesCollision<isMc>(collision);
+
+      // Fill candidate properties
+      rowsCommon.reserveTablesCandidates(sizeTableCand);
+      reserveTable(rowCandidatePar, fillCandidatePar, sizeTableCand);
+      reserveTable(rowCandidateSel, fillCandidateSel, sizeTableCand);
+      reserveTable(rowCandidateMl, fillCandidateMl, sizeTableCand);
+      reserveTable(rowCandidateId, fillCandidateId, sizeTableCand);
+      if constexpr (isMc) {
+        reserveTable(rowCandidateMc, fillCandidateMc, sizeTableCand);
+      }
+      int8_t flagMcRec = 0, origin = 0;
+      for (const auto& candidate : candidatesThisColl) {
+        if constexpr (isMl) {
+          if (!TESTBIT(candidate.isSelDstarToD0Pi(), aod::SelectionStep::RecoMl)) {
+            continue;
+          }
+        }
+        if constexpr (isMc) {
+          flagMcRec = candidate.flagMcMatchRec();
+          origin = candidate.originMcRec();
+          if constexpr (onlyBkg) {
+            if (TESTBIT(std::abs(flagMcRec), aod::hf_cand_dstar::DecayType::DstarToD0Pi)) {
+              continue;
+            }
+            if (downSampleBkgFactor < 1.) {
+              float pseudoRndm = candidate.ptProng0() * 1000. - static_cast<int64_t>(candidate.ptProng0() * 1000);
+              if (candidate.pt() < ptMaxForDownSample && pseudoRndm >= downSampleBkgFactor) {
+                continue;
+              }
+            }
+          }
+          if constexpr (onlySig) {
+            if (!TESTBIT(std::abs(flagMcRec), aod::hf_cand_dstar::DecayType::DstarToD0Pi)) {
+              continue;
+            }
+          }
+        }
+        auto prong0 = candidate.template prong0_as<TracksWPid>();
+        auto prong1 = candidate.template prong1_as<TracksWPid>();
+        auto prongSoftPi = candidate.template prongPi_as<TracksWPid>();
+        double y = candidate.y(o2::constants::physics::MassDStar);
+        double massDstar = candidate.invMassDstar();
+        std::vector<float> mlScoresDstarToD0Pi;
+        bool isD0 = prongSoftPi.sign() < 0;
+        if constexpr (isMl) {
+          std::copy(candidate.mlProbDstarToD0Pi().begin(), candidate.mlProbDstarToD0Pi().end(), std::back_inserter(mlScoresDstarToD0Pi));
+        }
+        fillTablesCandidate(candidate, prong0, prong1, prongSoftPi, isD0 ? 0 : 1, y, massDstar, flagMcRec, origin, mlScoresDstarToD0Pi);
+      }
+    }
+  }
+
+  void processData(CollisionsWCentMult const& collisions,
+                   SelectedCandidates const&,
+                   TracksWPid const& tracks,
+                   aod::BCs const& bcs)
+  {
+    processCandidates<false, false, false, false>(collisions, candidatesAll, tracks, bcs);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processData, "Process data", true);
+
+  void processMcSig(CollisionsWMcCentMult const& collisions,
+                    SelectedCandidatesMc const&,
+                    TypeMcCollisions const& mcCollisions,
+                    MatchedGenCandidatesMc const& mcParticles,
+                    TracksWPid const& tracks,
+                    aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<false, true, false, true>(collisions, candidatesMcSig, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcSig, "Process MC only for signals", false);
+
+  void processMcBkg(CollisionsWMcCentMult const& collisions,
+                    SelectedCandidatesMc const&,
+                    TypeMcCollisions const& mcCollisions,
+                    MatchedGenCandidatesMc const& mcParticles,
+                    TracksWPid const& tracks,
+                    aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<false, true, true, false>(collisions, candidatesMcBkg, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcBkg, "Process MC only for background", false);
+
+  void processMcAll(CollisionsWMcCentMult const& collisions,
+                    SelectedCandidatesMc const&,
+                    TypeMcCollisions const& mcCollisions,
+                    MatchedGenCandidatesMc const& mcParticles,
+                    TracksWPid const& tracks,
+                    aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<false, true, false, false>(collisions, candidatesMcAll, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcAll, "Process MC", false);
+
+  // ML versions
+
+  void processDataMl(CollisionsWCentMult const& collisions,
+                     SelectedCandidatesMl const&,
+                     TracksWPid const& tracks,
+                     aod::BCs const& bcs)
+  {
+    processCandidates<true, false, false, false>(collisions, candidatesMlAll, tracks, bcs);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processDataMl, "Process data with ML", false);
+
+  void processMcMlSig(CollisionsWMcCentMult const& collisions,
+                      SelectedCandidatesMcMl const&,
+                      TypeMcCollisions const& mcCollisions,
+                      MatchedGenCandidatesMc const& mcParticles,
+                      TracksWPid const& tracks,
+                      aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<true, true, false, true>(collisions, candidatesMcMlSig, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcMlSig, "Process MC with ML only for signals", false);
+
+  void processMcMlBkg(CollisionsWMcCentMult const& collisions,
+                      SelectedCandidatesMcMl const&,
+                      TypeMcCollisions const& mcCollisions,
+                      MatchedGenCandidatesMc const& mcParticles,
+                      TracksWPid const& tracks,
+                      aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<true, true, true, false>(collisions, candidatesMcMlBkg, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcMlBkg, "Process MC with ML only for background", false);
+
+  void processMcMlAll(CollisionsWMcCentMult const& collisions,
+                      SelectedCandidatesMcMl const&,
+                      TypeMcCollisions const& mcCollisions,
+                      MatchedGenCandidatesMc const& mcParticles,
+                      TracksWPid const& tracks,
+                      aod::BCs const& bcs)
+  {
+    rowsCommon.preProcessMcCollisions(mcCollisions, mcParticlesPerMcCollision, mcParticles);
+    processCandidates<true, true, false, false>(collisions, candidatesMcMlAll, tracks, bcs);
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcMlAll, "Process MC with ML", false);
+
+  void processMcGenOnly(TypeMcCollisions const& mcCollisions,
+                        MatchedGenCandidatesMc const& mcParticles)
+  {
+    rowsCommon.processMcParticles(mcCollisions, mcParticlesPerMcCollision, mcParticles, mass);
+  }
+  PROCESS_SWITCH(HfDerivedDataCreatorDstarToD0Pi, processMcGenOnly, "Process MC gen. only", false);
+};
+
+WorkflowSpec defineDataProcessing(ConfigContext const& cfgc)
+{
+  return WorkflowSpec{adaptAnalysisTask<HfDerivedDataCreatorDstarToD0Pi>(cfgc)};
+}


Reviews:
user: login: vkucera
body: Hi @Mingze129 , overall looks good to me, thanks. Please see my comments for minor suggestions.
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T15:02:08Z
user: login: vkucera
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T15:03:35Z
user: login: vkucera
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T15:09:45Z
user: login: Mingze129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:40:18Z
user: login: Mingze129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T15:42:37Z
user: login: vkucera
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-12T16:08:41Z
user: login: Mingze129
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T18:00:13Z
user: login: vkucera
body: Thanks @Mingze129 for addressing my suggestions.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-12T20:25:06Z

Review comments:
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: vkucera
body: Is it true that it is based on `derivedDataCreatorLcToPKPi`?
created_at: 2025-05-12T14:57:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
+///
+/// \author Vít Kučera <vit.kucera@cern.ch>, Inha University
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: vkucera
body: ```suggestion
```
created_at: 2025-05-12T14:57:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: vkucera
body: ```suggestion
/// \brief Producer of derived tables of D*+ candidates, collisions and MC particles
```
created_at: 2025-05-12T14:58:21Z
author_association: COLLABORATOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
path: PWGHF/DataModel/DerivedTables.h
user: login: vkucera
body: ```suggestion
// D*+
```
created_at: 2025-05-12T14:59:37Z
author_association: COLLABORATOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
+// ----------------
+
+// candidates for removal:
+// PxProng0, PyProng0, PzProng0,... (same for 1, 2), we can keep Pt, Eta, Phi instead
+// XY: CpaXY, DecayLengthXY, ErrorDecayLengthXY
+// normalised: DecayLengthNormalised, DecayLengthXYNormalised, ImpactParameterNormalised0
path: PWGHF/DataModel/DerivedTables.h
user: login: vkucera
body: ```suggestion
```
created_at: 2025-05-12T15:00:01Z
author_association: COLLABORATOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
path: PWGHF/DataModel/DerivedTables.h
user: login: vkucera
body: Please also remove the `(todo)` comment from line 42.
created_at: 2025-05-12T15:00:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
+///
+/// \author Vít Kučera <vit.kucera@cern.ch>, Inha University
+/// \author Mingze Li <mingze.li@cern.cn>, CCNU
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "CommonConstants/PhysicsConstants.h"
+#include "Framework/AnalysisTask.h"
+#include "Framework/runDataProcessing.h"
+
+#include "Common/Core/RecoDecay.h"
+#include "Common/DataModel/Centrality.h"
+#include "Common/DataModel/Multiplicity.h"
+
+#include "PWGLF/DataModel/mcCentrality.h"
+
+#include "PWGHF/DataModel/CandidateReconstructionTables.h"
+#include "PWGHF/DataModel/CandidateSelectionTables.h"
+#include "PWGHF/DataModel/DerivedTables.h"
+#include "PWGHF/Utils/utilsDerivedData.h"
+#include "PWGHF/Utils/utilsPid.h"
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: vkucera
body: I don't think you use this.
```suggestion
```
created_at: 2025-05-12T15:03:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
+///
+/// \author Vít Kučera <vit.kucera@cern.ch>, Inha University
+/// \author Mingze Li <mingze.li@cern.cn>, CCNU
+
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "CommonConstants/PhysicsConstants.h"
+#include "Framework/AnalysisTask.h"
+#include "Framework/runDataProcessing.h"
+
+#include "Common/Core/RecoDecay.h"
+#include "Common/DataModel/Centrality.h"
+#include "Common/DataModel/Multiplicity.h"
+
+#include "PWGLF/DataModel/mcCentrality.h"
+
+#include "PWGHF/DataModel/CandidateReconstructionTables.h"
+#include "PWGHF/DataModel/CandidateSelectionTables.h"
+#include "PWGHF/DataModel/DerivedTables.h"
+#include "PWGHF/Utils/utilsDerivedData.h"
+#include "PWGHF/Utils/utilsPid.h"
+
+using namespace o2;
+using namespace o2::framework;
+using namespace o2::framework::expressions;
+using namespace o2::aod::pid_tpc_tof_utils;
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: vkucera
body: ```suggestion
```
created_at: 2025-05-12T15:09:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,375 @@
+// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
+// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
+// All rights not expressly granted are reserved.
+//
+// This software is distributed under the terms of the GNU General Public
+// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
+//
+// In applying this license CERN does not waive the privileges and immunities
+// granted to it by virtue of its status as an Intergovernmental Organization
+// or submit itself to any jurisdiction.
+
+/// \file derivedDataCreatorDstarToD0Pi.cxx
+/// \brief Producer of derived tables of Dstar candidates, collisions and MC particles
+/// \note Based on derivedDataCreatorLcToPKPi.cxx
path: PWGHF/TableProducer/derivedDataCreatorDstarToD0Pi.cxx
user: login: Mingze129
body: At the beginning, I referred to the Bplus code. However, I quickly realized that there are many differences between Bplus and our case, while Dplus and Lc are more similar to what we need. Therefore, I believe this code based on Lc is more appropriate.
created_at: 2025-05-12T15:40:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
path: PWGHF/DataModel/DerivedTables.h
user: login: Mingze129
body: The （todo）comment of D*+ has been removed.
created_at: 2025-05-12T15:42:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
path: PWGHF/DataModel/DerivedTables.h
user: login: vkucera
body: I didn't mean to remove the entire line, just the `(todo)` part (since it is done now).
created_at: 2025-05-12T16:08:41Z
author_association: COLLABORATOR
diff_hunk: @@ -731,6 +744,63 @@ DECLARE_SOA_TABLE_STAGED(HfDplusMcs, "HFDPMC", //! Table with MC candidate info
                          hf_cand_mc::FlagMcDecayChanRec,
                          o2::soa::Marker<MarkerDplus>);
 
+// ----------------
+// Dstar
path: PWGHF/DataModel/DerivedTables.h
user: login: Mingze129
body: Sorri I misunderstood it. It fixed now.
created_at: 2025-05-12T18:00:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-09T12:44:52Z
body: **O2 linter results:** ❌ 4 errors, ⚠️ 0 warnings, 🔕 0 disabled
<!-- thollander/actions-comment-pull-request "o2-linter" -->
author_association: NONE
user: login: Mingze129
created_at: 2025-05-12T15:46:21Z
body: Hi @vkucera , Thank you for your detailed review. 
I believe I have revised the code according to your suggestions. Please check it.
author_association: CONTRIBUTOR
user: login: alibuild
created_at: 2025-05-12T19:25:17Z
body: Error while checking build/O2Physics/o2 for e70504d71278b4d9bf8ca4393c9b16a9221877da at 2025-05-12 21:25:
```
No log files found
```
Full log [here](https://ali-ci.cern.ch/alice-build-logs/AliceO2Group/O2Physics/11147/e70504d71278b4d9bf8ca4393c9b16a9221877da/build_O2Physics_o2/pretty.html).

author_association: COLLABORATOR