Project
Loading...
Searching...
No Matches
TrackerTraits.cxx
Go to the documentation of this file.
1// Copyright 2019-2020 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.
15
16#include <algorithm>
17#include <array>
18#include <iterator>
19#include <cmath>
20#include <limits>
21#include <stdexcept>
22#include <string>
23#include <tuple>
24#include <utility>
25
26#include <oneapi/tbb/blocked_range.h>
27
29#include "Framework/Logger.h"
30#include "GPUCommonMath.h"
51
53{
54
57
63
64namespace
65{
66constexpr uint8_t kCompatibilityAbsCharge = 1;
67const o2::track::PID kCompatibilityPID = o2::track::PID::Pion;
68
69void reserveGenericTrackPublication(TimeFrame& frame, std::size_t candidateCount, std::size_t maxReferencesPerTrack)
70{
71 auto& tracks = frame.getGenericTracks();
72 auto& references = frame.getTrackClusterIndices();
73 if (candidateCount > tracks.max_size() - tracks.size() ||
74 (maxReferencesPerTrack != 0 && candidateCount > (references.max_size() - references.size()) / maxReferencesPerTrack)) {
75 throw std::length_error{"GenericTrack publication exceeds the output container capacity"};
76 }
77 tracks.reserve(tracks.size() + candidateCount);
78 references.reserve(references.size() + candidateCount * maxReferencesPerTrack);
79}
80
81bool appendGenericTrack(TimeFrame& frame,
82 const TrackingCandidate& candidate,
83 gsl::span<const gsl::span<const GlobalMeasurement>> layerMeasurements)
84{
85 GenericTrack track = candidate.track;
86 track.hitLayers = {};
87 std::vector<TrackClusterReference> resolvedReferences;
88 resolvedReferences.reserve(layerMeasurements.size());
89 for (std::size_t position = 0; position < layerMeasurements.size(); ++position) {
90 const int localIndex = candidate.getClusterIndex(static_cast<int>(position));
91 if (localIndex == o2::its::constants::UnusedIndex) {
92 continue;
93 }
94 if (localIndex < 0 || static_cast<std::size_t>(localIndex) >= layerMeasurements[position].size()) {
95 return false;
96 }
97 const auto& measurement = layerMeasurements[position][localIndex];
98 const TrackClusterReference reference{LayerId{static_cast<uint16_t>(position)}, 0, measurement.clusterId};
99 if (!reference.isValid()) {
100 return false;
101 }
102 resolvedReferences.push_back(reference);
103 track.hitLayers.set(static_cast<int>(position));
104 }
105 if (!track.innerState.hasRecognizedKind() || !track.outerState.hasRecognizedKind() ||
106 !std::isfinite(track.timestamp.getTimeStamp()) ||
107 !std::isfinite(track.timestamp.getTimeStampError()) || track.timestamp.getTimeStampError() <= 0.f || resolvedReferences.empty()) {
108 return false;
109 }
110
111 auto& tracks = frame.getGenericTracks();
112 auto& references = frame.getTrackClusterIndices();
113 const auto oldTrackSize = tracks.size();
114 const auto oldReferenceSize = references.size();
115 if (oldTrackSize > std::numeric_limits<uint32_t>::max() || oldReferenceSize > std::numeric_limits<uint32_t>::max() ||
116 resolvedReferences.size() > std::numeric_limits<uint32_t>::max() - oldReferenceSize) {
117 return false;
118 }
119
120 try {
121 for (const auto& reference : resolvedReferences) {
122 references.push_back(reference);
123 }
124 track.firstClusterRef = static_cast<uint32_t>(oldReferenceSize);
125 track.clusterRefEnd = static_cast<uint32_t>(references.size());
126 tracks.push_back(track);
127 } catch (...) {
128 references.resize(oldReferenceSize);
129 tracks.resize(oldTrackSize);
130 throw;
131 }
132 return true;
133}
134
135// A static diamond vertex represents all primary vertices and has no event
136// timestamp. Derive its envelope from the tested ROF's configured bounds;
137// TimeEstBC cannot represent a full TimeFrame. The resulting timestamp is
138// compatible by construction with that ROF.
139template <typename ROFOverlapView>
140Vertex diamondVertexForROF(const Vertex& base, const ROFOverlapView& rofOverlapView, int layer, int rofId)
141{
142 Vertex v = base;
143 v.setTimeStamp(rofOverlapView.getLayer(layer).getROFTimeBounds(rofId, true));
144 return v;
145}
146
147// Convert ROOT-visible parameters to the device-portable record once per iteration.
148} // namespace
149
151{
152 if (view.iteration < 0) {
153 throw std::invalid_argument{"CA traversal: iteration out of range (iteration " + std::to_string(view.iteration) + ")"};
154 }
155 int maxNvertices{-1};
157 maxNvertices = view.frame.getMaxVerticesPerROF();
158 }
159 int iVertex = std::min(maxNvertices, 0);
160 do {
161 computeLayerTracklets(view, view.iteration, iVertex);
162 computeLayerCells(view, view.iteration);
163 findCellsNeighbours(view, view.iteration);
164 findRoads(view, view.iteration);
165 } while (++iVertex < maxNvertices);
166}
167
168void TrackerTraits::computeLayerTracklets(IterationContext& context, const int iteration, int iVertex)
169{
170 auto& scratch = context.scratch;
171 const auto scratchEdgeCount = scratch.getTracklets().size();
172 for (size_t edgeId = 0; edgeId < scratchEdgeCount; ++edgeId) {
173 scratch.getTracklets()[edgeId].clear();
174 scratch.getTrackletsLabel(edgeId).clear();
175 std::fill(scratch.getTrackletsLookupTable()[edgeId].begin(), scratch.getTrackletsLookupTable()[edgeId].end(), 0);
176 }
177
178 const auto edgeIds = context.configuration.edgeIds();
179 const auto& mMemoryPool = scratch.getMemoryPool();
180 auto* mFrame = &context.frame;
181 const auto& trkParam = context.configuration.parameters;
182 const auto& mTraversalGraph = context.topology;
183 const auto& mKernelParameters = context.configuration.kernelParameters;
184 const auto& mLayerGlobalMeasurements = context.layerGlobalMeasurements;
185 const auto& topology = mTraversalGraph;
186 const Vertex diamondVert(trkParam.Diamond, trkParam.DiamondCov, 1, 1.f);
187
188 mTaskArena->execute([&] {
189 auto forTracklets = [&](int fromLayer, int toLayer, SurfaceKind kind,
190 const TrackletProjectionCache& edgeCache, int pivotROF, auto&& emit) {
191 if (!mFrame->isROFEnabled(fromLayer, pivotROF)) {
192 return;
193 }
194 // Derive a diamond vertex for this pivot ROF; each invocation owns its
195 // stack frame, so this is safe inside the parallel dispatch.
196 Vertex diamondForROF{};
197 gsl::span<const Vertex> primaryVertices;
198 if (trkParam.UseDiamond) {
199 diamondForROF = diamondVertexForROF(diamondVert, mFrame->getROFViews(fromLayer).overlap,
200 mFrame->getROFLocalLayer(fromLayer), pivotROF);
201 primaryVertices = gsl::span<const Vertex>(&diamondForROF, 1);
202 } else {
203 primaryVertices = mFrame->getPrimaryVertices(fromLayer, pivotROF);
204 }
205 if (primaryVertices.empty()) {
206 return;
207 }
208 const int startVtx = iVertex >= 0 ? iVertex : 0;
209 const int endVtx = iVertex >= 0 ? o2::gpu::CAMath::Min(iVertex + 1, int(primaryVertices.size())) : int(primaryVertices.size());
210 if (endVtx <= startVtx || (iVertex + 1) > primaryVertices.size()) {
211 return;
212 }
213
214 const auto& rofOverlap = mFrame->getROFOverlap(fromLayer, toLayer, pivotROF);
215 if (!rofOverlap.getEntries()) {
216 return;
217 }
218
219 auto layer0 = mFrame->getClustersOnLayer(pivotROF, fromLayer);
220 if (layer0.empty()) {
221 return;
222 }
223
224 for (int iCluster = 0; iCluster < int(layer0.size()); ++iCluster) {
225 const GlobalMeasurement& sourceMeasurement = layer0[iCluster];
226 const int currentSortedIndex = mFrame->getSortedIndex(pivotROF, fromLayer, iCluster);
227 if (mFrame->isClusterUsed(fromLayer, sourceMeasurement.clusterId)) {
228 continue;
229 }
230
231 for (int iV = startVtx; iV < endVtx; ++iV) {
232 const auto& pv = primaryVertices[iV];
233 if (!mFrame->isVertexCompatible(fromLayer, pivotROF, pv)) {
234 continue;
235 }
236 if (pv.isFlagSet(Vertex::Flags::UPCMode) != trkParam.PassFlags[IterationStep::SelectUPCVertices]) {
237 continue;
238 }
239 const auto& indexTableUtils = mFrame->getIndexTableUtils(toLayer);
240 TrackletSearchWindow window{};
241 if (!projectTrackletSearchWindow(sourceMeasurement, pv, mFrame->getBeamPositionVariance(),
242 kind, edgeCache, indexTableUtils,
243 mKernelParameters.nSigmaCut, window)) {
244 continue;
245 }
246 const auto bins = window.bins;
247 int rowBinsNum = bins.w - bins.y + 1;
248 if (rowBinsNum < 0) {
249 rowBinsNum += indexTableUtils.getNrowBins();
250 }
251 rowBinsNum = std::max(0, rowBinsNum);
252
253 for (int targetROF = rofOverlap.getFirstEntry(); targetROF < rofOverlap.getEntriesBound(); ++targetROF) {
254 if (!mFrame->isROFEnabled(toLayer, targetROF)) {
255 continue;
256 }
257 auto layer1 = mFrame->getClustersOnLayer(targetROF, toLayer);
258 if (layer1.empty()) {
259 continue;
260 }
261 const auto ts = mFrame->getROFTimeStamp(fromLayer, pivotROF, toLayer, targetROF);
262 if (!ts.isCompatible(pv.getTimeStamp())) {
263 continue;
264 }
265 const auto& targetIndexTable = mFrame->getIndexTable(targetROF, toLayer);
266 const int colBinRange = (bins.z - bins.x) + 1;
267 for (int iRow = 0; iRow < rowBinsNum; ++iRow) {
268 int iRowBin = bins.y + iRow;
269 iRowBin %= indexTableUtils.getNrowBins();
270 if (iRowBin < 0 || iRowBin >= indexTableUtils.getNrowBins()) {
271 break;
272 }
273 const int firstBinIdx = indexTableUtils.getBinIndex(bins.x, iRowBin);
274 const int maxBinIdx = firstBinIdx + colBinRange;
275 const int firstRow = targetIndexTable[firstBinIdx];
276 const int lastRow = targetIndexTable[maxBinIdx];
277 for (int iNext = firstRow; iNext < lastRow; ++iNext) {
278 if (iNext >= int(layer1.size())) {
279 break;
280 }
281 const GlobalMeasurement& targetMeasurement = layer1[iNext];
282 if (mFrame->isClusterUsed(toLayer, targetMeasurement.clusterId)) {
283 continue;
284 }
285
286 const float targetReferenceCoordinate = kind == SurfaceKind::Cylinder ? targetMeasurement.radius : targetMeasurement.z;
287 const float targetProjectedCoordinate = kind == SurfaceKind::Cylinder ? targetMeasurement.z : targetMeasurement.radius;
288 const float referenceDelta = targetReferenceCoordinate - window.sourceReferenceCoordinate;
289 const float candidatePrediction = window.sourceProjectedCoordinate + window.slope * referenceDelta;
290 const float candidateVariance = window.varianceConstant +
291 referenceDelta * (window.varianceLinear + referenceDelta * window.varianceQuadratic);
292 const float projectedResidual = candidatePrediction - targetProjectedCoordinate;
293 const float phiResidual = std::remainder(window.phiPrediction - targetMeasurement.phi, o2::constants::math::TwoPI);
294
295 if (!(candidateVariance > 0.f && window.phiVariance > 0.f)) {
296 continue;
297 }
298 const float chi2 = o2::its::math_utils::Sq(projectedResidual) / candidateVariance +
299 o2::its::math_utils::Sq(phiResidual) / window.phiVariance;
300 if (chi2 >= o2::its::math_utils::Sq(mKernelParameters.nSigmaCut)) {
301 continue;
302 }
303 // The segment dip follows the directed edge for every surface kind.
304 // A vanishing transverse chord also leaves its azimuth undefined.
305 const float transverseChord = std::hypot(targetMeasurement.x - sourceMeasurement.x,
306 targetMeasurement.y - sourceMeasurement.y);
307 if (!(transverseChord > 1.e-6f)) {
308 continue;
309 }
310 const float tanL = (targetMeasurement.z - sourceMeasurement.z) / transverseChord;
311 const float phi{o2::gpu::GPUCommonMath::ATan2(sourceMeasurement.y - targetMeasurement.y,
312 sourceMeasurement.x - targetMeasurement.x)};
313 emit(currentSortedIndex, mFrame->getSortedIndex(targetROF, toLayer, iNext), tanL, phi, ts);
314 }
315 }
316 }
317 }
318 }
319 };
320
321 const int maxConcurrency = std::max(1, mTaskArena->max_concurrency());
322 const int nConcurrentSinks = std::min(static_cast<int>(edgeIds.size()), maxConcurrency);
323 tbb::parallel_for(0, static_cast<int>(edgeIds.size()), [&](const int edgeIndex) {
324 const auto edgeId = edgeIds[edgeIndex];
325 const auto& edge = topology.getEdge(edgeId);
326 const int fromLayer = edge.from.value();
327 const int toLayer = edge.to.value();
328 const auto kind = topology.getSurface(edge.from).kind;
329 const TrackletProjectionCache edgeCache{
330 fromLayer, toLayer, context.detectorConfiguration.getRepresentativeRadius(edge.from), context.detectorConfiguration.getRepresentativeRadius(edge.to),
331 mFrame->getMinR(toLayer), mFrame->getMaxR(toLayer),
332 mFrame->getMinZ(toLayer), mFrame->getMaxZ(toLayer),
333 context.detectorConfiguration.positionResolutions[fromLayer],
334 scratch.getEdgeMSAngle(edgeId.value()), scratch.getEdgePhiCut(edgeId.value())};
335 const int endROF = mFrame->getROFTiming(fromLayer).mNROFsTF;
336 const auto key = CapacityEstimator::makeKey(SlabSite::Tracklets, iteration, iVertex + 1, edgeId);
337 const auto scale = static_cast<double>(mFrame->getClusters()[fromLayer].size());
338 const auto capacity = mFrame->getCapacityEstimator().capacity(key, scale);
339 UnorderedSlabSink<Tracklet> sink{{.capacity = capacity, .nThreads = maxConcurrency, .nConcurrentSinks = nConcurrentSinks}, mMemoryPool.get()};
340 tbb::parallel_for(0, endROF, [&](const int pivotROF) {
341 auto& handle = sink.local();
342 forTracklets(fromLayer, toLayer, kind, edgeCache, pivotROF,
343 [&handle](auto&&... args) { handle.emplace(std::forward<decltype(args)>(args)...); });
344 });
345 const auto stats = sink.stats();
346 sink.finalizeUnordered(scratch.getTracklets()[edgeId.value()]);
347 mFrame->getCapacityEstimator().update(key, scale, stats.requested, stats.capacity, stats.emitted,
348 stats.spilled, stats.overflowed, stats.memoryLimited);
349 });
350
351 tbb::parallel_for(0, static_cast<int>(edgeIds.size()), [&](const int edgeIndex) {
352 const auto edgeId = edgeIds[edgeIndex];
354 // duplicates can exist simply since we evaluate per vertex
355 auto& trkl{scratch.getTracklets()[edgeId.value()]};
356 std::sort(trkl.begin(), trkl.end());
357 trkl.erase(std::unique(trkl.begin(), trkl.end()), trkl.end());
358 trkl.shrink_to_fit();
359 auto& lut{scratch.getTrackletsLookupTable()[edgeId.value()]};
360 if (!trkl.empty()) {
361 for (const auto& tkl : trkl) {
362 lut[tkl.firstClusterIndex + 1]++;
363 }
364 std::inclusive_scan(lut.begin(), lut.end(), lut.begin());
365 }
366 });
367
369 if (mFrame->hasMCinformation() && trkParam.CreateArtefactLabels) {
370 tbb::parallel_for(0, static_cast<int>(edgeIds.size()), [&](const int edgeIndex) {
371 const auto edgeId = edgeIds[edgeIndex];
372 const auto& edge = topology.getEdge(edgeId);
373 const int fromLayer = edge.from.value();
374 const int toLayer = edge.to.value();
375 for (auto& trk : scratch.getTracklets()[edgeId.value()]) {
376 MCCompLabel label;
377 const auto currentId = mFrame->getClusters()[fromLayer][trk.firstClusterIndex].clusterId;
378 const auto nextId = mFrame->getClusters()[toLayer][trk.secondClusterIndex].clusterId;
379 for (const auto& lab1 : mFrame->getLabels(LayerId{static_cast<uint16_t>(fromLayer)}, currentId)) {
380 for (const auto& lab2 : mFrame->getLabels(LayerId{static_cast<uint16_t>(toLayer)}, nextId)) {
381 if (lab1 == lab2 && lab1.isValid()) {
382 label = lab1;
383 break;
384 }
385 }
386 if (label.isValid()) {
387 break;
388 }
389 }
390 scratch.getTrackletsLabel(edgeId.value()).emplace_back(label);
391 }
392 });
393 }
394 });
395}
396
397void TrackerTraits::computeLayerCells(IterationContext& context, const int iteration)
398{
399 auto& scratch = context.scratch;
400 const auto scratchCellCount = scratch.getCells().size();
401 for (size_t cellPathId = 0; cellPathId < scratchCellCount; ++cellPathId) {
402 deepVectorClear(scratch.getCells()[cellPathId]);
403 deepVectorClear(scratch.getCellsLookupTable()[cellPathId]);
404 if (context.frame.hasMCinformation() && context.configuration.parameters.CreateArtefactLabels) {
405 deepVectorClear(scratch.getCellsLabel(cellPathId));
406 }
407 }
408
409 const auto cellIds = context.configuration.cellIds();
410 const auto& mMemoryPool = scratch.getMemoryPool();
411 const auto& trkParam = context.configuration.parameters;
412 const auto mBz = context.bz;
413 const auto& mTraversalGraph = context.topology;
414 const auto& mKernelParameters = context.configuration.kernelParameters;
415 const auto& mLayerGlobalMeasurements = context.layerGlobalMeasurements;
416 const auto& topology = mTraversalGraph;
417
418 mTaskArena->execute([&] {
419 auto forTrackletCells = [&](int firstEdgeId, int secondEdgeId, const std::array<int, 3>& hitLayers, int iTracklet, auto&& emit) {
420 const Tracklet& currentTracklet{scratch.getTracklets()[firstEdgeId][iTracklet]};
421 const int nextLayerClusterIndex{currentTracklet.secondClusterIndex};
422 const int nextLayerFirstTrackletIndex{scratch.getTrackletsLookupTable()[secondEdgeId][nextLayerClusterIndex]};
423 const int nextLayerLastTrackletIndex{scratch.getTrackletsLookupTable()[secondEdgeId][nextLayerClusterIndex + 1]};
424 for (int iNextTracklet{nextLayerFirstTrackletIndex}; iNextTracklet < nextLayerLastTrackletIndex; ++iNextTracklet) {
425 const Tracklet& nextTracklet{scratch.getTracklets()[secondEdgeId][iNextTracklet]};
426 if (nextTracklet.firstClusterIndex != nextLayerClusterIndex) {
427 break;
428 }
429 if (!currentTracklet.getTimeStamp().isCompatible(nextTracklet.getTimeStamp())) {
430 continue;
431 }
432
434 const int sortedId[3]{currentTracklet.firstClusterIndex, nextTracklet.firstClusterIndex, nextTracklet.secondClusterIndex};
435
436 const float edgeMSAngle = scratch.getEdgeMSAngle(secondEdgeId);
437 const float angularTolerance = mKernelParameters.nSigmaCut * edgeMSAngle;
438 const float lambda01 = std::atan(currentTracklet.tanLambda);
439 const float lambda12 = std::atan(nextTracklet.tanLambda);
440 const float sinTheta = std::max(std::abs(std::cos(0.5f * (lambda01 + lambda12))),
441 o2::constants::math::Almost0);
442 const bool isDisk = topology.getSurface(LayerId{static_cast<uint16_t>(hitLayers[1])}).kind == SurfaceKind::Disk;
443 // The disk edge estimate uses pT_min as p. Convert to the candidate
444 // momentum with 1/p = sin(theta)/pT_min for the dip-angle allowance.
445 const float dipAngleTolerance = isDisk ? angularTolerance * sinTheta : angularTolerance;
446 const float deltaLambda = std::abs(lambda01 - lambda12);
447 if (deltaLambda > dipAngleTolerance) {
448 continue;
449 }
450
451 const auto& inner = mLayerGlobalMeasurements[hitLayers[0]][sortedId[0]];
452 const auto& middle = mLayerGlobalMeasurements[hitLayers[1]][sortedId[1]];
453 const auto& outer = mLayerGlobalMeasurements[hitLayers[2]][sortedId[2]];
454 const float length01 = std::hypot(inner.x - middle.x, inner.y - middle.y);
455 const float length12 = std::hypot(middle.x - outer.x, middle.y - outer.y);
456 const float maximumCurvature = std::min({std::abs(o2::constants::math::B2C * mBz) /
457 mKernelParameters.trackletMinPt,
458 2.f / length01,
459 2.f / length12});
460 const float maximumBending =
461 std::asin(std::clamp(0.5f * maximumCurvature * length01, 0.f, 1.f)) +
462 std::asin(std::clamp(0.5f * maximumCurvature * length12, 0.f, 1.f));
463 const float deltaPhi = std::abs(std::remainder(currentTracklet.phi - nextTracklet.phi,
464 o2::constants::math::TwoPI));
465 // For disks, projection into azimuth cancels the momentum correction.
466 const float azimuthalTolerance = isDisk ? angularTolerance : angularTolerance / sinTheta;
467 if (deltaPhi > maximumBending + azimuthalTolerance) {
468 continue;
469 }
470
471 const std::array<GlobalMeasurement, 3> measurements{inner, middle, outer};
472 TripletFitFactor tripletFactor{};
473 if (makeTripletFitFactor(measurements, tripletFactor)) {
474 TimeEstBC ts = currentTracklet.getTimeStamp();
475 ts += nextTracklet.getTimeStamp();
476 // Build directly from the resolved plan positions; plan validation
477 // already checked them against the cell's hit-surface mask.
478 const LayerMask hitLayerMask{hitLayers[0], hitLayers[1], hitLayers[2]};
479 Triplet seed{hitLayerMask, sortedId[0], sortedId[1], sortedId[2], iTracklet, iNextTracklet, ts};
480 seed.tripletFactor() = tripletFactor;
481 emit(std::move(seed));
482 }
483 }
484 };
485
486 const int maxConcurrency = std::max(1, mTaskArena->max_concurrency());
487 for (const auto cellId : cellIds) {
488 const auto& cellTopology = topology.getPath(cellId);
489 const auto firstEdgeId = cellTopology.first;
490 const auto secondEdgeId = cellTopology.second;
491 if (scratch.getTracklets()[firstEdgeId.value()].empty() ||
492 scratch.getTracklets()[secondEdgeId.value()].empty()) {
493 continue;
494 }
495
496 const auto& firstEdge = topology.getEdge(cellTopology.first);
497 const auto& secondEdge = topology.getEdge(cellTopology.second);
498 const std::array<int, 3> layers{firstEdge.from.value(), firstEdge.to.value(), secondEdge.to.value()};
499
500 auto& layerCells = scratch.getCells()[cellId.value()];
501 auto& lut = scratch.getCellsLookupTable()[cellId.value()];
502 const int currentLayerTrackletsNum{static_cast<int>(scratch.getTracklets()[firstEdgeId.value()].size())};
503 const auto key = CapacityEstimator::makeKey(SlabSite::Cells, iteration, 0, cellId);
504 const auto scale = static_cast<double>(currentLayerTrackletsNum);
505 const auto capacity = context.frame.getCapacityEstimator().capacity(key, scale);
506 GroupedSlabSink<Triplet> sink{{.capacity = capacity, .nThreads = maxConcurrency}, mMemoryPool.get()};
507 tbb::parallel_for(0, currentLayerTrackletsNum, [&](const int iTracklet) {
508 auto& handle = sink.local();
509 handle.beginProducer(iTracklet);
510 forTrackletCells(firstEdgeId.value(), secondEdgeId.value(), layers, iTracklet,
511 [&handle](Triplet seed) { handle.emplace(std::move(seed)); });
512 });
513 const auto stats = sink.stats();
514 sink.finalizeGrouped(static_cast<size_t>(currentLayerTrackletsNum), lut, layerCells);
515 context.frame.getCapacityEstimator().update(key, scale, stats.requested, stats.capacity, stats.emitted,
516 stats.spilled, stats.overflowed, stats.memoryLimited);
517
518 if (context.frame.hasMCinformation() && trkParam.CreateArtefactLabels) {
519 auto& labels = scratch.getCellsLabel(cellId.value());
520 labels.reserve(layerCells.size());
521 for (const auto& cell : layerCells) {
522 MCCompLabel currentLab{scratch.getTrackletsLabel(firstEdgeId.value())[cell.getFirstTrackletIndex()]};
523 MCCompLabel nextLab{scratch.getTrackletsLabel(secondEdgeId.value())[cell.getSecondTrackletIndex()]};
524 labels.emplace_back(currentLab == nextLab ? currentLab : MCCompLabel());
525 }
526 }
527 }
528 });
529
530 const auto scratchEdgeCount = scratch.getTracklets().size();
531 for (size_t edgeId = 0; edgeId < scratchEdgeCount; ++edgeId) {
532 deepVectorClear(scratch.getTracklets()[edgeId]);
533 deepVectorClear(scratch.getTrackletsLabel(edgeId));
534 }
535}
536
537void TrackerTraits::findCellsNeighbours(IterationContext& context, const int iteration)
538{
539 auto& scratch = context.scratch;
540 const auto& memoryPool = scratch.getMemoryPool();
541 const auto& topology = context.topology;
542 const auto& globalMeasurements = context.layerGlobalMeasurements;
543 const auto& params = context.configuration.kernelParameters;
544 for (std::size_t slot = 0; slot < scratch.getCellsNeighbours().size(); ++slot) {
545 deepVectorClear(scratch.getCellsNeighbours()[slot]);
546 deepVectorClear(scratch.getCellsNeighboursTopology()[slot]);
547 deepVectorClear(scratch.getCellsNeighboursLUT()[slot]);
548 }
549 const auto& scheduledCells = context.configuration.topology.scheduledPaths;
550 const auto scratchCellCount = scratch.getCells().size();
551 if (scratch.getCellsLookupTable().size() != scratchCellCount ||
552 scratch.getCellsNeighbours().size() != scratchCellCount ||
553 scratch.getCellsNeighboursTopology().size() != scratchCellCount ||
554 scratch.getCellsNeighboursLUT().size() != scratchCellCount) {
555 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
556 }
557 mTaskArena->execute([&] {
558 std::vector<bounded_vector<TripletNeighbour>> cellsNeighboursByTarget;
559 cellsNeighboursByTarget.reserve(scratchCellCount);
560 for (size_t cellPathId = 0; cellPathId < scratchCellCount; ++cellPathId) {
561 cellsNeighboursByTarget.emplace_back(memoryPool.get());
562 }
563
564 for (const auto cellId : scheduledCells) {
565 if (static_cast<size_t>(cellId.value()) >= scratchCellCount ||
566 static_cast<size_t>(cellId.value()) >= scratch.getCellsLookupTable().size()) {
567 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
568 }
569 const auto& cellTopology = topology.getPath(cellId);
570 const float currentMSAngle = scratch.getEdgeMSAngle(cellTopology.second.value());
571 const float currentAngularVariance = currentMSAngle * currentMSAngle;
572 if (scratch.getCells()[cellId.value()].empty()) {
573 continue;
574 }
575 const auto successors = topology.getPathsStartingWithEdge(cellTopology.second);
576 if (!successors.getEntries()) {
577 continue;
578 }
579
580 struct SuccessorBinding {
581 CellPathId cellId;
582 float angularVariance;
583 };
584 std::array<SuccessorBinding, MaxLayoutSurfaces> successorBindings{};
585 size_t successorBindingCount = 0;
586 if (successors.getEntries() > successorBindings.size()) {
587 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
588 }
589 for (uint32_t iSuccessor = 0; iSuccessor < successors.getEntries(); ++iSuccessor) {
590 const auto nextCellId = topology.pathsByFirstEdge[successors.getFirstEntry() + iSuccessor];
591 if (static_cast<size_t>(nextCellId.value()) >= scratch.getCells().size() ||
592 static_cast<size_t>(nextCellId.value()) >= scratch.getCellsLookupTable().size()) {
593 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
594 }
595 if (scratch.getCells()[nextCellId.value()].empty() ||
596 scratch.getCellsLookupTable()[nextCellId.value()].empty()) {
597 continue;
598 }
599 const auto& nextCellTopology = topology.getPath(nextCellId);
600 const float nextMSAngle = scratch.getEdgeMSAngle(nextCellTopology.second.value());
601 successorBindings[successorBindingCount++] = {nextCellId, nextMSAngle * nextMSAngle};
602 }
603
604 const int maxConcurrency = std::max(1, mTaskArena->max_concurrency());
605 const auto key = CapacityEstimator::makeKey(SlabSite::Neighbours, iteration, 0, cellId);
606 const auto scale = static_cast<double>(scratch.getCells()[cellId.value()].size());
607 const auto capacity = context.frame.getCapacityEstimator().capacity(key, scale);
608 UnorderedSlabSink<TripletNeighbour> sink{{.capacity = capacity, .nThreads = maxConcurrency}, memoryPool.get()};
609 tbb::parallel_for(0, static_cast<int>(scratch.getCells()[cellId.value()].size()), [&](const int iCell) {
610 auto& handle = sink.local();
611 const auto& currentTriplet{scratch.getCells()[cellId.value()][iCell]};
612 const int nextLayerTrackletIndex{currentTriplet.getSecondTrackletIndex()};
613 for (size_t iSuccessor = 0; iSuccessor < successorBindingCount; ++iSuccessor) {
614 const auto& successor = successorBindings[iSuccessor];
615 const auto& nextCellLUT = scratch.getCellsLookupTable()[successor.cellId.value()];
616 if (nextLayerTrackletIndex < 0 || nextLayerTrackletIndex + 1 >= static_cast<int>(nextCellLUT.size())) {
617 continue;
618 }
619 const int nextLayerFirstCellIndex{nextCellLUT[nextLayerTrackletIndex]};
620 const int nextLayerLastCellIndex{nextCellLUT[nextLayerTrackletIndex + 1]};
621 if (nextLayerFirstCellIndex < 0 || nextLayerLastCellIndex < nextLayerFirstCellIndex ||
622 nextLayerLastCellIndex > static_cast<int>(scratch.getCells()[successor.cellId.value()].size())) {
623 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
624 }
625 for (int iNextCell{nextLayerFirstCellIndex}; iNextCell < nextLayerLastCellIndex; ++iNextCell) {
626 const auto& nextTripletRef{scratch.getCells()[successor.cellId.value()][iNextCell]};
627 if (nextTripletRef.getFirstTrackletIndex() != nextLayerTrackletIndex || !currentTriplet.getTimeStamp().isCompatible(nextTripletRef.getTimeStamp())) {
628 break;
629 }
630
631 const auto currentMiddle = currentTriplet.getClusterReference(1);
632 const auto currentOuter = currentTriplet.getClusterReference(2);
633 const auto nextInner = nextTripletRef.getClusterReference(0);
634 const auto nextMiddle = nextTripletRef.getClusterReference(1);
635 if (currentMiddle.surfacePosition != nextInner.surfacePosition ||
636 currentMiddle.clusterIndex != nextInner.clusterIndex ||
637 currentOuter.surfacePosition != nextMiddle.surfacePosition ||
638 currentOuter.clusterIndex != nextMiddle.clusterIndex) {
639 continue;
640 }
641
642 const std::array<TripleClusterReference, 4> references{
643 currentTriplet.getClusterReference(0), currentMiddle,
644 currentOuter, nextTripletRef.getClusterReference(2)};
645 std::array<GlobalMeasurement, 4> measurements{};
646 bool measurementsValid = true;
647 for (std::size_t hit = 0; hit < references.size(); ++hit) {
648 const auto reference = references[hit];
649 if (reference.surfacePosition < 0 ||
650 static_cast<std::size_t>(reference.surfacePosition) >= globalMeasurements.size() ||
651 reference.clusterIndex < 0 ||
652 static_cast<std::size_t>(reference.clusterIndex) >= globalMeasurements[reference.surfacePosition].size()) {
653 measurementsValid = false;
654 break;
655 }
656 measurements[hit] = globalMeasurements[reference.surfacePosition][reference.clusterIndex];
657 }
658 AdjacentTripletFitResult adjacentFit{};
659 const bool fitValid = measurementsValid &&
661 currentTriplet.tripletFactor(), nextTripletRef.tripletFactor(), measurements,
662 {currentAngularVariance, successor.angularVariance}, adjacentFit);
663 if (!fitValid || adjacentFit.chi2 > params.maxChi2ClusterAttachment) {
664 continue;
665 }
666
667 const int nextLevel = currentTriplet.getLevel() + 1;
668 handle.emplace(cellId.value(), iCell, successor.cellId.value(), iNextCell, nextLevel);
669 }
670 }
671 });
672
673 const auto stats = sink.stats();
674 bounded_vector<TripletNeighbour> sourceNeighbours{memoryPool.get()};
675 sink.finalizeUnordered(sourceNeighbours);
676 context.frame.getCapacityEstimator().update(key, scale, stats.requested, stats.capacity, stats.emitted,
677 stats.spilled, stats.overflowed, stats.memoryLimited);
678 for (const auto& neighbour : sourceNeighbours) {
679 cellsNeighboursByTarget[neighbour.nextCellTopology].push_back(neighbour);
680 if (neighbour.level > scratch.getCells()[neighbour.nextCellTopology][neighbour.nextCell].getLevel()) {
681 scratch.getCells()[neighbour.nextCellTopology][neighbour.nextCell].setLevel(neighbour.level);
682 }
683 }
684 }
685
686 for (size_t cellPathId = 0; cellPathId < scratchCellCount; ++cellPathId) {
687 auto& cellsNeighbours = cellsNeighboursByTarget[cellPathId];
688 if (cellsNeighbours.empty()) {
689 continue;
690 }
691
692 std::sort(cellsNeighbours.begin(), cellsNeighbours.end(), [](const auto& a, const auto& b) {
693 return std::tie(a.nextCell, a.cellTopology, a.cell) < std::tie(b.nextCell, b.cellTopology, b.cell);
694 });
695
696 auto& cellsNeighbourLUT = scratch.getCellsNeighboursLUT()[cellPathId];
697 cellsNeighbourLUT.assign(scratch.getCells()[cellPathId].size(), 0);
698 for (const auto& neigh : cellsNeighbours) {
699 ++cellsNeighbourLUT[neigh.nextCell];
700 }
701 std::inclusive_scan(cellsNeighbourLUT.begin(), cellsNeighbourLUT.end(), cellsNeighbourLUT.begin());
702
703 scratch.getCellsNeighbours()[cellPathId].reserve(cellsNeighbours.size());
704 scratch.getCellsNeighboursTopology()[cellPathId].reserve(cellsNeighbours.size());
705 std::ranges::transform(cellsNeighbours, std::back_inserter(scratch.getCellsNeighbours()[cellPathId]), [](const auto& neigh) { return neigh.cell; });
706 std::ranges::transform(cellsNeighbours, std::back_inserter(scratch.getCellsNeighboursTopology()[cellPathId]), [](const auto& neigh) { return neigh.cellTopology; });
707 }
708 });
709 for (auto& cellLUT : scratch.getCellsLookupTable()) {
710 deepVectorClear(cellLUT);
711 }
712}
713
714bool TrackerTraits::buildTrackSeed(IterationContext& context, int,
715 const Triplet& cell, TrackSeed& output) const
716{
717 std::array<const GlobalMeasurement*, 3> globals{};
718 std::array<const SurfaceMeasurement*, 3> measurements{};
719 std::array<const SurfaceDescriptor*, 3> surfaces{};
720 for (int hit = 0; hit < 3; ++hit) {
721 const auto reference = cell.getClusterReference(hit);
722 const auto surface = LayerId{static_cast<uint16_t>(reference.surfacePosition)};
723 globals[hit] = &context.layerGlobalMeasurements[reference.surfacePosition][reference.clusterIndex];
724 measurements[hit] = context.frame.getSurfaceMeasurement(surface, globals[hit]->clusterId);
725 surfaces[hit] = &context.topology.getSurface(surface);
726 }
727
728 SurfaceTrackState state{};
729 float chi2{0.f};
730 const auto& outer = *measurements[2];
731 const auto kind = surfaces[2]->kind;
732
733 float sinPhi = 0.f, cosPhi = 0.f, tanLambda = 0.f, qOverPt = 1.f / o2::track::kMostProbablePt;
734 float curvatureSquared = 1.f;
735
736 state.referenceCoordinate = outer.frame.q;
737 state.alpha = (kind == SurfaceKind::Cylinder) ? outer.frame.frameAngle : 0.f;
738 state.parameters[0] = outer.frame.u;
739 state.parameters[1] = outer.frame.v;
740
741 float cosAlpha, sinAlpha, x[3], y[3];
742 o2::math_utils::detail::sincos(state.alpha, sinAlpha, cosAlpha);
743 for (int i{0}; i < 3; ++i) {
744 const auto& pos = globals[i]->position;
745 x[i] = pos.x * cosAlpha + pos.y * sinAlpha;
746 y[i] = -pos.x * sinAlpha + pos.y * cosAlpha;
747 }
748 const float dx = x[2] - x[1];
749 const float dy = y[2] - y[1];
750 const float chordLength = std::hypot(dx, dy);
751 const float inverseLength = 1.f / chordLength;
752
753 const float chordCos = dx * inverseLength;
754 const float chordSin = dy * inverseLength;
755 tanLambda = -0.5f *
756 (math_utils::computeTanDipAngle(x[0], y[0], x[1], y[1], globals[0]->position.z, globals[1]->position.z) +
757 math_utils::computeTanDipAngle(x[1], y[1], x[2], y[2], globals[1]->position.z, globals[2]->position.z));
758
759 if (std::abs(context.bz) < 0.01f) {
760 cosPhi = chordCos;
761 sinPhi = chordSin;
762 } else {
763 const float curvature =
764 math_utils::computeCurvature(
765 x[2], y[2], x[1], y[1], x[0], y[0]);
766
767 const float halfSin = 0.5f * curvature * chordLength;
768 const float halfCos =
769 std::sqrt((1.f - halfSin) * (1.f + halfSin));
770
771 cosPhi = chordCos * halfCos - chordSin * halfSin;
772 sinPhi = chordSin * halfCos + chordCos * halfSin;
773 qOverPt = curvature /
774 (context.bz * o2::constants::math::B2C);
775 curvatureSquared = curvature * curvature;
776 }
777
778 float phi = o2::gpu::GPUCommonMath::ASin(sinPhi);
779 if (cosPhi < 0.f) {
780 phi = o2::constants::math::PI - phi;
781 } else if (phi < 0.f) {
782 phi += o2::constants::math::TwoPI;
783 }
784
785 state.parameters[2] = (kind == SurfaceKind::Cylinder) ? sinPhi : phi;
786 state.parameters[3] = tanLambda;
787 state.parameters[4] = qOverPt;
788 state.covariance[packedCovarianceIndex(0, 0)] = outer.covariance.uu;
789 state.covariance[packedCovarianceIndex(1, 0)] = outer.covariance.uv;
790 state.covariance[packedCovarianceIndex(1, 1)] = outer.covariance.vv;
791 state.covariance[packedCovarianceIndex(2, 2)] = (kind == SurfaceKind::Cylinder) ? o2::track::kCSnp2max : o2::track::kCSnp2max / (cosPhi * cosPhi);
792 state.covariance[packedCovarianceIndex(3, 3)] = o2::track::kCTgl2max;
793 state.covariance[packedCovarianceIndex(4, 4)] = o2::track::kC1Pt2max * std::clamp(curvatureSquared, 0.0005f, 1.f);
794
795 state.kind = kind;
796 state.flags = 0;
797 state.absCharge = kCompatibilityAbsCharge;
798 state.pid = kCompatibilityPID;
799
800 const std::array<const SurfaceMeasurement*, 2> attachmentMeasurements{measurements[1], measurements[0]};
801 const std::array<const SurfaceDescriptor*, 2> attachmentSurfaces{surfaces[1], surfaces[0]};
802 for (int step = 0; step < 2; ++step) {
803 const auto& targetSurface = *attachmentSurfaces[step];
804 if (!Propagator::attachMeasurement(
805 state, targetSurface, *attachmentMeasurements[step], context.bz,
806 material::MaterialTraversalDirection::OppositeMomentum,
807 step == 1,
808 context.configuration.kernelParameters.maxChi2ClusterAttachment,
809 chi2)) {
810 return false;
811 }
812 }
813
814 output = TrackSeed{cell, state, chi2};
815 return true;
816}
817
818template <typename InputSeed>
819void TrackerTraits::processNeighbours(IterationContext& context, int iteration, CellPathId startingPath,
820 int defaultCellPathId, int startLevel, int currentLevel,
821 const bounded_vector<InputSeed>& currentSeeds,
822 bounded_vector<RoadSeedEmission>& updatedCells,
823 const TrackingKernelParameters& params)
824{
825 auto* scratch = &context.scratch;
826 const auto& mMemoryPool = scratch->getMemoryPool();
827 const auto mBz = context.bz;
828 const auto& mLayerGlobalMeasurements = context.layerGlobalMeasurements;
829 const int activeSurfaceCount = context.configuration.topology.nLayers;
830
831 mTaskArena->execute([&] {
832 auto forTripletNeighbours = [&](int iCell, auto&& emit) {
833 const auto& input = currentSeeds[iCell];
834 const auto& currentCell = [&]() -> const auto& {
835 if constexpr (std::is_same_v<InputSeed, Triplet>) {
836 return input;
837 } else {
838 return input.seed;
839 }
840 }();
841 int cellId = iCell;
842 int cellPathId = defaultCellPathId;
843 if constexpr (std::is_same_v<InputSeed, RoadSeedEmission>) {
844 cellId = input.cellId;
845 cellPathId = input.cellPathId;
846 }
847
848 if (currentCell.getLevel() != currentLevel) {
849 return;
850 }
851 if constexpr (std::is_same_v<InputSeed, Triplet>) {
852 for (int layer = 0; layer < activeSurfaceCount; ++layer) {
853 const int clusterIndex = currentCell.getCluster(layer);
854 if (clusterIndex != o2::its::constants::UnusedIndex &&
855 context.frame.isClusterUsed(layer, mLayerGlobalMeasurements[layer][clusterIndex].clusterId)) {
856 return;
857 }
858 }
859 }
860
861 if (cellPathId < 0 || scratch->getCellsNeighboursLUT()[cellPathId].empty()) {
862 return;
863 }
864 const int startNeighbourId{cellId ? scratch->getCellsNeighboursLUT()[cellPathId][cellId - 1] : 0};
865 const int endNeighbourId{scratch->getCellsNeighboursLUT()[cellPathId][cellId]};
866 TrackSeed baseSeed{};
867 if constexpr (std::is_same_v<InputSeed, Triplet>) {
868
869 if (!buildTrackSeed(context, cellPathId, currentCell, baseSeed)) {
870 return;
871 }
872 } else {
873 baseSeed = currentCell;
874 }
875 for (int iNeighbourCell{startNeighbourId}; iNeighbourCell < endNeighbourId; ++iNeighbourCell) {
876 const int neighbourCellPathId = scratch->getCellsNeighboursTopology()[cellPathId][iNeighbourCell];
877 const int neighbourCellId = scratch->getCellsNeighbours()[cellPathId][iNeighbourCell];
878 const auto& neighbourCell = scratch->getCells()[neighbourCellPathId][neighbourCellId];
879 if (neighbourCell.getSecondTrackletIndex() != currentCell.getFirstTrackletIndex()) {
880 continue;
881 }
882 if (!currentCell.getTimeStamp().isCompatible(neighbourCell.getTimeStamp())) {
883 continue;
884 }
885 if (currentCell.getLevel() - 1 != neighbourCell.getLevel()) {
886 continue;
887 }
888 const int neighbourLayer = neighbourCell.getInnerLayer();
889 if (neighbourLayer < 0 || neighbourLayer >= activeSurfaceCount) {
890 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
891 }
892 const int neighbourCluster = neighbourCell.getFirstClusterIndex();
893 const auto& neighbourGlobal = mLayerGlobalMeasurements[neighbourLayer][neighbourCluster];
894 if (context.frame.isClusterUsed(neighbourLayer, neighbourGlobal.clusterId)) {
895 continue;
896 }
897
899 TrackSeed seed{baseSeed};
900 seed.getTimeStamp() = currentCell.getTimeStamp();
901 seed.getTimeStamp() += neighbourCell.getTimeStamp();
902
903 const auto* measurement = context.frame.getSurfaceMeasurement(LayerId{static_cast<uint16_t>(neighbourLayer)}, neighbourGlobal.clusterId);
904 if (measurement == nullptr) {
905 continue;
906 }
907 float chi2 = seed.getChi2();
908
909 const bool attached = Propagator::attachMeasurement(seed.state(), context.topology.getSurface(LayerId{static_cast<uint16_t>(neighbourLayer)}), *measurement, mBz,
910 material::MaterialTraversalDirection::OppositeMomentum, true,
911 params.maxChi2ClusterAttachment, chi2);
912 if (!attached) {
913 continue;
914 }
915 seed.setChi2(chi2);
916
917 seed.setCluster(neighbourLayer, neighbourCluster);
918 auto hitLayerMask = seed.getHitLayerMask();
919 hitLayerMask.set(neighbourLayer);
920 seed.setHitLayerMask(hitLayerMask);
921 seed.setLevel(neighbourCell.getLevel());
922 seed.setFirstTrackletIndex(neighbourCell.getFirstTrackletIndex());
923 seed.setSecondTrackletIndex(neighbourCell.getSecondTrackletIndex());
924 emit(RoadSeedEmission{std::move(seed), neighbourCellId, neighbourCellPathId});
925 }
926 };
927
928 const int nCells = static_cast<int>(currentSeeds.size());
929 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, iteration,
930 CapacityEstimator::makeVariant(startLevel, currentLevel),
931 startingPath);
932 const auto scale = static_cast<double>(nCells);
933 const auto capacity = context.frame.getCapacityEstimator().capacity(key, scale);
934 GroupedSlabSink<RoadSeedEmission> sink{{.capacity = capacity, .nThreads = std::max(1, mTaskArena->max_concurrency())}, mMemoryPool.get()};
935 tbb::parallel_for(0, nCells, [&](const int iCell) {
936 auto& handle = sink.local();
937 handle.beginProducer(iCell);
938 forTripletNeighbours(iCell, [&handle](RoadSeedEmission emission) { handle.emplace(std::move(emission)); });
939 });
940 const auto stats = sink.stats();
941 bounded_vector<int> lut{mMemoryPool.get()};
942 sink.finalizeGrouped(static_cast<size_t>(nCells), lut, updatedCells);
943 context.frame.getCapacityEstimator().update(key, scale, stats.requested, stats.capacity, stats.emitted,
944 stats.spilled, stats.overflowed, stats.memoryLimited);
945 });
946}
947
948void TrackerTraits::findRoads(IterationContext& context, const int iteration)
949{
950 auto* scratch = &context.scratch;
951 const auto& mMemoryPool = scratch->getMemoryPool();
952 const auto& trkParam = context.configuration.parameters;
953 const auto mBz = context.bz;
954 const auto& mTraversalGraph = context.topology;
955 const auto& mKernelParameters = context.configuration.kernelParameters;
956 const auto& mLayerGlobalMeasurements = context.layerGlobalMeasurements;
957 const gsl::span<const CellPathId> roadStartCells = context.configuration.topology.roadStartPaths;
958 const int activeSurfaceCount = context.configuration.topology.nLayers;
959 bounded_vector<bounded_vector<int>> firstClusters(activeSurfaceCount, bounded_vector<int>(mMemoryPool.get()), mMemoryPool.get());
960 firstClusters.resize(activeSurfaceCount);
961 // Road starts are the binding's seeding-eligible sparse-plan subsequence.
962 // CellPathId values use compact slots; LayerId directly indexes layout-owned
963 // layer data.
964 // Filter roads by absolute q/pT in parameters[4]'s units, identically for
965 // both families. Non-finite values fail the finite-bound comparison.
966 constexpr float maxAbsQOverPt = 1.e3f;
967 const auto seedingLayerMask = context.topology.seedingLayers;
968 const auto nonSeedingLayerMask = ~seedingLayerMask;
969 const int cellsPerRoad = seedingLayerMask.count() - 2;
970 const auto& componentOffsets = context.configuration.topology.roadStartComponentOffsets;
971 const auto holeLayerMask = context.frame.getDetectorConfiguration().getHoleLayers();
972 if (componentOffsets.empty() || componentOffsets.front() != 0 || componentOffsets.back() != roadStartCells.size()) {
973 throw std::invalid_argument{"CA traversal: sparse topology mismatch (iteration " + std::to_string(iteration) + ")"};
974 }
975 for (size_t component = 0; component + 1 < componentOffsets.size(); ++component) {
976 const auto componentRoadStarts = roadStartCells.subspan(componentOffsets[component],
977 componentOffsets[component + 1] - componentOffsets[component]);
978 for (int startLevel{cellsPerRoad}; startLevel >= trkParam.CellMinimumLevel(); --startLevel) {
979
980 auto seedFilter = [&](const auto& seed) {
981 const auto hitLayerMask = seed.getHitLayerMask();
982 const auto effectiveHoleMask = hitLayerMask.holeMask() & ~nonSeedingLayerMask;
983 // Missing layers may be allowed, but do not count toward MinTrackLength.
984 return effectiveHoleMask.isAllowedHoleMask(trkParam.MaxHoles, holeLayerMask) &&
985 hitLayerMask.count() >= trkParam.MinTrackLength &&
986 std::abs(seed.getQOverPt()) <= maxAbsQOverPt && seed.getChi2() <= trkParam.MaxChi2NDF * ((startLevel + 2) * 2 - 5);
987 };
988
989 bounded_vector<TrackSeed> trackSeeds(mMemoryPool.get());
990 // The binding supplies the ownership-filtered road-start span.
991 for (const auto startId : componentRoadStarts) {
992 // Cell population is per-event/per-vertex data, so check it against
993 // the current vertex rather than caching it in the pass plan.
994 if (scratch->getCells()[startId.value()].empty()) {
995 continue;
996 }
997
998 bounded_vector<RoadSeedEmission> currentCells(mMemoryPool.get()), updatedCells(mMemoryPool.get());
999
1000 processNeighbours(context, iteration, startId, startId.value(), startLevel, startLevel,
1001 scratch->getCells()[startId.value()], updatedCells, mKernelParameters);
1002
1003 int level = startLevel;
1004 while (level > 2 && !updatedCells.empty()) {
1005 currentCells.swap(updatedCells);
1006 deepVectorClear(updatedCells); // Release the previous expansion before producing the next one.
1007 --level;
1008 processNeighbours(context, iteration, startId, o2::its::constants::UnusedIndex, startLevel, level,
1009 currentCells, updatedCells, mKernelParameters);
1010 }
1011 deepVectorClear(currentCells);
1012
1013 const auto accepted = std::count_if(updatedCells.begin(), updatedCells.end(),
1014 [&](const auto& cell) { return seedFilter(cell.seed); });
1015 trackSeeds.reserve(trackSeeds.size() + accepted);
1016 for (auto& cell : updatedCells) {
1017 if (seedFilter(cell.seed)) {
1018 trackSeeds.push_back(std::move(cell.seed));
1019 }
1020 }
1021 }
1022
1023 if (trackSeeds.empty()) {
1024 continue;
1025 }
1026
1027 bounded_vector<TrackingCandidate> tracks(mMemoryPool.get());
1028 mTaskArena->execute([&] {
1029 const int nSeeds = static_cast<int>(trackSeeds.size());
1030 const auto key = CapacityEstimator::makeKey(SlabSite::Tracks, iteration,
1031 CapacityEstimator::makeVariant(startLevel, static_cast<int>(component)), 0);
1032 const auto scale = static_cast<double>(nSeeds);
1033 const auto capacity = context.frame.getCapacityEstimator().capacity(key, scale);
1034 GroupedSlabSink<TrackingCandidate> sink{{.capacity = capacity, .nThreads = std::max(1, mTaskArena->max_concurrency())}, mMemoryPool.get()};
1035 tbb::parallel_for(0, nSeeds, [&](const int iSeed) {
1036 SurfaceTrackState innerState{};
1037 SurfaceTrackState outerState{};
1038 float chi2 = 0.f;
1039
1040 if (!fitTrackSeedLegs(trackSeeds[iSeed], context.frame, mLayerGlobalMeasurements,
1041 mTraversalGraph.getSurfaceCatalogView(), mBz,
1042 trkParam.ShiftRefToCluster, trkParam.MaxChi2ClusterAttachment, trkParam.MaxChi2NDF,
1043 trkParam.RepeatRefitOut, gsl::span<const float>(trkParam.MinPt),
1044 innerState, outerState, chi2)) {
1045 return;
1046 }
1047 TrackingCandidate temporaryTrack;
1048 temporaryTrack.seed = trackSeeds[iSeed];
1049 temporaryTrack.track.innerState = innerState;
1050 temporaryTrack.track.outerState = outerState;
1051 temporaryTrack.track.chi2 = chi2;
1052 auto& handle = sink.local();
1053 handle.beginProducer(iSeed);
1054 handle.emplace(std::move(temporaryTrack));
1055 });
1056 const auto stats = sink.stats();
1057 bounded_vector<int> lut{mMemoryPool.get()};
1058 sink.finalizeGrouped(static_cast<size_t>(nSeeds), lut, tracks);
1059 context.frame.getCapacityEstimator().update(key, scale, stats.requested, stats.capacity, stats.emitted,
1060 stats.spilled, stats.overflowed, stats.memoryLimited);
1061 deepVectorClear(trackSeeds);
1062 });
1063
1064 // Same ordering as o2::its::track::isBetter (longer track, then lower chi2).
1065 std::sort(tracks.begin(), tracks.end(), [](const TrackingCandidate& a, const TrackingCandidate& b) {
1066 const auto ncla = a.getNumberOfClusters();
1067 const auto nclb = b.getNumberOfClusters();
1068 return (ncla == nclb) ? (a.track.chi2 < b.track.chi2) : ncla > nclb;
1069 });
1070 acceptTracks(context, iteration, tracks, firstClusters);
1071 }
1072 }
1073}
1074
1075void TrackerTraits::acceptTracks(IterationContext& context, int iteration,
1076 bounded_vector<TrackingCandidate>& tracks,
1077 bounded_vector<bounded_vector<int>>& firstClusters)
1078{
1079 auto* scratch = &context.scratch;
1080 auto* mFrame = &context.frame;
1081 const auto& trkParam = context.configuration.parameters;
1082 const auto& mLayerGlobalMeasurements = context.layerGlobalMeasurements;
1083 const int activeSurfaceCount = context.configuration.topology.nLayers;
1084 reserveGenericTrackPublication(*mFrame, tracks.size(), static_cast<std::size_t>(activeSurfaceCount));
1085 for (auto& track : tracks) {
1086 int nShared = 0;
1087 bool isFirstShared{false};
1088 int firstLayer{-1}, firstCluster{-1};
1089 for (int iLayer{0}; iLayer < activeSurfaceCount; ++iLayer) {
1090 if (track.getClusterIndex(iLayer) == o2::its::constants::UnusedIndex) {
1091 continue;
1092 }
1093 const auto clusterId = mLayerGlobalMeasurements[iLayer][track.getClusterIndex(iLayer)].clusterId;
1094 bool isShared = mFrame->isClusterUsed(iLayer, clusterId);
1095 nShared += int(isShared);
1096 if (firstLayer < 0) {
1097 firstCluster = track.getClusterIndex(iLayer);
1098 isFirstShared = isShared && trkParam.AllowSharingFirstCluster && std::find(firstClusters[iLayer].begin(), firstClusters[iLayer].end(), firstCluster) != firstClusters[iLayer].end();
1099 firstLayer = iLayer;
1100 }
1101 }
1102
1104 if (nShared - int(isFirstShared && trkParam.AllowSharingFirstCluster) > trkParam.SharedMaxClusters) {
1105 continue;
1106 }
1107
1108 bool firstCls{true}, nominalCompatible{true};
1109 TimeEstBC nominalTS, expandedTS;
1110 float smallestROFHalf = std::numeric_limits<float>::max();
1111 for (int iLayer{0}; iLayer < activeSurfaceCount; ++iLayer) {
1112 if (track.getClusterIndex(iLayer) == o2::its::constants::UnusedIndex) {
1113 continue;
1114 }
1115 smallestROFHalf = std::min(smallestROFHalf, mFrame->getROFTiming(iLayer).mROFLength * 0.5f);
1116 const auto clusterId = mLayerGlobalMeasurements[iLayer][track.getClusterIndex(iLayer)].clusterId;
1117 mFrame->markUsedCluster(iLayer, clusterId);
1118 int currentROF = mFrame->getClusterROF(iLayer, track.getClusterIndex(iLayer));
1119 const auto nominalROFTS = mFrame->getROFTiming(iLayer).getROFTimeBounds(currentROF);
1120 const auto expandedROFTS = mFrame->getROFTiming(iLayer).getROFTimeBounds(currentROF, true);
1121 if (firstCls) {
1122 firstCls = false;
1123 nominalTS = nominalROFTS;
1124 expandedTS = expandedROFTS;
1125 } else {
1126 if (nominalCompatible) {
1127 if (nominalTS.isCompatible(nominalROFTS)) {
1128 nominalTS += nominalROFTS;
1129 } else {
1130 nominalCompatible = false;
1131 }
1132 }
1133 if (!expandedTS.isCompatible(expandedROFTS)) {
1134 LOGP(fatal, "TS {}+/-{} are incompatible with {}+/-{}, this should not happen!", expandedROFTS.getTimeStamp(), expandedROFTS.getTimeStampError(), expandedTS.getTimeStamp(), expandedTS.getTimeStampError());
1135 }
1136 expandedTS += expandedROFTS;
1137 }
1138 }
1139 track.track.timestamp = (nominalCompatible ? nominalTS : expandedTS).makeSymmetrical();
1140 // Match the legacy track timestamp, including its uncertainty clamp.
1141 track.track.timestamp.setTimeStampError(std::min(track.track.timestamp.getTimeStampError(), smallestROFHalf));
1142 if (!appendGenericTrack(*mFrame, track, mLayerGlobalMeasurements)) {
1143 LOGP(fatal, "GenericTrack publication failed for an accepted CA track");
1144 }
1145
1146 if (trkParam.AllowSharingFirstCluster) {
1147 firstClusters[firstLayer].push_back(firstCluster);
1148 }
1149 }
1150}
1151
1152void TrackerTraits::setNThreads(int n, std::shared_ptr<tbb::task_arena>& arena)
1153{
1154#if defined(OPTIMISATION_OUTPUT)
1155 mTaskArena = std::make_shared<tbb::task_arena>(1);
1156#else
1157 if (arena == nullptr) {
1158 mTaskArena = std::make_shared<tbb::task_arena>(std::abs(n));
1159 LOGP(info, "Setting tracker with {} threads.", n);
1160 } else {
1161 mTaskArena = arena;
1162 }
1163#endif
1164}
1165
1166} // namespace o2::itsmft::tracking
Cross-timeframe output-size prediction.
Shared CA tracking configuration for ITS and MFT.
CA geometric triplet types with hole-layer support.
int32_t i
int32_t lastRow
Shared index-table utilities for periodic-phi surface charts.
SurfaceTrackState state
float chi2
std::vector< o2::MCCompLabel > labels
std::vector< o2::its::TrackITS > tracks
bounded_vector< float > bins
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
useful math constants
uint16_t pos
Definition RawData.h:3
Lock-free slot allocator and single-pass sink.
Runtime-plan-owned, detector-neutral CA workspace.
GPU-portable whole-track seed for common CA tracking.
StringRef key
static constexpr KeyType makeKey(SlabSite site, int iteration, int variant, int slot) noexcept
void runTraversal(IterationContext &view)
Shared CA tracker traits: same ITS-style tracklet/cell/road logic; MFT uses x-y LUT and forward refit...
GLdouble n
Definition glcorearb.h:1982
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizeiptr size
Definition glcorearb.h:659
GLuint GLuint end
Definition glcorearb.h:469
const GLdouble * v
Definition glcorearb.h:832
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLint y
Definition glcorearb.h:270
GLint reference
Definition glcorearb.h:5487
GLenum const GLfloat * params
Definition glcorearb.h:272
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLint level
Definition glcorearb.h:275
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
uint8_t int statusCode int
double component(const Vec3 &vector, int dimension)
double angularTolerance(double radius)
Angular tolerance equivalent to a kTolerance arc length at the given radius.
constexpr int UnusedIndex
Definition Constants.h:32
return temporaryTrack
int const float const TrackSeed< NLayers > & seed
const bool const int TrackITSInternal< NLayers > & track
bool fitTrackSeedLegs(const TrackSeed &seed, const TimeFrame &frame, gsl::span< const gsl::span< const GlobalMeasurement > > layerGlobals, SurfaceCatalogView surfaceCatalog, float bz, bool shiftReferenceToMeasurement, float maxChi2ClusterAttachment, float maxChi2NDF, bool repeatRefitOut, gsl::span< const float > minPt, SurfaceTrackState &outParamIn, SurfaceTrackState &outParamOut, float &outChi2) noexcept
void deepVectorClear(std::vector< T > &vec)
detail::Identifier< LayerIdTag, uint16_t > LayerId
Definition IdTypes.h:60
bool fitAdjacentTripletFactors(const TripletFitFactor &firstFactor, const TripletFitFactor &secondFactor, const std::array< GlobalMeasurement, 4 > &measurements, const std::array< float, 2 > &angularVariance, AdjacentTripletFitResult &result) noexcept
o2::its::Vertex Vertex
Definition TimeFrame.h:47
detail::Identifier< CellPathIdTag, uint16_t > CellPathId
Definition IdTypes.h:62
bool makeTripletFitFactor(const std::array< GlobalMeasurement, 3 > &measurements, TripletFitFactor &factor) noexcept
bool projectTrackletSearchWindow(const GlobalMeasurement &sourceMeasurement, const o2::its::Vertex &vertex, float beamPositionVariance, SurfaceKind kind, const TrackletProjectionCache &edgeCache, const o2::itsmft::IndexTableUtilsCore &indexUtils, float nSigmaCut, TrackletSearchWindow &out)
Enum< T >::Iterator begin(Enum< T >)
Definition Defs.h:158
value_T step
Definition TrackUtils.h:42
a couple of static helper functions to create timestamp values for CCDB queries or override obsolete ...
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
gsl::span< const gsl::span< const GlobalMeasurement > > layerGlobalMeasurements
const IterationConfiguration & configuration
int getMaxVerticesPerROF() const noexcept