LDMX Software
ecal::CLUE3D Class Reference

Classes

struct  Cluster
 
struct  LayerCluster
 
struct  Params
 

Public Member Functions

 CLUE3D (const Params &p)
 
std::vector< Cluster > cluster (const std::vector< ldmx::EcalHit > &hits)
 Run on all hits; returned hit indices refer to the input vector.
 
const std::vector< LayerCluster > & layerClusters () const
 
double lambda (int layer) const
 Profile slope used at a layer [mm].
 

Private Member Functions

void cluster2D (const std::vector< int > &layer_hits)
 
void cluster3D ()
 
void fitAxis (Cluster &c, const std::vector< int > &hit_idx, const std::vector< double > &frac) const
 

Private Attributes

Params p_ {}
 
const std::vector< ldmx::EcalHit > * hits_ {nullptr}
 
std::vector< int > layer_of_
 
std::vector< char > core_
 
std::vector< LayerCluster > lcs_
 

Detailed Description

Definition at line 18 of file CLUE3D.h.

Constructor & Destructor Documentation

◆ CLUE3D()

ecal::CLUE3D::CLUE3D ( const Params & p)
inlineexplicit

Definition at line 92 of file CLUE3D.h.

92: p_{p} {}

Member Function Documentation

◆ cluster()

std::vector< CLUE3D::Cluster > ecal::CLUE3D::cluster ( const std::vector< ldmx::EcalHit > & hits)

Run on all hits; returned hit indices refer to the input vector.

Definition at line 233 of file CLUE3D.cxx.

