JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.77k stars 5.49k forks source link

`transpose` should not be recursive #51813

Closed adienes closed 1 year ago

adienes commented 1 year ago

I understand the argument for adjoint, but transpose is really something different. I think it's somewhat silly that e.g. Matrix{String} cannot be transpose'd. if a type wants to opt-in to recursive transpose it can do so via dispatch

nsajko commented 1 year ago

The transpose docs already point to permutedims as a non-recursive generalization: https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#Base.transpose

Related previous discussion: #4774, #20978

adienes commented 1 year ago

I'm quite confused why https://github.com/JuliaLang/julia/issues/20978 was closed

it looks like near the end, people were mostly in agreement that

[recursive] transpose however is lacking even a single motivation or actual use case.

or to put it under a trait

probably makes sense even if we do something else as well), some kind of type-check or trait for whether an element should do recursive transpose

So I'm confused. There seemed to be pretty solid consensus that transpose should be made non-recursive - e.g. https://github.com/JuliaLang/julia/issues/20978#issuecomment-285865225, https://github.com/JuliaLang/julia/issues/20978#issuecomment-285942526, https://github.com/JuliaLang/julia/issues/20978#issuecomment-285993057, https://github.com/JuliaLang/julia/issues/20978#issuecomment-348464449, and https://github.com/JuliaLang/julia/pull/23424.

I'll cast a small vote in favor of non-recursive transpose and ctranspose for AbstractArrays, with both being recursive on AbstractArray{T} where T<:AbstractArray.

and yet the issue was closed leaving transpose recursive? 😕

I understand that this is in some cases mathematically consistent with other functions. however a Matrix{String} is not really a mathematical object, just something I'm using for data preprocessing. if a Matrix must be only objects that should be interpreted as linear maps / tensors / yada yada, then I shouldn't be able to construct a Matrix{String} in the first place. actually with that said, I don't think this issue should have the linear algebra label

.

this is one of those situations where it feels that we have let some esoteric principle win out over a really annoying usability problem. like really, take a step back from the linalg textbook for a second, does it really make sense that transpose(::Matrix{T}) only works if T itself is transposable? to me that's such a strange design choice

nsajko commented 1 year ago

[recursive] transpose however is lacking even a single motivation or actual use case

No, the recursiveness seems to be necessary for a transpose, consider the following:

transpose_necessary_property1(t, a) = (t∘t)(a) == a

transpose_necessary_property2(t, u, v) = t(v*u) == t(u)*t(v)

for candidate ∈ (permutedims, transpose)
  v = [rand(2, 2) for _ ∈ 0:1, _ ∈ 0:2]
  u = [rand(2, 2) for _ ∈ 0:2, _ ∈ 0:2]

  transpose_necessary_property1(candidate, u) ||
    println(candidate, " fails the first necessary property, can't be transpose")

  transpose_necessary_property2(candidate, u, v) ||
    println(candidate, " fails the second necessary property, can't be transpose")
end

It outputs:

permutedims fails the second necessary property, can't be transpose

nsajko commented 1 year ago

This PR and the associated discussion are also relevant: #7244.

adienes commented 1 year ago

transpose_necessary_property2(t, u, v) = t(v*u) == t(u)*t(v)

this is only necessary if you treat the elements as scalars and every Matrix as a linear map and not just an arbitrary AbstractArray. for transpose as a data preprocessing tool it is absolutely not necesary

which is exactly what I am protesting: I find it very silly that permutedims is considered a generic Array method but transpose is only for linear algebra use, when it seems clear to me that transpose should be a special case of permutedims

I understand that this has been discussed at length before, but reading through some threads I really don't see any good justification or consensus that transpose should be recursive. it seems it is only this way as a historical accident + momentum, with justifications bolted on after the fact.

the only compelling use case at all I see in those discussions is to transpose block matrices, which should have been done with a new type + dispatch

oxinabox commented 1 year ago

All of our basic linear algebra operations are recursive. Like *, dot and a few others.

It's required that transpose also be recursive to maintain a' * b == dot(a, b).

There is a strong case to be made that we should indeed get rid of this behaviour in Julia 2.0 (if that ever happens). And leave the representations of interesting vector spaces represented nested arrays to a special type -- not to Array But we would need to get rid of whole recursive thing. Not just transpose.

Anyways this is breaking so this appropriate labels. We won't be able to do this any time soon (if ever), if we do every decide we want to do that.

nsajko commented 1 year ago

leave the representations of interesting vector spaces represented nested arrays to a special type -- not to Array

This reminds of the mathematical programming language Fricas. Its type system and standard library distinguish between:

  1. "two-dimensional array" types, which are basically 2D tables and their element type is completely arbitary

  2. "matrix" types, which may share some of their implementation with some "two-dimensional array" types, but their allowed element type is somewhat restricted (it must be a multiplicative monoid) so that the usual mathematical operations could be defined on them safely

However, it seems to me that the trend in Julia is to use traits instead of abstract types? So I guess adienes' suggestion may materialize partially, but only in Julia 2?

jariji commented 1 year ago

It's required that transpose also be recursive to maintain a' * b == dot(a, b).

I don't see transpose in this expression, only ' which is adjoint.

nsajko commented 1 year ago

Rethinking this issue, I'm finding it a bit absurd. It's not even a feature request, seeing as the single-argument permutedims method already behaves exactly as requested. So this issue could basically be rephrased as "please make transpose not behave like a transpose for Matrix", which is clearly nonsensical.

Perhaps the underlying issue is the already discussed way Julia's Base and LinearAlgebra conflate the non-mathematical "multidimensional table" and mathematical "multidimensional array/cartesian tensor" type categories, however:

  1. If that's an issue, it should be a separate issue on Github.

  2. It seems to me that this conflation may be just a red herring here. This is because, even if we got a cleaner design in the future, one that doesn't conflate non-algebraic and algebraic usage, I'm imagining that transpose wouldn't even be defined for the non-algebraic/"just table" types, as it's really a linear/tensor algebra concept.

adienes commented 1 year ago

I follow, but do not agree with the argument.

anyway, looks like this is unlikely to change. thank you for the thoughts

nsajko commented 1 year ago

You could just leave it open, many other open issues exist with the https://github.com/JuliaLang/julia/labels/speculative or https://github.com/JuliaLang/julia/labels/breaking labels.