Skip to content

FrametoTable for Arrow backend#980

Open
arnavdham wants to merge 12 commits into
AIDASoft:masterfrom
arnavdham:feature/frameToTable
Open

FrametoTable for Arrow backend#980
arnavdham wants to merge 12 commits into
AIDASoft:masterfrom
arnavdham:feature/frameToTable

Conversation

@arnavdham

Copy link
Copy Markdown
Contributor

Description
This PR isolates and implements a frameToTable conversion layer to map PODIO Frames and collection data types to Apache Arrow Tables. The implementation maps each PODIO Frame to a single row in an Arrow Table, with individual collections mapped to List<Struct<...>> columns.

Proposed Changes
Core Translation & Registries

  1. ArrowTypeRegistry: Thread-safe global registry mapping PODIO collection type name strings to their corresponding Apache Arrow DataTypes.
  2. ArrowConverterRegistry: Thread-safe global registry mapping PODIO collection type name strings to callback functions that serialize collection buffers into Arrow arrays.
  3. ArrowFrameConverter: Implements convertFrameToTable, translating:
    • Regular collections (delegated to registered converters).
    • Subset collections (translated to reference coordinate structs with collectionID and index).
    • Frame parameters (bundled into a frame_parameters struct column containing maps of string keys to list values).

Code Generation Integration

  1. ArrowMapper.h.jinja2 & ArrowMapper.cc.jinja2: Templates that generate schema definition maps and register serializers dynamically for custom EDMs.
  2. macros/arrow.jinja2: Jinja2 serialization macros that recursively parse complex types, fixed-size arrays, and one-to-one/one-to-many relationship structures to build Arrow array representations.
  3. cpp_generator.py: Extracts structural metadata and triggers the generation of ArrowMapper files.

@arnavdham arnavdham changed the title Feature/frame to table FrametoTable for Arrow backend Jun 23, 2026
@arnavdham

arnavdham commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

Related Issue: #981

@arnavdham arnavdham marked this pull request as ready for review June 24, 2026 04:06
@arnavdham

Copy link
Copy Markdown
Contributor Author

@jmcarcell Please take a look. I'm not sure why some of the pretests fail.

  • /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/scalar.h:39:10: fatal error: arrow/util/decimal.h: No such file or directory
    39 | #include "arrow/util/decimal.h"

  • The following tests FAILED:
    6 - read_python_frame_root (Not Run)
    17 - read_python_frame_rntuple (Not Run)
    40 - write_python_frame_root (Timeout)
    41 - write_python_frame_rntuple (Timeout)
    48 - read_python_frame_sio (Not Run)
    54 - write_python_frame_sio (Timeout)

@veprbl

veprbl commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

I think, there is a missing symlink on the CVMFS:

% ls /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h
ls: cannot access /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h: No such file or directory
% ls /cvmfs/sft.cern.ch/lcg/releases/arrow/11.0.0-27569/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h
/cvmfs/sft.cern.ch/lcg/releases/arrow/11.0.0-27569/x86_64-el9-gcc13-opt/include/arrow/util/decimal.h

I will try contacting the LCG support.

@jmcarcell jmcarcell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jmcarcell Please take a look. I'm not sure why some of the pretests fail.

* /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/scalar.h:39:10: fatal error: arrow/util/decimal.h: No such file or directory
  39 | #include "arrow/util/decimal.h"

* The following tests FAILED:
  6 - read_python_frame_root (Not Run)
  17 - read_python_frame_rntuple (Not Run)
  40 - write_python_frame_root (Timeout)
  41 - write_python_frame_rntuple (Timeout)
  48 - read_python_frame_sio (Not Run)
  54 - write_python_frame_sio (Timeout)

Timeouts happen from time to time, simply ignore them. The workflow for LCG_104 is fixed now. You will need to rebase to pick #979 to avoid issues with sanitizers. I have made some comments inline and I have a few others:

  • Is mapping a Frame to a row in Arrow the natural way of doing this? What are the alternatives?
  • Values are copied through the object layer, did you think about this differently? There is arrow::Buffer::Wrap, maybe that could work?

Comment thread cmake/podioMacros.cmake Outdated
Comment thread tests/unittests/CMakeLists.txt Outdated
Comment thread src/CMakeLists.txt Outdated
Comment thread cmake/podioMacros.cmake Outdated
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
#include <nlohmann/json.hpp>

