Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ntpl018_low_precision_floats.C
Go to the documentation of this file.
1/// \file
2/// \ingroup tutorial_ntuple
3/// \notebook
4/// Example creating low-precision floating point fields in RNTuple
5///
6/// RNTuple supports storing floating points on disk with less precision than their in-memory representation.
7/// Under the right circumstances, this can in save storage space while not significantly altering the results
8/// of an analysis.
9///
10/// Storing low-precision floats is done by setting their column representation to one of the dedicated column types:
11/// - Real16 (half-precision IEEE754 fp),
12/// - Real32Trunc (single-precision IEEE754 with truncated mantissa, using from 10 to 31 bits in total) and
13/// - Real32Quant (floating point within a specified range, represented as an integer with N bits of precision in a
14/// linear quantized form).
15///
16/// To use these column types in RNTuple, one creates a RField<float> or RField<double> and sets its desired column
17/// representation by calling, respectively:
18/// - RField<float>::SetHalfPrecision() (for Real16)
19/// - RField<float>::SetTruncated() (for Real32Trunc)
20/// - RField<float>::SetQuantized() (for Real32Quant)
21///
22/// Other than these, one can also setup the field to use the ROOT `Double32_t` type, either via
23/// RField<double>::SetDouble32() or by directly creating one such field via RFieldBase::Create("f", "Double32_t").
24///
25/// \macro_image
26/// \macro_code
27///
28/// \date February 2026
29/// \author The ROOT Team
30
31static constexpr char const *kNTupleName = "ntpl";
32static constexpr char const *kNTupleFileName = "ntpl018_low_precision_floats.root";
33static constexpr int kNEvents = 50;
34
35struct Event {
36 std::vector<float> fPt;
37 std::vector<double> fE;
38};
39
40static void Write()
41{
42 auto model = ROOT::RNTupleModel::Create();
43
44 // Create 3 float fields: one backed by a Real16 column, one backed by a Real32Trunc column
45 // and one backed by a Real32Quant column.
46 // Since we need to call methods on the RField objects in order to make them into our specific column types,
47 // we don't use MakeField but rather we explicitly create the RFields and then use AddField on the model.
48 {
49 auto fieldReal16 = std::make_unique<ROOT::RField<float>>("myReal16");
50 fieldReal16->SetHalfPrecision(); // this is now a Real16-backed float field
51 model->AddField(std::move(fieldReal16));
52 }
53 {
54 auto fieldReal32Trunc = std::make_unique<ROOT::RField<float>>("myReal32Trunc");
55 // Let's say we want 20 bits of precision. This means that this float's mantissa will be truncated to (20 - 9) =
56 // 11 bits.
57 fieldReal32Trunc->SetTruncated(20);
58 model->AddField(std::move(fieldReal32Trunc));
59 }
60 {
61 auto fieldReal32Quant = std::make_unique<ROOT::RField<float>>("myReal32Quant");
62 // Declare that this field will never store values outside of the [-1, 1] range (this will be checked dynamically)
63 // and that we want to dedicate 24 bits to this number on disk.
64 fieldReal32Quant->SetQuantized(-1., 1., 24);
65 model->AddField(std::move(fieldReal32Quant));
66 }
67
68 // We can also change the column type of a struct/class subfield:
69 {
70 auto fieldEvents = std::make_unique<ROOT::RField<Event>>("myEvents");
71 // Note that we iterate over `*fieldEvents`, not over fieldEvents->GetMutableSubfields(), as the latter won't
72 // recurse into fieldEvents's grandchildren. By iterating over the field itself we are sure to visit the entire
73 // field hierarchy, including the fields we need to change.
74 // The hierarchy of fieldEvents is like this:
75 //
76 // myEvents: RField<Event>
77 // fPt: RField<vector<float>>
78 // _0: RField<float> <-- we need to change this
79 // fE: RField<vector<double>
80 // _0: RField<double> <-- we need to change this
81 //
82 for (auto &field : *fieldEvents) {
83 if (auto *fldDouble = dynamic_cast<ROOT::RField<double> *>(&field)) {
84 std::cout << "Setting field " << field.GetQualifiedFieldName() << " to truncated.\n";
85 fldDouble->SetTruncated(16);
86 } else if (auto *fldFloat = dynamic_cast<ROOT::RField<float> *>(&field)) {
87 std::cout << "Setting field " << field.GetQualifiedFieldName() << " to truncated.\n";
88 fldFloat->SetTruncated(16);
89 }
90 }
91 model->AddField(std::move(fieldEvents));
92 }
93
94 // Get the pointers to the fields we just added:
95 const auto &entry = model->GetDefaultEntry();
96 auto myReal16 = entry.GetPtr<float>("myReal16");
97 auto myReal32Trunc = entry.GetPtr<float>("myReal32Trunc");
98 auto myReal32Quant = entry.GetPtr<float>("myReal32Quant");
99 auto myEvents = entry.GetPtr<Event>("myEvents");
100
102
103 // fill our entries
104 gRandom->SetSeed();
105 for (int i = 0; i < kNEvents; i++) {
106 *myReal16 = gRandom->Rndm();
109 myEvents->fPt.push_back(i);
110 myEvents->fE.push_back(i);
111 writer->Fill();
112 }
113}
114
115static void Read()
116{
118
119 // We can read back our fields as regular floats. We can also read them as double if we impose our own model when
120 // creating the reader.
121 const auto &entry = reader->GetModel().GetDefaultEntry();
122 auto myReal16 = entry.GetPtr<float>("myReal16");
123 auto myReal32Trunc = entry.GetPtr<float>("myReal32Trunc");
124 auto myReal32Quant = entry.GetPtr<float>("myReal32Quant");
125 auto myEvents = entry.GetPtr<Event>("myEvents");
126
127 for (auto idx : reader->GetEntryRange()) {
128 reader->LoadEntry(idx);
129
130 float eventsAvgPt = 0.f;
131 for (float pt : myEvents->fPt)
132 eventsAvgPt += pt;
133 eventsAvgPt /= myEvents->fPt.size();
134 double eventsAvgE = 0.f;
135 for (double e : myEvents->fE)
136 eventsAvgE += e;
137 eventsAvgE /= myEvents->fE.size();
138
139 std::cout << "[" << idx << "] Real16: " << *myReal16 << ", Real32Trunc: " << *myReal32Trunc
140 << ", Real32Quant: " << *myReal32Quant << ", Events avg pt: " << eventsAvgPt << ", E: " << eventsAvgE
141 << "\n";
142 }
143}
144
146{
147 Write();
148 Read();
149}
#define e(i)
Definition RSha256.hxx:103
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
R__EXTERN TRandom * gRandom
Definition TRandom.h:62
Classes with dictionaries that can be inspected by TClass.
Definition RField.hxx:323
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 > Recreate(std::unique_ptr< ROOT::RNTupleModel > model, std::string_view ntupleName, std::string_view storage, const ROOT::RNTupleWriteOptions &options=ROOT::RNTupleWriteOptions())
Creates an RNTupleWriter backed by storage, overwriting it if one with the same URI exists.
virtual void SetSeed(ULong_t seed=0)
Set the random generator seed.
Definition TRandom.cxx:614
Double_t Rndm() override
Machine independent random number generator.
Definition TRandom.cxx:558
TPaveText * pt