3636#include < Framework/HistogramRegistry.h>
3737#include < Framework/HistogramSpec.h>
3838#include < Framework/InitContext.h>
39+ #include < Framework/O2DatabasePDGPlugin.h>
3940#include < Framework/runDataProcessing.h>
4041
4142#include < TFile.h>
4243#include < TH1.h>
4344#include < THn.h>
4445#include < TNamed.h>
4546#include < TObjArray.h>
47+ #include < TPDGCode.h>
4648#include < TRandom3.h>
4749
4850#include < algorithm>
@@ -78,7 +80,7 @@ struct EnergyFlowGfw {
7880
7981 enum ActivityAxisMode : int {
8082 CentralityAxis = 0 ,
81- NGlobalTracksAxis ,
83+ NchAxis ,
8284 ZdcSpectatorAxis
8385 };
8486
@@ -99,6 +101,20 @@ struct EnergyFlowGfw {
99101 HasAllTiming
100102 };
101103
104+ enum EfficiencyStep : uint8_t {
105+ EfficiencyGenerated = 0 ,
106+ EfficiencyReconstructed,
107+ EfficiencyStepCount
108+ };
109+
110+ enum EfficiencySpecies : uint8_t {
111+ EfficiencyCharged = 0 ,
112+ EfficiencyPion,
113+ EfficiencyKaon,
114+ EfficiencyProton,
115+ EfficiencySpeciesCount
116+ };
117+
102118 struct : ConfigurableGroup {
103119 Configurable<float > vertexZ{" vertexZ" , 10 .f , " Accepted |z-vertex| (cm)" };
104120 Configurable<std::pair<float , float >> ptCuts{" ptCuts" , {0 .2f , 5 .f }, " Track pT cuts (GeV/c)" };
@@ -118,6 +134,8 @@ struct EnergyFlowGfw {
118134 Configurable<int > cfgBootstrapSamples{" cfgBootstrapSamples" , 10 , " Number of FlowContainer bootstrap subsamples; zero disables bootstrapping" };
119135 Configurable<int > cfgActivityAxis{" cfgActivityAxis" , CentralityAxis, " FlowContainer x-axis: 0=centrality, 1=number of filtered global tracks, 2=corrected ZDC spectator estimate" };
120136 Configurable<float > cfgMaxCentrality{" cfgMaxCentrality" , 100 .f , " Maximum accepted centrality percentile" };
137+ Configurable<bool > cfgFillEfficiency{" fillEfficiency" , false , " Fill MC tracking efficiency; ignored by data processes" };
138+ Configurable<bool > cfgFillAcceptance{" fillAcceptance" , false , " Fill GFW acceptance weights instead of running the reconstructed analysis" };
121139
122140 Configurable<float > cfgSpectatorEnergyPerNucleon{" cfgSpectatorEnergyPerNucleon" , 1 .f , " ZDC energy corresponding to one spectator nucleon" };
123141 Configurable<float > cfgMaxTotalSpectators{" cfgMaxTotalSpectators" , 500 .f , " Maximum accepted spectator count from ZNA+ZNC+ZPA+ZPC" };
@@ -152,16 +170,26 @@ struct EnergyFlowGfw {
152170 ((requireGlobalTrackInFilter()) || (aod::track::isGlobalTrackSDD == static_cast <uint8_t >(true )));
153171
154172 using CollisionsRun2 = soa::Filtered<soa::Join<aod::Collisions, aod::EvSels, aod::CentRun2V0Ms>>;
173+ using CollisionsRun2MC = soa::Join<aod::Collisions, aod::EvSels, aod::CentRun2V0Ms, aod::McCollisionLabels>;
155174 using CollisionsRun3 = soa::Filtered<soa::Join<aod::Collisions, aod::EvSels, aod::CentFT0Cs>>;
175+ using CollisionsRun3MC = soa::Join<aod::Collisions, aod::EvSels, aod::CentFT0Cs, aod::McCollisionLabels>;
156176 using Tracks = soa::Filtered<soa::Join<aod::Tracks, aod::TracksExtra, aod::TrackSelection,
157177 aod::pidTPCFullPi, aod::pidTPCFullKa, aod::pidTPCFullPr,
158- aod::pidTOFbeta, aod::pidTOFFullPi, aod::pidTOFFullKa, aod::pidTOFFullPr>>;
178+ aod::pidTOFFullPi, aod::pidTOFFullKa, aod::pidTOFFullPr>>;
179+ using TracksMC = soa::Filtered<soa::Join<aod::Tracks, aod::TracksExtra, aod::TrackSelection,
180+ aod::pidTPCFullPi, aod::pidTPCFullKa, aod::pidTPCFullPr,
181+ aod::pidTOFFullPi, aod::pidTOFFullKa, aod::pidTOFFullPr,
182+ aod::McTrackLabels>>;
159183 using BCsRun2 = soa::Join<aod::BCs, aod::Timestamps, aod::BcSels, aod::Run2MatchedToBCSparse>;
160184 using BCsRun3 = soa::Join<aod::BCs, aod::Timestamps, aod::BcSels, aod::Run3MatchedToBCSparse>;
161185
186+ Preslice<TracksMC> tracksPerCollision = aod::track::collisionId;
187+
162188 OutputObj<FlowContainer> flowContainer{" FlowContainer" };
189+ OutputObj<GFWWeights> acceptanceWeights{GFWWeights (" weights" )};
163190 HistogramRegistry registry{" registry" };
164191 Service<o2::ccdb::BasicCCDBManager> ccdb{};
192+ Service<o2::framework::O2DatabasePDG> pdgDB{};
165193
166194 std::unique_ptr<GFW > gfw{std::make_unique<GFW >()};
167195 std::unique_ptr<TRandom3> random{std::make_unique<TRandom3>(0 )};
@@ -176,8 +204,9 @@ struct EnergyFlowGfw {
176204
177205 void init (InitContext const &)
178206 {
179- if (doprocessRun2 && doprocessRun3) {
180- LOGF (fatal, " Enable only one input format: Run 2 AOD or Run 3 AOD" );
207+ const int enabledProcesses = static_cast <int >(doprocessRun2) + static_cast <int >(doprocessRun2MC) + static_cast <int >(doprocessRun3) + static_cast <int >(doprocessRun3MC);
208+ if (enabledProcesses > 1 ) {
209+ LOGF (fatal, " Enable only one input process: Run 2 data, Run 2 MC, Run 3 data, or Run 3 MC" );
181210 }
182211 if (cuts.ptCuts ->first >= cuts.ptCuts ->second || cuts.etaMax <= 0 .f ) {
183212 LOGF (fatal, " Require ptMin < ptMax and etaMax > 0" );
@@ -194,8 +223,11 @@ struct EnergyFlowGfw {
194223 if (cfgActivityAxis < CentralityAxis || cfgActivityAxis > ZdcSpectatorAxis) {
195224 LOGF (fatal, " cfgActivityAxis must be 0 (centrality), 1 (filtered global tracks), or 2 (corrected ZDC spectators)" );
196225 }
197- if (cfgMaxCentrality < 0 .f || cfgMaxCentrality > 100 .f || cfgSpectatorEnergyPerNucleon <= 0 .f || cfgMaxTotalSpectators < 0 .f || cfgZdcResponseZNA <= 0 .f || cfgZdcResponseZNC <= 0 .f || cfgZdcResponseZPA <= 0 .f || cfgZdcResponseZPC <= 0 .f || cfgZdcMaxAbsTimeForQa <= 0 .f ) {
198- LOGF (fatal, " The maximum centrality must be in [0, 100], the spectator energy scale, ZDC response factors, and ZDC QA time range must be positive, and the spectator threshold non-negative" );
226+ if (!std::isfinite (cfgMaxCentrality.value ) || cfgMaxCentrality < 0 .f || cfgSpectatorEnergyPerNucleon <= 0 .f || cfgMaxTotalSpectators < 0 .f || cfgZdcResponseZNA <= 0 .f || cfgZdcResponseZNC <= 0 .f || cfgZdcResponseZPA <= 0 .f || cfgZdcResponseZPC <= 0 .f || cfgZdcMaxAbsTimeForQa <= 0 .f ) {
227+ LOGF (fatal, " The maximum centrality must be finite and non-negative, the spectator energy scale, ZDC response factors, and ZDC QA time range must be positive, and the spectator threshold non-negative" );
228+ }
229+ if (cfgMaxCentrality > 100 .f ) {
230+ LOGF (warning, " cfgMaxCentrality is %.1f (> 100); dummy centrality values will be accepted. Use the Nch or spectator activity axis when calibrated centrality is unavailable" , cfgMaxCentrality.value );
199231 }
200232
201233 const int numberOfRegions = cfgRegions->GetSize ();
@@ -247,7 +279,7 @@ struct EnergyFlowGfw {
247279 gfw->CreateRegions ();
248280 flowContainer.setObject (new FlowContainer (" FlowContainer" ));
249281 const AxisSpec activityAxis = [this ]() {
250- if (cfgActivityAxis.value == NGlobalTracksAxis ) {
282+ if (cfgActivityAxis.value == NchAxis ) {
251283 return AxisSpec{axisNGlobalTracks, " N_{global tracks}" };
252284 }
253285 if (cfgActivityAxis.value == ZdcSpectatorAxis) {
@@ -260,6 +292,9 @@ struct EnergyFlowGfw {
260292 } else {
261293 flowContainer->Initialize (profileNames.get (), activityAxis, cfgBootstrapSamples);
262294 }
295+ if (cfgFillAcceptance) {
296+ acceptanceWeights->init (true , false );
297+ }
263298
264299 const AxisSpec etaAxis{32 , -cuts.etaMax .value , cuts.etaMax .value , " #eta" };
265300 registry.add (" event/centrality" , " Accepted events;centrality;events" , HistType::kTH1F , {axisCentrality});
@@ -271,6 +306,19 @@ struct EnergyFlowGfw {
271306 registry.add (" track/energy" , " Energy entering the GFW;E_{T} (GeV);tracks" , HistType::kTH1F , {axisEnergy});
272307 registry.add (" track/pid" , " PID mass assignment;species;tracks" , HistType::kTH1F , {{4 , -0.5 , 3.5 }});
273308
309+ if (cfgFillEfficiency && (doprocessRun2MC || doprocessRun3MC)) {
310+ const AxisSpec efficiencySpeciesAxis{EfficiencySpeciesCount, -0.5 , static_cast <float >(EfficiencySpeciesCount) - 0.5 , " particle" };
311+ const AxisSpec efficiencyStepAxis{EfficiencyStepCount, -0.5 , static_cast <float >(EfficiencyStepCount) - 0.5 , " generated/reconstructed" };
312+ registry.add (" Efficiency/efficiencyHist" , " MC tracking efficiency;#it{p}_{T}^{MC};centrality (%);particle;step" , HistType::kTHnSparseF , {axisPt, axisCentrality, efficiencySpeciesAxis, efficiencyStepAxis});
313+ auto efficiencyHistogram = registry.get <THnSparse>(HIST (" Efficiency/efficiencyHist" ));
314+ efficiencyHistogram->GetAxis (2 )->SetBinLabel (EfficiencyCharged + 1 , " charged" );
315+ efficiencyHistogram->GetAxis (2 )->SetBinLabel (EfficiencyPion + 1 , " pion" );
316+ efficiencyHistogram->GetAxis (2 )->SetBinLabel (EfficiencyKaon + 1 , " kaon" );
317+ efficiencyHistogram->GetAxis (2 )->SetBinLabel (EfficiencyProton + 1 , " proton" );
318+ efficiencyHistogram->GetAxis (3 )->SetBinLabel (EfficiencyGenerated + 1 , " generated" );
319+ efficiencyHistogram->GetAxis (3 )->SetBinLabel (EfficiencyReconstructed + 1 , " reconstructed" );
320+ }
321+
274322 registry.add (" zdc/qaSelection" , " ZDC QA availability;condition;events" , HistType::kTH1F , {{6 , -0.5 , 5.5 }});
275323 registry.add (" zdc/energyZNA" , " Triggered events with matched ZDC;E_{ZNA};events" , HistType::kTH1F , {axisZdcNeutronEnergy});
276324 registry.add (" zdc/energyZNC" , " Triggered events with matched ZDC;E_{ZNC};events" , HistType::kTH1F , {axisZdcNeutronEnergy});
@@ -685,11 +733,79 @@ struct EnergyFlowGfw {
685733 return std::hypot (track.pt (), mass / std::cosh (track.eta ()));
686734 }
687735
736+ template <typename TParticle>
737+ bool isEfficiencyParticle (TParticle const & particle) const
738+ {
739+ if (!particle.isPhysicalPrimary () || std::abs (particle.eta ()) >= cuts.etaMax .value || particle.pt () <= cuts.ptCuts .value .first || particle.pt () >= cuts.ptCuts .value .second ) {
740+ return false ;
741+ }
742+ const auto * pdgParticle = pdgDB->GetParticle (particle.pdgCode ());
743+ return pdgParticle && pdgParticle->Charge () != 0 .;
744+ }
745+
746+ template <typename TParticle>
747+ int efficiencySpecies (TParticle const & particle) const
748+ {
749+ const int absolutePdg = std::abs (particle.pdgCode ());
750+ if (absolutePdg == PDG_t::kPiPlus ) {
751+ return EfficiencyPion;
752+ }
753+ if (absolutePdg == PDG_t::kKPlus ) {
754+ return EfficiencyKaon;
755+ }
756+ if (absolutePdg == PDG_t::kProton ) {
757+ return EfficiencyProton;
758+ }
759+ return EfficiencyCharged;
760+ }
761+
762+ template <typename TParticle>
763+ void fillEfficiencyParticle (TParticle const & particle, float centrality, EfficiencyStep step)
764+ {
765+ if (!isEfficiencyParticle (particle)) {
766+ return ;
767+ }
768+ registry.fill (HIST (" Efficiency/efficiencyHist" ), particle.pt (), centrality, EfficiencyCharged, step);
769+ const int species = efficiencySpecies (particle);
770+ if (species != EfficiencyCharged) {
771+ registry.fill (HIST (" Efficiency/efficiencyHist" ), particle.pt (), centrality, species, step);
772+ }
773+ }
774+
775+ template <typename TParticles, typename TTracks>
776+ void fillTrackingEfficiency (int mcCollisionIndex, TParticles const & particles, TTracks const & tracks, float centrality)
777+ {
778+ for (const auto & particle : particles) {
779+ if (particle.mcCollisionId () != mcCollisionIndex) {
780+ continue ;
781+ }
782+ fillEfficiencyParticle (particle, centrality, EfficiencyGenerated);
783+ }
784+ for (const auto & track : tracks) {
785+ if (!track.has_mcParticle ()) {
786+ continue ;
787+ }
788+ const auto & particle = track.mcParticle ();
789+ if (particle.mcCollisionId () != mcCollisionIndex) {
790+ continue ;
791+ }
792+ fillEfficiencyParticle (particle, centrality, EfficiencyReconstructed);
793+ }
794+ }
795+
796+ template <typename TCollision, typename TTracks>
797+ void fillAcceptanceWeights (TCollision const & collision, TTracks const & tracks, float centrality)
798+ {
799+ for (const auto & track : tracks) {
800+ acceptanceWeights->fill (track.phi (), track.eta (), collision.posZ (), track.pt (), centrality, 0 );
801+ }
802+ }
803+
688804 template <typename TCollision, typename TTracks>
689805 void processCollision (TCollision const & collision, TTracks const & tracks, float centrality, float spectators)
690806 {
691807 float activity = centrality;
692- if (cfgActivityAxis.value == NGlobalTracksAxis ) {
808+ if (cfgActivityAxis.value == NchAxis ) {
693809 activity = static_cast <float >(tracks.size ());
694810 } else if (cfgActivityAxis.value == ZdcSpectatorAxis) {
695811 activity = spectators;
@@ -755,28 +871,122 @@ struct EnergyFlowGfw {
755871 if (!eventSelected<BCsRun2>(collision, collision.alias_bit (kINT7 ) && collision.sel7 (), static_cast <float >(tracks.size ()), collision.centRun2V0M (), spectators)) {
756872 return ;
757873 }
874+ if (cfgFillAcceptance) {
875+ fillAcceptanceWeights (collision, tracks, collision.centRun2V0M ());
876+ return ;
877+ }
758878 const auto & bc = collision.foundBC_as <BCsRun2>();
759879 loadEfficiency (bc.timestamp ());
760880 loadAcceptance (bc.timestamp (), bc.runNumber ());
761881 processCollision (collision, tracks, collision.centRun2V0M (), spectators);
762882 }
763883 PROCESS_SWITCH (EnergyFlowGfw, processRun2, " Process Run 2 AOD data" , false );
764884
885+ void processRun2MC (aod::McCollisions::iterator const & mcCollision,
886+ soa::SmallGroups<CollisionsRun2MC> const & collisions,
887+ BCsRun2 const &,
888+ aod::Zdcs const &,
889+ TracksMC const & tracks,
890+ aod::McParticles const & mcParticles)
891+ {
892+ if (collisions.size () != 1 ) {
893+ return ;
894+ }
895+
896+ for (const auto & collision : collisions) {
897+ if (std::abs (collision.posZ ()) >= cuts.vertexZ .value ) {
898+ return ;
899+ }
900+ const auto groupedTracks = tracks.sliceBy (tracksPerCollision, collision.globalIndex ());
901+ float spectators = -1 .f ;
902+ if (!eventSelected<BCsRun2>(collision, collision.alias_bit (kINT7 ) && collision.sel7 (), static_cast <float >(groupedTracks.size ()), collision.centRun2V0M (), spectators)) {
903+ return ;
904+ }
905+
906+ bool filledCorrections = false ;
907+ if (cfgFillEfficiency) {
908+ fillTrackingEfficiency (mcCollision.globalIndex (), mcParticles, groupedTracks, collision.centRun2V0M ());
909+ filledCorrections = true ;
910+ }
911+ if (cfgFillAcceptance) {
912+ fillAcceptanceWeights (collision, groupedTracks, collision.centRun2V0M ());
913+ filledCorrections = true ;
914+ }
915+ if (filledCorrections) {
916+ return ;
917+ }
918+
919+ const auto & bc = collision.foundBC_as <BCsRun2>();
920+ loadEfficiency (bc.timestamp ());
921+ loadAcceptance (bc.timestamp (), bc.runNumber ());
922+ processCollision (collision, groupedTracks, collision.centRun2V0M (), spectators);
923+ return ;
924+ }
925+ }
926+ PROCESS_SWITCH (EnergyFlowGfw, processRun2MC, " Process matched Run 2 MC for efficiency, acceptance, or reconstructed closure" , false );
927+
765928 void processRun3 (CollisionsRun3::iterator const & collision, Tracks const & tracks, BCsRun3 const &, aod::Zdcs const &)
766929 {
767930 float spectators = -1 .f ;
768931 if (!eventSelected<BCsRun3>(collision, collision.sel8 (), static_cast <float >(tracks.size ()), collision.centFT0C (), spectators)) {
769932 return ;
770933 }
934+ if (cfgFillAcceptance) {
935+ fillAcceptanceWeights (collision, tracks, collision.centFT0C ());
936+ return ;
937+ }
771938 const auto & bc = collision.foundBC_as <BCsRun3>();
772939 loadEfficiency (bc.timestamp ());
773940 loadAcceptance (bc.timestamp (), bc.runNumber ());
774941 processCollision (collision, tracks, collision.centFT0C (), spectators);
775942 }
776943 PROCESS_SWITCH (EnergyFlowGfw, processRun3, " Process Run 3 AOD data" , true );
944+
945+ void processRun3MC (aod::McCollisions::iterator const & mcCollision,
946+ soa::SmallGroups<CollisionsRun3MC> const & collisions,
947+ BCsRun3 const &,
948+ aod::Zdcs const &,
949+ TracksMC const & tracks,
950+ aod::McParticles const & mcParticles)
951+ {
952+ if (collisions.size () != 1 ) {
953+ return ;
954+ }
955+
956+ for (const auto & collision : collisions) {
957+ if (std::abs (collision.posZ ()) >= cuts.vertexZ .value ) {
958+ return ;
959+ }
960+ const auto groupedTracks = tracks.sliceBy (tracksPerCollision, collision.globalIndex ());
961+ float spectators = -1 .f ;
962+ if (!eventSelected<BCsRun3>(collision, collision.sel8 (), static_cast <float >(groupedTracks.size ()), collision.centFT0C (), spectators)) {
963+ return ;
964+ }
965+
966+ bool filledCorrections = false ;
967+ if (cfgFillEfficiency) {
968+ fillTrackingEfficiency (mcCollision.globalIndex (), mcParticles, groupedTracks, collision.centFT0C ());
969+ filledCorrections = true ;
970+ }
971+ if (cfgFillAcceptance) {
972+ fillAcceptanceWeights (collision, groupedTracks, collision.centFT0C ());
973+ filledCorrections = true ;
974+ }
975+ if (filledCorrections) {
976+ return ;
977+ }
978+
979+ const auto & bc = collision.foundBC_as <BCsRun3>();
980+ loadEfficiency (bc.timestamp ());
981+ loadAcceptance (bc.timestamp (), bc.runNumber ());
982+ processCollision (collision, groupedTracks, collision.centFT0C (), spectators);
983+ return ;
984+ }
985+ }
986+ PROCESS_SWITCH (EnergyFlowGfw, processRun3MC, " Process matched Run 3 MC for efficiency, acceptance, or reconstructed closure" , false );
777987};
778988
779- WorkflowSpec defineDataProcessing (ConfigContext const & cfgc )
989+ WorkflowSpec defineDataProcessing (ConfigContext const & context )
780990{
781- return WorkflowSpec{adaptAnalysisTask<EnergyFlowGfw>(cfgc )};
991+ return WorkflowSpec{adaptAnalysisTask<EnergyFlowGfw>(context )};
782992}
0 commit comments