CbmRoot
Loading...
Searching...
No Matches
Hitfinder.cxx
Go to the documentation of this file.
1/* Copyright (C) 2022 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
2 SPDX-License-Identifier: GPL-3.0-only
3 Authors: Felix Weiglhofer [committer], Kilian Hunold */
4
5#include "Hitfinder.h"
6
7using namespace cbm::algo;
8
9// Export Constants
11
12
13// Kernel Definitions
15XPU_D void sts::SortDigis::operator()(context& ctx) { ctx.cmem<sts::TheHitfinder>().SortDigisInSpaceAndTime(ctx); }
16
18XPU_D void sts::FindClusters::operator()(context& ctx) { ctx.cmem<sts::TheHitfinder>().FindClustersSingleStep(ctx); }
19
22{
23 ctx.cmem<sts::TheHitfinder>().CalculateOffsetsParallel(ctx);
24}
25
28{
29 ctx.cmem<sts::TheHitfinder>().FindClustersParallel(ctx);
30}
31
34{
35 ctx.cmem<sts::TheHitfinder>().CalculateClustersParallel(ctx);
36}
37
40
42XPU_D void sts::FindHits::operator()(context& ctx) { ctx.cmem<sts::TheHitfinder>().FindHits(ctx); }
43
53{
54 int iModule = ctx.block_idx_x();
55 CbmStsDigi* digis = &digisPerModule[digiOffsetPerModule[iModule]];
56 CbmStsDigi* digisTmp = &digisPerModuleTmp[digiOffsetPerModule[iModule]];
57 int nDigis = GetNDigis(iModule);
58
59 SortDigis::sort_t digiSort(ctx.pos(), ctx.smem());
60
61 CbmStsDigi* digisOut = digiSort.sort(digis, nDigis, digisTmp, [](const CbmStsDigi a) {
62 return ((unsigned long int) a.GetChannel()) << 32 | (unsigned long int) (a.GetTimeU32());
63 });
64
65 if (digisOut == digisTmp) {
66 // Copy back to digis
67 for (int i = ctx.thread_idx_x(); i < nDigis; i += ctx.block_dim_x()) {
68 digis[i] = digisTmp[i];
69 }
70 }
71}
72
74{
75 CalculateOffsetsParallel(ctx);
76 xpu::barrier(ctx.pos());
77 FindClustersParallel(ctx);
78 xpu::barrier(ctx.pos());
79 CalculateClustersParallel(ctx);
80}
81
93 unsigned int* channelOffsets, unsigned int const nDigis) const
94{
95 channelOffsets[0] = 0;
96
97 for (unsigned int pos = ctx.thread_idx_x(); pos < nDigis - 1; pos += (unsigned int) ctx.block_dim_x()) {
98 auto const currChannel = digis[pos].GetChannel();
99 auto const nextChannel = digis[pos + 1].GetChannel();
100 if (currChannel != nextChannel) {
101 for (int i = currChannel + 1; i <= nextChannel; i++) {
102 channelOffsets[i] = pos + 1;
103 }
104 }
105 }
106
107 for (int c = digis[nDigis - 1].GetChannel() + 1; c < nChannels; c++) {
108 channelOffsets[c] = nDigis;
109 }
110}
111
121{
122 int const iModule = ctx.block_idx_x();
123 CbmStsDigi* digis = &digisPerModule[digiOffsetPerModule[iModule]];
124 auto const nDigis = GetNDigis(iModule);
125
126 if (nDigis == 0) return;
127
128 auto* channelOffsets = &channelOffsetPerModule[iModule * nChannels];
129
130 CalculateChannelOffsets(ctx, digis, channelOffsets, nDigis);
131}
132
142{
143 const i32 iGThread = ctx.block_dim_x() * ctx.block_idx_x() + ctx.thread_idx_x();
144 const i32 nDigisTotal = digiOffsetPerModule[2 * nModules];
145 if (iGThread >= nDigisTotal) {
146 return;
147 };
148
149 i32 iModule = 0;
150 while (size_t(iGThread) >= digiOffsetPerModule[iModule + 1]) {
151 iModule++;
152 }
153
154 i32 iLocal = iGThread - digiOffsetPerModule[iModule];
155
156 const CbmStsDigi myDigi = digisPerModule[iGThread];
157
158 u32 nDigis = GetNDigis(iModule);
159 if (nDigis == 0) return;
160
161 xpu::view digis(&digisPerModule[digiOffsetPerModule[iModule]], nDigis);
162 xpu::view digiConnector(&digiConnectorsPerModule[digiOffsetPerModule[iModule]], nDigis);
163 xpu::view channelOffsets(&channelOffsetPerModule[iModule * nChannels], nChannels);
164
165 u16 channel = myDigi.GetChannel();
166 if (channel == nChannels - 1) return;
167
168 //DeltaCalculation
169 const f32 tResol = GetTimeResolution(iModule, channel);
170 const RecoParams::STS& params = ctx.cmem<Params>().sts;
171 const f32 deltaT = (params.timeCutDigiAbs > 0.f ? params.timeCutDigiAbs : params.timeCutDigiSig * 1.4142f * tResol);
172
173 const u32 nextChannelEnd = (channel + 2 < nChannels) ? channelOffsets[channel + 2] : nDigis;
174
175 // Binary search for the first possible digi
176 u32 start = channelOffsets[channel + 1];
177 u32 end = nextChannelEnd;
178 while (start < end) {
179 u32 mid = (start + end) / 2;
180 real timeDiffDigis = GetTimeDiff(myDigi, digis[mid]);
181 if (deltaT < timeDiffDigis) {
182 start = mid + 1;
183 }
184 else {
185 end = mid;
186 }
187 }
188
189 for (auto nextIter = start; nextIter < nextChannelEnd; nextIter++) {
190
191 const CbmStsDigi otherDigi = digis[nextIter];
192
193 // This should never happen?
194 if (myDigi.GetChannel() >= otherDigi.GetChannel()) continue;
195
196 real timeDiffDigis = xpu::abs(GetTimeDiff(myDigi, otherDigi));
197 if (deltaT < timeDiffDigis) {
198 break;
199 }
200
201 // How to handle noise? Digis in same channel within a small time window of the previous digi are discarded by old cluster finder
202
203 // How to handle if multiple digis try to connect to the same digi?
204 digiConnector[iLocal].SetNext(nextIter);
205 digiConnector[nextIter].SetHasPrevious(true);
206 break;
207 }
208}
209
219{
220 const int nModuleSides = 2 * nModules;
221
222 int iModule = 0;
223 int iThread = ctx.block_dim_x() * ctx.block_idx_x() + ctx.thread_idx_x();
224
225 for (; iModule < nModuleSides; iModule++) {
226 i32 nDigis = GetNDigis(iModule);
227 if (iThread < nDigis) {
228 break;
229 }
230 iThread -= nDigis;
231 }
232
233 if (iModule >= nModuleSides) {
234 return;
235 }
236
237 const CbmStsDigi* digis = &digisPerModule[digiOffsetPerModule[iModule]];
238 auto* digiConnector = &digiConnectorsPerModule[digiOffsetPerModule[iModule]];
239
240 // Local index of digi in sensor
241 const int iDigi = iThread;
242
243 if (digiConnector[iDigi].HasPrevious()) {
244 return;
245 }
246
247 if (!digiConnector[iDigi].HasNext()) {
248 // Cluster has 1 element
249 CreateClusterFromConnectors1(iModule, digis, iDigi);
250 }
251 else if (!digiConnector[digiConnector[iDigi].next()].HasNext()) {
252 // Cluster has 2 elements
253 CreateClusterFromConnectors2(iModule, digis, digiConnector, iDigi);
254 }
255 else {
256 // Cluster has >2 elements
257 CreateClusterFromConnectorsN(iModule, digis, digiConnector, iDigi);
258 }
259}
260
283 sts::DigiConnector* digiConnector, unsigned int const nDigis) const
284{
285 int const iModule = ctx.block_idx_x();
286 for (unsigned int currIter = ctx.thread_idx_x(); currIter < nDigis; currIter += (unsigned int) ctx.block_dim_x()) {
287
288 if (digiConnector[currIter].HasPrevious()) continue;
289 }
290}
291
292XPU_D void sts::Hitfinder::CreateClusterFromConnectors1(int const iModule, const CbmStsDigi* digis, int digiIndex) const
293{
294 const CbmStsDigi& digi = digis[digiIndex];
295
296 uint32_t time = digi.GetTimeU32();
297
298 const float timeError = asic.timeResolution;
299
300 sts::Cluster cluster{
301 .fCharge = asic.AdcToCharge(digi.GetChargeU16()),
302 .fSize = 1,
303 .fPosition = float(digi.GetChannel()) + (IsBackside(iModule) ? nChannels : 0.f),
304 .fPositionError = 1.f / xpu::sqrt(24.f),
305 .fTime = time,
306 .fTimeError = timeError,
307 };
308
309 SaveMaxError(timeError, iModule);
310
311 AddCluster(iModule, time, cluster);
312}
313
314XPU_D void sts::Hitfinder::CreateClusterFromConnectors2(int const iModule, const CbmStsDigi* digis,
315 sts::DigiConnector* digiConnector, int const digiIndex) const
316{
317
318 const CbmStsDigi& digi1 = digis[digiIndex];
319 const CbmStsDigi& digi2 = digis[digiConnector[digiIndex].next()];
320
321 float eNoiseSq = asic.noise * asic.noise;
322
323 float chargePerAdc = asic.dynamicRange / float(asic.nAdc);
324 float eDigitSq = chargePerAdc * chargePerAdc / 12.f;
325
326 float x1 = float(digi1.GetChannel());
327 float q1 = asic.AdcToCharge(digi1.GetChargeU16());
328 float q2 = asic.AdcToCharge(digi2.GetChargeU16());
329
330 // Periodic position for clusters round the edge
331 if (digi1.GetChannel() > digi2.GetChannel()) {
332 x1 -= float(nChannels);
333 }
334
335 // Uncertainties of the charge measurements
336 float width1 = LandauWidth(q1);
337 float eq1sq = width1 * width1 + eNoiseSq + eDigitSq;
338 float width2 = LandauWidth(q2);
339 float eq2sq = width2 * width2 + eNoiseSq + eDigitSq;
340
341 // Cluster time
342 float time = 0.5f * (digi1.GetTimeU32() + digi2.GetTimeU32());
343 float timeError = asic.timeResolution * 0.70710678f; // 1/sqrt(2)
344
345 // Cluster position
346 float x = x1 + 0.5f + (q2 - q1) / 3.f / xpu::max(q1, q2);
347
348 // Correct negative position for clusters around the edge
349 if (x < -0.5f) {
350 x += float(nChannels);
351 }
352
353 // Uncertainty on cluster position. See software note.
354 float ex0sq = 0.f;
355 float ex1sq = 0.f;
356 float ex2sq = 0.f;
357 if (q1 < q2) {
358 ex0sq = (q2 - q1) * (q2 - q1) / q2 / q2 / 72.f;
359 ex1sq = eq1sq / q2 / q2 / 9.f;
360 ex2sq = eq2sq * q1 * q1 / q2 / q2 / q2 / q2 / 9.f;
361 }
362 else {
363 ex0sq = (q2 - q1) * (q2 - q1) / q1 / q1 / 72.f;
364 ex1sq = eq1sq * q2 * q2 / q1 / q1 / q1 / q1 / 9.f;
365 ex2sq = eq2sq / q1 / q1 / 9.f;
366 }
367 float xError = xpu::sqrt(ex0sq + ex1sq + ex2sq);
368
369 // Cluster charge
370 float charge = q1 + q2;
371
372 if (IsBackside(iModule)) x += nChannels;
373
374 sts::Cluster cls{
375 .fCharge = charge,
376 .fSize = 2,
377 .fPosition = x,
378 .fPositionError = xError,
379 .fTime = uint32_t(time),
380 .fTimeError = timeError,
381 };
382
383 SaveMaxError(timeError, iModule);
384 AddCluster(iModule, time, cls);
385}
386
387XPU_D void sts::Hitfinder::CreateClusterFromConnectorsN(int iModule, const CbmStsDigi* digis,
388 sts::DigiConnector* digiConnector, int digiIndex) const
389{
391
392 //This Lambda calculates all charges for a single digi inside of a cluster
393 auto calculateClusterCharges = [this, &cProps, &digis, &digiConnector](int index) {
394 CbmStsDigi digi = digis[index];
395 float eNoiseSq = asic.noise * asic.noise;
396 float chargePerAdc = asic.dynamicRange / float(asic.nAdc);
397 float eDigitSq = chargePerAdc * chargePerAdc / 12.f;
398 cProps.tResolSum += asic.timeResolution;
399 cProps.tSum += digi.GetTimeU32();
400 float charge = asic.AdcToCharge(digi.GetChargeU16());
401 float lWidth = LandauWidth(charge);
402 float eChargeSq = lWidth * lWidth + eNoiseSq + eDigitSq;
403
404 if (!digiConnector[index].HasPrevious()) {
405 //begin of Cluster (most left Element)
406 cProps.chanF = digi.GetChannel();
407 cProps.qF = charge;
408 cProps.eqFsq = eChargeSq;
409 }
410 else if (!digiConnector[index].HasNext()) {
411 //end of Cluster (most right Element)
412 cProps.chanL = digi.GetChannel();
413 cProps.qL = charge;
414 cProps.eqLsq = eChargeSq;
415 }
416 else {
417 cProps.qM += charge;
418 cProps.eqMsq += eChargeSq;
419 }
420 cProps.xSum += charge * float(digi.GetChannel());
421 };
422
423 calculateClusterCharges(digiIndex);
424
425 // Calculate ClusterCharges
426 while (digiConnector[digiIndex].HasNext()) {
427 digiIndex = digiConnector[digiIndex].next();
428 calculateClusterCharges(digiIndex);
429 }
430
431 // Periodic channel position for clusters round the edge
432 if (cProps.chanF > cProps.chanL) cProps.chanF -= nChannels;
433
434 // Cluster time and total charge
435 float nDigis = ChanDist(cProps.chanF, cProps.chanL) + 1;
436 cProps.tSum = cProps.tSum / nDigis;
437 float timeError = (cProps.tResolSum / nDigis) / xpu::sqrt(nDigis);
438 float qSum = cProps.qF + cProps.qM + cProps.qL;
439
440 // Average charge in middle strips
441 cProps.qM /= (nDigis - 2.f);
442 cProps.eqMsq /= (nDigis - 2.f);
443
444 // Cluster position
445 float x = 0.5f * (float(cProps.chanF + cProps.chanL) + (cProps.qL - cProps.qF) / cProps.qM);
446
447 // Correct negative cluster position for clusters round the edge
448 if (x < -0.5f) {
449 x += float(nChannels);
450 }
451
452 // Cluster position error
453 float exFsq = cProps.eqFsq / cProps.qM / cProps.qM / 4.f; // error from first charge
454 float exMsq = cProps.eqMsq * (cProps.qL - cProps.qF) * (cProps.qL - cProps.qF) / cProps.qM / cProps.qM / cProps.qM
455 / cProps.qM / 4.f;
456 float exLsq = cProps.eqLsq / cProps.qM / cProps.qM / 4.f;
457 float xError = xpu::sqrt(exFsq + exMsq + exLsq);
458
459 // Correction for corrupt clusters
460 if (x < cProps.chanF || x > cProps.chanL) {
461 x = cProps.xSum / qSum;
462 }
463
464 if (IsBackside(iModule)) {
465 x += nChannels;
466 }
467
468 sts::Cluster cls{
469 .fCharge = qSum,
470 .fSize = int(nDigis),
471 .fPosition = x,
472 .fPositionError = xError,
473 .fTime = uint32_t(cProps.tSum),
474 .fTimeError = timeError,
475 };
476
477 SaveMaxError(timeError, iModule);
478 AddCluster(iModule, cProps.tSum, cls);
479}
480
482{
483 // By default sort all modules in parallel,
484 // but in debug mode sort only one module at a time
485 // to narrow down the error source
486 int firstModule = ctx.block_idx_x();
487 int lastModule = ctx.block_idx_x();
488 if (ctx.grid_dim_x() == 1) {
489 firstModule = 0;
490 lastModule = 2 * nModules - 1;
491 }
492
493 for (int iModule = firstModule; iModule <= lastModule; iModule++) {
494 size_t offset = iModule * maxClustersPerModule;
495 ClusterIdx* clusterIdx = &clusterIdxPerModule[offset];
496 ClusterIdx* clusterIdxTmp = &clusterIdxPerModuleTmp[offset];
497 int nClusters = nClustersPerModule[iModule];
498
499 // if (nClusters == 0 && ctx.thread_idx_x() == 0) {
500 // printf("Module %d: No clusters found, exit early\n", iModule);
501 // }
502 if (nClusters == 0) return;
503
504 // if (ctx.thread_idx_x() == 0) {
505 // printf("Module %d: Start sorting %d clusters\n", iModule, nClusters);
506 // }
507
508 SortClusters::sort_t clsSort(ctx.pos(), ctx.smem());
509 clusterIdx = clsSort.sort(clusterIdx, nClusters, clusterIdxTmp, [](const ClusterIdx a) { return a.fTime; });
510
511 // if (ctx.thread_idx_x() == 0) {
512 // printf("Module %d: Finished sorting %d clusters\n", iModule, nClusters);
513 // }
514
515 if (ctx.thread_idx_x() == 0) clusterIdxSortedPerModule[iModule] = clusterIdx;
516 }
517}
518
520{
521 int iModule = 0;
522 int iThread = ctx.block_dim_x() * ctx.block_idx_x() + ctx.thread_idx_x();
523
524#if XPU_IS_CPU
525 iModule = iThread / kFindHitsChunksPerModule;
526#else
527 for (; iModule < nModules; iModule++) {
528 if (iThread < nClustersPerModule[iModule]) break;
529 iThread -= nClustersPerModule[iModule];
530 }
531#endif
532
533 if (iModule >= nModules) {
534 return;
535 }
536
537 int iModuleF = iModule;
538 int iModuleB = iModuleF + nModules;
539 size_t offsetF = iModuleF * maxClustersPerModule;
540 size_t offsetB = iModuleB * maxClustersPerModule;
541 ClusterIdx* clusterIdxF = clusterIdxSortedPerModule[iModuleF];
542 ClusterIdx* clusterIdxB = clusterIdxSortedPerModule[iModuleB];
543 sts::Cluster* clusterDataF = &clusterDataPerModule[offsetF];
544 sts::Cluster* clusterDataB = &clusterDataPerModule[offsetB];
545 int nClustersF = nClustersPerModule[iModuleF];
546 int nClustersB = nClustersPerModule[iModuleB];
547 size_t nHitsWritten = nHitsPerModule[iModule];
548
549 // global parameters
550 const RecoParams::STS& params = ctx.cmem<Params>().sts;
551
552 const bool doChargeCorrelation = params.doChargeCorrelation;
553 const real ChargeDelta = params.chargeCorrelationDelta; // Allowed charge difference between clusters to create a hit
554
555
556 if (nClustersF == 0 || nClustersB == 0) {
557 return;
558 }
559
560 // Only used on CPU
561 [[maybe_unused]] int nClustersPerChunk = (nClustersF + kFindHitsChunksPerModule - 1) / kFindHitsChunksPerModule;
562 [[maybe_unused]] int iChunk = iThread % kFindHitsChunksPerModule;
563
564 int iClusterF =
565#if XPU_IS_CPU
566 iChunk * nClustersPerChunk;
567#else
568 iThread;
569#endif
570 if (iClusterF >= nClustersF) {
571 return;
572 }
573
574
575 // Stop processing if memory limits are exceed by a large amount
576 // In general we would still want to process all clusters to get an idea
577 // by how much memory estimates are wrong.
578 // However they are currently chosen very generous, so if they are exceeded by a large amount
579 // something must have gone awry. (e.g. monster events in the mCBM data that explode the hit finding combinatorics)
580 if (nHitsWritten > 2 * maxHitsPerModule) {
581 return;
582 }
583
584 HitfinderCache pars;
585 {
586 SensorPar cfg = sensorPars[iModule];
587
588 pars.dY = cfg.dY;
589 pars.pitch = cfg.pitch;
590 pars.stereoF = cfg.stereoF;
591 pars.stereoB = cfg.stereoB;
592 pars.lorentzF = cfg.lorentzF;
593 pars.lorentzB = cfg.lorentzB;
594 pars.tanStereoF = xpu::tan(pars.stereoF * xpu::deg_to_rad());
595 pars.tanStereoB = xpu::tan(pars.stereoB * xpu::deg_to_rad());
596 pars.errorFac = 1.f / (pars.tanStereoB - pars.tanStereoF) / (pars.tanStereoB - pars.tanStereoF);
597 pars.dX = float(nChannels) * pars.pitch;
598 }
599
600 float maxTerrF = maxClusterTimeErrorByModuleSide[iModuleF];
601 float maxTerrB = maxClusterTimeErrorByModuleSide[iModuleB];
602
603 float maxSigmaBoth = 4.f * xpu::sqrt(maxTerrF * maxTerrF + maxTerrB * maxTerrB);
604
605 int startB = 0;
606
607 // Use binary search to find the first cluster on back side that can be matched
608 // with the current cluster on front side
609 int start = startB;
610 int end = nClustersB;
611 while (end - start > 1) {
612 int mid = (start + end) / 2;
613 auto timeDiffCluster = GetTimeDiff(clusterIdxF[iClusterF], clusterIdxB[mid]);
614 if (maxSigmaBoth < timeDiffCluster) {
615 start = mid;
616 }
617 else {
618 end = mid;
619 }
620 }
621 startB = start;
622
623 int endClusterF =
624#if XPU_IS_CPU
625 xpu::min(iClusterF + nClustersPerChunk, nClustersF);
626#else
627 iClusterF + 1;
628#endif
629
630 for (; iClusterF < endClusterF; iClusterF++) {
631 ClusterIdx clsIdxF = clusterIdxF[iClusterF];
632 sts::Cluster clsDataF = clusterDataF[clsIdxF.fIdx];
633
634 float maxSigma = 4.f * xpu::sqrt(clsDataF.fTimeError * clsDataF.fTimeError + maxTerrB * maxTerrB);
635
636 for (int iClusterB = startB; iClusterB < nClustersB; iClusterB++) {
637 ClusterIdx clsIdxB = clusterIdxB[iClusterB];
638 sts::Cluster clsDataB = clusterDataB[clsIdxB.fIdx];
639
640 auto timeDiff = GetTimeDiff(clsIdxF, clsIdxB);
641
642 if (timeDiff > 0 && timeDiff > maxSigmaBoth) {
643 startB++;
644 continue;
645 }
646 else if (timeDiff > 0 && timeDiff > maxSigma) {
647 continue;
648 }
649 else if (timeDiff < 0 && xpu::abs(timeDiff) > maxSigma) {
650 break;
651 }
652
653 // Critical to check for charge correlation AFTER time difference
654 // as time difference advances cluster index on back side
655 // reducing the combinatorics drastically
656 if (doChargeCorrelation) {
657 float chargeDiff = clsDataF.fCharge - clsDataB.fCharge;
658 if (xpu::abs(chargeDiff) > ChargeDelta) {
659 continue;
660 }
661 }
662
663 float timeCut = -1.f;
664 if (params.timeCutClusterAbs > 0.f)
665 timeCut = params.timeCutClusterAbs;
666 else if (params.timeCutClusterSig > 0.f) {
667 float eF = clsDataF.fTimeError;
668 float eB = clsDataB.fTimeError;
669 timeCut = params.timeCutClusterSig * xpu::sqrt(eF * eF + eB * eB);
670 }
671
672 if (xpu::abs(timeDiff) > timeCut) {
673 continue;
674 }
675
676 IntersectClusters(iModule, pars, clsIdxF, clsDataF, clsIdxB, clsDataB);
677 } // end loop over clusters on back side
678 } // end loop over clusters on front side
679 // clang-format on
680}
681
682XPU_D void sts::Hitfinder::IntersectClusters(int iBlock, const HitfinderCache& pars, const ClusterIdx& idxF,
683 const sts::Cluster& clsF, const ClusterIdx& idxB,
684 const sts::Cluster& clsB) const
685{
686 // --- Calculate cluster centre position on readout edge
687
688 float xF = GetClusterPosition(pars, clsF.fPosition, true);
689 float exF = clsF.fPositionError * pars.pitch;
690 float du = exF * xpu::cos(xpu::deg_to_rad() * pars.stereoF);
691 float xB = GetClusterPosition(pars, clsB.fPosition, false);
692 float exB = clsB.fPositionError * pars.pitch;
693 float dv = exB * xpu::cos(xpu::deg_to_rad() * pars.stereoB);
694
695 // // --- Should be inside active area
696 if (xF < 0.f || xF > pars.dX || xB < 0.f || xB > pars.dX) {
697 return;
698 }
699
700 // // --- Calculate number of line segments due to horizontal
701 // // --- cross-connection. If x(y=0) does not fall on the bottom edge,
702 // // --- the strip is connected to the one corresponding to the line
703 // // --- with top edge coordinate xF' = xF +/- Dx. For odd combinations
704 // // --- of stereo angle and sensor dimensions, this could even happen
705 // // --- multiple times. For each of these lines, the intersection with
706 // // --- the line on the other side is calculated. If inside the active area,
707 // // --- a hit is created.
708 int nF = (xF + pars.dY * pars.tanStereoF) / pars.dX;
709 int nB = (xB + pars.dY * pars.tanStereoB) / pars.dX;
710
711 // --- If n is positive, all lines from 0 to n must be considered,
712 // --- if it is negative (phi negative), all lines from n to 0.
713 int nF1 = xpu::min(0, nF);
714 int nF2 = xpu::max(0, nF);
715 int nB1 = xpu::min(0, nB);
716 int nB2 = xpu::max(0, nB);
717
718 // --- Double loop over possible lines
719 float xC = -1.f;
720 float yC = -1.f;
721 float varX = 0.f;
722 float varY = 0.f;
723 float varXY = 0.f;
724 for (int iF = nF1; iF <= nF2; iF++) {
725 float xFi = xF - float(iF) * pars.dX;
726 for (int iB = nB1; iB <= nB2; iB++) {
727 float xBi = xB - float(iB) * pars.dX;
728
729 // --- Intersect the two lines
730 bool found = Intersect(pars, xFi, exF, xBi, exB, xC, yC, varX, varY, varXY);
731 if (not found) continue;
732
733 // --- Transform into sensor system with origin at sensor centre
734
735 xC -= 0.5f * pars.dX;
736 yC -= 0.5f * pars.dY;
737
738 CreateHit(iBlock, xC, yC, varX, varY, varXY, idxF, clsF, idxB, clsB, du, dv);
739 }
740 }
741}
742
743XPU_D float sts::Hitfinder::GetClusterPosition(const HitfinderCache& pars, float centre, bool isFront) const
744{
745 // Take integer channel
746
747 int iChannel = int(centre);
748 float xDif = centre - float(iChannel);
749
750 // Calculate corresponding strip on sensor
751
752 int iStrip = iChannel - (isFront ? 0 : nChannels);
753
754 // Re-add difference to integer channel. Convert channel to
755 // coordinate
756 float xCluster = (float(iStrip) + xDif + 0.5f) * pars.pitch;
757
758 // // Correct for Lorentz-Shift
759 // // Simplification: The correction uses only the y component of the
760 // // magnetic field. The shift is calculated using the mid-plane of the
761 // // sensor, which is not correct for tracks not traversing the entire
762 // // sensor thickness (i.e., are created or stopped somewhere in the sensor).
763 // // However, this is the best one can do in reconstruction.
764 xCluster -= (isFront ? pars.lorentzF : pars.lorentzB);
765
766 return xCluster;
767}
768
769XPU_D bool sts::Hitfinder::Intersect(const HitfinderCache& pars, float xF, float exF, float xB, float exB, float& x,
770 float& y, float& varX, float& varY, float& varXY) const
771{
772
773 // In the coordinate system with origin at the bottom left corner,
774 // a line along the strips with coordinate x0 at the top edge is
775 // given by the function y(x) = Dy - ( x - x0 ) / tan(phi), if
776 // the stereo angle phi does not vanish. Two lines yF(x), yB(x) with top
777 // edge coordinates xF, xB intersect at
778 // x = ( tan(phiB)*xF - tan(phiF)*xB ) / (tan(phiB) - tan(phiF)
779 // y = Dy + ( xB - xF ) / ( tan(phiB) - tan(phiF) )
780 // For the case that one of the stereo angles vanish (vertical strips),
781 // the calculation of the intersection is straightforward.
782
783 // --- First check whether stereo angles are different. Else there is
784 // --- no intersection.
785 // TODO: if this is true, no hits can be found? So just do this once at the beginning?
786 if (xpu::abs(pars.stereoF - pars.stereoB) < 0.5f) return false;
787
788 // --- Now treat vertical front strips
789 if (xpu::abs(pars.stereoF) < 0.001f) {
790 x = xF;
791 y = pars.dY - (xF - xB) / pars.tanStereoB;
792 varX = exF * exF;
793 varY = (exF * exF + exB * exB) / pars.tanStereoB / pars.tanStereoB;
794 varXY = -1.f * exF * exF / pars.tanStereoB;
795 return IsInside(pars, x - pars.dX / 2.f, y - pars.dY / 2.f);
796 }
797
798 // --- Maybe the back side has vertical strips?
799 if (xpu::abs(pars.stereoB) < 0.001f) {
800 x = xB;
801 y = pars.dY - (xB - xF) / pars.tanStereoF;
802 varX = exB * exB;
803 varY = (exF * exF + exB * exB) / pars.tanStereoF / pars.tanStereoF;
804 varXY = -1.f * exB * exB / pars.tanStereoF;
805 return IsInside(pars, x - pars.dX / 2.f, y - pars.dY / 2.f);
806 }
807
808 // --- OK, both sides have stereo angle
809
810 x = (pars.tanStereoB * xF - pars.tanStereoF * xB) / (pars.tanStereoB - pars.tanStereoF);
811 y = pars.dY + (xB - xF) / (pars.tanStereoB - pars.tanStereoF);
812 varX =
813 pars.errorFac * (exF * exF * pars.tanStereoB * pars.tanStereoB + exB * exB * pars.tanStereoF * pars.tanStereoF);
814 varY = pars.errorFac * (exF * exF + exB * exB);
815 varXY = -1.f * pars.errorFac * (exF * exF * pars.tanStereoB + exB * exB * pars.tanStereoF);
816
817 return IsInside(pars, x - pars.dX / 2.f, y - pars.dY / 2.f);
818}
819
820XPU_D bool sts::Hitfinder::IsInside(const HitfinderCache& pars, float x, float y) const
821{
822 // clang-format off
823 return x >= -pars.dX / 2.f
824 && x <= pars.dX / 2.f
825 && y >= -pars.dY / 2.f
826 && y <= pars.dY / 2.f;
827 // clang-format on
828}
829
830XPU_D void sts::Hitfinder::CreateHit(int iModule, float xLocal, float yLocal, float varX, float varY, float varXY,
831 const ClusterIdx& idxF, const Cluster& clsF, const ClusterIdx& idxB,
832 const Cluster& clsB, float du, float dv) const
833{
834 // --- Transform into global coordinate system
835 float globalX, globalY, globalZ;
836 ToGlobal(iModule, xLocal, yLocal, 0.f, globalX, globalY, globalZ);
837
838 // We assume here that the local-to-global transformations is only translation
839 // plus maybe rotation upside down or front-side back. In that case, the
840 // global covariance matrix is the same as the local one.
841 float errX = xpu::sqrt(varX);
842 float errY = xpu::sqrt(varY);
843
844 // --- Calculate hit time (average of cluster times)
845 float hitTime = 0.5f * (float(idxF.fTime) + float(idxB.fTime));
846 float etF = clsF.fTimeError;
847 float etB = clsB.fTimeError;
848 float hitTimeError = 0.5f * xpu::sqrt(etF * etF + etB * etB);
849
850 sts::Hit hit{
851 .fX = globalX,
852 .fY = globalY,
853 .fZ = globalZ,
854 .fTime = static_cast<u32>(hitTime),
855 .fDx = errX,
856 .fDy = errY,
857 .fDxy = varXY,
858 .fTimeError = hitTimeError,
859 .fDu = du,
860 .fDv = dv,
861
862 .fFrontClusterId = idxF.fIdx,
863 .fBackClusterId = idxB.fIdx,
864 };
865
866 int idx = xpu::atomic_add(&nHitsPerModule[iModule], 1);
867
868 if (size_t(idx) >= maxHitsPerModule) {
869 xpu::atomic_add(&monitor->nHitBucketOverflow, 1);
870 return;
871 }
872
873 hitsPerModule[iModule * hitsAllocatedPerModule + idx] = hit;
874}
875
876XPU_D float sts::Hitfinder::LandauWidth(float charge) const
877{
878 if (charge <= landauStepSize) return landauTable[0];
879 if (charge > landauStepSize * (landauTableSize - 1)) return landauTable[landauTableSize - 1];
880
881 int tableIdx = xpu::ceil(charge / landauStepSize);
882 float e2 = tableIdx * landauStepSize;
883 float v2 = landauTable[tableIdx];
884 tableIdx--;
885 float e1 = tableIdx * landauStepSize;
886 float v1 = landauTable[tableIdx];
887 return v1 + (charge - e1) * (v2 - v1) / (e2 - e1);
888}
889
890XPU_D void sts::Hitfinder::ToGlobal(int iModule, float lx, float ly, float lz, float& gx, float& gy, float& gz) const
891{
892 const float* tr = &localToGlobalTranslationByModule[iModule * 3];
893 const float* rot = &localToGlobalRotationByModule[iModule * 9];
894
895 gx = tr[0] + lx * rot[0] + ly * rot[1] + lz * rot[2];
896 gy = tr[1] + lx * rot[3] + ly * rot[4] + lz * rot[5];
897 gz = tr[2] + lx * rot[6] + ly * rot[7] + lz * rot[8];
898}
XPU_EXPORT(sts::TheHitfinder)
XPU_EXPORT(cbm::algo::Params)
Data class for a single-channel message in the STS.
Definition CbmStsDigi.h:40
XPU_D uint16_t GetChannel() const
Channel number in module @value Channel number.
Definition CbmStsDigi.h:93
XPU_D uint16_t GetChargeU16() const
Definition CbmStsDigi.h:108
XPU_D uint32_t GetTimeU32() const
Definition CbmStsDigi.h:123
XPU_D unsigned int next() const
Definition Hitfinder.h:179
XPU_D void CreateClusterFromConnectors1(int const iModule, const CbmStsDigi *digis, int const digiIndex) const
XPU_D void CalculateChannelOffsets(FindClusters::context &ctx, CbmStsDigi *digis, unsigned int *channelOffsets, unsigned int nDigis) const
Definition Hitfinder.cxx:92
XPU_D void SortClusters(SortClusters::context &) const
XPU_D void CalculateOffsetsParallel(FindClusters::context &) const
XPU_D bool Intersect(const HitfinderCache &pars, float xF, float exF, float xB, float exB, float &x, float &y, float &varX, float &varY, float &varXY) const
XPU_D void FindClustersSingleStep(FindClusters::context &) const
Definition Hitfinder.cxx:73
XPU_D bool IsInside(const HitfinderCache &pars, float x, float y) const
XPU_D float LandauWidth(float charge) const
XPU_D void CreateClusterFromConnectorsN(int const iModule, const CbmStsDigi *digis, DigiConnector *digiConnector, int const digiIndex) const
XPU_D void CalculateClustersDigiWise(FindClusters::context &ctx, CbmStsDigi *digis, DigiConnector *digiConnector, unsigned int const nDigis) const
XPU_D void IntersectClusters(int iBlock, const HitfinderCache &pars, const ClusterIdx &idxF, const sts::Cluster &clsF, const ClusterIdx &idxB, const sts::Cluster &clsB) const
XPU_D void SortDigisInSpaceAndTime(SortDigis::context &) const
Definition Hitfinder.cxx:52
XPU_D void CalculateClustersParallel(FindClusters::context &) const
XPU_D void CreateClusterFromConnectors2(int const iModule, const CbmStsDigi *digis, DigiConnector *digiConnector, int const digiIndex) const
XPU_D void ToGlobal(int iModule, float lx, float ly, float lz, float &gx, float &gy, float &gz) const
XPU_D void CreateHit(int iBlocks, float xLocal, float yLocal, float varX, float varY, float varXY, const ClusterIdx &idxF, const Cluster &clsF, const ClusterIdx &idxB, const sts::Cluster &clsB, float du, float dv) const
XPU_D void FindHits(FindHits::context &) const
XPU_D void FindClustersParallel(FindClusters::context &) const
XPU_D float GetClusterPosition(const HitfinderCache &pars, float centre, bool isFront) const
std::int32_t i32
Definition Definitions.h:20
float f32
Definition Definitions.h:24
std::uint32_t u32
Definition Definitions.h:21
@ kFindHitsChunksPerModule
Definition Hitfinder.h:40
std::uint16_t u16
Definition Definitions.h:19
XPU_D void operator()(context &)
Definition Hitfinder.cxx:21
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:81
u32 fTime
Cluster time (average of digi times) [ns].
Definition Hitfinder.h:53
real fTimeError
Error of cluster time [ns].
real fPosition
Cluster centre in channel number units.
real fPositionError
Cluster centre error (r.m.s.) in channel number units.
real fCharge
Total charge.
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:95
XPU_D void operator()(context &)
Definition Hitfinder.cxx:33
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:88
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:74
XPU_D void operator()(context &)
Definition Hitfinder.cxx:18
XPU_D void operator()(context &)
Definition Hitfinder.cxx:42
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:111
XPU_D void operator()(context &)
Definition Hitfinder.cxx:39
xpu::block_sort< u32, ClusterIdx, kSortClustersBlockSize, kSortClustersItemsPerThread > sort_t
Definition Hitfinder.h:101
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:104
XPU_D void operator()(context &)
Definition Hitfinder.cxx:15
xpu::block_sort< u64, CbmStsDigi, kSortDigisBlockSize, kSortDigisItemsPerThread > sort_t
Definition Hitfinder.h:64
xpu::kernel_context< shared_memory, constants > context
Definition Hitfinder.h:67