-
-
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
Make behavior of ^(::Matrix{<:Integer},::Integer)
consistent with behavior of ^(::Integer, ::Integer)
#23368
Make behavior of ^(::Matrix{<:Integer},::Integer)
consistent with behavior of ^(::Integer, ::Integer)
#23368
Conversation
… Also enure that the same promotion rules as `^(::S, ::T)`
… Also enure that the same promotion rules as `^(::S, ::T)`
…ayer/julia into afniedermayer/fix23366
Thanks for opening the PR. I think it would be a good idea simply to make Lines 177 to 178 in 7a1156c
isone instead of looping as you do here. I think it is fine to use isone also when testing the negative identity. It is a corner case.
|
@andreasnoack , thanks for reviewing this PR. Sure, simply using function _isabsone(A) # checks whether the absolute value of A isone
m, n = size(A)
if m != n || !isdiag(A)
return false
end
is_eye, is_minus_eye = true, true
for i = 1:m
is_eye = is_eye && A[i, i] == 1
is_minus_eye = is_minus_eye && A[i, i] == -1
if !is_eye && !is_minus_eye
return false
end
end
return true
end
_isone_twice(A) = isone(A) || isone(-A)
A = fill(42,(10000,10000));
_isabsone(A); _isone_twice(A); # warm up JIT
@time _isabsone(A)
@time _isone_twice(A) which gives the output:
So If you think the simplicity is worth this performance penalty, I'll go ahead and change the pull request as you suggested. |
On another note, is it actually useful that we allow |
Codecov Report
@@ Coverage Diff @@
## master JuliaLang/julia#23368 +/- ##
==========================================
- Coverage 55.01% 54.98% -0.03%
==========================================
Files 275 275
Lines 51145 51146 +1
==========================================
- Hits 28138 28124 -14
- Misses 23007 23022 +15
Continue to review full report at Codecov.
|
I think it is worth to avoid the code duplication. With this change, the existing |
…er},::Integer)` as for `^(::Integer,::Integer)`
@andreasnoack , yes, I guess p<0 doesn't happen that often. I adjusted the code to avoid code duplication. If p<0 should turn out to be more common, one could still modify the code at some point in the future. (Or people could just use UniformScaling instead of a Matrix.) |
test/linalg/dense.jl
Outdated
UInt128, UInt16, UInt32, UInt64, UInt8, | ||
BigInt) | ||
@test_throws DomainError elty[1 1;1 0]^-2 | ||
@test elty[1 1;1 0]^2 == elty[2 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.
Perhaps use some @inferred
here since thats what this PR is about? Something like
@test (@inferred elty[1 1; 1 0]^2) == elty[2 1; 1 1]
should do the trick :)
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.
Yes, you're right, I'll adjust that. (I'm not a big fan of @inferred
in unit tests, since it results in confusing test failure messages, see https://discourse.julialang.org/t/more-informative-inferred-especially-for-unit-testing/5481 . But I guess there's no better solution at the moment.)
test/linalg/dense.jl
Outdated
end | ||
for elty2 = (Int64, BigInt) | ||
TT = Base.promote_op(^, elty, elty2) | ||
@test Base.return_types(^, (Array{elty,2}, elty2)) == [Array{TT,2}] |
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 this could also be rewritten using @inferred
. (But this test might be redundant if you add an @inferred
above)
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 test isn't redundant because there's an additional issue going on that I discovered while fixing this issue. So far [1 1;1 0]^big(10000)
returned Matrix{Int64}
(and negative values) rather than promoting to Matrix{BigInt}
. This was inconsistent with 2^big(10000)
. I fixed this issue 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.
Okay nice, perhaps worth adding a comment about that so it does not get removed by someone later on. Perhaps this could otherwise be tested with something like
@test ([1 1; 1 0]^big(1))::Matrix{BigInt} == [1 1; 1 0]
such that we test the return value and not just the type? :)
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 the test would only check for one particular value, whereas return_type proves type correctness for all possible values of a type. And [1 1;1 0]^big(1)
is just one example of type promotion, there's others as well, e.g. Int32[1 1;1 0]^Int64(1)
. So it should be:
@test (@inferred elty[1 1;1 0]^elty2(1))::Matrix{TT} == [1 1;1 0]
I'll also add a comment to clarify.
base/intfuncs.jl
Outdated
x == 1 && return copy(x) | ||
x == -1 && return iseven(p) ? one(x) : copy(x) | ||
isone(x) && return copy(x) | ||
isone(-x) && return iseven(p) ? one(x) : copy(x) | ||
throw_domerr_powbysq(p) |
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 error message printed in throw_domerr_powbysq
could perhaps be updated to apply to integer matrices as well? Easiest would probably be to add a second method that explains the situation for integer matrices. Then this line could instead be
throw_domerr_powbysq(x, p)
and dispatch on x
.
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 the same thought. I was a bit reluctant to make the error handling more complicated, since this A^p
for p<0 seems to be a are thing for matrices. Where should throw_domerr_powbysq(x::AbstractMatrix,p)
go? I guess dense.jl would be the right place, since intfucs.jl doesn't know anything about 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 would put it here, since this is where it is used. If we add a specialized power by squaring method for matrices it can be moved to dense.jl
later on. (Which, btw would be a good idea, since we can probably allocate some work arrays and allocate less in matrix multiplication, but thats for another PR :)).
@fredrikekre , that's an important question you're asking: maybe p<0 should lead to a domain error even if isone(x) or isone(-x) both for |
`^(::Matrix{<:Integer},::Integer)`, added `@inferred` and comments to test
There was a merge conflict because |
`^(::Matrix{<:Integer},::Integer)`, added `@inferred` and comments to test
…ayer/julia into afniedermayer/fix23366
base/intfuncs.jl
Outdated
to_power_type(x) = x | ||
@noinline throw_domerr_powbysq(p) = throw(DomainError(p, | ||
to_power_type(x) = convert(Base.promote_op(*, typeof(x), typeof(x)), x) | ||
@noinline throw_domerr_powbysq(::Any, p) = throw(DomainError(p, | ||
string("Cannot raise an integer x to a negative power ", p, '.', |
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.
Since this is generic function, it might make sense to make this message generic if it doesn't make it (much) less informative. What about just writing "Cannot raise an integer x to a negative power. Try converting input to float". We could keep this version but restrict the inputs to Integer
s.
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.
So you mean three versions of throw_domerr_powbysq
: for ::Any
(short message), ::Integer
(message above), and ::AbstractMatrix
(message below)?
base/linalg/dense.jl
Outdated
@@ -358,6 +358,10 @@ kron(a::AbstractVector, b::AbstractMatrix) = kron(reshape(a, length(a), 1), b) | |||
|
|||
# Matrix power | |||
(^)(A::AbstractMatrix, p::Integer) = p < 0 ? Base.power_by_squaring(inv(A), -p) : Base.power_by_squaring(A, p) | |||
function (^)(A::AbstractMatrix{T}, p::Integer) where T<:Integer | |||
TT = Base.promote_op(^, T, typeof(p)) |
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.
Why is it necessary to promote here? I don't think this version is required. Couldn't you just change the previous method to
(^)(A::AbstractMatrix, p::Integer) = Base.power_by_squaring(A, p)
?
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 guess the previous discussion got hidden by github: #23368 (comment)
The conversion is needed to make sure that Integer promotion rules carry over to matrices. For example, without this conversion [1 1;1 0]^big(10000)
would return Matrix{Int64}
(and negative values) rather than Matrix{BigInt}
. This would be inconsistent with the behavior of 2^big(10000)
, which returns BigInt
.
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 see.
Below you can just write Base.power_by_squaring(convert(AbstractMatrix{TT}, A), p)
. Then it should be ready to go.
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.
Since this is breaking it would be nice to add a sentence in NEWS.md
about this. It would be great if you could do that @afniedermayer, otherwise I (or someone else) can fix it in a separate PR.
^(::Matrix{<:Integer},::Integer)
type stable^(::Matrix{<:Integer},::Integer)
consistent with behavior of ^(::Integer, ::Integer)
I added some text to NEWS.md. I also changed the title of this PR and of JuliaLang/LinearAlgebra.jl#459 to reflect that this is not just about type stability but also about promotion rules (e.g. We're good to go from my side. @fredrikekre and @andreasnoack , thanks for reviewing this PR! |
I split the NEWS.md bullet into two, and removed some unnecessary |
@andreasnoack wanna take a last look/merge? |
Fixes JuliaLang/LinearAlgebra.jl#459. Makes
^(::Matrix{S},::T) where {S<:Integer, T<:Integer}
type stable. Also ensures the same promotion rules as^(::S, ::T)
.