Skip to content

feat: add branches to existing TTrees#1669

Open
Yokubas wants to merge 73 commits into
scikit-hep:mainfrom
Yokubas:Yokubas/add-branches
Open

feat: add branches to existing TTrees#1669
Yokubas wants to merge 73 commits into
scikit-hep:mainfrom
Yokubas:Yokubas/add-branches

Conversation

@Yokubas

@Yokubas Yokubas commented Jun 27, 2026

Copy link
Copy Markdown

This PR continues the work started by @zbilodeau in #1155, which implemented the ability to add new branches (columns) to existing TTrees in place. The initial implementation and commits are Zoë's — this PR picks up where she left off, resolves merge conflicts with the current main branch, and aims to fix the remaining corner cases and test failures before merging.
This is an initial PR — nothing new has been added or fixed yet. Work is ongoing as part of the IRIS-HEP IMPRESS-U Summer 2026 Fellowship.

zbilodea and others added 30 commits April 10, 2024 10:49
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
1215 3 1212 19
View the top 3 failed test(s) by shortest run time
tests/test_1155_feat_add_branches_to_existing_ttree.py::test_TreeEventSimple0
Stack Traces | 0.515s run time
tmp_path = PosixPath('.../pytest-of-ar1092/pytest-316/test_TreeEventSimple00')

    def test_TreeEventSimple0(tmp_path):
        with uproot.recreate(os.path.join(tmp_path, "TreeEventTreeSimple0.root")) as file:
            file.mktree(
                "TreeEventTreeSimple0",
                {"b1": np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.int64)},
            )
    
        with uproot.update(os.path.join(tmp_path, "TreeEventTreeSimple0.root")) as file:
            file.add_branches(
                "TreeEventTreeSimple0",
                {"b2": np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.int64)},
            )
    
        with uproot.open(
            os.path.join(tmp_path, "TreeEventTreeSimple0.root"),
            minimal_ttree_metadata=False,
        ) as new:
            assert ak.all(
                new["TreeEventTreeSimple0"]["b2"].array()
                == np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.int64)
            )
    
        inFile = ROOT.TFile.Open(
            os.path.join(tmp_path, "TreeEventTreeSimple0.root"), "READ"
        )
        tree = inFile.Get("TreeEventTreeSimple0;1")
        for i, x in enumerate(tree):
            assert getattr(x, "b2") == np.int64(i + 1)
        inFile.Close()
    
        df = ROOT.RDataFrame(
            "TreeEventTreeSimple0", os.path.join(tmp_path, "TreeEventTreeSimple0.root")
        )
>       npy = ak.from_rdataframe(df, columns=("b1", "b2"))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

df         = <cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>
file       = <WritableDirectory '/' at 0x15094e627bd0>
i          = 9
inFile     = <cppyy.gbl.TFile object at 0x5632c51b87e0>
new        = <ReadOnlyDirectory '/' at 0x150a4c2768f0>
tmp_path   = PosixPath('.../pytest-of-ar1092/pytest-316/test_TreeEventSimple00')
tree       = <cppyy.gbl.TTree object at 0x5632c942fd50>
x          = <cppyy.gbl.TTree object at 0x5632c942fd50>

