-
-
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
Support ⊙
and ⊗
as elementwise- and outer-product operators
#35150
Conversation
For the elementwise product, couldn't we allow passing |
See #34156 |
That would be great, but I don't see how we'd support older Julia versions with Compat.jl. (Yes, I have a Compat.jl PR already queued up....) |
It is actually possible to get this to work already: julia> const var".*" = (x...,) -> .*(x...)
#3 (generic function with 1 method)
julia> map(.*, 1:6, 2:7)
6-element Array{Int64,1}:
2
6
12
20
30
42 Although this is quite hacky and the proper way would probably be to do this during lowering, as proposed in #34156. |
Several packages use |
Not sure we should use up
|
WRT For |
Re |
I would definitely contest that this is the correct definition of In quantum mechanics, at least, if edit: the definition of |
The |
Re Perhaps giving a meaning to |
That seems like a reasonable definition for a tensor product (edit: and seems more convenient than |
I had a good discussion with my housemates about this, who each had different preferences, and the understanding we came to seemed to be that including the adjoint makes sense if you are only ever going to tensor two things together, and in that context you are usually wanting to put the second argument in the dual space anyway, so one might as well use As another way to see that, if you include the adjoint, the binary operator is no longer associative. |
I could happily change this to not take the conjugate. |
What do you think about also adding ⊗(a::AbstractVector, b::AbstractVector) = a * transpose(b) That way we leave open the possibility of implementing @mcabbott's suggestion of |
Perhaps it should have an ascii form, like Defining |
Yeah, that's really what I was thinking here without actually having imposed it. You can tell from the shortage of tests I wasn't thinking beyond a fairly narrow range of uses 😄. This really came out of an extensive discussion regarding color arithmetic in image processing (JuliaGraphics/ColorVectorSpace.jl#126). I guess I should update this even if we end up not doing it, in case others come over and see the same issues. |
270a59b
to
e8b884c
Compare
OK, let's see what breaks: |
@nanosoldier |
While there are indeed different conventions of what ⊗ should do (a tensor or monoidal product can be defined even for objects which are not vectors or tensors, e.g. cobordisms or tangles or ..., any objects and morphisms in a tensor category), one aspect thar all these choices agree on is that in a linear category, of which the category Vect of vector spaces is the prime example, the tensor product is a bilinear operation, i.e. it is linear (and thus not antilinear) in both of its arguments. In simple words, there should be no complex conjugation/adjoint involved. I too like the definition of @mcabbott. |
And thus, I strongly disagree with the statements about complex vectors on that wikipedia page of the outer product. Note: edited because originally typed on an old tablet/browser with poor Github support. |
Good. There seems to be a growing consensus, but just to check, is the current behavior of this (modified) PR reasonable? |
I think the vector method should avoid recursive transpose:
I was concerned that the I wonder how others feel about the And docs... news item for |
Indeed, the docstring still needs updating. I think it is good that I am not sure about the absence of an ascii alternative. I am not sure about the generic name |
1701d6f
to
80385cf
Compare
There is one problem with julia> using StaticArrays
julia> a = [SVector{2}(1, 2), SVector{2}(-3, 5)]
2-element Array{SArray{Tuple{2},Int64,1,2},1}:
[1, 2]
[-3, 5]
julia> a .* a # this fails, as is right and proper
ERROR: MethodError: no method matching *(::SArray{Tuple{2},Int64,1,2}, ::SArray{Tuple{2},Int64,1,2})
Closest candidates are:
*(::Any, ::Any, ::Any, ::Any...) at operators.jl:529
*(::LinearAlgebra.Adjoint{var"#s666",var"#s665"} where var"#s665"<:LinearAlgebra.AbstractTriangular where var"#s666", ::AbstractArray{T,1} where T) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.5/LinearAlgebra/src/triangular.jl:1995
*(::LinearAlgebra.Adjoint{var"#s666",var"#s665"} where var"#s665"<:(Union{LinearAlgebra.Hermitian{T,S}, LinearAlgebra.Hermitian{Complex{T},S}, LinearAlgebra.Symmetric{T,S}} where S where T<:Real) where var"#s666", ::AbstractArray{T,1} where T) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.5/LinearAlgebra/src/symmetric.jl:588
...
Stacktrace:
[1] _broadcast_getindex_evalf at ./broadcast.jl:631 [inlined]
[2] _broadcast_getindex at ./broadcast.jl:604 [inlined]
[3] getindex at ./broadcast.jl:564 [inlined]
[4] copy at ./broadcast.jl:854 [inlined]
[5] materialize(::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1},Nothing,typeof(*),Tuple{Array{SArray{Tuple{2},Int64,1,2},1},Array{SArray{Tuple{2},Int64,1,2},1}}}) at ./broadcast.jl:820
[6] top-level scope at REPL[3]:1
julia> a ..* a # but it would be nice to have this work
ERROR: syntax: invalid operator "..*" near column 4
Stacktrace:
[1] top-level scope at none:0 |
From a physics point of view, I'm not really sure if I agree that transpose is the most natural definition of an outer product. At least to me, the defining property of the outer product is that
That's definitely not the only problem with that hack. The more I think about it, the more I doubt that this should be in Base until we have a proper solution for all infix operators. Otherwise, I find it hard to justify why we should export the dotted version just for the |
I agreed with the conjugate at the beginning, but generalizing to higher-dimensional tensors seems to be the stronger argument. |
To the latecomers: I'm trying to do this as a public service to avoid stealing good operators. My actual goal is to avoid having to define While I haven't used the Tensor* packages much there's obvious room to combine them with image processing, hence I don't want conflicts. Thus, @Jutho and the Colors world have to get on the same page. To be honest I would prefer to see this in a |
That makes sense to me. Nothing else in the |
But what about three-arg |
|
I don't think I am supposed to be tagged there, but in an ideal world (v2.0 or v3.0), I'd love to see a MultilinearAlgebra standard library that handles dual spaces properly with a proper definition of In my opinion, If I like what is done in TensorKit, but would change a few minor things. In a MultilinearAlgebra package, similar to TensorKit, I would also define tensors in terms of the vector spaces
Similarly, a covector would be
A matrix representing a map from
An arbitrary tensor could be expressed as:
Then a proper definition of
Similarly,
and
where More generally, if
i.e. you just concatenate the list of vector spaces in the parameter. Tensors cannot be denoted by something like With this, a simple and consistent
is only defined if Matrix vector multiplication is then:
More generally,
This provides the general machinery for MultilinearAlgebra and then LinearAlgebra becomes a specialized extension of this, e.g.
and given matrices
Getting back to this PR, again, I would hesitate to put |
@EricForgy, I just wanted to point out that in TensorKit.jl, M and N do not actually denote the upper and lower / contravariant and covariant indices. That information is encoded in the vector space. For what they are, I am happy to discuss elsewhere. @timholy , I am not reluctant to depend on a package (big or small) if I see the use of it. However, with TensorKit.jl, I am defining a completely new type hierarchy distinct from This is my last thought about To me, Julia is so successful at inter-package functionality and generic programming, because If a package defines a new number type which has two multiplications (for whatever reason, e.g. exact and approximate, fast and slow, two different definitions, ...), and that package decides to use That is just to say, I think it is good for But I wouldn't know why such a hypothetical |
I think I now agree with the complaint that, as it stands, this But I also agree that there's value to giving symbols canonical meanings, even if there's a bit of slack in the definition. And costs to moving that to some 20-line package which will tend to get forgotten. So perhaps Or roughly With this
Perhaps @EricForgy -- you may like Jiahao Chen's talk https://www.youtube.com/watch?v=C2RO34b_oPM which I only found today, but seems like a good introduction to the world in which |
I haven't read all of this but the existence of this many words to be spilled by different people on the subject tells me what I need to know—this is too contentious to go in a stdlib. That taken together with the fact that it's possible to have this in a TensorCore.jl package instead indicates fairly strongly to me that this should be reverted and that a TensorCore.jl package be introduced instead which anyone who agrees with these meanings for these operators is welcome to use. |
This makes me feel quite disappointed, not for the specific outcome, but for the way this went down. There was a constructive and long discussion about this for some time, a consensus was reached, a nice implementation was provided, everything was left open for a long time. This gets merged, one person shouts out that this is not good without all that much of motivation, or arguments which apply much more strongly to existing functionality, and frankly in a rather condescending tone. Other people try to reconstruct several of the previous arguments for how this consensus was reached in a detailed (and thus quite long) manner. Which then leads to the conclusion that this is all too many words, and therefore does not belong here. Luckily this is not at all how I typically experience the Julia community. Hence, revert away. With this PR, I have to probably spend 30 seconds fixing some conflicts in some of my libraries to change a freshly defined |
Should
Whereas This addresses the desire for generic concepts, nicely expressed by @Jutho. And addresses the objection of not quite fitting into LinearAlgebra. (The implementation is careful not to take a view of LinearAlgebra's slightly odd Adjoint types.) Perhaps moving |
As someone who was lurking here, I just want to chime in that I think this implementation is great and I would be sad to see it reverted. The complaints from Eric seem to generally be complaints about how Should we just stop developing LinearAlgebra because a few people are unhappy that we didn't take dual spaces seriously enough? No! A conversation about how Base + stdlibs handles co(ntra)variance will need to wait for 2.0. Regarding names, I agree with @mcabbott that if I was at a black-board with someone and they said "V tensor U", I'd know they meant "the tensor product between V and U", and this is an expression I've heard many times before. That said, if people really want them to be renamed |
Jutho said:
I have nothing but adoration for everyone commenting on this PR and if my tone said anything else, then that says more about weakness in my ability to communicate in ascii (and maybe because I have other soul-crushing responsibilites at the moment and not a lot of time to comment properly) than any intention on my part. I do apologize for any miscommunication. I tried to explain my initial allergic reaction. Initially, it was simply about introducing unicode. Once unicode is more universally workable in Windows REPL (I think it will be soon), I'll have less alergic reactions to unicode, but that is an insignificant matter not worth discussing further I think. Rereading the thread again, I think even Tim said:
I (respectfully) disagree. In LinearAlgebra, an MxN array of numbers is meant to represent a linear map between finite-dimensional vector spaces. On the linked Wikipedia page, I see: Although this is represented as a two-dimensional array of numbers they call "matrix", it is not a linear map and is out of scope (imo) for LinearAlgebra. That is a twice-contravariant tensor. As I noted above, there are four distinct geometric elements that can be achieved from tensor product of vectors and dual vectors:
All of the above have representations as 2D arrays of numbers but only the second one is a linear map appropriate for LinearAlgebra. If anyone tries to use A little further down the WIkipedia page, I see this: This notation only makes sense if Instead of dispatching If this PR is reverted, then I don't take it as a waste of anyone's time and I hope the discussion here can lead to a truly great implementation of MultilinearAlgebra. |
Dear @EricForgy , that comment was certainly not addressed to you (and I should probably not have made it anyway, this was a spur of disappointment). I like what your attempts at a more rigorous treatment of covariant and contravariant tensors, but I think that goes beyond the scope of |
Thanks Jutho. I certainly agree that getting a robust formulation of contravariant and covariant tensors is outside the scope of LinearAlgebra. I do think (aside from the possible exception of What I see for Julia 2.0 is a new standard library And to Mason's point,
I don't see it that way. I am not concerned about |
I've decided to revert this, see #35744. The discussion was extremely fruitful and it has resulted in a new package, TensorCore, that can be used on any version of Julia except nightly (and will be usable there too once the reversion merges). To those who are disappointed, do not think this is the end of the road. There will be a Julia 1.6 release, after all, and the principle of "do no harm" means it's better to be patient and get it right than to rush it in now at a time where it is still controversial. If you want to see this eventually in LinearAlgebra or a new standard library, there is a very clear path forward: use TensorCore, depend on TensorCore, and develop TensorCore. Show that it is such a good foundation that everyone should want it. There is a glorious history of good packages becoming features of Julia itself, and there is no reason to believe it couldn't be true here one day as well. So, to those who are disappointed, keep in mind that you are the ones who can change it! |
I certainly didn't intend to be condescending. From my perspective, there was a very long technical discussion that I didn't have the time or expertise to follow, which I was hoping would result in an "executive summary" of some kind before the resolution of this issue. But that didn't happen, instead there was a fairly abrupt merging of the PR without any summary. I think that the output of these long conversations needs to be more than just a PR that could potentially be merged, it should also include a (short) summary of the reasons why, not just for those who were participating in the conversation, but also for those of us who are on the hook for maintaining, explaining, and justifying any APIs that ship with Julia. It's a bit different when the people having the conversation are the same people who are on the hook for maintaining and explaining it, but that's mostly not the case here: of the many people who participated here, only @timholy spends a meaningful amount of time maintaining Base or stdlib/LinearAlgebra.jl — and he seems to prefer (as is done now) to have this in a separate package. I would note that I wasn't entirely alone here: @stevengj also expressed disapproval of this PR—and I've learned over the years to pay attention even if he is the only person expressing disapproval of something. He was, for example, the only person who complained about the soft scope change before 1.0, which turned out to be kind of a big kerfluffle. This issue was also discussed on the most recent triage call (which is when I wrote some of my comments), and the common sentiment was: what have they signed us up for with this PR and and why? In any case, I strongly believe that having this in a small package outside of stdlibs as has now been done is a much better way for this to be developed, adopted and maintained. |
Dear @StefanKarpinski, this comment was in fact also not addressed to you, but let's just forget about it. I certainly understand your point of view as maintainer, and it makes more sense to me than any of the other counter arguments presented, such as the ambiguity of the symbol and the name, which applies much stronger to some other symbols exported by That's also what I meant with inconsistency, but it is I assume unavoidable that such inconsistencies sometimes arise in a project which is simultaneously developed by so many enthusiastic developers and contributors. And maybe some of those (such as |
It would be great to have an issue (is there one already?) listing all the dodgy stuff in LinearAlgebra that we want to deprecate or revisit. I certainly wouldn't spend much time defending |
The argument "we already do X, which makes even less sense, so why not also do Y?" is definitely not the kind of argument we should accept. ["There's already a bit of mud in the house, so why not also smear dog poop everywhere?"] Instead, we should not do Y and open an issue about changing X in 2.0 when we can. |
While we have broadcasting and
a*b'
, sometimes you need to pass an operator as an argument to a function. Since we already have⋅
as a synonym fordot
, these seemed to make sense.