-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Consistency in conversion to AbstractArray{T,N}
in LinearAlgebra and Base
#34995
Comments
Following up, I have done some more tests to illustrate what I'm thinking. Changes could be limited to something like the following: using LinearAlgebra
AbstractMatrix{T}(A::Adjoint) where {T} = Adjoint(AbstractVector{T}(A.data))
AbstractMatrix{T}(A::Transpose) where {T} = Transpose(AbstractVector{T}(A.data))
for t in (:LowerTriangular, :UnitLowerTriangular, :UpperTriangular, :UnitUpperTriangular)
@eval AbstractArray{T,2}(A::$t) where {T} = $t(AbstractMatrix{T}(A.data))
end
AbstractArray{T,1}(r::AbstractRange) where {T<:Real} = convert(T, first(r)):convert(T, step(r)):convert(T, last(r))
AbstractArray{T,1}(r::AbstractUnitRange) where {T<:Real} = convert(T, first(r)):convert(T, last(r)) The point is that I've included the conversion of ranges above to show an example: julia> d = Diagonal(1:3)
3×3 Diagonal{Int64,UnitRange{Int64}}:
1 ⋅ ⋅
⋅ 2 ⋅
⋅ ⋅ 3
julia> convert(AbstractMatrix{Float64}, d)
3×3 Diagonal{Float64,StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}}:
1.0 ⋅ ⋅
⋅ 2.0 ⋅
⋅ ⋅ 3.0
julia> convert(AbstractMatrix{BigFloat}, d)
3×3 Diagonal{BigFloat,StepRangeLen{BigFloat,BigFloat,BigFloat}}:
1.0 ⋅ ⋅
⋅ 2.0 ⋅
⋅ ⋅ 3.0 The structure of the array is preserved. In contrast, the current behaviour is: julia> d = Diagonal(1:3)
3×3 Diagonal{Int64,UnitRange{Int64}}:
1 ⋅ ⋅
⋅ 2 ⋅
⋅ ⋅ 3
julia> convert(AbstractMatrix{Float64}, d)
3×3 Diagonal{Float64,Array{Float64,1}}:
1.0 ⋅ ⋅
⋅ 2.0 ⋅
⋅ ⋅ 3.0
julia> convert(AbstractMatrix{Int}, d)
3×3 Diagonal{Int64,UnitRange{Int64}}:
1 ⋅ ⋅
⋅ 2 ⋅
⋅ ⋅ 3 The structure of the range object is lost in the conversion of the element type to |
Hmm, chasing a few resulting errors in Julia's test suite, I'm beginning to see the issues here. Knowing what to look for now, I also came across #22218. There is an interplay between |
By reviewing code related to this issue I actually found a bug in which a vector is unintentionally overwritten, for which I'll file a separate issue. The bug is arguably due to the ambiguity surrounding the copy and mutability semantics of The first problem that led to this issue is that currently at least two packages in JuliaArrays (StaticArrays and LazyArrays) implement conversion to Meanwhile, I've learned more about the code in LinearAlgebra, and contrary to what I thought before a fairly modest solution exists which is not that intrusive (as far as I can tell). I'm preparing a pull request to illustrate it. Mainly, the idea is to consistently use copy_oftype(A::AbstractArray{T}, ::Type{T}) where {T} = copy(A)
copy_oftype(A::AbstractArray{T,N}, ::Type{S}) where {T,N,S} = convert(AbstractArray{S,N}, A) to copy_oftype(A::AbstractArray{T}, ::Type{T}) where {T} = copy(A)
copy_oftype(A::AbstractArray{T,N}, ::Type{S}) where {T,N,S} = copyto!(similar(A, S), A) This does look mightily scary at first, but it simply makes the new fallback of The semantics would be more orthogonal than they are currently:
That is mostly how these two functions are already used, it is just not how they are currently implemented. With the change above, immutable arrays can specialize In order to make this work, I had to define For completeness, an alternative could be to start using |
@daanhb Is this obsolete or did you close this because of no feedback? |
Sorry for the confusion @andreasnoack - I closed because #40831 was merged. I thought the PR was linked to this issue, but now I see that it wasn't. There was more than enough feedback, thanks! |
It seems that conversion to the abstract type
AbstractArray{T,N}
is not implemented consistently in the array types of LinearAlgebra. Some types (Hermitian, Symmetric and Diagonal) explicitly support it, but others (Transpose, Adjoint and the triangular types) do so only implicitly. This leads to different behaviour, especially when using immutable arrays.Perhaps this conversion is not widely used, but it is frequently useful at least to me :-) Writing
convert(AbstractArray{T,N}, myarray)
is a convenient way to generically update the element type of a matrix, while retaining its structure. It can also be used merely to ensure that an array has a certain element typeT
, and that is how it is used for example in the constructor ofDiagonal{T}
and in many other places in LinearAlgebra.Base defines the conversion
convert(AbstractArray{T,N}, myarray)
and subsequently calls the abstract constructorAbstractArray{T,N}(myarray)
. The fallback of this constructor does:copyto!(similar(myarray, T), myarray)
.This fallback will always return a mutable array, even when
myarray
was immutable. This makes sense for a fallback, otherwise the data ofmyarray
could not be copied back into the new array. But it means the result may not be optimal.An example of this fallback at work is the following:
Conversion to
AbstractVector{Int}
is fine, but changing the element type fromInt
toFloat64
suddenly makes the result a mutable array (MArray
rather thanSArray
), which may be less efficient later on. This can of course be fixed in StaticArrays and it soon will be (see JuliaArrays/StaticArrays.jl#747).However, in StaticArrays one can not fix the linear algebra types in stdlib. Fixing the example above, i.e., returning immutable
SVector
andSArray
types upon conversion, one gets the following inconsistent behaviour when usingSymmetric
orUpperTriangular
:The point is that the symmetric type retains the immutable
SArray
after conversion, but the triangular type now carries a mutableMArray
. The reason is thatSymmetric
implements theAbstractMatrix{T}(A::Symmetric)
constructor and recursively converts its data, butUpperTriangular
doesn't. The result is correct, but in this case arguably not optimal.A simpler example strictly within Base is:
This is the same fallback at work. One could argue that in some cases a better answer would be
1.0:1.0:3.0
(which is immutable).These issues are easily fixed and I'd be happy to do so. In fact I already tested what would happen, see JuliaArrays/StaticArrays.jl#747 (comment). However, that depends on whether the "fix" is actually desirable.
Is
convert(AbstractVector{Float64}, myvector)
a good way to generically change an element type?Do developers currently assume that the output after this conversion is mutable (I found exactly one example of that assumption in SparseArrays here)?
Could this affect the promotion of arrays somewhere?
There have been several issues about the construction of array, but I found none specifically on the conversion to an abstract type.
The text was updated successfully, but these errors were encountered: