I am a bit curious about the question in the title. Since it is unanswered in the docstring, I list its definition as a reference.
# Symmetric and Hermitian matrices
struct Symmetric{T,S<:AbstractMatrix{<:T}} <: AbstractMatrix{T}
data::S
uplo::Char
function Symmetric{T,S}(data, uplo::Char) where {T,S<:AbstractMatrix{<:T}}
require_one_based_indexing(data)
(uplo != 'U' && uplo != 'L') && throw_uplo()
new{T,S}(data, uplo)
end
end
Since Symmetric
is merely a “shell”, I wonder what is the purpose of T
(the design that singles it out). Consider the following example
julia> using LinearAlgebra
julia> function m() return rand(-9:.1:9, 2, 2) end
m (generic function with 1 method)
julia> A = m()
2Ă—2 Matrix{Float64}:
1.9 3.7
8.7 -1.8
julia> LinearAlgebra.symmetric_type(typeof(A[1, 1]))
Float64
julia> LinearAlgebra.symmetric_type(typeof(A))
Symmetric{Float64, Matrix{Float64}}
julia> B = [m() for i in 1:2, j in 1:2]
2Ă—2 Matrix{Matrix{Float64}}:
[8.6 2.1; 0.8 8.9] [0.7 -8.4; -2.3 -2.8]
[6.4 4.1; -5.8 1.7] [4.6 -3.0; 4.5 5.6]
julia> LinearAlgebra.symmetric_type(typeof(B[1, 1]))
Symmetric{Float64, Matrix{Float64}}
julia> LinearAlgebra.symmetric_type(typeof(B))
Symmetric{AbstractMatrix, Matrix{Matrix{Float64}}}
See the last line, AbstractMatrix
is hardly instructive.
I mean, what about
The related code is here
(and line 60 therein).
T
here is the type of the elements inside of the Symmetric
, indicated by <: AbstractMatrix{T}
(where that same T
designates the element type).
Are you perhaps looking for eltype
?
1 Like
Yes. But then, isn’t this T
redundant? making the type expression looks cumbersome. (I’ve revised the title of the topic). Is the T
indispensable? We could have infer/dispatch conveniently, e.g. with the aid of eltype
you suggested.
You need the T
so you can write <: AbstractMatrix{T}
in the definition. You can’t just do struct Symmetric{S}
or struct Symmetric{S<:AbstractMatrix{T}}
(since this last one needs T
to be defined first).
3 Likes
Why not do this
julia> struct MySymmetric{S <: AbstractMatrix{T} where T <: Union{Number, AbstractMatrix}}
data::S
end
julia> using LinearAlgebra
julia> A = rand(2, 2); B = [rand(2, 2) for i in 1:2, j in 1:2];
julia> MySymmetric(A)
MySymmetric{Matrix{Float64}}([0.26880897363440703 0.6476423313174478; 0.6105024571886544 0.1442311885018166])
julia> LinearAlgebra.Symmetric(A)
2Ă—2 Symmetric{Float64, Matrix{Float64}}:
0.268809 0.647642
0.647642 0.144231
julia> MySymmetric(B)
MySymmetric{Matrix{Matrix{Float64}}}([[0.8054973493291133 0.7597395285524011; 0.14616471160829714 0.42034533671996954] [0.737336878028752 0.8408610455649348; 0.48620788006404236 0.15674775620016002]; [0.8856275147903988 0.7373043349918851; 0.3587458066422289 0.32907006075331047] [0.8815327773307925 0.8533663448400511; 0.15092667149027095 0.18201636320297676]])
julia> LinearAlgebra.Symmetric(B)
2Ă—2 Symmetric{AbstractMatrix, Matrix{Matrix{Float64}}}:
[0.805497 0.75974; 0.75974 0.420345] [0.737337 0.840861; 0.486208 0.156748]
[0.737337 0.486208; 0.840861 0.156748] [0.881533 0.853366; 0.853366 0.182016]
julia> typeof(MySymmetric(A)) <: AbstractMatrix{Float64}
false
julia> typeof(Symmetric(A)) <: AbstractMatrix{Float64}
true
So you want to declare
struct MySymmetric{S <: AbstractMatrix{T}} <: AbstractMatrix{T} end
but that doesn’t work because T
is not defined. You can fix it by adding the extra type parameter
struct MySymmetric{T, S <: AbstractMatrix{T}} <: AbstractMatrix{T} end
4 Likes
You can define that with a local T
struct MySymmetric{S<:AbstractMatrix} end
# or equivalently
struct MySymmetric{S<:AbstractMatrix{T where T}} end
but the main problem is then still that MySymmetric{Matrix{Float64}
is never a subtype of AbstractMatrix{Float64}
as one can see in the first typeof
in the post above me. And that is something one wants for nice default dispatches for all eltypes
.
3 Likes
Yes, your last line is good. I copy-paste it here
struct MySymmetric{T, S <: AbstractMatrix{T}} <: AbstractMatrix{T} end
But the LinearAlgebra.jl define it as
struct Symmetric{T, S <: AbstractMatrix{<:T}} <: AbstractMatrix{T} end
We see that the latter uses <: T
, what is the notivation? so that
Looks a bit inconsistent.
I now understand this point, thank you.
I’m unsure if this is intentional, probably just an oversight that has not been corrected since.
There is a discussion in the relevant PR but it did not clarify the issue for me, I don’t understand the possibility they mention.
1 Like
My understanding is that it’s because of matrices of matrices (transpose
of a number has the same type, but transpose
of a matrix is of type Transpose
).
Example behavior:
julia> A = [1 2;
3 4]
2Ă—2 Matrix{Int64}:
1 2
3 4
julia> B = [[A] [A];
[A] [A]]
2Ă—2 Matrix{Matrix{Int64}}:
[1 2; 3 4] [1 2; 3 4]
[1 2; 3 4] [1 2; 3 4]
julia> C = Symmetric(B)
2Ă—2 Symmetric{AbstractMatrix, Matrix{Matrix{Int64}}}:
[1 2; 2 4] [1 2; 3 4]
[1 3; 2 4] [1 2; 2 4]
julia> C[1, 1]
2Ă—2 Symmetric{Int64, Matrix{Int64}}:
1 2
2 4
julia> C[1, 2]
2Ă—2 Matrix{Int64}:
1 2
3 4
julia> C[2, 1]
2Ă—2 transpose(::Matrix{Int64}) with eltype Int64:
1 3
2 4
(three different eltypes, all of which are <: AbstractMatrix
)
2 Likes
Do make sense. But in this case, T
is not some “eltype” but a “least common eltype”. And in your example, T
is AbstractMatrix
, indicating that the dispatch advantage is lost. (But I guess this usage is not practical)
Wait a minute. There is still a question.
The LinearAlgebra.jl defines
struct Symmetric{T, S <: AbstractMatrix{<:T}} <: AbstractMatrix{T} end
And you call C = Symmetric(B)
, where B isa Matrix{Matrix{Int64}}
.
At the time of construction (which is “earlier”), T
can surely be Matrix{Int64}
.
It’s after some manipulations (at a “later” time) that julia find AbstractMatrix
can serve as a “least common supertype” for all the eltypes.
The weird thing is: Did I tell julia that what role I want T
to play? Did I stipulate that T
should be the “least common supertype” at that “later” time? Seems I hadn’t.
Then why can’t T
also be Matrix{Int64}
?