Project
Loading...
Searching...
No Matches
testTimeFrameLifecycle.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// TimeFrame lifecycle, transactional configuration, and direct loading.
13//
14// A. Reset lifecycle: TimeFrame::resetTimeFrame() unconditionally clears all
15// TimeFrame data while preserving detector configuration and allocator
16// identity. Post-reset checks always obtain fresh views.
17//
18// B. Strong configuration transactionality: a BoundedMemoryResource failure
19// while staging a valid replacement must preserve the live configuration,
20// workspace, allocator and capacities, as well as an already loaded TimeFrame,
21// its allocator-backed storage, navigation, and results.
22//
23// C. TimeFrame loading resets and fills the configured frame directly.
24// Callers reset the frame after catching loading failures.
25
26#define BOOST_TEST_MODULE ITSMFT TimeFrame lifecycle
27#define BOOST_TEST_MAIN
28#define BOOST_TEST_DYN_LINK
29#include <boost/test/unit_test.hpp>
30
31#include <algorithm>
32#include <array>
33#include <memory>
34#include <optional>
35#include <vector>
36
37#include <gsl/gsl>
38
53
54using namespace o2::itsmft;
55using namespace o2::itsmft::tracking;
56
57namespace
58{
59
60// Deterministic, geometry-free stand-in for detector geometry decoding
61// (same construction as testTimeFrameNormalizedSource.cxx / testMultiSourceLoading.cxx):
62// sensorID is used directly as the detector-local layer, global/frame
63// coordinates are pure functions of (sensorID, row, col), and pattern
64// consumption goes through the real production helper so cursor bookkeeping
65// is exercised identically to production decoding.
66class LegacyLikeDecoder
67{
68 public:
69 explicit LegacyLikeDecoder(o2::detectors::DetID::ID detector) : mDetector(detector) {}
70
72 const CompClusterExt& cluster,
73 gsl::span<const unsigned char>::iterator& patterns,
74 const TopologyDictionary* dict,
75 uint32_t) const
76 {
77 const auto clusterData = o2::itsmft::ioutils::extractClusterData(cluster, patterns, dict);
79 const int sensorID = cluster.getSensorID();
80 auto& decoded = result;
81 decoded.global = {static_cast<float>(sensorID) * 10.f, static_cast<float>(cluster.getRow()), static_cast<float>(cluster.getCol())};
82 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};
83 decoded.rowColumnCovariance = {clusterData.sig2Row, 0.f, clusterData.sig2Col};
84 decoded.nPixels = clusterData.nPixels;
85 decoded.layer = sensorID;
86 // Counts only clusters this decoder actually turned into a measurement
87 // (the early-return failure paths above never reach here), so a test can
88 // prove every cluster of a given input was successfully decoded by
89 // checking how much this counter advanced across that call.
90 ++decodeCount;
91 return result;
92 }
93
94 mutable int decodeCount{0};
95
96 private:
98};
99
100const TopologyDictionary& dict()
101{
102 static const TopologyDictionary d;
103 return d;
104}
105
106constexpr std::array<unsigned char, 3> onePixelPattern{1, 1, 0x80}; // 1x1, 1 pixel
107constexpr std::array<unsigned char, 3> threePixelPattern{1, 3, 0xE0}; // 1x3, 3 pixels
108
109std::vector<unsigned char> concatPatterns(std::initializer_list<gsl::span<const unsigned char>> parts)
110{
111 std::vector<unsigned char> bytes;
112 for (const auto& p : parts) {
113 bytes.insert(bytes.end(), p.begin(), p.end());
114 }
115 return bytes;
116}
117
118std::vector<SurfaceDescriptor> makeITSTestCatalog()
119{
120 std::vector<SurfaceDescriptor> surfaces;
121 surfaces.reserve(ITSNLayers);
122 for (uint16_t i = 0; i < ITSNLayers; ++i) {
123 surfaces.push_back(SurfaceDescriptor{i, static_cast<uint8_t>(o2::detectors::DetID::ITS), SurfaceKind::Cylinder});
124 }
125 return surfaces;
126}
127
128std::vector<LayerId> identitySurfaces(uint16_t nLayers)
129{
130 std::vector<LayerId> mapping;
131 mapping.reserve(nLayers);
132 for (uint16_t i = 0; i < nLayers; ++i) {
133 mapping.push_back(LayerId{i});
134 }
135 return mapping;
136}
137
138DetectorConfiguration catalogLayout(SurfaceCatalogView catalog)
139{
140 return DetectorConfiguration{gsl::span<const SurfaceDescriptor>{catalog.surfaces, catalog.nSurfaces}};
141}
142
143GlobalPoint3F expectedGlobal(int sensorID, int row, int col)
144{
145 return {static_cast<float>(sensorID) * 10.f, static_cast<float>(row), static_cast<float>(col)};
146}
147
148struct Fixture {
149 std::vector<CompClusterExt> clusters;
150 std::vector<unsigned char> patterns;
151 std::vector<ROFRecord> rofs;
153};
154
155// 4 clusters on layers {0,1,0,2}, partitioned into 3 ROFs: ROF0={c0,c1},
156// ROF1={c2}, ROF2={c3}. Identical shape to testTimeFrameNormalizedSource.cxx's
157// fixture, so parity with that accepted test coverage is preserved.
158Fixture makeFixture()
159{
160 Fixture f;
161 f.clusters = {
162 CompClusterExt{10, 20, CompCluster::InvalidPatternID, 0}, // sensor 0 -> layer 0
163 CompClusterExt{11, 21, CompCluster::InvalidPatternID, 1}, // sensor 1 -> layer 1
164 CompClusterExt{12, 22, CompCluster::InvalidPatternID, 0}, // sensor 0 -> layer 0
165 CompClusterExt{13, 23, CompCluster::InvalidPatternID, 2}, // sensor 2 -> layer 2
166 };
167 f.patterns = concatPatterns({onePixelPattern, threePixelPattern, onePixelPattern, threePixelPattern});
168 f.rofs = {
169 ROFRecord{{100, 5}, 0, 0, 2},
170 ROFRecord{{140, 5}, 1, 2, 1},
171 ROFRecord{{1000, 6}, 2, 3, 1}};
172 for (uint32_t i = 0; i < f.clusters.size(); ++i) {
173 f.labels.addElement(i, o2::MCCompLabel{static_cast<int>(i) + 1, 0, 0});
174 }
175 return f;
176}
177
178// A second, distinct, independently valid fixture: different sensors/layers
179// (3,4,3,5,3 instead of 0,1,0,2), different rows/columns, a different
180// pattern arrangement, a different ROF partition (3 ROFs over 5 clusters
181// instead of 4), and its own separate MCTruthContainer with different label
182// values. Used as the *replacement* load in the strong-exception-safety
183// test, so that if any partial commit ever leaked through, it would be
184// observable as foreign data (wrong layer, wrong coordinates, wrong label)
185// rather than being masked by coincidentally reloading the same values.
186Fixture makeReplacementFixture()
187{
188 Fixture f;
189 f.clusters = {
190 CompClusterExt{50, 60, CompCluster::InvalidPatternID, 3}, // sensor 3 -> layer 3
191 CompClusterExt{51, 61, CompCluster::InvalidPatternID, 4}, // sensor 4 -> layer 4
192 CompClusterExt{52, 62, CompCluster::InvalidPatternID, 3}, // sensor 3 -> layer 3
193 CompClusterExt{53, 63, CompCluster::InvalidPatternID, 5}, // sensor 5 -> layer 5
194 CompClusterExt{54, 64, CompCluster::InvalidPatternID, 3}, // sensor 3 -> layer 3
195 };
196 f.patterns = concatPatterns({threePixelPattern, threePixelPattern, onePixelPattern, threePixelPattern, onePixelPattern});
197 f.rofs = {
198 ROFRecord{{500, 1}, 0, 0, 3},
199 ROFRecord{{540, 1}, 1, 3, 1},
200 ROFRecord{{2000, 2}, 2, 4, 1}};
201 for (uint32_t i = 0; i < f.clusters.size(); ++i) {
202 f.labels.addElement(i, o2::MCCompLabel{static_cast<int>(i) + 101, 1, 1});
203 }
204 return f;
205}
206
207struct Expected {
208 uint32_t externalIndex;
209 int layer;
210 int sensorID;
211 int row, col;
212 uint32_t sourceROF;
213 uint32_t nPixels;
214};
215
216const std::vector<Expected> expectedClusters{
217 {0, 0, 0, 10, 20, 0, 1},
218 {1, 1, 1, 11, 21, 0, 3},
219 {2, 0, 0, 12, 22, 1, 1},
220 {3, 2, 2, 13, 23, 2, 3},
221};
222
223void verifyFixtureLoaded(const TimeFrame& frame, const Fixture& f)
224{
225 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{0}).size(), 2u);
226 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{1}).size(), 1u);
227 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{2}).size(), 1u);
228 for (int l = 3; l < ITSNLayers; ++l) {
229 BOOST_CHECK_EQUAL(frame.getGlobalMeasurements(LayerId{static_cast<uint16_t>(l)}).size(), 0u);
230 BOOST_CHECK_EQUAL(frame.getNrof(l), static_cast<int>(f.rofs.size()));
231 }
232
233 BOOST_CHECK_EQUAL(frame.getNrof(0), static_cast<int>(f.rofs.size()));
234
235 for (std::size_t expectedIndex = 0; expectedIndex < expectedClusters.size(); ++expectedIndex) {
236 const auto& e = expectedClusters[expectedIndex];
237 const auto localClusterId = static_cast<uint32_t>(std::count_if(
238 expectedClusters.begin(), expectedClusters.begin() + expectedIndex,
239 [&](const auto& previous) { return previous.layer == e.layer; }));
240 const GlobalMeasurement* globalMeasurement = nullptr;
241 const SurfaceMeasurement* measurement = nullptr;
242 const auto surface = LayerId{static_cast<uint16_t>(e.layer)};
243 const auto globals = frame.getGlobalMeasurements(surface);
244 for (size_t index = 0; index < globals.size(); ++index) {
245 if (globals[index].clusterId == localClusterId) {
246 globalMeasurement = &globals[index];
247 measurement = frame.getSurfaceMeasurement(surface, localClusterId);
248 break;
249 }
250 }
251 BOOST_REQUIRE(globalMeasurement != nullptr);
252 BOOST_REQUIRE(measurement != nullptr);
253
254 const auto g = expectedGlobal(e.sensorID, e.row, e.col);
255 BOOST_CHECK_EQUAL(globalMeasurement->position.x, g.x);
256 BOOST_CHECK_EQUAL(globalMeasurement->position.y, g.y);
257 BOOST_CHECK_EQUAL(globalMeasurement->position.z, g.z);
258
259 BOOST_CHECK_EQUAL(measurement->frame.q, static_cast<float>(e.sensorID) + 100.f);
260 BOOST_CHECK_EQUAL(measurement->frame.u, static_cast<float>(e.row) + 1.f);
261 BOOST_CHECK_EQUAL(measurement->frame.v, static_cast<float>(e.col) + 2.f);
262 BOOST_CHECK_EQUAL(measurement->frame.frameAngle, 0.01f * e.sensorID);
263
265 BOOST_CHECK_EQUAL(measurement->covariance.uv, 0.f);
267
268 BOOST_CHECK_EQUAL(globalMeasurement->clusterId, localClusterId);
269 const auto normalizedLabels = frame.getLabels(surface, localClusterId);
270 BOOST_REQUIRE_EQUAL(normalizedLabels.size(), 1u);
271 BOOST_CHECK(normalizedLabels[0] == o2::MCCompLabel(static_cast<int>(e.externalIndex) + 1, 0, 0));
272 }
273}
274
275void configureFrame(TimeFrame& frame, SurfaceCatalogView catalog,
276 std::shared_ptr<BoundedMemoryResource> pool = std::make_shared<BoundedMemoryResource>())
277{
278 auto layout = catalogLayout(catalog);
279 BOOST_REQUIRE(frame.configure(std::move(layout), 0, 0, std::move(pool)));
280}
281
282} // namespace
283
284// --- A. Wipe lifecycle -------------------------------------------------
285
286BOOST_AUTO_TEST_CASE(WipeClearsNormalizedFrameButPreservesDetId)
287{
288 const auto catalog = makeITSTestCatalog();
289 const auto orderedSurfaces = identitySurfaces(ITSNLayers);
290 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
291 LegacyLikeDecoder decoder{o2::detectors::DetID::ITS};
292 const o2::InteractionRecord origin{50, 5};
293 const o2::its::LayerTiming timing{.mROFLength = 40};
294
295 TimeFrame frame;
296 const auto plan = catalogLayout(catalogView);
297 configureFrame(frame, catalogView);
298 const auto estimatorKey = CapacityEstimator::makeKey(SlabSite::Cells, 2, 0, CellPathId{3});
299 frame.getCapacityEstimator().update(estimatorKey, 1000., 8000, 8000, false, false);
300 const auto learnedCapacity = frame.getCapacityEstimator().capacity(estimatorKey, 1000.);
301 BOOST_REQUIRE_GT(learnedCapacity, 1024u);
302
303 const auto f = makeFixture();
304 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSource(frame, decoder, origin, timing, f.clusters, f.patterns, f.rofs, &dict(), &f.labels, o2::detectors::DetID::ITS,
305 gsl::span<const LayerId>{orderedSurfaces}, plan.getSurfaceCatalog()));
306 // Sanity: the successful load itself has the expected content, matching
307 // the accepted parity coverage in testTimeFrameNormalizedSource.cxx.
308 verifyFixtureLoaded(frame, f);
309
310 frame.resetTimeFrame();
311 BOOST_CHECK_EQUAL(frame.getCapacityEstimator().capacity(estimatorKey, 1000.), learnedCapacity);
312
313 // --- inspect only freshly obtained normalized accessors/views ---
316 for (uint16_t s = 0; s < ITSNLayers; ++s) {
317 BOOST_CHECK(frame.getGlobalMeasurements(LayerId{s}).empty());
318 }
319 BOOST_CHECK(frame.getLabels(LayerId{0}, 0).empty());
320
321 // Gate 4 B3.1: neither owner stores mDetId any more -- the plan lives on
322 // `plan` above, entirely outside both TimeFrame and LegacyTrackerScratch,
323 // so resetTimeFrame() has no detector-identity state to preserve or clear.
324}
325
326BOOST_AUTO_TEST_CASE(FailedConfigurationAllocationLeavesClearedFrame)
327{
328 const auto catalog = makeITSTestCatalog();
329 const auto orderedSurfaces = identitySurfaces(ITSNLayers);
330 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
331 TimeFrame frame;
332 const auto estimatorKey = CapacityEstimator::makeKey(SlabSite::Tracklets, 1, 0, EdgeId{2});
333 frame.getCapacityEstimator().update(estimatorKey, 1000., 9000, 9000, false, false);
334 const auto learnedCapacity = frame.getCapacityEstimator().capacity(estimatorKey, 1000.);
335 const auto* const scratch = &frame.getScratch();
336 auto layout = catalogLayout(catalogView);
337 auto failingPool = std::make_shared<BoundedMemoryResource>(0);
338
339 BOOST_CHECK(!frame.configure(std::move(layout), 1, 1, failingPool));
340 BOOST_CHECK_EQUAL(frame.getCapacityEstimator().capacity(estimatorKey, 1000.), learnedCapacity);
341 BOOST_CHECK_EQUAL(failingPool->getThrowCount(), 1u);
342 BOOST_CHECK_EQUAL(failingPool->getUsedMemory(), 0u);
343
344 BOOST_CHECK(!frame.isConfigured());
345 BOOST_CHECK(&frame.getScratch() == scratch);
346 BOOST_CHECK(frame.getMemoryPool().get() == failingPool.get());
347 BOOST_CHECK(frame.getScratch().getMemoryPool().get() == failingPool.get());
352 BOOST_CHECK(frame.getGenericTracks().empty());
353 BOOST_CHECK(frame.getTrackClusterIndices().empty());
355}
356
357BOOST_AUTO_TEST_CASE(ConfigurationAdoptionResetsIncompatibleCapacityEstimates)
358{
359 const auto catalog = makeITSTestCatalog();
360 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
361 TimeFrame frame;
362 const auto key = CapacityEstimator::makeKey(SlabSite::Roads, 3,
364 frame.getCapacityEstimator().update(key, 1000., 12000, 12000, false, false);
365 BOOST_REQUIRE_GT(frame.getCapacityEstimator().capacity(key, 1000.), 1024u);
366
367 configureFrame(frame, catalogView);
368
369 BOOST_CHECK_EQUAL(frame.getCapacityEstimator().capacity(key, 1000.), 1024u);
370}
371
372BOOST_AUTO_TEST_CASE(CallerResetsAfterMalformedTimeFrameLoad)
373{
374 const auto catalog = makeITSTestCatalog();
375 const auto orderedSurfaces = identitySurfaces(ITSNLayers);
376 const SurfaceCatalogView catalogView{catalog.data(), static_cast<uint32_t>(catalog.size())};
377 LegacyLikeDecoder decoder{o2::detectors::DetID::ITS};
378 const o2::InteractionRecord origin{50, 5};
379 const o2::its::LayerTiming timing{.mROFLength = 40};
380 const auto baselineFixture = makeFixture();
381 auto malformedReplacement = makeReplacementFixture();
382 const auto plan = catalogLayout(catalogView);
383 TimeFrame frame;
384 configureFrame(frame, catalogView);
385 BOOST_REQUIRE_NO_THROW(test::loadTimeFrameSource(frame, decoder, origin, timing, baselineFixture.clusters,
386 baselineFixture.patterns, baselineFixture.rofs, &dict(),
387 &baselineFixture.labels, o2::detectors::DetID::ITS,
388 gsl::span<const LayerId>{orderedSurfaces}, plan.getSurfaceCatalog()));
389 verifyFixtureLoaded(frame, baselineFixture);
390
391 malformedReplacement.rofs.front().setFirstEntry(1);
392 BOOST_CHECK_EXCEPTION(test::loadTimeFrameSource(frame, decoder, origin, timing, malformedReplacement.clusters,
393 malformedReplacement.patterns, malformedReplacement.rofs, &dict(),
394 &malformedReplacement.labels, o2::detectors::DetID::ITS,
395 gsl::span<const LayerId>{orderedSurfaces}, plan.getSurfaceCatalog()),
396 std::runtime_error, [](const std::runtime_error& error) { return std::string(error.what()).find("Invalid ROF cluster range") != std::string::npos; });
397 frame.resetTimeFrame();
400}
header::DataOrigin origin
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.
std::vector< o2::MCCompLabel > labels
Definition of a container to keep Monte Carlo truth external to simulation objects.
uint32_t col
Definition RawData.h:4
Runtime-plan-owned, detector-neutral CA workspace.
StringRef key
A container to hold and manage MC truth information/labels.
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
static constexpr int makeVariant(int high, int low) noexcept
static constexpr KeyType makeKey(SlabSite site, int iteration, int variant, int slot) noexcept
size_t capacity(uint64_t key, double scale) const
void update(uint64_t key, double scale, size_t emitted, size_t capacityUsed, bool overflowed, bool memoryLimited)
std::size_t getNCells() const noexcept
std::size_t getNEdges() const noexcept
GLuint64EXT * result
Definition glcorearb.h:5662
GLuint index
Definition glcorearb.h:781
GLdouble f
Definition glcorearb.h:310
GLboolean GLboolean g
Definition glcorearb.h:1233
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
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
constexpr int ITSNLayers
ITS CA layer count.
const int float col
BOOST_AUTO_TEST_CASE(Cluster_messageable)
const DetectorConfiguration & getDetectorConfiguration() const noexcept
Definition TimeFrame.h:157
int getNrof(int layer) const
Definition TimeFrame.h:97
TimeFrameScratch & getScratch()
std::size_t getTotalMeasurements() const noexcept
Definition TimeFrame.cxx:96
bool isConfigured() const noexcept
Definition TimeFrame.h:156
gsl::span< const o2::MCCompLabel > getLabels(LayerId layer, uint32_t clusterId) const
Definition TimeFrame.cxx:88
CapacityEstimator & getCapacityEstimator() noexcept
Definition TimeFrame.h:151
uint32_t getNMeasurementSurfaces() const noexcept
Definition TimeFrame.h:90
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
auto & getMemoryPool() const noexcept
Definition TimeFrame.h:171
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