Skip to content

Restore inferrable return type for block tensor slicing - #73

Open
lkdvos wants to merge 1 commit into
mainfrom
ld-stable-slicing
Open

Restore inferrable return type for block tensor slicing#73
lkdvos wants to merge 1 commit into
mainfrom
ld-stable-slicing

Conversation

@lkdvos

@lkdvos lkdvos commented Aug 26, 2026

Copy link
Copy Markdown
Member

Fixes a type-inference regression introduced in v0.3.16 by #72, and bumps the version to v0.3.17.

The regression

_slice_getindex chose between returning a single block and a sliced block tensor with a runtime inds isa NTuple{M, Int} check, and _slice_getindex_single reached that check with an index tuple built as ntuple(i -> i == d ? only(indices) : 1, numind(t)) — element type Union{Int, UnitRange{Int}}, so the check could not be resolved statically and the union leaked into the return type.

On an MPS-environment-shaped tensor (size (1, 3, 1)):

t[2:(end - 1)] inferred as
v0.3.15 BlockTensorMap{…}
v0.3.16 Union{BlockTensorMap{…}, TensorMap{…}}

The same applied to t[:], t[1:2:3] and t[[1, 3]]; a logical mask (t[[true, false, true]]) inferred as Any, for a second reason — a mask may consume several dimensions, so Base.to_indices cannot infer one mixed with the 1 fillers.

The fix

Make the single-block-vs-slice choice by dispatch (_slice_getindex_full), and fill the trivial dimensions with 1:1 rather than 1 so Int stays out of the index tuple. Masks are normalized to an index vector before being placed.

After the fix, every index form on every shape infers concretely — Int, UnitRange, StepRange, Colon, index vector and logical mask, single-index and full-rank, dense and sparse.

Why it matters

MPSKit's Jordan-form derivative operators store O3 = typeof(A) where A is built from GL[2:(end - 1)], so the widened slice type made AC_hamiltonian infer as the bare abstract JordanMPO_AC_Hamiltonian, and from there excitations with ChepigaAnsatz/ChepigaAnsatz2 inferred Tuple{Any, Vector} — a CI failure in MPSKit's @testinferred tests. There is no measurable runtime cost (DMRG, 20 sites, χ=32, 5 sweeps: 0.353 s on v0.3.15 vs 0.363 s on v0.3.16), so this is purely a type-stability contract regression.

Verification

  • Full test suite green on Julia 1.12.6, Aqua included (no new method ambiguities).
  • Slicing produces identical types and values to released v0.3.16 across a battery of 13 index forms × 4 tensor shapes (dense/sparse, one- and three-dimensional). The remaining diffs against v0.3.15 are all v0.3.16's intentional fixes (repeated indices, step ranges, uninitialized blocks).
  • With the fix dev'd in, MPSKit's failing excitations "finite" testset passes 25/25 (it was 21 pass / 4 fail), on both Julia 1.10.11 and 1.12.6.

Tests

test/abstracttensor/indexing.jl gains:

  • a single-index slicing testset on a multi-index tensor with one nontrivial dimension. The existing single-index tests use a genuinely one-dimensional tensor, which takes the M == numind(t) fast path, so this path had no coverage at all.
  • an indexing inference sweep asserting a concrete return type for every index form across six tensor shapes, so a future rewrite of the index handling is caught for all of them rather than the hand-picked cases.

Both fail on v0.3.16 (the sweep flags 5 of the 6 single-index forms).

🤖 Generated with Claude Code

`_slice_getindex` chose between returning a single block and a sliced block tensor with a
runtime `inds isa NTuple{M, Int}` check, and `_slice_getindex_single` reached that check with
an index tuple of element type `Union{Int, UnitRange{Int}}`, so the check could not be
resolved statically. `blocktensor[2:(end - 1)]` on a tensor with a single nontrivial
dimension therefore inferred as `Union{BlockTensorMap{...}, TensorMap{...}}` rather than
`BlockTensorMap{...}`, and a logical mask inferred as `Any`.

Make the choice by dispatch instead, and fill the trivial dimensions with `1:1` so that `Int`
stays out of the index tuple. A mask is normalized up front, since it may consume several
dimensions and `Base.to_indices` cannot infer one mixed with the fillers.

Runtime behaviour is unchanged: slicing was verified to give identical types and values to
v0.3.16 across every index form (`Int`, ranges, step ranges, `Colon`, index vectors, logical
masks; single-index and full-rank) on dense and sparse tensors of one and three dimensions.

Downstream this widened MPSKit's Jordan-form derivative operators to their abstract type,
which in turn made `excitations` with `ChepigaAnsatz`/`ChepigaAnsatz2` uninferrable.

The new tests cover the single-index path on a multi-index tensor, which had none: the
existing single-index tests use a genuinely one-dimensional tensor and so take the
`M == numind(t)` fast path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
src/tensors/abstractblocktensor/abstractarray.jl 71.71% <100.00%> (+1.91%) ⬆️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

1 participant