diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index a9e4259e..08d0f0a5 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -24,6 +24,8 @@ jobs: matrix: version: - "1" + - "lts" + - "pre" group: - "Core" - "Downstream" diff --git a/Project.toml b/Project.toml index 6a4a3dc1..32e4a821 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "RecursiveArrayTools" uuid = "731186ca-8d62-57ce-b412-fbd966d074cd" authors = ["Chris Rackauckas "] -version = "3.27.0" +version = "3.27.1" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" @@ -21,8 +21,8 @@ FastBroadcast = "7034ab61-46d4-4ed7-9d0f-46aef9175898" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" MonteCarloMeasurements = "0987c9cc-fe09-11e8-30f0-b96dd679fdca" -SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c" Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f" @@ -43,7 +43,7 @@ ArrayInterface = "7.6" DocStringExtensions = "0.9" FastBroadcast = "0.2.8, 0.3" ForwardDiff = "0.10.19" -GPUArraysCore = "0.1.1" +GPUArraysCore = "0.1.1, 0.2" IteratorInterfaceExtensions = "1" LinearAlgebra = "1.10" Measurements = "2.3" diff --git a/ext/RecursiveArrayToolsReverseDiffExt.jl b/ext/RecursiveArrayToolsReverseDiffExt.jl index d551992d..c82d1c79 100644 --- a/ext/RecursiveArrayToolsReverseDiffExt.jl +++ b/ext/RecursiveArrayToolsReverseDiffExt.jl @@ -24,7 +24,8 @@ end return Array(VA), Array_adjoint end -@adjoint function Base.view(A::AbstractVectorOfArray{<:ReverseDiff.TrackedReal, N}, I::Colon...) where {N} +@adjoint function Base.view( + A::AbstractVectorOfArray{<:ReverseDiff.TrackedReal, N}, I::Colon...) where {N} view_adjoint = let A = A, I = I function (y) A = recursivecopy(A) diff --git a/ext/RecursiveArrayToolsSparseArraysExt.jl b/ext/RecursiveArrayToolsSparseArraysExt.jl index f9261bac..929f63f5 100644 --- a/ext/RecursiveArrayToolsSparseArraysExt.jl +++ b/ext/RecursiveArrayToolsSparseArraysExt.jl @@ -3,7 +3,8 @@ module RecursiveArrayToolsSparseArraysExt import SparseArrays import RecursiveArrayTools -function Base.copyto!(dest::SparseArrays.AbstractCompressedVector, A::RecursiveArrayTools.ArrayPartition) +function Base.copyto!( + dest::SparseArrays.AbstractCompressedVector, A::RecursiveArrayTools.ArrayPartition) @assert length(dest) == length(A) cur = 1 @inbounds for i in 1:length(A.x) @@ -17,4 +18,4 @@ function Base.copyto!(dest::SparseArrays.AbstractCompressedVector, A::RecursiveA dest end -end \ No newline at end of file +end diff --git a/ext/RecursiveArrayToolsZygoteExt.jl b/ext/RecursiveArrayToolsZygoteExt.jl index 415c7f92..14cb9e87 100644 --- a/ext/RecursiveArrayToolsZygoteExt.jl +++ b/ext/RecursiveArrayToolsZygoteExt.jl @@ -146,7 +146,6 @@ end view(A, I...), view_adjoint end - @adjoint function Broadcast.broadcasted(::typeof(+), x::AbstractVectorOfArray, y::Union{Zygote.Numeric, AbstractVectorOfArray}) broadcast(+, x, y), ȳ -> (nothing, map(x -> Zygote.unbroadcast(x, ȳ), (x, y))...) diff --git a/src/vector_of_array.jl b/src/vector_of_array.jl index 36f95ed2..eef8625f 100644 --- a/src/vector_of_array.jl +++ b/src/vector_of_array.jl @@ -29,6 +29,7 @@ returns a vector of the series for each component, that is, `A[i,:]` for each `i A plot recipe is provided, which plots the `A[i,:]` series. There is also support for `VectorOfArray` constructed from multi-dimensional arrays + ```julia VectorOfArray(u::AbstractArray{AT}) where {T, N, AT <: AbstractArray{T, N}} ``` @@ -60,8 +61,9 @@ A[1, :] # all time periods for f(t) A.t ``` """ -mutable struct DiffEqArray{T, N, A, B, F, S, D <: Union{Nothing, ParameterTimeseriesCollection}} <: - AbstractDiffEqArray{T, N, A} +mutable struct DiffEqArray{ + T, N, A, B, F, S, D <: Union{Nothing, ParameterTimeseriesCollection}} <: + AbstractDiffEqArray{T, N, A} u::A # A <: AbstractVector{<: AbstractArray{T, N - 1}} t::B p::F @@ -177,7 +179,9 @@ function DiffEqArray(vec::AbstractVector{T}, ::NTuple{N, Int}, p = nothing, sys = nothing; discretes = nothing) where {T, N} - DiffEqArray{eltype(T), N, typeof(vec), typeof(ts), typeof(p), typeof(sys), typeof(discretes)}(vec, + DiffEqArray{ + eltype(T), N, typeof(vec), typeof(ts), typeof(p), typeof(sys), typeof(discretes)}( + vec, ts, p, sys, @@ -197,7 +201,8 @@ end function DiffEqArray(vec::AbstractVector{VT}, ts::AbstractVector, ::NTuple{N, Int}, p; discretes = nothing) where {T, N, VT <: AbstractArray{T, N}} - DiffEqArray{eltype(T), N, typeof(vec), typeof(ts), typeof(p), Nothing, typeof(discretes)}(vec, + DiffEqArray{ + eltype(T), N, typeof(vec), typeof(ts), typeof(p), Nothing, typeof(discretes)}(vec, ts, p, nothing, @@ -253,7 +258,7 @@ function DiffEqArray(vec::AbstractVector{VT}, typeof(ts), typeof(p), typeof(sys), - typeof(discretes), + typeof(discretes) }(vec, ts, p, @@ -375,19 +380,23 @@ Base.@propagate_inbounds function _getindex(A::AbstractDiffEqArray, ::NotSymboli end struct ParameterIndexingError <: Exception - sym + sym::Any end function Base.showerror(io::IO, pie::ParameterIndexingError) - print(io, "Indexing with parameters is deprecated. Use `getp(A, $(pie.sym))` for parameter indexing.") + print(io, + "Indexing with parameters is deprecated. Use `getp(A, $(pie.sym))` for parameter indexing.") end # Symbolic Indexing Methods for (symtype, elsymtype, valtype, errcheck) in [ - (ScalarSymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, Any, :(is_parameter(A, sym) && !is_timeseries_parameter(A, sym))), - (ArraySymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, Any, :(is_parameter(A, sym) && !is_timeseries_parameter(A, sym))), - (NotSymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, Union{<:Tuple, <:AbstractArray}, - :(all(x -> is_parameter(A, x) && !is_timeseries_parameter(A, x), sym))), + (ScalarSymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, Any, + :(is_parameter(A, sym) && !is_timeseries_parameter(A, sym))), + (ArraySymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, Any, + :(is_parameter(A, sym) && !is_timeseries_parameter(A, sym))), + (NotSymbolic, SymbolicIndexingInterface.SymbolicTypeTrait, + Union{<:Tuple, <:AbstractArray}, + :(all(x -> is_parameter(A, x) && !is_timeseries_parameter(A, x), sym))) ] @eval Base.@propagate_inbounds function _getindex(A::AbstractDiffEqArray, ::$symtype, ::$elsymtype, sym::$valtype, arg...) @@ -413,8 +422,9 @@ Base.@propagate_inbounds function Base.getindex(A::AbstractVectorOfArray, _arg, elsymtype = symbolic_type(eltype(_arg)) if symtype == NotSymbolic() && elsymtype == NotSymbolic() - if _arg isa Union{Tuple, AbstractArray} && any(x -> symbolic_type(x) != NotSymbolic(), _arg) - _getindex(A, symtype, elsymtype, _arg, args...) + if _arg isa Union{Tuple, AbstractArray} && + any(x -> symbolic_type(x) != NotSymbolic(), _arg) + _getindex(A, symtype, elsymtype, _arg, args...) else _getindex(A, symtype, _arg, args...) end @@ -536,9 +546,7 @@ end function Base.zero(VA::AbstractVectorOfArray) val = copy(VA) - for i in eachindex(VA.u) - val.u[i] = zero(VA.u[i]) - end + val.u = zero.(VA.u) return val end @@ -707,7 +715,7 @@ end # Tools for creating similar objects Base.eltype(::Type{<:AbstractVectorOfArray{T}}) where {T} = T -# TODO: Is there a better way to do this? + @inline function Base.similar(VA::AbstractVectorOfArray, args...) if args[end] isa Type return Base.similar(eltype(VA)[], args..., size(VA)) @@ -715,22 +723,24 @@ Base.eltype(::Type{<:AbstractVectorOfArray{T}}) where {T} = T return Base.similar(eltype(VA)[], args...) end end -@inline function Base.similar(VA::VectorOfArray, ::Type{T} = eltype(VA)) where {T} - VectorOfArray([similar(VA[:, i], T) for i in eachindex(VA.u)]) -end -# for VectorOfArray with multi-dimensional parent arrays of arrays where all elements are the same type function Base.similar(vec::VectorOfArray{ T, N, AT}) where {T, N, AT <: AbstractArray{<:AbstractArray{T}}} return VectorOfArray(similar.(Base.parent(vec))) end -# special-case when the multi-dimensional parent array is just an AbstractVector (call the old method) -function Base.similar(vec::VectorOfArray{ - T, N, AT}) where {T, N, AT <: AbstractVector{<:AbstractArray{T}}} - return Base.similar(vec, eltype(vec)) +@inline function Base.similar(VA::VectorOfArray, ::Type{T} = eltype(VA)) where {T} + VectorOfArray(similar.(VA.u, T)) end +@inline function Base.similar(VA::VectorOfArray, dims::N) where {N <: Number} + l = length(VA) + if dims <= l + VectorOfArray(similar.(VA.u[1:dims])) + else + VectorOfArray([similar.(VA.u); [similar(VA.u[end]) for _ in (l + 1):dims]]) + end +end # fill! # For DiffEqArray it ignores ts and fills only u @@ -782,6 +792,9 @@ end @inline Statistics.cor(VA::AbstractVectorOfArray; kwargs...) = cor(Array(VA); kwargs...) @inline Base.adjoint(VA::AbstractVectorOfArray) = Adjoint(VA) +# linear algebra +ArrayInterface.issingular(va::AbstractVectorOfArray) = ArrayInterface.issingular(Matrix(va)) + # make it show just like its data function Base.show(io::IO, m::MIME"text/plain", x::AbstractVectorOfArray) (println(io, summary(x), ':'); show(io, m, x.u)) @@ -892,7 +905,9 @@ for (type, N_expr) in [ else unpacked = unpack_voa(bc, i) arr_type = StaticArraysCore.similar_type(dest[:, i]) - dest[:, i] = if length(unpacked) == 1 + dest[:, i] = if length(unpacked) == 1 && length(dest[:, i]) == 1 + arr_type(unpacked[1]) + elseif length(unpacked) == 1 fill(copy(unpacked), arr_type) else arr_type(unpacked[j] for j in eachindex(unpacked)) diff --git a/test/basic_indexing.jl b/test/basic_indexing.jl index c2053159..a71100b3 100644 --- a/test/basic_indexing.jl +++ b/test/basic_indexing.jl @@ -248,13 +248,13 @@ function foo!(u) end foo!(u_matrix) foo!(u_vector) -@test all(u_matrix .== [3, 10]) +@test all(u_matrix .== [3, 10]) @test all(vec(u_matrix) .≈ vec(u_vector)) # test that, for VectorOfArray with multi-dimensional parent arrays, # broadcast and `similar` preserve the structure of the parent array @test typeof(parent(similar(u_matrix))) == typeof(parent(u_matrix)) -@test typeof(parent((x->x).(u_matrix))) == typeof(parent(u_matrix)) +@test typeof(parent((x -> x).(u_matrix))) == typeof(parent(u_matrix)) # test efficiency num_allocs = @allocations foo!(u_matrix) diff --git a/test/copy_static_array_test.jl b/test/copy_static_array_test.jl index 76b9668b..ffcfa52c 100644 --- a/test/copy_static_array_test.jl +++ b/test/copy_static_array_test.jl @@ -82,3 +82,35 @@ b = recursivecopy(a) @test a[1] == b[1] a[1] *= 2 @test a[1] != b[1] + +# Broadcasting when SVector{N} where N = 1 +a = [SVector(0.0) for _ in 1:2] +a_voa = VectorOfArray(a) +b_voa = copy(a_voa) +a_voa[1] = SVector(1.0) +a_voa[2] = SVector(1.0) +@. b_voa = a_voa +@test b_voa[1] == a_voa[1] +@test b_voa[2] == a_voa[2] + +a = [SVector(0.0) for _ in 1:2] +a_voa = VectorOfArray(a) +a_voa .= 1.0 +@test a_voa[1] == SVector(1.0) +@test a_voa[2] == SVector(1.0) + +# Broadcasting when SVector{N} where N > 1 +a = [SVector(0.0, 0.0) for _ in 1:2] +a_voa = VectorOfArray(a) +b_voa = copy(a_voa) +a_voa[1] = SVector(1.0, 1.0) +a_voa[2] = SVector(1.0, 1.0) +@. b_voa = a_voa +@test b_voa[1] == a_voa[1] +@test b_voa[2] == a_voa[2] + +a = [SVector(0.0, 0.0) for _ in 1:2] +a_voa = VectorOfArray(a) +a_voa .= 1.0 +@test a_voa[1] == SVector(1.0, 1.0) +@test a_voa[2] == SVector(1.0, 1.0) diff --git a/test/interface_tests.jl b/test/interface_tests.jl index 51b92a30..f370e21d 100644 --- a/test/interface_tests.jl +++ b/test/interface_tests.jl @@ -145,6 +145,10 @@ testva2 = similar(testva) @test typeof(testva2) == typeof(testva) @test size(testva2) == size(testva) +testva3 = similar(testva, 10) +@test typeof(testva3) == typeof(testva) +@test length(testva3) == 10 + # Fill AbstractVectorOfArray and check all testval = 3.0 fill!(testva2, testval) diff --git a/test/linalg.jl b/test/linalg.jl index cd778aa5..23fedffb 100644 --- a/test/linalg.jl +++ b/test/linalg.jl @@ -1,5 +1,5 @@ using RecursiveArrayTools, Test, Random -using LinearAlgebra +using LinearAlgebra, ArrayInterface n, m = 5, 6 bb = rand(n), rand(m) @@ -55,3 +55,5 @@ mat = Array(va) @test size(va') == (size(va', 1), size(va', 2)) == (size(va, 2), size(va, 1)) @test all(va'[i] == mat'[i] for i in eachindex(mat')) @test Array(va') == mat' + +@test !ArrayInterface.issingular(VectorOfArray([rand(2),rand(2)])) diff --git a/test/partitions_test.jl b/test/partitions_test.jl index 1b0cf3f8..2625941e 100644 --- a/test/partitions_test.jl +++ b/test/partitions_test.jl @@ -155,7 +155,6 @@ y = ArrayPartition(ArrayPartition([1], [2.0]), ArrayPartition([3], [4.0])) @test all(isnan, ArrayPartition([NaN], [NaN])) @test all(isnan, ArrayPartition([NaN], ArrayPartition([NaN]))) - # broadcasting _scalar_op(y) = y + 1 # Can't do `@inferred(_scalar_op.(x))` so we wrap that in a function: @@ -303,7 +302,7 @@ end end @testset "Scalar copyto!" begin - u = [2.0,1.0] - copyto!(u, ArrayPartition(1.0,-1.2)) - @test u == [1.0,-1.2] + u = [2.0, 1.0] + copyto!(u, ArrayPartition(1.0, -1.2)) + @test u == [1.0, -1.2] end