ATLAS Offline Software
Loading...
Searching...
No Matches
ColumnReaderTTree.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
6
7//
8// includes
9//
10
12
16
17#include <TTree.h>
18
19//
20// method implementations
21//
22
23namespace columnar
24{
25 namespace TestUtils
26 {
27 template <typename T>
28 class BranchReader final
29 {
30 std::string m_branchName;
31 TBranch* m_branch = nullptr;
32 bool m_isStatic = std::is_pod_v<T>;
33 T* m_data{new T()};
34
35 public:
36 BranchReader(const std::string& val_branchName)
37 : m_branchName(val_branchName)
38 {
39 if (m_branchName.find("Aux.") != std::string::npos)
40 m_isStatic = true;
41 }
42
43 ~BranchReader() noexcept
44 {
45 delete m_data;
46 }
47
48 BranchReader(const BranchReader&) = delete;
50
51 void setIsStatic(bool isStatic)
52 {
53 m_isStatic = isStatic;
54 }
55
56 [[nodiscard]] const std::string& branchName() const
57 {
58 return m_branchName;
59 }
60
61 [[nodiscard]] std::string columnName() const
62 {
63 std::string columnName = m_branchName;
64 if (auto index = columnName.find("AuxDyn."); index != std::string::npos)
65 columnName.replace(index, 6, "");
66 else if (auto index = columnName.find("Aux."); index != std::string::npos)
67 columnName.replace(index, 3, "");
68 else if (columnName.find(".") != std::string::npos)
69 throw std::runtime_error("branch name does not contain AuxDyn or Aux: " +m_branchName);
70 return columnName;
71 }
72
73 [[nodiscard]] std::string containerName() const
74 {
75 if (auto index = m_branchName.find("AuxDyn."); index != std::string::npos)
76 return m_branchName.substr(0, index);
77 else if (auto index = m_branchName.find("Aux."); index != std::string::npos)
78 return m_branchName.substr(0, index);
79 else if (m_branchName.find(".") == std::string::npos)
80 return m_branchName;
81 else
82 throw std::runtime_error("branch name does not contain AuxDyn or Aux: " +m_branchName);
83 }
84
85 void connectTree(TTree* tree)
86 {
87 m_branch = tree->GetBranch(m_branchName.c_str());
88 if (!m_branch)
89 throw std::runtime_error("failed to get branch: " + m_branchName);
90 m_branch->SetMakeClass(1);
91 if (m_isStatic)
92 m_branch->SetAddress(m_data);
93 else
94 m_branch->SetAddress(&m_data);
95 }
96
97 void connectTree(const Backend& b)
98 {
99 auto* tree = std::get<TTree*>(b); // throws if wrong backend
101 }
102
103 const T& getEntry(Long64_t entry)
104 {
105 if (!m_branch)
106 throw std::runtime_error("branch not connected: " + m_branchName);
107 if (m_branch->GetEntry(entry) <= 0)
108 throw std::runtime_error("failed to get entry " + std::to_string(entry) + " for branch: " + m_branchName);
109 if (m_data == nullptr)
110 throw std::runtime_error("got nullptr reading data for branch: " + m_branchName);
111 return *m_data;
112 }
113
114 const T& getCachedEntry() const
115 {
116 return *m_data;
117 }
118
119 std::optional<float> entrySize() const
120 {
121 if (!m_branch)
122 return std::nullopt;
123 return static_cast<float>(m_branch->GetZipBytes()) / m_branch->GetEntries();
124 }
125
126 std::optional<float> uncompressedSize() const
127 {
128 if (!m_branch)
129 return std::nullopt;
130 return static_cast<float>(m_branch->GetTotBytes()) / m_branch->GetEntries();
131 }
132
133 // technically this is const-correct, but I don't want to convince
134 // the code checker of that
135 std::optional<unsigned> numBaskets()
136 {
137 if (!m_branch)
138 return std::nullopt;
139 return m_branch->GetListOfBaskets()->GetSize();
140 }
141 };
142
143 template <typename T>
144 class BranchReaderArray final
145 {
146 public:
147 std::string m_branchName;
148 TBranch* m_branch = nullptr;
149 std::vector<T> m_dataVec;
150
151 public:
152 BranchReaderArray(const std::string& val_branchName)
153 : m_branchName(val_branchName)
154 {}
155
158
159 [[nodiscard]] std::string columnName() const
160 {
161 std::string columnName = m_branchName;
162 if (auto index = columnName.find("AuxDyn."); index != std::string::npos)
163 columnName.replace(index, 6, "");
164 else if (auto index = columnName.find("Aux."); index != std::string::npos)
165 columnName.replace(index, 3, "");
166 else if (columnName.find(".") != std::string::npos)
167 throw std::runtime_error("branch name does not contain AuxDyn or Aux: " + m_branchName);
168 return columnName;
169 }
170
171 [[nodiscard]] std::string containerName() const
172 {
173 if (auto index = m_branchName.find("AuxDyn."); index != std::string::npos)
174 return m_branchName.substr(0, index);
175 else if (auto index = m_branchName.find("Aux."); index != std::string::npos)
176 return m_branchName.substr(0, index);
177 else if (m_branchName.find(".") == std::string::npos)
178 return m_branchName;
179 else
180 throw std::runtime_error("branch name does not contain AuxDyn or Aux: " + m_branchName);
181 }
182 void connectTree (TTree *tree)
183 {
184 m_branch = tree->GetBranch (m_branchName.c_str());
185 if (!m_branch)
186 throw std::runtime_error ("failed to get branch: " + m_branchName);
187 m_branch->SetMakeClass (1);
188 // FIX ME: I have to have some hard-coded size, see explanation
189 // below.
190 m_dataVec.resize (100);
191 if (!m_dataVec.empty())
192 m_branch->SetAddress (m_dataVec.data());
193 }
194
195 std::span<const T> getEntry (Long64_t entry, std::size_t size)
196 {
197 if (!m_branch)
198 throw std::runtime_error ("branch not connected: " + m_branchName);
199 if (m_dataVec.size() < size)
200 {
201 // FIX ME: in one of the latest releases the repointing below
202 // breaks, and causes memory corruption. so I'm now
203 // preallocating and fail rather than reallocate, and the
204 // problem goes away. maybe it should be investigated at some
205 // point, but this is a test and I already spend a fair amount
206 // of time investigating this. the harm is that this test
207 // consumes a few hundreds bytes more in memory and we may have
208 // to occasionally increase the buffer size to cover all test
209 // files and branch lengths.
210 throw std::runtime_error ("requested size exceeds buffer size for branch: " + m_branchName);
211 // m_dataVec.resize (size);
212 // m_branch->SetAddress (m_dataVec.data());
213 }
214 if (size > 0 && m_branch->GetEntry (entry) <= 0)
215 throw std::runtime_error ("failed to get entry " + std::to_string (entry) + " for branch: " + m_branchName);
216 return std::span<const T>(m_dataVec.data(), size);
217 }
218
219 std::optional<float> entrySize () const
220 {
221 if (!m_branch)
222 return std::nullopt;
223 return static_cast<float>(m_branch->GetZipBytes()) / m_branch->GetEntries();
224 }
225
226 std::optional<float> uncompressedSize () const
227 {
228 if (!m_branch)
229 return std::nullopt;
230 return static_cast<float>(m_branch->GetTotBytes()) / m_branch->GetEntries();
231 }
232
233 // technically this is const-correct, but I don't want to convince
234 // the code checker of that
235 std::optional<unsigned> numBaskets ()
236 {
237 if (!m_branch)
238 return std::nullopt;
239 return m_branch->GetListOfBaskets()->GetSize();
240 }
241 };
242
243 template<typename T>
245 {
250 const std::vector<ColumnarOffsetType>* offsetColumn = nullptr;
251 std::vector<ColumnarOffsetType> offsets = {0};
255 unsigned entries = 0;
256 unsigned nullEntries = 0;
257
258 ColumnReaderVectorSplitLink (const std::string& val_branchName)
259 : branchReaderSize (val_branchName), branchReaderKey (val_branchName + ".m_persKey"), branchReaderIndex (val_branchName + ".m_persIndex"), benchmarkUnpack (branchReaderSize.columnName()+"(unpack)"), benchmark (branchReaderSize.columnName())
260 {
261 outputColumns.push_back ({.name = branchReaderSize.columnName()});
262 outputColumns.push_back ({.name = branchReaderSize.containerName(), .isOffset = true, .primary = false});
263 }
264
265 virtual bool connect (Backend source, std::unordered_map<std::string,const std::vector<ColumnarOffsetType>*>& offsetColumns, std::unordered_map<std::string,ColumnInfo>& requestedColumns) override
266 {
267 auto iter = requestedColumns.find (outputColumns.at(0).name);
268 if (iter == requestedColumns.end())
269 return false;
270 outputColumns.at(0).enabled = true;
271 auto* tree = std::get<TTree*>(source);
272 branchReaderSize.connectTree (tree);
273 branchReaderKey.connectTree (tree);
274 branchReaderIndex.connectTree (tree);
275
276 if (iter->second.offsetName != outputColumns.at(1).name)
277 throw std::runtime_error ("offset name mismatch: " + iter->second.offsetName + " != " + outputColumns.at(1).name);
278
279 for (const auto & keyColumn : columnData.connect (iter->second, offsetColumns, requestedColumns))
280 {
281 outputColumns.push_back ({.name = keyColumn, .primary = false, .enabled = true});
282 requestedColumns.erase (keyColumn);
283 }
284 requestedColumns.erase (iter);
285
286 if (auto offsetIter = offsetColumns.find (outputColumns.at(1).name); offsetIter != offsetColumns.end())
287 offsetColumn = offsetIter->second;
288 else
289 offsetColumns.emplace (outputColumns.at(1).name, &offsets);
290
291 iter = requestedColumns.find (outputColumns.at(1).name);
292 if (iter != requestedColumns.end())
293 {
294 outputColumns.at(1).enabled = true;
295 requestedColumns.erase (iter);
296 }
297
298 return true;
299 }
300
301 virtual void clearColumns () override
302 {
303 columnData.clear();
304 offsets.clear();
305 offsets.push_back (0);
306 }
307
308 virtual void getEntry (Long64_t entry) override
309 {
310 benchmark.startTimer ();
311 std::size_t branchDataSize = branchReaderSize.getEntry (entry);
312 auto branchDataKey = branchReaderKey.getEntry (entry, branchDataSize);
313 auto branchDataIndex = branchReaderIndex.getEntry (entry, branchDataSize);
314 benchmark.stopTimer ();
315 benchmarkUnpack.startTimer ();
316 columnData.checkOffsets (offsets.size() - 1);
317 for (std::size_t index = 0; index < branchDataSize; ++index)
318 {
319 if (branchDataIndex[index] == static_cast<UInt_t>(-1))
320 columnData.addEmptyLink ();
321 else
322 columnData.addSplitLink (branchDataIndex[index], branchDataKey[index], offsets.size()-1);
323 }
324 offsets.push_back (columnData.size());
325 if (offsetColumn)
326 {
327 if (offsetColumn->size() != offsets.size())
328 throw std::runtime_error ("offset column not filled yet: " + outputColumns.at(1).name);
329 if (offsetColumn->back() != offsets.back())
330 throw std::runtime_error ("offset column does not match: " + outputColumns.at(1).name);
331 }
332 benchmarkUnpack.stopTimer ();
333 }
334
335 virtual void setData (ColumnVectorData& colData) override
336 {
337 if (outputColumns.at(0).columnIndex != ColumnVectorHeader::nullIndex)
338 colData.setColumn (outputColumns.at(0).columnIndex, columnData.size(), columnData.data());
339 if (outputColumns.at(1).columnIndex != ColumnVectorHeader::nullIndex)
340 colData.setColumn (outputColumns.at(1).columnIndex, offsets.size(), offsets.data());
341 for (std::size_t i = 2; i < outputColumns.size(); ++ i)
342 {
343 if (outputColumns.at(i).columnIndex != ColumnVectorHeader::nullIndex)
344 colData.setColumn (outputColumns.at(i).columnIndex, columnData.keysColumn(i-2).size(), columnData.keysColumn(i-2).data());
345 }
346 }
347
348 [[nodiscard]] virtual BranchPerfData getPerfData (float emptyTime) override
349 {
350 BranchPerfData result;
351 result.name = branchReaderSize.columnName();
352 result.timeRead = benchmark.getEntryTime(emptyTime);
353 result.timeUnpack = benchmarkUnpack.getEntryTime(emptyTime);
354 benchmark.setSilence();
355 benchmarkUnpack.setSilence();
356 result.entrySize = branchReaderSize.entrySize().value() + branchReaderKey.entrySize().value() + branchReaderIndex.entrySize().value();
357 result.uncompressedSize = branchReaderSize.uncompressedSize().value() + branchReaderKey.uncompressedSize().value() + branchReaderIndex.uncompressedSize().value();
358 result.numBaskets = branchReaderSize.numBaskets().value() + branchReaderKey.numBaskets().value() + branchReaderIndex.numBaskets().value();
359 result.entries = entries;
360 result.nullEntries = nullEntries;
361 return result;
362 }
363
364 virtual void collectColumnData () override
365 {
366 entries += columnData.size();
367 for (const auto& index : columnData)
368 {
370 nullEntries += 1;
371 }
372 }
373 };
374
375 void makeTTreeColumnReaders (std::vector<std::shared_ptr<TestUtils::ColumnReader>>& knownColumns, TTree *tree)
376 {
377 tree->SetMakeClass(1);
378 {
379 std::unordered_map<std::string, TBranch*> branches;
380 {
381 TIter branchIter(tree->GetListOfBranches());
382 TObject* obj = nullptr;
383 while ((obj = branchIter()))
384 {
385 TBranch* branch = nullptr;
386 if ((branch = dynamic_cast<TBranch*>(obj)))
387 {
388 branches.emplace(branch->GetName(), branch);
389 TIter subBranchIter(branch->GetListOfBranches());
390 while ((obj = subBranchIter()))
391 {
392 if (auto subBranch = dynamic_cast<TBranch*>(obj))
393 branches.emplace(subBranch->GetName(), subBranch);
394 }
395 }
396 }
397 }
398
399 for (const auto& [name, branch] : branches)
400 {
401 if (name.find("AuxDyn.") != std::string::npos ||
402 name.find("Aux.") != std::string::npos)
403 {
404 TClass* branchClass = nullptr;
405 EDataType branchType{};
406 branch->GetExpectedType(branchClass, branchType);
407 if (branchClass == nullptr)
408 {
409 switch (branchType)
410 {
411 case kInt_t:
412 knownColumns.push_back(std::make_shared<ColumnReaderScalar<std::int32_t, BranchReader>>(branch->GetName()));
413 break;
414 case kUInt_t:
415 knownColumns.push_back(std::make_shared<ColumnReaderScalar<std::uint32_t, BranchReader>>(branch->GetName()));
416 break;
417 case kULong_t:
418 knownColumns.push_back(std::make_shared<ColumnReaderScalar<std::uint64_t, BranchReader>>(branch->GetName()));
419 break;
420 case kULong64_t:
421 knownColumns.push_back(std::make_shared<ColumnReaderScalar<std::uint64_t, BranchReader>>(branch->GetName()));
422 break;
423 case kFloat_t:
424 knownColumns.push_back(std::make_shared<ColumnReaderScalar<float, BranchReader>>(branch->GetName()));
425 break;
426 default:
427 // no-op
428 break;
429 }
430 } else
431 {
432 if (*branchClass->GetTypeInfo() == typeid(std::vector<float>))
433 {
434 knownColumns.push_back (std::make_shared<ColumnReaderVector<float,BranchReader>> (branch->GetName()));
435 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<char>))
436 {
437 knownColumns.push_back (std::make_shared<ColumnReaderVector<char,BranchReader>> (branch->GetName()));
438 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::int8_t>))
439 {
440 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::int8_t,BranchReader>> (branch->GetName()));
441 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::uint8_t>))
442 {
443 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::uint8_t,BranchReader>> (branch->GetName()));
444 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::int16_t>))
445 {
446 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::int16_t,BranchReader>> (branch->GetName()));
447 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::uint16_t>))
448 {
449 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::uint16_t,BranchReader>> (branch->GetName()));
450 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::int32_t>))
451 {
452 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::int32_t,BranchReader>> (branch->GetName()));
453 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::uint32_t>))
454 {
455 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::uint32_t,BranchReader>> (branch->GetName()));
456 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::int64_t>))
457 {
458 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::int64_t,BranchReader>> (branch->GetName()));
459 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::uint64_t>))
460 {
461 knownColumns.push_back (std::make_shared<ColumnReaderVector<std::uint64_t,BranchReader>> (branch->GetName()));
462 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::vector<float>>))
463 {
464 knownColumns.push_back (std::make_shared<ColumnReaderVectorVector<float,BranchReader>> (branch->GetName()));
465 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::vector<std::int32_t>>))
466 {
467 knownColumns.push_back (std::make_shared<ColumnReaderVectorVector<std::int32_t,BranchReader>> (branch->GetName()));
468 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::vector<std::uint64_t>>))
469 {
470 knownColumns.push_back (std::make_shared<ColumnReaderVectorVector<std::uint64_t,BranchReader>> (branch->GetName()));
471 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::vector<std::vector<std::size_t>>>))
472 {
473 knownColumns.push_back (std::make_shared<ColumnReaderVectorVectorVector<std::size_t,BranchReader>> (branch->GetName()));
474 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::vector<std::vector<unsigned char>>>))
475 {
476 knownColumns.push_back (std::make_shared<ColumnReaderVectorVectorVector<unsigned char,BranchReader>> (branch->GetName()));
477 } else if (*branchClass->GetTypeInfo() == typeid(std::vector<std::string>))
478 {
479 knownColumns.push_back (std::make_shared<ColumnReaderMetNames<BranchReader>> (branch->GetName()));
480 }
481 }
482 }
483 }
484 }
485 // This is a fallback for the case that we don't have an explicit
486 // `samplingPattern` branch in our input file (i.e. an older file),
487 // to allow us to still test tools needing it. This is likely not
488 // something that actual users can do (they need the new files), but
489 // for testing it seems like a reasonable workaround.
490 knownColumns.push_back(std::make_shared<ColumnReaderSamplingPattern<BranchReader>>("egammaClusters"));
491
492 // For branches that are element links they need to be explicitly
493 // declared to have the correct xAOD type, correct split setting,
494 // and correct linked containers.
495
496 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorLink<xAOD::CaloClusterContainer,BranchReader>>("AnalysisElectronsAuxDyn.caloClusterLinks"));
497 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorLink<xAOD::TrackParticleContainer, BranchReader>>("AnalysisElectronsAuxDyn.trackParticleLinks"));
498 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorLink<xAOD::CaloClusterContainer,BranchReader>>("AnalysisPhotonsAuxDyn.caloClusterLinks"));
499 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorLink<xAOD::VertexContainer, BranchReader>>("AnalysisPhotonsAuxDyn.vertexLinks"));
500 knownColumns.push_back(std::make_shared<ColumnReaderVectorSplitLink<xAOD::TrackParticleContainer>>("AnalysisMuonsAuxDyn.inDetTrackParticleLink"));
501 knownColumns.push_back(std::make_shared<ColumnReaderVectorSplitLink<xAOD::TrackParticleContainer>>("AnalysisMuonsAuxDyn.combinedTrackParticleLink"));
502 knownColumns.push_back(std::make_shared<ColumnReaderVectorSplitLink<xAOD::TrackParticleContainer>>("AnalysisMuonsAuxDyn.extrapolatedMuonSpectrometerTrackParticleLink"));
503 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorLink<xAOD::TrackParticleContainer, BranchReader>>("GSFConversionVerticesAuxDyn.trackParticleLinks"));
504 knownColumns.push_back(std::make_shared<ColumnReaderVectorSplitLink<xAOD::TrackParticleContainer>>("GSFTrackParticlesAuxDyn.originalTrackParticle"));
505 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorVariantLink<xAOD::IParticleContainer, BranchReader>>("AnalysisJetsAuxDyn.GhostTrack"));
506 knownColumns.push_back(std::make_shared<ColumnReaderVectorLink<xAOD::JetContainer, BranchReader>>("METAssoc_AnalysisMETAux.jetLink"));
507 knownColumns.push_back(std::make_shared<ColumnReaderVectorVectorVariantLink<xAOD::IParticleContainer, BranchReader>>("METAssoc_AnalysisMETAux.objectLinks"));
508 }
509 }
510}
size_t size() const
Number of registered mappings.
a class that holds the columnar data for a single call
void setColumn(std::size_t columnIndex, std::size_t size, CT *dataPtr)
set the data for the given column
static constexpr std::size_t nullIndex
the index used for an invalid index (always has to be 0)
this is a simple benchmarking helper class wrapping timers from std::chrono
Definition Benchmark.h:51
BranchReaderArray(const std::string &val_branchName)
std::span< const T > getEntry(Long64_t entry, std::size_t size)
BranchReaderArray(const BranchReaderArray &)=delete
std::optional< float > uncompressedSize() const
std::optional< float > entrySize() const
BranchReaderArray & operator=(const BranchReaderArray &)=delete
const std::string & branchName() const
std::optional< float > entrySize() const
BranchReader(const BranchReader &)=delete
BranchReader(const std::string &val_branchName)
std::optional< unsigned > numBaskets()
BranchReader & operator=(const BranchReader &)=delete
std::optional< float > uncompressedSize() const
std::vector< OutputColumnInfo > outputColumns
std::variant< TTree *, RNTupleBackend * > Backend
void makeTTreeColumnReaders(std::vector< std::shared_ptr< TestUtils::ColumnReader > > &knownColumns, TTree *tree)
constexpr ColumnarOffsetType invalidObjectIndex
the value for an invalid element index
Definition index.py:1
the performance data for reading a single branch/column
TChain * tree