Belle II Software development
ECLCRFinderModule.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 <ecl/modules/eclCRFinder/ECLCRFinderModule.h>
11
12/* ECL headeers. */
13#include <ecl/dataobjects/ECLCalDigit.h>
14#include <ecl/dataobjects/ECLConnectedRegion.h>
15#include <ecl/geometry/ECLNeighbours.h>
16
17/* Basf2 headers. */
18#include <framework/gearbox/Unit.h>
19#include <framework/logging/Logger.h>
20#include <mdst/dataobjects/EventLevelClusteringInfo.h>
21
22/* C++ headers. */
23#include <algorithm>
24#include <iostream>
25#include <array>
26
27// NAMESPACE(S)
28using namespace Belle2;
29
30//-----------------------------------------------------------------
31// Register the Module
32//-----------------------------------------------------------------
33REG_MODULE(ECLCRFinder);
34REG_MODULE(ECLCRFinderPureCsI);
35
36//-----------------------------------------------------------------
37// Implementation
38//-----------------------------------------------------------------
41{
42 // Set description
43 setDescription("ECLCRFinderModule");
44
45 // Parallel processing certification.
47
48 // Add module parameters.
49 addParam("energyCut0", m_energyCut[0], "Seed energy cut.", 20.0 * Belle2::Unit::MeV);
50 addParam("energyCut1", m_energyCut[1], "Growth energy cut.", 20.0 * Belle2::Unit::MeV);
51 addParam("energyCut2", m_energyCut[2], "Digit energy cut.", 0.5 * Belle2::Unit::MeV);
52 addParam("timeCut0", m_timeCut[0], "Seed time cut (negative values for residual cut).", 400.);
53 addParam("timeCut1", m_timeCut[1], "Growth time cut (negative values for residual cut).", 400.);
54 addParam("timeCut2", m_timeCut[2], "Digit time cut (negative values for residual cut).", 99999.);
55 addParam("timeCut0maxEnergy", m_timeCut_maxEnergy[0], "Time cut is only applied below this energy for seed crystals.",
56 99999.0 * Belle2::Unit::MeV);
57 addParam("timeCut1maxEnergy", m_timeCut_maxEnergy[1], "Time cut is only applied below this energy for growth crystals.",
58 99999.0 * Belle2::Unit::MeV);
59 addParam("timeCut2maxEnergy", m_timeCut_maxEnergy[2], "Time cut is only applied below this energy for digits.",
60 0.0 * Belle2::Unit::MeV);
61 addParam("mapType0", m_mapType[0], "Map type for seed crystals.", std::string("N"));
62 addParam("mapType1", m_mapType[1], "Map type for growth crystals.", std::string("N"));
63 addParam("mapPar0", m_mapPar[0],
64 "Map parameter for seed crystals (radius (type=R), integer (for type=N) or fraction (for type=MC)).", 1.0);
65 addParam("mapPar1", m_mapPar[1],
66 "Map parameter for growth crystals (radius (type=R), integer (for type=N) or fraction (for type=MC)).", 1.0);
67 addParam("skipFailedTimeFitDigits", m_skipFailedTimeFitDigits, "Digits with failed fits are skipped when checking timing cuts.", 0);
68 addParam("useParametersFromDatabase", m_useParametersFromDatabase, "get energy and time cuts from payload", true);
69
70}
71
76
78{
79 B2DEBUG(200, "ECLCRFinderModule::initialize()");
80
81 // Register dataobjects.
85
86 // Register relations.
87 m_eclConnectedRegions.registerRelationTo(m_eclCalDigits);
88
89 // Initialize neighbour maps.
90 m_neighbourMaps.resize(2);
93
94 // Resize the vectors
95 m_cellIdToCheckVec.resize(8737);
96 m_cellIdToSeedVec.resize(8737);
97 m_cellIdToGrowthVec.resize(8737);
98 m_cellIdToDigitVec.resize(8737);
99 m_cellIdToTempCRIdVec.resize(8737);
100 m_calDigitStoreArrPosition.resize(8737);
101
102 m_adj.resize(8737);
103 m_visited.resize(8737);
104}
105
106//-----------------------------------------------------------------
107//..By default, get the energy and time thresholds from the
108// eclClusteringParameters dbOject
110{
112 std::array<double, 3> CRF_energyCut = m_eclClusteringParameters->getCRFEnergyCut();
113 m_energyCut[0] = CRF_energyCut[0];
114 m_energyCut[1] = CRF_energyCut[1];
115 m_energyCut[2] = CRF_energyCut[2];
116
117 std::array<double, 3> CRF_timeCut = m_eclClusteringParameters->getCRFTimeCut();
118 m_timeCut[0] = CRF_timeCut[0];
119 m_timeCut[1] = CRF_timeCut[1];
120 m_timeCut[2] = CRF_timeCut[2];
121
122 std::array<double, 3> CRF_timeCutMaxEnergy = m_eclClusteringParameters->getCRFTimeCutMaxEnergy();
123 m_timeCut_maxEnergy[0] = CRF_timeCutMaxEnergy[0];
124 m_timeCut_maxEnergy[1] = CRF_timeCutMaxEnergy[1];
125 m_timeCut_maxEnergy[2] = CRF_timeCutMaxEnergy[2];
126 }
127
128 // Check user inputs: [2]: digit, [1]: growth, [0]: seed
129 // overall energy thresholds
130 if (std::isless(m_energyCut[0], m_energyCut[1])) B2FATAL("ECLCRFinderModule::beginRun(): m_energyCut[0]=" << m_energyCut[0] <<
131 " must be larger or equal than m_energyCut[1]=" << m_energyCut[1]);
132 if (std::isless(m_energyCut[1], m_energyCut[2])) B2FATAL("ECLCRFinderModule::beginRun(): m_energyCut[1]=" << m_energyCut[1] <<
133 " must be larger or equal than m_energyCut[2]=" << m_energyCut[2]);
134 // timing threshold (can depend on energy, but we make the check here even stronger by checking that the timing is looser without checking the timing energy range)
135 if (std::isgreater(m_timeCut[0], m_timeCut[1]))
136 B2FATAL("ECLCRFinderModule::beginRun(): m_timeCut[0] must be less or equal than m_timeCut[1].");
137 if (std::isgreater(m_timeCut[1], m_timeCut[2]))
138 B2FATAL("ECLCRFinderModule::beginRun(): m_timeCut[1] must be less or equal than m_timeCut[2].");
139}
140
141
142//-----------------------------------------------------------------
143
145{
146 B2DEBUG(200, "ECLCRFinderModule::event()");
147
148 // Reset the vector(s).
149 std::fill(m_cellIdToCheckVec.begin(), m_cellIdToCheckVec.end(), 0);
150 std::fill(m_cellIdToSeedVec.begin(), m_cellIdToSeedVec.end(), 0);
151 std::fill(m_cellIdToGrowthVec.begin(), m_cellIdToGrowthVec.end(), 0);
152 std::fill(m_cellIdToDigitVec.begin(), m_cellIdToDigitVec.end(), 0);
153 std::fill(m_cellIdToTempCRIdVec.begin(), m_cellIdToTempCRIdVec.end(), 0);
154
155 // Fill a vector that can be used to map cellid -> store array position
156 std::fill(m_calDigitStoreArrPosition.begin(), m_calDigitStoreArrPosition.end(), -1);
157 for (int i = 0; i < m_eclCalDigits.getEntries(); i++) {
158 m_calDigitStoreArrPosition[m_eclCalDigits[i]->getCellId()] = i;
159 }
160
161 // Clear the map(s).
162 m_cellIdToTempCRIdMap.clear();
163
164 //-------------------------------------------------------
165 // fill digits into maps
166 for (const auto& eclCalDigit : m_eclCalDigits) {
167 const double energy = eclCalDigit.getEnergy();
168 const double time = eclCalDigit.getTime();
169 const double timeresolution = eclCalDigit.getTimeResolution();
170 const int cellid = eclCalDigit.getCellId();
171 const bool fitfailed = eclCalDigit.isFailedFit();
172
173 double timeresidual = 999.;
174 if (!fitfailed and fabs(timeresolution) > 1e-9) {
175 timeresidual = time / timeresolution;
176 }
177
178 // Negative timecut is interpreted as cut on time residual, positive cut as cut on the time!
179 // Start filling all crystals to a map. Growth and seed crystals are strict subsets.
180 if (std::isgreaterequal(energy, m_energyCut[2])) {
181 if (fitfailed > 0 and m_skipFailedTimeFitDigits > 0) continue;
182 if (!fitfailed
183 and energy < m_timeCut_maxEnergy[2]) { //check timing cuts only if we have a good fit and if the energy is below threshold
184 if (m_timeCut[2] > 1e-9 and fabs(time) > m_timeCut[2]) continue;
185 if (m_timeCut[2] < -1e-9 and fabs(timeresidual) > fabs(m_timeCut[2])) continue;
186 }
187 m_cellIdToDigitVec[cellid] = 1;
188 B2DEBUG(250, "ECLCRFinderModule::event(), adding 'all digit' cellid = " << cellid << " " << energy << " " << time << " " <<
189 timeresidual);
190
191 // check growth only if they already passed the digit check
192 if (std::isgreaterequal(energy, m_energyCut[1])) {
193 if (!fitfailed
194 and energy < m_timeCut_maxEnergy[1]) { //check timing cuts only if we have a good fit and if the energy is below threshold
195 if (m_timeCut[1] > 1e-9 and fabs(time) > m_timeCut[1]) continue;
196 if (m_timeCut[1] < -1e-9 and fabs(timeresidual) > fabs(m_timeCut[1])) continue;
197 }
198 m_cellIdToGrowthVec[cellid] = 1;
199 B2DEBUG(250, "ECLCRFinderModule::event(), adding 'growth digit' cellid = " << cellid << " " << energy << " " << time << " " <<
200 timeresidual);
201
202
203 // check seed only if they already passed the growth check
204 if (std::isgreaterequal(energy, m_energyCut[0])) {
205 if (!fitfailed
206 and energy < m_timeCut_maxEnergy[0]) { //check timing cuts only if we have a good fit and if the energy is below threshold
207 if (m_timeCut[0] > 1e-9 and fabs(time) > m_timeCut[0]) continue;
208 if (m_timeCut[0] < -1e-9 and fabs(timeresidual) > fabs(m_timeCut[0])) continue;
209 }
210 m_cellIdToSeedVec[cellid] = 1;
211 B2DEBUG(250, "ECLCRFinderModule::event(), adding 'seed digit' cellid = " << cellid << " " << energy << " " << time << " " <<
212 timeresidual);
213 } // end seed
214 } //end growth
215 }// end digit
216 }//end filling maps
217
218 // we start with seed crystals A and attach all growth crystals B
219 std::vector<std::vector<int>> connectedRegions_AB = getConnectedRegions(m_cellIdToSeedVec, m_cellIdToGrowthVec, 0);
220 std::vector<int> connectedRegions_AB_flattened = flattenVector(connectedRegions_AB);
221 std::vector<int> AB = oneHotVector(connectedRegions_AB_flattened, m_cellIdToSeedVec.size());
222
223 // Check if any of the growth crystals could grow to other growth crystals
224 std::vector<std::vector<int>> connectedRegions_ABB = getConnectedRegions(AB, m_cellIdToGrowthVec, 0);
225 std::vector<int> connectedRegions_ABB_flattened = flattenVector(connectedRegions_ABB);
226 std::vector<int> ABB = oneHotVector(connectedRegions_ABB_flattened, AB.size());
227
228 // and finally: attach all normal digits
229 std::vector<std::vector<int>> connectedRegions_ABBC = getConnectedRegions(ABB, m_cellIdToDigitVec, 0);
230
231 //final step: merge all CRs that share at least one crystal
232 std::vector<std::set<int>> connectedRegionsMerged_ABBC_sets = mergeVectorsUsingBFSTraversal(connectedRegions_ABBC);
233
234 // Create CRs and add relations to digits.
235 unsigned int connectedRegionID = 0;
236 for (const auto& xcr : connectedRegionsMerged_ABBC_sets) {
237
238 // Append to store array
239 const auto aCR = m_eclConnectedRegions.appendNew();
240
241 // Set CR ID
242 aCR->setCRId(connectedRegionID);
243 connectedRegionID++;
244
245 // Add all digits
246 for (int x : xcr) {
247 const int pos = m_calDigitStoreArrPosition[x];
248 aCR->addRelationTo(m_eclCalDigits[pos], 1.0);
249 }
250 }
251
252}
253
255{
256 B2DEBUG(200, "ECLCRFinderModule::endRun()");
257}
258
259
261{
262 B2DEBUG(200, "ECLCRFinderModule::terminate()");
263 for (unsigned int i = 0; i < m_neighbourMaps.size(); i++) {
264 if (m_neighbourMaps[i]) delete m_neighbourMaps[i];
265 }
266
267}
268
269std::vector<int> ECLCRFinderModule::flattenVector(std::vector<std::vector<int>>& A)
270{
271 std::vector<int> C;
272 for (const auto& B : A) {
273 C.insert(C.end(), B.begin(), B.end());
274 }
275 std::sort(C.begin(), C.end());
276 C.erase(std::unique(C.begin(), C.end()), C.end());
277 return C;
278}
279
280std::vector<int> ECLCRFinderModule::oneHotVector(std::vector<int>& A, const int n)
281{
282 std::vector<int> C(n, 0);
283 for (int x : A) {
284 if (x >= 0 && x < n) {
285 C[x] = 1;
286 }
287 }
288 return C;
289}
290
291std::vector<std::set<int>> ECLCRFinderModule::mergeVectorsUsingBFSTraversal(std::vector<std::vector<int>>& A)
292{
293 std::vector<std::set<int>> output;
294
295 // 1. Data Structures for lookup
296 // We use a fixed-size adjacency list for all crystals (Max ID 8736)
297 std::vector<int> relevantNodes;
298 relevantNodes.reserve(A.size() * 4);
299
300 for (int node = 0; node < 8737; ++node) {
301 m_adj[node].clear();
302 m_visited[node] = false;
303 }
304
305 // 2. Build the Graph
306 // If a vector contains {1, 2, 3}, we mark them as connected neighbors.
307 for (const auto& group : A) {
308 if (group.empty()) continue;
309
310 int root = group[0];
311 relevantNodes.push_back(root);
312
313 for (size_t i = 1; i < group.size(); ++i) {
314 int neighbor = group[i];
315 relevantNodes.push_back(neighbor);
316
317 // Add bi-directional edges
318 m_adj[root].push_back(neighbor);
319 m_adj[neighbor].push_back(root);
320 }
321 }
322
323 // 3. Find Connected Components (BFS)
324 // We only iterate over nodes that actually appeared in the event.
325 for (int startNode : relevantNodes) {
326 if (m_visited[startNode]) continue;
327
328 // Start a new cluster
329 std::set<int> component;
330 std::vector<int> q; // BFS Queue
331
332 m_visited[startNode] = true;
333 q.push_back(startNode);
334 component.insert(startNode);
335
336 int head = 0;
337 while (head < static_cast<int>(q.size())) {
338 int u = q[head++];
339
340 for (int v : m_adj[u]) {
341 if (!m_visited[v]) {
342 m_visited[v] = true;
343 component.insert(v);
344 q.push_back(v);
345 }
346 }
347 }
348
349 // Move the finished component to output
350 output.push_back(std::move(component));
351 }
352
353 return output;
354}
355
356std::vector<std::vector<int>> ECLCRFinderModule::getConnectedRegions(const std::vector<int>& A, const std::vector<int>& B,
357 const int maptype)
358{
359 std::vector<std::vector<int>> connectedRegions;
360
361 // We iterate 0..8737 for the seeds (A), as A is a sparse vector mapped to cellID
362 for (unsigned int i = 0; i < A.size(); ++i) {
363 if (A[i] > 0) {
364 std::vector<int> region;
365 // Reserve with neighbor count to prevent reallocations
366 region.reserve(21);
367 region.push_back(i);
368
369 // Instead of looping j = 0 to 8737, we only loop over actual geometric neighbors.
370 const auto& neighbors = m_neighbourMaps[maptype]->getNeighbours(i);
371
372 for (int neighborID : neighbors) {
373 // We only care if this specific neighbor is active in vector B
374 if (neighborID < static_cast<int>(B.size()) && B[neighborID] > 0) {
375 region.push_back(neighborID);
376 }
377 }
378
379 std::sort(region.begin(), region.end());
380 region.erase(std::unique(region.begin(), region.end()), region.end());
381
382 connectedRegions.push_back(region);
383 }
384 }
385
386 return connectedRegions;
387}
std::vector< bool > m_visited
Vector to keep track in BFS.
double m_mapPar[2]
Parameters for neighbour maps.
virtual const char * eventLevelClusteringInfoName() const
Name to be used for default option: EventLevelClusteringInfo.
std::vector< int > m_cellIdToDigitVec
cellid -> above threshold digits.
virtual ~ECLCRFinderModule()
Destructor.
StoreArray< ECLConnectedRegion > m_eclConnectedRegions
Store array: ECLConnectedRegion.
double m_timeCut[3]
Time cut for seed, neighbours, ...
std::vector< int > oneHotVector(std::vector< int > &A, const int n)
Convert vector of cell ids to 0/1 vectors from 1-8737.
std::vector< int > m_cellIdToGrowthVec
cellid -> growth digits.
virtual void initialize() override
Initialize.
std::map< int, int > m_cellIdToTempCRIdMap
cellid -> temporary CR.
double m_energyCut[3]
Energy cut for seed, neighbours, ...
std::vector< int > m_cellIdToCheckVec
Digit vectors.
virtual void event() override
Event.
std::vector< int > m_cellIdToSeedVec
cellid -> seed digit.
virtual void endRun() override
End run.
std::vector< int > flattenVector(std::vector< std::vector< int > > &A)
Convert vector of vectors to one long vector.
virtual void terminate() override
Terminate (close ROOT files here if you have opened any).
std::vector< std::set< int > > mergeVectorsUsingBFSTraversal(std::vector< std::vector< int > > &A)
Find all lists of cell-ids that share at least one cell using Breadth First Search (BFS) graph traver...
std::vector< int > m_cellIdToTempCRIdVec
Connected Region map.
int m_skipFailedTimeFitDigits
Handling of digits with failed time fits.
std::vector< int > m_calDigitStoreArrPosition
vector (ECLElementNumbers::c_NCrystals + 1 entries) with cell id to store array positions
virtual void beginRun() override
Begin.
std::vector< std::vector< int > > getConnectedRegions(const std::vector< int > &A, const std::vector< int > &B, const int maptype)
Get all connected regions.
DBObjPtr< ECLClusteringParameters > m_eclClusteringParameters
ECLClusteringParameters payload for parameters.
bool m_useParametersFromDatabase
get energy and time cuts from payload
virtual const char * eclConnectedRegionArrayName() const
Name to be used for default option: ECLConnectedRegions.
std::vector< std::vector< int > > m_adj
Vectors for BFS search.
std::vector< ECL::ECLNeighbours * > m_neighbourMaps
Neighbour maps.
virtual const char * eclCalDigitArrayName() const
Name to be used for default or PureCsI option: ECLCalDigits.
std::string m_mapType[2]
Neighbour map types.
double m_timeCut_maxEnergy[3]
Time cut is only applied below this energy, ...
StoreObjPtr< EventLevelClusteringInfo > m_eventLevelClusteringInfo
Store object pointer: EventLevelClusteringInfo.
StoreArray< ECLCalDigit > m_eclCalDigits
Store array: ECLCalDigit.
Class to get the neighbours for a given cell id.
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
static const double MeV
[megaelectronvolt]
Definition Unit.h:114
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
Abstract base class for different kinds of events.