234 {
235 hits_ = &hits;
236 lcs_.clear();
237 layer_of_.assign(hits.size(), 0);
238 std::map<int, std::vector<int>> by_layer;
239 for (std::size_t i = 0; i < hits.size(); i++) {
240 if (hits[i].getEnergy() <= p_.hit_ecut_ || hits[i].getEnergy() <= 0)
241 continue;
242 layer_of_[i] = ldmx::EcalID(hits[i].getID()).layer();
243 by_layer[layer_of_[i]].push_back(i);
244 }
245 for (const auto& [layer, idx] : by_layer) cluster2D(idx);
246 cluster3D();
247
248 // hits of 3D linked layer clusters define the axes
249 core_.assign(hits.size(), 0);
250 for (const auto& lc : lcs_)
251 if (lc.cid_ >= 0)
252 for (int h : lc.hits_) core_[h] = 1;
253
254 // hit members of the 3D clusters
255 int ncl = 0;
256 for (const auto& lc : lcs_) ncl = std::max(ncl, lc.cid_ + 1);
257 std::vector<std::vector<int>> members(ncl);
258 for (const auto& lc : lcs_)
259 if (lc.cid_ >= 0)
260 members[lc.cid_].insert(members[lc.cid_].end(), lc.hits_.begin(),
261 lc.hits_.end());
262 auto fit_all = [&](std::vector<Cluster>& cls,
263 const std::vector<std::vector<int>>& mem) {
264 cls.assign(mem.size(), Cluster{});
265 for (std::size_t k = 0; k < mem.size(); k++)
266 fitAxis(cls[k], mem[k], std::vector<double>(mem[k].size(), 1.));
267 };
268 std::vector<Cluster> cls;
269 fit_all(cls, members);
270
271 // attach orphan layer clusters to the nearest axis, else keep alone
272 for (const auto& lc : lcs_) {
273 if (lc.cid_ >= 0) continue;
274 int best = -1;
275 double best_d = p_.attach_radius_;
276 for (std::size_t k = 0; k < cls.size(); k++) {
277 double d = dist(lc.x_, lc.y_, cls[k].axisX(lc.z_), cls[k].axisY(lc.z_));
278 if (d < best_d) {
279 best_d = d;
280 best = k;
281 }
282 }
283 if (best >= 0) {
284 members[best].insert(members[best].end(), lc.hits_.begin(),
285 lc.hits_.end());
286 } else {
287 members.push_back(lc.hits_);
288 for (int h : lc.hits_) core_[h] = 1;
289 }
290 }
291 fit_all(cls, members);
292
293 // merge satellites into a much larger nearby cluster
294 {
295 std::vector<int> order(cls.size());
296 std::iota(order.begin(), order.end(), 0);
297 std::sort(order.begin(), order.end(),
298 [&](int a, int b) { return cls[a].e_ < cls[b].e_; });
299 std::vector<double> esum(cls.size());
300 std::vector<int> nlay(cls.size());
301 for (std::size_t k = 0; k < cls.size(); k++) {
302 esum[k] = cls[k].e_;
303 std::vector<int> ls;
304 for (int h : members[k]) ls.push_back(layer_of_[h]);
305 std::sort(ls.begin(), ls.end());
306 nlay[k] = std::unique(ls.begin(), ls.end()) - ls.begin();
307 }
308 for (int sat : order) {
309 int best = -1;
310 double best_d = p_.sc_radius_;
311 bool short_sat = nlay[sat] < p_.sc_min_layers_;
312 for (std::size_t k = 0; k < cls.size(); k++) {
313 // only merge into clusters that are not short themselves
314 if (static_cast<int>(k) == sat || members[k].empty() ||
315 esum[k] <= esum[sat] || nlay[k] < p_.sc_min_layers_ ||
316 (!short_sat && esum[sat] >= p_.sc_ratio_ * esum[k]))
317 continue;
318 double d = dist(cls[sat].x_, cls[sat].y_, cls[k].axisX(cls[sat].z_),
319 cls[k].axisY(cls[sat].z_));
320 if (d < best_d) {
321 best_d = d;
322 best = k;
323 }
324 }
325 if (best < 0) continue;
326 members[best].insert(members[best].end(), members[sat].begin(),
327 members[sat].end());
328 for (int h : members[sat]) core_[h] = 0;
329 esum[best] += esum[sat];
330 members[sat].clear();
331 }
332 std::vector<std::vector<int>> left;
333 for (auto& m : members)
334 if (!m.empty()) left.push_back(std::move(m));
335 members = std::move(left);
336 fit_all(cls, members);
337 }
338
339 // drop tiny clusters
340 std::vector<std::vector<int>> kept;
341 for (std::size_t k = 0; k < cls.size(); k++)
342 if (cls[k].e_ >= p_.min_cluster_energy_) kept.push_back(members[k]);
343 members = std::move(kept);
344 fit_all(cls, members);
345 std::vector<std::vector<double>> fracs(members.size());
346 for (std::size_t k = 0; k < members.size(); k++)
347 fracs[k].assign(members[k].size(), 1.);
348
349 // share hits between large clusters with an exponential profile
350 std::vector<int> big;
351 for (std::size_t k = 0; k < cls.size(); k++)
352 if (cls[k].e_ >= p_.share_min_energy_) big.push_back(k);
353 if (p_.share_ && big.size() > 1) {
354 std::vector<int> shared_hits;
355 for (int k : big)
356 shared_hits.insert(shared_hits.end(), members[k].begin(),
357 members[k].end());
358 std::vector<std::vector<int>> mem(big.size());
359 std::vector<std::vector<double>> fr(big.size());
360 for (int it = 0; it < p_.share_iters_; it++) {
361 for (auto& v : mem) v.clear();
362 for (auto& v : fr) v.clear();
363 for (int h : shared_hits) {
364 double hz = hits[h].getZPos();
365 double lam = lambda(layer_of_[h]);
366 std::vector<double> w(big.size());
367 double sw{0};
368 for (std::size_t b = 0; b < big.size(); b++) {
369 const auto& c = cls[big[b]];
370 double d = dist(hits[h].getXPos(), hits[h].getYPos(), c.axisX(hz),
371 c.axisY(hz));
372 w[b] = c.e_ * std::exp(-d / lam);
373 sw += w[b];
374 }
375 if (sw <= 0) {
376 // far from all axes: keep with the closest
377 std::size_t bb = 0;
378 double dmin = std::numeric_limits<double>::max();
379 for (std::size_t b = 0; b < big.size(); b++) {
380 const auto& c = cls[big[b]];
381 double d = dist(hits[h].getXPos(), hits[h].getYPos(), c.axisX(hz),
382 c.axisY(hz));
383 if (d < dmin) {
384 dmin = d;
385 bb = b;
386 }
387 }
388 w.assign(big.size(), 0.);
389 w[bb] = sw = 1.;
390 }
391 for (std::size_t b = 0; b < big.size(); b++) {
392 double f = w[b] / sw;
393 if (f < 1e-4) continue;
394 mem[b].push_back(h);
395 fr[b].push_back(f);
396 }
397 }
398 for (std::size_t b = 0; b < big.size(); b++) {
399 fitAxis(cls[big[b]], mem[b], fr[b]);
400 }
401 }
402 for (std::size_t b = 0; b < big.size(); b++) {
403 members[big[b]] = mem[b];
404 fracs[big[b]] = fr[b];
405 }
406 // merge clusters that converged onto the same axis
407 for (std::size_t i = 0; i < big.size(); i++) {
408 for (std::size_t j = i + 1; j < big.size(); j++) {
409 auto& a = cls[big[i]];
410 auto& b = cls[big[j]];
411 if (members[big[i]].empty() || members[big[j]].empty()) continue;
412 double zz = a.e_ < b.e_ ? a.z_ : b.z_;
413 double d = dist(a.axisX(zz), a.axisY(zz), b.axisX(zz), b.axisY(zz));
414 // small ghosts between showers also merge
415 bool ghost =
416 d < p_.ghost_dist_ &&
417 std::min(a.e_, b.e_) < p_.ghost_ratio_ * std::max(a.e_, b.e_);
418 if (d > p_.coincide_dist_ && !ghost) continue;
419 std::map<int, double> f;
420 for (std::size_t k = 0; k < members[big[i]].size(); k++)
421 f[members[big[i]][k]] += fracs[big[i]][k];
422 for (std::size_t k = 0; k < members[big[j]].size(); k++)
423 f[members[big[j]][k]] += fracs[big[j]][k];
424 members[big[i]].clear();
425 fracs[big[i]].clear();
426 for (auto& [h, v] : f) {
427 members[big[i]].push_back(h);
428 fracs[big[i]].push_back(v);
429 }
430 members[big[j]].clear();
431 fracs[big[j]].clear();
432 fitAxis(a, members[big[i]], fracs[big[i]]);
433 }
434 }
435 }
436
437 // final quantities
438 std::vector<Cluster> out;
439 for (std::size_t k = 0; k < members.size(); k++) {
440 Cluster c;
441 fitAxis(c, members[k], fracs[k]);
442 if (c.e_ < p_.min_cluster_energy_) continue;
443 c.hits_ = members[k];
444 c.frac_ = fracs[k];
445 double sxx{0}, syy{0}, szz{0}, sx{0}, sy{0}, e0{0};
446 for (std::size_t i = 0; i < c.hits_.size(); i++) {
447 const auto& h = hits[c.hits_[i]];
448 double e = c.frac_[i] * h.getEnergy();
449 sx += e * h.getXPos();
450 sy += e * h.getYPos();
451 sxx += e * h.getXPos() * h.getXPos();
452 syy += e * h.getYPos() * h.getYPos();
453 szz += e * (h.getZPos() - c.z_) * (h.getZPos() - c.z_);
454 if (layer_of_[c.hits_[i]] == 0) {
455 c.x0_ += e * h.getXPos();
456 c.y0_ += e * h.getYPos();
457 c.z0_ = h.getZPos();
458 e0 += e;
459 }
460 }
461 sx /= c.e_;
462 sy /= c.e_;
463 c.rms_x_ = std::sqrt(std::max(0., sxx / c.e_ - sx * sx));
464 c.rms_y_ = std::sqrt(std::max(0., syy / c.e_ - sy * sy));
465 c.rms_z_ = std::sqrt(szz / c.e_);
466 if (e0 > 0) {
467 c.x0_ /= e0;
468 c.y0_ /= e0;
469 }
470 out.push_back(std::move(c));
471 }
472 std::sort(out.begin(), out.end(),
473 [](const Cluster& a, const Cluster& b) { return a.e_ > b.e_; });
474 return out;
475}
double lambda(int layer) const
Profile slope used at a layer [mm].
Definition CLUE3D.cxx:47
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
int layer() const
Get the value of the layer field from the ID.
Definition EcalID.h:99

