Project
Loading...
Searching...
No Matches
LineVertexerHelpers.cxx
Go to the documentation of this file.
1// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3// All rights not expressly granted are reserved.
4//
5// This software is distributed under the terms of the GNU General Public
6// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7//
8// In applying this license CERN does not waive the privileges and immunities
9// granted to it by virtue of its status as an Intergovernmental Organization
10// or submit itself to any jurisdiction.
11
12#include <algorithm>
13#include <array>
14#include <cmath>
15#include <cstdint>
16#include <limits>
17#include <numeric>
18#include <queue>
19#include <unordered_map>
20#include <utility>
21#include <vector>
22
23#include <Math/SMatrix.h>
24#include <Math/SVector.h>
25
28
30{
31namespace
32{
34using SVector3 = ROOT::Math::SVector<float, 3>;
35
36constexpr float TukeyC = 4.685f;
37constexpr float TukeyC2 = TukeyC * TukeyC;
38constexpr float InitialScale2 = 5.f;
39constexpr float MinScale2 = 1.f;
40constexpr float MedianToSigma = 1.4826f;
41constexpr float VertexShiftZTol = 0.01f;
42constexpr float VertexShiftR2Tol = 1.e-4f;
43constexpr int MaxFitIterations = 10;
44constexpr int MaxSeedsPerCluster = 32;
45constexpr float MinRelativePeakSupport = 0.1f;
46constexpr int MaxHistogramBins = 0x7fff;
47constexpr float TieTolerance = 1e-5f;
48
49struct LineRef {
50 LineRef(const Line& line, const int index, const float beamX, const float beamY, const float maxZ) : lineIndex(index)
51 {
52 const auto symTime = line.mTime.makeSymmetrical();
53 tCenter = symTime.getTimeStamp();
54 tHalfWidth = symTime.getTimeStampError();
55 if (!Line::getZAtBeam(line, beamX, beamY, zBeam)) {
57 return;
58 }
59 if (!std::isfinite(zBeam) || o2::gpu::CAMath::Abs(zBeam) > maxZ) {
61 }
62 }
63 bool isDead() const noexcept { return lineIndex == constants::UnusedIndex; }
64
66 float zBeam = 0.f;
67 float tCenter = 0.f;
68 float tHalfWidth = 0.f;
69};
70
71struct VertexSeed {
72 explicit VertexSeed(const std::shared_ptr<BoundedMemoryResource>& mr) : contributors(mr.get()), assigned(mr.get()) {}
73
74 std::array<float, 3> vertex = {};
75 TimeEstBC time;
76 float scale2 = InitialScale2;
77 bounded_vector<int> contributors;
78 bounded_vector<int> assigned;
79 bool valid = false;
80 bool isUsableSeed() const noexcept
81 {
82 return valid && contributors.size() >= 2;
83 }
84};
85
86void compactSeeds(bounded_vector<VertexSeed>& seeds)
87{
88 seeds.erase(std::remove_if(seeds.begin(), seeds.end(), [](const VertexSeed& seed) {
89 return !seed.isUsableSeed();
90 }),
91 seeds.end());
92}
93
94struct Histogram2D {
95 explicit Histogram2D(const std::shared_ptr<BoundedMemoryResource>& mr) : bins(mr.get()) {}
96
97 int nTimeBins = 0;
98 int nZBins = 0;
99 float timeMin = 0.f;
100 float zMin = 0.f;
101 float timeBinSize = 1.f;
102 float zBinSize = 1.f;
103 bounded_vector<float> bins;
104
105 int getIndex(const int tBin, const int zBin) const noexcept
106 {
107 return (tBin * nZBins) + zBin;
108 }
109
110 std::pair<int, int> decodeIndex(const int index) const noexcept
111 {
112 return {index / nZBins, index % nZBins};
113 }
114
115 int getTimeBin(const float time) const noexcept
116 {
117 if (time < timeMin) {
118 return -1;
119 }
120 const auto bin = static_cast<int>((time - timeMin) / timeBinSize);
121 return (bin >= 0 && bin < nTimeBins) ? bin : -1;
122 }
123
124 int getZBin(const float z) const noexcept
125 {
126 if (z < zMin) {
127 return -1;
128 }
129 const auto bin = static_cast<int>((z - zMin) / zBinSize);
130 return (bin >= 0 && bin < nZBins) ? bin : -1;
131 }
132
133 void fill(const float time, const float z, const float weight) noexcept
134 {
135 const auto tBin = getTimeBin(time);
136 const auto zBin = getZBin(z);
137 if (tBin < 0 || zBin < 0) {
138 return;
139 }
140 bins[getIndex(tBin, zBin)] += weight;
141 }
142
143 int findPeakBin() const noexcept
144 {
145 float bestWeight = 0.f;
146 int bestIndex = -1;
147 for (int index = 0; index < static_cast<int>(bins.size()); ++index) {
148 if (bins[index] > bestWeight) {
149 bestWeight = bins[index];
150 bestIndex = index;
151 }
152 }
153 return bestIndex;
154 }
155
156 void suppressBin(const int index) noexcept
157 {
158 if (index >= 0 && index < static_cast<int>(bins.size())) {
159 bins[index] = -1.f;
160 }
161 }
162
163 void suppressNeighborhood(const int index, const int radiusTime, const int radiusZ) noexcept
164 {
165 if (index < 0) {
166 return;
167 }
168 const auto [tBin, zBin] = decodeIndex(index);
169 for (int dt = -radiusTime; dt <= radiusTime; ++dt) {
170 const auto tt = tBin + dt;
171 if (tt < 0 || tt >= nTimeBins) {
172 continue;
173 }
174 for (int dz = -radiusZ; dz <= radiusZ; ++dz) {
175 const auto zz = zBin + dz;
176 if (zz < 0 || zz >= nZBins) {
177 continue;
178 }
179 bins[getIndex(tt, zz)] = -1.f;
180 }
181 }
182 }
183
184 float getNeighborhoodSum(const int index, const int radiusTime, const int radiusZ) const noexcept
185 {
186 if (index < 0) {
187 return 0.f;
188 }
189 const auto [tBin, zBin] = decodeIndex(index);
190 float sum = 0.f;
191 for (int dt = -radiusTime; dt <= radiusTime; ++dt) {
192 const auto tt = tBin + dt;
193 if (tt < 0 || tt >= nTimeBins) {
194 continue;
195 }
196 for (int dz = -radiusZ; dz <= radiusZ; ++dz) {
197 const auto zz = zBin + dz;
198 if (zz < 0 || zz >= nZBins) {
199 continue;
200 }
201 const auto value = bins[getIndex(tt, zz)];
202 if (value > 0.f) {
203 sum += value;
204 }
205 }
206 }
207 return sum;
208 }
209
210 float getTimeBinCenter(const int tBin) const noexcept
211 {
212 return timeMin + ((static_cast<float>(tBin) + 0.5f) * timeBinSize);
213 }
214
215 float getZBinCenter(const int zBin) const noexcept
216 {
217 return zMin + ((static_cast<float>(zBin) + 0.5f) * zBinSize);
218 }
219
220 TimeEstBC getTimeInterval(const int tBin) const noexcept
221 {
222 const auto lowFloat = timeMin + (static_cast<float>(tBin) * timeBinSize);
223 const auto highFloat = lowFloat + timeBinSize;
224 const auto low = std::max<double>(0., std::floor(lowFloat));
225 const auto high = std::max(low + 1., (double)std::ceil(highFloat));
226 constexpr auto maxTS = std::numeric_limits<TimeStampType>::max();
227 const auto clampedLow = std::min<double>(low, maxTS - 1.);
228 const auto width = std::min<double>(high - clampedLow, std::numeric_limits<TimeStampErrorType>::max());
229 return {static_cast<TimeStampType>(clampedLow), static_cast<TimeStampErrorType>(std::max<double>(1., width))};
230 }
231
232 TimeEstBC getTimeNeighborhoodInterval(const int tBin, const int radius) const noexcept
233 {
234 const auto lowBin = std::max(0, tBin - radius);
235 const auto highBin = std::min(nTimeBins - 1, tBin + radius);
236 const auto lowFloat = timeMin + (static_cast<float>(lowBin) * timeBinSize);
237 const auto highFloat = timeMin + (static_cast<float>(highBin + 1) * timeBinSize);
238 const auto low = std::max<double>(0., std::floor(lowFloat));
239 const auto high = std::max(low + 1., (double)std::ceil(highFloat));
240 constexpr auto maxTS = std::numeric_limits<TimeStampType>::max();
241 const auto clampedLow = std::min<double>(low, maxTS - 1.);
242 const auto width = std::min<double>(high - clampedLow, std::numeric_limits<TimeStampErrorType>::max());
243 return {static_cast<TimeStampType>(clampedLow), static_cast<TimeStampErrorType>(std::max<double>(1., width))};
244 }
245};
246
247class SeedHistogram
248{
249 public:
250 SeedHistogram(std::span<const int> members,
251 std::span<const LineRef> lineRefs,
252 std::span<const Line> lines,
253 const Settings& settings)
254 : mMembers(members), mLineRefs(lineRefs), mSeedMemberRadiusTime(settings.seedMemberRadiusTime), mSeedMemberRadiusZ(settings.seedMemberRadiusZ), mMemoryPool(settings.memoryPool), mHistogram(mMemoryPool)
255 {
256 const auto zBinSize = 0.25f * settings.clusterCut;
257 const auto timeBinSize = medianTimeError(lines);
258
259 float minZ = std::numeric_limits<float>::max();
260 float maxZ = std::numeric_limits<float>::lowest();
261 float minTime = std::numeric_limits<float>::max();
262 float maxTime = std::numeric_limits<float>::lowest();
263 for (const auto lineRefIdx : mMembers) {
264 minZ = std::min(minZ, mLineRefs[lineRefIdx].zBeam);
265 maxZ = std::max(maxZ, mLineRefs[lineRefIdx].zBeam);
266 minTime = std::min(minTime, mLineRefs[lineRefIdx].tCenter);
267 maxTime = std::max(maxTime, mLineRefs[lineRefIdx].tCenter);
268 }
269
270 const auto dz = std::max(0.f, maxZ - minZ);
271 const auto dt = std::max(0.f, maxTime - minTime);
272 mHistogram.nZBins = 1 + static_cast<int>(dz / zBinSize);
273 mHistogram.nTimeBins = 1 + static_cast<int>(dt / timeBinSize);
274 if (mHistogram.nTimeBins * mHistogram.nZBins > MaxHistogramBins) {
275 if (mHistogram.nTimeBins > mHistogram.nZBins) {
276 mHistogram.nTimeBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nZBins));
277 } else {
278 mHistogram.nZBins = std::max(1, (MaxHistogramBins - 1) / std::max(1, mHistogram.nTimeBins));
279 }
280 }
281
282 mHistogram.timeBinSize = std::max(timeBinSize, dt / (float)std::max(1, mHistogram.nTimeBins));
283 mHistogram.zBinSize = std::max(zBinSize, dz / (float)std::max(1, mHistogram.nZBins));
284 const auto paddedTime = 0.5f * ((float)mHistogram.nTimeBins * mHistogram.timeBinSize - dt);
285 const auto paddedZ = 0.5f * ((float)mHistogram.nZBins * mHistogram.zBinSize - dz);
286 mHistogram.timeMin = minTime - paddedTime;
287 mHistogram.zMin = minZ - paddedZ;
288 mHistogram.bins.assign((size_t)mHistogram.nTimeBins * (size_t)mHistogram.nZBins, 0.f);
289
290 for (const auto lineRefIdx : mMembers) {
291 mHistogram.fill(mLineRefs[lineRefIdx].tCenter, mLineRefs[lineRefIdx].zBeam, 1.f);
292 }
293 }
294
295 int findPeakBin() const noexcept
296 {
297 return mHistogram.findPeakBin();
298 }
299
300 float getPeakSupport(const int peakIndex) const noexcept
301 {
302 return mHistogram.getNeighborhoodSum(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ);
303 }
304
305 bounded_vector<int> collectLocalMembers(const int peakIndex, const int radiusTime, const int radiusZ) const
306 {
307 bounded_vector<int> localMembers(mMemoryPool.get());
308 localMembers.reserve(mMembers.size());
309 const auto [timeBin, zBin] = mHistogram.decodeIndex(peakIndex);
310 for (const auto lineRefIdx : mMembers) {
311 const auto memberTimeBin = mHistogram.getTimeBin(mLineRefs[lineRefIdx].tCenter);
312 const auto memberZBin = mHistogram.getZBin(mLineRefs[lineRefIdx].zBeam);
313 if (memberTimeBin < 0 || memberZBin < 0) {
314 continue;
315 }
316 if (o2::gpu::GPUCommonMath::Abs(memberTimeBin - timeBin) > radiusTime) {
317 continue;
318 }
319 if (o2::gpu::GPUCommonMath::Abs(memberZBin - zBin) > radiusZ) {
320 continue;
321 }
322 localMembers.push_back(lineRefIdx);
323 }
324 return localMembers;
325 }
326
327 TimeEstBC getPeakTimeInterval(const int peakIndex, const int radius = 0) const noexcept
328 {
329 return mHistogram.getTimeNeighborhoodInterval(mHistogram.decodeIndex(peakIndex).first, radius);
330 }
331
332 float getPeakZCenter(const int peakIndex) const noexcept
333 {
334 return mHistogram.getZBinCenter(mHistogram.decodeIndex(peakIndex).second);
335 }
336
337 void suppressPeak(const int peakIndex) noexcept
338 {
339 mHistogram.suppressBin(peakIndex);
340 }
341
342 void suppressPeakNeighborhood(const int peakIndex) noexcept
343 {
344 mHistogram.suppressNeighborhood(peakIndex, mSeedMemberRadiusTime, mSeedMemberRadiusZ);
345 }
346
347 private:
348 float medianTimeError(std::span<const Line> lines) const
349 {
350 bounded_vector<float> errors(mMemoryPool.get());
351 errors.reserve(mMembers.size());
352 for (const auto lineRefIdx : mMembers) {
353 errors.push_back(static_cast<float>(lines[mLineRefs[lineRefIdx].lineIndex].mTime.getTimeStampError()));
354 }
355 std::sort(errors.begin(), errors.end());
356 return errors.empty() ? 1.f : std::max(1.f, errors[errors.size() / 2]);
357 }
358
359 std::span<const int> mMembers;
360 std::span<const LineRef> mLineRefs;
361 int mSeedMemberRadiusTime = 1;
362 int mSeedMemberRadiusZ = 2;
363 std::shared_ptr<BoundedMemoryResource> mMemoryPool;
364 Histogram2D mHistogram;
365};
366
367float updateScale2(const std::span<const float> chi2s, const std::shared_ptr<BoundedMemoryResource>& mr) noexcept
368{
369 if (chi2s.empty()) {
370 return MinScale2;
371 }
372
373 bounded_vector<float> sorted(chi2s.begin(), chi2s.end(), mr.get());
374 std::sort(sorted.begin(), sorted.end());
375 const auto median = sorted[sorted.size() / 2];
376
377 for (auto& value : sorted) {
378 value = o2::gpu::GPUCommonMath::Abs(value - median);
379 }
380 std::sort(sorted.begin(), sorted.end());
381 const auto mad = sorted[sorted.size() / 2];
382 if (!std::isfinite(mad) || mad <= constants::Tolerance) {
383 return MinScale2;
384 }
385 return std::max(MinScale2, MedianToSigma * mad);
386}
387
388class VertexFit
389{
390 public:
391 void add(const Line& line, const float weight) noexcept
392 {
393 const auto& direction = line.cosinesDirector;
394 const auto& origin = line.originPoint;
395 const auto det = direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2];
396 if (det <= constants::Tolerance) {
397 return;
398 }
399
400 for (int i = 0; i < 3; ++i) {
401 for (int j = i; j < 3; ++j) {
402 mMatrix(i, j) += weight * (((i == j ? det : 0.f) - direction[i] * direction[j]) / det);
403 }
404 }
405
406 const auto dDotO = direction[0] * origin[0] + direction[1] * origin[1] + direction[2] * origin[2];
407 for (int i = 0; i < 3; ++i) {
408 mRhs(i) += weight * ((direction[i] * dDotO - det * origin[i]) / det);
409 }
410 }
411
412 bool solve(std::array<float, 3>& vertexOut) const noexcept
413 {
414 SymMatrix3 inv{mMatrix};
415 if (!inv.InvertFast()) {
416 return false;
417 }
418 const auto solution = inv * mRhs;
419 vertexOut[0] = static_cast<float>(-solution(0));
420 vertexOut[1] = static_cast<float>(-solution(1));
421 vertexOut[2] = static_cast<float>(-solution(2));
422 return std::isfinite(vertexOut[0]) && std::isfinite(vertexOut[1]) && std::isfinite(vertexOut[2]);
423 }
424
425 private:
426 SymMatrix3 mMatrix;
427 SVector3 mRhs;
428};
429
430VertexSeed fitSeed(const VertexSeed& initialSeed,
431 std::span<const int> members,
432 std::span<const LineRef> lineRefs,
433 std::span<const Line> lines,
434 const std::shared_ptr<BoundedMemoryResource>& mr,
435 const float pairCut2)
436{
437 VertexSeed seed{mr};
438 seed.vertex = initialSeed.vertex;
439 seed.time = initialSeed.time;
440 seed.scale2 = initialSeed.scale2;
441 seed.valid = false;
442 seed.contributors.clear();
443 seed.assigned.clear();
444 if (members.size() < 2) {
445 return seed;
446 }
447
448 for (int iteration = 0; iteration < MaxFitIterations; ++iteration) {
449 VertexFit vertexFit;
450 TimeEstBC commonTime{};
451 bool hasCommonTime = false;
452 bounded_vector<int> contributors{mr.get()};
453 const auto scale2 = std::max(seed.scale2, MinScale2);
454 const auto tukeyFactor = 1.f / (scale2 * TukeyC2);
455
456 for (const auto lineRefIdx : members) {
457 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
458 const auto& line = lines[lineIdx];
459 if (!line.mTime.isCompatible(seed.time)) {
460 continue;
461 }
462 if (hasCommonTime && !line.mTime.isCompatible(commonTime)) {
463 continue;
464 }
465
466 const auto chi2 = Line::getDistance2FromPoint(line, seed.vertex) / pairCut2;
467 auto weight = 1.f - (chi2 * tukeyFactor);
468 if (weight <= 0.f) {
469 continue;
470 }
471 weight *= weight;
472
473 if (!hasCommonTime) {
474 commonTime = line.mTime;
475 hasCommonTime = true;
476 } else {
477 commonTime += line.mTime;
478 }
479
480 contributors.push_back(lineRefIdx);
481 vertexFit.add(line, weight);
482 }
483
484 if (!hasCommonTime || contributors.size() < 2) {
485 return seed;
486 }
487
488 std::sort(contributors.begin(), contributors.end());
489
490 std::array<float, 3> updatedVertex{};
491 if (!vertexFit.solve(updatedVertex)) {
492 return seed;
493 }
494
495 const auto sameContributors = contributors == seed.contributors;
496 const auto dz = o2::gpu::GPUCommonMath::Abs(updatedVertex[2] - seed.vertex[2]);
497 const auto oldR2 = (seed.vertex[0] * seed.vertex[0]) + (seed.vertex[1] * seed.vertex[1]);
498 const auto newR2 = (updatedVertex[0] * updatedVertex[0]) + (updatedVertex[1] * updatedVertex[1]);
499 const auto dr2 = o2::gpu::GPUCommonMath::Abs(newR2 - oldR2);
500
501 seed.vertex = updatedVertex;
502 seed.time = commonTime;
503 bounded_vector<float> updatedChi2s{mr.get()};
504 updatedChi2s.reserve(contributors.size());
505 for (const auto lineRefIx : contributors) {
506 updatedChi2s.push_back(Line::getDistance2FromPoint(lines[lineRefs[lineRefIx].lineIndex], seed.vertex) / pairCut2);
507 }
508 seed.scale2 = updateScale2(updatedChi2s, mr);
509 seed.contributors = std::move(contributors);
510 seed.valid = true;
511
512 if (sameContributors && dz < VertexShiftZTol && dr2 < VertexShiftR2Tol) {
513 break;
514 }
515 }
516
517 return seed;
518}
519
520size_t countSharedContributors(std::span<const int> lhs, std::span<const int> rhs) noexcept
521{
522 size_t shared = 0;
523 auto lhsIt = lhs.begin();
524 auto rhsIt = rhs.begin();
525 while (lhsIt != lhs.end() && rhsIt != rhs.end()) {
526 if (*lhsIt == *rhsIt) {
527 ++shared;
528 ++lhsIt;
529 ++rhsIt;
530 } else if (*lhsIt < *rhsIt) {
531 ++lhsIt;
532 } else {
533 ++rhsIt;
534 }
535 }
536 return shared;
537}
538
539bounded_vector<int> collectCompatibleContributors(const VertexSeed& seed,
540 std::span<const int> members,
541 std::span<const LineRef> lineRefs,
542 std::span<const Line> lines,
543 const std::shared_ptr<BoundedMemoryResource>& mr,
544 const float pairCut2)
545{
546 bounded_vector<int> contributors{mr.get()};
547 contributors.reserve(members.size());
548 for (const auto lineRefIdx : members) {
549 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
550 const auto& line = lines[lineIdx];
551 if (!line.mTime.isCompatible(seed.time)) {
552 continue;
553 }
554 if (Line::getDistance2FromPoint(line, seed.vertex) >= pairCut2) {
555 continue;
556 }
557 contributors.push_back(lineRefIdx);
558 }
559 std::sort(contributors.begin(), contributors.end());
560 return contributors;
561}
562
563void deduplicateSeeds(bounded_vector<VertexSeed>& seeds, const Settings& settings)
564{
565 if (seeds.size() < 2) {
566 return;
567 }
568
569 std::sort(seeds.begin(), seeds.end(), [](const VertexSeed& lhs, const VertexSeed& rhs) {
570 if (lhs.contributors.size() != rhs.contributors.size()) {
571 return lhs.contributors.size() > rhs.contributors.size();
572 }
573 if (o2::gpu::GPUCommonMath::Abs(lhs.scale2 - rhs.scale2) > constants::Tolerance) {
574 return lhs.scale2 < rhs.scale2;
575 }
576 return lhs.vertex[2] < rhs.vertex[2];
577 });
578
579 const auto dedupZCut = settings.seedDedupZCut > 0.f ? settings.seedDedupZCut : 0.25f * settings.clusterCut;
580 for (size_t i = 0; i < seeds.size(); ++i) {
581 auto& candidate = seeds[i];
582 if (!candidate.isUsableSeed()) {
583 candidate.valid = false;
584 continue;
585 }
586 bool duplicate = false;
587 for (size_t j = 0; j < i; ++j) {
588 const auto& kept = seeds[j];
589 if (!kept.isUsableSeed()) {
590 continue;
591 }
592 if (!candidate.time.isCompatible(kept.time)) {
593 continue;
594 }
595 const auto shared = countSharedContributors(candidate.contributors, kept.contributors);
596 const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size());
597 const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]);
598 const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() ||
599 kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2;
600 const bool overlapDuplicate = shared > 0 && shared * 2 >= minSize;
601 const bool nearbyDuplicate = zDelta < dedupZCut && (shared > 0 || clearlyWorse);
602 if (overlapDuplicate || nearbyDuplicate) {
603 duplicate = true;
604 break;
605 }
606 }
607 if (duplicate) {
608 candidate.valid = false;
609 }
610 }
611 compactSeeds(seeds);
612}
613
614void deduplicateRefittedSeeds(bounded_vector<VertexSeed>& seeds, const Settings& settings)
615{
616 if (seeds.size() < 2) {
617 return;
618 }
619
620 std::sort(seeds.begin(), seeds.end(), [](const VertexSeed& lhs, const VertexSeed& rhs) {
621 if (lhs.contributors.size() != rhs.contributors.size()) {
622 return lhs.contributors.size() > rhs.contributors.size();
623 }
624 if (o2::gpu::GPUCommonMath::Abs(lhs.scale2 - rhs.scale2) > constants::Tolerance) {
625 return lhs.scale2 < rhs.scale2;
626 }
627 return lhs.vertex[2] < rhs.vertex[2];
628 });
629
630 const auto zCut = settings.refitDedupZCut > 0.f ? settings.refitDedupZCut : 0.25f * settings.clusterCut;
631 for (size_t i = 0; i < seeds.size(); ++i) {
632 auto& candidate = seeds[i];
633 if (!candidate.isUsableSeed()) {
634 candidate.valid = false;
635 continue;
636 }
637 bool duplicate = false;
638 for (size_t j = 0; j < i; ++j) {
639 const auto& kept = seeds[j];
640 if (!kept.isUsableSeed()) {
641 continue;
642 }
643 if (!candidate.time.isCompatible(kept.time)) {
644 continue;
645 }
646 const auto shared = countSharedContributors(candidate.contributors, kept.contributors);
647 const auto minSize = std::min(candidate.contributors.size(), kept.contributors.size());
648 const auto zDelta = o2::gpu::GPUCommonMath::Abs(candidate.vertex[2] - kept.vertex[2]);
649 const bool overlapDuplicate = shared > 0 && shared * 2 >= minSize;
650 const bool lowSupportPair = std::min(candidate.contributors.size(), kept.contributors.size()) < 4;
651 const bool clearlyWorse = kept.contributors.size() > candidate.contributors.size() ||
652 kept.scale2 + constants::Tolerance < 0.9f * candidate.scale2;
653 const bool geometricDuplicate = zDelta < zCut && (lowSupportPair || clearlyWorse);
654 if (overlapDuplicate || geometricDuplicate) {
655 duplicate = true;
656 break;
657 }
658 }
659 if (duplicate) {
660 candidate.valid = false;
661 }
662 }
663 compactSeeds(seeds);
664}
665
666struct OrderedComponent {
667 explicit OrderedComponent(const std::shared_ptr<BoundedMemoryResource>& mr) : members(mr.get()) {}
668 float center = 0.f;
669 bounded_vector<int> members;
670};
671
672bounded_vector<bounded_vector<int>> buildCoarseClusters(std::span<const LineRef> lineRefs,
673 std::span<const Line> lines,
674 const Settings& settings)
675{
676 bounded_vector<bounded_vector<int>> clusters(settings.memoryPool.get());
677 if (lineRefs.size() < 2) {
678 return clusters;
679 }
680
681 bounded_vector<int> sortedByLower(lineRefs.size(), settings.memoryPool.get());
682 std::iota(sortedByLower.begin(), sortedByLower.end(), 0);
683 std::sort(sortedByLower.begin(), sortedByLower.end(), [&](const int lhs, const int rhs) {
684 const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower();
685 const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower();
686 if (lhsLower != rhsLower) {
687 return lhsLower < rhsLower;
688 }
689 return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex;
690 });
691
692 const auto coarseZWindow = settings.coarseZWindow > 0.f ? settings.coarseZWindow : settings.clusterCut;
693 bounded_vector<int> parent(lineRefs.size(), settings.memoryPool.get());
694 bounded_vector<int> componentSize(lineRefs.size(), 1, settings.memoryPool.get());
695 std::iota(parent.begin(), parent.end(), 0);
696 float minZ = std::numeric_limits<float>::max();
697 float maxZ = std::numeric_limits<float>::lowest();
698 for (const auto& lineRef : lineRefs) {
699 minZ = std::min(minZ, lineRef.zBeam);
700 maxZ = std::max(maxZ, lineRef.zBeam);
701 }
702 const auto nZBins = std::max(1, 1 + static_cast<int>((maxZ - minZ) / coarseZWindow));
703 auto getZBin = [&](const float z) {
704 return std::clamp(static_cast<int>((z - minZ) / coarseZWindow), 0, nZBins - 1);
705 };
706
707 auto findRoot = [&](int idx) {
708 int root = idx;
709 while (parent[root] != root) {
710 root = parent[root];
711 }
712 while (parent[idx] != idx) {
713 const auto next = parent[idx];
714 parent[idx] = root;
715 idx = next;
716 }
717 return root;
718 };
719
720 auto unite = [&](const int lhs, const int rhs) {
721 auto lhsRoot = findRoot(lhs);
722 auto rhsRoot = findRoot(rhs);
723 if (lhsRoot == rhsRoot) {
724 return;
725 }
726 if (componentSize[lhsRoot] < componentSize[rhsRoot]) {
727 std::swap(lhsRoot, rhsRoot);
728 }
729 parent[rhsRoot] = lhsRoot;
730 componentSize[lhsRoot] += componentSize[rhsRoot];
731 };
732
733 using ActiveEntry = std::pair<TimeStampType, int>;
734 bounded_vector<ActiveEntry> activeEntries(settings.memoryPool.get());
735 std::priority_queue<ActiveEntry, bounded_vector<ActiveEntry>, std::greater<>> activeByUpper(std::greater<>{}, std::move(activeEntries));
736 bounded_vector<uint8_t> activeMask(lineRefs.size(), 0, settings.memoryPool.get());
737 bounded_vector<bounded_vector<int>> activeByZBin(settings.memoryPool.get());
738 activeByZBin.reserve(nZBins);
739 for (int iBin = 0; iBin < nZBins; ++iBin) {
740 activeByZBin.emplace_back();
741 }
742 for (const auto lineRefIdx : sortedByLower) {
743 const auto& lineRef = lineRefs[lineRefIdx];
744 const auto& line = lines[lineRef.lineIndex];
745 const auto currentLower = line.mTime.lower();
746
747 while (!activeByUpper.empty() && activeByUpper.top().first < currentLower) {
748 activeMask[activeByUpper.top().second] = 0;
749 activeByUpper.pop();
750 }
751
752 const auto zBin = getZBin(lineRef.zBeam);
753 for (int neighborBin = std::max(0, zBin - 1); neighborBin <= std::min(nZBins - 1, zBin + 1); ++neighborBin) {
754 auto& bucket = activeByZBin[neighborBin];
755 size_t writePos = 0;
756 for (size_t readPos = 0; readPos < bucket.size(); ++readPos) {
757 const auto oLineRefIdx = bucket[readPos];
758 if (!activeMask[oLineRefIdx]) {
759 continue;
760 }
761 bucket[writePos++] = oLineRefIdx;
762 const auto& oLineRef = lineRefs[oLineRefIdx];
763 if (o2::gpu::GPUCommonMath::Abs(lineRef.zBeam - oLineRef.zBeam) >= coarseZWindow) {
764 continue;
765 }
766 const auto& otherLine = lines[oLineRef.lineIndex];
767 if (line.mTime.isCompatible(otherLine.mTime)) {
768 unite(lineRefIdx, oLineRefIdx);
769 }
770 }
771 bucket.resize(writePos);
772 }
773
774 activeMask[lineRefIdx] = 1;
775 activeByUpper.emplace(line.mTime.upper(), lineRefIdx);
776 activeByZBin[zBin].push_back(lineRefIdx);
777 }
778
779 std::unordered_map<int, bounded_vector<int>> components;
780 components.reserve(lineRefs.size());
781 for (int lineRefIdx = 0; lineRefIdx < static_cast<int>(lineRefs.size()); ++lineRefIdx) {
782 const auto root = findRoot(lineRefIdx);
783 auto [it, inserted] = components.try_emplace(root, std::pmr::polymorphic_allocator<int>{settings.memoryPool.get()});
784 (void)inserted;
785 it->second.push_back(lineRefIdx);
786 }
787
788 bounded_vector<OrderedComponent> orderedComponents(settings.memoryPool.get());
789 orderedComponents.reserve(components.size());
790 for (auto& [root, members] : components) {
791 (void)root;
792 if (members.size() < 2) {
793 continue;
794 }
795 std::sort(members.begin(), members.end(), [&](const int lhs, const int rhs) {
796 const auto lhsLower = lines[lineRefs[lhs].lineIndex].mTime.lower();
797 const auto rhsLower = lines[lineRefs[rhs].lineIndex].mTime.lower();
798 if (lhsLower != rhsLower) {
799 return lhsLower < rhsLower;
800 }
801 return lineRefs[lhs].lineIndex < lineRefs[rhs].lineIndex;
802 });
803 orderedComponents.emplace_back(settings.memoryPool);
804 orderedComponents.back().center = lineRefs[members.front()].tCenter;
805 orderedComponents.back().members = std::move(members);
806 }
807
808 std::sort(orderedComponents.begin(), orderedComponents.end(), [](const auto& lhs, const auto& rhs) {
809 if (o2::gpu::GPUCommonMath::Abs(lhs.center - rhs.center) > TieTolerance) {
810 return lhs.center < rhs.center;
811 }
812 return lhs.members.front() < rhs.members.front();
813 });
814 clusters.reserve(orderedComponents.size());
815 for (auto& component : orderedComponents) {
816 clusters.push_back(std::move(component.members));
817 }
818 return clusters;
819}
820
821bounded_vector<VertexSeed> buildSeeds(std::span<const int> members,
822 std::span<const LineRef> lineRefs,
823 std::span<const Line> lines,
824 const Settings& settings)
825{
826 SeedHistogram histogram(members, lineRefs, lines, settings);
827 bounded_vector<VertexSeed> seeds(settings.memoryPool.get());
828 seeds.reserve(MaxSeedsPerCluster);
829 float leadingPeakSupport = 0.f;
830
831 while (static_cast<int>(seeds.size()) < MaxSeedsPerCluster) {
832 const auto peak = histogram.findPeakBin();
833 if (peak < 0) {
834 break;
835 }
836 const auto peakSupport = histogram.getPeakSupport(peak);
837 if (peakSupport < 2.f) {
838 break;
839 }
840 if (leadingPeakSupport <= 0.f) {
841 leadingPeakSupport = peakSupport;
842 } else if (peakSupport < std::max(2.f, MinRelativePeakSupport * leadingPeakSupport)) {
843 break;
844 }
845 auto localMembers = histogram.collectLocalMembers(peak, 0, 0);
846 if (localMembers.size() < 2) {
847 localMembers = histogram.collectLocalMembers(peak, settings.seedMemberRadiusTime, settings.seedMemberRadiusZ);
848 }
849 if (localMembers.size() < 2) {
850 histogram.suppressPeak(peak);
851 continue;
852 }
853
854 VertexSeed seed(settings.memoryPool);
855 seed.vertex = {settings.beamX, settings.beamY, histogram.getPeakZCenter(peak)};
856 seed.time = histogram.getPeakTimeInterval(peak);
857 seed.scale2 = InitialScale2;
858
859 auto fitted = fitSeed(seed, localMembers, lineRefs, lines, settings.memoryPool, settings.pairCut2);
860 if (fitted.valid && fitted.contributors.size() >= 2) {
861 seeds.push_back(std::move(fitted));
862 histogram.suppressPeakNeighborhood(peak);
863 } else {
864 histogram.suppressPeak(peak);
865 }
866 }
867
868 return seeds;
869}
870
871void assignLinesToSeeds(bounded_vector<VertexSeed>& seeds,
872 std::span<const int> members,
873 std::span<const LineRef> lineRefs,
874 std::span<const Line> lines,
875 const float pairCut2)
876{
877 for (auto& seed : seeds) {
878 seed.assigned.clear();
879 }
880
881 for (const auto lineRefIdx : members) {
882 const auto lineIdx = lineRefs[lineRefIdx].lineIndex;
883 const auto& line = lines[lineIdx];
884
885 int bestSeed = -1;
886 float bestScore = std::numeric_limits<float>::max();
887 size_t bestMult = 0;
888 float bestZResidual = std::numeric_limits<float>::max();
889
890 for (int seedIdx = 0; seedIdx < static_cast<int>(seeds.size()); ++seedIdx) {
891 const auto& seed = seeds[seedIdx];
892 if (!seed.valid || seed.contributors.size() < 2) {
893 continue;
894 }
895 if (!line.mTime.isCompatible(seed.time)) {
896 continue;
897 }
898
899 const auto distance2 = Line::getDistance2FromPoint(line, seed.vertex);
900 if (distance2 >= pairCut2) {
901 continue;
902 }
903
904 const auto score = distance2 / std::max(seed.scale2, MinScale2);
905 const auto zResidual = o2::gpu::GPUCommonMath::Abs(lineRefs[lineRefIdx].zBeam - seed.vertex[2]);
906 const auto multiplicity = seed.contributors.size();
907
908 const auto betterScore = score + TieTolerance < bestScore;
909 const auto betterMultiplicity = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance && multiplicity > bestMult;
910 const auto betterZ = o2::gpu::GPUCommonMath::Abs(score - bestScore) <= TieTolerance &&
911 multiplicity == bestMult && zResidual + constants::Tolerance < bestZResidual;
912 if (betterScore || betterMultiplicity || betterZ) {
913 bestSeed = seedIdx;
914 bestScore = score;
915 bestMult = multiplicity;
916 bestZResidual = zResidual;
917 }
918 }
919
920 if (bestSeed >= 0) {
921 seeds[bestSeed].assigned.push_back(lineRefIdx);
922 }
923 }
924}
925
926ClusterWithLines materializeCluster(const VertexSeed& seed,
927 std::span<const LineRef> lineRefs,
928 std::span<const Line> lines,
929 const std::shared_ptr<BoundedMemoryResource>& mr)
930{
931 bounded_vector<int> lineIndices{mr.get()};
932 lineIndices.reserve(seed.contributors.size());
933 for (const auto lineRefIdx : seed.contributors) {
934 lineIndices.push_back(lineRefs[lineRefIdx].lineIndex);
935 }
936 std::sort(lineIndices.begin(), lineIndices.end());
937 lineIndices.erase(std::unique(lineIndices.begin(), lineIndices.end()), lineIndices.end());
938
939 if (lineIndices.size() < 2) {
940 return {ClusterLines{}, bounded_vector<int>{mr.get()}};
941 }
942
943 ClusterLines fit{std::span<const int>{lineIndices.data(), lineIndices.size()}, lines};
944 return {std::move(fit), std::move(lineIndices)};
945}
946
947} // namespace
948
949bounded_vector<ClusterWithLines> buildClusters(std::span<const Line> lines, const Settings& settings)
950{
951 bounded_vector<ClusterWithLines> clusters(settings.memoryPool.get());
952 if (lines.size() < 2) {
953 return clusters;
954 }
955
956 bounded_vector<LineRef> refs(settings.memoryPool.get());
957 refs.reserve(lines.size());
958 for (int lineIdx = 0; lineIdx < static_cast<int>(lines.size()); ++lineIdx) {
959 LineRef ref(lines[lineIdx], lineIdx, settings.beamX, settings.beamY, settings.maxZ);
960 if (!ref.isDead()) {
961 refs.push_back(ref);
962 }
963 }
964
965 if (refs.size() < 2) {
966 return clusters;
967 }
968
969 const auto coarseClusters = buildCoarseClusters(refs, lines, settings);
970
971 for (const auto& members : coarseClusters) {
972 auto seeds = buildSeeds(members, refs, lines, settings);
973 if (seeds.empty()) {
974 continue;
975 }
976
977 for (auto& seed : seeds) {
978 if (!seed.isUsableSeed()) {
979 seed.valid = false;
980 continue;
981 }
982 auto contributors = collectCompatibleContributors(seed, members, refs, lines, settings.memoryPool, settings.pairCut2);
983 if (contributors.size() < 2) {
984 seed.valid = false;
985 continue;
986 }
987 seed.contributors = std::move(contributors);
988 }
989 compactSeeds(seeds);
990 if (seeds.empty()) {
991 continue;
992 }
993 deduplicateSeeds(seeds, settings);
994 if (seeds.empty()) {
995 continue;
996 }
997 assignLinesToSeeds(seeds, members, refs, lines, settings.pairCut2);
998 for (auto& seed : seeds) {
999 if (seed.assigned.size() < 2) {
1000 seed.valid = false;
1001 continue;
1002 }
1003 seed = fitSeed(seed, seed.assigned, refs, lines, settings.memoryPool, settings.pairCut2);
1004 if (!seed.isUsableSeed()) {
1005 seed.valid = false;
1006 continue;
1007 }
1008 }
1009 compactSeeds(seeds);
1010 deduplicateRefittedSeeds(seeds, settings);
1011 for (auto& refit : seeds) {
1012 auto cluster = materializeCluster(refit, refs, lines, settings.memoryPool);
1013 if (cluster.fit.getSize() < 2) {
1014 continue;
1015 }
1016 if (!cluster.fit.isValid()) {
1017 continue;
1018 }
1019 clusters.push_back(std::move(cluster));
1020 }
1021 }
1022
1023 return clusters;
1024}
1025
1026} // namespace o2::its::line_vertexer
size_t minSize
uint64_t vertex
Definition RawEventData.h:9
int16_t time
Definition RawEventData.h:4
std::vector< double > sum
int32_t i
float chi2
bounded_vector< int > members
float tCenter
float tHalfWidth
bounded_vector< int > assigned
float timeMin
int nTimeBins
float scale2
int lineIndex
float zBeam
float center
bounded_vector< int > contributors
float zMin
bounded_vector< float > bins
float timeBinSize
float zBinSize
bool valid
uint32_t j
Definition RawData.h:0
GLuint index
Definition glcorearb.h:781
GLint GLsizei width
Definition glcorearb.h:270
GLuint GLuint GLfloat weight
Definition glcorearb.h:5477
GLint GLenum GLint components
Definition glcorearb.h:5520
GLsizei const GLfloat * value
Definition glcorearb.h:819
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLfloat GLfloat GLfloat GLfloat GLfloat GLfloat maxZ
Definition glcorearb.h:2910
GLint ref
Definition glcorearb.h:291
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
GLfloat GLfloat minZ
Definition glcorearb.h:2910
double component(const Vec3 &vector, int dimension)
int32_t const char int32_t line
auto get(const std::byte *buffer, size_t=0)
Definition DataHeader.h:457
constexpr int UnusedIndex
Definition Constants.h:32
constexpr float Tolerance
Definition Constants.h:30
bounded_vector< ClusterWithLines > buildClusters(std::span< const Line > lines, const Settings &settings)
int const float const TrackSeed< NLayers > & seed
const float beamX
uint32_t TimeStampErrorType
Definition TimeEstBC.h:27
const float zDelta
uint32_t TimeStampType
Definition TimeEstBC.h:26
const float const float beamY
const float const float const float const float std::array< float, 3 > std::array< float, 3 > & direction
const track::TrackFitContext< NLayers > & fit
if(!cell.getPxPyPzGlo(direction))
const float const float const float const float std::array< float, 3 > & origin
D const SVectorGPU< T, D > & rhs
Definition SMatrixGPU.h:193
R median(std::vector< T > v)
Definition fit.h:520
constexpr auto getIndex(const container_T &container, typename container_T::const_iterator iter) -> typename container_T::source_type
Definition algorithm.h:65
static float getDistance2FromPoint(const Line &line, const std::array< float, 3 > &point)
std::shared_ptr< BoundedMemoryResource > memoryPool
std::vector< Cluster > clusters