LDMX Software
CLUE.cxx
1
2#include "Ecal/CLUE.h"
3
4#include <algorithm>
5#include <cmath>
6#include <iomanip>
7#include <map>
8#include <set>
9#include <stack>
10
11#include "DetDescr/EcalID.h"
12
13namespace ecal {
14
21template <typename T>
22T CLUE::dist(T x1, T y1, T x2, T y2) {
23 auto delta_x = x1 - x2;
24 auto delta_y = y1 - y2;
25 auto r_square = delta_x * delta_x + delta_y * delta_y;
26 return std::sqrt(r_square);
27}
28
29// 3D version, overloaded
30template <typename T>
31T CLUE::dist(T x1, T y1, T z1, T x2, T y2, T z2) {
32 auto delta_x = x1 - x2;
33 auto delta_y = y1 - y2;
34 auto delta_z = z1 - z2;
35 auto r_square = delta_x * delta_x + delta_y * delta_y + delta_z * delta_z;
36 return std::sqrt(r_square);
37}
38
39/* Old code, idea was to do electron reclustering based on first layer
40 centroids' distances to each other I.e. if electrons are close together =>
41 likely merged => recluster Did not quite work and I don't remember the idea
42 anymore but leaving the code here for inspo */
43
44void CLUE::electronSeparation(std::vector<ldmx::EcalHit> hits) {
45 std::vector<double> layer_thickness = {2., 3.5, 5.3, 5.3, 5.3, 5.3,
46 5.3, 5.3, 5.3, 5.3, 5.3, 10.5,
47 10.5, 10.5, 10.5, 10.5};
48 double air = 10.;
49 // sort hits in z
50 std::sort(hits.begin(), hits.end(),
51 [](const ldmx::EcalHit& a, const ldmx::EcalHit& b) {
52 return a.getZPos() < b.getZPos();
53 });
54
55 std::vector<ldmx::EcalHit> first_layers;
56 std::vector<IntermediateCluster> first_layer_clusters;
57 int layer_tag = 0;
58 double layer_z = hits[0].getZPos();
59 for (const auto& hit : hits) {
60 if (hit.getZPos() > layer_z + layer_thickness[layer_tag] + air) {
61 layer_tag++;
62 // if (layerTag > limit) break;
63 break;
64 }
65 first_layers.push_back(hit);
66 IntermediateCluster cluster(hit);
67 cluster.setLayer(layer_tag);
68 first_layer_clusters.push_back(cluster);
69 }
70 bool merge = false;
71 do {
72 merge = false;
73 for (int i = 0; i < first_layer_clusters.size(); i++) {
74 if (first_layer_clusters[i].empty()) continue;
75 // if (firstLayerClusters[i].energy() >= seedThreshold_) {
76 for (int j = i + 1; j < first_layer_clusters.size(); j++) {
77 if (first_layer_clusters[j].empty()) continue;
78 if (dist(first_layer_clusters[i].centroidX(),
79 first_layer_clusters[i].centroidY(),
80 first_layer_clusters[j].centroidX(),
81 first_layer_clusters[j].centroidY()) < 8.) {
82 first_layer_clusters[i].add(first_layer_clusters[j]);
83 first_layer_clusters[j].clear();
84 merge = true;
85 }
86 }
87 // } else break;
88 }
89 } while (merge);
90 ldmx_log(trace) << "--- ELECTRON SEPARATION ---";
91 for (int i = 0; i < first_layer_clusters.size(); i++) {
92 if (first_layer_clusters[i].empty()) continue;
93 ldmx_log(trace) << " Cluster " << i
94 << " x: " << first_layer_clusters[i].centroidX()
95 << " y: " << first_layer_clusters[i].centroidY();
96 for (int j = i + 1; j < first_layer_clusters.size(); j++) {
97 if (first_layer_clusters[j].empty()) continue;
98 auto d = dist(first_layer_clusters[i].centroidX(),
99 first_layer_clusters[i].centroidY(),
100 first_layer_clusters[j].centroidX(),
101 first_layer_clusters[j].centroidY());
102 ldmx_log(trace) << "Dist to cluster " << j << ": " << d;
103 }
104 }
105}
106
107// Function to create layers from hits based on their layer number
108std::vector<std::vector<const ldmx::EcalHit*>> CLUE::createLayers(
109 const std::vector<const ldmx::EcalHit*>& hits) {
110 ldmx_log(trace) << "--- LAYER CREATION ---";
111 ldmx_log(trace) << "Number of layers: " << nbr_of_layers_;
112
113 // vector of layers, each layer having a vector of hits
114 // initialize with nbr_of_layers_ empty vectors
115 std::vector<std::vector<const ldmx::EcalHit*>> hits_per_layer(nbr_of_layers_);
116
117 // Track highest energy per layer for proper rho_c calculation
118 std::vector<double> layer_max_energies(nbr_of_layers_, 0.0);
119
120 // Clear any existing layer_rho_c_ values
121 layer_rho_c_.clear();
122
123 // This is rather ad-hoc, but this way it's still tunable via rhoc_ parameter
124 double rhoc_factor = rhoc_ / 250.;
125
126 for (const auto& hit : hits) {
127 ldmx::EcalID ecal_id(hit->getID());
128 // layer number from EcalID, starting at 0
129 int layer = ecal_id.layer();
130
131 if (layer >= nbr_of_layers_) {
132 ldmx_log(trace) << "Skipping hit in layer " << layer
133 << " (beyond nbr_of_layers_ = " << nbr_of_layers_ << ")";
134 continue;
135 }
136
137 // Track the highest energy in this specific layer
138 if (hit->getEnergy() > layer_max_energies[layer]) {
139 layer_max_energies[layer] = hit->getEnergy();
140 }
141
142 ldmx_log(trace) << " Adding hit with energy " << hit->getEnergy()
143 << " to layer " << layer;
144 hits_per_layer[layer].push_back(hit);
145 } // end of loop over hits
146
147 // Calculate rhoc for each layer based on that layer's maximum energy
148 for (int i = 0; i < nbr_of_layers_; i++) {
149 double rho_c = layer_max_energies[i] / rhoc_factor;
150 layer_rho_c_.push_back(rho_c);
151 ldmx_log(trace) << "Layer " << i << " has " << hits_per_layer[i].size()
152 << " hits, max energy " << layer_max_energies[i]
153 << ", rho_c " << rho_c;
154 }
155
156 ldmx_log(trace) << "Created " << hits_per_layer.size() << " layers";
157 return hits_per_layer;
158} // end of createLayers
159
160float CLUE::roundToDecimal(float x_, int num_decimal_precision_digits) {
161 float power_of_10 = std::pow(10, num_decimal_precision_digits);
162 return std::round(x_ * power_of_10) / power_of_10;
163}
164
165std::vector<std::shared_ptr<CLUE::Density>> CLUE::setup(
166 const std::vector<const ldmx::EcalHit*>& hits) {
167 std::vector<std::shared_ptr<Density>> densities;
168 std::map<std::pair<float, float>, std::shared_ptr<Density>> density_map;
169 event_centroid_ = IntermediateCluster();
170 ldmx_log(trace) << "--- SETUP ---";
171 ldmx_log(trace) << "Building densities";
172 for (const auto& hit : hits) {
173 // collapse z dimension
174 float x = roundToDecimal(hit->getXPos(), 4);
175 float y = roundToDecimal(hit->getYPos(), 4);
176 float z = roundToDecimal(hit->getZPos(), 4);
177 ldmx_log(trace) << " New hit { x: " << x << " y: " << y << "}"
178 << " (and z: " << z << ")";
179 std::pair<float, float> coords;
180 if (dc_ != 0 && nbr_of_layers_ > 1) {
181 // if more than one layer, divide hit into densities with side dc
182 double i = std::ceil(std::abs(x) / dc_);
183 double j = std::ceil(std::abs(y) / dc_);
184 if (x < 0) {
185 i = -i;
186 x = (i + 0.5) * dc_;
187 } else {
188 x = (i - 0.5) * dc_;
189 }
190 if (y < 0) {
191 j = -j;
192 y = (j + 0.5) * dc_;
193 } else {
194 y = (j - 0.5) * dc_;
195 }
196 coords = {i, j};
197 ldmx_log(trace) << " Index " << i << ", " << j << "; x: " << x
198 << " y: " << y;
199 } else {
200 // if just one layer, have all densities with the same x,y be in same
201 // density
202 coords = {x, y};
203 }
204
205 if (density_map.find(coords) == density_map.end()) {
206 density_map.emplace(coords, std::make_shared<CLUE::Density>(x, y));
207 ldmx_log(trace) << " * New density created";
208 } else {
209 ldmx_log(trace) << " --> Found density with x: "
210 << density_map[coords]->x_
211 << " y: " << density_map[coords]->y_;
212 }
213 density_map[coords]->hits_.push_back(hit);
214 density_map[coords]->total_energy_ += hit->getEnergy();
215 density_map[coords]->z_ += hit->getZPos();
216
217 event_centroid_.add(hit);
218 } // end of loop over hits
219
220 densities.reserve(density_map.size());
221 for (const auto& entry : density_map) {
222 densities.push_back(std::move(entry.second));
223 }
224 // sort according to energy
225 std::sort(densities.begin(), densities.end(),
226 [](const std::shared_ptr<CLUE::Density>& a,
227 const std::shared_ptr<CLUE::Density>& b) {
228 return a->total_energy_ > b->total_energy_;
229 });
230
231 ldmx_log(trace) << "Decide parents";
232
233 // decide delta_ and follower_of_
234 for (int i = 0; i < densities.size(); i++) {
235 densities[i]->index_ = i;
236 // avg z position
237 densities[i]->z_ = densities[i]->z_ / densities[i]->hits_.size();
238 ldmx_log(trace) << " Index: " << i << "; x: " << densities[i]->x_
239 << "; y: " << densities[i]->y_
240 << "; Energy: " << densities[i]->total_energy_;
241 // loop through all higher energy densities
242 for (int j = 0; j < i; j++) {
243 float distance_2d = dist(densities[i]->x_, densities[i]->y_,
244 densities[j]->x_, densities[j]->y_);
245 // condition energyJ > energyI but this should be baked in as we sorted
246 // according to energy
247 if ((distance_2d < dm_) && (distance_2d < densities[i]->delta_)) {
248 densities[i]->delta_ = distance_2d;
249 densities[i]->follower_of_ = j;
250 ldmx_log(trace) << " New parent, index " << j
251 << "; delta_2d: " << std::setprecision(4)
252 << distance_2d;
253 }
254 }
255 }
256 return densities;
257} // end of setup
258
259// connecting_layers marks if we're currently doing 3D clustering (i.e.
260// connecting seeds between layers) otherwise, layer_index tells us which layer
261// number we're working on
262std::vector<std::vector<const ldmx::EcalHit*>> CLUE::clustering(
263 std::vector<std::shared_ptr<CLUE::Density>>& densities,
264 bool connecting_layers, int layer_index) {
265 ldmx_log(trace) << "--- CLUSTERING ---";
266 ldmx_log(trace) << "Number of densities: " << densities.size()
267 << "; connecting_layers: " << connecting_layers
268 << "; layer_index: " << layer_index;
269 if (!connecting_layers && nbr_of_layers_ > 1) {
270 // if layerwise clustering override rhoc_ and deltac_ with per-layer values
271 rhoc_ = layer_rho_c_[layer_index];
272 ldmx_log(trace) << "Setting rho_c on layer " << layer_index << " to "
273 << rhoc_;
274 if (layer_index * 2 - 1 < radius_.size()) {
275 deltac_ = radius_[layer_index * 2 - 1];
276 ldmx_log(trace) << "Setting delta_c on layer " << layer_index << " to "
277 << deltac_;
278 }
279 } else if (connecting_layers) {
280 // if doing 3D clustering, override deltac_ and rhoc_ with other values
281 // NOT IMPLEMENTED YET
282 // if currently doing 3D clustering
283 // IF 3D clustering
284 // deltao_ = 200.;
285 // deltac_ = 100.;
286 // rhoc_ = 1000.;
287 }
288
289 bool energy_overload = false;
290 double max_energy = 10000.;
291 clustering_loops_ = 0;
292 double delta_c_mod = deltac_;
293 double centroid_radius = 10.;
294
295 // stores seeds of this layer
296 std::vector<std::shared_ptr<Density>>& layer_seeds = seeds_[layer_index];
297
298 // stores hits in cluster
299 std::vector<std::vector<const ldmx::EcalHit*>> clusters;
300 // keeps track of which densities have merged; only used if reclustering
301 std::vector<bool> merged_densities; // index_= cluster id
302 merged_densities.resize(densities.size());
303 // keeps track of cluster energies
304 std::vector<double> cluster_energies;
305 do {
306 // while no cluster has merged
307 if (energy_overload) {
308 // makes delta_c smaller if clusters have merged
309 delta_c_mod = delta_c_mod / 1.1;
310 ldmx_log(trace) << "Energy overload, new delta_cmod: " << delta_c_mod;
311 energy_overload = false;
312 }
313
314 clustering_loops_++;
315 ldmx_log(trace) << "Clustering loop " << clustering_loops_;
316
317 // cluster index
318 int k = 0;
319
320 layer_seeds.clear();
321 layer_seeds.reserve(densities.size());
322 clusters.clear();
323 clusters.reserve(densities.size());
324 cluster_energies.clear();
325 cluster_energies.reserve(densities.size());
326
327 std::stack<int> cluster_stack;
328 // stores followers of densities at corr index_
329 std::vector<std::vector<int>> followers;
330 followers.resize(densities.size());
331
332 // Mark as seed, follower, or outlier
333 for (auto& density : densities) {
334 // funky line to generalize this function for both 2D and 3D case
335 ldmx_log(trace) << " Index: " << density->index_
336 << "; x: " << density->x_ << "; y: " << density->y_
337 << "; Energy: " << density->total_energy_
338 << " Parent ID: " << density->follower_of_
339 << "; Delta: " << density->delta_;
340
341 bool is_seed;
342 if (delta_c_mod != deltac_ && density->cluster_id_ >= 0 &&
343 merged_densities[density->cluster_id_] &&
344 dist(density->x_, density->y_, event_centroid_.centroidX(),
345 event_centroid_.centroidY()) < centroid_radius) {
346 // if energy has been overloaded and this density belongs to cluster
347 // that was overloaded and this density is close enough to event
348 // centroid use modded delta c
349 is_seed =
350 density->total_energy_ > rhoc_ && density->delta_ > delta_c_mod;
351 } else {
352 is_seed = density->total_energy_ > rhoc_ && density->delta_ > deltac_;
353 if (is_seed) {
354 ldmx_log(trace) << " Distance to event centroid: "
355 << dist(density->x_, density->y_,
356 event_centroid_.centroidX(),
357 event_centroid_.centroidY());
358 }
359 }
360 bool is_outlier =
361 (density->total_energy_ < rhoc_) && (density->delta_ > deltao_);
362 density->cluster_id_ = -1;
363 if (is_seed) {
364 ldmx_log(trace) << " This is a Seed";
365 ldmx_log(trace) << " Distance to centroid: "
366 << dist(density->x_, density->y_,
367 event_centroid_.centroidX(),
368 event_centroid_.centroidY())
369 << "; with delta " << density->delta_;
370 ldmx_log(trace) << " Setting cluster ID to " << k;
371 density->cluster_id_ = k;
372 k++;
373 // get the index of the seed density
374 cluster_stack.push(density->index_);
375 clusters.push_back(density->hits_);
376 cluster_energies.push_back(density->total_energy_);
377 layer_seeds.push_back(density);
378 } else if (!is_outlier) {
379 ldmx_log(trace) << " This is a Follower";
380 int& parent_index = density->follower_of_;
381 if (parent_index != -1)
382 followers[parent_index].push_back(density->index_);
383 else
384 ldmx_log(error)
385 << " Somehow found a follower with parent index -1: id = "
386 << density->index_;
387 } else {
388 ldmx_log(trace) << " This is an Outlier";
389 }
390 }
391
392 merged_densities.clear();
393 merged_densities.resize(densities.size());
394
395 // Go through all seeds and add followers, then follower's followers, etc.
396 while (cluster_stack.size() > 0) {
397 auto& d = densities[cluster_stack.top()];
398 cluster_stack.pop();
399 auto& cid = d->cluster_id_;
400 // for indices of followers of dp
401 for (const auto follower_index : followers[d->index_]) {
402 auto& follower = densities[follower_index];
403 // Set cluster index of the follower to the cluster index of `d`
404 follower->cluster_id_ = cid;
405 cluster_energies[cid] += follower->total_energy_;
406
407 if (reclustering_ && cluster_energies[cid] > max_energy &&
408 delta_c_mod > 0.5 && clustering_loops_ < 100) {
409 // If reclustering is on and cluster energy is too high,
410 // delta_c_mod is not too low, and we haven't tried for too long
411 merged_densities[cid] = true;
412 if (!energy_overload && clustering_loops_ == 99) {
413 ldmx_log(warn) << "Merging clusters, max cluster loops hit";
414 }
415 energy_overload = true;
416 if (clustering_loops_ != 1) {
417 goto endwhile; // Don't break on the first loop to save the initial
418 // cluster number
419 }
420 }
421
422 clusters[cid].insert(std::end(clusters[cid]),
423 std::begin(follower->hits_),
424 std::end(follower->hits_));
425 // Add follower to the stack so its followers can also get the correct
426 // cluster index
427 cluster_stack.push(follower_index);
428 }
429 }
430 // for first clusteringloop, we want to save number of clusters before
431 // reclustering
432 if (clustering_loops_ == 1 && energy_overload)
433 initial_cluster_nbr_ = clusters.size();
434 endwhile:;
435 } while (energy_overload);
436 // if we have more than one layer and we are not currently doing CLUE3D
437 if (!connecting_layers && nbr_of_layers_ > 1) {
438 // Overwrite seed densities' properties with cluster properties
439 // Might be cleaner to just create new densities for cluster seeds
440 for (auto& seed : layer_seeds) {
441 seed->delta_ = std::numeric_limits<float>::max();
442 seed->hits_ = clusters[seed->cluster_id_];
443 seed->total_energy_ = cluster_energies[seed->cluster_id_];
444 seed->index_ = seed_index_;
445 seed_index_++;
446 }
447 // Sort seeds in layer based on energy
448 std::sort(layer_seeds.begin(), layer_seeds.end(),
449 [](const std::shared_ptr<Density>& a,
450 const std::shared_ptr<Density>& b) {
451 return a->total_energy_ > b->total_energy_;
452 });
453 }
454 return clusters;
455} // end of clustering
456
457// Function to connect seeds between layers to form 3D clusters
458// ONLY used in CLUE3D
459std::vector<std::shared_ptr<CLUE::Density>> CLUE::setupForClue3D() {
460 ldmx_log(trace) << "--- LAYER SETUP ---";
461 std::vector<std::shared_ptr<CLUE::Density>> densities;
462 layer_rho_c_.clear();
463 for (int layer = 0; layer < nbr_of_layers_; layer++) {
464 ldmx_log(trace) << " LAYER " << layer << " with " << seeds_[layer].size()
465 << " seeds";
466 auto& seeds_in_current_layer = seeds_[layer];
467 double highest_energy = 0.;
468 for (const auto& current_seed : seeds_in_current_layer) {
469 // for each seed in layer
470 current_seed->layer_ = layer;
471 if (current_seed->total_energy_ > highest_energy)
472 highest_energy = current_seed->total_energy_;
473 ldmx_log(trace) << " Density with index " << current_seed->index_
474 << ", energy: " << current_seed->total_energy_
475 << " position (x,y)= {" << current_seed->x_ << ","
476 << current_seed->y_ << ")";
477 int depth = 1;
478 // decide delta_ and followerof from seeds in previous and next layer_
479 // do {
480 // depth++;
481 if ((layer - depth >= 0) && (layer - depth < seeds_.size())) {
482 ldmx_log(trace) << " Looking at pre-layer: " << layer - depth;
483 // look at previous layer
484 ldmx_log(trace) << " In previous layer... ";
485 auto& previous_layer = seeds_[layer - depth];
486 ldmx_log(trace) << " Got " << previous_layer.size() << " seeds";
487 for (const auto& prev_seed : previous_layer) {
488 // for each seed in previous layer
489 auto distance_2d_prev = dist(current_seed->x_, current_seed->y_,
490 prev_seed->x_, prev_seed->y_);
491 auto dz_prev = std::abs(current_seed->z_ - prev_seed->z_);
492 ldmx_log(trace) << " DeltaXY to index " << prev_seed->index_
493 << ": " << std::setprecision(4) << distance_2d_prev;
494 ldmx_log(trace) << " DeltaZ to index " << prev_seed->index_
495 << ": " << std::setprecision(4) << dz_prev;
496 if (prev_seed->total_energy_ > current_seed->total_energy_ &&
497 distance_2d_prev < current_seed->delta_ &&
498 dz_prev < current_seed->z_delta_) {
499 ldmx_log(trace) << " New parent: index " << prev_seed->index_
500 << " on layer " << layer - depth << "; energy "
501 << prev_seed->total_energy_;
502 ldmx_log(trace) << " New 2D distance to prev-layer: "
503 << std::setprecision(4) << distance_2d_prev;
504 ldmx_log(trace)
505 << " New delta Z: " << std::setprecision(4) << dz_prev;
506 current_seed->delta_ = distance_2d_prev;
507 current_seed->z_delta_ = dz_prev;
508 current_seed->follower_of_ = prev_seed->index_;
509 } else if (prev_seed->total_energy_ < current_seed->total_energy_) {
510 ldmx_log(trace) << " Breaking on index " << prev_seed->index_
511 << " with energy " << prev_seed->total_energy_;
512 break;
513 }
514 }
515 }
516
517 if (layer + depth < nbr_of_layers_ && layer + depth < seeds_.size()) {
518 ldmx_log(trace) << " Looking at post-layer: " << layer + depth;
519 auto& next_layer = seeds_[layer + depth];
520 ldmx_log(trace) << " Got " << next_layer.size() << " seeds";
521 for (const auto& next_seed : next_layer) {
522 auto distance_2d_next = dist(current_seed->x_, current_seed->y_,
523 next_seed->x_, next_seed->y_);
524 auto dz_next = std::abs(current_seed->z_ - next_seed->z_);
525 ldmx_log(trace) << " DeltaXY to index " << next_seed->index_
526 << ": " << std::setprecision(4) << distance_2d_next;
527 ldmx_log(trace) << " DeltaZ to index_" << next_seed->index_
528 << ": " << std::setprecision(4) << dz_next;
529 if (next_seed->total_energy_ > current_seed->total_energy_ &&
530 distance_2d_next < current_seed->delta_ &&
531 dz_next < current_seed->z_delta_) {
532 ldmx_log(trace) << " New parent: index_" << next_seed->index_
533 << " on layer " << layer + depth << "; energy "
534 << next_seed->total_energy_;
535 ldmx_log(trace) << " New 2D distance to next-layer: "
536 << std::setprecision(4) << distance_2d_next;
537 ldmx_log(trace)
538 << " New delta_Z: " << std::setprecision(4) << dz_next;
539 current_seed->delta_ = distance_2d_next;
540 current_seed->z_delta_ = dz_next;
541 current_seed->follower_of_ = next_seed->index_;
542 } else if (next_seed->total_energy_ < current_seed->total_energy_) {
543 ldmx_log(trace) << " Breaking on index_" << next_seed->index_
544 << " with energy " << next_seed->total_energy_;
545 break;
546 }
547 } // end loop on seeds in next layer
548 } // end of looking at next layer
549 // } while (currentLayer[i]->layerFollowerOf == -1 && (layer - depth >=
550 // 0 || layer + depth < nbr_of_layers_));
551 ldmx_log(trace) << " Done setting parents.";
552 densities.push_back(current_seed);
553 }
554 // TODO: This 2 needs to be configurable
555 layer_rho_c_.push_back(highest_energy / 2);
556 } // end loop over layers
557 return densities;
558} // end of setupForClue3D
559
560void CLUE::convertToIntermediateClusters(
561 std::vector<std::vector<const ldmx::EcalHit*>>& clusters) {
562 // Convert to workingecalclusters to ensure compatibility with
563 // EcalClusterProducer
564 for (const auto& cluster : clusters) {
565 IntermediateCluster intermediate_cluster{},
566 intermediate_cluster_first_layer{};
567
568 for (const auto& hit : cluster) {
569 intermediate_cluster.add(hit);
570 // if hit is in first layer, add to first layer cluster
571 ldmx::EcalID ecal_id(hit->getID());
572 auto layer = ecal_id.layer();
573 intermediate_cluster.setLayer(layer);
574 if (layer == 0) {
575 intermediate_cluster_first_layer.add(hit);
576 intermediate_cluster_first_layer.setLayer(layer);
577 }
578 }
579 final_clusters_.push_back(intermediate_cluster);
580 first_layer_centroids_.push_back(intermediate_cluster_first_layer);
581 auto cent_x = intermediate_cluster.centroidX();
582 auto cent_y = intermediate_cluster.centroidY();
583 auto event_cent_x = event_centroid_.centroidX();
584 auto event_cent_y = event_centroid_.centroidY();
585 const auto& distance = dist(cent_x, cent_y, event_cent_x, event_cent_y);
586 centroid_distances_.push_back(distance);
587 }
588}
589
590void CLUE::cluster(const std::vector<ldmx::EcalHit>& unsorted_hits, double dc,
591 double rc, double delta_c, double delta_o, int nbr_of_layers,
592 bool reclustering) {
593 ldmx_log(info) << "Starting CLUE clustering with parameters:" << "dc " << dc
594 << ", rc " << rc << ", delta_c " << delta_c << ", delta_o "
595 << delta_o << ", nbr_of_layers " << nbr_of_layers
596 << ", reclustering " << reclustering;
597 // cutoff distance for local density
598 dc_ = dc;
599 // min density to promote as seed/max density to demote as outlier
600 rhoc_ = rc;
601 // min separation distance for seeds
602 deltac_ = delta_c;
603 // min separation distance for outliers
604 deltao_ = delta_o;
605 // max distance to parent for both seeds and followers
606 dm_ = std::max(delta_c, delta_o);
607
608 // Recluster merged clusters or not
609 reclustering_ = reclustering;
610 nbr_of_layers_ = nbr_of_layers;
611
612 if (nbr_of_layers_ < 1) {
613 // anything below 1 => include all layers
614 nbr_of_layers_ = max_layers_;
615 } else if (nbr_of_layers_ > max_layers_) {
616 ldmx_log(warn) << "nbr_of_layers_ " << nbr_of_layers_
617 << " exceeds max layers " << max_layers_
618 << ", setting to max layers";
619 nbr_of_layers_ = max_layers_;
620 }
621
622 // first copy *addresses* so we are only ever passing around pointers
623 std::vector<const ldmx::EcalHit*> hits;
624 hits.reserve(unsorted_hits.size());
625 ldmx_log(debug) << "Clustering " << unsorted_hits.size() << " hits";
626 for (const auto& unsorted_hit : unsorted_hits) {
627 hits.push_back(&unsorted_hit);
628 }
629 // sort hits by Z position
630 ldmx_log(debug) << "Sorting hits by Z position";
631 std::sort(hits.begin(), hits.end(),
632 [](const ldmx::EcalHit* a, const ldmx::EcalHit* b) {
633 return a->getZPos() < b->getZPos();
634 });
635
636 seeds_.resize(nbr_of_layers_);
637
638 if (nbr_of_layers_ > 1) {
639 ldmx_log(debug) << "Creating layers";
640 // returns a vector of layers, with each layer having a vector of hits
641 const auto layers = createLayers(hits);
642 ldmx_log(debug) << "Doing layer-wise clustering on " << layers.size()
643 << " layers";
644 for (int i = 0; i < layers.size(); i++) {
645 ldmx_log(trace) << "--- LAYER " << i + 1 << " ---";
646 auto densities = setup(layers[i]);
647 auto clusters = clustering(densities, false, i);
648 convertToIntermediateClusters(clusters);
649 // clustering without 3D
650 }
651 // Below for CLUE3D, comment for just layer clustering
652 // This does not work properly yet
653 // auto densities = setupForClue3D();
654 // auto clusters = clustering(densities, true);
655 // convertToIntermediateClusters(clusters);
656 } else {
657 ldmx_log(debug) << "Only one layer, doing 2D clustering";
658 auto densities = setup(hits);
659 auto clusters = clustering(densities, false);
660 convertToIntermediateClusters(clusters);
661 }
662}
663
664} // namespace ecal
A version of CLUE (CMS) for clustering in ECal.
Class that defines an ECal detector ID with a cell number.
recon::WorkingCluster< ldmx::EcalHit > IntermediateCluster
Type alias for WorkingCluster specialized for EcalHit.
T dist(T x1, T y1, T x2, T y2)
Euclidean distance between two points.
Definition CLUE.cxx:22
Stores reconstructed hit information from the ECAL.
Definition EcalHit.h:19
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
double centroidY() const
Get the centroid Y position (energy-weighted).
double centroidX() const
Get the centroid X position (energy-weighted).
void add(const HitType *hit)
Add a hit to the cluster using its stored position.