References lambda(), and ldmx::EcalID::layer().

Referenced by ecal::EcalClusterProducer::produce().

◆ cluster2D()

void ecal::CLUE3D::cluster2D ( const std::vector< int > & layer_hits)
private

Definition at line 54 of file CLUE3D.cxx.

54 {
55 const auto& hits = *hits_;
56 const int n = idx.size();
57 std::vector<double> rho(n), delta(n, std::numeric_limits<double>::max());
58 std::vector<int> nh(n, -1);
59 auto x = [&](int i) { return hits[idx[i]].getXPos(); };
60 auto y = [&](int i) { return hits[idx[i]].getYPos(); };
61 auto e = [&](int i) { return hits[idx[i]].getEnergy(); };
62
63 // self plus weighted neighbors within dc
64 for (int i = 0; i < n; i++) {
65 rho[i] = e(i);
66 for (int j = 0; j < n; j++) {
67 if (j != i && dist(x(i), y(i), x(j), y(j)) < p_.dc2d_)
68 rho[i] += p_.kernel2d_ * e(j);
69 }
70 }
71 // detid breaks density ties
72 auto higher = [&](int a, int b) {
73 if (rho[a] != rho[b]) return rho[a] > rho[b];
74 return hits[idx[a]].getID() > hits[idx[b]].getID();
75 };
76 for (int i = 0; i < n; i++) {
77 for (int j = 0; j < n; j++) {
78 if (j == i || !higher(j, i)) continue;
79 double d = dist(x(i), y(i), x(j), y(j));
80 if (d < p_.deltao2d_ && d < delta[i]) {
81 delta[i] = d;
82 nh[i] = j;
83 }
84 }
85 }
86 std::vector<int> order(n);
87 std::iota(order.begin(), order.end(), 0);
88 std::sort(order.begin(), order.end(), higher);
89
90 // orphans with no higher neighbor make their own cluster
91 std::vector<int> lc_of(n, -1);
92 const int first = lcs_.size();
93 for (int i : order) {
94 bool seed = nh[i] < 0 || (rho[i] >= p_.rhoc2d_ && delta[i] > p_.deltas2d_);
95 if (seed) {
96 lc_of[i] = lcs_.size();
97 lcs_.emplace_back();
98 } else {
99 lc_of[i] = lc_of[nh[i]];
100 }
101 lcs_[lc_of[i]].hits_.push_back(idx[i]);
102 }
103
104 for (std::size_t c = first; c < lcs_.size(); c++) {
105 auto& lc = lcs_[c];
106 std::vector<double> hx, hy, he;
107 for (int h : lc.hits_) {
108 hx.push_back(hits[h].getXPos());
109 hy.push_back(hits[h].getYPos());
110 he.push_back(hits[h].getEnergy());
111 lc.e_ += hits[h].getEnergy();
112 }
113 logCentroid(hx, hy, he, p_.w0_, p_.pos_window_, lc.x_, lc.y_);
114 lc.z_ = hits[lc.hits_[0]].getZPos();
115 lc.layer_ = layer_of_[lc.hits_[0]];
116 }
117}

