Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-22 09:06:36

0001 /// \file RNTupleDS.hxx
0002 /// \ingroup NTuple ROOT7
0003 /// \author Jakob Blomer <jblomer@cern.ch>
0004 /// \author Enrico Guiraud <enrico.guiraud@cern.ch>
0005 /// \date 2018-10-04
0006 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
0007 /// is welcome!
0008 
0009 /*************************************************************************
0010  * Copyright (C) 1995-2025, Rene Brun and Fons Rademakers.               *
0011  * All rights reserved.                                                  *
0012  *                                                                       *
0013  * For the licensing terms see $ROOTSYS/LICENSE.                         *
0014  * For the list of contributors see $ROOTSYS/README/CREDITS.             *
0015  *************************************************************************/
0016 
0017 #ifndef ROOT_RNTupleDS
0018 #define ROOT_RNTupleDS
0019 
0020 #include <ROOT/RDataSource.hxx>
0021 #include <ROOT/RNTupleDescriptor.hxx>
0022 #include <ROOT/RNTupleTypes.hxx>
0023 #include <string_view>
0024 
0025 #include <condition_variable>
0026 #include <cstdint>
0027 #include <memory>
0028 #include <mutex>
0029 #include <optional>
0030 #include <string>
0031 #include <thread>
0032 #include <vector>
0033 #include <unordered_map>
0034 
0035 // Follow RDF namespace convention
0036 namespace ROOT {
0037 class RDataFrame;
0038 }
0039 namespace ROOT::Internal::RDF {
0040 /**
0041  * \brief Internal overload of the function that allows passing a range of entries
0042  *
0043  * The event range will be respected when processing this RNTuple. It is assumed
0044  * that processing happens within one thread only.
0045  */
0046 ROOT::RDataFrame FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames,
0047                              const std::pair<ULong64_t, ULong64_t> &range);
0048 /**
0049  * \brief Retrieves the cluster boundaries and the number of entries for the input RNTuple
0050  *
0051  * \param[in] ntupleName The name of the RNTuple dataset
0052  * \param[in] location The location of the RNTuple dataset (e.g. a path to a file)
0053  *
0054  * \note This function is a helper for the Python side to avoid having to deal
0055  *       with the shared descriptor guard.
0056  */
0057 std::pair<std::vector<ROOT::Internal::RNTupleClusterBoundaries>, ROOT::NTupleSize_t>
0058 GetClustersAndEntries(std::string_view ntupleName, std::string_view location);
0059 } // namespace ROOT::Internal::RDF
0060 
0061 namespace ROOT {
0062 class RFieldBase;
0063 class RDataFrame;
0064 class RNTuple;
0065 } // namespace ROOT
0066 namespace ROOT::Detail::RDF {
0067 class RNodeBase;
0068 }
0069 namespace ROOT::RDF {
0070 template <typename T>
0071 class RInterface;
0072 }
0073 namespace ROOT::Internal::RDF {
0074 class RNTupleColumnReader;
0075 std::vector<std::pair<std::uint64_t, std::uint64_t>>
0076 GetDatasetGlobalClusterBoundaries(const ROOT::RDF::RInterface<ROOT::Detail::RDF::RNodeBase> &node);
0077 }
0078 namespace ROOT::Internal {
0079 class RPageSource;
0080 }
0081 
0082 namespace ROOT::RDF {
0083 class RNTupleDS final : public ROOT::RDF::RDataSource {
0084    friend class ROOT::Internal::RDF::RNTupleColumnReader;
0085 
0086    /// The PrepareNextRanges() method populates the fNextRanges list with REntryRangeDS records.
0087    /// The GetEntryRanges() swaps fNextRanges and fCurrentRanges and uses the list of
0088    /// REntryRangeDS records to return the list of ranges ready to use by the RDF loop manager.
0089    struct REntryRangeDS {
0090       std::unique_ptr<ROOT::Internal::RPageSource> fSource;
0091       ULong64_t fFirstEntry = 0; ///< First entry index in fSource
0092       /// End entry index in fSource, e.g. the number of entries in the range is fLastEntry - fFirstEntry
0093       ULong64_t fLastEntry = 0;
0094       std::string_view fFileName; ///< Storage location of the current RNTuple
0095    };
0096 
0097    /// A clone of the first pages source's descriptor.
0098    ROOT::RNTupleDescriptor fPrincipalDescriptor;
0099 
0100    /// The data source may be constructed with an ntuple name and a list of files
0101    std::string fNTupleName;
0102    std::vector<std::string> fFileNames;
0103    /// The staging area is relevant for chains of files, i.e. when fFileNames is not empty. In this case,
0104    /// files are opened in the background in batches of size `fNSlots` and kept in the staging area.
0105    /// The first file (chains or no chains) is always opened on construction in order to process the schema.
0106    /// For all subsequent files, the corresponding page sources in the staging area only executed `LoadStructure()`,
0107    /// i.e. they should have a compressed buffer of the meta-data available.
0108    /// Concretely:
0109    ///   1. We open the first file on construction to read the schema and then move the corresponding page source
0110    ///      in the staging area.
0111    ///   2. On `Initialize()`, we start the I/O background thread, which in turn opens the first batch of files.
0112    ///   3. At the beginning of `GetEntryRanges()`, we
0113    ///      a) wait for the I/O thread to finish,
0114    ///      b) call `PrepareNextRanges()` in the main thread to move the page sources from the staging area
0115    ///         into `fNextRanges`; this will also call `Attach()` on the page sources (i.e., deserialize the meta-data),
0116    ///         and
0117    ///      c) trigger staging of the next batch of files in the I/O background thread.
0118    ///   4. On `Finalize()`, the I/O background thread is stopped.
0119    std::vector<std::unique_ptr<ROOT::Internal::RPageSource>> fStagingArea;
0120    std::size_t fNextFileIndex = 0; ///< Index into fFileNames to the next file to process
0121 
0122    /// We prepare a prototype field for every column. If a column reader is actually requested
0123    /// in GetColumnReaders(), we move a clone of the field into a new column reader for RDataFrame.
0124    /// Only the clone connects to the backing page store and acquires I/O resources.
0125    /// The field IDs are set in the context of the first source and used as keys in fFieldId2QualifiedName.
0126    std::vector<std::unique_ptr<ROOT::RFieldBase>> fProtoFields;
0127    /// Columns may be requested with types other than with which they were initially added as proto fields. For example,
0128    /// a column with a `ROOT::RVec<float>` proto field may instead be requested as a `std::vector<float>`. In case this
0129    /// happens, we create an alternative proto field and store it here, with the original index in `fProtoFields` as
0130    /// key. A single column can have more than one alternative proto fields.
0131    std::unordered_map<std::size_t, std::vector<std::unique_ptr<ROOT::RFieldBase>>> fAlternativeProtoFields;
0132    /// Connects the IDs of active proto fields and their subfields to their fully qualified name (a.b.c.d).
0133    /// This enables the column reader to rewire the field IDs when the file changes (chain),
0134    /// using the fully qualified name as a search key in the descriptor of the other page sources.
0135    std::unordered_map<ROOT::DescriptorId_t, std::string> fFieldId2QualifiedName;
0136    std::vector<std::string> fColumnNames;
0137    std::vector<std::string> fColumnTypes;
0138    std::vector<std::string> fTopLevelFieldNames;
0139    /// List of column readers returned by GetColumnReaders() organized by slot. Used to reconnect readers
0140    /// to new page sources when the files in the chain change.
0141    std::vector<std::vector<ROOT::Internal::RDF::RNTupleColumnReader *>> fActiveColumnReaders;
0142 
0143    ULong64_t fSeenEntriesNoGlobalRange = 0; ///< The number of entries seen so far in GetEntryRanges()
0144 
0145    std::vector<REntryRangeDS> fCurrentRanges; ///< Basis for the ranges returned by the last GetEntryRanges() call
0146    std::vector<REntryRangeDS> fNextRanges;    ///< Basis for the ranges populated by the PrepareNextRanges() call
0147    /// Maps the first entries from the ranges of the last GetEntryRanges() call to their corresponding index in
0148    /// the fCurrentRanges vectors.  This is necessary because the returned ranges get distributed arbitrarily
0149    /// onto slots.  In the InitSlot method, the column readers use this map to find the correct range to connect to.
0150    std::unordered_map<ULong64_t, std::size_t> fFirstEntry2RangeIdx;
0151    // Keep track of the scheduled entries - necessary for processing of GlobalEntries
0152    std::vector<std::pair<ULong64_t, ULong64_t>> fOriginalRanges;
0153    /// One element per slot, corresponding to the current range index for that slot, as filled by InitSlot
0154    std::vector<std::size_t> fSlotsToRangeIdxs;
0155 
0156    /// The background thread that runs StageNextSources()
0157    std::thread fThreadStaging;
0158    /// Protects the shared state between the main thread and the I/O thread
0159    std::mutex fMutexStaging;
0160    /// Signal for the state information of fIsReadyForStaging and fHasNextSources
0161    std::condition_variable fCvStaging;
0162    /// Is true when the staging thread should start working
0163    bool fIsReadyForStaging = false;
0164    /// Is true when the staging thread has populated the next batch of files to fStagingArea
0165    bool fHasNextSources = false;
0166    /// Is true when the I/O thread should quit
0167    bool fStagingThreadShouldTerminate = false;
0168 
0169    /// \brief Holds useful information about fields added to the RNTupleDS
0170    struct RFieldInfo {
0171       ROOT::DescriptorId_t fFieldId;
0172       std::size_t fNRepetitions;
0173       // Enable `std::vector::emplace_back` for this type
0174       RFieldInfo(ROOT::DescriptorId_t fieldId, std::size_t nRepetitions)
0175          : fFieldId(fieldId), fNRepetitions(nRepetitions)
0176       {
0177       }
0178    };
0179 
0180    /// Provides the RDF column "colName" given the field identified by fieldID. For records and collections,
0181    /// AddField recurses into the sub fields. The fieldInfos argument is a list of objects holding info
0182    /// about the fields of the outer collection(s) (w.r.t. fieldId). For instance, if fieldId refers to an
0183    /// `std::vector<Jet>`, with
0184    /// ~~~{.cpp}
0185    /// struct Jet {
0186    ///    float pt;
0187    ///    float eta;
0188    /// };
0189    /// ~~~
0190    /// AddField will recurse into `Jet.pt` and `Jet.eta` and provide the two inner fields as `ROOT::VecOps::RVec<float>`
0191    /// each.
0192    ///
0193    /// In case the field is a collection of type `ROOT::VecOps::RVec`, `std::vector` or `std::array`, its corresponding
0194    /// column is added as a `ROOT::VecOps::RVec`. Otherwise, the collection field's on-disk type is used. Note, however,
0195    /// that inner record members of such collections will still be added as `ROOT::VecOps::RVec` (e.g., `std::set<Jet>
0196    /// will be added as a `std::set`, but `Jet.[pt|eta] will be added as `ROOT::VecOps::RVec<float>).
0197    void AddField(const ROOT::RNTupleDescriptor &desc, std::string_view colName, ROOT::DescriptorId_t fieldId,
0198                  std::vector<RFieldInfo> fieldInfos, bool convertToRVec = true);
0199 
0200    /// The main function of the fThreadStaging background thread
0201    void ExecStaging();
0202    /// Starting from `fNextFileIndex`, opens the next `fNSlots` files. Calls `LoadStructure()` on the opened files.
0203    /// The very first file is already available from the constructor.
0204    void StageNextSources();
0205    /// Populates fNextRanges with the next set of entry ranges. Moves files from the staging area as necessary
0206    /// and aligns ranges with cluster boundaries for scheduling the tail of files.
0207    /// Upon return, the fNextRanges list is ordered.  It has usually fNSlots elements; fewer if there
0208    /// is not enough work to give at least one cluster to every slot.
0209    void PrepareNextRanges();
0210 
0211    explicit RNTupleDS(std::unique_ptr<ROOT::Internal::RPageSource> pageSource);
0212 
0213    ROOT::RFieldBase *GetFieldWithTypeChecks(std::string_view fieldName, const std::type_info &tid);
0214 
0215    friend ROOT::RDataFrame ROOT::Internal::RDF::FromRNTuple(std::string_view ntupleName,
0216                                                             const std::vector<std::string> &fileNames,
0217                                                             const std::pair<ULong64_t, ULong64_t> &range);
0218 
0219    // This function needs to acess private members fNTupleName and fFileNames
0220    friend std::vector<std::pair<std::uint64_t, std::uint64_t>> ROOT::Internal::RDF::GetDatasetGlobalClusterBoundaries(
0221       const ROOT::RDF::RInterface<ROOT::Detail::RDF::RNodeBase> &node);
0222 
0223    explicit RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames,
0224                       const std::pair<ULong64_t, ULong64_t> &range);
0225 
0226 public:
0227    RNTupleDS(std::string_view ntupleName, std::string_view fileName);
0228    RNTupleDS(std::string_view ntupleName, const std::vector<std::string> &fileNames);
0229    // Rule of five
0230    RNTupleDS(const RNTupleDS &) = delete;
0231    RNTupleDS &operator=(const RNTupleDS &) = delete;
0232    RNTupleDS(RNTupleDS &&) = delete;
0233    RNTupleDS &operator=(RNTupleDS &&) = delete;
0234    ~RNTupleDS() final;
0235 
0236    void SetNSlots(unsigned int nSlots) final;
0237    std::size_t GetNFiles() const final { return fFileNames.empty() ? 1 : fFileNames.size(); }
0238    const std::vector<std::string> &GetColumnNames() const final { return fColumnNames; }
0239    const std::vector<std::string> &GetTopLevelFieldNames() const final { return fTopLevelFieldNames; }
0240    bool HasColumn(std::string_view colName) const final;
0241    std::string GetTypeName(std::string_view colName) const final;
0242    std::vector<std::pair<ULong64_t, ULong64_t>> GetEntryRanges() final;
0243    std::string GetLabel() final { return "RNTupleDS"; }
0244 
0245    void Initialize() final;
0246    void InitSlot(unsigned int slot, ULong64_t firstEntry) final;
0247    void FinalizeSlot(unsigned int slot) final;
0248    void Finalize() final;
0249 
0250    std::unique_ptr<ROOT::Detail::RDF::RColumnReaderBase>
0251    GetColumnReaders(unsigned int /*slot*/, std::string_view /*name*/, const std::type_info &) final;
0252 
0253    ROOT::RDF::RSampleInfo
0254    CreateSampleInfo(unsigned int,
0255                     const std::unordered_map<std::string, ROOT::RDF::Experimental::RSample *> &) const final;
0256 
0257    // Old API, unused
0258    bool SetEntry(unsigned int, ULong64_t) final { return true; }
0259 
0260 protected:
0261    Record_t GetColumnReadersImpl(std::string_view name, const std::type_info &) final;
0262 };
0263 } // namespace ROOT::RDF
0264 
0265 namespace ROOT::RDF {
0266 RDataFrame FromRNTuple(std::string_view ntupleName, std::string_view fileName);
0267 RDataFrame FromRNTuple(std::string_view ntupleName, const std::vector<std::string> &fileNames);
0268 } // namespace ROOT::RDF
0269 
0270 #endif