Belle II Software development
KLMClustersReconstructorModule.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9/* Own header. */
10#include <klm/modules/KLMClustersReconstructor/KLMClustersReconstructorModule.h>
11
12/* KLM headers. */
13#include <klm/dataobjects/KLMElementNumbers.h>
14#include <klm/dataobjects/bklm/BKLMElementNumbers.h>
15#include <klm/dataobjects/eklm/EKLMElementNumbers.h>
16
17/* ROOT headers. */
18#include <Math/VectorUtil.h>
19
20/* C++ headers. */
21#include <algorithm>
22#include <cmath>
23#include <unordered_set>
24
25using namespace Belle2;
26
27REG_MODULE(KLMClustersReconstructor);
28
34{
35 setDescription("Unified BKLM/EKLM module for the reconstruction of KLMClusters.");
37 addParam("ClusteringAngle", m_ClusteringAngle, "Clustering angle (rad).",
38 0.26);
39 addParam("PositionMode", m_PositionModeString,
40 "Vertex position mode: 'FullAverage', 'FirstLayer', 'FirstTwoLayers', "
41 "or 'SuccessiveTwoLayers'.",
42 std::string("FirstTwoLayers"));
43 addParam("ClusterMode", m_ClusterModeString,
44 "Clusterization mode ('AnyHit' or 'FirstHit').",
45 std::string("AnyHit"));
46 addParam("RemoveOutlierHits", m_RemoveOutlierHits,
47 "If true, iteratively remove angular outliers after clustering "
48 "(dropped hits re-enter the pool). If false, behavior matches the "
49 "original algorithm.",
50 false);
51 addParam("OutlierTrimAngle", m_OutlierTrimAngle,
52 "Used only if removeOutlierHits: floor (minimum) angular threshold (rad). "
53 "Effective cut is max(OutlierTrimAngle, k * MAD(residuals)) with fixed k=3.",
54 0.18);
55}
56
60
62{
63 m_KLMClusters.registerInDataStore();
64 m_Hit2ds.isRequired();
65 m_KLMClusters.registerRelationTo(m_Hit2ds);
66 if (m_PositionModeString == "FullAverage")
68 else if (m_PositionModeString == "FirstLayer")
70 else if (m_PositionModeString == "FirstTwoLayers")
72 else if (m_PositionModeString == "SuccessiveTwoLayers")
74 else
75 B2FATAL("Incorrect PositionMode argument.");
76 if (m_ClusterModeString == "AnyHit")
78 else if (m_ClusterModeString == "FirstHit")
80 else
81 B2FATAL("Incorrect ClusterMode argument.");
82}
83
84static bool compareDistance(const KLMHit2d* hit1, const KLMHit2d* hit2)
85{
86 return hit1->getPosition().R() < hit2->getPosition().R();
87}
88
89static KLMHit2d* hitWithMinR(const std::vector<KLMHit2d*>& hits)
90{
91 KLMHit2d* best = hits[0];
92 double bestR = best->getPosition().R();
93 for (size_t k = 1; k < hits.size(); k++) {
94 double r = hits[k]->getPosition().R();
95 if (r < bestR) {
96 bestR = r;
97 best = hits[k];
98 }
99 }
100 return best;
101}
102
103/* Unit vector from origin to the hit position (zero if hit is at origin). */
104static ROOT::Math::XYZVector unitDirection(const ROOT::Math::XYZVector& v)
105{
106 double r = v.R();
107 if (r <= 0)
108 return ROOT::Math::XYZVector{0, 0, 0};
109 return v * (1.0 / r);
110}
111
112/* Reference direction: innermost-R hit direction on first iteration,
113 * otherwise the normalized sum of unit directions of the current inliers. */
114static ROOT::Math::XYZVector referenceDirection(const std::vector<KLMHit2d*>& inliers,
115 bool seedByInnermost)
116{
117 if (seedByInnermost)
118 return unitDirection(hitWithMinR(inliers)->getPosition());
119 ROOT::Math::XYZVector sum{0, 0, 0};
120 for (const KLMHit2d* h : inliers)
121 sum = sum + unitDirection(h->getPosition());
122 if (sum.R() <= 0)
123 return unitDirection(hitWithMinR(inliers)->getPosition());
124 return sum * (1.0 / sum.R());
125}
126
127/* Adaptive angular threshold: max(floor, k * MAD of angular residuals to ref). */
128static double adaptiveThreshold(const ROOT::Math::XYZVector& ref,
129 const std::vector<KLMHit2d*>& hits,
130 double floorAngle, double madFactor)
131{
132 std::vector<double> residuals;
133 residuals.reserve(hits.size());
134 for (const KLMHit2d* h : hits)
135 residuals.push_back(ROOT::Math::VectorUtil::Angle(h->getPosition(), ref));
136 std::vector<double> sorted = residuals;
137 std::sort(sorted.begin(), sorted.end());
138 double median = sorted[sorted.size() / 2];
139 std::vector<double> absDev;
140 absDev.reserve(residuals.size());
141 for (double r : residuals)
142 absDev.push_back(std::fabs(r - median));
143 std::sort(absDev.begin(), absDev.end());
144 double mad = absDev[absDev.size() / 2];
145 return std::max(floorAngle, madFactor * mad);
146}
147
148void KLMClustersReconstructorModule::applyOutlierRemoval(std::vector<KLMHit2d*>& clusterHits,
149 std::vector<KLMHit2d*>& poolHits)
150{
151 /* Fixed tuning: standard k=3 for MAD scale; iteration cap (fixed-point exits early);
152 * min inlier fraction 0.3 avoids over-trimming a cluster to fewer than 30% the hits. */
153 constexpr double kMADFactor = 3.0;
154 constexpr int kMaxOutlierIterations = 5;
155 constexpr double kMinInlierFraction = 0.3;
156
157 if (!m_RemoveOutlierHits || clusterHits.size() <= 1)
158 return;
159
160 const std::vector<KLMHit2d*> before = clusterHits;
161 const std::size_t minInliers = std::max<std::size_t>(
162 2, static_cast<std::size_t>(std::ceil(kMinInlierFraction * before.size())));
163
164 std::vector<KLMHit2d*> inliers = before;
165 for (int iter = 0; iter < kMaxOutlierIterations; iter++) {
166 const ROOT::Math::XYZVector ref =
167 referenceDirection(inliers, /*seedByInnermost=*/iter == 0);
168 const double threshold =
169 adaptiveThreshold(ref, before, m_OutlierTrimAngle, kMADFactor);
170 std::vector<KLMHit2d*> next;
171 next.reserve(before.size());
172 for (KLMHit2d* h : before) {
173 if (ROOT::Math::VectorUtil::Angle(h->getPosition(), ref) <= threshold)
174 next.push_back(h);
175 }
176 if (next.size() < minInliers)
177 break;
178 if (next.size() == inliers.size()) {
179 std::vector<KLMHit2d*> a = next, b = inliers;
180 std::sort(a.begin(), a.end());
181 std::sort(b.begin(), b.end());
182 if (a == b) {
183 inliers = std::move(next);
184 break;
185 }
186 }
187 inliers = std::move(next);
188 }
189
190 if (inliers.size() < minInliers)
191 return;
192 if (inliers.size() == before.size())
193 return;
194 if (inliers.empty())
195 return;
196
197 std::unordered_set<KLMHit2d*> inlierSet(inliers.begin(), inliers.end());
198 std::vector<KLMHit2d*> outliers;
199 outliers.reserve(before.size() - inliers.size());
200 for (KLMHit2d* h : before) {
201 if (inlierSet.find(h) == inlierSet.end())
202 outliers.push_back(h);
203 }
204 clusterHits.swap(inliers);
205 poolHits.insert(poolHits.end(), outliers.begin(), outliers.end());
206 sort(poolHits.begin(), poolHits.end(), compareDistance);
207}
208
210{
211 //static double mass = Const::Klong.getMass();
212 int i, nLayers, innermostLayer, nHits;
215 int* layerHitsBKLM, *layerHitsEKLM;
216 float minTime = -1;
217 double p;//, v;
218 std::vector<KLMHit2d*> klmHit2ds, klmClusterHits;
219 std::vector<KLMHit2d*>::iterator it, it0, it2;
220 KLMCluster* klmCluster;
221 layerHitsBKLM = new int[nLayersBKLM];
222 layerHitsEKLM = new int[nLayersEKLM];
223 /* Fill vector of 2d hits. */
224 nHits = m_Hit2ds.getEntries();
225 for (i = 0; i < nHits; i++) {
226 if (m_Hit2ds[i]->isOutOfTime())
227 continue;
228 klmHit2ds.push_back(m_Hit2ds[i]);
229 }
230 /* Sort by the distance from center. */
231 sort(klmHit2ds.begin(), klmHit2ds.end(), compareDistance);
232 /* Clustering. */
233 while (klmHit2ds.size() > 0) {
234 klmClusterHits.clear();
235 it = klmHit2ds.begin();
236 klmClusterHits.push_back(*it);
237 it = klmHit2ds.erase(it);
238 while (it != klmHit2ds.end()) {
239 it2 = klmClusterHits.begin();
240 switch (m_ClusterMode) {
241 case c_AnyHit:
242 while (it2 != klmClusterHits.end()) {
243 if (ROOT::Math::VectorUtil::Angle(
244 (*it)->getPosition(), (*it2)->getPosition()) <
246 klmClusterHits.push_back(*it);
247 it = klmHit2ds.erase(it);
248 goto clusterFound;
249 } else
250 ++it2;
251 }
252 break;
253 case c_FirstHit:
254 if (ROOT::Math::VectorUtil::Angle(
255 (*it)->getPosition(), (*it2)->getPosition()) <
257 klmClusterHits.push_back(*it);
258 it = klmHit2ds.erase(it);
259 goto clusterFound;
260 }
261 break;
262 }
263 ++it;
264clusterFound:;
265 }
267 applyOutlierRemoval(klmClusterHits, klmHit2ds);
268 ROOT::Math::XYZVector clusterPosition{0, 0, 0};
269 for (i = 0; i < nLayersBKLM; i++)
270 layerHitsBKLM[i] = 0;
271 for (i = 0; i < nLayersEKLM; i++)
272 layerHitsEKLM[i] = 0;
273 minTime = -1;
274 /* Minimal time and per-layer hit counts. */
275 for (it = klmClusterHits.begin(); it != klmClusterHits.end(); ++it) {
276 if (minTime < 0 || (*it)->getTime() < minTime)
277 minTime = (*it)->getTime();
278 if ((*it)->getSubdetector() == KLMElementNumbers::c_BKLM)
279 layerHitsBKLM[(*it)->getLayer() - 1]++;
280 else
281 layerHitsEKLM[(*it)->getLayer() - 1]++;
282 }
283 it0 = klmClusterHits.begin();
284 nHits = 0;
286 for (it = klmClusterHits.begin(); it != klmClusterHits.end(); ++it) {
287 clusterPosition = clusterPosition + (*it)->getPosition();
288 nHits++;
289 }
290 } else {
291 int selSubdet[2];
292 int selLayer[2];
293 int selCount = 0;
295 selSubdet[0] = (*it0)->getSubdetector();
296 selLayer[0] = (*it0)->getLayer();
297 selCount = 1;
298 } else if (m_PositionMode == c_FirstTwoLayers) {
299 for (i = 0; i < nLayersBKLM && selCount < 2; i++) {
300 if (layerHitsBKLM[i] > 0) {
301 selSubdet[selCount] = KLMElementNumbers::c_BKLM;
302 selLayer[selCount] = i + 1;
303 selCount++;
304 }
305 }
306 for (i = 0; i < nLayersEKLM && selCount < 2; i++) {
307 if (layerHitsEKLM[i] > 0) {
308 selSubdet[selCount] = KLMElementNumbers::c_EKLM;
309 selLayer[selCount] = i + 1;
310 selCount++;
311 }
312 }
313 if (selCount == 0) {
314 selSubdet[0] = (*it0)->getSubdetector();
315 selLayer[0] = (*it0)->getLayer();
316 selCount = 1;
317 }
319 bool foundPair = false;
320 for (i = 0; i < nLayersBKLM - 1; i++) {
321 if (layerHitsBKLM[i] > 0 && layerHitsBKLM[i + 1] > 0) {
322 selSubdet[0] = KLMElementNumbers::c_BKLM;
323 selLayer[0] = i + 1;
324 selSubdet[1] = KLMElementNumbers::c_BKLM;
325 selLayer[1] = i + 2;
326 selCount = 2;
327 foundPair = true;
328 break;
329 }
330 }
331 if (!foundPair) {
332 for (i = 0; i < nLayersEKLM - 1; i++) {
333 if (layerHitsEKLM[i] > 0 && layerHitsEKLM[i + 1] > 0) {
334 selSubdet[0] = KLMElementNumbers::c_EKLM;
335 selLayer[0] = i + 1;
336 selSubdet[1] = KLMElementNumbers::c_EKLM;
337 selLayer[1] = i + 2;
338 selCount = 2;
339 foundPair = true;
340 break;
341 }
342 }
343 }
344 if (!foundPair) {
345 selSubdet[0] = (*it0)->getSubdetector();
346 selLayer[0] = (*it0)->getLayer();
347 selCount = 1;
348 }
349 }
350 for (it = klmClusterHits.begin(); it != klmClusterHits.end(); ++it) {
351 int sd = (*it)->getSubdetector();
352 int ly = (*it)->getLayer();
353 bool use = false;
354 for (i = 0; i < selCount; i++) {
355 if (sd == selSubdet[i] && ly == selLayer[i]) {
356 use = true;
357 break;
358 }
359 }
360 if (use) {
361 clusterPosition = clusterPosition + (*it)->getPosition();
362 nHits++;
363 }
364 }
365 if (nHits == 0)
366 B2FATAL("KLMClustersReconstructor: no hits for vertex position.");
367 }
368 clusterPosition = clusterPosition * (1.0 / nHits);
369 /* Find innermost layer. */
370 nLayers = 0;
371 innermostLayer = -1;
372 for (i = 0; i < nLayersBKLM; i++) {
373 if (layerHitsBKLM[i] > 0) {
374 nLayers++;
375 if (innermostLayer < 0)
376 innermostLayer = i + 1;
377 }
378 }
379 for (i = 0; i < nLayersEKLM; i++) {
380 if (layerHitsEKLM[i] > 0) {
381 nLayers++;
382 if (innermostLayer < 0)
383 innermostLayer = i + 1;
384 }
385 }
386 /* Calculate energy. */
387 //if (it0->inBKLM()) {
388 /*
389 * TODO: The constant is from BKLM K0L reconstructor,
390 * it must be recalculated.
391 */
392 p = klmClusterHits.size() * 0.215;
393 /* FIXME: Reimplement time calculation after completion of time calibration.
394 } else {
395 v = clusterPosition.R() / minTime / Const::speedOfLight;
396 if (v < 0.999999)
397 p = mass * v / sqrt(1.0 - v * v);
398 else
399 p = 0;
400 }*/
401 klmCluster = m_KLMClusters.appendNew(
402 clusterPosition.X(), clusterPosition.Y(), clusterPosition.Z(), minTime, nLayers,
403 innermostLayer, p);
404 for (it = klmClusterHits.begin(); it != klmClusterHits.end(); ++it)
405 klmCluster->addRelationTo(*it);
406
407 /* number of KLM digits in the cluster (BKLM via BKLMHit1d and EKLM directly). */
408 int nDigits = 0;
409 for (it = klmClusterHits.begin(); it != klmClusterHits.end(); ++it) {
410 auto bklmhit1ds = (*it)->getRelationsTo<BKLMHit1d>();
411 for (const auto& bklmhit1d : bklmhit1ds) {
412 auto klmdigits = bklmhit1d.getRelationsTo<KLMDigit>();
413 nDigits += klmdigits.size();
414 }
415 auto klmdigits = (*it)->getRelationsTo<KLMDigit>();
416 nDigits += klmdigits.size();
417 }
418 klmCluster->setKLMnDigits(nDigits);
419 }
420
421 delete[] layerHitsBKLM;
422 delete[] layerHitsEKLM;
423}
424
static constexpr int getMaximalLayerNumber()
Get maximal layer number (1-based).
Store one reconstructed BKLM 1D hit as a ROOT object.
Definition BKLMHit1d.h:30
static constexpr int getMaximalLayerNumber()
Get maximal layer number.
KLM cluster data.
Definition KLMCluster.h:29
void setKLMnDigits(int nDigits)
Set number of KLM digits in the cluster.
Definition KLMCluster.h:276
enum ClusterMode m_ClusterMode
Clusterization mode.
void event() override
This method is called for each event.
double m_OutlierTrimAngle
Floor (minimum) angular threshold in rad; effective cut is max(floor, k*MAD).
std::string m_PositionModeString
Vertex position calculation mode.
enum PositionMode m_PositionMode
Vertex position calculation mode.
@ c_SuccessiveTwoLayers
Innermost adjacent layer pair with hits; else FirstLayer.
@ c_FirstTwoLayers
Two innermost layers with hits (BKLM then EKLM).
void applyOutlierRemoval(std::vector< KLMHit2d * > &clusterHits, std::vector< KLMHit2d * > &poolHits)
Optional post-cluster hit filtering.
StoreArray< KLMCluster > m_KLMClusters
KLM clusters.
bool m_RemoveOutlierHits
If true, drop angular outliers after clustering and re-queue them for other clusters.
StoreArray< KLMHit2d > m_Hit2ds
Two-dimensional hits.
KLM digit (class representing a digitized hit in RPCs or scintillators).
Definition KLMDigit.h:29
KLM 2d hit.
Definition KLMHit2d.h:33
ROOT::Math::XYZVector getPosition() const
Get hit global position.
Definition KLMHit2d.h:315
void setDescription(const std::string &description)
Sets the description of the module.
Definition Module.cc:214
void setPropertyFlags(unsigned int propertyFlags)
Sets the flags for the module properties.
Definition Module.cc:208
Module()
Constructor.
Definition Module.cc:30
@ c_ParallelProcessingCertified
This module can be run in parallel processing mode safely (All I/O must be done through the data stor...
Definition Module.h:80
void addRelationTo(const RelationsInterface< BASE > *object, float weight=1.0, const std::string &namedRelation="") const
Add a relation from this object to another object (with caching).
RelationVector< TO > getRelationsTo(const std::string &name="", const std::string &namedRelation="") const
Get the relations that point from this object to another store array.
void addParam(const std::string &name, T &paramVariable, const std::string &description, const T &defaultValue)
Adds a new parameter to the module.
Definition Module.h:559
#define REG_MODULE(moduleName)
Register the given module (without 'Module' suffix) with the framework.
Definition Module.h:649
ExpRunEvt getPosition(const std::vector< Evt > &events, double tEdge)
Get the exp-run-evt number from the event time [hours].
Definition Splitter.h:341
Abstract base class for different kinds of events.