Skip to content

RNTuple: top-level tuple fields do not round-trip as tuples #1714

Description

@ariostas

🤖 AI text below 🤖

Split out of #1713, which now tracks only the optional-record bug.

A top-level tuple field written by uproot does not read back as a tuple. Reproduces on current main (a5f44fd) with plain uproot.recreate / uproot.open.

import awkward as ak, uproot

arr = ak.Array([(1.0, 2), (3.0, 4)])          # 2 * (float64, int64)
with uproot.recreate("tup.root") as f:
    f["t"] = arr

with uproot.open("tup.root") as f:
    form, _ = f["t"].to_akform()
    print(form.is_tuple)                       # False, expected True

The tuple comes back as a record whose field names are the strings '0' and '1'.

Why

Tuple recovery exists and works — the reader restores tuple-ness when every on-disk member name matches _[0-9]+:

https://github.com/scikit-hep/uproot5/blob/main/src/uproot/models/RNTuple.py#L693

if all(re.fullmatch(r"_[0-9]+", name) is not None for name in record_names):
    namelist = None      # namelist=None -> RecordForm is a tuple

(and the same idea at models/RNTuple.py#L635 for the collection role).

The writer applies that naming convention for nested records but not at the top level:

# _cascadentuple.py#L355 — nested
subfield_name = f"_{i}" if akform.is_tuple else akform.fields[i]

# _cascadentuple.py#L439 — top level (generate_field_col_records)
for field_name, topakform in zip(akform.fields, akform.contents, strict=True):

akform.fields on a tuple is ['0', '1'] — no underscore — so the reader's heuristic misses. Measured:

top-level  ak.Array([(1.0, 2), (3.0, 4)])
    on-disk names: ['0', '1']              is_tuple: written True -> read False
nested     ak.Array({"pair": [(1.0, 2), (3.0, 4)]})
    on-disk names: ['pair', '_0', '_1']    is_tuple: written True -> read True

Nested tuples round-trip correctly; only the top level is affected.

Possible direction

Apply the same f"_{i}" if akform.is_tuple rule in generate_field_col_records. Note this changes on-disk field names for top-level tuples, so it is a format-visible change rather than a pure bug fix.

Note

This surfaced while reviewing #1687, whose extend() form check is the first code to compare a form reloaded from disk against the original. That check refuses this shape cleanly before writing anything, so #1687 is not affected — the mismatch is pre-existing and independent of it.

Environment

  • uproot 5.7.6.dev29+g425d57430 (main @ a5f44fd)
  • awkward 2.11.0

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions