-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support conversion to AbstractArray #747
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks very neat, thanks.
It seems it should also work with the likes of Symmetric(SA[1 2; 2 3])
— can you add a small test for that (compare a couple of the types in StaticArrayLike
).
test/abstractarray.jl
Outdated
# Issue #746 | ||
# conversion to AbstractArray changes the eltype from Int to Float64 | ||
sv = SVector(1,2) | ||
@test convert(AbstractArray{Float64}, sv) isa SVector{2,Float64} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth throwing in some @inferred
here for some of these tests. For example:
@test convert(AbstractArray{Float64}, sv) isa SVector{2,Float64} | |
@test @inferred(convert(AbstractArray{Float64}, sv)) isa SVector{2,Float64} |
src/convert.jl
Outdated
AbstractArray{T}(sa::StaticArray{S,T}) where {S,T} = sa | ||
AbstractArray{T,N}(sa::StaticArray{S,T,N}) where {S,T,N} = sa | ||
AbstractArray{T}(sa::StaticArray{S,U}) where {S,T,U} = similar_type(typeof(sa),T,Size(sa))(Tuple(sa)) | ||
AbstractArray{T,N}(sa::StaticArray{S,U,N}) where {S,T,U,N} = similar_type(typeof(sa),T,Size(sa))(Tuple(sa)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think you need the Tuple
here as the existing constructor on line 5 should take care of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. I meant to do this years ago :)
The plot thickens: I ended up testing all of the matrices in However, the other types ( julia> tri = UpperTriangular(SA[1 2; 0 3])
2×2 UpperTriangular{Int64,SArray{Tuple{2,2},Int64,2,4}}:
1 2
⋅ 3
julia> convert(AbstractArray{Float64}, tri)
2×2 UpperTriangular{Float64,MArray{Tuple{2,2},Float64,2,4}}:
1.0 2.0
⋅ 3.0 At least it does not throw an error and the result is correct, just not optimal. I've added tests for the types that do not completely work, but commented them out for now. This can not be fixed in |
Hmm, I thought about filing an issue with Julia, but it suddenly is not such a clear-cut case anymore. Construction of arrays has seen a great deal of discussion and people may have different expectations, but even regardless of that currently Worse, in fact it is breaking for StaticArrays too, because I did not realize before that this line actually works: julia> using StaticArrays
julia> convert(AbstractVector{Float64}, SVector(1, 2))
2-element MArray{Tuple{2},Float64,1,2} with indices SOneTo(2):
1.0
2.0 The original example in #746 failed not because the constructor is not defined, but because the fallback in Base did not work for the non-isbits type In my mind A simple example unrelated to static arrays is the following: julia> convert(AbstractVector{Float64}, 1:3)
3-element Array{Float64,1}:
1.0
2.0
3.0 Which method would give me |
I guess one alternative for my original use case may be to just use broadcast: julia> BigFloat.(SVector(1,2))
2-element SArray{Tuple{2},BigFloat,1,2} with indices SOneTo(2):
1.0
2.0 This typically doesn't preserve structure though: julia> Float64.(1:3)
3-element Array{Float64,1}:
1.0
2.0
3.0 |
On julia> convert(AbstractVector{Int}, SVector(1,2))
2-element SArray{Tuple{2},Int64,1,2} with indices SOneTo(2):
1
2
julia> convert(AbstractVector{Float64}, SVector(1,2))
2-element MArray{Tuple{2},Float64,1,2} with indices SOneTo(2):
1.0
2.0 The fallback conversion to a mutable container may just be because, well, one needs a mutable container in order to be able to copy data back into it. |
This is arguably a bug but not really possible to fix efficiently and generically due to Julia's semantics of immutable and mutable objects. You may want a
This is how I expect it to behave: preserving "as much as possible" about the type of the source array, but changing its |
Just to be sure, I've checked what happens in Julia itself with conversions to To test I've added a few lines (only three in total) to base/range.jl and stdlib/LinearAlgebra/src/triangular.jl, starting from Julia-1.4.0-rc2, to get the following behaviour: julia> convert(AbstractVector{Float64}, 1:5)
1.0:1.0:5.0
julia> using LinearAlgebra
julia> tri = UpperTriangular(Diagonal(1:3))
3×3 UpperTriangular{Int64,Diagonal{Int64,UnitRange{Int64}}}:
1 0 0
⋅ 2 0
⋅ ⋅ 3
julia> convert(AbstractMatrix{Float64}, tri)
3×3 UpperTriangular{Float64,Diagonal{Float64,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}}:
1.0 0.0 0.0
⋅ 2.0 0.0
⋅ ⋅ 3.0
julia> convert(AbstractMatrix{BigInt}, tri)
3×3 UpperTriangular{BigInt,Diagonal{BigInt,UnitRange{BigInt}}}:
1 0 0
⋅ 2 0
⋅ ⋅ 3 In other words, the conversion preserves the immutable range type, and both UpperTriangular and Diagonal recursively invoke The results with static vectors would be similar, but I wanted to check Julia's test suite so I went for the immutable ranges. Two small things:
|
I agree with this, the breakage will be clearly visible - and hopefully limited. It would help if there was a generic function for the following two behaviours:
The latter can be implemented purely in terms of |
test/abstractarray.jl
Outdated
# unituptri = UnitUpperTriangular(SA[1 2; 0 1]) | ||
# @test @inferred(convert(AbstractArray{Float64}, unituptri)) isa UnitUpperTriangular{Float64,SMatrix{2,2,Float64,4}} | ||
# unitlotri = UnitLowerTriangular(SA[1 0; 2 1]) | ||
# @test @inferred(convert(AbstractArray{Float64}, unitlotri)) isa UnitLowerTriangular{Float64,SMatrix{2,2,Float64,4}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can we work around this by adding new constructors, eg, Transpose(m::StaticMatrix) = ...
?
Either that or uncomment these and leave them as @test_broken
for now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The construction of the matrices is fine, fortunately, with all these types, it is just the conversion. I think that, in order to fix it here, we would have to intercept functions like AbstractMatrix{T}(A::UpperTriangular{T,<:SArray})
. That seems a bit intrusive, so I've added @test_broken
for now like you suggest here. In the meantime I did file an issue with Julia (JuliaLang/julia#34995) to hopefully fix it upstream.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defining AbstractMatrix{T}(A::UpperTriangular{T,<:SArray})
seems somewhat ok to me — we already do these kinds of things elsewhere in the package.
Having said that, it's definitely not ideal — thanks for submitting the upstream issue.
Julia Base defines conversion to the abstract types
AbstractArray{T}
andAbstractArray{T,N}
by invoking their (abstract) constructors. These fail with an error for static vectors (see #746).This pull request defines the
AbstractArray{T}
andAbstractArray{T,N}
constructors for static arrays in terms ofsimilar_type
. This enables the following:One use case is to generically change the element type of a vector, in the example above from
Int
toFloat64
.I've added tests for SVector, SMatrix, MVector and MMatrix. Perhaps there are others?
Also, should the conversions perhaps be
@inline
?