Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ntpl019_attributes.C
Go to the documentation of this file.
1/// \file
2/// \ingroup tutorial_ntuple
3/// \notebook
4/// Example using RNTuple attributes
5///
6/// RNTuple Attributes are the way to store custom metadata in RNTuple.
7///
8/// They work by associating rows of user-defined metadata to ranges of entries in their parent RNTuple
9/// (called the "main RNTuple"). These rows of metadata, called "attribute entries", are
10/// defined much like a regular RNTuple by an RNTupleModel and they belong to an "attribute set".
11///
12/// An attribute set is a standalone collection of attributes which is linked to one and only one RNTuple.
13/// Attribute sets are identified by their name and, similarly to RNTuples, they are created with
14/// an associated Model and can be written and read via bespoke classes (RNTupleAttrSetWriter/Reader).
15/// These classes are never used by themselves but are created from an existing RNTupleWriter or Reader.
16///
17/// Each main RNTuple can have an arbitrary number of associated attribute sets, though usually one is
18/// enough for most purposes.
19/// This tutorial shows how to create, write and read back attributes from an RNTuple.
20///
21/// NOTE: The RNTuple attributes are experimental at this point.
22/// Functionality and interface are still subject to changes.
23///
24/// \macro_code
25///
26/// \date April 2026
27/// \author The ROOT Team
28
29#include <ROOT/RNTupleModel.hxx>
32
33#include <TFile.h>
34#include <TRandom3.h>
35
36#include <iostream>
37#include <memory>
38#include <utility>
39
40constexpr const char *kFileName = "ntpl019_attributes.root";
41constexpr const char *kNTupleName = "ntpl";
42
43struct Event {
44 // ... some event data here ...
45 double pt = 0.;
46};
47
48static void Write()
49{
50 // Define the model of our main RNTuple.
51 auto model = ROOT::RNTupleModel::Create();
52 auto pEvent = model->MakeField<Event>("event");
53
54 // Create our main RNTuple.
55 // NOTE: currently attributes are only supported in TFile-based RNTuples, so we must
56 // create the RNTupleWriter via Append(), not Recreate(). This will be relaxed in the future.
57 auto file = std::unique_ptr<TFile>(TFile::Open(kFileName, "RECREATE"));
58 auto writer = ROOT::RNTupleWriter::Append(std::move(model), kNTupleName, *file);
59
60 // Define the model for the attribute set we want to create.
62 attrModel->SetDescription("Metadata containing the events' provenance"); // (this is optional)
63 auto pRunNumber = attrModel->MakeField<std::int32_t>("runNumber");
64
65 // Create the attribute set from the main RNTupleWriter. We name it "Provenance".
66 auto attrSet = writer->CreateAttributeSet(std::move(attrModel), "Provenance");
67
68 // Writing attributes works like this:
69 // 1. begin an attribute range
70 // 2. fill your main RNTuple data as usual
71 // 3. commit the attribute range.
72 //
73 // Between the beginning and committing of each attribute range you can set the values of all the
74 // fields in the attribute model; all the main RNTuple data you filled in before committing the range
75 // will have those values as metadata associated to it.
76 //
77 // Beginning an attribute range is done like this:
78 auto attrRange = attrSet->BeginRange();
79
80 // Here you can assign values to your attributes. In this case we only have 1 attribute (the integer "runNumber"),
81 // so we only fill that.
82 // Note that attribute values can be assigned anywhere between BeginRange() and CommitRange(), so we could do
83 // this step even after the main data filling.
84 *pRunNumber = 0;
85
86 // Now fill in the main RNTuple data
88 for (int i = 0; i < 100; ++i) {
89 *pEvent = {rng.Rndm()};
90 writer->Fill();
91
92 // For explanatory purpose, let's say every 10 entries we have a new run number.
93 if (i % 10 == 0 && i > 0) {
94 // Close the attribute range. To do this you need to move the attribute range into the CommitRange
95 // method, so you cannot reuse `attrRange`.
96 attrSet->CommitRange(std::move(attrRange));
97 // If you need to open a new range, call BeginRange again.
98 attrRange = attrSet->BeginRange();
99 // Then we can assign the new run number.
100 *pRunNumber += 1;
101 }
102 }
103
104 // IMPORTANT: attributes are not written to storage until you call CommitRange and, differently from regular data,
105 // they are NOT automatically written on destruction. If you don't call CommitRange, the attribute data will not
106 // be stored.
107 // In general, you can check if the attribute range was not committed via its operator bool():
108 if (attrRange) {
109 attrSet->CommitRange(std::move(attrRange));
110 }
111}
112
113static bool IsGoodRunNumber(std::int32_t runNo)
114{
115 // For illustratory purposes, let's pretend we know that runNumber 4 is "good" for our analysis.
116 return runNo == 4;
117}
118
119static void Read()
120{
121 // Open the main RNTuple for reading
123
124 // To read back the list of available attribute sets:
125 std::cout << "Here are the attribute sets linked to the RNTuple '" << kNTupleName << "':\n";
126 for (const auto &attrSetDesc : reader->GetDescriptor().GetAttrSetIterable()) {
127 std::cout << " " << attrSetDesc.GetName() << "\n";
128 }
129
130 // Open a specific attribute set
131 auto attrSet = reader->OpenAttributeSet("Provenance");
132 // Fetch pointers to all attributes you want to read.
133 auto pRunNumber = attrSet->GetModel().GetDefaultEntry().GetPtr<std::int32_t>("runNumber");
134
135 std::cout << "\nOpened attribute set '" << attrSet->GetDescriptor().GetName() << "' with description: \""
136 << attrSet->GetDescriptor().GetDescription() << "\"\n";
137 // Loop over the main entries and, for each, print its associated run number
138 for (auto mainIdx : reader->GetEntryRange()) {
139 // There are various ways to access attributes from a main entry index (see the RNTupleAttrSetReader's
140 // documentation). Here we use GetAttributes to get all attributes associated to the main entry `mainIdx`:
141 std::cout << "Entry " << mainIdx << " has the following attributes associated to it:\n";
142 for (auto attrIdx : attrSet->GetAttributes(mainIdx)) {
143 auto range = attrSet->LoadEntry(attrIdx);
144 std::cout << " runNumber = " << *pRunNumber << " (valid for range [" << *range.GetFirst() << ", "
145 << *range.GetLast() << "])\n";
146 }
147 }
148
149 // You can also do the opposite lookup, by looping over all attributes first...
150 auto pEvent = reader->GetModel().GetDefaultEntry().GetPtr<Event>("event");
151 for (auto attrIdx : attrSet->GetAttributes()) {
152 auto range = attrSet->LoadEntry(attrIdx);
153 // ...and then deciding whether you want to load the corresponding range or not.
155 std::cout << "\nRun " << *pRunNumber << " is good. Events:\n";
156 for (auto mainIdx = range.GetStart(); mainIdx < range.GetEnd(); ++mainIdx) {
157 reader->LoadEntry(mainIdx);
158 std::cout << " Event " << mainIdx << " with pt = " << pEvent->pt << "\n";
159 }
160 }
161 }
162}
163
165{
166 Write();
167 Read();
168}
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
static std::unique_ptr< RNTupleModel > Create()
static std::unique_ptr< RNTupleReader > Open(std::string_view ntupleName, std::string_view storage, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Open an RNTuple for reading.
static std::unique_ptr< RNTupleWriter > Append(std::unique_ptr< ROOT::RNTupleModel > model, std::string_view ntupleName, TDirectory &fileOrDirectory, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Creates an RNTupleWriter that writes into an existing TFile or TDirectory, without overwriting its co...
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Definition TFile.cxx:3787
Random number generator class based on M.
Definition TRandom3.h:27
TPaveText * pt