LDMX Software
EcalClusterAnalyzer.cxx
2
3namespace dqm {
4
7 ps.get<bool>("use_simulated_electron_number");
8 nbr_of_electrons_ = ps.get<int>("nbr_of_electrons");
9
10 ecal_sim_hit_coll_ = ps.get<std::string>("ecal_sim_hit_coll");
11 ecal_sim_hit_pass_ = ps.get<std::string>("ecal_sim_hit_pass");
12
13 rec_hit_coll_name_ = ps.get<std::string>("rec_hit_coll_name");
14 rec_hit_pass_name_ = ps.get<std::string>("rec_hit_pass_name");
15
16 cluster_coll_name_ = ps.get<std::string>("cluster_coll_name");
17 cluster_pass_name_ = ps.get<std::string>("cluster_pass_name");
18
19 ecal_sp_hits_passname_ = ps.get<std::string>("ecal_sp_hits_passname");
20 inverse_skim_ = ps.get<bool>("inverse_skim");
21 n_ecal_clusters_min_ = ps.get<int>("n_ecal_clusters_min");
22 return;
23}
24
26 const auto& ecal_rec_hits{event.getCollection<ldmx::EcalHit>(
27 rec_hit_coll_name_, rec_hit_pass_name_)};
28 const auto& ecal_sim_hits{event.getCollection<ldmx::SimCalorimeterHit>(
29 ecal_sim_hit_coll_, ecal_sim_hit_pass_)};
30 const auto& ecal_clusters{event.getCollection<ldmx::EcalCluster>(
31 cluster_coll_name_, cluster_pass_name_)};
32
33 // Determine the number of recoil electrons in the event
34 // By default from the TS track counting
35 int nbr_of_electrons{event.getElectronCount()};
36 // If configured to use the simulated electron number, use that instead
38 nbr_of_electrons = nbr_of_electrons_;
39 }
40
41 std::map<int, int> layer_cluster_count;
42 for (const auto& cluster : ecal_clusters) {
43 auto layer = cluster.getLayer();
44 layer_cluster_count[layer]++;
45 }
46
47 int total_clusters = 0;
48 for (const auto& [layer, count] : layer_cluster_count) {
49 total_clusters += count;
50 }
51
52 int n_ecal_clusters = 0;
53 if (layer_cluster_count.size() != 0) {
54 n_ecal_clusters = static_cast<int>(std::round(
55 static_cast<double>(total_clusters) / layer_cluster_count.size()));
56 }
57
58 ldmx_log(info) << "Avg number of clusters per layer: " << n_ecal_clusters;
59 // Fill histograms with the number of clusters
60 histograms_.fill("number_of_clusters", total_clusters);
61 histograms_.fill("number_of_clusters_per_layer", n_ecal_clusters);
62 histograms_.fill("number_of_clusters_first_layer", layer_cluster_count[0]);
63
64 // Fill simplied 3-bin histogram to check the prediction
65 if (n_ecal_clusters == nbr_of_electrons) {
66 // correct
67 histograms_.fill("correctly_predicted_events", 1);
68 } else if (n_ecal_clusters < nbr_of_electrons) {
69 // undercounting
70 histograms_.fill("correctly_predicted_events", 0);
71 } else if (n_ecal_clusters > nbr_of_electrons) {
72 // overcounting
73 histograms_.fill("correctly_predicted_events", 2);
74 }
75
76 std::unordered_map<int, std::pair<int, std::vector<double>>> hit_info;
77 hit_info.reserve(ecal_rec_hits.size());
78
79 // Determine the truth information for the recoil electron
80 std::vector<std::vector<float>> sp_electron_positions;
81 const auto& ecal_sp_hits{event.getCollection<ldmx::SimTrackerHit>(
82 "EcalScoringPlaneHits", ecal_sp_hits_passname_)};
83
84 std::vector<ldmx::SimTrackerHit> sorted_sp_hits = ecal_sp_hits;
85 std::sort(sorted_sp_hits.begin(), sorted_sp_hits.end(),
86 [](const ldmx::SimTrackerHit& a, const ldmx::SimTrackerHit& b) {
87 return a.getTrackID() < b.getTrackID();
88 });
89
90 ldmx_log(trace) << "Number of ECal Scoring Plane Hits: "
91 << sorted_sp_hits.size();
92
93 // Collect positions of all recoil electrons on the SP
94 // relying on the track ID to identify them
95 unsigned int n_filled = 0;
96 for (const ldmx::SimTrackerHit& sp_hit : sorted_sp_hits) {
97 if (sp_hit.getPdgID() != 11) continue;
98 if (sp_hit.getMomentum()[2] <= 0) continue;
99 ldmx::SimSpecialID hit_id(sp_hit.getID());
100 // Ecal scoring plane is plane 31
101 if (hit_id.plane() != 31) continue;
102 if (n_filled < nbr_of_electrons) {
103 ldmx_log(trace) << "\tSP Hit to be added with Track ID : "
104 << sp_hit.getTrackID() << ", SP Hit Position ("
105 << sp_hit.getPosition()[0] << ", "
106 << sp_hit.getPosition()[1] << ", "
107 << sp_hit.getPosition()[2] << ") mm";
108 sp_electron_positions.push_back(sp_hit.getPosition());
109 n_filled++;
110 }
111 }
112
113 ldmx_log(info) << "Number of ECal CLUE clusters: " << n_ecal_clusters
114 << ", TS counted electrons: " << nbr_of_electrons
115 << ", SP electrons: " << sp_electron_positions.size();
116
117 double sp_ele_dist{9999.};
118 if (nbr_of_electrons == 2 && sp_electron_positions.size() > 1) {
119 // Measures sp_ele_distance between two electrons in the ECal scoring plane
120 // TODO: generalize for n electrons
121 std::vector<float> pos1;
122 std::vector<float> pos2;
123 pos1 = sp_electron_positions[0];
124 pos2 = sp_electron_positions[1];
125 sp_ele_dist = std::sqrt((pos1[0] - pos2[0]) * (pos1[0] - pos2[0]) +
126 (pos1[1] - pos2[1]) * (pos1[1] - pos2[1]));
127
128 } // end block about the scoring plane hits
129
130 ldmx_log(trace) << "Distance between the two e- in the ECal scoring plane: "
131 << sp_ele_dist << " mm";
132
133 // Loop over the rechits and find the matching simhits
134 ldmx_log(trace) << "Loop over the rechits and find the matching simhits";
135 for (const auto& hit : ecal_rec_hits) {
136 auto it = std::find_if(
137 ecal_sim_hits.begin(), ecal_sim_hits.end(),
138 [&hit](const auto& sim_hit) { return sim_hit.getID() == hit.getID(); });
139 if (it != ecal_sim_hits.end()) {
140 // if found a simhit matching this rechit
141 int ancestor = 0;
142 int prev_ancestor = 0;
143 bool tagged = false;
144 int tag = 0;
145 std::vector<double> edep;
146 edep.resize(nbr_of_electrons + 1);
147 for (int i = 0; i < it->getNumberOfContribs(); i++) {
148 // for each contrib in this simhit
149 const auto& contrib = it->getContrib(i);
150 // get origin electron ID
151 ancestor = contrib.origin_id_;
152 // store energy from this contrib at index = origin electron ID
153 if (ancestor <= nbr_of_electrons) edep[ancestor] += contrib.edep_;
154 if (!tagged && i != 0 && prev_ancestor != ancestor) {
155 // if origin electron ID does not match previous origin electron ID
156 // this hit has contributions from several electrons, ie mixed case
157 tag = 0;
158 tagged = true;
159 }
160 prev_ancestor = ancestor;
161 }
162 if (!tagged) {
163 // if not tagged, hit was from a single electron
164 tag = prev_ancestor;
165 }
166 histograms_.fill("ancestors", tag);
167 hit_info.insert({hit.getID(), std::make_pair(tag, edep)});
168 } // end if simhit found
169 } // end loop on the rechits
170
171 // Loop over the clusters
172 int clustered_hits = 0;
173 ldmx_log(trace) << "Loop over the clusters, N = " << n_ecal_clusters;
174 for (const auto& cl : ecal_clusters) {
175 auto layer = cl.getLayer();
176 ldmx_log(trace) << "Cluster in layer " << layer
177 << ", energy: " << cl.getEnergy()
178 << ", number of hits: " << cl.getHitIDs().size();
179 auto cluster_centroid_x = cl.getCentroidX();
180 auto cluster_centroid_y = cl.getCentroidY();
181 auto cluster_rms_x = cl.getRMSX();
182 auto cluster_rms_y = cl.getRMSY();
183
184 // Find the closest sp_electron_positions to the cluster centroid
185 double min_distance = 9999.;
186 double sp_clue_x_residuals = 9999.;
187 double sp_clue_y_residuals = 9999.;
188 for (const auto& sp_pos : sp_electron_positions) {
189 double distance = std::sqrt(
190 (sp_pos[0] - cluster_centroid_x) * (sp_pos[0] - cluster_centroid_x) +
191 (sp_pos[1] - cluster_centroid_y) * (sp_pos[1] - cluster_centroid_y));
192 if (distance < min_distance) {
193 min_distance = distance;
194 sp_clue_x_residuals = sp_pos[0] - cluster_centroid_x;
195 sp_clue_y_residuals = sp_pos[1] - cluster_centroid_y;
196 }
197 } // end loop on the scoring plane electron positions
198 // Fill histogram with the distance to the closest scoring plane electron
199 ldmx_log(trace) << "\tCluster centroid: (" << cluster_centroid_x << " +/- "
200 << cluster_rms_x << ", " << cluster_centroid_y << " +/- "
201 << cluster_rms_y
202 << " mm; min distance to SP electron: " << min_distance
203 << " mm";
204 if (layer == 0) {
205 histograms_.fill("sp_clue_distance", min_distance);
206 histograms_.fill("sp_clue_x_residual", sp_clue_x_residuals);
207 histograms_.fill("sp_clue_y_residual", sp_clue_y_residuals);
208 }
209 histograms_.fill("sp_clue_distance_vs_layer", layer, min_distance);
210
211 // for each cluster
212 // total number of hits coming from electron, index = electron ID
213 std::vector<double> n_hits_from_electron;
214 n_hits_from_electron.resize(nbr_of_electrons + 1);
215 // total number of energy coming from electron, index = electron ID
216 std::vector<double> energy_from_electron;
217 energy_from_electron.resize(nbr_of_electrons + 1);
218 double energy_sum = 0.;
219 double n_sum = 0.;
220
221 const auto& hit_ids = cl.getHitIDs();
222 for (const auto& id : hit_ids) {
223 // for each hit in cluster, find previously stored info
224 auto it = hit_info.find(id);
225 if (it != hit_info.end()) {
226 auto t = it->second;
227 // origin electron ID (or 0 for mixed)
228 auto id_electron = t.first;
229 // energy vector
230 auto energies = t.second;
231 // increment number of hits coming from this electron
232 n_hits_from_electron[id_electron]++;
233 n_sum++;
234
235 double hit_energy_sum = 0.;
236 for (int i = 1; i < nbr_of_electrons + 1; i++) {
237 // loop through energy vector
238 if (energies[i] > 0.) {
239 energy_sum += energies[i];
240 // add energy from electron i in this hit to total energy from
241 // electron i in cluster
242 energy_from_electron[i] += energies[i];
243 }
244 }
245 // if mixed hit, add the total energy of this hit to mixed hit energy
246 // counter
247 if (id_electron == 0) energy_from_electron[0] += hit_energy_sum;
248 energy_sum += hit_energy_sum;
249
250 clustered_hits++;
251 } // end if hit info found
252 } // end loop on the hit IDs in the cluster
253
254 if (energy_sum > 0) {
255 // get largest energy contribution
256 double max_energy_contribution = *max_element(
257 energy_from_electron.begin(), energy_from_electron.end());
258 // energy purity = largest contribution / all energy
259 histograms_.fill("energy_percentage",
260 100. * (max_energy_contribution / energy_sum));
261 if (energy_from_electron[0] > 0.)
262 histograms_.fill("mixed_hit_energy",
263 100. * (energy_from_electron[0] / energy_sum));
264
265 histograms_.fill("total_energy_vs_hits", energy_sum,
266 cl.getHitIDs().size());
267 histograms_.fill("total_energy_vs_purity", energy_sum,
268 100. * (max_energy_contribution / energy_sum));
269
270 if (nbr_of_electrons == 2) {
271 histograms_.fill("sp_ele_distance_vs_purity", sp_ele_dist,
272 100. * (max_energy_contribution / energy_sum));
273 }
274 }
275 if (n_sum > 0) {
276 double n_max = *max_element(n_hits_from_electron.begin(),
277 n_hits_from_electron.end());
278 histograms_.fill("same_ancestor", 100. * (n_max / n_sum));
279 }
280 } // end loop on the clusters
281
282 histograms_.fill("clusterless_hits", (ecal_rec_hits.size() - clustered_hits));
283 histograms_.fill("total_rechits_in_event", ecal_rec_hits.size());
285 "clusterless_hits_percentage",
286 100. * (ecal_rec_hits.size() - clustered_hits) / ecal_rec_hits.size());
287
288 if (inverse_skim_) {
289 // inverse operation: drop events with enough clusters
290 if (n_ecal_clusters > n_ecal_clusters_min_) {
292 } else {
294 }
295 } else {
296 // normal operation: keep events with enough clusters
297 if (n_ecal_clusters > n_ecal_clusters_min_) {
299 } else {
301 }
302 }
303}
304
305} // namespace dqm
306
Analysis of cluster performance.
#define DECLARE_ANALYZER(CLASS)
Macro which allows the framework to construct an analyzer given its name during configuration.
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:42
void fill(const std::string &name, const T &val)
Fill a 1D histogram.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:29
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:78
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