Project
Loading...
Searching...
No Matches
testMultiSourceLoading.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 MultiSourceLoading
13#define BOOST_TEST_MAIN
14#define BOOST_TEST_DYN_LINK
15#include <boost/test/unit_test.hpp>
16
17#include <limits>
18#include <cmath>
19#include <memory>
20#include <vector>
21
22#include <gsl/gsl>
23
34
35using namespace o2::itsmft;
36using namespace o2::itsmft::tracking;
37
38namespace
39{
40
41// Host-only test decoder (no geometry singletons): maps a chip ID to a
42// detector-local layer via an explicit table, and reuses the same pattern
43// consumption path (extractClusterData) that the production decoder uses,
44// so pattern-cursor bookkeeping is exercised identically.
45enum class Corruption {
46 None,
47 NegativeLayer,
48 LayerOutOfRange
49};
50
51class FakeClusterDecoder
52{
53 public:
54 FakeClusterDecoder(o2::detectors::DetID::ID detector, std::vector<int> sensorToLayer, bool disk, Corruption corruption = Corruption::None)
55 : mDetector(detector), mSensorToLayer(std::move(sensorToLayer)), mDisk(disk), mCorruption(corruption)
56 {
57 }
58
60 const CompClusterExt& cluster,
61 gsl::span<const unsigned char>::iterator& patterns,
62 const TopologyDictionary* dict,
63 uint32_t) const
64 {
65 if (mCorruption == Corruption::NegativeLayer) {
67 result.layer = -1;
68 return result;
69 }
70 if (mCorruption == Corruption::LayerOutOfRange) {
72 result.layer = std::numeric_limits<int>::max();
73 return result;
74 }
75
76 const auto clusterData = o2::itsmft::ioutils::extractClusterData(cluster, patterns, dict);
78 const auto sensorID = cluster.getSensorID();
79 const int layer = (sensorID >= 0 && static_cast<size_t>(sensorID) < mSensorToLayer.size()) ? mSensorToLayer[sensorID] : -1;
80 auto& decoded = result;
81 decoded.global = {static_cast<float>(sensorID), static_cast<float>(cluster.getRow()), static_cast<float>(cluster.getCol())};
82 decoded.cylinderFrame = {10.f + sensorID, 1.f, 2.f, 0.1f};
83 decoded.rowColumnCovariance = {clusterData.sig2Row, 0.f, clusterData.sig2Col};
84 decoded.nPixels = clusterData.nPixels;
85 decoded.layer = layer;
86 return result;
87 }
88
89 private:
91 std::vector<int> mSensorToLayer;
92 bool mDisk;
93 Corruption mCorruption;
94};
95
96// Geometry-free decoder used only to exercise the normalized loader's
97// dictionary/common/group/explicit pattern contract. Pattern ID 0 represents
98// a common dictionary entry (no explicit bytes), pattern ID 1 represents a
99// grouped dictionary entry (explicit bytes required), and InvalidPatternID
100// represents an ordinary explicit pattern.
101class PatternContractDecoder
102{
103 public:
105 const CompClusterExt& cluster,
106 gsl::span<const unsigned char>::iterator& patterns,
107 const TopologyDictionary* dictionary,
108 uint32_t) const
109 {
111 if (dictionary == nullptr) {
112 throw std::runtime_error("Cluster dictionary is not available");
113 }
114 uint32_t nPixels = 1;
115 if (cluster.getPatternID() != 0) {
116 const o2::itsmft::ClusterPattern pattern{patterns};
117 nPixels = static_cast<uint32_t>(pattern.getNPixels());
118 }
119
120 auto& decoded = result;
121 decoded.global = {1.f, 2.f, 3.f};
122 decoded.cylinderFrame = {4.f, 5.f, 6.f, 0.f};
123 decoded.rowColumnCovariance = {0.1f, 0.f, 0.2f};
124 decoded.nPixels = nPixels;
125 decoded.layer = 0;
126 return result;
127 }
128};
129
130struct BuiltLayout {
132 std::vector<SurfaceDescriptor> surfaces;
133
134 bool valid() const noexcept { return layout.valid(); }
135 SurfaceCatalogView getCatalog() const noexcept
136 {
137 return layout.getSurfaceCatalog();
138 }
139};
140
141// 4-surface disconnected ITS(cylinder)+MFT(disk) layout: surfaces {0,1} are
142// ITS layers 0/1, surfaces {2,3} are MFT layers 0/1. No edges are
143// needed to exercise loading.
144BuiltLayout makeCombinedLayout()
145{
146 std::vector<SurfaceDescriptor> surfaces;
147 surfaces.push_back(SurfaceDescriptor{0, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
148 surfaces.push_back(SurfaceDescriptor{1, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
149 surfaces.push_back(SurfaceDescriptor{0, static_cast<uint8_t>(o2::detectors::DetID::MFT), SurfaceKind::Disk});
150 surfaces.push_back(SurfaceDescriptor{1, static_cast<uint8_t>(o2::detectors::DetID::MFT), SurfaceKind::Disk});
151 const std::vector<uint16_t> componentOffsets = {0, 2};
152 return BuiltLayout{DetectorConfiguration{surfaces, componentOffsets}, std::move(surfaces)};
153}
154
155void configureFrame(TimeFrame& frame, const BuiltLayout& built)
156{
157 BOOST_REQUIRE(frame.configure(DetectorConfiguration{built.layout},
158 0, 0, std::make_shared<BoundedMemoryResource>()));
159}
160
161// One explicit (non-grouped) 1-pixel pattern: rowSpan=1, colSpan=1, one
162// bitmap byte. Three bytes are consumed per cluster.
163constexpr std::array<unsigned char, 3> onePixelPattern{1, 1, 0x80};
164
165std::vector<unsigned char> makePatternBytes(size_t nClusters)
166{
167 std::vector<unsigned char> bytes;
168 bytes.reserve(nClusters * onePixelPattern.size());
169 for (size_t i = 0; i < nClusters; ++i) {
170 bytes.insert(bytes.end(), onePixelPattern.begin(), onePixelPattern.end());
171 }
172 return bytes;
173}
174
175const TopologyDictionary& dict()
176{
177 static const TopologyDictionary d;
178 return d;
179}
180
181const std::array<LayerId, 2> itsLayerToSurface{LayerId{0}, LayerId{1}};
182const std::array<LayerId, 2> mftLayerToSurface{LayerId{2}, LayerId{3}};
183const std::array<LayerId, 1> firstITSSurface{LayerId{0}};
184const std::array<LayerId, 1> secondITSSurface{LayerId{1}};
185const std::array<LayerId, 1> firstMFTSurface{LayerId{2}};
186
187} // namespace
188
189BOOST_AUTO_TEST_CASE(SingleITSSourceLoadsIntoExpectedSurfaces)
190{
191 const auto layout = makeCombinedLayout();
192 BOOST_REQUIRE(layout.valid());
193
194 const std::vector<CompClusterExt> clusters{
195 {10, 20, CompCluster::InvalidPatternID, 0}, // sensor 0 -> layer 0
196 {11, 21, CompCluster::InvalidPatternID, 1}, // sensor 1 -> layer 1
197 };
198 const auto patterns = makePatternBytes(clusters.size());
199 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 2}};
200
201 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0, 1}, false};
204 src.detector = o2::detectors::DetID::ITS;
205 src.clusters = clusters;
206 src.patterns = patterns;
207 src.rofs = rofs;
208 src.dictionary = &dict();
209 src.layerToSurface = itsLayerToSurface;
210 src.timing = o2::its::LayerTiming{.mROFLength = 40};
211 src.setDecoder(decoder);
212
213 TimeFrame frame;
214 configureFrame(frame, layout);
215 std::vector<std::vector<uint32_t>> externalIndicesBySurface;
216 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0},
217 &externalIndicesBySurface));
218
219 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 1u);
220 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{1}).size(), 1u);
221 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{2}).size(), 0u);
222 BOOST_CHECK_EQUAL(externalIndicesBySurface[0][0], 0u);
223}
224
225BOOST_AUTO_TEST_CASE(SystematicErrorsUseMappedSurfacesForBothDetectorsExactlyOnce)
226{
227 auto layout = makeCombinedLayout();
228 layout.layout.systError2Row = {0.01f, 0.f, 0.03f, 0.f};
229 layout.layout.systError2Col = {0.f, 0.02f, 0.f, 0.04f};
230 TimeFrame frame;
231 configureFrame(frame, layout);
232
233 const std::vector<CompClusterExt> clusters{{10, 20, CompCluster::InvalidPatternID, 0},
234 {11, 21, CompCluster::InvalidPatternID, 1}};
235 const auto patterns = makePatternBytes(clusters.size());
236 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 2}};
237 FakeClusterDecoder itsDecoder{o2::detectors::DetID::ITS, {0, 1}, false};
238 FakeClusterDecoder mftDecoder{o2::detectors::DetID::MFT, {0, 1}, true};
239 // Reverse each source's mapping so local-layer indexing cannot pass by accident.
240 const std::array<LayerId, 2> itsMapping{LayerId{1}, LayerId{0}};
241 const std::array<LayerId, 2> mftMapping{LayerId{3}, LayerId{2}};
242 std::array<test::TestClusterSourceInput, 2> sources;
243 for (uint16_t i = 0; i < sources.size(); ++i) {
244 auto& source = sources[i];
246 source.detector = i == 0 ? o2::detectors::DetID::ITS : o2::detectors::DetID::MFT;
247 source.clusters = clusters;
248 source.patterns = patterns;
249 source.rofs = rofs;
250 source.dictionary = &dict();
251 source.layerToSurface = i == 0 ? itsMapping : mftMapping;
252 source.timing = o2::its::LayerTiming{.mROFLength = 40};
253 source.setDecoder(i == 0 ? itsDecoder : mftDecoder);
254 }
255
256 for (int reload = 0; reload < 2; ++reload) {
257 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSources(frame, sources, layout.getCatalog(), {0, 0}));
258 for (uint16_t surface = 0; surface < 4; ++surface) {
259 const auto row = ioutils::DefClusError2Row + layout.layout.systError2Row[surface];
260 const auto col = ioutils::DefClusError2Col + layout.layout.systError2Col[surface];
261 const auto* measurement = frame.getSurfaceMeasurement(LayerId{surface}, 0);
262 BOOST_REQUIRE(measurement);
263 BOOST_CHECK_EQUAL(measurement->covariance.uu, row);
264 BOOST_CHECK_EQUAL(measurement->covariance.uv, 0.f);
265 BOOST_CHECK_EQUAL(measurement->covariance.vv, col);
266 const auto globals = frame.getGlobalMeasurements(LayerId{surface});
267 BOOST_REQUIRE_EQUAL(globals.size(), 1u);
268 const auto& covariance = globals.front().covariance;
269 if (surface < 2) {
270 const float sine = std::sin(0.1f), cosine = std::cos(0.1f);
271 BOOST_CHECK_CLOSE(covariance[GlobalMeasurement::XX], sine * sine * row, 1.e-4f);
272 BOOST_CHECK_CLOSE(covariance[GlobalMeasurement::XY], -sine * cosine * row, 1.e-4f);
273 BOOST_CHECK_CLOSE(covariance[GlobalMeasurement::YY], cosine * cosine * row, 1.e-4f);
275 } else {
279 }
280 }
281 }
282}
283
284BOOST_AUTO_TEST_CASE(ClusterLoadingDoesNotRequireTiming)
285{
286 // Decoding and ROF cluster boundaries do not require a timing configuration.
287 const auto layout = makeCombinedLayout();
288 BOOST_REQUIRE(layout.valid());
289
290 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
291 const auto patterns = makePatternBytes(clusters.size());
292 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
293
294 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
297 src.detector = o2::detectors::DetID::ITS;
298 src.clusters = clusters;
299 src.patterns = patterns;
300 src.rofs = rofs;
301 src.dictionary = &dict();
302 src.layerToSurface = itsLayerToSurface;
303 src.setDecoder(decoder);
304
305 TimeFrame frame;
306 configureFrame(frame, layout);
307 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}));
309 BOOST_CHECK_EQUAL(frame.getClusterROF(0, 0), 0);
310}
311
312BOOST_AUTO_TEST_CASE(SingleMFTSourceLoadsIntoExpectedSurfaces)
313{
314 const auto layout = makeCombinedLayout();
315 BOOST_REQUIRE(layout.valid());
316
317 const std::vector<CompClusterExt> clusters{
320 };
321 const auto patterns = makePatternBytes(clusters.size());
322 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 2}};
323
324 FakeClusterDecoder decoder{o2::detectors::DetID::MFT, {0, 1}, true};
327 src.detector = o2::detectors::DetID::MFT;
328 src.clusters = clusters;
329 src.patterns = patterns;
330 src.rofs = rofs;
331 src.dictionary = &dict();
332 src.layerToSurface = mftLayerToSurface;
333 src.timing = o2::its::LayerTiming{.mROFLength = 40};
334 src.setDecoder(decoder);
335
336 TimeFrame frame;
337 configureFrame(frame, layout);
338 std::vector<std::vector<uint32_t>> externalIndicesBySurface;
339 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0},
340 &externalIndicesBySurface));
341
342 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{2}).size(), 1u);
343 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{3}).size(), 1u);
344 BOOST_CHECK_EQUAL(externalIndicesBySurface[2][0], 0u);
345}
346
347BOOST_AUTO_TEST_CASE(CombinedITSAndMFTSourcesLoadTogether)
348{
349 const auto layout = makeCombinedLayout();
350 BOOST_REQUIRE(layout.valid());
351
352 const std::vector<CompClusterExt> itsClusters{{1, 1, CompCluster::InvalidPatternID, 0},
355 const auto itsPatterns = makePatternBytes(itsClusters.size());
356 const std::vector<ROFRecord> itsRofs{ROFRecord{{0, 0}, 0, 0, 2},
357 ROFRecord{{40, 0}, 1, 2, 0},
358 ROFRecord{{80, 0}, 2, 2, 1}};
359 FakeClusterDecoder itsDecoder{o2::detectors::DetID::ITS, {0, 1}, false};
360
361 const std::vector<CompClusterExt> mftClusters{{2, 2, CompCluster::InvalidPatternID, 0}};
362 const auto mftPatterns = makePatternBytes(mftClusters.size());
363 const std::vector<ROFRecord> mftRofs{ROFRecord{{0, 0}, 0, 0, 1}};
364 FakeClusterDecoder mftDecoder{o2::detectors::DetID::MFT, {1}, true}; // sensor 0 -> layer 1 -> surface 2
365
366 const std::array<LayerId, 2> itsMapping{LayerId{1}, LayerId{0}};
367 const std::array<LayerId, 2> mftMapping{LayerId{3}, LayerId{2}};
368 std::array<test::TestClusterSourceInput, 2> sources{};
369 sources[0].id = ClusterSourceId{0};
370 sources[0].detector = o2::detectors::DetID::ITS;
371 sources[0].clusters = itsClusters;
372 sources[0].patterns = itsPatterns;
373 sources[0].rofs = itsRofs;
374 sources[0].dictionary = &dict();
375 sources[0].layerToSurface = itsMapping;
376 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
377 sources[0].setDecoder(itsDecoder);
378
379 sources[1].id = ClusterSourceId{1};
380 sources[1].detector = o2::detectors::DetID::MFT;
381 sources[1].clusters = mftClusters;
382 sources[1].patterns = mftPatterns;
383 sources[1].rofs = mftRofs;
384 sources[1].dictionary = &dict();
385 sources[1].layerToSurface = mftMapping;
386 sources[1].timing = o2::its::LayerTiming{.mROFLength = 50};
387 sources[1].setDecoder(mftDecoder);
388
389 TimeFrame frame;
390 configureFrame(frame, layout);
391 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSources(frame, sources, layout.getCatalog(), {0, 0}));
392
393 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 1u);
394 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{1}).size(), 2u);
395 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{2}).size(), 1u);
396 const std::array<std::vector<int>, 4> expectedBoundaries{{{0, 1, 1, 1}, {0, 1, 1, 2}, {0, 1}, {0, 0}}};
397 for (int surface = 0; surface < 4; ++surface) {
398 const auto boundaries = frame.getROFrameClusters(surface);
399 BOOST_CHECK_EQUAL_COLLECTIONS(boundaries.begin(), boundaries.end(), expectedBoundaries[surface].begin(), expectedBoundaries[surface].end());
400 BOOST_CHECK_EQUAL(frame.getROFLocalLayer(surface), 1 - surface % 2);
401 }
402
403 // A missing mapped layer is rejected before any source is decoded.
404 sources[1].layerToSurface = firstMFTSurface;
405 BOOST_CHECK_THROW(test::loadTimeFrameSources(frame, sources, layout.getCatalog(), {0, 0}), std::runtime_error);
407}
408
409BOOST_AUTO_TEST_CASE(TwoSourcesCannotOwnTheSameSurface)
410{
411 const auto layout = makeCombinedLayout();
412 BOOST_REQUIRE(layout.valid());
413
414 const std::vector<CompClusterExt> clustersA{{1, 1, CompCluster::InvalidPatternID, 0}};
415 const std::vector<CompClusterExt> clustersB{{2, 2, CompCluster::InvalidPatternID, 0}};
416 const auto patternsA = makePatternBytes(clustersA.size());
417 const auto patternsB = makePatternBytes(clustersB.size());
418 const std::vector<ROFRecord> rofsA{ROFRecord{{0, 0}, 0, 0, 1}};
419 const std::vector<ROFRecord> rofsB{ROFRecord{{0, 0}, 0, 0, 1}};
420
421 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
422 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
423
424 std::array<test::TestClusterSourceInput, 2> sources{};
425 sources[0].id = ClusterSourceId{0};
426 sources[0].detector = o2::detectors::DetID::ITS;
427 sources[0].clusters = clustersA;
428 sources[0].patterns = patternsA;
429 sources[0].rofs = rofsA;
430 sources[0].dictionary = &dict();
431 sources[0].layerToSurface = itsLayerToSurface;
432 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
433 sources[0].setDecoder(decoderA);
434
435 sources[1].id = ClusterSourceId{1};
436 sources[1].detector = o2::detectors::DetID::ITS;
437 sources[1].clusters = clustersB;
438 sources[1].patterns = patternsB;
439 sources[1].rofs = rofsB;
440 sources[1].dictionary = &dict();
441 sources[1].layerToSurface = itsLayerToSurface;
442 sources[1].timing = o2::its::LayerTiming{.mROFLength = 40};
443 sources[1].setDecoder(decoderB);
444
445 TimeFrame frame;
446 configureFrame(frame, layout);
447 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid source-to-surface layer mapping") != std::string::npos; });
449}
450
451BOOST_AUTO_TEST_CASE(IdenticalExternalIndicesInDifferentSourcesDoNotCollide)
452{
453 const auto layout = makeCombinedLayout();
454 BOOST_REQUIRE(layout.valid());
455
456 const std::vector<CompClusterExt> clustersA{{1, 1, CompCluster::InvalidPatternID, 0}}; // external index 0
457 const std::vector<CompClusterExt> clustersB{{2, 2, CompCluster::InvalidPatternID, 0}}; // external index 0 too
458 const auto patternsA = makePatternBytes(clustersA.size());
459 const auto patternsB = makePatternBytes(clustersB.size());
460 const std::vector<ROFRecord> rofsA{ROFRecord{{0, 0}, 0, 0, 1}};
461 const std::vector<ROFRecord> rofsB{ROFRecord{{0, 0}, 0, 0, 1}};
462
464 labelsA.addElement(0, o2::MCCompLabel{1, 0, 0});
466 labelsB.addElement(0, o2::MCCompLabel{2, 0, 0});
467
468 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
469 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
470
471 std::array<test::TestClusterSourceInput, 2> sources{};
472 sources[0].id = ClusterSourceId{0};
473 sources[0].detector = o2::detectors::DetID::ITS;
474 sources[0].clusters = clustersA;
475 sources[0].patterns = patternsA;
476 sources[0].rofs = rofsA;
477 sources[0].dictionary = &dict();
478 sources[0].labels = &labelsA;
479 sources[0].layerToSurface = firstITSSurface;
480 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
481 sources[0].setDecoder(decoderA);
482
483 sources[1].id = ClusterSourceId{1};
484 sources[1].detector = o2::detectors::DetID::ITS;
485 sources[1].clusters = clustersB;
486 sources[1].patterns = patternsB;
487 sources[1].rofs = rofsB;
488 sources[1].dictionary = &dict();
489 sources[1].labels = &labelsB;
490 sources[1].layerToSurface = secondITSSurface;
491 sources[1].timing = o2::its::LayerTiming{.mROFLength = 40};
492 sources[1].setDecoder(decoderB);
493
494 TimeFrame frame;
495 configureFrame(frame, layout);
496 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}));
497
498 const auto onSurfaceZero = frame.getGlobalMeasurements(LayerId{0});
499 BOOST_REQUIRE_EQUAL(onSurfaceZero.size(), 1u);
500 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{1}).size(), 1u);
501 BOOST_CHECK_EQUAL(onSurfaceZero[0].clusterId, 0u);
502 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{1})[0].clusterId, 0u);
503
504 const auto labelSpanA = frame.getLabels(LayerId{0}, 0);
505 const auto labelSpanB = frame.getLabels(LayerId{1}, 0);
506 BOOST_REQUIRE_EQUAL(labelSpanA.size(), 1u);
507 BOOST_REQUIRE_EQUAL(labelSpanB.size(), 1u);
508 BOOST_CHECK(labelSpanA[0] != labelSpanB[0]);
509}
510
511BOOST_AUTO_TEST_CASE(OriginalClusterIdResolvesLabelsAndCompactGlobal)
512{
513 const auto layout = makeCombinedLayout();
514 BOOST_REQUIRE(layout.valid());
515
516 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
517 const auto patterns = makePatternBytes(clusters.size());
518 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
519
521 labels.addElement(0, o2::MCCompLabel{1, 0, 0});
522
523 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
526 src.detector = o2::detectors::DetID::ITS;
527 src.clusters = clusters;
528 src.patterns = patterns;
529 src.rofs = rofs;
530 src.dictionary = &dict();
531 src.labels = &labels;
532 src.layerToSurface = itsLayerToSurface;
533 src.timing = o2::its::LayerTiming{.mROFLength = 40};
534 src.setDecoder(decoder);
535
536 TimeFrame frame;
537 configureFrame(frame, layout);
538 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}));
539
540 constexpr uint32_t clusterId = 0;
541 const auto labelPlain = frame.getLabels(LayerId{0}, clusterId);
542 BOOST_REQUIRE_EQUAL(labelPlain.size(), 1u);
543
544 // The sorted global value carries only the stable source-local ID.
545 const auto measurement = frame.getGlobalMeasurements(LayerId{0})[0];
546 BOOST_CHECK_EQUAL(measurement.clusterId, clusterId);
547}
548
549BOOST_AUTO_TEST_CASE(IndependentROFCountsAcrossSourcesAreAllowed)
550{
551 const auto layout = makeCombinedLayout();
552 BOOST_REQUIRE(layout.valid());
553
554 // Source A: 3 ROFs of 1 cluster each. Source B: 1 ROF of 1 cluster.
555 const std::vector<CompClusterExt> clustersA{
559 const auto patternsA = makePatternBytes(clustersA.size());
560 const std::vector<ROFRecord> rofsA{
561 ROFRecord{{0, 0}, 0, 0, 1},
562 ROFRecord{{40, 0}, 1, 1, 1},
563 ROFRecord{{80, 0}, 2, 2, 1}};
564
565 const std::vector<CompClusterExt> clustersB{{4, 4, CompCluster::InvalidPatternID, 0}};
566 const auto patternsB = makePatternBytes(clustersB.size());
567 const std::vector<ROFRecord> rofsB{ROFRecord{{0, 0}, 0, 0, 1}};
568
569 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
570 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
571
572 std::array<test::TestClusterSourceInput, 2> sources{};
573 sources[0].id = ClusterSourceId{0};
574 sources[0].detector = o2::detectors::DetID::ITS;
575 sources[0].clusters = clustersA;
576 sources[0].patterns = patternsA;
577 sources[0].rofs = rofsA;
578 sources[0].dictionary = &dict();
579 sources[0].layerToSurface = firstITSSurface;
580 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
581 sources[0].setDecoder(decoderA);
582
583 sources[1].id = ClusterSourceId{1};
584 sources[1].detector = o2::detectors::DetID::ITS;
585 sources[1].clusters = clustersB;
586 sources[1].patterns = patternsB;
587 sources[1].rofs = rofsB;
588 sources[1].dictionary = &dict();
589 sources[1].layerToSurface = secondITSSurface;
590 sources[1].timing = o2::its::LayerTiming{.mROFLength = 100};
591 sources[1].setDecoder(decoderB);
592
593 TimeFrame frame;
594 configureFrame(frame, layout);
595 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}));
596
598 BOOST_CHECK_EQUAL(frame.getNrof(0), 3);
599 BOOST_CHECK_EQUAL(frame.getNrof(1), 1);
600}
601
602BOOST_AUTO_TEST_CASE(OverlappingAndNonOverlappingSourceTimingIntervals)
603{
604 const auto layout = makeCombinedLayout();
605 BOOST_REQUIRE(layout.valid());
606
607 const std::vector<CompClusterExt> clustersA{{1, 1, CompCluster::InvalidPatternID, 0}};
608 const std::vector<CompClusterExt> clustersB{{2, 2, CompCluster::InvalidPatternID, 0}};
609 const auto patternsA = makePatternBytes(clustersA.size());
610 const auto patternsB = makePatternBytes(clustersB.size());
611 // Source A ROF at BC 0..40 (TF-relative); source B ROF at real BC 30 -> its
612 // own interval overlaps A's despite a different, unrelated ROF ordinal.
613 const std::vector<ROFRecord> rofsA{ROFRecord{{0, 0}, 0, 0, 1}};
614 const std::vector<ROFRecord> rofsB{ROFRecord{{30, 0}, 0, 0, 1}};
615 // Source C ROF at real BC 1000: far away, must not overlap A.
616 const std::vector<CompClusterExt> clustersC{{3, 3, CompCluster::InvalidPatternID, 0}};
617 const auto patternsC = makePatternBytes(clustersC.size());
618 const std::vector<ROFRecord> rofsC{ROFRecord{{1000, 0}, 0, 0, 1}};
619
620 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
621 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
622 FakeClusterDecoder decoderC{o2::detectors::DetID::MFT, {0}, true};
623
624 std::array<test::TestClusterSourceInput, 3> sources{};
625 sources[0].id = ClusterSourceId{0};
626 sources[0].detector = o2::detectors::DetID::ITS;
627 sources[0].clusters = clustersA;
628 sources[0].patterns = patternsA;
629 sources[0].rofs = rofsA;
630 sources[0].dictionary = &dict();
631 sources[0].layerToSurface = firstITSSurface;
632 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
633 sources[0].setDecoder(decoderA);
634
635 sources[1].id = ClusterSourceId{1};
636 sources[1].detector = o2::detectors::DetID::ITS;
637 sources[1].clusters = clustersB;
638 sources[1].patterns = patternsB;
639 sources[1].rofs = rofsB;
640 sources[1].dictionary = &dict();
641 sources[1].layerToSurface = secondITSSurface;
642 sources[1].timing = o2::its::LayerTiming{.mROFLength = 40};
643 sources[1].setDecoder(decoderB);
644
645 sources[2].id = ClusterSourceId{2};
646 sources[2].detector = o2::detectors::DetID::MFT;
647 sources[2].clusters = clustersC;
648 sources[2].patterns = patternsC;
649 sources[2].rofs = rofsC;
650 sources[2].dictionary = &dict();
651 sources[2].layerToSurface = firstMFTSurface;
652 sources[2].timing = o2::its::LayerTiming{.mROFLength = 40};
653 sources[2].setDecoder(decoderC);
654
655 TimeFrame frame;
656 configureFrame(frame, layout);
657 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}));
658
660}
661
662BOOST_AUTO_TEST_CASE(TriggeredAndContinuousReadoutAreBothSupportedTogether)
663{
664 // Continuous source: ROFs sit at a fixed cadence equal to the readout
665 // length, so consecutive interval begins are regularly spaced by
666 // rofLength (mirrors a periodic strobe). Triggered source: ROFs sit at
667 // sparse, irregular real interaction records (individual triggers) with a
668 // short trigger-specific window, so consecutive interval begins follow the
669 // trigger BCs exactly rather than any ordinal*rofLength formula. Both must
670 // load into the same frame and their intervals must remain independently
671 // and correctly comparable via intersection.
672 const auto layout = makeCombinedLayout();
673 BOOST_REQUIRE(layout.valid());
674
675 const std::vector<CompClusterExt> continuousClusters{
679 const auto continuousPatterns = makePatternBytes(continuousClusters.size());
680 const std::vector<ROFRecord> continuousRofs{
681 ROFRecord{{0, 0}, 0, 0, 1},
682 ROFRecord{{40, 0}, 1, 1, 1},
683 ROFRecord{{80, 0}, 2, 2, 1}};
684 constexpr uint32_t continuousRofLength = 40;
685
686 const std::vector<CompClusterExt> triggeredClusters{
690 const auto triggeredPatterns = makePatternBytes(triggeredClusters.size());
691 // Sparse, irregular trigger BCs; a short single-BC-scale trigger window.
692 const std::vector<ROFRecord> triggeredRofs{
693 ROFRecord{{5, 0}, 0, 0, 1},
694 ROFRecord{{137, 0}, 1, 1, 1},
695 ROFRecord{{812, 0}, 2, 2, 1}};
696 constexpr uint32_t triggeredRofLength = 4;
697
698 FakeClusterDecoder continuousDecoder{o2::detectors::DetID::ITS, {0}, false};
699 FakeClusterDecoder triggeredDecoder{o2::detectors::DetID::ITS, {0}, false};
700
701 std::array<test::TestClusterSourceInput, 2> sources{};
702 sources[0].id = ClusterSourceId{0};
703 sources[0].detector = o2::detectors::DetID::ITS;
704 sources[0].clusters = continuousClusters;
705 sources[0].patterns = continuousPatterns;
706 sources[0].rofs = continuousRofs;
707 sources[0].dictionary = &dict();
708 sources[0].layerToSurface = firstITSSurface;
709 sources[0].timing = o2::its::LayerTiming{.mROFLength = continuousRofLength};
710 sources[0].setDecoder(continuousDecoder);
711
712 sources[1].id = ClusterSourceId{1};
713 sources[1].detector = o2::detectors::DetID::ITS;
714 sources[1].clusters = triggeredClusters;
715 sources[1].patterns = triggeredPatterns;
716 sources[1].rofs = triggeredRofs;
717 sources[1].dictionary = &dict();
718 sources[1].layerToSurface = secondITSSurface;
719 sources[1].timing = o2::its::LayerTiming{.mROFLength = triggeredRofLength};
720 sources[1].setDecoder(triggeredDecoder);
721
722 TimeFrame frame;
723 configureFrame(frame, layout);
724 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}));
725
727}
728
729BOOST_AUTO_TEST_CASE(SourceSpecificPatternCursorsAreIndependent)
730{
731 const auto layout = makeCombinedLayout();
732 BOOST_REQUIRE(layout.valid());
733
734 const std::vector<CompClusterExt> clustersA{
737 const std::vector<CompClusterExt> clustersB{
740 const auto patternsA = makePatternBytes(clustersA.size());
741 const auto patternsB = makePatternBytes(clustersB.size());
742 const std::vector<ROFRecord> rofsA{ROFRecord{{0, 0}, 0, 0, 2}};
743 const std::vector<ROFRecord> rofsB{ROFRecord{{0, 0}, 0, 0, 2}};
744
745 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
746 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
747
748 std::array<test::TestClusterSourceInput, 2> sources{};
749 sources[0].id = ClusterSourceId{0};
750 sources[0].detector = o2::detectors::DetID::ITS;
751 sources[0].clusters = clustersA;
752 sources[0].patterns = patternsA;
753 sources[0].rofs = rofsA;
754 sources[0].dictionary = &dict();
755 sources[0].layerToSurface = firstITSSurface;
756 sources[0].timing = o2::its::LayerTiming{.mROFLength = 40};
757 sources[0].setDecoder(decoderA);
758
759 sources[1].id = ClusterSourceId{1};
760 sources[1].detector = o2::detectors::DetID::ITS;
761 sources[1].clusters = clustersB;
762 sources[1].patterns = patternsB;
763 sources[1].rofs = rofsB;
764 sources[1].dictionary = &dict();
765 sources[1].layerToSurface = secondITSSurface;
766 sources[1].timing = o2::its::LayerTiming{.mROFLength = 40};
767 sources[1].setDecoder(decoderB);
768
769 TimeFrame frame;
770 configureFrame(frame, layout);
771 std::vector<std::vector<uint32_t>> clusterSizesBySurface;
772 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0},
773 nullptr, &clusterSizesBySurface));
774
775 // Every cluster consumed exactly one 1-pixel pattern regardless of source.
776 for (const auto layer : {LayerId{0}, LayerId{1}}) {
777 for (const auto& m : frame.getGlobalMeasurements(layer)) {
778 BOOST_CHECK_EQUAL(clusterSizesBySurface[layer.value()][m.clusterId], 1u);
779 }
780 }
781}
782
783BOOST_AUTO_TEST_CASE(CommonDictionaryPatternDoesNotConsumeExplicitBytes)
784{
785 const auto layout = makeCombinedLayout();
786 const std::vector<CompClusterExt> clusters{
787 {1, 1, 0, 0}, // common dictionary pattern
788 {2, 2, CompCluster::InvalidPatternID, 0}}; // explicit pattern
789 const std::vector<unsigned char> patterns{onePixelPattern.begin(), onePixelPattern.end()};
790 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 2}};
791 PatternContractDecoder decoder;
792
795 src.detector = o2::detectors::DetID::ITS;
796 src.clusters = clusters;
797 src.patterns = patterns;
798 src.rofs = rofs;
799 src.dictionary = &dict();
800 src.layerToSurface = itsLayerToSurface;
801 src.timing = o2::its::LayerTiming{.mROFLength = 40};
802 src.setDecoder(decoder);
803
804 TimeFrame frame;
805 configureFrame(frame, layout);
806 std::vector<std::vector<uint32_t>> clusterSizesBySurface;
807 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0},
808 nullptr, &clusterSizesBySurface));
809 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 2u);
810 BOOST_CHECK_EQUAL(clusterSizesBySurface[0][frame.getGlobalMeasurements(LayerId{0})[0].clusterId], 1u);
811 BOOST_CHECK_EQUAL(clusterSizesBySurface[0][frame.getGlobalMeasurements(LayerId{0})[1].clusterId], 1u);
812}
813
814BOOST_AUTO_TEST_CASE(ExactPatternConsumptionSucceedsAndTrailingBytesAreRejected)
815{
816 const auto layout = makeCombinedLayout();
817 PatternContractDecoder decoder;
818 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
819 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
820
821 auto makeSource = [&](gsl::span<const unsigned char> patterns) {
824 src.detector = o2::detectors::DetID::ITS;
825 src.clusters = clusters;
826 src.patterns = patterns;
827 src.rofs = rofs;
828 src.dictionary = &dict();
829 src.layerToSurface = itsLayerToSurface;
830 src.timing = o2::its::LayerTiming{.mROFLength = 40};
831 src.setDecoder(decoder);
832 return src;
833 };
834
835 const std::vector<unsigned char> exact{onePixelPattern.begin(), onePixelPattern.end()};
836 auto exactSource = makeSource(exact);
837 TimeFrame frame;
838 configureFrame(frame, layout);
839 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&exactSource, 1), {0, 0}));
840
841 const std::vector<unsigned char> trailing{1, 1, 0x80, 0xff};
842 auto trailingSource = makeSource(trailing);
843 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&trailingSource, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Trailing cluster pattern data") != std::string::npos; });
844 frame.resetTimeFrame();
846
847 auto missingDictionarySource = makeSource(exact);
848 missingDictionarySource.dictionary = nullptr;
849 BOOST_CHECK_EXCEPTION(test::loadSources(
850 frame, layout.getCatalog(),
851 gsl::span<const test::TestClusterSourceInput>(&missingDictionarySource, 1), {0, 0}),
852 std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Cluster dictionary is not available") != std::string::npos; });
854}
855
856BOOST_AUTO_TEST_CASE(MissingDictionaryThrowsBeforeProductionGeometryDecode)
857{
858 const auto layout = makeCombinedLayout();
859 TimeFrame frame;
860 configureFrame(frame, layout);
861 const std::array<CompClusterExt, 1> clusters{{{1, 1, CompCluster::InvalidPatternID, 0}}};
863 source.id = ClusterSourceId{0};
864 source.clusters = clusters;
865 source.patterns = onePixelPattern;
866 source.layerToSurface = itsLayerToSurface;
867 BOOST_CHECK_EXCEPTION(loadTimeFrameSources(frame, gsl::span<const ClusterSourceInput>{&source, 1}, layout.getCatalog()),
868 std::runtime_error, [](const std::runtime_error& error) {
869 return std::string(error.what()).find("Cluster dictionary is not available source=0") != std::string::npos;
870 });
871}
872
873BOOST_AUTO_TEST_CASE(AbsentLabelsAreLegal)
874{
875 const auto layout = makeCombinedLayout();
876 BOOST_REQUIRE(layout.valid());
877
878 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
879 const auto patterns = makePatternBytes(clusters.size());
880 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
881
882 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
885 src.detector = o2::detectors::DetID::ITS;
886 src.clusters = clusters;
887 src.patterns = patterns;
888 src.rofs = rofs;
889 src.dictionary = &dict();
890 src.labels = nullptr; // no MC labels for this source
891 src.layerToSurface = itsLayerToSurface;
892 src.timing = o2::its::LayerTiming{.mROFLength = 40};
893 src.setDecoder(decoder);
894
895 TimeFrame frame;
896 configureFrame(frame, layout);
897 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}));
898
899 BOOST_CHECK(frame.getLabels(LayerId{0}, 0).empty());
900 BOOST_CHECK(frame.getLabels(LayerId{}, 0).empty());
901}
902
903BOOST_AUTO_TEST_CASE(NonDenseAndDuplicateAndInvalidSourceIdsAreRejected)
904{
905 const auto layout = makeCombinedLayout();
906 BOOST_REQUIRE(layout.valid());
907 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
908 const auto patterns = makePatternBytes(clusters.size());
909 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
910 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
911 FakeClusterDecoder decoderB{o2::detectors::DetID::ITS, {0}, false};
912
913 auto makeSource = [&](ClusterSourceId id, FakeClusterDecoder& decoder) {
915 src.id = id;
916 src.detector = o2::detectors::DetID::ITS;
917 src.clusters = clusters;
918 src.patterns = patterns;
919 src.rofs = rofs;
920 src.dictionary = &dict();
921 src.layerToSurface = itsLayerToSurface;
922 src.timing = o2::its::LayerTiming{.mROFLength = 40};
923 src.setDecoder(decoder);
924 return src;
925 };
926
927 {
928 // Non-dense: ids {0, 2} for two sources.
929 std::array<test::TestClusterSourceInput, 2> sources{makeSource(ClusterSourceId{0}, decoderA), makeSource(ClusterSourceId{2}, decoderB)};
930 TimeFrame frame;
931 configureFrame(frame, layout);
932 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Source IDs must be dense") != std::string::npos; });
933 }
934 {
935 // Duplicate ids {0, 0}.
936 std::array<test::TestClusterSourceInput, 2> sources{makeSource(ClusterSourceId{0}, decoderA), makeSource(ClusterSourceId{0}, decoderB)};
937 TimeFrame frame;
938 configureFrame(frame, layout);
939 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Duplicate source ID") != std::string::npos; });
940 }
941 {
942 // Explicitly invalid id.
943 std::array<test::TestClusterSourceInput, 1> sources{makeSource(ClusterSourceId::invalid(), decoderA)};
944 TimeFrame frame;
945 configureFrame(frame, layout);
946 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Source IDs must be dense") != std::string::npos; });
947 }
948}
949
950BOOST_AUTO_TEST_CASE(InvalidROFClusterRangesAreRejected)
951{
952 const auto layout = makeCombinedLayout();
953 BOOST_REQUIRE(layout.valid());
954 const std::vector<CompClusterExt> clusters{
957 const auto patterns = makePatternBytes(clusters.size());
958 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
959
960 auto makeSrc = [&](const std::vector<ROFRecord>& rofs) {
963 src.detector = o2::detectors::DetID::ITS;
964 src.clusters = clusters;
965 src.patterns = patterns;
966 src.rofs = rofs;
967 src.dictionary = &dict();
968 src.layerToSurface = itsLayerToSurface;
969 src.timing = o2::its::LayerTiming{.mROFLength = 40};
970 src.setDecoder(decoder);
971 return src;
972 };
973
974 {
975 // Out of bounds: firstEntry+nEntries exceeds the cluster span.
976 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 5}};
977 auto src = makeSrc(rofs);
978 TimeFrame frame;
979 configureFrame(frame, layout);
980 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
981 }
982 {
983 // Overlapping ranges.
984 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 2}, ROFRecord{{40, 0}, 1, 1, 1}};
985 auto src = makeSrc(rofs);
986 TimeFrame frame;
987 configureFrame(frame, layout);
988 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
989 }
990 {
991 // Leading gap: first ROF does not begin at cluster index 0.
992 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 1, 1}};
993 auto src = makeSrc(rofs);
994 TimeFrame frame;
995 configureFrame(frame, layout);
996 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
997 }
998 {
999 // Internal gap: rof0 covers [0,1), rof1 covers [2,2) i.e. starts at 2
1000 // while only cluster index 1 is unreferenced in between (2 clusters
1001 // total, so this leaves cluster 1 outside any ROF).
1002 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}, ROFRecord{{40, 0}, 1, 2, 0}};
1003 auto src = makeSrc(rofs);
1004 TimeFrame frame;
1005 configureFrame(frame, layout);
1006 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
1007 }
1008 {
1009 // Trailing cluster: the ROFs cover only the first cluster, leaving the
1010 // second cluster unreferenced by any ROF.
1011 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1012 auto src = makeSrc(rofs);
1013 TimeFrame frame;
1014 configureFrame(frame, layout);
1015 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
1016 }
1017 {
1018 // Clusters without ROFs: zero ROFs is only valid when clusters is also
1019 // empty, but this source has two clusters.
1020 const std::vector<ROFRecord> rofs{};
1021 auto src = makeSrc(rofs);
1022 TimeFrame frame;
1023 configureFrame(frame, layout);
1024 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
1025 }
1026}
1027
1028BOOST_AUTO_TEST_CASE(ZeroROFsIsValidWithZeroClusters)
1029{
1030 const auto layout = makeCombinedLayout();
1031 BOOST_REQUIRE(layout.valid());
1032 const std::vector<CompClusterExt> clusters{};
1033 const std::vector<unsigned char> patterns{};
1034 const std::vector<ROFRecord> rofs{};
1035 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
1036
1038 src.id = ClusterSourceId{0};
1039 src.detector = o2::detectors::DetID::ITS;
1040 src.clusters = clusters;
1041 src.patterns = patterns;
1042 src.rofs = rofs;
1043 src.dictionary = &dict();
1044 src.layerToSurface = itsLayerToSurface;
1045 src.timing = o2::its::LayerTiming{.mROFLength = 40};
1046 src.setDecoder(decoder);
1047
1048 TimeFrame frame;
1049 configureFrame(frame, layout);
1050 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}));
1052}
1053
1054BOOST_AUTO_TEST_CASE(InvalidLayerToSurfaceMappingIsRejected)
1055{
1056 const auto layout = makeCombinedLayout();
1057 BOOST_REQUIRE(layout.valid());
1058 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 1}}; // sensor 1 -> layer 1
1059 const auto patterns = makePatternBytes(clusters.size());
1060 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1061 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {-1, 1}, false};
1062
1064 src.id = ClusterSourceId{0};
1065 src.detector = o2::detectors::DetID::ITS;
1066 src.clusters = clusters;
1067 src.patterns = patterns;
1068 src.rofs = rofs;
1069 src.dictionary = &dict();
1070 src.layerToSurface = gsl::span<const LayerId>(itsLayerToSurface.data(), 1); // too short: only covers layer 0
1071 src.timing = o2::its::LayerTiming{.mROFLength = 40};
1072 src.setDecoder(decoder);
1073
1074 TimeFrame frame;
1075 configureFrame(frame, layout);
1076 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid source-to-surface layer mapping") != std::string::npos; });
1077}
1078
1079BOOST_AUTO_TEST_CASE(DetectorSurfaceMismatchIsRejected)
1080{
1081 const auto layout = makeCombinedLayout();
1082 BOOST_REQUIRE(layout.valid());
1083 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
1084 const auto patterns = makePatternBytes(clusters.size());
1085 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1086 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
1087
1089 src.id = ClusterSourceId{0};
1090 src.detector = o2::detectors::DetID::ITS;
1091 src.clusters = clusters;
1092 src.patterns = patterns;
1093 src.rofs = rofs;
1094 src.dictionary = &dict();
1095 // Deliberately mapped to an MFT surface: ITS source, MFT surface.
1096 const std::array<LayerId, 1> wrongMapping{LayerId{2}};
1097 src.layerToSurface = wrongMapping;
1098 src.timing = o2::its::LayerTiming{.mROFLength = 40};
1099 src.setDecoder(decoder);
1100
1101 TimeFrame frame;
1102 configureFrame(frame, layout);
1103 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Source detector does not match its surface") != std::string::npos; });
1104}
1105
1106BOOST_AUTO_TEST_CASE(UnsafeDecodedLayerIsRejected)
1107{
1108 // The loader validates the decoded detector-local layer before using it to
1109 // index the authoritative layer-to-surface mapping.
1110 const auto layout = makeCombinedLayout();
1111 BOOST_REQUIRE(layout.valid());
1112 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
1113 const auto patterns = makePatternBytes(clusters.size());
1114 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1115
1116 const std::array<Corruption, 2> corruptions{
1117 Corruption::NegativeLayer, Corruption::LayerOutOfRange};
1118 for (const auto corruption : corruptions) {
1119 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false, corruption};
1121 src.id = ClusterSourceId{0};
1122 src.detector = o2::detectors::DetID::ITS;
1123 src.clusters = clusters;
1124 src.patterns = patterns;
1125 src.rofs = rofs;
1126 src.dictionary = &dict();
1127 src.layerToSurface = itsLayerToSurface;
1128 src.timing = o2::its::LayerTiming{.mROFLength = 40};
1129 src.setDecoder(decoder);
1130
1131 TimeFrame frame;
1132 configureFrame(frame, layout);
1133 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&src, 1), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid source-to-surface layer mapping") != std::string::npos; });
1135 }
1136}
1137
1138BOOST_AUTO_TEST_CASE(LoadingClearsPreviousDataBeforeValidation)
1139{
1140 const auto layout = makeCombinedLayout();
1141 BOOST_REQUIRE(layout.valid());
1142
1143 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
1144 const auto patterns = makePatternBytes(clusters.size());
1145 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1146 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
1147
1149 goodSrc.id = ClusterSourceId{0};
1150 goodSrc.detector = o2::detectors::DetID::ITS;
1151 goodSrc.clusters = clusters;
1152 goodSrc.patterns = patterns;
1153 goodSrc.rofs = rofs;
1154 goodSrc.dictionary = &dict();
1155 goodSrc.layerToSurface = itsLayerToSurface;
1156 goodSrc.timing = o2::its::LayerTiming{.mROFLength = 40};
1157 goodSrc.setDecoder(decoder);
1158
1159 TimeFrame frame;
1160 configureFrame(frame, layout);
1161 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&goodSrc, 1), {0, 0}));
1162 BOOST_REQUIRE_EQUAL(frame.getTotalMeasurements(), 1u);
1163
1164 // Now attempt an invalid load (duplicate ids) on the SAME frame.
1165 std::array<test::TestClusterSourceInput, 2> badSources{goodSrc, goodSrc}; // both id==0
1166 BOOST_CHECK_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(badSources), {0, 0}), std::runtime_error);
1167
1169}
1170
1171BOOST_AUTO_TEST_CASE(CallerResetsAfterPartialLoadFailure)
1172{
1173 // The second source returns an invalid layer after the first source has
1174 // already populated the frame. A direct caller owns cleanup after the throw.
1175 const auto layout = makeCombinedLayout();
1176 BOOST_REQUIRE(layout.valid());
1177
1178 const std::vector<CompClusterExt> clusters{{1, 1, CompCluster::InvalidPatternID, 0}};
1179 const auto patterns = makePatternBytes(clusters.size());
1180 const std::vector<ROFRecord> rofs{ROFRecord{{0, 0}, 0, 0, 1}};
1182 labels.addElement(0, o2::MCCompLabel{1, 0, 0});
1183 FakeClusterDecoder decoder{o2::detectors::DetID::ITS, {0}, false};
1184
1186 goodSrc.id = ClusterSourceId{0};
1187 goodSrc.detector = o2::detectors::DetID::ITS;
1188 goodSrc.clusters = clusters;
1189 goodSrc.patterns = patterns;
1190 goodSrc.rofs = rofs;
1191 goodSrc.dictionary = &dict();
1192 goodSrc.labels = &labels;
1193 goodSrc.layerToSurface = itsLayerToSurface;
1194 goodSrc.timing = o2::its::LayerTiming{.mROFLength = 40};
1195 goodSrc.setDecoder(decoder);
1196
1197 TimeFrame frame;
1198 configureFrame(frame, layout);
1199 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(&goodSrc, 1), {0, 0}));
1200
1201 BOOST_REQUIRE_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 1u);
1202 BOOST_REQUIRE_EQUAL(frame.getLabels(LayerId{0}, 0).size(), 1u);
1203
1204 FakeClusterDecoder decoderA{o2::detectors::DetID::ITS, {0}, false};
1205 FakeClusterDecoder decoderB{o2::detectors::DetID::MFT, {0}, true, Corruption::NegativeLayer};
1206
1207 test::TestClusterSourceInput srcA = goodSrc;
1208 srcA.setDecoder(decoderA);
1209
1211 srcB.id = ClusterSourceId{1};
1212 srcB.detector = o2::detectors::DetID::MFT;
1213 srcB.clusters = clusters;
1214 srcB.patterns = patterns;
1215 srcB.rofs = rofs;
1216 srcB.dictionary = &dict();
1217 const std::array<LayerId, 1> mapping{LayerId{2}};
1218 srcB.layerToSurface = mapping;
1220 srcB.setDecoder(decoderB);
1221
1222 std::array<test::TestClusterSourceInput, 2> sources{srcA, srcB};
1223 BOOST_CHECK_EXCEPTION(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>(sources), {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid source-to-surface layer mapping") != std::string::npos; });
1224
1225 frame.resetTimeFrame();
1226 BOOST_CHECK(frame.getGlobalMeasurements(LayerId{0}).empty());
1227 BOOST_CHECK(frame.getLabels(LayerId{0}, 0).empty());
1228 BOOST_CHECK(frame.getLabels(LayerId{2}, 0).empty());
1229}
1230
1231BOOST_AUTO_TEST_CASE(EmptyFrameAccessorsAvoidNullPointerArithmetic)
1232{
1233 TimeFrame frame;
1234
1235 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{0}, 0) == nullptr);
1236 BOOST_CHECK(frame.getLabels(LayerId{0}, 0).empty());
1237
1238 // Loading zero sources into a layout with surfaces is legal and must
1239 // leave every per-surface bucket empty.
1240 const auto layout = makeCombinedLayout();
1241 BOOST_REQUIRE(layout.valid());
1242 configureFrame(frame, layout);
1243 BOOST_REQUIRE_NO_THROW(test::loadSources(frame, layout.getCatalog(), gsl::span<const test::TestClusterSourceInput>{}, {0, 0}));
1245
1246 BOOST_CHECK(frame.getSurfaceMeasurement(LayerId{0}, 0) == nullptr);
1247 BOOST_CHECK(frame.getGlobalMeasurements(LayerId{0}).empty());
1248}
1249
1250BOOST_AUTO_TEST_CASE(UnconfiguredFrameRejectsEvenAnEmptyLoad)
1251{
1252 // A layout with no surfaces at all, combined with zero sources, is the
1253 // most degenerate legal input: nothing to validate, nothing to decode,
1254 // nothing to commit.
1255 const SurfaceCatalogView emptyCatalog{};
1256
1257 TimeFrame frame;
1258 BOOST_CHECK_EXCEPTION(test::loadSources(frame, emptyCatalog, gsl::span<const test::TestClusterSourceInput>{}, {0, 0}), std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("TimeFrame is not configured") != std::string::npos; });
1261}
1262
1263BOOST_AUTO_TEST_CASE(MaxVerticesIncludesEachDistinctSourceLookup)
1264{
1265 TimeFrame frame;
1267 configureFrame(frame, makeCombinedLayout());
1269
1270 // Two sources share storage but have different row indices. Each lookup
1271 // is also shared by both surfaces belonging to that source.
1272 const std::array<RuntimeROFTableEntry, 4> entries{{{0, 2}, {0, 3}, {0, 5}, {0, 7}}};
1273 const std::array<RuntimeROFTableEntry, 2> itsIndices{{{0, 1}, {1, 1}}};
1274 const std::array<RuntimeROFTableEntry, 2> mftIndices{{{2, 1}, {3, 1}}};
1275 RuntimeROFViews itsViews, mftViews;
1276 itsViews.vertexLookup = {entries.data(), itsIndices.data(), nullptr, 2};
1277 mftViews.vertexLookup = {entries.data(), mftIndices.data(), nullptr, 2};
1278 frame.setROFViews(itsViews);
1280 const std::array<int, 2> boundaries{0, 0};
1281 frame.setROFClusters(2, boundaries);
1282 frame.setROFViews(2, mftViews, 0);
1283 frame.setROFClusters(3, boundaries);
1284 frame.setROFViews(3, mftViews, 1);
1286
1287 // Lookup views can cover different numbers of rows in the same table.
1288 mftViews.vertexLookup.mLayerCount = 1;
1289 frame.setROFViews(2, mftViews, 0);
1291}
Definition of the ITSMFT compact cluster.
Definition of the ClusterTopology class.
Passive common TimeFrame owner.
std::vector< long > entries
int32_t i
Definition of the ITSMFT ROFrame (trigger) record.
std::vector< o2::MCCompLabel > labels
Definition of a container to keep Monte Carlo truth external to simulation objects.
bool valid
int nClusters
A container to hold and manage MC truth information/labels.
void addElement(uint32_t dataindex, TruthElement const &element, bool noElement=false)
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 getPatternID() const
Definition CompCluster.h:59
UShort_t getCol() const
Definition CompCluster.h:58
static constexpr unsigned short InvalidPatternID
Definition CompCluster.h:46
SurfaceCatalogView getSurfaceCatalog() const noexcept
const GLfloat * m
Definition glcorearb.h:4066
GLenum src
Definition glcorearb.h:1767
GLuint64EXT * result
Definition glcorearb.h:5662
GLsizei GLsizei GLchar * source
Definition glcorearb.h:798
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLsizei GLenum * sources
Definition glcorearb.h:2516
GLuint id
Definition glcorearb.h:650
constexpr float DefClusError2Col
Definition IOUtils.h:67
ClusterData< T > extractClusterData(const CompClusterExt &c, gsl::span< const unsigned char >::iterator &patterns, const TopologyDictionary *dict)
Definition IOUtils.h:81
constexpr float DefClusError2Row
Definition IOUtils.h:66
void loadTimeFrameSources(TimeFrame &, gsl::span< const ClusterSourceInput >, SurfaceCatalogView, std::vector< std::vector< uint32_t > > *externalIndicesBySurface=nullptr, std::vector< std::vector< uint32_t > > *clusterSizesBySurface=nullptr)
Definition IOUtils.cxx:337
const int float col
BOOST_AUTO_TEST_CASE(Cluster_messageable)
Marks an empty item in the context.
const o2::itsmft::TopologyDictionary * dictionary
Definition IOUtils.h:124
const o2::dataformats::MCTruthContainer< o2::MCCompLabel > * labels
Definition IOUtils.h:125
gsl::span< const o2::itsmft::CompClusterExt > clusters
Definition IOUtils.h:121
gsl::span< const unsigned char > patterns
Definition IOUtils.h:122
o2::detectors::DetID::ID detector
Definition IOUtils.h:120
gsl::span< const o2::itsmft::ROFRecord > rofs
Definition IOUtils.h:123
gsl::span< const LayerId > layerToSurface
Definition IOUtils.h:126
RuntimeROFVertexLookupView vertexLookup
Definition ROFViews.h:375
void setROFClusters(std::size_t position, gsl::span< const int > boundaries)
gsl::span< const int > getROFrameClusters(int layer) const
int getNrof(int layer) const
Definition TimeFrame.h:97
void setROFViews(RuntimeROFViews views) noexcept
std::size_t getTotalMeasurements() const noexcept
Definition TimeFrame.cxx:96
gsl::span< const o2::MCCompLabel > getLabels(LayerId layer, uint32_t clusterId) const
Definition TimeFrame.cxx:88
uint32_t getNMeasurementSurfaces() const noexcept
Definition TimeFrame.h:90
int getROFLocalLayer(int layer) const noexcept
Definition TimeFrame.h:130
const SurfaceMeasurement * getSurfaceMeasurement(LayerId layer, uint32_t clusterId) const noexcept
Definition TimeFrame.cxx:79
int getMaxVerticesPerROF() const noexcept
gsl::span< const GlobalMeasurement > getGlobalMeasurements(LayerId surface) const
Definition TimeFrame.cxx:46
int getClusterROF(int layer, int cluster) const
bool configure(DetectorConfiguration &&layout, std::size_t maxEdges, std::size_t maxCells, std::shared_ptr< BoundedMemoryResource > memoryPool)
BOOST_CHECK(tree)
coder decode(ctfImage, triggersD, clustersD)
std::vector< Cluster > clusters
BOOST_CHECK_EQUAL(triggersD.size(), triggers.size())
std::vector< int > row
std::array< uint16_t, 5 > pattern