tests/test_1155_feat_add_branches_to_existing_ttree.py:438: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:41: in dispatch
    with OperationErrorContext(name, args, kwargs):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('b1', 'b2')}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../site-packages/awkward/_errors.py:80: in __exit__
    raise self.decorate_exception(exception_type, exception_value)
        exception_type = <class 'TypeError'>
        exception_value = TypeError('Could not find "is_awkward_type<Long64_t>" (set cppyy.set_debug() for C++ errors):\n  bool awkward::is_awkward_type() =>\n    TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument')
        self       = <awkward._errors.OperationErrorContext object at 0x15094c3f24e0>
        traceback  = <traceback object at 0x15091f0c0540>
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:42: in dispatch
    gen_or_result = func(*args, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('b1', 'b2')}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:56: in from_rdataframe
    return _impl(rdf, columns, highlevel, behavior, with_name, offsets_type, keep_order)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        attrs      = None
        behavior   = None
        columns    = ('b1', 'b2')
        highlevel  = True
        keep_order = False
        offsets_type = 'int64'
        rdf        = <cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>
        with_name  = None
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:88: in _impl
    out = ak._connect.rdataframe.from_rdataframe.from_rdataframe(
        awkward    = <module 'awkward' from '.../test-env/lib/python3.13.../site-packages/awkward/__init__.py'>
        behavior   = None
        columns    = ('b1', 'b2')
        data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>
        highlevel  = True
        keep_order = False
        offsets_type = 'int64_t'
        project    = False
        with_name  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>
columns = ('b1', 'b2', 'rdfentry_'), highlevel = True, behavior = None
with_name = None, offsets_type = 'int64_t', keep_order = False

    def from_rdataframe(
        data_frame, columns, highlevel, behavior, with_name, offsets_type, keep_order
    ):
        if hasattr(data_frame, "proxied_node"):
            raise NotImplementedError("Distributed RDataFrame is not yet supported")
    
        def cpp_builder_type(depth, data_type):
            if depth == 1:
                return f"awkward::LayoutBuilder::Numpy<{data_type}>>"
            else:
                return f"awkward::LayoutBuilder::ListOffset<int64_t, {cpp_builder_type(depth - 1, data_type)}>"
    
        def cpp_fill_offsets_and_flatten(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    for (auto const& it : vec1) {
                        builder1.append(it);
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    for (auto const& vec{depth - 1} : vec{depth}) {{
                        auto& builder{depth - 1} = builder{depth}.begin_list();
                        {cpp_fill_offsets_and_flatten(depth - 1)}
                        builder{depth}.end_list();
                    }}
                    """
                )
    
        def cpp_fill_function(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_from(BUILDER& builder, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {
                        for (auto const& it : result) {
                            builder.append(it);
                        }
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_offsets_and_flatten{depth}(BUILDER& builder{depth}, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {{
                        for (auto const& vec{depth - 1} : result) {{
                          auto& builder{depth - 1} = builder{depth}.begin_list();
                          {cpp_fill_offsets_and_flatten(depth - 1)}
                          builder{depth}.end_list();
                        }}
                    }}
                    """
                )
    
        def form_dtype(form):
            if isinstance(form, ak.forms.NumpyForm) and form.inner_shape == ():
                return primitive_to_dtype(form.primitive)
            elif isinstance(form, ak.forms.ListOffsetForm):
                return form_dtype(form.content)
    
        # Register Take action for each column
        # 'Take' is a lazy action:
        column_types = {}
        result_ptrs = {}
        contents = {}
        index = {}
        awkward_type_cols = {}
    
        columns = (*columns, "rdfentry_")
        maybe_indexed = keep_order
    
        # Important note: This loop is separate from the next one
        # in order not to trigger the additional RDataFrame
        # Event loops
        for col in columns:
            column_types[col] = data_frame.GetColumnType(col)
            result_ptrs[col] = data_frame.Take[column_types[col]](col)
    
>           if ROOT.awkward.is_awkward_type[column_types[col]]():
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: Could not find "is_awkward_type<Long64_t>" (set cppyy.set_debug() for C++ errors):
E             bool awkward::is_awkward_type() =>
E               TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument
E           
E           This error occurred while calling
E           
E               ak.from_rdataframe(
E                   RDataFrame-instance
E                   columns = ('b1', 'b2')
E               )

awkward_type_cols = {}
behavior   = None
col        = 'b1'
column_types = {'b1': 'Long64_t'}
columns    = ('b1', 'b2', 'rdfentry_')
contents   = {}
cpp_builder_type = <function from_rdataframe.<locals>.cpp_builder_type at 0x150a4dac6d40>
cpp_fill_function = <function from_rdataframe.<locals>.cpp_fill_function at 0x150a4dac5120>
cpp_fill_offsets_and_flatten = <function from_rdataframe.<locals>.cpp_fill_offsets_and_flatten at 0x150a4dac7380>
data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632b1d8c960>
form_dtype = <function from_rdataframe.<locals>.form_dtype at 0x150a4dac5bc0>
highlevel  = True
index      = {}
keep_order = False
maybe_indexed = False
offsets_type = 'int64_t'
result_ptrs = {'b1': <cppyy.gbl.ROOT.RDF.RResultPtr<vector<Long64_t> > object at 0x5632c98a9f10>}
with_name  = None

.../test-env/lib/python3.13.../_connect/rdataframe/from_rdataframe.py:147: TypeError
tests/test_1155_feat_add_branches_to_existing_ttree.py::test_TreeEventSimple1
Stack Traces | 0.527s run time
tmp_path = PosixPath('.../pytest-of-ar1092/pytest-316/test_TreeEventSimple10')

    def test_TreeEventSimple1(tmp_path):
        with uproot.recreate(os.path.join(tmp_path, "TreeEventTreeSimple1.root")) as f:
            f.mktree(
                "TreeEventTreeSimple1",
                {"existing_branch": np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.float32)},
            )
    
        with uproot.update(os.path.join(tmp_path, "TreeEventTreeSimple1.root")) as file:
            file.add_branches(
                "TreeEventTreeSimple1",
                {"new_v": np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.float32)},
            )
    
        with uproot.open(
            os.path.join(tmp_path, "TreeEventTreeSimple1.root"),
            minimal_ttree_metadata=False,
        ) as new:
            assert ak.all(
                new["TreeEventTreeSimple1"]["new_v"].array()
                == np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], np.float32)
            )
    
        inFile = ROOT.TFile.Open(
            os.path.join(tmp_path, "TreeEventTreeSimple1.root"), "READ"
        )
        tree = inFile.Get("TreeEventTreeSimple1;1")
        for i, x in enumerate(tree):
            assert getattr(x, "new_v") == np.float32(i + 1)
        inFile.Close()
    
        df = ROOT.RDataFrame(
            "TreeEventTreeSimple1", os.path.join(tmp_path, "TreeEventTreeSimple1.root")
        )
