Skip to content

Fix sum(::AbstractVectorOfArray) over-constraining the element type (#595)#597

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-sum-element-type-assertion-595
May 30, 2026
Merged

Fix sum(::AbstractVectorOfArray) over-constraining the element type (#595)#597
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:fix-sum-element-type-assertion-595

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor

Please ignore until reviewed by @ChrisRackauckas. Opened as a draft.

Fixes #595.

Problem

sum(::AbstractVectorOfArray) was defined as:

function Base.sum(VA::AbstractVectorOfArray{T}) where {T}
    return sum(sum, VA.u)::T
end

The ::T assertion forces the result to equal the recursive element type T. That is wrong whenever summation does not return the element type:

  • The reported ReverseDiff failure. For VectorOfArray([tunables]) under ReverseDiff.gradient, T = TrackedReal{Float64,Float64,<:TrackedArray} — each element carries its origin TrackedArray in the third type parameter. A computed sum, however, is a fresh TrackedReal{Float64,Float64,Nothing} (no origin), so the assertion throws:

    TypeError: in typeassert, expected ReverseDiff.TrackedReal{Float64, Float64, ReverseDiff.TrackedArray{…}},
    got a value of type ReverseDiff.TrackedReal{Float64, Float64, Nothing}
    
  • It is not AD-specific. sum(VectorOfArray([Int8[1,2,3]])) also throws TypeError: expected Int8, got a value of type Int64 — ordinary integer promotion (Int8 → Int64) trips the same assertion.

Why the assertion existed

It was added in 69798c3 to stop Julia 1.10 LTS inference from widening the self-recursive sum(::AbstractVectorOfArray) (a VectorOfArray may contain VectorOfArrays) to Any, which is what @inferred sum(VA[VA[zeros(4,4)]]) guards against. Simply deleting ::T restores correctness but reintroduces that inference regression (verified: the nested case infers Any again).

Fix

Assert the type sum actually produces — the Base.add_sum accumulator type — instead of the element type:

function Base.sum(VA::AbstractVectorOfArray{T}) where {T}
    return sum(sum, VA.u)::Base.promote_op(Base.add_sum, T, T)
end

add_sum is the exact reduction operator Base.sum uses, so promote_op(add_sum, T, T) is Int64 for Int8, Float64 for Float64, and TrackedReal{…,Nothing} for ReverseDiff. promote_op is computed by inference over the scalar eltype (no VoA recursion), so it still pins the return type and keeps @inferred green on the LTS, while being correct for promoting and AD eltypes. If inference cannot determine add_sum(::T,::T) it returns Any, degrading gracefully to a no-op assertion (never wrong).

Tests

  • test/interface_tests.jl: Int8/Float32 promotion (sum(VA[Int8[1,2,3]]) === Int64(6), …) plus @inferred.
  • test/adjoints.jl: the ReverseDiff MWE from the issue (single- and nested-VoA gradients). ReverseDiff added to the test target (it is already a [weakdeps]/compat entry).

Verified locally on Julia 1.10.11, 1.11.9, 1.12.6, and 1.13.0-rc1 — all correctness and inference checks pass. Real test files pass: Partitions 115/115, Interface 139/139, Adjoint 13/13. Runic check is clean.

🤖 Generated with Claude Code

…ciML#595)

`Base.sum(VA::AbstractVectorOfArray{T})` asserted `sum(sum, VA.u)::T`, forcing
the result to equal the recursive element type `T`. That is wrong whenever
summation does not return the element type:

- integer promotion: `sum(VectorOfArray([Int8[1,2,3]]))` produces an `Int64`,
  so `::Int8` threw a `TypeError` (a regression with no AD involved);
- ReverseDiff: the eltype is `TrackedReal{V,D,<:TrackedArray}` (elements carry
  their origin array) but a computed sum is `TrackedReal{V,D,Nothing}`, so the
  assertion threw, breaking `ReverseDiff.gradient(x -> sum(VectorOfArray([x])), x)`.

The assertion was originally added (69798c3) to stop Julia 1.10's inference from
widening the self-recursive `sum(::AbstractVectorOfArray)` to `Any` on deeply
nested VectorOfArrays. Removing it restores correctness but reintroduces that
inference regression. Instead, assert the type `sum` actually produces, i.e. the
`Base.add_sum` accumulator type `Base.promote_op(Base.add_sum, T, T)`. This still
pins the return type for inference (so `@inferred sum(VA[VA[zeros(4,4)]])` keeps
passing on the LTS) while being correct for promoting and AD eltypes.

Tested on Julia 1.10, 1.11, 1.12 and 1.13-rc1.

Adds regression tests: Int8/Float32 promotion in interface_tests.jl and the
ReverseDiff MWE from the issue in adjoints.jl (ReverseDiff added to test target).

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review May 30, 2026 14:20
@ChrisRackauckas ChrisRackauckas merged commit ea14ec9 into SciML:master May 30, 2026
26 of 37 checks passed
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.

sum doesn't work for VectorOfArray{ReverseDiff.TrackedReal{..}, ...}

2 participants