Skip to content

fix: normalize negative integer slice bounds in indexing#1156

Merged
henryiii merged 4 commits into
developfrom
fix-negative-slice-index
Jun 26, 2026
Merged

fix: normalize negative integer slice bounds in indexing#1156
henryiii merged 4 commits into
developfrom
fix-negative-slice-index

Conversation

@henryiii

@henryiii henryiii commented Jun 25, 2026

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Brings NumPy-style negative and out-of-range slice indexing to boost-histogram, fixing the bug at the underlying layer (mirrors scikit-hep/hist#699, so hist and other UHI consumers get it for free).

Negative bounds

Negative slice bounds raised ValueError: begin < end required (e.g. h[1:-2]) because plain negative ints were passed straight to Boost.Histogram, which does not apply NumPy-style negative indexing. Axis._process_callable now normalizes plain negative ints relative to the axis length. Locators (bh.loc, bh.tag.at, rebin/sum steps) are callables handled earlier and stay untouched, so flow access like tag.at(-1) still reaches underflow. Scalar negative indexing (h[-1]) already worked via _compute_uhi_index.

Out-of-range bounds (clamping)

Out-of-range bounds now clamp to [0, len] like NumPy, so every non-empty slice matches NumPy exactly:

slice result
h[5:100] h[5:]
h[:100] h[:]
h[-15:5] h[:5]
h[-15:] h[:]

Empty slices

NumPy returns an empty array for slices like h[1:-15] or h[5:2], but a Boost.Histogram Regular/Variable axis cannot have zero bins. These now raise a clear IndexError explaining the limitation instead of the cryptic begin < end required / bins > 0 required. The check lives in _handle_slice, so intentional flow access via integration — e.g. h[3::sum] summing the overflow on a size-3 axis — is preserved.

Tests

tests/test_histogram_indexing.py:

  • test_negative_index_access — negative scalars, slices, one-sided, dict-form, per-axis length on 2D.
  • test_out_of_range_slice_clamps_like_numpy — extents matched against np.arange for clamped cases.
  • test_empty_slice_raisesIndexError for the empty cases, including reversed h[5:2:sum].

One pre-existing assertion in test_single_flow_bin was updated: h[3::sum] on a 3-category axis (start past the last bin) now raises the clearer IndexError instead of ValueError.

Verified slice extents match numpy across negative/out-of-range/empty cases. Full suite: 1127 passed, 1 xfailed.

Note on the Hist 🐍 3.14 CI job: any failure there is unrelated — the downstream hist test suite passes; only its trailing mypy step trips on a numpy-stubs/Python-3.14 version mismatch (numpy/__init__.pyi: Type statement is only supported in Python 3.12 and greater).

Negative slice bounds raised `ValueError: begin < end required` (e.g.
`h[1:-2]`) because plain negative ints were passed straight to
Boost.Histogram, which does not apply NumPy-style negative indexing.

`Axis._process_callable` now normalizes plain negative ints relative to
the axis length, like Python/NumPy. Locators (`bh.loc`, `bh.tag.at`,
rebin/sum steps) are callables handled earlier and stay untouched. The
fix flows through `_process_loc`, so it applies to both `__getitem__`
and `__setitem__`. Scalar negative indexing already worked via
`_compute_uhi_index`.

Mirrors scikit-hep/hist#699, fixing the bug at the underlying layer.

Assisted-by: ClaudeCode:claude-opus-4.8
@github-actions github-actions Bot added the needs changelog Might need a changelog entry label Jun 25, 2026
henryiii and others added 3 commits June 26, 2026 00:35
Out-of-range slice bounds now clamp to [0, len] like NumPy slicing
(e.g. h[5:100] -> h[5:], h[-15:5] -> h[:5]), instead of relying on
Boost's internal behavior or raising low-level errors.

NumPy returns an empty array for slices like h[1:-15] or h[5:2], but a
Boost.Histogram axis cannot have zero bins. These now raise a clear
IndexError explaining the limitation instead of the cryptic
"begin < end required" / "bins > 0 required". The check lives in
_handle_slice so that intentional flow access via integration
(e.g. h[3::sum] summing the overflow) is preserved.

Assisted-by: ClaudeCode:claude-opus-4.8
numpy 2.5's stubs use a `type X = ...` statement that mypy rejects under
the downstream hist session ("Type statement is only supported in Python
3.12 and greater"), failing the Hist 3.14 job. Pin numpy below 2.5 until
the toolchain catches up.

Assisted-by: ClaudeCode:claude-opus-4.8
@henryiii
henryiii marked this pull request as ready for review June 26, 2026 04:52
@henryiii
henryiii merged commit 25362b6 into develop Jun 26, 2026
21 checks passed
@henryiii
henryiii deleted the fix-negative-slice-index branch June 26, 2026 04:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs changelog Might need a changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant