RecursiveArrayTools.jl icon indicating copy to clipboard operation
RecursiveArrayTools.jl copied to clipboard

Issue with broadcasting a `VectorOfArray` with differently sized vectors

Open JoshuaLampert opened this issue 8 months ago • 2 comments

Describe the bug 🐞

Setting a slice of a VectorOfArray, where the inner vectors have different sizes, gives an error, see below.

Expected behavior

It should work.

Minimal Reproducible Example 👇

julia> f = VectorOfArray([[1.0], [2.0, 3.0]])
VectorOfArray{Float64,2}:
2-element Vector{Vector{Float64}}:
 [1.0]
 [2.0, 3.0]

julia> f[:, 1] .= [2.0] # This is as expected
1-element view(::VectorOfArray{Float64, 2, Vector{Vector{Float64}}}, :, 1) with eltype Float64:
 2.0

julia> f[:, 2] .= [4.0, 5.0] # This errors (see below)

Error & Stacktrace ⚠️

julia> f[:, 2] .= [4.0, 5.0]
ERROR: DimensionMismatch: array could not be broadcast to match destination
Stacktrace:
 [1] check_broadcast_shape
   @ ./broadcast.jl:552 [inlined]
 [2] check_broadcast_axes
   @ ./broadcast.jl:555 [inlined]
 [3] instantiate
   @ ./broadcast.jl:310 [inlined]
 [4] materialize!
   @ ./broadcast.jl:883 [inlined]
 [5] materialize!(dest::SubArray{Float64, 1, VectorOfArray{…}, Tuple{…}, false}, bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{…}, Nothing, typeof(identity), Tuple{…}})
   @ Base.Broadcast ./broadcast.jl:880
 [6] top-level scope
   @ REPL[100]:1
Some type information was truncated. Use `show(err)` to see complete types.

Environment (please complete the following information):

  • Output of using Pkg; Pkg.status()
Status `/tmp/jl_U8APBX/Project.toml`
  [731186ca] RecursiveArrayTools v3.33.0
  • Output of using Pkg; Pkg.status(; mode = PKGMODE_MANIFEST)
Status `/tmp/jl_U8APBX/Manifest.toml`
  [7d9f7c33] Accessors v0.1.42
  [79e6a3ab] Adapt v4.3.0
  [4fba245c] ArrayInterface v7.19.0
  [a33af91c] CompositionsBase v0.1.2
  [187b0558] ConstructionBase v1.5.8
  [a8cc5b0e] Crayons v4.1.1
  [9a962f9c] DataAPI v1.16.0
  [e2d170a0] DataValueInterfaces v1.0.0
  [ffbed154] DocStringExtensions v0.9.4
  [e2ba6199] ExprTools v0.1.10
  [46192b85] GPUArraysCore v0.2.0
  [3587e190] InverseFunctions v0.1.17
  [82899510] IteratorInterfaceExtensions v1.0.0
  [b964fa9f] LaTeXStrings v1.4.0
  [1914dd2f] MacroTools v0.5.16
  [bac558e1] OrderedCollections v1.8.1
⌅ [aea7be01] PrecompileTools v1.2.1
  [21216c6a] Preferences v1.4.3
  [08abe8d2] PrettyTables v2.4.0
  [3cdcf5f2] RecipesBase v1.3.4
  [731186ca] RecursiveArrayTools v3.33.0
  [189a3867] Reexport v1.2.2
  [ae029012] Requires v1.3.1
  [7e49a35a] RuntimeGeneratedFunctions v0.5.15
  [1e83bf80] StaticArraysCore v1.4.3
  [10745b16] Statistics v1.11.1
  [892a3eda] StringManipulation v0.4.1
  [2efcf032] SymbolicIndexingInterface v0.3.40
  [3783bdb8] TableTraits v1.0.1
  [bd369af6] Tables v1.12.0
  [56f22d72] Artifacts v1.11.0
  [2a0f44e3] Base64 v1.11.0
  [ade2ca70] Dates v1.11.0
  [8f399da3] Libdl v1.11.0
  [37e2e46d] LinearAlgebra v1.11.0
  [d6f4376e] Markdown v1.11.0
  [de0858da] Printf v1.11.0
  [9a3f8284] Random v1.11.0
  [ea8e919c] SHA v0.7.0
  [9e88b42a] Serialization v1.11.0
  [fa267f1f] TOML v1.0.3
  [cf7118a7] UUIDs v1.11.0
  [4ec0a83e] Unicode v1.11.0
  [e66e0078] CompilerSupportLibraries_jll v1.1.1+0
  [4536629a] OpenBLAS_jll v0.3.27+1
  [8e850b90] libblastrampoline_jll v5.11.0+0
Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
  • Output of versioninfo()
Julia Version 1.11.5
Commit 760b2e5b739 (2025-04-14 06:53 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 12 × 13th Gen Intel(R) Core(TM) i5-1345U
  WORD_SIZE: 64
  LLVM: libLLVM-16.0.6 (ORCJIT, goldmont)
Threads: 1 default, 0 interactive, 1 GC (on 12 virtual cores)

Additional context

Similar to #453 and https://github.com/SciML/OrdinaryDiffEq.jl/issues/2728, but these might also be different issues.

JoshuaLampert avatar May 30 '25 12:05 JoshuaLampert

Maybe it's related that

julia> f = VectorOfArray([[1.0], [2.0, 3.0]])
VectorOfArray{Float64,2}:
2-element Vector{Vector{Float64}}:
 [1.0]
 [2.0, 3.0]

julia> size(f)
(1, 2)

julia> f[end, 2]
2.0

It's not clear to me what size should return in the case of differently sized vectors. Also end is not working as one would expect/hope for. Are differently sized vectors in a VectorOfArray even supposed to be supported? IMHO, it would be nice if they would.

JoshuaLampert avatar Jun 01 '25 20:06 JoshuaLampert

Are differently sized vectors in a VectorOfArray even supposed to be supported? IMHO, it would be nice if they would.

Yes it is. But the Julia AbstractArray interface does not support it. This is why VectorOfArray is not an AbstractArray. But some of the core Julia code makes this assumption, somewhat accidentally, so we need to avoid the fallbacks that do this. We generally do that pretty well, printing and whatnot all have workarounds. But I think broadcast is the one piece of code where we fallback to "treat it like a vector" and that can go wrong. We might need a full rewrite of the broadcast system here: the ComponentArrays code does the recursion better and we should adopt that.

ChrisRackauckas avatar Jun 20 '25 12:06 ChrisRackauckas

Thanks for letting Claude work on #453. Good news is that #494 not only fixed #453, but also this issue. I just tested on master:

julia> using RecursiveArrayTools
Precompiling RecursiveArrayTools finished.
  2 dependencies successfully precompiled in 2 seconds. 43 already precompiled.

julia> f = VectorOfArray([[1.0], [2.0, 3.0]])
VectorOfArray{Float64,2}:
2-element Vector{Vector{Float64}}:
 [1.0]
 [2.0, 3.0]

julia> f[:, 2] .= [4.0, 5.0]
2-element view(::VectorOfArray{Float64, 2, Vector{Vector{Float64}}}, Base.OneTo(2), 2) with eltype Float64:
 4.0
 5.0

julia> f
VectorOfArray{Float64,2}:
2-element Vector{Vector{Float64}}:
 [1.0]
 [4.0, 5.0]

JoshuaLampert avatar Oct 24 '25 15:10 JoshuaLampert