Skip to content

fix: ak.array_equal on unions with reordered index into list child - #4321

Open
aashirvad08 wants to merge 7 commits into
scikit-hep:mainfrom
aashirvad08:aashirvad08/union-equal
Open

aashirvad08 wants to merge 7 commits into
scikit-hep:mainfrom
aashirvad08:aashirvad08/union-equal

Conversation

@aashirvad08

Copy link
Copy Markdown
Contributor

Fixes ak.array_equal returning False for equal unions when a reordered index points into a list child
The issue was caused by derived nodes having different concrete types even though their packed representations were equivalent
This normalizes those nodes before the same_content_types check and adds regression tests covering reordered indices and nested list-record cases.

Closes #4316

@github-actions github-actions Bot added the type/fix PR title type: fix (set automatically) label Sep 2, 2026
@codecov

codecov Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 84.15%. Comparing base (bb124c1) to head (1a351f0).

Files with missing lines Patch % Lines
src/awkward/operations/ak_almost_equal.py 96.42% 1 Missing ⚠️

❌ Your patch check has failed because the patch coverage (96.42%) is below the target coverage (98.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
Files with missing lines Coverage Δ
src/awkward/operations/ak_almost_equal.py 96.29% <96.42%> (-0.04%) ⬇️

@TaiSakuma

Copy link
Copy Markdown
Member

@aashirvad08 Thank you for the PR.
#4317 is the predicate for #4316.
So #4317 is still open.
I'll take a look at this PR once #4317 is merged.

@TaiSakuma
TaiSakuma self-requested a review September 8, 2026 09:56
github-actions Bot added a commit that referenced this pull request Sep 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

The documentation preview is ready to be viewed at https://awkward-array.org/doc/pr/4321/

@@ -0,0 +1,154 @@
# BSD 3-Clause License; see https://github.com/scikit-hep/awkward/blob/main/LICENSE

from __future__ import annotations

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.

Suggested change
from __future__ import annotations

This is not used and can be removed.

@TaiSakuma

TaiSakuma commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

This PR doesn't fully resolve #4316 but fixes the reported reproducers. I can approve the PR if you replace "Closes #4316" in the PR description with something that doesn't automatically close the issue, for example, "Part of #4316".

In the next comment, I'll post which part of the issue this PR doesn't fix. If you would prefer this PR to close the issue, you can push more commits to fix the rest.

@TaiSakuma

Copy link
Copy Markdown
Member

🤖 The text below was written by Claude.


What remains of #4316. The false negative remains when the record under the list is nullable, which is the pyarrow default for a struct. There, the lazy carry turns an UnmaskedArray into an IndexedOptionArray, which packed_node does not restore. The following returns False on both main and this PR:

import pyarrow as pa
import awkward as ak

def dense(offsets, lists):
    return ak.from_arrow(
        pa.UnionArray.from_dense(
            pa.array([1, 1, 0], pa.int8()),
            pa.array(offsets, pa.int32()),
            [
                pa.array([2], pa.int64()),
                pa.array(lists, pa.list_(pa.struct([("x", pa.int64())]))),
            ],
        )
    )

a = dense([0, 1, 0], [[{"x": 1}], []])
b = dense([1, 0, 0], [[], [{"x": 1}]])
assert a.tolist() == b.tolist()
ak.array_equal(a, b)  # False

The comment in packed_node names two carries, RecordArray to IndexedArray and ListOffsetArray to ListArray; the carry above, UnmaskedArray to IndexedOptionArray, is a third.

One result changes outside #4316. With a categorical IndexedArray over records as a union child, ak.array_equal against the same records without the categorical wrapper changes from True on main to False on this PR, for both values of check_parameters:

import numpy as np
import awkward as ak

def union(child):
    return ak.Array(
        ak.contents.UnionArray(
            ak.index.Index8(np.array([0, 1, 1], np.int8)),
            ak.index.Index64(np.array([0, 0, 1])),
            [ak.contents.NumpyArray(np.array([1.5])), child],
        )
    )

def records(values):
    return ak.contents.RecordArray([ak.contents.NumpyArray(np.array(values))], ["y"])

categorical = ak.contents.IndexedArray(
    ak.index.Index64(np.array([1, 0])),
    records([5, 6]),
    parameters={"__array__": "categorical"},
)
ak.array_equal(union(categorical), union(records([6, 5])))  # main: True, this PR: False

The same comparison outside a union returns False on both main and this PR, so the new result agrees with it. ak.almost_equal returns True on both. The PR description does not mention this change, and no test in this PR covers it.

🤖 Generated with Claude Code

@aashirvad08

Copy link
Copy Markdown
Contributor Author

Thanks, both comments addressed. I’ve fixed the remaining case and added regression tests covering both issues.

This branch has not been deployed

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

Labels

type/fix PR title type: fix (set automatically)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ak.array_equal is False for equal unions with list-of-records child and reordered index

3 participants