@@ -55,12 +55,6 @@ static GPUdi() Charge UpdateHIPTailFilter(Charge filteredCharge, Charge charge,
5555 return filteredCharge + alpha * (charge - filteredCharge);
5656}
5757
58- static GPUdi () float HIPTailTimeMean(const HIPTailDescriptor& tail)
59- {
60- const float length = tail.tailEnd > tail.tailStart ? float (tail.tailEnd - tail.tailStart ) : 1 .f ;
61- return tail.tailStart + 0 .5f * (length - 1 .f );
62- }
63-
6458static GPUdi () float HIPTailTimeVariance(const HIPTailDescriptor& tail)
6559{
6660 const float length = tail.tailEnd > tail.tailStart ? float (tail.tailEnd - tail.tailStart ) : 1 .f ;
@@ -111,12 +105,14 @@ static GPUdi() uint16_t CloseHIPTails(
111105 if (idx < GPUTPCCFHIPTailConnector::MaxHIPTailsPerRow) {
112106 hipTails[idx] = {0 , 0 , (uint16_t )iPadHandle,
113107 (uint16_t )acc.activeHIPTail .start , (uint16_t )acc.activeHIPTail .end ,
108+ acc.activeSatStart , acc.activeSatEnd ,
114109 0 .f , 0 .f };
115110 }
116111 }
117112
118113 acc.tailFilterCharge = 0 ;
119114 acc.activeHIPTail .Reset ();
115+ acc.activeSatStart = acc.activeSatEnd = -1 ;
120116 }
121117
122118 GPUbarrier ();
@@ -183,10 +179,26 @@ static GPUdi() void ScanCachedCharges(Kernel::GPUSharedMemory& smem, uint16_t ti
183179 }
184180
185181 if constexpr (CheckHIPTrigger) {
186- if (acc.HIPtb < 0 && qs >= Charge (Kernel::MaxADC)) {
182+ // Track saturated plateaus. A plateau continuing from the previous chunk always triggers there as well,
183+ // so tracking them only in chunks with a trigger is sufficient.
184+ const bool isSaturated = qs >= Charge (Kernel::MaxADC);
185+ if (isSaturated && !acc.plateauOpen ) {
186+ acc.plateauStart = curTB;
187+ }
188+ acc.plateauOpen = isSaturated;
189+ // Plateau of the active tail continues from a previous chunk
190+ if (isSaturated && acc.activeSatStart > -1 && acc.activeSatStart == acc.plateauStart ) {
191+ acc.activeSatEnd = curTB;
192+ }
193+
194+ if (acc.HIPtb < 0 && isSaturated) {
187195 acc.HIPtb = acc.aboveThresholdStart ; // start of rising edge, not first sat TB
196+ acc.satStart = acc.plateauStart ; // Plateau may have started in the previous chunk if it crosses the chunk boundary
188197 smem.tails [pad] = {acc.HIPtb , 0 }; // Broadcast HIP start TB to neighboring pads / threads
189198 }
199+ if (isSaturated && acc.satStart > -1 && acc.satStart == acc.plateauStart ) {
200+ acc.satEnd = curTB;
201+ }
190202 }
191203
192204 if constexpr (CheckHIPTailEnd) {
@@ -278,6 +290,10 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
278290 }
279291
280292 acc.HIPtb = -1 ;
293+ acc.satStart = acc.satEnd = -1 ;
294+ if (!hasHIPTrigger) {
295+ acc.plateauOpen = false ; // No saturated TB in this chunk
296+ }
281297
282298 if (handlePad) {
283299
@@ -323,11 +339,27 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
323339 }
324340
325341 bool shouldCloseTail = acc.HIPtb > -1 && acc.activeHIPTail .HasValue ();
326- if (shouldCloseTail && acc.activeHIPTail .IsOpen ()) {
342+ // End the old tail at the rising edge of the new trigger, also if the tail filter already ended it later in this chunk.
343+ // Otherwise the old tail would take the saturated samples of the new trigger.
344+ if (shouldCloseTail && (acc.activeHIPTail .IsOpen () || acc.activeHIPTail .end > acc.HIPtb )) {
327345 DPRINT (" %d: end = %d\n " , iThread, acc.HIPtb );
328346 acc.activeHIPTail .end = acc.HIPtb ;
329347 }
330348
349+ // Tails are closed at the rising edge of the new trigger, which can lie before the saturated plateau of the old tail.
350+ // The new tail inherits the plateau if it now contains saturated samples, the old one keeps it only if it still does.
351+ int16_t newSatStart = acc.satStart ;
352+ int16_t newSatEnd = acc.satEnd ;
353+ if (shouldCloseTail && acc.activeSatStart > -1 ) {
354+ if (acc.satStart < 0 && acc.activeSatEnd >= acc.activeHIPTail .end ) {
355+ newSatStart = acc.activeSatStart ;
356+ newSatEnd = acc.activeSatEnd ;
357+ }
358+ if (acc.activeSatStart >= acc.activeHIPTail .end ) {
359+ acc.activeSatStart = acc.activeSatEnd = -1 ;
360+ }
361+ }
362+
331363 CloseHIPTails (smem, clusterer, iThread, nThreads, iPadHandle, basePos, chargeMap, acc, shouldCloseTail);
332364
333365 GPUbarrier ();
@@ -336,6 +368,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineGPU(int32_t nBlocks, int32_t
336368 DPRINT (" %d: start = %d\n " , iThread, acc.HIPtb );
337369 acc.activeHIPTail .SetOpen (acc.HIPtb );
338370 acc.tailFilterCharge = Charge (MaxADC);
371+ acc.activeSatStart = newSatStart;
372+ acc.activeSatEnd = newSatEnd;
339373 }
340374
341375 // Clear smem between iterations to prevent stale entries
@@ -404,9 +438,15 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
404438
405439 std::vector<Short8> localHipTbV (nVecPads, -1 );
406440 std::vector<Short8> broadcastHipTbV (nVecPads, -1 );
441+ std::vector<Short8> localSatStartV (nVecPads, -1 ); // start of the saturated plateau that triggered in the current chunk, only set for pads with a local trigger
442+ std::vector<Short8> localSatEndV (nVecPads, -1 ); // end of that plateau as far as seen in the current chunk
443+ std::vector<Short8> plateauStartV (nVecPads, -1 ); // first TB of the current / last saturated plateau on this pad
444+ std::vector<Short8> plateauOpenV (nVecPads, 0 ); // 1 while the previous TB was saturated
407445 std::vector<Short8> aboveThresholdStartV (nVecPads, -1 );
408446 std::vector<Short8> activeHIPTailStartV (nVecPads, -1 );
409447 std::vector<Short8> activeHIPTailEndV (nVecPads, -1 );
448+ std::vector<Short8> activeHIPTailSatStartV (nVecPads, -1 ); // start of the saturated plateau that triggered the active tail, -1 if inherited from a neighbor
449+ std::vector<Short8> activeHIPTailSatEndV (nVecPads, -1 ); // end of that plateau, extended while the plateau continues into later chunks
410450 std::vector<Charge8> tailFilterChargeV (nVecPads, Charge8{Vc::Zero});
411451
412452 for (int16_t t = 0 ; t < fragment.length ; t += NumOfCachedTBs) {
@@ -422,6 +462,12 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
422462 auto maxCharge = maxChargeV[iVecPad];
423463
424464 auto hipTb = Short8 (-1 );
465+ auto satStart = Short8 (-1 );
466+ auto satEnd = Short8 (-1 );
467+ auto plateauStart = plateauStartV[iVecPad];
468+ auto plateauOpen = plateauOpenV[iVecPad];
469+ const auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
470+ auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
425471 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
426472 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
427473 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
@@ -458,12 +504,23 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
458504 aboveThresholdStart (startRisingEdge) = t + localtime;
459505 aboveThresholdStart (!aboveRisingEdge) = -1 ;
460506
461- const auto hasNewTrigger = hipTb < 0 && unpackedCharges >= Charge (MaxADC);
507+ // Track saturated plateaus across chunk boundaries
508+ const auto isSaturated = unpackedCharges >= Charge (MaxADC);
509+ plateauStart (isSaturated && plateauOpen < 1 ) = t + localtime;
510+ plateauOpen = 0 ;
511+ plateauOpen (isSaturated) = 1 ;
512+ // Plateau of the active tail continues from a previous chunk
513+ activeHIPTailSatEnd (isSaturated && activeHIPTailSatStart > -1 && (activeHIPTailSatStart - plateauStart) == 0 ) = t + localtime;
514+
515+ const auto hasNewTrigger = hipTb < 0 && isSaturated;
462516 hipTb (hasNewTrigger) = aboveThresholdStart;
517+ satStart (hasNewTrigger) = plateauStart; // Plateau may have started in the previous chunk if it crosses the chunk boundary
518+ satEnd (isSaturated && satStart > -1 && (satStart - plateauStart) == 0 ) = t + localtime;
463519 hasAnyTrigger |= hasNewTrigger.isNotEmpty ();
464520 } else {
465521 consecCharges = 0 ;
466522 aboveThresholdStart = -1 ;
523+ plateauOpen = 0 ;
467524 }
468525
469526 const auto tailOpen = activeHIPTailStart > -1 && activeHIPTailEnd < 0 ;
@@ -477,6 +534,11 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
477534 maxChargeV[iVecPad] = maxCharge;
478535
479536 localHipTbV[iVecPad] = hipTb;
537+ localSatStartV[iVecPad] = satStart;
538+ localSatEndV[iVecPad] = satEnd;
539+ plateauStartV[iVecPad] = plateauStart;
540+ plateauOpenV[iVecPad] = plateauOpen;
541+ activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
480542 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
481543 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
482544 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
@@ -522,13 +584,30 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
522584 for (int16_t iVecPad = 0 ; iVecPad < nVecPads && hasAnyTrigger; iVecPad++) {
523585
524586 auto hipTb = broadcastHipTbV[iVecPad];
587+ const auto satStart = localSatStartV[iVecPad];
588+ const auto satEnd = localSatEndV[iVecPad];
525589 auto aboveThresholdStart = aboveThresholdStartV[iVecPad];
526590 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
527591 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
592+ auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
593+ auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
528594 auto tailFilterCharge = tailFilterChargeV[iVecPad];
529595
530596 const auto shouldCloseTail = hipTb > -1 && activeHIPTailStart > -1 ;
531- activeHIPTailEnd (shouldCloseTail && activeHIPTailEnd < 0 ) = hipTb;
597+ // End the old tail at the rising edge of the new trigger, also if the tail filter already ended it later in this chunk.
598+ // Otherwise the old tail would take the saturated samples of the new trigger.
599+ activeHIPTailEnd (shouldCloseTail && !(activeHIPTailEnd >= 0 && (activeHIPTailEnd - hipTb) < 1 )) = hipTb;
600+
601+ // Tails are closed at the rising edge of the new trigger, which can lie before the saturated plateau of the old tail.
602+ // The new tail inherits the plateau if it now contains saturated samples, the old one keeps it only if it still does.
603+ auto newSatStart = satStart;
604+ auto newSatEnd = satEnd;
605+ const auto inheritSat = shouldCloseTail && satStart < 0 && activeHIPTailSatStart > -1 && (activeHIPTailSatEnd - activeHIPTailEnd) >= 0 ;
606+ newSatStart (inheritSat) = activeHIPTailSatStart;
607+ newSatEnd (inheritSat) = activeHIPTailSatEnd;
608+ const auto oldLosesSat = shouldCloseTail && activeHIPTailSatStart > -1 && (activeHIPTailSatStart - activeHIPTailEnd) >= 0 ;
609+ activeHIPTailSatStart (oldLosesSat) = -1 ;
610+ activeHIPTailSatEnd (oldLosesSat) = -1 ;
532611
533612 // Closing tails will store them to global memory and zero the range
534613 // So it's enough to disable this part to fully disable the tail filter
@@ -556,6 +635,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
556635 .pad = uint16_t (pad),
557636 .tailStart = uint16_t (activeHIPTailStart[p]),
558637 .tailEnd = uint16_t (activeHIPTailEnd[p]),
638+ .satStart = int16_t (activeHIPTailSatStart[p]),
639+ .satEnd = int16_t (activeHIPTailSatEnd[p]),
559640 .qTot = tailQtot,
560641 .qMax = tailQMax,
561642 };
@@ -568,11 +649,15 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
568649
569650 activeHIPTailStart (hipTb > -1 ) = hipTb;
570651 activeHIPTailEnd (hipTb > -1 ) = -1 ;
652+ activeHIPTailSatStart (hipTb > -1 ) = newSatStart;
653+ activeHIPTailSatEnd (hipTb > -1 ) = newSatEnd;
571654 tailFilterCharge (hipTb > -1 ) = MaxADC;
572655
573656 aboveThresholdStartV[iVecPad] = aboveThresholdStart;
574657 activeHIPTailStartV[iVecPad] = activeHIPTailStart;
575658 activeHIPTailEndV[iVecPad] = activeHIPTailEnd;
659+ activeHIPTailSatStartV[iVecPad] = activeHIPTailSatStart;
660+ activeHIPTailSatEndV[iVecPad] = activeHIPTailSatEnd;
576661 tailFilterChargeV[iVecPad] = tailFilterCharge;
577662
578663 } // for (int32_t iVecPad = 0; iVecPad < nVecPads; iVecPad++)
@@ -583,6 +668,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
583668
584669 auto activeHIPTailStart = activeHIPTailStartV[iVecPad];
585670 auto activeHIPTailEnd = activeHIPTailEndV[iVecPad];
671+ const auto activeHIPTailSatStart = activeHIPTailSatStartV[iVecPad];
672+ const auto activeHIPTailSatEnd = activeHIPTailSatEndV[iVecPad];
586673
587674 const auto shouldCloseTail = activeHIPTailStart > -1 ;
588675 activeHIPTailEnd (shouldCloseTail && activeHIPTailEnd < 0 ) = fragment.length ;
@@ -611,6 +698,8 @@ GPUd() void GPUTPCCFCheckPadBaseline::CheckBaselineCPU(int32_t nBlocks, int32_t
611698 .pad = uint16_t (pad),
612699 .tailStart = uint16_t (activeHIPTailStart[p]),
613700 .tailEnd = uint16_t (activeHIPTailEnd[p]),
701+ .satStart = int16_t (activeHIPTailSatStart[p]),
702+ .satEnd = int16_t (activeHIPTailSatEnd[p]),
614703 .qTot = tailQtot,
615704 .qMax = tailQMax,
616705 };
@@ -675,6 +764,10 @@ GPUd() void GPUTPCCFHIPTailConnector::Thread<0>(int32_t nBlocks, int32_t nThread
675764 return t1.tailStart < t2.tailStart ;
676765 } else if (t1.tailEnd != t2.tailEnd ) {
677766 return t1.tailEnd < t2.tailEnd ;
767+ } else if (t1.satStart != t2.satStart ) {
768+ return t1.satStart < t2.satStart ;
769+ } else if (t1.satEnd != t2.satEnd ) {
770+ return t1.satEnd < t2.satEnd ;
678771 } else if (t1.qTot != t2.qTot ) {
679772 return t1.qTot < t2.qTot ;
680773 } else {
@@ -747,20 +840,23 @@ GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads,
747840 float qMax = 0 ;
748841 float padSum = 0 ;
749842 float padSqSum = 0 ;
750- float timeSum = 0 ;
843+ float satTimeSum = 0 ;
844+ uint32_t nSatTails = 0 ;
751845 uint32_t tailStart = (uint32_t )-1 ;
752846 uint32_t tailEnd = 0 ;
753847
754848 // Zero-th element is empty tail
755849 for (; tail != tails; tail = &tails[tail->iNext ]) {
756850 const float tailWeight = tail->qTot ;
757851 const float tailPad = tail->pad ;
758- const float tailTime = HIPTailTimeMean (*tail);
759852 qMax = CAMath::Max (qMax, tail->qMax );
760853 qTot += tail->qTot ;
761854 padSum += tailWeight * tailPad;
762855 padSqSum += tailWeight * tailPad * tailPad;
763- timeSum += tailWeight * tailTime;
856+ if (tail->satStart >= 0 && tail->satEnd >= 0 ) {
857+ satTimeSum += 0 .5f * (tail->satStart + tail->satEnd );
858+ nSatTails++;
859+ }
764860 tailStart = CAMath::Min<uint32_t >(tailStart, tail->tailStart );
765861 tailEnd = CAMath::Max<uint32_t >(tailEnd, tail->tailEnd );
766862
@@ -769,20 +865,24 @@ GPUd() void GPUTPCCFHIPClusterizer::Thread<0>(int32_t nBlocks, int32_t nThreads,
769865
770866 const float weightSum = CAMath::Max (qTot, 1 .f );
771867 const float padMean = padSum / weightSum;
772- const float timeMean = timeSum / weightSum; // TODO: Use timebin of saturated signal instead! Time mean is biased for long tails.
773868 const float padSigma = CAMath::Sqrt (CAMath::Max (0 .f , padSqSum / weightSum - padMean * padMean));
774869
775870 tpc::ClusterNative cn;
776871 cn.qMax = qMax;
777872 cn.setSaturatedQtot (qTot);
778873 cn.setSaturatedTailLength (tailEnd - tailStart);
779- float clusterTime = fragment.start + timeMean - clusterer.Param ().rec .tpc .clustersShiftTimebinsClusterizer ;
780- cn.setTimeFlags (clusterTime, 0 );
781874 cn.setPad (padMean);
782875 cn.setSigmaPad (padSigma);
783876
784877 if (cn.qMax >= 1023 ) {
785878
879+ // Use the middle of the saturated plateau, averaged over all tails of the cluster that were triggered by saturation on their own pad.
880+ // Computed only here: chains consisting only of tails inherited from neighboring pads have no saturated plateau,
881+ // but these never contain a saturated charge and are dropped by the qMax cut.
882+ const float clusterTime = fragment.start + satTimeSum / nSatTails - clusterer.Param ().rec .tpc .clustersShiftTimebinsClusterizer ;
883+ assert (!CAMath::IsNaN (clusterTime));
884+ cn.setTimeFlags (clusterTime, 0 );
885+
786886 uint32_t index;
787887
788888 if (!onlyMC) {
0 commit comments