Faulty Transpose and Adjoint Multiplication

a = transpose([-6.5])
b = [0, .95]'
Matrix(a) * Matrix(b) # Calling `Matrix`
a * b # Without calling `Matrix`
ERROR: DimensionMismatch("Cannot multiply two vectors")
size(a)
(1, 1)

size(b)
(1, 2)

Is this intentional or should I open an issue?

It works with 0.7.0.

julia> a = transpose([-6.5])
1Ă—1 LinearAlgebra.Transpose{Float64,Array{Float64,1}}:
 -6.5

julia> b = [0, .95]'
1Ă—2 LinearAlgebra.Adjoint{Float64,Array{Float64,1}}:
 0.0  0.95

julia> Matrix(a) * Matrix(b)
1Ă—2 Array{Float64,2}:
 0.0  -6.175

You have to call Matrix or reshape on the Transpose or Adjoint for it to work.
If you do it without Matrix it errors.

I see. Sorry, I missed the last line. This works:

reshape(a, 1, 1) * b

Weird!

julia> size(a)
(1, 1)

julia> size(reshape(a, 1, 1))
(1, 1)

I think this is/was intentional. Doing a*b in v0.6.4 gives an error that one cannot multiply two transposed vectors.

I haven’t used 0.7, but I suspect Julia sees a as a transposed vector, not a matrix and that calling size() on a transposed vector produces a 2-element tuple, tricking one into thinking that Julia sees a as a matrix.

And then there is this:

julia> a = [-6.5]
1-element Array{Float64,1}:
 -6.5

julia> a*b
1Ă—2 Array{Float64,2}:
 -0.0  -6.175

julia> size(a)
(1,)

Now this seems funny:

julia> a
1-element Array{Float64,1}:
 -6.5

julia> size(transpose(transpose(a)))
(1,)

julia> size(a)
(1,)

julia> size(transpose(a))
(1, 1)
julia-0.7> aa=[1]
1-element Array{Int64,1}:
 1

julia-0.7> @code_lowered transpose(transpose(aa))
CodeInfo(
105 1 ─ %1 = (Base.getproperty)(A, :parent)                                                                        │
    └──      return %1                                                                                             │
)

This is actually happening also with nontrivial (that is not one by one) vectors:

julia> size(transpose(a))
(1, 2)

julia> size(transpose(transpose(a)))
(2,)

julia> size(a)
(2,)

I guess it has to do with the fact that the actual transpose needs to somehow indicate that it is a “row”.

This is the “culprit” (line 123):

size(v::AdjOrTransAbsVec) = (1, length(v.parent))

Does a (N, ) vector really need to become a (1, N) size array when it is transposed? Is this an “issue-worthy” question?

1 Like

I opened 28772. It really doesn’t seem to be intentional or by design. I would consider it an issue to patch on the next release.

I have to admit that I would rather see the multiplication of two vectors to be undefined. However, I would take issue with the size of the transpose of a vector, which indicates that the result really is a matrix:

julia> size(a)
(2,)

julia> size(transpose(a))
(1, 2)

That doesn’t seem right to me.

1 Like

What do you recommend as an alternative though? I think length(size(a)) should be be equal to the dimension of a. So if a = aa' where aa is a column “vector”, then a would be a row “vector” which is 1D. But using the same size (2,) for example for both a and aa hides shape information. But then the shape is encoded in the type of a or aa so maybe that’s ok. Maybe size can also return an ArraySize instance that has both the size and shape (or array type) of a vector, matrix, adjoint, transpose, etc. I am not sure if these are good proposals but the current state is confusing.

This has been discussed extensively. Please take a look at https://github.com/JuliaLang/julia/issues/4774 and the issues linked in that issue before continuing here.

I think there was a lengthy discussion about this, see e.g. the comment Taking vector transposes seriously · Issue #4774 · JuliaLang/julia · GitHub referring to backwards compatibility.

The design of linear algebra operations on vectors and matrices in Julia relies on four types (refer to this nice presentation): scalar, vector, row vector, matrix. The dispatch then takes care of the correctness of the operations between these objects. There is no need for checking the size of the row vector starting with a singleton 1, for instance. Therefore I would argue that the correct implementation of the function size for the row vector should be

size(v::AdjOrTransAbsVec) = size(v.parent)

This would correspond with the notion of the row vector being a one-dimensional object of the same length as its transpose (the “column” vector).

That’s not how we decided to go. A row vector—i.e. the adjoint of a vector—is embedded into normal arrays by behavioral identification with row matrices. That is, a transposed vector mostly behaves like a flat 1 x nmatrix. It has been the way since 0.6 and it has worked quite well. The only thing that changed in 1.0 is that the specific RowVector type has been generalized to the Adjoint of a vector.

What would bother me about that is the inconsistency of the sizes. So if the row vector is behaviorally identified with a row matrix, why is the vector not identified with a column matrix? Why is the size of a row vector (1, N), but the size of a vector is (N,). Isn’t there a lack of consistency here?

I don’t really feel like rehashing the debate, especially since we are definitely not changing it now, so the debate would be fully moot. There’s several thousand comments about this in that issue and Andy Ferris’ PR which implemented the behavior we went with in the end. You can also watch Jiahao’s talk about #4774 last year.

Could it be at least documented if it isn’t? Something like, one should call Matrix on Transponse and Adjoint to ensure they are treated as the Matrix these represent mathematically for matrix multiplication?