◆ cluster3D()

void ecal::CLUE3D::cluster3D ( )
private

Definition at line 119 of file CLUE3D.cxx.

119 {
120 const int m = lcs_.size();
121 std::map<int, std::vector<int>> by_layer;
122 for (int i = 0; i < m; i++) by_layer[lcs_[i].layer_].push_back(i);
123
124 auto loop_siblings = [&](int i, auto&& f) {
125 int l = lcs_[i].layer_;
126 for (int dl = -p_.sibling_layers_; dl <= p_.sibling_layers_; dl++) {
127 if (dl == 0) continue;
128 auto it = by_layer.find(l + dl);
129 if (it == by_layer.end()) continue;
130 for (int j : it->second) f(j);
131 }
132 };
133
134 for (int i = 0; i < m; i++) {
135 auto& a = lcs_[i];
136 a.rho_ = a.e_;
137 loop_siblings(i, [&](int j) {
138 if (dist(a.x_, a.y_, lcs_[j].x_, lcs_[j].y_) < p_.dens_xy3d_)
139 a.rho_ += p_.kernel3d_ * lcs_[j].e_;
140 });
141 }
142 auto higher = [&](int a, int b) {
143 if (lcs_[a].rho_ != lcs_[b].rho_) return lcs_[a].rho_ > lcs_[b].rho_;
144 if (lcs_[a].e_ != lcs_[b].e_) return lcs_[a].e_ > lcs_[b].e_;
145 return a > b;
146 };
147 const double max_d = p_.outlier_mult3d_ * p_.crit_xy3d_;
148 for (int i = 0; i < m; i++) {
149 auto& a = lcs_[i];
150 a.delta_ = std::numeric_limits<double>::max();
151 a.nh_ = -1;
152 loop_siblings(i, [&](int j) {
153 if (!higher(j, i)) return;
154 double d = dist(a.x_, a.y_, lcs_[j].x_, lcs_[j].y_);
155 if (d < max_d && d < a.delta_) {
156 a.delta_ = d;
157 a.nh_ = j;
158 }
159 });
160 }
161 std::vector<int> order(m);
162 std::iota(order.begin(), order.end(), 0);
163 std::sort(order.begin(), order.end(), higher);
164 int ncl = 0;
165 for (int i : order) {
166 auto& a = lcs_[i];
167 bool seed = a.delta_ > p_.crit_xy3d_ && a.rho_ >= p_.crit_rho3d_ &&
168 a.e_ / a.rho_ > p_.self_frac3d_;
169 if (seed)
170 a.cid_ = ncl++;
171 else
172 a.cid_ = a.nh_ >= 0 ? lcs_[a.nh_].cid_ : -1;
173 }
174}

