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

make Adjoint/Transpose behave like typical constructors #25461

Merged
merged 2 commits into from
Jan 10, 2018

Conversation

Sacha0
Copy link
Member

@Sacha0 Sacha0 commented Jan 9, 2018

This pull request is the hopefully last set of functional changes in the JuliaLang/LinearAlgebra.jl#57 series (after #24969, #25083, #25125, #25148, #25217, and #25364).

Specifically, this pull request's first commit replaces some instances of Adjoint/Transpose with adjoint/transpose (namely those missed in #25364 but caught by changes in the second commit), and its second commit makes Adjoint/Transpose behave like typical constructors (instead of e.g. satisfying Adjoint(Adjoint(A)) === A).

Unless I am forgetting something (please remind me if so!), after this pull request only news, docs, and cleanup remain for JuliaLang/LinearAlgebra.jl#57. Best!

@Sacha0 Sacha0 added the linear algebra Linear algebra label Jan 9, 2018
@JeffBezanson
Copy link
Member

makes Adjoint/Transpose behave like typical constructors (instead of e.g. satisfying Adjoint(Adjoint(A)) === A)

I'm curious --- why do we need this? Constructors are not required to be idempotent; that's one of the main differences with convert.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 9, 2018

makes Adjoint/Transpose behave like typical constructors (instead of e.g. satisfying Adjoint(Adjoint(A)) === A)

I'm curious --- why do we need this? Constructors are not required to be idempotent

Thanks for reviewing! :) I am not certain I follow, so let's iterate:

If you meant "why do we need Adjoint(Adjoint(A)) === A and the equivalent for Transpose", then: This pull request makes Adjoint/Transpose no longer satisfy those identities, as adjoint/transpose now provide that behavior instead.

If you instead meant "why do we need the idempotent Adjoint(A::Adjoint) = A definition with which this pull request replaces the existing Adjoint(A::Adjoint) = A.parent definition, and likewise for Transpose", then: I do not think those definitions are necessary at this point; those definitions were suggested in #25217 (comment) and appeared to receive some support, so I rolled with them.

Thoughts? Thanks!

@JeffBezanson
Copy link
Member

why do we need the idempotent Adjoint(A::Adjoint) = A definition with which this pull request replaces the existing Adjoint(A::Adjoint) = A.parent definition

This is the one I meant. It seems confusing to have an Adjoint(x) method that doesn't return some kind of adjoint of x.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 9, 2018

This is the one I meant. It seems confusing to have an Adjoint(x) method that doesn't return some kind of adjoint of x.

I sympathize. I also appreciate what I believe was the motivation for this suggestion, i.e. that Adjoint(A::Adjoint) = A ensures Adjoint(A)::Adjoint. Proposal? :)

@JeffBezanson
Copy link
Member

ensures Adjoint(A)::Adjoint

Eh, we've probably violated that before.

@mbauman
Copy link
Member

mbauman commented Jan 9, 2018

I agree, this seems strange. I assumed by "proper constructor" in you meant that Adjoint(Adjoint(::Vector)) would return Adjoint{Adjoint{Vector}}. Would that satisfy your objectives here? That'd basically just mean removing all the specializations on these uppercase functions — they just always add another layer of wrapping.

The strange part is adjoint vectors, since they mean something special…

julia> f(x) = Adjoint{eltype(x), typeof(x)}(x)
f (generic function with 1 method)

julia> f([1,2,3])
1×3 Adjoint{Int64,Array{Int64,1}}:
 1  2  3

julia> f(f([1,2,3]))
3×1 Adjoint{Int64,Adjoint{Int64,Array{Int64,1}}}:
 1
 2
 3

julia> f(f(f([1,2,3])))
1×3 Adjoint{Int64,Adjoint{Int64,Adjoint{Int64,Array{Int64,1}}}}:
 1  2  3

julia> f(f(f([1,2,3]))) * [1,2,3]
1-element Array{Int64,1}:
 14

julia> f([1,2,3]) * [1,2,3]
14

It's funny, since we dispatch on Adjoint{T, AbstractVector} to implement those special cases, but Adjoint{T, AbstractVector} is itself an AbstractMatrix.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 9, 2018

Any of the possible definitions are fine by me at this stage; I am happy to explore whichever definition gains broad favor :). (With adjoint/transpose now the operations of choice, I am not certain that anything hits the relevant methods in practice.)

@martinholters
Copy link
Member

My naive expectations would be that Adjoint(x) is

  • the adjoint of x
  • of type Adjoint.

A case where these would hold but Adjoint(x).parent !== x might make sense would be Adjoint(Adjoint(Adjoint(x))) === Adjoint(x), but I'm not sure whether this is worth a special case.

@timholy
Copy link
Member

timholy commented Jan 9, 2018

I agree that Adjoint should always add an extra layer of wrapping. I understand that constructors are often idempotent; however, when the underlying mathematical operation is an involution, making the constructor idempotent would lead to long nights of self-doubt sitting under a ginko tree.

@StefanKarpinski
Copy link
Member

StefanKarpinski commented Jan 9, 2018

I deeply agree with @timholy's point. Perhaps that provides good justification for the adjoint and transpose functions being separate from the Adjoint and Transpose constructors: the constructors always add a layer of wrapping around what they're given. The functions, on the other hand, may undo a layer of wrapping if that would have the desired effect. Then x' should lower to adjoint(x). I've lost track of what we decided to do about the .' syntax, but if it did mean transposition, we would want x.' to lower to transpose(x) in that case.

@garrison
Copy link
Member

garrison commented Jan 9, 2018

Eh, we've probably violated that before.

I can think of at least two other constructors that are not idempotent: Iterators.Reverse and ConjArray.

@JeffBezanson
Copy link
Member

👍 to adding an extra layer of wrapping. I think I'll add a sentence or two to the manual with some of these examples.

@StefanKarpinski
Copy link
Member

I can think of at least two other constructors that are not idempotent: Iterators.Reverse and ConjArray.

While that's true, they probably should return the requested type, which means it would make sense to have a reverse function that removes a layer of Reverse wrapping if present or adds one if not. Similarly for ConjArray. We don't force constructors to produce an object of the requested type, but in general I think it's a good idea to do so and we should keep cases where we do not to an absolute minimum and work towards a state where it is always the case.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 9, 2018

Seems we have consensus! :) Being loathe to cause long nights of self-doubt sitting underneath a ginko tree, I will revise those methods post-haste.

@mbauman mbauman merged commit 0abc263 into JuliaLang:master Jan 10, 2018
@oscardssmith
Copy link
Member

Would it make sense for reverse et al. to remove as many layers of wrapping as possible rather than just one? This seems likely to be a more useful behavior.

@Sacha0
Copy link
Member Author

Sacha0 commented Jan 10, 2018

Thanks all! :)

@Sacha0 Sacha0 deleted the consify branch January 10, 2018 17:40
tkf added a commit to tkf/julia that referenced this pull request Jun 7, 2018
They always should add an extra wrapping, instead of eagerly do
adjoint/transpose.
See JuliaLang#25461
fredrikekre pushed a commit that referenced this pull request Jun 8, 2018
They always should add an extra wrapping, instead of eagerly do
adjoint/transpose.
See #25461
haampie pushed a commit to haampie/julia that referenced this pull request Jun 9, 2018
They always should add an extra wrapping, instead of eagerly do
adjoint/transpose.
See JuliaLang#25461
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
linear algebra Linear algebra
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants