Generate dictionary for dinamically created custom STL collections

Hello,

I’m working with a python-based framework which defines custom C structures to be saved using RDataFrame snapshots. The structures are defined during execution by calling ROOT.gInterpreter.Declare(), instead of having them defined in dedicated files. When saving the snapshots to a .root file, the following error arises:

Error in TTree::Branch: The class requested (vector<FatJetStruct>) for the branch “FatJets” is an instance of an stl collection and does not have a compiled CollectionProxy. Please generate the dictionary for this collection (vector) to avoid to write corrupted data.

I tried creating the corresponding dictionary by adding a line ROOT.gInterpreter.GenerateDictionary("vector<FatJetStruct>","vector") before snapshoting the RDataFrame. Four files are created (.cxx, .d, .pcm, .so), however, the error does not go away. Is there a way to validate that the generated dictionary? Is there a step missing in which the dictionary needs to be loaded after creation?

Attached is the .cxx file created with the GenerateDictionary() call
AutoDict_vector_FatJetStruct_.cxx (201 Bytes)

Thank you.

Cheers,
Matej


ROOT Version: 6.24/07
Platform: CentOS 7.8.2003
Compiler: g++ (GCC) 10.3.0


Hi @mroguljic ,

TL;DR:
I cannot reproduce the issue, I think we will need a minimal self-contained reproducer for your problem.

Given the following files:

// myclass.h
#ifndef MYCLASS
#define MYCLASS
struct MyClass { int data = -1; };
#endif
// foo.cpp
#include "myclass.h"
#include <ROOT/RDataFrame.hxx>
#include <TInterpreter.h>
#include <vector>

int main() {
  gInterpreter->GenerateDictionary("MyClass;std::vector<MyClass>", "myclass.h");

  {
    ROOT::RDataFrame(10)
        .Define("c",
                [](ULong64_t e) {
                  return std::vector<MyClass>{{int(e)}, {int(e)}};
                },
                {"rdfentry_"})
        .Snapshot<std::vector<MyClass>>("t", "f.root", {"c"});

    TFile f("f.root");
    auto *t = f.Get<TTree>("t");
    t->Scan();
  }

  // with jitting
  {
    ROOT::RDataFrame(10)
        .Define("c", [](ULong64_t e) { return std::vector<MyClass>{{int(e)}}; },
                {"rdfentry_"})
        .Snapshot<std::vector<MyClass>>("t", "f.root", {"c"});

    TFile f("f.root");
    auto *t = f.Get<TTree>("t");
    t->Scan();
  }
}

The following invocation works and produces the expected output both with ROOT master and with v6.24 installed via conda:

g++ -g -Wall -Wextra -Wpedantic -o "foo" "foo.cpp" $(root-config --cflags --libs) && ./foo

What am I doing differently?

Cheers,
Enrico

This is surprising. After the GenerateDictionary what does:

ROOT.TClass.GetClass("vector<FatJetStruct>").IsLoaded()

returns?

Maybe you are missing the header file for FatJetStruct, e.g. ROOT.gInterpreter.GenerateDictionary("vector<FatJetStruct>","FatJetStruct.h").

As an aside note that GenerateDictionary is good for quick testing but in general you probably don’t want to generate dictionaries as part of your program at every execution, see I/O of custom classes - ROOT and GitHub - eguiraud/root_dictionaries_tutorial: A tutorial on creating ROOT dictionaries to perform I/O of custom C++ classes .

1 Like

Hi @pcanal

.IsLoaded() returns False. I’m not sure if the generated dictionary is incorrect because of the problem described below, causing it not to load. However, no warnings or errors are printed during dictionary generation.

Hi @eguiraud

The problem is most likely in the non-existing header file. The framework I’m using defines the structure at execution time and uses gInterpreter to declare it. This is probably done this way because the content of the desired structure may change depending on the type of rootfile used (more specifically, NanoAOD version).

Is there a way to generate and load a dictionary for a class which only exists in memory (i.e., there is no corresponding header file)?

If not, a routine could be added to the framework which would write a header file and generate a dictionary if it does not exist. The linked tutorials should be instructive enough to help me with that.

Thanks for all the help.

Cheers,
Matej

Hi Matej,
I see, so you jit the definition of the class and then want to perform I/O of that type. I would expect this to be possible in principle, but I could not make it work either. I tried e.g.

#include <ROOT/RDataFrame.hxx>
#include <TInterpreter.h>

int main() {
  gInterpreter->Declare(R"(#pragma link C++ class MyClass;
#pragma link C++ class vector<MyClass>;

struct MyClass { int data = -1; };)");

  {
    ROOT::RDataFrame(10)
        .Define("c",
                "std::vector<MyClass>{{int(rdfentry_)}, {int(rdfentry_)}};")
        .Snapshot("t", "f.root", {"c"});

    TFile f("f.root");
    auto *t = f.Get<TTree>("t");
    t->Scan();
  }
}

Maybe @pcanal or @Axel can suggest the right spell (or otherwise they could confirm that this is not possible and you need a separate header that you can pass to GenerateDictionary).

Cheers,
Enrico

I’ll make a workaround which creates an appropriate header file and a dictionary from it.

Thanks for the help.

Just to confirm, ROOT currently does not support I/O of interpreted classes.