-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
check lengths in covector-vector products #36679
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
Conversation
From a Slack discussion @pablosanjose on this, in case anyone wants to chime in while we're fixing things with indexing: Pablo says
julia> o = OffsetArray(collect(1:10), 11:20); v = collect(1:10); o' * v
385
julia> o = OffsetArray(rand(3,3), 2:4, 1:3); v = rand(3); o * v
ERROR: ArgumentError: offset arrays are not supported but got an array with index other than 1
I say
It does feel to me that contracting an offset array with a regular array is an indication that you're doing something wrong on a conceptual level, but I'm not really sure Base should enforce that or not if it doesn't make the implementation any easier. |
if lu == 0 | ||
zero(eltype(u)) * zero(eltype(v)) | ||
else | ||
sum(uu*vv for (uu, vv) in zip(u, v)) | ||
end |
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.
If we are manually computing the zero length case anyway, sum(...; init = zero(eltype(u)) * zero(eltype(v)))
might be easier on the compiler and (potentially) improve the performance.
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.
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, so this is actually problematic because it makes u' * v
not work for any u
or v
for which zero(eltype(_))
isn't defined (i.e. things like arrays of arrays.
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 going to undo the suggested change.
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.
Now, this reads basically exactly (up to the *
vs dot
) as:
julia/stdlib/LinearAlgebra/src/generic.jl
Lines 886 to 899 in 971e769
function dot(x::AbstractArray, y::AbstractArray) | |
lx = length(x) | |
if lx != length(y) | |
throw(DimensionMismatch("first array has length $(lx) which does not match the length of the second, $(length(y)).")) | |
end | |
if lx == 0 | |
return dot(zero(eltype(x)), zero(eltype(y))) | |
end | |
s = zero(dot(first(x), first(y))) | |
for (Ix, Iy) in zip(eachindex(x), eachindex(y)) | |
@inbounds s += dot(x[Ix], y[Iy]) | |
end | |
s | |
end |
which I think is good.
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.
zero(eltype(_))
isn't defined (i.e. things like arrays of arrays.
Actually I feel like I did a similar suggestion before here that was ended up useless exactly due to this... I should have learned from a mistake.
@MasonProtter You'll need to rename the nonrecursive functions also at their call sites. 😜 |
Thanks for looking at this so quickly! One super minor thing, but you might consider using something other than I also wonder whether you might throw in to this PR my other suggestion about using instead: *(u::AdjointAbsVec{<:Number}, v::AbstractVector{<:Number}) |
It isn't: julia> v = [rand(2,2) for _ in 1:2]
2-element Array{Array{Float64,2},1}:
[0.4274917728716121 0.8685061037888708; 0.8947672491011569 0.4527571728561508]
[0.20207963479107605 0.8146694999967596; 0.07323308037672915 0.5415494652667667]
julia> v'*v
2×2 Array{Float64,2}:
1.02956 0.980679
0.980679 1.91625
julia> dot(v,v)
2.9458110362426355 And that is a fundamental change, because we do change from recursive reduction to a non-recursive one, since the recursive one was agreed to be inconsistent behavior. We have |
Right, good point, thanks. I took a stab at writing a News.md item that would have helped me understand all of this more quickly at the beginning (I have not been following any of this discussion). Feel free to use it if its right:
Note the "is semantically equivalent" is taken straight out of the docstring for |
🤦 I should stop trying to quickly commit things right before bed, sorry about that.
Honestly, I think this is more of a bug-fix than a change in intended semantics. Note that this makes |
The main motivation for me for a News item is that as it stands, anyone that has defined a custom vector type and implemented a custom An alternative to a news item might be just to make scalar eltype's always use |
CI failures appear to be unrelated. @dkarrasch @tkf could you re-review? This is important to get in for 1.5. |
Co-authored-by: Daniel Karrasch <daniel.karrasch@posteo.de>
*(u::AdjointAbsVec{<:Number}, v::AbstractVector{<:Number}) = dot(u.parent, v) | ||
*(u::TransposeAbsVec{T}, v::AbstractVector{T}) where {T<:Real} = dot(u.parent, 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.
Thanks for adding that change in. Any reason not to do the equivalent thing in 284 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.
No, I merged them, and now it looks very clean, actually. 😄
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.
It's causing method ambiguities that I'm not in the mood now of resolving. I've reverted that. I think the *(::TransVector,::AbstractVector)
is handled somewhere in matmul.jl
.
Codecov Report
@@ Coverage Diff @@
## master JuliaLang/julia#36679 +/- ##
==========================================
- Coverage 86.12% 86.12% -0.01%
==========================================
Files 350 350
Lines 65894 65917 +23
==========================================
+ Hits 56752 56768 +16
- Misses 9142 9149 +7
Continue to review full report at Codecov.
|
function _dot_nonrecursive(u, v) | ||
lu = length(u) | ||
if lu != length(v) | ||
throw(DimensionMismatch("first array has length $(lu) which does not match the length of the second, $(length(v)).")) | ||
end | ||
if lu == 0 |
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.
Re OffsetArrays support, I think it's enough to check axes
like this?
function _dot_nonrecursive(u, v) | |
lu = length(u) | |
if lu != length(v) | |
throw(DimensionMismatch("first array has length $(lu) which does not match the length of the second, $(length(v)).")) | |
end | |
if lu == 0 | |
function _dot_nonrecursive(u::AbstractVecOrMat, v::AbstractVecOrMat) | |
if !(axes(u, 1) == axes(v, 2) && axes(u, 2) == axes(v, 1)) | |
throw(DimensionMismatch("dimensions of the first array $(axes(u)) and the second array $(axes(v)) are incompatible for a dot product.")) | |
end | |
if isempty(u) |
I think this is a kind of error that would have thrown if we use here promote_shape(u', v)
(with a better error message). Since something like eachindex(a, b)
already throws an error with something like eachindex(1:3, OffsetArray(1:3, -2:0))
simply based on axes
, maybe it makes sense to check axes
here?
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.
Alternatively, we could just do
@inbounds sum(u[i]*v[i] for i in eachindex(u, v))
However, I'm still not actually convinced it's even desirable to throw an error here. I'm also not convinced we shouldn't throw an error, but I will point out that that in the current state of affairs transpose(u::OffsetVector) * v::Vector
currently has the same behaviour as the implementation in this PR (i.e. no error).
I feel like it'd be good at least for 1.5 since it's so near, to just follow the lead of transpose(u) * v
rather than being over eager about throwing errors.
julia> using OffsetArrays
julia> let u = OffsetArray([1,2,3], -1:1), v = [1,2,3]
transpose(u) * v
end
14
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 rather inclined towards throwing an error, as in the matrix-vector case. If the user really wants to contract an o::OffsetArray
with a v::Vector
, it is not complicated to do OffsetArrays.no_offset_view(o)' * v
. Perhaps no_offset_view
should be exported.
What's the status here? Would be good to get this resolved very soon. |
The remaining question is whether we should require that the two vectors have equal axes. Most functionality in |
Im pretty wary of the idea of throwing an error here since this change will have so little time for someone to try it out and complain if it causes a problem. I think being as conservative as possible, just fixing the bug caused from unequal length vectors is the right way to go. |
To me it seems clear this should be an error; the only other alternative I can see would be that julia> a = [1, 3]
2-element Array{Int64,1}:
1
3
julia> b = OffsetArray([3, 7], 2:3)
2-element OffsetArray(::Array{Int64,1}, 2:3) with eltype Int64 with indices 2:3:
3
7 should yield Right before releasing Juila 1 I tried to go through the entire code base and at least make stuff fragile (meaning, throw an error eagerly) with respect to non-1 indices if no one had yet gotten around to generalizing the operation. Looks like this is a case that got missed? |
Right now, the most important thing is to get something backportable, so I agree with Mason we should just fix the thing that changed in 1.5 and add further checks separately. |
Agreed, what we do immediately does not have to be where we are heading. |
Then I think this PR is ready to merge unless there are any last minute objections. |
(cherry picked from commit de9c371)
closes JuliaLang/LinearAlgebra.jl#746