-
-
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
Define copy!, setindex!, and arithmetic for Symmetric and Hermitian #19228
Conversation
@@ -133,6 +133,8 @@ convert{T}(::Type{AbstractMatrix{T}}, A::Hermitian) = Hermitian(convert(Abstract | |||
|
|||
copy{T,S}(A::Symmetric{T,S}) = (B = copy(A.data); Symmetric{T,typeof(B)}(B,A.uplo)) | |||
copy{T,S}(A::Hermitian{T,S}) = (B = copy(A.data); Hermitian{T,typeof(B)}(B,A.uplo)) | |||
copy!(dest::Symmetric, src::Symmetric) = copy!(dest.data, src.data) | |||
copy!(dest::Hermitian, src::Hermitian) = copy!(dest.data, src.data) |
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 is not correct if uplo differs
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.
Good catch! Will fix, thanks!
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.
should write a test too that would have caught that
@@ -56,6 +56,10 @@ ishermitian(J::UniformScaling) = isreal(J.λ) | |||
(-)(B::BitArray{2}, J::UniformScaling) = Array(B) - J | |||
(-)(J::UniformScaling, B::BitArray{2}) = J - Array(B) | |||
|
|||
for t in (:Symmetric, :Hermitian), op in (:+, :-) | |||
@eval ($op)(X::$t, J::UniformScaling) = ($t)(($op)(X.data, J), Symbol(X.uplo)) |
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.
should test that Hermitian plus a complex UniformScaling with nonzero imag component currently should error
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.
Done. Thanks!
eb2b3d7
to
1029227
Compare
While I'm at it, would it make sense to implement other operators on |
|
It appears that operations on |
That sounds useful. |
Okay, can do. |
@@ -133,6 +133,8 @@ convert{T}(::Type{AbstractMatrix{T}}, A::Hermitian) = Hermitian(convert(Abstract | |||
|
|||
copy{T,S}(A::Symmetric{T,S}) = (B = copy(A.data); Symmetric{T,typeof(B)}(B,A.uplo)) | |||
copy{T,S}(A::Hermitian{T,S}) = (B = copy(A.data); Hermitian{T,typeof(B)}(B,A.uplo)) | |||
copy!(dest::Symmetric, src::Symmetric) = copy!(dest.data, src.data) |
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 copy method should return the wrapped version, i.e. something like (copy!(dest.data, src.data);dest)
@@ -56,6 +56,10 @@ ishermitian(J::UniformScaling) = isreal(J.λ) | |||
(-)(B::BitArray{2}, J::UniformScaling) = Array(B) - J | |||
(-)(J::UniformScaling, B::BitArray{2}) = J - Array(B) | |||
|
|||
for t in (:Symmetric, :Hermitian), op in (:+, :-) | |||
@eval ($op)(X::$t, J::UniformScaling) = ($t)(($op)(X.data, J), Symbol(X.uplo)) |
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.
Did you try to define a setindex!
method for Symmetric
first? I think it might be sufficient.
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.
Yep, that seems to have done the trick. I was able to remove all of the special methods for Symmetric
and Hermitian
with UniformScaling
. Thanks!
Symmetric is a view wrapper type though, the inactive triangle should never be read from so its contents don't need to be symmetric |
1029227
to
93a1f58
Compare
@@ -107,6 +107,24 @@ end | |||
r | |||
end | |||
|
|||
function setindex!(A::Symmetric, v, i::Integer, j::Integer) | |||
if (A.uplo == 'U' && i >= j) || (A.uplo == 'L' && i <= j) |
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 this is right. It should only be allowed to modify the diagonal. Modifying off-diagonals will effectively change two values at a time so I think it should be an error.
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.
That makes sense. Then I'm back to defining specific methods for things, since only being able to set the diagonal doesn't allow more general operations, but maybe that's fine.
cc19d10
to
864aa15
Compare
Okay, I've improved my initial implementation of Thanks so much for your help, everyone! |
@@ -218,6 +248,10 @@ A_mul_B!{T<:BlasComplex,S<:StridedMatrix}(C::StridedMatrix{T}, A::StridedMatrix{ | |||
*(A::HermOrSym, B::HermOrSym) = full(A)*full(B) | |||
*(A::StridedMatrix, B::HermOrSym) = A*full(B) | |||
|
|||
for T in (:Symmetric, :Hermitian), op in (:+, :-, :*, :/) | |||
@eval ($op)(A::$T, x::Number) = ($T)(($op)(A.data, x), Symbol(A.uplo)) |
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 Hermitian
operations should probably be restricted to operations with x::Real
.
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 had thought about that, but there are cases where operating with a complex value could still produce a real diagonal. But restricting to Real
does seem the safer option.
|
||
function setindex!(A::Hermitian, v, i::Integer, j::Integer) | ||
i == j || throw(ArgumentError("Cannot set a non-diagonal index in a Hermitian matrix")) | ||
isreal(v) || throw(ArgumentError("Cannot set a nonreal value to the diagonal in a Hermitian matrix")) |
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.
little backwards? cannot set a diagonal entry to a non-real value?
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.
Good call. Fixed. Thanks!
864aa15
to
832416c
Compare
The CI failure appears to be unrelated. (One of the tests in the ambiguity test set is returning a non-boolean value.) |
That's a messy hack so when that test fails it tells you what the ambiguities were. But it's related. |
Ohhhh okay, so the |
Any idea what I could do to ameliorate that? As long as |
Probably have to define some ambiguity-resolving methods for the Bool case where the existing method and yours intersect? |
832416c
to
3009d49
Compare
That seems to have done the trick. Thanks so much, @tkelman! |
Looking better now, @andreasnoack? |
@@ -250,3 +250,36 @@ let a = randn(2,2) | |||
cc = copy(c) | |||
@test conj!(c) == conj(Array(c)) | |||
end | |||
|
|||
# 19225 | |||
let X = sparse([1 -1; -1 1]) |
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 think these should to into the sparse tests for modularity. However, you might want to test the functionality with dense arrays as well.
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 removed the sparse
wrapper for these tests, then copied a simplified version of what's currently here to the sparse tests. Let me know if the format looks okay.
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.
See the comment at line 255
All good now? |
Bump. @andreasnoack |
…uliaLang#19228) * Define copy! and setindex! for Symmetric and Hermitian * Define arithmetic operations on Symmetric/Hermitian with Number * Add a test for the ambiguous case * Separate sparse and dense tests
@@ -107,6 +107,21 @@ end | |||
r | |||
end | |||
|
|||
function setindex!(A::Symmetric, v, i::Integer, j::Integer) | |||
i == j || throw(ArgumentError("Cannot set a non-diagonal index in a symmetric matrix")) |
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 change doesn't make sense to me. Why shouldn't you be able to set an off diagonal element?
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.
Then it wouldn't be symmetric anymore.
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 is a symmetric wrapper so it is implicitly symmetric no matter what you do with it. Setting A[i,j] = v
is the same as A[i,j] = A[j,i] = v
.
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 problem then is that the behavior of setindex!
differs from everywhere else in that it sets multiple values at once.
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.
Yeah. We discussed that and it was considered too surprising. Are you considering a generic case where modifying the underlying buffer would be inconvenient?
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'm not sure what you mean by the right and wrong sides of a diagonal. Doesn't that only apply to triangular matrices?
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 mean the .uplo
field.
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.
Only being able to assign to the upper or lower triangle depending on the value of uplo
also breaks the abstraction; the user shouldn't have to care about which triangle is stored since all they want to know about it is that it's symmetric.
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.
we could introduce a symset! function, or something similar, that generically changes values in a way that maintains symmetry.
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 usefulness of a symset!
not withstanding, we could also think about permitting setindex!
operations that maintain symmetry. E.g. A[:,:] = 0
might be expected to work. Also A[[CartesianIndex(1,2), CartesianIndex(2,1)]] = 1
, although symset!
would of course be nicer for this case.
Just leaving a note here that this broke the following: julia> A = rand(2, 2); S = Symmetric(A); D = Diagonal(rand(2));
julia> S*D
ERROR: ArgumentError: Cannot set a non-diagonal index in a symmetric matrix
Stacktrace:
[1] setindex!(::Symmetric{Float64,Array{Float64,2}}, ::Float64, ::Int64, ::Int64) at ./linalg/symmetric.jl:126
[2] scale!(::Symmetric{Float64,Array{Float64,2}}, ::Symmetric{Float64,Array{Float64,2}}, ::Array{Float64,1}) at ./linalg/matmul.jl:19
[3] *(::Symmetric{Float64,Array{Float64,2}}, ::Diagonal{Float64}) at ./linalg/diagonal.jl:152
julia> D*S
ERROR: ArgumentError: Cannot set a non-diagonal index in a symmetric matrix
Stacktrace:
[1] setindex!(::Symmetric{Float64,Array{Float64,2}}, ::Float64, ::Int64, ::Int64) at ./linalg/symmetric.jl:126
[2] scale!(::Symmetric{Float64,Array{Float64,2}}, ::Array{Float64,1}, ::Symmetric{Float64,Array{Float64,2}}) at ./linalg/matmul.jl:34
[3] *(::Diagonal{Float64}, ::Symmetric{Float64,Array{Float64,2}}) at ./linalg/diagonal.jl:154 which I stumbled upon trying to add tests for #22396 |
Fixes JuliaLang/LinearAlgebra.jl#383
This implements
copy!
forSymmetric
andHermitian
types, as well as+
and-
forSymmetric
andHermitian
withUniformScaling
.