19#include <unordered_map>
23#include <Math/SMatrix.h>
24#include <Math/SVector.h>
37constexpr float TukeyC = 4.685f;
38constexpr float TukeyC2 = TukeyC * TukeyC;
39constexpr float InitialScale2 = 5.f;
40constexpr float MinScale2 = 1.f;
41constexpr float MedianToSigma = 1.4826f;
42constexpr float VertexShiftZTol = 0.01f;
43constexpr float VertexShiftR2Tol = 1.e-4f;
44constexpr int MaxFitIterations = 10;
45constexpr int MaxSeedsPerCluster = 32;
46constexpr float MinRelativePeakSupport = 0.1f;
47constexpr int MaxHistogramBins = 0x7fff;
48constexpr float TieTolerance = 1e-5f;
51 LineRef(
const Line& line,
const int index,
const float beamX,
const float beamY,
const float maxZ) :
lineIndex(
index)
53 const auto symTime =
line.mTime.makeSymmetrical();
54 tCenter = symTime.getTimeStamp();
56 const auto dx =
line.originPoint(0) - beamX;
57 const auto dy =
line.originPoint(1) - beamY;
58 const auto ux =
line.cosinesDirector(0);
59 const auto uy =
line.cosinesDirector(1);
60 const auto uz =
line.cosinesDirector(2);
61 const auto den = math_utils::SqSum(ux, uy);
62 if (den <= constants::Tolerance) {
66 const auto s0 = -((dx * ux) + (dy * uy)) / den;
67 const auto xb = dx + (
s0 * ux);
68 const auto yb = dy + (
s0 * uy);
70 if (!std::isfinite(zBeam) || o2::gpu::CAMath::Abs(zBeam) >
maxZ) {
74 bool isDead() const noexcept {
return lineIndex == constants::UnusedIndex; }
83 explicit VertexSeed(
const std::shared_ptr<BoundedMemoryResource>& mr) :
contributors(mr.get()),
assigned(mr.get()) {}
91 bool isUsableSeed() const noexcept
97void compactSeeds(bounded_vector<VertexSeed>& seeds)
99 seeds.erase(std::remove_if(seeds.begin(), seeds.end(), [](
const VertexSeed& seed) {
100 return !seed.isUsableSeed();
106 explicit Histogram2D(
const std::shared_ptr<BoundedMemoryResource>& mr) :
bins(mr.
get()) {}
116 int getIndex(
const int tBin,
const int zBin)
const noexcept
118 return (tBin *
nZBins) + zBin;
121 std::pair<int, int> decodeIndex(
const int index)
const noexcept
126 int getTimeBin(
const float time)
const noexcept
128 if (
time < timeMin) {
131 const auto bin =
static_cast<int>((
time -
timeMin) / timeBinSize);
132 return (bin >= 0 && bin < nTimeBins) ? bin : -1;
135 int getZBin(
const float z)
const noexcept
140 const auto bin =
static_cast<int>((
z -
zMin) / zBinSize);
141 return (bin >= 0 && bin < nZBins) ? bin : -1;
144 void fill(
const float time,
const float z,
const float weight)
noexcept
146 const auto tBin = getTimeBin(
time);
147 const auto zBin = getZBin(
z);
148 if (tBin < 0 || zBin < 0) {
154 int findPeakBin() const noexcept
156 float bestWeight = 0.f;
158 for (
int index = 0; index < static_cast<int>(
bins.size()); ++
index) {
159 if (bins[
index] > bestWeight) {
167 void suppressBin(
const int index)
noexcept
174 void suppressNeighborhood(
const int index,
const int radiusTime,
const int radiusZ)
noexcept
179 const auto [tBin, zBin] = decodeIndex(
index);
180 for (
int dt = -radiusTime; dt <= radiusTime; ++dt) {
181 const auto tt = tBin + dt;
182 if (tt < 0 || tt >= nTimeBins) {
185 for (
int dz = -radiusZ; dz <= radiusZ; ++dz) {
186 const auto zz = zBin + dz;
187 if (zz < 0 || zz >= nZBins) {
195 float getNeighborhoodSum(
const int index,
const int radiusTime,
const int radiusZ)
const noexcept
200 const auto [tBin, zBin] = decodeIndex(
index);
202 for (
int dt = -radiusTime; dt <= radiusTime; ++dt) {
203 const auto tt = tBin + dt;
204 if (tt < 0 || tt >= nTimeBins) {
207 for (
int dz = -radiusZ; dz <= radiusZ; ++dz) {
208 const auto zz = zBin + dz;
209 if (zz < 0 || zz >= nZBins) {
221 float getTimeBinCenter(
const int tBin)
const noexcept
226 float getZBinCenter(
const int zBin)
const noexcept
228 return zMin + ((
static_cast<float>(zBin) + 0.5f) *
zBinSize);
231 TimeEstBC getTimeInterval(
const int tBin)
const noexcept
233 const auto lowFloat =
timeMin + (
static_cast<float>(tBin) * timeBinSize);
235 const auto low = std::max<double>(0., std::floor(lowFloat));
236 const auto high = std::max(low + 1., (
double)std::ceil(highFloat));
237 constexpr auto maxTS = std::numeric_limits<TimeStampType>::max();
238 const auto clampedLow = std::min<double>(low, maxTS - 1.);
239 const auto width = std::min<double>(high - clampedLow, std::numeric_limits<TimeStampErrorType>::max());
243 TimeEstBC getTimeNeighborhoodInterval(
const int tBin,
const int radius)
const noexcept
245 const auto lowBin = std::max(0, tBin - radius);
246 const auto highBin = std::min(nTimeBins - 1, tBin + radius);
247 const auto lowFloat =
timeMin + (
static_cast<float>(lowBin) * timeBinSize);
248 const auto highFloat =
timeMin + (
static_cast<float>(highBin + 1) * timeBinSize);
249 const auto low = std::max<double>(0., std::floor(lowFloat));
250 const auto high = std::max(low + 1., (
double)std::ceil(highFloat));
251 constexpr auto maxTS = std::numeric_limits<TimeStampType>::max();
252 const auto clampedLow = std::min<double>(low, maxTS - 1.);
253 const auto width = std::min<double>(high - clampedLow, std::numeric_limits<TimeStampErrorType>::max());
261 SeedHistogram(std::span<const int>
members,
262 std::span<const LineRef> lineRefs,
263 std::span<const Line> lines,
264 const Settings& settings)
265 : mMembers(
members), mLineRefs(lineRefs), mSeedMemberRadiusTime(settings.seedMemberRadiusTime), mSeedMemberRadiusZ(settings.seedMemberRadiusZ), mMemoryPool(settings.memoryPool), mHistogram(mMemoryPool)
267 const auto zBinSize = 0.25f * settings.clusterCut;
270 float minZ = std::numeric_limits<float>::max();
271 float maxZ = std::numeric_limits<float>::lowest();
272 float minTime = std::numeric_limits<float>::max();
273 float maxTime = std::numeric_limits<float>::lowest();
274 for (
const auto lineRefIdx : mMembers) {
277 minTime = std::min(minTime, mLineRefs[lineRefIdx].
tCenter);
278 maxTime = std::max(maxTime, mLineRefs[lineRefIdx].
tCenter);
281 const auto dz = std::max(0.f,
maxZ -
minZ);
282 const auto dt = std::max(0.f, maxTime - minTime);
283 mHistogram.nZBins = 1 +
static_cast<int>(dz /
zBinSize);
284 mHistogram.nTimeBins = 1 +
static_cast<int>(dt /
timeBinSize);
285 if (mHistogram.nTimeBins * mHistogram.nZBins > MaxHistogramBins) {
286 if (mHistogram.nTimeBins > mHistogram.nZBins) {
287 mHistogram.nTimeBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nZBins));
289 mHistogram.nZBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nTimeBins));
293 mHistogram.timeBinSize = std::max(
timeBinSize, dt / (
float)std::max(1, mHistogram.nTimeBins));
294 mHistogram.zBinSize = std::max(
zBinSize, dz / (
float)std::max(1, mHistogram.nZBins));
295 const auto paddedTime = 0.5f * ((float)mHistogram.nTimeBins * mHistogram.timeBinSize - dt);
296 const auto paddedZ = 0.5f * ((float)mHistogram.nZBins * mHistogram.zBinSize - dz);
297 mHistogram.timeMin = minTime - paddedTime;
298 mHistogram.zMin =
minZ - paddedZ;
299 mHistogram.bins.assign((
size_t)mHistogram.nTimeBins * (size_t)mHistogram.nZBins, 0.f);
301 for (
const auto lineRefIdx : mMembers) {
302 mHistogram.fill(mLineRefs[lineRefIdx].
tCenter, mLineRefs[lineRefIdx].
zBeam, 1.f);
306 int findPeakBin() const noexcept
308 return mHistogram.findPeakBin();
311 float getPeakSupport(
const int peakIndex)
const noexcept
313 return mHistogram.getNeighborhoodSum(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ);
316 bounded_vector<int> collectLocalMembers(
const int peakIndex,
const int radiusTime,
const int radiusZ)
const
318 bounded_vector<int> localMembers(mMemoryPool.get());
319 localMembers.reserve(mMembers.size());
320 const auto [timeBin, zBin] = mHistogram.decodeIndex(peakIndex);
321 for (
const auto lineRefIdx : mMembers) {
322 const auto memberTimeBin = mHistogram.getTimeBin(mLineRefs[lineRefIdx].
tCenter);
323 const auto memberZBin = mHistogram.getZBin(mLineRefs[lineRefIdx].
zBeam);
324 if (memberTimeBin < 0 || memberZBin < 0) {
327 if (o2::gpu::GPUCommonMath::Abs(memberTimeBin - timeBin) > radiusTime) {
330 if (o2::gpu::GPUCommonMath::Abs(memberZBin - zBin) > radiusZ) {
333 localMembers.push_back(lineRefIdx);
338 TimeEstBC getPeakTimeInterval(
const int peakIndex,
const int radius = 0) const noexcept
340 return mHistogram.getTimeNeighborhoodInterval(mHistogram.decodeIndex(peakIndex).first, radius);
343 float getPeakZCenter(
const int peakIndex)
const noexcept
345 return mHistogram.getZBinCenter(mHistogram.decodeIndex(peakIndex).second);
348 void suppressPeak(
const int peakIndex)
noexcept
350 mHistogram.suppressBin(peakIndex);
353 void suppressPeakNeighborhood(
const int peakIndex)
noexcept
355 mHistogram.suppressNeighborhood(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ);
359 float medianTimeError(std::span<const Line> lines)
const
361 bounded_vector<float> errors(mMemoryPool.get());
362 errors.reserve(mMembers.size());
363 for (
const auto lineRefIdx : mMembers) {
364 errors.push_back(
static_cast<float>(lines[mLineRefs[lineRefIdx].
lineIndex].mTime.getTimeStampError()));
366 std::sort(errors.begin(), errors.end());
367 return errors.empty() ? 1.f : std::max(1.f, errors[errors.size() / 2]);
370 std::span<const int> mMembers;
371 std::span<const LineRef> mLineRefs;
372 int mSeedMemberRadiusTime = 1;
373 int mSeedMemberRadiusZ = 2;
374 std::shared_ptr<BoundedMemoryResource> mMemoryPool;
375 Histogram2D mHistogram;
378float updateScale2(
const std::span<const float> chi2s,
const std::shared_ptr<BoundedMemoryResource>& mr)
noexcept
384 bounded_vector<float> sorted(chi2s.begin(), chi2s.end(), mr.get());
385 std::sort(sorted.begin(), sorted.end());
386 const auto median = sorted[sorted.size() / 2];
388 for (
auto&
value : sorted) {
389 value = o2::gpu::GPUCommonMath::Abs(
value - median);
391 std::sort(sorted.begin(), sorted.end());
392 const auto mad = sorted[sorted.size() / 2];
393 if (!std::isfinite(mad) || mad <= constants::Tolerance) {
396 return std::max(MinScale2, MedianToSigma * mad);
402 void add(
const Line& line,
const float weight)
noexcept
404 const auto& direction =
line.cosinesDirector;
406 const auto det = ROOT::Math::Dot(direction, direction);
407 if (det <= constants::Tolerance) {
411 for (
int i = 0;
i < 3; ++
i) {
412 for (
int j =
i;
j < 3; ++
j) {
413 mMatrix(
i,
j) +=
weight * (((
i ==
j ? det : 0.f) - direction(
i) * direction(
j)) / det);
417 const auto dDotO = ROOT::Math::Dot(direction,
origin);
418 for (
int i = 0;
i < 3; ++
i) {
423 bool solve(std::array<float, 3>& vertexOut)
const noexcept
425 SymMatrix3 inv{mMatrix};
426 if (!inv.InvertFast()) {
429 const auto solution = inv * mRhs;
430 vertexOut[0] =
static_cast<float>(-solution(0));
431 vertexOut[1] =
static_cast<float>(-solution(1));
432 vertexOut[2] =
static_cast<float>(-solution(2));
433 return std::isfinite(vertexOut[0]) && std::isfinite(vertexOut[1]) && std::isfinite(vertexOut[2]);
441VertexSeed fitSeed(
const VertexSeed& initialSeed,
443 std::span<const LineRef> lineRefs,
444 std::span<const Line> lines,
445 const std::shared_ptr<BoundedMemoryResource>& mr,
446 const float pairCut2)
449 seed.vertex = initialSeed.vertex;
450 seed.time = initialSeed.time;
451 seed.scale2 = initialSeed.scale2;
453 seed.contributors.clear();
454 seed.assigned.clear();
459 for (
int iteration = 0; iteration < MaxFitIterations; ++iteration) {
461 TimeEstBC commonTime{};
462 bool hasCommonTime =
false;
464 const auto scale2 = std::max(seed.scale2, MinScale2);
465 const auto tukeyFactor = 1.f / (
scale2 * TukeyC2);
467 for (
const auto lineRefIdx :
members) {
468 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
469 const auto&
line = lines[lineIdx];
470 if (!
line.mTime.isCompatible(seed.time)) {
473 if (hasCommonTime && !
line.mTime.isCompatible(commonTime)) {
478 auto weight = 1.f - (chi2 * tukeyFactor);
484 if (!hasCommonTime) {
485 commonTime =
line.mTime;
486 hasCommonTime =
true;
488 commonTime +=
line.mTime;
492 vertexFit.add(line,
weight);
501 std::array<float, 3> updatedVertex{};
502 if (!vertexFit.solve(updatedVertex)) {
506 const auto sameContributors =
contributors == seed.contributors;
507 const auto dz = o2::gpu::GPUCommonMath::Abs(updatedVertex[2] - seed.vertex[2]);
508 const auto oldR2 = (seed.vertex[0] * seed.vertex[0]) + (seed.vertex[1] * seed.vertex[1]);
509 const auto newR2 = (updatedVertex[0] * updatedVertex[0]) + (updatedVertex[1] * updatedVertex[1]);
510 const auto dr2 = o2::gpu::GPUCommonMath::Abs(newR2 - oldR2);
512 seed.vertex = updatedVertex;
513 seed.time = commonTime;
514 bounded_vector<float> updatedChi2s{mr.get()};
519 seed.scale2 = updateScale2(updatedChi2s, mr);
523 if (sameContributors && dz < VertexShiftZTol && dr2 < VertexShiftR2Tol) {
531size_t countSharedContributors(std::span<const int> lhs, std::span<const int> rhs)
noexcept
534 auto lhsIt =
lhs.begin();
535 auto rhsIt =
rhs.begin();
536 while (lhsIt !=
lhs.end() && rhsIt !=
rhs.end()) {
537 if (*lhsIt == *rhsIt) {
541 }
else if (*lhsIt < *rhsIt) {
550bounded_vector<int> collectCompatibleContributors(
const VertexSeed& seed,
552 std::span<const LineRef> lineRefs,
553 std::span<const Line> lines,
554 const std::shared_ptr<BoundedMemoryResource>& mr,
555 const float pairCut2)
559 for (
const auto lineRefIdx :
members) {
560 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
561 const auto&
line = lines[lineIdx];
562 if (!
line.mTime.isCompatible(seed.time)) {
574void deduplicateSeeds(bounded_vector<VertexSeed>& seeds,
const Settings& settings)
576 if (seeds.size() < 2) {
580 std::sort(seeds.begin(), seeds.end(), [](
const VertexSeed& lhs,
const VertexSeed& rhs) {
581 if (lhs.contributors.size() != rhs.contributors.size()) {
582 return lhs.contributors.size() > rhs.contributors.size();
584 if (o2::gpu::GPUCommonMath::Abs(
lhs.scale2 -
rhs.scale2) > constants::Tolerance) {
585 return lhs.scale2 < rhs.scale2;
587 return lhs.vertex[2] <
rhs.vertex[2];
590 const auto dedupZCut = settings.seedDedupZCut > 0.f ? settings.seedDedupZCut : 0.25f * settings.clusterCut;
591 for (
size_t i = 0;
i < seeds.size(); ++
i) {
592 auto& candidate = seeds[
i];
593 if (!candidate.isUsableSeed()) {
594 candidate.valid =
false;
597 bool duplicate =
false;
598 for (
size_t j = 0;
j <
i; ++
j) {
599 const auto& kept = seeds[
j];
600 if (!kept.isUsableSeed()) {
603 if (!candidate.time.isCompatible(kept.time)) {
606 const auto shared = countSharedContributors(candidate.contributors, kept.contributors);
607 const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size());
608 const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]);
609 const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() ||
610 kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2;
611 const bool overlapDuplicate = shared > 0 && shared * 2 >=
minSize;
612 const bool nearbyDuplicate = zDelta < dedupZCut && (shared > 0 || clearlyWorse);
613 if (overlapDuplicate || nearbyDuplicate) {
619 candidate.valid =
false;
625void deduplicateRefittedSeeds(bounded_vector<VertexSeed>& seeds,
const Settings& settings)
627 if (seeds.size() < 2) {
631 std::sort(seeds.begin(), seeds.end(), [](
const VertexSeed& lhs,
const VertexSeed& rhs) {
632 if (lhs.contributors.size() != rhs.contributors.size()) {
633 return lhs.contributors.size() > rhs.contributors.size();
635 if (o2::gpu::GPUCommonMath::Abs(
lhs.scale2 -
rhs.scale2) > constants::Tolerance) {
636 return lhs.scale2 < rhs.scale2;
638 return lhs.vertex[2] <
rhs.vertex[2];
641 const auto zCut = settings.refitDedupZCut > 0.f ? settings.refitDedupZCut : 0.25f * settings.clusterCut;
642 for (
size_t i = 0;
i < seeds.size(); ++
i) {
643 auto& candidate = seeds[
i];
644 if (!candidate.isUsableSeed()) {
645 candidate.valid =
false;
648 bool duplicate =
false;
649 for (
size_t j = 0;
j <
i; ++
j) {
650 const auto& kept = seeds[
j];
651 if (!kept.isUsableSeed()) {
654 if (!candidate.time.isCompatible(kept.time)) {
657 const auto shared = countSharedContributors(candidate.contributors, kept.contributors);
658 const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size());
659 const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]);
660 const bool overlapDuplicate = shared > 0 && shared * 2 >=
minSize;
661 const bool lowSupportPair = std::min(candidate.contributors.size(), kept.contributors.size()) < 4;
662 const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() ||
663 kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2;
664 const bool geometricDuplicate = zDelta < zCut && (lowSupportPair || clearlyWorse);
665 if (overlapDuplicate || geometricDuplicate) {
671 candidate.valid =
false;
677struct OrderedComponent {
678 explicit OrderedComponent(
const std::shared_ptr<BoundedMemoryResource>& mr) :
members(mr.
get()) {}
683bounded_vector<bounded_vector<int>> buildCoarseClusters(std::span<const LineRef> lineRefs,
684 std::span<const Line> lines,
685 const Settings& settings)
687 bounded_vector<bounded_vector<int>>
clusters(settings.memoryPool.get());
688 if (lineRefs.size() < 2) {
692 bounded_vector<int> sortedByLower(lineRefs.size(), settings.memoryPool.get());
693 std::iota(sortedByLower.begin(), sortedByLower.end(), 0);
694 std::sort(sortedByLower.begin(), sortedByLower.end(), [&](
const int lhs,
const int rhs) {
695 const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower();
696 const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower();
697 if (lhsLower != rhsLower) {
698 return lhsLower < rhsLower;
700 return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex;
703 const auto coarseZWindow = settings.coarseZWindow > 0.f ? settings.coarseZWindow : settings.clusterCut;
704 bounded_vector<int> parent(lineRefs.size(), settings.memoryPool.get());
705 bounded_vector<int> componentSize(lineRefs.size(), 1, settings.memoryPool.get());
706 std::iota(parent.begin(), parent.end(), 0);
707 float minZ = std::numeric_limits<float>::max();
708 float maxZ = std::numeric_limits<float>::lowest();
709 for (
const auto& lineRef : lineRefs) {
710 minZ = std::min(
minZ, lineRef.zBeam);
711 maxZ = std::max(
maxZ, lineRef.zBeam);
713 const auto nZBins = std::max(1, 1 +
static_cast<int>((
maxZ -
minZ) / coarseZWindow));
714 auto getZBin = [&](
const float z) {
715 return std::clamp(
static_cast<int>((
z -
minZ) / coarseZWindow), 0,
nZBins - 1);
718 auto findRoot = [&](
int idx) {
720 while (parent[root] != root) {
723 while (parent[idx] != idx) {
724 const auto next = parent[
idx];
731 auto unite = [&](
const int lhs,
const int rhs) {
732 auto lhsRoot = findRoot(lhs);
733 auto rhsRoot = findRoot(rhs);
734 if (lhsRoot == rhsRoot) {
737 if (componentSize[lhsRoot] < componentSize[rhsRoot]) {
738 std::swap(lhsRoot, rhsRoot);
740 parent[rhsRoot] = lhsRoot;
741 componentSize[lhsRoot] += componentSize[rhsRoot];
744 using ActiveEntry = std::pair<TimeStampType, int>;
745 bounded_vector<ActiveEntry> activeEntries(settings.memoryPool.get());
746 std::priority_queue<ActiveEntry, bounded_vector<ActiveEntry>, std::greater<ActiveEntry>> activeByUpper(std::greater<ActiveEntry>{}, std::move(activeEntries));
747 bounded_vector<uint8_t> activeMask(lineRefs.size(), 0, settings.memoryPool.get());
748 bounded_vector<bounded_vector<int>> activeByZBin(settings.memoryPool.get());
749 activeByZBin.reserve(
nZBins);
750 for (
int iBin = 0; iBin <
nZBins; ++iBin) {
751 activeByZBin.emplace_back();
753 for (
const auto lineRefIdx : sortedByLower) {
754 const auto& lineRef = lineRefs[lineRefIdx];
755 const auto&
line = lines[lineRef.lineIndex];
756 const auto currentLower =
line.mTime.lower();
758 while (!activeByUpper.empty() && activeByUpper.top().first < currentLower) {
759 activeMask[activeByUpper.top().second] = 0;
763 const auto zBin = getZBin(lineRef.zBeam);
764 for (
int neighborBin = std::max(0, zBin - 1); neighborBin <= std::min(
nZBins - 1, zBin + 1); ++neighborBin) {
765 auto& bucket = activeByZBin[neighborBin];
767 for (
size_t readPos = 0; readPos < bucket.size(); ++readPos) {
768 const auto oLineRefIdx = bucket[readPos];
769 if (!activeMask[oLineRefIdx]) {
772 bucket[writePos++] = oLineRefIdx;
773 const auto& oLineRef = lineRefs[oLineRefIdx];
774 if (o2::gpu::GPUCommonMath::Abs(lineRef.zBeam - oLineRef.zBeam) >= coarseZWindow) {
777 const auto& otherLine = lines[oLineRef.lineIndex];
778 if (
line.mTime.isCompatible(otherLine.mTime)) {
779 unite(lineRefIdx, oLineRefIdx);
782 bucket.resize(writePos);
785 activeMask[lineRefIdx] = 1;
786 activeByUpper.emplace(
line.mTime.upper(), lineRefIdx);
787 activeByZBin[zBin].push_back(lineRefIdx);
790 std::unordered_map<int, bounded_vector<int>>
components;
792 for (
int lineRefIdx = 0; lineRefIdx < static_cast<int>(lineRefs.size()); ++lineRefIdx) {
793 const auto root = findRoot(lineRefIdx);
794 auto [it, inserted] =
components.try_emplace(root, std::pmr::polymorphic_allocator<int>{settings.memoryPool.get()});
796 it->second.push_back(lineRefIdx);
799 bounded_vector<OrderedComponent> orderedComponents(settings.memoryPool.get());
806 std::sort(
members.begin(),
members.end(), [&](
const int lhs,
const int rhs) {
807 const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower();
808 const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower();
809 if (lhsLower != rhsLower) {
810 return lhsLower < rhsLower;
812 return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex;
814 orderedComponents.emplace_back(settings.memoryPool);
815 orderedComponents.back().center = lineRefs[
members.front()].tCenter;
816 orderedComponents.back().members = std::move(
members);
819 std::sort(orderedComponents.begin(), orderedComponents.end(), [](
const auto& lhs,
const auto& rhs) {
820 if (o2::gpu::GPUCommonMath::Abs(lhs.center - rhs.center) > TieTolerance) {
821 return lhs.center < rhs.center;
823 return lhs.members.front() <
rhs.members.front();
825 clusters.reserve(orderedComponents.size());
826 for (
auto& component : orderedComponents) {
827 clusters.push_back(std::move(component.members));
832bounded_vector<VertexSeed> buildSeeds(std::span<const int>
members,
833 std::span<const LineRef> lineRefs,
834 std::span<const Line> lines,
835 const Settings& settings)
837 SeedHistogram histogram(
members, lineRefs, lines, settings);
838 bounded_vector<VertexSeed> seeds(settings.memoryPool.get());
839 seeds.reserve(MaxSeedsPerCluster);
840 float leadingPeakSupport = 0.f;
842 while (
static_cast<int>(seeds.size()) < MaxSeedsPerCluster) {
843 const auto peak = histogram.findPeakBin();
847 const auto peakSupport = histogram.getPeakSupport(peak);
848 if (peakSupport < 2.f) {
851 if (leadingPeakSupport <= 0.f) {
852 leadingPeakSupport = peakSupport;
853 }
else if (peakSupport < std::max(2.f, MinRelativePeakSupport * leadingPeakSupport)) {
856 auto localMembers = histogram.collectLocalMembers(peak, 0, 0);
857 if (localMembers.size() < 2) {
858 localMembers = histogram.collectLocalMembers(peak, settings.seedMemberRadiusTime, settings.seedMemberRadiusZ);
860 if (localMembers.size() < 2) {
861 histogram.suppressPeak(peak);
865 VertexSeed seed(settings.memoryPool);
866 seed.vertex = {settings.beamX, settings.beamY, histogram.getPeakZCenter(peak)};
867 seed.time = histogram.getPeakTimeInterval(peak);
868 seed.scale2 = InitialScale2;
870 auto fitted = fitSeed(seed, localMembers, lineRefs, lines, settings.memoryPool, settings.pairCut2);
871 if (fitted.valid && fitted.contributors.size() >= 2) {
872 seeds.push_back(std::move(fitted));
873 histogram.suppressPeakNeighborhood(peak);
875 histogram.suppressPeak(peak);
882void assignLinesToSeeds(bounded_vector<VertexSeed>& seeds,
884 std::span<const LineRef> lineRefs,
885 std::span<const Line> lines,
886 const float pairCut2)
888 for (
auto& seed : seeds) {
889 seed.assigned.clear();
892 for (
const auto lineRefIdx :
members) {
893 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
894 const auto&
line = lines[lineIdx];
897 float bestScore = std::numeric_limits<float>::max();
899 float bestZResidual = std::numeric_limits<float>::max();
901 for (
int seedIdx = 0; seedIdx < static_cast<int>(seeds.size()); ++seedIdx) {
902 const auto& seed = seeds[seedIdx];
903 if (!seed.valid || seed.contributors.size() < 2) {
906 if (!
line.mTime.isCompatible(seed.time)) {
910 const auto distance2 = Line::getDistance2FromPoint(line, seed.vertex);
911 if (distance2 >= pairCut2) {
915 const auto score = distance2 / std::max(seed.scale2, MinScale2);
916 const auto zResidual = o2::gpu::GPUCommonMath::Abs(lineRefs[lineRefIdx].
zBeam - seed.vertex[2]);
917 const auto multiplicity = seed.contributors.size();
919 const auto betterScore = score + TieTolerance < bestScore;
920 const auto betterMultiplicity = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance && multiplicity > bestMult;
921 const auto betterZ = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance &&
922 multiplicity == bestMult && zResidual + constants::Tolerance < bestZResidual;
923 if (betterScore || betterMultiplicity || betterZ) {
926 bestMult = multiplicity;
927 bestZResidual = zResidual;
932 seeds[bestSeed].assigned.push_back(lineRefIdx);
937ClusterLines materializeCluster(
const VertexSeed& seed,
938 std::span<const LineRef> lineRefs,
939 std::span<const Line> lines,
940 const std::shared_ptr<BoundedMemoryResource>& mr)
942 bounded_vector<int> lineIndices{mr.get()};
943 lineIndices.reserve(seed.contributors.size());
945 lineIndices.push_back(lineRefs[lineRefIdx].
lineIndex);
947 std::sort(lineIndices.begin(), lineIndices.end());
948 lineIndices.erase(std::unique(lineIndices.begin(), lineIndices.end()), lineIndices.end());
950 if (lineIndices.size() < 2) {
954 return {std::span<const int>{lineIndices.data(), lineIndices.size()}, lines};
962 if (lines.size() < 2) {
967 refs.reserve(lines.size());
968 for (
int lineIdx = 0; lineIdx < static_cast<int>(lines.size()); ++lineIdx) {
969 LineRef
ref(lines[lineIdx], lineIdx, settings.
beamX, settings.
beamY, settings.
maxZ);
975 if (refs.size() < 2) {
979 const auto coarseClusters = buildCoarseClusters(refs, lines, settings);
981 for (
const auto&
members : coarseClusters) {
982 auto seeds = buildSeeds(
members, refs, lines, settings);
987 for (
auto& seed : seeds) {
988 if (!seed.isUsableSeed()) {
1000 if (seeds.empty()) {
1003 deduplicateSeeds(seeds, settings);
1004 if (seeds.empty()) {
1008 for (
auto& seed : seeds) {
1009 if (seed.assigned.size() < 2) {
1013 seed = fitSeed(seed, seed.assigned, refs, lines, settings.
memoryPool, settings.
pairCut2);
1014 if (!seed.isUsableSeed()) {
1019 compactSeeds(seeds);
1020 deduplicateRefittedSeeds(seeds, settings);
1021 for (
auto& refit : seeds) {
1022 auto cluster = materializeCluster(refit, refs, lines, settings.
memoryPool);
1023 if (cluster.getSize() < 2) {
1026 if (!cluster.isValid()) {
1029 clusters.push_back(std::move(cluster));
header::DataOrigin origin
bounded_vector< int > members
bounded_vector< int > assigned
bounded_vector< int > contributors
bounded_vector< float > bins
float sum(float s, o2::dcs::DataPointValue v)
GLuint GLuint GLfloat weight
GLint GLenum GLint components
GLsizei const GLfloat * value
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat maxZ
GLdouble GLdouble GLdouble z
GLuint GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat s0
int32_t const char int32_t line
bounded_vector< ClusterLines > buildClusters(std::span< const Line > lines, const Settings &settings)
std::pmr::vector< T > bounded_vector
D const SVectorGPU< T, D > & rhs
if(!okForPhiMin(phi0, phi1))
R median(std::vector< T > v)
constexpr auto getIndex(const container_T &container, typename container_T::const_iterator iter) -> typename container_T::source_type
static float getDistance2FromPoint(const Line &line, const std::array< float, 3 > &point)
std::shared_ptr< BoundedMemoryResource > memoryPool
std::vector< Cluster > clusters