Skip to content

Consistency in conversion to AbstractArray{T,N} in LinearAlgebra and Base #34995

Closed
@daanhb

Description

@daanhb

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 type T, and that is how it is used for example in the constructor of Diagonal{T} and in many other places in LinearAlgebra.

Base defines the conversion convert(AbstractArray{T,N}, myarray) and subsequently calls the abstract constructor AbstractArray{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 of myarray 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:

julia> using StaticArrays

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

Conversion to AbstractVector{Int} is fine, but changing the element type from Int to Float64 suddenly makes the result a mutable array (MArray rather than SArray), 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 and SArray types upon conversion, one gets the following inconsistent behaviour when using Symmetric or UpperTriangular:

julia> s = Symmetric(SA[1 2; 2 3])
2×2 Symmetric{Int64,SArray{Tuple{2,2},Int64,2,4}}:
 1  2
 2  3

julia> convert(AbstractMatrix{Float64}, s)
2×2 Symmetric{Float64,SArray{Tuple{2,2},Float64,2,4}}:
 1.0  2.0
 2.0  3.0

julia> tri = UpperTriangular(SA[1 2; 0 3])
2×2 UpperTriangular{Int64,SArray{Tuple{2,2},Int64,2,4}}:
 1  2
   3

julia> convert(AbstractMatrix{Float64}, tri)
2×2 UpperTriangular{Float64,MArray{Tuple{2,2},Float64,2,4}}:
 1.0  2.0
     3.0

The point is that the symmetric type retains the immutable SArray after conversion, but the triangular type now carries a mutable MArray. The reason is that Symmetric implements the AbstractMatrix{T}(A::Symmetric) constructor and recursively converts its data, but UpperTriangular doesn't. The result is correct, but in this case arguably not optimal.

A simpler example strictly within Base is:

julia> convert(AbstractVector{Float64}, 1:3)
3-element Array{Float64,1}:
 1.0
 2.0
 3.0

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions