LDMX Software
EcalClusterAnalyzer.cxx
2
3#include <algorithm>
4#include <cmath>
5
6#include "DetDescr/SimSpecialID.h"
8#include "Ecal/Event/EcalHit.h"
11
12namespace dqm {
13
16 ps.get<bool>("use_simulated_electron_number");
17 nbr_of_electrons_ = ps.get<int>("nbr_of_electrons");
18
19 ecal_sim_hit_coll_ = ps.get<std::string>("ecal_sim_hit_coll");
20 ecal_sim_hit_pass_ = ps.get<std::string>("ecal_sim_hit_pass");
21
22 rec_hit_coll_name_ = ps.get<std::string>("rec_hit_coll_name");
23 rec_hit_pass_name_ = ps.get<std::string>("rec_hit_pass_name");
24
25 cluster_coll_name_ = ps.get<std::string>("cluster_coll_name");
26 cluster_pass_name_ = ps.get<std::string>("cluster_pass_name");
27
28 ecal_sp_hits_coll_name_ =
29 ps.getParameter<std::string>("ecal_sp_hits_coll_name");
30 ecal_sp_hits_pass_name_ =
31 ps.getParameter<std::string>("ecal_sp_hits_pass_name");
32 mixed_hit_cutoff_ = ps.getParameter<double>("mixed_hit_cutoff");
33
34 inverse_skim_ = ps.get<bool>("inverse_skim");
35 n_ecal_clusters_min_ = ps.get<int>("n_ecal_clusters_min");
36 return;
37}
38
40 const auto& ecal_rec_hits{event.getCollection<ldmx::EcalHit>(
41 rec_hit_coll_name_, rec_hit_pass_name_)};
42 const auto& ecal_sim_hits{event.getCollection<ldmx::SimCalorimeterHit>(
43 ecal_sim_hit_coll_, ecal_sim_hit_pass_)};
44 const auto& ecal_clusters{event.getCollection<ldmx::EcalCluster>(
45 cluster_coll_name_, cluster_pass_name_)};
46
47 // Determine the number of recoil electrons in the event
48 // By default from the TS track counting
49 int nbr_of_electrons{event.getElectronCount()};
50 // If configured to use the simulated electron number, use that instead
52 nbr_of_electrons = nbr_of_electrons_;
53 }
54
55 std::map<int, int> layer_cluster_count;
56 for (const auto& cluster : ecal_clusters) {
57 auto layer = cluster.getLayer();
58 layer_cluster_count[layer]++;
59 }
60
61 int total_clusters = 0;
62 for (const auto& [layer, count] : layer_cluster_count) {
63 total_clusters += count;
64 }
65
66 int n_ecal_clusters = 0;
67 if (layer_cluster_count.size() != 0) {
68 n_ecal_clusters = static_cast<int>(std::round(
69 static_cast<double>(total_clusters) / layer_cluster_count.size()));
70 }
71
72 ldmx_log(info) << "Avg number of clusters per layer: " << n_ecal_clusters;
73 // Fill histograms with the number of clusters
74 histograms_.fill("number_of_clusters", total_clusters);
75 histograms_.fill("number_of_clusters_per_layer", n_ecal_clusters);
76 histograms_.fill("number_of_clusters_first_layer", layer_cluster_count[0]);
77
78 // Fill simplied 3-bin histogram to check the prediction
79 if (n_ecal_clusters == nbr_of_electrons) {
80 // correct
81 histograms_.fill("correctly_predicted_events", 1);
82 } else if (n_ecal_clusters < nbr_of_electrons) {
83 // undercounting
84 histograms_.fill("correctly_predicted_events", 0);
85 } else if (n_ecal_clusters > nbr_of_electrons) {
86 // overcounting
87 histograms_.fill("correctly_predicted_events", 2);
88 }
89
90 std::unordered_map<int, std::pair<int, std::vector<double>>> hit_info;
91 hit_info.reserve(ecal_rec_hits.size());
92
93 // Determine the truth information for the recoil electron
94 std::vector<std::vector<float>> sp_electron_positions;
95 const auto& ecal_sp_hits{event.getCollection<ldmx::SimTrackerHit>(
96 ecal_sp_hits_coll_name_, ecal_sp_hits_pass_name_)};
97
98 std::vector<ldmx::SimTrackerHit> sorted_sp_hits = ecal_sp_hits;
99 std::sort(sorted_sp_hits.begin(), sorted_sp_hits.end(),
100 [](const ldmx::SimTrackerHit& a, const ldmx::SimTrackerHit& b) {
101 return a.getTrackID() < b.getTrackID();
102 });
103
104 ldmx_log(trace) << "Number of ECal Scoring Plane Hits: "
105 << sorted_sp_hits.size();
106
107 // Collect positions of all recoil electrons on the SP
108 // relying on the track ID to identify them
109 unsigned int n_filled = 0;
110 for (const ldmx::SimTrackerHit& sp_hit : sorted_sp_hits) {
111 if (sp_hit.getPdgID() != 11) continue;
112 if (sp_hit.getMomentum()[2] <= 0) continue;
113 ldmx::SimSpecialID hit_id(sp_hit.getID());
114 // Ecal scoring plane is plane 31
115 if (hit_id.plane() != 31) continue;
116 if (n_filled < nbr_of_electrons) {
117 ldmx_log(trace) << "\tSP Hit to be added with Track ID : "
118 << sp_hit.getTrackID() << ", SP Hit Position ("
119 << sp_hit.getPosition()[0] << ", "
120 << sp_hit.getPosition()[1] << ", "
121 << sp_hit.getPosition()[2] << ") mm";
122 sp_electron_positions.push_back(sp_hit.getPosition());
123 n_filled++;
124 }
125 }
126
127 ldmx_log(info) << "Number of ECal CLUE clusters: " << n_ecal_clusters
128 << ", TS counted electrons: " << nbr_of_electrons
129 << ", SP electrons: " << sp_electron_positions.size();
130
131 std::map<int, float> true_energy;
132 std::map<int, float> delta_energy;
133 double sp_ele_dist{9999.};
134 if (nbr_of_electrons == 2 && sp_electron_positions.size() > 1) {
135 // Measures sp_ele_distance between two electrons in the ECal scoring plane
136 // TODO: generalize for n electrons
137 std::vector<float> pos1;
138 std::vector<float> pos2;
139 pos1 = sp_electron_positions[0];
140 pos2 = sp_electron_positions[1];
141 sp_ele_dist = std::sqrt((pos1[0] - pos2[0]) * (pos1[0] - pos2[0]) +
142 (pos1[1] - pos2[1]) * (pos1[1] - pos2[1]));
143
144 histograms_.fill("sp_distance", sp_ele_dist);
145
146 } // end block about the scoring plane hits
147
148 ldmx_log(trace) << "Distance between the two e- in the ECal scoring plane: "
149 << sp_ele_dist << " mm";
150
151 double tot_event_energy = 0;
152 std::vector<double> tot_origin_edep;
153 tot_origin_edep.resize(nbr_of_electrons_ + 1);
154 int n_mixed = 0;
155
156 // Loop over the rechits and find the matching simhits
157 ldmx_log(trace) << "Loop over the rechits and find the matching simhits";
158 for (const auto& hit : ecal_rec_hits) {
159 auto it = std::find_if(
160 ecal_sim_hits.begin(), ecal_sim_hits.end(),
161 [&hit](const auto& sim_hit) { return sim_hit.getID() == hit.getID(); });
162 if (it != ecal_sim_hits.end()) {
163 // if found a simhit matching this rechit
164 ldmx_log(trace) << "\tFound simhit matching rechit with ID"
165 << hit.getID();
166 int ancestor = 0;
167 int prev_ancestor = 0;
168 bool tagged = false;
169 int tag = 0;
170 std::vector<double> edep;
171 edep.resize(nbr_of_electrons_ + 1);
172 double e_tot = 0; // keep track of total from all counted ancestors
173 ldmx_log(trace) << "\t\tIt has " << it->getNumberOfContribs()
174 << " contribs. ";
175 for (int i = 0; i < it->getNumberOfContribs(); i++) {
176 // for each contrib in this simhit
177 const auto& contrib = it->getContrib(i);
178 // get origin electron ID
179 ancestor = contrib.origin_id_;
180 ldmx_log(trace) << "\t\t\tAncestor ID " << ancestor << " with edep "
181 << contrib.edep_;
182 tot_event_energy += contrib.edep_;
183 // store energy from this contrib at index = origin electron ID
184 if (ancestor <= nbr_of_electrons) {
185 edep[ancestor] += contrib.edep_;
186 tot_origin_edep[ancestor] += contrib.edep_;
187 e_tot += contrib.edep_;
188 }
189 if (!tagged && i != 0 && prev_ancestor != ancestor) {
190 // if origin electron ID does not match previous origin electron ID
191 // this hit has contributions from several electrons, ie mixed case
192 tag = 0;
193 tagged = true;
194 ldmx_log(trace) << "\t\t\t\tMixed hit! Ancestor ID changed to "
195 << ancestor;
196 }
197 prev_ancestor = ancestor;
198 } // over contribs
199 // now check if mixed really means mixed, i.e. more than small fraction
200 // from a second electron.
201 if (tagged) {
202 for (int i = 1; i < nbr_of_electrons_ + 1; i++) {
203 if (edep[i] / e_tot >
204 1 - mixed_hit_cutoff_) { // one ancestor contributes at least the
205 // complement to the allowed mixing
206 // fraction
207 tagged = false;
208 ancestor =
209 distance(edep.begin(), max_element(edep.begin(), edep.end()));
210 ldmx_log(trace)
211 << "\t\t\t\tUndid mixed hit tagging, now ancestor = "
212 << ancestor;
213 break;
214 }
215 }
216 }
217 if (!tagged) {
218 // if not tagged, hit was from a single electron (within acceptable
219 // purity)
220 tag = ancestor; // prev_ancestor;
221 } else
222 n_mixed++;
223 histograms_.fill("ancestors", tag);
224 hit_info.insert({hit.getID(), std::make_pair(tag, edep)});
225 } // end if simhit found
226 } // end loop on the rechits
227
228 // Loop over the clusters
229 int clustered_hits = 0;
230 ldmx_log(trace) << "Loop over the clusters, N = " << n_ecal_clusters;
231 histograms_.fill("tag0frac_vs_SPdist", sp_ele_dist,
232 (float)n_mixed / ecal_rec_hits.size());
233 ldmx_log(debug) << "Got " << n_mixed << " mixed hits, a fraction of "
234 << (float)n_mixed / ecal_rec_hits.size();
235
236 if (ecal_clusters.size() >= 2) {
237 float d_x =
238 ecal_clusters[0].getCentroidX() - ecal_clusters[1].getCentroidX();
239 float d_y =
240 ecal_clusters[0].getCentroidY() - ecal_clusters[1].getCentroidY();
241 float d_r = std::sqrt(d_x * d_x + d_y * d_y);
242 histograms_.fill("cluster_distance", d_r);
243 ldmx_log(trace) << "Gt cluster distance (0,1) = " << d_r;
244 }
245
246 for (const auto& cl : ecal_clusters) {
247 auto layer = cl.getLayer();
248 ldmx_log(trace) << "Cluster in layer " << layer
249 << ", energy: " << cl.getEnergy()
250 << ", number of hits: " << cl.getHitIDs().size();
251 auto cluster_centroid_x = cl.getCentroidX();
252 auto cluster_centroid_y = cl.getCentroidY();
253 auto cluster_rms_x = cl.getRMSX();
254 auto cluster_rms_y = cl.getRMSY();
255
256 // Find the closest sp_electron_positions to the cluster centroid
257 double min_distance = 9999.;
258 double sp_clue_x_residuals = 9999.;
259 double sp_clue_y_residuals = 9999.;
260 for (const auto& sp_pos : sp_electron_positions) {
261 double distance = std::sqrt(
262 (sp_pos[0] - cluster_centroid_x) * (sp_pos[0] - cluster_centroid_x) +
263 (sp_pos[1] - cluster_centroid_y) * (sp_pos[1] - cluster_centroid_y));
264 if (distance < min_distance) {
265 min_distance = distance;
266 sp_clue_x_residuals = sp_pos[0] - cluster_centroid_x;
267 sp_clue_y_residuals = sp_pos[1] - cluster_centroid_y;
268 }
269 } // end loop on the scoring plane electron positions
270 // Fill histogram with the distance to the closest scoring plane electron
271 ldmx_log(trace) << "\tCluster centroid: (" << cluster_centroid_x << " +/- "
272 << cluster_rms_x << ", " << cluster_centroid_y << " +/- "
273 << cluster_rms_y
274 << " mm; min distance to SP electron: " << min_distance
275 << " mm";
276 if (layer == 0) {
277 histograms_.fill("sp_clue_distance", min_distance);
278 histograms_.fill("sp_clue_x_residual", sp_clue_x_residuals);
279 histograms_.fill("sp_clue_y_residual", sp_clue_y_residuals);
280 }
281 histograms_.fill("sp_clue_distance_vs_layer", layer, min_distance);
282
283 // for each cluster
284 // total number of hits coming from electron, index = electron ID
285 std::vector<double> n_hits_from_electron;
286 n_hits_from_electron.resize(nbr_of_electrons + 2);
287 // total number of energy coming from electron, index = electron ID
288 std::vector<double> energy_from_electron;
289 energy_from_electron.resize(nbr_of_electrons + 2);
290 double energy_sum = 0.;
291 double n_sum = 0.;
292 ldmx_log(trace) << "Looping over hits in the cluster";
293 const auto& hit_ids = cl.getHitIDs();
294 for (const auto& id : hit_ids) {
295 // for each hit in cluster, find previously stored info
296 auto it = hit_info.find(id);
297 if (it != hit_info.end()) {
298 auto t = it->second;
299 // origin electron ID (or 0 for mixed)
300 auto id_electron = t.first;
301 // energy vector
302 auto energies = t.second;
303 // increment number of hits coming from this electron
304 n_hits_from_electron[id_electron]++;
305 n_sum++;
306
307 double hit_energy_sum = 0.;
308 for (int i = 1; i < nbr_of_electrons + 1; i++) {
309 // loop through energy vector
310 if (energies[i] > 0.) {
311 energy_sum += energies[i];
312 // add energy from electron i in this hit to total energy from
313 // electron i in cluster
314 energy_from_electron[i] += energies[i];
315 }
316 }
317 // if mixed hit, add the total energy of this hit to mixed hit energy
318 // counter
319 if (id_electron == 0) energy_from_electron[0] += hit_energy_sum;
320 energy_sum += hit_energy_sum;
321
322 clustered_hits++;
323 } // end if hit info found
324 } // end loop on the hit IDs in the cluster
325
326 if (energy_sum > 0) {
327 // get largest energy contribution
328 double max_energy_contribution = *max_element(
329 energy_from_electron.begin(), energy_from_electron.end());
330 std::string to_log;
331 for (auto nb : energy_from_electron)
332 to_log.append(std::to_string(nb) + " ");
333 ldmx_log(debug) << "Energies vector is " << to_log;
334
335 // energy purity = largest contribution / all energy
336 histograms_.fill("energy_percentage",
337 100. * (max_energy_contribution / energy_sum));
338 if (energy_from_electron[0] > 0.)
339 histograms_.fill("mixed_hit_energy",
340 100. * (energy_from_electron[0] / energy_sum));
341
342 histograms_.fill("total_energy_vs_hits", energy_sum,
343 cl.getHitIDs().size());
344 histograms_.fill("total_energy_vs_purity", energy_sum,
345 100. * (max_energy_contribution / energy_sum));
346
347 if (nbr_of_electrons == 2) {
348 histograms_.fill("sp_ele_distance_vs_purity", sp_ele_dist,
349 100. * (max_energy_contribution / energy_sum));
350 }
351 }
352 if (n_sum > 0) {
353 double n_max = *max_element(n_hits_from_electron.begin(),
354 n_hits_from_electron.end());
355 histograms_.fill("same_ancestor", 100. * (n_max / n_sum));
356 }
357 // find the main contributor
358 auto elt = distance(
359 energy_from_electron.begin(),
360 max_element(energy_from_electron.begin(), energy_from_electron.end()));
361 ldmx_log(debug) << "Found that the maximum contributing trackID is " << elt;
362 delta_energy[elt] = cl.getEnergy() - true_energy[elt];
363 // delta_energy[2]=e[2]-true_energy[2];
364 histograms_.fill("cluster_RMSX", cl.getRMSX());
365 } // end loop on the clusters
366 std::string more_log;
367 for (auto nb : tot_origin_edep) more_log.append(std::to_string(nb) + " ");
368 ldmx_log(debug) << "Edep per ancestor in event is " << more_log;
369 ldmx_log(debug) << "Total energy deposited in event: " << tot_event_energy;
370
371 histograms_.fill("dE_cl2_vs_cl1", delta_energy[1], delta_energy[2]);
372 histograms_.fill("unclustered_hits", (ecal_rec_hits.size() - clustered_hits));
373 histograms_.fill("total_rechits_in_event", ecal_rec_hits.size());
375 "unclustered_hits_percentage",
376 100. * (ecal_rec_hits.size() - clustered_hits) / ecal_rec_hits.size());
377
378 if (inverse_skim_) {
379 // inverse operation: drop events with enough clusters
380 if (n_ecal_clusters > n_ecal_clusters_min_) {
382 } else {
384 }
385 } else {
386 // normal operation: keep events with enough clusters
387 if (n_ecal_clusters > n_ecal_clusters_min_) {
389 } else {
391 }
392 }
393}
394
395} // namespace dqm
396
Analysis of cluster performance.
Class that stores cluster information from the ECal.
#define DECLARE_ANALYZER(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
Class which stores simulated calorimeter hit information.
Class which encapsulates information from a hit in a simulated tracking detector.
int nbr_of_electrons_
What is the number of electrons in the event?
bool use_simulated_electron_number_
Use the number of simulated electrons instead of the number of determined by the TS track counting.
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.
void analyze(const framework::Event &event) override
Process the event and make histograms or summaries.
HistogramPool histograms_
helper object for making and filling histograms
void setStorageHint(framework::StorageControl::Hint hint)
Mark the current event as having the given storage control hint from this module_.
Implements an event buffer system for storing event data.
Definition Event.h:40
void fill(const std::string &name, const T &val)
Fill a 1D histogram.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Stores cluster information from the ECal.
Definition EcalCluster.h:20
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
Stores simulated calorimeter hit information.
Implements detector ids for special simulation-derived hits like scoring planes.
int plane() const
Get the value of the plane field from the ID, if it is a scoring plane.
Represents a simulated tracker hit in the simulation.
constexpr StorageControl::Hint HINT_SHOULD_DROP
storage control hint alias for backwards compatibility
constexpr StorageControl::Hint HINT_SHOULD_KEEP
storage control hint alias for backwards compatibility