◆ fitAxis()

void ecal::CLUE3D::fitAxis ( Cluster & c,
const std::vector< int > & hit_idx,
const std::vector< double > & frac ) const
private

Definition at line 176 of file CLUE3D.cxx.

177 {
178 const auto& hits = *hits_;
179 std::map<int, std::vector<std::size_t>> by_layer;
180 double etot{0}, sz{0};
181 for (std::size_t k = 0; k < idx.size(); k++) {
182 double e = frac[k] * hits[idx[k]].getEnergy();
183 if (e <= 0) continue;
184 by_layer[layer_of_[idx[k]]].push_back(k);
185 etot += e;
186 sz += e * hits[idx[k]].getZPos();
187 }
188 c.e_ = etot;
189 if (etot <= 0) return;
190 c.z_ = sz / etot;
191
192 // axis from core hits only, if any
193 bool any_core = false;
194 for (const auto& [layer, ks] : by_layer)
195 for (auto k : ks) any_core = any_core || core_[idx[k]];
196
197 // weighted line fit of per layer centroids, weight E_L / lambda^2
198 double s{0}, sz1{0}, szz{0}, sx{0}, sxz{0}, sy{0}, syz{0};
199 for (const auto& [layer, ks] : by_layer) {
200 std::vector<double> hx, hy, he;
201 for (auto k : ks) {
202 if (any_core && !core_[idx[k]]) continue;
203 hx.push_back(hits[idx[k]].getXPos());
204 hy.push_back(hits[idx[k]].getYPos());
205 he.push_back(frac[k] * hits[idx[k]].getEnergy());
206 }
207 if (he.empty()) continue;
208 double lx, ly;
209 logCentroid(hx, hy, he, p_.w0_, p_.pos_window_, lx, ly);
210 double el = std::accumulate(he.begin(), he.end(), 0.);
211 double w = el / (lambda(layer) * lambda(layer));
212 double z = hits[idx[ks[0]]].getZPos();
213 s += w;
214 sz1 += w * z;
215 szz += w * z * z;
216 sx += w * lx;
217 sxz += w * lx * z;
218 sy += w * ly;
219 syz += w * ly * z;
220 }
221 double den = s * szz - sz1 * sz1;
222 if (by_layer.size() > 1 && den > 1e-9 * s * szz) {
223 c.dxdz_ = (s * sxz - sz1 * sx) / den;
224 c.dydz_ = (s * syz - sz1 * sy) / den;
225 } else {
226 c.dxdz_ = c.dydz_ = 0;
227 }
228 double zbar = sz1 / s;
229 c.x_ = sx / s + c.dxdz_ * (c.z_ - zbar);
230 c.y_ = sy / s + c.dydz_ * (c.z_ - zbar);
231}

◆ lambda()

double ecal::CLUE3D::lambda ( int layer) const

Profile slope used at a layer [mm].

Definition at line 47 of file CLUE3D.cxx.

47 {
48 if (layer >= 0 && layer < static_cast<int>(p_.profile_lambda_.size()))
49 return p_.profile_lambda_[layer];
50 // median radius / 1.68 from v15 gun showers
51 return std::max(2., 1.9 * std::exp(0.12 * layer));
52}

Referenced by cluster().

◆ layerClusters()

const std::vector< LayerCluster > & ecal::CLUE3D::layerClusters ( ) const
inline

Definition at line 97 of file CLUE3D.h.

97{ return lcs_; }

Member Data Documentation

◆ core_

std::vector<char> ecal::CLUE3D::core_
private

Definition at line 111 of file CLUE3D.h.

◆ hits_

const std::vector<ldmx::EcalHit>* ecal::CLUE3D::hits_ {nullptr}
private

Definition at line 109 of file CLUE3D.h.

109{nullptr};

◆ layer_of_

std::vector<int> ecal::CLUE3D::layer_of_
private

Definition at line 110 of file CLUE3D.h.

◆ lcs_

std::vector<LayerCluster> ecal::CLUE3D::lcs_
private

Definition at line 112 of file CLUE3D.h.

◆ p_

Params ecal::CLUE3D::p_ {}
private

Definition at line 108 of file CLUE3D.h.

108{};

The documentation for this class was generated from the following files: