Project
Loading...
Searching...
No Matches
testCombinedTrackingComposition.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#define BOOST_TEST_MODULE ITSMFT CombinedTrackingComposition
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15
17#include <algorithm>
18#include <array>
19#include <cmath>
20#include <fstream>
21#include <iterator>
22#include <memory>
23#include <optional>
24#include <string>
25#include <vector>
26
27#include <boost/test/unit_test.hpp>
28
29#include <oneapi/tbb/task_arena.h>
30
31#include <TGeoGlobalMagField.h>
32#include "Field/MagneticField.h"
33
51
52using namespace o2::itsmft;
53using namespace o2::itsmft::tracking;
54
55namespace
56{
57
58struct GenericTrackPublicationExport {
59 o2::detectors::DetID::ID detector{};
62 gsl::span<const LayerId> layerMapping;
63};
64
65constexpr float Bz = 0.5f;
66constexpr std::array<unsigned char, 3> OnePixelPattern{1, 1, 0x80};
67
68const TopologyDictionary& dict()
69{
70 static const TopologyDictionary d;
71 return d;
72}
73
74void ensureTrivialMagneticFieldIsSet()
75{
76 static const bool done = [] {
77 TGeoGlobalMagField::Instance()->SetField(new o2::field::MagneticField());
78 TGeoGlobalMagField::Instance()->Lock();
79 return true;
80 }();
81 (void)done;
82}
83
84std::vector<LayerId> ordered(uint16_t first, uint16_t count)
85{
86 std::vector<LayerId> result;
87 result.reserve(count);
88 for (uint16_t i = 0; i < count; ++i) {
89 result.push_back(LayerId{static_cast<uint16_t>(first + i)});
90 }
91 return result;
92}
93
94class PrescribedDecoder
95{
96 public:
97 PrescribedDecoder(o2::detectors::DetID::ID detector, SurfaceKind kind, std::vector<DecodedCluster> clusters)
98 : mDetector{detector}, mKind{kind}, mClusters{std::move(clusters)}
99 {
100 }
101
103 const CompClusterExt& cluster,
104 gsl::span<const unsigned char>::iterator& patterns,
105 const TopologyDictionary* dictionary,
106 uint32_t externalIndex) const
107 {
108 const auto clusterData = o2::itsmft::ioutils::extractClusterData(cluster, patterns, dictionary);
110 if (externalIndex >= mClusters.size()) {
111 return result;
112 }
113 auto decoded = mClusters[externalIndex];
114 decoded.nPixels = clusterData.nPixels;
115 result = decoded;
116 return result;
117 }
118
119 private:
120 o2::detectors::DetID::ID mDetector;
121 SurfaceKind mKind;
122 std::vector<DecodedCluster> mClusters;
123};
124
125DecodedCluster diskCluster(float x, float y, float z, int layer)
126{
127 DecodedCluster cluster{};
128 cluster.global = {x, y, z};
129 cluster.rowColumnCovariance = {1.e-2f, 0.f, 1.e-2f};
130 cluster.layer = layer;
131 return cluster;
132}
133
134DecodedCluster cylinderCluster(float radius, float phi, float tanLambda, int layer)
135{
136 DecodedCluster cluster{};
137 cluster.global = {radius * std::cos(phi), radius * std::sin(phi), radius * tanLambda};
138 cluster.cylinderFrame = {cluster.global.x, cluster.global.y, cluster.global.z, 0.f};
139 cluster.rowColumnCovariance = {1.e-2f, 0.f, 1.e-2f};
140 cluster.layer = layer;
141 return cluster;
142}
143
155std::vector<DecodedCluster> buildItsHelixChainClusters(const std::vector<float>& radii, float bz, float pt, float phi0, float tanl)
156{
157 const float px = pt * std::cos(phi0);
158 const float py = pt * std::sin(phi0);
159 const float pz = pt * tanl;
160 o2::track::TrackPar seed(std::array<float, 3>{0.f, 0.f, 0.f}, std::array<float, 3>{px, py, pz}, 1, true);
161
162 std::vector<DecodedCluster> clusters;
163 clusters.reserve(radii.size());
164 for (size_t layer = 0; layer < radii.size(); ++layer) {
165 float xAtR = 0.f;
166 if (!seed.getXatLabR(radii[layer], xAtR, bz, o2::track::DirType::DirOutward)) {
167 return {};
168 }
169 bool ok = false;
170 const auto point = seed.getXYZGloAt(xAtR, bz, ok);
171 if (!ok) {
172 return {};
173 }
174 DecodedCluster cluster{};
175 cluster.global = {static_cast<float>(point.X()), static_cast<float>(point.Y()), static_cast<float>(point.Z())};
176 cluster.cylinderFrame = {cluster.global.x, cluster.global.y, cluster.global.z, 0.f};
177 cluster.rowColumnCovariance = {1.e-2f, 0.f, 1.e-2f};
178 cluster.layer = static_cast<int>(layer);
179 clusters.push_back(cluster);
180 }
181 return clusters;
182}
183
184TrackingParameters makeItsParams()
185{
186 auto p = test::makeTestTrackingParameters(o2::detectors::DetID::ITS);
187 // Tracklet formation needs a primary vertex to seed the search window
188 // (TrackerTraits.cxx's forTracklets()): with UseDiamond=false (ITS's own
189 // default) that must come from TimeFrame::getPrimaryVertices(), which
190 // these focused fixtures never populate. UseDiamond=true instead uses the
191 // fixed Diamond{0,0,0} vertex every synthetic radial chain below is built
192 // through, with no TimeFrame vertex needed.
193 p.UseDiamond = true;
194 return p;
195}
196
197TrackingParameters makeMftParams()
198{
199 auto p = test::makeTestTrackingParameters(o2::detectors::DetID::MFT);
200 p.UseDiamond = true;
201 p.CreateArtefactLabels = false;
202 return p;
203}
204
209test::TestClusterSourceInput makeSource(ClusterSourceId id, o2::detectors::DetID::ID det, const std::vector<LayerId>& surfaces,
210 const PrescribedDecoder& decoder, std::vector<CompClusterExt>& compactOut,
211 std::vector<unsigned char>& patternsOut, std::vector<ROFRecord>& rofsOut,
212 const std::vector<DecodedCluster>& decoded)
213{
214 compactOut.reserve(decoded.size());
215 patternsOut.reserve(decoded.size() * OnePixelPattern.size());
216 for (const auto& cluster : decoded) {
217 compactOut.emplace_back(0, 0, CompCluster::InvalidPatternID, cluster.layer);
218 patternsOut.insert(patternsOut.end(), OnePixelPattern.begin(), OnePixelPattern.end());
219 }
220 rofsOut = {ROFRecord{{100, 5}, 0, 0, static_cast<int>(compactOut.size())}};
221
223 source.id = id;
224 source.detector = det;
225 source.clusters = compactOut;
226 source.patterns = patternsOut;
227 source.rofs = rofsOut;
228 source.dictionary = &dict();
229 source.layerToSurface = surfaces;
230 source.timing = o2::its::LayerTiming{.mROFLength = 40};
231 source.setDecoder(decoder);
232 return source;
233}
234
238test::TestClusterSourceInput makeEmptySource(ClusterSourceId id, o2::detectors::DetID::ID det, const std::vector<LayerId>& surfaces,
239 const PrescribedDecoder& decoder)
240{
242 source.id = id;
243 source.detector = det;
244 source.dictionary = &dict();
245 source.layerToSurface = surfaces;
246 source.timing = o2::its::LayerTiming{.mROFLength = 40};
247 source.setDecoder(decoder);
248 return source;
249}
250
256template <o2::detectors::DetID::ID DetId, int NLayers>
257struct StandaloneRun {
258 TimeFrame frame;
259 std::vector<TrackingParameters> params;
260 std::shared_ptr<BoundedMemoryResource> pool = std::make_shared<BoundedMemoryResource>();
261 Tracker tracker;
262 TrackerTraits traits;
263 std::shared_ptr<tbb::task_arena> arena;
264 TimeFrameScratch* scratch = nullptr;
265 std::vector<SurfaceDescriptor> catalog;
266 bool success{false};
267
268 StandaloneRun(o2::detectors::DetID::ID det, SurfaceKind kind,
269 const TrackingParameters& singleParams, const std::vector<DecodedCluster>& decoded,
270 int rofLength = 40, LayerMask holeLayers = {})
271 : params{singleParams}
272 {
273 const auto orderedSurfaces = ordered(0, NLayers);
274 catalog.reserve(NLayers);
275 for (uint16_t i = 0; i < NLayers; ++i) {
276 SurfaceDescriptor surface{i, static_cast<uint8_t>(det), kind};
277 surface.chartRange = kind == SurfaceKind::Disk ? kMFTSurfaces[i].chartRange : SurfaceChartRange{-20.f, 20.f};
278 surface.referenceCoordinate = kind == SurfaceKind::Cylinder
279 ? kITSSurfaces[i].referenceCoordinate
280 : kMFTSurfaces[i].referenceCoordinate;
281 const float xOverX0 = det == o2::detectors::DetID::MFT ? kMFTSurfaces[i].material.xOverX0 : kITSSurfaces[i].material.xOverX0;
282 surface.material.xOverX0 = xOverX0;
283 surface.material.arealDensityGPerCm2 = xOverX0 * o2::its::constants::Radl * o2::its::constants::Rho;
284 catalog.push_back(surface);
285 }
286 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
287 TrackerInitialization configuration;
288 configuration.catalog = catalogView;
289 configuration.memoryPool = pool;
290 configuration.holeLayers = holeLayers;
291 configuration.plan = o2::itsmft::tracking::test::makeTrackingPlan(singleParams);
292 const auto configured = tracker.initialize(frame, configuration);
293 BOOST_REQUIRE(configured);
294 scratch = &frame.getScratch();
295 traits.setNThreads(1, arena);
296 frame.setBz(Bz);
297
298 std::vector<CompClusterExt> compact;
299 std::vector<unsigned char> patterns;
300 for (const auto& cluster : decoded) {
301 compact.emplace_back(0, 0, CompCluster::InvalidPatternID, cluster.layer);
302 patterns.insert(patterns.end(), OnePixelPattern.begin(), OnePixelPattern.end());
303 }
304 const std::vector<ROFRecord> rofs{ROFRecord{{100, 5}, 0, 0, static_cast<int>(compact.size())}};
305 PrescribedDecoder decoder{det, kind, decoded};
306 const auto layerMapping = ordered(0, NLayers);
307 test::loadTimeFrameSource(frame, decoder, o2::InteractionRecord{50, 5}, o2::its::LayerTiming{.mROFLength = static_cast<uint32_t>(rofLength)},
308 compact, patterns, rofs, &dict(), nullptr, det,
309 gsl::span<const LayerId>{layerMapping},
310 frame.getDetectorConfiguration().getSurfaceCatalog());
311
312 o2::its::LayerTiming layerTiming{};
313 layerTiming.mNROFsTF = 1;
314 layerTiming.mROFLength = rofLength;
316 for (int layer = 0; layer < NLayers; ++layer) {
317 rofTable.defineLayer(layer, layerTiming);
318 }
319 rofTable.init();
321 for (int layer = 0; layer < NLayers; ++layer) {
322 vtxTable.defineLayer(layer, layerTiming);
323 }
324 vtxTable.init();
326 mask.resetMask();
327 for (int layer = 0; layer < NLayers; ++layer) {
328 mask.setROFsEnabled(layer, 0, 1, 1);
329 }
330 frame.setROFViews(RuntimeROFViews{rofTable.getView(), vtxTable.getView(), mask.getView(), {}});
331 success = tracker.run(frame, traits);
332 }
333};
334
341struct CombinedTrackingComposer {
342 struct Result {
343 bool success{false};
344 size_t nITSTracks{0};
345 size_t nMFTTracks{0};
346 bool exceptionThrown{false};
347 };
348
350 TimeFrame* frame = nullptr;
351 std::optional<o2::its::LayerTiming> itsClock;
352 std::optional<o2::its::LayerTiming> mftClock;
353 bool publicationValid = false;
354
355 CombinedTrackingComposer(std::vector<TrackingParameters> itsParams, std::vector<TrackingParameters> mftParams)
356 : plan(std::move(itsParams), std::move(mftParams))
357 {
358 }
359
360 void adoptFrame(TimeFrame& f)
361 {
362 frame = &f;
363 plan.adoptFrame(f);
364 }
365 void setBz(float bz) { plan.setBz(bz); }
366 void setNThreads(int n) { plan.setNThreads(n); }
367
368 void clearPublicationSidecars() noexcept
369 {
371 }
372 void invalidatePublication() noexcept
373 {
374 itsClock.reset();
375 mftClock.reset();
376 publicationValid = false;
377 }
378 void markPublicationValid() noexcept
379 {
380 itsClock.emplace(plan.getITSROFViews().overlap.getClockLayer());
381 mftClock.emplace(plan.getMFTROFViews().overlap.getClockLayer());
382 publicationValid = true;
383 }
384 std::optional<GenericTrackPublicationExport> getITSPublicationExport() const
385 {
386 if (!publicationValid || !itsClock) {
387 return std::nullopt;
388 }
389 return GenericTrackPublicationExport{o2::detectors::DetID::ITS, ClusterSourceId{0}, *itsClock,
390 plan.getITSLayerMapping()};
391 }
392 std::optional<GenericTrackPublicationExport> getMFTPublicationExport() const
393 {
394 if (!publicationValid || !mftClock) {
395 return std::nullopt;
396 }
397 return GenericTrackPublicationExport{o2::detectors::DetID::MFT, ClusterSourceId{1}, *mftClock,
398 plan.getMFTLayerMapping()};
399 }
400
401 Result process(const test::TestClusterSourceInput& itsSource, const test::TestClusterSourceInput& mftSource, const o2::InteractionRecord& origin)
402 {
403 invalidatePublication();
404 clearPublicationSidecars();
405
406 plan.configureRofTables(itsSource, mftSource);
407 auto itsInput = itsSource;
408 auto mftInput = mftSource;
409 itsInput.rofViews = plan.getITSROFViews();
410 mftInput.rofViews = plan.getMFTROFViews();
411 try {
412 plan.validateSources(itsSource, mftSource);
413 const std::array<test::TestClusterSourceInput, 2> sources{itsInput, mftInput};
414 test::loadTimeFrameSources(*frame, gsl::span<const test::TestClusterSourceInput>{sources}, plan.catalogView(), origin);
415 } catch (const std::runtime_error&) {
417 frame->resetTimeFrame();
418 invalidatePublication();
419 return {false, 0, 0, true};
420 }
421
422 try {
423 const auto itsResult = plan.runITS();
424 if (!itsResult) {
426 frame->resetTimeFrame();
427 invalidatePublication();
428 return {itsResult, 0, 0};
429 }
430 const auto mftResult = plan.runMFT();
431 if (!mftResult) {
433 frame->resetTimeFrame();
434 invalidatePublication();
435 return {mftResult, 0, 0};
436 }
437 } catch (const std::exception&) {
439 frame->resetTimeFrame();
440 invalidatePublication();
441 return {false, 0, 0, true};
442 }
443
444 markPublicationValid();
445 const auto countFor = [this](int first) {
446 return static_cast<size_t>(std::count_if(this->frame->getGenericTracks().begin(), this->frame->getGenericTracks().end(),
447 [first](const auto& track) { return track.hitLayers.has(first); }));
448 };
449 return {true, countFor(0), countFor(ITSNLayers)};
450 }
451
452 const TimeFrameScratch& getITSScratch() const noexcept { return plan.getITSScratch(); }
453 const TimeFrameScratch& getMFTScratch() const noexcept { return plan.getMFTScratch(); }
454 gsl::span<const uint8_t> getITSSharedClusterFlags() const noexcept { return plan.getITSSharedClusterFlags(); }
455 gsl::span<const LayerId> getITSLayerMapping() const noexcept { return plan.getITSLayerMapping(); }
456 gsl::span<const LayerId> getMFTLayerMapping() const noexcept { return plan.getMFTLayerMapping(); }
457};
458
459CombinedTrackingComposer makeComposer(const TrackingParameters& itsParams, const TrackingParameters& mftParams)
460{
461 return CombinedTrackingComposer{std::vector<TrackingParameters>{itsParams}, std::vector<TrackingParameters>{mftParams}};
462}
463
464template <o2::detectors::DetID::ID DetId, int NLayers>
465void checkMinimumHitLayers(SurfaceKind kind, TrackingParameters params, std::vector<DecodedCluster> clusters)
466{
467 ensureTrivialMagneticFieldIsSet();
468 BOOST_REQUIRE_EQUAL(clusters.size(), static_cast<size_t>(NLayers));
469 const LayerMask allowedHoles{1u << 3};
470 params.MaxHoles = 1;
471 params.MinTrackLength = NLayers - 1;
472
473 // Exercise both an internal hole (span exceeds hit count) and a missing
474 // endpoint (no internal hole, but MaxHoles must not lower the minimum).
475 for (const int missingLayer : {3, NLayers - 1}) {
476 BOOST_TEST_CONTEXT("missing layer " << missingLayer)
477 {
478 auto incomplete = clusters;
479 incomplete.erase(incomplete.begin() + missingLayer);
480 StandaloneRun<DetId, NLayers> accepted{DetId, kind, params, incomplete, 40, allowedHoles};
481 BOOST_REQUIRE(accepted.success);
482 BOOST_REQUIRE_EQUAL(accepted.frame.getGenericTracks().size(), 1u);
483 BOOST_CHECK_EQUAL(accepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1);
484 BOOST_CHECK(!accepted.frame.getGenericTracks().front().hitLayers.has(missingLayer));
485
486 auto stricter = params;
487 stricter.MinTrackLength = NLayers;
488 StandaloneRun<DetId, NLayers> rejected{DetId, kind, stricter, incomplete, 40, allowedHoles};
489 BOOST_REQUIRE(rejected.success);
490 BOOST_CHECK(rejected.frame.getGenericTracks().empty());
491 }
492 }
493
494 // A skipped non-seeding surface is not a hole; it still cannot contribute
495 // a hit toward MinTrackLength.
496 clusters.erase(clusters.begin() + 3);
497 params.MaxHoles = 0;
498 params.SeedingLayers = LayerMask::span(0, NLayers - 1) & ~allowedHoles;
499 StandaloneRun<DetId, NLayers> sparseAccepted{DetId, kind, params, clusters};
500 BOOST_REQUIRE(sparseAccepted.success);
501 BOOST_REQUIRE_EQUAL(sparseAccepted.frame.getGenericTracks().size(), 1u);
502 BOOST_CHECK_EQUAL(sparseAccepted.frame.getGenericTracks().front().hitLayers.count(), NLayers - 1);
503 params.MinTrackLength = NLayers;
504 StandaloneRun<DetId, NLayers> sparseRejected{DetId, kind, params, clusters};
505 BOOST_REQUIRE(sparseRejected.success);
506 BOOST_CHECK(sparseRejected.frame.getGenericTracks().empty());
507}
508
509} // namespace
510
511BOOST_AUTO_TEST_CASE(CylinderRoadMinimumCountsHitLayers)
512{
513 const auto params = makeItsParams();
514 std::vector<float> radii;
515 for (const auto& surface : kITSSurfaces) {
516 radii.push_back(surface.referenceCoordinate);
517 }
518 checkMinimumHitLayers<o2::detectors::DetID::ITS, ITSNLayers>(
519 SurfaceKind::Cylinder, params, buildItsHelixChainClusters(radii, Bz, 1.f, 0.4f, 0.3f));
520}
521
522BOOST_AUTO_TEST_CASE(CombinedLoadingBackfillsOneGlobalWorkspace)
523{
524 // TrackerTraits::findRoads() unconditionally touches the global
525 // o2::base::Propagator singleton on first use, regardless of whether any
526 // road is actually found -- required before any clustersToTracks() call.
527 ensureTrivialMagneticFieldIsSet();
528 const auto itsSurfaces = ordered(0, ITSNLayers);
529 const auto mftSurfaces = ordered(ITSNLayers, MFTNLayers);
530 const auto itsClusters = std::vector<DecodedCluster>{cylinderCluster(3.f, 0.2f, 0.1f, 0), cylinderCluster(4.f, 0.2f, 0.1f, 1)};
531 const auto mftClusters = std::vector<DecodedCluster>{diskCluster(1.f, 0.5f, kMFTSurfaces[0].referenceCoordinate, 0), diskCluster(1.f, 0.5f, kMFTSurfaces[1].referenceCoordinate, 1)};
532
533 PrescribedDecoder itsDecoder{o2::detectors::DetID::ITS, SurfaceKind::Cylinder, itsClusters};
534 PrescribedDecoder mftDecoder{o2::detectors::DetID::MFT, SurfaceKind::Disk, mftClusters};
535 std::vector<CompClusterExt> itsCompact, mftCompact;
536 std::vector<unsigned char> itsPatterns, mftPatterns;
537 std::vector<ROFRecord> itsRofs, mftRofs;
538 const auto itsSource = makeSource(ClusterSourceId{0}, o2::detectors::DetID::ITS, itsSurfaces, itsDecoder, itsCompact, itsPatterns, itsRofs, itsClusters);
539 const auto mftSource = makeSource(ClusterSourceId{1}, o2::detectors::DetID::MFT, mftSurfaces, mftDecoder, mftCompact, mftPatterns, mftRofs, mftClusters);
540
541 auto itsParams = makeItsParams();
542 auto mftParams = makeMftParams();
543 itsParams.MinTrackLength = 4;
544 mftParams.MinTrackLength = 5;
545 auto composer = makeComposer(itsParams, mftParams);
546 TimeFrame frame;
547 composer.adoptFrame(frame);
548 composer.setBz(Bz);
549 composer.setNThreads(1);
550
551 constexpr uint32_t allCombinedSurfaces = (uint32_t{1} << (ITSNLayers + MFTNLayers)) - 1u;
552 BOOST_REQUIRE_EQUAL(composer.plan.itsTracker().getIterationConfigurations().size(), 1u);
553 const auto& combined = composer.plan.itsTracker().getIterationConfigurations()[0].parameters;
554 const auto& detector = frame.getDetectorConfiguration();
555 BOOST_CHECK_EQUAL(combined.NLayers, ITSNLayers + MFTNLayers);
556 BOOST_CHECK_EQUAL(combined.StartLayerMask.value(), allCombinedSurfaces);
557 BOOST_CHECK(combined.PassFlags == itsParams.PassFlags);
558 BOOST_REQUIRE(detector.indexTableConfigs.size() > 0);
559 BOOST_CHECK_EQUAL(detector.indexTableConfigs[0].getNcolBins(), itsParams.ColBins);
560 BOOST_CHECK_EQUAL(detector.indexTableConfigs[0].getNrowBins(), itsParams.RowBins);
561 BOOST_CHECK_EQUAL(combined.UseDiamond, itsParams.UseDiamond);
562 BOOST_CHECK_EQUAL_COLLECTIONS(std::begin(combined.Diamond), std::end(combined.Diamond),
563 std::begin(itsParams.Diamond), std::end(itsParams.Diamond));
564 BOOST_CHECK_EQUAL_COLLECTIONS(std::begin(combined.DiamondCov), std::end(combined.DiamondCov),
565 std::begin(itsParams.DiamondCov), std::end(itsParams.DiamondCov));
566 BOOST_CHECK_EQUAL(combined.MinTrackLength, itsParams.MinTrackLength);
567 BOOST_CHECK_EQUAL(combined.MaxHoles, itsParams.MaxHoles);
568 BOOST_CHECK_EQUAL(combined.NSigmaCut, itsParams.NSigmaCut);
569 BOOST_CHECK_EQUAL(combined.PVres, itsParams.PVres);
570 BOOST_CHECK_EQUAL(combined.TrackletMinPt, itsParams.TrackletMinPt);
571 BOOST_CHECK_EQUAL(combined.MaxChi2ClusterAttachment, itsParams.MaxChi2ClusterAttachment);
572 BOOST_CHECK_EQUAL(combined.MaxChi2NDF, itsParams.MaxChi2NDF);
573 BOOST_CHECK_EQUAL_COLLECTIONS(combined.MinPt.begin(), combined.MinPt.end(), itsParams.MinPt.begin(), itsParams.MinPt.end());
574 BOOST_CHECK_EQUAL(combined.RepeatRefitOut, itsParams.RepeatRefitOut);
575 BOOST_CHECK_EQUAL(combined.ShiftRefToCluster, itsParams.ShiftRefToCluster);
576 BOOST_CHECK_EQUAL(combined.PerPrimaryVertexProcessing, itsParams.PerPrimaryVertexProcessing);
577 BOOST_CHECK_EQUAL(combined.AllowSharingFirstCluster, itsParams.AllowSharingFirstCluster);
578 BOOST_CHECK_EQUAL(combined.SharedClusterMaxDeltaPhi, itsParams.SharedClusterMaxDeltaPhi);
579 BOOST_CHECK_EQUAL(combined.SharedClusterMaxDeltaEta, itsParams.SharedClusterMaxDeltaEta);
580 BOOST_CHECK_EQUAL(combined.SharedClusterOppositeSign, itsParams.SharedClusterOppositeSign);
581 BOOST_CHECK_EQUAL(combined.SharedMaxClusters, itsParams.SharedMaxClusters);
582
583 const auto checkConcatenated = [](const auto& actual, const auto& itsValues, const auto& mftValues) {
584 BOOST_REQUIRE_EQUAL(actual.size(), itsValues.size() + mftValues.size());
585 BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin(), actual.begin() + itsValues.size(), itsValues.begin(), itsValues.end());
586 BOOST_CHECK_EQUAL_COLLECTIONS(actual.begin() + itsValues.size(), actual.end(), mftValues.begin(), mftValues.end());
587 };
588 checkConcatenated(detector.addTimeError, itsParams.AddTimeError, mftParams.AddTimeError);
589 checkConcatenated(detector.layerResolution, itsParams.LayerResolution, mftParams.LayerResolution);
590 checkConcatenated(detector.systError2Row, itsParams.SystError2Row, mftParams.SystError2Row);
591 checkConcatenated(detector.systError2Col, itsParams.SystError2Col, mftParams.SystError2Col);
592 const auto catalog = frame.getDetectorConfiguration().getSurfaceCatalog();
593 BOOST_REQUIRE_EQUAL(catalog.nSurfaces, ITSNLayers + MFTNLayers);
594 for (uint32_t layer = 0; layer < catalog.nSurfaces; ++layer) {
595 const auto expected = layer < ITSNLayers ? kITSSurfaces[layer].material.xOverX0 : kMFTSurfaces[layer - ITSNLayers].material.xOverX0;
596 BOOST_CHECK_EQUAL(catalog.surfaces[layer].material.xOverX0, expected);
597 }
598
599 const auto result = composer.process(itsSource, mftSource, o2::InteractionRecord{50, 5});
600 BOOST_REQUIRE(result.success);
601
602 // The time frame owns two lookup records independently of the tracker cache.
605 BOOST_CHECK(&frame.getIndexTableUtils(0) != &detector.indexTableConfigs[0]);
606 BOOST_CHECK(frame.getIndexTableUtils(0).getCoordType() == IndexTableCoordType::PhiZ);
607 BOOST_CHECK(frame.getIndexTableUtils(ITSNLayers).getCoordType() == IndexTableCoordType::PhiR);
608
609 const auto topology = composer.plan.itsTracker().getIterationConfigurations()[0].getTopologyView(frame.getDetectorConfiguration().getSurfaceCatalog());
610 BOOST_CHECK_EQUAL(topology.seedingLayers.value(), allCombinedSurfaces);
611 BOOST_REQUIRE_EQUAL(topology.nEdges, static_cast<uint32_t>(ITSNLayers + MFTNLayers - 2));
612 for (uint16_t edgeId = 0; edgeId < topology.nEdges; ++edgeId) {
613 const auto& edge = topology.getEdge(EdgeId{edgeId});
614 const bool fromITS = edge.from.value() < ITSNLayers;
615 const bool toITS = edge.to.value() < ITSNLayers;
616 BOOST_CHECK_EQUAL(fromITS, toITS);
617 BOOST_CHECK(!(edge.from == LayerId{ITSNLayers - 1} && edge.to == LayerId{ITSNLayers}));
618 }
619
620 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(),
621 static_cast<int>(itsClusters.size() + mftClusters.size()));
622 BOOST_CHECK_EQUAL(&composer.getITSScratch(), &composer.getMFTScratch());
623 // The one workspace keeps source-local ROF numbering per global surface.
624 BOOST_CHECK_EQUAL(composer.frame->getNrof(0), 1);
625 BOOST_CHECK_EQUAL(composer.frame->getNrof(ITSNLayers), 1);
626}
627
628BOOST_AUTO_TEST_CASE(LoadFailureResetsWholeCombinedTFExactlyOnceAndInvalidatesPublication)
629{
630 ensureTrivialMagneticFieldIsSet();
631 const auto itsSurfaces = ordered(0, ITSNLayers);
632 const auto mftSurfaces = ordered(ITSNLayers, MFTNLayers);
633 const auto itsClusters = std::vector<DecodedCluster>{cylinderCluster(3.f, 0.2f, 0.1f, 0), cylinderCluster(4.f, 0.2f, 0.1f, 1)};
634 const auto mftClusters = std::vector<DecodedCluster>{diskCluster(1.f, 0.5f, kMFTSurfaces[0].referenceCoordinate, 0), diskCluster(1.f, 0.5f, kMFTSurfaces[1].referenceCoordinate, 1)};
635
636 PrescribedDecoder itsDecoder{o2::detectors::DetID::ITS, SurfaceKind::Cylinder, itsClusters};
637 PrescribedDecoder mftDecoder{o2::detectors::DetID::MFT, SurfaceKind::Disk, mftClusters};
638 std::vector<CompClusterExt> itsCompact, mftCompact;
639 std::vector<unsigned char> itsPatterns, mftPatterns;
640 std::vector<ROFRecord> itsRofs, mftRofs;
641 const auto itsSource = makeSource(ClusterSourceId{0}, o2::detectors::DetID::ITS, itsSurfaces, itsDecoder, itsCompact, itsPatterns, itsRofs, itsClusters);
642 auto mftSource = makeSource(ClusterSourceId{1}, o2::detectors::DetID::MFT, mftSurfaces, mftDecoder, mftCompact, mftPatterns, mftRofs, mftClusters);
643
644 auto composer = makeComposer(makeItsParams(), makeMftParams());
645 TimeFrame frame;
646 composer.adoptFrame(frame);
647 composer.setBz(Bz);
648 composer.setNThreads(1);
649
650 // First pass genuinely succeeds, so there is real state (scratches,
651 // GenericTracks, publication exports) for the second, failing pass to
652 // actually have to clear.
653 const auto first = composer.process(itsSource, mftSource, o2::InteractionRecord{50, 5});
654 BOOST_REQUIRE(first.success);
655 BOOST_REQUIRE(composer.getITSPublicationExport().has_value());
656 BOOST_REQUIRE(composer.getMFTPublicationExport().has_value());
657
658 // Malformed MFT ROF partition (a gap before the second cluster): a
659 // structural load failure test::loadTimeFrameSources() must
660 // reject before touching either scratch or the shared TimeFrame.
661 std::vector<ROFRecord> malformedMftRofs{ROFRecord{{100, 5}, 0, 0, 1}, ROFRecord{{140, 5}, 0, 2, 1}};
662 mftSource.rofs = malformedMftRofs;
663
664 const auto second = composer.process(itsSource, mftSource, o2::InteractionRecord{50, 5});
665 // MFT's own DropTFUponFailure defaults false (makeMftParams() never sets
666 // it), so this recoverable InvalidROFRange load error is still classified
667 // Structural.
668 BOOST_CHECK(!second.success);
669 BOOST_CHECK(second.exceptionThrown);
670 BOOST_CHECK_EQUAL(second.nITSTracks, 0u);
671 BOOST_CHECK_EQUAL(second.nMFTTracks, 0u);
672
673 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
674 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
675 BOOST_CHECK(frame.getGenericTracks().empty());
676 BOOST_CHECK(frame.getTrackClusterIndices().empty());
677 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
678 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
679}
680
681BOOST_AUTO_TEST_CASE(CombinedTrackingResourceFailureUsesSharedPolicyAndResetsWorkspace)
682{
683 ensureTrivialMagneticFieldIsSet();
684 const auto itsSurfaces = ordered(0, ITSNLayers);
685 const auto mftSurfaces = ordered(ITSNLayers, MFTNLayers);
686 const auto itsClusters = std::vector<DecodedCluster>{cylinderCluster(3.f, 0.2f, 0.1f, 0), cylinderCluster(4.f, 0.2f, 0.1f, 1)};
687 const auto mftClusters = std::vector<DecodedCluster>{diskCluster(1.f, 0.5f, kMFTSurfaces[0].referenceCoordinate, 0), diskCluster(1.f, 0.5f, kMFTSurfaces[1].referenceCoordinate, 1)};
688
689 PrescribedDecoder itsDecoder{o2::detectors::DetID::ITS, SurfaceKind::Cylinder, itsClusters};
690 PrescribedDecoder mftDecoder{o2::detectors::DetID::MFT, SurfaceKind::Disk, mftClusters};
691 std::vector<CompClusterExt> itsCompact, mftCompact;
692 std::vector<unsigned char> itsPatterns, mftPatterns;
693 std::vector<ROFRecord> itsRofs, mftRofs;
694 const auto itsSource = makeSource(ClusterSourceId{0}, o2::detectors::DetID::ITS, itsSurfaces, itsDecoder, itsCompact, itsPatterns, itsRofs, itsClusters);
695 const auto mftSource = makeSource(ClusterSourceId{1}, o2::detectors::DetID::MFT, mftSurfaces, mftDecoder, mftCompact, mftPatterns, mftRofs, mftClusters);
696
697 // One run has one resource budget and one failure policy. The combined
698 // scalar baseline is ITS, so exhausting that budget drops and resets the
699 // one frame-owned workspace atomically.
700 auto itsParams = makeItsParams();
701 itsParams.MaxMemory = 1;
702 itsParams.DropTFUponFailure = true;
703
704 auto composer = makeComposer(itsParams, makeMftParams());
705 TimeFrame frame;
706 composer.adoptFrame(frame);
707 composer.setBz(Bz);
708 composer.setNThreads(1);
709
710 const auto result = composer.process(itsSource, mftSource, o2::InteractionRecord{50, 5});
711 BOOST_CHECK(!result.success);
712 BOOST_CHECK(!result.exceptionThrown);
713 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
714 BOOST_CHECK_EQUAL(&composer.getITSScratch(), &composer.getMFTScratch());
715 BOOST_CHECK(frame.getGenericTracks().empty());
716 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
717 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
718}
719
720namespace
721{
722
725struct MinimalFixture {
726 std::vector<LayerId> itsSurfaces = ordered(0, ITSNLayers);
727 std::vector<LayerId> mftSurfaces = ordered(ITSNLayers, MFTNLayers);
728 std::vector<DecodedCluster> itsClusters{cylinderCluster(3.f, 0.2f, 0.1f, 0), cylinderCluster(4.f, 0.2f, 0.1f, 1)};
729 std::vector<DecodedCluster> mftClusters{diskCluster(1.f, 0.5f, kMFTSurfaces[0].referenceCoordinate, 0), diskCluster(1.f, 0.5f, kMFTSurfaces[1].referenceCoordinate, 1)};
730 PrescribedDecoder itsDecoder{o2::detectors::DetID::ITS, SurfaceKind::Cylinder, itsClusters};
731 PrescribedDecoder mftDecoder{o2::detectors::DetID::MFT, SurfaceKind::Disk, mftClusters};
732 std::vector<CompClusterExt> itsCompact, mftCompact;
733 std::vector<unsigned char> itsPatterns, mftPatterns;
734 std::vector<ROFRecord> itsRofs, mftRofs;
737
738 MinimalFixture()
739 {
740 itsSource = makeSource(ClusterSourceId{0}, o2::detectors::DetID::ITS, itsSurfaces, itsDecoder, itsCompact, itsPatterns, itsRofs, itsClusters);
741 mftSource = makeSource(ClusterSourceId{1}, o2::detectors::DetID::MFT, mftSurfaces, mftDecoder, mftCompact, mftPatterns, mftRofs, mftClusters);
742 }
743};
744
748void makeRofGap(std::vector<ROFRecord>& rofs)
749{
750 rofs = {ROFRecord{{100, 5}, 0, 0, 1}, ROFRecord{{140, 5}, 0, 2, 1}};
751}
752
753} // namespace
754
755BOOST_AUTO_TEST_CASE(MalformedITSInputIsAlwaysStructural)
756{
757 ensureTrivialMagneticFieldIsSet();
758
759 for (const bool itsDropTF : {true, false}) {
760 MinimalFixture fixture;
761 makeRofGap(fixture.itsRofs);
762 fixture.itsSource.rofs = fixture.itsRofs;
763
764 auto itsParams = makeItsParams();
765 itsParams.DropTFUponFailure = itsDropTF;
766 auto composer = makeComposer(itsParams, makeMftParams());
767 TimeFrame frame;
768 composer.adoptFrame(frame);
769 composer.setBz(Bz);
770 composer.setNThreads(1);
771
772 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
773 BOOST_CHECK(result.exceptionThrown);
774 BOOST_CHECK_MESSAGE(!result.success, "ITS DropTFUponFailure=" << itsDropTF);
775 // Every non-success path still performs exactly one whole reset:
776 // both scratches, the shared TimeFrame's GenericTracks, and both
777 // publication exports are empty/invalid regardless of classification.
778 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
779 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
780 BOOST_CHECK(frame.getGenericTracks().empty());
781 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
782 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
783 }
784}
785
786BOOST_AUTO_TEST_CASE(MalformedMFTInputIsAlwaysStructural)
787{
788 ensureTrivialMagneticFieldIsSet();
789
790 for (const bool combinedDropTF : {true, false}) {
791 MinimalFixture fixture;
792 makeRofGap(fixture.mftRofs);
793 fixture.mftSource.rofs = fixture.mftRofs;
794
795 auto itsParams = makeItsParams();
796 itsParams.DropTFUponFailure = combinedDropTF;
797 auto mftParams = makeMftParams();
798 mftParams.DropTFUponFailure = !combinedDropTF;
799 auto composer = makeComposer(itsParams, mftParams);
800 TimeFrame frame;
801 composer.adoptFrame(frame);
802 composer.setBz(Bz);
803 composer.setNThreads(1);
804
805 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
806 BOOST_CHECK(result.exceptionThrown);
807 BOOST_CHECK_MESSAGE(!result.success, "combined DropTFUponFailure=" << combinedDropTF);
808 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
809 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
810 BOOST_CHECK(frame.getGenericTracks().empty());
811 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
812 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
813 }
814}
815
816BOOST_AUTO_TEST_CASE(StructuralLoadErrorIsAlwaysStructuralRegardlessOfDropTF)
817{
818 ensureTrivialMagneticFieldIsSet();
819
820 // A missing dictionary must not turn into a dropped TF.
821 MinimalFixture fixture;
822 fixture.itsSource.dictionary = nullptr;
823
824 auto itsParams = makeItsParams();
825 itsParams.DropTFUponFailure = true;
826 auto composer = makeComposer(itsParams, makeMftParams());
827 TimeFrame frame;
828 composer.adoptFrame(frame);
829 composer.setBz(Bz);
830 composer.setNThreads(1);
831
832 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
833 BOOST_CHECK(!result.success);
834 BOOST_CHECK(result.exceptionThrown);
835 BOOST_CHECK(frame.getGenericTracks().empty());
836 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
837}
838
839BOOST_AUTO_TEST_CASE(UnrecognizedLoadSourceIsAlwaysStructural)
840{
841 ensureTrivialMagneticFieldIsSet();
842
843 // Unknown source IDs are rejected even when DropTFUponFailure is enabled.
844 MinimalFixture fixture;
845 fixture.itsSource.id = ClusterSourceId{5};
846
847 auto composer = makeComposer(makeItsParams(), makeMftParams());
848 TimeFrame frame;
849 composer.adoptFrame(frame);
850 composer.setBz(Bz);
851 composer.setNThreads(1);
852
853 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
854 BOOST_CHECK(!result.success);
855 BOOST_CHECK(result.exceptionThrown);
856 BOOST_CHECK(frame.getGenericTracks().empty());
857 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
858 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
859}
860
861BOOST_AUTO_TEST_CASE(StructuralTrackingExceptionIsClassifiedStructuralAfterWholeReset)
862{
863 ensureTrivialMagneticFieldIsSet();
864
865 // MaxMemory=1 with the shared DropTFUponFailure left false makes the one
866 // tracker propagate the resource exception to the composition boundary.
867 MinimalFixture fixture;
868 auto itsParams = makeItsParams();
869 itsParams.MaxMemory = 1;
870
871 auto composer = makeComposer(itsParams, makeMftParams());
872 TimeFrame frame;
873 composer.adoptFrame(frame);
874 composer.setBz(Bz);
875 composer.setNThreads(1);
876
877 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
878 BOOST_CHECK(!result.success);
879 BOOST_CHECK(result.exceptionThrown);
880 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
881 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
882 BOOST_CHECK(frame.getGenericTracks().empty());
883 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
884 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
885}
886
887BOOST_AUTO_TEST_CASE(OrderedSurfaceGettersAreAlwaysValidUnlikePublicationExports)
888{
889 auto composer = makeComposer(makeItsParams(), makeMftParams());
890 TimeFrame frame;
891 composer.adoptFrame(frame);
892
893 // Configuration is installed before ordered-surface access; publication
894 // exports remain unavailable until an event is processed.
895 const auto itsSurfacesBefore = composer.getITSLayerMapping();
896 const auto mftSurfacesBefore = composer.getMFTLayerMapping();
897 BOOST_REQUIRE_EQUAL(itsSurfacesBefore.size(), static_cast<size_t>(ITSNLayers));
898 BOOST_REQUIRE_EQUAL(mftSurfacesBefore.size(), static_cast<size_t>(MFTNLayers));
899 BOOST_CHECK(itsSurfacesBefore[0] == LayerId{0});
900 BOOST_CHECK(mftSurfacesBefore[0] == LayerId{ITSNLayers});
901 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
902 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
903
904 // Still identical after a failure (which invalidates the publication
905 // exports but must never move the fixed catalog-offset spans).
906 ensureTrivialMagneticFieldIsSet();
907 MinimalFixture fixture;
908 makeRofGap(fixture.mftRofs);
909 fixture.mftSource.rofs = fixture.mftRofs;
910 composer.setBz(Bz);
911 composer.setNThreads(1);
912 const auto failed = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
913 BOOST_REQUIRE(!failed.success);
914 BOOST_CHECK(composer.getITSLayerMapping().data() == itsSurfacesBefore.data());
915 BOOST_CHECK(composer.getMFTLayerMapping().data() == mftSurfacesBefore.data());
916}
917
918BOOST_AUTO_TEST_CASE(AtomicLoadFailureInvokesEngineResetOnlyAndLeavesNoPublicationState)
919{
920 // A load failure must reach the single frame reset directly --
921 // Tracker::run() (and therefore either leg's kernel sequence) must never run on a
922 // partially/never-loaded event. Externally this means: zero tracks
923 // reported, cleared scratch and ITS shared-cluster flags, and both
924 // publication exports invalidated.
925 ensureTrivialMagneticFieldIsSet();
926 MinimalFixture fixture;
927 makeRofGap(fixture.itsRofs);
928 fixture.itsSource.rofs = fixture.itsRofs;
929
930 auto composer = makeComposer(makeItsParams(), makeMftParams());
931 TimeFrame frame;
932 composer.adoptFrame(frame);
933 composer.setBz(Bz);
934 composer.setNThreads(1);
935
936 const auto result = composer.process(fixture.itsSource, fixture.mftSource, o2::InteractionRecord{50, 5});
937 BOOST_REQUIRE(!result.success);
938 BOOST_CHECK_EQUAL(result.nITSTracks, 0u);
939 BOOST_CHECK_EQUAL(result.nMFTTracks, 0u);
940
941 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
942 BOOST_CHECK_EQUAL(composer.frame->getTotalClusters(), 0);
943 BOOST_CHECK(frame.getGenericTracks().empty());
944 BOOST_CHECK(frame.getTrackClusterIndices().empty());
945 BOOST_CHECK(composer.getITSSharedClusterFlags().empty());
946 BOOST_CHECK(!composer.getITSPublicationExport().has_value());
947 BOOST_CHECK(!composer.getMFTPublicationExport().has_value());
948}
949
950BOOST_AUTO_TEST_CASE(DetectorConfigurationIsSharedAcrossPassesAndOwnsCatalogMaterial)
951{
952 auto init = test::makeCombinedConfiguration(makeItsParams(), makeMftParams());
953 std::vector<SurfaceDescriptor> catalog(init.catalog.surfaces, init.catalog.surfaces + init.catalog.nSurfaces);
954 catalog[0].material = {0.123f, 0.456f};
955 init.catalog = {catalog.data(), static_cast<uint32_t>(catalog.size())};
956 catalog[0].referenceCoordinate = 2.7f;
957 init.plan.execution = {123456789, true};
958 init.plan.iterations.resize(3, init.plan.iterations.front());
959 init.plan.iterations[1].TrackletMinPt = 0.2f;
960 init.plan.iterations[2].TrackletMinPt = 0.1f;
961 TimeFrame frame;
962 Tracker tracker;
963 BOOST_REQUIRE(tracker.initialize(frame, init));
964 catalog[0].referenceCoordinate = 99.f;
965 catalog[0].material = {};
966 const auto ownedCatalog = frame.getDetectorConfiguration().getSurfaceCatalog();
967 BOOST_CHECK_EQUAL(ownedCatalog.surfaces[0].material.xOverX0, 0.123f);
968 BOOST_CHECK_EQUAL(ownedCatalog.surfaces[0].material.arealDensityGPerCm2, 0.456f);
970 BOOST_CHECK_EQUAL(ownedCatalog.surfaces[0].referenceCoordinate, frame.getDetectorConfiguration().getRepresentativeRadius(LayerId{0}));
971 BOOST_CHECK_EQUAL(tracker.getExecutionPolicy().MaxMemory, 123456789u);
973 BOOST_REQUIRE_EQUAL(tracker.getIterationConfigurations().size(), 3u);
974 BOOST_CHECK_EQUAL(tracker.getIterationConfigurations()[1].parameters.TrackletMinPt, 0.2f);
975 BOOST_CHECK_EQUAL(tracker.getIterationConfigurations()[2].parameters.TrackletMinPt, 0.1f);
976
977 for (std::size_t iteration = 0; iteration < tracker.getIterationConfigurations().size(); ++iteration) {
978 const auto& pass = tracker.getIterationConfigurations()[iteration];
979 const IterationContext context{static_cast<int>(iteration), frame, frame.getScratch(), pass.getTopologyView(ownedCatalog), pass, {}, frame.getBz()};
980 BOOST_CHECK(&context.detectorConfiguration == &frame.getDetectorConfiguration());
981 BOOST_CHECK(context.topology.catalog.surfaces == ownedCatalog.surfaces);
982 }
983
984 const auto& cache = frame.getDetectorConfiguration().indexTableConfigs;
985 BOOST_REQUIRE_EQUAL(cache.configurationCount(), 2u);
986 BOOST_CHECK_EQUAL(&cache[0], &cache[ITSNLayers - 1]);
987 BOOST_CHECK_EQUAL(&cache[ITSNLayers], &cache[ITSNLayers + MFTNLayers - 1]);
988 BOOST_CHECK(&cache[0] != &cache[ITSNLayers]);
989 BOOST_CHECK(cache[0].getCoordType() == IndexTableCoordType::PhiZ);
990 BOOST_CHECK(cache[ITSNLayers].getCoordType() == IndexTableCoordType::PhiR);
991 auto copy = cache;
992 BOOST_CHECK(&copy[0] != &cache[0]);
993 BOOST_CHECK_EQUAL(&copy[0], &copy[1]);
994 BOOST_CHECK_EQUAL(copy[ITSNLayers].getNcolBins(), cache[ITSNLayers].getNcolBins());
995
996 // Neither discarding the tracker nor resetting event data releases detector state.
997 tracker = Tracker{};
998 frame.resetTimeFrame();
999 const auto& detector = frame.getDetectorConfiguration();
1000 BOOST_CHECK(frame.isConfigured());
1001 BOOST_CHECK(detector.getSurfaceCatalog().surfaces == ownedCatalog.surfaces);
1002 BOOST_CHECK_EQUAL(detector.getRepresentativeRadius(LayerId{0}), 2.7f);
1003 BOOST_CHECK_EQUAL(detector[LayerId{0}].material.xOverX0, 0.123f);
1004 BOOST_CHECK_EQUAL(detector.getComponentOffsets()[1], ITSNLayers);
1005
1006 auto detectorCopy = detector;
1007 BOOST_CHECK(detectorCopy.getSurfaceCatalog().surfaces != ownedCatalog.surfaces);
1008 BOOST_CHECK(&detectorCopy.indexTableConfigs[0] != &cache[0]);
1009 detectorCopy.layerResolution[0] = 42.f;
1010 auto movedDetector = std::move(detectorCopy);
1011 BOOST_CHECK_EQUAL(movedDetector.layerResolution[0], 42.f);
1012 BOOST_CHECK_EQUAL(movedDetector.getRepresentativeRadius(LayerId{0}), 2.7f);
1013 BOOST_CHECK_EQUAL(detector.layerResolution[0], init.plan.detector.LayerResolution[0]);
1014 BOOST_CHECK_EQUAL(movedDetector[LayerId{0}].material.xOverX0, 0.123f);
1015 BOOST_CHECK_EQUAL(detector.getRepresentativeRadius(LayerId{0}), 2.7f);
1016}
1017
1018BOOST_AUTO_TEST_CASE(InvalidDetectorInputsLeaveFrameUnconfiguredAndAllowRetry)
1019{
1020 auto init = test::makeCombinedConfiguration(makeItsParams(), makeMftParams());
1021 TimeFrame frame;
1022 Tracker tracker;
1023 for (const auto& boundaries : std::vector<std::vector<uint16_t>>{{}, {1}, {0, 0}, {0, 3, 2}, {0, ITSNLayers + MFTNLayers}}) {
1024 init.componentOffsets = boundaries;
1025 const auto result = tracker.initialize(frame, init);
1027
1028 BOOST_CHECK(!frame.isConfigured());
1031 BOOST_CHECK(tracker.getIterationConfigurations().empty());
1032 }
1033 init.componentOffsets = {0, ITSNLayers};
1034 init.holeLayers = LayerMask{uint32_t{1} << (ITSNLayers + MFTNLayers)};
1035 const auto result = tracker.initialize(frame, init);
1037
1038 BOOST_CHECK(!frame.isConfigured());
1039 init.holeLayers = {};
1040 BOOST_REQUIRE(tracker.initialize(frame, init));
1042}
1043
1044BOOST_AUTO_TEST_CASE(SingleKindIndexCacheUsesOneConfigurationAndRejectsInvalidCatalogs)
1045{
1046 for (const auto catalog : {SurfaceCatalogView{kITSSurfaces.data(), ITSNLayers},
1049 BOOST_REQUIRE(cache.reset(catalog));
1050 BOOST_CHECK_EQUAL(cache.size(), catalog.nSurfaces);
1052 BOOST_CHECK_EQUAL(&cache[0], &cache[catalog.nSurfaces - 1]);
1053 BOOST_CHECK(!cache.reset({nullptr, 1}));
1054 BOOST_CHECK_EQUAL(cache.size(), 0u);
1056 }
1057 auto invalid = kITSSurfaces[0];
1058 invalid.kind = static_cast<SurfaceKind>(255);
1060 BOOST_CHECK(!cache.reset({&invalid, 1}));
1061 BOOST_CHECK_EQUAL(cache.size(), 0u);
1062 BOOST_CHECK(!cache.reset({&invalid, MaxLayoutSurfaces + 1}));
1063}
1064
1065BOOST_AUTO_TEST_CASE(DenseTraversalIdsKeepTheirTypesAndRejectOutOfRangeSlots)
1066{
1067 auto init = test::makeCombinedConfiguration(makeItsParams(), makeMftParams());
1068 TimeFrame frame;
1069 Tracker tracker;
1070 BOOST_REQUIRE(tracker.initialize(frame, init));
1071 const auto& configuration = tracker.getIterationConfigurations().front();
1072 static_assert(std::is_same_v<decltype(configuration.edgeIds()[0]), EdgeId>);
1073 static_assert(std::is_same_v<decltype(configuration.cellIds()[0]), CellPathId>);
1074 for (const auto id : configuration.edgeIds()) {
1075 BOOST_REQUIRE(configuration.getEdgeSlot(id));
1076 BOOST_CHECK_EQUAL(*configuration.getEdgeSlot(id), id.value());
1077 }
1078 for (const auto id : configuration.cellIds()) {
1079 BOOST_REQUIRE(configuration.getCellSlot(id));
1080 BOOST_CHECK_EQUAL(*configuration.getCellSlot(id), id.value());
1081 }
1082 BOOST_CHECK(!configuration.getEdgeSlot(EdgeId{}));
1083 BOOST_CHECK(!configuration.getCellSlot(CellPathId{}));
1084 BOOST_CHECK(!configuration.getEdgeSlot(EdgeId{static_cast<uint16_t>(configuration.topology.edges.size())}));
1085 BOOST_CHECK(!configuration.getCellSlot(CellPathId{static_cast<uint16_t>(configuration.topology.paths.size())}));
1087 BOOST_CHECK(empty.edgeIds().empty());
1088 BOOST_CHECK(empty.cellIds().empty());
1089 BOOST_CHECK(!empty.getEdgeSlot(EdgeId{0}));
1090 BOOST_CHECK(!empty.getCellSlot(CellPathId{0}));
1091}
header::DataOrigin origin
Definition of the ITSMFT compact cluster.
Definition of the ClusterTopology class.
Base track model for the Barrel, params only, w/o covariance.
Shared CA tracking configuration for ITS and MFT.
Passive common TimeFrame owner.
int32_t i
bool done
#define failed(...)
Definition Utils.h:42
Definition of the ITSMFT ROFrame (trigger) record.
Tracker orchestrator.
Definition of the MagF class.
Runtime-plan-owned, detector-neutral CA workspace.
int ID
Detector identifiers: continuous, starting from 0.
Definition DetID.h:63
static constexpr unsigned short InvalidPatternID
Definition CompCluster.h:46
SurfaceCatalogView getSurfaceCatalog() const noexcept
bool reset(SurfaceCatalogView catalog) noexcept
void setNThreads(int n, std::shared_ptr< tbb::task_arena > &arena)
bool initialize(TimeFrame &frame, const TrackerInitialization &configuration)
Definition Tracker.cxx:414
const TrackingExecutionPolicy & getExecutionPolicy() const noexcept
Definition Tracker.h:65
bool run(TimeFrame &frame, TrackerTraits &traits)
Definition Tracker.cxx:572
gsl::span< const IterationConfiguration > getIterationConfigurations() const noexcept
Definition Tracker.h:64
void configureRofTables(const TestClusterSourceInput &itsSource, const TestClusterSourceInput &mftSource)
const TimeFrameScratch & getITSScratch() const noexcept
gsl::span< const LayerId > getITSLayerMapping() const noexcept
gsl::span< const uint8_t > getITSSharedClusterFlags() const noexcept
gsl::span< const LayerId > getMFTLayerMapping() const noexcept
void validateSources(const ClusterSourceInput &itsSource, const ClusterSourceInput &mftSource) const
const TimeFrameScratch & getMFTScratch() const noexcept
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
GLint GLsizei count
Definition glcorearb.h:399
GLuint64EXT * result
Definition glcorearb.h:5662
GLsizeiptr size
Definition glcorearb.h:659
GLint first
Definition glcorearb.h:399
GLdouble f
Definition glcorearb.h:310
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLint y
Definition glcorearb.h:270
GLenum const GLfloat * params
Definition glcorearb.h:272
typedef void(APIENTRYP PFNGLCULLFACEPROC)(GLenum mode)
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLint GLuint mask
Definition glcorearb.h:291
GLsizei GLenum * sources
Definition glcorearb.h:2516
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
ClusterData< T > extractClusterData(const CompClusterExt &c, gsl::span< const unsigned char >::iterator &patterns, const TopologyDictionary *dict)
Definition IOUtils.h:81
TrackingPlan makeTrackingPlan(const TrackingParameters &parameters)
constexpr std::array< SurfaceDescriptor, MFTNLayers > kMFTSurfaces
constexpr int MFTNLayers
MFT CA half-disk layer count.
constexpr std::array< SurfaceDescriptor, ITSNLayers > kITSSurfaces
constexpr int ITSNLayers
ITS CA layer count.
BOOST_AUTO_TEST_CASE(Cluster_messageable)
void empty(int)
int process(po::variables_map &vm)
RuntimeROFOverlapView overlap
Definition ROFViews.h:374
const DetectorConfiguration & getDetectorConfiguration() const noexcept
Definition TimeFrame.h:157
TimeFrameScratch & getScratch()
bool isConfigured() const noexcept
Definition TimeFrame.h:156
const auto & getIndexTableUtils() const
Definition TimeFrame.h:122
std::shared_ptr< BoundedMemoryResource > memoryPool
Definition Tracker.h:54
std::map< std::string, ID > expected
BOOST_CHECK(tree)
coder decode(ctfImage, triggersD, clustersD)
std::vector< Cluster > clusters
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())