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