TEST_CASE("ArrowFrameConverter - convertFrameToTable Verification", "[arrow][converter]") {
podio::Frame originalFrame;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple tests that use the header write_frame.h and the function makeFrame, maybe that would be useful not to have to create it yourself, and it's quite complete. Eventually, when doing the roundtrip from Arrow to Frame, you could also use the stuff in read_frame.h in a similar way.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I wasn't aware that this existed.

Comment thread tests/unittests/test_arrow_converter.cpp
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
auto* collectionBuilder = static_cast<arrow::ListBuilder*>(builder.get());
auto* objectBuilder = static_cast<arrow::StructBuilder*>(collectionBuilder->value_builder());

auto checkStatus = [](const arrow::Status& s, const std::string& msg) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does every operation have to be run through checkStatus? Is that standard?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, this mimics ARROW_RETURN_NOT_OK. As far as I know there isn't an existing one that would throw an exception for us.

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment thread include/podio/utilities/ArrowConverterRegistry.h Outdated
Comment thread python/templates/macros/arrow.jinja2 Outdated
checkStatus(builder_{{ relation.name }}_collId->Append(relObj_{{ relation.name }}.getObjectID().collectionID), "Failed to append relation collectionID");
checkStatus(builder_{{ relation.name }}_index->Append(relObj_{{ relation.name }}.getObjectID().index), "Failed to append relation index");
} else {
checkStatus(builder_{{ relation.name }}->AppendNull(), "Failed to append null relation struct");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this case? It's possible that the values for invalid objects (-1, -1) already serve as a fine marker for this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

Comment thread python/podio_gen/cpp_generator.py Outdated
"is_mult": is_mult,
}

def _arrow_field(self, name, type_expr, nullable=True):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't need nullable, definitely not by default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appears fixed now.

@arnavdham arnavdham force-pushed the feature/frameToTable branch from 0bf0945 to 3d3eac1 Compare June 29, 2026 02:18
@arnavdham

Copy link
Copy Markdown
Contributor Author

@jmcarcell Please take a look. I'm not sure why some of the pretests fail.

* /cvmfs/sft.cern.ch/lcg/views/LCG_104/x86_64-el9-gcc13-opt/include/arrow/scalar.h:39:10: fatal error: arrow/util/decimal.h: No such file or directory
  39 | #include "arrow/util/decimal.h"

* The following tests FAILED:
  6 - read_python_frame_root (Not Run)
  17 - read_python_frame_rntuple (Not Run)
  40 - write_python_frame_root (Timeout)
  41 - write_python_frame_rntuple (Timeout)
  48 - read_python_frame_sio (Not Run)
  54 - write_python_frame_sio (Timeout)

Timeouts happen from time to time, simply ignore them. The workflow for LCG_104 is fixed now. You will need to rebase to pick #979 to avoid issues with sanitizers. I have made some comments inline and I have a few others:

  • Is mapping a Frame to a row in Arrow the natural way of doing this? What are the alternatives?
  • Values are copied through the object layer, did you think about this differently? There is arrow::Buffer::Wrap, maybe that could work?
  1. In the future, we plan to map a Category to an Arrow Table, where each row in that table represents one individual Frame belonging to that category.
    It keeps all collections in a given category grouped together in a single self-contained table, similar to how other formats (like ROOT RNTuple) structure events.

Alternative: We could store one flat Arrow Table per collection type (e.g., a HitsTable, a TracksTable) with an event_id column.But, it would require complex joins/filters to reconstruct a single frame, adding overhead.

  1. Using arrow::Buffer::Wrap for zero-copy doesn't work directly here because:
    AOS vs. SOA Layout: PODIO stores collection elements contiguously as Array-of-Structs (AOS), whereas Arrow's StructArray requires a columnar Struct-of-Arrays (SOA) layout with separate memory buffers for each field.
    Might work for primitive members but not for complex structs.

Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment thread cmake/podioMacros.cmake Outdated
@arnavdham

Copy link
Copy Markdown
Contributor Author

@jmcarcell @veprbl I have made changes according to all comments. Let me know if anything is still needed.

@veprbl veprbl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some more nitpicks on unused things.

Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
Comment thread python/templates/ArrowMapper.h.jinja2 Outdated
schemaWithMetadata(std::shared_ptr<const arrow::KeyValueMetadata> metadata) {
return schema()->WithMetadata(std::move(metadata));
}
bool registerTypes();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we don't need to expose this library initialization. We don't expect to call this externally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have removed this from the .h file but now the ArrowMapper.h is empty entirely. Do I just remove the file entirely?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if the file is not used and it is empty then it should be deleted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was removed in 64ad735

Comment thread src/ArrowFrameConverter.cc Outdated
Comment thread python/templates/ArrowMapper.cc.jinja2 Outdated
Comment thread tests/unittests/test_arrow_converter.cpp Outdated
Comment thread src/CMakeLists.txt Outdated
Comment thread python/podio_gen/cpp_generator.py Outdated

@veprbl veprbl left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@arnavdham arnavdham requested a review from jmcarcell July 7, 2026 10:24
@jmcarcell

Copy link
Copy Markdown
Member
  1. In the future, we plan to map a Category to an Arrow Table, where each row in that table represents one individual Frame belonging to that category.
    It keeps all collections in a given category grouped together in a single self-contained table, similar to how other formats (like ROOT RNTuple) structure events.

Why not do this directly? Is it more complex than the current changes?

@veprbl

veprbl commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

@jmcarcell There is an alternative option mentioned in @arnavdham's message. We believe, the interface and data layout presented in this PR is optimal for the Arrow conversion tasks (IPC, columnar processing, etc), but we haven't settled on a proposal for file writing yet.

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