Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions src/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,34 @@ issymmetric(A::Hermitian{<:Real}) = true
issymmetric(A::Hermitian{<:Complex}) = isreal(A)
issymmetric(A::Symmetric) = true

# check if the symmetry is known from the type
_issymmetric(::Union{SymSymTri, Hermitian{<:Real}}) = true
_issymmetric(::Any) = false
"""
issymmetrictype(T::Type)

Return whether every instance `x` of the type `T` satisfies `issymmetric(x) == tue`,
that is, the fact that the instance is symmetric is known from its type.

!!! note
An instance `x::T` may still be symmetric when `issymmetrictype(T)` returns `false`.
"""
issymmetrictype(::Type) = false
issymmetrictype(::Type{<:Union{Symmetric,Hermitian{<:Real}}}) = true
issymmetrictype(::Type{<:Real}) = true
issymmetrictype(::Type{<:AbstractFloat}) = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be

issymmetrictype(::Type{<:Number}) = true

?

Copy link
Member Author

@jishnub jishnub Sep 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception is NaN, which isn't equal to itself. I didn't want to make this more generic in case the number types contain NaN as a field.

issymmetrictype(::Type{Complex{T}}) where {T} = issymmetrictype(T)

"""
ishermitiantype(T::Type)

Return whether every instance `x` of the type `T` satisfies `ishermitian(x) == tue`,
that is, the fact that the instance is hermitian is known from its type.

!!! note
An instance `x::T` may still be hermitian when `ishermitiantype(T)` returns `false`.
"""
ishermitiantype(::Type) = false
ishermitiantype(::Type{<:Union{Symmetric{<:Real},Hermitian}}) = true
ishermitiantype(::Type{<:Real}) = true
ishermitiantype(::Type{<:AbstractFloat}) = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this AbstractFloat rule?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to stay consistent with the current behavior

julia> issymmetric(NaN)
false

Copy link
Member

@stevengj stevengj Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That seems highly questionable to me, as opposed to issymmetric(::Number) = true and ishermitian(x::Number) = isreal(x).

e.g. we have have a type-based issymmetric test for floating-point Symmetric matrices, even though they can also have NaN values.

julia> A = Symmetric(fill(NaN, 3,3))
3×3 Symmetric{Float64, Matrix{Float64}}:
 NaN  NaN  NaN
 NaN  NaN  NaN
 NaN  NaN  NaN

julia> A == A'
false

julia> issymmetric(A)
true


adjoint(A::Hermitian) = A
transpose(A::Symmetric) = A
Expand Down
4 changes: 2 additions & 2 deletions src/tridiag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ function (::Type{SymTri})(A::AbstractMatrix) where {SymTri <: SymTridiagonal}
checksquare(A)
du = diag(A, 1)
d = diag(A)
if !(_issymmetric(A) || _checksymmetric(d, du, diag(A, -1)))
if !(issymmetrictype(typeof(A)) || _checksymmetric(d, du, diag(A, -1)))
throw(ArgumentError("matrix is not symmetric; cannot convert to SymTridiagonal"))
end
return SymTri(d, du)
end

_checksymmetric(d, du, dl) = all(((x, y),) -> x == transpose(y), zip(du, dl)) && all(issymmetric, d)
_checksymmetric(A::AbstractMatrix) = _issymmetric(A) || _checksymmetric(diagview(A), diagview(A, 1), diagview(A, -1))
_checksymmetric(A::AbstractMatrix) = issymmetrictype(typeof(A)) || _checksymmetric(diagview(A), diagview(A, 1), diagview(A, -1))

SymTridiagonal{T,V}(S::SymTridiagonal{T,V}) where {T,V<:AbstractVector{T}} = S
SymTridiagonal{T,V}(S::SymTridiagonal) where {T,V<:AbstractVector{T}} =
Expand Down
22 changes: 22 additions & 0 deletions test/symmetric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1343,4 +1343,26 @@ end
@test_throws msg LinearAlgebra.fillband!(Symmetric(A), 2, 0, 1)
end

@testset "issymmetrictype/ishermitiantype" begin
fsym(x) = Val(LinearAlgebra.issymmetrictype(typeof(x)))
@test @inferred(fsym(Symmetric(ones(2,2)))) == Val(true)
@test @inferred(fsym(Symmetric(ones(ComplexF64,2,2)))) == Val(true)
@test @inferred(fsym(Hermitian(ones(2,2)))) == Val(true)
@test @inferred(fsym(Hermitian(ones(ComplexF64,2,2)))) == Val(false)
@test @inferred(fsym(1)) == Val(true)
@test @inferred(fsym(1.0)) == Val(false)
@test @inferred(fsym(complex(1))) == Val(true)
@test @inferred(fsym(complex(1.0))) == Val(false)

fherm(x) = Val(LinearAlgebra.ishermitiantype(typeof(x)))
@test @inferred(fherm(Symmetric(ones(2,2)))) == Val(true)
@test @inferred(fherm(Symmetric(ones(ComplexF64,2,2)))) == Val(false)
@test @inferred(fherm(Hermitian(ones(2,2)))) == Val(true)
@test @inferred(fherm(Hermitian(ones(ComplexF64,2,2)))) == Val(true)
@test @inferred(fherm(1)) == Val(true)
@test @inferred(fherm(1.0)) == Val(false)
@test @inferred(fherm(complex(1))) == Val(false)
@test @inferred(fherm(complex(1.0))) == Val(false)
end

end # module TestSymmetric
4 changes: 0 additions & 4 deletions test/tridiag.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1155,10 +1155,6 @@ end

@testset "SymTridiagonal from Symmetric" begin
S = Symmetric(reshape(1:9, 3, 3))
@testset "helper functions" begin
@test LinearAlgebra._issymmetric(S)
@test !LinearAlgebra._issymmetric(Array(S))
end
ST = SymTridiagonal(S)
@test ST == SymTridiagonal(diag(S), diag(S,1))
S = Symmetric(Tridiagonal(1:3, 1:4, 1:3))
Expand Down