Skip to content
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

Deprecate scale! in favor of mul!, mul1!, and mul2! #25701

Merged
merged 5 commits into from
Jan 24, 2018
Merged

Conversation

andreasnoack
Copy link
Member

Fixes #24276

@@ -354,7 +354,8 @@ LinearAlgebra.tril!
LinearAlgebra.diagind
LinearAlgebra.diag
LinearAlgebra.diagm
LinearAlgebra.scale!
LinearAlgebra.mul1!
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps these should be moved to the ## Low-level matrix operations-section with mul!, ldiv! and rdiv! further down (~L433).

@deprecate scale!(A::AbstractMatrix, b::AbstractVector) mul1!(A, Diagonal(b))
@deprecate scale!(a::AbstractVector, B::AbstractMatrix) mul2!(Diagonal(a), B)
@deprecate scale!(C::AbstractMatrix, A::AbstractMatrix, b::AbstractVector) mul!(X, A, Diagonal(b))
@deprecate scale!(C::AbstractMatrix, a::AbstractVector, B::AbstractMatrix) mul!(X, Diagonal(a), B)
Copy link
Member

Choose a reason for hiding this comment

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

X -> C (or C -> X) and same on the line above.


"""
scale!(A, b)
scale!(b, A)
mul1!(A, b)
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps add ::AbstractArray and ::Number to the signature since there are other mul1! methods?

(similar to `Diagonal(b)*A`), again operating in-place on `A`. An `InexactError` exception is
thrown if the scaling produces a number not representable by the element type of `A`,
e.g. for integer types.

# Examples
```jldoctest
julia> a = [1 2; 3 4]
Copy link
Member

Choose a reason for hiding this comment

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

a -> A to match the signature?

julia> scale!(b,a)
# Examples
```jldoctest
julia> a = [1 2; 3 4]
Copy link
Member

Choose a reason for hiding this comment

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

a -> A to match the signature?

scale!(A::Union{UpperTriangular,LowerTriangular}, c::Number) = scale!(A,A,c)
scale!(c::Number, A::Union{UpperTriangular,LowerTriangular}) = scale!(A,c)
mul1!(A::Union{UpperTriangular,LowerTriangular}, c::Number) = mul!(A, A, c)
mul2!(c::Number, A::Union{UpperTriangular,LowerTriangular}) = mul1!(A', c')'
Copy link
Member

Choose a reason for hiding this comment

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

One too many '.

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this one is correct: c*A = (c*A)'' = ((c*A)')' = (A'*c')'

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I confused myself and thought the line ended with the char ')'

Copy link
Member

@stevengj stevengj Jan 23, 2018

Choose a reason for hiding this comment

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

I'm confused — why can't you simply define mul2!(c::Number, A) = mul1(A, c)?

Oh, I see, it's for non-commutative numbers like quaternions.

Copy link
Member

@stevengj stevengj Jan 23, 2018

Choose a reason for hiding this comment

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

Maybe we should have

mul2!(c::Union{Real,Complex}, A::Union{UpperTriangular,LowerTriangular}) = mul1!(A, c)?

if it makes a performance difference in the common case of commutative *.

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 one was hitting the generic fallback so it ended up being about twice as slow. I realized a better version would be to call mul!(A, c, A) and define that method. It also made me fix mul!(A, A, c) since it actually computed c*A. Eventually, we should fix the Adjoint/Transpose version so we avoid traversing the zero parts but that is an optimization for a separate PR.

2

julia> scale!(a,b)
julia> mul1!(a, 2)
Copy link
Member

Choose a reason for hiding this comment

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

a -> A


julia> b = [1; 2];
Scale an array `A` by a scalar `b` overwriting `A` in-place.
Copy link
Member

Choose a reason for hiding this comment

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

A -> B twice.

for j = 1:n
@inbounds A[j,j] = c
for i = (j + 1):n
@inbounds A[i,j] = B[i,j] * c
Copy link
Member

Choose a reason for hiding this comment

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

c * B[i,j] ?

@JeffBezanson
Copy link
Sponsor Member

JeffBezanson commented Jan 24, 2018

Network issue on travis. Now passed. Ready to merge?

@JeffBezanson JeffBezanson merged commit cf6303a into master Jan 24, 2018
@JeffBezanson JeffBezanson deleted the anj/scale branch January 24, 2018 23:08
@stevengj
Copy link
Member

I have to say that I find the names mul1! and mul2! mystifying... lmul! and rmul!?

@andreasnoack
Copy link
Member Author

I have to say that I find the names mul1! and mul2! mystifying... lmul! and rmul!?

I agree. They are actually the more obvious choices given that we already have ldiv! and rdiv!. I'll prepare a PR unless somebody objects strongly.

@StefanKarpinski
Copy link
Sponsor Member

Just to clarify, which argument it mutated by which function? The convention seems to be that a mutating left-division mutates the right argument while a mutating right-division mutates the left argument. Right? Er, I mean correct? So by analogy, lmul!(A, B) should mutate B while rmul!(A, B) should mutate A. This is probably what you're doing, @andreasnoack, but just checking.

@martinholters
Copy link
Member

That is very satisfyingly consistent with ldiv! and rdiv! ... but ... if you're not used to these, it's not too obvious what these muls do.

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Jan 29, 2018

but ... if you're not used to these

These aren't exactly the most mainstream functions so I think that "satisfying consistency" with another pair of niche in-place operators is as good as we can hope for. It is a very satisfying symmetry and kind of interesting since \ and / are different but there's only one * – which doesn't matter in the non-mutating case but does matter in the mutating case. The conclusion seems to be that for division you need separate left and right operators even without mutation but for multiplication you can gloss over that distinction... except in a mutating API, for which you need separate left and right multiplication operators.

@andreasnoack
Copy link
Member Author

These aren't exactly the most mainstream functions so I think that "satisfying consistency" with another pair of niche in-place operators is as good as we can hope for.

Yes, and it is also not obvious which argument mul1! mutates so most users would have to consult the documentation in any case. Furthermore, the l and r would be similar to the side argument of xtrmm in BLAS.

@martinholters
Copy link
Member

Thinking about this again, I find the consistency not so satisfying anymore: Usually func! is the mutating version of func, and the first argument is the one being mutated. Now ldiv! and rdiv! correspond to \ and / for the lack of all-letter-aliases of those. Oh, and one of them mutates its second argument. (Quick, without looking, which one?) But mul1! and mul2! both correspond to *. We don't distinguish "multiplying A with B from the right" and "multiplying B with A from the left". Should we?

Maybe we should rename ldiv! to ldiv2!? (Not really, but that would be the right king of consistency.)

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Jan 29, 2018

The key word above is "usually". Mutating function mutate their "subject" for some appropriate notion of subject, which is usually the first argument, but not always. In the case of left division, the subject is being divided from the left, so the subject is the right argument. In the case of right division, the subject is being divided from the right, so the subject is the left argument. The same logic applies to lmul! and rmul!. There is no need for left and right multiplication operators because the operator form is non-mutating and without mutation, it doesn't matter which argument is the "subject" – the end result is the same.

@Sacha0
Copy link
Member

Sacha0 commented Jan 29, 2018

I can appreciate the argument for renaming mul1!/mul2! to rmul!/lmul!. I can also appreciate an argument for doing the same and then swapping the argument order of ldiv! and lmul!, in that the description each such function is then "in-place (multiplication|division) of the first argument by, on the (left|right), the second argument". Three-argument ldiv! methods perhaps introduce a wrinkle in the latter however. The additional wrinkle I see is generalization to triple and higher products; the numbering approach scales, whereas the the l/r approach does not immediately appear to? Best!

@StefanKarpinski
Copy link
Sponsor Member

I don't know where the notion that the first argument should always the one to be mutated came from. That's neither empirically true in our existing APIs, nor a hard-and-fast principle that we've ever aimed to adhere to. It is often the case, but in cases where it doesn't make sense, we don't do that. This seems like one of those cases.

@Sacha0
Copy link
Member

Sacha0 commented Jan 30, 2018

It is often the case, but in cases where it doesn't make sense, we don't do that. This seems like one of those cases.

Playing devil's advocate (and noting that either argument order seems fine on this end), the perspective from which the mutated-argument-first order seems natural to me is the verb!(subject, object) convention (as in "left-/right-multiply subject by object"). Of course this convention is not objectively privileged, but it has grown natural to me given its prevalence. The triple and higher product concern is more objectively meaningful, but that's a bridge we can cross when we get to it :). Best!

@StefanKarpinski
Copy link
Sponsor Member

Writing any mutating version of A*B as xmul!(B, A) does not pass the sniff test to me. It does not seem that hard to remember that lmul!(A, B) mutates B. We're doing that for ldiv!(A, B) which has not presented any problems until we started overthinking it because of mul!.

@Sacha0
Copy link
Member

Sacha0 commented Jan 31, 2018

It does not seem that hard to remember that lmul!(A, B) mutates B. We're doing that for ldiv!(A, B) which has not presented any problems until we started overthinking it because of mul!.

This point does not seem so cut and dry. Among other data points, #25701 (comment) demonstrates that e.g. ldiv!/rdiv! argument order does indeed trip up highly intelligent, experienced parties. Best!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants