Project
Loading...
Searching...
No Matches
testComputeLayerCellsOrchestration.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.
11
12// Orchestration coverage for TrackerTraits<NLayers>::computeLayerCells after
13// its detector-family branch was replaced by a one-shot outer dispatch to
14// cell-seed leaves (Architecture.md Sec 10/10.1). cell-seed leaves's
15// numerical parity with the legacy inline formulas is already proven by
16// testTrackletFinding.cxx; this file does not re-derive or
17// duplicate that formula. It proves instead that the real public
18// computeLayerCells() entry point:
19// - resolves the three clusters for a candidate in strict
20// {inner, middle, outer} order and stores the corresponding linearized
21// triplet factor without prematurely constructing a track state;
22// - exercises cylinder and disk cells through the same public orchestration
23// entry point, with coordinate differences confined to cell-seed leaves;
24// - leaves cellIndex indexing, the LUT, MC-label construction, and
25// one-pass/two-pass ordering untouched;
26// - rejects invalid traversal schedules through the
27// existing public API alone, with no test-only seam into private
28// traversal-cache state.
29
30#define BOOST_TEST_MODULE ITSMFT ComputeLayerCells orchestration
31#define BOOST_TEST_MAIN
32#define BOOST_TEST_DYN_LINK
33
34#include <array>
35#include <cmath>
36#include <cstdint>
37#include <map>
38#include <memory>
39#include <vector>
40
41#include <boost/test/unit_test.hpp>
42
43#include <oneapi/tbb/task_arena.h>
44
61#include "MFTTracking/Constants.h"
62
64
66
68using namespace o2::itsmft;
69using namespace o2::itsmft::tracking;
70
71namespace
72{
73
74constexpr float Bz = 0.5f;
75
76// Preflight-only fixtures (Rig::establishLayout()) load zero real clusters --
77// this decoder's decode() is never actually invoked there. It exists only to
78// satisfy loadNormalizedSource()'s interface, mirroring
79// testTrackerFailureContract.cxx's LegacyLikeDecoder.
80class NeverDecodedDecoder
81{
82 public:
83 explicit NeverDecodedDecoder(o2::detectors::DetID::ID detector) : mDetector(detector) {}
84
86 const CompClusterExt&, gsl::span<const unsigned char>::iterator&, const TopologyDictionary*,
87 uint32_t) const
88 {
89 return {};
90 }
91
92 private:
94};
95
96// Stage-B normalized-CA-measurements slice: computeLayerCells() now reads
97// the TimeFrame's source-indexed SurfaceMeasurements. Candidate fixtures
98// therefore load their three clusters through the real loadNormalizedSource()
99// path -- backfilling both the normalized frame and every legacy
100// compatibility structure (unsorted clusters, TrackingFrameInfo, external
101// indices, ROF boundaries) together, in lockstep -- rather than poking legacy
102// structures directly. This decoder returns exactly the caller-supplied
103// SurfaceMeasurement for a given detector-local layer (encoded as the
104// synthetic CompClusterExt's chipID/sensorID) as decoded geometry facts.
105class FixedMeasurementDecoder
106{
107 public:
108 struct MeasurementPair {
109 DecodedCluster decoded{};
110 };
111
112 FixedMeasurementDecoder(o2::detectors::DetID::ID detector, SurfaceKind kind) : mDetector(detector), mKind(kind) {}
113
114 void setMeasurement(int layer, const MeasurementPair& measurement) { mByLayer[layer] = measurement; }
115
117 const CompClusterExt& cluster,
118 gsl::span<const unsigned char>::iterator&,
119 const TopologyDictionary*,
120 uint32_t) const
121 {
123 const int layer = cluster.getSensorID();
124 const auto it = mByLayer.find(layer);
125 BOOST_REQUIRE(it != mByLayer.end());
126 result = it->second.decoded;
127 result.layer = layer;
128 return result;
129 }
130
131 private:
132 o2::detectors::DetID::ID mDetector;
133 SurfaceKind mKind;
134 std::map<int, MeasurementPair> mByLayer;
135};
136
137const TopologyDictionary& dict()
138{
139 static const TopologyDictionary d;
140 return d;
141}
142
143std::vector<LayerId> identitySurfaces(uint16_t nLayers)
144{
145 std::vector<LayerId> mapping;
146 mapping.reserve(nLayers);
147 for (uint16_t i = 0; i < nLayers; ++i) {
148 mapping.push_back(LayerId{i});
149 }
150 return mapping;
151}
152
153// The test-only reference material values become the authoritative catalog
154// material before running computeLayerCells(). Production has no shadow vector.
155std::vector<SurfaceDescriptor> makeCatalog(uint16_t nLayers, o2::detectors::DetID::ID det,
156 gsl::span<const SurfaceKind> kinds, gsl::span<const float> layerxX0)
157{
158 std::vector<SurfaceDescriptor> surfaces;
159 surfaces.reserve(nLayers);
160 for (uint16_t i = 0; i < nLayers; ++i) {
161 const auto kind = kinds[i];
162 surfaces.push_back(SurfaceDescriptor{i, static_cast<uint8_t>(det), kind});
163 surfaces.back().chartRange = kind == SurfaceKind::Disk ? SurfaceChartRange{0.1f, 20.f} : SurfaceChartRange{-20.f, 20.f};
164 surfaces.back().referenceCoordinate = kind == SurfaceKind::Cylinder
165 ? 3.f + static_cast<float>(i)
166 : -0.4f - 0.2f * static_cast<float>(i);
167 const float xOverX0 = layerxX0[i];
168 surfaces.back().material.xOverX0 = xOverX0;
169 surfaces.back().material.arealDensityGPerCm2 = xOverX0 * o2::its::constants::Radl * o2::its::constants::Rho;
170 }
171 return surfaces;
172}
173
174// Same construction as testTrackletFinding.cxx's helpers -- plain
175// input-struct builders, not a reimplementation of any fit formula.
176GlobalMeasurement makeGlobalCluster(float x, float y, float z, int id = 0)
177{
178 GlobalMeasurement measurement{};
179 measurement.position = {x, y, z};
180 measurement.radius = std::hypot(x, y);
181 measurement.phi = std::atan2(y, x);
182 measurement.clusterId = static_cast<uint32_t>(id);
183 return measurement;
184}
185
186struct TestLocalMeasurement {
187 float xTrackingFrame{0.f};
188 float alphaTrackingFrame{0.f};
189 std::array<float, 2> positionTrackingFrame{};
190 std::array<float, 3> covarianceTrackingFrame{};
191};
192
193TestLocalMeasurement makeBarrelHit(float xTF, float alpha, float y, float z, float sigma2Y = 1.e-4f, float sigma2Z = 1.e-4f)
194{
195 return {xTF, alpha, {y, z}, {sigma2Y, 0.f, sigma2Z}};
196}
197
198TestLocalMeasurement makeDiskHit(float z, float x, float y, float sigma2X = 1.e-2f, float sigma2Y = 1.e-2f)
199{
200 return {z, 0.f, {x, y}, {sigma2X, 0.f, sigma2Y}};
201}
202
203// Test-local field-mapping helpers (not a production API), matching the same
204// Cylinder/Disk field mapping used by the production migration and by
205// testCellFinding.cxx: the single SurfaceMeasurement now
206// standing in for the retired {Cluster, TrackingFrameInfo} pair at each
207// candidate position.
208FixedMeasurementDecoder::MeasurementPair barrelMeasurementFor(const GlobalMeasurement& cluster, const TestLocalMeasurement& hit)
209{
210 FixedMeasurementDecoder::MeasurementPair measurement{};
211 measurement.decoded.global = {cluster.x, cluster.y, cluster.z};
212 measurement.decoded.cylinderFrame = {hit.xTrackingFrame, hit.positionTrackingFrame[0],
213 hit.positionTrackingFrame[1], hit.alphaTrackingFrame};
214 measurement.decoded.rowColumnCovariance = {hit.covarianceTrackingFrame[0],
215 hit.covarianceTrackingFrame[1],
216 hit.covarianceTrackingFrame[2]};
217 return measurement;
218}
219
220FixedMeasurementDecoder::MeasurementPair diskMeasurementFor(const GlobalMeasurement& cluster, const TestLocalMeasurement& hit)
221{
222 FixedMeasurementDecoder::MeasurementPair measurement{};
223 measurement.decoded.global = {cluster.x, cluster.y, cluster.z};
224 measurement.decoded.rowColumnCovariance = {hit.covarianceTrackingFrame[0], 0.f,
225 hit.covarianceTrackingFrame[2]};
226 return measurement;
227}
228
229void checkTripletFitFactorEqual(const TripletFitFactor& lhs, const TripletFitFactor& rhs)
230{
231 BOOST_CHECK_EQUAL(lhs.psi.theta, rhs.psi.theta);
232 BOOST_CHECK_EQUAL(lhs.psi.phi, rhs.psi.phi);
233 BOOST_CHECK_EQUAL(lhs.rho.theta, rhs.rho.theta);
234 BOOST_CHECK_EQUAL(lhs.rho.phi, rhs.rho.phi);
235 for (int hit = 0; hit < 3; ++hit) {
236 for (int coordinate = 0; coordinate < 3; ++coordinate) {
237 BOOST_CHECK_EQUAL(lhs.h[hit].theta[coordinate], rhs.h[hit].theta[coordinate]);
238 BOOST_CHECK_EQUAL(lhs.h[hit].phi[coordinate], rhs.h[hit].phi[coordinate]);
239 }
240 }
241}
242
243void checkTrackSeedContents(const TrackSeed& trackSeed, const Triplet& cell,
244 SurfaceKind expectedKind)
245{
246 BOOST_CHECK_EQUAL(trackSeed.getHitLayerMask().value(), cell.getHitLayerMask().value());
247 for (int slot = 0; slot < 3; ++slot) {
248 const auto reference = cell.getClusterReference(slot);
249 BOOST_CHECK_EQUAL(trackSeed.getCluster(reference.surfacePosition), reference.clusterIndex);
250 }
251 BOOST_CHECK_EQUAL(trackSeed.getLevel(), cell.getLevel());
252 BOOST_CHECK_EQUAL(trackSeed.getFirstTrackletIndex(), cell.getFirstTrackletIndex());
253 BOOST_CHECK_EQUAL(trackSeed.getSecondTrackletIndex(), cell.getSecondTrackletIndex());
254 BOOST_CHECK_EQUAL(trackSeed.getTimeStamp().getTimeStamp(), cell.getTimeStamp().getTimeStamp());
255 BOOST_CHECK_EQUAL(trackSeed.getTimeStamp().getTimeStampError(), cell.getTimeStamp().getTimeStampError());
256 BOOST_CHECK(trackSeed.state().kind == expectedKind);
257 BOOST_CHECK(std::isfinite(trackSeed.getChi2()));
258 for (const float parameter : trackSeed.state().parameters) {
259 BOOST_CHECK(std::isfinite(parameter));
260 }
261 for (const float covariance : trackSeed.state().covariance) {
262 BOOST_CHECK(std::isfinite(covariance));
263 }
264}
265
266void checkTrackSeedsEqual(const TrackSeed& lhs, const TrackSeed& rhs)
267{
268 BOOST_CHECK_EQUAL(lhs.getHitLayerMask().value(), rhs.getHitLayerMask().value());
269 BOOST_CHECK_EQUAL(lhs.getChi2(), rhs.getChi2());
270 BOOST_CHECK_EQUAL(lhs.getLevel(), rhs.getLevel());
271 BOOST_CHECK_EQUAL(lhs.getFirstTrackletIndex(), rhs.getFirstTrackletIndex());
272 BOOST_CHECK_EQUAL(lhs.getSecondTrackletIndex(), rhs.getSecondTrackletIndex());
273 BOOST_CHECK_EQUAL(lhs.getTimeStamp().getTimeStamp(), rhs.getTimeStamp().getTimeStamp());
274 BOOST_CHECK_EQUAL(lhs.getTimeStamp().getTimeStampError(), rhs.getTimeStamp().getTimeStampError());
275 for (int position = 0; position < TrackSeed::MaxSurfaces; ++position) {
276 BOOST_CHECK_EQUAL(lhs.getCluster(position), rhs.getCluster(position));
277 }
278 for (int parameter = 0; parameter < 5; ++parameter) {
279 BOOST_CHECK_EQUAL(lhs.state().parameters[parameter], rhs.state().parameters[parameter]);
280 }
281 for (int covariance = 0; covariance < 15; ++covariance) {
282 BOOST_CHECK_EQUAL(lhs.state().covariance[covariance], rhs.state().covariance[covariance]);
283 }
284 BOOST_CHECK_EQUAL(lhs.state().referenceCoordinate, rhs.state().referenceCoordinate);
285 BOOST_CHECK_EQUAL(lhs.state().alpha, rhs.state().alpha);
286 BOOST_CHECK(lhs.state().kind == rhs.state().kind);
287 BOOST_CHECK_EQUAL(lhs.state().flags, rhs.state().flags);
288 BOOST_CHECK_EQUAL(lhs.state().absCharge, rhs.state().absCharge);
289 BOOST_CHECK(lhs.state().pid == rhs.state().pid);
290}
291
292void checkTrackSeedMaterialization(TrackerTraits& traits, IterationContext& view,
293 int cellPathId, const Triplet& cell,
294 SurfaceKind expectedKind)
295{
296 TrackSeed trackSeed{};
297
299 traits, view, cellPathId, cell, trackSeed));
300 checkTrackSeedContents(trackSeed, cell, expectedKind);
301}
302
303// Minimal wiring TrackerTraits<NLayers>::computeLayerCells() needs: a real
304// layout/topology (so initialiseTimeFrame() genuinely binds
305// the edge/cell schedule and tracking parameters
306// -- computeLayerCells()'s own private caches, never poked directly), and a
307// validly-sized-but-empty normalized load (proven pattern from
308// testTrackerFailureContract.cxx: TimeFrame::initialise() unconditionally
309// reads mROFramesClusters sizes, which only loadNormalizedSource() sets up
310// safely, even for zero clusters).
311struct RigFrameStorage {
312 RigFrameStorage() : pool(std::make_shared<BoundedMemoryResource>()) { frame.setMemoryPool(pool); }
313
314 std::shared_ptr<BoundedMemoryResource> pool;
315 TimeFrame frame;
316};
317
318template <int NLayers>
319struct Rig : RigFrameStorage {
320
321 Rig(o2::detectors::DetID::ID det, SurfaceKind kind, int nThreads = 1)
322 : params(1),
323 mDet(det),
324 mKinds(NLayers, kind)
325 {
326 resetReferenceTrackingParameters(params[0], det);
327 // This file bypasses computeLayerTracklets()'s phi/z/index-table cuts
328 // entirely (candidates are injected directly, see
329 // injectCandidateTracklets() below): clearing RebuildClusterLUT keeps
330 // TimeFrame::initialise() from also exercising prepareClusters()'s
331 // index-table row/col binning and ROF-mask lookup on the synthetic
332 // candidate positions/ROF this file uses -- that out-of-scope subsystem
333 // is not configured for this file's candidates (in particular, no
334 // multiplicity/UPC ROF mask is ever loaded, so its default view is
335 // never a valid one to index once real clusters are present, unlike
336 // when this file loaded zero real clusters).
337 params[0].PassFlags.reset(IterationStep::RebuildClusterLUT);
338 traits.setNThreads(nThreads, arena);
339 frame.setBz(Bz);
340 }
341
342 // Establishes the catalog/layout and loads a (zero-cluster) normalized
343 // source. Deliberately not run by the constructor: it builds the catalog's
344 // nominal material from the *current* params[0].LayerxX0, so callers must
345 // finish any test reference material override before establishing the layout.
346 void establishLayout()
347 {
348 catalog = makeCatalog(static_cast<uint16_t>(NLayers), mDet, gsl::span<const SurfaceKind>{mKinds}, gsl::span<const float>(params[0].LayerxX0));
349 const auto orderedSurfaces = identitySurfaces(static_cast<uint16_t>(NLayers));
350 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
351 TrackerInitialization configuration;
352 configuration.catalog = catalogView;
353 configuration.memoryPool = pool;
354 configuration.holeLayers = holeLayers;
356 BOOST_REQUIRE(tracker.initialize(frame, configuration));
357 tf = &frame.getScratch();
358 const auto& layout = frame.getDetectorConfiguration();
359
360 NeverDecodedDecoder decoder{mDet};
361 const o2::InteractionRecord origin{50, 5};
362 const o2::its::LayerTiming timing{.mROFLength = 40};
363 const std::vector<CompClusterExt> noClusters;
364 const std::vector<unsigned char> noPatterns;
365 const std::vector<ROFRecord> noRofs;
366 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSource(frame, decoder, origin, timing, noClusters, noPatterns, noRofs, &dict(), nullptr, mDet,
367 gsl::span<const LayerId>{orderedSurfaces}, layout.getSurfaceCatalog()));
368 }
369
370 o2::detectors::DetID::ID detector() const noexcept { return mDet; }
371 SurfaceKind kind() const noexcept { return mKinds.front(); }
372 SurfaceKind kind(int layer) const noexcept { return mKinds[layer]; }
373 void setSurfaceKind(int layer, SurfaceKind kind) { mKinds[layer] = kind; }
374
375 std::vector<ReferenceTrackingParameters> params;
376 LayerMask holeLayers{};
377 // Gate 4 B3.1: `frame` declared before `tf` so it is constructed first and
378 // destroyed last (see TimeFrameScratch's own lifetime-contract doc).
379 TimeFrameScratch* tf{nullptr};
380 Tracker tracker;
381 std::array<gsl::span<const GlobalMeasurement>, MaxLayoutSurfaces> measurementSpans;
382 TrackerTraits traits;
383 std::shared_ptr<tbb::task_arena> arena;
384 // The catalog must outlive the immutable layout and all event-local views.
385 std::vector<SurfaceDescriptor> catalog;
386
387 private:
389 std::vector<SurfaceKind> mKinds;
390};
391
392template <int NLayers>
393IterationContext prepare(Rig<NLayers>& rig)
394{
395 return TrackerTestAccess::prepare(rig.tracker, rig.frame, 0, rig.measurementSpans);
396}
397
398template <int NLayers>
399TraversalTopologyView topologyView(const Rig<NLayers>& rig)
400{
401 return rig.tracker.getIterationConfigurations()[0].getTopologyView(rig.frame.getDetectorConfiguration().getSurfaceCatalog());
402}
403
404// Loads exactly the three supplied {cluster, hit} candidates at legacy
405// layers {0, 1, 2} (every test in this file locates its candidate cell via
406// findCellIndex(topology, 0, 1, 2), so the layer mapping is always this
407// identity triple) through the real loadNormalizedSource() path, via
408// FixedMeasurementDecoder -- so the normalized frame and every legacy
409// compatibility structure are populated together, in lockstep, exactly as
410// TrackerTraits::initialiseTimeFrame()'s one-time normalized-measurement
411// binding requires. Must be called after Rig::establishLayout() (which needs
412// the catalog/topology first) and before TrackerTraits::initialiseTimeFrame()
413// (which validates the normalized frame against the legacy structures this
414// call also populates).
415template <int NLayers>
416void loadCandidateClusters(Rig<NLayers>& rig,
417 const std::array<GlobalMeasurement, 3>& clusters,
418 const std::array<TestLocalMeasurement, 3>& hits)
419{
420 FixedMeasurementDecoder decoder{rig.detector(), rig.kind()};
421 std::vector<CompClusterExt> compClusters;
422 compClusters.reserve(3);
423 for (int layer = 0; layer < 3; ++layer) {
424 compClusters.emplace_back(0, 0, CompCluster::InvalidPatternID, static_cast<uint16_t>(layer));
425 const auto measurement = rig.kind(layer) == SurfaceKind::Disk
426 ? diskMeasurementFor(clusters[layer], hits[layer])
427 : barrelMeasurementFor(clusters[layer], hits[layer]);
428 decoder.setMeasurement(layer, measurement);
429 }
430 const std::vector<unsigned char> noPatterns;
431 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 3}};
432 const o2::InteractionRecord origin{50, 5};
433 const o2::its::LayerTiming timing{.mROFLength = 40};
434 const auto layerMapping = identitySurfaces(static_cast<uint16_t>(NLayers));
435 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSource(rig.frame, decoder, origin, timing, compClusters, noPatterns, rofs, &dict(), nullptr, rig.detector(),
436 gsl::span<const LayerId>{layerMapping}, rig.frame.getDetectorConfiguration().getSurfaceCatalog()));
437}
438
439// Finds the cellIndex whose two edges span exactly
440// inner->middle->outer, without assuming any particular enumeration order
441// out of the sparse topology's builder enumeration.
442template <typename TopologyView>
443int findCellIndex(const TopologyView& topology, int inner, int middle, int outer)
444{
445 for (int i = 0; i < topology.nPaths; ++i) {
446 const auto& cell = topology.getPath(CellPathId{static_cast<uint16_t>(i)});
447 const auto& first = topology.getEdge(cell.first);
448 const auto& second = topology.getEdge(cell.second);
449 if (first.from.value() == inner && first.to.value() == middle && second.from.value() == middle && second.to.value() == outer) {
450 return i;
451 }
452 }
453 return -1;
454}
455
456Tracklet candidateTracklet(const GlobalMeasurement& first, const GlobalMeasurement& second,
457 const o2::its::TimeEstBC& timestamp)
458{
459 const float transverseChord = std::hypot(second.x - first.x, second.y - first.y);
460 BOOST_REQUIRE_GT(transverseChord, 1.e-6f);
461 const float tanLambda = (second.z - first.z) / transverseChord;
462 const float phi = std::atan2(first.y - second.y, first.x - second.x);
463 return {0, 0, tanLambda, phi, timestamp};
464}
465
466// Bypasses the real (untouched, out-of-scope-for-this-change)
467// computeLayerTracklets() phi/z/index-table cuts entirely. The real loader
468// has already installed the authoritative compact globals and fitting
469// measurements; this helper only injects one tracklet per edge of cellIndex,
470// wired so
471// computeLayerCellsForKind<Tag>'s tracklet-pairing loop finds exactly one
472// candidate pair.
473template <int NLayers>
474void injectCandidateTracklets(Rig<NLayers>& rig, int cellIndex, const std::array<GlobalMeasurement, 3>& clusters)
475{
476 const auto topology = topologyView(rig);
477 const auto& cell = topology.getPath(CellPathId{static_cast<uint16_t>(cellIndex)});
478 const auto& first = topology.getEdge(cell.first);
479 const auto& second = topology.getEdge(cell.second);
480 const int layers[3] = {first.from.value(), first.to.value(), second.to.value()};
481
482 for (int i = 0; i < 3; ++i) {
483 BOOST_REQUIRE_EQUAL(rig.frame.getClusters()[layers[i]].size(), 1u);
484 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].x, clusters[i].x);
485 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].y, clusters[i].y);
486 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].z, clusters[i].z);
487 }
488
489 const o2::its::TimeEstBC ts{static_cast<uint32_t>(0), static_cast<uint16_t>(1)};
490 rig.tf->getTracklets()[cell.first.value()].push_back(candidateTracklet(clusters[0], clusters[1], ts));
491 rig.tf->getTracklets()[cell.second.value()].push_back(candidateTracklet(clusters[1], clusters[2], ts));
492
493 auto& secondLUT = rig.tf->getTrackletsLookupTable()[cell.second.value()];
494 secondLUT.resize(2);
495 secondLUT[0] = 0;
496 secondLUT[1] = 1;
497}
498
499// Gate 4 Slice 0b additions below: multi-cell parity coverage for the
500// migrated computeLayerCells()/computeLayerCellsForKind(), extending this
501// file's existing single-cell (always layers {0,1,2}) machinery to an
502// arbitrary ordered set of N>=3 layers so several simultaneously-populated
503// cells (sharing edges between adjacent triples) can be checked in one
504// run.
505
506// Same technique as loadCandidateClusters() (real loadNormalizedSource()
507// path via FixedMeasurementDecoder), generalized to N candidate layers
508// instead of the fixed {0,1,2} triple.
509template <int NLayers, size_t N>
510void loadCandidateClustersAtLayers(Rig<NLayers>& rig,
511 const std::array<int, N>& layers,
512 const std::array<GlobalMeasurement, N>& clusters,
513 const std::array<TestLocalMeasurement, N>& hits)
514{
515 FixedMeasurementDecoder decoder{rig.detector(), rig.kind()};
516 std::vector<CompClusterExt> compClusters;
517 compClusters.reserve(N);
518 for (size_t i = 0; i < N; ++i) {
519 compClusters.emplace_back(0, 0, CompCluster::InvalidPatternID, static_cast<uint16_t>(layers[i]));
520 const auto measurement = rig.kind(layers[i]) == SurfaceKind::Disk
521 ? diskMeasurementFor(clusters[i], hits[i])
522 : barrelMeasurementFor(clusters[i], hits[i]);
523 decoder.setMeasurement(layers[i], measurement);
524 }
525 const std::vector<unsigned char> noPatterns;
526 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, static_cast<int>(N)}};
527 const o2::InteractionRecord origin{50, 5};
528 const o2::its::LayerTiming timing{.mROFLength = 40};
529 const auto layerMapping = identitySurfaces(static_cast<uint16_t>(NLayers));
530 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSource(rig.frame, decoder, origin, timing, compClusters, noPatterns, rofs, &dict(), nullptr, rig.detector(),
531 gsl::span<const LayerId>{layerMapping}, rig.frame.getDetectorConfiguration().getSurfaceCatalog()));
532}
533
534// Finds the edgeId spanning exactly from->to, mirroring
535// findCellIndex()'s linear-search style over the legacy view.
536template <typename TopologyView>
537int findEdgeId(const TopologyView& topology, int from, int to)
538{
539 for (int i = 0; i < topology.nEdges; ++i) {
540 const auto& t = topology.getEdge(EdgeId{static_cast<uint16_t>(i)});
541 if (t.from.value() == from && t.to.value() == to) {
542 return i;
543 }
544 }
545 return -1;
546}
547
548// Generalizes injectCandidateTracklets() to an ordered chain of N>=3 layers:
549// writes each physical layer's single cluster exactly once, then touches
550// each of the N-1 adjacent-pair edges exactly once (one synthetic
551// tracklet + one LUT {0,1}), regardless of how many downstream cells in the
552// chain share that edge. Naively calling the single-cell
553// injectCandidateTracklets() once per overlapping cell would instead
554// double-write any shared edge (extra duplicate tracklet, and a LUT
555// left however the last call set it) and silently clobber a shared physical
556// layer's cluster across calls -- this helper touches every physical layer
557// and every edge exactly once, by construction.
558template <int NLayers, size_t N>
559void injectChainCandidateTracklets(Rig<NLayers>& rig, const std::array<int, N>& layers, const std::array<GlobalMeasurement, N>& clusters)
560{
561 static_assert(N >= 3, "a chain needs at least 3 layers to form one cell");
562 const auto topology = topologyView(rig);
563 for (size_t i = 0; i < N; ++i) {
564 BOOST_REQUIRE_EQUAL(rig.frame.getClusters()[layers[i]].size(), 1u);
565 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].x, clusters[i].x);
566 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].y, clusters[i].y);
567 BOOST_CHECK_EQUAL(rig.frame.getClusters()[layers[i]][0].z, clusters[i].z);
568 }
569
570 const o2::its::TimeEstBC ts{static_cast<uint32_t>(0), static_cast<uint16_t>(1)};
571 for (size_t i = 0; i + 1 < N; ++i) {
572 const int edgeId = findEdgeId(topology, layers[i], layers[i + 1]);
573 BOOST_REQUIRE_GE(edgeId, 0);
574 rig.tf->getTracklets()[edgeId].push_back(candidateTracklet(clusters[i], clusters[i + 1], ts));
575 auto& lut = rig.tf->getTrackletsLookupTable()[edgeId];
576 lut.resize(2);
577 lut[0] = 0;
578 lut[1] = 1;
579 }
580}
581
582std::array<TestLocalMeasurement, 3> makeLocalMeasurements(
583 const std::array<SurfaceKind, 3>& kinds,
584 const std::array<GlobalMeasurement, 3>& clusters)
585{
586 std::array<TestLocalMeasurement, 3> hits{};
587 for (int layer = 0; layer < 3; ++layer) {
588 const auto& position = clusters[layer].position;
589 hits[layer] = kinds[layer] == SurfaceKind::Disk
590 ? makeDiskHit(position.z, position.x, position.y)
591 : makeBarrelHit(position.x, 0.f, position.y, position.z);
592 }
593 return hits;
594}
595
596template <int NLayers>
597void checkDirectTrackSeedConstruction(const std::array<SurfaceKind, 3>& kinds,
598 const std::array<GlobalMeasurement, 3>& clusters)
599{
600 Rig<NLayers> rig{o2::detectors::DetID::ITS, kinds[0]};
601 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
602 for (int layer = 0; layer < 3; ++layer) {
603 rig.setSurfaceKind(layer, kinds[layer]);
604 rig.params[0].LayerxX0[layer] = 0.f;
605 }
606 rig.establishLayout();
607 loadCandidateClusters(rig, clusters, makeLocalMeasurements(kinds, clusters));
608
609 auto view = prepare(rig);
610 const int cellPathId = findCellIndex(topologyView(rig), 0, 1, 2);
611 BOOST_REQUIRE_GE(cellPathId, 0);
612 Triplet cell{0, 0, 0, 0, 17, 23, o2::its::TimeEstBC{111, 9}};
613 cell.setLevel(6);
614
616 TrackSeed second{};
617
619 rig.traits, view, cellPathId, cell, first));
621 rig.traits, view, cellPathId, cell, second));
622
623 checkTrackSeedContents(first, cell, kinds[0]);
624 checkTrackSeedsEqual(first, second);
625}
626
627constexpr std::array<SurfaceKind, 3> CylinderCylinderCylinder{
628 SurfaceKind::Cylinder, SurfaceKind::Cylinder, SurfaceKind::Cylinder};
629constexpr std::array<SurfaceKind, 3> DiskDiskDisk{
630 SurfaceKind::Disk, SurfaceKind::Disk, SurfaceKind::Disk};
631constexpr std::array<SurfaceKind, 3> CylinderDiskCylinder{
632 SurfaceKind::Cylinder, SurfaceKind::Disk, SurfaceKind::Cylinder};
633constexpr std::array<SurfaceKind, 3> DiskCylinderDisk{
634 SurfaceKind::Disk, SurfaceKind::Cylinder, SurfaceKind::Disk};
635
636const std::array<GlobalMeasurement, 3> NominalTrackSeedClusters{
637 makeGlobalCluster(3.0f, 0.100f, 0.90f, 0),
638 makeGlobalCluster(4.0f, 0.150f, 1.05f, 0),
639 makeGlobalCluster(5.0f, 0.201f, 1.25f, 0)};
640
641} // namespace
642
643BOOST_AUTO_TEST_CASE(BuildTrackSeedCylinderCylinderCylinderIsDeterministic)
644{
645 checkDirectTrackSeedConstruction<ITSNLayers>(CylinderCylinderCylinder, NominalTrackSeedClusters);
646}
647
648BOOST_AUTO_TEST_CASE(BuildTrackSeedDiskDiskDiskIsDeterministic)
649{
650 checkDirectTrackSeedConstruction<ITSNLayers>(DiskDiskDisk, NominalTrackSeedClusters);
651}
652
653BOOST_AUTO_TEST_CASE(BuildTrackSeedCylinderDiskCylinderConvertsBackToCylinder)
654{
655 checkDirectTrackSeedConstruction<ITSNLayers>(CylinderDiskCylinder, NominalTrackSeedClusters);
656}
657
658BOOST_AUTO_TEST_CASE(BuildTrackSeedDiskCylinderDiskConvertsBackToDisk)
659{
660 checkDirectTrackSeedConstruction<ITSNLayers>(DiskCylinderDisk, NominalTrackSeedClusters);
661}
662
663BOOST_AUTO_TEST_CASE(BuildTrackSeedDegenerateMixedTripletPreservesDestination)
664{
665 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder};
666 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
667 for (int layer = 0; layer < 3; ++layer) {
668 rig.setSurfaceKind(layer, CylinderDiskCylinder[layer]);
669 rig.params[0].LayerxX0[layer] = 0.f;
670 }
671 rig.establishLayout();
672
673 const std::array<GlobalMeasurement, 3> degenerateClusters{
674 makeGlobalCluster(3.f, 0.1f, 0.9f, 0),
675 makeGlobalCluster(3.f, 0.1f, 1.0f, 0),
676 makeGlobalCluster(3.f, 0.1f, 1.1f, 0)};
677 loadCandidateClusters(rig, degenerateClusters,
678 makeLocalMeasurements(CylinderDiskCylinder, degenerateClusters));
679 auto view = prepare(rig);
680 const int cellPathId = findCellIndex(topologyView(rig), 0, 1, 2);
681 BOOST_REQUIRE_GE(cellPathId, 0);
682 Triplet cell{0, 0, 0, 0, 17, 23, o2::its::TimeEstBC{111, 9}};
683 cell.setLevel(6);
684
685 SurfaceTrackState sentinelState{};
686 sentinelState.kind = SurfaceKind::Disk;
687 sentinelState.referenceCoordinate = -42.f;
688 sentinelState.parameters[0] = 13.f;
689 TrackSeed destination{cell, sentinelState, 71.f};
690 const TrackSeed before = destination;
691
693 rig.traits, view, cellPathId, cell, destination));
694
695 checkTrackSeedsEqual(destination, before);
696}
697
698// --- Barrel: real orchestration matches the cell-seed leaves oracle -----
699
700BOOST_AUTO_TEST_CASE(CylinderComputeLayerCellsMatchesBuildTripletOracle)
701{
702 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder};
703 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
704 rig.params[0].LayerxX0[0] = 0.005f; // inner
705 rig.params[0].LayerxX0[1] = 0.005f; // middle
706 rig.params[0].LayerxX0[2] = 0.f; // outer: contractually unused by Cylinder
707 rig.establishLayout();
708
709 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(3.0f, 0.100f, 0.9f, 0),
710 makeGlobalCluster(4.0f, 0.150f, 1.05f, 0),
711 makeGlobalCluster(5.0f, 0.201f, 1.20f, 0)};
712 loadCandidateClusters(rig, clusters,
713 {makeBarrelHit(3.f, 0.f, 0.100f, 0.9f),
714 makeBarrelHit(4.f, 0.f, 0.150f, 1.05f),
715 makeBarrelHit(5.f, 0.f, 0.201f, 1.20f)});
716
717 auto view = TrackerTestAccess::prepare(rig.tracker, rig.frame, 0, rig.measurementSpans);
718
719 const auto topology = topologyView(rig);
720 const int cellIndex = findCellIndex(topology, 0, 1, 2);
721 BOOST_REQUIRE_GE(cellIndex, 0);
722
723 injectCandidateTracklets(rig, cellIndex, clusters);
724
725 // Any other cellIndex keeps its empty-edge early-continue
726 // path: cleared once up front, never touched again.
727 int othercellIndex = -1;
728 for (int i = 0; i < topology.nPaths; ++i) {
729 if (i != cellIndex) {
730 othercellIndex = i;
731 break;
732 }
733 }
734 BOOST_REQUIRE_GE(othercellIndex, 0);
735
736 TrackerTestAccess::computeCells(rig.traits, view);
737
738 BOOST_CHECK(rig.tf->getCells()[othercellIndex].empty());
739 BOOST_CHECK(rig.tf->getCellsLookupTable()[othercellIndex].empty());
740 BOOST_CHECK(rig.tf->getCellsLabel(othercellIndex).empty());
741
742 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
743 const auto& producedCell = rig.tf->getCells()[cellIndex][0];
744 BOOST_CHECK(producedCell.tripletFactor().isValid());
745 for (int slot = 0; slot < 3; ++slot) {
746 const auto reference = producedCell.getClusterReference(slot);
747 BOOST_CHECK_EQUAL(reference.surfacePosition, slot);
748 BOOST_CHECK_EQUAL(reference.clusterIndex, producedCell.getClusters()[slot]);
749 }
750
751 BOOST_REQUIRE_EQUAL(rig.tf->getCellsLookupTable()[cellIndex].size(), 2u);
752 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][0], 0);
753 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][1], 1);
754
755 // hasMCinformation() is false (no labels were loaded), so label
756 // construction is skipped, exactly as before this change.
757 BOOST_CHECK(rig.tf->getCellsLabel(cellIndex).empty());
758
759 // Oracle: independently reconstruct the geometry-only factor from the
760 // ordered global measurements. Track-state construction belongs to
761 // TrackerTraits::buildTrackSeed(), after the CA has selected a cell.
762 const auto layerGlobalMeasurements = gsl::span<const gsl::span<const GlobalMeasurement>>{view.layerGlobalMeasurements};
763 const auto& oracleGlobalInner = layerGlobalMeasurements[0][producedCell.getFirstClusterIndex()];
764 const auto& oracleGlobalMiddle = layerGlobalMeasurements[1][producedCell.getSecondClusterIndex()];
765 const auto& oracleGlobalOuter = layerGlobalMeasurements[2][producedCell.getThirdClusterIndex()];
766 const std::array<GlobalMeasurement, 3> measurements{
767 oracleGlobalInner, oracleGlobalMiddle, oracleGlobalOuter};
768 TripletFitFactor oracleFactor{};
769 BOOST_REQUIRE(makeTripletFitFactor(measurements, oracleFactor));
770 checkTripletFitFactorEqual(producedCell.tripletFactor(), oracleFactor);
771 checkTrackSeedMaterialization(rig.traits, view, cellIndex, producedCell,
772 SurfaceKind::Cylinder);
773}
774
775BOOST_AUTO_TEST_CASE(CylinderCellCombinationUsesTrackletMinPtScattering)
776{
777 auto acceptedCells = [](float trackletMinPt) {
778 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder};
779 rig.params[0].TrackletMinPt = trackletMinPt;
780 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
781 rig.params[0].LayerxX0[0] = 0.005f;
782 rig.params[0].LayerxX0[1] = 0.01f;
783 rig.params[0].LayerxX0[2] = 0.f;
784 rig.establishLayout();
785
786 const std::array<GlobalMeasurement, 3> clusters{
787 makeGlobalCluster(3.f, 0.100f, 0.9f),
788 makeGlobalCluster(4.f, 0.150f, 1.05f),
789 makeGlobalCluster(5.f, 0.201f, 1.22f)};
790 loadCandidateClusters(rig, clusters,
791 {makeBarrelHit(3.f, 0.f, 0.100f, 0.9f, 1.e-6f, 1.e-6f),
792 makeBarrelHit(4.f, 0.f, 0.150f, 1.05f, 1.e-6f, 1.e-6f),
793 makeBarrelHit(5.f, 0.f, 0.201f, 1.22f, 1.e-6f, 1.e-6f)});
794
795 auto view = prepare(rig);
796 const auto topology = topologyView(rig);
797 const int cellIndex = findCellIndex(topology, 0, 1, 2);
798 BOOST_REQUIRE_GE(cellIndex, 0);
799 injectCandidateTracklets(rig, cellIndex, clusters);
800 TrackerTestAccess::computeCells(rig.traits, view);
801 return rig.tf->getCells()[cellIndex].size();
802 };
803
804 BOOST_CHECK_EQUAL(acceptedCells(0.3f), 1u);
805 BOOST_CHECK_EQUAL(acceptedCells(1.f), 0u);
806}
807
808BOOST_AUTO_TEST_CASE(ForwardCellAcceptsBendingWithinScatteringTolerance)
809{
810 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
811 rig.params[0].TrackletMinPt = 0.3f;
812 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
813 rig.params[0].LayerxX0[0] = 0.015f;
814 rig.params[0].LayerxX0[1] = 0.017f;
815 rig.params[0].LayerxX0[2] = 0.02f;
816 rig.establishLayout();
817
818 const std::array<GlobalMeasurement, 3> clusters{
819 makeGlobalCluster(1.f, 0.f, -0.4f),
820 makeGlobalCluster(1.01f, 0.f, -0.6f),
821 makeGlobalCluster(1.01995f, 0.000998f, -0.8f)};
822 loadCandidateClusters(rig, clusters,
823 {makeDiskHit(-0.4f, 1.f, 0.f),
824 makeDiskHit(-0.6f, 1.01f, 0.f),
825 makeDiskHit(-0.8f, 1.01995f, 0.000998f)});
826
827 auto view = prepare(rig);
828 const auto topology = topologyView(rig);
829 const int cellIndex = findCellIndex(topology, 0, 1, 2);
830 BOOST_REQUIRE_GE(cellIndex, 0);
831 injectCandidateTracklets(rig, cellIndex, clusters);
832 TrackerTestAccess::computeCells(rig.traits, view);
833
834 BOOST_CHECK_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
835}
836
837BOOST_AUTO_TEST_CASE(ForwardCellAzimuthalToleranceDoesNotGrowWithTanLambda)
838{
839 auto acceptedCells = [](float deltaPhi, float deltaZ) {
840 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
841 rig.params[0].TrackletMinPt = 0.3f;
842 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
843 rig.params[0].LayerxX0[1] = 0.017f;
844 rig.establishLayout();
845
846 // Equal transverse lengths and longitudinal steps keep the dip-angle
847 // difference zero. Only the bend and the common inclination vary.
848 const float outerX = 1.01f + 0.01f * std::cos(deltaPhi);
849 const float outerY = 0.01f * std::sin(deltaPhi);
850 const std::array<GlobalMeasurement, 3> clusters{
851 makeGlobalCluster(1.f, 0.f, -0.4f),
852 makeGlobalCluster(1.01f, 0.f, -0.4f - deltaZ),
853 makeGlobalCluster(outerX, outerY, -0.4f - 2.f * deltaZ)};
854 loadCandidateClusters(rig, clusters,
855 {makeDiskHit(clusters[0].z, clusters[0].x, clusters[0].y),
856 makeDiskHit(clusters[1].z, clusters[1].x, clusters[1].y),
857 makeDiskHit(clusters[2].z, clusters[2].x, clusters[2].y)});
858
859 auto view = prepare(rig);
860 const int cellIndex = findCellIndex(topologyView(rig), 0, 1, 2);
861 BOOST_REQUIRE_GE(cellIndex, 0);
862 injectCandidateTracklets(rig, cellIndex, clusters);
863 TrackerTestAccess::computeCells(rig.traits, view);
864 return rig.tf->getCells()[cellIndex].size();
865 };
866
867 for (const float deltaZ : {0.2f, 0.8f}) {
868 BOOST_CHECK_EQUAL(acceptedCells(0.02f, deltaZ), 1u);
869 BOOST_CHECK_EQUAL(acceptedCells(0.2f, deltaZ), 0u);
870 }
871}
872
873BOOST_AUTO_TEST_CASE(ForwardCellDipToleranceScalesWithInclination)
874{
875 auto acceptedCells = [](float meanTanLambda, float deltaLambda) {
876 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
877 rig.params[0].TrackletMinPt = 0.3f;
878 rig.params[0].LayerxX0[1] = 0.017f;
879 rig.establishLayout();
880
881 // No transverse bend: isolate the dip-angle difference around a chosen
882 // common inclination, keeping the transverse segment lengths fixed.
883 const float meanLambda = std::atan(meanTanLambda);
884 const float middleZ = -0.4f + 0.01f * std::tan(meanLambda - 0.5f * deltaLambda);
885 const float outerZ = middleZ + 0.01f * std::tan(meanLambda + 0.5f * deltaLambda);
886 const std::array<GlobalMeasurement, 3> clusters{
887 makeGlobalCluster(1.f, 0.f, -0.4f),
888 makeGlobalCluster(1.01f, 0.f, middleZ),
889 makeGlobalCluster(1.02f, 0.f, outerZ)};
890 loadCandidateClusters(rig, clusters,
891 {makeDiskHit(clusters[0].z, clusters[0].x, clusters[0].y),
892 makeDiskHit(clusters[1].z, clusters[1].x, clusters[1].y),
893 makeDiskHit(clusters[2].z, clusters[2].x, clusters[2].y)});
894
895 auto view = prepare(rig);
896 const int cellIndex = findCellIndex(topologyView(rig), 0, 1, 2);
897 BOOST_REQUIRE_GE(cellIndex, 0);
898 injectCandidateTracklets(rig, cellIndex, clusters);
899 TrackerTestAccess::computeCells(rig.traits, view);
900 return rig.tf->getCells()[cellIndex].size();
901 };
902
903 for (const float sign : {-1.f, 1.f}) {
904 BOOST_CHECK_EQUAL(acceptedCells(sign * 20.f, 0.001f), 1u);
905 BOOST_CHECK_EQUAL(acceptedCells(sign * 5.f, 0.01f), 1u);
906 BOOST_CHECK_EQUAL(acceptedCells(sign * 20.f, 0.01f), 0u);
907 }
908}
909
910// --- Disk: real orchestration matches the generic cell-seed oracle -------
911
912BOOST_AUTO_TEST_CASE(DiskComputeLayerCellsMatchesBuildTripletOracle)
913{
914 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
915 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
916 rig.params[0].TrackletMinPt = 0.3f;
917 rig.params[0].LayerxX0[0] = 0.015f; // inner
918 rig.params[0].LayerxX0[1] = 0.017f; // middle
919 rig.params[0].LayerxX0[2] = 0.02f; // outer
920 rig.establishLayout();
921
922 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0),
923 makeGlobalCluster(1.3f, 0.62f, -0.6f, 0),
924 makeGlobalCluster(1.7f, 0.78f, -0.9f, 0)};
925 loadCandidateClusters(rig, clusters,
926 {makeDiskHit(-0.4f, 1.0f, 0.5f),
927 makeDiskHit(-0.6f, 1.3f, 0.62f),
928 makeDiskHit(-0.9f, 1.7f, 0.78f)});
929
930 auto view = prepare(rig);
931
932 const auto topology = topologyView(rig);
933 const int cellIndex = findCellIndex(topology, 0, 1, 2);
934 BOOST_REQUIRE_GE(cellIndex, 0);
935
936 injectCandidateTracklets(rig, cellIndex, clusters);
937
938 TrackerTestAccess::computeCells(rig.traits, view);
939
940 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
941 const auto& producedCell = rig.tf->getCells()[cellIndex][0];
942 BOOST_CHECK(producedCell.tripletFactor().isValid());
943 for (int slot = 0; slot < 3; ++slot) {
944 const auto reference = producedCell.getClusterReference(slot);
945 BOOST_CHECK_EQUAL(reference.surfacePosition, slot);
946 BOOST_CHECK_EQUAL(reference.clusterIndex, producedCell.getClusters()[slot]);
947 }
948
949 const auto layerGlobalMeasurements = gsl::span<const gsl::span<const GlobalMeasurement>>{view.layerGlobalMeasurements};
950 const auto& oracleGlobalInner = layerGlobalMeasurements[0][producedCell.getFirstClusterIndex()];
951 const auto& oracleGlobalMiddle = layerGlobalMeasurements[1][producedCell.getSecondClusterIndex()];
952 const auto& oracleGlobalOuter = layerGlobalMeasurements[2][producedCell.getThirdClusterIndex()];
953 const std::array<GlobalMeasurement, 3> measurements{
954 oracleGlobalInner, oracleGlobalMiddle, oracleGlobalOuter};
955 TripletFitFactor oracleFactor{};
956 BOOST_REQUIRE(makeTripletFitFactor(measurements, oracleFactor));
957 checkTripletFitFactorEqual(producedCell.tripletFactor(), oracleFactor);
958 checkTrackSeedMaterialization(rig.traits, view, cellIndex, producedCell,
959 SurfaceKind::Disk);
960}
961
962// --- One-pass vs two-pass: identical result regardless of thread count ----
963
964BOOST_AUTO_TEST_CASE(CylinderComputeLayerCellsOnePassAndTwoPassAgree)
965{
966 struct Result {
967 int cellIndex{-1};
968 std::vector<int> lut;
969 TripletFitFactor factor{};
970 int cl0{-1}, cl1{-1}, cl2{-1};
971 };
972
973 auto run = [](int nThreads) {
974 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder, nThreads};
975 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
976 rig.params[0].LayerxX0[0] = 0.005f;
977 rig.params[0].LayerxX0[1] = 0.005f;
978 rig.establishLayout();
979
980 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(3.0f, 0.100f, 0.9f, 0),
981 makeGlobalCluster(4.0f, 0.150f, 1.05f, 0),
982 makeGlobalCluster(5.0f, 0.201f, 1.20f, 0)};
983 loadCandidateClusters(rig, clusters,
984 {makeBarrelHit(3.f, 0.f, 0.100f, 0.9f),
985 makeBarrelHit(4.f, 0.f, 0.150f, 1.05f),
986 makeBarrelHit(5.f, 0.f, 0.201f, 1.20f)});
987
988 auto view = prepare(rig);
989
990 const auto topology = topologyView(rig);
991 const int cellIndex = findCellIndex(topology, 0, 1, 2);
992 BOOST_REQUIRE_GE(cellIndex, 0);
993
994 injectCandidateTracklets(rig, cellIndex, clusters);
995
996 TrackerTestAccess::computeCells(rig.traits, view);
997
998 Result r;
999 r.cellIndex = cellIndex;
1000 const auto& lut = rig.tf->getCellsLookupTable()[cellIndex];
1001 r.lut.assign(lut.begin(), lut.end());
1002 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1003 const auto& cell = rig.tf->getCells()[cellIndex][0];
1004 r.factor = cell.tripletFactor();
1005 r.cl0 = cell.getFirstClusterIndex();
1006 r.cl1 = cell.getSecondClusterIndex();
1007 r.cl2 = cell.getThirdClusterIndex();
1008 return r;
1009 };
1010
1011 const auto onePass = run(1);
1012 const auto twoPass = run(4);
1013
1014 BOOST_CHECK_EQUAL(onePass.cellIndex, twoPass.cellIndex);
1015 BOOST_CHECK_EQUAL_COLLECTIONS(onePass.lut.begin(), onePass.lut.end(), twoPass.lut.begin(), twoPass.lut.end());
1016 checkTripletFitFactorEqual(onePass.factor, twoPass.factor);
1017 BOOST_CHECK_EQUAL(onePass.cl0, twoPass.cl0);
1018 BOOST_CHECK_EQUAL(onePass.cl1, twoPass.cl1);
1019 BOOST_CHECK_EQUAL(onePass.cl2, twoPass.cl2);
1020}
1021
1022BOOST_AUTO_TEST_CASE(DiskCellRejectsKinkBeyondNominalScatteringTolerance)
1023{
1024 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
1025 rig.params[0].TrackletMinPt = 0.3f;
1026 rig.establishLayout();
1027
1028 // Keep the dip-angle change beyond the tolerance with 0.0084 X/X0
1029 // per MFT surface, so this remains an angular-rejection test.
1030 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0),
1031 makeGlobalCluster(1.3f, 0.62f, -0.6f, 0),
1032 makeGlobalCluster(1.7f, 0.78f, -1.0f, 0)};
1033 loadCandidateClusters(rig, clusters,
1034 {makeDiskHit(-0.4f, 1.0f, 0.5f),
1035 makeDiskHit(-0.6f, 1.3f, 0.62f),
1036 makeDiskHit(-1.0f, 1.7f, 0.78f)});
1037 auto view = prepare(rig);
1038 const auto topology = topologyView(rig);
1039 const int cellIndex = findCellIndex(topology, 0, 1, 2);
1040 BOOST_REQUIRE_GE(cellIndex, 0);
1041 injectCandidateTracklets(rig, cellIndex, clusters);
1042
1043 const auto& path = topology.getPath(CellPathId{static_cast<uint16_t>(cellIndex)});
1044 const auto& first = rig.tf->getTracklets()[path.first.value()][0];
1045 const auto& second = rig.tf->getTracklets()[path.second.value()][0];
1046 const float deltaLambda = std::abs(std::atan(first.tanLambda) - std::atan(second.tanLambda));
1047 const float angularTolerance = view.configuration.kernelParameters.nSigmaCut * rig.tf->getEdgeMSAngle(path.second.value());
1048 BOOST_REQUIRE_GT(deltaLambda, angularTolerance);
1049 // Exclude a failed triplet fit as the reason for rejecting this candidate.
1050 const std::array<GlobalMeasurement, 3> measurements{view.layerGlobalMeasurements[0][0],
1051 view.layerGlobalMeasurements[1][0],
1052 view.layerGlobalMeasurements[2][0]};
1053 TripletFitFactor factor{};
1054 BOOST_REQUIRE(makeTripletFitFactor(measurements, factor));
1055 BOOST_REQUIRE(factor.isValid());
1056
1057 TrackerTestAccess::computeCells(rig.traits, view);
1058 BOOST_CHECK(rig.tf->getCells()[cellIndex].empty());
1059}
1060
1061BOOST_AUTO_TEST_CASE(DiskComputeLayerCellsOnePassAndTwoPassAgree)
1062{
1063 struct Result {
1064 int cellIndex{-1};
1065 std::vector<int> lut;
1066 TripletFitFactor factor{};
1067 int cl0{-1}, cl1{-1}, cl2{-1};
1068 };
1069
1070 auto run = [](int nThreads) {
1071 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk, nThreads};
1072 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
1073 rig.params[0].TrackletMinPt = 0.3f;
1074 rig.establishLayout();
1075
1076 // Straight triplet on the synthetic disk planes, comfortably inside the
1077 // nominal-material angular cut: this test checks threading, not rejection.
1078 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0),
1079 makeGlobalCluster(1.3f, 0.62f, -0.6f, 0),
1080 makeGlobalCluster(1.6f, 0.74f, -0.8f, 0)};
1081 loadCandidateClusters(rig, clusters,
1082 {makeDiskHit(-0.4f, 1.0f, 0.5f),
1083 makeDiskHit(-0.6f, 1.3f, 0.62f),
1084 makeDiskHit(-0.8f, 1.6f, 0.74f)});
1085
1086 auto view = prepare(rig);
1087
1088 const auto topology = topologyView(rig);
1089 const int cellIndex = findCellIndex(topology, 0, 1, 2);
1090 BOOST_REQUIRE_GE(cellIndex, 0);
1091
1092 injectCandidateTracklets(rig, cellIndex, clusters);
1093
1094 TrackerTestAccess::computeCells(rig.traits, view);
1095
1096 Result r;
1097 r.cellIndex = cellIndex;
1098 const auto& lut = rig.tf->getCellsLookupTable()[cellIndex];
1099 r.lut.assign(lut.begin(), lut.end());
1100 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1101 const auto& cell = rig.tf->getCells()[cellIndex][0];
1102 r.factor = cell.tripletFactor();
1103 r.cl0 = cell.getFirstClusterIndex();
1104 r.cl1 = cell.getSecondClusterIndex();
1105 r.cl2 = cell.getThirdClusterIndex();
1106 return r;
1107 };
1108
1109 const auto onePass = run(1);
1110 const auto twoPass = run(4);
1111
1112 BOOST_CHECK_EQUAL(onePass.cellIndex, twoPass.cellIndex);
1113 BOOST_CHECK_EQUAL_COLLECTIONS(onePass.lut.begin(), onePass.lut.end(), twoPass.lut.begin(), twoPass.lut.end());
1114 checkTripletFitFactorEqual(onePass.factor, twoPass.factor);
1115 BOOST_CHECK_EQUAL(onePass.cl0, twoPass.cl0);
1116 BOOST_CHECK_EQUAL(onePass.cl1, twoPass.cl1);
1117 BOOST_CHECK_EQUAL(onePass.cl2, twoPass.cl2);
1118}
1119
1120BOOST_AUTO_TEST_CASE(RepeatedComputeLayerCellsCallsDoNotRebindOrIncreaseCounts)
1121{
1122 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
1123 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
1124 rig.params[0].TrackletMinPt = 0.3f;
1125 rig.establishLayout();
1126
1127 // Use the same accepted straight triplet as the threading test above.
1128 const std::array<GlobalMeasurement, 3> clusters{makeGlobalCluster(1.0f, 0.5f, -0.4f, 0),
1129 makeGlobalCluster(1.3f, 0.62f, -0.6f, 0),
1130 makeGlobalCluster(1.6f, 0.74f, -0.8f, 0)};
1131 loadCandidateClusters(rig, clusters,
1132 {makeDiskHit(-0.4f, 1.0f, 0.5f),
1133 makeDiskHit(-0.6f, 1.3f, 0.62f),
1134 makeDiskHit(-0.8f, 1.6f, 0.74f)});
1135
1136 auto view = prepare(rig);
1137
1138 const auto topology = topologyView(rig);
1139 const int cellIndex = findCellIndex(topology, 0, 1, 2);
1140 BOOST_REQUIRE_GE(cellIndex, 0);
1141
1142 injectCandidateTracklets(rig, cellIndex, clusters);
1143
1144 TrackerTestAccess::computeCells(rig.traits, view);
1145 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1146 const auto firstFactor = rig.tf->getCells()[cellIndex][0].tripletFactor();
1147
1148 TrackerTestAccess::computeCells(rig.traits, view);
1149 TrackerTestAccess::computeCells(rig.traits, view);
1150
1151 // Re-inject tracklets and recompute (the underlying candidate clusters/
1152 // measurements loaded above are untouched -- reloading them here would
1153 // invalidate the frame-owned source measurement lookup without a fresh
1154 // initialiseTimeFrame() call to re-resolve it, which is not what this test
1155 // checks): a fresh call after the tracklets were consumed must still
1156 // reproduce the identical triplet factor through the same cache.
1157 injectCandidateTracklets(rig, cellIndex, clusters);
1158 TrackerTestAccess::computeCells(rig.traits, view);
1159
1160 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1161 checkTripletFitFactorEqual(rig.tf->getCells()[cellIndex][0].tripletFactor(), firstFactor);
1162}
1163
1164// Material-correction preflight has its own focused test target; this file
1165// covers only direct cell-stage orchestration.
1166
1167BOOST_AUTO_TEST_CASE(CylinderComputeLayerCellsMultiCellChainProducesCorrectCellsAndOrder)
1168{
1169 // 5-layer chain at global X = 3..7 (small Y, alpha=0.f, matching the
1170 // single-cell oracle tests' convention above): proves edge-level/
1171 // cell-level parity across three simultaneously-populated cells (0,1,2),
1172 // (1,2,3), (2,3,4) -- each resolved through the migrated
1173 // computeLayerCellsForKind() via a fresh mSurfaceToLegacyLayer lookup
1174 // per derived path -- not just the single path the tests above check,
1175 // while every non-participating cellIndex stays empty.
1176 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder};
1177 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
1178 for (int layer = 0; layer < ITSNLayers; ++layer) {
1179 rig.params[0].LayerxX0[layer] = 0.005f;
1180 }
1181 rig.establishLayout();
1182
1183 // Y values lie exactly on one real circle (center (0,5000), radius 5000,
1184 // through the origin) rather than an ad hoc linear Y(X): a single
1185 // physically consistent curvature across all 5 points avoids the
1186 // rotation-boundary edge cases (Propagator.cxx's
1187 // csp*ca+snp*sa<0 checks) an inconsistent, near-degenerate linear Y(X)
1188 // can trip for some sub-triples but not others.
1189 constexpr std::array<int, 5> layers{0, 1, 2, 3, 4};
1190 constexpr std::array<float, 5> xs{3.f, 4.f, 5.f, 6.f, 7.f};
1191 constexpr std::array<float, 5> ys{0.0009f, 0.0016f, 0.0025f, 0.0036f, 0.0049f};
1192 constexpr std::array<float, 5> zs{0.90f, 1.05f, 1.20f, 1.35f, 1.50f};
1193 std::array<GlobalMeasurement, 5> clusters;
1194 std::array<TestLocalMeasurement, 5> hits;
1195 for (size_t i = 0; i < 5; ++i) {
1196 clusters[i] = makeGlobalCluster(xs[i], ys[i], zs[i], 0);
1197 hits[i] = makeBarrelHit(xs[i], 0.f, ys[i], zs[i]);
1198 }
1199 loadCandidateClustersAtLayers(rig, layers, clusters, hits);
1200
1201 auto view = prepare(rig);
1202
1203 const auto topology = topologyView(rig);
1204 injectChainCandidateTracklets(rig, layers, clusters);
1205
1206 TrackerTestAccess::computeCells(rig.traits, view);
1207
1208 const std::array<std::array<int, 3>, 3> triples{{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}};
1209 std::array<int, 3> topologyIds{};
1210 std::vector<bool> participating(topology.nPaths, false);
1211 for (size_t i = 0; i < triples.size(); ++i) {
1212 const auto& triple = triples[i];
1213 const int cellIndex = findCellIndex(topology, triple[0], triple[1], triple[2]);
1214 BOOST_REQUIRE_GE(cellIndex, 0);
1215 topologyIds[i] = cellIndex;
1216 participating[cellIndex] = true;
1217
1218 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1219 const auto& producedCell = rig.tf->getCells()[cellIndex][0];
1220 BOOST_CHECK_EQUAL(producedCell.getFirstClusterIndex(), 0);
1221 BOOST_CHECK_EQUAL(producedCell.getSecondClusterIndex(), 0);
1222 BOOST_CHECK_EQUAL(producedCell.getThirdClusterIndex(), 0);
1223 BOOST_CHECK_EQUAL(producedCell.getHitLayerMask().value(), LayerMask(triple[0], triple[1], triple[2]).value());
1224
1225 BOOST_REQUIRE_EQUAL(rig.tf->getCellsLookupTable()[cellIndex].size(), 2u);
1226 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][0], 0);
1227 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][1], 1);
1228 }
1229
1230 for (int i = 0; i < topology.nPaths; ++i) {
1231 if (!participating[i]) {
1232 BOOST_CHECK(rig.tf->getCells()[i].empty());
1233 }
1234 }
1235
1236 TrackerTestAccess::findNeighbours(rig.traits, view);
1237 for (size_t i = 0; i < topologyIds.size(); ++i) {
1238 BOOST_CHECK_EQUAL(rig.tf->getCells()[topologyIds[i]][0].getLevel(), static_cast<int>(i + 1));
1239 if (i == 0) {
1240 BOOST_CHECK(rig.tf->getCellsNeighbours()[topologyIds[i]].empty());
1241 continue;
1242 }
1243 BOOST_REQUIRE_EQUAL(rig.tf->getCellsNeighbours()[topologyIds[i]].size(), 1u);
1244 BOOST_CHECK_EQUAL(rig.tf->getCellsNeighbours()[topologyIds[i]][0], 0);
1245 BOOST_CHECK_EQUAL(rig.tf->getCellsNeighboursTopology()[topologyIds[i]][0], topologyIds[i - 1]);
1246 }
1247}
1248
1249BOOST_AUTO_TEST_CASE(DiskComputeLayerCellsMultiCellChainProducesCorrectCellsAndOrder)
1250{
1251 // Same multi-cell parity property for the Disk/forward family:
1252 // cell-seed leaves genuinely branches per family (Cylinder
1253 // reads [1] then [0]; Disk reads [2],[1],[0] -- see the comment on
1254 // that call in computeLayerCellsForKind()), so multi-edge
1255 // cell-chaining for Disk is real, otherwise-unproven coverage.
1256 Rig<MFTNLayers> rig{o2::detectors::DetID::MFT, SurfaceKind::Disk};
1257 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
1258 rig.params[0].TrackletMinPt = 0.3f;
1259 rig.establishLayout();
1260
1261 constexpr std::array<int, 5> layers{0, 1, 2, 3, 4};
1262 constexpr std::array<float, 5> xs{1.0f, 1.3f, 1.6f, 1.9f, 2.2f};
1263 constexpr std::array<float, 5> ys{0.50f, 0.62f, 0.74f, 0.86f, 0.98f};
1264 constexpr std::array<float, 5> zs{-0.40f, -0.60f, -0.80f, -1.00f, -1.20f};
1265 std::array<GlobalMeasurement, 5> clusters;
1266 std::array<TestLocalMeasurement, 5> hits;
1267 for (size_t i = 0; i < 5; ++i) {
1268 clusters[i] = makeGlobalCluster(xs[i], ys[i], zs[i], 0);
1269 hits[i] = makeDiskHit(zs[i], xs[i], ys[i]);
1270 }
1271 loadCandidateClustersAtLayers(rig, layers, clusters, hits);
1272
1273 auto view = prepare(rig);
1274
1275 const auto topology = topologyView(rig);
1276 injectChainCandidateTracklets(rig, layers, clusters);
1277
1278 TrackerTestAccess::computeCells(rig.traits, view);
1279
1280 const std::array<std::array<int, 3>, 3> triples{{{0, 1, 2}, {1, 2, 3}, {2, 3, 4}}};
1281 std::array<int, 3> topologyIds{};
1282 std::vector<bool> participating(topology.nPaths, false);
1283 for (size_t i = 0; i < triples.size(); ++i) {
1284 const auto& triple = triples[i];
1285 const int cellIndex = findCellIndex(topology, triple[0], triple[1], triple[2]);
1286 BOOST_REQUIRE_GE(cellIndex, 0);
1287 topologyIds[i] = cellIndex;
1288 participating[cellIndex] = true;
1289
1290 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1291 const auto& producedCell = rig.tf->getCells()[cellIndex][0];
1292 BOOST_CHECK_EQUAL(producedCell.getHitLayerMask().value(), LayerMask(triple[0], triple[1], triple[2]).value());
1293
1294 BOOST_REQUIRE_EQUAL(rig.tf->getCellsLookupTable()[cellIndex].size(), 2u);
1295 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][0], 0);
1296 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][1], 1);
1297 }
1298
1299 for (int i = 0; i < topology.nPaths; ++i) {
1300 if (!participating[i]) {
1301 BOOST_CHECK(rig.tf->getCells()[i].empty());
1302 }
1303 }
1304
1305 TrackerTestAccess::findNeighbours(rig.traits, view);
1306 for (size_t i = 0; i < topologyIds.size(); ++i) {
1307 BOOST_CHECK_EQUAL(rig.tf->getCells()[topologyIds[i]][0].getLevel(), static_cast<int>(i + 1));
1308 if (i == 0) {
1309 BOOST_CHECK(rig.tf->getCellsNeighbours()[topologyIds[i]].empty());
1310 continue;
1311 }
1312 BOOST_REQUIRE_EQUAL(rig.tf->getCellsNeighbours()[topologyIds[i]].size(), 1u);
1313 BOOST_CHECK_EQUAL(rig.tf->getCellsNeighbours()[topologyIds[i]][0], 0);
1314 BOOST_CHECK_EQUAL(rig.tf->getCellsNeighboursTopology()[topologyIds[i]][0], topologyIds[i - 1]);
1315 }
1316}
1317
1318BOOST_AUTO_TEST_CASE(CylinderComputeLayerCellsHoleCellReconstructsCorrectLayerMask)
1319{
1320 // MaxHoles=1 with layer 1 an allowed hole introduces a (0,2)-skip-1
1321 // edge; combined with the adjacent (2,3) edge this forms cell
1322 // (0,2,3) -- a direct, non-adjacent exercise of resolveCellHitLayers()
1323 // (mSurfaceToLegacyLayer) resolving a cell's endpoints correctly, and of
1324 // hole/skipped-surface behaviour staying identical to the pre-migration
1325 // code (which read the same fromLayer/toLayer straight off the legacy
1326 // view). No cluster is placed on layer 1 at all.
1327 Rig<ITSNLayers> rig{o2::detectors::DetID::ITS, SurfaceKind::Cylinder};
1328 rig.params[0].MaxChi2ClusterAttachment = 1.e6f;
1329 rig.params[0].MaxHoles = 1;
1330 rig.holeLayers = LayerMask{static_cast<uint16_t>(1u << 1)};
1331 rig.establishLayout();
1332
1333 constexpr std::array<int, 3> layers{0, 2, 3};
1334 const std::array<GlobalMeasurement, 3> clusters{
1335 makeGlobalCluster(3.f, 0.10f, 0.90f, 0),
1336 makeGlobalCluster(5.f, 0.20f, 1.20f, 0),
1337 makeGlobalCluster(6.f, 0.25f, 1.35f, 0)};
1338 const std::array<TestLocalMeasurement, 3> hits{
1339 makeBarrelHit(3.f, 0.f, 0.10f, 0.90f),
1340 makeBarrelHit(5.f, 0.f, 0.20f, 1.20f),
1341 makeBarrelHit(6.f, 0.f, 0.25f, 1.35f)};
1342 loadCandidateClustersAtLayers(rig, layers, clusters, hits);
1343
1344 auto view = prepare(rig);
1345
1346 const auto topology = topologyView(rig);
1347 injectChainCandidateTracklets(rig, layers, clusters);
1348
1349 TrackerTestAccess::computeCells(rig.traits, view);
1350
1351 const int cellIndex = findCellIndex(topology, 0, 2, 3);
1352 BOOST_REQUIRE_GE(cellIndex, 0);
1353 BOOST_REQUIRE_EQUAL(rig.tf->getCells()[cellIndex].size(), 1u);
1354 const auto& producedCell = rig.tf->getCells()[cellIndex][0];
1355 BOOST_CHECK_EQUAL(producedCell.getHitLayerMask().value(), LayerMask(0, 2, 3).value());
1356
1357 BOOST_REQUIRE_EQUAL(rig.tf->getCellsLookupTable()[cellIndex].size(), 2u);
1358 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][0], 0);
1359 BOOST_CHECK_EQUAL(rig.tf->getCellsLookupTable()[cellIndex][1], 1);
1360
1361 for (int i = 0; i < topology.nPaths; ++i) {
1362 if (i != cellIndex) {
1363 BOOST_CHECK(rig.tf->getCells()[i].empty());
1364 }
1365 }
1366}
header::DataOrigin origin
Definition of the ITSMFT compact cluster.
Definition of the ClusterTopology class.
Shared CA tracking configuration for ITS and MFT.
Passive common TimeFrame owner.
int32_t i
Definition of the ITSMFT ROFrame (trigger) record.
Tracker orchestrator.
useful math constants
Runtime-plan-owned, detector-neutral CA workspace.
GPU-portable whole-track seed for common CA tracking.
int ID
Detector identifiers: continuous, starting from 0.
Definition DetID.h:63
UShort_t getSensorID() const
static constexpr unsigned short InvalidPatternID
Definition CompCluster.h:46
const SurfaceTrackState & state
Definition TrackSeed.h:51
static constexpr int MaxSurfaces
Definition TrackSeed.h:41
void setNThreads(int n, std::shared_ptr< tbb::task_arena > &arena)
Shared CA tracker traits: same ITS-style tracklet/cell/road logic; MFT uses x-y LUT and forward refit...
GLfloat GLfloat GLfloat alpha
Definition glcorearb.h:279
GLint GLenum GLint x
Definition glcorearb.h:403
GLuint64EXT * result
Definition glcorearb.h:5662
GLint first
Definition glcorearb.h:399
GLint y
Definition glcorearb.h:270
GLint reference
Definition glcorearb.h:5487
GLsizei const GLfloat * value
Definition glcorearb.h:819
GLenum const GLfloat * params
Definition glcorearb.h:272
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLboolean r
Definition glcorearb.h:1233
GLuint id
Definition glcorearb.h:650
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
constexpr float Radl
Definition Constants.h:34
constexpr float Rho
Definition Constants.h:35
TrackingPlan makeTrackingPlan(const TrackingParameters &parameters)
constexpr int ITSNLayers
ITS CA layer count.
constexpr uint32_t MaxLayoutSurfaces
Definition IdTypes.h:70
bool makeTripletFitFactor(const std::array< GlobalMeasurement, 3 > &measurements, TripletFitFactor &factor) noexcept
BOOST_AUTO_TEST_CASE(Cluster_messageable)
std::unique_ptr< GPUReconstructionTimeframe > tf
std::shared_ptr< BoundedMemoryResource > memoryPool
Definition Tracker.h:54
static IterationContext prepare(Tracker &tracker, TimeFrame &frame, int iteration, std::array< gsl::span< const GlobalMeasurement >, MaxLayoutSurfaces > &measurementSpans)
static void computeCells(TrackerTraits &traits, IterationContext &view)
static bool buildTrackSeed(TrackerTraits &traits, IterationContext &view, int cellPathId, const Triplet &cell, TrackSeed &output)
static void findNeighbours(TrackerTraits &traits, IterationContext &view)
BOOST_CHECK(tree)
coder decode(ctfImage, triggersD, clustersD)
std::vector< Cluster > clusters
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())