Project
Loading...
Searching...
No Matches
testGenericTrack.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// Gate 4 GenericTrack foundation. Covers:
13// - GenericTrack/TrackClusterReference/o2::its::TimeStamp layout and
14// device-compatibility traits;
15// - isValidTrackRange()'s exact validity condition (empty/default, single-,
16// multi- and hole-containing ranges, out-of-range and reversed ranges);
17// - sorted global storage and source-indexed fitting-measurement lookup;
18// - cross-surface and cross-source TrackClusterReference resolution;
19// - that a completed track's hitLayers is the union of the LayerId of
20// every measurement its range references, and that each resolved
21// measurement's own surface matches the reference it was resolved from;
22// - that TimeFrame loading clears GenericTrack/track-label/track-reference
23// storage on both success and failure;
24// - that TimeFrame::resetTimeFrame() invalidates those result sidecars
25// together;
26// - reuse of the legacy timestamp in GenericTrack.
27//
28// This slice does not populate GenericTrack from CA seeds: every track/range
29// below is constructed directly by the test.
30
31#define BOOST_TEST_MODULE ITSMFT GenericTrack
32#define BOOST_TEST_MAIN
33#define BOOST_TEST_DYN_LINK
34#include <boost/test/unit_test.hpp>
35
36#include <array>
37#include <limits>
38#include <memory>
39#include <optional>
40#include <stdexcept>
41#include <type_traits>
42#include <vector>
43
44#include <gsl/gsl>
45
60
61using namespace o2::itsmft;
62using namespace o2::itsmft::tracking;
63
64// GenericTrack uses shared tracking state and the legacy symmetric timestamp.
65BOOST_AUTO_TEST_CASE(GenericTrackUsesSharedStateAndLegacyTimestamp)
66{
67 GenericTrack track{};
68 track.innerState.kind = SurfaceKind::Cylinder;
69 track.outerState.kind = SurfaceKind::Cylinder;
70 track.chi2 = 1.5f;
71 track.timestamp = o2::its::TimeStamp{120.f, 20.f};
72 track.hitLayers.set(0);
73 track.firstClusterRef = 0;
74 track.clusterRefEnd = 1;
75 BOOST_CHECK(track.hitLayers.has(0));
76 BOOST_CHECK_EQUAL(trackClusterRefCount(track), 1u);
77
79 BOOST_CHECK(reference.layer == LayerId{0});
80 BOOST_CHECK_EQUAL(reference.clusterId, 17u);
81}
82
83BOOST_AUTO_TEST_CASE(GenericTrackLayoutAndDeviceCompatibilityTraits)
84{
85 // The legacy timestamp inherits its storage, so GenericTrack no longer
86 // promises standard layout; it remains trivially copyable.
87 static_assert(std::is_trivially_copyable_v<GenericTrack>);
88 static_assert(sizeof(GenericTrack) == 208);
89 static_assert(alignof(GenericTrack) == alignof(o2::its::TimeStamp));
90 static_assert(std::is_standard_layout_v<TrackClusterReference>);
91 static_assert(std::is_trivially_copyable_v<TrackClusterReference>);
92 static_assert(std::is_trivially_copyable_v<o2::its::TimeStamp>);
93
94 static_assert(std::is_same_v<decltype(GenericTrack::hitLayers), LayerMask>);
95 static_assert(std::is_same_v<decltype(GenericTrack::innerState), SurfaceTrackState>);
96 static_assert(std::is_same_v<decltype(GenericTrack::outerState), SurfaceTrackState>);
97 static_assert(std::is_same_v<decltype(GenericTrack::timestamp), o2::its::TimeStamp>);
98 static_assert(std::is_same_v<decltype(GenericTrack::firstClusterRef), uint32_t>);
99 static_assert(std::is_same_v<decltype(GenericTrack::clusterRefEnd), uint32_t>);
100 static_assert(std::is_same_v<decltype(TrackClusterReference::layer), LayerId>);
101 static_assert(std::is_same_v<decltype(TrackClusterReference::clusterId), uint32_t>);
102
103 // Default-constructed: zeroed range, empty mask, no NLayers/detector
104 // dependency of any kind. Not constructed as `constexpr` here:
105 // o2::track::PID's constructor (SurfaceTrackState::pid's default
106 // member initializer) is not itself constexpr, so a GenericTrack instance
107 // cannot be a core-constant-expression -- a property of PID, unrelated to
108 // GenericTrack's trivial-copyability asserted above.
109 const GenericTrack defaultTrack{};
110 BOOST_CHECK_EQUAL(defaultTrack.firstClusterRef, 0u);
111 BOOST_CHECK_EQUAL(defaultTrack.clusterRefEnd, 0u);
112 BOOST_CHECK(defaultTrack.hitLayers.empty());
113 BOOST_CHECK_EQUAL(defaultTrack.chi2, 0.f);
114 BOOST_CHECK_EQUAL(defaultTrack.timestamp.getTimeStampError(), 0.f);
115}
116
117// --- isValidTrackRange() -------------------------------------------------
118
119BOOST_AUTO_TEST_CASE(EmptyDefaultRangeIsValidForAnyContainerSize)
120{
121 const GenericTrack track{};
122 BOOST_CHECK(isValidTrackRange(track, 0));
123 BOOST_CHECK(isValidTrackRange(track, 5));
124 BOOST_CHECK_EQUAL(trackClusterRefCount(track), 0u);
125}
126
127BOOST_AUTO_TEST_CASE(ValidSingleMultiAndHoleContainingRanges)
128{
129 // Single-hit range: [0,1) into a 1-element array.
130 GenericTrack single{};
131 single.firstClusterRef = 0;
132 single.clusterRefEnd = 1;
133 BOOST_CHECK(isValidTrackRange(single, 1));
134 BOOST_CHECK_EQUAL(trackClusterRefCount(single), 1u);
135
136 // Multi-hit range: [1,4) into a 5-element array (some entries before/after
137 // the range belong to other tracks sharing the same flat array).
138 GenericTrack multi{};
139 multi.firstClusterRef = 1;
140 multi.clusterRefEnd = 4;
141 BOOST_CHECK(isValidTrackRange(multi, 5));
142 BOOST_CHECK_EQUAL(trackClusterRefCount(multi), 3u);
143
144 // Hole-containing: the range itself is a dense [first,end) span of
145 // *present* references (holes are never stored as sentinel entries); a
146 // hole instead shows up as a gap in hitLayers' LayerId numbering. A
147 // 2-hit track on surfaces {0,2} (skipping surface 1) is a valid,
148 // completed, hole-containing track: its range is still contiguous and
149 // valid, only its mask has a gap.
150 GenericTrack withHole{};
151 withHole.firstClusterRef = 0;
152 withHole.clusterRefEnd = 2;
153 withHole.hitLayers.set(0);
154 withHole.hitLayers.set(2);
155 BOOST_CHECK(isValidTrackRange(withHole, 2));
156 BOOST_CHECK_EQUAL(withHole.hitLayers.count(), 2);
157 BOOST_CHECK(!withHole.hitLayers.has(1)); // the hole
158}
159
160BOOST_AUTO_TEST_CASE(OutOfRangeAndReversedRangesAreRejected)
161{
162 GenericTrack pastEnd{};
163 pastEnd.firstClusterRef = 0;
164 pastEnd.clusterRefEnd = 6;
165 BOOST_CHECK(!isValidTrackRange(pastEnd, 5)); // clusterRefEnd > size
166
167 GenericTrack exactlyAtSize{};
168 exactlyAtSize.firstClusterRef = 0;
169 exactlyAtSize.clusterRefEnd = 5;
170 BOOST_CHECK(isValidTrackRange(exactlyAtSize, 5)); // clusterRefEnd == size is valid (half-open)
171
172 GenericTrack reversed{};
173 reversed.firstClusterRef = 3;
174 reversed.clusterRefEnd = 1;
175 BOOST_CHECK(!isValidTrackRange(reversed, 5)); // firstClusterRef > clusterRefEnd
176}
177
178// --- Per-surface measurement storage / TrackClusterReference resolution --
179
180namespace
181{
182
183// Minimal, geometry-free decoder (same construction as
184// testMultiSourceLoading.cxx/testTimeFrameLifecycle.cxx): sensorID is used
185// directly as the detector-local layer.
186class FakeClusterDecoder
187{
188 public:
189 FakeClusterDecoder(o2::detectors::DetID::ID detector, bool disk) : mDetector(detector), mDisk(disk) {}
190
192 const CompClusterExt& cluster,
193 gsl::span<const unsigned char>::iterator& patterns,
194 const TopologyDictionary* dict,
195 uint32_t) const
196 {
197 const auto clusterData = o2::itsmft::ioutils::extractClusterData(cluster, patterns, dict);
199 const int sensorID = cluster.getSensorID();
200 auto& decoded = result;
201 decoded.global = {static_cast<float>(sensorID), static_cast<float>(cluster.getRow()), static_cast<float>(cluster.getCol())};
202 decoded.cylinderFrame = {10.f + sensorID, 1.f, 2.f, 0.1f};
203 decoded.rowColumnCovariance = {clusterData.sig2Row, 0.f, clusterData.sig2Col};
204 decoded.nPixels = clusterData.nPixels;
205 decoded.layer = sensorID;
206 return result;
207 }
208
209 private:
210 o2::detectors::DetID::ID mDetector;
211 bool mDisk;
212};
213
214struct BuiltLayout {
216 std::vector<SurfaceDescriptor> surfaces;
217
218 SurfaceCatalogView getCatalog() const noexcept
219 {
220 return layout.getSurfaceCatalog();
221 }
222};
223
224// 4-surface disconnected ITS(cylinder){0,1,2}+MFT(disk){3} layout, matching
225// this file's fixtures below.
226BuiltLayout makeCombinedLayout()
227{
228 std::vector<SurfaceDescriptor> surfaces;
229 surfaces.push_back(SurfaceDescriptor{0, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
230 surfaces.push_back(SurfaceDescriptor{1, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
231 surfaces.push_back(SurfaceDescriptor{2, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
232 surfaces.push_back(SurfaceDescriptor{0, static_cast<uint8_t>(o2::detectors::DetID::MFT), SurfaceKind::Disk});
233 const std::vector<uint16_t> componentOffsets = {0, 3};
234 return BuiltLayout{DetectorConfiguration{surfaces, componentOffsets}, std::move(surfaces)};
235}
236
237constexpr std::array<unsigned char, 3> onePixelPattern{1, 1, 0x80};
238
239std::vector<unsigned char> makePatternBytes(size_t nClusters)
240{
241 std::vector<unsigned char> bytes;
242 bytes.reserve(nClusters * onePixelPattern.size());
243 for (size_t i = 0; i < nClusters; ++i) {
244 bytes.insert(bytes.end(), onePixelPattern.begin(), onePixelPattern.end());
245 }
246 return bytes;
247}
248
249const TopologyDictionary& dict()
250{
251 static const TopologyDictionary d;
252 return d;
253}
254
255// Builds a combined ITS(surfaces {0,1,2}, source 0)+MFT(surface {3}, source
256// 1) TimeFrame with exactly one measurement on each of surfaces
257// {0,1,3} (surface 2 is left empty, a deliberate hole in the catalog's own
258// numbering -- not exercised by any track in these tests, only present to
259// prove per-surface storage does not require every surface to be non-empty).
260void loadThreeMeasurementFrame(TimeFrame& frame, const BuiltLayout& layout,
261 std::vector<std::vector<uint32_t>>* externalIndicesBySurface = nullptr,
262 std::vector<std::vector<uint32_t>>* clusterSizesBySurface = nullptr)
263{
264 if (!frame.isConfigured()) {
265 BOOST_REQUIRE(frame.configure(DetectorConfiguration{layout.layout},
266 0, 0, std::make_shared<BoundedMemoryResource>()));
267 }
268 const std::vector<CompClusterExt> itsClusters{
271 };
272 const auto itsPatterns = makePatternBytes(itsClusters.size());
273 const std::vector<ROFRecord> itsRofs{ROFRecord{{0, 0}, 0, 0, 2}};
274 const std::array<LayerId, 2> itsLayerToSurface{LayerId{0}, LayerId{1}};
275 static const FakeClusterDecoder itsDecoder{o2::detectors::DetID::ITS, false};
276
277 const std::vector<CompClusterExt> mftClusters{{5, 6, CompCluster::InvalidPatternID, 0}};
278 const auto mftPatterns = makePatternBytes(mftClusters.size());
279 const std::vector<ROFRecord> mftRofs{ROFRecord{{0, 0}, 0, 0, 1}};
280 const std::array<LayerId, 1> mftLayerToSurface{LayerId{3}};
281 static const FakeClusterDecoder mftDecoder{o2::detectors::DetID::MFT, true};
282
283 std::array<test::TestClusterSourceInput, 2> sources{};
284 sources[0].id = ClusterSourceId{0};
285 sources[0].detector = o2::detectors::DetID::ITS;
286 sources[0].clusters = itsClusters;
287 sources[0].patterns = itsPatterns;
288 sources[0].rofs = itsRofs;
289 sources[0].dictionary = &dict();
290 sources[0].layerToSurface = itsLayerToSurface;
291 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
292 sources[0].setDecoder(itsDecoder);
293
294 sources[1].id = ClusterSourceId{1};
295 sources[1].detector = o2::detectors::DetID::MFT;
296 sources[1].clusters = mftClusters;
297 sources[1].patterns = mftPatterns;
298 sources[1].rofs = mftRofs;
299 sources[1].dictionary = &dict();
300 sources[1].layerToSurface = mftLayerToSurface;
301 sources[1].timing = o2::its::LayerTiming{.mROFLength = 50};
302 sources[1].setDecoder(mftDecoder);
303
304 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0},
305 externalIndicesBySurface, clusterSizesBySurface));
306}
307
308} // namespace
309
310BOOST_AUTO_TEST_CASE(SurfaceMeasurementStorageUsesStablePreSortIndices)
311{
312 const auto layout = makeCombinedLayout();
313 TimeFrame frame;
314 loadThreeMeasurementFrame(frame, layout);
315
316 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 1u);
317 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{1}).size(), 1u);
318 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{2}).size(), 0u);
319 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{3}).size(), 1u);
320
321 // The compact global on each layer carries its stable position in that
322 // layer's pre-sort measurement arrays.
323 const auto& onZero = frame.getGlobalMeasurements(LayerId{0})[0];
324 const auto& onOne = frame.getGlobalMeasurements(LayerId{1})[0];
325 const auto& onThree = frame.getGlobalMeasurements(LayerId{3})[0];
326 BOOST_CHECK_EQUAL(onZero.clusterId, 0u);
327 BOOST_CHECK_EQUAL(onOne.clusterId, 0u);
328 BOOST_CHECK_EQUAL(onThree.clusterId, 0u);
329 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{0}, onZero.clusterId) != nullptr);
330 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{1}, onOne.clusterId) != nullptr);
331 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{3}, onThree.clusterId) != nullptr);
332
333 // An ID beyond the TimeFrame-owned surface's dense range is unresolved.
334 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{0}, 99) == nullptr);
335 // Surface 2 has zero measurements: even index 0 is out of range.
336 BOOST_CHECK(frame.getGlobalMeasurements(LayerId{2}).empty());
337 // Invalid surface id (out of range for a 4-surface catalog).
338 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{4}, 0) == nullptr);
339}
340
341BOOST_AUTO_TEST_CASE(CrossSurfaceAndCrossSourceTrackClusterReferenceResolution)
342{
343 const auto layout = makeCombinedLayout();
344 TimeFrame frame;
345 loadThreeMeasurementFrame(frame, layout);
346
347 // A single common track crossing the ITS/MFT source boundary, traversal
348 // order inner to outer: surface 0 (ITS, source 0), surface 1 (ITS, source
349 // 0), surface 3 (MFT, source 1) -- skipping surface 2 as a hole. Each
350 // reference pairs the surface with that surface's own (surface-local)
351 // measurement index, never a raw external cluster index or a global
352 // position.
353 const std::vector<TrackClusterReference> trackClusterIndices{
354 {LayerId{0}, 0, 0},
355 {LayerId{1}, 0, 0},
356 {LayerId{3}, 0, 0},
357 };
358
359 GenericTrack track{};
360 track.firstClusterRef = 0;
361 track.clusterRefEnd = static_cast<uint32_t>(trackClusterIndices.size());
362 track.hitLayers.set(0);
363 track.hitLayers.set(1);
364 track.hitLayers.set(3);
365 BOOST_REQUIRE(isValidTrackRange(track, static_cast<uint32_t>(trackClusterIndices.size())));
366
367 bool foundITSZero = false, foundITSOne = false, foundMFT = false;
368 for (uint32_t i = track.firstClusterRef; i < track.clusterRefEnd; ++i) {
369 const auto& reference = trackClusterIndices[i];
370 const auto* measurement = frame.getSurfaceMeasurement(reference.layer, reference.clusterId);
371 BOOST_REQUIRE(measurement != nullptr);
372 if (reference.layer == LayerId{0}) {
373 foundITSZero = true;
374 } else if (reference.layer == LayerId{1}) {
375 foundITSOne = true;
376 } else if (reference.layer == LayerId{3}) {
377 foundMFT = true;
378 }
379 }
380 BOOST_CHECK(foundITSZero);
381 BOOST_CHECK(foundITSOne);
382 BOOST_CHECK(foundMFT);
383}
384
385BOOST_AUTO_TEST_CASE(HitSurfacesEqualsUnionAndEachMeasurementSurfaceMatchesItsReference)
386{
387 const auto layout = makeCombinedLayout();
388 TimeFrame frame;
389 loadThreeMeasurementFrame(frame, layout);
390
391 const std::vector<TrackClusterReference> trackClusterIndices{
392 {LayerId{0}, 0, 0},
393 {LayerId{1}, 0, 0},
394 {LayerId{3}, 0, 0},
395 };
396
397 GenericTrack track{};
398 track.firstClusterRef = 0;
399 track.clusterRefEnd = static_cast<uint32_t>(trackClusterIndices.size());
400 track.hitLayers.set(0);
401 track.hitLayers.set(1);
402 track.hitLayers.set(3);
403
404 LayerMask observed{};
405 BOOST_REQUIRE(isValidTrackRange(track, static_cast<uint32_t>(trackClusterIndices.size())));
406 for (uint32_t i = track.firstClusterRef; i < track.clusterRefEnd; ++i) {
407 const auto& reference = trackClusterIndices[i];
408 const auto* measurement = frame.getSurfaceMeasurement(reference.layer, reference.clusterId);
409 BOOST_REQUIRE(measurement != nullptr);
410 observed.set(reference.layer.value());
411 }
412 BOOST_CHECK(observed == track.hitLayers);
413
414 // A hole-containing sub-track referencing only surfaces 0 and 3 (skipping
415 // 1): still a valid, completed track, mask still matches exactly the
416 // (smaller) referenced set.
417 const std::vector<TrackClusterReference> holeIndices{
418 {LayerId{0}, 0, 0},
419 {LayerId{3}, 0, 0},
420 };
421 GenericTrack holeTrack{};
422 holeTrack.firstClusterRef = 0;
423 holeTrack.clusterRefEnd = 2;
424 holeTrack.hitLayers.set(0);
425 holeTrack.hitLayers.set(3);
426
427 LayerMask observedHole{};
428 BOOST_REQUIRE(isValidTrackRange(holeTrack, static_cast<uint32_t>(holeIndices.size())));
429 for (uint32_t i = holeTrack.firstClusterRef; i < holeTrack.clusterRefEnd; ++i) {
430 const auto& reference = holeIndices[i];
431 const auto* measurement = frame.getSurfaceMeasurement(reference.layer, reference.clusterId);
432 BOOST_REQUIRE(measurement != nullptr);
433 observedHole.set(reference.layer.value());
434 }
435 BOOST_CHECK(observedHole == holeTrack.hitLayers);
436 BOOST_CHECK(!observedHole.has(1)); // the hole
437}
438
439// --- TimeFrame reload/wipe lifecycle --------------------------------------
440
441namespace
442{
443
444// Deterministic, geometry-free stand-in for detector geometry decoding
445// (same construction as testTimeFrameLifecycle.cxx): sensorID is used
446// directly as the detector-local layer.
447class LegacyLikeDecoder
448{
449 public:
450 explicit LegacyLikeDecoder(o2::detectors::DetID::ID detector) : mDetector(detector) {}
451
453 const CompClusterExt& cluster,
454 gsl::span<const unsigned char>::iterator& patterns,
455 const TopologyDictionary* dict,
456 uint32_t) const
457 {
458 const auto clusterData = o2::itsmft::ioutils::extractClusterData(cluster, patterns, dict);
460 const int sensorID = cluster.getSensorID();
461 auto& decoded = result;
462 decoded.global = {static_cast<float>(sensorID) * 10.f, static_cast<float>(cluster.getRow()), static_cast<float>(cluster.getCol())};
463 decoded.cylinderFrame = {static_cast<float>(sensorID) + 100.f, static_cast<float>(cluster.getRow()) + 1.f, static_cast<float>(cluster.getCol()) + 2.f, 0.01f * sensorID};
464 decoded.rowColumnCovariance = {clusterData.sig2Row, 0.f, clusterData.sig2Col};
465 decoded.nPixels = clusterData.nPixels;
466 decoded.layer = sensorID;
467 return result;
468 }
469
470 private:
471 o2::detectors::DetID::ID mDetector;
472};
473
474std::vector<SurfaceDescriptor> makeITSTestCatalog()
475{
476 std::vector<SurfaceDescriptor> surfaces;
477 surfaces.reserve(ITSNLayers);
478 for (uint16_t i = 0; i < ITSNLayers; ++i) {
479 surfaces.push_back(SurfaceDescriptor{i, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
480 }
481 return surfaces;
482}
483
484std::vector<LayerId> identitySurfaces(uint16_t nLayers)
485{
486 std::vector<LayerId> mapping;
487 mapping.reserve(nLayers);
488 for (uint16_t i = 0; i < nLayers; ++i) {
489 mapping.push_back(LayerId{i});
490 }
491 return mapping;
492}
493
494struct TimeFrameFixture {
496 std::vector<std::vector<uint32_t>> externalIndicesBySurface;
497 std::vector<std::vector<uint32_t>> clusterSizesBySurface;
498 std::vector<LayerId> layerMapping{identitySurfaces(ITSNLayers)};
499 // Keep the catalog with the layout fixture so initialization inputs have one
500 // explicit owner.
501 std::vector<SurfaceDescriptor> catalog{makeITSTestCatalog()};
502 LegacyLikeDecoder decoder{o2::detectors::DetID::ITS};
504 o2::its::LayerTiming timing{.mROFLength = 40};
505
506 TimeFrameFixture()
507 {
508 DetectorConfiguration layout{gsl::span<const SurfaceDescriptor>{catalog}};
509 BOOST_REQUIRE(tf.configure(std::move(layout), 0, 0,
510 std::make_shared<BoundedMemoryResource>()));
511 }
512
513 // One cluster on layer 0, one ROF: the minimal input that succeeds.
514 void load()
515 {
516 const std::vector<CompClusterExt> clusters{{0, 1, CompCluster::InvalidPatternID, 0}};
517 const auto patterns = makePatternBytes(clusters.size());
518 const std::vector<ROFRecord> rofs{ROFRecord{{100, 5}, 0, 0, 1}};
519 test::loadTimeFrameSource(tf, decoder, origin, timing, clusters, patterns, rofs, &dict(), nullptr, o2::detectors::DetID::ITS,
520 gsl::span<const LayerId>{layerMapping}, tf.getDetectorConfiguration().getSurfaceCatalog(),
521 &externalIndicesBySurface, &clusterSizesBySurface);
522 }
523};
524
525struct TestGenericTrack {
527 std::vector<TrackClusterReference> references;
528};
529
530TestGenericTrack makeTestGenericTrack()
531{
532 TestGenericTrack record;
533 record.track.innerState.kind = SurfaceKind::Cylinder;
534 record.track.outerState.kind = SurfaceKind::Cylinder;
535 record.track.timestamp = {120.f, 20.f};
536 record.track.hitLayers.set(0);
537 record.references.push_back({LayerId{0}, 0, 0});
538 return record;
539}
540
541uint32_t storeTestGenericTrack(TimeFrame& frame, TestGenericTrack record)
542{
543 const auto index = static_cast<uint32_t>(frame.getGenericTracks().size());
544 record.track.firstClusterRef = static_cast<uint32_t>(frame.getTrackClusterIndices().size());
545 frame.getTrackClusterIndices().insert(frame.getTrackClusterIndices().end(), record.references.begin(), record.references.end());
546 record.track.clusterRefEnd = static_cast<uint32_t>(frame.getTrackClusterIndices().size());
547 frame.getGenericTracks().push_back(record.track);
548 return index;
549}
550
551// Populates the common result sidecars with arbitrary, self-consistent
552// content so a subsequent clear can be observed.
553void populateCommonResults(TimeFrame& tf)
554{
555 tf.getTrackClusterIndices().push_back(TrackClusterReference{LayerId{0}, 0, 0});
556 tf.getTrackClusterIndices().push_back(TrackClusterReference{LayerId{1}, 0, 1});
558 track.firstClusterRef = 0;
559 track.clusterRefEnd = 2;
560 track.hitLayers.set(0);
561 track.hitLayers.set(1);
562 tf.getGenericTracks().push_back(track);
563 tf.getTrackLabels().push_back(o2::MCCompLabel{1, 0, 0, false});
564}
565
566} // namespace
567
568BOOST_AUTO_TEST_CASE(SuccessfulReloadClearsCommonTrackResults)
569{
570 TimeFrameFixture fixture;
571 BOOST_REQUIRE_NO_THROW(fixture.load());
572
573 populateCommonResults(fixture.tf);
574 BOOST_REQUIRE_EQUAL(fixture.tf.getGenericTracks().size(), 1u);
575 BOOST_REQUIRE_EQUAL(fixture.tf.getTrackLabels().size(), 1u);
576 BOOST_REQUIRE_EQUAL(fixture.tf.getTrackClusterIndices().size(), 2u);
577
578 // A second, independently successful load on the same TimeFrame: the
579 // normalized frame is replaced, and the common track result sidecars built
580 // against the previous frame must be cleared in the same successful commit.
581 BOOST_REQUIRE_NO_THROW(fixture.load());
582 BOOST_CHECK(fixture.tf.getGenericTracks().empty());
583 BOOST_CHECK(fixture.tf.getTrackLabels().empty());
584 BOOST_CHECK(fixture.tf.getTrackClusterIndices().empty());
585}
586
587BOOST_AUTO_TEST_CASE(FailedLoadClearsCommonTrackResults)
588{
589 TimeFrameFixture fixture;
590 BOOST_REQUIRE_NO_THROW(fixture.load());
591
592 populateCommonResults(fixture.tf);
593 BOOST_REQUIRE_EQUAL(fixture.tf.getGenericTracks().size(), 1u);
594 BOOST_REQUIRE_EQUAL(fixture.tf.getTrackLabels().size(), 1u);
595 BOOST_REQUIRE_EQUAL(fixture.tf.getTrackClusterIndices().size(), 2u);
596 BOOST_REQUIRE(fixture.tf.getTotalMeasurements() > 0u);
597
598 // Deliberately fail: the frame loader preflight rejects an
599 // unsupported detector before touching anything.
600 const std::vector<CompClusterExt> clusters{{0, 1, CompCluster::InvalidPatternID, 0}};
601 const auto patterns = makePatternBytes(clusters.size());
602 const std::vector<ROFRecord> rofs{ROFRecord{{200, 5}, 0, 0, 1}};
603 const auto& orderedSurfaces = fixture.layerMapping;
604 BOOST_CHECK_EXCEPTION(test::loadTimeFrameSource(fixture.tf, fixture.decoder, fixture.origin, fixture.timing, clusters, patterns, rofs,
605 &dict(), nullptr, o2::detectors::DetID::TPC,
606 gsl::span<const LayerId>{orderedSurfaces}, fixture.tf.getDetectorConfiguration().getSurfaceCatalog()),
607 std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Unsupported source detector") != std::string::npos; });
608
609 BOOST_CHECK_EQUAL(fixture.tf.getTotalMeasurements(), 0u);
610 BOOST_CHECK(fixture.tf.getGenericTracks().empty());
611 BOOST_CHECK(fixture.tf.getTrackLabels().empty());
612 BOOST_CHECK(fixture.tf.getTrackClusterIndices().empty());
613}
614
615BOOST_AUTO_TEST_CASE(TimeFrameWipeInvalidatesCommonTrackResultsTogether)
616{
618 populateCommonResults(tf);
619
620 BOOST_REQUIRE_EQUAL(tf.getGenericTracks().size(), 1u);
621 BOOST_REQUIRE_EQUAL(tf.getTrackLabels().size(), 1u);
622 BOOST_REQUIRE_EQUAL(tf.getTrackClusterIndices().size(), 2u);
623 BOOST_REQUIRE(isValidTrackRange(tf.getGenericTracks()[0], static_cast<uint32_t>(tf.getTrackClusterIndices().size())));
624
625 tf.resetTimeFrame();
626
627 BOOST_CHECK(tf.getGenericTracks().empty());
628 BOOST_CHECK(tf.getTrackLabels().empty());
629 BOOST_CHECK(tf.getTrackClusterIndices().empty());
630
631 // Reload after wipe: both containers accept new content independently of
632 // whatever they held before, confirming they are ordinary per-event state
633 // rather than something resetTimeFrame() leaves in a half-cleared condition.
634 tf.getTrackClusterIndices().push_back(TrackClusterReference{LayerId{2}, 0, 0});
635 GenericTrack reloaded{};
636 reloaded.firstClusterRef = 0;
637 reloaded.clusterRefEnd = 1;
638 tf.getGenericTracks().push_back(reloaded);
639 BOOST_CHECK_EQUAL(tf.getGenericTracks().size(), 1u);
640 BOOST_CHECK_EQUAL(tf.getTrackClusterIndices().size(), 1u);
641}
642
643BOOST_AUTO_TEST_CASE(TrackPublicationTimestampIsSymmetricAndClamped)
644{
645 o2::its::LayerTiming clock{};
646 clock.mROFLength = 14;
647 const auto timestamp = makeOutputTimestamp({110.f, 10.f}, clock);
648 BOOST_CHECK_EQUAL(timestamp.getTimeStamp(), 110.f);
649 BOOST_CHECK_EQUAL(timestamp.getTimeStampError(), 7.f);
650}
651
652BOOST_AUTO_TEST_CASE(TrackPublicationPreservesLegacyTimestampPrecisionAndRange)
653{
654 o2::its::LayerTiming clock{.mNROFsTF = 2, .mROFLength = 100000};
655 for (const auto& interval : std::array<o2::its::TimeEstBC, 2>{{{5, 9}, {0, 100000}}}) {
656 const auto expected = interval.makeSymmetrical();
657 const auto actual = makeOutputTimestamp(expected, clock);
658 BOOST_CHECK_EQUAL(actual.getTimeStamp(), expected.getTimeStamp());
659 BOOST_CHECK_EQUAL(actual.getTimeStampError(), expected.getTimeStampError());
660 }
661}
662
663BOOST_AUTO_TEST_CASE(TrackPublicationUsesLegacyPublicationOrder)
664{
665 TimeFrameFixture fixture;
666 BOOST_REQUIRE_NO_THROW(fixture.load());
667
668 auto later = makeTestGenericTrack();
669 later.track.timestamp = {220.f, 20.f};
670 later.track.chi2 = 1.f;
671 auto earlier = makeTestGenericTrack();
672 earlier.track.timestamp = {120.f, 20.f};
673 earlier.track.chi2 = 2.f;
674 BOOST_CHECK_EQUAL(storeTestGenericTrack(fixture.tf, later), 0u);
675 BOOST_CHECK_EQUAL(storeTestGenericTrack(fixture.tf, earlier), 1u);
676
677 o2::its::LayerTiming clock{};
678 clock.mROFLength = 40;
679 const std::vector<uint32_t> selection{0u, 1u};
680 const auto ordered = makeLegacyOutputOrder(fixture.tf, selection, clock);
681 BOOST_REQUIRE(ordered);
682 BOOST_REQUIRE_EQUAL(ordered->size(), 2u);
683 BOOST_CHECK_EQUAL((*ordered)[0], 1u);
684 BOOST_CHECK_EQUAL((*ordered)[1], 0u);
685}
686
687BOOST_AUTO_TEST_CASE(TrackPublicationOrderUsesClampedTimesAndChi2WithoutChangingTracks)
688{
689 TimeFrame frame;
690 frame.getGenericTracks().resize(3);
691 // Unclamped lower edges would put track 0 first. The clock clamp makes
692 // track 1 earlier and gives tracks 0 and 2 equal lower edges.
693 frame.getGenericTracks()[0].timestamp = {110.f, 30.f};
694 frame.getGenericTracks()[0].chi2 = 2.f;
695 frame.getGenericTracks()[1].timestamp = {100.f, 5.f};
696 frame.getGenericTracks()[2].timestamp = {105.f, 5.f};
697 frame.getGenericTracks()[2].chi2 = 1.f;
698 const o2::its::LayerTiming clock{.mROFLength = 20};
699 const auto ordered = makeLegacyOutputOrder(frame, {0, 1, 2}, clock);
700 BOOST_REQUIRE(ordered);
701 const std::vector<uint32_t> expected{1, 2, 0};
702 BOOST_CHECK_EQUAL_COLLECTIONS(ordered->begin(), ordered->end(), expected.begin(), expected.end());
703 BOOST_CHECK_EQUAL(frame.getGenericTracks()[0].timestamp.getTimeStampError(), 30.f);
704 BOOST_CHECK_EQUAL(makeOutputTimestamp(frame.getGenericTracks()[0].timestamp, clock).getTimeStampError(), 10.f);
705
706 for (const auto& timestamp : std::array<o2::its::TimeStamp, 4>{{{0.f, 0.f}, {1.f, -1.f}, {std::numeric_limits<float>::infinity(), 1.f}, {1.f, std::numeric_limits<float>::quiet_NaN()}}}) {
707 frame.getGenericTracks()[0].timestamp = timestamp;
708 BOOST_CHECK(!makeLegacyOutputOrder(frame, {0}, clock));
709 }
710}
711
712BOOST_AUTO_TEST_CASE(TrackPublicationUsesLegacyClockSemantics)
713{
714 for (const uint32_t length : {9u, 10u}) {
715 o2::its::LayerTiming legacy{};
716 legacy.mNROFsTF = 4;
717 legacy.mROFLength = length;
718 legacy.mROFDelay = 3;
719 legacy.mROFBias = 2;
720 const std::array<o2::its::TimeEstBC, 4> intervals{{{5, 1}, {5, length}, {5 + length, length}, {5 + 3 * length, length}}};
721 for (const auto& interval : intervals) {
722 const auto timestamp = interval.makeSymmetrical();
723 auto expected = timestamp;
724 if (expected.getTimeStampError() > legacy.mROFLength * .5f) {
725 expected.setTimeStampError(legacy.mROFLength * .5f);
726 }
727 const auto actual = makeOutputTimestamp(timestamp, legacy);
728 BOOST_CHECK_EQUAL(actual.getTimeStamp(), expected.getTimeStamp());
729 BOOST_CHECK_EQUAL(actual.getTimeStampError(), expected.getTimeStampError());
730 BOOST_CHECK_EQUAL(legacy.getROF(actual), legacy.getROF(expected));
731 }
732 }
733}
734
735BOOST_AUTO_TEST_CASE(TrackPublicationRejectsMalformedReferences)
736{
737 TimeFrameFixture fixture;
738 BOOST_REQUIRE_NO_THROW(fixture.load());
739 const auto record = makeTestGenericTrack();
740 storeTestGenericTrack(fixture.tf, record);
741 const auto surfaces = gsl::span<const LayerId>{fixture.layerMapping};
742 const auto selected = selectGenericTracksForSurfaces(fixture.tf, surfaces);
743 BOOST_REQUIRE(selected);
744 BOOST_REQUIRE_EQUAL(selected->size(), 1u);
745 BOOST_CHECK_EQUAL((*selected)[0], 0u);
746 const std::array<LayerId, 1> foreignSurfaces{LayerId{3}};
747 const auto foreign = selectGenericTracksForSurfaces(fixture.tf, foreignSurfaces);
748 BOOST_REQUIRE(foreign);
749 BOOST_CHECK(foreign->empty());
750 const auto refs = fixture.tf.getTrackClusterIndices().size();
751 fixture.tf.getGenericTracks()[0].clusterRefEnd = refs + 1;
752 BOOST_CHECK(!selectGenericTracksForSurfaces(fixture.tf, surfaces));
753 fixture.tf.getGenericTracks()[0].clusterRefEnd = refs;
754 fixture.tf.getTrackClusterIndices()[0].layer = LayerId::invalid();
755 BOOST_CHECK(!selectGenericTracksForSurfaces(fixture.tf, surfaces));
756 BOOST_CHECK_EQUAL(fixture.tf.getGenericTracks().size(), 1u);
757 BOOST_CHECK_EQUAL(fixture.tf.getTrackClusterIndices().size(), refs);
758}
759
760BOOST_AUTO_TEST_CASE(TrackPublicationROFsPreserveInputMetadataAndIgnoreOutOfRangeTimes)
761{
762 o2::its::LayerTiming clock{};
763 clock.mNROFsTF = 2;
764 clock.mROFLength = 40;
765 clock.mROFDelay = 100;
766 const std::vector<ROFRecord> input{{{100, 5}, 0, 7, 3}, {{100, 6}, 1, 2, 3}};
767 auto output = input;
768 const TrackPublicationTimingContext context{input, clock};
769 const std::vector<o2::its::TimeStamp> times{{110.f, 0.f}, {120.f, 0.f}, {150.f, 0.f}, {1000.f, 0.f}};
770 finalizeROFs(output, times, context);
771 BOOST_REQUIRE_EQUAL(output.size(), 2u);
772 BOOST_CHECK_EQUAL(output[0].getFirstEntry(), 0);
773 BOOST_CHECK_EQUAL(output[0].getNEntries(), 2);
774 BOOST_CHECK_EQUAL(output[1].getFirstEntry(), 2);
775 BOOST_CHECK_EQUAL(output[1].getNEntries(), 1);
776 for (size_t i = 0; i < input.size(); ++i) {
777 BOOST_CHECK(output[i].getBCData() == input[i].getBCData());
778 BOOST_CHECK_EQUAL(output[i].getFlags(), input[i].getFlags());
779 BOOST_CHECK_EQUAL(input[i].getNEntries(), 3);
780 }
781}
header::DataOrigin origin
std::vector< unsigned long > times
Definition of the ITSMFT compact cluster.
Definition of the ClusterTopology class.
Passive common TimeFrame owner.
int32_t i
Definition of the ITSMFT ROFrame (trigger) record.
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
Definition of a container to keep Monte Carlo truth external to simulation objects.
Runtime-plan-owned, detector-neutral CA workspace.
int nClusters
int ID
Detector identifiers: continuous, starting from 0.
Definition DetID.h:63
UShort_t getSensorID() const
UShort_t getRow() const
Definition CompCluster.h:57
UShort_t getCol() const
Definition CompCluster.h:58
static constexpr unsigned short InvalidPatternID
Definition CompCluster.h:46
SurfaceCatalogView getSurfaceCatalog() const noexcept
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint index
Definition glcorearb.h:781
GLint reference
Definition glcorearb.h:5487
GLuint GLsizei GLsizei * length
Definition glcorearb.h:790
GLsizei GLenum * sources
Definition glcorearb.h:2516
uint8_t itsSharedClusterMap uint8_t
constexpr std::array< std::string_view, BasicOp::Conditional+1 > mapping
a map between BasicOp and tokens in string expressions
const bool const int nLayers
const bool const int TrackITSInternal< NLayers > & track
ClusterData< T > extractClusterData(const CompClusterExt &c, gsl::span< const unsigned char >::iterator &patterns, const TopologyDictionary *dict)
Definition IOUtils.h:81
std::optional< std::vector< uint32_t > > selectGenericTracksForSurfaces(const TimeFrame &frame, gsl::span< const LayerId > sourceSurfaces)
uint32_t trackClusterIndicesSize noexcept
o2::its::TimeStamp makeOutputTimestamp(o2::its::TimeStamp timestamp, const o2::its::LayerTiming &clock) noexcept
constexpr int ITSNLayers
ITS CA layer count.
void finalizeROFs(std::vector< o2::itsmft::ROFRecord > &rofs, const std::vector< o2::its::TimeStamp > &times, const TrackPublicationTimingContext &context)
std::optional< std::vector< uint32_t > > makeLegacyOutputOrder(const TimeFrame &frame, std::vector< uint32_t > selection, const o2::its::LayerTiming &clock)
BOOST_AUTO_TEST_CASE(Cluster_messageable)
std::unique_ptr< GPUReconstructionTimeframe > tf
bool isConfigured() const noexcept
Definition TimeFrame.h:156
const SurfaceMeasurement * getSurfaceMeasurement(LayerId layer, uint32_t clusterId) const noexcept
Definition TimeFrame.cxx:79
gsl::span< const GlobalMeasurement > getGlobalMeasurements(LayerId surface) const
Definition TimeFrame.cxx:46
bool configure(DetectorConfiguration &&layout, std::size_t maxEdges, std::size_t maxCells, std::shared_ptr< BoundedMemoryResource > memoryPool)
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())