>       npy = ak.from_rdataframe(df, columns=("existing_branch", "new_v"))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

df         = <cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>
f          = <WritableDirectory '/' at 0x15094e6248a0>
file       = <WritableDirectory '/' at 0x150acdb778c0>
i          = 9
inFile     = <cppyy.gbl.TFile object at 0x5632c3311940>
new        = <ReadOnlyDirectory '/' at 0x150a4cd03ee0>
tmp_path   = PosixPath('.../pytest-of-ar1092/pytest-316/test_TreeEventSimple10')
tree       = <cppyy.gbl.TTree object at 0x5632c987f600>
x          = <cppyy.gbl.TTree object at 0x5632c987f600>

tests/test_1155_feat_add_branches_to_existing_ttree.py:476: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:41: in dispatch
    with OperationErrorContext(name, args, kwargs):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('existing_branch', 'new_v')}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../site-packages/awkward/_errors.py:80: in __exit__
    raise self.decorate_exception(exception_type, exception_value)
        exception_type = <class 'TypeError'>
        exception_value = TypeError('Could not find "is_awkward_type<Float_t>" (set cppyy.set_debug() for C++ errors):\n  bool awkward::is_awkward_type() =>\n    TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument')
        self       = <awkward._errors.OperationErrorContext object at 0x15091ecbe030>
        traceback  = <traceback object at 0x15094d462040>
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:42: in dispatch
    gen_or_result = func(*args, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('existing_branch', 'new_v')}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:56: in from_rdataframe
    return _impl(rdf, columns, highlevel, behavior, with_name, offsets_type, keep_order)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        attrs      = None
        behavior   = None
        columns    = ('existing_branch', 'new_v')
        highlevel  = True
        keep_order = False
        offsets_type = 'int64'
        rdf        = <cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>
        with_name  = None
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:88: in _impl
    out = ak._connect.rdataframe.from_rdataframe.from_rdataframe(
        awkward    = <module 'awkward' from '.../test-env/lib/python3.13.../site-packages/awkward/__init__.py'>
        behavior   = None
        columns    = ('existing_branch', 'new_v')
        data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>
        highlevel  = True
        keep_order = False
        offsets_type = 'int64_t'
        project    = False
        with_name  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>
columns = ('existing_branch', 'new_v', 'rdfentry_'), highlevel = True
behavior = None, with_name = None, offsets_type = 'int64_t', keep_order = False

    def from_rdataframe(
        data_frame, columns, highlevel, behavior, with_name, offsets_type, keep_order
    ):
        if hasattr(data_frame, "proxied_node"):
            raise NotImplementedError("Distributed RDataFrame is not yet supported")
    
        def cpp_builder_type(depth, data_type):
            if depth == 1:
                return f"awkward::LayoutBuilder::Numpy<{data_type}>>"
            else:
                return f"awkward::LayoutBuilder::ListOffset<int64_t, {cpp_builder_type(depth - 1, data_type)}>"
    
        def cpp_fill_offsets_and_flatten(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    for (auto const& it : vec1) {
                        builder1.append(it);
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    for (auto const& vec{depth - 1} : vec{depth}) {{
                        auto& builder{depth - 1} = builder{depth}.begin_list();
                        {cpp_fill_offsets_and_flatten(depth - 1)}
                        builder{depth}.end_list();
                    }}
                    """
                )
    
        def cpp_fill_function(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_from(BUILDER& builder, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {
                        for (auto const& it : result) {
                            builder.append(it);
                        }
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_offsets_and_flatten{depth}(BUILDER& builder{depth}, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {{
                        for (auto const& vec{depth - 1} : result) {{
                          auto& builder{depth - 1} = builder{depth}.begin_list();
                          {cpp_fill_offsets_and_flatten(depth - 1)}
                          builder{depth}.end_list();
                        }}
                    }}
                    """
                )
    
        def form_dtype(form):
            if isinstance(form, ak.forms.NumpyForm) and form.inner_shape == ():
                return primitive_to_dtype(form.primitive)
            elif isinstance(form, ak.forms.ListOffsetForm):
                return form_dtype(form.content)
    
        # Register Take action for each column
        # 'Take' is a lazy action:
        column_types = {}
        result_ptrs = {}
        contents = {}
        index = {}
        awkward_type_cols = {}
    
        columns = (*columns, "rdfentry_")
        maybe_indexed = keep_order
    
        # Important note: This loop is separate from the next one
        # in order not to trigger the additional RDataFrame
        # Event loops
        for col in columns:
            column_types[col] = data_frame.GetColumnType(col)
            result_ptrs[col] = data_frame.Take[column_types[col]](col)
    
>           if ROOT.awkward.is_awkward_type[column_types[col]]():
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: Could not find "is_awkward_type<Float_t>" (set cppyy.set_debug() for C++ errors):
E             bool awkward::is_awkward_type() =>
E               TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument
E           
E           This error occurred while calling
E           
E               ak.from_rdataframe(
E                   RDataFrame-instance
E                   columns = ('existing_branch', 'new_v')
E               )

awkward_type_cols = {}
behavior   = None
col        = 'existing_branch'
column_types = {'existing_branch': 'Float_t'}
columns    = ('existing_branch', 'new_v', 'rdfentry_')
contents   = {}
cpp_builder_type = <function from_rdataframe.<locals>.cpp_builder_type at 0x150a4d137600>
cpp_fill_function = <function from_rdataframe.<locals>.cpp_fill_function at 0x150a4d136f20>
cpp_fill_offsets_and_flatten = <function from_rdataframe.<locals>.cpp_fill_offsets_and_flatten at 0x150a4d135da0>
data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632c9356280>
form_dtype = <function from_rdataframe.<locals>.form_dtype at 0x150a4d137420>
highlevel  = True
index      = {}
keep_order = False
maybe_indexed = False
offsets_type = 'int64_t'
result_ptrs = {'existing_branch': <cppyy.gbl.ROOT.RDF.RResultPtr<vector<float> > object at 0x5632c37ca450>}
with_name  = None

.../test-env/lib/python3.13.../_connect/rdataframe/from_rdataframe.py:147: TypeError
tests/test_1155_feat_add_branches_to_existing_ttree.py::test_ak_arrays
Stack Traces | 1.31s run time
tmp_path = PosixPath('.../pytest-of-ar1092/pytest-316/test_ak_arrays0')

    def test_ak_arrays(tmp_path):
        data = np.array(
            [
                1,
                2,
            ],
            dtype=np.int64,
        )
        data1 = np.array([2, 3, 4, 5], dtype=np.int64)
        data2 = np.array([3, 4, 5], dtype=np.int64)
    
        with uproot.recreate(os.path.join(tmp_path, "ak_test.root")) as file:
            file.mktree(
                "whatever",
                {
                    "b1": ak.Array([data, data1, data2]),
                    "b2": ak.Array([data1, data2, data]),
                },
            )
    
        with uproot.update(os.path.join(tmp_path, "ak_test.root")) as write:
            write.add_branches(
                "whatever",
                {
                    "b3": ak.Array([data2, data, data1]),
                },
            )
    
        with uproot.open(
            os.path.join(tmp_path, "ak_test.root"), minimal_ttree_metadata=False
        ) as new:
            new["whatever"].arrays()
            inFile = ROOT.TFile.Open(os.path.join(tmp_path, "ak_test.root"), "READ")
            tree = inFile.Get("whatever")
            for x in tree:
                getattr(x, "b1")
            inFile.Close()
            df3 = ROOT.RDataFrame("whatever", os.path.join(tmp_path, "ak_test.root"))
>           npy3 = ak.from_rdataframe(df3, columns=("b1", "b2", "b3"), keep_order=True)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

data       = array([1, 2])
data1      = array([2, 3, 4, 5])
data2      = array([3, 4, 5])
df3        = <cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>
file       = <WritableDirectory '/' at 0x15094de5dbe0>
inFile     = <cppyy.gbl.TFile object at 0x5632c3776ef0>
new        = <ReadOnlyDirectory '/' at 0x150ace0975f0>
tmp_path   = PosixPath('.../pytest-of-ar1092/pytest-316/test_ak_arrays0')
tree       = <cppyy.gbl.TTree object at 0x5632c51abf50>
write      = <WritableDirectory '/' at 0x15094de5cbb0>
x          = <cppyy.gbl.TTree object at 0x5632c51abf50>

tests/test_1155_feat_add_branches_to_existing_ttree.py:272: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:41: in dispatch
    with OperationErrorContext(name, args, kwargs):
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('b1', 'b2', 'b3'), 'keep_order': True}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../site-packages/awkward/_errors.py:80: in __exit__
    raise self.decorate_exception(exception_type, exception_value)
        exception_type = <class 'TypeError'>
        exception_value = TypeError('Could not find "is_awkward_type<ROOT::VecOps::RVec<Long64_t>>" (set cppyy.set_debug() for C++ errors):\n  bool awkward::is_awkward_type() =>\n    TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument')
        self       = <awkward._errors.OperationErrorContext object at 0x15091e3ab160>
        traceback  = <traceback object at 0x15091ef2b340>
.../test-env/lib/python3.13....../site-packages/awkward/_dispatch.py:42: in dispatch
    gen_or_result = func(*args, **kwargs)
                    ^^^^^^^^^^^^^^^^^^^^^
        args       = (<cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>,)
        dispatch   = <function from_rdataframe at 0x150dcbbb3c40>
        func       = <function from_rdataframe at 0x150dcbbb3b00>
        kwargs     = {'columns': ('b1', 'b2', 'b3'), 'keep_order': True}
        name       = 'ak.from_rdataframe'
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:56: in from_rdataframe
    return _impl(rdf, columns, highlevel, behavior, with_name, offsets_type, keep_order)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        attrs      = None
        behavior   = None
        columns    = ('b1', 'b2', 'b3')
        highlevel  = True
        keep_order = True
        offsets_type = 'int64'
        rdf        = <cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>
        with_name  = None
.../test-env/lib/python3.13.../awkward/operations/ak_from_rdataframe.py:88: in _impl
    out = ak._connect.rdataframe.from_rdataframe.from_rdataframe(
        awkward    = <module 'awkward' from '.../test-env/lib/python3.13.../site-packages/awkward/__init__.py'>
        behavior   = None
        columns    = ('b1', 'b2', 'b3')
        data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>
        highlevel  = True
        keep_order = True
        offsets_type = 'int64_t'
        project    = False
        with_name  = None
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>
columns = ('b1', 'b2', 'b3', 'rdfentry_'), highlevel = True, behavior = None
with_name = None, offsets_type = 'int64_t', keep_order = True

    def from_rdataframe(
        data_frame, columns, highlevel, behavior, with_name, offsets_type, keep_order
    ):
        if hasattr(data_frame, "proxied_node"):
            raise NotImplementedError("Distributed RDataFrame is not yet supported")
    
        def cpp_builder_type(depth, data_type):
            if depth == 1:
                return f"awkward::LayoutBuilder::Numpy<{data_type}>>"
            else:
                return f"awkward::LayoutBuilder::ListOffset<int64_t, {cpp_builder_type(depth - 1, data_type)}>"
    
        def cpp_fill_offsets_and_flatten(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    for (auto const& it : vec1) {
                        builder1.append(it);
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    for (auto const& vec{depth - 1} : vec{depth}) {{
                        auto& builder{depth - 1} = builder{depth}.begin_list();
                        {cpp_fill_offsets_and_flatten(depth - 1)}
                        builder{depth}.end_list();
                    }}
                    """
                )
    
        def cpp_fill_function(depth):
            if depth == 1:
                return textwrap.dedent(
                    """
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_from(BUILDER& builder, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {
                        for (auto const& it : result) {
                            builder.append(it);
                        }
                    }
                    """
                )
            else:
                return textwrap.dedent(
                    f"""
                    template<class BUILDER, typename PRIMITIVE>
                    void fill_offsets_and_flatten{depth}(BUILDER& builder{depth}, ROOT::RDF::RResultPtr<std::vector<PRIMITIVE>>& result) {{
                        for (auto const& vec{depth - 1} : result) {{
                          auto& builder{depth - 1} = builder{depth}.begin_list();
                          {cpp_fill_offsets_and_flatten(depth - 1)}
                          builder{depth}.end_list();
                        }}
                    }}
                    """
                )
    
        def form_dtype(form):
            if isinstance(form, ak.forms.NumpyForm) and form.inner_shape == ():
                return primitive_to_dtype(form.primitive)
            elif isinstance(form, ak.forms.ListOffsetForm):
                return form_dtype(form.content)
    
        # Register Take action for each column
        # 'Take' is a lazy action:
        column_types = {}
        result_ptrs = {}
        contents = {}
        index = {}
        awkward_type_cols = {}
    
        columns = (*columns, "rdfentry_")
        maybe_indexed = keep_order
    
        # Important note: This loop is separate from the next one
        # in order not to trigger the additional RDataFrame
        # Event loops
        for col in columns:
            column_types[col] = data_frame.GetColumnType(col)
            result_ptrs[col] = data_frame.Take[column_types[col]](col)
    
>           if ROOT.awkward.is_awkward_type[column_types[col]]():
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E           TypeError: Could not find "is_awkward_type<ROOT::VecOps::RVec<Long64_t>>" (set cppyy.set_debug() for C++ errors):
E             bool awkward::is_awkward_type() =>
E               TypeError: unbound method awkward::is_awkward_type must be called with a awkward instance as first argument
E           
E           This error occurred while calling
E           
E               ak.from_rdataframe(
E                   RDataFrame-instance
E                   columns = ('b1', 'b2', 'b3')
E                   keep_order = True
E               )

awkward_type_cols = {}
behavior   = None
col        = 'b1'
column_types = {'b1': 'ROOT::VecOps::RVec<Long64_t>'}
columns    = ('b1', 'b2', 'b3', 'rdfentry_')
contents   = {}
cpp_builder_type = <function from_rdataframe.<locals>.cpp_builder_type at 0x150ace12aa20>
cpp_fill_function = <function from_rdataframe.<locals>.cpp_fill_function at 0x150ace12b060>
cpp_fill_offsets_and_flatten = <function from_rdataframe.<locals>.cpp_fill_offsets_and_flatten at 0x150ace12a7a0>
data_frame = <cppyy.gbl.ROOT.RDataFrame object at 0x5632bf70e750>
form_dtype = <function from_rdataframe.<locals>.form_dtype at 0x150ace12a3e0>
highlevel  = True
index      = {}
keep_order = True
maybe_indexed = True
offsets_type = 'int64_t'
result_ptrs = {'b1': <cppyy.gbl.ROOT.RDF.RResultPtr<vector<ROOT::VecOps::RVec<Long64_t> > > object at 0x5632c02a9c30>}
with_name  = None

.../test-env/lib/python3.13.../_connect/rdataframe/from_rdataframe.py:147: TypeError

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@ianna ianna changed the title add branches to existing TTrees feat: add branches to existing TTrees Jul 4, 2026
@ianna

ianna commented Jul 4, 2026

Copy link
Copy Markdown
Member

@all-contributors please add @Yokubas for code

@allcontributors

Copy link
Copy Markdown
Contributor

@ianna

I've put up a pull request to add @Yokubas! 🎉

Yokubas and others added 21 commits July 7, 2026 10:26
…e to known bug with complex C++ class branches
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants