Skip to content

Commit 8116d20

Browse files
committed
[PWGEM] Add new task for sanity check of EMCal cluster during different stages
- Add new task for sanity check of EMCal clusters and the connected MC infoation before skimming, after skimming and after (re)association of the McParticles to EmMcParticles.
1 parent e4b4c19 commit 8116d20

2 files changed

Lines changed: 293 additions & 0 deletions

File tree

PWGEM/PhotonMeson/Tasks/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,8 @@ o2physics_add_dpl_workflow(emcal-photon-mc-task
205205
SOURCES emcalPhotonMcTask.cxx
206206
PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore O2Physics::PWGEMPhotonMesonCore
207207
COMPONENT_NAME Analysis)
208+
209+
o2physics_add_dpl_workflow(emcal-mc-sanity-check
210+
SOURCES emcalMcSanityCheck.cxx
211+
PUBLIC_LINK_LIBRARIES O2::Framework O2Physics::AnalysisCore
212+
COMPONENT_NAME Analysis)
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
/// \file emcalMcSanityCheck.cxx
13+
/// \brief Task to test MC info for EMCal during different stages of out workflow
14+
/// \author M. Hemmer, marvin.hemmer@cern.ch
15+
16+
#include "PWGEM/PhotonMeson/DataModel/GammaTablesRedux.h"
17+
#include "PWGEM/PhotonMeson/DataModel/gammaTables.h"
18+
#include "PWGEM/PhotonMeson/Utils/emcalHistoDefinitions.h"
19+
#include "PWGJE/DataModel/EMCALClusters.h"
20+
21+
#include <Framework/ASoA.h>
22+
#include <Framework/AnalysisDataModel.h>
23+
#include <Framework/AnalysisTask.h>
24+
#include <Framework/Configurable.h>
25+
#include <Framework/HistogramRegistry.h>
26+
#include <Framework/HistogramSpec.h>
27+
#include <Framework/InitContext.h>
28+
#include <Framework/OutputObjHeader.h>
29+
#include <Framework/runDataProcessing.h>
30+
31+
#include <TH1.h>
32+
33+
#include <cmath>
34+
#include <cstddef>
35+
#include <cstdint>
36+
#include <limits>
37+
#include <string>
38+
#include <vector>
39+
40+
using namespace o2;
41+
using namespace o2::aod;
42+
using namespace o2::framework;
43+
using namespace o2::framework::expressions;
44+
using namespace o2::soa;
45+
46+
constexpr float PointOnePercent = 0.001f;
47+
constexpr float OnePercent = 0.01f;
48+
constexpr float TenPercent = 0.01f;
49+
constexpr float NinetyPercent = 0.9f;
50+
constexpr float OneHundredTenPercent = 1.1f;
51+
52+
// LSB from packing in MinClusters is 1 MeV
53+
constexpr float EnergyPackingLsb = 1.f / o2::aod::emcdownscaling::downscalingFactors[o2::aod::emcdownscaling::kEnergy];
54+
55+
// Rounding error from packing should be half the LSB so 0.5 MeV
56+
constexpr float EnergyComparisonEpsilon = 0.5f * EnergyPackingLsb;
57+
58+
// uint16_t saturates at 65535 so max value of energy for MinClusters should be 65.535 GeV
59+
constexpr float EnergyPackingSaturationValue = static_cast<float>(std::numeric_limits<uint16_t>::max()) * EnergyPackingLsb;
60+
61+
enum class EnergyComparisonStatus : uint8_t {
62+
Match = 0, // agrees within half a packing LSB
63+
Mismatch = 1, // disagrees beyond epsilon, and not explained by saturation
64+
Saturated = 2 // reference energy exceeded the uint16_t packing range
65+
};
66+
67+
enum class McParticleComparisonStatus : uint8_t {
68+
Match = 0, // same particle species, energy within epsilon, cluster fraction same
69+
DiffSpecies = 1, // not the same species
70+
DiffEnergy = 2, // energy not within epsilon
71+
DiffFraction = 3 // cluster fraction not within epsilon
72+
};
73+
74+
enum class EnergyRatioStatus : uint8_t {
75+
PointOnePercent = 0,
76+
OnePercent,
77+
TenPercent,
78+
NinetyPercent,
79+
Ok,
80+
OneHundredTenPercent
81+
};
82+
83+
struct EmcalMcSanityCheck {
84+
85+
using BeforeSkimmerCluster = soa::Join<EMCALClusters, EMCALMCClusters>;
86+
using AfterSkimmerCluster = soa::Join<MinClusters, EMCClusterMCLabels_001>;
87+
using AfterAssociateCluster = soa::Join<MinClusters, EMEMCClusterMCLabels_001>;
88+
using BeforeAfterAssociateCluster = soa::Join<MinClusters, EMCClusterMCLabels_001, EMEMCClusterMCLabels_001>;
89+
90+
HistogramRegistry registry{"registry", {}, OutputObjHandlingPolicy::AnalysisObject, false, false};
91+
92+
void init(InitContext&)
93+
{
94+
const AxisSpec axisParticleClusterFracRatioStatus{6, -0.5, 5.5};
95+
96+
auto hParticleClusterFracRatioStatus = registry.add<TH1>("BeforeSkimmer/hParticleClusterFracRatioStatus", "hParticleClusterFracRatioStatus;;counts", HistType::kTH1D, {axisParticleClusterFracRatioStatus});
97+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(1, "#it{E}_{part}/(frac#it{E}_{clus}) < 0.001");
98+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(2, "0.001 #leq #it{E}_{part}/(frac#it{E}_{clus}) < 0.01");
99+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(3, "0.01 #leq #it{E}_{part}/(frac#it{E}_{clus}) < 0.1");
100+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(4, "0.1 #leq #it{E}_{part}/(frac#it{E}_{clus}) < 0.9");
101+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(5, "0.9 #leq #it{E}_{part}/(frac#it{E}_{clus}) < 1.1");
102+
hParticleClusterFracRatioStatus->GetXaxis()->SetBinLabel(6, "#it{E}_{part}/(frac#it{E}_{clus}) > 1.1");
103+
104+
if (doprocessAfterSkimmer) {
105+
registry.addClone("BeforeSkimmer/", "AfterSkimmer/");
106+
}
107+
108+
if (doprocessAfterAssociation) {
109+
registry.addClone("BeforeSkimmer/", "AfterAssociate/");
110+
}
111+
112+
if (doprocessCompareBeforeAfterAssociate) {
113+
auto hMcParticleStatus = registry.add<TH1>("hMcParticleStatus", "hMcParticleStatus;;counts", HistType::kTH1D, {{4, -0.5, 3.5}});
114+
hMcParticleStatus->GetXaxis()->SetBinLabel(static_cast<int>(McParticleComparisonStatus::Match) + 1, "Match");
115+
hMcParticleStatus->GetXaxis()->SetBinLabel(static_cast<int>(McParticleComparisonStatus::DiffSpecies) + 1, "DiffSpecies");
116+
hMcParticleStatus->GetXaxis()->SetBinLabel(static_cast<int>(McParticleComparisonStatus::DiffEnergy) + 1, "DiffEnergy");
117+
hMcParticleStatus->GetXaxis()->SetBinLabel(static_cast<int>(McParticleComparisonStatus::DiffFraction) + 1, "DiffFraction");
118+
}
119+
}; // end init
120+
121+
EnergyRatioStatus getEnergyRatioStatus(float clusterE, float mcParticleE, float frac)
122+
{
123+
float ratio = mcParticleE / (clusterE * frac);
124+
// the most likely outcome due to hadrons not depositing their full energy
125+
if (ratio > OneHundredTenPercent) [[likely]] {
126+
return EnergyRatioStatus::OneHundredTenPercent;
127+
}
128+
if (NinetyPercent <= ratio && ratio < OneHundredTenPercent) {
129+
return EnergyRatioStatus::Ok;
130+
}
131+
if (TenPercent <= ratio && ratio < NinetyPercent) {
132+
return EnergyRatioStatus::NinetyPercent;
133+
}
134+
if (OnePercent <= ratio && ratio < TenPercent) [[unlikely]] {
135+
return EnergyRatioStatus::TenPercent;
136+
}
137+
if (PointOnePercent <= ratio && ratio < OnePercent) [[unlikely]] {
138+
return EnergyRatioStatus::OnePercent;
139+
}
140+
// everything else now has to be below 0.1%
141+
return EnergyRatioStatus::PointOnePercent;
142+
}
143+
144+
// referenceEnergy: full-precision energy (e.g. BeforeSkimmer's cluster.energy()).
145+
// compressedEnergy: energy unpacked from a uint16_t-packed table (e.g. MinClusters' cluster.e()).
146+
EnergyComparisonStatus compareClusterEnergy(float referenceEnergy, float compressedEnergy)
147+
{
148+
if (referenceEnergy > EnergyPackingSaturationValue) {
149+
return EnergyComparisonStatus::Saturated;
150+
}
151+
return std::abs(referenceEnergy - compressedEnergy) <= EnergyComparisonEpsilon
152+
? EnergyComparisonStatus::Match
153+
: EnergyComparisonStatus::Mismatch;
154+
}
155+
156+
void processBeforeSkimmer(BeforeSkimmerCluster const& clusters, McParticles_001 const& mcParticles)
157+
{
158+
if (clusters.size() == 0 || mcParticles.size() == 0) {
159+
return;
160+
}
161+
auto mcParticle = mcParticles.begin();
162+
163+
for (const auto& cluster : clusters) {
164+
if (!cluster.has_mcParticle()) {
165+
continue;
166+
}
167+
const auto& ids = cluster.mcParticleIds();
168+
const auto& fracs = cluster.amplitudeA();
169+
for (std::size_t i = 0; i < ids.size(); ++i) {
170+
const auto& id = ids[i];
171+
const auto& frac = fracs[i];
172+
mcParticle.setCursor(id);
173+
registry.fill(HIST("BeforeSkimmer/hParticleClusterFracRatioStatus"), static_cast<int>(getEnergyRatioStatus(cluster.energy(), mcParticle.e(), frac)));
174+
}
175+
}
176+
}
177+
PROCESS_SWITCH(EmcalMcSanityCheck, processBeforeSkimmer, "Process EMCal cluster information before the skimmerGammaCalo", true);
178+
179+
void processAfterSkimmer(AfterSkimmerCluster const& clusters, McParticles_001 const& mcParticles)
180+
{
181+
if (clusters.size() == 0 || mcParticles.size() == 0) {
182+
return;
183+
}
184+
auto mcParticle = mcParticles.begin();
185+
186+
for (const auto& cluster : clusters) {
187+
if (!cluster.has_mcParticle()) {
188+
continue;
189+
}
190+
const auto& ids = cluster.mcParticleIds();
191+
const auto& fracs = cluster.amplitude();
192+
for (std::size_t i = 0; i < ids.size(); ++i) {
193+
const auto& id = ids[i];
194+
const auto& frac = fracs[i];
195+
mcParticle.setCursor(id);
196+
registry.fill(HIST("AfterSkimmer/hParticleClusterFracRatioStatus"), static_cast<int>(getEnergyRatioStatus(cluster.e(), mcParticle.e(), frac)));
197+
}
198+
}
199+
}
200+
PROCESS_SWITCH(EmcalMcSanityCheck, processAfterSkimmer, "Process EMCal cluster information after the skimmerGammaCalo", true);
201+
202+
void processAfterAssociation(AfterAssociateCluster const& clusters, EMMCParticles_001 const& mcParticles)
203+
{
204+
if (clusters.size() == 0 || mcParticles.size() == 0) {
205+
return;
206+
}
207+
auto mcParticle = mcParticles.begin();
208+
209+
for (const auto& cluster : clusters) {
210+
if (!cluster.has_emmcparticle()) {
211+
continue;
212+
}
213+
const auto& ids = cluster.emmcparticleIds();
214+
const auto& fracs = cluster.amplitude();
215+
for (std::size_t i = 0; i < ids.size(); ++i) {
216+
const auto& id = ids[i];
217+
const auto& frac = fracs[i];
218+
mcParticle.setCursor(id);
219+
registry.fill(HIST("AfterAssociate/hParticleClusterFracRatioStatus"), static_cast<int>(getEnergyRatioStatus(cluster.e(), mcParticle.e(), frac)));
220+
}
221+
}
222+
}
223+
PROCESS_SWITCH(EmcalMcSanityCheck, processAfterAssociation, "Process EMCal cluster information before the associateMCinfoPhoton", false);
224+
225+
void processCompareBeforeAfterAssociate(MinClusters const& clusters,
226+
EMCClusterMCLabels_001 const& beforeLabels,
227+
EMEMCClusterMCLabels_001 const& afterLabels,
228+
McParticles_001 const& mcParticles,
229+
EMMCParticles_001 const& emMcParticles)
230+
{
231+
if (clusters.size() == 0 || mcParticles.size() == 0 || emMcParticles.size() == 0) {
232+
return;
233+
}
234+
235+
auto mcParticle = mcParticles.begin();
236+
auto emMcParticle = emMcParticles.begin();
237+
238+
for (int64_t i = 0; i < clusters.size(); ++i) {
239+
auto before = beforeLabels.iteratorAt(i);
240+
auto after = afterLabels.iteratorAt(i);
241+
242+
if (!after.has_emmcparticle() || !before.has_mcParticle()) {
243+
continue;
244+
}
245+
246+
if (after.emmcparticleIds().size() != before.mcParticleIds().size()) {
247+
LOG(fatal) << "Number of EmMcParticles and McParticles does not match (" << after.emmcparticleIds().size() << " vs " << before.mcParticleIds().size() << ")";
248+
}
249+
for (size_t id = 0; id < after.emmcparticleIds().size(); ++id) {
250+
bool isSameSpecies = true;
251+
bool isSameEnergy = true;
252+
bool hasSameFraction = true;
253+
254+
mcParticle.setCursor(before.mcParticleIds()[id]);
255+
emMcParticle.setCursor(after.emmcparticleIds()[id]);
256+
257+
if (mcParticle.pdgCode() != emMcParticle.pdgCode()) {
258+
isSameSpecies = false;
259+
}
260+
if (mcParticle.e() != emMcParticle.e()) {
261+
isSameEnergy = false;
262+
}
263+
if (before.amplitude()[id] != after.amplitude()[id]) {
264+
hasSameFraction = false;
265+
}
266+
267+
if (isSameSpecies && isSameEnergy && hasSameFraction) {
268+
registry.fill(HIST("hMcParticleStatus"), 0);
269+
}
270+
if (!isSameSpecies) {
271+
registry.fill(HIST("hMcParticleStatus"), 1);
272+
}
273+
if (!isSameEnergy) {
274+
registry.fill(HIST("hMcParticleStatus"), 2);
275+
}
276+
if (!hasSameFraction) {
277+
registry.fill(HIST("hMcParticleStatus"), 3);
278+
}
279+
}
280+
}
281+
}
282+
PROCESS_SWITCH(EmcalMcSanityCheck, processCompareBeforeAfterAssociate, "Process and compare EMCal cluster and McParticle information before and after the associateMCinfoPhoton", false);
283+
}; // End struct EmcalMcSanityCheck
284+
285+
WorkflowSpec defineDataProcessing(ConfigContext const& context)
286+
{
287+
return WorkflowSpec{adaptAnalysisTask<EmcalMcSanityCheck>(context)};
288+
}

0 commit comments

Comments
 (0)